SDL: Take the joystick lock when processing GameInput device callbacks

From ae076bdc2a7e667a2775c3fba2d7ed539dd8a01d Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 7 Aug 2024 15:53:40 -0700
Subject: [PATCH] Take the joystick lock when processing GameInput device
 callbacks

---
 src/joystick/gdk/SDL_gameinputjoystick.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/joystick/gdk/SDL_gameinputjoystick.c b/src/joystick/gdk/SDL_gameinputjoystick.c
index 34b113b73b7f2..60b70a1764060 100644
--- a/src/joystick/gdk/SDL_gameinputjoystick.c
+++ b/src/joystick/gdk/SDL_gameinputjoystick.c
@@ -60,7 +60,6 @@ typedef struct joystick_hwdata
     GameInputCallbackToken system_button_callback_token;
 } GAMEINPUT_InternalJoystickHwdata;
 
-// FIXME: We need a lock to protect the device list
 static GAMEINPUT_InternalList g_GameInputList = { NULL };
 static void *g_hGameInputDLL = NULL;
 static IGameInput *g_pGameInput = NULL;
@@ -90,6 +89,8 @@ static int GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice)
     char tmp[4];
     int idx = 0;
 
+    SDL_AssertJoysticksLocked();
+
     info = IGameInputDevice_GetDeviceInfo(pDevice);
     if (info->capabilities & GameInputDeviceCapabilityWireless) {
         bus = SDL_HARDWARE_BUS_BLUETOOTH;
@@ -157,6 +158,8 @@ static int GAMEINPUT_InternalRemoveByIndex(int idx)
     GAMEINPUT_InternalDevice *elem;
     int bytes = 0;
 
+    SDL_AssertJoysticksLocked();
+
     if (idx < 0 || idx >= g_GameInputList.count) {
         return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx);
     }
@@ -187,6 +190,7 @@ static int GAMEINPUT_InternalRemoveByIndex(int idx)
 static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx)
 {
     /* We're guaranteed that the index is in range when this is called */
+    SDL_AssertJoysticksLocked();
     return g_GameInputList.devices[idx];
 }
 
@@ -206,6 +210,8 @@ static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback(
         return;
     }
 
+    SDL_LockJoysticks();
+
     if (currentStatus & GameInputDeviceConnected) {
         GAMEINPUT_InternalAddOrFind(device);
     } else {
@@ -218,6 +224,8 @@ static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback(
             }
         }
     }
+
+    SDL_UnlockJoysticks();
 }
 
 static void GAMEINPUT_JoystickDetect(void);
@@ -270,6 +278,8 @@ static int GAMEINPUT_JoystickInit(void)
 
 static int GAMEINPUT_JoystickGetCount(void)
 {
+    SDL_AssertJoysticksLocked();
+
     return g_GameInputList.count;
 }
 
@@ -278,6 +288,8 @@ static void GAMEINPUT_JoystickDetect(void)
     int idx;
     GAMEINPUT_InternalDevice *elem = NULL;
 
+    SDL_AssertJoysticksLocked();
+
     for (idx = 0; idx < g_GameInputList.count; ++idx) {
         elem = g_GameInputList.devices[idx];
         if (!elem) {
@@ -301,6 +313,8 @@ static SDL_bool GAMEINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 produ
     int idx = 0;
     GAMEINPUT_InternalDevice *elem = NULL;
 
+    SDL_AssertJoysticksLocked();
+
     if (vendor_id == USB_VENDOR_MICROSOFT &&
         product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) {
         /* The Xbox One controller shows up as a hardcoded raw input VID/PID, which we definitely handle */