[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