Skip to content

Commit

Permalink
Improved XInput VID/PID detection and added SDL_wcsstr() and SDL_wcsn…
Browse files Browse the repository at this point in the history
…cmp()
  • Loading branch information
slouken committed Nov 21, 2019
1 parent eb8f14b commit 6509644
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 75 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Expand Up @@ -760,7 +760,7 @@ if(LIBC)
set(HAVE_SIGNAL_H 1)
foreach(_FN
malloc calloc realloc free qsort abs memset memcpy memmove memcmp
wcslen wcscmp
wcslen wcslcpy wcslcat wcsdup wcsstr wcscmp wcsncmp
strlen _strrev _strupr _strlwr strchr strrchr strstr itoa _ltoa
_ultoa strtol strtoul strtoll strtod atoi atof strcmp strncmp
_stricmp _strnicmp strtok_s sscanf
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Expand Up @@ -335,7 +335,7 @@ if test x$enable_libc = xyes; then
AC_DEFINE(HAVE_MPROTECT, 1, [ ])
]),
)
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)
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)

AC_CHECK_LIB(m, pow, [LIBS="$LIBS -lm"; EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lm"])
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)
Expand Down
3 changes: 3 additions & 0 deletions include/SDL_config.h.cmake
Expand Up @@ -96,7 +96,10 @@
#cmakedefine HAVE_WCSLEN 1
#cmakedefine HAVE_WCSLCPY 1
#cmakedefine HAVE_WCSLCAT 1
#cmakedefine HAVE_WCSDUP 1
#cmakedefine HAVE_WCSSTR 1
#cmakedefine HAVE_WCSCMP 1
#cmakedefine HAVE_WCSNCMP 1
#cmakedefine HAVE_STRLEN 1
#cmakedefine HAVE_STRLCPY 1
#cmakedefine HAVE_STRLCAT 1
Expand Down
3 changes: 3 additions & 0 deletions include/SDL_config.h.in
Expand Up @@ -99,7 +99,10 @@
#undef HAVE_WCSLEN
#undef HAVE_WCSLCPY
#undef HAVE_WCSLCAT
#undef HAVE_WCSDUP
#undef HAVE_WCSSTR
#undef HAVE_WCSCMP
#undef HAVE_WCSNCMP
#undef HAVE_STRLEN
#undef HAVE_STRLCPY
#undef HAVE_STRLCAT
Expand Down
5 changes: 4 additions & 1 deletion include/SDL_stdinc.h
Expand Up @@ -453,11 +453,14 @@ extern DECLSPEC void *SDLCALL SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_
extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len);

extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr);
extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
extern DECLSPEC wchar_t *SDLCALL SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle);

extern DECLSPEC int SDLCALL SDL_wcscmp(const wchar_t *str1, const wchar_t *str2);
extern DECLSPEC int SDLCALL SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen);

extern DECLSPEC size_t SDLCALL SDL_strlen(const char *str);
extern DECLSPEC size_t SDLCALL SDL_strlcpy(SDL_OUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen);
Expand Down
169 changes: 137 additions & 32 deletions src/joystick/windows/SDL_dinputjoystick.c
Expand Up @@ -235,45 +235,150 @@ SetDIerror(const char *function, HRESULT code)
return SDL_SetError("%s() DirectX error 0x%8.8lx", function, code);
}

#if 0 /* Microsoft recommended implementation, but slower than checking raw devices */
#define COBJMACROS
#include <wbemidl.h>
#include <oleauto.h>

static const IID CLSID_WbemLocator = { 0x4590f811, 0x1d3a, 0x11d0,{ 0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 } };
static const IID IID_IWbemLocator = { 0xdc12a687, 0x737f, 0x11cf,{ 0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 } };

static SDL_bool
SDL_IsXInputDevice(const GUID* pGuidProductFromDirectInput)
WIN_IsXInputDevice(const GUID* pGuidProductFromDirectInput)
{
static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_XOneWiredGamepad = { MAKELONG(0x045E, 0x02FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_XOneWirelessGamepad = { MAKELONG(0x045E, 0x02DD), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_XOneNewWirelessGamepad = { MAKELONG(0x045E, 0x02D1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_XOneSWirelessGamepad = { MAKELONG(0x045E, 0x02EA), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_XOneSBluetoothGamepad = { MAKELONG(0x045E, 0x02E0), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_XOneEliteWirelessGamepad = { MAKELONG(0x045E, 0x02E3), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };

static const GUID *s_XInputProductGUID[] = {
&IID_ValveStreamingGamepad,
&IID_X360WiredGamepad, /* Microsoft's wired X360 controller for Windows. */
&IID_X360WirelessGamepad, /* Microsoft's wireless X360 controller for Windows. */
&IID_XOneWiredGamepad, /* Microsoft's wired Xbox One controller for Windows. */
&IID_XOneWirelessGamepad, /* Microsoft's wireless Xbox One controller for Windows. */
&IID_XOneNewWirelessGamepad, /* Microsoft's updated wireless Xbox One controller (w/ 3.5 mm jack) for Windows. */
&IID_XOneSWirelessGamepad, /* Microsoft's wireless Xbox One S controller for Windows. */
&IID_XOneSBluetoothGamepad, /* Microsoft's Bluetooth Xbox One S controller for Windows. */
&IID_XOneEliteWirelessGamepad /* Microsoft's wireless Xbox One Elite controller for Windows. */
};
IWbemLocator* pIWbemLocator = NULL;
IEnumWbemClassObject* pEnumDevices = NULL;
IWbemClassObject* pDevices[20];
IWbemServices* pIWbemServices = NULL;
BSTR bstrNamespace = NULL;
BSTR bstrDeviceID = NULL;
BSTR bstrClassName = NULL;
DWORD uReturned = 0;
SDL_bool bIsXinputDevice = SDL_FALSE;
UINT iDevice = 0;
VARIANT var;
HRESULT hr;

SDL_zero(pDevices);

// Create WMI
hr = CoCreateInstance(&CLSID_WbemLocator,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IWbemLocator,
(LPVOID*)&pIWbemLocator);
if (FAILED(hr) || pIWbemLocator == NULL)
goto LCleanup;

bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2"); if (bstrNamespace == NULL) goto LCleanup;
bstrClassName = SysAllocString(L"Win32_PNPEntity"); if (bstrClassName == NULL) goto LCleanup;
bstrDeviceID = SysAllocString(L"DeviceID"); if (bstrDeviceID == NULL) goto LCleanup;

// Connect to WMI
hr = IWbemLocator_ConnectServer(pIWbemLocator, bstrNamespace, NULL, NULL, 0L,
0L, NULL, NULL, &pIWbemServices);
if (FAILED(hr) || pIWbemServices == NULL) {
goto LCleanup;
}

size_t iDevice;
UINT i;
// Switch security level to IMPERSONATE.
CoSetProxyBlanket((IUnknown *)pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);

if (!SDL_XINPUT_Enabled()) {
return SDL_FALSE;
}
hr = IWbemServices_CreateInstanceEnum(pIWbemServices, bstrClassName, 0, NULL, &pEnumDevices);
if (FAILED(hr) || pEnumDevices == NULL)
goto LCleanup;

/* Check for well known XInput device GUIDs */
/* 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. */
for (iDevice = 0; iDevice < SDL_arraysize(s_XInputProductGUID); ++iDevice) {
if (SDL_memcmp(pGuidProductFromDirectInput, s_XInputProductGUID[iDevice], sizeof(GUID)) == 0) {
return SDL_TRUE;
// Loop over all devices
for (;;) {
// Get 20 at a time
hr = IEnumWbemClassObject_Next(pEnumDevices, 10000, SDL_arraysize(pDevices), pDevices, &uReturned);
if (FAILED(hr)) {
goto LCleanup;
}
if (uReturned == 0) {
break;
}

for (iDevice = 0; iDevice < uReturned; iDevice++) {
// For each device, get its device ID
hr = IWbemClassObject_Get(pDevices[iDevice], bstrDeviceID, 0L, &var, NULL, NULL);
if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL) {
// Check if the device ID contains "IG_". If it does, then it's an XInput device
// This information can not be found from DirectInput
if (SDL_wcsstr(var.bstrVal, L"IG_")) {
char *bstrVal = WIN_StringToUTF8(var.bstrVal);

// If it does, then get the VID/PID from var.bstrVal
DWORD dwPid = 0, dwVid = 0, dwVidPid;
const char *strVid, *strPid;
strVid = SDL_strstr(bstrVal, "VID_");
if (strVid && SDL_sscanf(strVid, "VID_%4X", &dwVid) != 1)
dwVid = 0;
strPid = SDL_strstr(bstrVal, "PID_");
if (strPid && SDL_sscanf(strPid, "PID_%4X", &dwPid) != 1)
dwPid = 0;

SDL_free(bstrVal);

// Compare the VID/PID to the DInput device
dwVidPid = MAKELONG(dwVid, dwPid);
if (dwVidPid == pGuidProductFromDirectInput->Data1) {
bIsXinputDevice = SDL_TRUE;
goto LCleanup;
}
}
}
IWbemClassObject_Release(pDevices[iDevice]);
}
}

LCleanup:
if (bstrNamespace) {
SysFreeString(bstrNamespace);
}
if (bstrDeviceID) {
SysFreeString(bstrDeviceID);
}
if (bstrClassName) {
SysFreeString(bstrClassName);
}
for (iDevice = 0; iDevice < SDL_arraysize(pDevices); iDevice++) {
if (pDevices[iDevice]) {
IWbemClassObject_Release(pDevices[iDevice]);
}
}
if (pEnumDevices) {
IEnumWbemClassObject_Release(pEnumDevices);
}
if (pIWbemLocator) {
IWbemLocator_Release(pIWbemLocator);
}
if (pIWbemServices) {
IWbemServices_Release(pIWbemServices);
}

return bIsXinputDevice;
}
#endif /* 0 */

static SDL_bool
SDL_IsXInputDevice(const GUID* pGuidProductFromDirectInput)
{
UINT i;

if (!SDL_XINPUT_Enabled()) {
return SDL_FALSE;
}

if (SDL_memcmp(&pGuidProductFromDirectInput->Data4[2], "PIDVID", 6) == 0) {
Uint16 vendor_id = (Uint16)LOWORD(pGuidProductFromDirectInput->Data1);
Uint16 product_id = (Uint16)HIWORD(pGuidProductFromDirectInput->Data1);
if (SDL_IsJoystickXbox360(vendor_id, product_id) || SDL_IsJoystickXboxOne(vendor_id, product_id) ||
(vendor_id == 0x28DE && product_id == 0x11FF)) {
return SDL_TRUE;
}
}

/* Go through RAWINPUT (WinXP and later) to find HID devices. */
/* Cache this if we end up using it. */
Expand Down
87 changes: 58 additions & 29 deletions src/joystick/windows/SDL_xinputjoystick.c
Expand Up @@ -26,6 +26,7 @@

#include "SDL_assert.h"
#include "SDL_hints.h"
#include "SDL_log.h"
#include "SDL_timer.h"
#include "SDL_windowsjoystick_c.h"
#include "SDL_xinputjoystick_c.h"
Expand Down Expand Up @@ -138,51 +139,79 @@ GuessXInputDevice(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)
return; /* oh well. */
}

/* First see if we have a cached entry for this index */
if (s_arrXInputDevicePath[userid]) {
for (i = 0; i < device_count; i++) {
RID_DEVICE_INFO rdi;
char devName[128];
UINT rdiSize = sizeof(rdi);
UINT nameSize = SDL_arraysize(devName);

rdi.cbSize = sizeof(rdi);
if (devices[i].dwType == RIM_TYPEHID &&
GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1 &&
GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != (UINT)-1) {
if (SDL_strcmp(devName, s_arrXInputDevicePath[userid]) == 0) {
*pVID = (Uint16)rdi.hid.dwVendorId;
*pPID = (Uint16)rdi.hid.dwProductId;
*pVersion = (Uint16)rdi.hid.dwVersionNumber;
return;
}
}
}
}

for (i = 0; i < device_count; i++) {
RID_DEVICE_INFO rdi;
char devName[128];
UINT rdiSize = sizeof(rdi);
UINT nameSize = SDL_arraysize(devName);

rdi.cbSize = sizeof(rdi);
if ((devices[i].dwType == RIM_TYPEHID) &&
(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 (devices[i].dwType == RIM_TYPEHID &&
GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1 &&
GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != (UINT)-1) {
#ifdef DEBUG_JOYSTICK
SDL_Log("Raw input device: VID = 0x%x, PID = 0x%x, %s\n", rdi.hid.dwVendorId, rdi.hid.dwProductId, devName);
#endif
if (SDL_strstr(devName, "IG_") != NULL) {
SDL_bool found = SDL_FALSE;
for (j = 0; j < SDL_arraysize(s_arrXInputDevicePath); ++j) {
if (!s_arrXInputDevicePath[j]) {
continue;
}
if (SDL_strcmp(devName, s_arrXInputDevicePath[j]) == 0) {
found = SDL_TRUE;
break;
}
}
if (!s_arrXInputDevicePath[j]) {
if (found) {
/* We already have this device in our XInput device list */
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 (s_arrXInputDevicePath[userid]) {
SDL_free(s_arrXInputDevicePath[userid]);
/* 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 (s_arrXInputDevicePath[userid]) {
SDL_free(s_arrXInputDevicePath[userid]);
}
s_arrXInputDevicePath[userid] = SDL_strdup(devName);
return;
}
s_arrXInputDevicePath[userid] = SDL_strdup(devName);
break;
}
}
SDL_free(devices);
#endif /* ifndef __WINRT__ */

/* The device wasn't in the raw HID device list, it's probably Bluetooth */
*pVID = 0x045e; /* Microsoft */
*pPID = 0x02fd; /* XBox One S Bluetooth */
*pVersion = 0;
}

static void
Expand Down

0 comments on commit 6509644

Please sign in to comment.