From bc623d1af6774b4b557bffe2a53adac5c273db92 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Mon, 16 Feb 2026 10:35:38 -0800 Subject: [PATCH] Moved mouse/keyboard detection to a separate thread on Windows Getting device names can hang for a long time on certain devices, so make sure this is done on a separate thread to avoid blocking initialization and the main loop. Fixes https://github.com/libsdl-org/SDL/issues/12913 --- src/core/windows/SDL_hid.c | 38 +++++++++++++++++++++++++++ src/video/windows/SDL_windowsevents.c | 27 +++++-------------- src/video/windows/SDL_windowsevents.h | 1 - src/video/windows/SDL_windowsvideo.c | 3 --- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/core/windows/SDL_hid.c b/src/core/windows/SDL_hid.c index f4bd5cef95..1f629d58d2 100644 --- a/src/core/windows/SDL_hid.c +++ b/src/core/windows/SDL_hid.c @@ -175,12 +175,39 @@ static CM_Register_NotificationFunc CM_Register_Notification; static CM_Unregister_NotificationFunc CM_Unregister_Notification; static HCMNOTIFICATION s_DeviceNotificationFuncHandle; static Uint64 s_LastDeviceNotification = 1; +static HANDLE s_HotplugEvent = INVALID_HANDLE_VALUE; +static SDL_AtomicInt s_HotplugRunning; +static SDL_Thread *s_HotplugThread; + +#ifdef SDL_VIDEO_DRIVER_WINDOWS +// Defined in SDL_windowsevents.c +extern void WIN_CheckKeyboardAndMouseHotplug(bool hid_loaded); +#endif + +static int SDLCALL DeviceHotplugThread(void *unused) +{ + bool hid_loaded = WIN_LoadHIDDLL(); + + // Always run the initial device detection + do { +#ifdef SDL_VIDEO_DRIVER_WINDOWS + WIN_CheckKeyboardAndMouseHotplug(hid_loaded); +#endif + WaitForSingleObject(s_HotplugEvent, INFINITE); + } while (SDL_GetAtomicInt(&s_HotplugRunning)); + + if (hid_loaded) { + WIN_UnloadHIDDLL(); + } + return 0; +} static DWORD CALLBACK SDL_DeviceNotificationFunc(HCMNOTIFICATION hNotify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA eventData, DWORD event_data_size) { if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL || action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { s_LastDeviceNotification = SDL_GetTicksNS(); + SetEvent(s_HotplugEvent); } return ERROR_SUCCESS; } @@ -192,6 +219,11 @@ void WIN_InitDeviceNotification(void) return; } + // Start the device hotplug thread + SDL_SetAtomicInt(&s_HotplugRunning, true); + s_HotplugEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + s_HotplugThread = SDL_CreateThread(DeviceHotplugThread, "DeviceHotplugThread", NULL); + cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll"); if (cfgmgr32_lib_handle) { CM_Register_Notification = (CM_Register_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Register_Notification"); @@ -225,6 +257,12 @@ void WIN_QuitDeviceNotification(void) // Make sure we have balanced calls to init/quit SDL_assert(s_DeviceNotificationsRequested == 0); + // Stop the device hotplug thread + SDL_SetAtomicInt(&s_HotplugRunning, false); + SetEvent(s_HotplugEvent); + SDL_WaitThread(s_HotplugThread, NULL); + s_HotplugThread = NULL; + if (cfgmgr32_lib_handle) { if (s_DeviceNotificationFuncHandle && CM_Unregister_Notification) { CM_Unregister_Notification(s_DeviceNotificationFuncHandle); diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 1155ba59c9..3e9be16b92 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -852,8 +852,6 @@ void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start) data->last_rawinput_poll = poll_finish; } -#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count) { int new_count = (*count + 1); @@ -878,7 +876,6 @@ static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count) return false; } -#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, Uint16 vendor, Uint16 product, const char *default_name, bool hid_loaded) { char *vendor_name = NULL; @@ -964,8 +961,9 @@ static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instanc return name; } -void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check) +void WIN_CheckKeyboardAndMouseHotplug(bool hid_loaded) { + SDL_VideoDevice *_this = SDL_GetVideoDevice(); PRAWINPUTDEVICELIST raw_devices = NULL; UINT raw_device_count = 0; int old_keyboard_count = 0; @@ -977,18 +975,13 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check int new_mouse_count = 0; SDL_MouseID *new_mice = NULL; - if (!_this->internal->detect_device_hotplug) { + if (!_this || + SDL_strcmp(_this->name, "windows") != 0 || + !_this->internal->detect_device_hotplug || + _this->internal->gameinput_context) { return; } - // Check to see if anything has changed - static Uint64 s_last_device_change; - Uint64 last_device_change = WIN_GetLastDeviceNotification(); - if (!initial_check && last_device_change == s_last_device_change) { - return; - } - s_last_device_change = last_device_change; - if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) { return; // oh well. } @@ -1010,7 +1003,6 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check old_keyboards = SDL_GetKeyboards(&old_keyboard_count); old_mice = SDL_GetMice(&old_mouse_count); - bool hid_loaded = WIN_LoadHIDDLL(); for (UINT i = 0; i < raw_device_count; i++) { RID_DEVICE_INFO rdi; char devName[MAX_PATH] = { 0 }; @@ -1077,9 +1069,6 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check break; } } - if (hid_loaded) { - WIN_UnloadHIDDLL(); - } for (int i = old_keyboard_count; i--;) { if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { @@ -2718,10 +2707,6 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) } } - if (!_this->internal->gameinput_context) { - WIN_CheckKeyboardAndMouseHotplug(_this, false); - } - WIN_UpdateIMECandidates(_this); #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) diff --git a/src/video/windows/SDL_windowsevents.h b/src/video/windows/SDL_windowsevents.h index d49518b8f7..45f5527dc6 100644 --- a/src/video/windows/SDL_windowsevents.h +++ b/src/video/windows/SDL_windowsevents.h @@ -30,7 +30,6 @@ extern HINSTANCE SDL_Instance; extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam); extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); extern void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start); -extern void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check); extern void WIN_PumpEvents(SDL_VideoDevice *_this); extern void WIN_PumpEventsForHWND(SDL_VideoDevice *_this, HWND hwnd); extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index b7e4991bee..056a69883a 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -636,9 +636,6 @@ static bool WIN_VideoInit(SDL_VideoDevice *_this) WIN_InitKeyboard(_this); WIN_InitMouse(_this); WIN_InitDeviceNotification(); - if (!_this->internal->gameinput_context) { - WIN_CheckKeyboardAndMouseHotplug(_this, true); - } #endif SDL_AddHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this);