[1/2] xinput: Implemented core.

Juan Jose Gonzalez juanj.gh at gmail.com
Sun Feb 14 04:15:13 CST 2016


From: Juan Gonzalez <juanj.gh at gmail.com>

Adds a core implementation for XInput which can be used with different
backends.

Signed-off-by: Juan Jose Gonzalez <juanj.gh at gmail.com>
---
 dlls/xinput1_3/Makefile.in      |   5 +-
 dlls/xinput1_3/xinput1_3_main.c | 789 ++++++++++++++++++++++++++++++++++++++--
 dlls/xinput1_3/xinput_backend.h | 188 ++++++++++
 dlls/xinput1_3/xinput_util.c    | 182 +++++++++
 dlls/xinput1_3/xinput_util.h    |  71 ++++
 5 files changed, 1200 insertions(+), 35 deletions(-)
 create mode 100644 dlls/xinput1_3/xinput_backend.h
 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..f65c295 100644
--- a/dlls/xinput1_3/Makefile.in
+++ b/dlls/xinput1_3/Makefile.in
@@ -1,7 +1,10 @@
 MODULE    = xinput1_3.dll
 IMPORTLIB = xinput
+IMPORTS   = uuid advapi32
 
 C_SRCS = \
-	xinput1_3_main.c
+	xinput_util.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..832803e 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,764 @@
  */
 
 #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"
+
 WINE_DEFAULT_DEBUG_CHANNEL(xinput);
 
+#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;
+
+typedef struct _XINPUTW_SLOT {
+    DWORD slot_index;
+
+    XINPUT_STATE state;
+    BOOL state_has_changes;
+    XINPUT_VIBRATION rumble_state;
+    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 XINPUTW_KEYSTROKE * KeystrokeQueueGetFront(XINPUTW_KEYSTROKE_QUEUE * queue) {
+    if (queue->head == queue->tail) return NULL;
+    return queue->elements + queue->head;
+}
+
+static void KeystrokeQueuePop(XINPUTW_KEYSTROKE_QUEUE * queue) {
+    if (queue->head == queue->tail)
+        return;
+    if (++queue->head >= KEYSTROKE_QUEUE_SIZE)
+        queue->head = 0;
+}
+
+static void 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 */
+        KeystrokeQueuePop(queue);
+    }
+    memcpy((void *)(queue->elements + queue->tail), (void *)element, sizeof(element));
+    if(++queue->tail >= KEYSTROKE_QUEUE_SIZE)
+        queue->tail = 0;
+}
+
+static void KeystrokeQueueClear(XINPUTW_KEYSTROKE_QUEUE * queue) {
+    queue->head = 0;
+    queue->tail = 0;
+}
+
+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));
+    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 PushKeystroke(DWORD slot, ULONGLONG timestamp, WORD virtual_key, BOOL pressed) {
+    XINPUTW_KEYSTROKE keystroke;
+
+    TRACE("slot: %hu, keystroke: %hu\n", slot, virtual_key);
+    if (pressed)
+        keystroke.keystroke.Flags = XINPUT_KEYSTROKE_KEYDOWN;
+    else
+        keystroke.keystroke.Flags = XINPUT_KEYSTROKE_KEYUP;
+
+    keystroke.timestamp = timestamp;
+    keystroke.keystroke.VirtualKey = virtual_key;
+    keystroke.keystroke.Unicode = 0;
+    keystroke.keystroke.UserIndex = slot;
+    keystroke.keystroke.HidCode = 0;
+
+    if (slots[slot].backend != NULL)
+        KeystrokeQueuePush(&slots[slot].keystrokes, &keystroke);
+}
+
+static void ParseAxisEvent(DWORD slot, const XINPUTW_EVENT *event) {
+    XINPUTW_VALUE target_value;
+    BYTE *trigger_val;
+    SHORT *thumb_val;
+    WORD virtual_key;
+
+    TRACE("slot %d, input code %d, value %d\n", slot, event->code, event->value);
+
+    trigger_val = NULL;
+    thumb_val = NULL;
+    virtual_key = 0xffff;
+
+    /* TODO: Add VK_PAD_?THUMB_{UP,UPRIGHT,RIGHT,DOWNRIGHT,DOWN,...} virtual keys */
+    switch (event->code) {
+        case WINE_AXIS_LTRIGGER:
+            trigger_val = &slots[slot].state.Gamepad.bLeftTrigger;
+            virtual_key = VK_PAD_LTRIGGER;
+            break;
+        case WINE_AXIS_RTRIGGER:
+            trigger_val = &slots[slot].state.Gamepad.bRightTrigger;
+            virtual_key = VK_PAD_RTRIGGER;
+            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) {
+            /* Post a new event for XInputGetKeystroke */
+            /* TODO: Add hysteresis (ie. only push the KEYUP event if the last event 
+             * was KEYDOWN and the value falls below 1/3, and vice versa) */
+            if (*trigger_val < 128 && target_value >= 128)
+                PushKeystroke(slot, event->timestamp, virtual_key, TRUE);
+            else if (*trigger_val >= 128 && target_value < 128)
+                PushKeystroke(slot, event->timestamp, virtual_key, FALSE);
+
+            /* Update the state for XInputGetState */
+            *trigger_val = (BYTE)target_value;
+            slots[slot].state_has_changes = TRUE;
+        }
+    } 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) {
+            /* Post a new event for XInputGetKeystroke */
+            if (virtual_key != 0xffff) {
+                /* TODO: Add hysteresis */
+                if (*thumb_val < 0 && target_value >= 0)
+                    PushKeystroke(slot, event->timestamp, virtual_key, TRUE);
+                else if (*thumb_val >= 0 && target_value < 0)
+                    PushKeystroke(slot, event->timestamp, virtual_key, FALSE);
+            }
+
+            /* Update the state for XInputGetState */
+            *thumb_val = (WORD)target_value;
+            slots[slot].state_has_changes = TRUE;
+        }
+    }
+}
+
+static void ParseBtnEvent(DWORD slot, const XINPUTW_EVENT *event) {
+    WORD bit_mask;
+    WORD virtual_key;
+
+    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;
+            virtual_key = VK_PAD_A;
+            break;
+        case WINE_BTN_B:
+            bit_mask = XINPUT_GAMEPAD_B;
+            virtual_key = VK_PAD_B;
+            break;
+        case WINE_BTN_X:
+            bit_mask = XINPUT_GAMEPAD_X;
+            virtual_key = VK_PAD_X;
+            break;
+        case WINE_BTN_Y:
+            bit_mask = XINPUT_GAMEPAD_Y;
+            virtual_key = VK_PAD_Y;
+            break;
+        case WINE_BTN_START:
+            bit_mask = XINPUT_GAMEPAD_START;
+            virtual_key = VK_PAD_START;
+            break;
+        case WINE_BTN_BACK:
+            bit_mask = XINPUT_GAMEPAD_BACK;
+            virtual_key = VK_PAD_BACK;
+            break;
+        case WINE_BTN_LSHOULDER:
+            bit_mask = XINPUT_GAMEPAD_LEFT_SHOULDER;
+            virtual_key = VK_PAD_LSHOULDER;
+            break;
+        case WINE_BTN_RSHOULDER:
+            bit_mask = XINPUT_GAMEPAD_RIGHT_SHOULDER;
+            virtual_key = VK_PAD_RSHOULDER;
+            break;
+        case WINE_BTN_LTHUMB:
+            bit_mask = XINPUT_GAMEPAD_LEFT_THUMB;
+            virtual_key = VK_PAD_LTHUMB_PRESS;
+            break;
+        case WINE_BTN_RTHUMB:
+            bit_mask = XINPUT_GAMEPAD_RIGHT_THUMB;
+            virtual_key = VK_PAD_RTHUMB_PRESS;
+            break;
+        case WINE_BTN_DPAD_UP:
+            bit_mask = XINPUT_GAMEPAD_DPAD_UP;
+            virtual_key = VK_PAD_DPAD_UP;
+            break;
+        case WINE_BTN_DPAD_DOWN:
+            bit_mask = XINPUT_GAMEPAD_DPAD_DOWN;
+            virtual_key = VK_PAD_DPAD_DOWN;
+            break;
+        case WINE_BTN_DPAD_LEFT:
+            bit_mask = XINPUT_GAMEPAD_DPAD_LEFT;
+            virtual_key = VK_PAD_DPAD_LEFT;
+            break;
+        case WINE_BTN_DPAD_RIGHT:
+            bit_mask = XINPUT_GAMEPAD_DPAD_RIGHT;
+            virtual_key = VK_PAD_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) {
+        /* Post a new event for XInputGetKeystroke */
+        PushKeystroke(slot, event->timestamp, virtual_key, btn_is_pressed);
+
+        /* 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;
+    }
+}
+
+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;
+    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;
+        }
+
+        if ((keystroke = KeystrokeQueueGetFront(&slots[dwUserIndex].keystrokes)) != NULL) {
+            memcpy((void *)pKeystroke, (void *)&keystroke->keystroke, sizeof(pKeystroke));
+            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++) {
+
+            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;
+            }
+
+            if ((keystroke = KeystrokeQueueGetFront(&slots[i].keystrokes)) != NULL) {
+                if (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) {
+                        LeaveCriticalSection(&slots[earliest_event_slot].cs_status);
+                        LeaveCriticalSection(&slots[earliest_event_slot].cs_device);
+                    }
+
+                    earliest_event_slot = i;
+                    earliest_timestamp = keystroke->timestamp;
+                } else {
+                    LeaveCriticalSection(&slots[i].cs_status);
+                    LeaveCriticalSection(&slots[i].cs_device);
+                }
+            }
+        }
+
+        if (earliest_event_slot != -1) {
+            keystroke = KeystrokeQueueGetFront(&slots[earliest_event_slot].keystrokes);
+            memcpy((void *)pKeystroke, (void *)&keystroke->keystroke, sizeof(keystroke->keystroke));
+            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 +786,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 +809,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..d260b9c
--- /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_CORE_H
+#define __WINE_DLLS_XINPUT_CORE_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_CORE_H */
\ No newline at end of file
diff --git a/dlls/xinput1_3/xinput_util.c b/dlls/xinput1_3/xinput_util.c
new file mode 100644
index 0000000..60ccdfc
--- /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..36d830b
--- /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_UTILS_H
+#define __WINE_DLLS_XINPUT_UTILS_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_UTILS_H */
\ No newline at end of file
-- 
2.7.1




More information about the wine-patches mailing list