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
;
68 CRITICAL_SECTION crit
;
76 LONG LeftTriggerRange
[3];
79 LONG RightTriggerRange
[3];
82 static DWORD last_check
= 0;
84 static void MarkUsage(struct hid_platform_private
*private, WORD usage
, LONG min
, LONG max
, USHORT bits
)
88 case HID_USAGE_GENERIC_X
:
89 private->ThumbLXRange
[0] = min
;
90 private->ThumbLXRange
[1] = bits
;
91 private->ThumbLXRange
[2] = max
- min
;
93 case HID_USAGE_GENERIC_Y
:
94 private->ThumbLYRange
[0] = min
;
95 private->ThumbLYRange
[1] = bits
;
96 private->ThumbLYRange
[2] = max
- min
;
98 case HID_USAGE_GENERIC_Z
:
99 private->LeftTriggerRange
[0] = min
;
100 private->LeftTriggerRange
[1] = bits
;
101 private->LeftTriggerRange
[2] = max
- min
;
103 case HID_USAGE_GENERIC_RX
:
104 private->ThumbRXRange
[0] = min
;
105 private->ThumbRXRange
[1] = bits
;
106 private->ThumbRXRange
[2] = max
- min
;
108 case HID_USAGE_GENERIC_RY
:
109 private->ThumbRYRange
[0] = min
;
110 private->ThumbRYRange
[1] = bits
;
111 private->ThumbRYRange
[2] = max
- min
;
113 case HID_USAGE_GENERIC_RZ
:
114 private->RightTriggerRange
[0] = min
;
115 private->RightTriggerRange
[1] = bits
;
116 private->RightTriggerRange
[2] = max
- min
;
121 static BOOL
VerifyGamepad(PHIDP_PREPARSED_DATA ppd
, XINPUT_CAPABILITIES
*xinput_caps
, struct hid_platform_private
*private, HIDP_CAPS
*caps
)
123 HIDP_BUTTON_CAPS
*button_caps
;
124 HIDP_VALUE_CAPS
*value_caps
;
127 int button_count
= 0;
128 USHORT button_caps_count
= 0;
129 USHORT value_caps_count
= 0;
132 memset(xinput_caps
, 0, sizeof(XINPUT_CAPABILITIES
));
134 button_caps_count
= caps
->NumberInputButtonCaps
;
135 button_caps
= HeapAlloc(GetProcessHeap(), 0, sizeof(*button_caps
) * button_caps_count
);
136 HidP_GetButtonCaps(HidP_Input
, button_caps
, &button_caps_count
, ppd
);
137 for (i
= 0; i
< button_caps_count
; i
++)
139 if (button_caps
[i
].UsagePage
!= HID_USAGE_PAGE_BUTTON
)
141 if (button_caps
[i
].IsRange
)
142 button_count
= max(button_count
, button_caps
[i
].Range
.UsageMax
);
144 button_count
= max(button_count
, button_caps
[i
].NotRange
.Usage
);
146 HeapFree(GetProcessHeap(), 0, button_caps
);
147 if (button_count
< 14)
148 WARN("Too few buttons, Continue\n");
149 xinput_caps
->Gamepad
.wButtons
= 0xffff;
151 value_caps_count
= caps
->NumberInputValueCaps
;
152 value_caps
= HeapAlloc(GetProcessHeap(), 0, sizeof(*value_caps
) * value_caps_count
);
153 HidP_GetValueCaps(HidP_Input
, value_caps
, &value_caps_count
, ppd
);
154 for (i
= 0; i
< value_caps_count
; i
++)
156 if (value_caps
[i
].UsagePage
!= HID_USAGE_PAGE_GENERIC
)
158 if (value_caps
[i
].IsRange
)
161 for (u
= value_caps
[i
].Range
.UsageMin
; u
<=value_caps
[i
].Range
.UsageMax
; u
++)
162 MarkUsage(private, u
, value_caps
[i
].LogicalMin
, value_caps
[i
].LogicalMax
, value_caps
[i
].BitSize
);
165 MarkUsage(private, value_caps
[i
].NotRange
.Usage
, value_caps
[i
].LogicalMin
, value_caps
[i
].LogicalMax
, value_caps
[i
].BitSize
);
167 HeapFree(GetProcessHeap(), 0, value_caps
);
169 if (private->LeftTriggerRange
[1])
170 xinput_caps
->Gamepad
.bLeftTrigger
= (1u << (sizeof(xinput_caps
->Gamepad
.bLeftTrigger
) + 1)) - 1;
172 WARN("Missing axis LeftTrigger\n");
173 if (private->RightTriggerRange
[1])
174 xinput_caps
->Gamepad
.bRightTrigger
= (1u << (sizeof(xinput_caps
->Gamepad
.bRightTrigger
) + 1)) - 1;
176 WARN("Missing axis RightTrigger\n");
177 if (private->ThumbLXRange
[1])
178 xinput_caps
->Gamepad
.sThumbLX
= (1u << (sizeof(xinput_caps
->Gamepad
.sThumbLX
) + 1)) - 1;
180 WARN("Missing axis ThumbLX\n");
181 if (private->ThumbLYRange
[1])
182 xinput_caps
->Gamepad
.sThumbLY
= (1u << (sizeof(xinput_caps
->Gamepad
.sThumbLY
) + 1)) - 1;
184 WARN("Missing axis ThumbLY\n");
185 if (private->ThumbRXRange
[1])
186 xinput_caps
->Gamepad
.sThumbRX
= (1u << (sizeof(xinput_caps
->Gamepad
.sThumbRX
) + 1)) - 1;
188 WARN("Missing axis ThumbRX\n");
189 if (private->ThumbRYRange
[1])
190 xinput_caps
->Gamepad
.sThumbRY
= (1u << (sizeof(xinput_caps
->Gamepad
.sThumbRY
) + 1)) - 1;
192 WARN("Missing axis ThumbRY\n");
194 xinput_caps
->Type
= XINPUT_DEVTYPE_GAMEPAD
;
195 xinput_caps
->SubType
= XINPUT_DEVSUBTYPE_GAMEPAD
;
197 value_caps_count
= caps
->NumberOutputValueCaps
;
198 if (value_caps_count
> 0)
199 xinput_caps
->Flags
|= XINPUT_CAPS_FFB_SUPPORTED
;
204 static void build_private(struct hid_platform_private
*private, PHIDP_PREPARSED_DATA ppd
, HIDP_CAPS
*caps
, HANDLE device
, WCHAR
*path
)
208 private->device
= device
;
209 private->report_length
= caps
->InputReportByteLength
+ 1;
210 private->current_report
= 0;
211 private->reports
[0] = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, private->report_length
);
212 private->reports
[1] = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, private->report_length
);
213 size
= (strlenW(path
) + 1) * sizeof(WCHAR
);
214 private->device_path
= HeapAlloc(GetProcessHeap(), 0, size
);
215 memcpy(private->device_path
, path
, size
);
216 private->enabled
= TRUE
;
218 InitializeCriticalSection(&private->crit
);
219 private->crit
.DebugInfo
->Spare
[0] = (DWORD_PTR
)(__FILE__
": JoystickImpl*->generic.base.crit");
222 void HID_find_gamepads(xinput_controller
*devices
)
224 HDEVINFO device_info_set
;
226 SP_DEVICE_INTERFACE_DATA interface_data
;
227 SP_DEVICE_INTERFACE_DETAIL_DATA_W
*data
;
228 PHIDP_PREPARSED_DATA ppd
;
229 DWORD detail_size
= MAX_PATH
* sizeof(WCHAR
);
230 HANDLE device
= INVALID_HANDLE_VALUE
;
235 idx
= GetTickCount();
236 if ((idx
- last_check
) < 2000)
240 HidD_GetHidGuid(&hid_guid
);
242 EnterCriticalSection(&hid_xinput_crit
);
244 device_info_set
= SetupDiGetClassDevsW(&hid_guid
, NULL
, NULL
, DIGCF_DEVICEINTERFACE
);
246 data
= HeapAlloc(GetProcessHeap(), 0 , sizeof(*data
) + detail_size
);
247 data
->cbSize
= sizeof(*data
);
249 ZeroMemory(&interface_data
, sizeof(interface_data
));
250 interface_data
.cbSize
= sizeof(interface_data
);
253 while (SetupDiEnumDeviceInterfaces(device_info_set
, NULL
, &hid_guid
, idx
++,
254 &interface_data
) && didx
< XUSER_MAX_COUNT
)
256 static const WCHAR ig
[] = {'I','G','_',0};
257 if (!SetupDiGetDeviceInterfaceDetailW(device_info_set
,
258 &interface_data
, data
, sizeof(*data
) + detail_size
, NULL
, NULL
))
261 if (!strstrW(data
->DevicePath
, ig
))
264 for (i
= 0; i
< XUSER_MAX_COUNT
; i
++)
266 struct hid_platform_private
*private = devices
[i
].platform_private
;
267 if (devices
[i
].connected
&& !strcmpW(data
->DevicePath
, private->device_path
))
270 if (i
!= XUSER_MAX_COUNT
)
272 device
= CreateFileW(data
->DevicePath
, GENERIC_READ
|GENERIC_WRITE
, FILE_SHARE_READ
|FILE_SHARE_WRITE
, NULL
, OPEN_EXISTING
, 0, 0 );
273 if (device
== INVALID_HANDLE_VALUE
)
276 HidD_GetPreparsedData(device
, &ppd
);
277 HidP_GetCaps(ppd
, &Caps
);
278 if (Caps
.UsagePage
== HID_USAGE_PAGE_GENERIC
&&
279 (Caps
.Usage
== HID_USAGE_GENERIC_GAMEPAD
||
280 Caps
.Usage
== HID_USAGE_GENERIC_JOYSTICK
||
281 Caps
.Usage
== 0x8 /* Multi-axis Controller */))
283 struct hid_platform_private
*private = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(struct hid_platform_private
));
284 if (VerifyGamepad(ppd
, &(devices
[didx
].caps
), private, &Caps
))
286 TRACE("Found gamepad %i %s\n",didx
, debugstr_w(data
->DevicePath
));
287 devices
[didx
].connected
= TRUE
;
288 build_private(private, ppd
, &Caps
, device
, data
->DevicePath
);
289 devices
[didx
].platform_private
= private;
295 HidD_FreePreparsedData(ppd
);
296 HeapFree(GetProcessHeap(), 0, private);
302 HidD_FreePreparsedData(ppd
);
304 device
= INVALID_HANDLE_VALUE
;
306 HeapFree(GetProcessHeap(), 0, data
);
307 SetupDiDestroyDeviceInfoList(device_info_set
);
308 LeaveCriticalSection(&hid_xinput_crit
);
312 static void remove_gamepad(xinput_controller
*device
)
314 if (device
->connected
)
316 struct hid_platform_private
*private = device
->platform_private
;
318 EnterCriticalSection(&private->crit
);
319 CloseHandle(private->device
);
320 HeapFree(GetProcessHeap(), 0, private->reports
[0]);
321 HeapFree(GetProcessHeap(), 0, private->reports
[1]);
322 HeapFree(GetProcessHeap(), 0, private->device_path
);
323 HidD_FreePreparsedData(private->ppd
);
324 device
->platform_private
= NULL
;
325 device
->connected
= FALSE
;
326 LeaveCriticalSection(&private->crit
);
327 DeleteCriticalSection(&private->crit
);
328 HeapFree(GetProcessHeap(), 0, private);
332 void HID_destroy_gamepads(xinput_controller
*devices
)
335 EnterCriticalSection(&hid_xinput_crit
);
336 for (i
= 0; i
< XUSER_MAX_COUNT
; i
++)
337 remove_gamepad(&devices
[i
]);
338 LeaveCriticalSection(&hid_xinput_crit
);
341 #define SIGN(v,b) ((b==8)?(BYTE)v:(b==16)?(SHORT)v:(INT)v)
342 #define SCALE_SHORT(v,r) (SHORT)((((0xffff)*(SIGN(v,r[1]) - r[0]))/r[2])-32767)
343 #define SCALE_BYTE(v,r) (BYTE)((((0xff)*(SIGN(v,r[1]) - r[0]))/r[2]))
345 void HID_update_state(xinput_controller
* device
)
347 struct hid_platform_private
*private = device
->platform_private
;
349 CHAR
*report
= private->reports
[(private->current_report
)%2];
350 CHAR
*target_report
= private->reports
[(private->current_report
+1)%2];
356 if (!private->enabled
)
359 EnterCriticalSection(&private->crit
);
360 if (!HidD_GetInputReport(private->device
, target_report
, private->report_length
))
362 if (GetLastError() == ERROR_ACCESS_DENIED
|| GetLastError() == ERROR_INVALID_HANDLE
)
363 remove_gamepad(device
);
365 ERR("Failed to get Input Report (%x)\n", GetLastError());
366 LeaveCriticalSection(&private->crit
);
369 if (memcmp(report
, target_report
, private->report_length
) == 0)
371 LeaveCriticalSection(&private->crit
);
375 private->current_report
= (private->current_report
+1)%2;
377 device
->state
.dwPacketNumber
++;
379 HidP_GetUsages(HidP_Input
, HID_USAGE_PAGE_BUTTON
, 0, buttons
, &button_length
, private->ppd
, target_report
, private->report_length
);
381 device
->state
.Gamepad
.wButtons
= 0;
382 for (i
= 0; i
< button_length
; i
++)
386 case 1: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_A
; break;
387 case 2: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_B
; break;
388 case 3: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_X
; break;
389 case 4: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_Y
; break;
390 case 5: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_LEFT_SHOULDER
; break;
391 case 6: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_RIGHT_SHOULDER
; break;
392 case 7: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_LEFT_THUMB
; break;
393 case 8: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_RIGHT_THUMB
; break;
395 case 9: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_START
; break;
396 case 10: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_BACK
; break;
397 case 11: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_GUIDE
; break;
398 case 12: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_DPAD_UP
; break;
399 case 13: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_DPAD_DOWN
; break;
400 case 14: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_DPAD_LEFT
; break;
401 case 15: device
->state
.Gamepad
.wButtons
|= XINPUT_GAMEPAD_DPAD_RIGHT
; break;
405 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_X
, &value
, private->ppd
, target_report
, private->report_length
);
406 device
->state
.Gamepad
.sThumbLX
= SCALE_SHORT(value
, private->ThumbLXRange
);
408 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_Y
, &value
, private->ppd
, target_report
, private->report_length
);
409 device
->state
.Gamepad
.sThumbLY
= -SCALE_SHORT(value
, private->ThumbLYRange
);
411 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_RX
, &value
, private->ppd
, target_report
, private->report_length
);
412 device
->state
.Gamepad
.sThumbRX
= SCALE_SHORT(value
, private->ThumbRXRange
);
414 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_RY
, &value
, private->ppd
, target_report
, private->report_length
);
415 device
->state
.Gamepad
.sThumbRY
= -SCALE_SHORT(value
, private->ThumbRYRange
);
417 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_RZ
, &value
, private->ppd
, target_report
, private->report_length
);
418 device
->state
.Gamepad
.bRightTrigger
= SCALE_BYTE(value
, private->RightTriggerRange
);
420 HidP_GetUsageValue(HidP_Input
, HID_USAGE_PAGE_GENERIC
, 0, HID_USAGE_GENERIC_Z
, &value
, private->ppd
, target_report
, private->report_length
);
421 device
->state
.Gamepad
.bLeftTrigger
= SCALE_BYTE(value
, private->LeftTriggerRange
);
422 LeaveCriticalSection(&private->crit
);
425 DWORD
HID_set_state(xinput_controller
* device
, XINPUT_VIBRATION
* state
)
427 struct hid_platform_private
*private = device
->platform_private
;
437 if (device
->caps
.Flags
& XINPUT_CAPS_FFB_SUPPORTED
)
439 device
->caps
.Vibration
.wLeftMotorSpeed
= state
->wLeftMotorSpeed
;
440 device
->caps
.Vibration
.wRightMotorSpeed
= state
->wRightMotorSpeed
;
442 if (private->enabled
)
447 report
.pad1
[0] = 0x8;
448 report
.pad1
[1] = 0x0;
449 report
.left
= (BYTE
)(state
->wLeftMotorSpeed
/ 255);
450 report
.right
= (BYTE
)(state
->wRightMotorSpeed
/ 255);
451 memset(&report
.pad2
, 0, sizeof(report
.pad2
));
453 EnterCriticalSection(&private->crit
);
454 rc
= HidD_SetOutputReport(private->device
, &report
, sizeof(report
));
455 LeaveCriticalSection(&private->crit
);
457 return ERROR_SUCCESS
;
458 return GetLastError();
460 return ERROR_SUCCESS
;
463 return ERROR_NOT_SUPPORTED
;
466 void HID_enable(xinput_controller
* device
, BOOL enable
)
468 struct hid_platform_private
*private = device
->platform_private
;
470 if (device
->caps
.Flags
& XINPUT_CAPS_FFB_SUPPORTED
)
472 EnterCriticalSection(&private->crit
);
473 if (private->enabled
&& !enable
)
475 XINPUT_VIBRATION state
;
476 state
.wLeftMotorSpeed
= 0;
477 state
.wRightMotorSpeed
= 0;
478 HID_set_state(device
, &state
);
480 else if (!private->enabled
&& enable
)
482 HID_set_state(device
, &device
->caps
.Vibration
);
484 LeaveCriticalSection(&private->crit
);
487 private->enabled
= enable
;