Skip to content

Commit 11cc0e8

Browse files
authored
Fetch Bluetooth LE HID device info from Windows devnode properties (#309)
As per MS, HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString [doesn't work](https://docs.microsoft.com/en-us/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html) with Bluetooth LE (BLE) devices. Add special handling for BLE devices and fill `manufacturer_string`/`product_string`/`serial_number` using BLE-specific API. Fixes: #282
1 parent 4d63a50 commit 11cc0e8

File tree

1 file changed

+200
-18
lines changed

1 file changed

+200
-18
lines changed

windows/hid.c

Lines changed: 200 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
https://github.com/libusb/hidapi .
2121
********************************************************/
2222

23+
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
24+
// Do not warn about mbsrtowcs and wcsncpy usage.
25+
// https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt
26+
#define _CRT_SECURE_NO_WARNINGS
27+
#endif
28+
2329
#include <windows.h>
2430

2531
#ifndef _NTDEF_
@@ -135,6 +141,34 @@ static struct hid_api_version api_version = {
135141

136142
static HMODULE lib_handle = NULL;
137143
static BOOLEAN initialized = FALSE;
144+
145+
typedef DWORD RETURN_TYPE;
146+
typedef RETURN_TYPE CONFIGRET;
147+
typedef DWORD DEVNODE, DEVINST;
148+
typedef DEVNODE* PDEVNODE, * PDEVINST;
149+
typedef WCHAR* DEVNODEID_W, * DEVINSTID_W;
150+
151+
#define CR_SUCCESS (0x00000000)
152+
#define CR_BUFFER_SMALL (0x0000001A)
153+
154+
#define CM_LOCATE_DEVNODE_NORMAL 0x00000000
155+
156+
#define DEVPROP_TYPEMOD_LIST 0x00002000
157+
158+
#define DEVPROP_TYPE_STRING 0x00000012
159+
#define DEVPROP_TYPE_STRING_LIST (DEVPROP_TYPE_STRING|DEVPROP_TYPEMOD_LIST)
160+
161+
typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags);
162+
typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags);
163+
typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
164+
typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
165+
166+
static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL;
167+
static CM_Get_Parent_ CM_Get_Parent = NULL;
168+
static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL;
169+
static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL;
170+
171+
static HMODULE cfgmgr32_lib_handle = NULL;
138172
#endif /* HIDAPI_USE_DDK */
139173

140174
struct hid_device_ {
@@ -151,6 +185,7 @@ struct hid_device_ {
151185
char *read_buf;
152186
OVERLAPPED ol;
153187
OVERLAPPED write_ol;
188+
struct hid_device_info* device_info;
154189
};
155190

156191
static hid_device *new_hid_device()
@@ -171,6 +206,7 @@ static hid_device *new_hid_device()
171206
dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
172207
memset(&dev->write_ol, 0, sizeof(dev->write_ol));
173208
dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL);
209+
dev->device_info = NULL;
174210

175211
return dev;
176212
}
@@ -184,6 +220,7 @@ static void free_hid_device(hid_device *dev)
184220
free(dev->write_buf);
185221
free(dev->feature_buf);
186222
free(dev->read_buf);
223+
free(dev->device_info);
187224
free(dev);
188225
}
189226

@@ -248,6 +285,29 @@ static int lookup_functions()
248285
else
249286
return -1;
250287

288+
cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll");
289+
if (cfgmgr32_lib_handle) {
290+
#if defined(__GNUC__)
291+
# pragma GCC diagnostic push
292+
# pragma GCC diagnostic ignored "-Wcast-function-type"
293+
#endif
294+
#define RESOLVE(x) x = (x##_)GetProcAddress(cfgmgr32_lib_handle, #x);
295+
RESOLVE(CM_Locate_DevNodeW);
296+
RESOLVE(CM_Get_Parent);
297+
RESOLVE(CM_Get_DevNode_PropertyW);
298+
RESOLVE(CM_Get_Device_Interface_PropertyW);
299+
#undef RESOLVE
300+
#if defined(__GNUC__)
301+
# pragma GCC diagnostic pop
302+
#endif
303+
}
304+
else {
305+
CM_Locate_DevNodeW = NULL;
306+
CM_Get_Parent = NULL;
307+
CM_Get_DevNode_PropertyW = NULL;
308+
CM_Get_Device_Interface_PropertyW = NULL;
309+
}
310+
251311
return 0;
252312
}
253313
#endif
@@ -299,11 +359,135 @@ int HID_API_EXPORT hid_exit(void)
299359
if (lib_handle)
300360
FreeLibrary(lib_handle);
301361
lib_handle = NULL;
362+
if (cfgmgr32_lib_handle)
363+
FreeLibrary(cfgmgr32_lib_handle);
364+
cfgmgr32_lib_handle = NULL;
302365
initialized = FALSE;
303366
#endif
304367
return 0;
305368
}
306369

370+
static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node)
371+
{
372+
ULONG len;
373+
CONFIGRET cr;
374+
DEVPROPTYPE property_type;
375+
376+
static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac }, 10 }; // DEVPROP_TYPE_STRING
377+
static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 1 }; // DEVPROP_TYPE_STRING
378+
static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 4 }; // DEVPROP_TYPE_STRING
379+
380+
/* Manufacturer String */
381+
len = 0;
382+
cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, NULL, &len, 0);
383+
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) {
384+
free(dev->manufacturer_string);
385+
dev->manufacturer_string = (wchar_t*)calloc(len, sizeof(BYTE));
386+
CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, (PBYTE)dev->manufacturer_string, &len, 0);
387+
}
388+
389+
/* Serial Number String (MAC Address) */
390+
len = 0;
391+
cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, NULL, &len, 0);
392+
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) {
393+
free(dev->serial_number);
394+
dev->serial_number = (wchar_t*)calloc(len, sizeof(BYTE));
395+
CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, (PBYTE)dev->serial_number, &len, 0);
396+
}
397+
398+
/* Get devnode grandparent to reach out Bluetooth LE device node */
399+
cr = CM_Get_Parent(&dev_node, dev_node, 0);
400+
if (cr != CR_SUCCESS)
401+
return;
402+
403+
/* Product String */
404+
len = 0;
405+
cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, NULL, &len, 0);
406+
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) {
407+
free(dev->product_string);
408+
dev->product_string = (wchar_t*)calloc(len, sizeof(BYTE));
409+
CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, (PBYTE)dev->product_string, &len, 0);
410+
}
411+
}
412+
413+
static void hid_internal_get_info(struct hid_device_info* dev)
414+
{
415+
char *tmp = NULL;
416+
wchar_t *interface_path = NULL, *device_id = NULL, *compatible_ids = NULL;
417+
mbstate_t state;
418+
ULONG len;
419+
CONFIGRET cr;
420+
DEVPROPTYPE property_type;
421+
DEVINST dev_node;
422+
423+
static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57 }, 256 }; // DEVPROP_TYPE_STRING
424+
static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}, 4 }; // DEVPROP_TYPE_STRING_LIST
425+
426+
if (!CM_Get_Device_Interface_PropertyW ||
427+
!CM_Locate_DevNodeW ||
428+
!CM_Get_Parent ||
429+
!CM_Get_DevNode_PropertyW)
430+
goto end;
431+
432+
tmp = dev->path;
433+
434+
len = (ULONG)strlen(tmp);
435+
interface_path = (wchar_t*)calloc(len + 1, sizeof(wchar_t));
436+
memset(&state, 0, sizeof(state));
437+
438+
if (mbsrtowcs(interface_path, &tmp, len, &state) == (size_t)-1)
439+
goto end;
440+
441+
/* Get the device id from interface path */
442+
len = 0;
443+
cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, NULL, &len, 0);
444+
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) {
445+
device_id = (wchar_t*)calloc(len, sizeof(BYTE));
446+
cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, (PBYTE)device_id, &len, 0);
447+
}
448+
if (cr != CR_SUCCESS)
449+
goto end;
450+
451+
/* Open devnode from device id */
452+
cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL);
453+
if (cr != CR_SUCCESS)
454+
goto end;
455+
456+
/* Get devnode parent */
457+
cr = CM_Get_Parent(&dev_node, dev_node, 0);
458+
if (cr != CR_SUCCESS)
459+
goto end;
460+
461+
/* Get the compatible ids from parent devnode */
462+
len = 0;
463+
cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, NULL, &len, 0);
464+
if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING_LIST) {
465+
compatible_ids = (wchar_t*)calloc(len, sizeof(BYTE));
466+
cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, (PBYTE)compatible_ids, &len, 0);
467+
}
468+
if (cr != CR_SUCCESS)
469+
goto end;
470+
471+
/* Now we can parse parent's compatible IDs to find out the device bus type */
472+
for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) {
473+
/* Normalize to upper case */
474+
for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p);
475+
476+
/* Bluetooth LE devices */
477+
if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) {
478+
/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices
479+
Request this info via dev node properties instead.
480+
https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */
481+
hid_internal_get_ble_info(dev, dev_node);
482+
break;
483+
}
484+
}
485+
end:
486+
free(interface_path);
487+
free(device_id);
488+
free(compatible_ids);
489+
}
490+
307491
static struct hid_device_info *hid_get_device_info(const char *path, HANDLE handle)
308492
{
309493
struct hid_device_info *dev = NULL; /* return object */
@@ -390,6 +574,8 @@ static struct hid_device_info *hid_get_device_info(const char *path, HANDLE hand
390574
}
391575
}
392576

577+
hid_internal_get_info(dev);
578+
393579
return dev;
394580
}
395581

@@ -650,6 +836,8 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
650836

651837
dev->read_buf = (char*) malloc(dev->input_report_length);
652838

839+
dev->device_info = hid_get_device_info(path, dev->device_handle);
840+
653841
return dev;
654842

655843
err_pp_data:
@@ -948,39 +1136,33 @@ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
9481136

9491137
int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
9501138
{
951-
BOOL res;
952-
953-
res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
954-
if (!res) {
955-
register_error(dev, "HidD_GetManufacturerString");
1139+
if (!dev->device_info || !string || !maxlen)
9561140
return -1;
957-
}
1141+
1142+
wcsncpy(string, dev->device_info->manufacturer_string, maxlen);
1143+
string[maxlen] = L'\0';
9581144

9591145
return 0;
9601146
}
9611147

9621148
int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
9631149
{
964-
BOOL res;
965-
966-
res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
967-
if (!res) {
968-
register_error(dev, "HidD_GetProductString");
1150+
if (!dev->device_info || !string || !maxlen)
9691151
return -1;
970-
}
1152+
1153+
wcsncpy(string, dev->device_info->product_string, maxlen);
1154+
string[maxlen] = L'\0';
9711155

9721156
return 0;
9731157
}
9741158

9751159
int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
9761160
{
977-
BOOL res;
978-
979-
res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
980-
if (!res) {
981-
register_error(dev, "HidD_GetSerialNumberString");
1161+
if (!dev->device_info || !string || !maxlen)
9821162
return -1;
983-
}
1163+
1164+
wcsncpy(string, dev->device_info->serial_number, maxlen);
1165+
string[maxlen] = L'\0';
9841166

9851167
return 0;
9861168
}

0 commit comments

Comments
 (0)