This adds tests to validate the bogus HID devices exposed native XInput
driver. Interactive tests are also included to validate the HID report
values, and more specifically the weird combined triggers, which is
currently not implemented correctly in Wine.
Some third-party libraries, such as SDL, are known to rely on this bogus
HID devices and hardcode the expected report structure.
This has been tested on Windows 10 with XBox 360 controllers as well
as Xbox One S controllers.
The latter are detected as 045e:02ea on Linux, but as 045e:02ff on
Windows for some reason, and their descriptor seems a little bit
different.
Their exposed HID device also doesn't seem to work at all and ReadFile
never succeeds, so the interactive tests are skipped for these devices.
Signed-off-by: Rémi Bernon <rbernon(a)codeweavers.com>
---
dlls/xinput1_3/tests/Makefile.in | 2 +-
dlls/xinput1_3/tests/xinput.c | 516 +++++++++++++++++++++++++++++++
2 files changed, 517 insertions(+), 1 deletion(-)
diff --git a/dlls/xinput1_3/tests/Makefile.in b/dlls/xinput1_3/tests/Makefile.in
index 718b47f21ba..260a8a634e4 100644
--- a/dlls/xinput1_3/tests/Makefile.in
+++ b/dlls/xinput1_3/tests/Makefile.in
@@ -1,5 +1,5 @@
TESTDLL = xinput1_3.dll
-IMPORTS = user32
+IMPORTS = user32 hid setupapi
C_SRCS = \
xinput.c
diff --git a/dlls/xinput1_3/tests/xinput.c b/dlls/xinput1_3/tests/xinput.c
index a6e2107446c..41769e23ec9 100644
--- a/dlls/xinput1_3/tests/xinput.c
+++ b/dlls/xinput1_3/tests/xinput.c
@@ -20,7 +20,12 @@
#include <windows.h>
#include <stdio.h>
+#include "winioctl.h"
#include "xinput.h"
+#include "shlwapi.h"
+#include "setupapi.h"
+#include "ddk/hidsdi.h"
+#include "ddk/hidclass.h"
#include "wine/test.h"
static DWORD (WINAPI *pXInputGetState)(DWORD, XINPUT_STATE*);
@@ -278,6 +283,516 @@ static void test_get_batteryinformation(void)
ok(result == ERROR_BAD_ARGUMENTS, "XInputGetBatteryInformation returned (%d)\n", result);
}
+#define check_member_(file, line, val, exp, fmt, member) \
+ ok_(file, line)((val).member == (exp).member, \
+ "got " #member " " fmt ", expected " fmt "\n", \
+ (val).member, (exp).member)
+#define check_member(val, exp, fmt, member) check_member_(__FILE__, __LINE__, val, exp, fmt, member)
+
+static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA preparsed,
+ HIDD_ATTRIBUTES *attrs, HIDP_CAPS *hid_caps)
+{
+ const HIDP_CAPS expect_hid_caps =
+ {
+ .Usage = HID_USAGE_GENERIC_GAMEPAD,
+ .UsagePage = HID_USAGE_PAGE_GENERIC,
+ .InputReportByteLength =
+ attrs->VendorID == 0x045e && attrs->ProductID == 0x02ff ? 16 :
+ 15,
+ .OutputReportByteLength = 0,
+ .FeatureReportByteLength = 0,
+ .NumberLinkCollectionNodes = 4,
+ .NumberInputButtonCaps = 1,
+ .NumberInputValueCaps = 6,
+ .NumberInputDataIndices =
+ attrs->VendorID == 0x045e && attrs->ProductID == 0x02ff ? 22 :
+ 16,
+ .NumberFeatureButtonCaps = 0,
+ .NumberFeatureValueCaps = 0,
+ .NumberFeatureDataIndices = 0,
+ };
+ const HIDP_BUTTON_CAPS expect_button_caps[] =
+ {
+ {
+ .UsagePage = HID_USAGE_PAGE_BUTTON,
+ .BitField = 2,
+ .LinkUsage = HID_USAGE_GENERIC_GAMEPAD,
+ .LinkUsagePage = HID_USAGE_PAGE_GENERIC,
+ .IsRange = TRUE,
+ .IsAbsolute = TRUE,
+ .Range.UsageMin = 0x01,
+ .Range.UsageMax =
+ attrs->VendorID == 0x045e && attrs->ProductID == 0x02ff ? 0x10 :
+ 0x0a,
+ .Range.DataIndexMin = 5,
+ .Range.DataIndexMax =
+ attrs->VendorID == 0x045e && attrs->ProductID == 0x02ff ? 20 :
+ 14,
+ },
+ };
+ const HIDP_VALUE_CAPS expect_value_caps[] =
+ {
+ {
+ .UsagePage = HID_USAGE_PAGE_GENERIC,
+ .BitField = 2,
+ .LinkUsagePage = HID_USAGE_PAGE_GENERIC,
+ .LinkCollection = 1,
+ .IsAbsolute = TRUE,
+ .BitSize = 16,
+ .ReportCount = 1,
+ .LogicalMax = -1,
+ .PhysicalMax = -1,
+ .NotRange.Usage = HID_USAGE_GENERIC_Y,
+ .NotRange.DataIndex = 0,
+ },
+ {
+ .UsagePage = HID_USAGE_PAGE_GENERIC,
+ .BitField = 2,
+ .LinkUsagePage = HID_USAGE_PAGE_GENERIC,
+ .LinkCollection = 1,
+ .IsAbsolute = TRUE,
+ .BitSize = 16,
+ .ReportCount = 1,
+ .LogicalMax = -1,
+ .PhysicalMax = -1,
+ .NotRange.Usage = HID_USAGE_GENERIC_X,
+ .NotRange.DataIndex = 1,
+ },
+ {
+ .UsagePage = HID_USAGE_PAGE_GENERIC,
+ .BitField = 2,
+ .LinkUsagePage = HID_USAGE_PAGE_GENERIC,
+ .LinkCollection = 2,
+ .IsAbsolute = TRUE,
+ .BitSize = 16,
+ .ReportCount = 1,
+ .LogicalMax = -1,
+ .PhysicalMax = -1,
+ .NotRange.Usage = HID_USAGE_GENERIC_RY,
+ .NotRange.DataIndex = 2,
+ },
+ {
+ .UsagePage = HID_USAGE_PAGE_GENERIC,
+ .BitField = 2,
+ .LinkUsagePage = HID_USAGE_PAGE_GENERIC,
+ .LinkCollection = 2,
+ .IsAbsolute = TRUE,
+ .BitSize = 16,
+ .ReportCount = 1,
+ .LogicalMax = -1,
+ .PhysicalMax = -1,
+ .NotRange.Usage = HID_USAGE_GENERIC_RX,
+ .NotRange.DataIndex = 3,
+ },
+ {
+ .UsagePage = HID_USAGE_PAGE_GENERIC,
+ .BitField = 2,
+ .LinkUsagePage = HID_USAGE_PAGE_GENERIC,
+ .LinkCollection = 3,
+ .IsAbsolute = TRUE,
+ .BitSize = 16,
+ .ReportCount = 1,
+ .LogicalMax = -1,
+ .PhysicalMax = -1,
+ .NotRange.Usage = HID_USAGE_GENERIC_Z,
+ .NotRange.DataIndex = 4,
+ },
+ {
+ .UsagePage = HID_USAGE_PAGE_GENERIC,
+ .BitField = 66,
+ .LinkUsagePage = HID_USAGE_PAGE_GENERIC,
+ .LinkUsage = HID_USAGE_GENERIC_GAMEPAD,
+ .IsAbsolute = TRUE,
+ .HasNull = TRUE,
+ .BitSize = 4,
+ .Units = 14,
+ .ReportCount = 1,
+ .LogicalMin = 1,
+ .LogicalMax = 8,
+ .PhysicalMin = 0x0000,
+ .PhysicalMax = 0x103b,
+ .NotRange.Usage = HID_USAGE_GENERIC_HATSWITCH,
+ .NotRange.DataIndex =
+ attrs->VendorID == 0x045e && attrs->ProductID == 0x02ff ? 21 :
+ 15,
+ },
+ };
+ static const HIDP_LINK_COLLECTION_NODE expect_collections[] =
+ {
+ {
+ .LinkUsage = HID_USAGE_GENERIC_GAMEPAD,
+ .LinkUsagePage = HID_USAGE_PAGE_GENERIC,
+ .CollectionType = 1,
+ .NumberOfChildren = 3,
+ .FirstChild = 3,
+ },
+ { .LinkUsagePage = HID_USAGE_PAGE_GENERIC, .NextSibling = 0, },
+ { .LinkUsagePage = HID_USAGE_PAGE_GENERIC, .NextSibling = 1, },
+ { .LinkUsagePage = HID_USAGE_PAGE_GENERIC, .NextSibling = 2, },
+ };
+
+ HIDP_LINK_COLLECTION_NODE collections[16];
+ HIDP_BUTTON_CAPS button_caps[16];
+ HIDP_VALUE_CAPS value_caps[16];
+ XINPUT_CAPABILITIES xi_caps;
+ char buffer[200] = {0};
+ ULONG length, value;
+ XINPUT_STATE state;
+ USAGE usages[15];
+ NTSTATUS status;
+ USHORT count;
+ DWORD i, res;
+ BOOL ret;
+
+ res = pXInputGetCapabilities(index, 0, &xi_caps);
+ ok(res == ERROR_SUCCESS, "XInputGetCapabilities %d returned %u\n", index, res);
+
+ res = pXInputGetState(index, &state);
+ ok(res == ERROR_SUCCESS, "XInputGetState %d returned %u\n", index, res);
+
+ ok(hid_caps->UsagePage == HID_USAGE_PAGE_GENERIC, "unexpected usage page %04x\n", hid_caps->UsagePage);
+ ok(hid_caps->Usage == HID_USAGE_GENERIC_GAMEPAD, "unexpected usage %04x\n", hid_caps->Usage);
+
+ check_member(*hid_caps, expect_hid_caps, "%04x", Usage);
+ check_member(*hid_caps, expect_hid_caps, "%04x", UsagePage);
+ todo_wine
+ check_member(*hid_caps, expect_hid_caps, "%d", InputReportByteLength);
+ todo_wine_if(xi_caps.Flags & XINPUT_CAPS_FFB_SUPPORTED)
+ check_member(*hid_caps, expect_hid_caps, "%d", OutputReportByteLength);
+ check_member(*hid_caps, expect_hid_caps, "%d", FeatureReportByteLength);
+ todo_wine
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberLinkCollectionNodes);
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberInputButtonCaps);
+ todo_wine
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberInputValueCaps);
+ todo_wine
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberInputDataIndices);
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberOutputButtonCaps);
+ todo_wine_if(xi_caps.Flags & XINPUT_CAPS_FFB_SUPPORTED)
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberOutputValueCaps);
+ todo_wine_if(xi_caps.Flags & XINPUT_CAPS_FFB_SUPPORTED)
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberOutputDataIndices);
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberFeatureButtonCaps);
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberFeatureValueCaps);
+ check_member(*hid_caps, expect_hid_caps, "%d", NumberFeatureDataIndices);
+
+ length = hid_caps->NumberLinkCollectionNodes;
+ status = HidP_GetLinkCollectionNodes(collections, &length, preparsed);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetLinkCollectionNodes returned %#x\n", status);
+ todo_wine
+ ok(length == ARRAY_SIZE(expect_collections), "got %d collections\n", length);
+
+ for (i = 0; i < min(length, ARRAY_SIZE(expect_collections)); ++i)
+ {
+ winetest_push_context("collections[%d]", i);
+ todo_wine_if(i == 1)
+ check_member(collections[i], expect_collections[i], "%04x", LinkUsage);
+ check_member(collections[i], expect_collections[i], "%04x", LinkUsagePage);
+ check_member(collections[i], expect_collections[i], "%d", Parent);
+ todo_wine_if(i == 0)
+ check_member(collections[i], expect_collections[i], "%d", NumberOfChildren);
+ check_member(collections[i], expect_collections[i], "%d", NextSibling);
+ todo_wine_if(i == 0)
+ check_member(collections[i], expect_collections[i], "%d", FirstChild);
+ check_member(collections[i], expect_collections[i], "%d", CollectionType);
+ check_member(collections[i], expect_collections[i], "%d", IsAlias);
+ winetest_pop_context();
+ }
+
+ count = hid_caps->NumberInputButtonCaps;
+ status = HidP_GetButtonCaps(HidP_Input, button_caps, &count, preparsed);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetButtonCaps returned %#x\n", status);
+ ok(count == ARRAY_SIZE(expect_button_caps), "got %d button caps\n", count);
+
+ for (i = 0; i < ARRAY_SIZE(expect_button_caps); ++i)
+ {
+ winetest_push_context("button_caps[%d]", i);
+ check_member(button_caps[i], expect_button_caps[i], "%04x", UsagePage);
+ check_member(button_caps[i], expect_button_caps[i], "%d", ReportID);
+ check_member(button_caps[i], expect_button_caps[i], "%d", IsAlias);
+ check_member(button_caps[i], expect_button_caps[i], "%d", BitField);
+ todo_wine
+ check_member(button_caps[i], expect_button_caps[i], "%d", LinkCollection);
+ todo_wine
+ check_member(button_caps[i], expect_button_caps[i], "%04x", LinkUsage);
+ check_member(button_caps[i], expect_button_caps[i], "%04x", LinkUsagePage);
+ check_member(button_caps[i], expect_button_caps[i], "%d", IsRange);
+ check_member(button_caps[i], expect_button_caps[i], "%d", IsStringRange);
+ check_member(button_caps[i], expect_button_caps[i], "%d", IsDesignatorRange);
+ check_member(button_caps[i], expect_button_caps[i], "%d", IsAbsolute);
+
+ if (!button_caps[i].IsRange && !expect_button_caps[i].IsRange)
+ {
+ check_member(button_caps[i], expect_button_caps[i], "%04x", NotRange.Usage);
+ check_member(button_caps[i], expect_button_caps[i], "%d", NotRange.DataIndex);
+ }
+ else if (button_caps[i].IsRange && expect_button_caps[i].IsRange)
+ {
+ check_member(button_caps[i], expect_button_caps[i], "%04x", Range.UsageMin);
+ todo_wine
+ check_member(button_caps[i], expect_button_caps[i], "%04x", Range.UsageMax);
+ todo_wine
+ check_member(button_caps[i], expect_button_caps[i], "%d", Range.DataIndexMin);
+ todo_wine
+ check_member(button_caps[i], expect_button_caps[i], "%d", Range.DataIndexMax);
+ }
+
+ if (!button_caps[i].IsRange && !expect_button_caps[i].IsRange)
+ check_member(button_caps[i], expect_button_caps[i], "%d", NotRange.StringIndex);
+ else if (button_caps[i].IsStringRange && expect_button_caps[i].IsStringRange)
+ {
+ check_member(button_caps[i], expect_button_caps[i], "%d", Range.StringMin);
+ check_member(button_caps[i], expect_button_caps[i], "%d", Range.StringMax);
+ }
+
+ if (!button_caps[i].IsDesignatorRange && !expect_button_caps[i].IsDesignatorRange)
+ check_member(button_caps[i], expect_button_caps[i], "%d", NotRange.DesignatorIndex);
+ else if (button_caps[i].IsDesignatorRange && expect_button_caps[i].IsDesignatorRange)
+ {
+ check_member(button_caps[i], expect_button_caps[i], "%d", Range.DesignatorMin);
+ check_member(button_caps[i], expect_button_caps[i], "%d", Range.DesignatorMax);
+ }
+ winetest_pop_context();
+ }
+
+ count = hid_caps->NumberInputValueCaps;
+ status = HidP_GetValueCaps(HidP_Input, value_caps, &count, preparsed);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetValueCaps returned %#x\n", status);
+ todo_wine
+ ok(count == ARRAY_SIZE(expect_value_caps), "got %d value caps\n", count);
+
+ for (i = 0; i < min(count, ARRAY_SIZE(expect_value_caps)); ++i)
+ {
+ winetest_push_context("value_caps[%d]", i);
+ check_member(value_caps[i], expect_value_caps[i], "%04x", UsagePage);
+ check_member(value_caps[i], expect_value_caps[i], "%d", ReportID);
+ check_member(value_caps[i], expect_value_caps[i], "%d", IsAlias);
+ todo_wine_if(i == 5)
+ check_member(value_caps[i], expect_value_caps[i], "%d", BitField);
+ todo_wine_if(i >= 2)
+ check_member(value_caps[i], expect_value_caps[i], "%d", LinkCollection);
+ todo_wine
+ check_member(value_caps[i], expect_value_caps[i], "%d", LinkUsage);
+ check_member(value_caps[i], expect_value_caps[i], "%d", LinkUsagePage);
+ check_member(value_caps[i], expect_value_caps[i], "%d", IsRange);
+ check_member(value_caps[i], expect_value_caps[i], "%d", IsStringRange);
+ check_member(value_caps[i], expect_value_caps[i], "%d", IsDesignatorRange);
+ check_member(value_caps[i], expect_value_caps[i], "%d", IsAbsolute);
+
+ todo_wine_if(i == 5)
+ check_member(value_caps[i], expect_value_caps[i], "%d", HasNull);
+ todo_wine_if(i == 5)
+ check_member(value_caps[i], expect_value_caps[i], "%d", BitSize);
+ check_member(value_caps[i], expect_value_caps[i], "%d", ReportCount);
+ check_member(value_caps[i], expect_value_caps[i], "%d", UnitsExp);
+ todo_wine_if(i == 5)
+ check_member(value_caps[i], expect_value_caps[i], "%d", Units);
+ todo_wine_if(i == 5)
+ check_member(value_caps[i], expect_value_caps[i], "%d", LogicalMin);
+ todo_wine
+ check_member(value_caps[i], expect_value_caps[i], "%d", LogicalMax);
+ check_member(value_caps[i], expect_value_caps[i], "%d", PhysicalMin);
+ todo_wine
+ check_member(value_caps[i], expect_value_caps[i], "%d", PhysicalMax);
+
+ if (!value_caps[i].IsRange && !expect_value_caps[i].IsRange)
+ {
+ todo_wine
+ check_member(value_caps[i], expect_value_caps[i], "%04x", NotRange.Usage);
+ todo_wine_if(i == 5)
+ check_member(value_caps[i], expect_value_caps[i], "%d", NotRange.DataIndex);
+ }
+ else if (value_caps[i].IsRange && expect_value_caps[i].IsRange)
+ {
+ check_member(value_caps[i], expect_value_caps[i], "%04x", Range.UsageMin);
+ check_member(value_caps[i], expect_value_caps[i], "%04x", Range.UsageMax);
+ check_member(value_caps[i], expect_value_caps[i], "%d", Range.DataIndexMin);
+ check_member(value_caps[i], expect_value_caps[i], "%d", Range.DataIndexMax);
+ }
+
+ if (!value_caps[i].IsRange && !expect_value_caps[i].IsRange)
+ check_member(value_caps[i], expect_value_caps[i], "%d", NotRange.StringIndex);
+ else if (value_caps[i].IsStringRange && expect_value_caps[i].IsStringRange)
+ {
+ check_member(value_caps[i], expect_value_caps[i], "%d", Range.StringMin);
+ check_member(value_caps[i], expect_value_caps[i], "%d", Range.StringMax);
+ }
+
+ if (!value_caps[i].IsDesignatorRange && !expect_value_caps[i].IsDesignatorRange)
+ check_member(value_caps[i], expect_value_caps[i], "%d", NotRange.DesignatorIndex);
+ else if (value_caps[i].IsDesignatorRange && expect_value_caps[i].IsDesignatorRange)
+ {
+ check_member(value_caps[i], expect_value_caps[i], "%d", Range.DesignatorMin);
+ check_member(value_caps[i], expect_value_caps[i], "%d", Range.DesignatorMax);
+ }
+ winetest_pop_context();
+ }
+
+ status = HidP_InitializeReportForID(HidP_Input, 0, preparsed, buffer, hid_caps->InputReportByteLength);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_InitializeReportForID returned %#x\n", status);
+
+ SetLastError(0xdeadbeef);
+ memset(buffer, 0, sizeof(buffer));
+ ret = HidD_GetInputReport(device, buffer, hid_caps->InputReportByteLength);
+ todo_wine
+ ok(!ret, "HidD_GetInputReport succeeded\n");
+ todo_wine
+ ok(GetLastError() == ERROR_INVALID_PARAMETER, "HidD_GetInputReport returned error %u\n", GetLastError());
+
+ if (!winetest_interactive) skip("skipping interactive tests\n");
+ /* ReadFile on Xbox One For Windows controller seems to never succeed */
+ else if (attrs->VendorID == 0x045e && attrs->ProductID == 0x02ff) skip("skipping interactive tests (Xbox One For Windows)\n");
+ else
+ {
+ trace("press A button on gamepad %d\n", index);
+
+ SetLastError(0xdeadbeef);
+ memset(buffer, 0, sizeof(buffer));
+ length = hid_caps->InputReportByteLength;
+ ret = ReadFile(device, buffer, hid_caps->InputReportByteLength, &length, NULL);
+ ok(ret, "ReadFile failed, last error %u\n", GetLastError());
+ ok(length == hid_caps->InputReportByteLength, "ReadFile returned length %u\n", length);
+
+ res = pXInputGetState(index, &state);
+ ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res);
+ ok(state.Gamepad.wButtons & XINPUT_GAMEPAD_A, "unexpected button state %#x\n", state.Gamepad.wButtons);
+
+ length = ARRAY_SIZE(usages);
+ status = HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, usages, &length, preparsed, buffer, hid_caps->InputReportByteLength);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetUsages returned %#x\n", status);
+ ok(length == 1, "got length %u\n", length);
+ ok(usages[0] == 1, "got usages[0] %u\n", usages[0]);
+
+ trace("release A on gamepad %d\n", index);
+
+ SetLastError(0xdeadbeef);
+ memset(buffer, 0, sizeof(buffer));
+ length = hid_caps->InputReportByteLength;
+ ret = ReadFile(device, buffer, hid_caps->InputReportByteLength, &length, NULL);
+ ok(ret, "ReadFile failed, last error %u\n", GetLastError());
+ ok(length == hid_caps->InputReportByteLength, "ReadFile returned length %u\n", length);
+
+ res = pXInputGetState(index, &state);
+ ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res);
+ ok(!state.Gamepad.wButtons, "unexpected button state %#x\n", state.Gamepad.wButtons);
+
+ length = ARRAY_SIZE(usages);
+ status = HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, usages, &length, preparsed, buffer, hid_caps->InputReportByteLength);
+ todo_wine
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetUsages returned %#x\n", status);
+ ok(length == 0, "got length %u\n", length);
+
+ trace("press both LT and RT on gamepad %d\n", index);
+
+ do
+ {
+ SetLastError(0xdeadbeef);
+ memset(buffer, 0, sizeof(buffer));
+ length = hid_caps->InputReportByteLength;
+ ret = ReadFile(device, buffer, hid_caps->InputReportByteLength, &length, NULL);
+ ok(ret, "ReadFile failed, last error %u\n", GetLastError());
+ ok(length == hid_caps->InputReportByteLength, "ReadFile returned length %u\n", length);
+
+ res = pXInputGetState(index, &state);
+ ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res);
+
+ value = 0;
+ status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_X, &value, preparsed, buffer, hid_caps->InputReportByteLength);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetUsageValue returned %#x\n", status);
+ ok(value == state.Gamepad.sThumbLX + 32768, "got LX value %d\n", value);
+ value = 0;
+ status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Y, &value, preparsed, buffer, hid_caps->InputReportByteLength);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetUsageValue returned %#x\n", status);
+ ok(value == 32767 - state.Gamepad.sThumbLY, "got LY value %d\n", value);
+ value = 0;
+ status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RX, &value, preparsed, buffer, hid_caps->InputReportByteLength);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetUsageValue returned %#x\n", status);
+ ok(value == state.Gamepad.sThumbRX + 32768, "got LX value %d\n", value);
+ value = 0;
+ status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RY, &value, preparsed, buffer, hid_caps->InputReportByteLength);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetUsageValue returned %#x\n", status);
+ ok(value == 32767 - state.Gamepad.sThumbRY, "got LY value %d\n", value);
+ value = 0;
+ status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Z, &value, preparsed, buffer, hid_caps->InputReportByteLength);
+ ok(status == HIDP_STATUS_SUCCESS, "HidP_GetUsageValue returned %#x\n", status);
+ todo_wine
+ ok(value == 32768 + (state.Gamepad.bLeftTrigger - state.Gamepad.bRightTrigger) * 128, "got Z value %d (RT %d, LT %d)\n",
+ value, state.Gamepad.bRightTrigger, state.Gamepad.bLeftTrigger);
+ value = 0;
+ status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RZ, &value, preparsed, buffer, hid_caps->InputReportByteLength);
+ todo_wine
+ ok(status == HIDP_STATUS_USAGE_NOT_FOUND, "HidP_GetUsageValue returned %#x\n", status);
+ } while (ret && (state.Gamepad.bRightTrigger != 255 || state.Gamepad.bLeftTrigger != 255));
+ }
+}
+
+static BOOL try_open_hid_device(const WCHAR *path, HANDLE *device, PHIDP_PREPARSED_DATA *preparsed,
+ HIDD_ATTRIBUTES *attrs, HIDP_CAPS *caps)
+{
+ PHIDP_PREPARSED_DATA preparsed_data = NULL;
+ HANDLE device_file;
+
+ device_file = CreateFileW(path, FILE_READ_ACCESS | FILE_WRITE_ACCESS, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ if (device_file == INVALID_HANDLE_VALUE) return FALSE;
+
+ if (!HidD_GetPreparsedData(device_file, &preparsed_data)) goto failed;
+ if (!HidD_GetAttributes(device_file, attrs)) goto failed;
+ if (HidP_GetCaps(preparsed_data, caps) != HIDP_STATUS_SUCCESS) goto failed;
+
+ *device = device_file;
+ *preparsed = preparsed_data;
+ return TRUE;
+
+failed:
+ CloseHandle(device_file);
+ HidD_FreePreparsedData(preparsed_data);
+ return FALSE;
+}
+
+static void test_hid_reports(void)
+{
+ static const WCHAR prefix[] = L"\\\\?\\HID#VID_0000&PID_0000";
+ char buffer[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W) + MAX_PATH * sizeof(WCHAR)];
+ SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail = (void *)buffer;
+ SP_DEVICE_INTERFACE_DATA iface = {sizeof(iface)};
+ SP_DEVINFO_DATA devinfo = {sizeof(devinfo)};
+ PHIDP_PREPARSED_DATA preparsed;
+ HIDD_ATTRIBUTES attrs;
+ HIDP_CAPS caps;
+ HDEVINFO set;
+ HANDLE device;
+ UINT32 i = 0, cnt = 0;
+ GUID hid;
+
+ HidD_GetHidGuid(&hid);
+
+ set = SetupDiGetClassDevsW(&hid, NULL, NULL, DIGCF_DEVICEINTERFACE);
+ ok(set != INVALID_HANDLE_VALUE, "SetupDiGetClassDevsW failed, error %u\n", GetLastError());
+
+ while (SetupDiEnumDeviceInterfaces(set, NULL, &hid, i++, &iface))
+ {
+ detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
+ if (!SetupDiGetDeviceInterfaceDetailW(set, &iface, detail, sizeof(buffer), NULL, &devinfo))
+ continue;
+
+ if (!try_open_hid_device(detail->DevicePath, &device, &preparsed, &attrs, &caps))
+ continue;
+
+ if (wcslen(detail->DevicePath) <= wcslen(prefix) ||
+ wcsnicmp(detail->DevicePath + wcslen(prefix), L"&IG_", 4 ))
+ continue;
+
+ trace("found xinput HID device %s\n", wine_dbgstr_w(detail->DevicePath));
+ check_hid_caps(cnt++, device, preparsed, &attrs, &caps);
+
+ CloseHandle(device);
+ HidD_FreePreparsedData(preparsed);
+ }
+
+ SetupDiDestroyDeviceInfoList(set);
+}
+
START_TEST(xinput)
{
struct
@@ -320,6 +835,7 @@ START_TEST(xinput)
if (!pXInputGetStateEx)
pXInputGetStateEx = pXInputGetStateEx_Ordinal;
+ test_hid_reports();
test_set_state();
test_get_state();
test_get_capabilities();
--
2.32.0