Hold the joystick lock while opening the HID device on non-Android platforms

On Windows the main thread can be enumerating DirectInput devices while the Windows.Gaming.Input thread is calling back with a new controller available, and in this case HIDAPI_IsDevicePresent() returned false since the controller initialization hadn't completed yet, creating a duplicate controller.

Fixes https://github.com/libsdl-org/SDL/issues/7304

(cherry picked from commit ece8a7bb8e2dae9cb53115980cea9ef1213a0160)
This commit is contained in:
Sam Lantinga 2023-02-16 09:50:04 -08:00
parent d948e6c3c5
commit e86f494317

View file

@ -380,44 +380,65 @@ static void HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device, SDL_bool *remove
if (HIDAPI_GetDeviceDriver(device)) { if (HIDAPI_GetDeviceDriver(device)) {
/* We might have a device driver for this device, try opening it and see */ /* We might have a device driver for this device, try opening it and see */
if (device->num_children == 0) { if (device->num_children == 0) {
SDL_hid_device *dev;
/* Wait a little bit for the device to initialize */
SDL_Delay(10);
#ifdef __ANDROID__
/* On Android we need to leave joysticks unlocked because it calls /* On Android we need to leave joysticks unlocked because it calls
* out to the main thread for permissions and the main thread can * out to the main thread for permissions and the main thread can
* be in the process of handling controller input. * be in the process of handling controller input.
* *
* See https://github.com/libsdl-org/SDL/issues/6347 for details * See https://github.com/libsdl-org/SDL/issues/6347 for details
*/ */
int lock_count = 0; {
SDL_HIDAPI_Device *curr; SDL_HIDAPI_Device *curr;
SDL_hid_device *dev; int lock_count = 0;
char *path = SDL_strdup(device->path); char *path = SDL_strdup(device->path);
/* Wait a little bit for the device to initialize */ /* Wait a little bit for the device to initialize */
SDL_Delay(10); SDL_Delay(10);
SDL_AssertJoysticksLocked(); SDL_AssertJoysticksLocked();
while (SDL_JoysticksLocked()) { while (SDL_JoysticksLocked()) {
++lock_count; ++lock_count;
SDL_UnlockJoysticks(); SDL_UnlockJoysticks();
} }
dev = SDL_hid_open_path(path, 0); dev = SDL_hid_open_path(path, 0);
while (lock_count > 0) { while (lock_count > 0) {
--lock_count; --lock_count;
SDL_LockJoysticks(); SDL_LockJoysticks();
} }
SDL_free(path); SDL_free(path);
/* Make sure the device didn't get removed while opening the HID path */ /* Make sure the device didn't get removed while opening the HID path */
for (curr = SDL_HIDAPI_devices; curr && curr != device; curr = curr->next) { for (curr = SDL_HIDAPI_devices; curr && curr != device; curr = curr->next) {
} continue;
if (curr == NULL) { }
*removed = SDL_TRUE; if (curr == NULL) {
if (dev) { *removed = SDL_TRUE;
SDL_hid_close(dev); if (dev) {
SDL_hid_close(dev);
}
return;
} }
return;
} }
#else
/* On other platforms we want to keep the lock so other threads wait for
* us to finish opening the controller before checking to see whether the
* HIDAPI driver is handling the device.
*
* On Windows, for example, the main thread can be enumerating DirectInput
* devices while the Windows.Gaming.Input thread is calling back with a new
* controller available.
*
* See https://github.com/libsdl-org/SDL/issues/7304 for details.
*/
dev = SDL_hid_open_path(device->path, 0);
#endif
if (dev == NULL) { if (dev == NULL) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,