diff --git a/app/streaming/input/gamepad.cpp b/app/streaming/input/gamepad.cpp index e7f70847..deb8b4e1 100644 --- a/app/streaming/input/gamepad.cpp +++ b/app/streaming/input/gamepad.cpp @@ -24,6 +24,8 @@ static bool isKeyboardMouseInputAllowed() // How long between polling the gamepad to send virtual mouse input #define MOUSE_EMULATION_POLLING_INTERVAL 50 +#define HOTPLUG_REENUMERATION_INTERVAL_MS 2000 + // Determines how fast the mouse will move each interval #define MOUSE_EMULATION_MOTION_MULTIPLIER 4 @@ -99,32 +101,117 @@ SdlInputHandler::ensureStateForGamepad(SDL_JoystickID id) void SdlInputHandler::pollForMissingGamepads() { + static uint32_t s_LastForcedReenumerationTick = 0; + SDL_JoystickUpdate(); SDL_GameControllerUpdate(); - cleanupDetachedGamepads(); + auto recoverUntrackedGamepads = [this](int joystickCount) { + for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) { + if (!SDL_IsGameController(deviceIndex)) { + continue; + } - const int joystickCount = SDL_NumJoysticks(); - for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) { - if (!SDL_IsGameController(deviceIndex)) { + SDL_JoystickID jsId = SDL_JoystickGetDeviceInstanceID(deviceIndex); + if (jsId < 0 || findStateForGamepad(jsId) != nullptr) { + continue; + } + + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Polling recovered missing gamepad add event (device index: %d, instance ID: %d)", + deviceIndex, + jsId); + + SDL_ControllerDeviceEvent controllerEvent = {}; + controllerEvent.type = SDL_CONTROLLERDEVICEADDED; + controllerEvent.which = deviceIndex; + handleControllerDeviceEvent(&controllerEvent); + } + }; + + int joystickCount = SDL_NumJoysticks(); + if (joystickCount != m_LastJoystickCount) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Detected joystick count change: %d -> %d", + m_LastJoystickCount, + joystickCount); + m_LastJoystickCount = joystickCount; + } + + for (int i = 0; i < MAX_GAMEPADS; i++) { + GamepadState* state = &m_GamepadState[i]; + if (state->controller == nullptr) { continue; } - SDL_JoystickID jsId = SDL_JoystickGetDeviceInstanceID(deviceIndex); - if (jsId < 0 || findStateForGamepad(jsId) != nullptr) { + bool presentInDeviceList = false; + for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) { + if (SDL_JoystickGetDeviceInstanceID(deviceIndex) == state->jsId) { + presentInDeviceList = true; + break; + } + } + + if (presentInDeviceList) { continue; } SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, - "Polling recovered missing gamepad add event (device index: %d, instance ID: %d)", - deviceIndex, - jsId); + "Polling detected disconnected gamepad instance %d in slot %d; cleaning up", + state->jsId, + i); SDL_ControllerDeviceEvent controllerEvent = {}; - controllerEvent.type = SDL_CONTROLLERDEVICEADDED; - controllerEvent.which = deviceIndex; + controllerEvent.type = SDL_CONTROLLERDEVICEREMOVED; + controllerEvent.which = state->jsId; handleControllerDeviceEvent(&controllerEvent); } + + recoverUntrackedGamepads(joystickCount); + + if (joystickCount == 0) { + uint32_t now = SDL_GetTicks(); + if (SDL_TICKS_PASSED(now, s_LastForcedReenumerationTick + HOTPLUG_REENUMERATION_INTERVAL_MS)) { + s_LastForcedReenumerationTick = now; + + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "No joysticks visible; forcing SDL joystick/gamecontroller re-enumeration"); + + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "SDL_InitSubSystem(SDL_INIT_JOYSTICK) failed during re-enumeration: %s", + SDL_GetError()); + return; + } + + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed during re-enumeration: %s", + SDL_GetError()); + return; + } + + MappingManager mappingManager; + mappingManager.applyMappings(); + + SDL_JoystickUpdate(); + SDL_GameControllerUpdate(); + + joystickCount = SDL_NumJoysticks(); + if (joystickCount != m_LastJoystickCount) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Detected joystick count change after re-enumeration: %d -> %d", + m_LastJoystickCount, + joystickCount); + m_LastJoystickCount = joystickCount; + } + + recoverUntrackedGamepads(joystickCount); + } + } } void SdlInputHandler::cleanupDetachedGamepads() diff --git a/app/streaming/input/input.cpp b/app/streaming/input/input.cpp index c5f8f95b..3e4a71ec 100644 --- a/app/streaming/input/input.cpp +++ b/app/streaming/input/input.cpp @@ -19,6 +19,7 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i m_PendingMouseButtonsAllUpOnVideoRegionLeave(false), m_PointerRegionLockActive(false), m_PointerRegionLockToggledByUser(false), + m_LastJoystickCount(-1), m_FakeCaptureActive(false), m_CaptureSystemKeysMode(prefs.captureSysKeysMode), m_MouseCursorCapturedVisibilityState(SDL_DISABLE), diff --git a/app/streaming/input/input.h b/app/streaming/input/input.h index b0890b31..815d95f4 100644 --- a/app/streaming/input/input.h +++ b/app/streaming/input/input.h @@ -234,6 +234,7 @@ private: bool m_PointerRegionLockToggledByUser; int m_GamepadMask; + int m_LastJoystickCount; GamepadState m_GamepadState[MAX_GAMEPADS]; QSet m_KeysDown; bool m_FakeCaptureActive;