This only matches with digitizer devices for now, and its purpose is mainly to make testing possible with the virtual device on Wine, but could be ultimately useful for hidraw pointing devices, assuming we would support that in winebus / wineusb.
Later, it could also be extended to support mouse devices, but for now we use a different path with user drivers sending the hardware messages instead. Using a NT device for that has a lot of performance implications that will need to be resolved first, and it's unlikely the best way to do that. On the backend side, accessing the raw mouse devices usually requires privileges anyway.
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/hidclass.sys/pnp.c | 156 +++++++++++++++++++++++++++------------- 1 file changed, 105 insertions(+), 51 deletions(-)
diff --git a/dlls/hidclass.sys/pnp.c b/dlls/hidclass.sys/pnp.c index ab3cd5b2cb5..39714abc6b1 100644 --- a/dlls/hidclass.sys/pnp.c +++ b/dlls/hidclass.sys/pnp.c @@ -372,6 +372,80 @@ static NTSTATUS fdo_pnp(DEVICE_OBJECT *device, IRP *irp) } }
+static WCHAR *query_hardware_ids(DEVICE_OBJECT *device) +{ + static const WCHAR vid_pid_format[] = L"HID\VID_%04X&PID_%04X"; + static const WCHAR vid_usage_format[] = L"HID\VID_%04X&UP:%04X_U:%04X"; + static const WCHAR usage_format[] = L"HID_DEVICE_UP:%04X_U:%04X"; + static const WCHAR hid_format[] = L"HID_DEVICE"; + + BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; + HIDP_COLLECTION_DESC *desc = ext->u.pdo.device_desc.CollectionDesc; + HID_COLLECTION_INFORMATION *info = &ext->u.pdo.information; + WCHAR *dst; + DWORD size; + + size = sizeof(vid_pid_format); + size += sizeof(vid_usage_format); + size += sizeof(usage_format); + size += sizeof(hid_format); + + if ((dst = ExAllocatePool(PagedPool, size + sizeof(WCHAR)))) + { + DWORD len = size / sizeof(WCHAR), pos = 0; + pos += swprintf( dst + pos, len - pos, vid_pid_format, info->VendorID, info->ProductID ) + 1; + pos += swprintf( dst + pos, len - pos, vid_usage_format, info->VendorID, desc->UsagePage, desc->Usage ) + 1; + pos += swprintf( dst + pos, len - pos, usage_format, desc->UsagePage, desc->Usage ) + 1; + pos += swprintf( dst + pos, len - pos, hid_format ) + 1; + dst[pos] = 0; + } + + return dst; +} + +static WCHAR *query_compatible_ids(DEVICE_OBJECT *device) +{ + WCHAR *dst; + if ((dst = ExAllocatePool(PagedPool, sizeof(WCHAR)))) dst[0] = 0; + return dst; +} + +static WCHAR *query_device_id(DEVICE_OBJECT *device) +{ + BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; + DWORD size = (wcslen(ext->device_id) + 1) * sizeof(WCHAR); + WCHAR *dst; + + if ((dst = ExAllocatePool(PagedPool, size))) + memcpy(dst, ext->device_id, size); + + return dst; +} + +static WCHAR *query_instance_id(DEVICE_OBJECT *device) +{ + BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; + DWORD size = (wcslen(ext->instance_id) + 1) * sizeof(WCHAR); + WCHAR *dst; + + if ((dst = ExAllocatePool(PagedPool, size))) + memcpy(dst, ext->instance_id, size); + + return dst; +} + +static WCHAR *query_container_id(DEVICE_OBJECT *device) +{ + BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; + DWORD size = (wcslen(ext->container_id) + 1) * sizeof(WCHAR); + WCHAR *dst; + + if ((dst = ExAllocatePool(PagedPool, size))) + memcpy(dst, ext->instance_id, size); + + return dst; +} + static NTSTATUS pdo_pnp(DEVICE_OBJECT *device, IRP *irp) { IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation(irp); @@ -387,60 +461,40 @@ static NTSTATUS pdo_pnp(DEVICE_OBJECT *device, IRP *irp) { case IRP_MN_QUERY_ID: { - WCHAR *id = ExAllocatePool(PagedPool, sizeof(WCHAR) * REGSTR_VAL_MAX_HCID_LEN); - TRACE("IRP_MN_QUERY_ID[%i]\n", irpsp->Parameters.QueryId.IdType); - switch (irpsp->Parameters.QueryId.IdType) + BUS_QUERY_ID_TYPE type = irpsp->Parameters.QueryId.IdType; + switch (type) { - case BusQueryHardwareIDs: - case BusQueryCompatibleIDs: + case BusQueryHardwareIDs: + irp->IoStatus.Information = (ULONG_PTR)query_hardware_ids(device); + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; + break; + case BusQueryCompatibleIDs: + irp->IoStatus.Information = (ULONG_PTR)query_compatible_ids(device); + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; + break; + case BusQueryDeviceID: + irp->IoStatus.Information = (ULONG_PTR)query_device_id(device); + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; + break; + case BusQueryInstanceID: + irp->IoStatus.Information = (ULONG_PTR)query_instance_id(device); + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; + break; + case BusQueryContainerID: + if (ext->container_id[0]) { - WCHAR *ptr; - ptr = id; - /* Device instance ID */ - lstrcpyW(ptr, ext->device_id); - ptr += lstrlenW(ext->device_id); - lstrcpyW(ptr, L"\"); - ptr += 1; - lstrcpyW(ptr, ext->instance_id); - ptr += lstrlenW(ext->instance_id) + 1; - /* Device ID */ - lstrcpyW(ptr, ext->device_id); - ptr += lstrlenW(ext->device_id) + 1; - /* Bus ID */ - lstrcpyW(ptr, L"HID"); - ptr += lstrlenW(L"HID") + 1; - *ptr = 0; - irp->IoStatus.Information = (ULONG_PTR)id; - status = STATUS_SUCCESS; - break; + irp->IoStatus.Information = (ULONG_PTR)query_container_id(device); + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; } - case BusQueryDeviceID: - lstrcpyW(id, ext->device_id); - irp->IoStatus.Information = (ULONG_PTR)id; - status = STATUS_SUCCESS; - break; - case BusQueryInstanceID: - lstrcpyW(id, ext->instance_id); - irp->IoStatus.Information = (ULONG_PTR)id; - status = STATUS_SUCCESS; - break; - case BusQueryContainerID: - if (ext->container_id[0]) - { - lstrcpyW(id, ext->container_id); - irp->IoStatus.Information = (ULONG_PTR)id; - status = STATUS_SUCCESS; - } - else - { - ExFreePool(id); - } - break; - - case BusQueryDeviceSerialNumber: - FIXME("unimplemented id type %#x\n", irpsp->Parameters.QueryId.IdType); - ExFreePool(id); - break; + break; + default: + WARN("IRP_MN_QUERY_ID type %u, not implemented!\n", type); + break; } break; }
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/hidclass.sys/device.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/dlls/hidclass.sys/device.c b/dlls/hidclass.sys/device.c index 8fde1fe10a5..62dc9f86379 100644 --- a/dlls/hidclass.sys/device.c +++ b/dlls/hidclass.sys/device.c @@ -527,7 +527,6 @@ static NTSTATUS hid_device_xfer_report( BASE_DEVICE_EXTENSION *ext, ULONG code,
NTSTATUS WINAPI pdo_ioctl(DEVICE_OBJECT *device, IRP *irp) { - struct hid_queue *queue = irp->Tail.Overlay.OriginalFileObject->FsContext; IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; NTSTATUS status = irp->IoStatus.Status; @@ -637,7 +636,10 @@ NTSTATUS WINAPI pdo_ioctl(DEVICE_OBJECT *device, IRP *irp) if (irpsp->Parameters.DeviceIoControl.InputBufferLength != sizeof(ULONG)) status = STATUS_BUFFER_OVERFLOW; else + { + struct hid_queue *queue = irp->Tail.Overlay.OriginalFileObject->FsContext; status = hid_queue_resize( queue, *(ULONG *)irp->AssociatedIrp.SystemBuffer ); + } break; } case IOCTL_GET_NUM_DEVICE_INPUT_BUFFERS: @@ -646,6 +648,7 @@ NTSTATUS WINAPI pdo_ioctl(DEVICE_OBJECT *device, IRP *irp) status = STATUS_BUFFER_TOO_SMALL; else { + struct hid_queue *queue = irp->Tail.Overlay.OriginalFileObject->FsContext; *(ULONG *)irp->AssociatedIrp.SystemBuffer = queue->length; irp->IoStatus.Information = sizeof(ULONG); status = STATUS_SUCCESS;
From: Rémi Bernon rbernon@codeweavers.com
This only matches with digitizer devices for now, it could later be used for HID mice but for now we use a different path for that. --- MAINTAINERS | 1 + configure.ac | 1 + dlls/mouhid.sys/Makefile.in | 7 ++ dlls/mouhid.sys/main.c | 164 ++++++++++++++++++++++++++++++++ dlls/mouhid.sys/mouhid.inf | 22 +++++ dlls/mouhid.sys/mouhid.rc | 20 ++++ dlls/mouhid.sys/mouhid.sys.spec | 1 + loader/wine.inf.in | 1 + 8 files changed, 217 insertions(+) create mode 100644 dlls/mouhid.sys/Makefile.in create mode 100644 dlls/mouhid.sys/main.c create mode 100644 dlls/mouhid.sys/mouhid.inf create mode 100644 dlls/mouhid.sys/mouhid.rc create mode 100644 dlls/mouhid.sys/mouhid.sys.spec
diff --git a/MAINTAINERS b/MAINTAINERS index 4bab36bfdf9..2e259f12d70 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -169,6 +169,7 @@ P: Aric Stewart aric@codeweavers.com F: dlls/hid/ F: dlls/hidclass.sys/ F: dlls/hidparse.sys/ +F: dlls/mouhid.sys/ F: dlls/winehid.sys/
HTTP server diff --git a/configure.ac b/configure.ac index 57f64ebffc3..533c3b974d1 100644 --- a/configure.ac +++ b/configure.ac @@ -2792,6 +2792,7 @@ WINE_CONFIG_MAKEFILE(dlls/mmdevapi/tests) WINE_CONFIG_MAKEFILE(dlls/mmdevldr.vxd,enable_win16) WINE_CONFIG_MAKEFILE(dlls/mmsystem.dll16,enable_win16) WINE_CONFIG_MAKEFILE(dlls/monodebg.vxd,enable_win16) +WINE_CONFIG_MAKEFILE(dlls/mouhid.sys) WINE_CONFIG_MAKEFILE(dlls/mountmgr.sys) WINE_CONFIG_MAKEFILE(dlls/mouse.drv16,enable_win16) WINE_CONFIG_MAKEFILE(dlls/mp3dmod) diff --git a/dlls/mouhid.sys/Makefile.in b/dlls/mouhid.sys/Makefile.in new file mode 100644 index 00000000000..816d7e58120 --- /dev/null +++ b/dlls/mouhid.sys/Makefile.in @@ -0,0 +1,7 @@ +MODULE = mouhid.sys +IMPORTS = ntoskrnl +EXTRADLLFLAGS = -Wl,--subsystem,native + +SOURCES = \ + main.c \ + mouhid.rc diff --git a/dlls/mouhid.sys/main.c b/dlls/mouhid.sys/main.c new file mode 100644 index 00000000000..af54ffbd70b --- /dev/null +++ b/dlls/mouhid.sys/main.c @@ -0,0 +1,164 @@ +/* + * Copyright 2024 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 <stdarg.h> +#include <stdlib.h> + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "winioctl.h" +#include "ntuser.h" + +#include "ddk/wdm.h" +#include "ddk/hidport.h" +#include "ddk/hidpddi.h" +#include "ddk/hidtypes.h" + +#include "wine/hid.h" +#include "wine/debug.h" +#include "wine/list.h" + +WINE_DEFAULT_DEBUG_CHANNEL(hid); + +struct device +{ + LONG removed; + DEVICE_OBJECT *bus_device; +}; + +static inline struct device *impl_from_DEVICE_OBJECT( DEVICE_OBJECT *device ) +{ + return (struct device *)device->DeviceExtension; +} + +static NTSTATUS WINAPI driver_ioctl( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + struct device *impl = impl_from_DEVICE_OBJECT( device ); + + if (InterlockedOr( &impl->removed, FALSE )) + { + irp->IoStatus.Status = STATUS_DELETE_PENDING; + irp->IoStatus.Information = 0; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_DELETE_PENDING; + } + + TRACE( "device %p, irp %p, code %#lx, bus_device %p.\n", device, irp, code, impl->bus_device ); + + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( impl->bus_device, irp ); +} + +static NTSTATUS WINAPI set_event_completion( DEVICE_OBJECT *device, IRP *irp, void *context ) +{ + if (irp->PendingReturned) KeSetEvent( (KEVENT *)context, IO_NO_INCREMENT, FALSE ); + return STATUS_MORE_PROCESSING_REQUIRED; +} + +static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + struct device *impl = impl_from_DEVICE_OBJECT( device ); + UCHAR code = stack->MinorFunction; + NTSTATUS status; + KEVENT event; + + TRACE( "device %p, irp %p, code %#x, bus_device %p.\n", device, irp, code, impl->bus_device ); + + switch (stack->MinorFunction) + { + case IRP_MN_START_DEVICE: + KeInitializeEvent( &event, NotificationEvent, FALSE ); + IoCopyCurrentIrpStackLocationToNext( irp ); + IoSetCompletionRoutine( irp, set_event_completion, &event, TRUE, TRUE, TRUE ); + + status = IoCallDriver( impl->bus_device, irp ); + if (status == STATUS_PENDING) + { + KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, NULL ); + status = irp->IoStatus.Status; + } + + if (status) irp->IoStatus.Status = status; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return status; + + case IRP_MN_SURPRISE_REMOVAL: + status = STATUS_SUCCESS; + break; + + case IRP_MN_REMOVE_DEVICE: + IoSkipCurrentIrpStackLocation( irp ); + status = IoCallDriver( impl->bus_device, irp ); + IoDetachDevice( impl->bus_device ); + IoDeleteDevice( device ); + return status; + + default: + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( impl->bus_device, irp ); + } + + return STATUS_SUCCESS; +} + +static NTSTATUS WINAPI add_device( DRIVER_OBJECT *driver, DEVICE_OBJECT *bus_device ) +{ + struct device *impl; + DEVICE_OBJECT *device; + NTSTATUS status; + + TRACE( "driver %p, bus_device %p.\n", driver, bus_device ); + + if ((status = IoCreateDevice( driver, sizeof(struct device), NULL, FILE_DEVICE_BUS_EXTENDER, + 0, FALSE, &device ))) + { + ERR( "failed to create bus FDO, status %#lx.\n", status ); + return status; + } + + impl = device->DeviceExtension; + impl->bus_device = bus_device; + + IoAttachDeviceToDeviceStack( device, bus_device ); + device->Flags &= ~DO_DEVICE_INITIALIZING; + return STATUS_SUCCESS; +} + +static void WINAPI driver_unload( DRIVER_OBJECT *driver ) +{ + TRACE( "driver %p\n", driver ); +} + +NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path ) +{ + TRACE( "driver %p, path %s.\n", driver, debugstr_w(path->Buffer) ); + + driver->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = driver_ioctl; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = driver_ioctl; + driver->MajorFunction[IRP_MJ_PNP] = driver_pnp; + driver->DriverExtension->AddDevice = add_device; + driver->DriverUnload = driver_unload; + + return STATUS_SUCCESS; +} diff --git a/dlls/mouhid.sys/mouhid.inf b/dlls/mouhid.sys/mouhid.inf new file mode 100644 index 00000000000..efc65794fdc --- /dev/null +++ b/dlls/mouhid.sys/mouhid.inf @@ -0,0 +1,22 @@ +[Version] +Signature="$CHICAGO$" +ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318} +Class=System + +[Manufacturer] +Wine=mfg_section + +[mfg_section] +HID touch screen device=device_section,HID_DEVICE_UP:000D_U:0004 + +[device_section.Services] +AddService = mouhid,0x2,svc_section + +[svc_section] +Description="Wine HID mouse device driver" +DisplayName="Wine HID mouse" +ServiceBinary="%12%\mouhid.sys" +LoadOrderGroup="WinePlugPlay" +ServiceType=1 +StartType=3 +ErrorControl=1 diff --git a/dlls/mouhid.sys/mouhid.rc b/dlls/mouhid.sys/mouhid.rc new file mode 100644 index 00000000000..e395b5f5fea --- /dev/null +++ b/dlls/mouhid.sys/mouhid.rc @@ -0,0 +1,20 @@ +/* + * Copyright 2024 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 + */ + +/* @makedep: mouhid.inf */ +1 WINE_DATA_FILE mouhid.inf diff --git a/dlls/mouhid.sys/mouhid.sys.spec b/dlls/mouhid.sys/mouhid.sys.spec new file mode 100644 index 00000000000..76421d7e35b --- /dev/null +++ b/dlls/mouhid.sys/mouhid.sys.spec @@ -0,0 +1 @@ +# nothing to export diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 06551152f3a..35644cbd285 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -2608,6 +2608,7 @@ services,"@%11%\ws2_32.dll,-4"
[InfFiles] input.inf,"@%12%\hidclass.sys,-1" +mouhid.inf,"@%12%\mouhid.sys,-1" winebus.inf,"@%12%\winebus.sys,-1" winehid.inf,"@%12%\winehid.sys,-1" wineusb.inf,"@%12%\wineusb.sys,-1"
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/dinput/tests/device8.c | 1 - dlls/mouhid.sys/Makefile.in | 2 +- dlls/mouhid.sys/main.c | 119 ++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-)
diff --git a/dlls/dinput/tests/device8.c b/dlls/dinput/tests/device8.c index fa5b800b74f..2c44bb4c5c6 100644 --- a/dlls/dinput/tests/device8.c +++ b/dlls/dinput/tests/device8.c @@ -1906,7 +1906,6 @@ static void test_hid_touch_screen(void) .report_id = 1, .report_len = 2, .report_buf = {1,0x02}, - .todo = TRUE, };
RAWINPUTDEVICE rawdevice = {.usUsagePage = HID_USAGE_PAGE_DIGITIZER, .usUsage = HID_USAGE_DIGITIZER_TOUCH_SCREEN}; diff --git a/dlls/mouhid.sys/Makefile.in b/dlls/mouhid.sys/Makefile.in index 816d7e58120..52e65a82cb3 100644 --- a/dlls/mouhid.sys/Makefile.in +++ b/dlls/mouhid.sys/Makefile.in @@ -1,5 +1,5 @@ MODULE = mouhid.sys -IMPORTS = ntoskrnl +IMPORTS = ntoskrnl hidparse EXTRADLLFLAGS = -Wl,--subsystem,native
SOURCES = \ diff --git a/dlls/mouhid.sys/main.c b/dlls/mouhid.sys/main.c index af54ffbd70b..f652857f7ac 100644 --- a/dlls/mouhid.sys/main.c +++ b/dlls/mouhid.sys/main.c @@ -42,6 +42,13 @@ struct device { LONG removed; DEVICE_OBJECT *bus_device; + PHIDP_PREPARSED_DATA preparsed; + + ULONG caps_count; + ULONG contact_max; + HIDP_VALUE_CAPS *id_caps; + HIDP_VALUE_CAPS *x_caps; + HIDP_VALUE_CAPS *y_caps; };
static inline struct device *impl_from_DEVICE_OBJECT( DEVICE_OBJECT *device ) @@ -49,6 +56,21 @@ static inline struct device *impl_from_DEVICE_OBJECT( DEVICE_OBJECT *device ) return (struct device *)device->DeviceExtension; }
+static inline 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 inline 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 NTSTATUS WINAPI driver_ioctl( DEVICE_OBJECT *device, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); @@ -69,6 +91,98 @@ static NTSTATUS WINAPI driver_ioctl( DEVICE_OBJECT *device, IRP *irp ) return IoCallDriver( impl->bus_device, irp ); }
+static NTSTATUS call_hid_device( DEVICE_OBJECT *device, DWORD major, DWORD code, void *in_buf, + DWORD in_len, void *out_buf, DWORD out_len ) +{ + struct device *impl = impl_from_DEVICE_OBJECT( device ); + IO_STATUS_BLOCK io; + NTSTATUS status; + KEVENT event; + IRP *irp; + + KeInitializeEvent( &event, NotificationEvent, FALSE ); + irp = IoBuildDeviceIoControlRequest( code, device, in_buf, in_len, out_buf, out_len, + FALSE, &event, &io ); + if (!irp) return STATUS_NO_MEMORY; + + status = IoCallDriver( impl->bus_device, irp ); + if (status == STATUS_PENDING) KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, NULL ); + return io.Status; +} + +static NTSTATUS initialize_device( DEVICE_OBJECT *device ) +{ + struct device *impl = impl_from_DEVICE_OBJECT( device ); + HID_COLLECTION_INFORMATION info; + USHORT usage, count, report_len; + HIDP_VALUE_CAPS value_caps; + char *report_buf; + NTSTATUS status; + HIDP_CAPS caps; + + if ((status = call_hid_device( device, IRP_MJ_DEVICE_CONTROL, IOCTL_HID_GET_COLLECTION_INFORMATION, + &info, 0, &info, sizeof(info) ))) + return status; + if (!(impl->preparsed = malloc( info.DescriptorSize ))) return STATUS_NO_MEMORY; + if ((status = call_hid_device( device, IRP_MJ_DEVICE_CONTROL, IOCTL_HID_GET_COLLECTION_DESCRIPTOR, + NULL, 0, impl->preparsed, info.DescriptorSize ))) + return status; + + status = HidP_GetCaps( impl->preparsed, &caps ); + if (status != HIDP_STATUS_SUCCESS) return status; + + count = 1; + usage = HID_USAGE_DIGITIZER_CONTACT_COUNT_MAX; + status = HidP_GetSpecificValueCaps( HidP_Feature, HID_USAGE_PAGE_DIGITIZER, 0, usage, + &value_caps, &count, impl->preparsed ); + if (status == HIDP_STATUS_SUCCESS) + { + report_len = caps.FeatureReportByteLength; + if (!(report_buf = malloc( report_len ))) return STATUS_NO_MEMORY; + report_buf[0] = value_caps.ReportID; + + status = call_hid_device( device, IRP_MJ_DEVICE_CONTROL, IOCTL_HID_GET_FEATURE, NULL, 0, report_buf, report_len ); + if (!status) status = HidP_GetUsageValue( HidP_Feature, HID_USAGE_PAGE_DIGITIZER, 0, usage, + &impl->contact_max, impl->preparsed, report_buf, report_len ); + free( report_buf ); + if (status && status != HIDP_STATUS_SUCCESS) return status; + + count = 1; + usage = HID_USAGE_DIGITIZER_CONTACT_COUNT; + status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_DIGITIZER, 0, usage, + &value_caps, &count, impl->preparsed ); + if (status != HIDP_STATUS_SUCCESS) goto failed; + + count = caps.NumberLinkCollectionNodes; + usage = HID_USAGE_DIGITIZER_CONTACT_ID; + if (!(impl->id_caps = malloc( sizeof(*impl->id_caps) * caps.NumberLinkCollectionNodes ))) return STATUS_NO_MEMORY; + status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_DIGITIZER, 0, usage, + impl->id_caps, &count, impl->preparsed ); + if (status != HIDP_STATUS_SUCCESS) goto failed; + impl->caps_count = count; + + count = impl->caps_count; + usage = HID_USAGE_GENERIC_X; + if (!(impl->x_caps = malloc( sizeof(*impl->x_caps) * impl->caps_count ))) return STATUS_NO_MEMORY; + status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0, usage, + impl->x_caps, &count, impl->preparsed ); + if (status != HIDP_STATUS_SUCCESS) goto failed; + + count = impl->caps_count; + usage = HID_USAGE_GENERIC_Y; + if (!(impl->y_caps = malloc( sizeof(*impl->y_caps) * impl->caps_count ))) return STATUS_NO_MEMORY; + status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0, usage, + impl->y_caps, &count, impl->preparsed ); + if (status != HIDP_STATUS_SUCCESS) goto failed; + + return STATUS_SUCCESS; + } + +failed: + ERR( "%#x usage not found, unsupported device\n", usage ); + return STATUS_NOT_SUPPORTED; +} + static NTSTATUS WINAPI set_event_completion( DEVICE_OBJECT *device, IRP *irp, void *context ) { if (irp->PendingReturned) KeSetEvent( (KEVENT *)context, IO_NO_INCREMENT, FALSE ); @@ -98,6 +212,7 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, NULL ); status = irp->IoStatus.Status; } + if (!status) status = initialize_device( device );
if (status) irp->IoStatus.Status = status; IoCompleteRequest( irp, IO_NO_INCREMENT ); @@ -111,6 +226,10 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) IoSkipCurrentIrpStackLocation( irp ); status = IoCallDriver( impl->bus_device, irp ); IoDetachDevice( impl->bus_device ); + free( impl->id_caps ); + free( impl->x_caps ); + free( impl->y_caps ); + free( impl->preparsed ); IoDeleteDevice( device ); return status;
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mouhid.sys/main.c | 93 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-)
diff --git a/dlls/mouhid.sys/main.c b/dlls/mouhid.sys/main.c index f652857f7ac..247740301a5 100644 --- a/dlls/mouhid.sys/main.c +++ b/dlls/mouhid.sys/main.c @@ -44,6 +44,11 @@ struct device DEVICE_OBJECT *bus_device; PHIDP_PREPARSED_DATA preparsed;
+ FILE_OBJECT dummy_file; + IO_STATUS_BLOCK io; + ULONG report_len; + char *report_buf; + ULONG caps_count; ULONG contact_max; HIDP_VALUE_CAPS *id_caps; @@ -71,6 +76,71 @@ static inline LONG scale_value( ULONG value, const HIDP_VALUE_CAPS *caps, LONG m return min + MulDiv( tmp - caps->LogicalMin, max - min, caps->LogicalMax - caps->LogicalMin ); }
+static NTSTATUS WINAPI read_completion( DEVICE_OBJECT *device, IRP *irp, void *context ); + +static NTSTATUS start_device_read( DEVICE_OBJECT *device ) +{ + struct device *impl = impl_from_DEVICE_OBJECT( device ); + IO_STACK_LOCATION *stack; + NTSTATUS status; + IRP *irp; + + TRACE( "device %p\n", device ); + + irp = IoBuildAsynchronousFsdRequest( IRP_MJ_READ, device, impl->report_buf, + impl->report_len, NULL, &impl->io ); + if (!irp) return STATUS_NO_MEMORY; + irp->Tail.Overlay.OriginalFileObject = &impl->dummy_file; + stack = IoGetNextIrpStackLocation( irp ); + stack->FileObject = &impl->dummy_file; + + TRACE( "created irp %p\n", irp ); + + IoSetCompletionRoutine( irp, read_completion, device, TRUE, TRUE, TRUE ); + if ((status = IoCallDriver( impl->bus_device, irp )) && status != STATUS_PENDING) return status; + return STATUS_SUCCESS; +} + +static void process_hid_report( struct device *impl, char *report_buf, UINT report_len ) +{ + ULONG contact_count; + NTSTATUS status; + + TRACE( "impl %p, report_buf %p, report_len %u\n", impl, report_buf, report_len ); + + status = HidP_GetUsageValue( HidP_Input, HID_USAGE_PAGE_DIGITIZER, 0, HID_USAGE_DIGITIZER_CONTACT_COUNT, + &contact_count, impl->preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) return; + if (contact_count > impl->contact_max) + { + WARN( "got %lu contacts, capping to %lu.\n", contact_count, impl->contact_max ); + contact_count = impl->contact_max; + } + + FIXME( "Not implemented\n" ); +} + +static NTSTATUS WINAPI read_completion( DEVICE_OBJECT *device, IRP *irp, void *context ) +{ + struct device *impl = impl_from_DEVICE_OBJECT( context ); + NTSTATUS status; + + TRACE( "device %p, irp %p, context %p\n", device, irp, context ); + + if (irp->IoStatus.Status) + WARN( "device read failed with status %#lx, stopping\n", irp->IoStatus.Status ); + else + { + process_hid_report( impl, impl->report_buf, impl->report_len ); + + if (!InterlockedOr( &impl->removed, FALSE ) && (status = start_device_read( context ))) + ERR( "Failed to start next read, status %#lx\n", status ); + } + + if (irp->PendingReturned) IoMarkIrpPending( irp ); + return STATUS_SUCCESS; +} + static NTSTATUS WINAPI driver_ioctl( DEVICE_OBJECT *device, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); @@ -95,16 +165,26 @@ static NTSTATUS call_hid_device( DEVICE_OBJECT *device, DWORD major, DWORD code, DWORD in_len, void *out_buf, DWORD out_len ) { struct device *impl = impl_from_DEVICE_OBJECT( device ); + IO_STACK_LOCATION *stack; IO_STATUS_BLOCK io; NTSTATUS status; KEVENT event; IRP *irp;
KeInitializeEvent( &event, NotificationEvent, FALSE ); - irp = IoBuildDeviceIoControlRequest( code, device, in_buf, in_len, out_buf, out_len, - FALSE, &event, &io ); + if (major == IRP_MJ_DEVICE_CONTROL) + irp = IoBuildDeviceIoControlRequest( code, device, in_buf, in_len, out_buf, out_len, + FALSE, &event, &io ); + else + irp = IoBuildSynchronousFsdRequest( code, device, out_buf, out_len, NULL, &event, &io ); if (!irp) return STATUS_NO_MEMORY;
+ irp->Tail.Overlay.OriginalFileObject = &impl->dummy_file; + if (code == IRP_MJ_CREATE) irp->Flags |= IRP_CREATE_OPERATION; + if (code == IRP_MJ_CLOSE) irp->Flags |= IRP_CLOSE_OPERATION; + stack = IoGetNextIrpStackLocation( irp ); + stack->FileObject = &impl->dummy_file; + status = IoCallDriver( impl->bus_device, irp ); if (status == STATUS_PENDING) KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, NULL ); return io.Status; @@ -120,6 +200,7 @@ static NTSTATUS initialize_device( DEVICE_OBJECT *device ) NTSTATUS status; HIDP_CAPS caps;
+ if ((status = call_hid_device( device, IRP_MJ_CREATE, 0, NULL, 0, NULL, 0 ))) return status; if ((status = call_hid_device( device, IRP_MJ_DEVICE_CONTROL, IOCTL_HID_GET_COLLECTION_INFORMATION, &info, 0, &info, sizeof(info) ))) return status; @@ -175,6 +256,10 @@ static NTSTATUS initialize_device( DEVICE_OBJECT *device ) impl->y_caps, &count, impl->preparsed ); if (status != HIDP_STATUS_SUCCESS) goto failed;
+ impl->report_len = caps.InputReportByteLength; + if (!(impl->report_buf = malloc( impl->report_len ))) return STATUS_NO_MEMORY; + impl->report_buf[0] = value_caps.ReportID; + return STATUS_SUCCESS; }
@@ -213,6 +298,7 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) status = irp->IoStatus.Status; } if (!status) status = initialize_device( device ); + if (!status) status = start_device_read( device );
if (status) irp->IoStatus.Status = status; IoCompleteRequest( irp, IO_NO_INCREMENT ); @@ -220,6 +306,8 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp )
case IRP_MN_SURPRISE_REMOVAL: status = STATUS_SUCCESS; + if (InterlockedExchange( &impl->removed, TRUE )) break; + call_hid_device( device, IRP_MJ_CLOSE, 0, NULL, 0, NULL, 0 ); break;
case IRP_MN_REMOVE_DEVICE: @@ -229,6 +317,7 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) free( impl->id_caps ); free( impl->x_caps ); free( impl->y_caps ); + free( impl->report_buf ); free( impl->preparsed ); IoDeleteDevice( device ); return status;
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mouhid.sys/main.c | 92 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-)
diff --git a/dlls/mouhid.sys/main.c b/dlls/mouhid.sys/main.c index 247740301a5..c1b4288e1dd 100644 --- a/dlls/mouhid.sys/main.c +++ b/dlls/mouhid.sys/main.c @@ -38,6 +38,13 @@
WINE_DEFAULT_DEBUG_CHANNEL(hid);
+struct contact +{ + struct list entry; + ULONG id; + POINT pos; +}; + struct device { LONG removed; @@ -49,6 +56,9 @@ struct device ULONG report_len; char *report_buf;
+ ULONG contact_count; + struct list contacts; + ULONG caps_count; ULONG contact_max; HIDP_VALUE_CAPS *id_caps; @@ -101,10 +111,56 @@ static NTSTATUS start_device_read( DEVICE_OBJECT *device ) return STATUS_SUCCESS; }
+static void add_contact( struct device *impl, struct list *old_contacts, ULONG id, LONG x, LONG y ) +{ + struct contact *contact; + + LIST_FOR_EACH_ENTRY( contact, old_contacts, struct contact, entry ) + if (contact->id == id) break; + + if (&contact->entry != old_contacts) + { + list_remove( &contact->entry ); + + contact->pos.x = x; + contact->pos.y = y; + TRACE( "updating contact %#lx, pos %s\n", contact->id, wine_dbgstr_point( &contact->pos ) ); + } + else if ((contact = calloc( 1, sizeof(*contact) ))) + { + contact->id = id; + contact->pos.x = x; + contact->pos.y = y; + TRACE( "new contact %#lx, pos %s\n", contact->id, wine_dbgstr_point( &contact->pos ) ); + } + else + { + ERR( "failed to allocate new contact\n" ); + return; + } + + list_add_tail( &impl->contacts, &contact->entry ); +} + +static void release_contacts( struct list *contacts ) +{ + struct contact *contact, *next; + + LIST_FOR_EACH_ENTRY_SAFE( contact, next, contacts, struct contact, entry ) + { + TRACE( "releasing contact %#lx, pos %s\n", contact->id, wine_dbgstr_point( &contact->pos ) ); + list_remove( &contact->entry ); + free( contact ); + } +} + static void process_hid_report( struct device *impl, char *report_buf, UINT report_len ) { - ULONG contact_count; + struct list old_contacts = LIST_INIT( old_contacts ); + ULONG i, value, contact_count, usage_count, id; + LONG x = 0, y = 0; NTSTATUS status; + USHORT usage;
TRACE( "impl %p, report_buf %p, report_len %u\n", impl, report_buf, report_len );
@@ -117,7 +173,36 @@ static void process_hid_report( struct device *impl, char *report_buf, UINT repo contact_count = impl->contact_max; }
- FIXME( "Not implemented\n" ); + list_move_tail( &old_contacts, &impl->contacts ); + + for (i = 0; i < impl->caps_count; i++) + { + USHORT collection = impl->id_caps[i].LinkCollection; + + usage_count = 1; + usage = HID_USAGE_DIGITIZER_TIP_SWITCH; + status = HidP_GetUsages( HidP_Input, HID_USAGE_PAGE_DIGITIZER, collection, &usage, &usage_count, + impl->preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS || !usage_count) continue; + + status = HidP_GetUsageValue( HidP_Input, HID_USAGE_PAGE_DIGITIZER, collection, HID_USAGE_DIGITIZER_CONTACT_ID, + &id, impl->preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) continue; + + status = HidP_GetUsageValue( HidP_Input, HID_USAGE_PAGE_GENERIC, collection, HID_USAGE_GENERIC_X, + &value, impl->preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) continue; + else x = scale_value( value, impl->x_caps + i, 0, 65535 ); + + status = HidP_GetUsageValue( HidP_Input, HID_USAGE_PAGE_GENERIC, collection, HID_USAGE_GENERIC_Y, + &value, impl->preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) continue; + else y = scale_value( value, impl->y_caps + i, 0, 65535 ); + + add_contact( impl, &old_contacts, id, x, y ); + } + + release_contacts( &old_contacts ); }
static NTSTATUS WINAPI read_completion( DEVICE_OBJECT *device, IRP *irp, void *context ) @@ -200,6 +285,8 @@ static NTSTATUS initialize_device( DEVICE_OBJECT *device ) NTSTATUS status; HIDP_CAPS caps;
+ list_init( &impl->contacts ); + if ((status = call_hid_device( device, IRP_MJ_CREATE, 0, NULL, 0, NULL, 0 ))) return status; if ((status = call_hid_device( device, IRP_MJ_DEVICE_CONTROL, IOCTL_HID_GET_COLLECTION_INFORMATION, &info, 0, &info, sizeof(info) ))) @@ -314,6 +401,7 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) IoSkipCurrentIrpStackLocation( irp ); status = IoCallDriver( impl->bus_device, irp ); IoDetachDevice( impl->bus_device ); + release_contacts( &impl->contacts ); free( impl->id_caps ); free( impl->x_caps ); free( impl->y_caps );
Test failure is an unrelated nsi crash.