Commit c89e04694dd72271ff3c43f1defd29936342e252

Ryan C. Gordon 2013-08-28T16:35:32

Better XInput detection code for DirectInput device enumeration. This code is way faster than the Wbem code, and less ugly.

diff --git a/src/joystick/windows/SDL_dxjoystick.c b/src/joystick/windows/SDL_dxjoystick.c
index eb4ab2a..53dec5c 100644
--- a/src/joystick/windows/SDL_dxjoystick.c
+++ b/src/joystick/windows/SDL_dxjoystick.c
@@ -46,33 +46,9 @@
 #include "../../events/SDL_events_c.h"
 #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. */
 #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
 #define DIDFT_OPTIONAL      0x80000000
 #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_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);
 
-/*-----------------------------------------------------------------------------
- *
- * code from MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014(v=vs.85).aspx
- *
- * Enum each PNP device using WMI and check each device ID to see if it contains
- * "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 PRAWINPUTDEVICELIST SDL_RawDevList = NULL;
+static UINT SDL_RawDevListCount = 0;
+
+static SDL_bool
+SDL_IsXInputDevice( const GUID* pGuidProductFromDirectInput )
 {
     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;
     }
 
     /* 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 ) {
         if (SDL_memcmp(pGuidProductFromDirectInput, s_XInputProductGUID[iDevice], sizeof(GUID)) == 0) {
             return SDL_TRUE;
         }
     }
 
-    SDL_memset( pDevices, 0x0, sizeof(pDevices) );
-
-    /* CoInit if needed */
-    hr = CoInitialize(NULL);
-    bCleanupCOM = SUCCEEDED(hr);
-
-    /* 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;
-
-    /* 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 );
-                    }
+    /* 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. */
+        }
 
-                    /* Compare the VID/PID to the DInput device */
-                    dwVidPid = MAKELONG( dwVid, dwPid );
-                    if( dwVidPid == pGuidProductFromDirectInput->Data1 )
-                    {
-                        bIsXinputDevice = SDL_TRUE;
-                    }
-                }
-                if ( pDeviceString )
-                    SDL_free( pDeviceString );
+        SDL_RawDevList = (PRAWINPUTDEVICELIST) SDL_malloc(sizeof (RAWINPUTDEVICELIST) * SDL_RawDevListCount);
+        if (SDL_RawDevList == NULL) {
+            SDL_OutOfMemory();
+            return SDL_FALSE;
+        }
 
-                if ( bIsXinputDevice )
-                    break;
-            }
-            SAFE_RELEASE( pDevices[iDevice] );
+        if (GetRawInputDeviceList(SDL_RawDevList, &SDL_RawDevListCount, sizeof (RAWINPUTDEVICELIST)) == -1) {
+             SDL_free(SDL_RawDevList);
+             SDL_RawDevList = NULL;
+             return SDL_FALSE;  /* oh well. */
         }
     }
 
-LCleanup:
-
-    for( iDevice=0; iDevice<20; iDevice++ )
-        SAFE_RELEASE( pDevices[iDevice] );
-    SAFE_RELEASE( pEnumDevices );
-    SAFE_RELEASE( pIWbemLocator );
-    SAFE_RELEASE( pIWbemServices );
-
-    if ( bstrNamespace )
-        SysFreeString( bstrNamespace );
-    if ( bstrClassName )
-        SysFreeString( bstrClassName );
-    if ( bstrDeviceID )
-        SysFreeString( bstrDeviceID );
-
-    if( bCleanupCOM )
-        CoUninitialize();
+    for (i = 0; i < SDL_RawDevListCount; i++) {
+        RID_DEVICE_INFO rdi;
+        char devName[128];
+        UINT rdiSize = sizeof (rdi);
+        UINT nameSize = SDL_arraysize(devName);
+
+        rdi.cbSize = sizeof (rdi);
+        if ( (SDL_RawDevList[i].dwType == RIM_TYPEHID) &&
+             (GetRawInputDeviceInfoA(SDL_RawDevList[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) &&
+             (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == ((LONG)pGuidProductFromDirectInput->Data1)) &&
+             (GetRawInputDeviceInfoA(SDL_RawDevList[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) &&
+             (SDL_strstr(devName, "IG_") != NULL) ) {
+             return SDL_TRUE;
+        }
+    }
 
-    return bIsXinputDevice;
+    return SDL_FALSE;
 }
 
 
@@ -808,7 +703,7 @@ static BOOL CALLBACK
 
     s_bDeviceAdded = SDL_TRUE;
 
-    bXInputDevice = IsXInputDevice( &pdidInstance->guidProduct );
+    bXInputDevice = SDL_IsXInputDevice( &pdidInstance->guidProduct );
 
     pNewJoystick = (JoyStick_DeviceData *)SDL_malloc( sizeof(JoyStick_DeviceData) );
 
@@ -872,6 +767,9 @@ void SDL_SYS_JoystickDetect()
             EnumJoysticksCallback,
             &pCurList, DIEDFL_ATTACHEDONLY);
 
+        SDL_free(SDL_RawDevList);  /* in case we used this. */
+        SDL_RawDevList = NULL;
+
         SDL_UnlockMutex( s_mutexJoyStickEnum );
     }