Better XInput detection code for DirectInput device enumeration.

This code is way faster than the Wbem code, and less ugly.
This commit is contained in:
Ryan C. Gordon 2013-08-28 16:35:32 -04:00
parent 540cb5389c
commit c89e04694d

View File

@ -46,33 +46,9 @@
#include "../../events/SDL_events_c.h" #include "../../events/SDL_events_c.h"
#endif #endif
/* The latest version of mingw-w64 defines IID_IWbemLocator in wbemcli.h
instead of declaring it like Visual Studio and other mingw32 compilers.
So, we need to take care of this here before we define INITGUID.
*/
#ifdef __MINGW32__
#define __IWbemLocator_INTERFACE_DEFINED__
#endif /* __MINGW32__ */
#define INITGUID /* Only set here, if set twice will cause mingw32 to break. */ #define INITGUID /* Only set here, if set twice will cause mingw32 to break. */
#include "SDL_dxjoystick_c.h" #include "SDL_dxjoystick_c.h"
#ifdef __MINGW32__
/* And now that we've included wbemcli.h we need to declare these interfaces */
typedef struct IWbemLocatorVtbl {
BEGIN_INTERFACE
HRESULT (WINAPI *QueryInterface)(IWbemLocator *This,REFIID riid,void **ppvObject);
ULONG (WINAPI *AddRef)(IWbemLocator *This);
ULONG (WINAPI *Release)(IWbemLocator *This);
HRESULT (WINAPI *ConnectServer)(IWbemLocator *This,const BSTR strNetworkResource,const BSTR strUser,const BSTR strPassword,const BSTR strLocale,LONG lSecurityFlags,const BSTR strAuthority,IWbemContext *pCtx,IWbemServices **ppNamespace);
END_INTERFACE
} IWbemLocatorVtbl;
struct IWbemLocator {
CONST_VTBL struct IWbemLocatorVtbl *lpVtbl;
};
#define IWbemLocator_ConnectServer(This,strNetworkResource,strUser,strPassword,strLocale,lSecurityFlags,strAuthority,pCtx,ppNamespace) (This)->lpVtbl->ConnectServer(This,strNetworkResource,strUser,strPassword,strLocale,lSecurityFlags,strAuthority,pCtx,ppNamespace)
#endif /* __MINGW32__ */
#ifndef DIDFT_OPTIONAL #ifndef DIDFT_OPTIONAL
#define DIDFT_OPTIONAL 0x80000000 #define DIDFT_OPTIONAL 0x80000000
#endif #endif
@ -396,156 +372,75 @@ SetDIerror(const char *function, HRESULT code)
} \ } \
} }
DEFINE_GUID(CLSID_WbemLocator, 0x4590f811,0x1d3a,0x11d0,0x89,0x1F,0x00,0xaa,0x00,0x4b,0x2e,0x24);
DEFINE_GUID(IID_IWbemLocator, 0xdc12a687,0x737f,0x11cf,0x88,0x4d,0x00,0xaa,0x00,0x4b,0x2e,0x24);
DEFINE_GUID(IID_ValveStreamingGamepad, MAKELONG( 0x28DE, 0x11FF ),0x0000,0x0000,0x00,0x00,0x50,0x49,0x44,0x56,0x49,0x44); DEFINE_GUID(IID_ValveStreamingGamepad, MAKELONG( 0x28DE, 0x11FF ),0x0000,0x0000,0x00,0x00,0x50,0x49,0x44,0x56,0x49,0x44);
DEFINE_GUID(IID_X360WiredGamepad, MAKELONG( 0x045E, 0x02A1 ),0x0000,0x0000,0x00,0x00,0x50,0x49,0x44,0x56,0x49,0x44);
DEFINE_GUID(IID_X360WirelessGamepad, MAKELONG( 0x045E, 0x028E ),0x0000,0x0000,0x00,0x00,0x50,0x49,0x44,0x56,0x49,0x44);
/*----------------------------------------------------------------------------- static PRAWINPUTDEVICELIST SDL_RawDevList = NULL;
* static UINT SDL_RawDevListCount = 0;
* code from MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014(v=vs.85).aspx
* static SDL_bool
* Enum each PNP device using WMI and check each device ID to see if it contains SDL_IsXInputDevice( const GUID* pGuidProductFromDirectInput )
* "IG_" (ex. "VID_045E&PID_028E&IG_00"). If it does, then it's an XInput device
* Unfortunately this information can not be found by just using DirectInput
*-----------------------------------------------------------------------------*/
BOOL IsXInputDevice( const GUID* pGuidProductFromDirectInput )
{ {
static const GUID *s_XInputProductGUID[] = { static const GUID *s_XInputProductGUID[] = {
&IID_ValveStreamingGamepad &IID_ValveStreamingGamepad,
&IID_X360WiredGamepad, /* Microsoft's wired X360 controller for Windows. */
&IID_X360WirelessGamepad /* Microsoft's wireless X360 controller for Windows. */
}; };
IWbemLocator* pIWbemLocator = NULL;
IEnumWbemClassObject* pEnumDevices = NULL;
IWbemClassObject* pDevices[20];
IWbemServices* pIWbemServices = NULL;
DWORD uReturned = 0;
BSTR bstrNamespace = NULL;
BSTR bstrDeviceID = NULL;
BSTR bstrClassName = NULL;
SDL_bool bIsXinputDevice= SDL_FALSE;
UINT iDevice = 0;
VARIANT var;
HRESULT hr;
DWORD bCleanupCOM;
if (!s_bXInputEnabled) size_t iDevice;
{ SDL_bool retval = SDL_FALSE;
UINT i;
if (!s_bXInputEnabled) {
return SDL_FALSE; return SDL_FALSE;
} }
/* Check for well known XInput device GUIDs */ /* Check for well known XInput device GUIDs */
/* We need to do this for the Valve Streaming Gamepad because it's virtualized and doesn't show up in the device list. */ /* 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 ) { for ( iDevice = 0; iDevice < SDL_arraysize(s_XInputProductGUID); ++iDevice ) {
if (SDL_memcmp(pGuidProductFromDirectInput, s_XInputProductGUID[iDevice], sizeof(GUID)) == 0) { if (SDL_memcmp(pGuidProductFromDirectInput, s_XInputProductGUID[iDevice], sizeof(GUID)) == 0) {
return SDL_TRUE; return SDL_TRUE;
} }
} }
SDL_memset( pDevices, 0x0, sizeof(pDevices) ); /* Go through RAWINPUT (WinXP and later) to find HID devices. */
/* Cache this if we end up using it. */
if (SDL_RawDevList == NULL) {
if ((GetRawInputDeviceList(NULL, &SDL_RawDevListCount, sizeof (RAWINPUTDEVICELIST)) == -1) || (!SDL_RawDevListCount)) {
return SDL_FALSE; /* oh well. */
}
/* CoInit if needed */ SDL_RawDevList = (PRAWINPUTDEVICELIST) SDL_malloc(sizeof (RAWINPUTDEVICELIST) * SDL_RawDevListCount);
hr = CoInitialize(NULL); if (SDL_RawDevList == NULL) {
bCleanupCOM = SUCCEEDED(hr); SDL_OutOfMemory();
return SDL_FALSE;
}
/* Create WMI */ if (GetRawInputDeviceList(SDL_RawDevList, &SDL_RawDevListCount, sizeof (RAWINPUTDEVICELIST)) == -1) {
hr = CoCreateInstance( &CLSID_WbemLocator, SDL_free(SDL_RawDevList);
NULL, SDL_RawDevList = NULL;
CLSCTX_INPROC_SERVER, return SDL_FALSE; /* oh well. */
&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;
/* 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 );
hr = IWbemServices_CreateInstanceEnum( pIWbemServices, bstrClassName, 0, NULL, &pEnumDevices );
if( FAILED(hr) || pEnumDevices == NULL )
goto LCleanup;
/* Loop over all devices */
for( ;; )
{
/* Get 20 at a time */
hr = IEnumWbemClassObject_Next( pEnumDevices, 10000, 20, 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 */
char *pDeviceString = WIN_StringToUTF8( var.bstrVal );
if( SDL_strstr( pDeviceString, "IG_" ) )
{
/* If it does, then get the VID/PID from var.bstrVal */
long dwPid = 0, dwVid = 0;
char * strPid = NULL;
DWORD dwVidPid = 0;
char * strVid = SDL_strstr( pDeviceString, "VID_" );
if( strVid )
{
dwVid = SDL_strtol( strVid + 4, NULL, 16 );
}
strPid = SDL_strstr( pDeviceString, "PID_" );
if( strPid )
{
dwPid = SDL_strtol( strPid + 4, NULL, 16 );
}
/* Compare the VID/PID to the DInput device */
dwVidPid = MAKELONG( dwVid, dwPid );
if( dwVidPid == pGuidProductFromDirectInput->Data1 )
{
bIsXinputDevice = SDL_TRUE;
}
}
if ( pDeviceString )
SDL_free( pDeviceString );
if ( bIsXinputDevice )
break;
}
SAFE_RELEASE( pDevices[iDevice] );
} }
} }
LCleanup: for (i = 0; i < SDL_RawDevListCount; i++) {
RID_DEVICE_INFO rdi;
char devName[128];
UINT rdiSize = sizeof (rdi);
UINT nameSize = SDL_arraysize(devName);
for( iDevice=0; iDevice<20; iDevice++ ) rdi.cbSize = sizeof (rdi);
SAFE_RELEASE( pDevices[iDevice] ); if ( (SDL_RawDevList[i].dwType == RIM_TYPEHID) &&
SAFE_RELEASE( pEnumDevices ); (GetRawInputDeviceInfoA(SDL_RawDevList[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) &&
SAFE_RELEASE( pIWbemLocator ); (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == ((LONG)pGuidProductFromDirectInput->Data1)) &&
SAFE_RELEASE( pIWbemServices ); (GetRawInputDeviceInfoA(SDL_RawDevList[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) &&
(SDL_strstr(devName, "IG_") != NULL) ) {
return SDL_TRUE;
}
}
if ( bstrNamespace ) return SDL_FALSE;
SysFreeString( bstrNamespace );
if ( bstrClassName )
SysFreeString( bstrClassName );
if ( bstrDeviceID )
SysFreeString( bstrDeviceID );
if( bCleanupCOM )
CoUninitialize();
return bIsXinputDevice;
} }
@ -808,7 +703,7 @@ static BOOL CALLBACK
s_bDeviceAdded = SDL_TRUE; s_bDeviceAdded = SDL_TRUE;
bXInputDevice = IsXInputDevice( &pdidInstance->guidProduct ); bXInputDevice = SDL_IsXInputDevice( &pdidInstance->guidProduct );
pNewJoystick = (JoyStick_DeviceData *)SDL_malloc( sizeof(JoyStick_DeviceData) ); pNewJoystick = (JoyStick_DeviceData *)SDL_malloc( sizeof(JoyStick_DeviceData) );
@ -872,6 +767,9 @@ void SDL_SYS_JoystickDetect()
EnumJoysticksCallback, EnumJoysticksCallback,
&pCurList, DIEDFL_ATTACHEDONLY); &pCurList, DIEDFL_ATTACHEDONLY);
SDL_free(SDL_RawDevList); /* in case we used this. */
SDL_RawDevList = NULL;
SDL_UnlockMutex( s_mutexJoyStickEnum ); SDL_UnlockMutex( s_mutexJoyStickEnum );
} }