wine-wine/dlls/xinput1_3/xinput_main.c

471 lines
14 KiB
C

/*
* The Wine project - Xinput Joystick Library
* Copyright 2008 Andrew Fenn
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <assert.h>
#include <stdarg.h>
#include <string.h>
#include "wine/debug.h"
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "xinput.h"
#include "xinput_private.h"
/* Not defined in the headers, used only by XInputGetStateEx */
#define XINPUT_GAMEPAD_GUIDE 0x0400
WINE_DEFAULT_DEBUG_CHANNEL(xinput);
/* xinput_crit guards controllers array */
static CRITICAL_SECTION_DEBUG xinput_critsect_debug =
{
0, 0, &xinput_crit,
{ &xinput_critsect_debug.ProcessLocksList, &xinput_critsect_debug.ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": xinput_crit") }
};
CRITICAL_SECTION xinput_crit = { &xinput_critsect_debug, -1, 0, 0, 0, 0 };
static CRITICAL_SECTION_DEBUG controller_critsect_debug[XUSER_MAX_COUNT] =
{
{
0, 0, &controllers[0].crit,
{ &controller_critsect_debug[0].ProcessLocksList, &controller_critsect_debug[0].ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": controllers[0].crit") }
},
{
0, 0, &controllers[1].crit,
{ &controller_critsect_debug[1].ProcessLocksList, &controller_critsect_debug[1].ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": controllers[1].crit") }
},
{
0, 0, &controllers[2].crit,
{ &controller_critsect_debug[2].ProcessLocksList, &controller_critsect_debug[2].ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": controllers[2].crit") }
},
{
0, 0, &controllers[3].crit,
{ &controller_critsect_debug[3].ProcessLocksList, &controller_critsect_debug[3].ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": controllers[3].crit") }
},
};
xinput_controller controllers[XUSER_MAX_COUNT] = {
{{ &controller_critsect_debug[0], -1, 0, 0, 0, 0 }},
{{ &controller_critsect_debug[1], -1, 0, 0, 0, 0 }},
{{ &controller_critsect_debug[2], -1, 0, 0, 0, 0 }},
{{ &controller_critsect_debug[3], -1, 0, 0, 0, 0 }},
};
static BOOL verify_and_lock_device(xinput_controller *device)
{
if (!device->platform_private)
return FALSE;
EnterCriticalSection(&device->crit);
if (!device->platform_private)
{
LeaveCriticalSection(&device->crit);
return FALSE;
}
return TRUE;
}
static void unlock_device(xinput_controller *device)
{
LeaveCriticalSection(&device->crit);
}
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
{
switch(reason)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(inst);
break;
case DLL_PROCESS_DETACH:
if (reserved) break;
HID_destroy_gamepads(controllers);
break;
}
return TRUE;
}
void WINAPI DECLSPEC_HOTPATCH XInputEnable(BOOL enable)
{
int index;
TRACE("(enable %d)\n", enable);
/* Setting to false will stop messages from XInputSetState being sent
to the controllers. Setting to true will send the last vibration
value (sent to XInputSetState) to the controller and allow messages to
be sent */
HID_find_gamepads(controllers);
for (index = 0; index < XUSER_MAX_COUNT; index ++)
{
if (!verify_and_lock_device(&controllers[index])) continue;
HID_enable(&controllers[index], enable);
unlock_device(&controllers[index]);
}
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputSetState(DWORD index, XINPUT_VIBRATION* vibration)
{
DWORD ret;
TRACE("(index %u, vibration %p)\n", index, vibration);
HID_find_gamepads(controllers);
if (index >= XUSER_MAX_COUNT)
return ERROR_BAD_ARGUMENTS;
if (!verify_and_lock_device(&controllers[index]))
return ERROR_DEVICE_NOT_CONNECTED;
ret = HID_set_state(&controllers[index], vibration);
unlock_device(&controllers[index]);
return ret;
}
/* Some versions of SteamOverlayRenderer hot-patch XInputGetStateEx() and call
* XInputGetState() in the hook, so we need a wrapper. */
static DWORD xinput_get_state(DWORD index, XINPUT_STATE *state)
{
if (!state)
return ERROR_BAD_ARGUMENTS;
HID_find_gamepads(controllers);
if (index >= XUSER_MAX_COUNT)
return ERROR_BAD_ARGUMENTS;
if (!verify_and_lock_device(&controllers[index]))
return ERROR_DEVICE_NOT_CONNECTED;
HID_update_state(&controllers[index], state);
if (!controllers[index].platform_private)
{
/* update_state may have disconnected the controller */
unlock_device(&controllers[index]);
return ERROR_DEVICE_NOT_CONNECTED;
}
unlock_device(&controllers[index]);
return ERROR_SUCCESS;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetState(DWORD index, XINPUT_STATE* state)
{
DWORD ret;
TRACE("(index %u, state %p)!\n", index, state);
ret = xinput_get_state(index, state);
if (ret != ERROR_SUCCESS)
return ret;
/* The main difference between this and the Ex version is the media guide button */
state->Gamepad.wButtons &= ~XINPUT_GAMEPAD_GUIDE;
return ERROR_SUCCESS;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetStateEx(DWORD index, XINPUT_STATE* state)
{
TRACE("(index %u, state %p)!\n", index, state);
return xinput_get_state(index, state);
}
static const int JS_STATE_OFF = 0;
static const int JS_STATE_LOW = 1;
static const int JS_STATE_HIGH = 2;
static int joystick_state(const SHORT value)
{
if (value > 20000)
return JS_STATE_HIGH;
if (value < -20000)
return JS_STATE_LOW;
return JS_STATE_OFF;
}
static WORD js_vk_offs(const int x, const int y)
{
if (y == JS_STATE_OFF)
{
/*if (x == JS_STATE_OFF) shouldn't get here */
if (x == JS_STATE_LOW) return 3; /* LEFT */
/*if (x == JS_STATE_HIGH)*/ return 2; /* RIGHT */
}
if (y == JS_STATE_HIGH)
{
if (x == JS_STATE_OFF) return 0; /* UP */
if (x == JS_STATE_LOW) return 4; /* UPLEFT */
/*if (x == JS_STATE_HIGH)*/ return 5; /* UPRIGHT */
}
/*if (y == JS_STATE_LOW)*/
{
if (x == JS_STATE_OFF) return 1; /* DOWN */
if (x == JS_STATE_LOW) return 7; /* DOWNLEFT */
/*if (x == JS_STATE_HIGH)*/ return 6; /* DOWNRIGHT */
}
}
static DWORD check_joystick_keystroke(const DWORD index, XINPUT_KEYSTROKE *keystroke,
const SHORT *cur_x, const SHORT *cur_y, SHORT *last_x, SHORT *last_y,
const WORD base_vk)
{
int cur_vk = 0, cur_x_st, cur_y_st;
int last_vk = 0, last_x_st, last_y_st;
cur_x_st = joystick_state(*cur_x);
cur_y_st = joystick_state(*cur_y);
if (cur_x_st || cur_y_st)
cur_vk = base_vk + js_vk_offs(cur_x_st, cur_y_st);
last_x_st = joystick_state(*last_x);
last_y_st = joystick_state(*last_y);
if (last_x_st || last_y_st)
last_vk = base_vk + js_vk_offs(last_x_st, last_y_st);
if (cur_vk != last_vk)
{
if (last_vk)
{
/* joystick was set, and now different. send a KEYUP event, and set
* last pos to centered, so the appropriate KEYDOWN event will be
* sent on the next call. */
keystroke->VirtualKey = last_vk;
keystroke->Unicode = 0; /* unused */
keystroke->Flags = XINPUT_KEYSTROKE_KEYUP;
keystroke->UserIndex = index;
keystroke->HidCode = 0;
*last_x = 0;
*last_y = 0;
return ERROR_SUCCESS;
}
/* joystick was unset, send KEYDOWN. */
keystroke->VirtualKey = cur_vk;
keystroke->Unicode = 0; /* unused */
keystroke->Flags = XINPUT_KEYSTROKE_KEYDOWN;
keystroke->UserIndex = index;
keystroke->HidCode = 0;
*last_x = *cur_x;
*last_y = *cur_y;
return ERROR_SUCCESS;
}
*last_x = *cur_x;
*last_y = *cur_y;
return ERROR_EMPTY;
}
static BOOL trigger_is_on(const BYTE value)
{
return value > 30;
}
static DWORD check_for_keystroke(const DWORD index, XINPUT_KEYSTROKE *keystroke)
{
xinput_controller *device = &controllers[index];
const XINPUT_GAMEPAD *cur;
DWORD ret = ERROR_EMPTY;
int i;
static const struct {
int mask;
WORD vk;
} buttons[] = {
{ XINPUT_GAMEPAD_DPAD_UP, VK_PAD_DPAD_UP },
{ XINPUT_GAMEPAD_DPAD_DOWN, VK_PAD_DPAD_DOWN },
{ XINPUT_GAMEPAD_DPAD_LEFT, VK_PAD_DPAD_LEFT },
{ XINPUT_GAMEPAD_DPAD_RIGHT, VK_PAD_DPAD_RIGHT },
{ XINPUT_GAMEPAD_START, VK_PAD_START },
{ XINPUT_GAMEPAD_BACK, VK_PAD_BACK },
{ XINPUT_GAMEPAD_LEFT_THUMB, VK_PAD_LTHUMB_PRESS },
{ XINPUT_GAMEPAD_RIGHT_THUMB, VK_PAD_RTHUMB_PRESS },
{ XINPUT_GAMEPAD_LEFT_SHOULDER, VK_PAD_LSHOULDER },
{ XINPUT_GAMEPAD_RIGHT_SHOULDER, VK_PAD_RSHOULDER },
{ XINPUT_GAMEPAD_A, VK_PAD_A },
{ XINPUT_GAMEPAD_B, VK_PAD_B },
{ XINPUT_GAMEPAD_X, VK_PAD_X },
{ XINPUT_GAMEPAD_Y, VK_PAD_Y },
/* note: guide button does not send an event */
};
if (!verify_and_lock_device(device))
return ERROR_DEVICE_NOT_CONNECTED;
cur = &device->state.Gamepad;
/*** buttons ***/
for (i = 0; i < ARRAY_SIZE(buttons); ++i)
{
if ((cur->wButtons & buttons[i].mask) ^ (device->last_keystroke.wButtons & buttons[i].mask))
{
keystroke->VirtualKey = buttons[i].vk;
keystroke->Unicode = 0; /* unused */
if (cur->wButtons & buttons[i].mask)
{
keystroke->Flags = XINPUT_KEYSTROKE_KEYDOWN;
device->last_keystroke.wButtons |= buttons[i].mask;
}
else
{
keystroke->Flags = XINPUT_KEYSTROKE_KEYUP;
device->last_keystroke.wButtons &= ~buttons[i].mask;
}
keystroke->UserIndex = index;
keystroke->HidCode = 0;
ret = ERROR_SUCCESS;
goto done;
}
}
/*** triggers ***/
if (trigger_is_on(cur->bLeftTrigger) ^ trigger_is_on(device->last_keystroke.bLeftTrigger))
{
keystroke->VirtualKey = VK_PAD_LTRIGGER;
keystroke->Unicode = 0; /* unused */
keystroke->Flags = trigger_is_on(cur->bLeftTrigger) ? XINPUT_KEYSTROKE_KEYDOWN : XINPUT_KEYSTROKE_KEYUP;
keystroke->UserIndex = index;
keystroke->HidCode = 0;
device->last_keystroke.bLeftTrigger = cur->bLeftTrigger;
ret = ERROR_SUCCESS;
goto done;
}
if (trigger_is_on(cur->bRightTrigger) ^ trigger_is_on(device->last_keystroke.bRightTrigger))
{
keystroke->VirtualKey = VK_PAD_RTRIGGER;
keystroke->Unicode = 0; /* unused */
keystroke->Flags = trigger_is_on(cur->bRightTrigger) ? XINPUT_KEYSTROKE_KEYDOWN : XINPUT_KEYSTROKE_KEYUP;
keystroke->UserIndex = index;
keystroke->HidCode = 0;
device->last_keystroke.bRightTrigger = cur->bRightTrigger;
ret = ERROR_SUCCESS;
goto done;
}
/*** joysticks ***/
ret = check_joystick_keystroke(index, keystroke, &cur->sThumbLX, &cur->sThumbLY,
&device->last_keystroke.sThumbLX,
&device->last_keystroke.sThumbLY, VK_PAD_LTHUMB_UP);
if (ret == ERROR_SUCCESS)
goto done;
ret = check_joystick_keystroke(index, keystroke, &cur->sThumbRX, &cur->sThumbRY,
&device->last_keystroke.sThumbRX,
&device->last_keystroke.sThumbRY, VK_PAD_RTHUMB_UP);
if (ret == ERROR_SUCCESS)
goto done;
done:
unlock_device(device);
return ret;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetKeystroke(DWORD index, DWORD reserved, PXINPUT_KEYSTROKE keystroke)
{
TRACE("(index %u, reserved %u, keystroke %p)\n", index, reserved, keystroke);
if (index >= XUSER_MAX_COUNT && index != XUSER_INDEX_ANY)
return ERROR_BAD_ARGUMENTS;
if (index == XUSER_INDEX_ANY)
{
int i;
for (i = 0; i < XUSER_MAX_COUNT; ++i)
if (check_for_keystroke(i, keystroke) == ERROR_SUCCESS)
return ERROR_SUCCESS;
return ERROR_EMPTY;
}
return check_for_keystroke(index, keystroke);
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetCapabilities(DWORD index, DWORD flags, XINPUT_CAPABILITIES* capabilities)
{
TRACE("(index %u, flags 0x%x, capabilities %p)\n", index, flags, capabilities);
HID_find_gamepads(controllers);
if (index >= XUSER_MAX_COUNT)
return ERROR_BAD_ARGUMENTS;
if (!verify_and_lock_device(&controllers[index]))
return ERROR_DEVICE_NOT_CONNECTED;
if (flags & XINPUT_FLAG_GAMEPAD && controllers[index].caps.SubType != XINPUT_DEVSUBTYPE_GAMEPAD)
{
unlock_device(&controllers[index]);
return ERROR_DEVICE_NOT_CONNECTED;
}
memcpy(capabilities, &controllers[index].caps, sizeof(*capabilities));
unlock_device(&controllers[index]);
return ERROR_SUCCESS;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetDSoundAudioDeviceGuids(DWORD index, GUID* render_guid, GUID* capture_guid)
{
FIXME("(index %u, render guid %p, capture guid %p) Stub!\n", index, render_guid, capture_guid);
if (index >= XUSER_MAX_COUNT)
return ERROR_BAD_ARGUMENTS;
if (!controllers[index].platform_private)
return ERROR_DEVICE_NOT_CONNECTED;
return ERROR_NOT_SUPPORTED;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetBatteryInformation(DWORD index, BYTE type, XINPUT_BATTERY_INFORMATION* battery)
{
static int once;
if (!once++)
FIXME("(index %u, type %u, battery %p) Stub!\n", index, type, battery);
if (index >= XUSER_MAX_COUNT)
return ERROR_BAD_ARGUMENTS;
if (!controllers[index].platform_private)
return ERROR_DEVICE_NOT_CONNECTED;
return ERROR_NOT_SUPPORTED;
}