From: Thomas Portal <portal.thomas@protonmail.com> Wine's synthesized HID devices only carry the WINEBUS\WINE_COMP_HID compatible ID. A real Windows USB-HID device also exposes the USB class compatible IDs (USB\Class_03&SubClass_xx&Prot_yy and the two shorter forms) on its USB interface parent. Applications that walk to that parent and validate the IDs, for example the Elgato Stream Deck app through CM_Get_Parent, reject the device when they are missing. winebus does not enumerate a separate USB interface device, so report the IDs on the HID device itself, which is what such an application reaches from the HID node. wineusb.sys already reports the same IDs in the same format for the devices it enumerates, so only the hosting device differs. The class, subclass and protocol come from the udev interface and are reported only for USB devices that carry interface information, which leaves the other backends unchanged. The existing WINE_COMP_XINPUT and WINE_COMP_HID ids stay ahead of them so driver binding does not change. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59891 Signed-off-by: Thomas Portal <portal.thomas@protonmail.com> --- dlls/winebus.sys/bus_udev.c | 13 +++++++++++++ dlls/winebus.sys/main.c | 39 ++++++++++++++++++++++++++++++++----- dlls/winebus.sys/unixlib.h | 8 ++++++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index b307124c3be..e1714d0fa78 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -1174,6 +1174,17 @@ static void get_device_subsystem_info(struct udev_device *dev, const char *subsy } } +static void get_usb_interface_info(struct udev_device *dev, struct device_desc *desc) +{ + struct udev_device *iface; + const char *tmp; + + if (!(iface = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface"))) return; + if ((tmp = udev_device_get_sysattr_value(iface, "bInterfaceClass"))) sscanf(tmp, "%x", &desc->class); + if ((tmp = udev_device_get_sysattr_value(iface, "bInterfaceSubClass"))) sscanf(tmp, "%x", &desc->subclass); + if ((tmp = udev_device_get_sysattr_value(iface, "bInterfaceProtocol"))) sscanf(tmp, "%x", &desc->protocol); +} + static NTSTATUS hidraw_device_create(struct udev_device *dev, int fd, const char *devnode, struct device_desc desc) { #ifdef HAVE_LINUX_HIDRAW_H @@ -1370,6 +1381,8 @@ static void udev_add_device(struct udev_device *dev, int fd) if (bus == BUS_BLUETOOTH) desc.bus_type = BUS_TYPE_BLUETOOTH; else if (bus == BUS_USB) desc.bus_type = BUS_TYPE_USB; + if (desc.bus_type == BUS_TYPE_USB) get_usb_interface_info(dev, &desc); + if (!(subsystem = udev_device_get_subsystem(dev))) { WARN("udev_device_get_subsystem failed for %s.\n", debugstr_a(devnode)); diff --git a/dlls/winebus.sys/main.c b/dlls/winebus.sys/main.c index a6227be9908..74b53b50c66 100644 --- a/dlls/winebus.sys/main.c +++ b/dlls/winebus.sys/main.c @@ -259,16 +259,45 @@ static WCHAR *get_compatible_ids(DEVICE_OBJECT *device) static const WCHAR xinput_compat[] = L"WINEBUS\\WINE_COMP_XINPUT"; static const WCHAR hid_compat[] = L"WINEBUS\\WINE_COMP_HID"; struct device_extension *ext = (struct device_extension *)device->DeviceExtension; - DWORD size = sizeof(hid_compat); - WCHAR *dst; + WCHAR usb_compat[3][40]; + DWORD usb_count = 0, size, len, i; + WCHAR *dst, *pos; + + /* A real Windows USB-HID device exposes the USB class compatible IDs on its USB + interface parent. winebus has no separate interface device, so report them on + the HID device, where an application reaches them from the HID node through + CM_Get_Parent. desc.class stays zero for backends that read no USB interface, + which leaves them unchanged. */ + if (ext->desc.bus_type == BUS_TYPE_USB && ext->desc.class) + { + swprintf(usb_compat[usb_count++], ARRAY_SIZE(usb_compat[0]), + L"USB\\Class_%02x&SubClass_%02x&Prot_%02x", ext->desc.class, ext->desc.subclass, ext->desc.protocol); + swprintf(usb_compat[usb_count++], ARRAY_SIZE(usb_compat[0]), + L"USB\\Class_%02x&SubClass_%02x", ext->desc.class, ext->desc.subclass); + swprintf(usb_compat[usb_count++], ARRAY_SIZE(usb_compat[0]), L"USB\\Class_%02x", ext->desc.class); + } + size = sizeof(hid_compat); if (ext->desc.is_gamepad) size += sizeof(xinput_compat); + for (i = 0; i < usb_count; i++) size += (wcslen(usb_compat[i]) + 1) * sizeof(WCHAR); if ((dst = ExAllocatePool(PagedPool, size + sizeof(WCHAR)))) { - if (ext->desc.is_gamepad) memcpy(dst, xinput_compat, sizeof(xinput_compat)); - memcpy((char *)dst + size - sizeof(hid_compat), hid_compat, sizeof(hid_compat)); - dst[size / sizeof(WCHAR)] = 0; + pos = dst; + if (ext->desc.is_gamepad) + { + memcpy(pos, xinput_compat, sizeof(xinput_compat)); + pos += sizeof(xinput_compat) / sizeof(WCHAR); + } + memcpy(pos, hid_compat, sizeof(hid_compat)); + pos += sizeof(hid_compat) / sizeof(WCHAR); + for (i = 0; i < usb_count; i++) + { + len = wcslen(usb_compat[i]) + 1; + memcpy(pos, usb_compat[i], len * sizeof(WCHAR)); + pos += len; + } + *pos = 0; } return dst; diff --git a/dlls/winebus.sys/unixlib.h b/dlls/winebus.sys/unixlib.h index d2b17f4c6d3..fa99c62ea40 100644 --- a/dlls/winebus.sys/unixlib.h +++ b/dlls/winebus.sys/unixlib.h @@ -39,6 +39,9 @@ struct device_desc UINT input; UINT uid; UINT bus_type; + UINT class; + UINT subclass; + UINT protocol; BOOL is_gamepad; BOOL is_hidraw; @@ -159,9 +162,10 @@ enum unix_funcs static inline const char *debugstr_device_desc(struct device_desc *desc) { if (!desc) return "(null)"; - return wine_dbg_sprintf("{vid %04x, pid %04x, version %04x, input %d, uid %08x, is_gamepad %u, is_hidraw %u, bus_type %u}", + return wine_dbg_sprintf("{vid %04x, pid %04x, version %04x, input %d, uid %08x, is_gamepad %u, is_hidraw %u, bus_type %u, class %02x, subclass %02x, protocol %02x}", desc->vid, desc->pid, desc->version, desc->input, desc->uid, - desc->is_gamepad, desc->is_hidraw, desc->bus_type); + desc->is_gamepad, desc->is_hidraw, desc->bus_type, + desc->class, desc->subclass, desc->protocol); } static inline BOOL is_xbox_gamepad(WORD vid, WORD pid) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11205