[PATCH 3/3] xinput1_3: Read the controller state in the update thread.

Rémi Bernon rbernon at codeweavers.com
Fri Aug 6 04:05:58 CDT 2021


Signed-off-by: Rémi Bernon <rbernon at codeweavers.com>
---
 dlls/xinput1_3/hid.c            | 230 +++++++++++++++++---------------
 dlls/xinput1_3/xinput_main.c    |  10 +-
 dlls/xinput1_3/xinput_private.h |   1 -
 3 files changed, 123 insertions(+), 118 deletions(-)

diff --git a/dlls/xinput1_3/hid.c b/dlls/xinput1_3/hid.c
index 6aacf0ac9cd..79fa2104b1f 100644
--- a/dlls/xinput1_3/hid.c
+++ b/dlls/xinput1_3/hid.c
@@ -60,10 +60,12 @@ struct hid_platform_private {
     HIDP_CAPS caps;
 
     HANDLE device;
+    HANDLE read_event;
+    OVERLAPPED read_ovl;
     WCHAR *device_path;
     BOOL enabled;
 
-    char *input_report_buf[2];
+    char *input_report_buf;
     char *output_report_buf;
 
     struct axis_info lx, ly, ltrigger, rx, ry, rtrigger;
@@ -71,6 +73,7 @@ struct hid_platform_private {
 
 static HANDLE stop_event;
 static HANDLE done_event;
+static HANDLE update_event;
 
 static BOOL find_opened_device(SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail, int *free_slot)
 {
@@ -120,7 +123,8 @@ static void update_controller_list(void)
         if (i == XUSER_MAX_COUNT) break; /* no more slots */
 
         device = CreateFileW(detail->DevicePath, GENERIC_READ | GENERIC_WRITE,
-                             FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
+                             FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+                             FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL);
         if (device == INVALID_HANDLE_VALUE) continue;
 
         preparsed = NULL;
@@ -145,17 +149,35 @@ static void update_controller_list(void)
     SetupDiDestroyDeviceInfoList(set);
 }
 
+static void read_controller_state(xinput_controller *device);
+
 static DWORD WINAPI hid_update_thread_proc(void *param)
 {
-    HANDLE events[1];
-    DWORD count, ret = WAIT_TIMEOUT;
+    struct hid_platform_private *private;
+    xinput_controller *devices[XUSER_MAX_COUNT + 2];
+    HANDLE events[XUSER_MAX_COUNT + 2];
+    DWORD i, count = 2, ret = WAIT_TIMEOUT;
 
     do
     {
         EnterCriticalSection(&xinput_crit);
         if (ret == WAIT_TIMEOUT) update_controller_list();
+        if (ret < count - 2) read_controller_state(devices[ret]);
 
         count = 0;
+        for (i = 0; i < XUSER_MAX_COUNT; ++i)
+        {
+            if (!(private = controllers[i].platform_private)) continue;
+            EnterCriticalSection(&controllers[i].crit);
+            if (private->enabled)
+            {
+                devices[count] = controllers + i;
+                events[count] = private->read_event;
+                count++;
+            }
+            LeaveCriticalSection(&controllers[i].crit);
+        }
+        events[count++] = update_event;
         events[count++] = stop_event;
         LeaveCriticalSection(&xinput_crit);
     }
@@ -176,6 +198,9 @@ static BOOL WINAPI start_update_thread( INIT_ONCE *once, void *param, void **con
     done_event = CreateEventA(NULL, FALSE, FALSE, NULL);
     if (!done_event) ERR("failed to create stop event, error %u\n", GetLastError());
 
+    update_event = CreateEventA(NULL, FALSE, FALSE, NULL);
+    if (!update_event) ERR("failed to create update event, error %u\n", GetLastError());
+
     thread = CreateThread(NULL, 0, hid_update_thread_proc, NULL, 0, NULL);
     if (!thread) ERR("failed to create update thread, error %u\n", GetLastError());
     CloseHandle(thread);
@@ -287,36 +312,39 @@ static BOOL init_controller(xinput_controller *controller, PHIDP_PREPARSED_DATA
 {
     size_t size;
     struct hid_platform_private *private;
+    HANDLE event = NULL;
 
     if (!(private = calloc(1, sizeof(struct hid_platform_private)))) return FALSE;
     private->caps = *caps;
     if (!VerifyGamepad(ppd, &controller->caps, private)) goto failed;
+    if (!(event = CreateEventA(NULL, FALSE, FALSE, NULL))) goto failed;
 
     TRACE("Found gamepad %s\n", debugstr_w(device_path));
 
     private->ppd = ppd;
+    private->read_event = event;
     private->device = device;
-    if (!(private->input_report_buf[0] = calloc(1, private->caps.InputReportByteLength))) goto failed;
-    if (!(private->input_report_buf[1] = calloc(1, private->caps.InputReportByteLength))) goto failed;
+    if (!(private->input_report_buf = calloc(1, private->caps.InputReportByteLength))) goto failed;
     if (!(private->output_report_buf = calloc(1, private->caps.OutputReportByteLength))) goto failed;
     size = (lstrlenW(device_path) + 1) * sizeof(WCHAR);
     if (!(private->device_path = malloc(size))) goto failed;
     memcpy(private->device_path, device_path, size);
-    private->enabled = TRUE;
+    private->enabled = FALSE;
 
     memset(&controller->state, 0, sizeof(controller->state));
     memset(&controller->vibration, 0, sizeof(controller->vibration));
 
     EnterCriticalSection(&controller->crit);
     controller->platform_private = private;
+    HID_enable(controller, TRUE);
     LeaveCriticalSection(&controller->crit);
     return TRUE;
 
 failed:
     free(private->device_path);
-    free(private->input_report_buf[0]);
-    free(private->input_report_buf[1]);
+    free(private->input_report_buf);
     free(private->output_report_buf);
+    CloseHandle(event);
     free(private);
     return FALSE;
 }
@@ -335,11 +363,11 @@ static void remove_gamepad(xinput_controller *device)
     {
         struct hid_platform_private *private = device->platform_private;
 
+        HID_enable(device, FALSE);
         device->platform_private = NULL;
 
         CloseHandle(private->device);
-        free(private->input_report_buf[0]);
-        free(private->input_report_buf[1]);
+        free(private->input_report_buf);
         free(private->output_report_buf);
         free(private->device_path);
         HidD_FreePreparsedData(private->ppd);
@@ -358,6 +386,7 @@ void HID_stop_update_thread(void)
 
     CloseHandle(stop_event);
     CloseHandle(done_event);
+    CloseHandle(update_event);
 
     for (i = 0; i < XUSER_MAX_COUNT; i++)
         remove_gamepad(&controllers[i]);
@@ -373,125 +402,100 @@ static BYTE scale_byte(LONG value, const struct axis_info *axis)
     return (((ULONGLONG)(value - axis->min)) * 0xff) / axis->range;
 }
 
-void HID_update_state(xinput_controller *device, XINPUT_STATE *state)
+static void read_controller_state(xinput_controller *device)
 {
     struct hid_platform_private *private = device->platform_private;
-    int i;
-    char **report_buf = private->input_report_buf, *tmp;
-    ULONG report_len = private->caps.InputReportByteLength;
+    ULONG read_len, report_len = private->caps.InputReportByteLength;
+    char *report_buf = private->input_report_buf;
+    XINPUT_STATE state;
     NTSTATUS status;
-
     USAGE buttons[11];
     ULONG button_length, hat_value;
-    LONG value;
+    LONG i, value;
 
-    if (!private->enabled)
+    if (!GetOverlappedResult(private->device, &private->read_ovl, &read_len, TRUE))
+    {
+        if (GetLastError() == ERROR_OPERATION_ABORTED) return;
+        if (GetLastError() == ERROR_ACCESS_DENIED || GetLastError() == ERROR_INVALID_HANDLE) remove_gamepad(device);
+        else ERR("Failed to read input report, GetOverlappedResult failed with error %u\n", GetLastError());
         return;
+    }
+
+    button_length = ARRAY_SIZE(buttons);
+    status = HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, buttons, &button_length, private->ppd, report_buf, report_len);
+    if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsages HID_USAGE_PAGE_BUTTON returned %#x\n", status);
 
-    if (!HidD_GetInputReport(private->device, report_buf[0], report_len))
+    state.Gamepad.wButtons = 0;
+    for (i = 0; i < button_length; i++)
     {
-        if (GetLastError() == ERROR_ACCESS_DENIED || GetLastError() == ERROR_INVALID_HANDLE)
+        switch (buttons[i])
         {
-            EnterCriticalSection(&xinput_crit);
-            remove_gamepad(device);
-            LeaveCriticalSection(&xinput_crit);
+        case 1: state.Gamepad.wButtons |= XINPUT_GAMEPAD_A; break;
+        case 2: state.Gamepad.wButtons |= XINPUT_GAMEPAD_B; break;
+        case 3: state.Gamepad.wButtons |= XINPUT_GAMEPAD_X; break;
+        case 4: state.Gamepad.wButtons |= XINPUT_GAMEPAD_Y; break;
+        case 5: state.Gamepad.wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER; break;
+        case 6: state.Gamepad.wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER; break;
+        case 7: state.Gamepad.wButtons |= XINPUT_GAMEPAD_BACK; break;
+        case 8: state.Gamepad.wButtons |= XINPUT_GAMEPAD_START; break;
+        case 9: state.Gamepad.wButtons |= XINPUT_GAMEPAD_LEFT_THUMB; break;
+        case 10: state.Gamepad.wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB; break;
+        case 11: state.Gamepad.wButtons |= XINPUT_GAMEPAD_GUIDE; break;
         }
-        else ERR("Failed to get input report, HidD_GetInputReport failed with error %u\n", GetLastError());
-        return;
     }
 
-    if (memcmp(report_buf[0], report_buf[1], report_len) != 0)
+    status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_HATSWITCH, &hat_value, private->ppd, report_buf, report_len);
+    if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_HATSWITCH returned %#x\n", status);
+    else switch (hat_value)
     {
-        device->state.dwPacketNumber++;
-        button_length = ARRAY_SIZE(buttons);
-        status = HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, buttons, &button_length, private->ppd, report_buf[0], report_len);
-        if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsages HID_USAGE_PAGE_BUTTON returned %#x\n", status);
-
-        device->state.Gamepad.wButtons = 0;
-        for (i = 0; i < button_length; i++)
-        {
-            switch (buttons[i])
-            {
-                case 1: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_A; break;
-                case 2: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_B; break;
-                case 3: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_X; break;
-                case 4: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_Y; break;
-                case 5: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER; break;
-                case 6: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER; break;
-                case 7: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_BACK; break;
-                case 8: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_START; break;
-                case 9: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_LEFT_THUMB; break;
-                case 10: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_RIGHT_THUMB; break;
-                case 11: device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_GUIDE; break;
-            }
-        }
+    /* 8 1 2
+     * 7 0 3
+     * 6 5 4 */
+    case 0: break;
+    case 1: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_UP; break;
+    case 2: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_UP | XINPUT_GAMEPAD_DPAD_RIGHT; break;
+    case 3: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT; break;
+    case 4: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT | XINPUT_GAMEPAD_DPAD_DOWN; break;
+    case 5: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_DOWN; break;
+    case 6: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_DOWN | XINPUT_GAMEPAD_DPAD_LEFT; break;
+    case 7: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_LEFT; break;
+    case 8: state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_UP; break;
+    }
 
-        status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_HATSWITCH, &hat_value, private->ppd, report_buf[0], report_len);
-        if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_HATSWITCH returned %#x\n", status);
-        else
-        {
-            switch(hat_value){
-                /* 8 1 2
-                 * 7 0 3
-                 * 6 5 4 */
-                case 0:
-                    break;
-                case 1:
-                    device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_UP;
-                    break;
-                case 2:
-                    device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_UP | XINPUT_GAMEPAD_DPAD_RIGHT;
-                    break;
-                case 3:
-                    device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT;
-                    break;
-                case 4:
-                    device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT | XINPUT_GAMEPAD_DPAD_DOWN;
-                    break;
-                case 5:
-                    device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_DOWN;
-                    break;
-                case 6:
-                    device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_DOWN | XINPUT_GAMEPAD_DPAD_LEFT;
-                    break;
-                case 7:
-                    device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_LEFT;
-                    break;
-                case 8:
-                    device->state.Gamepad.wButtons |= XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_UP;
-                    break;
-            }
-        }
+    status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_X, &value, private->ppd, report_buf, report_len);
+    if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_X returned %#x\n", status);
+    else state.Gamepad.sThumbLX = scale_short(value, &private->lx);
 
-        status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_X, &value, private->ppd, report_buf[0], report_len);
-        if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_X returned %#x\n", status);
-        else device->state.Gamepad.sThumbLX = scale_short(value, &private->lx);
+    status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Y, &value, private->ppd, report_buf, report_len);
+    if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_Y returned %#x\n", status);
+    else state.Gamepad.sThumbLY = -scale_short(value, &private->ly) - 1;
 
-        status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Y, &value, private->ppd, report_buf[0], report_len);
-        if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_Y returned %#x\n", status);
-        else device->state.Gamepad.sThumbLY = -scale_short(value, &private->ly) - 1;
+    status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RX, &value, private->ppd, report_buf, report_len);
+    if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RX returned %#x\n", status);
+    else state.Gamepad.sThumbRX = scale_short(value, &private->rx);
 
-        status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RX, &value, private->ppd, report_buf[0], report_len);
-        if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RX returned %#x\n", status);
-        else device->state.Gamepad.sThumbRX = scale_short(value, &private->rx);
+    status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RY, &value, private->ppd, report_buf, report_len);
+    if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RY returned %#x\n", status);
+    else state.Gamepad.sThumbRY = -scale_short(value, &private->ry) - 1;
 
-        status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RY, &value, private->ppd, report_buf[0], report_len);
-        if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RY returned %#x\n", status);
-        else device->state.Gamepad.sThumbRY = -scale_short(value, &private->ry) - 1;
+    status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RZ, &value, private->ppd, report_buf, report_len);
+    if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RZ returned %#x\n", status);
+    else state.Gamepad.bRightTrigger = scale_byte(value, &private->rtrigger);
 
-        status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RZ, &value, private->ppd, report_buf[0], report_len);
-        if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RZ returned %#x\n", status);
-        else device->state.Gamepad.bRightTrigger = scale_byte(value, &private->rtrigger);
+    status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Z, &value, private->ppd, report_buf, report_len);
+    if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_Z returned %#x\n", status);
+    else state.Gamepad.bLeftTrigger = scale_byte(value, &private->ltrigger);
 
-        status = HidP_GetScaledUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Z, &value, private->ppd, report_buf[0], report_len);
-        if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetScaledUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_Z returned %#x\n", status);
-        else device->state.Gamepad.bLeftTrigger = scale_byte(value, &private->ltrigger);
+    EnterCriticalSection(&device->crit);
+    if (private->enabled)
+    {
+        state.dwPacketNumber = device->state.dwPacketNumber + 1;
+        device->state = state;
+        memset(&private->read_ovl, 0, sizeof(private->read_ovl));
+        private->read_ovl.hEvent = private->read_event;
+        ReadFile(private->device, private->input_report_buf, private->caps.InputReportByteLength, NULL, &private->read_ovl);
     }
-
-    tmp = report_buf[0];
-    report_buf[0] = report_buf[1];
-    report_buf[1] = tmp;
-    memcpy(state, &device->state, sizeof(*state));
+    LeaveCriticalSection(&device->crit);
 }
 
 DWORD HID_set_state(xinput_controller* device, XINPUT_VIBRATION* state)
@@ -545,5 +549,15 @@ void HID_enable(xinput_controller* device, BOOL enable)
         }
     }
 
+    if (private->enabled && !enable)
+        CancelIoEx(private->device, &private->read_ovl);
+    else if (!private->enabled && enable)
+    {
+        memset(&private->read_ovl, 0, sizeof(private->read_ovl));
+        private->read_ovl.hEvent = private->read_event;
+        ReadFile(private->device, private->input_report_buf, private->caps.InputReportByteLength, NULL, &private->read_ovl);
+    }
+
+    if (private->enabled != enable) SetEvent(update_event);
     private->enabled = enable;
 }
diff --git a/dlls/xinput1_3/xinput_main.c b/dlls/xinput1_3/xinput_main.c
index 56a5802fc6a..cac5af74b8d 100644
--- a/dlls/xinput1_3/xinput_main.c
+++ b/dlls/xinput1_3/xinput_main.c
@@ -164,15 +164,7 @@ static DWORD xinput_get_state(DWORD index, XINPUT_STATE *state)
     if (!verify_and_lock_device(&controllers[index]))
         return ERROR_DEVICE_NOT_CONNECTED;
 
-    HID_update_state(&controllers[index], state);
-
-    if (!controllers[index].platform_private)
-    {
-        /* update_state may have disconnected the controller */
-        unlock_device(&controllers[index]);
-        return ERROR_DEVICE_NOT_CONNECTED;
-    }
-
+    *state = controllers[index].state;
     unlock_device(&controllers[index]);
 
     return ERROR_SUCCESS;
diff --git a/dlls/xinput1_3/xinput_private.h b/dlls/xinput1_3/xinput_private.h
index b2bd8a1ed57..96ec1fc88ad 100644
--- a/dlls/xinput1_3/xinput_private.h
+++ b/dlls/xinput1_3/xinput_private.h
@@ -32,6 +32,5 @@ extern xinput_controller controllers[XUSER_MAX_COUNT];
 
 void HID_start_update_thread(void) DECLSPEC_HIDDEN;
 void HID_stop_update_thread(void) DECLSPEC_HIDDEN;
-void HID_update_state(xinput_controller* device, XINPUT_STATE *state) DECLSPEC_HIDDEN;
 DWORD HID_set_state(xinput_controller* device, XINPUT_VIBRATION* state) DECLSPEC_HIDDEN;
 void HID_enable(xinput_controller* device, BOOL enable) DECLSPEC_HIDDEN;
-- 
2.32.0




More information about the wine-devel mailing list