Skip to content

Commit bd6be4d

Browse files
authored
windows: try to get USB device serial number if not provided by HidD_GetSerialNumberString (#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.
1 parent 7eedb61 commit bd6be4d

File tree

3 files changed

+150
-58
lines changed

3 files changed

+150
-58
lines changed

hidapi/hidapi.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,9 @@ extern "C" {
169169
/** The USB interface which this logical device
170170
represents.
171171
172-
* Valid on both Linux implementations in all cases.
173-
* Valid on the Windows implementation only if the device
174-
contains more than one interface.
175-
* Valid on the Mac implementation if and only if the device
176-
is a USB HID device. */
172+
Valid only if the device is a USB HID device.
173+
Set to -1 in all other cases.
174+
*/
177175
int interface_number;
178176

179177
/** Pointer to the next device */

windows/hid.c

Lines changed: 134 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ typedef LONG NTSTATUS;
5252

5353
/*#define HIDAPI_USE_DDK*/
5454

55-
#include <devpropdef.h>
5655
#include "hidapi_cfgmgr32.h"
5756
#include "hidapi_hidclass.h"
5857
#include "hidapi_hidsdi.h"
@@ -409,6 +408,132 @@ static void* hid_internal_get_device_interface_property(const wchar_t* interface
409408
return property_value;
410409
}
411410

411+
static void hid_internal_towupper(wchar_t* string)
412+
{
413+
for (wchar_t* p = string; *p; ++p) *p = towupper(*p);
414+
}
415+
416+
static int hid_internal_extract_int_token_value(wchar_t* string, const wchar_t* token)
417+
{
418+
int token_value;
419+
wchar_t* startptr, * endptr;
420+
421+
startptr = wcsstr(string, token);
422+
if (!startptr)
423+
return -1;
424+
425+
startptr += wcslen(token);
426+
token_value = wcstol(startptr, &endptr, 16);
427+
if (endptr == startptr)
428+
return -1;
429+
430+
return token_value;
431+
}
432+
433+
static void hid_internal_get_usb_info(struct hid_device_info* dev, DEVINST dev_node)
434+
{
435+
wchar_t *device_id = NULL, *hardware_ids = NULL;
436+
437+
device_id = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
438+
if (!device_id)
439+
goto end;
440+
441+
/* Normalize to upper case */
442+
hid_internal_towupper(device_id);
443+
444+
/* Check for Xbox Common Controller class (XUSB) device.
445+
https://docs.microsoft.com/windows/win32/xinput/directinput-and-xusb-devices
446+
https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput
447+
*/
448+
if (hid_internal_extract_int_token_value(device_id, L"IG_") != -1) {
449+
/* Get devnode parent to reach out USB device. */
450+
if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS)
451+
goto end;
452+
}
453+
454+
/* Get the hardware ids from devnode */
455+
hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST);
456+
if (!hardware_ids)
457+
goto end;
458+
459+
/* Get additional information from USB device's Hardware ID
460+
https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers
461+
https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-interfaces-not-grouped-in-collections
462+
*/
463+
for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) {
464+
/* Normalize to upper case */
465+
hid_internal_towupper(hardware_id);
466+
467+
if (dev->release_number == 0) {
468+
/* USB_DEVICE_DESCRIPTOR.bcdDevice value. */
469+
int release_number = hid_internal_extract_int_token_value(hardware_id, L"REV_");
470+
if (release_number != -1) {
471+
dev->release_number = (unsigned short)release_number;
472+
}
473+
}
474+
475+
if (dev->interface_number == -1) {
476+
/* USB_INTERFACE_DESCRIPTOR.bInterfaceNumber value. */
477+
int interface_number = hid_internal_extract_int_token_value(hardware_id, L"MI_");
478+
if (interface_number != -1) {
479+
dev->interface_number = interface_number;
480+
}
481+
}
482+
}
483+
484+
/* Try to get USB device manufacturer string if not provided by HidD_GetManufacturerString. */
485+
if (wcslen(dev->manufacturer_string) == 0) {
486+
wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_Manufacturer, DEVPROP_TYPE_STRING);
487+
if (manufacturer_string) {
488+
free(dev->manufacturer_string);
489+
dev->manufacturer_string = manufacturer_string;
490+
}
491+
}
492+
493+
/* Try to get USB device serial number if not provided by HidD_GetSerialNumberString. */
494+
if (wcslen(dev->serial_number) == 0) {
495+
DEVINST usb_dev_node = dev_node;
496+
if (dev->interface_number != -1) {
497+
/* Get devnode parent to reach out composite parent USB device.
498+
https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-the-composite-parent-device
499+
*/
500+
if (CM_Get_Parent(&usb_dev_node, dev_node, 0) != CR_SUCCESS)
501+
goto end;
502+
}
503+
504+
/* Get the device id of the USB device. */
505+
free(device_id);
506+
device_id = hid_internal_get_devnode_property(usb_dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING);
507+
if (!device_id)
508+
goto end;
509+
510+
/* Extract substring after last '\\' of Instance ID.
511+
For USB devices it may contain device's serial number.
512+
https://docs.microsoft.com/windows-hardware/drivers/install/instance-ids
513+
*/
514+
for (wchar_t *ptr = device_id + wcslen(device_id); ptr > device_id; --ptr) {
515+
/* Instance ID is unique only within the scope of the bus.
516+
For USB devices it means that serial number is not available. Skip. */
517+
if (*ptr == L'&')
518+
break;
519+
520+
if (*ptr == L'\\') {
521+
free(dev->serial_number);
522+
dev->serial_number = _wcsdup(ptr + 1);
523+
break;
524+
}
525+
}
526+
}
527+
528+
/* If we can't get the interface number, it means that there is only one interface. */
529+
if (dev->interface_number == -1)
530+
dev->interface_number = 0;
531+
532+
end:
533+
free(device_id);
534+
free(hardware_ids);
535+
}
536+
412537
/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices
413538
Request this info via dev node properties instead.
414539
https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html
@@ -452,34 +577,9 @@ static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_n
452577
}
453578
}
454579

455-
/* USB Device Interface Number.
456-
It can be parsed out of the Hardware ID if a USB device is has multiple interfaces (composite device).
457-
See https://docs.microsoft.com/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections
458-
and https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers
459-
460-
hardware_id is always expected to be uppercase.
461-
*/
462-
static int hid_internal_get_interface_number(const wchar_t* hardware_id)
463-
{
464-
int interface_number;
465-
wchar_t *startptr, *endptr;
466-
const wchar_t *interface_token = L"&MI_";
467-
468-
startptr = wcsstr(hardware_id, interface_token);
469-
if (!startptr)
470-
return -1;
471-
472-
startptr += wcslen(interface_token);
473-
interface_number = wcstol(startptr, &endptr, 16);
474-
if (endptr == startptr)
475-
return -1;
476-
477-
return interface_number;
478-
}
479-
480580
static void hid_internal_get_info(const wchar_t* interface_path, struct hid_device_info* dev)
481581
{
482-
wchar_t *device_id = NULL, *compatible_ids = NULL, *hardware_ids = NULL;
582+
wchar_t *device_id = NULL, *compatible_ids = NULL;
483583
CONFIGRET cr;
484584
DEVINST dev_node;
485585

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

496-
/* Get the hardware ids from devnode */
497-
hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST);
498-
if (!hardware_ids)
499-
goto end;
500-
501-
/* Search for interface number in hardware ids */
502-
for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) {
503-
/* Normalize to upper case */
504-
for (wchar_t* p = hardware_id; *p; ++p) *p = towupper(*p);
505-
506-
dev->interface_number = hid_internal_get_interface_number(hardware_id);
507-
508-
if (dev->interface_number != -1)
509-
break;
510-
}
511-
512596
/* Get devnode parent */
513597
cr = CM_Get_Parent(&dev_node, dev_node, 0);
514598
if (cr != CR_SUCCESS)
@@ -522,13 +606,14 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi
522606
/* Now we can parse parent's compatible IDs to find out the device bus type */
523607
for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) {
524608
/* Normalize to upper case */
525-
for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p);
609+
hid_internal_towupper(compatible_id);
526610

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

@@ -562,7 +647,6 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi
562647
}
563648
end:
564649
free(device_id);
565-
free(hardware_ids);
566650
free(compatible_ids);
567651
}
568652

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

694+
if (dev == NULL) {
695+
return NULL;
696+
}
697+
610698
/* Fill out the record */
611699
dev->next = NULL;
612700
dev->path = hid_internal_UTF16toUTF8(path);
701+
dev->interface_number = -1;
613702

614703
attrib.Size = sizeof(HIDD_ATTRIBUTES);
615704
if (HidD_GetAttributes(handle, &attrib)) {

windows/hidapi_cfgmgr32.h

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
/* This part of the header mimics cfgmgr32.h,
3333
but only what is used by HIDAPI */
3434

35+
#include <initguid.h>
36+
#include <devpropdef.h>
37+
#include <propkeydef.h>
38+
3539
typedef DWORD RETURN_TYPE;
3640
typedef RETURN_TYPE CONFIGRET;
3741
typedef DWORD DEVNODE, DEVINST;
@@ -54,16 +58,17 @@ typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen,
5458
typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR Buffer, ULONG BufferLen, ULONG ulFlags);
5559

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

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

6873
#endif
6974

0 commit comments

Comments
 (0)