Centralize SDL input subsystem ownership for hotplug recovery
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:57:41 -07:00
commit d174341b6d
7 changed files with 315 additions and 117 deletions

View file

@ -2,7 +2,7 @@
#include <Limelight.h>
#include "SDL_compat.h"
#include "settings/mappingmanager.h"
#include "streaming/input/sdlinputsubsystems.h"
#include <QtMath>
@ -26,6 +26,8 @@ static bool isKeyboardMouseInputAllowed()
#define HOTPLUG_REENUMERATION_INTERVAL_MS 2000
#define HOTPLUG_REENUMERATION_MAX_INTERVAL_MS 8000
#define HOTPLUG_REENUMERATION_MIN_ZERO_POLLS 4
#define HOTPLUG_REENUMERATION_MIN_LAST_SEEN_MS 500
// Determines how fast the mouse will move each interval
#define MOUSE_EMULATION_MOTION_MULTIPLIER 4
@ -105,10 +107,13 @@ void SdlInputHandler::pollForMissingGamepads()
static uint32_t s_LastForcedReenumerationTick = 0;
static uint32_t s_ReenumerationIntervalMs = HOTPLUG_REENUMERATION_INTERVAL_MS;
static uint32_t s_ReenumerationAttempts = 0;
static uint32_t s_ConsecutiveZeroPolls = 0;
SDL_JoystickUpdate();
SDL_GameControllerUpdate();
uint32_t now = SDL_GetTicks();
auto recoverUntrackedGamepads = [this](int joystickCount) {
for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) {
if (!SDL_IsGameController(deviceIndex)) {
@ -142,9 +147,14 @@ void SdlInputHandler::pollForMissingGamepads()
}
if (joystickCount > 0) {
m_LastNonZeroJoystickTick = now;
s_ConsecutiveZeroPolls = 0;
s_ReenumerationAttempts = 0;
s_ReenumerationIntervalMs = HOTPLUG_REENUMERATION_INTERVAL_MS;
}
else {
s_ConsecutiveZeroPolls++;
}
for (int i = 0; i < MAX_GAMEPADS; i++) {
GamepadState* state = &m_GamepadState[i];
@ -177,54 +187,44 @@ void SdlInputHandler::pollForMissingGamepads()
recoverUntrackedGamepads(joystickCount);
if (joystickCount == 0) {
uint32_t now = SDL_GetTicks();
bool shouldAttemptReenumeration =
joystickCount == 0 &&
m_LastNonZeroJoystickTick != 0 &&
s_ConsecutiveZeroPolls >= HOTPLUG_REENUMERATION_MIN_ZERO_POLLS &&
SDL_TICKS_PASSED(now, m_LastNonZeroJoystickTick + HOTPLUG_REENUMERATION_MIN_LAST_SEEN_MS);
if (shouldAttemptReenumeration) {
if (SDL_TICKS_PASSED(now, s_LastForcedReenumerationTick + s_ReenumerationIntervalMs)) {
s_LastForcedReenumerationTick = now;
s_ReenumerationAttempts++;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"No joysticks visible; forcing SDL joystick/gamecontroller re-enumeration (attempt %u, interval %u ms)",
"No joysticks visible after reconciliation; forcing last-resort SDL re-enumeration (attempt %u, interval %u ms)",
s_ReenumerationAttempts,
s_ReenumerationIntervalMs);
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
if (SdlInputSubsystems::reenumerateGamepadSubsystems("StreamInputHotplug")) {
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;
}
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;
}
recoverUntrackedGamepads(joystickCount);
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);
if (joystickCount > 0) {
s_ReenumerationAttempts = 0;
s_ReenumerationIntervalMs = HOTPLUG_REENUMERATION_INTERVAL_MS;
if (joystickCount > 0) {
m_LastNonZeroJoystickTick = now;
s_ConsecutiveZeroPolls = 0;
s_ReenumerationAttempts = 0;
s_ReenumerationIntervalMs = HOTPLUG_REENUMERATION_INTERVAL_MS;
}
else {
s_ReenumerationIntervalMs = qMin(s_ReenumerationIntervalMs * 2,
static_cast<uint32_t>(HOTPLUG_REENUMERATION_MAX_INTERVAL_MS));
}
}
else {
s_ReenumerationIntervalMs = qMin(s_ReenumerationIntervalMs * 2,
@ -1236,14 +1236,20 @@ QString SdlInputHandler::getUnmappedGamepads()
{
QString ret;
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s",
SDL_GetError());
}
SdlInputSubsystems::LeaseOptions options = {};
options.joystick = true;
options.gameController = true;
#if !SDL_VERSION_ATLEAST(2, 0, 9)
options.haptic = false;
#endif
options.applyMappings = true;
options.flushControllerDeviceEvents = false;
MappingManager mappingManager;
mappingManager.applyMappings();
if (!SdlInputSubsystems::acquire("UnmappedGamepadProbe", options)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to acquire SDL input subsystems for unmapped gamepad probe");
return ret;
}
int numJoysticks = SDL_NumJoysticks();
for (int i = 0; i < numJoysticks; i++) {
@ -1286,7 +1292,7 @@ QString SdlInputHandler::getUnmappedGamepads()
}
}
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
SdlInputSubsystems::release("UnmappedGamepadProbe", options);
// Flush stale events so they aren't processed by the main session event loop
SDL_FlushEvents(SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED);