Better XInput detection code for DirectInput device enumeration.
authorRyan C. Gordon <icculus@icculus.org>
Wed, 28 Aug 2013 16:35:32 -0400
changeset 77068cc29a668223
parent 7705 e70dc8b38f26
child 7707 37e02f8fcfa8
Better XInput detection code for DirectInput device enumeration.

This code is way faster than the Wbem code, and less ugly.
src/joystick/windows/SDL_dxjoystick.c
     1.1 --- a/src/joystick/windows/SDL_dxjoystick.c	Wed Aug 28 00:07:02 2013 -0400
     1.2 +++ b/src/joystick/windows/SDL_dxjoystick.c	Wed Aug 28 16:35:32 2013 -0400
     1.3 @@ -46,33 +46,9 @@
     1.4  #include "../../events/SDL_events_c.h"
     1.5  #endif
     1.6  
     1.7 -/* The latest version of mingw-w64 defines IID_IWbemLocator in wbemcli.h
     1.8 -   instead of declaring it like Visual Studio and other mingw32 compilers.
     1.9 -   So, we need to take care of this here before we define INITGUID.
    1.10 -*/
    1.11 -#ifdef __MINGW32__
    1.12 -#define __IWbemLocator_INTERFACE_DEFINED__
    1.13 -#endif /* __MINGW32__ */
    1.14 -
    1.15  #define INITGUID /* Only set here, if set twice will cause mingw32 to break. */
    1.16  #include "SDL_dxjoystick_c.h"
    1.17  
    1.18 -#ifdef __MINGW32__
    1.19 -/* And now that we've included wbemcli.h we need to declare these interfaces */
    1.20 -typedef struct IWbemLocatorVtbl {
    1.21 -  BEGIN_INTERFACE
    1.22 -    HRESULT (WINAPI *QueryInterface)(IWbemLocator *This,REFIID riid,void **ppvObject);
    1.23 -    ULONG (WINAPI *AddRef)(IWbemLocator *This);
    1.24 -    ULONG (WINAPI *Release)(IWbemLocator *This);
    1.25 -    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);
    1.26 -  END_INTERFACE
    1.27 -} IWbemLocatorVtbl;
    1.28 -struct IWbemLocator {
    1.29 -  CONST_VTBL struct IWbemLocatorVtbl *lpVtbl;
    1.30 -};
    1.31 -#define IWbemLocator_ConnectServer(This,strNetworkResource,strUser,strPassword,strLocale,lSecurityFlags,strAuthority,pCtx,ppNamespace) (This)->lpVtbl->ConnectServer(This,strNetworkResource,strUser,strPassword,strLocale,lSecurityFlags,strAuthority,pCtx,ppNamespace)
    1.32 -#endif /* __MINGW32__ */
    1.33 -
    1.34  #ifndef DIDFT_OPTIONAL
    1.35  #define DIDFT_OPTIONAL      0x80000000
    1.36  #endif
    1.37 @@ -396,156 +372,75 @@
    1.38      }                                               \
    1.39  }
    1.40  
    1.41 -DEFINE_GUID(CLSID_WbemLocator,   0x4590f811,0x1d3a,0x11d0,0x89,0x1F,0x00,0xaa,0x00,0x4b,0x2e,0x24);
    1.42 -DEFINE_GUID(IID_IWbemLocator,    0xdc12a687,0x737f,0x11cf,0x88,0x4d,0x00,0xaa,0x00,0x4b,0x2e,0x24);
    1.43 +DEFINE_GUID(IID_ValveStreamingGamepad,  MAKELONG( 0x28DE, 0x11FF ),0x0000,0x0000,0x00,0x00,0x50,0x49,0x44,0x56,0x49,0x44);
    1.44 +DEFINE_GUID(IID_X360WiredGamepad,  MAKELONG( 0x045E, 0x02A1 ),0x0000,0x0000,0x00,0x00,0x50,0x49,0x44,0x56,0x49,0x44);
    1.45 +DEFINE_GUID(IID_X360WirelessGamepad,  MAKELONG( 0x045E, 0x028E ),0x0000,0x0000,0x00,0x00,0x50,0x49,0x44,0x56,0x49,0x44);
    1.46  
    1.47 -DEFINE_GUID(IID_ValveStreamingGamepad,  MAKELONG( 0x28DE, 0x11FF ),0x0000,0x0000,0x00,0x00,0x50,0x49,0x44,0x56,0x49,0x44);
    1.48 +static PRAWINPUTDEVICELIST SDL_RawDevList = NULL;
    1.49 +static UINT SDL_RawDevListCount = 0;
    1.50  
    1.51 -/*-----------------------------------------------------------------------------
    1.52 - *
    1.53 - * code from MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014(v=vs.85).aspx
    1.54 - *
    1.55 - * Enum each PNP device using WMI and check each device ID to see if it contains
    1.56 - * "IG_" (ex. "VID_045E&PID_028E&IG_00").  If it does, then it's an XInput device
    1.57 - * Unfortunately this information can not be found by just using DirectInput
    1.58 - *-----------------------------------------------------------------------------*/
    1.59 -BOOL IsXInputDevice( const GUID* pGuidProductFromDirectInput )
    1.60 +static SDL_bool
    1.61 +SDL_IsXInputDevice( const GUID* pGuidProductFromDirectInput )
    1.62  {
    1.63      static const GUID *s_XInputProductGUID[] = {
    1.64 -        &IID_ValveStreamingGamepad
    1.65 +        &IID_ValveStreamingGamepad,
    1.66 +        &IID_X360WiredGamepad,   /* Microsoft's wired X360 controller for Windows. */
    1.67 +        &IID_X360WirelessGamepad /* Microsoft's wireless X360 controller for Windows. */
    1.68      };
    1.69 -    IWbemLocator*           pIWbemLocator  = NULL;
    1.70 -    IEnumWbemClassObject*   pEnumDevices   = NULL;
    1.71 -    IWbemClassObject*       pDevices[20];
    1.72 -    IWbemServices*          pIWbemServices = NULL;
    1.73 -    DWORD                   uReturned      = 0;
    1.74 -    BSTR                    bstrNamespace  = NULL;
    1.75 -    BSTR                    bstrDeviceID   = NULL;
    1.76 -    BSTR                    bstrClassName  = NULL;
    1.77 -    SDL_bool                bIsXinputDevice= SDL_FALSE;
    1.78 -    UINT                    iDevice        = 0;
    1.79 -    VARIANT                 var;
    1.80 -    HRESULT                 hr;
    1.81 -    DWORD bCleanupCOM;
    1.82  
    1.83 -    if (!s_bXInputEnabled)
    1.84 -    {
    1.85 +    size_t iDevice;
    1.86 +    SDL_bool retval = SDL_FALSE;
    1.87 +    UINT i;
    1.88 +
    1.89 +    if (!s_bXInputEnabled) {
    1.90          return SDL_FALSE;
    1.91      }
    1.92  
    1.93      /* Check for well known XInput device GUIDs */
    1.94 -    /* We need to do this for the Valve Streaming Gamepad because it's virtualized and doesn't show up in the device list. */
    1.95 +    /* 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. */
    1.96      for ( iDevice = 0; iDevice < SDL_arraysize(s_XInputProductGUID); ++iDevice ) {
    1.97          if (SDL_memcmp(pGuidProductFromDirectInput, s_XInputProductGUID[iDevice], sizeof(GUID)) == 0) {
    1.98              return SDL_TRUE;
    1.99          }
   1.100      }
   1.101  
   1.102 -    SDL_memset( pDevices, 0x0, sizeof(pDevices) );
   1.103 +    /* Go through RAWINPUT (WinXP and later) to find HID devices. */
   1.104 +    /* Cache this if we end up using it. */
   1.105 +    if (SDL_RawDevList == NULL) {
   1.106 +        if ((GetRawInputDeviceList(NULL, &SDL_RawDevListCount, sizeof (RAWINPUTDEVICELIST)) == -1) || (!SDL_RawDevListCount)) {
   1.107 +            return SDL_FALSE;  /* oh well. */
   1.108 +        }
   1.109  
   1.110 -    /* CoInit if needed */
   1.111 -    hr = CoInitialize(NULL);
   1.112 -    bCleanupCOM = SUCCEEDED(hr);
   1.113 +        SDL_RawDevList = (PRAWINPUTDEVICELIST) SDL_malloc(sizeof (RAWINPUTDEVICELIST) * SDL_RawDevListCount);
   1.114 +        if (SDL_RawDevList == NULL) {
   1.115 +            SDL_OutOfMemory();
   1.116 +            return SDL_FALSE;
   1.117 +        }
   1.118  
   1.119 -    /* Create WMI */
   1.120 -    hr = CoCreateInstance( &CLSID_WbemLocator,
   1.121 -        NULL,
   1.122 -        CLSCTX_INPROC_SERVER,
   1.123 -        &IID_IWbemLocator,
   1.124 -        (LPVOID*) &pIWbemLocator);
   1.125 -    if( FAILED(hr) || pIWbemLocator == NULL )
   1.126 -        goto LCleanup;
   1.127 -
   1.128 -    bstrNamespace = SysAllocString( L"\\\\.\\root\\cimv2" );if( bstrNamespace == NULL ) goto LCleanup;
   1.129 -    bstrClassName = SysAllocString( L"Win32_PNPEntity" );   if( bstrClassName == NULL ) goto LCleanup;
   1.130 -    bstrDeviceID  = SysAllocString( L"DeviceID" );          if( bstrDeviceID == NULL )  goto LCleanup;
   1.131 -
   1.132 -    /* Connect to WMI */
   1.133 -    hr = IWbemLocator_ConnectServer( pIWbemLocator, bstrNamespace, NULL, NULL, 0L,
   1.134 -        0L, NULL, NULL, &pIWbemServices );
   1.135 -    if( FAILED(hr) || pIWbemServices == NULL )
   1.136 -        goto LCleanup;
   1.137 -
   1.138 -    /* Switch security level to IMPERSONATE. */
   1.139 -    CoSetProxyBlanket( (IUnknown *)pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
   1.140 -        RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE );
   1.141 -
   1.142 -    hr = IWbemServices_CreateInstanceEnum( pIWbemServices, bstrClassName, 0, NULL, &pEnumDevices );
   1.143 -    if( FAILED(hr) || pEnumDevices == NULL )
   1.144 -        goto LCleanup;
   1.145 -
   1.146 -    /* Loop over all devices */
   1.147 -    for( ;; )
   1.148 -    {
   1.149 -        /* Get 20 at a time */
   1.150 -        hr = IEnumWbemClassObject_Next( pEnumDevices, 10000, 20, pDevices, &uReturned );
   1.151 -        if( FAILED(hr) )
   1.152 -            goto LCleanup;
   1.153 -        if( uReturned == 0 )
   1.154 -            break;
   1.155 -
   1.156 -        for( iDevice=0; iDevice<uReturned; iDevice++ )
   1.157 -        {
   1.158 -            /* For each device, get its device ID */
   1.159 -            hr = IWbemClassObject_Get( pDevices[iDevice], bstrDeviceID, 0L, &var, NULL, NULL );
   1.160 -            if(  SUCCEEDED( hr ) && var.vt == VT_BSTR && var.bstrVal != NULL )
   1.161 -            {
   1.162 -                /* Check if the device ID contains "IG_".  If it does, then it's an XInput device */
   1.163 -                /* This information can not be found from DirectInput */
   1.164 -                char *pDeviceString = WIN_StringToUTF8( var.bstrVal );
   1.165 -                if( SDL_strstr( pDeviceString, "IG_" ) )
   1.166 -                {
   1.167 -                    /* If it does, then get the VID/PID from var.bstrVal */
   1.168 -                    long dwPid = 0, dwVid = 0;
   1.169 -                    char * strPid = NULL;
   1.170 -                    DWORD dwVidPid = 0;
   1.171 -                    char * strVid = SDL_strstr( pDeviceString, "VID_" );
   1.172 -                    if( strVid )
   1.173 -                    {
   1.174 -                        dwVid = SDL_strtol( strVid + 4, NULL, 16 );
   1.175 -                    }
   1.176 -                    strPid = SDL_strstr( pDeviceString, "PID_" );
   1.177 -                    if( strPid  )
   1.178 -                    {
   1.179 -                        dwPid = SDL_strtol( strPid + 4, NULL, 16 );
   1.180 -                    }
   1.181 -
   1.182 -                    /* Compare the VID/PID to the DInput device */
   1.183 -                    dwVidPid = MAKELONG( dwVid, dwPid );
   1.184 -                    if( dwVidPid == pGuidProductFromDirectInput->Data1 )
   1.185 -                    {
   1.186 -                        bIsXinputDevice = SDL_TRUE;
   1.187 -                    }
   1.188 -                }
   1.189 -                if ( pDeviceString )
   1.190 -                    SDL_free( pDeviceString );
   1.191 -
   1.192 -                if ( bIsXinputDevice )
   1.193 -                    break;
   1.194 -            }
   1.195 -            SAFE_RELEASE( pDevices[iDevice] );
   1.196 +        if (GetRawInputDeviceList(SDL_RawDevList, &SDL_RawDevListCount, sizeof (RAWINPUTDEVICELIST)) == -1) {
   1.197 +             SDL_free(SDL_RawDevList);
   1.198 +             SDL_RawDevList = NULL;
   1.199 +             return SDL_FALSE;  /* oh well. */
   1.200          }
   1.201      }
   1.202  
   1.203 -LCleanup:
   1.204 +    for (i = 0; i < SDL_RawDevListCount; i++) {
   1.205 +        RID_DEVICE_INFO rdi;
   1.206 +        char devName[128];
   1.207 +        UINT rdiSize = sizeof (rdi);
   1.208 +        UINT nameSize = SDL_arraysize(devName);
   1.209  
   1.210 -    for( iDevice=0; iDevice<20; iDevice++ )
   1.211 -        SAFE_RELEASE( pDevices[iDevice] );
   1.212 -    SAFE_RELEASE( pEnumDevices );
   1.213 -    SAFE_RELEASE( pIWbemLocator );
   1.214 -    SAFE_RELEASE( pIWbemServices );
   1.215 +        rdi.cbSize = sizeof (rdi);
   1.216 +        if ( (SDL_RawDevList[i].dwType == RIM_TYPEHID) &&
   1.217 +             (GetRawInputDeviceInfoA(SDL_RawDevList[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) &&
   1.218 +             (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == ((LONG)pGuidProductFromDirectInput->Data1)) &&
   1.219 +             (GetRawInputDeviceInfoA(SDL_RawDevList[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) &&
   1.220 +             (SDL_strstr(devName, "IG_") != NULL) ) {
   1.221 +             return SDL_TRUE;
   1.222 +        }
   1.223 +    }
   1.224  
   1.225 -    if ( bstrNamespace )
   1.226 -        SysFreeString( bstrNamespace );
   1.227 -    if ( bstrClassName )
   1.228 -        SysFreeString( bstrClassName );
   1.229 -    if ( bstrDeviceID )
   1.230 -        SysFreeString( bstrDeviceID );
   1.231 -
   1.232 -    if( bCleanupCOM )
   1.233 -        CoUninitialize();
   1.234 -
   1.235 -    return bIsXinputDevice;
   1.236 +    return SDL_FALSE;
   1.237  }
   1.238  
   1.239  
   1.240 @@ -808,7 +703,7 @@
   1.241  
   1.242      s_bDeviceAdded = SDL_TRUE;
   1.243  
   1.244 -    bXInputDevice = IsXInputDevice( &pdidInstance->guidProduct );
   1.245 +    bXInputDevice = SDL_IsXInputDevice( &pdidInstance->guidProduct );
   1.246  
   1.247      pNewJoystick = (JoyStick_DeviceData *)SDL_malloc( sizeof(JoyStick_DeviceData) );
   1.248  
   1.249 @@ -872,6 +767,9 @@
   1.250              EnumJoysticksCallback,
   1.251              &pCurList, DIEDFL_ATTACHEDONLY);
   1.252  
   1.253 +        SDL_free(SDL_RawDevList);  /* in case we used this. */
   1.254 +        SDL_RawDevList = NULL;
   1.255 +
   1.256          SDL_UnlockMutex( s_mutexJoyStickEnum );
   1.257      }
   1.258