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
#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()