Skip to content

Commit

Permalink
windows: try to get USB device serial number if not provided by HidD_…
Browse files Browse the repository at this point in the history
…GetSerialNumberString (signal11#464)

This is efficient for Xbox Common Controller class (XUSB) devices like Xbox 360 or Xbox One controllers that are missing serial number via usual HID API.
  • Loading branch information
DJm00n authored Mar 13, 2023
1 parent 7eedb61 commit bd6be4d
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 58 deletions.
8 changes: 3 additions & 5 deletions hidapi/hidapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,9 @@ extern "C" {
/** The USB interface which this logical device
represents.
* Valid on both Linux implementations in all cases.
* Valid on the Windows implementation only if the device
contains more than one interface.
* Valid on the Mac implementation if and only if the device
is a USB HID device. */
Valid only if the device is a USB HID device.
Set to -1 in all other cases.
*/
int interface_number;

/** Pointer to the next device */
Expand Down
179 changes: 134 additions & 45 deletions windows/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ typedef LONG NTSTATUS;

/*#define HIDAPI_USE_DDK*/

#include <devpropdef.h>
#include "hidapi_cfgmgr32.h"
#include "hidapi_hidclass.h"
#include "hidapi_hidsdi.h"
Expand Down Expand Up @@ -409,6 +408,132 @@ static void* hid_internal_get_device_interface_property(const wchar_t* interface
return property_value;
}

static void hid_internal_towupper(wchar_t* string)
{
for (wchar_t* p = string; *p; ++p) *p = towupper(*p);
}

static int hid_internal_extract_int_token_value(wchar_t* string, const wchar_t* token)
{
int token_value;
wchar_t* startptr, * endptr;

startptr = wcsstr(string, token);
if (!startptr)
return -1;

startptr += wcslen(token);
token_value = wcstol(startptr, &endptr, 16);
if (endptr == startptr)
return -1;

return token_value;
}

static void hid_internal_get_usb_info(struct hid_device_info* dev, DEVINST dev_node)
{
wchar_t *device_id = NULL, *hardware_ids = NULL;

device_id = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
if (!device_id)
goto end;

/* Normalize to upper case */
hid_internal_towupper(device_id);

/* Check for Xbox Common Controller class (XUSB) device.
https://docs.microsoft.com/windows/win32/xinput/directinput-and-xusb-devices
https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput
*/
if (hid_internal_extract_int_token_value(device_id, L"IG_") != -1) {
/* Get devnode parent to reach out USB device. */
if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS)
goto end;
}

/* Get the hardware ids from devnode */
hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST);
if (!hardware_ids)
goto end;

/* Get additional information from USB device's Hardware ID
https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers
https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-interfaces-not-grouped-in-collections
*/
for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) {
/* Normalize to upper case */
hid_internal_towupper(hardware_id);

if (dev->release_number == 0) {
/* USB_DEVICE_DESCRIPTOR.bcdDevice value. */
int release_number = hid_internal_extract_int_token_value(hardware_id, L"REV_");
if (release_number != -1) {
dev->release_number = (unsigned short)release_number;
}
}

if (dev->interface_number == -1) {
/* USB_INTERFACE_DESCRIPTOR.bInterfaceNumber value. */
int interface_number = hid_internal_extract_int_token_value(hardware_id, L"MI_");
if (interface_number != -1) {
dev->interface_number = interface_number;
}
}
}

/* Try to get USB device manufacturer string if not provided by HidD_GetManufacturerString. */
if (wcslen(dev->manufacturer_string) == 0) {
wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_Manufacturer, DEVPROP_TYPE_STRING);
if (manufacturer_string) {
free(dev->manufacturer_string);
dev->manufacturer_string = manufacturer_string;
}
}

/* Try to get USB device serial number if not provided by HidD_GetSerialNumberString. */
if (wcslen(dev->serial_number) == 0) {
DEVINST usb_dev_node = dev_node;
if (dev->interface_number != -1) {
/* Get devnode parent to reach out composite parent USB device.
https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-the-composite-parent-device
*/
if (CM_Get_Parent(&usb_dev_node, dev_node, 0) != CR_SUCCESS)
goto end;
}

/* Get the device id of the USB device. */
free(device_id);
device_id = hid_internal_get_devnode_property(usb_dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
if (!device_id)
goto end;

/* Extract substring after last '\\' of Instance ID.
For USB devices it may contain device's serial number.
https://docs.microsoft.com/windows-hardware/drivers/install/instance-ids
*/
for (wchar_t *ptr = device_id + wcslen(device_id); ptr > device_id; --ptr) {
/* Instance ID is unique only within the scope of the bus.
For USB devices it means that serial number is not available. Skip. */
if (*ptr == L'&')
break;

if (*ptr == L'\\') {
free(dev->serial_number);
dev->serial_number = _wcsdup(ptr + 1);
break;
}
}
}

/* If we can't get the interface number, it means that there is only one interface. */
if (dev->interface_number == -1)
dev->interface_number = 0;

end:
free(device_id);
free(hardware_ids);
}

/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices
Request this info via dev node properties instead.
https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html
Expand Down Expand Up @@ -452,34 +577,9 @@ static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_n
}
}

/* USB Device Interface Number.
It can be parsed out of the Hardware ID if a USB device is has multiple interfaces (composite device).
See https://docs.microsoft.com/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections
and https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers
hardware_id is always expected to be uppercase.
*/
static int hid_internal_get_interface_number(const wchar_t* hardware_id)
{
int interface_number;
wchar_t *startptr, *endptr;
const wchar_t *interface_token = L"&MI_";

startptr = wcsstr(hardware_id, interface_token);
if (!startptr)
return -1;

startptr += wcslen(interface_token);
interface_number = wcstol(startptr, &endptr, 16);
if (endptr == startptr)
return -1;

return interface_number;
}

static void hid_internal_get_info(const wchar_t* interface_path, struct hid_device_info* dev)
{
wchar_t *device_id = NULL, *compatible_ids = NULL, *hardware_ids = NULL;
wchar_t *device_id = NULL, *compatible_ids = NULL;
CONFIGRET cr;
DEVINST dev_node;

Expand All @@ -493,22 +593,6 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi
if (cr != CR_SUCCESS)
goto end;

/* Get the hardware ids from devnode */
hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST);
if (!hardware_ids)
goto end;

/* Search for interface number in hardware ids */
for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) {
/* Normalize to upper case */
for (wchar_t* p = hardware_id; *p; ++p) *p = towupper(*p);

dev->interface_number = hid_internal_get_interface_number(hardware_id);

if (dev->interface_number != -1)
break;
}

/* Get devnode parent */
cr = CM_Get_Parent(&dev_node, dev_node, 0);
if (cr != CR_SUCCESS)
Expand All @@ -522,13 +606,14 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi
/* Now we can parse parent's compatible IDs to find out the device bus type */
for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) {
/* Normalize to upper case */
for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p);
hid_internal_towupper(compatible_id);

/* USB devices
https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support
https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */
if (wcsstr(compatible_id, L"USB") != NULL) {
dev->bus_type = HID_API_BUS_USB;
hid_internal_get_usb_info(dev, dev_node);
break;
}

Expand Down Expand Up @@ -562,7 +647,6 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi
}
end:
free(device_id);
free(hardware_ids);
free(compatible_ids);
}

Expand Down Expand Up @@ -607,9 +691,14 @@ static struct hid_device_info *hid_internal_get_device_info(const wchar_t *path,
/* Create the record. */
dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info));

if (dev == NULL) {
return NULL;
}

/* Fill out the record */
dev->next = NULL;
dev->path = hid_internal_UTF16toUTF8(path);
dev->interface_number = -1;

attrib.Size = sizeof(HIDD_ATTRIBUTES);
if (HidD_GetAttributes(handle, &attrib)) {
Expand Down
21 changes: 13 additions & 8 deletions windows/hidapi_cfgmgr32.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
/* This part of the header mimics cfgmgr32.h,
but only what is used by HIDAPI */

#include <initguid.h>
#include <devpropdef.h>
#include <propkeydef.h>

typedef DWORD RETURN_TYPE;
typedef RETURN_TYPE CONFIGRET;
typedef DWORD DEVNODE, DEVINST;
Expand All @@ -54,16 +58,17 @@ typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen,
typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR Buffer, ULONG BufferLen, ULONG ulFlags);

// from devpkey.h
static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, {0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac} }, 10 }; // DEVPROP_TYPE_STRING
static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, {0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57} }, 256 }; // DEVPROP_TYPE_STRING
static DEVPROPKEY DEVPKEY_Device_HardwareIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 3 }; // DEVPROP_TYPE_STRING_LIST
static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 4 }; // DEVPROP_TYPE_STRING_LIST
static DEVPROPKEY DEVPKEY_Device_ContainerId = { { 0x8c7ed206, 0x3f8a, 0x4827, {0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c} }, 2 }; // DEVPROP_TYPE_GUID
DEFINE_DEVPROPKEY(DEVPKEY_NAME, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 256); // DEVPROP_TYPE_STRING
DEFINE_DEVPROPKEY(DEVPKEY_Device_HardwareIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 3); // DEVPROP_TYPE_STRING_LIST
DEFINE_DEVPROPKEY(DEVPKEY_Device_CompatibleIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 4); // DEVPROP_TYPE_STRING_LIST
DEFINE_DEVPROPKEY(DEVPKEY_Device_ContainerId, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2); // DEVPROP_TYPE_GUID

// from propkey.h
static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 1 }; // DEVPROP_TYPE_STRING
static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 4 }; // DEVPROP_TYPE_STRING
static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_ModelNumber = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 5 }; // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_DeviceAddress, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 1); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_Manufacturer, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 4); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_ModelNumber, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 5); // DEVPROP_TYPE_STRING

#endif

Expand Down

0 comments on commit bd6be4d

Please sign in to comment.