2 * The Wine project - Xinput Joystick Library
3 * Copyright 2008 Andrew Fenn
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
24 #include "wine/debug.h"
30 #include "xinput_private.h"
32 /* Not defined in the headers, used only by XInputGetStateEx */
33 #define XINPUT_GAMEPAD_GUIDE 0x0400
35 WINE_DEFAULT_DEBUG_CHANNEL(xinput
);
37 /* xinput_crit guards controllers array */
38 static CRITICAL_SECTION_DEBUG xinput_critsect_debug
=
41 { &xinput_critsect_debug
.ProcessLocksList
, &xinput_critsect_debug
.ProcessLocksList
},
42 0, 0, { (DWORD_PTR
)(__FILE__
": xinput_crit") }
44 CRITICAL_SECTION xinput_crit
= { &xinput_critsect_debug
, -1, 0, 0, 0, 0 };
46 static CRITICAL_SECTION_DEBUG controller_critsect_debug
[XUSER_MAX_COUNT
] =
49 0, 0, &controllers
[0].crit
,
50 { &controller_critsect_debug
[0].ProcessLocksList
, &controller_critsect_debug
[0].ProcessLocksList
},
51 0, 0, { (DWORD_PTR
)(__FILE__
": controllers[0].crit") }
54 0, 0, &controllers
[1].crit
,
55 { &controller_critsect_debug
[1].ProcessLocksList
, &controller_critsect_debug
[1].ProcessLocksList
},
56 0, 0, { (DWORD_PTR
)(__FILE__
": controllers[1].crit") }
59 0, 0, &controllers
[2].crit
,
60 { &controller_critsect_debug
[2].ProcessLocksList
, &controller_critsect_debug
[2].ProcessLocksList
},
61 0, 0, { (DWORD_PTR
)(__FILE__
": controllers[2].crit") }
64 0, 0, &controllers
[3].crit
,
65 { &controller_critsect_debug
[3].ProcessLocksList
, &controller_critsect_debug
[3].ProcessLocksList
},
66 0, 0, { (DWORD_PTR
)(__FILE__
": controllers[3].crit") }
70 xinput_controller controllers
[XUSER_MAX_COUNT
] = {
71 {{ &controller_critsect_debug
[0], -1, 0, 0, 0, 0 }},
72 {{ &controller_critsect_debug
[1], -1, 0, 0, 0, 0 }},
73 {{ &controller_critsect_debug
[2], -1, 0, 0, 0, 0 }},
74 {{ &controller_critsect_debug
[3], -1, 0, 0, 0, 0 }},
77 static BOOL
verify_and_lock_device(xinput_controller
*device
)
79 if (!device
->platform_private
)
82 EnterCriticalSection(&device
->crit
);
84 if (!device
->platform_private
)
86 LeaveCriticalSection(&device
->crit
);
93 static void unlock_device(xinput_controller
*device
)
95 LeaveCriticalSection(&device
->crit
);
98 BOOL WINAPI
DllMain(HINSTANCE inst
, DWORD reason
, LPVOID reserved
)
102 case DLL_PROCESS_ATTACH
:
103 DisableThreadLibraryCalls(inst
);
105 case DLL_PROCESS_DETACH
:
107 HID_destroy_gamepads(controllers
);
113 void WINAPI DECLSPEC_HOTPATCH
XInputEnable(BOOL enable
)
117 TRACE("(enable %d)\n", enable
);
119 /* Setting to false will stop messages from XInputSetState being sent
120 to the controllers. Setting to true will send the last vibration
121 value (sent to XInputSetState) to the controller and allow messages to
123 HID_find_gamepads(controllers
);
125 for (index
= 0; index
< XUSER_MAX_COUNT
; index
++)
127 if (!verify_and_lock_device(&controllers
[index
])) continue;
128 HID_enable(&controllers
[index
], enable
);
129 unlock_device(&controllers
[index
]);
133 DWORD WINAPI DECLSPEC_HOTPATCH
XInputSetState(DWORD index
, XINPUT_VIBRATION
* vibration
)
137 TRACE("(index %u, vibration %p)\n", index
, vibration
);
139 HID_find_gamepads(controllers
);
141 if (index
>= XUSER_MAX_COUNT
)
142 return ERROR_BAD_ARGUMENTS
;
143 if (!verify_and_lock_device(&controllers
[index
]))
144 return ERROR_DEVICE_NOT_CONNECTED
;
146 ret
= HID_set_state(&controllers
[index
], vibration
);
148 unlock_device(&controllers
[index
]);
153 /* Some versions of SteamOverlayRenderer hot-patch XInputGetStateEx() and call
154 * XInputGetState() in the hook, so we need a wrapper. */
155 static DWORD
xinput_get_state(DWORD index
, XINPUT_STATE
*state
)
158 return ERROR_BAD_ARGUMENTS
;
160 HID_find_gamepads(controllers
);
162 if (index
>= XUSER_MAX_COUNT
)
163 return ERROR_BAD_ARGUMENTS
;
164 if (!verify_and_lock_device(&controllers
[index
]))
165 return ERROR_DEVICE_NOT_CONNECTED
;
167 HID_update_state(&controllers
[index
], state
);
169 if (!controllers
[index
].platform_private
)
171 /* update_state may have disconnected the controller */
172 unlock_device(&controllers
[index
]);
173 return ERROR_DEVICE_NOT_CONNECTED
;
176 unlock_device(&controllers
[index
]);
178 return ERROR_SUCCESS
;
182 DWORD WINAPI DECLSPEC_HOTPATCH
XInputGetState(DWORD index
, XINPUT_STATE
* state
)
186 TRACE("(index %u, state %p)!\n", index
, state
);
188 ret
= xinput_get_state(index
, state
);
189 if (ret
!= ERROR_SUCCESS
)
192 /* The main difference between this and the Ex version is the media guide button */
193 state
->Gamepad
.wButtons
&= ~XINPUT_GAMEPAD_GUIDE
;
195 return ERROR_SUCCESS
;
198 DWORD WINAPI DECLSPEC_HOTPATCH
XInputGetStateEx(DWORD index
, XINPUT_STATE
* state
)
200 TRACE("(index %u, state %p)!\n", index
, state
);
202 return xinput_get_state(index
, state
);
205 static const int JS_STATE_OFF
= 0;
206 static const int JS_STATE_LOW
= 1;
207 static const int JS_STATE_HIGH
= 2;
209 static int joystick_state(const SHORT value
)
212 return JS_STATE_HIGH
;
218 static WORD
js_vk_offs(const int x
, const int y
)
220 if (y
== JS_STATE_OFF
)
222 /*if (x == JS_STATE_OFF) shouldn't get here */
223 if (x
== JS_STATE_LOW
) return 3; /* LEFT */
224 /*if (x == JS_STATE_HIGH)*/ return 2; /* RIGHT */
226 if (y
== JS_STATE_HIGH
)
228 if (x
== JS_STATE_OFF
) return 0; /* UP */
229 if (x
== JS_STATE_LOW
) return 4; /* UPLEFT */
230 /*if (x == JS_STATE_HIGH)*/ return 5; /* UPRIGHT */
232 /*if (y == JS_STATE_LOW)*/
234 if (x
== JS_STATE_OFF
) return 1; /* DOWN */
235 if (x
== JS_STATE_LOW
) return 7; /* DOWNLEFT */
236 /*if (x == JS_STATE_HIGH)*/ return 6; /* DOWNRIGHT */
240 static DWORD
check_joystick_keystroke(const DWORD index
, XINPUT_KEYSTROKE
*keystroke
,
241 const SHORT
*cur_x
, const SHORT
*cur_y
, SHORT
*last_x
, SHORT
*last_y
,
244 int cur_vk
= 0, cur_x_st
, cur_y_st
;
245 int last_vk
= 0, last_x_st
, last_y_st
;
247 cur_x_st
= joystick_state(*cur_x
);
248 cur_y_st
= joystick_state(*cur_y
);
249 if (cur_x_st
|| cur_y_st
)
250 cur_vk
= base_vk
+ js_vk_offs(cur_x_st
, cur_y_st
);
252 last_x_st
= joystick_state(*last_x
);
253 last_y_st
= joystick_state(*last_y
);
254 if (last_x_st
|| last_y_st
)
255 last_vk
= base_vk
+ js_vk_offs(last_x_st
, last_y_st
);
257 if (cur_vk
!= last_vk
)
261 /* joystick was set, and now different. send a KEYUP event, and set
262 * last pos to centered, so the appropriate KEYDOWN event will be
263 * sent on the next call. */
264 keystroke
->VirtualKey
= last_vk
;
265 keystroke
->Unicode
= 0; /* unused */
266 keystroke
->Flags
= XINPUT_KEYSTROKE_KEYUP
;
267 keystroke
->UserIndex
= index
;
268 keystroke
->HidCode
= 0;
273 return ERROR_SUCCESS
;
276 /* joystick was unset, send KEYDOWN. */
277 keystroke
->VirtualKey
= cur_vk
;
278 keystroke
->Unicode
= 0; /* unused */
279 keystroke
->Flags
= XINPUT_KEYSTROKE_KEYDOWN
;
280 keystroke
->UserIndex
= index
;
281 keystroke
->HidCode
= 0;
286 return ERROR_SUCCESS
;
295 static BOOL
trigger_is_on(const BYTE value
)
300 static DWORD
check_for_keystroke(const DWORD index
, XINPUT_KEYSTROKE
*keystroke
)
302 xinput_controller
*device
= &controllers
[index
];
303 const XINPUT_GAMEPAD
*cur
;
304 DWORD ret
= ERROR_EMPTY
;
307 static const struct {
311 { XINPUT_GAMEPAD_DPAD_UP
, VK_PAD_DPAD_UP
},
312 { XINPUT_GAMEPAD_DPAD_DOWN
, VK_PAD_DPAD_DOWN
},
313 { XINPUT_GAMEPAD_DPAD_LEFT
, VK_PAD_DPAD_LEFT
},
314 { XINPUT_GAMEPAD_DPAD_RIGHT
, VK_PAD_DPAD_RIGHT
},
315 { XINPUT_GAMEPAD_START
, VK_PAD_START
},
316 { XINPUT_GAMEPAD_BACK
, VK_PAD_BACK
},
317 { XINPUT_GAMEPAD_LEFT_THUMB
, VK_PAD_LTHUMB_PRESS
},
318 { XINPUT_GAMEPAD_RIGHT_THUMB
, VK_PAD_RTHUMB_PRESS
},
319 { XINPUT_GAMEPAD_LEFT_SHOULDER
, VK_PAD_LSHOULDER
},
320 { XINPUT_GAMEPAD_RIGHT_SHOULDER
, VK_PAD_RSHOULDER
},
321 { XINPUT_GAMEPAD_A
, VK_PAD_A
},
322 { XINPUT_GAMEPAD_B
, VK_PAD_B
},
323 { XINPUT_GAMEPAD_X
, VK_PAD_X
},
324 { XINPUT_GAMEPAD_Y
, VK_PAD_Y
},
325 /* note: guide button does not send an event */
328 if (!verify_and_lock_device(device
))
329 return ERROR_DEVICE_NOT_CONNECTED
;
331 cur
= &device
->state
.Gamepad
;
334 for (i
= 0; i
< ARRAY_SIZE(buttons
); ++i
)
336 if ((cur
->wButtons
& buttons
[i
].mask
) ^ (device
->last_keystroke
.wButtons
& buttons
[i
].mask
))
338 keystroke
->VirtualKey
= buttons
[i
].vk
;
339 keystroke
->Unicode
= 0; /* unused */
340 if (cur
->wButtons
& buttons
[i
].mask
)
342 keystroke
->Flags
= XINPUT_KEYSTROKE_KEYDOWN
;
343 device
->last_keystroke
.wButtons
|= buttons
[i
].mask
;
347 keystroke
->Flags
= XINPUT_KEYSTROKE_KEYUP
;
348 device
->last_keystroke
.wButtons
&= ~buttons
[i
].mask
;
350 keystroke
->UserIndex
= index
;
351 keystroke
->HidCode
= 0;
358 if (trigger_is_on(cur
->bLeftTrigger
) ^ trigger_is_on(device
->last_keystroke
.bLeftTrigger
))
360 keystroke
->VirtualKey
= VK_PAD_LTRIGGER
;
361 keystroke
->Unicode
= 0; /* unused */
362 keystroke
->Flags
= trigger_is_on(cur
->bLeftTrigger
) ? XINPUT_KEYSTROKE_KEYDOWN
: XINPUT_KEYSTROKE_KEYUP
;
363 keystroke
->UserIndex
= index
;
364 keystroke
->HidCode
= 0;
365 device
->last_keystroke
.bLeftTrigger
= cur
->bLeftTrigger
;
370 if (trigger_is_on(cur
->bRightTrigger
) ^ trigger_is_on(device
->last_keystroke
.bRightTrigger
))
372 keystroke
->VirtualKey
= VK_PAD_RTRIGGER
;
373 keystroke
->Unicode
= 0; /* unused */
374 keystroke
->Flags
= trigger_is_on(cur
->bRightTrigger
) ? XINPUT_KEYSTROKE_KEYDOWN
: XINPUT_KEYSTROKE_KEYUP
;
375 keystroke
->UserIndex
= index
;
376 keystroke
->HidCode
= 0;
377 device
->last_keystroke
.bRightTrigger
= cur
->bRightTrigger
;
383 ret
= check_joystick_keystroke(index
, keystroke
, &cur
->sThumbLX
, &cur
->sThumbLY
,
384 &device
->last_keystroke
.sThumbLX
,
385 &device
->last_keystroke
.sThumbLY
, VK_PAD_LTHUMB_UP
);
386 if (ret
== ERROR_SUCCESS
)
389 ret
= check_joystick_keystroke(index
, keystroke
, &cur
->sThumbRX
, &cur
->sThumbRY
,
390 &device
->last_keystroke
.sThumbRX
,
391 &device
->last_keystroke
.sThumbRY
, VK_PAD_RTHUMB_UP
);
392 if (ret
== ERROR_SUCCESS
)
396 unlock_device(device
);
401 DWORD WINAPI DECLSPEC_HOTPATCH
XInputGetKeystroke(DWORD index
, DWORD reserved
, PXINPUT_KEYSTROKE keystroke
)
403 TRACE("(index %u, reserved %u, keystroke %p)\n", index
, reserved
, keystroke
);
405 if (index
>= XUSER_MAX_COUNT
&& index
!= XUSER_INDEX_ANY
)
406 return ERROR_BAD_ARGUMENTS
;
408 if (index
== XUSER_INDEX_ANY
)
411 for (i
= 0; i
< XUSER_MAX_COUNT
; ++i
)
412 if (check_for_keystroke(i
, keystroke
) == ERROR_SUCCESS
)
413 return ERROR_SUCCESS
;
417 return check_for_keystroke(index
, keystroke
);
420 DWORD WINAPI DECLSPEC_HOTPATCH
XInputGetCapabilities(DWORD index
, DWORD flags
, XINPUT_CAPABILITIES
* capabilities
)
422 TRACE("(index %u, flags 0x%x, capabilities %p)\n", index
, flags
, capabilities
);
424 HID_find_gamepads(controllers
);
426 if (index
>= XUSER_MAX_COUNT
)
427 return ERROR_BAD_ARGUMENTS
;
429 if (!verify_and_lock_device(&controllers
[index
]))
430 return ERROR_DEVICE_NOT_CONNECTED
;
432 if (flags
& XINPUT_FLAG_GAMEPAD
&& controllers
[index
].caps
.SubType
!= XINPUT_DEVSUBTYPE_GAMEPAD
)
434 unlock_device(&controllers
[index
]);
435 return ERROR_DEVICE_NOT_CONNECTED
;
438 memcpy(capabilities
, &controllers
[index
].caps
, sizeof(*capabilities
));
440 unlock_device(&controllers
[index
]);
442 return ERROR_SUCCESS
;
445 DWORD WINAPI DECLSPEC_HOTPATCH
XInputGetDSoundAudioDeviceGuids(DWORD index
, GUID
* render_guid
, GUID
* capture_guid
)
447 FIXME("(index %u, render guid %p, capture guid %p) Stub!\n", index
, render_guid
, capture_guid
);
449 if (index
>= XUSER_MAX_COUNT
)
450 return ERROR_BAD_ARGUMENTS
;
451 if (!controllers
[index
].platform_private
)
452 return ERROR_DEVICE_NOT_CONNECTED
;
454 return ERROR_NOT_SUPPORTED
;
457 DWORD WINAPI DECLSPEC_HOTPATCH
XInputGetBatteryInformation(DWORD index
, BYTE type
, XINPUT_BATTERY_INFORMATION
* battery
)
462 FIXME("(index %u, type %u, battery %p) Stub!\n", index
, type
, battery
);
464 if (index
>= XUSER_MAX_COUNT
)
465 return ERROR_BAD_ARGUMENTS
;
466 if (!controllers
[index
].platform_private
)
467 return ERROR_DEVICE_NOT_CONNECTED
;
469 return ERROR_NOT_SUPPORTED
;