Commit 710ae62a79b905fff5408c5c9887594fc1b93d60

Sam Lantinga 2017-02-02T17:33:40

Remember XInput controllers that we've already seen, so when the raw device list changes we don't assign the old device to the new XInput userid. This isn't perfect, but at least we won't report the same device twice.

diff --git a/src/joystick/windows/SDL_xinputjoystick.c b/src/joystick/windows/SDL_xinputjoystick.c
index 6f02e59..7744bed 100644
--- a/src/joystick/windows/SDL_xinputjoystick.c
+++ b/src/joystick/windows/SDL_xinputjoystick.c
@@ -33,6 +33,7 @@
  * Internal stuff.
  */
 static SDL_bool s_bXInputEnabled = SDL_TRUE;
+static char *s_arrXInputDevicePath[XUSER_MAX_COUNT];
 
 
 static SDL_bool
@@ -113,12 +114,12 @@ GetXInputName(const Uint8 userid, BYTE SubType)
    and we'll be correct for the case where only one device is connected.
  */
 static void
-GuessXInputDevice(UINT device_index, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)
+GuessXInputDevice(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)
 {
 #ifndef __WINRT__   /* TODO: remove this ifndef __WINRT__ block, but only after integrating with UWP/WinRT's HID API */
 
     PRAWINPUTDEVICELIST devices = NULL;
-    UINT i, found_count = 0, device_count = 0;
+    UINT i, j, found_count = 0, device_count = 0;
 
     if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!device_count)) {
         return;  /* oh well. */
@@ -145,16 +146,36 @@ GuessXInputDevice(UINT device_index, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersio
             (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) &&
             (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) &&
             (SDL_strstr(devName, "IG_") != NULL)) {
+            SDL_bool found = SDL_FALSE;
+            for (j = 0; j < SDL_arraysize(s_arrXInputDevicePath); ++j) {
+                if (j == userid) {
+                    continue;
+                }
+                if (!s_arrXInputDevicePath[j]) {
+                    continue;
+                }
+                if (SDL_strcmp(devName, s_arrXInputDevicePath[j]) == 0) {
+                    found = SDL_TRUE;
+                    break;
+                }
+            }
+            if (found) {
+                /* We already have this device in our XInput device list */
+                continue;
+            }
+
+            /* We don't actually know if this is the right device for this
+             * userid, but we'll record it so we'll at least be consistent
+             * when the raw device list changes.
+             */
             *pVID = (Uint16)rdi.hid.dwVendorId;
             *pPID = (Uint16)rdi.hid.dwProductId;
             *pVersion = (Uint16)rdi.hid.dwVersionNumber;
-
-            if (found_count++ == device_index) {
-                /* We don't really know the order of the devices relative to XInput,
-                   but we'll guess that this is the correct one
-                 */
-                break;
+            if (s_arrXInputDevicePath[userid]) {
+                SDL_free(s_arrXInputDevicePath[userid]);
             }
+            s_arrXInputDevicePath[userid] = SDL_strdup(devName);
+            break;
         }
     }
     SDL_free(devices);
@@ -290,14 +311,12 @@ SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickde
 static void 
 UpdateXInputJoystickBatteryInformation(SDL_Joystick * joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
 {
-    if ( pBatteryInformation->BatteryType != BATTERY_TYPE_UNKNOWN )
-    {
+    if (pBatteryInformation->BatteryType != BATTERY_TYPE_UNKNOWN) {
         SDL_JoystickPowerLevel ePowerLevel = SDL_JOYSTICK_POWER_UNKNOWN;
         if (pBatteryInformation->BatteryType == BATTERY_TYPE_WIRED) {
             ePowerLevel = SDL_JOYSTICK_POWER_WIRED;
         } else {
-            switch ( pBatteryInformation->BatteryLevel )
-            {
+            switch (pBatteryInformation->BatteryLevel) {
             case BATTERY_LEVEL_EMPTY:
                 ePowerLevel = SDL_JOYSTICK_POWER_EMPTY;
                 break;
@@ -314,7 +333,7 @@ UpdateXInputJoystickBatteryInformation(SDL_Joystick * joystick, XINPUT_BATTERY_I
             }
         }
 
-        SDL_PrivateJoystickBatteryLevel( joystick, ePowerLevel );
+        SDL_PrivateJoystickBatteryLevel(joystick, ePowerLevel);
     }
 }
 
@@ -342,7 +361,7 @@ UpdateXInputJoystickState_OLD(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputS
         SDL_PrivateJoystickButton(joystick, button, (wButtons & s_XInputButtons[button]) ? SDL_PRESSED : SDL_RELEASED);
     }
 
-    UpdateXInputJoystickBatteryInformation( joystick, pBatteryInformation );
+    UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation);
 }
 
 static void
@@ -383,7 +402,7 @@ UpdateXInputJoystickState(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState
     }
     SDL_PrivateJoystickHat(joystick, 0, hat);
 
-    UpdateXInputJoystickBatteryInformation( joystick, pBatteryInformation );
+    UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation);
 }
 
 void
@@ -398,15 +417,20 @@ SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick)
 
     result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState);
     if (result == ERROR_DEVICE_NOT_CONNECTED) {
+        Uint8 userid = joystick->hwdata->userid;
+
         joystick->hwdata->send_remove_event = SDL_TRUE;
         joystick->hwdata->removed = SDL_TRUE;
+        if (s_arrXInputDevicePath[userid]) {
+            SDL_free(s_arrXInputDevicePath[userid]);
+            s_arrXInputDevicePath[userid] = NULL;
+        }
         return;
     }
 
-    SDL_zero( XBatteryInformation );
-    if ( XINPUTGETBATTERYINFORMATION )
-    {
-        result = XINPUTGETBATTERYINFORMATION( joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation );
+    SDL_zero(XBatteryInformation);
+    if (XINPUTGETBATTERYINFORMATION) {
+        result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation);
     }
 
     /* only fire events if the data changed from last time */