1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 
22 #include "xinputcheck.h"
23 
24 #include <string.h>
25 #include <stdlib.h>
26 
27 // XInputCheck is an XInput check abstracted away from SDL.
28 //
29 // If you need to update this file, please arrange the code such that
30 // it is possible to copy/paste directly from SDL. This makes code
31 // sharing easier! Right now, the SDL_IsXInputDevice is directly
32 // copied from SDL.
33 //
34 // Below, we have a few preprocessor defines, that allow the SDL functions
35 // we use, to run in our environment.
36 //
37 // After that, there is an implementation of the SDL_XINPUT_Enabled
38 // that always returns true.
39 //
40 // Then comes the implementation of SDL_IsXInputDevice, which is copied
41 // directly from SDL.
42 //
43 // At the bottom of the file, you'll find our wrapper interfaces --
44 // the public API for the XInputCheck library.
45 //
46 // Again, if you ever need to change anything in the SDL functions, well.
47 // Think again! If you find a bug, feel free to fix it, but report it
48 // upstream as well.
49 // If at all possible, try to use wrapper functions to implement "new"
50 // functionality (such as XInputCheck_ClearDeviceCache, for clearing
51 // the internal cache).
52 
53 // Preprocessor helpers for running SDL code as-is.
54 #define SDL_bool                bool
55 #define SDL_TRUE                true
56 #define SDL_FALSE               false
57 #define SDL_free                free
58 #define SDL_malloc              malloc
59 #define SDL_OutOfMemory         abort
60 #define SDL_strstr              strstr
61 #define SDL_memcmp              memcmp
62 #define SDL_arraysize(array)    (sizeof(array)/sizeof(array[0]))
63 
64 
65 static SDL_bool
SDL_XINPUT_Enabled()66 SDL_XINPUT_Enabled() {
67     return SDL_TRUE;
68 }
69 
70 static PRAWINPUTDEVICELIST SDL_RawDevList = NULL;
71 static UINT SDL_RawDevListCount = 0;
72 
73 static SDL_bool
SDL_IsXInputDevice(const GUID * pGuidProductFromDirectInput)74 SDL_IsXInputDevice(const GUID* pGuidProductFromDirectInput)
75 {
76     static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
77     static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
78     static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
79     static GUID IID_XOneWiredGamepad = { MAKELONG(0x045E, 0x02FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
80     static GUID IID_XOneWirelessGamepad = { MAKELONG(0x045E, 0x02DD), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
81     static GUID IID_XOneNewWirelessGamepad = { MAKELONG(0x045E, 0x02D1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
82     static GUID IID_XOneSWirelessGamepad = { MAKELONG(0x045E, 0x02EA), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
83     static GUID IID_XOneSBluetoothGamepad = { MAKELONG(0x045E, 0x02E0), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
84     static GUID IID_XOneEliteWirelessGamepad = { MAKELONG(0x045E, 0x02E3), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
85 
86     static const GUID *s_XInputProductGUID[] = {
87         &IID_ValveStreamingGamepad,
88         &IID_X360WiredGamepad,         /* Microsoft's wired X360 controller for Windows. */
89         &IID_X360WirelessGamepad,      /* Microsoft's wireless X360 controller for Windows. */
90         &IID_XOneWiredGamepad,         /* Microsoft's wired Xbox One controller for Windows. */
91         &IID_XOneWirelessGamepad,      /* Microsoft's wireless Xbox One controller for Windows. */
92         &IID_XOneNewWirelessGamepad,   /* Microsoft's updated wireless Xbox One controller (w/ 3.5 mm jack) for Windows. */
93         &IID_XOneSWirelessGamepad,     /* Microsoft's wireless Xbox One S controller for Windows. */
94         &IID_XOneSBluetoothGamepad,    /* Microsoft's Bluetooth Xbox One S controller for Windows. */
95         &IID_XOneEliteWirelessGamepad  /* Microsoft's wireless Xbox One Elite controller for Windows. */
96     };
97 
98     size_t iDevice;
99     UINT i;
100 
101     if (!SDL_XINPUT_Enabled()) {
102         return SDL_FALSE;
103     }
104 
105     /* Check for well known XInput device GUIDs */
106     /* 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. */
107     for (iDevice = 0; iDevice < SDL_arraysize(s_XInputProductGUID); ++iDevice) {
108         if (SDL_memcmp(pGuidProductFromDirectInput, s_XInputProductGUID[iDevice], sizeof(GUID)) == 0) {
109             return SDL_TRUE;
110         }
111     }
112 
113     /* Go through RAWINPUT (WinXP and later) to find HID devices. */
114     /* Cache this if we end up using it. */
115     if (SDL_RawDevList == NULL) {
116         if ((GetRawInputDeviceList(NULL, &SDL_RawDevListCount, sizeof(RAWINPUTDEVICELIST)) == -1) || (!SDL_RawDevListCount)) {
117             return SDL_FALSE;  /* oh well. */
118         }
119 
120         SDL_RawDevList = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * SDL_RawDevListCount);
121         if (SDL_RawDevList == NULL) {
122             SDL_OutOfMemory();
123             return SDL_FALSE;
124         }
125 
126         if (GetRawInputDeviceList(SDL_RawDevList, &SDL_RawDevListCount, sizeof(RAWINPUTDEVICELIST)) == -1) {
127             SDL_free(SDL_RawDevList);
128             SDL_RawDevList = NULL;
129             return SDL_FALSE;  /* oh well. */
130         }
131     }
132 
133     for (i = 0; i < SDL_RawDevListCount; i++) {
134         RID_DEVICE_INFO rdi;
135         char devName[128];
136         UINT rdiSize = sizeof(rdi);
137         UINT nameSize = SDL_arraysize(devName);
138 
139         rdi.cbSize = sizeof(rdi);
140         if ((SDL_RawDevList[i].dwType == RIM_TYPEHID) &&
141             (GetRawInputDeviceInfoA(SDL_RawDevList[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) &&
142             (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == ((LONG)pGuidProductFromDirectInput->Data1)) &&
143             (GetRawInputDeviceInfoA(SDL_RawDevList[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) &&
144             (SDL_strstr(devName, "IG_") != NULL)) {
145             return SDL_TRUE;
146         }
147     }
148 
149     return SDL_FALSE;
150 }
151 
XInputCheck_IsGuidProductXInputDevice(const GUID * pGuidProductFromDirectInput)152 bool XInputCheck_IsGuidProductXInputDevice(const GUID *pGuidProductFromDirectInput) {
153     return SDL_IsXInputDevice(pGuidProductFromDirectInput);
154 }
155 
XInputCheck_ClearDeviceCache()156 void XInputCheck_ClearDeviceCache() {
157     SDL_free(SDL_RawDevList);
158     SDL_RawDevList = NULL;
159     SDL_RawDevListCount = 0;
160 }
161