From d174341b6d2987b9ef92a35a428c952be7cdcba2 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Thu, 12 Feb 2026 07:57:41 -0700 Subject: [PATCH] Centralize SDL input subsystem ownership for hotplug recovery --- app/app.pro | 2 + app/gui/sdlgamepadkeynavigation.cpp | 44 +++-- app/streaming/input/gamepad.cpp | 100 ++++++----- app/streaming/input/input.cpp | 69 +++----- app/streaming/input/input.h | 1 + app/streaming/input/sdlinputsubsystems.cpp | 191 +++++++++++++++++++++ app/streaming/input/sdlinputsubsystems.h | 25 +++ 7 files changed, 315 insertions(+), 117 deletions(-) create mode 100644 app/streaming/input/sdlinputsubsystems.cpp create mode 100644 app/streaming/input/sdlinputsubsystems.h diff --git a/app/app.pro b/app/app.pro index 9fb043dd..717c753e 100644 --- a/app/app.pro +++ b/app/app.pro @@ -179,6 +179,7 @@ SOURCES += \ settings/streamingpreferences.cpp \ streaming/input/abstouch.cpp \ streaming/input/gamepad.cpp \ + streaming/input/sdlinputsubsystems.cpp \ streaming/input/input.cpp \ streaming/input/keyboard.cpp \ streaming/input/mouse.cpp \ @@ -219,6 +220,7 @@ HEADERS += \ cli/quitstream.h \ cli/startstream.h \ settings/streamingpreferences.h \ + streaming/input/sdlinputsubsystems.h \ streaming/input/input.h \ streaming/session.h \ streaming/audio/renderers/renderer.h \ diff --git a/app/gui/sdlgamepadkeynavigation.cpp b/app/gui/sdlgamepadkeynavigation.cpp index d35a70b6..21d44e09 100644 --- a/app/gui/sdlgamepadkeynavigation.cpp +++ b/app/gui/sdlgamepadkeynavigation.cpp @@ -4,10 +4,27 @@ #include #include -#include "settings/mappingmanager.h" +#include "streaming/input/sdlinputsubsystems.h" #define AXIS_NAVIGATION_REPEAT_DELAY 150 +namespace { + +SdlInputSubsystems::LeaseOptions guiNavSubsystemLeaseOptions() +{ + 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 = true; + return options; +} + +} + SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs) : m_Prefs(prefs), m_Enabled(false), @@ -31,32 +48,13 @@ void SdlGamepadKeyNavigation::enable() return; } - // We have to initialize and uninitialize this in enable()/disable() - // because we need to get out of the way of the Session class. If it - // doesn't get to reinitialize the GC subsystem, it won't get initial - // arrival events. Additionally, there's a race condition between - // our QML objects being destroyed and SDL being deinitialized that - // this solves too. - if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) { + if (!SdlInputSubsystems::acquire("GuiGamepadNavigation", guiNavSubsystemLeaseOptions())) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s", - SDL_GetError()); + "Failed to acquire SDL input subsystems for GUI gamepad navigation"); return; } - MappingManager mappingManager; - mappingManager.applyMappings(); - - // Drop all pending gamepad add events. SDL will generate these for us - // on first init of the GC subsystem. We can't depend on them due to - // overlapping lifetimes of SdlGamepadKeyNavigation instances, so we - // will attach ourselves. - // - // NB: We use SDL_JoystickUpdate() instead of SDL_PumpEvents() because - // the latter can do a bit more work that we want (like handling video - // events that we intentionally do not want to process yet). SDL_JoystickUpdate(); - SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED); // Open all currently attached game controllers int numJoysticks = SDL_NumJoysticks(); @@ -90,7 +88,7 @@ void SdlGamepadKeyNavigation::disable() m_Gamepads.removeAt(0); } - SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + SdlInputSubsystems::release("GuiGamepadNavigation", guiNavSubsystemLeaseOptions()); } void SdlGamepadKeyNavigation::notifyWindowFocus(bool hasFocus) diff --git a/app/streaming/input/gamepad.cpp b/app/streaming/input/gamepad.cpp index b1391755..810659fa 100644 --- a/app/streaming/input/gamepad.cpp +++ b/app/streaming/input/gamepad.cpp @@ -2,7 +2,7 @@ #include #include "SDL_compat.h" -#include "settings/mappingmanager.h" +#include "streaming/input/sdlinputsubsystems.h" #include @@ -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(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); diff --git a/app/streaming/input/input.cpp b/app/streaming/input/input.cpp index 3e4a71ec..109f7b9b 100644 --- a/app/streaming/input/input.cpp +++ b/app/streaming/input/input.cpp @@ -1,7 +1,7 @@ #include #include "SDL_compat.h" #include "streaming/session.h" -#include "settings/mappingmanager.h" +#include "streaming/input/sdlinputsubsystems.h" #include "path.h" #include "utils.h" @@ -9,6 +9,23 @@ #include #include +namespace { + +SdlInputSubsystems::LeaseOptions streamInputSubsystemLeaseOptions() +{ + SdlInputSubsystems::LeaseOptions options = {}; + options.joystick = true; + options.gameController = true; +#if !SDL_VERSION_ATLEAST(2, 0, 9) + options.haptic = true; +#endif + options.applyMappings = true; + options.flushControllerDeviceEvents = true; + return options; +} + +} + SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, int streamHeight) : m_MultiController(prefs.multiController), m_GamepadMouse(prefs.gamepadMouse), @@ -20,6 +37,7 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i m_PointerRegionLockActive(false), m_PointerRegionLockToggledByUser(false), m_LastJoystickCount(-1), + m_LastNonZeroJoystickTick(0), m_FakeCaptureActive(false), m_CaptureSystemKeysMode(prefs.captureSysKeysMode), m_MouseCursorCapturedVisibilityState(SDL_DISABLE), @@ -178,45 +196,11 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, streamIgnoreDevices.toUtf8()); SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, streamIgnoreDevicesExcept.toUtf8()); - // We must initialize joystick explicitly before gamecontroller in order - // to ensure we receive gamecontroller attach events for gamepads where - // SDL doesn't have a built-in mapping. By starting joystick first, we - // can allow mapping manager to update the mappings before GC attach - // events are generated. - SDL_assert(!SDL_WasInit(SDL_INIT_JOYSTICK)); - if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) { + if (!SdlInputSubsystems::acquire("StreamInput", streamInputSubsystemLeaseOptions())) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_InitSubSystem(SDL_INIT_JOYSTICK) failed: %s", - SDL_GetError()); + "Failed to acquire SDL input subsystems for streaming input"); } - MappingManager mappingManager; - mappingManager.applyMappings(); - - // Flush gamepad arrival and departure events which may be queued before - // starting the gamecontroller subsystem again. This prevents us from - // receiving duplicate arrival and departure events for the same gamepad. - SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED); - SDL_FlushEvent(SDL_CONTROLLERDEVICEREMOVED); - - // We need to reinit this each time, since you only get - // an initial set of gamepad arrival events once per init. - SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER)); - if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s", - SDL_GetError()); - } - -#if !SDL_VERSION_ATLEAST(2, 0, 9) - SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC)); - if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_InitSubSystem(SDL_INIT_HAPTIC) failed: %s", - SDL_GetError()); - } -#endif - // Initialize the gamepad mask with currently attached gamepads to avoid // causing gamepads to unexpectedly disappear and reappear on the host // during stream startup as we detect currently attached gamepads one at a time. @@ -250,16 +234,7 @@ SdlInputHandler::~SdlInputHandler() SDL_RemoveTimer(m_RightButtonReleaseTimer); SDL_RemoveTimer(m_DragTimer); -#if !SDL_VERSION_ATLEAST(2, 0, 9) - SDL_QuitSubSystem(SDL_INIT_HAPTIC); - SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC)); -#endif - - SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); - SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER)); - - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); - SDL_assert(!SDL_WasInit(SDL_INIT_JOYSTICK)); + SdlInputSubsystems::release("StreamInput", streamInputSubsystemLeaseOptions()); // Return background event handling to off SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0"); diff --git a/app/streaming/input/input.h b/app/streaming/input/input.h index 815d95f4..6c177722 100644 --- a/app/streaming/input/input.h +++ b/app/streaming/input/input.h @@ -235,6 +235,7 @@ private: int m_GamepadMask; int m_LastJoystickCount; + uint32_t m_LastNonZeroJoystickTick; GamepadState m_GamepadState[MAX_GAMEPADS]; QSet m_KeysDown; bool m_FakeCaptureActive; diff --git a/app/streaming/input/sdlinputsubsystems.cpp b/app/streaming/input/sdlinputsubsystems.cpp new file mode 100644 index 00000000..bb1d3646 --- /dev/null +++ b/app/streaming/input/sdlinputsubsystems.cpp @@ -0,0 +1,191 @@ +#include "streaming/input/sdlinputsubsystems.h" + +#include "settings/mappingmanager.h" + +namespace { + +int s_JoystickRefs = 0; +int s_GameControllerRefs = 0; +#if !SDL_VERSION_ATLEAST(2, 0, 9) +int s_HapticRefs = 0; +#endif + +bool retainSubsystem(Uint32 subsystem, int& refs, const char* subsystemName, const char* ownerTag) +{ + if (refs == 0 && SDL_InitSubSystem(subsystem) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to initialize %s subsystem for %s: %s", + subsystemName, + ownerTag, + SDL_GetError()); + return false; + } + + refs++; + return true; +} + +void releaseSubsystem(Uint32 subsystem, int& refs, const char* subsystemName, const char* ownerTag) +{ + if (refs <= 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Ignoring unbalanced release of %s subsystem for %s", + subsystemName, + ownerTag); + refs = 0; + return; + } + + refs--; + if (refs == 0) { + SDL_QuitSubSystem(subsystem); + } +} + +} + +namespace SdlInputSubsystems { + +bool acquire(const char* ownerTag, const LeaseOptions& options) +{ + bool acquiredJoystick = false; +#if !SDL_VERSION_ATLEAST(2, 0, 9) + bool acquiredGameController = false; + bool acquiredHaptic = false; +#endif + + if (options.joystick) { + if (!retainSubsystem(SDL_INIT_JOYSTICK, s_JoystickRefs, "joystick", ownerTag)) { + return false; + } + acquiredJoystick = true; + } + + if (options.gameController) { + if (!retainSubsystem(SDL_INIT_GAMECONTROLLER, s_GameControllerRefs, "gamecontroller", ownerTag)) { + if (acquiredJoystick) { + releaseSubsystem(SDL_INIT_JOYSTICK, s_JoystickRefs, "joystick", ownerTag); + } + return false; + } +#if !SDL_VERSION_ATLEAST(2, 0, 9) + acquiredGameController = true; +#endif + } + +#if !SDL_VERSION_ATLEAST(2, 0, 9) + if (options.haptic) { + if (!retainSubsystem(SDL_INIT_HAPTIC, s_HapticRefs, "haptic", ownerTag)) { + if (acquiredGameController) { + releaseSubsystem(SDL_INIT_GAMECONTROLLER, s_GameControllerRefs, "gamecontroller", ownerTag); + } + if (acquiredJoystick) { + releaseSubsystem(SDL_INIT_JOYSTICK, s_JoystickRefs, "joystick", ownerTag); + } + return false; + } + acquiredHaptic = true; + } +#endif + + if (options.applyMappings) { + MappingManager mappingManager; + mappingManager.applyMappings(); + } + + if (options.flushControllerDeviceEvents) { + SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED); + SDL_FlushEvent(SDL_CONTROLLERDEVICEREMOVED); + } + + return true; +} + +void release(const char* ownerTag, const LeaseOptions& options) +{ +#if !SDL_VERSION_ATLEAST(2, 0, 9) + if (options.haptic) { + releaseSubsystem(SDL_INIT_HAPTIC, s_HapticRefs, "haptic", ownerTag); + } +#endif + + if (options.gameController) { + releaseSubsystem(SDL_INIT_GAMECONTROLLER, s_GameControllerRefs, "gamecontroller", ownerTag); + } + + if (options.joystick) { + releaseSubsystem(SDL_INIT_JOYSTICK, s_JoystickRefs, "joystick", ownerTag); + } +} + +bool hasExclusiveGamepadOwnership() +{ + if (s_JoystickRefs != 1 || s_GameControllerRefs != 1) { + return false; + } + +#if !SDL_VERSION_ATLEAST(2, 0, 9) + if (s_HapticRefs != 1) { + return false; + } +#endif + + return true; +} + +bool reenumerateGamepadSubsystems(const char* ownerTag) +{ + if (!hasExclusiveGamepadOwnership()) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Skipping subsystem re-enumeration for %s due to shared ownership (joystick=%d gamecontroller=%d)", + ownerTag, + s_JoystickRefs, + s_GameControllerRefs); + return false; + } + +#if !SDL_VERSION_ATLEAST(2, 0, 9) + SDL_QuitSubSystem(SDL_INIT_HAPTIC); +#endif + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to reinitialize joystick subsystem for %s: %s", + ownerTag, + SDL_GetError()); + return false; + } + + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to reinitialize gamecontroller subsystem for %s: %s", + ownerTag, + SDL_GetError()); + return false; + } + +#if !SDL_VERSION_ATLEAST(2, 0, 9) + if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to reinitialize haptic subsystem for %s: %s", + ownerTag, + SDL_GetError()); + return false; + } +#endif + + MappingManager mappingManager; + mappingManager.applyMappings(); + + SDL_JoystickUpdate(); + SDL_GameControllerUpdate(); + + SDL_FlushEvents(SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED); + SDL_FlushEvents(SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMAPPED); + + return true; +} + +} diff --git a/app/streaming/input/sdlinputsubsystems.h b/app/streaming/input/sdlinputsubsystems.h new file mode 100644 index 00000000..c5e4e3c5 --- /dev/null +++ b/app/streaming/input/sdlinputsubsystems.h @@ -0,0 +1,25 @@ +#pragma once + +#include "SDL_compat.h" + +namespace SdlInputSubsystems { + +struct LeaseOptions { + bool joystick; + bool gameController; +#if !SDL_VERSION_ATLEAST(2, 0, 9) + bool haptic; +#endif + bool applyMappings; + bool flushControllerDeviceEvents; +}; + +bool acquire(const char* ownerTag, const LeaseOptions& options); + +void release(const char* ownerTag, const LeaseOptions& options); + +bool hasExclusiveGamepadOwnership(); + +bool reenumerateGamepadSubsystems(const char* ownerTag); + +}