From 0e22308282677eeed7b2d590b688807e10cd9cd2 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Fri, 23 Jan 2026 21:44:50 -0500 Subject: [PATCH] added more controller support --- .../app/src/main/cpp/android_input_system.cpp | 287 +++++++++++------- .../app/src/main/cpp/android_input_system.h | 4 +- 2 files changed, 188 insertions(+), 103 deletions(-) diff --git a/android/app/src/main/cpp/android_input_system.cpp b/android/app/src/main/cpp/android_input_system.cpp index 6a088ae..6228999 100644 --- a/android/app/src/main/cpp/android_input_system.cpp +++ b/android/app/src/main/cpp/android_input_system.cpp @@ -351,6 +351,7 @@ void AndroidInputSystem::PulseKeys(const DualScancode& sc, uint32_t durationMs) bool AndroidInputSystem::Poll() { SDL_GameControllerUpdate(); + SDL_JoystickUpdate(); const uint32_t now = SDL_GetTicks(); if (m_pendingReloadTriggerAtMs != 0 && now >= m_pendingReloadTriggerAtMs) @@ -392,6 +393,8 @@ void AndroidInputSystem::HandleEvent(const SDL_Event& ev) case SDL_CONTROLLERDEVICEADDED: case SDL_CONTROLLERDEVICEREMOVED: case SDL_CONTROLLERDEVICEREMAPPED: + case SDL_JOYDEVICEADDED: + case SDL_JOYDEVICEREMOVED: RefreshControllers(); break; case SDL_KEYDOWN: @@ -965,6 +968,11 @@ void AndroidInputSystem::CloseControllers() SDL_GameControllerClose(c.controller); c.controller = nullptr; } + if (c.joystick) + { + SDL_JoystickClose(c.joystick); + c.joystick = nullptr; + } } m_controllers.clear(); } @@ -976,77 +984,114 @@ void AndroidInputSystem::RefreshControllers() const int n = SDL_NumJoysticks(); for (int i = 0; i < n; i++) { - if (!SDL_IsGameController(i)) + ControllerState state; + SDL_Joystick* js = nullptr; + + if (SDL_IsGameController(i)) + { + SDL_GameController* controller = SDL_GameControllerOpen(i); + if (!controller) + { + const char* name = SDL_JoystickNameForIndex(i); + SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(i); + char guidStr[64] = {}; + SDL_JoystickGetGUIDString(guid, guidStr, sizeof(guidStr)); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open game controller %d: %s (guid=%s) (%s)", + i + 1, + name ? name : "Unknown", + guidStr, + SDL_GetError()); + continue; + } + + js = SDL_GameControllerGetJoystick(controller); + if (!js) + { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get joystick handle for controller %d: %s (%s)", + i + 1, + SDL_GameControllerName(controller) ? SDL_GameControllerName(controller) : "GameController", + SDL_GetError()); + SDL_GameControllerClose(controller); + continue; + } + + state.controller = controller; + state.isGameController = true; + std::memset(&state.details, 0, sizeof(state.details)); + + const char* name = SDL_GameControllerName(controller); + if (!name) name = "GameController"; + std::strncpy(state.details.name, name, MAX_NAME_LENGTH); + state.details.name[MAX_NAME_LENGTH] = '\0'; + + // Align with the desktop SDL "gamecontroller" path. + state.details.numAxes = 6; + state.details.numPOVs = 4; + state.details.numButtons = 17; + state.details.hasFFeedback = false; + + for (int a = 0; a < NUM_JOY_AXES; a++) + { + state.details.hasAxis[a] = false; + state.details.axisHasFF[a] = false; + std::strncpy(state.details.axisName[a], GetDefaultAxisName(a), MAX_NAME_LENGTH); + state.details.axisName[a][MAX_NAME_LENGTH] = '\0'; + } + + state.details.hasAxis[AXIS_X] = true; + state.details.hasAxis[AXIS_Y] = true; + state.details.hasAxis[AXIS_Z] = true; + state.details.hasAxis[AXIS_RX] = true; + state.details.hasAxis[AXIS_RY] = true; + state.details.hasAxis[AXIS_RZ] = true; + } + else { const char* name = SDL_JoystickNameForIndex(i); SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(i); char guidStr[64] = {}; SDL_JoystickGetGUIDString(guid, guidStr, sizeof(guidStr)); - SDL_Log("Unmapped joystick %d: %s (guid=%s). Add a mapping to %s to enable it.", + SDL_Log("Unmapped joystick %d: %s (guid=%s). Using raw joystick mapping; add a mapping to %s for standardized layout.", i + 1, name ? name : "Unknown", guidStr, m_gameControllerMappingsPath.empty() ? "Config/gamecontrollerdb.txt" : m_gameControllerMappingsPath.c_str()); - continue; + + js = SDL_JoystickOpen(i); + if (!js) + { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open joystick %d: %s (guid=%s) (%s)", + i + 1, + name ? name : "Unknown", + guidStr, + SDL_GetError()); + continue; + } + + state.joystick = js; + state.isGameController = false; + std::memset(&state.details, 0, sizeof(state.details)); + + const char* jsName = SDL_JoystickName(js); + if (!jsName) jsName = "Joystick"; + std::strncpy(state.details.name, jsName, MAX_NAME_LENGTH); + state.details.name[MAX_NAME_LENGTH] = '\0'; + + state.details.numAxes = SDL_JoystickNumAxes(js); + state.details.numPOVs = SDL_JoystickNumHats(js); + state.details.numButtons = SDL_JoystickNumButtons(js); + state.details.hasFFeedback = false; + + for (int a = 0; a < NUM_JOY_AXES; a++) + { + state.details.hasAxis[a] = state.details.numAxes > a; + state.details.axisHasFF[a] = false; + std::strncpy(state.details.axisName[a], GetDefaultAxisName(a), MAX_NAME_LENGTH); + state.details.axisName[a][MAX_NAME_LENGTH] = '\0'; + } } - SDL_GameController* controller = SDL_GameControllerOpen(i); - if (!controller) - { - const char* name = SDL_JoystickNameForIndex(i); - SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(i); - char guidStr[64] = {}; - SDL_JoystickGetGUIDString(guid, guidStr, sizeof(guidStr)); - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open game controller %d: %s (guid=%s) (%s)", - i + 1, - name ? name : "Unknown", - guidStr, - SDL_GetError()); - continue; - } - - SDL_Joystick* js = SDL_GameControllerGetJoystick(controller); - if (!js) - { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get joystick handle for controller %d: %s (%s)", - i + 1, - SDL_GameControllerName(controller) ? SDL_GameControllerName(controller) : "GameController", - SDL_GetError()); - SDL_GameControllerClose(controller); - continue; - } - - ControllerState state; - state.controller = controller; state.instanceId = SDL_JoystickInstanceID(js); - - std::memset(&state.details, 0, sizeof(state.details)); - const char* name = SDL_GameControllerName(controller); - if (!name) name = "GameController"; - std::strncpy(state.details.name, name, MAX_NAME_LENGTH); - state.details.name[MAX_NAME_LENGTH] = '\0'; - - // Align with the desktop SDL "gamecontroller" path. - state.details.numAxes = 6; - state.details.numPOVs = 4; - state.details.numButtons = 17; - state.details.hasFFeedback = false; - - for (int a = 0; a < NUM_JOY_AXES; a++) - { - state.details.hasAxis[a] = false; - state.details.axisHasFF[a] = false; - std::strncpy(state.details.axisName[a], GetDefaultAxisName(a), MAX_NAME_LENGTH); - state.details.axisName[a][MAX_NAME_LENGTH] = '\0'; - } - - state.details.hasAxis[AXIS_X] = true; - state.details.hasAxis[AXIS_Y] = true; - state.details.hasAxis[AXIS_Z] = true; - state.details.hasAxis[AXIS_RX] = true; - state.details.hasAxis[AXIS_RY] = true; - state.details.hasAxis[AXIS_RZ] = true; - m_controllers.push_back(state); } } @@ -1074,63 +1119,101 @@ const JoyDetails* AndroidInputSystem::GetJoyDetails(int joyNum) int AndroidInputSystem::AxisValueFor(const ControllerState& c, int axisNum) const { - if (!c.controller) + if (c.isGameController) + { + if (!c.controller) + return 0; + + switch (axisNum) + { + case AXIS_X: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_LEFTX); + case AXIS_Y: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_LEFTY); + case AXIS_Z: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); + case AXIS_RX: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_RIGHTX); + case AXIS_RY: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_RIGHTY); + case AXIS_RZ: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + default: return 0; + } + } + + if (!c.joystick) return 0; - switch (axisNum) - { - case AXIS_X: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_LEFTX); - case AXIS_Y: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_LEFTY); - case AXIS_Z: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); - case AXIS_RX: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_RIGHTX); - case AXIS_RY: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_RIGHTY); - case AXIS_RZ: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); - default: return 0; - } + if (axisNum < 0 || axisNum >= c.details.numAxes) + return 0; + + return SDL_JoystickGetAxis(c.joystick, axisNum); } -bool AndroidInputSystem::PovPressedFor(const ControllerState& c, int povDir) const +bool AndroidInputSystem::PovPressedFor(const ControllerState& c, int povNum, int povDir) const { - if (!c.controller) + if (c.isGameController) + { + if (!c.controller) + return false; + + switch (povDir) + { + case POV_UP: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_UP) != 0; + case POV_DOWN: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) != 0; + case POV_LEFT: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) != 0; + case POV_RIGHT: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) != 0; + default: return false; + } + } + + if (!c.joystick) + return false; + if (povNum < 0 || povNum >= c.details.numPOVs) return false; + const uint8_t hat = SDL_JoystickGetHat(c.joystick, povNum); switch (povDir) { - case POV_UP: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_UP) != 0; - case POV_DOWN: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) != 0; - case POV_LEFT: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) != 0; - case POV_RIGHT: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) != 0; + case POV_UP: return (hat & SDL_HAT_UP) != 0; + case POV_DOWN: return (hat & SDL_HAT_DOWN) != 0; + case POV_LEFT: return (hat & SDL_HAT_LEFT) != 0; + case POV_RIGHT: return (hat & SDL_HAT_RIGHT) != 0; default: return false; } } bool AndroidInputSystem::ButtonPressedFor(const ControllerState& c, int butNum) const { - if (!c.controller) - return false; - - // Match the desktop SDLInputSystem "useGameController" mapping. - switch (butNum) + if (c.isGameController) { - case 0: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_A) != 0; - case 1: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_B) != 0; - case 2: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_X) != 0; - case 3: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_Y) != 0; - case 4: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER) != 0; - case 5: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) != 0; - case 6: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_BACK) != 0; - case 7: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_START) != 0; - case 8: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_LEFTSTICK) != 0; - case 9: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK) != 0; - case 10: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE1) != 0; - case 11: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE2) != 0; - case 12: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_GUIDE) != 0; - case 13: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE3) != 0; - case 14: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE4) != 0; - case 15: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_MISC1) != 0; - case 16: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_TOUCHPAD) != 0; - default: return false; + if (!c.controller) + return false; + + // Match the desktop SDLInputSystem "useGameController" mapping. + switch (butNum) + { + case 0: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_A) != 0; + case 1: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_B) != 0; + case 2: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_X) != 0; + case 3: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_Y) != 0; + case 4: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER) != 0; + case 5: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) != 0; + case 6: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_BACK) != 0; + case 7: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_START) != 0; + case 8: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_LEFTSTICK) != 0; + case 9: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK) != 0; + case 10: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE1) != 0; + case 11: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE2) != 0; + case 12: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_GUIDE) != 0; + case 13: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE3) != 0; + case 14: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE4) != 0; + case 15: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_MISC1) != 0; + case 16: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_TOUCHPAD) != 0; + default: return false; + } } + + if (!c.joystick) + return false; + if (butNum < 0 || butNum >= c.details.numButtons) + return false; + return SDL_JoystickGetButton(c.joystick, butNum) != 0; } int AndroidInputSystem::GetJoyAxisValue(int joyNum, int axisNum) @@ -1165,7 +1248,7 @@ int AndroidInputSystem::GetJoyAxisValue(int joyNum, int axisNum) return AxisValueFor(m_controllers[static_cast(joyNum)], axisNum); } -bool AndroidInputSystem::IsJoyPOVInDir(int joyNum, int /*povNum*/, int povDir) +bool AndroidInputSystem::IsJoyPOVInDir(int joyNum, int povNum, int povDir) { if (m_controllers.empty()) return false; @@ -1174,7 +1257,7 @@ bool AndroidInputSystem::IsJoyPOVInDir(int joyNum, int /*povNum*/, int povDir) { for (const auto& c : m_controllers) { - if (PovPressedFor(c, povDir)) + if (PovPressedFor(c, povNum, povDir)) return true; } return false; @@ -1183,7 +1266,7 @@ bool AndroidInputSystem::IsJoyPOVInDir(int joyNum, int /*povNum*/, int povDir) if (joyNum < 0 || joyNum >= static_cast(m_controllers.size())) return false; - return PovPressedFor(m_controllers[static_cast(joyNum)], povDir); + return PovPressedFor(m_controllers[static_cast(joyNum)], povNum, povDir); } bool AndroidInputSystem::IsJoyButPressed(int joyNum, int butNum) diff --git a/android/app/src/main/cpp/android_input_system.h b/android/app/src/main/cpp/android_input_system.h index d318777..1f72ffe 100644 --- a/android/app/src/main/cpp/android_input_system.h +++ b/android/app/src/main/cpp/android_input_system.h @@ -67,8 +67,10 @@ private: struct ControllerState { SDL_GameController* controller = nullptr; + SDL_Joystick* joystick = nullptr; SDL_JoystickID instanceId = 0; JoyDetails details{}; + bool isGameController = false; }; struct DualScancode { @@ -104,7 +106,7 @@ private: int AxisValueFor(const ControllerState& c, int axisNum) const; bool ButtonPressedFor(const ControllerState& c, int butNum) const; - bool PovPressedFor(const ControllerState& c, int povDir) const; + bool PovPressedFor(const ControllerState& c, int povNum, int povDir) const; void EnsureGameControllerMappingsLoaded();