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 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.16.0](https://nodejs.org), which includes `npm`.
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=<(napi_build_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.3.1",
5
+ "version": "2.4.2",
6
6
  "main": "dist/index.js",
7
7
  "engines": {
8
- "node": ">=10.16.0"
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 --target 10.16.0 --strip",
50
- "prebuild-cross": "prebuildify-cross --napi --target 10.16.0 --strip",
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
- 4
76
+ 6
76
77
  ]
77
78
  }
78
79
  }
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::FunctionReference Device::constructor;
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(napi_env env, libusb_device* dev){
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 = Device::constructor.New({ Napi::External<libusb_device>::New(env, dev) });
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(napi_env env, libusb_config_descriptor * cdesc){
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
- Device::constructor = Napi::Persistent(func);
414
- Device::constructor.SuppressDestruct();
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 "uv_async_queue.h"
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
- libusb_context* usb_context;
16
- struct HotPlug {
17
- libusb_device* device;
18
- libusb_hotplug_event event;
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
- #ifdef USE_POLL
22
- #include <poll.h>
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
- std::map<int, uv_poll_t*> pollByFD;
22
+ DEBUG_LOG("HandleHotplug %p %i", dev, event);
27
23
 
28
- struct timeval zero_tv = {0, 0};
24
+ Napi::Value v8dev = Device::get(env, dev);
25
+ libusb_unref_device(dev);
29
26
 
30
- void onPollSuccess(uv_poll_t* handle, int status, int events){
31
- libusb_handle_events_timeout(usb_context, &zero_tv);
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
- void LIBUSB_CALL onPollFDAdded(int fd, short events, void *user_data){
35
- uv_poll_t *poll_fd;
36
- auto it = pollByFD.find(fd);
37
- if (it != pollByFD.end()){
38
- poll_fd = it->second;
39
- }else{
40
- poll_fd = (uv_poll_t*) malloc(sizeof(uv_poll_t));
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
- DEBUG_LOG("Added pollfd %i, %p", fd, poll_fd);
46
- unsigned flags = ((events&POLLIN) ? UV_READABLE:0)
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 LIBUSB_CALL onPollFDRemoved(int fd, void *user_data){
52
- auto it = pollByFD.find(fd);
53
- if (it != pollByFD.end()){
54
- DEBUG_LOG("Removed pollfd %i, %p", fd, it->second);
55
- uv_poll_stop(it->second);
56
- uv_close((uv_handle_t*) it->second, (uv_close_cb) free);
57
- pollByFD.erase(it);
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
- #else
62
- std::thread usb_thread;
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
- void USBThreadFn(){
65
- while(1) libusb_handle_events(usb_context);
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
- #ifdef USE_POLL
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
- libusb_set_debug(usb_context, info[0].As<Napi::Number>().Int32Value());
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 **devs;
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
- hotplugThis.SuppressDestruct();
209
- CHECK_USB(libusb_hotplug_register_callback(usb_context,
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, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
212
- hotplug_callback, NULL, &hotplugHandle));
213
- hotplugQueue.start(env);
214
- hotplugEnabled = true;
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
- if (hotplugEnabled) {
223
- libusb_hotplug_deregister_callback(usb_context, hotplugHandle);
224
- hotplugQueue.stop();
225
- hotplugEnabled = false;
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
- if (hotplugEnabled) {
234
- hotplugQueue.ref(env);
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
- if (hotplugEnabled) {
243
- hotplugQueue.unref(env);
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(napi_env env, int errorno) {
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
- #define NAPI_VERSION 4
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
- #endif
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(napi_env env, int errorno);
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(napi_env env, libusb_device* handle);
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(napi_env env, libusb_config_descriptor * cdesc);
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
- describe 'setDebugLevel', ->
24
- it 'should throw when passed invalid args', ->
25
- assert.throws((-> usb.setDebugLevel()), TypeError)
26
- assert.throws((-> usb.setDebugLevel(-1)), TypeError)
27
- assert.throws((-> usb.setDebugLevel(5)), TypeError)
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
- it 'should succeed with good args', ->
30
- assert.doesNotThrow(-> usb.setDebugLevel(0))
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)
@@ -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
+ });