├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── binding.gyp ├── example.js ├── index.c ├── index.js ├── msvc ├── libhidapi.sln ├── libhidapi.vcxproj └── libhidapi.vcxproj.filter ├── package.json ├── postinstall.js ├── preinstall.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | lib 3 | build 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "hidapi"] 2 | path = hidapi 3 | url = git://github.com/signal11/hidapi.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - node 5 | - lts/* 6 | os: 7 | - osx 8 | - linux 9 | - windows 10 | 11 | before_install: 12 | - npm run fetch-hidapi 13 | 14 | addons: 15 | apt: 16 | packages: 17 | - libudev-dev 18 | - libusb-1.0-0-dev 19 | 20 | before_deploy: 21 | - ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME-`uname -m`.tar" 22 | - npm run prebuild 23 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then npm run prebuild-ia32; ARCHIVE_NAME="${TRAVIS_TAG:-latest}-$TRAVIS_OS_NAME-all.tar"; fi 24 | - tar --create --verbose --file="$ARCHIVE_NAME" --directory "$TRAVIS_BUILD_DIR/prebuilds" . 25 | 26 | deploy: 27 | provider: releases 28 | draft: false 29 | prerelease: true 30 | api_key: 31 | secure: "ThF96hUpAuHyLTAeoto63MVpiDxzAVN7mROQ94L0k9Qm+AiBHPtO2v2hpfu49C5pgsVW3or/cXK4u9dkFoNX/mzPJfGaZy3MB4bJgzFZnydrStIEwsf5uiZAzQqKUgammrdpN7/AXazK/4w0VAaUz24Qe1fIMovkbbdjsx4oOHmPlqgH9U1jWDJz7Oi1vc5iOdIZpKYibiJveO9hOAbz999wQ/3502Up6Gzn43Toanth5GN4KN2IjAcne0HgvAZUORnzUreMgfKnKfxO6ZElGe1fimVh7HMotcD8pVNZNjsw/aY3JXXN+wOv3ywUENQTPaTMsd5QzqwfR/S5Ze993w+AqMyQUEZGw32dkTvQp5fMAftcOzIPnTf/Uuc70KJh52od+Jx4Vig826bKRxvRuJc6n3NRb+TNX+mnPmtNVKetetRchVX1NjT/V8V54X/Qdc/wTVoC6o55GDBpaTNtdg6gjttZd35oZgaz6Nq4iSIopIfFPclCIUDdyQGvNazyvH2uEP853t7pu1Pbrey+YvG2mxoBVsogCwPmx5IbXfA0QauKGQlVs8xAV4kEP9tCuT4mowPLLO3CALospOHemFZpgDzQggoYO93GG71bME1dKyysJwjOpe9plg2AGiVgL4v9rca6gustRqHYXnNPtmeCG9UaO9af1ngFXOd4ois=" 32 | file: "$ARCHIVE_NAME" 33 | skip_cleanup: true 34 | on: 35 | tags: true 36 | node: 'node' 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Hyperdivision ApS 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `hid` 2 | 3 | [![Build Status](https://travis-ci.org/Hyperdivision/hid.svg?branch=master)](https://travis-ci.org/Hyperdivision/hid) 4 | 5 | > Low-level bindings for USB/Bluetooth hidapi 6 | 7 | ## Usage 8 | 9 | ```js 10 | var hid = require('hid') 11 | 12 | // List all HID devices 13 | console.log(hid.enumerate(0, 0)) 14 | ``` 15 | 16 | ## API 17 | 18 | ### `const devices = hid.enumerate([vendor_id], [product_id])` 19 | 20 | Enumerate the HID Devices. 21 | 22 | This function returns an array of all the HID devices 23 | attached to the system which match `vendor_id` and `product_id`. 24 | If `vendor_id` is set to `0`, `null` or left out then any vendor matches. 25 | If `product_id` is set to `0`, `null` or left out then any product matches. 26 | If `vendor_id` and `product_id` are both set to `0`, `null` then 27 | all HID devices will be returned: 28 | 29 | ```js 30 | [{ 31 | path: String, 32 | vendor_id: Number, 33 | product_id: Number, 34 | serial_number: String, 35 | release_number: Number, 36 | manufacturer_string: String, 37 | product_string: String, 38 | usage_page: Number, 39 | usage: Number, 40 | interface_number: Number 41 | }] 42 | ``` 43 | 44 | ### `const device = hid.open(vendor_id, product_id, [serial_number])` 45 | 46 | Open a HID device using a Vendor ID (VID), Product ID 47 | (PID) and optionally a serial number. 48 | 49 | If `serial_number` is `null` or left out, the first device with the 50 | specified VID and PID is opened. 51 | 52 | This function returns an opaque external reference to a device, or throws an 53 | error on failure. The device is automatically closed on garbage collection. 54 | 55 | ### `const device = hid.open_path(path)` 56 | 57 | Open a HID device by its path name. 58 | 59 | The path name be determined by calling `hid.enumerate()`, or a 60 | platform-specific path name can be used (eg: `/dev/hidraw0` on 61 | Linux). 62 | 63 | This function returns an opaque external reference to a device, or throws an 64 | error on failure. The device is automatically closed on garbage collection. 65 | 66 | ### `const bytes = hid.write(device, data)` 67 | 68 | Write an Output report to a HID device. 69 | 70 | The first byte of `data` must contain the Report ID. For 71 | devices which only support a single report, this must be set 72 | to `0x0`. The remaining bytes contain the report data. Since 73 | the Report ID is mandatory, calls to `hid.write()` will always 74 | contain one more byte than the report contains. For example, 75 | if a hid report is 16 bytes long, 17 bytes must be passed to 76 | `hid.write()`, the Report ID (or `0x0`, for devices with a 77 | single report), followed by the report data (16 bytes). In 78 | this example, the length passed in would be 17. 79 | 80 | `hid.write()` will send the data on the first OUT endpoint, if 81 | one exists. If it does not, it will send the data through 82 | the Control Endpoint (Endpoint 0). 83 | 84 | `device` must be an opaque external reference to a device. 85 | `data` must be a `Buffer` that data is read from. 86 | 87 | This function returns the actual number of bytes written, or throws an error. 88 | 89 | ### `const bytes = hid.read_timeout(device, data, milliseconds)` 90 | 91 | Read an Input report from a HID device with timeout. 92 | 93 | Input reports are returned 94 | to the host through the INTERRUPT IN endpoint. The first byte will 95 | contain the Report number if the device uses numbered reports. 96 | 97 | `device` must be an opaque external reference to a device. 98 | `data` must be a `Buffer` that data is read into. 99 | `milliseconds` timeout in milliseconds or `-1` for blocking wait. 100 | 101 | This function returns the actual number of bytes read or throws an error. 102 | If no packet was available to be read within 103 | the timeout period, this function returns `0`. 104 | 105 | ### `const bytes = hid.read(device, data)` 106 | 107 | Read an Input report from a HID device. 108 | 109 | Input reports are returned 110 | to the host through the INTERRUPT IN endpoint. The first byte will 111 | contain the Report number if the device uses numbered reports. 112 | 113 | `device` must be an opaque external reference to a device. 114 | `data` must be a `Buffer` that data is read into. 115 | 116 | This function returns the actual number of bytes read or throws an error. 117 | If no packet was available to be read and 118 | the handle is in non-blocking mode, this function returns `0`. 119 | 120 | ### `hid.read_timeout_async(device, data, milliseconds, cb(err, bytes))` 121 | 122 | Read an Input report from a HID device with timeout asynchronously. 123 | 124 | Input reports are returned 125 | to the host through the INTERRUPT IN endpoint. The first byte will 126 | contain the Report number if the device uses numbered reports. 127 | 128 | `device` must be an opaque external reference to a device. 129 | `data` must be a `Buffer` that data is read into. 130 | `milliseconds` timeout in milliseconds or `-1` for blocking wait. 131 | `cb` must be a function taking `err` and `bytes` as arguments. 132 | 133 | This function calls `cb` with the actual number of bytes read or 134 | calls it with an error. 135 | If no packet was available to be read within 136 | the timeout period, this function calls `cb(null, 0)`. 137 | 138 | ### `hid.read_async(device, data, cb(err, bytes))` 139 | 140 | Read an Input report from a HID device asynchronously. 141 | 142 | Input reports are returned 143 | to the host through the INTERRUPT IN endpoint. The first byte will 144 | contain the Report number if the device uses numbered reports. 145 | 146 | `device` must be an opaque external reference to a device. 147 | `data` must be a `Buffer` that data is read into. 148 | `cb` must be a function taking `err` and `bytes` as arguments. 149 | 150 | This function calls `cb` with the actual number of bytes read or 151 | calls it with an error. 152 | If no packet was available to be read and 153 | the handle is in non-blocking mode, this function calls `cb(null, 0)`. 154 | 155 | ### `hid.set_nonblocking(device, nonblock)` 156 | 157 | Set the device handle to be non-blocking. 158 | 159 | In non-blocking mode calls to `hid.read()` will return 160 | immediately with a value of `0` if there is no data to be 161 | read. In blocking mode, `hid.read()` will wait (block) until 162 | there is data to read before returning. 163 | 164 | Nonblocking can be turned on and off at any time. 165 | 166 | `device` must be an opaque external reference to a device. 167 | `nonblock` enable or not the nonblocking reads 168 | - 1 to enable nonblocking 169 | - 0 to disable nonblocking. 170 | 171 | This function returns `0` on success or throws an error. 172 | 173 | ### `const bytes = hid.send_feature_report(device, data)` 174 | 175 | Send a Feature report to the device. 176 | 177 | Feature reports are sent over the Control endpoint as a 178 | Set_Report transfer. The first byte of `data` must 179 | contain the Report ID. For devices which only support a 180 | single report, this must be set to `0x0`. The remaining bytes 181 | contain the report data. Since the Report ID is mandatory, 182 | calls to `hid.send_feature_report()` will always contain one 183 | more byte than the report contains. For example, if a hid 184 | report is 16 bytes long, 17 bytes must be passed to 185 | `hid.send_feature_report()`: the Report ID (or `0x0`, for 186 | devices which do not use numbered reports), followed by the 187 | report data (16 bytes). In this example, the length passed 188 | in would be 17. 189 | 190 | `device` must be an opaque external reference to a device. 191 | `data` must be a `Buffer` that data is read from. 192 | 193 | This function returns the actual number of bytes written or throws an error. 194 | 195 | ### `const bytes = hid.get_feature_report(device, data)` 196 | 197 | Get a feature report from a HID device. 198 | 199 | Set the first byte of `data` to the Report ID of the 200 | report to be read. Make sure to allow space for this 201 | extra byte in `data`. Upon return, the first byte will 202 | still contain the Report ID, and the report data will 203 | start in `data[1]`. 204 | 205 | `device` must be an opaque external reference to a device. 206 | `data` must be a `Buffer` that data is read into. 207 | 208 | This function returns the number of bytes read plus 209 | one for the report ID (which is still in the first 210 | byte), or throws an error. 211 | 212 | ### `hid.get_feature_report_async(device, data, cb(err, bytes))` 213 | 214 | Get a feature report from a HID device asynchronously. 215 | 216 | Set the first byte of `data` to the Report ID of the 217 | report to be read. Make sure to allow space for this 218 | extra byte in `data`. Upon return, the first byte will 219 | still contain the Report ID, and the report data will 220 | start in `data[1]`. 221 | 222 | `device` must be an opaque external reference to a device. 223 | `data` must be a `Buffer` that data is read into. 224 | `cb` must be a function taking `err` and `bytes` as arguments. 225 | 226 | This function calls `cb` the number of bytes read plus 227 | one for the report ID (which is still in the first 228 | byte), or calls it with an error. 229 | 230 | ## Install 231 | 232 | ```sh 233 | npm install hid 234 | ``` 235 | 236 | ## Contributing 237 | 238 | To get setup you need to clone the repo including git submodules. 239 | `npm install` will run compilation scripts, but require the submodules 240 | to be initialised. 241 | 242 | ```sh 243 | git clone --recurse-submodules git://github.com/hyperdivision/hid.git 244 | cd hid 245 | npm run fetch-hidapi # Optional if you forgot --recurse-submodules 246 | npm install 247 | 248 | # Make changes ... 249 | npm run dev # recompile changes 250 | npm test # Run standard and run tests 251 | ``` 252 | 253 | ## License 254 | 255 | [ISC](LICENSE) 256 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'variables': { 3 | 'target_arch%': '' 4 | }, 5 | 'targets': [ 6 | { 7 | 'target_name': 'hidapi', 8 | 'include_dirs' : [ 9 | ' 2 | #include 3 | #ifdef _WIN32 4 | #include 5 | #endif 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define NAPI_RETURN_THROWS(call, message) \ 12 | if ((call)) { \ 13 | napi_throw_error(env, NULL, message); \ 14 | return NULL; \ 15 | } 16 | 17 | #define NAPI_EXTERNAL(name, val) \ 18 | NAPI_EXTERNAL_CAST(void *, name, val) 19 | 20 | #define NAPI_TYPEOF(name, val) \ 21 | napi_valuetype name##_valuetype; \ 22 | NAPI_STATUS_THROWS(napi_typeof(env, val, &name##_valuetype)); 23 | 24 | #define NAPI_ASSERT_TYPED_ARRAY(name, val, message) \ 25 | bool name##_is_typedarray; \ 26 | NAPI_STATUS_THROWS(napi_is_typedarray(env, val, &name##_is_typedarray)) \ 27 | if (name##_is_typedarray == 0) { \ 28 | napi_throw_type_error(env, NULL, message); \ 29 | return NULL; \ 30 | } 31 | 32 | #define NAPI_ASSERT_ARGV_TYPED_ARRAY(name, i, message) \ 33 | NAPI_ASSERT_TYPED_ARRAY(name, argv[i], message) 34 | 35 | #define NAPI_INT32_OPT(name, val, default) \ 36 | NAPI_TYPEOF(name, val) \ 37 | int32_t name; \ 38 | if (name##_valuetype == napi_null || name##_valuetype == napi_undefined) { \ 39 | name = default; \ 40 | } else if (napi_get_value_int32(env, val, &name) != napi_ok) { \ 41 | napi_throw_error(env, "EINVAL", "Expected number"); \ 42 | return NULL; \ 43 | } 44 | 45 | #define NAPI_ARGV_INT32_OPT(name, i, default) \ 46 | NAPI_INT32_OPT(name, argv[i], default) 47 | 48 | #define NAPI_ARGV_UTF8_OPT(name, size, i, default, default_size) \ 49 | NAPI_UTF8_OPT(name, size, argv[i], default, default_size) 50 | 51 | #define NAPI_EXTERNAL_CAST(type, name, val) \ 52 | type name; \ 53 | if (napi_get_value_external(env, val, (void **) &name) != napi_ok) { \ 54 | napi_throw_error(env, "EINVAL", "Expected external"); \ 55 | return NULL; \ 56 | } 57 | 58 | #define NAPI_RETURN_EXTERNAL(name, finalizer, finalizer_hint) \ 59 | napi_value return_external; \ 60 | NAPI_STATUS_THROWS(napi_create_external(env, name, finalizer, finalizer_hint, &return_external)) \ 61 | return return_external; 62 | 63 | #define NAPI_ARGV_EXTERNAL(name, i) \ 64 | NAPI_EXTERNAL(name, argv[i]) 65 | 66 | #define NAPI_ARGV_EXTERNAL_CAST(type, name, i) \ 67 | NAPI_EXTERNAL_CAST(type, name, argv[i]) 68 | 69 | #define THROW_HID_ERROR(handle, condition, default) \ 70 | if ((condition)) { \ 71 | char err_mbs_buffer[1024 + 1]; \ 72 | const wchar_t* err = hid_error(handle); \ 73 | NAPI_RETURN_THROWS(err == NULL, default); \ 74 | NAPI_RETURN_THROWS(wcstombs(err_mbs_buffer, err, 1024) == SIZE_MAX, "Failed convert error"); \ 75 | napi_throw_error(env, NULL, err_mbs_buffer); \ 76 | return NULL; \ 77 | } 78 | 79 | #define NAPI_ASSERT_ARGV_MIN(n) \ 80 | NAPI_RETURN_THROWS(argc < n, "Unsufficient arguments provided. Expected " #n) 81 | 82 | #define NAPI_EXPORT_NAMED_FUNCTION(key, name) \ 83 | { \ 84 | napi_value name##_fn; \ 85 | NAPI_STATUS_THROWS(napi_create_function(env, key, NAPI_AUTO_LENGTH, name, NULL, &name##_fn)) \ 86 | NAPI_STATUS_THROWS(napi_set_named_property(env, exports, key, name##_fn)) \ 87 | } 88 | 89 | NAPI_METHOD(napi_hid_enumerate) { 90 | NAPI_ARGV(2) 91 | NAPI_ARGV_INT32_OPT(vendor_id, 0, 0) 92 | NAPI_ARGV_INT32_OPT(product_id, 1, 0) 93 | 94 | char mbs_buffer[1024 + 1]; 95 | struct hid_device_info * device = hid_enumerate(vendor_id, product_id); 96 | struct hid_device_info * list = device; 97 | napi_value devices; 98 | NAPI_STATUS_THROWS(napi_create_array(env, &devices)) 99 | 100 | if (device == NULL) { 101 | return devices; 102 | } 103 | 104 | uint32_t i = 0; 105 | do { 106 | napi_value device_obj; 107 | NAPI_STATUS_THROWS(napi_create_object(env, &device_obj)) 108 | 109 | napi_value path_value; 110 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, device->path, NAPI_AUTO_LENGTH, &path_value)) 111 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "path", path_value)) 112 | 113 | napi_value vendor_id_value; 114 | NAPI_STATUS_THROWS(napi_create_uint32(env, device->vendor_id, &vendor_id_value)) 115 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "vendor_id", vendor_id_value)) 116 | 117 | napi_value product_id_value; 118 | NAPI_STATUS_THROWS(napi_create_uint32(env, device->product_id, &product_id_value)) 119 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "product_id", product_id_value)) 120 | 121 | 122 | if (wcstombs(mbs_buffer, device->serial_number, 1024) == SIZE_MAX) { 123 | napi_throw_error(env, NULL, "serial_number"); 124 | hid_free_enumeration(list); 125 | return NULL; 126 | } 127 | 128 | napi_value serial_number_value; 129 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, mbs_buffer, NAPI_AUTO_LENGTH, &serial_number_value)) 130 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "serial_number", serial_number_value)) 131 | 132 | napi_value release_number_value; 133 | NAPI_STATUS_THROWS(napi_create_uint32(env, device->release_number, &release_number_value)) 134 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "release_number", release_number_value)) 135 | 136 | 137 | if (wcstombs(mbs_buffer, device->manufacturer_string, 1024) == SIZE_MAX) { 138 | napi_throw_error(env, NULL, "manufacturer_string"); 139 | hid_free_enumeration(list); 140 | return NULL; 141 | } 142 | 143 | napi_value manufacturer_string_value; 144 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, mbs_buffer, NAPI_AUTO_LENGTH, &manufacturer_string_value)) 145 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "manufacturer_string", manufacturer_string_value)) 146 | 147 | 148 | if (wcstombs(mbs_buffer, device->product_string, 1024) == SIZE_MAX) { 149 | napi_throw_error(env, NULL, "product_string"); 150 | hid_free_enumeration(list); 151 | return NULL; 152 | } 153 | 154 | napi_value product_string_value; 155 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, mbs_buffer, NAPI_AUTO_LENGTH, &product_string_value)) 156 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "product_string", product_string_value)) 157 | 158 | napi_value usage_page_value; 159 | NAPI_STATUS_THROWS(napi_create_uint32(env, device->usage_page, &usage_page_value)) 160 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "usage_page", usage_page_value)) 161 | 162 | napi_value usage_value; 163 | NAPI_STATUS_THROWS(napi_create_uint32(env, device->usage, &usage_value)) 164 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "usage", usage_value)) 165 | 166 | napi_value interface_number_value; 167 | NAPI_STATUS_THROWS(napi_create_int32(env, device->interface_number, &interface_number_value)) 168 | NAPI_STATUS_THROWS(napi_set_named_property(env, device_obj, "interface_number", interface_number_value)) 169 | 170 | NAPI_STATUS_THROWS(napi_set_element(env, devices, i++, device_obj)) 171 | } while ((device = device->next) != NULL); 172 | 173 | hid_free_enumeration(list); 174 | 175 | return devices; 176 | } 177 | 178 | void napi_hid_device_finalizer (napi_env env, void* finalize_data, void* finalize_hint) { 179 | hid_close((hid_device *) finalize_data); 180 | } 181 | 182 | NAPI_METHOD(napi_hid_open) { 183 | NAPI_ARGV(3) 184 | NAPI_ASSERT_ARGV_MIN(2) 185 | NAPI_ARGV_INT32(vendor_id, 0) 186 | NAPI_ARGV_INT32(product_id, 1) 187 | 188 | wchar_t * wserial_number = NULL; 189 | if (argc == 3) { 190 | wchar_t wide_buffer[sizeof(wchar_t) * 257]; 191 | NAPI_ARGV_UTF8(serial_number, 1024 + 1, 2) 192 | NAPI_RETURN_THROWS(mbstowcs(wide_buffer, serial_number, sizeof(wchar_t) * 256) == SIZE_MAX, "Failed to convert serial number") 193 | } 194 | 195 | hid_device * device = hid_open(vendor_id, product_id, wserial_number); 196 | 197 | NAPI_RETURN_THROWS(device == NULL, "Failed open device") 198 | 199 | NAPI_RETURN_EXTERNAL(device, napi_hid_device_finalizer, NULL) 200 | } 201 | 202 | NAPI_METHOD(napi_hid_open_path) { 203 | NAPI_ARGV(1) 204 | NAPI_ASSERT_ARGV_MIN(1) 205 | NAPI_ARGV_UTF8(path, 1024 + 1, 0) 206 | 207 | hid_device * device = hid_open_path(path); 208 | 209 | NAPI_RETURN_THROWS(device == NULL, "Failed open_path device") 210 | 211 | NAPI_RETURN_EXTERNAL(device, napi_hid_device_finalizer, NULL) 212 | } 213 | 214 | NAPI_METHOD(napi_hid_write) { 215 | NAPI_ARGV(2) 216 | NAPI_ASSERT_ARGV_MIN(2) 217 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 218 | NAPI_ASSERT_ARGV_TYPED_ARRAY(data, 1, "data must be Buffer") 219 | NAPI_ARGV_BUFFER_CAST(const unsigned char *, data, 1) 220 | 221 | int n = hid_write(handle, data, data_len); 222 | 223 | THROW_HID_ERROR(handle, n < 0, "Failed write") 224 | 225 | NAPI_RETURN_INT32(n) 226 | } 227 | 228 | NAPI_METHOD(napi_hid_read_timeout) { 229 | NAPI_ARGV(3) 230 | NAPI_ASSERT_ARGV_MIN(3) 231 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 232 | NAPI_ASSERT_ARGV_TYPED_ARRAY(data, 1, "data must be Buffer") 233 | NAPI_ARGV_BUFFER_CAST(unsigned char *, data, 1) 234 | NAPI_ARGV_INT32(milliseconds, 2) 235 | 236 | int n = hid_read_timeout(handle, data, data_len, milliseconds); 237 | 238 | THROW_HID_ERROR(handle, n < 0, "Failed read_timeout") 239 | 240 | NAPI_RETURN_INT32(n) 241 | } 242 | 243 | NAPI_METHOD(napi_hid_read) { 244 | NAPI_ARGV(2) 245 | NAPI_ASSERT_ARGV_MIN(2) 246 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 247 | NAPI_ASSERT_ARGV_TYPED_ARRAY(data, 1, "data must be Buffer") 248 | NAPI_ARGV_BUFFER_CAST(unsigned char *, data, 1) 249 | 250 | int n = hid_read(handle, data, data_len); 251 | 252 | THROW_HID_ERROR(handle, n < 0, "Failed read") 253 | 254 | NAPI_RETURN_INT32(n) 255 | } 256 | 257 | typedef struct async_read_request { 258 | hid_device * handle; 259 | napi_ref data_ref; 260 | unsigned char * data; 261 | size_t data_len; 262 | napi_ref cb; 263 | int n; 264 | napi_async_work task; 265 | } async_read_request; 266 | 267 | void async_read_execute(napi_env env, void* req_v) { 268 | struct async_read_request * req = (async_read_request *)req_v; 269 | req->n = hid_read(req->handle, req->data, req->data_len); 270 | } 271 | 272 | void async_read_complete(napi_env env, napi_status status, void* data) { 273 | async_read_request * req = (async_read_request *)data; 274 | NAPI_STATUS_THROWS(status); 275 | 276 | napi_value global; 277 | NAPI_STATUS_THROWS(napi_get_global(env, &global)); 278 | 279 | napi_value argv[2]; 280 | if (req->n < 0) { 281 | // TODO try to read error from hid 282 | napi_value err_msg; 283 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, "Failed read", NAPI_AUTO_LENGTH, &err_msg)); 284 | NAPI_STATUS_THROWS(napi_create_error(env, NULL, err_msg, &argv[0])); 285 | NAPI_STATUS_THROWS(napi_get_undefined(env, &argv[1])); 286 | } else { 287 | NAPI_STATUS_THROWS(napi_get_null(env, &argv[0])); 288 | NAPI_STATUS_THROWS(napi_create_uint32(env, req->n, &argv[1])); 289 | } 290 | 291 | napi_value callback; 292 | NAPI_STATUS_THROWS(napi_get_reference_value(env, req->cb, &callback)); 293 | 294 | napi_value return_val; 295 | NAPI_STATUS_THROWS(napi_call_function(env, global, callback, 2, argv, &return_val)); 296 | NAPI_STATUS_THROWS(napi_delete_reference(env, req->cb)); 297 | NAPI_STATUS_THROWS(napi_delete_reference(env, req->data_ref)); 298 | NAPI_STATUS_THROWS(napi_delete_async_work(env, req->task)); 299 | free(req); 300 | } 301 | 302 | NAPI_METHOD(napi_hid_read_async) { 303 | NAPI_ARGV(3) 304 | NAPI_ASSERT_ARGV_MIN(3) 305 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 306 | NAPI_ASSERT_ARGV_TYPED_ARRAY(data, 1, "data must be Buffer") 307 | napi_value data_val = argv[1]; 308 | napi_value cb = argv[2]; 309 | 310 | async_read_request * req = (async_read_request *) malloc(sizeof(async_read_request)); 311 | req->handle = handle; 312 | req->n = 0; 313 | 314 | NAPI_STATUS_THROWS(napi_create_reference(env, cb, 1, &req->cb)); 315 | NAPI_STATUS_THROWS(napi_create_reference(env, data_val, 1, &req->data_ref)); 316 | NAPI_BUFFER_CAST(unsigned char *, data, data_val) 317 | req->data = data; 318 | req->data_len = data_len; 319 | 320 | napi_value async_resource_name; 321 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, "hid:read_async", NAPI_AUTO_LENGTH, &async_resource_name)) 322 | 323 | napi_create_async_work(env, NULL, async_resource_name, 324 | async_read_execute, 325 | async_read_complete, 326 | (void*)req, &req->task); 327 | 328 | NAPI_STATUS_THROWS(napi_queue_async_work(env, req->task)) 329 | 330 | return NULL; 331 | } 332 | 333 | typedef struct async_read_timeout_request { 334 | hid_device * handle; 335 | napi_ref data_ref; 336 | unsigned char * data; 337 | size_t data_len; 338 | int milliseconds; 339 | napi_ref cb; 340 | int n; 341 | napi_async_work task; 342 | } async_read_timeout_request; 343 | 344 | void async_read_timeout_execute(napi_env env, void* req_v) { 345 | struct async_read_timeout_request * req = (async_read_timeout_request *)req_v; 346 | req->n = hid_read_timeout(req->handle, req->data, req->data_len, req->milliseconds); 347 | } 348 | 349 | void async_read_timeout_complete(napi_env env, napi_status status, void* data) { 350 | async_read_timeout_request * req = (async_read_timeout_request *)data; 351 | NAPI_STATUS_THROWS(status); 352 | 353 | napi_value global; 354 | NAPI_STATUS_THROWS(napi_get_global(env, &global)); 355 | 356 | napi_value argv[2]; 357 | if (req->n < 0) { 358 | // TODO try to read error from hid 359 | napi_value err_msg; 360 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, "Failed read_timeout", NAPI_AUTO_LENGTH, &err_msg)); 361 | NAPI_STATUS_THROWS(napi_create_error(env, NULL, err_msg, &argv[0])); 362 | NAPI_STATUS_THROWS(napi_get_undefined(env, &argv[1])); 363 | } else { 364 | NAPI_STATUS_THROWS(napi_get_null(env, &argv[0])); 365 | NAPI_STATUS_THROWS(napi_create_uint32(env, req->n, &argv[1])); 366 | } 367 | 368 | napi_value callback; 369 | NAPI_STATUS_THROWS(napi_get_reference_value(env, req->cb, &callback)); 370 | 371 | napi_value return_val; 372 | NAPI_STATUS_THROWS(napi_call_function(env, global, callback, 2, argv, &return_val)); 373 | NAPI_STATUS_THROWS(napi_delete_reference(env, req->cb)); 374 | NAPI_STATUS_THROWS(napi_delete_reference(env, req->data_ref)); 375 | NAPI_STATUS_THROWS(napi_delete_async_work(env, req->task)); 376 | free(req); 377 | } 378 | 379 | NAPI_METHOD(napi_hid_read_timeout_async) { 380 | NAPI_ARGV(4) 381 | NAPI_ASSERT_ARGV_MIN(4) 382 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 383 | NAPI_ASSERT_ARGV_TYPED_ARRAY(data, 1, "data must be Buffer") 384 | napi_value data_val = argv[1]; 385 | NAPI_ARGV_INT32(milliseconds, 2) 386 | napi_value cb = argv[3]; 387 | 388 | async_read_timeout_request * req = (async_read_timeout_request *) malloc(sizeof(async_read_timeout_request)); 389 | req->handle = handle; 390 | req->milliseconds = milliseconds; 391 | req->n = 0; 392 | 393 | NAPI_STATUS_THROWS(napi_create_reference(env, cb, 1, &req->cb)); 394 | NAPI_STATUS_THROWS(napi_create_reference(env, data_val, 1, &req->data_ref)); 395 | NAPI_BUFFER_CAST(unsigned char *, data, data_val) 396 | req->data = data; 397 | req->data_len = data_len; 398 | 399 | napi_value async_resource_name; 400 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, "hid:read_timeout_async", NAPI_AUTO_LENGTH, &async_resource_name)) 401 | napi_create_async_work(env, NULL, async_resource_name, 402 | async_read_timeout_execute, 403 | async_read_timeout_complete, 404 | (void*)req, &req->task); 405 | 406 | NAPI_STATUS_THROWS(napi_queue_async_work(env, req->task)) 407 | 408 | return NULL; 409 | } 410 | 411 | NAPI_METHOD(napi_hid_set_nonblocking) { 412 | NAPI_ARGV(2) 413 | NAPI_ASSERT_ARGV_MIN(2) 414 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 415 | NAPI_ARGV_INT32(nonblock, 1) 416 | 417 | int res = hid_set_nonblocking(handle, nonblock); 418 | 419 | THROW_HID_ERROR(handle, res < 0, "Failed hid_set_nonblocking") 420 | 421 | return NULL; 422 | } 423 | 424 | NAPI_METHOD(napi_hid_send_feature_report) { 425 | NAPI_ARGV(2) 426 | NAPI_ASSERT_ARGV_MIN(2) 427 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 428 | NAPI_ASSERT_ARGV_TYPED_ARRAY(data, 1, "data must be Buffer") 429 | NAPI_ARGV_BUFFER_CAST(const unsigned char *, data, 1) 430 | 431 | int n = hid_send_feature_report(handle, data, data_len); 432 | 433 | THROW_HID_ERROR(handle, n < 0, "Failed send_feature_report") 434 | 435 | NAPI_RETURN_INT32(n) 436 | } 437 | 438 | NAPI_METHOD(napi_hid_get_feature_report) { 439 | NAPI_ARGV(2) 440 | NAPI_ASSERT_ARGV_MIN(2) 441 | // TODO: Needs type protection, eg. null/undefined 442 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 443 | NAPI_ASSERT_ARGV_TYPED_ARRAY(data, 1, "data must be Buffer") 444 | NAPI_ARGV_BUFFER_CAST(unsigned char *, data, 1) 445 | 446 | int n = hid_get_feature_report(handle, data, data_len); 447 | 448 | THROW_HID_ERROR(handle, n < 0, "Failed get_feature_report") 449 | 450 | NAPI_RETURN_INT32(n) 451 | } 452 | 453 | typedef struct async_get_feature_report_request { 454 | hid_device * handle; 455 | napi_ref data_ref; 456 | unsigned char * data; 457 | size_t data_len; 458 | int milliseconds; 459 | napi_ref cb; 460 | int n; 461 | napi_async_work task; 462 | } async_get_feature_report_request; 463 | 464 | void async_get_feature_report_execute(napi_env env, void* req_v) { 465 | struct async_get_feature_report_request * req = (async_get_feature_report_request *)req_v; 466 | req->n = hid_get_feature_report(req->handle, req->data, req->data_len); 467 | } 468 | 469 | void async_get_feature_report_complete(napi_env env, napi_status status, void* data) { 470 | async_get_feature_report_request * req = (async_get_feature_report_request *)data; 471 | NAPI_STATUS_THROWS(status); 472 | 473 | napi_value global; 474 | NAPI_STATUS_THROWS(napi_get_global(env, &global)); 475 | 476 | napi_value argv[2]; 477 | if (req->n < 0) { 478 | // TODO try to read error from hid 479 | napi_value err_msg; 480 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, "Failed get_feature_report", NAPI_AUTO_LENGTH, &err_msg)); 481 | NAPI_STATUS_THROWS(napi_create_error(env, NULL, err_msg, &argv[0])); 482 | NAPI_STATUS_THROWS(napi_get_undefined(env, &argv[1])); 483 | } else { 484 | NAPI_STATUS_THROWS(napi_get_null(env, &argv[0])); 485 | NAPI_STATUS_THROWS(napi_create_uint32(env, req->n, &argv[1])); 486 | } 487 | 488 | napi_value callback; 489 | NAPI_STATUS_THROWS(napi_get_reference_value(env, req->cb, &callback)); 490 | 491 | napi_value return_val; 492 | NAPI_STATUS_THROWS(napi_call_function(env, global, callback, 2, argv, &return_val)); 493 | NAPI_STATUS_THROWS(napi_delete_reference(env, req->cb)); 494 | NAPI_STATUS_THROWS(napi_delete_reference(env, req->data_ref)); 495 | NAPI_STATUS_THROWS(napi_delete_async_work(env, req->task)); 496 | free(req); 497 | } 498 | 499 | NAPI_METHOD(napi_hid_get_feature_report_async) { 500 | NAPI_ARGV(3) 501 | NAPI_ASSERT_ARGV_MIN(3) 502 | NAPI_ARGV_EXTERNAL_CAST(hid_device *, handle, 0) 503 | NAPI_ASSERT_ARGV_TYPED_ARRAY(data, 1, "data must be Buffer") 504 | napi_value data_val = argv[1]; 505 | napi_value cb = argv[2]; 506 | 507 | async_get_feature_report_request * req = (async_get_feature_report_request *) malloc(sizeof(async_get_feature_report_request)); 508 | req->handle = handle; 509 | req->n = 0; 510 | 511 | NAPI_STATUS_THROWS(napi_create_reference(env, cb, 1, &req->cb)); 512 | NAPI_STATUS_THROWS(napi_create_reference(env, data_val, 1, &req->data_ref)); 513 | NAPI_BUFFER_CAST(unsigned char *, data, data_val) 514 | req->data = data; 515 | req->data_len = data_len; 516 | 517 | napi_value async_resource_name; 518 | NAPI_STATUS_THROWS(napi_create_string_utf8(env, "hid:get_feature_report_async", NAPI_AUTO_LENGTH, &async_resource_name)) 519 | napi_create_async_work(env, NULL, async_resource_name, 520 | async_get_feature_report_execute, 521 | async_get_feature_report_complete, 522 | (void*)req, &req->task); 523 | 524 | NAPI_STATUS_THROWS(napi_queue_async_work(env, req->task)) 525 | 526 | return NULL; 527 | } 528 | 529 | NAPI_INIT() { 530 | if (hid_init() != 0) { 531 | napi_throw_error(env, NULL, "Failed to hid_init"); 532 | return; 533 | } 534 | 535 | NAPI_EXPORT_NAMED_FUNCTION("enumerate", napi_hid_enumerate); 536 | NAPI_EXPORT_NAMED_FUNCTION("open", napi_hid_open); 537 | NAPI_EXPORT_NAMED_FUNCTION("open_path", napi_hid_open_path); 538 | NAPI_EXPORT_NAMED_FUNCTION("write", napi_hid_write); 539 | NAPI_EXPORT_NAMED_FUNCTION("read_timeout", napi_hid_read_timeout); 540 | NAPI_EXPORT_NAMED_FUNCTION("read", napi_hid_read); 541 | NAPI_EXPORT_NAMED_FUNCTION("read_timeout_async", napi_hid_read_timeout_async); 542 | NAPI_EXPORT_NAMED_FUNCTION("read_async", napi_hid_read_async); 543 | NAPI_EXPORT_NAMED_FUNCTION("set_nonblocking", napi_hid_set_nonblocking); 544 | NAPI_EXPORT_NAMED_FUNCTION("send_feature_report", napi_hid_send_feature_report); 545 | NAPI_EXPORT_NAMED_FUNCTION("get_feature_report", napi_hid_get_feature_report); 546 | NAPI_EXPORT_NAMED_FUNCTION("get_feature_report_async", napi_hid_get_feature_report_async); 547 | 548 | 549 | NAPI_STATUS_THROWS(napi_add_env_cleanup_hook(env, (void *)hid_exit, NULL)); 550 | } 551 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('node-gyp-build')(__dirname) 2 | -------------------------------------------------------------------------------- /msvc/libhidapi.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.645 5 | MinimumVisualStudioVersion = 10.0.40219.1s 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libhidapi", "libhidapi.vcxproj", "{00AE2EE2-A9FE-49AA-B950-90AB5384A606}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|Win32 = Debug|Win32 12 | Release|x64 = Release|x64 13 | Release|Win32 = Release|Win32 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606}.Debug|x64.ActiveCfg = Debug|x64 17 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606}.Debug|x64.Build.0 = Debug|x64 18 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606}.Debug|Win32.ActiveCfg = Debug|Win32 19 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606}.Debug|Win32.Build.0 = Debug|Win32 20 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606}.Release|x64.ActiveCfg = Release|x64 21 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606}.Release|x64.Build.0 = Release|x64 22 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606}.Release|Win32.ActiveCfg = Release|Win32 23 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606}.Release|Win32.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /msvc/libhidapi.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {00AE2EE2-A9FE-49AA-B950-90AB5384A606} 24 | Win32Proj 25 | 8.1 26 | 27 | 28 | 29 | DynamicLibrary 30 | true 31 | MultiByte 32 | false 33 | v141 34 | 35 | 36 | DynamicLibrary 37 | false 38 | true 39 | MultiByte 40 | v141 41 | 42 | 43 | DynamicLibrary 44 | true 45 | MultiByte 46 | false 47 | v141 48 | 49 | 50 | DynamicLibrary 51 | false 52 | true 53 | MultiByte 54 | v141 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | $(SolutionDir)..\hidapi\Build\$(Configuration)\$(Platform)\ 77 | $(SolutionDir)..\hidapi\Build\$(Configuration)\$(Platform)\Intermediate\ 78 | 79 | 80 | true 81 | $(SolutionDir)..\hidapi\Build\$(Configuration)\$(Platform)\ 82 | $(SolutionDir)..\hidapi\Build\$(Configuration)\$(Platform)\Intermediate\ 83 | 84 | 85 | $(SolutionDir)..\hidapi\Build\$(Configuration)\$(Platform)\ 86 | $(SolutionDir)..\hidapi\Build\$(Configuration)\$(Platform)\Intermediate\ 87 | 88 | 89 | $(SolutionDir)..\hidapi\Build\$(Configuration)\$(Platform)\ 90 | $(SolutionDir)..\hidapi\Build\$(Configuration)\$(Platform)\Intermediate\ 91 | 92 | 93 | 94 | WIN32;_DEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS;%(PreprocessorDefinitions) 95 | MultiThreadedDebugDLL 96 | Level3 97 | ProgramDatabase 98 | Disabled 99 | $(SolutionDir)..;$(SolutionDir)..\hidapi\hidapi;$(SolutionDir)..\hidapi;%(AdditionalIncludeDirectories) 100 | 101 | 102 | Console 103 | true 104 | 105 | 106 | 107 | 108 | Level3 109 | 110 | 111 | Full 112 | true 113 | true 114 | WIN32;NDEBUG;_WINDOWS;_USRDLL;HIDAPI_EXPORTS;%(PreprocessorDefinitions) 115 | MultiThreadedDLL 116 | Speed 117 | $(SolutionDir)..;$(SolutionDir)..\hidapi\hidapi;$(SolutionDir)..\hidapi;%(AdditionalIncludeDirectories) 118 | 119 | 120 | Console 121 | true 122 | true 123 | true 124 | 125 | 126 | 127 | 128 | $(SolutionDir)..\hidapi\hidapi;$(SolutionDir)..\hidapi;%(AdditionalIncludeDirectories) 129 | 130 | 131 | setupapi.lib;%(AdditionalDependencies) 132 | 133 | 134 | 135 | 136 | $(SolutionDir)..\hidapi\hidapi;$(SolutionDir)..\hidapi;%(AdditionalIncludeDirectories) 137 | 138 | 139 | setupapi.lib;%(AdditionalDependencies) 140 | 141 | 142 | 143 | 144 | $(SolutionDir)..\hidapi\hidapi;$(SolutionDir)..\hidapi;%(AdditionalIncludeDirectories) 145 | 146 | 147 | setupapi.lib;%(AdditionalDependencies) 148 | 149 | 150 | 151 | 152 | $(SolutionDir)..\hidapi\hidapi;$(SolutionDir)..\hidapi;%(AdditionalIncludeDirectories) 153 | 154 | 155 | setupapi.lib;%(AdditionalDependencies) 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /msvc/libhidapi.vcxproj.filter: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hid", 3 | "version": "1.0.1", 4 | "description": "Low-level bindings for USB/Bluetooth hidapi", 5 | "main": "index.js", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "dependencies": { 10 | "ini": "^1.3.5", 11 | "napi-macros": "^1.8.2", 12 | "node-gyp-build": "^4.1.0" 13 | }, 14 | "devDependencies": { 15 | "node-gyp": "^4.0.0", 16 | "prebuildify": "^3.0.0", 17 | "standard": "^12.0.1", 18 | "tape": "^4.10.1" 19 | }, 20 | "scripts": { 21 | "pretest": "standard", 22 | "test": "node test.js", 23 | "dev": "node-gyp build", 24 | "clean": "rm -rf build tmp lib hidapi/Build", 25 | "fetch-hidapi": "git submodule update --recursive --init", 26 | "install": "node-gyp-build \"node preinstall.js\" \"node postinstall.js\"", 27 | "prebuild": "prebuildify --napi --strip --preinstall \"node preinstall.js\" --postinstall \"node postinstall.js\"", 28 | "prebuild-ia32": "prebuildify --arch=ia32 --napi --strip --preinstall \"node preinstall.js\" --postinstall \"node postinstall.js\"" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/hyperdivision/hid.git" 33 | }, 34 | "keywords": [], 35 | "author": "Emil Bay ", 36 | "license": "ISC", 37 | "bugs": { 38 | "url": "https://github.com/hyperdivision/hid/issues" 39 | }, 40 | "homepage": "https://github.com/hyperdivision/hid#readme" 41 | } 42 | -------------------------------------------------------------------------------- /postinstall.js: -------------------------------------------------------------------------------- 1 | var os = require('os') 2 | var fs = require('fs') 3 | var path = require('path') 4 | var proc = require('child_process') 5 | var ini = require('ini') 6 | 7 | var release = path.join(__dirname, 'build/Release') 8 | var debug = path.join(__dirname, 'build/Debug') 9 | var tmp = path.join(__dirname, 'tmp') 10 | var build = fs.existsSync(release) ? release : debug 11 | var arch = process.env.PREBUILD_ARCH || os.arch() 12 | 13 | switch (os.platform()) { 14 | case 'win32': 15 | buildWindows() 16 | break 17 | 18 | case 'darwin': 19 | buildDarwin() 20 | break 21 | 22 | case 'freebsd': 23 | case 'openbsd': 24 | default: 25 | buildUnix() 26 | break 27 | } 28 | 29 | function buildWindows () { 30 | var lib = path.join(__dirname, 'lib/libhidapi-' + arch + '.dll') 31 | var dst = path.join(build, 'libhidapi.dll') 32 | if (fs.existsSync(dst)) return 33 | copy(lib, dst, function (err) { 34 | if (err) throw err 35 | }) 36 | } 37 | 38 | function buildUnix () { 39 | var lib = fs.realpathSync(path.join(__dirname, 'lib/libhidapi-hidraw-' + arch + '.so')) 40 | 41 | var la = ini.decode(fs.readFileSync(path.join(tmp, 'lib/libhidapi-hidraw.la')).toString()) 42 | var dst = path.join(build, la.dlname) 43 | 44 | if (fs.existsSync(dst)) return 45 | copy(lib, dst, function (err) { 46 | if (err) throw err 47 | }) 48 | } 49 | 50 | function buildDarwin () { 51 | var lib = path.join(__dirname, 'lib/libhidapi-' + arch + '.dylib') 52 | var dst = path.join(build, 'libhidapi.dylib') 53 | if (fs.existsSync(dst)) return 54 | copy(lib, dst, function (err) { 55 | if (err) throw err 56 | proc.exec('install_name_tool -id "@loader_path/libhidapi.dylib" libhidapi.dylib', { cwd: build }, function (err) { 57 | if (err) throw err 58 | proc.exec('install_name_tool -change "' + lib + '" "@loader_path/libhidapi.dylib" hidapi.node', { cwd: build }, function (err) { 59 | if (err) throw err 60 | }) 61 | }) 62 | }) 63 | } 64 | 65 | function copy (a, b, cb) { 66 | fs.stat(a, function (err, st) { 67 | if (err) return cb(err) 68 | fs.readFile(a, function (err, buf) { 69 | if (err) return cb(err) 70 | fs.writeFile(b, buf, function (err) { 71 | if (err) return cb(err) 72 | fs.chmod(b, st.mode, cb) 73 | }) 74 | }) 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /preinstall.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs') 4 | var os = require('os') 5 | var proc = require('child_process') 6 | var path = require('path') 7 | var ini = require('ini') 8 | 9 | var dir = path.join(__dirname, 'hidapi') 10 | var tmp = path.join(__dirname, 'tmp') 11 | var arch = process.env.PREBUILD_ARCH || os.arch() 12 | 13 | if (process.argv.indexOf('--arch') > -1) { 14 | arch = process.argv[process.argv.indexOf('--arch') + 1] 15 | } 16 | 17 | var warch = arch === 'x64' ? 'x64' : 'Win32' 18 | 19 | if (process.argv.indexOf('--print-arch') > -1) { 20 | console.log(arch) 21 | process.exit(0) 22 | } 23 | 24 | if (process.argv.indexOf('--print-lib') > -1) { 25 | switch (os.platform()) { 26 | case 'darwin': 27 | console.log('../lib/libhidapi-' + arch + '.dylib') 28 | break 29 | case 'openbsd': 30 | case 'freebsd': 31 | case 'linux': 32 | console.log(path.join(__dirname, 'lib/libhidapi-hidraw-' + arch + '.so')) 33 | break 34 | case 'win32': 35 | console.log('../hidapi/Build/Release/' + warch + '/libhidapi.lib') 36 | break 37 | default: 38 | process.exit(1) 39 | } 40 | 41 | process.exit(0) 42 | } 43 | 44 | mkdirSync(path.join(__dirname, 'lib')) 45 | 46 | switch (os.platform()) { 47 | case 'darwin': 48 | buildDarwin() 49 | break 50 | 51 | case 'win32': 52 | buildWindows() 53 | break 54 | 55 | default: 56 | buildUnix('hidraw', 'so', function (err) { 57 | if (err) throw err 58 | }) 59 | break 60 | } 61 | 62 | function findMsBuild () { 63 | var possiblePathSuffixes = [ 64 | '/Microsoft Visual Studio/2017/BuildTools/MSBuild/15.0/Bin/msbuild.exe', 65 | '/Microsoft Visual Studio/2017/Enterprise/MSBuild/15.0/Bin/msbuild.exe', 66 | '/Microsoft Visual Studio/2017/Professional/MSBuild/15.0/Bin/msbuild.exe', 67 | '/Microsoft Visual Studio/2017/Community/MSBuild/15.0/Bin/msbuild.exe', 68 | '/MSBuild/14.0/Bin/MSBuild.exe', 69 | '/MSBuild/12.0/Bin/MSBuild.exe' 70 | ] 71 | 72 | // First try X86 paths (on 64 bit machine which is most likely) then 32 bit 73 | var possiblePaths = possiblePathSuffixes.map(p => process.env['PROGRAMFILES(X86)'] + p) 74 | .concat(possiblePathSuffixes.map(p => process.env['PROGRAMFILES'] + p)) 75 | 76 | possiblePaths.push(process.env.WINDIR + '/Microsoft.NET/Framework/v4.0.30319/MSBuild.exe') 77 | 78 | for (var counter = 0; counter < possiblePaths.length; counter++) { 79 | var possiblePath = path.resolve(possiblePaths[counter]) 80 | try { 81 | fs.accessSync(possiblePath) 82 | return possiblePath 83 | } catch (error) { 84 | // Binary not found checking next path 85 | } 86 | } 87 | 88 | console.error('MSBuild not found') 89 | console.error('You can run "npm install --global --production windows-build-tools" to fix this.') 90 | 91 | process.exit(1) 92 | } 93 | 94 | function buildWindows () { 95 | var res = path.join(__dirname, 'lib/libhidapi-' + arch + '.dll') 96 | if (fs.existsSync(res)) return 97 | 98 | var msbuild = findMsBuild() 99 | var args = ['/p:Configuration=Release;Platform=' + warch, '/nologo'] 100 | spawn(msbuild, args, { cwd: path.join(__dirname, 'msvc'), stdio: 'inherit' }, function (err) { 101 | if (err) throw err 102 | 103 | var dll = path.join(dir, 'Build/Release/' + warch + '/libhidapi.dll') 104 | 105 | fs.rename(dll, res, function (err) { 106 | if (err) throw err 107 | }) 108 | }) 109 | } 110 | 111 | function buildUnix (driver, ext, cb) { 112 | var res = path.join(__dirname, `lib/libhidapi-${driver != null ? driver + '-' : ''}` + arch + '.' + ext) 113 | if (fs.existsSync(res)) return cb(null, res) 114 | 115 | spawn('./bootstrap', [], { cwd: dir, stdio: 'inherit' }, function (err) { 116 | if (err) throw err 117 | spawn('./configure', ['--prefix=' + tmp], { cwd: dir, stdio: 'inherit' }, function (err) { 118 | if (err) throw err 119 | spawn('make', ['clean'], { cwd: dir, stdio: 'inherit' }, function (err) { 120 | if (err) throw err 121 | spawn('make', ['install'], { cwd: dir, stdio: 'inherit' }, function (err) { 122 | if (err) throw err 123 | 124 | var la = ini.decode(fs.readFileSync(path.join(tmp, `lib/libhidapi${driver != null ? '-' + driver : ''}.la`)).toString()) 125 | 126 | var lib = fs.realpathSync(path.join(la.libdir, la.dlname)) 127 | fs.rename(lib, res, function (err) { 128 | if (err) throw err 129 | if (cb) cb(null, res) 130 | }) 131 | }) 132 | }) 133 | }) 134 | }) 135 | } 136 | 137 | function buildDarwin () { 138 | buildUnix(null, 'dylib', function (err, res) { 139 | if (err) throw err 140 | spawn('install_name_tool', ['-id', res, res], { stdio: 'inherit' }, function (err) { 141 | if (err) throw err 142 | }) 143 | }) 144 | } 145 | 146 | function spawn (cmd, args, opts, cb) { 147 | var c = proc.spawn(cmd, args, opts) 148 | c.on('exit', function (code) { 149 | if (code) return cb(new Error(cmd + ' exited with ' + code)) 150 | cb(null) 151 | }) 152 | } 153 | 154 | function mkdirSync (p) { 155 | try { 156 | fs.mkdirSync(p) 157 | } catch (err) { 158 | // do nothing 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const hid = require('.') 3 | 4 | test('fail', function (assert) { 5 | assert.throws(() => hid.enumerate('')) 6 | assert.throws(() => hid.enumerate('', 0)) 7 | assert.throws(() => hid.enumerate(0, '')) 8 | 9 | assert.throws(() => hid.open()) 10 | assert.throws(() => hid.open('')) 11 | assert.throws(() => hid.open('', 0)) 12 | assert.throws(() => hid.open(0, '')) 13 | assert.throws(() => hid.open(0, '', 0)) 14 | assert.throws(() => hid.open('', 0, 0)) 15 | assert.throws(() => hid.open(0, 0, 0)) 16 | assert.throws(() => hid.write()) 17 | assert.throws(() => hid.write(null)) 18 | assert.throws(() => hid.write(null, Buffer.alloc(0))) 19 | 20 | assert.end() 21 | }) 22 | --------------------------------------------------------------------------------