usb 2.3.1 → 2.4.2
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/CHANGELOG.md +25 -0
- package/README.md +1 -1
- package/binding.gyp +4 -3
- package/package.json +6 -5
- package/prebuilds/android-arm/node.napi.armv7.node +0 -0
- package/prebuilds/android-arm64/node.napi.armv8.node +0 -0
- package/prebuilds/darwin-x64+arm64/node.napi.node +0 -0
- package/prebuilds/linux-arm/node.napi.armv6.node +0 -0
- package/prebuilds/linux-arm/node.napi.armv7.node +0 -0
- package/prebuilds/linux-arm64/node.napi.armv8.node +0 -0
- package/prebuilds/linux-ia32/node.napi.node +0 -0
- package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/node.napi.musl.node +0 -0
- package/prebuilds/win32-ia32/node.napi.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/src/device.cc +20 -22
- package/src/node_usb.cc +95 -119
- package/src/node_usb.h +27 -15
- package/src/thread_name.cc +79 -0
- package/src/thread_name.h +11 -0
- package/src/transfer.cc +0 -5
- package/test/usb.coffee +18 -7
- package/test/worker.cjs +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.2] - 2022-05-27
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Fixed multiple events with device detection on Windows - [`512`](https://github.com/node-usb/node-usb/pull/512) ([Alba Mendez](https://github.com/mildsunrise))
|
|
7
|
+
|
|
8
|
+
## [2.4.1] - 2022-05-07
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Fixed node engine ranges in package.json - ([Rob Moran](https://github.com/thegecko))
|
|
12
|
+
|
|
13
|
+
## [2.4.0] - 2022-05-02
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Made addon context aware - [`512`](https://github.com/node-usb/node-usb/pull/512) ([Rob Moran](https://github.com/thegecko) & [Alba Mendez](https://github.com/mildsunrise))
|
|
17
|
+
- Fixed deprecation of `libusb_set_option` - [`510`](https://github.com/node-usb/node-usb/pull/510) ([Rob Moran](https://github.com/thegecko))
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Breaking:** Updated to N-API version 6 (Node >=10.20.0 <11.x, >=12.17.0 <13.0, >=14.0.0) - [`509`](https://github.com/node-usb/node-usb/pull/509) ([Rob Moran](https://github.com/thegecko))
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Added `rebuild` command - [`511`](https://github.com/node-usb/node-usb/pull/511) ([Rob Moran](https://github.com/thegecko))
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
- Removed `USE_POLL` build definition to simplify code - [`507`](https://github.com/node-usb/node-usb/pull/507) ([Rob Moran](https://github.com/thegecko))
|
|
27
|
+
|
|
3
28
|
## [2.3.1] - 2022-04-11
|
|
4
29
|
|
|
5
30
|
### Changed
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ This is a refactoring / rewrite of Christopher Klein's [node-usb](https://github
|
|
|
10
10
|
|
|
11
11
|
# Prerequisites
|
|
12
12
|
|
|
13
|
-
[Node.js >= v10.
|
|
13
|
+
[Node.js >= v10.20.0](https://nodejs.org), which includes `npm`.
|
|
14
14
|
|
|
15
15
|
## Windows
|
|
16
16
|
|
package/binding.gyp
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
'./src/node_usb.cc',
|
|
20
20
|
'./src/device.cc',
|
|
21
21
|
'./src/transfer.cc',
|
|
22
|
+
'./src/thread_name.cc',
|
|
22
23
|
],
|
|
23
24
|
'cflags_cc': [
|
|
24
25
|
'-std=c++14'
|
|
@@ -26,13 +27,12 @@
|
|
|
26
27
|
'defines': [
|
|
27
28
|
'_FILE_OFFSET_BITS=64',
|
|
28
29
|
'_LARGEFILE_SOURCE',
|
|
29
|
-
'NAPI_VERSION
|
|
30
|
+
'NAPI_VERSION=6',
|
|
30
31
|
],
|
|
31
32
|
'include_dirs+': [
|
|
32
33
|
'src/',
|
|
33
34
|
"<!@(node -p \"require('node-addon-api').include\")"
|
|
34
35
|
],
|
|
35
|
-
|
|
36
36
|
'conditions' : [
|
|
37
37
|
['use_system_libusb=="false" and OS!="freebsd"', {
|
|
38
38
|
'dependencies': [
|
|
@@ -93,7 +93,8 @@
|
|
|
93
93
|
'AdditionalOptions': [ '/EHsc' ],
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
|
-
}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
97
98
|
]
|
|
98
99
|
},
|
|
99
100
|
]
|
package/package.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"name": "usb",
|
|
3
3
|
"description": "Library to access USB devices",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.4.2",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=10.
|
|
8
|
+
"node": ">=10.20.0 <11.x || >=12.17.0 <13.0 || >=14.0.0"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"prepare": "yarn compile",
|
|
40
40
|
"install": "node-gyp-build",
|
|
41
|
+
"rebuild": "node-gyp rebuild",
|
|
41
42
|
"clean": "git clean -dfx",
|
|
42
43
|
"compile": "tsc && yarn lint && yarn docs",
|
|
43
44
|
"lint": "eslint . --ext .ts",
|
|
@@ -46,8 +47,8 @@
|
|
|
46
47
|
"full-test": "mocha --require coffeescript/register test/*.coffee",
|
|
47
48
|
"valgrind": "coffee -c test/*.coffee; valgrind --leak-check=full --show-possibly-lost=no node --expose-gc --trace-gc node_modules/mocha/bin/_mocha -R spec",
|
|
48
49
|
"docs": "typedoc",
|
|
49
|
-
"prebuild": "prebuildify --napi --
|
|
50
|
-
"prebuild-cross": "prebuildify-cross --napi --
|
|
50
|
+
"prebuild": "prebuildify --napi --strip",
|
|
51
|
+
"prebuild-cross": "prebuildify-cross --napi --strip",
|
|
51
52
|
"prepublishOnly": "prebuildify-ci download",
|
|
52
53
|
"prebuild-download": "prebuildify-ci download"
|
|
53
54
|
},
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
},
|
|
73
74
|
"binary": {
|
|
74
75
|
"napi_versions": [
|
|
75
|
-
|
|
76
|
+
6
|
|
76
77
|
]
|
|
77
78
|
}
|
|
78
79
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/device.cc
CHANGED
|
@@ -9,22 +9,24 @@
|
|
|
9
9
|
|
|
10
10
|
#define MAX_PORTS 7
|
|
11
11
|
|
|
12
|
-
Napi::
|
|
13
|
-
|
|
14
|
-
Device::Device(const Napi::CallbackInfo & info) : Napi::ObjectWrap<Device>(info), device_handle(0), refs_(0)
|
|
15
|
-
#ifndef USE_POLL
|
|
16
|
-
, completionQueue(handleCompletion)
|
|
17
|
-
#endif
|
|
18
|
-
{
|
|
12
|
+
Device::Device(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Device>(info), env(0), device_handle(0), refs_(0), completionQueue(handleCompletion) {
|
|
13
|
+
env = info.Env();
|
|
19
14
|
device = info[0].As<Napi::External<libusb_device>>().Data();
|
|
20
15
|
libusb_ref_device(device);
|
|
16
|
+
|
|
17
|
+
std::map<libusb_device*, Device*>& byPtr = env.GetInstanceData<ModuleData>()->byPtr;
|
|
21
18
|
byPtr[device] = this;
|
|
19
|
+
|
|
22
20
|
DEBUG_LOG("Created device %p", this);
|
|
23
21
|
Constructor(info);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
Device::~Device(){
|
|
24
|
+
Device::~Device() {
|
|
27
25
|
DEBUG_LOG("Freed device %p", this);
|
|
26
|
+
|
|
27
|
+
ModuleData* instanceData = env.GetInstanceData<ModuleData>();
|
|
28
|
+
std::map<libusb_device*, Device*>& byPtr = instanceData->byPtr;
|
|
29
|
+
|
|
28
30
|
auto it = byPtr.find(device);
|
|
29
31
|
if (it != byPtr.end() && it->second == this)
|
|
30
32
|
byPtr.erase(it);
|
|
@@ -32,12 +34,12 @@ Device::~Device(){
|
|
|
32
34
|
libusb_unref_device(device);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
// Map pinning each libusb_device to a particular V8 instance
|
|
36
|
-
std::map<libusb_device*, Device*> Device::byPtr;
|
|
37
|
-
|
|
38
37
|
// Get a V8 instance for a libusb_device: either the existing one from the map,
|
|
39
38
|
// or create a new one and add it to the map.
|
|
40
|
-
Napi::Object Device::get(
|
|
39
|
+
Napi::Object Device::get(Napi::Env env, libusb_device* dev) {
|
|
40
|
+
ModuleData* instanceData = env.GetInstanceData<ModuleData>();
|
|
41
|
+
std::map<libusb_device*, Device*>& byPtr = instanceData->byPtr;
|
|
42
|
+
|
|
41
43
|
auto it = byPtr.find(dev);
|
|
42
44
|
if (it != byPtr.end()) {
|
|
43
45
|
auto value = it->second->Value();
|
|
@@ -46,7 +48,7 @@ Napi::Object Device::get(napi_env env, libusb_device* dev){
|
|
|
46
48
|
return value;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
Napi::Object obj =
|
|
51
|
+
Napi::Object obj = instanceData->deviceConstructor.New({ Napi::External<libusb_device>::New(env, dev) });
|
|
50
52
|
return obj;
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -90,7 +92,7 @@ Napi::Value Device::Constructor(const Napi::CallbackInfo& info) {
|
|
|
90
92
|
return info.This();
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
Napi::Object Device::cdesc2V8(
|
|
95
|
+
Napi::Object Device::cdesc2V8(Napi::Env env, libusb_config_descriptor * cdesc) {
|
|
94
96
|
Napi::Object v8cdesc = Napi::Object::New(env);
|
|
95
97
|
|
|
96
98
|
STRUCT_TO_V8(v8cdesc, *cdesc, bLength)
|
|
@@ -166,7 +168,7 @@ Napi::Value Device::GetConfigDescriptor(const Napi::CallbackInfo& info) {
|
|
|
166
168
|
return v8cdesc;
|
|
167
169
|
}
|
|
168
170
|
|
|
169
|
-
Napi::Value Device::GetAllConfigDescriptors(const Napi::CallbackInfo& info){
|
|
171
|
+
Napi::Value Device::GetAllConfigDescriptors(const Napi::CallbackInfo& info) {
|
|
170
172
|
ENTER_METHOD(Device, 0);
|
|
171
173
|
libusb_config_descriptor * cdesc;
|
|
172
174
|
struct libusb_device_descriptor dd;
|
|
@@ -180,7 +182,7 @@ Napi::Value Device::GetAllConfigDescriptors(const Napi::CallbackInfo& info){
|
|
|
180
182
|
return v8cdescriptors;
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
Napi::Value Device::GetParent(const Napi::CallbackInfo& info){
|
|
185
|
+
Napi::Value Device::GetParent(const Napi::CallbackInfo& info) {
|
|
184
186
|
ENTER_METHOD(Device, 0);
|
|
185
187
|
libusb_device* dev = libusb_get_parent(self->device);
|
|
186
188
|
if(dev)
|
|
@@ -193,9 +195,7 @@ Napi::Value Device::Open(const Napi::CallbackInfo& info) {
|
|
|
193
195
|
ENTER_METHOD(Device, 0);
|
|
194
196
|
if (!self->device_handle){
|
|
195
197
|
CHECK_USB(libusb_open(self->device, &self->device_handle));
|
|
196
|
-
#ifndef USE_POLL
|
|
197
198
|
completionQueue.start(info.Env());
|
|
198
|
-
#endif
|
|
199
199
|
}
|
|
200
200
|
return env.Undefined();
|
|
201
201
|
}
|
|
@@ -205,9 +205,7 @@ Napi::Value Device::Close(const Napi::CallbackInfo& info) {
|
|
|
205
205
|
if (self->canClose()){
|
|
206
206
|
libusb_close(self->device_handle);
|
|
207
207
|
self->device_handle = NULL;
|
|
208
|
-
#ifndef USE_POLL
|
|
209
208
|
completionQueue.stop();
|
|
210
|
-
#endif
|
|
211
209
|
}else{
|
|
212
210
|
THROW_ERROR("Can't close device with a pending request");
|
|
213
211
|
}
|
|
@@ -410,8 +408,8 @@ Napi::Object Device::Init(Napi::Env env, Napi::Object exports) {
|
|
|
410
408
|
});
|
|
411
409
|
exports.Set("Device", func);
|
|
412
410
|
|
|
413
|
-
|
|
414
|
-
|
|
411
|
+
ModuleData* instanceData = env.GetInstanceData<ModuleData>();
|
|
412
|
+
instanceData->deviceConstructor = Napi::Persistent(func);
|
|
415
413
|
|
|
416
414
|
return exports;
|
|
417
415
|
}
|
package/src/node_usb.cc
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#include "node_usb.h"
|
|
2
|
-
#include "
|
|
3
|
-
#include <thread>
|
|
2
|
+
#include "thread_name.h"
|
|
4
3
|
|
|
5
4
|
Napi::Value SetDebugLevel(const Napi::CallbackInfo& info);
|
|
6
5
|
Napi::Value UseUsbDkBackend(const Napi::CallbackInfo& info);
|
|
@@ -12,87 +11,87 @@ Napi::Value RefHotplugEvents(const Napi::CallbackInfo& info);
|
|
|
12
11
|
Napi::Value UnrefHotplugEvents(const Napi::CallbackInfo& info);
|
|
13
12
|
void initConstants(Napi::Object target);
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
14
|
+
void handleHotplug(HotPlug* info){
|
|
15
|
+
Napi::ObjectReference* hotplugThis = info->hotplugThis;
|
|
16
|
+
Napi::Env env = hotplugThis->Env();
|
|
17
|
+
Napi::HandleScope scope(env);
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
#include <uv.h>
|
|
24
|
-
#include <sys/time.h>
|
|
19
|
+
libusb_device* dev = info->device;
|
|
20
|
+
libusb_hotplug_event event = info->event;
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
DEBUG_LOG("HandleHotplug %p %i", dev, event);
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
Napi::Value v8dev = Device::get(env, dev);
|
|
25
|
+
libusb_unref_device(dev);
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
Napi::String eventName;
|
|
28
|
+
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) {
|
|
29
|
+
DEBUG_LOG("Device arrived");
|
|
30
|
+
eventName = Napi::String::New(env, "attach");
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
uv_poll_init(uv_default_loop(), poll_fd, fd);
|
|
42
|
-
pollByFD.insert(std::make_pair(fd, poll_fd));
|
|
32
|
+
} else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) {
|
|
33
|
+
DEBUG_LOG("Device left");
|
|
34
|
+
eventName = Napi::String::New(env, "detach");
|
|
35
|
+
|
|
36
|
+
} else {
|
|
37
|
+
DEBUG_LOG("Unhandled hotplug event %d\n", event);
|
|
38
|
+
return;
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| ((events&POLLOUT)? UV_WRITABLE:0);
|
|
48
|
-
uv_poll_start(poll_fd, flags, onPollSuccess);
|
|
41
|
+
hotplugThis->Get("emit").As<Napi::Function>().MakeCallback(hotplugThis->Value(), { eventName, v8dev });
|
|
42
|
+
delete info;
|
|
49
43
|
}
|
|
50
44
|
|
|
51
|
-
void
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
void USBThreadFn(ModuleData* instanceData) {
|
|
46
|
+
SetThreadName("node-usb events");
|
|
47
|
+
libusb_context* usb_context = instanceData->usb_context;
|
|
48
|
+
|
|
49
|
+
while(true) {
|
|
50
|
+
if (instanceData->handlingEvents == false) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
libusb_handle_events(usb_context);
|
|
58
54
|
}
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
ModuleData::ModuleData(libusb_context* usb_context) : usb_context(usb_context), hotplugQueue(handleHotplug) {
|
|
58
|
+
handlingEvents = true;
|
|
59
|
+
usb_thread = std::thread(USBThreadFn, this);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ModuleData::~ModuleData() {
|
|
63
|
+
handlingEvents = false;
|
|
64
|
+
libusb_interrupt_event_handler(usb_context);
|
|
65
|
+
usb_thread.join();
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
if (usb_context != nullptr) {
|
|
68
|
+
libusb_exit(usb_context);
|
|
69
|
+
usb_context = nullptr;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
int LIBUSB_CALL hotplug_callback(libusb_context* ctx, libusb_device* device,
|
|
74
|
+
libusb_hotplug_event event, void* user_data) {
|
|
75
|
+
libusb_ref_device(device);
|
|
76
|
+
ModuleData* instanceData = (ModuleData*)user_data;
|
|
77
|
+
instanceData->hotplugQueue.post(new HotPlug {device, event, &instanceData->hotplugThis});
|
|
78
|
+
return 0;
|
|
66
79
|
}
|
|
67
|
-
#endif
|
|
68
80
|
|
|
69
81
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
70
82
|
Napi::HandleScope scope(env);
|
|
71
|
-
|
|
72
83
|
initConstants(exports);
|
|
73
84
|
|
|
74
85
|
// Initialize libusb. On error, halt initialization.
|
|
86
|
+
libusb_context* usb_context = nullptr;
|
|
75
87
|
int res = libusb_init(&usb_context);
|
|
88
|
+
|
|
76
89
|
exports.Set("INIT_ERROR", Napi::Number::New(env, res));
|
|
77
90
|
if (res != 0) {
|
|
78
91
|
return exports;
|
|
79
92
|
}
|
|
80
93
|
|
|
81
|
-
|
|
82
|
-
assert(libusb_pollfds_handle_timeouts(usb_context));
|
|
83
|
-
libusb_set_pollfd_notifiers(usb_context, onPollFDAdded, onPollFDRemoved, NULL);
|
|
84
|
-
|
|
85
|
-
const struct libusb_pollfd** pollfds = libusb_get_pollfds(usb_context);
|
|
86
|
-
assert(pollfds);
|
|
87
|
-
for(const struct libusb_pollfd** i=pollfds; *i; i++){
|
|
88
|
-
onPollFDAdded((*i)->fd, (*i)->events, NULL);
|
|
89
|
-
}
|
|
90
|
-
free(pollfds);
|
|
91
|
-
|
|
92
|
-
#else
|
|
93
|
-
usb_thread = std::thread(USBThreadFn);
|
|
94
|
-
usb_thread.detach();
|
|
95
|
-
#endif
|
|
94
|
+
env.SetInstanceData(new ModuleData(usb_context));
|
|
96
95
|
|
|
97
96
|
Device::Init(env, exports);
|
|
98
97
|
Transfer::Init(env, exports);
|
|
@@ -117,7 +116,8 @@ Napi::Value SetDebugLevel(const Napi::CallbackInfo& info) {
|
|
|
117
116
|
THROW_BAD_ARGS("Usb::SetDebugLevel argument is invalid. [uint:[0-4]]!")
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
|
|
119
|
+
libusb_context* usb_context = env.GetInstanceData<ModuleData>()->usb_context;
|
|
120
|
+
libusb_set_option(usb_context, LIBUSB_OPTION_LOG_LEVEL, info[0].As<Napi::Number>().Int32Value());
|
|
121
121
|
return env.Undefined();
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -125,6 +125,7 @@ Napi::Value UseUsbDkBackend(const Napi::CallbackInfo& info) {
|
|
|
125
125
|
Napi::Env env = info.Env();
|
|
126
126
|
Napi::HandleScope scope(env);
|
|
127
127
|
|
|
128
|
+
libusb_context* usb_context = env.GetInstanceData<ModuleData>()->usb_context;
|
|
128
129
|
libusb_set_option(usb_context, LIBUSB_OPTION_USE_USBDK);
|
|
129
130
|
return env.Undefined();
|
|
130
131
|
}
|
|
@@ -132,7 +133,9 @@ Napi::Value UseUsbDkBackend(const Napi::CallbackInfo& info) {
|
|
|
132
133
|
Napi::Value GetDeviceList(const Napi::CallbackInfo& info) {
|
|
133
134
|
Napi::Env env = info.Env();
|
|
134
135
|
Napi::HandleScope scope(env);
|
|
135
|
-
libusb_device
|
|
136
|
+
libusb_device** devs;
|
|
137
|
+
|
|
138
|
+
libusb_context* usb_context = env.GetInstanceData<ModuleData>()->usb_context;
|
|
136
139
|
int cnt = libusb_get_device_list(usb_context, &devs);
|
|
137
140
|
CHECK_USB(cnt);
|
|
138
141
|
|
|
@@ -156,62 +159,28 @@ Napi::Value GetLibusbCapability(const Napi::CallbackInfo& info) {
|
|
|
156
159
|
return Napi::Number::New(env, res);
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
Napi::ObjectReference hotplugThis;
|
|
160
|
-
|
|
161
|
-
void handleHotplug(HotPlug* info){
|
|
162
|
-
Napi::Env env = hotplugThis.Env();
|
|
163
|
-
Napi::HandleScope scope(env);
|
|
164
|
-
|
|
165
|
-
libusb_device* dev = info->device;
|
|
166
|
-
libusb_hotplug_event event = info->event;
|
|
167
|
-
delete info;
|
|
168
|
-
|
|
169
|
-
DEBUG_LOG("HandleHotplug %p %i", dev, event);
|
|
170
|
-
|
|
171
|
-
Napi::Value v8dev = Device::get(env, dev);
|
|
172
|
-
libusb_unref_device(dev);
|
|
173
|
-
|
|
174
|
-
Napi::String eventName;
|
|
175
|
-
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) {
|
|
176
|
-
DEBUG_LOG("Device arrived");
|
|
177
|
-
eventName = Napi::String::New(env, "attach");
|
|
178
|
-
|
|
179
|
-
} else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) {
|
|
180
|
-
DEBUG_LOG("Device left");
|
|
181
|
-
eventName = Napi::String::New(env, "detach");
|
|
182
|
-
|
|
183
|
-
} else {
|
|
184
|
-
DEBUG_LOG("Unhandled hotplug event %d\n", event);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
hotplugThis.Get("emit").As<Napi::Function>().MakeCallback(hotplugThis.Value(), { eventName, v8dev });
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
bool hotplugEnabled = 0;
|
|
192
|
-
libusb_hotplug_callback_handle hotplugHandle;
|
|
193
|
-
UVQueue<HotPlug*> hotplugQueue(handleHotplug);
|
|
194
|
-
|
|
195
|
-
int LIBUSB_CALL hotplug_callback(libusb_context *ctx, libusb_device *device,
|
|
196
|
-
libusb_hotplug_event event, void *user_data) {
|
|
197
|
-
libusb_ref_device(device);
|
|
198
|
-
hotplugQueue.post(new HotPlug {device, event});
|
|
199
|
-
return 0;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
162
|
Napi::Value EnableHotplugEvents(const Napi::CallbackInfo& info) {
|
|
203
163
|
Napi::Env env = info.Env();
|
|
204
164
|
Napi::HandleScope scope(env);
|
|
165
|
+
ModuleData* instanceData = env.GetInstanceData<ModuleData>();
|
|
205
166
|
|
|
206
|
-
if (!hotplugEnabled) {
|
|
207
|
-
hotplugThis.Reset(info.This().As<Napi::Object>(), 1);
|
|
208
|
-
|
|
209
|
-
|
|
167
|
+
if (!instanceData->hotplugEnabled) {
|
|
168
|
+
instanceData->hotplugThis.Reset(info.This().As<Napi::Object>(), 1);
|
|
169
|
+
|
|
170
|
+
libusb_context* usb_context = instanceData->usb_context;
|
|
171
|
+
CHECK_USB(libusb_hotplug_register_callback(
|
|
172
|
+
usb_context,
|
|
210
173
|
(libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
|
|
211
|
-
(libusb_hotplug_flag)0,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
174
|
+
(libusb_hotplug_flag)0,
|
|
175
|
+
LIBUSB_HOTPLUG_MATCH_ANY,
|
|
176
|
+
LIBUSB_HOTPLUG_MATCH_ANY,
|
|
177
|
+
LIBUSB_HOTPLUG_MATCH_ANY,
|
|
178
|
+
hotplug_callback,
|
|
179
|
+
instanceData,
|
|
180
|
+
&instanceData->hotplugHandle
|
|
181
|
+
));
|
|
182
|
+
instanceData->hotplugQueue.start(env);
|
|
183
|
+
instanceData->hotplugEnabled = true;
|
|
215
184
|
}
|
|
216
185
|
return env.Undefined();
|
|
217
186
|
}
|
|
@@ -219,10 +188,13 @@ Napi::Value EnableHotplugEvents(const Napi::CallbackInfo& info) {
|
|
|
219
188
|
Napi::Value DisableHotplugEvents(const Napi::CallbackInfo& info) {
|
|
220
189
|
Napi::Env env = info.Env();
|
|
221
190
|
Napi::HandleScope scope(env);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
191
|
+
ModuleData* instanceData = env.GetInstanceData<ModuleData>();
|
|
192
|
+
|
|
193
|
+
if (instanceData->hotplugEnabled) {
|
|
194
|
+
libusb_context* usb_context = instanceData->usb_context;
|
|
195
|
+
libusb_hotplug_deregister_callback(usb_context, instanceData->hotplugHandle);
|
|
196
|
+
instanceData->hotplugQueue.stop();
|
|
197
|
+
instanceData->hotplugEnabled = false;
|
|
226
198
|
}
|
|
227
199
|
return env.Undefined();
|
|
228
200
|
}
|
|
@@ -230,8 +202,10 @@ Napi::Value DisableHotplugEvents(const Napi::CallbackInfo& info) {
|
|
|
230
202
|
Napi::Value RefHotplugEvents(const Napi::CallbackInfo& info) {
|
|
231
203
|
Napi::Env env = info.Env();
|
|
232
204
|
Napi::HandleScope scope(env);
|
|
233
|
-
|
|
234
|
-
|
|
205
|
+
ModuleData* instanceData = env.GetInstanceData<ModuleData>();
|
|
206
|
+
|
|
207
|
+
if (instanceData->hotplugEnabled) {
|
|
208
|
+
instanceData->hotplugQueue.ref(env);
|
|
235
209
|
}
|
|
236
210
|
return env.Undefined();
|
|
237
211
|
}
|
|
@@ -239,8 +213,10 @@ Napi::Value RefHotplugEvents(const Napi::CallbackInfo& info) {
|
|
|
239
213
|
Napi::Value UnrefHotplugEvents(const Napi::CallbackInfo& info) {
|
|
240
214
|
Napi::Env env = info.Env();
|
|
241
215
|
Napi::HandleScope scope(env);
|
|
242
|
-
|
|
243
|
-
|
|
216
|
+
ModuleData* instanceData = env.GetInstanceData<ModuleData>();
|
|
217
|
+
|
|
218
|
+
if (instanceData->hotplugEnabled) {
|
|
219
|
+
instanceData->hotplugQueue.unref(env);
|
|
244
220
|
}
|
|
245
221
|
return env.Undefined();
|
|
246
222
|
}
|
|
@@ -362,7 +338,7 @@ void initConstants(Napi::Object target){
|
|
|
362
338
|
DEFINE_CONSTANT(target, LIBUSB_ERROR_OTHER);
|
|
363
339
|
}
|
|
364
340
|
|
|
365
|
-
Napi::Error libusbException(
|
|
341
|
+
Napi::Error libusbException(Napi::Env env, int errorno) {
|
|
366
342
|
const char* err = libusb_error_name(errorno);
|
|
367
343
|
Napi::Error e = Napi::Error::New(env, err);
|
|
368
344
|
e.Set("errno", (double)errorno);
|
package/src/node_usb.h
CHANGED
|
@@ -8,35 +8,36 @@
|
|
|
8
8
|
#ifdef _WIN32
|
|
9
9
|
#include <WinSock2.h>
|
|
10
10
|
#endif
|
|
11
|
-
#include <libusb.h>
|
|
12
11
|
|
|
13
|
-
#
|
|
12
|
+
#include <thread>
|
|
13
|
+
#include <libusb.h>
|
|
14
14
|
#include <napi.h>
|
|
15
15
|
#include <node_buffer.h>
|
|
16
16
|
|
|
17
17
|
#include "helpers.h"
|
|
18
|
-
|
|
19
|
-
#ifndef USE_POLL
|
|
20
18
|
#include "uv_async_queue.h"
|
|
21
|
-
|
|
19
|
+
|
|
20
|
+
struct HotPlug {
|
|
21
|
+
libusb_device* device;
|
|
22
|
+
libusb_hotplug_event event;
|
|
23
|
+
Napi::ObjectReference* hotplugThis;
|
|
24
|
+
};
|
|
22
25
|
|
|
23
26
|
struct Transfer;
|
|
24
27
|
|
|
25
|
-
Napi::Error libusbException(
|
|
28
|
+
Napi::Error libusbException(Napi::Env env, int errorno);
|
|
26
29
|
void handleCompletion(Transfer* self);
|
|
27
30
|
|
|
28
31
|
struct Device: public Napi::ObjectWrap<Device> {
|
|
32
|
+
Napi::Env env;
|
|
29
33
|
libusb_device* device;
|
|
30
34
|
libusb_device_handle* device_handle;
|
|
31
35
|
|
|
32
36
|
int refs_;
|
|
33
|
-
|
|
34
|
-
#ifndef USE_POLL
|
|
35
37
|
UVQueue<Transfer*> completionQueue;
|
|
36
|
-
#endif
|
|
37
38
|
|
|
38
39
|
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
39
|
-
static Napi::Object get(
|
|
40
|
+
static Napi::Object get(Napi::Env env, libusb_device* handle);
|
|
40
41
|
|
|
41
42
|
inline void ref(){ refs_ = Ref();}
|
|
42
43
|
inline void unref(){ refs_ = Unref();}
|
|
@@ -45,8 +46,7 @@ struct Device: public Napi::ObjectWrap<Device> {
|
|
|
45
46
|
Device(const Napi::CallbackInfo& info);
|
|
46
47
|
~Device();
|
|
47
48
|
|
|
48
|
-
static Napi::Object cdesc2V8(
|
|
49
|
-
|
|
49
|
+
static Napi::Object cdesc2V8(Napi::Env env, libusb_config_descriptor * cdesc);
|
|
50
50
|
|
|
51
51
|
Napi::Value GetConfigDescriptor(const Napi::CallbackInfo& info);
|
|
52
52
|
Napi::Value GetAllConfigDescriptors(const Napi::CallbackInfo& info);
|
|
@@ -67,12 +67,24 @@ struct Device: public Napi::ObjectWrap<Device> {
|
|
|
67
67
|
|
|
68
68
|
Napi::Value ClearHalt(const Napi::CallbackInfo& info);
|
|
69
69
|
protected:
|
|
70
|
-
static std::map<libusb_device*, Device*> byPtr;
|
|
71
|
-
static Napi::FunctionReference constructor;
|
|
72
|
-
|
|
73
70
|
Napi::Value Constructor(const Napi::CallbackInfo& info);
|
|
74
71
|
};
|
|
75
72
|
|
|
73
|
+
struct ModuleData {
|
|
74
|
+
libusb_context* usb_context;
|
|
75
|
+
std::thread usb_thread;
|
|
76
|
+
std::atomic<bool> handlingEvents;
|
|
77
|
+
|
|
78
|
+
bool hotplugEnabled = 0;
|
|
79
|
+
libusb_hotplug_callback_handle hotplugHandle;
|
|
80
|
+
UVQueue<HotPlug*> hotplugQueue;
|
|
81
|
+
Napi::ObjectReference hotplugThis;
|
|
82
|
+
std::map<libusb_device*, Device*> byPtr;
|
|
83
|
+
Napi::FunctionReference deviceConstructor;
|
|
84
|
+
|
|
85
|
+
ModuleData(libusb_context* usb_context);
|
|
86
|
+
~ModuleData();
|
|
87
|
+
};
|
|
76
88
|
|
|
77
89
|
struct Transfer: public Napi::ObjectWrap<Transfer> {
|
|
78
90
|
libusb_transfer* transfer;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Definition of a SetThreadName function with an emphasis in "portability",
|
|
2
|
+
// meaning we prefer cautiously failing over creating build errors (or crashes)
|
|
3
|
+
// on exotic platforms. To do this, we only include/link against very
|
|
4
|
+
// few & widely available symbols.
|
|
5
|
+
#include "thread_name.h"
|
|
6
|
+
|
|
7
|
+
#if defined(__linux__)
|
|
8
|
+
|
|
9
|
+
// For Linux use the prctl API directly. prctl symbol
|
|
10
|
+
// should be available under any libc.
|
|
11
|
+
#include <sys/prctl.h>
|
|
12
|
+
|
|
13
|
+
// Define here to avoid relying on kernel headers being present
|
|
14
|
+
#define PR_SET_NAME 15 /* Set process name */
|
|
15
|
+
|
|
16
|
+
bool SetThreadName(const char* name) {
|
|
17
|
+
return prctl(PR_SET_NAME, name, 0, 0, 0) >= 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#elif defined(__APPLE__)
|
|
21
|
+
|
|
22
|
+
// For MacOS use the dynamic linker because I don't
|
|
23
|
+
// want to take any risks
|
|
24
|
+
#include <dlfcn.h>
|
|
25
|
+
|
|
26
|
+
extern "C" typedef int (*SetNameFn)(const char*);
|
|
27
|
+
|
|
28
|
+
bool SetThreadName(const char* name) {
|
|
29
|
+
auto pthread_setname_np = reinterpret_cast<SetNameFn>(
|
|
30
|
+
dlsym(RTLD_DEFAULT, "pthread_setname_np"));
|
|
31
|
+
if (pthread_setname_np == nullptr)
|
|
32
|
+
return false;
|
|
33
|
+
return pthread_setname_np(name) == 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#elif defined(_WIN32)
|
|
37
|
+
|
|
38
|
+
// For Windows, we use the new SetThreadDescription API which
|
|
39
|
+
// is only available in newish versions. To avoid taking any
|
|
40
|
+
// risks (and because on certain versions it's the only
|
|
41
|
+
// option to access the API), we use the dynamic linker
|
|
42
|
+
#include <windows.h>
|
|
43
|
+
|
|
44
|
+
extern "C" typedef HRESULT (WINAPI *SetThreadDescriptionFn)(HANDLE, PCWSTR);
|
|
45
|
+
|
|
46
|
+
static SetThreadDescriptionFn RetrieveSymbol(const char* objectName) {
|
|
47
|
+
auto mod = GetModuleHandleA(objectName);
|
|
48
|
+
if (mod == nullptr) return nullptr;
|
|
49
|
+
auto symbol = GetProcAddress(mod, "SetThreadDescription");
|
|
50
|
+
return reinterpret_cast<SetThreadDescriptionFn>(symbol);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#include <locale>
|
|
54
|
+
#include <codecvt>
|
|
55
|
+
#include <string>
|
|
56
|
+
|
|
57
|
+
bool SetThreadName(const char* name) {
|
|
58
|
+
auto SetThreadDescription = RetrieveSymbol("Kernel32.dll");
|
|
59
|
+
// apparently, MSDN is wrong and the symbol is defined in
|
|
60
|
+
// KernelBase.dll, so try that too
|
|
61
|
+
if (SetThreadDescription == nullptr)
|
|
62
|
+
SetThreadDescription = RetrieveSymbol("KernelBase.dll");
|
|
63
|
+
|
|
64
|
+
if (SetThreadDescription == nullptr) return false;
|
|
65
|
+
|
|
66
|
+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
67
|
+
std::wstring wide_name = converter.from_bytes(name);
|
|
68
|
+
|
|
69
|
+
auto result = SetThreadDescription(GetCurrentThread(), wide_name.c_str());
|
|
70
|
+
return SUCCEEDED(result);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#else
|
|
74
|
+
|
|
75
|
+
bool SetThreadName(const char* name) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#endif
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets the name of the calling thread to `name`, which is
|
|
3
|
+
* a NUL terminated UTF-8 string. Returns true if success,
|
|
4
|
+
* false if error or unsupported platform.
|
|
5
|
+
*
|
|
6
|
+
* Since this is an OS-dependent operation, the requirements
|
|
7
|
+
* of `name` may vary depending on platform. For example on
|
|
8
|
+
* Linux, the maximum length (excluding the terminator) is
|
|
9
|
+
* 16 bytes, and UTF-8 isn't strictly required (only conventional).
|
|
10
|
+
*/
|
|
11
|
+
bool SetThreadName(const char* name);
|
package/src/transfer.cc
CHANGED
|
@@ -84,12 +84,7 @@ extern "C" void LIBUSB_CALL usbCompletionCb(libusb_transfer *transfer){
|
|
|
84
84
|
Transfer* t = static_cast<Transfer*>(transfer->user_data);
|
|
85
85
|
DEBUG_LOG("Completion callback %p", t);
|
|
86
86
|
assert(t != NULL);
|
|
87
|
-
|
|
88
|
-
#ifdef USE_POLL
|
|
89
|
-
handleCompletion(t);
|
|
90
|
-
#else
|
|
91
87
|
t->device->completionQueue.post(t);
|
|
92
|
-
#endif
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
void handleCompletion(Transfer* self){
|
package/test/usb.coffee
CHANGED
|
@@ -4,6 +4,7 @@ usb = require('../').usb
|
|
|
4
4
|
getDeviceList = require('../').getDeviceList
|
|
5
5
|
findByIds = require('../').findByIds
|
|
6
6
|
findBySerialNumber = require('../').findBySerialNumber
|
|
7
|
+
Worker = require('worker_threads').Worker
|
|
7
8
|
|
|
8
9
|
if typeof gc is 'function'
|
|
9
10
|
# running with --expose-gc, do a sweep between tests so valgrind blames the right one
|
|
@@ -20,14 +21,14 @@ describe 'USB Module', ->
|
|
|
20
21
|
assert.throws -> usb.Device()
|
|
21
22
|
assert.throws -> usb.Device.prototype.open.call({})
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
describe 'setDebugLevel', ->
|
|
25
|
+
it 'should throw when passed invalid args', ->
|
|
26
|
+
assert.throws((-> usb.setDebugLevel()), TypeError)
|
|
27
|
+
assert.throws((-> usb.setDebugLevel(-1)), TypeError)
|
|
28
|
+
assert.throws((-> usb.setDebugLevel(5)), TypeError)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
it 'should succeed with good args', ->
|
|
31
|
+
assert.doesNotThrow(-> usb.setDebugLevel(0))
|
|
31
32
|
|
|
32
33
|
describe 'getDeviceList', ->
|
|
33
34
|
it 'should return at least one device', ->
|
|
@@ -201,3 +202,13 @@ describe 'Device', ->
|
|
|
201
202
|
|
|
202
203
|
after ->
|
|
203
204
|
device.close()
|
|
205
|
+
|
|
206
|
+
if process.platform != 'win32'
|
|
207
|
+
describe 'Context Aware', ->
|
|
208
|
+
it 'should handle opening the same device from different contexts', ->
|
|
209
|
+
for n in [1..5]
|
|
210
|
+
worker = new Worker('./test/worker.cjs')
|
|
211
|
+
worker.on 'message', (serial) ->
|
|
212
|
+
assert.equal(serial, 'TEST_DEVICE')
|
|
213
|
+
worker.on 'exit', (code) ->
|
|
214
|
+
assert.equal(code, 0)
|
package/test/worker.cjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const parentPort = require('worker_threads').parentPort
|
|
2
|
+
const findByIds = require('../dist').findByIds;
|
|
3
|
+
|
|
4
|
+
const device = findByIds(0x59e3, 0x0a23);
|
|
5
|
+
device.open();
|
|
6
|
+
device.getStringDescriptor(device.deviceDescriptor.iSerialNumber, (_, serial) => {
|
|
7
|
+
parentPort.postMessage(serial);
|
|
8
|
+
device.close();
|
|
9
|
+
});
|