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
This commit is contained in:
Sam Lantinga
2026-02-16 10:35:38 -08:00
parent a98e49409e
commit bc623d1af6
4 changed files with 44 additions and 25 deletions

View File

@@ -175,12 +175,39 @@ static CM_Register_NotificationFunc CM_Register_Notification;
static CM_Unregister_NotificationFunc CM_Unregister_Notification; static CM_Unregister_NotificationFunc CM_Unregister_Notification;
static HCMNOTIFICATION s_DeviceNotificationFuncHandle; static HCMNOTIFICATION s_DeviceNotificationFuncHandle;
static Uint64 s_LastDeviceNotification = 1; 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) 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 || if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ||
action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) {
s_LastDeviceNotification = SDL_GetTicksNS(); s_LastDeviceNotification = SDL_GetTicksNS();
SetEvent(s_HotplugEvent);
} }
return ERROR_SUCCESS; return ERROR_SUCCESS;
} }
@@ -192,6 +219,11 @@ void WIN_InitDeviceNotification(void)
return; 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"); cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll");
if (cfgmgr32_lib_handle) { if (cfgmgr32_lib_handle) {
CM_Register_Notification = (CM_Register_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Register_Notification"); 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 // Make sure we have balanced calls to init/quit
SDL_assert(s_DeviceNotificationsRequested == 0); 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 (cfgmgr32_lib_handle) {
if (s_DeviceNotificationFuncHandle && CM_Unregister_Notification) { if (s_DeviceNotificationFuncHandle && CM_Unregister_Notification) {
CM_Unregister_Notification(s_DeviceNotificationFuncHandle); CM_Unregister_Notification(s_DeviceNotificationFuncHandle);

View File

@@ -852,8 +852,6 @@ void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start)
data->last_rawinput_poll = poll_finish; data->last_rawinput_poll = poll_finish;
} }
#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count) static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count)
{ {
int new_count = (*count + 1); int new_count = (*count + 1);
@@ -878,7 +876,6 @@ static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count)
return false; 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) 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; char *vendor_name = NULL;
@@ -964,8 +961,9 @@ static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instanc
return name; 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; PRAWINPUTDEVICELIST raw_devices = NULL;
UINT raw_device_count = 0; UINT raw_device_count = 0;
int old_keyboard_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; int new_mouse_count = 0;
SDL_MouseID *new_mice = NULL; 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; 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)) { if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) {
return; // oh well. return; // oh well.
} }
@@ -1010,7 +1003,6 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check
old_keyboards = SDL_GetKeyboards(&old_keyboard_count); old_keyboards = SDL_GetKeyboards(&old_keyboard_count);
old_mice = SDL_GetMice(&old_mouse_count); old_mice = SDL_GetMice(&old_mouse_count);
bool hid_loaded = WIN_LoadHIDDLL();
for (UINT i = 0; i < raw_device_count; i++) { for (UINT i = 0; i < raw_device_count; i++) {
RID_DEVICE_INFO rdi; RID_DEVICE_INFO rdi;
char devName[MAX_PATH] = { 0 }; char devName[MAX_PATH] = { 0 };
@@ -1077,9 +1069,6 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check
break; break;
} }
} }
if (hid_loaded) {
WIN_UnloadHIDDLL();
}
for (int i = old_keyboard_count; i--;) { for (int i = old_keyboard_count; i--;) {
if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { 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); WIN_UpdateIMECandidates(_this);
#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)

View File

@@ -30,7 +30,6 @@ extern HINSTANCE SDL_Instance;
extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam); 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 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_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_PumpEvents(SDL_VideoDevice *_this);
extern void WIN_PumpEventsForHWND(SDL_VideoDevice *_this, HWND hwnd); extern void WIN_PumpEventsForHWND(SDL_VideoDevice *_this, HWND hwnd);
extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window); extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);

View File

@@ -636,9 +636,6 @@ static bool WIN_VideoInit(SDL_VideoDevice *_this)
WIN_InitKeyboard(_this); WIN_InitKeyboard(_this);
WIN_InitMouse(_this); WIN_InitMouse(_this);
WIN_InitDeviceNotification(); WIN_InitDeviceNotification();
if (!_this->internal->gameinput_context) {
WIN_CheckKeyboardAndMouseHotplug(_this, true);
}
#endif #endif
SDL_AddHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this); SDL_AddHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this);