Recover gamepad reconnect by forcing SDL re-enumeration
Some checks are pending
Build / setup (push) Waiting to run
Build / build-appimage (push) Blocked by required conditions
Build / build-steamlink (push) Blocked by required conditions
Build / build-windows-macos (push) Blocked by required conditions

This commit is contained in:
Joey Yakimowich-Payne 2026-02-12 07:38:51 -07:00
commit 5f41d9a4c3
3 changed files with 100 additions and 11 deletions

View file

@ -24,6 +24,8 @@ static bool isKeyboardMouseInputAllowed()
// How long between polling the gamepad to send virtual mouse input // How long between polling the gamepad to send virtual mouse input
#define MOUSE_EMULATION_POLLING_INTERVAL 50 #define MOUSE_EMULATION_POLLING_INTERVAL 50
#define HOTPLUG_REENUMERATION_INTERVAL_MS 2000
// Determines how fast the mouse will move each interval // Determines how fast the mouse will move each interval
#define MOUSE_EMULATION_MOTION_MULTIPLIER 4 #define MOUSE_EMULATION_MOTION_MULTIPLIER 4
@ -99,32 +101,117 @@ SdlInputHandler::ensureStateForGamepad(SDL_JoystickID id)
void SdlInputHandler::pollForMissingGamepads() void SdlInputHandler::pollForMissingGamepads()
{ {
static uint32_t s_LastForcedReenumerationTick = 0;
SDL_JoystickUpdate(); SDL_JoystickUpdate();
SDL_GameControllerUpdate(); 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(); SDL_JoystickID jsId = SDL_JoystickGetDeviceInstanceID(deviceIndex);
for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) { if (jsId < 0 || findStateForGamepad(jsId) != nullptr) {
if (!SDL_IsGameController(deviceIndex)) { 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; continue;
} }
SDL_JoystickID jsId = SDL_JoystickGetDeviceInstanceID(deviceIndex); bool presentInDeviceList = false;
if (jsId < 0 || findStateForGamepad(jsId) != nullptr) { for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) {
if (SDL_JoystickGetDeviceInstanceID(deviceIndex) == state->jsId) {
presentInDeviceList = true;
break;
}
}
if (presentInDeviceList) {
continue; continue;
} }
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Polling recovered missing gamepad add event (device index: %d, instance ID: %d)", "Polling detected disconnected gamepad instance %d in slot %d; cleaning up",
deviceIndex, state->jsId,
jsId); i);
SDL_ControllerDeviceEvent controllerEvent = {}; SDL_ControllerDeviceEvent controllerEvent = {};
controllerEvent.type = SDL_CONTROLLERDEVICEADDED; controllerEvent.type = SDL_CONTROLLERDEVICEREMOVED;
controllerEvent.which = deviceIndex; controllerEvent.which = state->jsId;
handleControllerDeviceEvent(&controllerEvent); 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() void SdlInputHandler::cleanupDetachedGamepads()

View file

@ -19,6 +19,7 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false), m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
m_PointerRegionLockActive(false), m_PointerRegionLockActive(false),
m_PointerRegionLockToggledByUser(false), m_PointerRegionLockToggledByUser(false),
m_LastJoystickCount(-1),
m_FakeCaptureActive(false), m_FakeCaptureActive(false),
m_CaptureSystemKeysMode(prefs.captureSysKeysMode), m_CaptureSystemKeysMode(prefs.captureSysKeysMode),
m_MouseCursorCapturedVisibilityState(SDL_DISABLE), m_MouseCursorCapturedVisibilityState(SDL_DISABLE),

View file

@ -234,6 +234,7 @@ private:
bool m_PointerRegionLockToggledByUser; bool m_PointerRegionLockToggledByUser;
int m_GamepadMask; int m_GamepadMask;
int m_LastJoystickCount;
GamepadState m_GamepadState[MAX_GAMEPADS]; GamepadState m_GamepadState[MAX_GAMEPADS];
QSet<short> m_KeysDown; QSet<short> m_KeysDown;
bool m_FakeCaptureActive; bool m_FakeCaptureActive;