diff --git a/.gitmodules b/.gitmodules index e37ef969513..070f66973bf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,5 +20,5 @@ path = libs/qmdnsengine url = https://github.com/nitroshare/qmdnsengine.git [submodule "resources/SDL_GameControllerDB"] - path = resources/SDL_GameControllerDB + path = libs/SDL_GameControllerDB url = https://github.com/mdqinc/SDL_GameControllerDB.git diff --git a/resources/SDL_GameControllerDB b/libs/SDL_GameControllerDB similarity index 100% rename from resources/SDL_GameControllerDB rename to libs/SDL_GameControllerDB diff --git a/qgcresources.qrc b/qgcresources.qrc index 4c545fa8df6..06dfc6bfc2b 100644 --- a/qgcresources.qrc +++ b/qgcresources.qrc @@ -100,9 +100,6 @@ resources/calibration/mode2/radioYawLeft.png resources/calibration/mode2/radioYawRight.png - - resources/SDL_GameControllerDB/gamecontrollerdb.txt - resources/audio/alert.wav diff --git a/src/Joystick/CMakeLists.txt b/src/Joystick/CMakeLists.txt index d77517ee7e4..bdb8d863094 100644 --- a/src/Joystick/CMakeLists.txt +++ b/src/Joystick/CMakeLists.txt @@ -40,7 +40,7 @@ target_sources(Joystick JoystickSDL.h ) -target_compile_definitions(Joystick PRIVATE QGC_SDL_JOYSTICK) +target_compile_definitions(Joystick PRIVATE QGC_SDL_JOYSTICK SDL_MAIN_HANDLED) FetchContent_Declare(SDL_GameControllerDB GIT_REPOSITORY https://github.com/mdqinc/SDL_GameControllerDB.git @@ -62,14 +62,10 @@ qt_add_resources(Joystick "gamecontrollerdb.txt" qt_add_qml_module(Joystick URI QGroundControl.JoystickManager VERSION 1.0 - OUTPUT_TARGETS Joystick_targets IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY} ) -cmake_print_variables(Joystick_targets) - -set(MINIMUM_SDL2_VERSION 2.30.0) - +set(MINIMUM_SDL2_VERSION 2.24.0) if(NOT QGC_BUILD_DEPENDENCIES) find_package(SDL2 ${MINIMUM_SDL2_VERSION}) if(TARGET SDL2::SDL2) @@ -93,7 +89,7 @@ message(STATUS "Building JoystickSDL") include(FetchContent) FetchContent_Declare(SDL GIT_REPOSITORY https://github.com/libsdl-org/SDL.git - GIT_TAG release-2.30.7 + GIT_TAG release-2.30.10 GIT_SHALLOW TRUE GIT_PROGRESS TRUE ) diff --git a/src/Joystick/Joystick.cc b/src/Joystick/Joystick.cc index 7aa744d7d9d..316be685bfe 100644 --- a/src/Joystick/Joystick.cc +++ b/src/Joystick/Joystick.cc @@ -23,7 +23,7 @@ #include #include -// JoystickLog Category declaration moved to QGCLoggingCategory.cc to allow access in Vehicle +QGC_LOGGING_CATEGORY(JoystickLog, "Joystick") QGC_LOGGING_CATEGORY(JoystickValuesLog, "JoystickValuesLog") int Joystick::_transmitterMode = 2; @@ -41,8 +41,9 @@ AssignableButtonAction::AssignableButtonAction(QObject* parent, QString action_, { } -Joystick::Joystick(const QString& name, int axisCount, int buttonCount, int hatCount) - : _name (name) +Joystick::Joystick(const QString& name, int axisCount, int buttonCount, int hatCount, QObject *parent) + : QThread(parent) + , _name (name) , _axisCount (axisCount) , _buttonCount (buttonCount) , _hatCount (hatCount) diff --git a/src/Joystick/Joystick.h b/src/Joystick/Joystick.h index e1665ca2a21..b3937f4b33f 100644 --- a/src/Joystick/Joystick.h +++ b/src/Joystick/Joystick.h @@ -22,7 +22,7 @@ #include #include -// JoystickLog Category declaration moved to QGCLoggingCategory.cc to allow access in Vehicle +Q_DECLARE_LOGGING_CATEGORY(JoystickLog) Q_DECLARE_LOGGING_CATEGORY(JoystickValuesLog) Q_DECLARE_METATYPE(GRIPPER_ACTIONS) @@ -62,7 +62,7 @@ class Joystick : public QThread Q_MOC_INCLUDE("QmlObjectListModel.h") Q_MOC_INCLUDE("Vehicle.h") public: - Joystick(const QString& name, int axisCount, int buttonCount, int hatCount); + Joystick(const QString& name, int axisCount, int buttonCount, int hatCount, QObject *parent = nullptr); virtual ~Joystick(); diff --git a/src/Joystick/JoystickAndroid.cc b/src/Joystick/JoystickAndroid.cc index 539c90006d6..9797469878c 100644 --- a/src/Joystick/JoystickAndroid.cc +++ b/src/Joystick/JoystickAndroid.cc @@ -9,49 +9,39 @@ #include "JoystickAndroid.h" #include "JoystickManager.h" +#include "AndroidInterface.h" #include "QGCLoggingCategory.h" #include #include -int JoystickAndroid::_androidBtnListCount; -int *JoystickAndroid::_androidBtnList; -int JoystickAndroid::ACTION_DOWN; -int JoystickAndroid::ACTION_UP; -int JoystickAndroid::AXIS_HAT_X; -int JoystickAndroid::AXIS_HAT_Y; -QMutex JoystickAndroid::m_mutex; +QGC_LOGGING_CATEGORY(JoystickAndroidLog, "qgc.joystick.joystickandroid") -static void clear_jni_exception() -{ - QJniEnvironment jniEnv; - if (jniEnv->ExceptionCheck()) { - jniEnv->ExceptionDescribe(); - jniEnv->ExceptionClear(); - } -} +QList JoystickAndroid::_androidBtnList(_androidBtnListCount); +int JoystickAndroid::ACTION_DOWN = 0; +int JoystickAndroid::ACTION_UP = 0; +int JoystickAndroid::AXIS_HAT_X = 0; +int JoystickAndroid::AXIS_HAT_Y = 0; +QMutex JoystickAndroid::_mutex; -JoystickAndroid::JoystickAndroid(const QString& name, int axisCount, int buttonCount, int id) - : Joystick(name,axisCount,buttonCount,0) +JoystickAndroid::JoystickAndroid(const QString &name, int axisCount, int buttonCount, int id, QObject *parent) + : Joystick(name, axisCount, buttonCount, 0, parent) , deviceId(id) { - int i; - + btnCode.resize(_buttonCount); + axisCode.resize(_axisCount); + btnValue.resize(_buttonCount); + axisValue.resize(_axisCount); + QJniEnvironment env; - QJniObject inputDevice = QJniObject::callStaticObjectMethod("android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", id); - - //set button mapping (number->code) - jintArray b = env->NewIntArray(_androidBtnListCount); - env->SetIntArrayRegion(b,0,_androidBtnListCount,_androidBtnList); - - QJniObject btns = inputDevice.callObjectMethod("hasKeys", "([I)[Z", b); - jbooleanArray jSupportedButtons = btns.object(); - jboolean* supportedButtons = env->GetBooleanArrayElements(jSupportedButtons, nullptr); - //create a mapping table (btnCode) that maps button number with button code - btnValue = new bool[_buttonCount]; - btnCode = new int[_buttonCount]; + const jintArray btnArr = env->NewIntArray(_androidBtnListCount); + env->SetIntArrayRegion(btnArr, 0, _androidBtnListCount, _androidBtnList.constData()); + const QJniObject inputDevice = QJniObject::callStaticObjectMethod("android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", id); + const QJniObject btns = inputDevice.callObjectMethod("hasKeys", "([I)[Z", btnArr); + const jbooleanArray jSupportedButtons = btns.object(); + jboolean *const supportedButtons = env->GetBooleanArrayElements(jSupportedButtons, nullptr); int c = 0; - for (i = 0; i < _androidBtnListCount; i++) { + for (int i = 0; i < _androidBtnListCount; i++) { if (supportedButtons[i]) { btnValue[c] = false; btnCode[c] = _androidBtnList[i]; @@ -61,14 +51,10 @@ JoystickAndroid::JoystickAndroid(const QString& name, int axisCount, int buttonC env->ReleaseBooleanArrayElements(jSupportedButtons, supportedButtons, 0); - // set axis mapping (number->code) - axisValue = new int[_axisCount]; - axisCode = new int[_axisCount]; - QJniObject rangeListNative = inputDevice.callObjectMethod("getMotionRanges", "()Ljava/util/List;"); - for (i = 0; i < _axisCount; i++) { - QJniObject range = rangeListNative.callObjectMethod("get", "(I)Ljava/lang/Object;",i); + const QJniObject rangeListNative = inputDevice.callObjectMethod("getMotionRanges", "()Ljava/util/List;"); + for (int i = 0; i < _axisCount; i++) { + const QJniObject range = rangeListNative.callObjectMethod("get", "(I)Ljava/lang/Object;", i); axisCode[i] = range.callMethod("getAxis"); - // Don't allow two axis with the same code for (int j = 0; j < i; j++) { if (axisCode[i] == axisCode[j]) { axisCode[i] = -1; @@ -77,48 +63,44 @@ JoystickAndroid::JoystickAndroid(const QString& name, int axisCount, int buttonC } axisValue[i] = 0; } + qCDebug(JoystickAndroidLog) << "axis:" << _axisCount << "buttons:" << _buttonCount; - qCDebug(JoystickLog) << "axis:" <<_axisCount << "buttons:" <<_buttonCount; QtAndroidPrivate::registerGenericMotionEventListener(this); QtAndroidPrivate::registerKeyEventListener(this); } -JoystickAndroid::~JoystickAndroid() { - delete btnCode; - delete axisCode; - delete btnValue; - delete axisValue; - +JoystickAndroid::~JoystickAndroid() +{ QtAndroidPrivate::unregisterGenericMotionEventListener(this); QtAndroidPrivate::unregisterKeyEventListener(this); } - -QMap JoystickAndroid::discover() { +QMap JoystickAndroid::discover() +{ static QMap ret; - QMutexLocker lock(&m_mutex); + QMutexLocker lock(&_mutex); + + const QJniObject object = QJniObject::callStaticObjectMethod("android/view/InputDevice", "getDeviceIds"); + jintArray jarr = object.object(); QJniEnvironment env; - QJniObject o = QJniObject::callStaticObjectMethod("android/view/InputDevice", "getDeviceIds"); - jintArray jarr = o.object(); - int sz = env->GetArrayLength(jarr); - jint *buff = env->GetIntArrayElements(jarr, nullptr); + const int len = env->GetArrayLength(jarr); + jint *const buff = env->GetIntArrayElements(jarr, nullptr); - int SOURCE_GAMEPAD = QJniObject::getStaticField("android/view/InputDevice", "SOURCE_GAMEPAD"); - int SOURCE_JOYSTICK = QJniObject::getStaticField("android/view/InputDevice", "SOURCE_JOYSTICK"); + const int SOURCE_GAMEPAD = QJniObject::getStaticField("android/view/InputDevice", "SOURCE_GAMEPAD"); + const int SOURCE_JOYSTICK = QJniObject::getStaticField("android/view/InputDevice", "SOURCE_JOYSTICK"); QList names; + for (int i = 0; i < len; ++i) { + const QJniObject inputDevice = QJniObject::callStaticObjectMethod("android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", buff[i]); + const int sources = inputDevice.callMethod("getSources", "()I"); + if (((sources & SOURCE_GAMEPAD) != SOURCE_GAMEPAD) && ((sources & SOURCE_JOYSTICK) != SOURCE_JOYSTICK)) { + continue; + } - for (int i = 0; i < sz; ++i) { - QJniObject inputDevice = QJniObject::callStaticObjectMethod("android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", buff[i]); - int sources = inputDevice.callMethod("getSources", "()I"); - if (((sources & SOURCE_GAMEPAD) != SOURCE_GAMEPAD) //check if the input device is interesting to us - && ((sources & SOURCE_JOYSTICK) != SOURCE_JOYSTICK)) continue; - - // get id and name - QString id = inputDevice.callObjectMethod("getDescriptor", "()Ljava/lang/String;").toString(); - QString name = inputDevice.callObjectMethod("getName", "()Ljava/lang/String;").toString(); + const QString id = inputDevice.callObjectMethod("getDescriptor", "()Ljava/lang/String;").toString(); + const QString name = inputDevice.callObjectMethod("getName", "()Ljava/lang/String;").toString(); names.push_back(name); @@ -126,22 +108,23 @@ QMap JoystickAndroid::discover() { continue; } - // get number of axis - QJniObject rangeListNative = inputDevice.callObjectMethod("getMotionRanges", "()Ljava/util/List;"); - int axisCount = rangeListNative.callMethod("size"); + const QJniObject rangeListNative = inputDevice.callObjectMethod("getMotionRanges", "()Ljava/util/List;"); + const int axisCount = rangeListNative.callMethod("size"); - // get number of buttons - jintArray a = env->NewIntArray(_androidBtnListCount); - env->SetIntArrayRegion(a,0,_androidBtnListCount,_androidBtnList); - QJniObject btns = inputDevice.callObjectMethod("hasKeys", "([I)[Z", a); - jbooleanArray jSupportedButtons = btns.object(); - jboolean* supportedButtons = env->GetBooleanArrayElements(jSupportedButtons, nullptr); + jintArray arr = env->NewIntArray(_androidBtnListCount); + env->SetIntArrayRegion(arr, 0, _androidBtnListCount, _androidBtnList.constData()); + const QJniObject btns = inputDevice.callObjectMethod("hasKeys", "([I)[Z", arr); + const jbooleanArray jSupportedButtons = btns.object(); + jboolean *const supportedButtons = env->GetBooleanArrayElements(jSupportedButtons, nullptr); int buttonCount = 0; - for (int j=0;j<_androidBtnListCount;j++) - if (supportedButtons[j]) buttonCount++; + for (int j = 0; j < _androidBtnListCount; j++) { + if (supportedButtons[j]) { + buttonCount++; + } + } env->ReleaseBooleanArrayElements(jSupportedButtons, supportedButtons, 0); - qCDebug(JoystickLog) << "\t" << name << "id:" << buff[i] << "axes:" << axisCount << "buttons:" << buttonCount; + qCDebug(JoystickAndroidLog) << name << "id:" << buff[i] << "axes:" << axisCount << "buttons:" << buttonCount; ret[name] = new JoystickAndroid(name, axisCount, buttonCount, buff[i]); } @@ -159,104 +142,98 @@ QMap JoystickAndroid::discover() { return ret; } +bool JoystickAndroid::handleKeyEvent(jobject event) +{ + QMutexLocker lock(&_mutex); -bool JoystickAndroid::handleKeyEvent(jobject event) { QJniObject ev(event); - QMutexLocker lock(&m_mutex); const int _deviceId = ev.callMethod("getDeviceId", "()I"); - if (_deviceId!=deviceId) return false; - + if (_deviceId != deviceId) { + return false; + } + const int action = ev.callMethod("getAction", "()I"); const int keyCode = ev.callMethod("getKeyCode", "()I"); - for (int i = 0; i <_buttonCount; i++) { - if (btnCode[i] == keyCode) { - if (action == ACTION_DOWN) btnValue[i] = true; - if (action == ACTION_UP) btnValue[i] = false; - return true; + for (int i = 0; i < _buttonCount; i++) { + if (btnCode[i] != keyCode) { + continue; } + + if (action == ACTION_DOWN) { + btnValue[i] = true; + } else if (action == ACTION_UP) { + btnValue[i] = false; + } + + return true; } + return false; } -bool JoystickAndroid::handleGenericMotionEvent(jobject event) { +bool JoystickAndroid::handleGenericMotionEvent(jobject event) +{ + QMutexLocker lock(&_mutex); + QJniObject ev(event); - QMutexLocker lock(&m_mutex); const int _deviceId = ev.callMethod("getDeviceId", "()I"); - if (_deviceId!=deviceId) return false; - - for (int i = 0; i <_axisCount; i++) { - const float v = ev.callMethod("getAxisValue", "(I)F",axisCode[i]); - axisValue[i] = static_cast((v*32767.f)); + if (_deviceId != deviceId) { + return false; } - return true; -} -bool JoystickAndroid::_open(void) { - return true; -} - -void JoystickAndroid::_close(void) { -} + for (int i = 0; i < _axisCount; i++) { + const float v = ev.callMethod("getAxisValue", "(I)F", axisCode[i]); + axisValue[i] = static_cast(v * 32767.f); + } -bool JoystickAndroid::_update(void) -{ return true; } -bool JoystickAndroid::_getButton(int i) { - return btnValue[ i ]; -} - -int JoystickAndroid::_getAxis(int i) { - return axisValue[ i ]; -} - -int JoystickAndroid::_getAndroidHatAxis(int axisHatCode) { - for(int i = 0; i < _axisCount; i++) { +int JoystickAndroid::_getAndroidHatAxis(int axisHatCode) +{ + for (int i = 0; i < _axisCount; i++) { if (axisCode[i] == axisHatCode) { return _getAxis(i); } } + return 0; } -bool JoystickAndroid::_getHat(int hat,int i) { +bool JoystickAndroid::_getHat(int hat, int i) +{ // Android supports only one hat button if (hat != 0) { return false; } switch (i) { - case 0: - return _getAndroidHatAxis(AXIS_HAT_Y) < 0; - case 1: - return _getAndroidHatAxis(AXIS_HAT_Y) > 0; - case 2: - return _getAndroidHatAxis(AXIS_HAT_X) < 0; - case 3: - return _getAndroidHatAxis(AXIS_HAT_X) > 0; - default: - return false; + case 0: + return (_getAndroidHatAxis(AXIS_HAT_Y) < 0); + case 1: + return (_getAndroidHatAxis(AXIS_HAT_Y) > 0); + case 2: + return (_getAndroidHatAxis(AXIS_HAT_X) < 0); + case 3: + return (_getAndroidHatAxis(AXIS_HAT_X) > 0); + default: + return false; } } -//helper method -bool JoystickAndroid::init() { - //this gets list of all possible buttons - this is needed to check how many buttons our gamepad supports - //instead of the whole logic below we could have just a simple array of hardcoded int values as these 'should' not change +bool JoystickAndroid::init() +{ + static QList ret(_androidBtnListCount); - //int JoystickAndroid::_androidBtnListCount; - _androidBtnListCount = 31; - static int ret[31]; //there are 31 buttons in total accordingy to the API - int i; - //int *JoystickAndroid:: _androidBtnList = ret; - clear_jni_exception(); + (void) AndroidInterface::cleanJavaException(); + + int i; for (i = 1; i <= 16; i++) { - QString name = "KEYCODE_BUTTON_"+QString::number(i); - ret[i-1] = QJniObject::getStaticField("android/view/KeyEvent", name.toStdString().c_str()); + const QString name = QStringLiteral("KEYCODE_BUTTON_") + QString::number(i); + ret[i - 1] = QJniObject::getStaticField("android/view/KeyEvent", name.toStdString().c_str()); } i--; @@ -284,41 +261,41 @@ bool JoystickAndroid::init() { return true; } -static const char kJniClassName[] {"org/mavlink/qgroundcontrol/QGCUsbSerialManager"}; - static void jniUpdateAvailableJoysticks(JNIEnv *envA, jobject thizA) { - Q_UNUSED(envA); - Q_UNUSED(thizA); + Q_UNUSED(envA); Q_UNUSED(thizA); + + qCDebug(JoystickAndroidLog) << "jniUpdateAvailableJoysticks triggered"; - qCDebug(JoystickLog) << "jniUpdateAvailableJoysticks triggered"; emit JoystickManager::instance()->updateAvailableJoysticksSignal(); } void JoystickAndroid::setNativeMethods() { - qCDebug(JoystickLog) << "Registering Native Functions"; + qCDebug(JoystickAndroidLog) << "Registering Native Functions"; - // REGISTER THE C++ FUNCTION WITH JNI - JNINativeMethod javaMethods[] { - {"nativeUpdateAvailableJoysticks", "()V", reinterpret_cast(jniUpdateAvailableJoysticks)} + static const JNINativeMethod javaMethods[] { + {"nativeUpdateAvailableJoysticks", "()V", reinterpret_cast(jniUpdateAvailableJoysticks)} }; - clear_jni_exception(); + static constexpr const char *kJniClassName = "org/mavlink/qgroundcontrol/QGCUsbSerialManager"; + + (void) AndroidInterface::cleanJavaException(); + QJniEnvironment jniEnv; jclass objectClass = jniEnv->FindClass(kJniClassName); - if(!objectClass) { - clear_jni_exception(); - qWarning() << "Couldn't find class:" << kJniClassName; + if (!objectClass) { + (void) AndroidInterface::cleanJavaException(); + qCWarning(JoystickAndroidLog) << "Couldn't find class:" << kJniClassName; return; } - jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0])); - + const jint val = jniEnv->RegisterNatives(objectClass, javaMethods, std::size(javaMethods)); if (val < 0) { - qWarning() << "Error registering methods: " << val; + qCWarning(JoystickAndroidLog) << "Error registering methods:" << val; } else { - qCDebug(JoystickLog) << "Native Functions Registered"; + qCDebug(JoystickAndroidLog) << "Native Functions Registered"; } - clear_jni_exception(); + + (void) AndroidInterface::cleanJavaException(); } diff --git a/src/Joystick/JoystickAndroid.h b/src/Joystick/JoystickAndroid.h index 2d5ab491c89..39a4006e2a7 100644 --- a/src/Joystick/JoystickAndroid.h +++ b/src/Joystick/JoystickAndroid.h @@ -9,44 +9,47 @@ #pragma once -#include "Joystick.h" +#include #include +#include "Joystick.h" + +Q_DECLARE_LOGGING_CATEGORY(JoystickAndroidLog) + class JoystickAndroid : public Joystick, public QtAndroidPrivate::GenericMotionEventListener, public QtAndroidPrivate::KeyEventListener { public: - JoystickAndroid(const QString& name, int axisCount, int buttonCount, int id); + JoystickAndroid(const QString &name, int axisCount, int buttonCount, int id, QObject *parent = nullptr); ~JoystickAndroid(); static bool init(); - static void setNativeMethods(); - static QMap discover(); private: + bool _open() final { return true; } + void _close() final {} + bool _update() final { return true; } + + bool _getButton(int i) final { return btnValue[i]; } + int _getAxis(int i) final { return axisValue[i]; } + bool _getHat(int hat, int i) final; + + int _getAndroidHatAxis(int axisHatCode); + bool handleKeyEvent(jobject event); bool handleGenericMotionEvent(jobject event); - int _getAndroidHatAxis(int axisHatCode); - virtual bool _open (); - virtual void _close (); - virtual bool _update (); + int deviceId = 0; - virtual bool _getButton (int i); - virtual int _getAxis (int i); - virtual bool _getHat (int hat,int i); + QList btnCode; + QList axisCode; + QList btnValue; + QList axisValue; - int *btnCode; - int *axisCode; - bool *btnValue; - int *axisValue; - - static int * _androidBtnList; //list of all possible android buttons - static int _androidBtnListCount; + static constexpr int _androidBtnListCount = 31; + static QList _androidBtnList; static int ACTION_DOWN, ACTION_UP, AXIS_HAT_X, AXIS_HAT_Y; - static QMutex m_mutex; - - int deviceId; + static QMutex _mutex; }; diff --git a/src/Joystick/JoystickManager.cc b/src/Joystick/JoystickManager.cc index a003c1d69e1..fd00d7caf83 100644 --- a/src/Joystick/JoystickManager.cc +++ b/src/Joystick/JoystickManager.cc @@ -11,10 +11,10 @@ #include "Joystick.h" #if defined(QGC_SDL_JOYSTICK) #include "JoystickSDL.h" + #include #elif defined(Q_OS_ANDROID) #include "JoystickAndroid.h" #endif -#include "MultiVehicleManager.h" #include "QGCLoggingCategory.h" #include @@ -33,7 +33,7 @@ JoystickManager::JoystickManager(QObject *parent) { // qCDebug(JoystickManagerLog) << Q_FUNC_INFO << this; - _joystickCheckTimer->setInterval(1000); + _joystickCheckTimer->setInterval(kTimerInterval); _joystickCheckTimer->setSingleShot(false); } @@ -69,6 +69,7 @@ void JoystickManager::init() _joystickCheckTimer->start(); }); #endif + (void) connect(_joystickCheckTimer, &QTimer::timeout, this, &JoystickManager::_updateAvailableJoysticks); _joystickCheckTimerCounter = 5; _joystickCheckTimer->start(); @@ -76,7 +77,7 @@ void JoystickManager::init() void JoystickManager::_setActiveJoystickFromSettings() { - QMap newMap; + QMap newMap; #ifdef QGC_SDL_JOYSTICK newMap = JoystickSDL::discover(); @@ -95,7 +96,7 @@ void JoystickManager::_setActiveJoystickFromSettings() if (!newMap.contains(it->first)) { qCDebug(JoystickManagerLog) << "Releasing joystick:" << it->first; it->second->stopPolling(); - it->second->wait(1000); + (void) it->second->wait(kTimeout); it->second->deleteLater(); } } @@ -144,7 +145,7 @@ void JoystickManager::setActiveJoystick(Joystick *joystick) _activeJoystick = joystick; - if (_activeJoystick != nullptr) { + if (_activeJoystick) { qCDebug(JoystickManagerLog) << "Set active:" << _activeJoystick->name(); QSettings settings; @@ -192,10 +193,12 @@ void JoystickManager::_updateAvailableJoysticks() case SDL_QUIT: qCDebug(JoystickManagerLog) << "SDL ERROR:" << SDL_GetError(); break; + case SDL_CONTROLLERDEVICEADDED: case SDL_JOYDEVICEADDED: qCDebug(JoystickManagerLog) << "Joystick added:" << event.jdevice.which; _setActiveJoystickFromSettings(); break; + case SDL_CONTROLLERDEVICEREMOVED: case SDL_JOYDEVICEREMOVED: qCDebug(JoystickManagerLog) << "Joystick removed:" << event.jdevice.which; _setActiveJoystickFromSettings(); diff --git a/src/Joystick/JoystickManager.h b/src/Joystick/JoystickManager.h index b549c5d0df4..46bd25e6319 100644 --- a/src/Joystick/JoystickManager.h +++ b/src/Joystick/JoystickManager.h @@ -17,7 +17,6 @@ Q_DECLARE_LOGGING_CATEGORY(JoystickManagerLog) class Joystick; -class MultiVehicleManager; class QTimer; class JoystickManager : public QObject @@ -68,6 +67,8 @@ private slots: int _joystickCheckTimerCounter = 0;; QTimer *_joystickCheckTimer = nullptr; + static constexpr int kTimerInterval = 1000; + static constexpr int kTimeout = 1000; static constexpr const char *_settingsGroup = "JoystickManager"; static constexpr const char *_settingsKeyActiveJoystick = "ActiveJoystick"; }; diff --git a/src/Joystick/JoystickSDL.cc b/src/Joystick/JoystickSDL.cc index 8c2632a7517..d8bdadd366a 100644 --- a/src/Joystick/JoystickSDL.cc +++ b/src/Joystick/JoystickSDL.cc @@ -8,184 +8,208 @@ ****************************************************************************/ #include "JoystickSDL.h" -#include "MultiVehicleManager.h" #include "QGCLoggingCategory.h" -#include #include #include +#include + +#include + +QGC_LOGGING_CATEGORY(JoystickSDLLog, "qgc.joystick.joysticksdl") -JoystickSDL::JoystickSDL(const QString& name, int axisCount, int buttonCount, int hatCount, int index, bool isGameController) - : Joystick(name,axisCount,buttonCount,hatCount) +JoystickSDL::JoystickSDL(const QString &name, int axisCount, int buttonCount, int hatCount, int index, bool isGameController, QObject *parent) + : Joystick(name, axisCount, buttonCount, hatCount, parent) , _isGameController(isGameController) , _index(index) { - // qCDebug(JoystickLog) << Q_FUNC_INFO << this; + // qCDebug(JoystickSDLLog) << Q_FUNC_INFO << this; - if(_isGameController) _setDefaultCalibration(); + if (_isGameController) { + _setDefaultCalibration(); + } } JoystickSDL::~JoystickSDL() { - // qCDebug(JoystickLog) << Q_FUNC_INFO << this; + // qCDebug(JoystickSDLLog) << Q_FUNC_INFO << this; } -bool JoystickSDL::init(void) { +bool JoystickSDL::init() +{ SDL_SetMainReady(); if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK) < 0) { - SDL_JoystickEventState(SDL_DISABLE); - qCWarning(JoystickLog) << "Couldn't initialize SimpleDirectMediaLayer:" << SDL_GetError(); + (void) SDL_JoystickEventState(SDL_DISABLE); + qCWarning(JoystickSDLLog) << "Failed to initialize SDL:" << SDL_GetError(); return false; } + _loadGameControllerMappings(); return true; } -QMap JoystickSDL::discover() { +QMap JoystickSDL::discover() +{ static QMap ret; - QMap newRet; + QMap newRet; - // Load available joysticks + qCDebug(JoystickSDLLog) << "Discovering joysticks"; - qCDebug(JoystickLog) << "Available joysticks"; - - for (int i=0; i(newRet[name]); - if (j->index() != i) { - j->setIndex(i); // This joystick index has been remapped by SDL + JoystickSDL *const joystick = static_cast(newRet[name]); + if (joystick->index() != i) { + joystick->setIndex(i); // This joystick index has been remapped by SDL } + // Anything left in ret after we exit the loop has been removed (unplugged) and needs to be cleaned up. // We will handle that in JoystickManager in case the removed joystick was in use. - ret.remove(name); - qCDebug(JoystickLog) << "\tSkipping duplicate" << name; + (void) ret.remove(name); + + qCDebug(JoystickSDLLog) << "Skipping duplicate" << name; + continue; } - } - if (!newRet.count()) { - qCDebug(JoystickLog) << "\tnone found"; - } + SDL_Joystick *const sdlJoystick = SDL_JoystickOpen(i); + if (!sdlJoystick) { + qCWarning(JoystickSDLLog) << "SDL failed opening joystick" << qPrintable(name) << "error:" << SDL_GetError(); + continue; + } - ret = newRet; - return ret; -} + SDL_ClearError(); + const int axisCount = SDL_JoystickNumAxes(sdlJoystick); + const int buttonCount = SDL_JoystickNumButtons(sdlJoystick); + const int hatCount = SDL_JoystickNumHats(sdlJoystick); + if ((axisCount < 0) || (buttonCount < 0) || (hatCount < 0)) { + qCWarning(JoystickSDLLog) << "SDL error parsing joystick features:" << SDL_GetError(); + } + SDL_JoystickClose(sdlJoystick); -void JoystickSDL::_loadGameControllerMappings(void) { - QFile file(":/db/mapping/joystick/gamecontrollerdb.txt"); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - qWarning() << "Couldn't load GameController mapping database."; - return; - } + const bool isGameController = SDL_IsGameController(i); + qCDebug(JoystickSDLLog) << name << "axes:" << axisCount << "buttons:" << buttonCount << "hats:" << hatCount << "isGC:" << isGameController; + + // Check for joysticks with duplicate names and differentiate the keys when necessary. + // This is required when using an Xbox 360 wireless receiver that always identifies as + // 4 individual joysticks, regardless of how many joysticks are actually connected to the + // receiver. Using GUID does not help, all of these devices present the same GUID. + const QString originalName = name; + uint8_t duplicateIdx = 1; + while (newRet[name]) { + name = QString("%1 %2").arg(originalName).arg(duplicateIdx++); + } - QTextStream s(&file); + newRet[name] = new JoystickSDL(name, qMax(0, axisCount), qMax(0, buttonCount), qMax(0, hatCount), i, isGameController); + } - while (!s.atEnd()) { - SDL_GameControllerAddMapping(s.readLine().toStdString().c_str()); + if (newRet.isEmpty()) { + qCDebug(JoystickSDLLog) << "None found"; } + + ret = newRet; + return ret; } -bool JoystickSDL::_open(void) { - if ( _isGameController ) { - sdlController = SDL_GameControllerOpen(_index); - sdlJoystick = SDL_GameControllerGetJoystick(sdlController); +bool JoystickSDL::_open() +{ + if (_isGameController) { + _sdlController = SDL_GameControllerOpen(_index); + _sdlJoystick = SDL_GameControllerGetJoystick(_sdlController); } else { - sdlJoystick = SDL_JoystickOpen(_index); + _sdlJoystick = SDL_JoystickOpen(_index); } - if (!sdlJoystick) { - qCWarning(JoystickLog) << "SDL_JoystickOpen failed:" << SDL_GetError(); + if (!_sdlJoystick) { + qCWarning(JoystickSDLLog) << "SDL_JoystickOpen failed:" << SDL_GetError(); return false; } - qCDebug(JoystickLog) << "Opened joystick at" << sdlJoystick; + qCDebug(JoystickSDLLog) << "Opened" << SDL_JoystickName(_sdlJoystick) << "joystick at" << _sdlJoystick; return true; } -void JoystickSDL::_close(void) { - if (sdlJoystick == nullptr) { - qCDebug(JoystickLog) << "Attempt to close null joystick!"; +void JoystickSDL::_close() +{ + if (!_sdlJoystick) { + qCWarning(JoystickSDLLog) << "Attempt to close null joystick!"; return; } - qCDebug(JoystickLog) << "Closing" << SDL_JoystickName(sdlJoystick) << "at" << sdlJoystick; + qCDebug(JoystickSDLLog) << "Closing" << SDL_JoystickName(_sdlJoystick) << "joystick at" << _sdlJoystick; if (_isGameController) { - SDL_GameControllerClose(sdlController); + SDL_GameControllerClose(_sdlController); } else { - SDL_JoystickClose(sdlJoystick); + SDL_JoystickClose(_sdlJoystick); } - sdlJoystick = nullptr; - sdlController = nullptr; + _sdlJoystick = nullptr; + _sdlController = nullptr; } -bool JoystickSDL::_update(void) +bool JoystickSDL::_update() { if (_isGameController) { SDL_GameControllerUpdate(); } else { SDL_JoystickUpdate(); } + return true; } -bool JoystickSDL::_getButton(int i) { +bool JoystickSDL::_getButton(int i) +{ + int button = -1; + if (_isGameController) { - return SDL_GameControllerGetButton(sdlController, SDL_GameControllerButton(i)) == 1; + button = SDL_GameControllerGetButton(_sdlController, SDL_GameControllerButton(i)); } else { - return SDL_JoystickGetButton(sdlJoystick, i) == 1; + button = SDL_JoystickGetButton(_sdlJoystick, i); } + + return (button == 1); } -int JoystickSDL::_getAxis(int i) { +int JoystickSDL::_getAxis(int i) +{ + int axis = -1; + if (_isGameController) { - return SDL_GameControllerGetAxis(sdlController, SDL_GameControllerAxis(i)); + axis = SDL_GameControllerGetAxis(_sdlController, SDL_GameControllerAxis(i)); } else { - return SDL_JoystickGetAxis(sdlJoystick, i); + axis = SDL_JoystickGetAxis(_sdlJoystick, i); + } + + return axis; +} + +bool JoystickSDL::_getHat(int hat, int i) +{ + static constexpr uint8_t hatButtons[] = {SDL_HAT_UP, SDL_HAT_DOWN, SDL_HAT_LEFT, SDL_HAT_RIGHT}; + + if (i >= std::size(hatButtons)) { + return false; } + + return ((SDL_JoystickGetHat(_sdlJoystick, hat) & hatButtons[i]) != 0); } -bool JoystickSDL::_getHat(int hat, int i) { - uint8_t hatButtons[] = {SDL_HAT_UP,SDL_HAT_DOWN,SDL_HAT_LEFT,SDL_HAT_RIGHT}; - if (i < int(sizeof(hatButtons))) { - return (SDL_JoystickGetHat(sdlJoystick, hat) & hatButtons[i]) != 0; +void JoystickSDL::_loadGameControllerMappings() +{ + QFile file(QStringLiteral(":/db/mapping/joystick/gamecontrollerdb.txt")); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCWarning(JoystickSDLLog) << "Couldn't load GameController mapping database."; + return; + } + + QTextStream stream(&file); + while (!stream.atEnd()) { + if (SDL_GameControllerAddMapping(stream.readLine().toStdString().c_str()) == -1) { + qCWarning(JoystickSDLLog) << "Couldn't add GameController mapping:" << SDL_GetError(); + } } - return false; } diff --git a/src/Joystick/JoystickSDL.h b/src/Joystick/JoystickSDL.h index de400a615bb..b1a662898a9 100644 --- a/src/Joystick/JoystickSDL.h +++ b/src/Joystick/JoystickSDL.h @@ -7,50 +7,48 @@ * ****************************************************************************/ -/// @file -/// @brief SDL Joystick Interface - #pragma once +#include + #include "Joystick.h" -#define SDL_MAIN_HANDLED +struct _SDL_Joystick; +typedef struct _SDL_Joystick SDL_Joystick; -#include +struct _SDL_GameController; +typedef struct _SDL_GameController SDL_GameController; -class MultiVehicleManager; +Q_DECLARE_LOGGING_CATEGORY(JoystickSDLLog) -/// @brief SDL Joystick Interface class JoystickSDL : public Joystick { public: - JoystickSDL(const QString& name, int axisCount, int buttonCount, int hatCount, int index, bool isGameController); + JoystickSDL(const QString &name, int axisCount, int buttonCount, int hatCount, int index, bool isGameController, QObject *parent = nullptr); ~JoystickSDL(); - static QMap discover(); - static bool init(void); - - int index(void) const { return _index; } + int index() const { return _index; } void setIndex(int index) { _index = index; } - // This can be uncommented to hide the calibration buttons for gamecontrollers in the future - // bool requiresCalibration(void) final { return !_isGameController; } + // bool requiresCalibration() final { return !_isGameController; } -private: - static void _loadGameControllerMappings(); + static bool init(); + static QMap discover(); - bool _open () final; - void _close () final; - bool _update () final; +private: + bool _open() final; + void _close() final; + bool _update() final; - bool _getButton (int i) final; - int _getAxis (int i) final; - bool _getHat (int hat,int i) final; + bool _getButton(int i) final; + int _getAxis(int i) final; + bool _getHat(int hat, int i) final; - SDL_Joystick* sdlJoystick; - SDL_GameController* sdlController; + static void _loadGameControllerMappings(); - bool _isGameController; - int _index; ///< Index for SDL_JoystickOpen + bool _isGameController = false; + int _index = -1; + SDL_Joystick *_sdlJoystick = nullptr; + SDL_GameController *_sdlController = nullptr; }; diff --git a/src/Utilities/QGCLoggingCategory.cc b/src/Utilities/QGCLoggingCategory.cc index ec3580dc76e..23132b0fd0e 100644 --- a/src/Utilities/QGCLoggingCategory.cc +++ b/src/Utilities/QGCLoggingCategory.cc @@ -26,8 +26,6 @@ QGC_LOGGING_CATEGORY(ParameterManagerLog, "ParameterManagerLog") QGC_LOGGING_CATEGORY(GuidedActionsControllerLog, "GuidedActionsControllerLog") QGC_LOGGING_CATEGORY(LocalizationLog, "LocalizationLog") QGC_LOGGING_CATEGORY(VideoAllLog, kVideoAllLogCategory) -QGC_LOGGING_CATEGORY(JoystickLog, "JoystickLog") - QGCLoggingCategoryRegister* _instance = nullptr; diff --git a/src/Utilities/QGCLoggingCategory.h b/src/Utilities/QGCLoggingCategory.h index bd22a3f3426..63aeab845b1 100644 --- a/src/Utilities/QGCLoggingCategory.h +++ b/src/Utilities/QGCLoggingCategory.h @@ -22,7 +22,6 @@ Q_DECLARE_LOGGING_CATEGORY(ParameterManagerLog) Q_DECLARE_LOGGING_CATEGORY(GuidedActionsControllerLog) Q_DECLARE_LOGGING_CATEGORY(LocalizationLog) Q_DECLARE_LOGGING_CATEGORY(VideoAllLog) // turns on all individual QGC video logs -Q_DECLARE_LOGGING_CATEGORY(JoystickLog) /// @def QGC_LOGGING_CATEGORY /// This is a QGC specific replacement for Q_LOGGING_CATEGORY. It will register the category name into a