xinput1_3/tests: Add some HID report tests.

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@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Rémi Bernon 2021-08-10 13:22:16 +02:00 committed by Alexandre Julliard
parent fa20ac430b
commit 7a571127e4
2 changed files with 517 additions and 1 deletions

View File

@ -1,5 +1,5 @@
TESTDLL = xinput1_3.dll
IMPORTS = user32
IMPORTS = user32 hid setupapi
C_SRCS = \
xinput.c

View File

@ -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();