2 * The Wine project - Xinput Joystick HID interface
3 * Copyright 2018 Aric Stewart
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include "wine/port.h"
27 #include "wine/debug.h"
40 #include "ddk/hidsdi.h"
45 #include "xinput_private.h"
47 #include "wine/unicode.h"
49 WINE_DEFAULT_DEBUG_CHANNEL(xinput
);
51 #define XINPUT_GAMEPAD_GUIDE 0x0400
53 static CRITICAL_SECTION hid_xinput_crit
;
54 static CRITICAL_SECTION_DEBUG hid_critsect_debug
=
56 0, 0, &hid_xinput_crit
,
57 { &hid_critsect_debug
.ProcessLocksList
, &hid_critsect_debug
.ProcessLocksList
},
58 0, 0, { (DWORD_PTR
)(__FILE__
": hid_xinput_crit") }
60 static CRITICAL_SECTION hid_xinput_crit
= { &hid_critsect_debug
, -1, 0, 0, 0, 0 };
62 struct hid_platform_private
{
63 PHIDP_PREPARSED_DATA ppd
;
67 CRITICAL_SECTION crit
;
75 LONG LeftTriggerRange
[3];
78 LONG RightTriggerRange
[3];
81 static DWORD last_check
= 0;
83 static void MarkUsage(struct hid_platform_private
*private, WORD usage
, LONG min
, LONG max
, USHORT bits
)
87 case HID_USAGE_GENERIC_X
:
88 private->ThumbLXRange
[0] = min
;
89 private->ThumbLXRange
[1] = bits
;
90 private->ThumbLXRange
[2] = max
- min
;
92 case HID_USAGE_GENERIC_Y
:
93 private->ThumbLYRange
[0] = min
;
94 private->ThumbLYRange
[1] = bits
;
95 private->ThumbLYRange
[2] = max
- min
;
97 case HID_USAGE_GENERIC_Z
:
98 private->LeftTriggerRange
[0] = min
;
99 private->LeftTriggerRange
[1] = bits
;
100 private->LeftTriggerRange
[2] = max
- min
;
102 case HID_USAGE_GENERIC_RX
:
103 private->ThumbRXRange
[0] = min
;
104 private->ThumbRXRange
[1] = bits
;
105 private->ThumbRXRange
[2] = max
- min
;
107 case HID_USAGE_GENERIC_RY
:
108 private->ThumbRYRange
[0] = min
;
109 private->ThumbRYRange
[1] = bits
;
110 private->ThumbRYRange
[2] = max
- min
;
112 case HID_USAGE_GENERIC_RZ
:
113 private->RightTriggerRange
[0] = min
;
114 private->RightTriggerRange
[1] = bits
;
115 private->RightTriggerRange
[2] = max
- min
;
120 static BOOL
VerifyGamepad(PHIDP_PREPARSED_DATA ppd
, XINPUT_CAPABILITIES
*xinput_caps
, struct hid_platform_private
*private, HIDP_CAPS
*caps
)
122 HIDP_BUTTON_CAPS
*button_caps
;
123 HIDP_VALUE_CAPS
*value_caps
;
126 int button_count
= 0;
127 USHORT button_caps_count
= 0;
128 USHORT value_caps_count
= 0;
131 memset(xinput_caps
, 0, sizeof(XINPUT_CAPABILITIES
));
133 button_caps_count
= caps
->NumberInputButtonCaps
;
134 button_caps
= HeapAlloc(GetProcessHeap(), 0, sizeof(*button_caps
) * button_caps_count
);
135 HidP_GetButtonCaps(HidP_Input
, button_caps
, &button_caps_count
, ppd
);
136 for (i
= 0; i
< button_caps_count
; i
++)
138 if (button_caps
[i
].UsagePage
!= HID_USAGE_PAGE_BUTTON
)
140 if (button_caps
[i
].IsRange
)
141 button_count
= max(button_count
, button_caps
[i
].Range
.UsageMax
);
143 button_count
= max(button_count
, button_caps
[i
].NotRange
.Usage
);
145 HeapFree(GetProcessHeap(), 0, button_caps
);
146 if (button_count
< 14)
147 WARN("Too few buttons, Continue\n");
148 xinput_caps
->Gamepad
.wButtons
= 0xffff;
150 value_caps_count
= caps
->NumberInputValueCaps
;
151 value_caps
= HeapAlloc(GetProcessHeap(), 0, sizeof(*value_caps
) * value_caps_count
);
152 HidP_GetValueCaps(HidP_Input
, value_caps
, &value_caps_count
, ppd
);
153 for (i
= 0; i
< value_caps_count
; i
++)
155 if (value_caps
[i
].UsagePage
!= HID_USAGE_PAGE_GENERIC
)
157 if (value_caps
[i
].IsRange
)
160 for (u
= value_caps
[i
].Range
.UsageMin
; u
<=value_caps
[i
].Range
.UsageMax
; u
++)
161 MarkUsage(private, u
, value_caps
[i
].LogicalMin
, value_caps
[i
].LogicalMax
, value_caps
[i
].BitSize
);
164 MarkUsage(private, value_caps
[i
].NotRange
.Usage
, value_caps
[i
].LogicalMin
, value_caps
[i
].LogicalMax
, value_caps
[i
].BitSize
);
166 HeapFree(GetProcessHeap(), 0, value_caps
);
168 if (private->LeftTriggerRange
[1])
169 xinput_caps
->Gamepad
.bLeftTrigger
= (1u << (sizeof(xinput_caps
->Gamepad
.bLeftTrigger
) + 1)) - 1;
171 WARN("Missing axis LeftTrigger\n");
172 if (private->RightTriggerRange
[1])
173 xinput_caps
->Gamepad
.bRightTrigger
= (1u << (sizeof(xinput_caps
->Gamepad
.bRightTrigger
) + 1)) - 1;
175 WARN("Missing axis RightTrigger\n");
176 if (private->ThumbLXRange
[1])
177 xinput_caps
->Gamepad
.sThumbLX
= (1u << (sizeof(xinput_caps
->Gamepad
.sThumbLX
) + 1)) - 1;
179 WARN("Missing axis ThumbLX\n");
180 if (private->ThumbLYRange
[1])
181 xinput_caps
->Gamepad
.sThumbLY
= (1u << (sizeof(xinput_caps
->Gamepad
.sThumbLY
) + 1)) - 1;
183 WARN("Missing axis ThumbLY\n");
184 if (private->ThumbRXRange
[1])
185 xinput_caps
->Gamepad
.sThumbRX
= (1u << (sizeof(xinput_caps
->Gamepad
.sThumbRX
) + 1)) - 1;
187 WARN("Missing axis ThumbRX\n");
188 if (private->ThumbRYRange
[1])
189 xinput_caps
->Gamepad
.sThumbRY
= (1u << (sizeof(xinput_caps
->Gamepad
.sThumbRY
) + 1)) - 1;
191 WARN("Missing axis ThumbRY\n");
193 xinput_caps
->Type
= XINPUT_DEVTYPE_GAMEPAD
;
194 xinput_caps
->SubType
= XINPUT_DEVSUBTYPE_GAMEPAD
;
196 value_caps_count
= caps
->NumberOutputValueCaps
;
197 if (value_caps_count
> 0)
198 xinput_caps
->Flags
|= XINPUT_CAPS_FFB_SUPPORTED
;
203 static void build_private(struct hid_platform_private
*private, PHIDP_PREPARSED_DATA ppd
, HIDP_CAPS
*caps
, HANDLE device
, WCHAR
*path
)
207 private->device
= device
;
208 private->report_length
= caps
->InputReportByteLength
+ 1;
209 private->current_report
= 0;
210 private->reports
[0] = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, private->report_length
);
211 private->reports
[1] = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, private->report_length
);
212 size
= (strlenW(path
) + 1) * sizeof(WCHAR
);
213 private->device_path
= HeapAlloc(GetProcessHeap(), 0, size
);
214 memcpy(private->device_path
, path
, size
);
216 InitializeCriticalSection(&private->crit
);
217 private->crit
.DebugInfo
->Spare
[0] = (DWORD_PTR
)(__FILE__
": JoystickImpl*->generic.base.crit");
220 void HID_find_gamepads(xinput_controller
*devices
)
222 HDEVINFO device_info_set
;
224 SP_DEVICE_INTERFACE_DATA interface_data
;
225 SP_DEVICE_INTERFACE_DETAIL_DATA_W
*data
;
226 PHIDP_PREPARSED_DATA ppd
;
227 DWORD detail_size
= MAX_PATH
* sizeof(WCHAR
);
228 HANDLE device
= INVALID_HANDLE_VALUE
;
233 idx
= GetTickCount();
234 if ((idx
- last_check
) < 2000)
238 HidD_GetHidGuid(&hid_guid
);
240 EnterCriticalSection(&hid_xinput_crit
);
242 device_info_set
= SetupDiGetClassDevsW(&hid_guid
, NULL
, NULL
, DIGCF_DEVICEINTERFACE
);
244 data
= HeapAlloc(GetProcessHeap(), 0 , sizeof(*data
) + detail_size
);
245 data
->cbSize
= sizeof(*data
);
247 ZeroMemory(&interface_data
, sizeof(interface_data
));
248 interface_data
.cbSize
= sizeof(interface_data
);
251 while (SetupDiEnumDeviceInterfaces(device_info_set
, NULL
, &hid_guid
, idx
++,
252 &interface_data
) && didx
< XUSER_MAX_COUNT
)
254 static const WCHAR ig
[] = {'I','G','_',0};
255 if (!SetupDiGetDeviceInterfaceDetailW(device_info_set
,
256 &interface_data
, data
, sizeof(*data
) + detail_size
, NULL
, NULL
))
259 if (!strstrW(data
->DevicePath
, ig
))
262 for (i
= 0; i
< XUSER_MAX_COUNT
; i
++)
264 struct hid_platform_private
*private = devices
[i
].platform_private
;
265 if (devices
[i
].connected
&& !strcmpW(data
->DevicePath
, private->device_path
))
268 if (i
!= XUSER_MAX_COUNT
)
270 device
= CreateFileW(data
->DevicePath
, GENERIC_READ
|GENERIC_WRITE
, FILE_SHARE_READ
|FILE_SHARE_WRITE
, NULL
, OPEN_EXISTING
, 0, 0 );
271 if (device
== INVALID_HANDLE_VALUE
)
274 HidD_GetPreparsedData(device
, &ppd
);
275 HidP_GetCaps(ppd
, &Caps
);
276 if (Caps
.UsagePage
== HID_USAGE_PAGE_GENERIC
&&
277 (Caps
.Usage
== HID_USAGE_GENERIC_GAMEPAD
||
278 Caps
.Usage
== HID_USAGE_GENERIC_JOYSTICK
||
279 Caps
.Usage
== 0x8 /* Multi-axis Controller */))
281 struct hid_platform_private
*private = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(struct hid_platform_private
));
282 if (VerifyGamepad(ppd
, &(devices
[didx
].caps
), private, &Caps
))
284 TRACE("Found gamepad %i %s\n",didx
, debugstr_w(data
->DevicePath
));
285 devices
[didx
].connected
= TRUE
;
286 build_private(private, ppd
, &Caps
, device
, data
->DevicePath
);
287 devices
[didx
].platform_private
= private;
293 HidD_FreePreparsedData(ppd
);
294 HeapFree(GetProcessHeap(), 0, private);
300 HidD_FreePreparsedData(ppd
);
302 device
= INVALID_HANDLE_VALUE
;
304 HeapFree(GetProcessHeap(), 0, data
);
305 SetupDiDestroyDeviceInfoList(device_info_set
);
306 LeaveCriticalSection(&hid_xinput_crit
);
310 static void remove_gamepad(xinput_controller
*device
)
312 if (device
->connected
)
314 struct hid_platform_private
*private = device
->platform_private
;
316 EnterCriticalSection(&private->crit
);
317 CloseHandle(private->device
);
318 HeapFree(GetProcessHeap(), 0, private->reports
[0]);
319 HeapFree(GetProcessHeap(), 0, private->reports
[1]);
320 HeapFree(GetProcessHeap(), 0, private->device_path
);
321 HidD_FreePreparsedData(private->ppd
);
322 device
->platform_private
= NULL
;
323 device
->connected
= FALSE
;
324 LeaveCriticalSection(&private->crit
);
325 DeleteCriticalSection(&private->crit
);
326 HeapFree(GetProcessHeap(), 0, private);
330 void HID_destroy_gamepads(xinput_controller
*devices
)
333 EnterCriticalSection(&hid_xinput_crit
);
334 for (i
= 0; i
< XUSER_MAX_COUNT
; i
++)
335 remove_gamepad(&devices
[i
]);
336 LeaveCriticalSection(&hid_xinput_crit
);
339 #define SIGN(v,b) ((b==8)?(BYTE)v:(b==16)?(SHORT)v:(INT)v)
340 #define SCALE_SHORT(v,r) (SHORT)((((0xffff)*(SIGN(v,r[1]) - r[0]))/r[2])-32767)
341 #define SCALE_BYTE(v,r) (BYTE)((((0xff)*(SIGN(v,r[1]) - r[0]))/r[2]))
343 void HID_update_state(xinput_controller
* device
)
345 struct hid_platform_private
*private = device
->platform_private
;
347 CHAR
*report
= private->reports
[(private->current_report
)%2];
348 CHAR
*target_report
= private->reports
[(private->current_report
+1)%2];
354 EnterCriticalSection(&private->crit
);
355 if (!HidD_GetInputReport(private->device
, target_report
, private->report_length
))
357 if (GetLastError() == ERROR_ACCESS_DENIED
|| GetLastError() == ERROR_INVALID_HANDLE
)
358 remove_gamepad(device
);
360 ERR("Failed to get Input Report (%x)\n", GetLastError());
361 LeaveCriticalSection(&private->crit
);
364 if (memcmp(report
, target_report
, private->report_length
) == 0)
366 LeaveCriticalSection(&private->crit
);
370 private->current_report
= (private->current_report
+1)%2;
372 device
->state
.dwPacketNumber
++;
374 HidP_GetUsages(HidP_Input
, HID_USAGE_PAGE_BUTTON
, 0, buttons
, &button_length
, private->ppd
, target_report
, private->report_length
);
376 device
->state
.Gamepad
.wButtons
= 0;
377 for (i
= 0; i
< button_length
; i
++)
381 case 1: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_A
; break;
382 case 2: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_B
; break;
383 case 3: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_X
; break;
384 case 4: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_Y
; break;
385 case 5: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_LEFT_SHOULDER
; break;
386 case 6: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_RIGHT_SHOULDER
; break;
387 case 7: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_LEFT_THUMB
; break;
388 case 8: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_RIGHT_THUMB
; break;
390 case 9: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_START
; break;
391 case 10: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_BACK
; break;
392 case 11: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_GUIDE
; break;
393 case 12: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_DPAD_UP
; break;
394 case 13: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_DPAD_DOWN
; break;
395 case 14: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_DPAD_LEFT
; break;
396 case 15: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_DPAD_RIGHT
; break;
400 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_X
, &value
, private->ppd
, target_report
, private->report_length
);
401 device
->state
.Gamepad
.sThumbLX
= SCALE_SHORT(value
, private->ThumbLXRange
);
403 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_Y
, &value
, private->ppd
, target_report
, private->report_length
);
404 device
->state
.Gamepad
.sThumbLY
= -SCALE_SHORT(value
, private->ThumbLYRange
);
406 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_RX
, &value
, private->ppd
, target_report
, private->report_length
);
407 device
->state
.Gamepad
.sThumbRX
= SCALE_SHORT(value
, private->ThumbRXRange
);
409 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_RY
, &value
, private->ppd
, target_report
, private->report_length
);
410 device
->state
.Gamepad
.sThumbRY
= -SCALE_SHORT(value
, private->ThumbRYRange
);
412 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_RZ
, &value
, private->ppd
, target_report
, private->report_length
);
413 device
->state
.Gamepad
.bRightTrigger
= SCALE_BYTE(value
, private->RightTriggerRange
);
415 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_Z
, &value
, private->ppd
, target_report
, private->report_length
);
416 device
->state
.Gamepad
.bLeftTrigger
= SCALE_BYTE(value
, private->LeftTriggerRange
);
417 LeaveCriticalSection(&private->crit
);
420 DWORD
HID_set_state(xinput_controller
* device
, XINPUT_VIBRATION
* state
)
422 struct hid_platform_private
*private = device
->platform_private
;
432 if (device
->caps
.Flags
& XINPUT_CAPS_FFB_SUPPORTED
)
436 device
->caps
.Vibration
.wLeftMotorSpeed
= state
->wLeftMotorSpeed
;
437 device
->caps
.Vibration
.wRightMotorSpeed
= state
->wRightMotorSpeed
;
440 report
.pad1
[0] = 0x8;
441 report
.pad1
[1] = 0x0;
442 report
.left
= (BYTE
)(state
->wLeftMotorSpeed
/ 255);
443 report
.right
= (BYTE
)(state
->wRightMotorSpeed
/ 255);
444 memset(&report
.pad2
, 0, sizeof(report
.pad2
));
446 EnterCriticalSection(&private->crit
);
447 rc
= HidD_SetOutputReport(private->device
, &report
, sizeof(report
));
448 LeaveCriticalSection(&private->crit
);
450 return ERROR_SUCCESS
;
451 return GetLastError();
454 return ERROR_NOT_SUPPORTED
;