Improved XInput VID/PID detection and added SDL_wcsstr() and SDL_wcsncmp()
authorSam Lantinga <slouken@libsdl.org>
Wed, 20 Nov 2019 16:42:50 -0800
changeset 132637e5406b2375a
parent 13262 d6decc5d2464
child 13264 951801b5b148
Improved XInput VID/PID detection and added SDL_wcsstr() and SDL_wcsncmp()
CMakeLists.txt
configure.ac
include/SDL_config.h.cmake
include/SDL_config.h.in
include/SDL_stdinc.h
src/joystick/windows/SDL_dinputjoystick.c
src/joystick/windows/SDL_xinputjoystick.c
src/stdlib/SDL_string.c
     1.1 --- a/CMakeLists.txt	Wed Nov 20 20:40:50 2019 +0300
     1.2 +++ b/CMakeLists.txt	Wed Nov 20 16:42:50 2019 -0800
     1.3 @@ -760,7 +760,7 @@
     1.4      set(HAVE_SIGNAL_H 1)
     1.5      foreach(_FN
     1.6              malloc calloc realloc free qsort abs memset memcpy memmove memcmp
     1.7 -            wcslen wcscmp
     1.8 +            wcslen wcslcpy wcslcat wcsdup wcsstr wcscmp wcsncmp
     1.9              strlen _strrev _strupr _strlwr strchr strrchr strstr itoa _ltoa
    1.10              _ultoa strtol strtoul strtoll strtod atoi atof strcmp strncmp
    1.11              _stricmp _strnicmp strtok_s sscanf
     2.1 --- a/configure.ac	Wed Nov 20 20:40:50 2019 +0300
     2.2 +++ b/configure.ac	Wed Nov 20 16:42:50 2019 -0800
     2.3 @@ -335,7 +335,7 @@
     2.4          AC_DEFINE(HAVE_MPROTECT, 1, [ ])
     2.5          ]),
     2.6      )
     2.7 -    AC_CHECK_FUNCS(malloc calloc realloc free getenv setenv putenv unsetenv qsort abs bcopy memset memcpy memmove wcslen wcscmp strlen strlcpy strlcat _strrev _strupr _strlwr strchr strrchr strstr strtok_r itoa _ltoa _uitoa _ultoa strtol strtoul _i64toa _ui64toa strtoll strtoull atoi atof strcmp strncmp _stricmp strcasecmp _strnicmp strncasecmp vsscanf vsnprintf fopen64 fseeko fseeko64 sigaction setjmp nanosleep sysconf sysctlbyname getauxval poll _Exit)
     2.8 +    AC_CHECK_FUNCS(malloc calloc realloc free getenv setenv putenv unsetenv qsort abs bcopy memset memcpy memmove wcslen wcslcpy wcslcat wcsdup wcsstr wcscmp wcsncmp strlen strlcpy strlcat _strrev _strupr _strlwr strchr strrchr strstr itoa _ltoa _uitoa _ultoa strtol strtoul _i64toa _ui64toa strtoll strtoull atoi atof strcmp strncmp _stricmp strcasecmp _strnicmp strncasecmp vsscanf vsnprintf fopen64 fseeko fseeko64 sigaction setjmp nanosleep sysconf sysctlbyname getauxval poll _Exit)
     2.9  
    2.10      AC_CHECK_LIB(m, pow, [LIBS="$LIBS -lm"; EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lm"])
    2.11      AC_CHECK_FUNCS(acos acosf asin asinf atan atanf atan2 atan2f ceil ceilf copysign copysignf cos cosf exp expf fabs fabsf floor floorf fmod fmodf log logf log10 log10f pow powf scalbn scalbnf sin sinf sqrt sqrtf tan tanf)
     3.1 --- a/include/SDL_config.h.cmake	Wed Nov 20 20:40:50 2019 +0300
     3.2 +++ b/include/SDL_config.h.cmake	Wed Nov 20 16:42:50 2019 -0800
     3.3 @@ -96,7 +96,10 @@
     3.4  #cmakedefine HAVE_WCSLEN 1
     3.5  #cmakedefine HAVE_WCSLCPY 1
     3.6  #cmakedefine HAVE_WCSLCAT 1
     3.7 +#cmakedefine HAVE_WCSDUP 1
     3.8 +#cmakedefine HAVE_WCSSTR 1
     3.9  #cmakedefine HAVE_WCSCMP 1
    3.10 +#cmakedefine HAVE_WCSNCMP 1
    3.11  #cmakedefine HAVE_STRLEN 1
    3.12  #cmakedefine HAVE_STRLCPY 1
    3.13  #cmakedefine HAVE_STRLCAT 1
     4.1 --- a/include/SDL_config.h.in	Wed Nov 20 20:40:50 2019 +0300
     4.2 +++ b/include/SDL_config.h.in	Wed Nov 20 16:42:50 2019 -0800
     4.3 @@ -99,7 +99,10 @@
     4.4  #undef HAVE_WCSLEN
     4.5  #undef HAVE_WCSLCPY
     4.6  #undef HAVE_WCSLCAT
     4.7 +#undef HAVE_WCSDUP
     4.8 +#undef HAVE_WCSSTR
     4.9  #undef HAVE_WCSCMP
    4.10 +#undef HAVE_WCSNCMP
    4.11  #undef HAVE_STRLEN
    4.12  #undef HAVE_STRLCPY
    4.13  #undef HAVE_STRLCAT
     5.1 --- a/include/SDL_stdinc.h	Wed Nov 20 20:40:50 2019 +0300
     5.2 +++ b/include/SDL_stdinc.h	Wed Nov 20 16:42:50 2019 -0800
     5.3 @@ -453,11 +453,14 @@
     5.4  extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
     5.5  extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len);
     5.6  
     5.7 -extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
     5.8  extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr);
     5.9  extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
    5.10  extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
    5.11 +extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
    5.12 +extern DECLSPEC wchar_t *SDLCALL SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle);
    5.13 +
    5.14  extern DECLSPEC int SDLCALL SDL_wcscmp(const wchar_t *str1, const wchar_t *str2);
    5.15 +extern DECLSPEC int SDLCALL SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen);
    5.16  
    5.17  extern DECLSPEC size_t SDLCALL SDL_strlen(const char *str);
    5.18  extern DECLSPEC size_t SDLCALL SDL_strlcpy(SDL_OUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen);
     6.1 --- a/src/joystick/windows/SDL_dinputjoystick.c	Wed Nov 20 20:40:50 2019 +0300
     6.2 +++ b/src/joystick/windows/SDL_dinputjoystick.c	Wed Nov 20 16:42:50 2019 -0800
     6.3 @@ -235,45 +235,150 @@
     6.4      return SDL_SetError("%s() DirectX error 0x%8.8lx", function, code);
     6.5  }
     6.6  
     6.7 +#if 0 /* Microsoft recommended implementation, but slower than checking raw devices */
     6.8 +#define COBJMACROS
     6.9 +#include <wbemidl.h>
    6.10 +#include <oleauto.h>
    6.11 +
    6.12 +static const IID CLSID_WbemLocator = { 0x4590f811, 0x1d3a, 0x11d0,{ 0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 } };
    6.13 +static const IID IID_IWbemLocator = { 0xdc12a687, 0x737f, 0x11cf,{ 0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 } };
    6.14 +
    6.15 +static SDL_bool
    6.16 +WIN_IsXInputDevice(const GUID* pGuidProductFromDirectInput)
    6.17 +{
    6.18 +	IWbemLocator*           pIWbemLocator = NULL;
    6.19 +	IEnumWbemClassObject*   pEnumDevices = NULL;
    6.20 +	IWbemClassObject*       pDevices[20];
    6.21 +	IWbemServices*          pIWbemServices = NULL;
    6.22 +	BSTR                    bstrNamespace = NULL;
    6.23 +	BSTR                    bstrDeviceID = NULL;
    6.24 +	BSTR                    bstrClassName = NULL;
    6.25 +	DWORD                   uReturned = 0;
    6.26 +	SDL_bool                bIsXinputDevice = SDL_FALSE;
    6.27 +	UINT                    iDevice = 0;
    6.28 +	VARIANT                 var;
    6.29 +	HRESULT                 hr;
    6.30 +
    6.31 +	SDL_zero(pDevices);
    6.32 +
    6.33 +	// Create WMI
    6.34 +	hr = CoCreateInstance(&CLSID_WbemLocator,
    6.35 +		NULL,
    6.36 +		CLSCTX_INPROC_SERVER,
    6.37 +		&IID_IWbemLocator,
    6.38 +		(LPVOID*)&pIWbemLocator);
    6.39 +	if (FAILED(hr) || pIWbemLocator == NULL)
    6.40 +		goto LCleanup;
    6.41 +
    6.42 +	bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2"); if (bstrNamespace == NULL) goto LCleanup;
    6.43 +	bstrClassName = SysAllocString(L"Win32_PNPEntity");   if (bstrClassName == NULL) goto LCleanup;
    6.44 +	bstrDeviceID = SysAllocString(L"DeviceID");          if (bstrDeviceID == NULL)  goto LCleanup;
    6.45 +
    6.46 +	// Connect to WMI 
    6.47 +	hr = IWbemLocator_ConnectServer(pIWbemLocator, bstrNamespace, NULL, NULL, 0L,
    6.48 +		0L, NULL, NULL, &pIWbemServices);
    6.49 +	if (FAILED(hr) || pIWbemServices == NULL) {
    6.50 +		goto LCleanup;
    6.51 +    }
    6.52 +
    6.53 +	// Switch security level to IMPERSONATE. 
    6.54 +	CoSetProxyBlanket((IUnknown *)pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
    6.55 +		RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
    6.56 +
    6.57 +	hr = IWbemServices_CreateInstanceEnum(pIWbemServices, bstrClassName, 0, NULL, &pEnumDevices);
    6.58 +	if (FAILED(hr) || pEnumDevices == NULL)
    6.59 +		goto LCleanup;
    6.60 +
    6.61 +	// Loop over all devices
    6.62 +	for (;;) {
    6.63 +		// Get 20 at a time
    6.64 +		hr = IEnumWbemClassObject_Next(pEnumDevices, 10000, SDL_arraysize(pDevices), pDevices, &uReturned);
    6.65 +		if (FAILED(hr)) {
    6.66 +			goto LCleanup;
    6.67 +        }
    6.68 +		if (uReturned == 0) {
    6.69 +			break;
    6.70 +        }
    6.71 +
    6.72 +		for (iDevice = 0; iDevice < uReturned; iDevice++) {
    6.73 +			// For each device, get its device ID
    6.74 +			hr = IWbemClassObject_Get(pDevices[iDevice], bstrDeviceID, 0L, &var, NULL, NULL);
    6.75 +			if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL) {
    6.76 +				// Check if the device ID contains "IG_".  If it does, then it's an XInput device
    6.77 +				// This information can not be found from DirectInput 
    6.78 +				if (SDL_wcsstr(var.bstrVal, L"IG_")) {
    6.79 +					char *bstrVal = WIN_StringToUTF8(var.bstrVal);
    6.80 +
    6.81 +					// If it does, then get the VID/PID from var.bstrVal
    6.82 +					DWORD dwPid = 0, dwVid = 0, dwVidPid;
    6.83 +					const char *strVid, *strPid;
    6.84 +					strVid = SDL_strstr(bstrVal, "VID_");
    6.85 +					if (strVid && SDL_sscanf(strVid, "VID_%4X", &dwVid) != 1)
    6.86 +						dwVid = 0;
    6.87 +					strPid = SDL_strstr(bstrVal, "PID_");
    6.88 +					if (strPid && SDL_sscanf(strPid, "PID_%4X", &dwPid) != 1)
    6.89 +						dwPid = 0;
    6.90 +
    6.91 +					SDL_free(bstrVal);
    6.92 +
    6.93 +					// Compare the VID/PID to the DInput device
    6.94 +					dwVidPid = MAKELONG(dwVid, dwPid);
    6.95 +					if (dwVidPid == pGuidProductFromDirectInput->Data1) {
    6.96 +						bIsXinputDevice = SDL_TRUE;
    6.97 +						goto LCleanup;
    6.98 +					}
    6.99 +				}
   6.100 +			}
   6.101 +			IWbemClassObject_Release(pDevices[iDevice]);
   6.102 +		}
   6.103 +	}
   6.104 +
   6.105 +LCleanup:
   6.106 +	if (bstrNamespace) {
   6.107 +		SysFreeString(bstrNamespace);
   6.108 +    }
   6.109 +	if (bstrDeviceID) {
   6.110 +		SysFreeString(bstrDeviceID);
   6.111 +    }
   6.112 +	if (bstrClassName) {
   6.113 +		SysFreeString(bstrClassName);
   6.114 +    }
   6.115 +	for (iDevice = 0; iDevice < SDL_arraysize(pDevices); iDevice++) {
   6.116 +		if (pDevices[iDevice]) {
   6.117 +			IWbemClassObject_Release(pDevices[iDevice]);
   6.118 +		}
   6.119 +	}
   6.120 +	if (pEnumDevices) {
   6.121 +		IEnumWbemClassObject_Release(pEnumDevices);
   6.122 +	}
   6.123 +	if (pIWbemLocator) {
   6.124 +		IWbemLocator_Release(pIWbemLocator);
   6.125 +	}
   6.126 +	if (pIWbemServices) {
   6.127 +		IWbemServices_Release(pIWbemServices);
   6.128 +	}
   6.129 +
   6.130 +	return bIsXinputDevice;
   6.131 +}
   6.132 +#endif /* 0 */
   6.133 +
   6.134  static SDL_bool
   6.135  SDL_IsXInputDevice(const GUID* pGuidProductFromDirectInput)
   6.136  {
   6.137 -    static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.138 -    static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.139 -    static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.140 -    static GUID IID_XOneWiredGamepad = { MAKELONG(0x045E, 0x02FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.141 -    static GUID IID_XOneWirelessGamepad = { MAKELONG(0x045E, 0x02DD), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.142 -    static GUID IID_XOneNewWirelessGamepad = { MAKELONG(0x045E, 0x02D1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.143 -    static GUID IID_XOneSWirelessGamepad = { MAKELONG(0x045E, 0x02EA), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.144 -    static GUID IID_XOneSBluetoothGamepad = { MAKELONG(0x045E, 0x02E0), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.145 -    static GUID IID_XOneEliteWirelessGamepad = { MAKELONG(0x045E, 0x02E3), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
   6.146 +	UINT i;
   6.147  
   6.148 -    static const GUID *s_XInputProductGUID[] = {
   6.149 -        &IID_ValveStreamingGamepad,
   6.150 -        &IID_X360WiredGamepad,         /* Microsoft's wired X360 controller for Windows. */
   6.151 -        &IID_X360WirelessGamepad,      /* Microsoft's wireless X360 controller for Windows. */
   6.152 -        &IID_XOneWiredGamepad,         /* Microsoft's wired Xbox One controller for Windows. */
   6.153 -        &IID_XOneWirelessGamepad,      /* Microsoft's wireless Xbox One controller for Windows. */
   6.154 -        &IID_XOneNewWirelessGamepad,   /* Microsoft's updated wireless Xbox One controller (w/ 3.5 mm jack) for Windows. */
   6.155 -        &IID_XOneSWirelessGamepad,     /* Microsoft's wireless Xbox One S controller for Windows. */
   6.156 -        &IID_XOneSBluetoothGamepad,    /* Microsoft's Bluetooth Xbox One S controller for Windows. */
   6.157 -        &IID_XOneEliteWirelessGamepad  /* Microsoft's wireless Xbox One Elite controller for Windows. */
   6.158 -    };
   6.159 +	if (!SDL_XINPUT_Enabled()) {
   6.160 +		return SDL_FALSE;
   6.161 +	}
   6.162  
   6.163 -    size_t iDevice;
   6.164 -    UINT i;
   6.165 -
   6.166 -    if (!SDL_XINPUT_Enabled()) {
   6.167 -        return SDL_FALSE;
   6.168 -    }
   6.169 -
   6.170 -    /* Check for well known XInput device GUIDs */
   6.171 -    /* This lets us skip RAWINPUT for popular devices. Also, we need to do this for the Valve Streaming Gamepad because it's virtualized and doesn't show up in the device list. */
   6.172 -    for (iDevice = 0; iDevice < SDL_arraysize(s_XInputProductGUID); ++iDevice) {
   6.173 -        if (SDL_memcmp(pGuidProductFromDirectInput, s_XInputProductGUID[iDevice], sizeof(GUID)) == 0) {
   6.174 -            return SDL_TRUE;
   6.175 -        }
   6.176 -    }
   6.177 +	if (SDL_memcmp(&pGuidProductFromDirectInput->Data4[2], "PIDVID", 6) == 0) {
   6.178 +		Uint16 vendor_id = (Uint16)LOWORD(pGuidProductFromDirectInput->Data1);
   6.179 +		Uint16 product_id = (Uint16)HIWORD(pGuidProductFromDirectInput->Data1);
   6.180 +		if (SDL_IsJoystickXbox360(vendor_id, product_id) || SDL_IsJoystickXboxOne(vendor_id, product_id) ||
   6.181 +			(vendor_id == 0x28DE && product_id == 0x11FF)) {
   6.182 +			return SDL_TRUE;
   6.183 +		}
   6.184 +	}
   6.185  
   6.186      /* Go through RAWINPUT (WinXP and later) to find HID devices. */
   6.187      /* Cache this if we end up using it. */
     7.1 --- a/src/joystick/windows/SDL_xinputjoystick.c	Wed Nov 20 20:40:50 2019 +0300
     7.2 +++ b/src/joystick/windows/SDL_xinputjoystick.c	Wed Nov 20 16:42:50 2019 -0800
     7.3 @@ -26,6 +26,7 @@
     7.4  
     7.5  #include "SDL_assert.h"
     7.6  #include "SDL_hints.h"
     7.7 +#include "SDL_log.h"
     7.8  #include "SDL_timer.h"
     7.9  #include "SDL_windowsjoystick_c.h"
    7.10  #include "SDL_xinputjoystick_c.h"
    7.11 @@ -138,6 +139,28 @@
    7.12          return;  /* oh well. */
    7.13      }
    7.14  
    7.15 +    /* First see if we have a cached entry for this index */
    7.16 +    if (s_arrXInputDevicePath[userid]) {
    7.17 +        for (i = 0; i < device_count; i++) {
    7.18 +            RID_DEVICE_INFO rdi;
    7.19 +            char devName[128];
    7.20 +            UINT rdiSize = sizeof(rdi);
    7.21 +            UINT nameSize = SDL_arraysize(devName);
    7.22 +
    7.23 +            rdi.cbSize = sizeof(rdi);
    7.24 +            if (devices[i].dwType == RIM_TYPEHID &&
    7.25 +                GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1 &&
    7.26 +                GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != (UINT)-1) {
    7.27 +                if (SDL_strcmp(devName, s_arrXInputDevicePath[userid]) == 0) {
    7.28 +                    *pVID = (Uint16)rdi.hid.dwVendorId;
    7.29 +                    *pPID = (Uint16)rdi.hid.dwProductId;
    7.30 +                    *pVersion = (Uint16)rdi.hid.dwVersionNumber;
    7.31 +                    return;
    7.32 +                }
    7.33 +            }
    7.34 +        }
    7.35 +    }
    7.36 +
    7.37      for (i = 0; i < device_count; i++) {
    7.38          RID_DEVICE_INFO rdi;
    7.39          char devName[128];
    7.40 @@ -145,44 +168,50 @@
    7.41          UINT nameSize = SDL_arraysize(devName);
    7.42  
    7.43          rdi.cbSize = sizeof(rdi);
    7.44 -        if ((devices[i].dwType == RIM_TYPEHID) &&
    7.45 -            (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) &&
    7.46 -            (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) &&
    7.47 -            (SDL_strstr(devName, "IG_") != NULL)) {
    7.48 -            SDL_bool found = SDL_FALSE;
    7.49 -            for (j = 0; j < SDL_arraysize(s_arrXInputDevicePath); ++j) {
    7.50 -                if (j == userid) {
    7.51 +        if (devices[i].dwType == RIM_TYPEHID &&
    7.52 +            GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1 &&
    7.53 +            GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != (UINT)-1) {
    7.54 +#ifdef DEBUG_JOYSTICK
    7.55 +            SDL_Log("Raw input device: VID = 0x%x, PID = 0x%x, %s\n", rdi.hid.dwVendorId, rdi.hid.dwProductId, devName);
    7.56 +#endif
    7.57 +            if (SDL_strstr(devName, "IG_") != NULL) {
    7.58 +                SDL_bool found = SDL_FALSE;
    7.59 +                for (j = 0; j < SDL_arraysize(s_arrXInputDevicePath); ++j) {
    7.60 +                    if (!s_arrXInputDevicePath[j]) {
    7.61 +                        continue;
    7.62 +                    }
    7.63 +                    if (SDL_strcmp(devName, s_arrXInputDevicePath[j]) == 0) {
    7.64 +                        found = SDL_TRUE;
    7.65 +                        break;
    7.66 +                    }
    7.67 +                }
    7.68 +                if (found) {
    7.69 +                    /* We already have this device in our XInput device list */
    7.70                      continue;
    7.71                  }
    7.72 -                if (!s_arrXInputDevicePath[j]) {
    7.73 -                    continue;
    7.74 +
    7.75 +                /* We don't actually know if this is the right device for this
    7.76 +                 * userid, but we'll record it so we'll at least be consistent
    7.77 +                 * when the raw device list changes.
    7.78 +                 */
    7.79 +                *pVID = (Uint16)rdi.hid.dwVendorId;
    7.80 +                *pPID = (Uint16)rdi.hid.dwProductId;
    7.81 +                *pVersion = (Uint16)rdi.hid.dwVersionNumber;
    7.82 +                if (s_arrXInputDevicePath[userid]) {
    7.83 +                    SDL_free(s_arrXInputDevicePath[userid]);
    7.84                  }
    7.85 -                if (SDL_strcmp(devName, s_arrXInputDevicePath[j]) == 0) {
    7.86 -                    found = SDL_TRUE;
    7.87 -                    break;
    7.88 -                }
    7.89 +                s_arrXInputDevicePath[userid] = SDL_strdup(devName);
    7.90 +                return;
    7.91              }
    7.92 -            if (found) {
    7.93 -                /* We already have this device in our XInput device list */
    7.94 -                continue;
    7.95 -            }
    7.96 -
    7.97 -            /* We don't actually know if this is the right device for this
    7.98 -             * userid, but we'll record it so we'll at least be consistent
    7.99 -             * when the raw device list changes.
   7.100 -             */
   7.101 -            *pVID = (Uint16)rdi.hid.dwVendorId;
   7.102 -            *pPID = (Uint16)rdi.hid.dwProductId;
   7.103 -            *pVersion = (Uint16)rdi.hid.dwVersionNumber;
   7.104 -            if (s_arrXInputDevicePath[userid]) {
   7.105 -                SDL_free(s_arrXInputDevicePath[userid]);
   7.106 -            }
   7.107 -            s_arrXInputDevicePath[userid] = SDL_strdup(devName);
   7.108 -            break;
   7.109          }
   7.110      }
   7.111      SDL_free(devices);
   7.112  #endif  /* ifndef __WINRT__ */
   7.113 +
   7.114 +    /* The device wasn't in the raw HID device list, it's probably Bluetooth */
   7.115 +    *pVID = 0x045e; /* Microsoft */
   7.116 +    *pPID = 0x02fd; /* XBox One S Bluetooth */
   7.117 +    *pVersion = 0;
   7.118  }
   7.119  
   7.120  static void
     8.1 --- a/src/stdlib/SDL_string.c	Wed Nov 20 20:40:50 2019 +0300
     8.2 +++ b/src/stdlib/SDL_string.c	Wed Nov 20 16:42:50 2019 -0800
     8.3 @@ -421,17 +421,6 @@
     8.4  #endif /* HAVE_STRLEN */
     8.5  }
     8.6  
     8.7 -wchar_t *
     8.8 -SDL_wcsdup(const wchar_t *string)
     8.9 -{
    8.10 -    size_t len = ((SDL_wcslen(string) + 1) * sizeof(wchar_t));
    8.11 -    wchar_t *newstr = (wchar_t *)SDL_malloc(len);
    8.12 -    if (newstr) {
    8.13 -        SDL_memcpy(newstr, string, len);
    8.14 -    }
    8.15 -    return newstr;
    8.16 -}
    8.17 -
    8.18  size_t
    8.19  SDL_wcslen(const wchar_t * string)
    8.20  {
    8.21 @@ -477,6 +466,34 @@
    8.22  #endif /* HAVE_WCSLCAT */
    8.23  }
    8.24  
    8.25 +wchar_t *
    8.26 +SDL_wcsdup(const wchar_t *string)
    8.27 +{
    8.28 +    size_t len = ((SDL_wcslen(string) + 1) * sizeof(wchar_t));
    8.29 +    wchar_t *newstr = (wchar_t *)SDL_malloc(len);
    8.30 +    if (newstr) {
    8.31 +        SDL_memcpy(newstr, string, len);
    8.32 +    }
    8.33 +    return newstr;
    8.34 +}
    8.35 +
    8.36 +wchar_t *
    8.37 +SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle)
    8.38 +{
    8.39 +#if defined(HAVE_WCSSTR)
    8.40 +    return SDL_const_cast(wchar_t*,wcsstr(haystack, needle));
    8.41 +#else
    8.42 +    size_t length = SDL_wcslen(needle);
    8.43 +    while (*haystack) {
    8.44 +        if (SDL_wcsncmp(haystack, needle, length) == 0) {
    8.45 +            return (wchar_t *)haystack;
    8.46 +        }
    8.47 +        ++haystack;
    8.48 +    }
    8.49 +    return NULL;
    8.50 +#endif /* HAVE_WCSSTR */
    8.51 +}
    8.52 +
    8.53  int
    8.54  SDL_wcscmp(const wchar_t *str1, const wchar_t *str2)
    8.55  {
    8.56 @@ -493,6 +510,22 @@
    8.57  #endif /* HAVE_WCSCMP */
    8.58  }
    8.59  
    8.60 +int
    8.61 +SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen)
    8.62 +{
    8.63 +#if defined(HAVE_WCSNCMP)
    8.64 +    return wcsncmp(str1, str2, maxlen);
    8.65 +#else
    8.66 +    while (*str1 && *str2) {
    8.67 +        if (*str1 != *str2)
    8.68 +            break;
    8.69 +        ++str1;
    8.70 +        ++str2;
    8.71 +    }
    8.72 +    return (int)(*str1 - *str2);
    8.73 +#endif /* HAVE_WCSNCMP */
    8.74 +}
    8.75 +
    8.76  size_t
    8.77  SDL_strlcpy(SDL_OUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen)
    8.78  {