[v2 1/3] xinput: Implemented core.
Juan Jose Gonzalez
juanj.gh at gmail.com
Fri Feb 26 14:23:43 CST 2016
From: Juan Gonzalez <juanj.gh at gmail.com>
Adds a core implementation for XInput which can be used with different
backends.
[v2]
xinput: Small fixes
xinput: Refactored/improved VirtualKey code
Signed-off-by: Juan Jose Gonzalez <juanj.gh at gmail.com>
---
dlls/xinput1_3/Makefile.in | 7 +-
dlls/xinput1_3/xinput1_3_main.c | 708 +++++++++++++++++++++++++++++++++--
dlls/xinput1_3/xinput_backend.h | 188 ++++++++++
dlls/xinput1_3/xinput_core.h | 159 ++++++++
dlls/xinput1_3/xinput_core_vk.c | 435 +++++++++++++++++++++
dlls/xinput1_3/xinput_core_vkqueue.c | 70 ++++
dlls/xinput1_3/xinput_util.c | 182 +++++++++
dlls/xinput1_3/xinput_util.h | 71 ++++
8 files changed, 1785 insertions(+), 35 deletions(-)
create mode 100644 dlls/xinput1_3/xinput_backend.h
create mode 100644 dlls/xinput1_3/xinput_core.h
create mode 100644 dlls/xinput1_3/xinput_core_vk.c
create mode 100644 dlls/xinput1_3/xinput_core_vkqueue.c
create mode 100644 dlls/xinput1_3/xinput_util.c
create mode 100644 dlls/xinput1_3/xinput_util.h
diff --git a/dlls/xinput1_3/Makefile.in b/dlls/xinput1_3/Makefile.in
index cf8f730..86b5053 100644
--- a/dlls/xinput1_3/Makefile.in
+++ b/dlls/xinput1_3/Makefile.in
@@ -1,7 +1,12 @@
MODULE = xinput1_3.dll
IMPORTLIB = xinput
+IMPORTS = uuid advapi32
C_SRCS = \
- xinput1_3_main.c
+ xinput_util.c \
+ xinput_core_vk.c \
+ xinput_core_vkqueue.c \
+ xinput1_3_main.c \
+
RC_SRCS = version.rc
diff --git a/dlls/xinput1_3/xinput1_3_main.c b/dlls/xinput1_3/xinput1_3_main.c
index 63f725b..4ffe2be 100644
--- a/dlls/xinput1_3/xinput1_3_main.c
+++ b/dlls/xinput1_3/xinput1_3_main.c
@@ -1,6 +1,8 @@
/*
- * The Wine project - Xinput Joystick Library
+ * The Wine project - XInput Joystick Library
+ *
* Copyright 2008 Andrew Fenn
+ * Copyright 2016 Juan Jose Gonzalez
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -18,91 +20,683 @@
*/
#include "config.h"
-#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_backend.h"
+#include "xinput_util.h"
+#include "xinput_core.h"
+
WINE_DEFAULT_DEBUG_CHANNEL(xinput);
+
+typedef struct _XINPUTW_SLOT {
+ DWORD slot_index;
+
+ XINPUT_STATE state;
+ BOOL state_has_changes;
+ XINPUT_VIBRATION rumble_state;
+
+ XINPUTW_VK_STATES vkStates;
+ XINPUTW_KEYSTROKE_QUEUE keystrokes;
+ INT16 battery_level;
+ XINPUTW_DEV_CAPABILITIES capabilities;
+
+ const XINPUTW_BACKEND *backend;
+
+ CRITICAL_SECTION cs_device;
+ CRITICAL_SECTION cs_status;
+} XINPUTW_SLOT;
+
+/*
+ * NULL-Terminated array of pointers to backends.
+ */
+const XINPUTW_BACKEND *xinput_backends[] = {
+ NULL
+};
+
+/* The epsilon / deadzone to consider a button pressed. */
+#define DEFAULT_VAL_TO_BTN_EPSILON (XINPUTW_VAL_MAX / 4)
+static XINPUTW_VALUE val_to_btn_epsilon;
+
+static BOOL is_core_initialized;
+static CRITICAL_SECTION cs_core_initalization;
+static BOOL is_xinput_enabled;
+static XINPUTW_SLOT slots[XUSER_MAX_COUNT];
+
+
+/* Invert the value of an axis while avoiding overflows */
+static XINPUTW_VALUE InvertXIWValue(XINPUTW_VALUE value) {
+ TRACE("value: %d\n", value);
+ if (value == XINPUTW_VAL_MIN) return XINPUTW_VAL_MAX;
+ if (value == XINPUTW_VAL_MAX) return XINPUTW_VAL_MIN;
+ return -value;
+}
+
+static void slot_close(DWORD slot) {
+ if (slots[slot].backend == NULL) return;
+
+ EnterCriticalSection(&slots[slot].cs_device);
+
+ (slots[slot].backend->DisconnectDevice)(slot);
+ slots[slot].backend = NULL;
+
+ memset((void *)&slots[slot].state, 0, sizeof(slots[slot].state));
+ slots[slot].state_has_changes = FALSE;
+ memset((void *)&slots[slot].rumble_state, 0, sizeof(slots[slot].rumble_state));
+ memset((void *)&slots[slot].vkStates, 0, sizeof(slots[slot].vkStates));
+ xiw_vk_KeystrokeQueueClear(&slots[slot].keystrokes);
+ slots[slot].battery_level = -1;
+ memset((void *)&slots[slot].capabilities, 0, sizeof(slots[slot].capabilities));
+
+ LeaveCriticalSection(&slots[slot].cs_device);
+}
+
+static void ParseAxisEvent(DWORD slot, const XINPUTW_EVENT *event) {
+ XINPUTW_VALUE target_value;
+ BYTE *trigger_val;
+ SHORT *thumb_val;
+
+ TRACE("slot %d, input code %d, value %d\n", slot, event->code, event->value);
+
+ trigger_val = NULL;
+ thumb_val = NULL;
+
+ switch (event->code) {
+ case WINE_AXIS_LTRIGGER:
+ trigger_val = &slots[slot].state.Gamepad.bLeftTrigger;
+ break;
+ case WINE_AXIS_RTRIGGER:
+ trigger_val = &slots[slot].state.Gamepad.bRightTrigger;
+ break;
+ case WINE_AXIS_LTHUMB_X:
+ thumb_val = &slots[slot].state.Gamepad.sThumbLX;
+ break;
+ case WINE_AXIS_LTHUMB_Y:
+ thumb_val = &slots[slot].state.Gamepad.sThumbLY;
+ break;
+ case WINE_AXIS_RTHUMB_X:
+ thumb_val = &slots[slot].state.Gamepad.sThumbRX;
+ break;
+ case WINE_AXIS_RTHUMB_Y:
+ thumb_val = &slots[slot].state.Gamepad.sThumbRY;
+ break;
+ default:
+ WARN("invalid code %d\n", event->code);
+ return;
+ }
+
+ if (trigger_val != NULL) {
+ target_value = xiw_util_ConvFromXIWValue(
+ event->value_map.axis == AXIS_MAP_INVERTED ? InvertXIWValue(event->value) : event->value,
+ 0, 255);
+ if(*trigger_val != (BYTE)target_value) {
+ /* Update the state for XInputGetState */
+ *trigger_val = (BYTE)target_value;
+ slots[slot].state_has_changes = TRUE;
+
+ /* Post a new event for XInputGetKeystroke if necessary */
+ xiw_vk_Update(slot, event->timestamp, event->code, &slots[slot].state, &slots[slot].vkStates, &slots[slot].keystrokes);
+ }
+ } else {
+ target_value = xiw_util_ConvFromXIWValue(
+ event->value_map.axis == AXIS_MAP_INVERTED ? InvertXIWValue(event->value) : event->value,
+ -32768, 32767);
+ if(*thumb_val != (WORD)target_value) {
+ /* Update the state for XInputGetState */
+ *thumb_val = (WORD)target_value;
+ slots[slot].state_has_changes = TRUE;
+
+ /* Post a new event for XInputGetKeystroke if necessary */
+ xiw_vk_Update(slot, event->timestamp, event->code, &slots[slot].state, &slots[slot].vkStates, &slots[slot].keystrokes);
+ }
+ }
+}
+
+static void ParseBtnEvent(DWORD slot, const XINPUTW_EVENT *event) {
+ WORD bit_mask;
+
+ BOOL btn_is_pressed;
+ WORD masked_value;
+
+ TRACE("slot %d, input code %d, value %d\n", slot, event->code, event->value);
+
+ switch (event->code) {
+ case WINE_BTN_A:
+ bit_mask = XINPUT_GAMEPAD_A;
+ break;
+ case WINE_BTN_B:
+ bit_mask = XINPUT_GAMEPAD_B;
+ break;
+ case WINE_BTN_X:
+ bit_mask = XINPUT_GAMEPAD_X;
+ break;
+ case WINE_BTN_Y:
+ bit_mask = XINPUT_GAMEPAD_Y;
+ break;
+ case WINE_BTN_START:
+ bit_mask = XINPUT_GAMEPAD_START;
+ break;
+ case WINE_BTN_BACK:
+ bit_mask = XINPUT_GAMEPAD_BACK;
+ break;
+ case WINE_BTN_LSHOULDER:
+ bit_mask = XINPUT_GAMEPAD_LEFT_SHOULDER;
+ break;
+ case WINE_BTN_RSHOULDER:
+ bit_mask = XINPUT_GAMEPAD_RIGHT_SHOULDER;
+ break;
+ case WINE_BTN_LTHUMB:
+ bit_mask = XINPUT_GAMEPAD_LEFT_THUMB;
+ break;
+ case WINE_BTN_RTHUMB:
+ bit_mask = XINPUT_GAMEPAD_RIGHT_THUMB;
+ break;
+ case WINE_BTN_DPAD_UP:
+ bit_mask = XINPUT_GAMEPAD_DPAD_UP;
+ break;
+ case WINE_BTN_DPAD_DOWN:
+ bit_mask = XINPUT_GAMEPAD_DPAD_DOWN;
+ break;
+ case WINE_BTN_DPAD_LEFT:
+ bit_mask = XINPUT_GAMEPAD_DPAD_LEFT;
+ break;
+ case WINE_BTN_DPAD_RIGHT:
+ bit_mask = XINPUT_GAMEPAD_DPAD_RIGHT;
+ break;
+ default:
+ WARN("invalid xinput_code %d\n", event->code);
+ return;
+ }
+
+ switch (event->value_map.button) {
+ case VAL_TO_BTN_LT_ZERO:
+ btn_is_pressed = event->value < -val_to_btn_epsilon;
+ break;
+ case VAL_TO_BTN_LE_ZERO:
+ btn_is_pressed = event->value <= val_to_btn_epsilon;
+ break;
+ case VAL_TO_BTN_ZERO:
+ btn_is_pressed = event->value >= -val_to_btn_epsilon && event->value <= val_to_btn_epsilon;
+ break;
+ case VAL_TO_BTN_GT_ZERO:
+ btn_is_pressed = event->value > val_to_btn_epsilon;
+ break;
+ case VAL_TO_BTN_GE_ZERO:
+ btn_is_pressed = event->value >= -val_to_btn_epsilon;
+ break;
+ default:
+ WARN("invalid button map %d\n", event->value_map.button);
+ return;
+ }
+
+ masked_value = (btn_is_pressed ? ~((WORD)0) : 0) & bit_mask;
+ if ((slots[slot].state.Gamepad.wButtons & bit_mask) != masked_value) {
+ /* Update the state for XInputGetState */
+ slots[slot].state.Gamepad.wButtons = (slots[slot].state.Gamepad.wButtons & ~bit_mask) | masked_value;
+ slots[slot].state_has_changes = TRUE;
+
+ /* Post a new event for XInputGetKeystroke if necessary */
+ xiw_vk_Update(slot, event->timestamp, event->code, &slots[slot].state, &slots[slot].vkStates, &slots[slot].keystrokes);
+ }
+}
+
+static BOOL TryConnectDevice(DWORD target_slot_index) {
+ const XINPUTW_BACKEND **backend;
+
+ TRACE("slot %hu\n", target_slot_index);
+
+ EnterCriticalSection(&slots[target_slot_index].cs_device);
+
+ if (slots[target_slot_index].backend != NULL) {
+ LeaveCriticalSection(&slots[target_slot_index].cs_device);
+ return TRUE;
+ }
+
+ for (backend = xinput_backends; (*backend) != NULL; ++backend) {
+ if ((*backend)->Initialize == NULL) {
+ TRACE("skipping disabled backend %s\n", (*backend)->Name);
+ continue;
+ }
+
+ if ((*backend)->TryConnectDevice(target_slot_index, &slots[target_slot_index].capabilities)) {
+ TRACE("successfully connected slot %d from backend %s\n", target_slot_index, (*backend)->Name);
+ slots[target_slot_index].backend = *backend;
+
+ LeaveCriticalSection(&slots[target_slot_index].cs_device);
+ return TRUE;
+ }
+ TRACE("could not connect slot %d from backend %s\n", target_slot_index, (*backend)->Name);
+ }
+
+ LeaveCriticalSection(&slots[target_slot_index].cs_device);
+ return FALSE;
+}
+
+static WORD GetCapabilitiesBtnFlag(XINPUTW_EVENT_CODE code) {
+ switch (code) {
+ case WINE_BTN_A:
+ return XINPUT_GAMEPAD_A;
+ case WINE_BTN_B:
+ return XINPUT_GAMEPAD_B;
+ case WINE_BTN_Y:
+ return XINPUT_GAMEPAD_Y;
+ case WINE_BTN_X:
+ return XINPUT_GAMEPAD_X;
+ case WINE_BTN_START:
+ return XINPUT_GAMEPAD_START;
+ case WINE_BTN_BACK:
+ return XINPUT_GAMEPAD_BACK;
+ case WINE_BTN_LSHOULDER:
+ return XINPUT_GAMEPAD_LEFT_THUMB;
+ case WINE_BTN_RSHOULDER:
+ return XINPUT_GAMEPAD_RIGHT_THUMB;
+ case WINE_BTN_LTHUMB:
+ return XINPUT_GAMEPAD_LEFT_SHOULDER;
+ case WINE_BTN_RTHUMB:
+ return XINPUT_GAMEPAD_RIGHT_SHOULDER;
+ case WINE_BTN_DPAD_UP:
+ return XINPUT_GAMEPAD_DPAD_UP;
+ case WINE_BTN_DPAD_DOWN:
+ return XINPUT_GAMEPAD_DPAD_DOWN;
+ case WINE_BTN_DPAD_LEFT:
+ return XINPUT_GAMEPAD_DPAD_LEFT;
+ case WINE_BTN_DPAD_RIGHT:
+ return XINPUT_GAMEPAD_DPAD_RIGHT;
+ default:
+ return 0;
+ }
+}
+
+static WORD GetResolutionBitmap(BYTE dev_max_bits, BYTE target_byte_size) {
+ WORD max_bits, result;
+
+ max_bits = (WORD)target_byte_size * 8;
+ if (dev_max_bits < max_bits)
+ max_bits = (WORD)dev_max_bits;
+
+ result = (WORD)((1ul << (max_bits + 1)) - 1);
+ TRACE("dev_max_bits %uh, target_byte_size %uh\n", dev_max_bits, target_byte_size);
+
+ return result;
+}
+
+static void EnsureInitialized(void) {
+ unsigned int i;
+ const XINPUTW_BACKEND **backend;
+ HKEY defkey, appkey;
+
+ EnterCriticalSection(&cs_core_initalization);
+ if (!is_core_initialized) {
+ TRACE("initializing core\n");
+
+ is_xinput_enabled = TRUE;
+
+ xiw_util_OpenCfgKeys(&defkey, &appkey, NULL);
+ val_to_btn_epsilon = (XINPUTW_VALUE) xiw_util_GetCfgValueDW(defkey, appkey,
+ "ValueToButtonEpsilon", DEFAULT_VAL_TO_BTN_EPSILON);
+
+ for (i = 0; i < XUSER_MAX_COUNT; i++) {
+ memset((void *)&slots[i], 0, sizeof(slots[i]));
+
+ InitializeCriticalSection(&slots[i].cs_device);
+ InitializeCriticalSection(&slots[i].cs_status);
+ }
+
+ for (backend = xinput_backends; *backend != NULL; ++backend) {
+ if ((*backend)->Initialize == NULL) {
+ TRACE("skipping disabled backend %s\n", (*backend)->Name);
+ continue;
+ }
+
+ TRACE("initializing backend %s\n", (*backend)->Name);
+ (*(*backend)->Initialize)();
+ }
+
+ is_core_initialized = TRUE;
+ }
+ LeaveCriticalSection(&cs_core_initalization);
+}
+
+/*
+ * Notify xinput_core of a change in the gamepad state
+ * slot_index: The index of the slot, as passed when the core calls get_new_device.
+ * event: The event being pushed
+ *
+ * This function can be called asynchronously whenever an event occurs.
+ */
+void xiw_core_PushEvent(DWORD slot_index, const XINPUTW_EVENT *event) {
+ TRACE("slot %hu\n", slot_index);
+
+ EnsureInitialized();
+
+ if (slot_index > XUSER_MAX_COUNT)
+ return;
+
+ EnterCriticalSection(&slots[slot_index].cs_status);
+
+ if (event->code >= WINE_BTN_MIN && event->code <= WINE_BTN_MAX)
+ ParseBtnEvent(slot_index, event);
+ else if (event->code >= WINE_AXIS_MIN && event->code <= WINE_AXIS_MAX)
+ ParseAxisEvent(slot_index, event);
+ else
+ WARN("invalid xinput_code %d\n", event->code);
+
+ LeaveCriticalSection(&slots[slot_index].cs_status);
+}
+
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
{
+ DWORD i;
+
switch(reason)
{
- case DLL_WINE_PREATTACH:
- return FALSE; /* prefer native version */
- case DLL_PROCESS_ATTACH:
- DisableThreadLibraryCalls(inst);
- break;
+ case DLL_PROCESS_ATTACH:
+ DisableThreadLibraryCalls(inst);
+ is_core_initialized = FALSE;
+ InitializeCriticalSection(&cs_core_initalization);
+ break;
+ case DLL_PROCESS_DETACH:
+ for (i = 0; i < XUSER_MAX_COUNT; ++i) {
+ slot_close(i);
+ DeleteCriticalSection(&slots[i].cs_device);
+ DeleteCriticalSection(&slots[i].cs_status);
+ }
+ DeleteCriticalSection(&cs_core_initalization);
+ break;
}
+
return TRUE;
}
void WINAPI XInputEnable(BOOL 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 */
- FIXME("(%d) Stub!\n", enable);
+ DWORD i;
+ XINPUTW_DEV_RUMBLE rumble;
+
+ TRACE("xinput %s\n", enable ? "enabled" : "disabled");
+
+ EnsureInitialized();
+
+ is_xinput_enabled = enable;
+ for (i = 0; i < XUSER_MAX_COUNT; ++i) {
+ EnterCriticalSection(&slots[i].cs_device);
+ if (slots[i].backend != NULL) {
+ rumble.hf = is_xinput_enabled ? slots[i].rumble_state.wLeftMotorSpeed : 0;
+ rumble.lf = is_xinput_enabled ? slots[i].rumble_state.wRightMotorSpeed : 0;
+ if (!(slots[i].backend->SetRumble)(i, &rumble)) {
+ slot_close(i);
+ }
+ }
+ LeaveCriticalSection(&slots[i].cs_device);
+ }
}
DWORD WINAPI XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration)
{
- FIXME("(%d %p) Stub!\n", dwUserIndex, pVibration);
+ XINPUTW_DEV_RUMBLE rumble;
+
+ TRACE("slot %hu\n", dwUserIndex);
+
+ EnsureInitialized();
if (dwUserIndex < XUSER_MAX_COUNT)
{
- return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
+ EnterCriticalSection(&slots[dwUserIndex].cs_device);
+ if (!TryConnectDevice(dwUserIndex)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ EnterCriticalSection(&slots[dwUserIndex].cs_status);
+
+ memcpy((void *)&slots[dwUserIndex].rumble_state, (void *)pVibration,
+ sizeof(slots[dwUserIndex].rumble_state));
+
+ if (is_xinput_enabled) {
+ rumble.hf = pVibration->wLeftMotorSpeed;
+ rumble.lf = pVibration->wRightMotorSpeed;
+ if (!(slots[dwUserIndex].backend->SetRumble)(dwUserIndex, &rumble)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_status);
+ slot_close(dwUserIndex);
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+ }
+
+ LeaveCriticalSection(&slots[dwUserIndex].cs_status);
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_SUCCESS;
}
return ERROR_BAD_ARGUMENTS;
}
DWORD WINAPI DECLSPEC_HOTPATCH XInputGetState(DWORD dwUserIndex, XINPUT_STATE* pState)
{
- static int warn_once;
+ TRACE("slot %hu\n", dwUserIndex);
- if (!warn_once++)
- FIXME("(%u %p)\n", dwUserIndex, pState);
+ EnsureInitialized();
if (dwUserIndex < XUSER_MAX_COUNT)
{
- return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
+ if (!is_xinput_enabled) {
+ memset((void *)pState, 0, sizeof(*pState));
+ return ERROR_SUCCESS;
+ }
+
+ EnterCriticalSection(&slots[dwUserIndex].cs_device);
+ if (!TryConnectDevice(dwUserIndex)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ EnterCriticalSection(&slots[dwUserIndex].cs_status);
+
+
+ if (!(slots[dwUserIndex].backend->SyncKeyState)(dwUserIndex)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_status);
+ slot_close(dwUserIndex);
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ if (slots[dwUserIndex].state_has_changes) {
+ ++slots[dwUserIndex].state.dwPacketNumber;
+ slots[dwUserIndex].state_has_changes = FALSE;
+ }
+
+ memcpy((void *)pState, (const void *)&slots[dwUserIndex].state, sizeof(slots[dwUserIndex].state));
+
+ LeaveCriticalSection(&slots[dwUserIndex].cs_status);
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+
+ return ERROR_SUCCESS;
}
return ERROR_BAD_ARGUMENTS;
}
DWORD WINAPI XInputGetKeystroke(DWORD dwUserIndex, DWORD dwReserve, PXINPUT_KEYSTROKE pKeystroke)
{
- FIXME("(%d %d %p) Stub!\n", dwUserIndex, dwReserve, pKeystroke);
+ int earliest_event_slot;
+ ULONGLONG earliest_timestamp;
+ DWORD i;
+ const XINPUTW_KEYSTROKE *keystroke;
+ DWORD result;
+
+ TRACE("slot %hu\n", dwUserIndex);
+
+ EnsureInitialized();
if (dwUserIndex < XUSER_MAX_COUNT)
{
- return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
+ if (!is_xinput_enabled) return ERROR_EMPTY;
+
+ EnterCriticalSection(&slots[dwUserIndex].cs_device);
+ if (!TryConnectDevice(dwUserIndex)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ EnterCriticalSection(&slots[dwUserIndex].cs_status);
+
+ if (!(slots[dwUserIndex].backend->SyncKeyState)(dwUserIndex)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_status);
+ slot_close(dwUserIndex);
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ /* Send REPEAT keystrokes if necessary */
+ xiw_vk_Repeat(dwUserIndex, &slots[dwUserIndex].vkStates, &slots[dwUserIndex].keystrokes);
+
+ if ((keystroke = xiw_vk_KeystrokeQueueGetFront(&slots[dwUserIndex].keystrokes)) != NULL) {
+ memcpy((void *)pKeystroke, (void *)&keystroke->keystroke, sizeof(XINPUT_KEYSTROKE));
+ xiw_vk_KeystrokeQueuePop(&slots[dwUserIndex].keystrokes);
+ result = ERROR_SUCCESS;
+ } else {
+ result = ERROR_EMPTY;
+ }
+
+ LeaveCriticalSection(&slots[dwUserIndex].cs_status);
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return result;
+
+ } else if (dwUserIndex == XUSER_INDEX_ANY) {
+ if (!is_xinput_enabled) return ERROR_EMPTY;
+ earliest_event_slot = -1;
+
+ for (i = 0; i < XUSER_MAX_COUNT; i++) {
+ TRACE("Checking slot %d\n", i);
+ EnterCriticalSection(&slots[i].cs_device);
+ if (!TryConnectDevice(i)) {
+ LeaveCriticalSection(&slots[i].cs_device);
+ continue;
+ }
+
+ /* Grab the current slot's critical sections */
+ EnterCriticalSection(&slots[i].cs_status);
+ if (!(slots[i].backend->SyncKeyState)(i)) {
+ LeaveCriticalSection(&slots[i].cs_status);
+ slot_close(i);
+ LeaveCriticalSection(&slots[i].cs_device);
+ continue;
+ }
+
+ /* Send REPEAT keystrokes if necessary */
+ xiw_vk_Repeat(i, &slots[i].vkStates, &slots[i].keystrokes);
+
+ if ((keystroke = xiw_vk_KeystrokeQueueGetFront(&slots[i].keystrokes)) != NULL
+ && (earliest_event_slot == -1 || earliest_timestamp > keystroke->timestamp)) {
+ /* Only keep the relevant slot's critical section, release the previous one */
+ if (earliest_event_slot != -1) {
+ TRACE("Closing previous slot %d\n", earliest_event_slot);
+ LeaveCriticalSection(&slots[earliest_event_slot].cs_status);
+ LeaveCriticalSection(&slots[earliest_event_slot].cs_device);
+ }
+ TRACE("New top slot is %d\n", i);
+
+ earliest_event_slot = i;
+ earliest_timestamp = keystroke->timestamp;
+ } else {
+ TRACE("Closing slot %d\n", i);
+ LeaveCriticalSection(&slots[i].cs_status);
+ LeaveCriticalSection(&slots[i].cs_device);
+ }
+ }
+
+ if (earliest_event_slot != -1) {
+ TRACE("Closing slot %d\n", earliest_event_slot);
+ keystroke = xiw_vk_KeystrokeQueueGetFront(&slots[earliest_event_slot].keystrokes);
+ memcpy((void *)pKeystroke, (void *)&keystroke->keystroke, sizeof(XINPUT_KEYSTROKE));
+ xiw_vk_KeystrokeQueuePop(&slots[earliest_event_slot].keystrokes);
+
+ /* Every other slot's critical sections were already released */
+ LeaveCriticalSection(&slots[earliest_event_slot].cs_status);
+ LeaveCriticalSection(&slots[earliest_event_slot].cs_device);
+
+ return ERROR_SUCCESS;
+ }
+
+ return ERROR_EMPTY;
}
return ERROR_BAD_ARGUMENTS;
}
DWORD WINAPI XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES* pCapabilities)
{
- static int warn_once;
+ XINPUTW_DEV_CAPABILITIES *capabilities;
+ int i;
- if (!warn_once++)
- FIXME("(%d %d %p)\n", dwUserIndex, dwFlags, pCapabilities);
+ TRACE("slot %hu\n", dwUserIndex);
+
+ EnsureInitialized();
if (dwUserIndex < XUSER_MAX_COUNT)
{
- return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
+ EnterCriticalSection(&slots[dwUserIndex].cs_device);
+ if (!TryConnectDevice(dwUserIndex)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+
+ capabilities = &slots[dwUserIndex].capabilities;
+
+ pCapabilities->Type = XINPUT_DEVTYPE_GAMEPAD;
+ /* FIXME: Actually check the subtype (based on the available buttons) */
+ pCapabilities->SubType = XINPUT_DEVSUBTYPE_GAMEPAD;
+
+ /* Set flags to zero. FF capabilities are checked further down */
+ pCapabilities->Flags = 0;
+
+ pCapabilities->Gamepad.wButtons = 0;
+ /* Set button info */
+ for (i = WINE_BTN_MIN; i <= WINE_BTN_MAX; ++i) {
+ if (capabilities->buttons & (1 << (i - WINE_BTN_MIN)))
+ pCapabilities->Gamepad.wButtons |= GetCapabilitiesBtnFlag(i);
+ }
+
+ /* Set axis info */
+ pCapabilities->Gamepad.bLeftTrigger = (BYTE)GetResolutionBitmap(
+ capabilities->axes[WINE_AXIS_LTRIGGER - WINE_AXIS_MIN],
+ sizeof(pCapabilities->Gamepad.bLeftTrigger));
+ pCapabilities->Gamepad.bRightTrigger = (BYTE)GetResolutionBitmap(
+ capabilities->axes[WINE_AXIS_RTRIGGER - WINE_AXIS_MIN],
+ sizeof(pCapabilities->Gamepad.bRightTrigger));
+ pCapabilities->Gamepad.sThumbLX = (WORD)GetResolutionBitmap(
+ capabilities->axes[WINE_AXIS_LTHUMB_X - WINE_AXIS_MIN],
+ sizeof(pCapabilities->Gamepad.sThumbLX));
+ pCapabilities->Gamepad.sThumbLY = (WORD)GetResolutionBitmap(
+ capabilities->axes[WINE_AXIS_LTHUMB_Y - WINE_AXIS_MIN],
+ sizeof(pCapabilities->Gamepad.sThumbLY));
+ pCapabilities->Gamepad.sThumbRX = (WORD)GetResolutionBitmap(
+ capabilities->axes[WINE_AXIS_RTHUMB_X - WINE_AXIS_MIN],
+ sizeof(pCapabilities->Gamepad.sThumbRX));
+ pCapabilities->Gamepad.sThumbRY = (WORD)GetResolutionBitmap(
+ capabilities->axes[WINE_AXIS_RTHUMB_Y - WINE_AXIS_MIN],
+ sizeof(pCapabilities->Gamepad.sThumbRY));
+
+ /* Check force feedback capabilities */
+ if (capabilities->has_rumble) {
+ /* FIXME: XINPUT_CAPS_FFB_SUPPORTED is not defined */
+ /* pCapabilities->Flags |= XINPUT_CAPS_FFB_SUPPORTED; */
+
+ /* Evdev uses 16bit numbers to describe the rumble motor speed. No need to check */
+ pCapabilities->Vibration.wLeftMotorSpeed = 0xffff;
+ pCapabilities->Vibration.wRightMotorSpeed = 0xffff;
+ } else {
+ pCapabilities->Vibration.wLeftMotorSpeed = 0;
+ pCapabilities->Vibration.wRightMotorSpeed = 0;
+ }
+
+
+ return ERROR_SUCCESS;
}
return ERROR_BAD_ARGUMENTS;
}
@@ -111,9 +705,22 @@ DWORD WINAPI XInputGetDSoundAudioDeviceGuids(DWORD dwUserIndex, GUID* pDSoundRen
{
FIXME("(%d %p %p) Stub!\n", dwUserIndex, pDSoundRenderGuid, pDSoundCaptureGuid);
+ EnsureInitialized();
+
if (dwUserIndex < XUSER_MAX_COUNT)
{
- return ERROR_DEVICE_NOT_CONNECTED;
+ EnterCriticalSection(&slots[dwUserIndex].cs_device);
+ if (!TryConnectDevice(dwUserIndex)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+
+ /* Audio not supported (yet?) */
+ *pDSoundRenderGuid = GUID_NULL;
+ *pDSoundCaptureGuid = GUID_NULL;
+
+ return ERROR_SUCCESS;
/* If controller exists then return ERROR_SUCCESS */
}
return ERROR_BAD_ARGUMENTS;
@@ -121,12 +728,45 @@ DWORD WINAPI XInputGetDSoundAudioDeviceGuids(DWORD dwUserIndex, GUID* pDSoundRen
DWORD WINAPI XInputGetBatteryInformation(DWORD dwUserIndex, BYTE deviceType, XINPUT_BATTERY_INFORMATION* pBatteryInfo)
{
- FIXME("(%d %u %p) Stub!\n", dwUserIndex, deviceType, pBatteryInfo);
+ TRACE("slot %hu\n", dwUserIndex);
+
+ EnsureInitialized();
if (dwUserIndex < XUSER_MAX_COUNT)
{
- return ERROR_DEVICE_NOT_CONNECTED;
- /* If controller exists then return ERROR_SUCCESS */
+ EnterCriticalSection(&slots[dwUserIndex].cs_device);
+ if (!TryConnectDevice(dwUserIndex)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ EnterCriticalSection(&slots[dwUserIndex].cs_status);
+
+ if (!(slots[dwUserIndex].backend->SyncBatteryState)(dwUserIndex, &slots[dwUserIndex].battery_level)) {
+ LeaveCriticalSection(&slots[dwUserIndex].cs_status);
+ slot_close(dwUserIndex);
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ if (slots[dwUserIndex].battery_level < 0) {
+ pBatteryInfo->BatteryType = BATTERY_TYPE_UNKNOWN;
+ pBatteryInfo->BatteryLevel = BATTERY_LEVEL_FULL;
+ } else {
+ pBatteryInfo->BatteryType = BATTERY_TYPE_ALKALINE;
+ if (slots[dwUserIndex].battery_level < 0x2000)
+ pBatteryInfo->BatteryLevel = BATTERY_LEVEL_EMPTY;
+ else if (slots[dwUserIndex].battery_level < 0x4000)
+ pBatteryInfo->BatteryLevel = BATTERY_LEVEL_LOW;
+ else if (slots[dwUserIndex].battery_level < 0x6000)
+ pBatteryInfo->BatteryLevel = BATTERY_LEVEL_MEDIUM;
+ else
+ pBatteryInfo->BatteryLevel = BATTERY_LEVEL_FULL;
+ }
+
+ LeaveCriticalSection(&slots[dwUserIndex].cs_status);
+ LeaveCriticalSection(&slots[dwUserIndex].cs_device);
+ return ERROR_SUCCESS;
}
return ERROR_BAD_ARGUMENTS;
}
diff --git a/dlls/xinput1_3/xinput_backend.h b/dlls/xinput1_3/xinput_backend.h
new file mode 100644
index 0000000..1cb3037
--- /dev/null
+++ b/dlls/xinput1_3/xinput_backend.h
@@ -0,0 +1,188 @@
+/*
+ * The Wine project - XInput Joystick Library - core<->backend interface
+ *
+ * Copyright 2016 Juan Gonzalez
+ *
+ * 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
+ */
+
+#ifndef __WINE_DLLS_XINPUT_BACKEND_H
+#define __WINE_DLLS_XINPUT_BACKEND_H
+
+
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "winerror.h"
+
+#include "xinput.h"
+
+#define XINPUTW_VAL_MIN (-0x8000)
+#define XINPUTW_VAL_MAX (0x7fff)
+typedef INT16 XINPUTW_VALUE;
+
+/* Contains information about the rumble status of a device */
+typedef struct _XINPUTW_DEV_RUMBLE {
+ WORD hf;
+ WORD lf;
+} XINPUTW_DEV_RUMBLE;
+
+typedef struct _XINPUTW_DEV_CAPABILITIES {
+ /* Bitmap representing the available buttons. Set with xiw_util_SetCapabilitiesBtn */
+ WORD buttons;
+
+ /* Axis resolution bit count. set using xiw_util_SetCapabilitiesAxis */
+ BYTE axes[6];
+
+ /* Whether the slot supports rumble (force feedback) */
+ BOOL has_rumble;
+} XINPUTW_DEV_CAPABILITIES;
+
+/* Defines the interface exposed by a backend to xinput_core */
+typedef struct _XINPUTW_BACKEND {
+ /*
+ * Printable name of the backend
+ */
+ const char *Name;
+
+ /*
+ * Initialize the backend. Is called exactly once by xinput_core
+ * during initialization, before calling anything else in this backend.
+ * If this pointer is NULL, the backend will not be used
+ */
+ void (*Initialize)(void);
+
+ /*
+ * Try to connect a new device to the given slot.
+ *
+ * slot_index: The index of the free slot where a device could be connected
+ * capabilities (out): If the function was successful, the capabilities structure should be
+ * filled by the backend
+ *
+ * Returns: TRUE if successful, FALSE if not (ie. if no new device is available)
+ */
+ BOOL (*TryConnectDevice)(DWORD target_slot_index, XINPUTW_DEV_CAPABILITIES *capabilities);
+
+ /*
+ * Close the device at the given slot. Called if access to the device fails, or when shutting down xinput.
+ *
+ * slot_index: The index of the slot to be disconnected
+ */
+ void (*DisconnectDevice)(DWORD slot_index);
+
+ /*
+ * Synchronize the gamepad state for a given slot.
+ *
+ * Returns: TRUE if successful (even for no-ops), FALSE if the device was disconnected
+ */
+ BOOL (*SyncKeyState)(DWORD slot_index);
+
+ /*
+ * Synchronize the gamepad battery for a given slot.
+ *
+ * slot_index: The index of the slot.
+ * battery_level (out): The current battery level. If the battery state is unknown,
+ * battery_level should be set to a value less than 0 (eg. -1)
+ *
+ * Returns: TRUE if successful (even for no-ops), FALSE if the device was disconnected
+ */
+ BOOL (*SyncBatteryState)(DWORD slot_index, INT16 *battery_level);
+
+ /*
+ * Synchronize the gamepad battery for a given slot.
+ *
+ * slot_index: The index of the slot.
+ * rumble: The rumble values to be set. The effect should continue until SetRumble is called again
+ *
+ * Returns: TRUE if successful (even for no-ops), FALSE if the device was disconnected
+ */
+ BOOL (*SetRumble)(DWORD slot_index, const XINPUTW_DEV_RUMBLE *rumble);
+
+} XINPUTW_BACKEND;
+
+typedef enum _XINPUTW_EVENT_CODE {
+ WINE_BTN_A = 0ul,
+ WINE_BTN_B,
+ WINE_BTN_Y,
+ WINE_BTN_X,
+ WINE_BTN_START,
+ WINE_BTN_BACK,
+ WINE_BTN_LSHOULDER,
+ WINE_BTN_RSHOULDER,
+ WINE_BTN_LTHUMB,
+ WINE_BTN_RTHUMB,
+ WINE_BTN_DPAD_UP,
+ WINE_BTN_DPAD_DOWN,
+ WINE_BTN_DPAD_LEFT,
+ WINE_BTN_DPAD_RIGHT,
+ WINE_AXIS_LTRIGGER,
+ WINE_AXIS_RTRIGGER,
+ WINE_AXIS_LTHUMB_X,
+ WINE_AXIS_LTHUMB_Y,
+ WINE_AXIS_RTHUMB_X,
+ WINE_AXIS_RTHUMB_Y,
+ WINE_CONTROL_COUNT
+} XINPUTW_EVENT_CODE;
+
+#define WINE_BTN_MIN WINE_BTN_A
+#define WINE_BTN_MAX WINE_BTN_DPAD_RIGHT
+#define WINE_AXIS_MIN WINE_AXIS_LTRIGGER
+#define WINE_AXIS_MAX WINE_AXIS_RTHUMB_Y
+
+/**
+ * Defines the condition under which a numeric input value is considered an "on"-state for a button
+ */
+typedef enum _VAL_TO_BTN_MAP {
+ VAL_TO_BTN_NONE,
+ VAL_TO_BTN_LT_ZERO,
+ VAL_TO_BTN_LE_ZERO,
+ VAL_TO_BTN_ZERO,
+ VAL_TO_BTN_GT_ZERO,
+ VAL_TO_BTN_GE_ZERO
+} VAL_TO_BTN_MAP;
+
+/**
+ * Defines whether a numeric input value should be inverted when mapping it to an axis
+ */
+typedef enum _AXIS_MAP {
+ AXIS_MAP_REGULAR,
+ AXIS_MAP_INVERTED
+} AXIS_MAP;
+
+typedef union _XINPUTW_EVENT_MAP {
+ VAL_TO_BTN_MAP button;
+ AXIS_MAP axis;
+} XINPUTW_EVENT_MAP;
+
+typedef struct _XINPUTW_EVENT {
+ XINPUTW_EVENT_CODE code;
+ XINPUTW_VALUE value;
+ XINPUTW_EVENT_MAP value_map;
+ /* Timestamp as provided by GetTickCount64 */
+ ULONGLONG timestamp;
+} XINPUTW_EVENT;
+
+/*
+ * Notify xinput_core of a change in the gamepad state
+ * slot_index: The index of the slot, as passed when the core calls get_new_device.
+ * event: The event being pushed
+ *
+ * This function can be called asynchronously whenever an event occurs.
+ */
+void xiw_core_PushEvent(DWORD slot_index, const XINPUTW_EVENT *event);
+
+#endif /* __WINE_DLLS_XINPUT_BACKEND_H */
diff --git a/dlls/xinput1_3/xinput_core.h b/dlls/xinput1_3/xinput_core.h
new file mode 100644
index 0000000..d7cb90e
--- /dev/null
+++ b/dlls/xinput1_3/xinput_core.h
@@ -0,0 +1,159 @@
+/*
+ * The Wine project - XInput Joystick Library - utility methods
+ *
+ * Copyright 2016 Juan Jose Gonzalez
+ *
+ * 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
+ */
+
+#ifndef __WINE_DLLS_XINPUT_CORE_H
+#define __WINE_DLLS_XINPUT_CORE_H
+
+#include "xinput_util.h"
+
+#define KEYSTROKE_QUEUE_SIZE 1024
+
+/* Stores a keystroke with a timestamp */
+typedef struct _XINPUTW_KEYSTROKE {
+ ULONGLONG timestamp;
+ XINPUT_KEYSTROKE keystroke;
+} XINPUTW_KEYSTROKE;
+
+/* Stores several keystrokes in a queue */
+typedef struct _XINPUTW_KEYSTROKE_QUEUE {
+ XINPUTW_KEYSTROKE elements[KEYSTROKE_QUEUE_SIZE];
+ /* The first valid element */
+ unsigned int head;
+ /* One element after the last valid one, ie. the first free element */
+ unsigned int tail;
+} XINPUTW_KEYSTROKE_QUEUE;
+
+
+/* Defines the current state of a control. Used to emit VirtualKey events */
+typedef enum _XINPUTW_VK_AREA {
+ /* Deadzone / Released */
+ VK_AREA_NONE,
+ /* Triggers / Buttons */
+ VK_AREA_PRESSED,
+ /* Thumbpads */
+ VK_AREA_L,
+ VK_AREA_LD,
+ VK_AREA_D,
+ VK_AREA_RD,
+ VK_AREA_R,
+ VK_AREA_RU,
+ VK_AREA_U,
+ VK_AREA_LU
+} XINPUTW_VK_AREA;
+
+/* Used to track the state (pressed/not) of a single control to emit VirtualKey events as needed */
+typedef struct _XINPUTW_VK_STATE {
+ /* Currently pressed VirtualKey area */
+ XINPUTW_VK_AREA area;
+ /* Timestamp of last sent Keystroke */
+ ULONGLONG timestamp;
+ BOOL isRepeat;
+} XINPUTW_VK_STATE;
+
+/*
+ * Control names. Each thumbpad is a single entry, as opposed to XINPUTW_EVENT_CODE,
+ * where each thumbpad has an entry per axis
+ */
+typedef enum _XINPUTW_VK_CONTROLNAME {
+ WVK_BTN_A = 0ul,
+ WVK_BTN_B,
+ WVK_BTN_Y,
+ WVK_BTN_X,
+ WVK_BTN_START,
+ WVK_BTN_BACK,
+ WVK_BTN_LSHOULDER,
+ WVK_BTN_RSHOULDER,
+ WVK_BTN_LTHUMB,
+ WVK_BTN_RTHUMB,
+ WVK_BTN_DPAD_UP,
+ WVK_BTN_DPAD_DOWN,
+ WVK_BTN_DPAD_LEFT,
+ WVK_BTN_DPAD_RIGHT,
+ WVK_AXIS_LTRIGGER,
+ WVK_AXIS_RTRIGGER,
+ WVK_AXIS_LTHUMB,
+ WVK_AXIS_RTHUMB,
+} XINPUTW_VK_CONTROLNAME;
+
+/* Contains all the VK states of a gamepad */
+typedef struct _XINPUTW_VK_STATES {
+ XINPUTW_VK_STATE items[WVK_AXIS_RTHUMB + 1];
+ BOOL lThumbIsSquare;
+ BOOL rThumbIsSquare;
+} XINPUTW_VK_STATES;
+
+/*
+ * Adds a new VirtualKey event to be returned by XInputGetKeystroke
+ *
+ * queue: A pointer to the XINPUTW_KEYSTROKE_QUEUE the event should be pushed into
+ * element: A pointer to the new event. The event will be copied, allowing the caller to modify the
+ * data afterwards without altering the event in the queue
+ */
+void xiw_vk_KeystrokeQueuePush(XINPUTW_KEYSTROKE_QUEUE * queue, const XINPUTW_KEYSTROKE * element);
+
+/*
+ * Discards the front element from the VirtualKey event queue
+ *
+ * queue: A pointer to the XINPUTW_KEYSTROKE_QUEUE whose first element should be discarded
+ */
+void xiw_vk_KeystrokeQueuePop(XINPUTW_KEYSTROKE_QUEUE * queue);
+
+/*
+ * Returns the front element from the VirtualKey event queue. That event is kept in the queue until
+ * xiw_vk_KeystrokeQueuePop is called
+ *
+ * queue: A pointer to the XINPUTW_KEYSTROKE_QUEUE the event should be retrieved from
+ *
+ * Returns: The first element from the queue, or NULL if the queue is empty. If any other
+ * operation is performed on the queue, data integrity cannot be guaranteed
+ */
+const XINPUTW_KEYSTROKE * xiw_vk_KeystrokeQueueGetFront(XINPUTW_KEYSTROKE_QUEUE * queue);
+
+/*
+ * Clears the VirtualKey event queue
+ *
+ * queue: A pointer to the XINPUTW_KEYSTROKE_QUEUE to be cleared
+ */
+void xiw_vk_KeystrokeQueueClear(XINPUTW_KEYSTROKE_QUEUE * queue);
+
+/*
+ * Notifies the VirtualKey code of an changed value to push VirtualKey events if necessary
+ *
+ * slot_index: The index of the slot/controller where the change happened
+ * timestamp: The timestamp of the event as returned by GetTickCount64()
+ * code: The XInput Wine event code, ie. the source of the changed value
+ * state: The updated state of the controller
+ * vkStates: The current VirtualKey states of the controller
+ * keystrokeQueue: The VirtualKey keystroke queue of the controller
+ */
+void xiw_vk_Update(DWORD slot_index, ULONGLONG timestamp, XINPUTW_EVENT_CODE code,
+ const XINPUT_STATE * state, XINPUTW_VK_STATES * vkStates,
+ XINPUTW_KEYSTROKE_QUEUE * keystrokeQueue);
+
+/*
+ * Emits REPEAT VirtualKey events if necessary. Gets called on every call to XInputGetKeystroke
+ *
+ * slot_index: The index of the slot/controller where the change happened
+ * vkStates: The current VirtualKey states of the controller
+ * keystrokeQueue: The VirtualKey keystroke queue of the controller
+ */
+void xiw_vk_Repeat(DWORD slot_index, XINPUTW_VK_STATES *vkStates, XINPUTW_KEYSTROKE_QUEUE * keystrokeQueue);
+
+#endif /* __WINE_DLLS_XINPUT_CORE_H */
diff --git a/dlls/xinput1_3/xinput_core_vk.c b/dlls/xinput1_3/xinput_core_vk.c
new file mode 100644
index 0000000..398161a
--- /dev/null
+++ b/dlls/xinput1_3/xinput_core_vk.c
@@ -0,0 +1,435 @@
+/*
+ * The Wine project - XInput Joystick Library - VirtualKey helper methods
+ *
+ * Copyright 2016 Juan Jose Gonzalez
+ *
+ * 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
+ */
+
+/*
+ * The exported methods calculate whether or not to emit a VirtualKey press for XInputGetKeystrokes
+ * based on an update on the position of the controllers thumb pads or triggers
+ *
+ * The switch from pressed to unpreseed for any VK is performed with hysteresis, ie. there is a
+ * margin outside of the current VK's "active" area where the code will not emit a button-up event.
+ * This is useful to reduce ringing, ie quick changes between states caused by minor changes in the
+ * reported control values.
+ *
+ * Thumpad considerations:
+ * - The values for the thumbpads are mapped onto the range -1 to 1 to make a unit circle
+ * - The center region, or dead-zone, is the circle of radius 0.5
+ * - Each VirtualKey is alloted a 45-degree segment on the ring of radius 0.5 to 1
+ *
+ * Each thumbpad has eight VirtualKeys, one for each side and one for each corner. To simplify
+ * the detection of when an area is entered and when it is left, the entire value range of the
+ * thumbpad is mapped onto a 45-degree area, where the 22.5-degree bisecting line marks the border
+ * between a side and a corner. The mapping is as follows:
+ * - map the value range onto the first quadrant (90-degree segment) by removing the
+ * sign from x and y, thereby mirroring along the x and y axis as needed
+ * - mirror the area between 45 and 90 degrees to the area between 0 and 45 degrees by swapping x
+ * and y if y is larger than x, thereby mirroring along the 45-degree line as needed
+ *
+ * The dead-zone region is left when the distance to the center (0,0) is greater than (0.5 - margin)
+ * A VirtualKey region is left when (ORed):
+ * - The distance to the center (0,0) is less than (0.5 - margin)
+ * - The current position is not on the VKs area and the distance from the 22.5-degree bisecting
+ * line is greater than (margin). This is calculated by projecting the current position onto a
+ * vector P of unit lenght which points towards 112.5 degrees, ie. perpendicular to the 22.5
+ * degree line. The length of this projection, ie. the distance to the 22.5 degree line, is
+ * (P.x * x + P.Y * y)
+ *
+ * Chosen margin: 0.07 (for a total hysteresis region of 0.14 or 14% of the radius)
+ */
+
+
+#include "config.h"
+
+#include "wine/debug.h"
+
+#include "windef.h"
+#include "winbase.h"
+#include "winerror.h"
+
+#include "xinput.h"
+
+#include "xinput_backend.h"
+#include "xinput_core.h"
+
+#define SQUARE(a) (a * a)
+
+WINE_DEFAULT_DEBUG_CHANNEL(xinput);
+
+static const int REPEAT_DELAY_MS = 500;
+static const int REPEAT_PERIOD_MS = 180;
+
+static const float HYSTERESIS_MARGIN = 0.07;
+static const float DEADZONE = 0.5;
+
+static void Swap(float *x, float *y) {
+ float z;
+ z = *x;
+ *x = *y;
+ *y = z;
+}
+
+static WORD BtnGetVirtualKey(XINPUTW_VK_CONTROLNAME control) {
+ switch (control) {
+ case WVK_BTN_A: return VK_PAD_A;
+ case WVK_BTN_B: return VK_PAD_B;
+ case WVK_BTN_Y: return VK_PAD_Y;
+ case WVK_BTN_X: return VK_PAD_X;
+ case WVK_BTN_START: return VK_PAD_START;
+ case WVK_BTN_BACK: return VK_PAD_BACK;
+ case WVK_BTN_LSHOULDER: return VK_PAD_LSHOULDER;
+ case WVK_BTN_RSHOULDER: return VK_PAD_RSHOULDER;
+ case WVK_BTN_LTHUMB: return VK_PAD_LTHUMB_PRESS;
+ case WVK_BTN_RTHUMB: return VK_PAD_RTHUMB_PRESS;
+ case WVK_BTN_DPAD_UP: return VK_PAD_DPAD_UP;
+ case WVK_BTN_DPAD_DOWN: return VK_PAD_DPAD_DOWN;
+ case WVK_BTN_DPAD_LEFT: return VK_PAD_DPAD_LEFT;
+ case WVK_BTN_DPAD_RIGHT: return VK_PAD_DPAD_RIGHT;
+ case WVK_AXIS_LTRIGGER: return VK_PAD_LTRIGGER;
+ case WVK_AXIS_RTRIGGER: return VK_PAD_RTRIGGER;
+ default: return 0;
+ }
+}
+
+static WORD LThumbGetVirtualKey(XINPUTW_VK_AREA area) {
+ switch (area) {
+ case VK_AREA_L: return VK_PAD_LTHUMB_LEFT;
+ case VK_AREA_LD: return VK_PAD_LTHUMB_DOWNLEFT;
+ case VK_AREA_D: return VK_PAD_LTHUMB_DOWN;
+ case VK_AREA_RD: return VK_PAD_LTHUMB_DOWNRIGHT;
+ case VK_AREA_R: return VK_PAD_LTHUMB_RIGHT;
+ case VK_AREA_RU: return VK_PAD_LTHUMB_UPRIGHT;
+ case VK_AREA_U: return VK_PAD_LTHUMB_UP;
+ case VK_AREA_LU: return VK_PAD_LTHUMB_UPLEFT;
+ default: return 0;
+ }
+}
+
+static WORD RThumbGetVirtualKey(XINPUTW_VK_AREA area) {
+ switch (area) {
+ case VK_AREA_L: return VK_PAD_RTHUMB_LEFT;
+ case VK_AREA_LD: return VK_PAD_RTHUMB_DOWNLEFT;
+ case VK_AREA_D: return VK_PAD_RTHUMB_DOWN;
+ case VK_AREA_RD: return VK_PAD_RTHUMB_DOWNRIGHT;
+ case VK_AREA_R: return VK_PAD_RTHUMB_RIGHT;
+ case VK_AREA_RU: return VK_PAD_RTHUMB_UPRIGHT;
+ case VK_AREA_U: return VK_PAD_RTHUMB_UP;
+ case VK_AREA_LU: return VK_PAD_RTHUMB_UPLEFT;
+ default: return 0;
+ }
+}
+
+/*
+ * Maps an XINPUTW_VK_AREA to the virtual key it would emit for a specific control
+ */
+static WORD GetVirtualKey(XINPUTW_VK_CONTROLNAME control, XINPUTW_VK_AREA area) {
+ if (control >= WVK_BTN_A && control <= WVK_AXIS_RTRIGGER)
+ return area != VK_AREA_NONE ? BtnGetVirtualKey(control) : 0;
+ switch (control) {
+ case WVK_AXIS_LTHUMB: return LThumbGetVirtualKey(area);
+ case WVK_AXIS_RTHUMB: return RThumbGetVirtualKey(area);
+ default: return 0;
+ }
+
+}
+
+/*
+ * Maps XInput Wine event codes to their respective controls
+ */
+static XINPUTW_VK_CONTROLNAME GetControlFromEventCode(XINPUTW_EVENT_CODE code) {
+ switch (code) {
+ case WINE_BTN_A: return WVK_BTN_A;
+ case WINE_BTN_B: return WVK_BTN_B;
+ case WINE_BTN_Y: return WVK_BTN_Y;
+ case WINE_BTN_X: return WVK_BTN_X;
+ case WINE_BTN_START: return WVK_BTN_START;
+ case WINE_BTN_BACK: return WVK_BTN_BACK;
+ case WINE_BTN_LSHOULDER: return WVK_BTN_LSHOULDER;
+ case WINE_BTN_RSHOULDER: return WVK_BTN_RSHOULDER;
+ case WINE_BTN_LTHUMB: return WVK_BTN_LTHUMB;
+ case WINE_BTN_RTHUMB: return WVK_BTN_RTHUMB;
+ case WINE_BTN_DPAD_UP: return WVK_BTN_DPAD_UP;
+ case WINE_BTN_DPAD_DOWN: return WVK_BTN_DPAD_DOWN;
+ case WINE_BTN_DPAD_LEFT: return WVK_BTN_DPAD_LEFT;
+ case WINE_BTN_DPAD_RIGHT: return WVK_BTN_DPAD_RIGHT;
+ case WINE_AXIS_LTRIGGER: return WVK_AXIS_LTRIGGER;
+ case WINE_AXIS_RTRIGGER: return WVK_AXIS_RTRIGGER;
+ case WINE_AXIS_LTHUMB_X:
+ case WINE_AXIS_LTHUMB_Y:
+ return WVK_AXIS_LTHUMB;
+ case WINE_AXIS_RTHUMB_X:
+ case WINE_AXIS_RTHUMB_Y:
+ return WVK_AXIS_RTHUMB;
+ default: return (XINPUTW_VK_CONTROLNAME)-1;
+ }
+}
+
+/*
+ * Checks whether the requested button is pressed in the XInput state
+ */
+static BOOL BtnIsPressedMask(WORD wButtons, XINPUTW_VK_CONTROLNAME control) {
+ switch (control) {
+ case WVK_BTN_A: return (wButtons & XINPUT_GAMEPAD_A) != 0;
+ case WVK_BTN_B: return (wButtons & XINPUT_GAMEPAD_B) != 0;
+ case WVK_BTN_Y: return (wButtons & XINPUT_GAMEPAD_Y) != 0;
+ case WVK_BTN_X: return (wButtons & XINPUT_GAMEPAD_X) != 0;
+ case WVK_BTN_START: return (wButtons & XINPUT_GAMEPAD_START) != 0;
+ case WVK_BTN_BACK: return (wButtons & XINPUT_GAMEPAD_BACK) != 0;
+ case WVK_BTN_LSHOULDER: return (wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
+ case WVK_BTN_RSHOULDER: return (wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
+ case WVK_BTN_LTHUMB: return (wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
+ case WVK_BTN_RTHUMB: return (wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
+ case WVK_BTN_DPAD_UP: return (wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
+ case WVK_BTN_DPAD_DOWN: return (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
+ case WVK_BTN_DPAD_LEFT: return (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
+ case WVK_BTN_DPAD_RIGHT: return (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
+ default: return FALSE;
+ }
+}
+
+/*
+ * Checks whether the requested trigger is pressed in the XInput state, applying hysteresis
+ */
+static BOOL TriggerIsPressed(BYTE triggerValue, XINPUTW_VK_AREA currentArea) {
+ return (currentArea == VK_AREA_PRESSED && triggerValue > 255 * (DEADZONE - HYSTERESIS_MARGIN))
+ || (currentArea != VK_AREA_PRESSED && triggerValue > 255 * (DEADZONE + HYSTERESIS_MARGIN));
+}
+
+/*
+ * Checks if the thumbpad's location is whithin the correct 90-degree zone
+ * of the currently active area
+ */
+static BOOL CheckValidCurrentArea(BOOL mirroredX, BOOL mirroredY, BOOL mirroredDiag, XINPUTW_VK_AREA currentArea) {
+ switch (currentArea) {
+ case VK_AREA_L: return !mirroredDiag && mirroredX;
+ case VK_AREA_R: return !mirroredDiag && !mirroredX;
+ case VK_AREA_U: return !mirroredDiag && !mirroredY;
+ case VK_AREA_D: return !mirroredDiag && mirroredY;
+ case VK_AREA_LU: return mirroredX && !mirroredY;
+ case VK_AREA_RU: return !mirroredX && !mirroredY;
+ case VK_AREA_LD: return mirroredX && mirroredY;
+ case VK_AREA_RD: return !mirroredX && mirroredY;
+ default:
+ WARN("invalid parameter currentArea: %d", currentArea);
+ return FALSE;
+ }
+}
+
+/*
+ * Pushes a VK event if the current button or trigger state differs from the previous
+ */
+static void UpdateButton(DWORD slot_index, ULONGLONG timestamp, XINPUTW_VK_CONTROLNAME control,
+ BOOL isPressed, XINPUTW_VK_STATES *vkStates,
+ XINPUTW_KEYSTROKE_QUEUE * keystrokeQueue) {
+ XINPUTW_VK_STATE * vkState;
+ XINPUTW_VK_AREA newArea;
+ XINPUTW_KEYSTROKE keystroke;
+
+ newArea = isPressed ? VK_AREA_PRESSED : VK_AREA_NONE;
+
+ vkState = &(vkStates->items[control]);
+ if (vkState->area != newArea) {
+ memset((void*)&keystroke, 0, sizeof(XINPUTW_KEYSTROKE));
+ keystroke.timestamp = timestamp;
+ keystroke.keystroke.Flags = isPressed ? XINPUT_KEYSTROKE_KEYDOWN : XINPUT_KEYSTROKE_KEYUP;
+ keystroke.keystroke.UserIndex = slot_index;
+ keystroke.keystroke.VirtualKey = GetVirtualKey(control, VK_AREA_PRESSED);
+ xiw_vk_KeystrokeQueuePush(keystrokeQueue, &keystroke);
+
+ vkState->area = newArea;
+ vkState->timestamp = timestamp;
+ vkState->isRepeat = FALSE;
+ }
+}
+
+/*
+ * Pushes VK events if the current thumbpad area differs from the previous
+ */
+static void UpdateThumb(DWORD slot_index, ULONGLONG timestamp, XINPUTW_VK_CONTROLNAME control,
+ XINPUTW_VK_AREA newArea, XINPUTW_VK_STATES *vkStates,
+ XINPUTW_KEYSTROKE_QUEUE * keystrokeQueue) {
+ XINPUTW_VK_STATE * vkState;
+ XINPUTW_KEYSTROKE keystroke;
+
+ vkState = &(vkStates->items[control]);
+ if (vkState->area != newArea) {
+ memset((void*)&keystroke, 0, sizeof(XINPUTW_KEYSTROKE));
+ keystroke.timestamp = timestamp;
+ keystroke.keystroke.UserIndex = slot_index;
+
+ if (vkState->area != VK_AREA_NONE) {
+ keystroke.keystroke.Flags = XINPUT_KEYSTROKE_KEYUP;
+ keystroke.keystroke.VirtualKey = GetVirtualKey(control, vkState->area);
+ xiw_vk_KeystrokeQueuePush(keystrokeQueue, &keystroke);
+ }
+
+ if (newArea != VK_AREA_NONE) {
+ keystroke.keystroke.Flags = XINPUT_KEYSTROKE_KEYDOWN;
+ keystroke.keystroke.VirtualKey = GetVirtualKey(control, newArea);
+ xiw_vk_KeystrokeQueuePush(keystrokeQueue, &keystroke);
+ }
+
+ vkState->area = newArea;
+ vkState->timestamp = timestamp;
+ vkState->isRepeat = FALSE;
+ }
+}
+
+/*
+ * Calculates the new area of for a thumbpad
+ */
+static XINPUTW_VK_AREA GetThumbArea(SHORT x, SHORT y, BOOL * thumbIsSquare, XINPUTW_VK_AREA currentArea) {
+ /* projection vector perpendicular to the 22.5 degree line */
+ const float projectionX = -0.38268343236, projectionY = 0.92387953251;
+ /* distance from the mapped point to the 22.5 line */
+ float projectionDistance;
+
+ /* x and y for the mapped 45-decree sector, normalized */
+ float nx, ny;
+ BOOL mirroredX, mirroredY, mirroredDiag;
+
+ float r;
+
+ mirroredX = x < 0;
+ mirroredY = y < 0;
+ nx = (mirroredX ? -(x + 1) : x) / 32767.0;
+ ny = (mirroredY ? -(y + 1) : y) / 32767.0;
+ mirroredDiag = ny > nx;
+ if (mirroredDiag)
+ Swap(&nx, &ny);
+
+ r = nx*nx + ny*ny;
+ if (r > 1.4) {
+ TRACE("detected a square thumbpad area\n");
+ *thumbIsSquare = TRUE;
+ }
+
+ if (r > 0.1 && *thumbIsSquare) {
+ TRACE("mapping square are coords with radius %f", r);
+ r = (SQUARE(SQUARE(nx)) + SQUARE(nx) * SQUARE(ny)) / r;
+ TRACE(" -> new radius: %f", r);
+ }
+
+
+ if ((currentArea == VK_AREA_NONE && r < SQUARE(DEADZONE + HYSTERESIS_MARGIN))
+ || (currentArea != VK_AREA_NONE && r < SQUARE(DEADZONE - HYSTERESIS_MARGIN)))
+ return VK_AREA_NONE;
+
+ projectionDistance = nx * projectionX + ny * projectionY;
+
+ /* Check if we're in the right 90-degree zone */
+ if (CheckValidCurrentArea(mirroredX, mirroredY, mirroredDiag, currentArea)) {
+ /* Check if we're also in the correct 45-degree + margin segment */
+ switch (currentArea) {
+ case VK_AREA_L:
+ case VK_AREA_R:
+ case VK_AREA_U:
+ case VK_AREA_D:
+ if (projectionDistance <= HYSTERESIS_MARGIN) return currentArea;
+ case VK_AREA_LU:
+ case VK_AREA_RU:
+ case VK_AREA_LD:
+ case VK_AREA_RD:
+ if (projectionDistance > -HYSTERESIS_MARGIN) return currentArea;
+ default:
+ WARN("currentArea should not be %d\n", currentArea);
+ }
+ }
+
+ /* At this point we've exited the previous area and need to find the new one */
+ if (mirroredX) {
+ if (mirroredY) {
+ if(projectionDistance > 0) return VK_AREA_LD;
+ } else {
+ if(projectionDistance > 0) return VK_AREA_LU;
+ }
+ if (!mirroredDiag) return VK_AREA_L;
+ } else {
+ if (mirroredY) {
+ if(projectionDistance > 0) return VK_AREA_RD;
+ } else {
+ if(projectionDistance > 0) return VK_AREA_RU;
+ }
+ if (!mirroredDiag) return VK_AREA_R;
+ }
+ return mirroredY ? VK_AREA_D : VK_AREA_U;
+}
+
+
+void xiw_vk_Update(DWORD slot_index, ULONGLONG timestamp, XINPUTW_EVENT_CODE code,
+ const XINPUT_STATE * state, XINPUTW_VK_STATES * vkStates,
+ XINPUTW_KEYSTROKE_QUEUE * keystrokeQueue) {
+ XINPUTW_VK_CONTROLNAME control;
+
+ control = GetControlFromEventCode(code);
+ if (control == (XINPUTW_VK_CONTROLNAME)-1) {
+ WARN("unknown event code %d\n", code);
+ return;
+ }
+
+ if (control >= WVK_BTN_A && control <= WVK_BTN_DPAD_RIGHT) {
+ UpdateButton(slot_index, timestamp, control, BtnIsPressedMask(state->Gamepad.wButtons, control),
+ vkStates, keystrokeQueue);
+ } else if (control == WVK_AXIS_LTRIGGER || control == WVK_AXIS_RTRIGGER) {
+ UpdateButton(slot_index, timestamp, control,
+ TriggerIsPressed(control == WVK_AXIS_LTRIGGER
+ ? state->Gamepad.bLeftTrigger : state->Gamepad.bRightTrigger,
+ vkStates->items[control].area),
+ vkStates, keystrokeQueue);
+ } else if (control == WVK_AXIS_LTHUMB || control == WVK_AXIS_RTHUMB) {
+ UpdateThumb(slot_index, timestamp, control,
+ GetThumbArea(
+ control == WVK_AXIS_LTHUMB ? state->Gamepad.sThumbLX : state->Gamepad.sThumbRX,
+ control == WVK_AXIS_LTHUMB ? state->Gamepad.sThumbLY : state->Gamepad.sThumbRY,
+ control == WVK_AXIS_LTHUMB ? &(vkStates->lThumbIsSquare) : &(vkStates->rThumbIsSquare),
+ vkStates->items[control].area),
+ vkStates, keystrokeQueue);
+ }
+}
+
+void xiw_vk_Repeat(DWORD slot_index, XINPUTW_VK_STATES *vkStates, XINPUTW_KEYSTROKE_QUEUE * keystrokeQueue) {
+ XINPUTW_VK_CONTROLNAME control;
+ ULONGLONG timestamp;
+ XINPUTW_KEYSTROKE keystroke;
+ XINPUTW_VK_STATE * vkState;
+
+ timestamp = GetTickCount64();
+
+ /* Set default keystroke options */
+ memset((void*)&keystroke, 0, sizeof(XINPUTW_KEYSTROKE));
+
+ keystroke.keystroke.Flags = XINPUT_KEYSTROKE_KEYDOWN | XINPUT_KEYSTROKE_REPEAT;
+ keystroke.keystroke.UserIndex = slot_index;
+
+ for (control = WVK_BTN_A; control <= WVK_AXIS_RTHUMB; ++control) {
+ vkState = &(vkStates->items[control]);
+ keystroke.keystroke.VirtualKey = GetVirtualKey(control, vkState->area);
+
+ while (keystroke.keystroke.VirtualKey != 0 && (
+ (!vkState->isRepeat && timestamp - vkState->timestamp > REPEAT_DELAY_MS)
+ || (vkState->isRepeat && timestamp - vkState->timestamp > REPEAT_PERIOD_MS)
+ )) {
+ vkState->timestamp += vkState->isRepeat ? REPEAT_PERIOD_MS : REPEAT_DELAY_MS;
+
+ keystroke.timestamp = vkState->timestamp;
+ xiw_vk_KeystrokeQueuePush(keystrokeQueue, &keystroke);
+
+ vkState->isRepeat = TRUE;
+ }
+ }
+}
diff --git a/dlls/xinput1_3/xinput_core_vkqueue.c b/dlls/xinput1_3/xinput_core_vkqueue.c
new file mode 100644
index 0000000..7371cad
--- /dev/null
+++ b/dlls/xinput1_3/xinput_core_vkqueue.c
@@ -0,0 +1,70 @@
+/*
+ * The Wine project - XInput Joystick Library - VirtualKey queue management
+ *
+ * Copyright 2016 Juan Jose Gonzalez
+ *
+ * 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 "config.h"
+
+#include "wine/debug.h"
+#include "xinput_core.h"
+
+static const int KEYSTROKE_QUEUE_DISCARD_MS = 500;
+
+static const XINPUTW_KEYSTROKE * KeystrokeQueueGetFrontInternal(XINPUTW_KEYSTROKE_QUEUE * queue) {
+ if (queue->head == queue->tail) return NULL;
+ return queue->elements + queue->head;
+}
+
+static void KeystrokeQueueDiscardOldEntries(XINPUTW_KEYSTROKE_QUEUE * queue) {
+ ULONGLONG timestamp;
+ const XINPUTW_KEYSTROKE * front;
+
+ timestamp = GetTickCount64();
+
+ while ((front = KeystrokeQueueGetFrontInternal(queue))
+ && front->timestamp < timestamp - KEYSTROKE_QUEUE_DISCARD_MS) {
+ xiw_vk_KeystrokeQueuePop(queue);
+ }
+}
+
+const XINPUTW_KEYSTROKE * xiw_vk_KeystrokeQueueGetFront(XINPUTW_KEYSTROKE_QUEUE * queue) {
+ KeystrokeQueueDiscardOldEntries(queue);
+ return KeystrokeQueueGetFrontInternal(queue);
+}
+
+void xiw_vk_KeystrokeQueuePop(XINPUTW_KEYSTROKE_QUEUE * queue) {
+ if (queue->head == queue->tail)
+ return;
+ if (++(queue->head) >= KEYSTROKE_QUEUE_SIZE)
+ queue->head = 0;
+}
+
+void xiw_vk_KeystrokeQueuePush(XINPUTW_KEYSTROKE_QUEUE * queue, const XINPUTW_KEYSTROKE * element) {
+ if (queue->tail + 1 == queue->head || (queue->tail + 1 >= KEYSTROKE_QUEUE_SIZE && queue->head == 0)) {
+ /* No more space in the queue, discard the oldest (first) element */
+ xiw_vk_KeystrokeQueuePop(queue);
+ }
+ memcpy((void *)(queue->elements + queue->tail), (void *)element, sizeof(XINPUTW_KEYSTROKE));
+ if(++(queue->tail) >= KEYSTROKE_QUEUE_SIZE)
+ queue->tail = 0;
+}
+
+void xiw_vk_KeystrokeQueueClear(XINPUTW_KEYSTROKE_QUEUE * queue) {
+ queue->head = 0;
+ queue->tail = 0;
+}
diff --git a/dlls/xinput1_3/xinput_util.c b/dlls/xinput1_3/xinput_util.c
new file mode 100644
index 0000000..151aaff
--- /dev/null
+++ b/dlls/xinput1_3/xinput_util.c
@@ -0,0 +1,182 @@
+/*
+ * The Wine project - XInput Joystick Library - utility methods
+ *
+ * Copyright 2016 Juan Jose Gonzalez
+ *
+ * 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 "config.h"
+
+#include "xinput_util.h"
+
+#include "wine/debug.h"
+
+#include "windef.h"
+#include "winbase.h"
+#include "winerror.h"
+#include "winreg.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(xinput);
+
+static XINPUTW_VALUE RangeFindCenter(XINPUTW_VALUE min, XINPUTW_VALUE max) {
+ /* This is a heuristic, but it should work most of the time */
+ if(min == (-max) - 1 || min == -max) return 0;
+ else return (XINPUTW_VALUE)((INT32)min + ((INT32)max + (INT32)min) / 2);
+}
+
+static XINPUTW_VALUE ConvToRange(XINPUTW_VALUE value, XINPUTW_VALUE source_min, XINPUTW_VALUE source_max,
+ XINPUTW_VALUE target_min, XINPUTW_VALUE target_max) {
+ INT32 value_internal, source_range, source_center, target_range, target_center;
+ BOOL below_center;
+
+ source_center = RangeFindCenter(source_min, source_max);
+ target_center = RangeFindCenter(target_min, target_max);
+ below_center = value <= source_center;
+
+ /* account for assymetric ranges (using twos' complement) */
+ value_internal = below_center ? (INT32)source_center - (INT32)value : (INT32)value - (INT32)source_center;
+ source_range = below_center ? (INT32)source_center - (INT32)source_min : (INT32)source_max - (INT32)source_center;
+ target_range = below_center ? (INT32)target_center - (INT32)target_min : (INT32)target_max - (INT32)target_center;
+
+ if (source_range == 0 || target_range == 0) {
+ value_internal = target_center;
+ } else {
+ /* scale and shift the value */
+ value_internal = (value_internal * target_range) / source_range;
+ value_internal = below_center ? target_center - value_internal : target_center + value_internal;
+ }
+ TRACE("value %d, source: [%d, %d], target: [%d, %d], result: %d\n", value, source_min, source_max,
+ target_min, target_max, (XINPUTW_VALUE)value_internal);
+ return (XINPUTW_VALUE)value_internal;
+}
+
+static BYTE GetRangeBitCount(XINPUTW_VALUE min, XINPUTW_VALUE max) {
+ BYTE result;
+ WORD range;
+
+ result = 0;
+ range = (WORD)((INT32)max - (INT32)min);
+
+ while (range != 0) {
+ ++result;
+ range = range >> 1;
+ }
+
+ return result;
+}
+
+
+XINPUTW_VALUE xiw_util_ConvToXIWValue(XINPUTW_VALUE value, XINPUTW_VALUE range_min, XINPUTW_VALUE range_max) {
+ return ConvToRange(value, range_min, range_max, XINPUTW_VAL_MIN, XINPUTW_VAL_MAX);
+}
+
+XINPUTW_VALUE xiw_util_ConvFromXIWValue(XINPUTW_VALUE value, XINPUTW_VALUE range_min, XINPUTW_VALUE range_max) {
+ return ConvToRange(value, XINPUTW_VAL_MIN, XINPUTW_VAL_MAX, range_min, range_max);
+}
+
+void xiw_util_SetCapabilitiesBtn(WORD *buttons, XINPUTW_EVENT_CODE code, BOOL value) {
+ WORD mask;
+
+ if (code < WINE_BTN_A || code > WINE_BTN_DPAD_RIGHT) return;
+ mask = 1ul << code;
+ *buttons = (*buttons & ~mask) | (value ? mask : 0);
+}
+
+void xiw_util_SetCapabilitiesAxis(BYTE *axes, XINPUTW_EVENT_CODE code, XINPUTW_VALUE min, XINPUTW_VALUE max) {
+ if (code < WINE_AXIS_LTRIGGER || code > WINE_AXIS_RTHUMB_Y) return;
+ axes[code - WINE_AXIS_LTRIGGER] = GetRangeBitCount(min, max);
+}
+
+
+/*
+ * Taken and adapted from dlls/dinput/device.c
+ */
+BOOL xiw_util_OpenCfgKeys(HKEY *defkey, HKEY *appkey, const char *subkey_path) {
+ char buffer[MAX_PATH+16];
+ DWORD len;
+
+ *appkey = 0;
+
+ /* Wine registry key: HKCU\Software\Wine\XInput */
+ strcpy(buffer, "Software\\Wine\\XInput");
+ if (subkey_path != NULL) {
+ if (subkey_path[0] == '\\') {
+ strcat(buffer, subkey_path);
+ } else {
+ strcat(buffer, "\\");
+ strcat(buffer, subkey_path);
+ }
+ }
+ if (RegOpenKeyA(HKEY_CURRENT_USER, buffer, defkey) != ERROR_SUCCESS)
+ *defkey = 0;
+
+ len = GetModuleFileNameA(0, buffer, MAX_PATH);
+ if (len && len < MAX_PATH) {
+ HKEY tmpkey;
+
+ /* Wine registry key: HKCU\Software\Wine\AppDefaults\app.exe\XInput */
+ if (RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\AppDefaults", &tmpkey) == ERROR_SUCCESS) {
+ char *p, *appname = buffer;
+ if ((p = strrchr(appname, '/'))) appname = p + 1;
+ if ((p = strrchr(appname, '\\'))) appname = p + 1;
+ strcat(appname, "\\XInput");
+ if (subkey_path != NULL) {
+ if (subkey_path[0] == '\\') {
+ strcat(buffer, subkey_path);
+ } else {
+ strcat(buffer, "\\");
+ strcat(buffer, subkey_path);
+ }
+ }
+
+ if (RegOpenKeyA(tmpkey, appname, appkey) != ERROR_SUCCESS)
+ *appkey = 0;
+ RegCloseKey(tmpkey);
+ }
+ }
+
+ return *defkey || *appkey;
+}
+
+/*
+ * Taken and adapted from dlls/dinput/device.c
+ */
+LONG xiw_util_GetCfgValueGeneric(HKEY defkey, HKEY appkey, const char *name, BYTE *buffer, DWORD size, DWORD *type) {
+ LONG rc;
+
+ /* Try to load the app-specific key */
+ if (appkey) {
+ /* If the key was not found, try the default. On any other return code, return that */
+ if ((rc = RegQueryValueExA(appkey, name, NULL, type, (LPBYTE)buffer, &size)) != ERROR_FILE_NOT_FOUND)
+ return rc;
+ }
+
+ if (defkey)
+ return RegQueryValueExA(defkey, name, NULL, type, (LPBYTE)buffer, &size);
+
+ return ERROR_FILE_NOT_FOUND;
+}
+
+DWORD xiw_util_GetCfgValueDW(HKEY defkey, HKEY appkey, const char *name, DWORD defaultValue) {
+ DWORD key_type, value;
+ LONG rc;
+ rc = xiw_util_GetCfgValueGeneric(defkey, appkey, "ValueToButtonEpsilon",
+ (BYTE *)&value, sizeof(DWORD), &key_type);
+ if (rc == ERROR_SUCCESS && key_type == REG_DWORD)
+ return value;
+
+ return defaultValue;
+}
diff --git a/dlls/xinput1_3/xinput_util.h b/dlls/xinput1_3/xinput_util.h
new file mode 100644
index 0000000..813f17b
--- /dev/null
+++ b/dlls/xinput1_3/xinput_util.h
@@ -0,0 +1,71 @@
+/*
+ * The Wine project - XInput Joystick Library - utility methods
+ *
+ * Copyright 2016 Juan Jose Gonzalez
+ *
+ * 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
+ */
+
+#ifndef __WINE_DLLS_XINPUT_UTIL_H
+#define __WINE_DLLS_XINPUT_UTIL_H
+
+#include "xinput_backend.h"
+
+XINPUTW_VALUE xiw_util_ConvToXIWValue(XINPUTW_VALUE value, XINPUTW_VALUE range_min, XINPUTW_VALUE range_max);
+XINPUTW_VALUE xiw_util_ConvFromXIWValue(XINPUTW_VALUE value, XINPUTW_VALUE range_min, XINPUTW_VALUE range_max);
+
+void xiw_util_SetCapabilitiesAxis(uint8_t *axes, XINPUTW_EVENT_CODE code, XINPUTW_VALUE min, XINPUTW_VALUE max);
+void xiw_util_SetCapabilitiesBtn(WORD *buttons, XINPUTW_EVENT_CODE code, BOOL value);
+
+
+/*
+ * Get the default and the app-specific config keys.
+ *
+ * defkey (out): The default (ie. global) xinput config key
+ * appkey (out): The app-specific xinput config key
+ * subkey_path: Fetch a specific key under the XInput root keys. Can be set to NULL
+ *
+ * Taken and adapted from dlls/dinput/device.c
+ */
+BOOL xiw_util_OpenCfgKeys(HKEY *defkey, HKEY *appkey, const char *subkey_path);
+
+/*
+ * Get a config value from an app-specific registry key with a default fallback.
+ *
+ * defkey: The default root key
+ * appkey: The app-specific root key
+ * name: The value name under the specified keys
+ * buffer (out): The buffer where the value will be saved
+ * size: Specifies the buffer size
+ * type (out): Returns the found config value type according to the RegQueryValueEx spec
+ *
+ * Returns: ERROR_SUCCESS on success
+ */
+LONG xiw_util_GetCfgValueGeneric(HKEY defkey, HKEY appkey, const char *name, BYTE *buffer, DWORD size, DWORD *type);
+
+
+/*
+ * Get a DWORD config value from an app-specific registry key or default key, or set it to a default value.
+ *
+ * defkey: The default root key
+ * appkey: The app-specific root key
+ * name: The value name under the specified keys
+ *
+ * Returns: The requested value or the default if it could not be found
+ */
+DWORD xiw_util_GetCfgValueDW(HKEY defkey, HKEY appkey, const char *name, DWORD defaultValue);
+
+
+#endif /* __WINE_DLLS_XINPUT_UTIL_H */
--
2.7.1
More information about the wine-patches
mailing list