@mcesystems/usb-device-listener 1.0.30 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/Release/usb_device_listener.node +0 -0
- package/dist/types.d.ts +15 -2
- package/dist/types.d.ts.map +1 -1
- package/native/addon.cc +2 -0
- package/native/usb_listener_common.h +3 -1
- package/native/usb_listener_mac.cc +115 -6
- package/native/usb_listener_win.cc +117 -6
- package/native/usb_listener_win.h +2 -0
- package/package.json +1 -1
|
Binary file
|
package/dist/types.d.ts
CHANGED
|
@@ -8,10 +8,23 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export interface DeviceInfo {
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Device identifier
|
|
12
|
+
* For devices with USB serial numbers: the serial number (formatted with dash for iOS style)
|
|
13
|
+
* For devices without serial: platform-specific format "USB\VID_xxxx&PID_xxxx\..."
|
|
14
|
+
* iOS devices: formatted as "XXXXXXXX-XXXXXXXXXXXXXXXX" (e.g., "00008030-001234567890402E")
|
|
13
15
|
*/
|
|
14
16
|
deviceId: string;
|
|
17
|
+
/**
|
|
18
|
+
* Raw USB serial number (without formatting)
|
|
19
|
+
* Empty string if the device doesn't expose a serial number
|
|
20
|
+
*/
|
|
21
|
+
serialNumber: string;
|
|
22
|
+
/**
|
|
23
|
+
* USB product name / device description
|
|
24
|
+
* Examples: "iPhone", "SAMSUNG_Android", "Arduino Uno"
|
|
25
|
+
* Empty string if not available
|
|
26
|
+
*/
|
|
27
|
+
deviceName: string;
|
|
15
28
|
/**
|
|
16
29
|
* USB Vendor ID (decimal)
|
|
17
30
|
* Use .toString(16) to convert to hex string
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAE/B;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAEhC;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAE7C;;;;;;;;OAQG;IACH,aAAa,IAAI,IAAI,CAAC;IAEtB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAE/C;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAErD;;;;;;;;;;;;;;OAcG;IACH,WAAW,IAAI,UAAU,EAAE,CAAC;CAC5B"}
|
package/native/addon.cc
CHANGED
|
@@ -97,6 +97,8 @@ Napi::Value ListDevices(const Napi::CallbackInfo& info) {
|
|
|
97
97
|
for (size_t i = 0; i < devices.size(); i++) {
|
|
98
98
|
Napi::Object obj = Napi::Object::New(env);
|
|
99
99
|
obj.Set("deviceId", Napi::String::New(env, devices[i].deviceId));
|
|
100
|
+
obj.Set("serialNumber", Napi::String::New(env, devices[i].serialNumber));
|
|
101
|
+
obj.Set("deviceName", Napi::String::New(env, devices[i].deviceName));
|
|
100
102
|
obj.Set("vid", Napi::Number::New(env, devices[i].vid));
|
|
101
103
|
obj.Set("pid", Napi::Number::New(env, devices[i].pid));
|
|
102
104
|
obj.Set("locationInfo", Napi::String::New(env, devices[i].locationInfo));
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* Platform-agnostic: used by both Windows and macOS implementations
|
|
15
15
|
*/
|
|
16
16
|
struct DeviceInfo {
|
|
17
|
-
std::string deviceId; //
|
|
17
|
+
std::string deviceId; // Device ID (serial number if available, otherwise platform-specific)
|
|
18
|
+
std::string serialNumber; // USB serial number (raw, without formatting)
|
|
19
|
+
std::string deviceName; // USB product name / device description
|
|
18
20
|
uint16_t vid{0}; // Vendor ID
|
|
19
21
|
uint16_t pid{0}; // Product ID
|
|
20
22
|
std::string locationInfo; // Physical port location (platform-specific format)
|
|
@@ -25,6 +25,99 @@ static const char* GetUSBDeviceClassName() {
|
|
|
25
25
|
return kIOUSBDeviceClassName;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Get a string property from an IOKit service
|
|
30
|
+
* Returns empty string if the property is not available
|
|
31
|
+
*/
|
|
32
|
+
static std::string GetStringProperty(io_service_t service, CFStringRef propertyName) {
|
|
33
|
+
CFStringRef stringRef = (CFStringRef)IORegistryEntryCreateCFProperty(
|
|
34
|
+
service, propertyName, kCFAllocatorDefault, 0);
|
|
35
|
+
|
|
36
|
+
if (!stringRef) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Convert CFString to std::string
|
|
41
|
+
CFIndex length = CFStringGetLength(stringRef);
|
|
42
|
+
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
|
43
|
+
std::string result(maxSize, '\0');
|
|
44
|
+
|
|
45
|
+
if (CFStringGetCString(stringRef, &result[0], maxSize, kCFStringEncodingUTF8)) {
|
|
46
|
+
result.resize(strlen(result.c_str()));
|
|
47
|
+
} else {
|
|
48
|
+
result.clear();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
CFRelease(stringRef);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the USB product name / device name from the IOKit service
|
|
57
|
+
* Returns empty string if not available
|
|
58
|
+
*/
|
|
59
|
+
static std::string GetDeviceName(io_service_t service) {
|
|
60
|
+
// Try kUSBProductString first (standard USB property)
|
|
61
|
+
std::string name = GetStringProperty(service, CFSTR(kUSBProductString));
|
|
62
|
+
|
|
63
|
+
// Fallback to "USB Product Name"
|
|
64
|
+
if (name.empty()) {
|
|
65
|
+
name = GetStringProperty(service, CFSTR("USB Product Name"));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Fallback to IOKit device name
|
|
69
|
+
if (name.empty()) {
|
|
70
|
+
io_name_t deviceName;
|
|
71
|
+
if (IORegistryEntryGetName(service, deviceName) == KERN_SUCCESS) {
|
|
72
|
+
name = deviceName;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return name;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the USB serial number from the IOKit service
|
|
81
|
+
* Returns empty string if serial number is not available
|
|
82
|
+
*/
|
|
83
|
+
static std::string GetSerialNumber(io_service_t service) {
|
|
84
|
+
// Try kUSBSerialNumberString first (standard USB property)
|
|
85
|
+
std::string serial = GetStringProperty(service, CFSTR(kUSBSerialNumberString));
|
|
86
|
+
|
|
87
|
+
// Fallback to "USB Serial Number" property name
|
|
88
|
+
if (serial.empty()) {
|
|
89
|
+
serial = GetStringProperty(service, CFSTR("USB Serial Number"));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return serial;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Format serial number in iOS-style dash-separated format
|
|
97
|
+
* iOS UDIDs are typically 24-25 characters and formatted as: XXXXXXXX-XXXXXXXXXXXXXXXX
|
|
98
|
+
* Example: 00008030-001234567890402E
|
|
99
|
+
*/
|
|
100
|
+
static std::string FormatSerialAsDeviceId(const std::string& serial) {
|
|
101
|
+
if (serial.empty()) {
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// iOS serial numbers are typically 24-25 chars without dashes
|
|
106
|
+
// Format: first 8 chars, dash, remaining chars
|
|
107
|
+
// If it already contains dashes, return as-is
|
|
108
|
+
if (serial.find('-') != std::string::npos) {
|
|
109
|
+
return serial;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// iOS UDID format: 8 chars + dash + rest (typically 16 chars)
|
|
113
|
+
if (serial.length() >= 24) {
|
|
114
|
+
return serial.substr(0, 8) + "-" + serial.substr(8);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// For shorter serials (like Android), return as-is
|
|
118
|
+
return serial;
|
|
119
|
+
}
|
|
120
|
+
|
|
28
121
|
USBListener::USBListener() = default;
|
|
29
122
|
|
|
30
123
|
USBListener::~USBListener() {
|
|
@@ -229,6 +322,8 @@ void USBListener::HandleDeviceAdded(io_iterator_t iterator) {
|
|
|
229
322
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
230
323
|
Napi::Object obj = Napi::Object::New(env);
|
|
231
324
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
325
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
326
|
+
obj.Set("deviceName", Napi::String::New(env, data->deviceName));
|
|
232
327
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
233
328
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
234
329
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -280,6 +375,8 @@ void USBListener::HandleDeviceRemoved(io_iterator_t iterator) {
|
|
|
280
375
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
281
376
|
Napi::Object obj = Napi::Object::New(env);
|
|
282
377
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
378
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
379
|
+
obj.Set("deviceName", Napi::String::New(env, data->deviceName));
|
|
283
380
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
284
381
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
285
382
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -336,12 +433,24 @@ bool USBListener::GetDeviceInfoFromService(io_service_t service, DeviceInfo& inf
|
|
|
336
433
|
// Get Location ID (used for physical port identification)
|
|
337
434
|
info.locationInfo = GetLocationId(service);
|
|
338
435
|
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
436
|
+
// Get USB serial number
|
|
437
|
+
info.serialNumber = GetSerialNumber(service);
|
|
438
|
+
|
|
439
|
+
// Get device name
|
|
440
|
+
info.deviceName = GetDeviceName(service);
|
|
441
|
+
|
|
442
|
+
// Use serial number as device ID if available (formatted for iOS style)
|
|
443
|
+
// Otherwise fall back to VID/PID/location combination
|
|
444
|
+
if (!info.serialNumber.empty()) {
|
|
445
|
+
info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
|
|
446
|
+
} else {
|
|
447
|
+
// Fallback: Create a unique device ID combining VID, PID, and location
|
|
448
|
+
std::ostringstream ss;
|
|
449
|
+
ss << "USB\\VID_" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << info.vid
|
|
450
|
+
<< "&PID_" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << info.pid
|
|
451
|
+
<< "\\" << info.locationInfo;
|
|
452
|
+
info.deviceId = ss.str();
|
|
453
|
+
}
|
|
345
454
|
|
|
346
455
|
return true;
|
|
347
456
|
}
|
|
@@ -187,6 +187,8 @@ void USBListener::HandleDeviceChange(WPARAM wParam, LPARAM lParam) {
|
|
|
187
187
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
188
188
|
Napi::Object obj = Napi::Object::New(env);
|
|
189
189
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
190
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
191
|
+
obj.Set("deviceName", Napi::String::New(env, data->deviceName));
|
|
190
192
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
191
193
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
192
194
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -203,6 +205,8 @@ void USBListener::HandleDeviceChange(WPARAM wParam, LPARAM lParam) {
|
|
|
203
205
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
204
206
|
Napi::Object obj = Napi::Object::New(env);
|
|
205
207
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
208
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
209
|
+
obj.Set("deviceName", Napi::String::New(env, data->deviceName));
|
|
206
210
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
207
211
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
208
212
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -267,9 +271,9 @@ bool USBListener::GetDeviceInfo(const std::string& devicePath, DeviceInfo& info)
|
|
|
267
271
|
if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
|
|
268
272
|
if (_wcsicmp(instanceId, deviceIdW.c_str()) == 0) {
|
|
269
273
|
std::wstring idW = instanceId;
|
|
270
|
-
|
|
274
|
+
std::string fullDeviceId = std::string(idW.begin(), idW.end());
|
|
271
275
|
|
|
272
|
-
if (!GetVidPid(
|
|
276
|
+
if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
|
|
273
277
|
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
|
274
278
|
return false;
|
|
275
279
|
}
|
|
@@ -280,6 +284,17 @@ bool USBListener::GetDeviceInfo(const std::string& devicePath, DeviceInfo& info)
|
|
|
280
284
|
return false;
|
|
281
285
|
}
|
|
282
286
|
|
|
287
|
+
// Get serial number and use as device ID if available
|
|
288
|
+
info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
|
|
289
|
+
if (!info.serialNumber.empty()) {
|
|
290
|
+
info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
|
|
291
|
+
} else {
|
|
292
|
+
info.deviceId = fullDeviceId;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get device name
|
|
296
|
+
info.deviceName = GetDeviceName(deviceInfoSet, devInfoData);
|
|
297
|
+
|
|
283
298
|
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
|
284
299
|
return true;
|
|
285
300
|
}
|
|
@@ -324,6 +339,78 @@ bool USBListener::GetVidPid(const std::string& deviceId, uint16_t& vid, uint16_t
|
|
|
324
339
|
}
|
|
325
340
|
}
|
|
326
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Get device description/name from Windows registry
|
|
344
|
+
*/
|
|
345
|
+
std::string USBListener::GetDeviceName(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData) {
|
|
346
|
+
WCHAR buffer[MAX_PATH];
|
|
347
|
+
|
|
348
|
+
// Try to get device description first (usually the friendly name)
|
|
349
|
+
if (SetupDiGetDeviceRegistryPropertyW(deviceInfoSet, &devInfoData, SPDRP_DEVICEDESC,
|
|
350
|
+
nullptr, reinterpret_cast<PBYTE>(buffer), sizeof(buffer), nullptr)) {
|
|
351
|
+
std::wstring nameW = buffer;
|
|
352
|
+
return std::string(nameW.begin(), nameW.end());
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Fallback to friendly name
|
|
356
|
+
if (SetupDiGetDeviceRegistryPropertyW(deviceInfoSet, &devInfoData, SPDRP_FRIENDLYNAME,
|
|
357
|
+
nullptr, reinterpret_cast<PBYTE>(buffer), sizeof(buffer), nullptr)) {
|
|
358
|
+
std::wstring nameW = buffer;
|
|
359
|
+
return std::string(nameW.begin(), nameW.end());
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return "";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get USB serial number from device instance ID
|
|
367
|
+
* The serial number is typically the last part after the last backslash
|
|
368
|
+
*/
|
|
369
|
+
std::string USBListener::GetSerialNumber(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData) {
|
|
370
|
+
WCHAR instanceId[MAX_PATH];
|
|
371
|
+
if (!SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
|
|
372
|
+
return "";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
std::wstring idW = instanceId;
|
|
376
|
+
std::string deviceId(idW.begin(), idW.end());
|
|
377
|
+
|
|
378
|
+
// The serial number is typically after the last backslash in the device instance ID
|
|
379
|
+
// Format: USB\VID_XXXX&PID_XXXX\SERIAL_NUMBER
|
|
380
|
+
size_t lastBackslash = deviceId.rfind('\\');
|
|
381
|
+
if (lastBackslash != std::string::npos && lastBackslash + 1 < deviceId.length()) {
|
|
382
|
+
return deviceId.substr(lastBackslash + 1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return "";
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Format serial number in iOS-style dash-separated format
|
|
390
|
+
* iOS UDIDs are typically 24-25 characters and formatted as: XXXXXXXX-XXXXXXXXXXXXXXXX
|
|
391
|
+
* Example: 00008030-001234567890402E
|
|
392
|
+
*/
|
|
393
|
+
static std::string FormatSerialAsDeviceId(const std::string& serial) {
|
|
394
|
+
if (serial.empty()) {
|
|
395
|
+
return "";
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// iOS serial numbers are typically 24-25 chars without dashes
|
|
399
|
+
// Format: first 8 chars, dash, remaining chars
|
|
400
|
+
// If it already contains dashes, return as-is
|
|
401
|
+
if (serial.find('-') != std::string::npos) {
|
|
402
|
+
return serial;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// iOS UDID format: 8 chars + dash + rest (typically 16 chars)
|
|
406
|
+
if (serial.length() >= 24) {
|
|
407
|
+
return serial.substr(0, 8) + "-" + serial.substr(8);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// For shorter serials (like Android), return as-is
|
|
411
|
+
return serial;
|
|
412
|
+
}
|
|
413
|
+
|
|
327
414
|
void USBListener::EnumerateConnectedDevices() {
|
|
328
415
|
HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
329
416
|
if (deviceInfoSet == INVALID_HANDLE_VALUE) {
|
|
@@ -338,9 +425,9 @@ void USBListener::EnumerateConnectedDevices() {
|
|
|
338
425
|
if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
|
|
339
426
|
DeviceInfo info;
|
|
340
427
|
std::wstring idW = instanceId;
|
|
341
|
-
|
|
428
|
+
std::string fullDeviceId = std::string(idW.begin(), idW.end());
|
|
342
429
|
|
|
343
|
-
if (!GetVidPid(
|
|
430
|
+
if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
|
|
344
431
|
continue;
|
|
345
432
|
}
|
|
346
433
|
|
|
@@ -349,6 +436,17 @@ void USBListener::EnumerateConnectedDevices() {
|
|
|
349
436
|
continue;
|
|
350
437
|
}
|
|
351
438
|
|
|
439
|
+
// Get serial number and use as device ID if available
|
|
440
|
+
info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
|
|
441
|
+
if (!info.serialNumber.empty()) {
|
|
442
|
+
info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
|
|
443
|
+
} else {
|
|
444
|
+
info.deviceId = fullDeviceId;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Get device name
|
|
448
|
+
info.deviceName = GetDeviceName(deviceInfoSet, devInfoData);
|
|
449
|
+
|
|
352
450
|
// Cache device with exclusive lock
|
|
353
451
|
{
|
|
354
452
|
std::unique_lock lock(m_cacheMutex);
|
|
@@ -359,6 +457,8 @@ void USBListener::EnumerateConnectedDevices() {
|
|
|
359
457
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
360
458
|
Napi::Object obj = Napi::Object::New(env);
|
|
361
459
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
460
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
461
|
+
obj.Set("deviceName", Napi::String::New(env, data->deviceName));
|
|
362
462
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
363
463
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
364
464
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -394,9 +494,9 @@ std::vector<DeviceInfo> USBListener::ListAllDevices() {
|
|
|
394
494
|
if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
|
|
395
495
|
DeviceInfo info;
|
|
396
496
|
std::wstring idW = instanceId;
|
|
397
|
-
|
|
497
|
+
std::string fullDeviceId = std::string(idW.begin(), idW.end());
|
|
398
498
|
|
|
399
|
-
if (!GetVidPid(
|
|
499
|
+
if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
|
|
400
500
|
continue;
|
|
401
501
|
}
|
|
402
502
|
|
|
@@ -405,6 +505,17 @@ std::vector<DeviceInfo> USBListener::ListAllDevices() {
|
|
|
405
505
|
continue;
|
|
406
506
|
}
|
|
407
507
|
|
|
508
|
+
// Get serial number and use as device ID if available
|
|
509
|
+
info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
|
|
510
|
+
if (!info.serialNumber.empty()) {
|
|
511
|
+
info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
|
|
512
|
+
} else {
|
|
513
|
+
info.deviceId = fullDeviceId;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Get device name
|
|
517
|
+
info.deviceName = GetDeviceName(deviceInfoSet, devInfoData);
|
|
518
|
+
|
|
408
519
|
devices.push_back(info);
|
|
409
520
|
}
|
|
410
521
|
}
|
|
@@ -74,6 +74,8 @@ private:
|
|
|
74
74
|
bool GetDeviceInfo(const std::string& devicePath, DeviceInfo& info); // Get device info from Windows API (for connections)
|
|
75
75
|
bool GetDeviceInfoFromPath(const std::string& devicePath, DeviceInfo& info); // Parse device info from path (for disconnections)
|
|
76
76
|
bool GetLocationInfo(DEVINST devInst, std::string& locationInfo); // Get physical port location
|
|
77
|
+
std::string GetSerialNumber(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData); // Get USB serial number
|
|
78
|
+
std::string GetDeviceName(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData); // Get device description/name
|
|
77
79
|
bool GetVidPid(const std::string& deviceId, uint16_t& vid, uint16_t& pid); // Parse VID/PID from device ID string
|
|
78
80
|
void EnumerateConnectedDevices(); // Enumerate devices on startup
|
|
79
81
|
|