Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winexinput.sys/main.c | 4 ---- 1 file changed, 4 deletions(-)
diff --git a/dlls/winexinput.sys/main.c b/dlls/winexinput.sys/main.c index a0b6c9bf0eb..386b4bbb228 100644 --- a/dlls/winexinput.sys/main.c +++ b/dlls/winexinput.sys/main.c @@ -220,12 +220,8 @@ static NTSTATUS WINAPI pdo_pnp(DEVICE_OBJECT *device, IRP *irp) }
case IRP_MN_QUERY_CAPABILITIES: - { - DEVICE_CAPABILITIES *caps = stack->Parameters.DeviceCapabilities.Capabilities; - caps->RawDeviceOK = 1; status = STATUS_SUCCESS; break; - }
default: FIXME("code %#x, not implemented!\n", code);
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winexinput.sys/main.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/dlls/winexinput.sys/main.c b/dlls/winexinput.sys/main.c index 386b4bbb228..515d109a2ad 100644 --- a/dlls/winexinput.sys/main.c +++ b/dlls/winexinput.sys/main.c @@ -223,6 +223,10 @@ static NTSTATUS WINAPI pdo_pnp(DEVICE_OBJECT *device, IRP *irp) status = STATUS_SUCCESS; break;
+ case IRP_MN_QUERY_DEVICE_RELATIONS: + status = irp->IoStatus.Status; + break; + default: FIXME("code %#x, not implemented!\n", code); status = irp->IoStatus.Status;
So everything will be ready in the device fdo (or fail to start the device entirely), when we need it later.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winexinput.sys/Makefile.in | 2 +- dlls/winexinput.sys/main.c | 65 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-)
diff --git a/dlls/winexinput.sys/Makefile.in b/dlls/winexinput.sys/Makefile.in index 592c90295b2..2e9af1429d8 100644 --- a/dlls/winexinput.sys/Makefile.in +++ b/dlls/winexinput.sys/Makefile.in @@ -1,5 +1,5 @@ MODULE = winexinput.sys -IMPORTS = ntoskrnl +IMPORTS = ntoskrnl hidparse EXTRADLLFLAGS = -mno-cygwin -Wl,--subsystem,native
C_SRCS = \ diff --git a/dlls/winexinput.sys/main.c b/dlls/winexinput.sys/main.c index 515d109a2ad..afa3b20f1bf 100644 --- a/dlls/winexinput.sys/main.c +++ b/dlls/winexinput.sys/main.c @@ -31,6 +31,7 @@ #include "cfgmgr32.h" #include "ddk/wdm.h" #include "ddk/hidport.h" +#include "ddk/hidpddi.h"
#include "wine/asm.h" #include "wine/debug.h" @@ -75,6 +76,11 @@ struct func_device /* the bogus HID gamepad, as exposed by native XUSB */ DEVICE_OBJECT *gamepad_device; WCHAR instance_id[MAX_DEVICE_ID_LEN]; + + /* everything below requires holding the cs */ + CRITICAL_SECTION cs; + ULONG report_len; + char *report_buf; };
static inline struct func_device *fdo_from_DEVICE_OBJECT(DEVICE_OBJECT *device) @@ -273,6 +279,59 @@ static NTSTATUS create_child_pdos(DEVICE_OBJECT *device) return STATUS_SUCCESS; }
+static NTSTATUS sync_ioctl(DEVICE_OBJECT *device, DWORD code, void *in_buf, DWORD in_len, void *out_buf, DWORD out_len) +{ + IO_STATUS_BLOCK io; + KEVENT event; + IRP *irp; + + KeInitializeEvent(&event, NotificationEvent, FALSE); + irp = IoBuildDeviceIoControlRequest(code, device, in_buf, in_len, out_buf, out_len, TRUE, &event, &io); + if (IoCallDriver(device, irp) == STATUS_PENDING) KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + + return io.Status; +} + +static NTSTATUS initialize_device(DEVICE_OBJECT *device) +{ + struct func_device *fdo = fdo_from_DEVICE_OBJECT(device); + ULONG i, report_desc_len, report_count; + PHIDP_REPORT_DESCRIPTOR report_desc; + PHIDP_PREPARSED_DATA preparsed; + HIDP_DEVICE_DESC device_desc; + HIDP_REPORT_IDS *reports; + HID_DESCRIPTOR hid_desc; + NTSTATUS status; + HIDP_CAPS caps; + + if ((status = sync_ioctl(fdo->bus_device, IOCTL_HID_GET_DEVICE_DESCRIPTOR, NULL, 0, &hid_desc, sizeof(hid_desc)))) + return status; + + if (!(report_desc_len = hid_desc.DescriptorList[0].wReportLength)) return STATUS_UNSUCCESSFUL; + if (!(report_desc = malloc(report_desc_len))) return STATUS_NO_MEMORY; + + status = sync_ioctl(fdo->bus_device, IOCTL_HID_GET_REPORT_DESCRIPTOR, NULL, 0, report_desc, report_desc_len); + if (!status) status = HidP_GetCollectionDescription(report_desc, report_desc_len, PagedPool, &device_desc); + free(report_desc); + if (status != HIDP_STATUS_SUCCESS) return status; + + preparsed = device_desc.CollectionDesc->PreparsedData; + status = HidP_GetCaps(preparsed, &caps); + if (status != HIDP_STATUS_SUCCESS) return status; + + reports = device_desc.ReportIDs; + report_count = device_desc.ReportIDsLength; + for (i = 0; i < report_count; ++i) if (!reports[i].ReportID || reports[i].InputLength) break; + if (i == report_count) i = 0; /* no input report?!, just use first ID */ + + fdo->report_len = caps.InputReportByteLength; + if (!(fdo->report_buf = malloc(fdo->report_len))) return STATUS_NO_MEMORY; + fdo->report_buf[0] = reports[i].ReportID; + + HidP_FreeCollectionDescription(&device_desc); + return STATUS_SUCCESS; +} + static NTSTATUS WINAPI set_event_completion(DEVICE_OBJECT *device, IRP *irp, void *context) { if (irp->PendingReturned) KeSetEvent((KEVENT *)context, IO_NO_INCREMENT, FALSE); @@ -329,6 +388,7 @@ static NTSTATUS WINAPI fdo_pnp(DEVICE_OBJECT *device, IRP *irp) KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); status = irp->IoStatus.Status; } + if (!status) status = initialize_device(device); if (!status) status = create_child_pdos(device);
if (status) irp->IoStatus.Status = status; @@ -339,6 +399,8 @@ static NTSTATUS WINAPI fdo_pnp(DEVICE_OBJECT *device, IRP *irp) IoSkipCurrentIrpStackLocation(irp); status = IoCallDriver(fdo->bus_device, irp); IoDetachDevice(fdo->bus_device); + RtlDeleteCriticalSection(&fdo->cs); + free(fdo->report_buf); IoDeleteDevice(device); return status;
@@ -422,6 +484,9 @@ static NTSTATUS WINAPI add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *bus_devi fdo->bus_device = bus_device; wcscpy(fdo->instance_id, instance_id);
+ RtlInitializeCriticalSection(&fdo->cs); + fdo->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": func_device.cs"); + TRACE("device %p, bus_id %s, device_id %s, instance_id %s.\n", device, debugstr_w(bus_id), debugstr_w(fdo->base.device_id), debugstr_w(fdo->instance_id));
Although these devices will be HID compatible we need to not have them listed on the HID class, as they should only be used internally by Wine XInput implementation.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/hidclass.sys/hid.h | 1 + dlls/hidclass.sys/pnp.c | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/dlls/hidclass.sys/hid.h b/dlls/hidclass.sys/hid.h index 1ca6a926872..e65fabb2aea 100644 --- a/dlls/hidclass.sys/hid.h +++ b/dlls/hidclass.sys/hid.h @@ -84,6 +84,7 @@ typedef struct _BASE_DEVICE_EXTENSION * for convenience. */ WCHAR device_id[MAX_DEVICE_ID_LEN]; WCHAR instance_id[MAX_DEVICE_ID_LEN]; + const GUID *class_guid;
BOOL is_fdo; } BASE_DEVICE_EXTENSION; diff --git a/dlls/hidclass.sys/pnp.c b/dlls/hidclass.sys/pnp.c index db45aea2fd3..8755afbce6c 100644 --- a/dlls/hidclass.sys/pnp.c +++ b/dlls/hidclass.sys/pnp.c @@ -38,6 +38,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(hid);
DEFINE_DEVPROPKEY(DEVPROPKEY_HID_HANDLE, 0xbc62e415, 0xf4fe, 0x405c, 0x8e, 0xda, 0x63, 0x6f, 0xb5, 0x9f, 0x08, 0x98, 2); +DEFINE_GUID(GUID_DEVINTERFACE_WINEXINPUT, 0x6c53d5fd, 0x6480, 0x440f, 0xb6, 0x18, 0x47, 0x67, 0x50, 0xc5, 0xe1, 0xa6);
#if defined(__i386__) && !defined(_WIN32)
@@ -133,6 +134,7 @@ static NTSTATUS WINAPI driver_add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *b { WCHAR device_id[MAX_DEVICE_ID_LEN], instance_id[MAX_DEVICE_ID_LEN]; BASE_DEVICE_EXTENSION *ext; + BOOL is_xinput_class; DEVICE_OBJECT *fdo; NTSTATUS status; minidriver *minidriver; @@ -166,6 +168,10 @@ static NTSTATUS WINAPI driver_add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *b swprintf(ext->device_id, ARRAY_SIZE(ext->device_id), L"HID\%s", wcsrchr(device_id, '\') + 1); wcscpy(ext->instance_id, instance_id);
+ is_xinput_class = !wcsncmp(device_id, L"WINEXINPUT\", 7) && wcsstr(device_id, L"&XI_") != NULL; + if (is_xinput_class) ext->class_guid = &GUID_DEVINTERFACE_WINEXINPUT; + else ext->class_guid = &GUID_DEVINTERFACE_HID; + status = minidriver->AddDevice(minidriver->minidriver.DriverObject, fdo); if (status != STATUS_SUCCESS) { @@ -220,6 +226,7 @@ static void create_child(minidriver *minidriver, DEVICE_OBJECT *fdo) KeInitializeSpinLock(&pdo_ext->u.pdo.irp_queue_lock); wcscpy(pdo_ext->device_id, fdo_ext->device_id); wcscpy(pdo_ext->instance_id, fdo_ext->instance_id); + pdo_ext->class_guid = fdo_ext->class_guid;
pdo_ext->u.pdo.information.VendorID = attr.VendorID; pdo_ext->u.pdo.information.ProductID = attr.ProductID; @@ -445,7 +452,7 @@ static NTSTATUS pdo_pnp(DEVICE_OBJECT *device, IRP *irp) }
case IRP_MN_START_DEVICE: - if ((status = IoRegisterDeviceInterface(device, &GUID_DEVINTERFACE_HID, NULL, &ext->u.pdo.link_name))) + if ((status = IoRegisterDeviceInterface(device, ext->class_guid, NULL, &ext->u.pdo.link_name))) { ERR("Failed to register interface, status %#x.\n", status); break;
This internal xinput PDO is an HID compatible pass-through device, but it needs to be kept private and is listed on the internal WINEXINPUT device interface class, instead of the HID device interface class.
This is a Wine extension for convenience and native XInput driver uses a different, undocumented, device interface.
We now filter the report read requests to make sure only one is sent through to the lower bus device, and we complete both gamepad and xinput read requests at once using the returned data.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winexinput.sys/main.c | 158 ++++++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-)
diff --git a/dlls/winexinput.sys/main.c b/dlls/winexinput.sys/main.c index afa3b20f1bf..60f7a8ed5dd 100644 --- a/dlls/winexinput.sys/main.c +++ b/dlls/winexinput.sys/main.c @@ -53,6 +53,7 @@ __ASM_STDCALL_FUNC(wrap_fastcall_func1, 8, struct device { BOOL is_fdo; + BOOL is_gamepad; BOOL removed; WCHAR device_id[MAX_DEVICE_ID_LEN]; }; @@ -75,12 +76,18 @@ struct func_device
/* the bogus HID gamepad, as exposed by native XUSB */ DEVICE_OBJECT *gamepad_device; + + /* the Wine-specific hidden HID device, used by XInput */ + DEVICE_OBJECT *xinput_device; + WCHAR instance_id[MAX_DEVICE_ID_LEN];
/* everything below requires holding the cs */ CRITICAL_SECTION cs; ULONG report_len; char *report_buf; + IRP *pending_read; + BOOL pending_is_gamepad; };
static inline struct func_device *fdo_from_DEVICE_OBJECT(DEVICE_OBJECT *device) @@ -90,6 +97,108 @@ static inline struct func_device *fdo_from_DEVICE_OBJECT(DEVICE_OBJECT *device) else return CONTAINING_RECORD(impl, struct phys_device, base)->fdo; }
+static NTSTATUS WINAPI read_completion(DEVICE_OBJECT *device, IRP *xinput_irp, void *context) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(xinput_irp); + ULONG offset, read_len = stack->Parameters.DeviceIoControl.OutputBufferLength; + struct func_device *fdo = fdo_from_DEVICE_OBJECT(device); + char *read_buf = xinput_irp->UserBuffer; + IRP *gamepad_irp = context; + + gamepad_irp->IoStatus.Status = xinput_irp->IoStatus.Status; + gamepad_irp->IoStatus.Information = xinput_irp->IoStatus.Information; + + if (!xinput_irp->IoStatus.Status) + { + RtlEnterCriticalSection(&fdo->cs); + offset = fdo->report_buf[0] ? 0 : 1; + memcpy(fdo->report_buf + offset, read_buf, read_len); + memcpy(gamepad_irp->UserBuffer, read_buf, read_len); + RtlLeaveCriticalSection(&fdo->cs); + } + + IoCompleteRequest(gamepad_irp, IO_NO_INCREMENT); + if (xinput_irp->PendingReturned) IoMarkIrpPending(xinput_irp); + return STATUS_SUCCESS; +} + +/* check for a pending read from the other PDO, and complete both at a time. + * if there's none, save irp as pending, the other PDO will complete it. + * if the device is being removed, complete irp with an error. */ +static NTSTATUS try_complete_pending_read(DEVICE_OBJECT *device, IRP *irp) +{ + struct func_device *fdo = fdo_from_DEVICE_OBJECT(device); + struct device *impl = impl_from_DEVICE_OBJECT(device); + IRP *pending, *xinput_irp, *gamepad_irp; + BOOL removed, pending_is_gamepad; + + RtlEnterCriticalSection(&fdo->cs); + pending_is_gamepad = fdo->pending_is_gamepad; + if ((removed = impl->removed)) + pending = NULL; + else if ((pending = fdo->pending_read)) + fdo->pending_read = NULL; + else + { + fdo->pending_read = irp; + fdo->pending_is_gamepad = impl->is_gamepad; + IoMarkIrpPending(irp); + } + RtlLeaveCriticalSection(&fdo->cs); + + if (removed) + { + irp->IoStatus.Status = STATUS_DELETE_PENDING; + irp->IoStatus.Information = 0; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_DELETE_PENDING; + } + + if (!pending) return STATUS_PENDING; + + /* only one read at a time per device from hidclass.sys design */ + if (pending_is_gamepad == impl->is_gamepad) ERR("multiple read requests!\n"); + gamepad_irp = impl->is_gamepad ? irp : pending; + xinput_irp = impl->is_gamepad ? pending : irp; + + /* pass xinput irp down, and complete gamepad irp on its way back */ + IoCopyCurrentIrpStackLocationToNext(xinput_irp); + IoSetCompletionRoutine(xinput_irp, read_completion, gamepad_irp, TRUE, TRUE, TRUE); + return IoCallDriver(fdo->bus_device, xinput_irp); +} + +static NTSTATUS WINAPI gamepad_internal_ioctl(DEVICE_OBJECT *device, IRP *irp) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + struct func_device *fdo = fdo_from_DEVICE_OBJECT(device); + + TRACE("device %p, irp %p, code %#x, bus_device %p.\n", device, irp, code, fdo->bus_device); + + switch (code) + { + case IOCTL_HID_GET_INPUT_REPORT: + { + HID_XFER_PACKET *packet = (HID_XFER_PACKET *)irp->UserBuffer; + + RtlEnterCriticalSection(&fdo->cs); + memcpy(packet->reportBuffer, fdo->report_buf, fdo->report_len); + irp->IoStatus.Information = fdo->report_len; + RtlLeaveCriticalSection(&fdo->cs); + + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; + } + + default: + IoSkipCurrentIrpStackLocation(irp); + return IoCallDriver(fdo->bus_device, irp); + } + + return STATUS_SUCCESS; +} + static NTSTATUS WINAPI internal_ioctl(DEVICE_OBJECT *device, IRP *irp) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); @@ -107,6 +216,9 @@ static NTSTATUS WINAPI internal_ioctl(DEVICE_OBJECT *device, IRP *irp)
TRACE("device %p, irp %p, code %#x, bus_device %p.\n", device, irp, code, fdo->bus_device);
+ if (code == IOCTL_HID_READ_REPORT) return try_complete_pending_read(device, irp); + if (impl->is_gamepad) return gamepad_internal_ioctl(device, irp); + IoSkipCurrentIrpStackLocation(irp); return IoCallDriver(fdo->bus_device, irp); } @@ -172,6 +284,7 @@ static NTSTATUS WINAPI pdo_pnp(DEVICE_OBJECT *device, IRP *irp) struct device *impl = impl_from_DEVICE_OBJECT(device); ULONG code = stack->MinorFunction; NTSTATUS status; + IRP *pending;
TRACE("device %p, irp %p, code %#x, bus_device %p.\n", device, irp, code, fdo->bus_device);
@@ -184,6 +297,18 @@ static NTSTATUS WINAPI pdo_pnp(DEVICE_OBJECT *device, IRP *irp) case IRP_MN_SURPRISE_REMOVAL: status = STATUS_SUCCESS; if (InterlockedExchange(&impl->removed, TRUE)) break; + + RtlEnterCriticalSection(&fdo->cs); + pending = fdo->pending_read; + fdo->pending_read = NULL; + RtlLeaveCriticalSection(&fdo->cs); + + if (pending) + { + pending->IoStatus.Status = STATUS_DELETE_PENDING; + pending->IoStatus.Information = 0; + IoCompleteRequest(pending, IO_NO_INCREMENT); + } break;
case IRP_MN_REMOVE_DEVICE: @@ -247,7 +372,7 @@ static NTSTATUS WINAPI pdo_pnp(DEVICE_OBJECT *device, IRP *irp) static NTSTATUS create_child_pdos(DEVICE_OBJECT *device) { struct func_device *fdo = fdo_from_DEVICE_OBJECT(device); - DEVICE_OBJECT *gamepad_device; + DEVICE_OBJECT *gamepad_device, *xinput_device; struct phys_device *pdo; UNICODE_STRING name_str; WCHAR *tmp, name[255]; @@ -264,10 +389,23 @@ static NTSTATUS create_child_pdos(DEVICE_OBJECT *device) return status; }
+ swprintf(name, ARRAY_SIZE(name), L"\Device\WINEXINPUT#%p&%p&1", + device->DriverObject, fdo->bus_device); + RtlInitUnicodeString(&name_str, name); + + if ((status = IoCreateDevice(device->DriverObject, sizeof(struct phys_device), + &name_str, 0, 0, FALSE, &xinput_device))) + { + ERR("failed to create xinput device, status %#x.\n", status); + IoDeleteDevice(gamepad_device); + return status; + } + fdo->gamepad_device = gamepad_device; pdo = gamepad_device->DeviceExtension; pdo->fdo = fdo; pdo->base.is_fdo = FALSE; + pdo->base.is_gamepad = TRUE; wcscpy(pdo->base.device_id, fdo->base.device_id);
if ((tmp = wcsstr(pdo->base.device_id, L"&MI_"))) memcpy(tmp, L"&IG", 6); @@ -275,6 +413,18 @@ static NTSTATUS create_child_pdos(DEVICE_OBJECT *device)
TRACE("device %p, gamepad device %p.\n", device, gamepad_device);
+ fdo->xinput_device = xinput_device; + pdo = xinput_device->DeviceExtension; + pdo->fdo = fdo; + pdo->base.is_fdo = FALSE; + pdo->base.is_gamepad = FALSE; + wcscpy(pdo->base.device_id, fdo->base.device_id); + + if ((tmp = wcsstr(pdo->base.device_id, L"&MI_"))) memcpy(tmp, L"&XI", 6); + else wcscat(pdo->base.device_id, L"&XI_00"); + + TRACE("device %p, xinput device %p.\n", device, xinput_device); + IoInvalidateDeviceRelations(fdo->bus_device, BusRelations); return STATUS_SUCCESS; } @@ -363,6 +513,12 @@ static NTSTATUS WINAPI fdo_pnp(DEVICE_OBJECT *device, IRP *irp) }
devices->Count = 0; + if ((child = fdo->xinput_device)) + { + devices->Objects[devices->Count] = child; + call_fastcall_func1(ObfReferenceObject, child); + devices->Count++; + } if ((child = fdo->gamepad_device)) { devices->Objects[devices->Count] = child;
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/xinput1_3/tests/xinput.c | 84 ++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 27 deletions(-)
diff --git a/dlls/xinput1_3/tests/xinput.c b/dlls/xinput1_3/tests/xinput.c index 272a41c690c..811fe045d10 100644 --- a/dlls/xinput1_3/tests/xinput.c +++ b/dlls/xinput1_3/tests/xinput.c @@ -434,10 +434,10 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre HIDP_LINK_COLLECTION_NODE collections[16]; HIDP_BUTTON_CAPS button_caps[16]; HIDP_VALUE_CAPS value_caps[16]; + XINPUT_STATE last_state, state; XINPUT_CAPABILITIES xi_caps; char buffer[200] = {0}; ULONG length, value; - XINPUT_STATE state; USAGE usages[15]; NTSTATUS status; USHORT count; @@ -637,19 +637,31 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre 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); + res = pXInputGetState(index, &last_state); + ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res);
- 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); + trace("press A button on gamepad %d\n", index);
- res = pXInputGetState(index, &state); - ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res); + do + { + Sleep(5); + res = pXInputGetState(index, &state); + ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res); + } while (res == ERROR_SUCCESS && state.dwPacketNumber == last_state.dwPacketNumber); ok(state.Gamepad.wButtons & XINPUT_GAMEPAD_A, "unexpected button state %#x\n", state.Gamepad.wButtons);
+ /* now read as many reports from the device to get a consistent final state */ + for (i = 0; i < (state.dwPacketNumber - last_state.dwPacketNumber); ++i) + { + 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); + } + last_state = state; + 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); @@ -658,17 +670,26 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre
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); + do + { + Sleep(5); + res = pXInputGetState(index, &state); + ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res); + } while (res == ERROR_SUCCESS && state.dwPacketNumber == last_state.dwPacketNumber); ok(!state.Gamepad.wButtons, "unexpected button state %#x\n", state.Gamepad.wButtons);
+ /* now read as many reports from the device to get a consistent final state */ + for (i = 0; i < (state.dwPacketNumber - last_state.dwPacketNumber); ++i) + { + 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); + } + last_state = state; + 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); @@ -678,15 +699,24 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre
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); + do + { + Sleep(5); + res = pXInputGetState(index, &state); + ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res); + } while (res == ERROR_SUCCESS && state.dwPacketNumber == last_state.dwPacketNumber);
- res = pXInputGetState(index, &state); - ok(res == ERROR_SUCCESS, "XInputGetState returned %#x\n", res); + /* now read as many reports from the device to get a consistent final state */ + for (i = 0; i < (state.dwPacketNumber - last_state.dwPacketNumber); ++i) + { + 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); + } + last_state = state;
value = 0; status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_X, &value, preparsed, buffer, hid_caps->InputReportByteLength);
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/xinput1_3/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/dlls/xinput1_3/main.c b/dlls/xinput1_3/main.c index 4770b64681e..44493ddb2a1 100644 --- a/dlls/xinput1_3/main.c +++ b/dlls/xinput1_3/main.c @@ -43,6 +43,8 @@
#include "wine/debug.h"
+DEFINE_GUID(GUID_DEVINTERFACE_WINEXINPUT,0x6c53d5fd,0x6480,0x440f,0xb6,0x18,0x47,0x67,0x50,0xc5,0xe1,0xa6); + /* Not defined in the headers, used only by XInputGetStateEx */ #define XINPUT_GAMEPAD_GUIDE 0x0400
@@ -324,7 +326,7 @@ static void update_controller_list(void) GUID guid; int i;
- HidD_GetHidGuid(&guid); + guid = GUID_DEVINTERFACE_WINEXINPUT;
set = SetupDiGetClassDevsW(&guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); detail->cbSize = sizeof(*detail); @@ -335,8 +337,6 @@ static void update_controller_list(void) if (!SetupDiGetDeviceInterfaceDetailW(set, &iface, detail, sizeof(buffer), NULL, NULL)) continue;
- if (!wcsstr(detail->DevicePath, L"IG_")) continue; - if (find_opened_device(detail, &i)) continue; /* already opened */ if (i == XUSER_MAX_COUNT) break; /* no more slots */
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winexinput.sys/main.c | 251 +++++++++++++++++++++++++-- dlls/winexinput.sys/pop_hid_macros.h | 83 +++++++++ dlls/winexinput.sys/psh_hid_macros.h | 85 +++++++++ dlls/xinput1_3/tests/xinput.c | 25 --- 4 files changed, 409 insertions(+), 35 deletions(-) create mode 100644 dlls/winexinput.sys/pop_hid_macros.h create mode 100644 dlls/winexinput.sys/psh_hid_macros.h
diff --git a/dlls/winexinput.sys/main.c b/dlls/winexinput.sys/main.c index 60f7a8ed5dd..e547f745347 100644 --- a/dlls/winexinput.sys/main.c +++ b/dlls/winexinput.sys/main.c @@ -50,6 +50,75 @@ __ASM_STDCALL_FUNC(wrap_fastcall_func1, 8, #define call_fastcall_func1(func,a) func(a) #endif
+#include "psh_hid_macros.h" + +const BYTE xinput_report_desc[] = +{ + USAGE_PAGE(1, HID_USAGE_PAGE_GENERIC), + USAGE(1, HID_USAGE_GENERIC_GAMEPAD), + COLLECTION(1, Application), + USAGE(1, 0), + COLLECTION(1, Physical), + USAGE(1, HID_USAGE_GENERIC_X), + USAGE(1, HID_USAGE_GENERIC_Y), + LOGICAL_MAXIMUM(2, 0xffff), + PHYSICAL_MAXIMUM(2, 0xffff), + REPORT_SIZE(1, 16), + REPORT_COUNT(1, 2), + INPUT(1, Data|Var|Abs), + END_COLLECTION, + + COLLECTION(1, Physical), + USAGE(1, HID_USAGE_GENERIC_RX), + USAGE(1, HID_USAGE_GENERIC_RY), + REPORT_COUNT(1, 2), + INPUT(1, Data|Var|Abs), + END_COLLECTION, + + COLLECTION(1, Physical), + USAGE(1, HID_USAGE_GENERIC_Z), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + END_COLLECTION, + + USAGE_PAGE(1, HID_USAGE_PAGE_BUTTON), + USAGE_MINIMUM(1, 1), + USAGE_MAXIMUM(1, 10), + LOGICAL_MAXIMUM(1, 1), + PHYSICAL_MAXIMUM(1, 1), + REPORT_COUNT(1, 10), + REPORT_SIZE(1, 1), + INPUT(1, Data|Var|Abs), + + USAGE_PAGE(1, HID_USAGE_PAGE_GENERIC), + USAGE(1, HID_USAGE_GENERIC_HATSWITCH), + LOGICAL_MINIMUM(1, 1), + LOGICAL_MAXIMUM(1, 8), + PHYSICAL_MAXIMUM(2, 0x103b), + REPORT_SIZE(1, 4), + REPORT_COUNT(4, 1), + UNIT(1, 0x0e /* none */), + INPUT(1, Data|Var|Abs|Null), + + REPORT_COUNT(1, 18), + REPORT_SIZE(1, 1), + INPUT(1, Cnst|Var|Abs), + END_COLLECTION, +}; + +#include "pop_hid_macros.h" + +struct xinput_state +{ + WORD lx_axis; + WORD ly_axis; + WORD rx_axis; + WORD ry_axis; + WORD trigger; + WORD buttons; + WORD padding; +}; + struct device { BOOL is_fdo; @@ -82,12 +151,22 @@ struct func_device
WCHAR instance_id[MAX_DEVICE_ID_LEN];
+ HIDP_VALUE_CAPS lx_caps; + HIDP_VALUE_CAPS ly_caps; + HIDP_VALUE_CAPS lt_caps; + HIDP_VALUE_CAPS rx_caps; + HIDP_VALUE_CAPS ry_caps; + HIDP_VALUE_CAPS rt_caps; + /* everything below requires holding the cs */ CRITICAL_SECTION cs; ULONG report_len; char *report_buf; IRP *pending_read; BOOL pending_is_gamepad; + + HIDP_DEVICE_DESC device_desc; + struct xinput_state xinput_state; };
static inline struct func_device *fdo_from_DEVICE_OBJECT(DEVICE_OBJECT *device) @@ -97,6 +176,73 @@ static inline struct func_device *fdo_from_DEVICE_OBJECT(DEVICE_OBJECT *device) else return CONTAINING_RECORD(impl, struct phys_device, base)->fdo; }
+static LONG sign_extend(ULONG value, const HIDP_VALUE_CAPS *caps) +{ + UINT sign = 1 << (caps->BitSize - 1); + if (sign <= 1 || caps->LogicalMin >= 0) return value; + return value - ((value & sign) << 1); +} + +static LONG scale_value(ULONG value, const HIDP_VALUE_CAPS *caps, LONG min, LONG max) +{ + LONG tmp = sign_extend(value, caps); + if (caps->LogicalMin > caps->LogicalMax) return 0; + if (caps->LogicalMin > tmp || caps->LogicalMax < tmp) return 0; + return min + MulDiv(tmp - caps->LogicalMin, max - min, caps->LogicalMax - caps->LogicalMin); +} + +static void translate_report_to_xinput_state(struct func_device *fdo) +{ + ULONG lx = 0, ly = 0, rx = 0, ry = 0, lt = 0, rt = 0, hat = 0; + PHIDP_PREPARSED_DATA preparsed; + USAGE usages[10]; + NTSTATUS status; + ULONG i, count; + + preparsed = fdo->device_desc.CollectionDesc->PreparsedData; + + count = ARRAY_SIZE(usages); + status = HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, usages, &count, preparsed, + fdo->report_buf, fdo->report_len); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsages HID_USAGE_PAGE_BUTTON returned %#x\n", status); + status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_HATSWITCH, &hat, preparsed, + fdo->report_buf, fdo->report_len); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_HATSWITCH returned %#x\n", status); + status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_X, &lx, preparsed, + fdo->report_buf, fdo->report_len); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_X returned %#x\n", status); + status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Y, &ly, preparsed, + fdo->report_buf, fdo->report_len); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_Y returned %#x\n", status); + status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Z, <, preparsed, + fdo->report_buf, fdo->report_len); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_Z returned %#x\n", status); + status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RX, &rx, preparsed, + fdo->report_buf, fdo->report_len); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RX returned %#x\n", status); + status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RY, &ry, preparsed, + fdo->report_buf, fdo->report_len); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RY returned %#x\n", status); + status = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_RZ, &rt, preparsed, + fdo->report_buf, fdo->report_len); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetUsageValue HID_USAGE_PAGE_GENERIC / HID_USAGE_GENERIC_RZ returned %#x\n", status); + + if (hat < 1 || hat > 8) fdo->xinput_state.buttons = 0; + else fdo->xinput_state.buttons = hat << 10; + for (i = 0; i < count; i++) + { + if (usages[i] < 1 || usages[i] > 10) continue; + fdo->xinput_state.buttons |= (1 << (usages[i] - 1)); + } + fdo->xinput_state.lx_axis = scale_value(lx, &fdo->lx_caps, 0, 65535); + fdo->xinput_state.ly_axis = scale_value(ly, &fdo->ly_caps, 0, 65535); + fdo->xinput_state.rx_axis = scale_value(rx, &fdo->rx_caps, 0, 65535); + fdo->xinput_state.ry_axis = scale_value(ry, &fdo->ry_caps, 0, 65535); + rt = scale_value(rt, &fdo->rt_caps, 0, 255); + lt = scale_value(lt, &fdo->lt_caps, 0, 255); + fdo->xinput_state.trigger = 0x8000 + (lt - rt) * 128; +} + static NTSTATUS WINAPI read_completion(DEVICE_OBJECT *device, IRP *xinput_irp, void *context) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(xinput_irp); @@ -113,7 +259,9 @@ static NTSTATUS WINAPI read_completion(DEVICE_OBJECT *device, IRP *xinput_irp, v RtlEnterCriticalSection(&fdo->cs); offset = fdo->report_buf[0] ? 0 : 1; memcpy(fdo->report_buf + offset, read_buf, read_len); - memcpy(gamepad_irp->UserBuffer, read_buf, read_len); + translate_report_to_xinput_state(fdo); + memcpy(gamepad_irp->UserBuffer, &fdo->xinput_state, sizeof(fdo->xinput_state)); + gamepad_irp->IoStatus.Information = sizeof(fdo->xinput_state); RtlLeaveCriticalSection(&fdo->cs); }
@@ -170,6 +318,7 @@ static NTSTATUS try_complete_pending_read(DEVICE_OBJECT *device, IRP *irp) static NTSTATUS WINAPI gamepad_internal_ioctl(DEVICE_OBJECT *device, IRP *irp) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); + ULONG output_len = stack->Parameters.DeviceIoControl.OutputBufferLength; ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; struct func_device *fdo = fdo_from_DEVICE_OBJECT(device);
@@ -177,20 +326,54 @@ static NTSTATUS WINAPI gamepad_internal_ioctl(DEVICE_OBJECT *device, IRP *irp)
switch (code) { - case IOCTL_HID_GET_INPUT_REPORT: + case IOCTL_HID_GET_DEVICE_DESCRIPTOR: { - HID_XFER_PACKET *packet = (HID_XFER_PACKET *)irp->UserBuffer; + HID_DESCRIPTOR *descriptor = (HID_DESCRIPTOR *)irp->UserBuffer;
- RtlEnterCriticalSection(&fdo->cs); - memcpy(packet->reportBuffer, fdo->report_buf, fdo->report_len); - irp->IoStatus.Information = fdo->report_len; - RtlLeaveCriticalSection(&fdo->cs); + irp->IoStatus.Information = sizeof(*descriptor); + if (output_len < sizeof(*descriptor)) + { + irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_BUFFER_TOO_SMALL; + } + + memset(descriptor, 0, sizeof(*descriptor)); + descriptor->bLength = sizeof(*descriptor); + descriptor->bDescriptorType = HID_HID_DESCRIPTOR_TYPE; + descriptor->bcdHID = HID_REVISION; + descriptor->bCountry = 0; + descriptor->bNumDescriptors = 1; + descriptor->DescriptorList[0].bReportType = HID_REPORT_DESCRIPTOR_TYPE; + descriptor->DescriptorList[0].wReportLength = sizeof(xinput_report_desc);
irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
+ case IOCTL_HID_GET_REPORT_DESCRIPTOR: + irp->IoStatus.Information = sizeof(xinput_report_desc); + if (output_len < sizeof(xinput_report_desc)) + { + irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_BUFFER_TOO_SMALL; + } + + memcpy(irp->UserBuffer, xinput_report_desc, sizeof(xinput_report_desc)); + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; + + case IOCTL_HID_GET_INPUT_REPORT: + case IOCTL_HID_SET_OUTPUT_REPORT: + case IOCTL_HID_GET_FEATURE: + case IOCTL_HID_SET_FEATURE: + irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_INVALID_PARAMETER; + default: IoSkipCurrentIrpStackLocation(irp); return IoCallDriver(fdo->bus_device, irp); @@ -442,13 +625,28 @@ static NTSTATUS sync_ioctl(DEVICE_OBJECT *device, DWORD code, void *in_buf, DWOR return io.Status; }
+static void check_value_caps(struct func_device *fdo, USHORT usage, HIDP_VALUE_CAPS *caps) +{ + switch (usage) + { + case HID_USAGE_GENERIC_X: fdo->lx_caps = *caps; break; + case HID_USAGE_GENERIC_Y: fdo->ly_caps = *caps; break; + case HID_USAGE_GENERIC_Z: fdo->lt_caps = *caps; break; + case HID_USAGE_GENERIC_RX: fdo->rx_caps = *caps; break; + case HID_USAGE_GENERIC_RY: fdo->ry_caps = *caps; break; + case HID_USAGE_GENERIC_RZ: fdo->rt_caps = *caps; break; + } +} + static NTSTATUS initialize_device(DEVICE_OBJECT *device) { struct func_device *fdo = fdo_from_DEVICE_OBJECT(device); - ULONG i, report_desc_len, report_count; + ULONG i, u, button_count, report_desc_len, report_count; PHIDP_REPORT_DESCRIPTOR report_desc; PHIDP_PREPARSED_DATA preparsed; + HIDP_BUTTON_CAPS *button_caps; HIDP_DEVICE_DESC device_desc; + HIDP_VALUE_CAPS *value_caps; HIDP_REPORT_IDS *reports; HID_DESCRIPTOR hid_desc; NTSTATUS status; @@ -467,18 +665,50 @@ static NTSTATUS initialize_device(DEVICE_OBJECT *device)
preparsed = device_desc.CollectionDesc->PreparsedData; status = HidP_GetCaps(preparsed, &caps); - if (status != HIDP_STATUS_SUCCESS) return status; + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetCaps returned %#x\n", status); + + button_count = 0; + if (!(button_caps = malloc(sizeof(*button_caps) * caps.NumberInputButtonCaps))) return STATUS_NO_MEMORY; + status = HidP_GetButtonCaps(HidP_Input, button_caps, &caps.NumberInputButtonCaps, preparsed); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetButtonCaps returned %#x\n", status); + else for (i = 0; i < caps.NumberInputButtonCaps; i++) + { + if (button_caps[i].UsagePage != HID_USAGE_PAGE_BUTTON) continue; + if (button_caps[i].IsRange) button_count = max(button_count, button_caps[i].Range.UsageMax); + else button_count = max(button_count, button_caps[i].NotRange.Usage); + } + free(button_caps); + if (button_count < 10) WARN("only %u buttons found\n", button_count); + + if (!(value_caps = malloc(sizeof(*value_caps) * caps.NumberInputValueCaps))) return STATUS_NO_MEMORY; + status = HidP_GetValueCaps(HidP_Input, value_caps, &caps.NumberInputValueCaps, preparsed); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetValueCaps returned %#x\n", status); + else for (i = 0; i < caps.NumberInputValueCaps; i++) + { + HIDP_VALUE_CAPS *caps = value_caps + i; + if (caps->UsagePage != HID_USAGE_PAGE_GENERIC) continue; + if (!caps->IsRange) check_value_caps(fdo, caps->NotRange.Usage, caps); + else for (u = caps->Range.UsageMin; u <=caps->Range.UsageMax; u++) check_value_caps(fdo, u, value_caps + i); + } + free(value_caps); + + if (!fdo->lt_caps.UsagePage) WARN("missing lt axis\n"); + if (!fdo->rt_caps.UsagePage) WARN("missing rt axis\n"); + if (!fdo->lx_caps.UsagePage) WARN("missing lx axis\n"); + if (!fdo->ly_caps.UsagePage) WARN("missing ly axis\n"); + if (!fdo->rx_caps.UsagePage) WARN("missing rx axis\n"); + if (!fdo->ry_caps.UsagePage) WARN("missing ry axis\n");
reports = device_desc.ReportIDs; report_count = device_desc.ReportIDsLength; for (i = 0; i < report_count; ++i) if (!reports[i].ReportID || reports[i].InputLength) break; if (i == report_count) i = 0; /* no input report?!, just use first ID */
+ fdo->device_desc = device_desc; fdo->report_len = caps.InputReportByteLength; if (!(fdo->report_buf = malloc(fdo->report_len))) return STATUS_NO_MEMORY; fdo->report_buf[0] = reports[i].ReportID;
- HidP_FreeCollectionDescription(&device_desc); return STATUS_SUCCESS; }
@@ -556,6 +786,7 @@ static NTSTATUS WINAPI fdo_pnp(DEVICE_OBJECT *device, IRP *irp) status = IoCallDriver(fdo->bus_device, irp); IoDetachDevice(fdo->bus_device); RtlDeleteCriticalSection(&fdo->cs); + HidP_FreeCollectionDescription(&fdo->device_desc); free(fdo->report_buf); IoDeleteDevice(device); return status; diff --git a/dlls/winexinput.sys/pop_hid_macros.h b/dlls/winexinput.sys/pop_hid_macros.h new file mode 100644 index 00000000000..767c26e8ecb --- /dev/null +++ b/dlls/winexinput.sys/pop_hid_macros.h @@ -0,0 +1,83 @@ +/* + * HID report helper macros. + * + * Copyright 2021 Rémi Bernon for CodeWeavers + * + * 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 + */ + +#undef Data +#undef Cnst +#undef Array +#undef Var +#undef Abs +#undef Rel +#undef NoWrap +#undef Wrap +#undef NonLin +#undef Lin +#undef NoPref +#undef Pref +#undef NoNull +#undef Null +#undef NonVol +#undef Vol +#undef Bits +#undef Buff + +#undef Physical +#undef Application +#undef Logical +#undef Report +#undef NamedArray +#undef UsageSwitch +#undef UsageModifier + +#undef SHORT_ITEM_0 +#undef SHORT_ITEM_1 +#undef SHORT_ITEM_2 +#undef SHORT_ITEM_4 + +#undef LONG_ITEM + +#undef INPUT +#undef OUTPUT +#undef FEATURE +#undef COLLECTION +#undef END_COLLECTION + +#undef USAGE_PAGE +#undef LOGICAL_MINIMUM +#undef LOGICAL_MAXIMUM +#undef PHYSICAL_MINIMUM +#undef PHYSICAL_MAXIMUM +#undef UNIT_EXPONENT +#undef UNIT +#undef REPORT_SIZE +#undef REPORT_ID +#undef REPORT_COUNT +#undef PUSH +#undef POP + +#undef USAGE +#undef USAGE_MINIMUM +#undef USAGE_MAXIMUM +#undef DESIGNATOR_INDEX +#undef DESIGNATOR_MINIMUM +#undef DESIGNATOR_MAXIMUM +#undef STRING_INDEX +#undef STRING_MINIMUM +#undef STRING_MAXIMUM +#undef DELIMITER diff --git a/dlls/winexinput.sys/psh_hid_macros.h b/dlls/winexinput.sys/psh_hid_macros.h new file mode 100644 index 00000000000..4623af20598 --- /dev/null +++ b/dlls/winexinput.sys/psh_hid_macros.h @@ -0,0 +1,85 @@ +/* + * HID report helper macros. + * + * Copyright 2021 Rémi Bernon for CodeWeavers + * + * 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 <hidusage.h> + +#define Data 0 +#define Cnst 0x01 +#define Ary 0 +#define Var 0x02 +#define Abs 0 +#define Rel 0x04 +#define NoWrap 0 +#define Wrap 0x08 +#define NonLin 0 +#define Lin 0x10 +#define NoPref 0 +#define Pref 0x20 +#define NoNull 0 +#define Null 0x40 +#define NonVol 0 +#define Vol 0x80 +#define Bits 0 +#define Buff 0x100 + +#define Physical 0x00 +#define Application 0x01 +#define Logical 0x02 +#define Report 0x03 +#define NamedArray 0x04 +#define UsageSwitch 0x05 +#define UsageModifier 0x06 + +#define SHORT_ITEM_0(tag,type) (((tag)<<4)|((type)<<2)|0) +#define SHORT_ITEM_1(tag,type,data) (((tag)<<4)|((type)<<2)|1),((data)&0xff) +#define SHORT_ITEM_2(tag,type,data) (((tag)<<4)|((type)<<2)|2),((data)&0xff),(((data)>>8)&0xff) +#define SHORT_ITEM_4(tag,type,data) (((tag)<<4)|((type)<<2)|3),((data)&0xff),(((data)>>8)&0xff),(((data)>>16)&0xff),(((data)>>24)&0xff) + +#define LONG_ITEM(tag,size) SHORT_ITEM_2(0xf,0x3,((tag)<<8)|(size)) + +#define INPUT(n,data) SHORT_ITEM_##n(0x8,0,data) +#define OUTPUT(n,data) SHORT_ITEM_##n(0x9,0,data) +#define FEATURE(n,data) SHORT_ITEM_##n(0xb,0,data) +#define COLLECTION(n,data) SHORT_ITEM_##n(0xa,0,data) +#define END_COLLECTION SHORT_ITEM_0(0xc,0) + +#define USAGE_PAGE(n,data) SHORT_ITEM_##n(0x0,1,data) +#define LOGICAL_MINIMUM(n,data) SHORT_ITEM_##n(0x1,1,data) +#define LOGICAL_MAXIMUM(n,data) SHORT_ITEM_##n(0x2,1,data) +#define PHYSICAL_MINIMUM(n,data) SHORT_ITEM_##n(0x3,1,data) +#define PHYSICAL_MAXIMUM(n,data) SHORT_ITEM_##n(0x4,1,data) +#define UNIT_EXPONENT(n,data) SHORT_ITEM_##n(0x5,1,data) +#define UNIT(n,data) SHORT_ITEM_##n(0x6,1,data) +#define REPORT_SIZE(n,data) SHORT_ITEM_##n(0x7,1,data) +#define REPORT_ID(n,data) SHORT_ITEM_##n(0x8,1,data) +#define REPORT_COUNT(n,data) SHORT_ITEM_##n(0x9,1,data) +#define PUSH(n,data) SHORT_ITEM_##n(0xa,1,data) +#define POP(n,data) SHORT_ITEM_##n(0xb,1,data) + +#define USAGE(n,data) SHORT_ITEM_##n(0x0,2,data) +#define USAGE_MINIMUM(n,data) SHORT_ITEM_##n(0x1,2,data) +#define USAGE_MAXIMUM(n,data) SHORT_ITEM_##n(0x2,2,data) +#define DESIGNATOR_INDEX(n,data) SHORT_ITEM_##n(0x3,2,data) +#define DESIGNATOR_MINIMUM(n,data) SHORT_ITEM_##n(0x4,2,data) +#define DESIGNATOR_MAXIMUM(n,data) SHORT_ITEM_##n(0x5,2,data) +#define STRING_INDEX(n,data) SHORT_ITEM_##n(0x6,2,data) +#define STRING_MINIMUM(n,data) SHORT_ITEM_##n(0x7,2,data) +#define STRING_MAXIMUM(n,data) SHORT_ITEM_##n(0x8,2,data) +#define DELIMITER(n,data) SHORT_ITEM_##n(0x9,2,data) diff --git a/dlls/xinput1_3/tests/xinput.c b/dlls/xinput1_3/tests/xinput.c index 811fe045d10..60c5817c79a 100644 --- a/dlls/xinput1_3/tests/xinput.c +++ b/dlls/xinput1_3/tests/xinput.c @@ -455,21 +455,15 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre
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); 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); @@ -522,11 +516,8 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre 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); }
@@ -551,7 +542,6 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre 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) @@ -560,11 +550,8 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre 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 == 5) check_member(value_caps[i], expect_value_caps[i], "%d", LinkCollection); - todo_wine_if(i == 5) 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); @@ -572,27 +559,19 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre 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_if(i >= 4) 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) @@ -627,9 +606,7 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre 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"); @@ -737,12 +714,10 @@ static void check_hid_caps(DWORD index, HANDLE device, PHIDP_PREPARSED_DATA pre 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)); }
It doesn't matter much but, assuming the rest is okay, maybe postpone this last patch and I'll resend it later, there's some very long lines that could be made shorter and the line wrapping got off somehow.