├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── binding.gyp ├── doc └── debugging-notes.md ├── index.d.ts ├── index.js ├── license ├── package-lock.json ├── package.json ├── src ├── detection.cpp ├── detection.h ├── detection_linux.cpp ├── detection_mac.cpp ├── detection_win.cpp ├── deviceList.cpp └── deviceList.h └── test ├── .eslintrc ├── dev-helper-test.js ├── fixtures ├── requiring-exit-gracefully.js ├── sigint-after-start-monitoring-exit-gracefully.js ├── start-delayed-stop-monitoring-exit-gracefully.js └── start-stop-monitoring-exit-gracefully.js ├── lib ├── child-executor.js ├── command-runner.js └── set-timeout-promise-helper.js └── test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "parserOptions": { 7 | "ecmaFeatures": { 8 | "arrowFunctions": true, 9 | "blockBindings": true, 10 | "classes": true, 11 | "defaultParams": true, 12 | "destructuring": true, 13 | "forOf": true, 14 | "generators": true, 15 | "modules": true, 16 | "objectLiteralComputedProperties": true, 17 | "objectLiteralShorthandMethods": true, 18 | "objectLiteralShorthandProperties": true, 19 | "spread": true, 20 | "templateStrings": true, 21 | "unicodeCodePointEscapes": true 22 | }, 23 | }, 24 | "rules": { 25 | // 26 | //Possible Errors 27 | // 28 | "comma-dangle": 2, 29 | "no-dupe-args": 2, 30 | "no-dupe-keys": 2, 31 | "no-extra-semi": 2, 32 | "no-invalid-regexp": 2, 33 | "no-regex-spaces": 2, 34 | 35 | // Best Practices 36 | // 37 | "no-console": 2, 38 | "complexity": [1, 4], 39 | "max-depth": [2, 3], 40 | "no-extra-bind": 1, 41 | "default-case": 2, 42 | "dot-notation": 2, 43 | "eqeqeq": 2, 44 | "no-alert": 2, 45 | "no-eval": 2, 46 | "no-implied-eval": 2, 47 | "no-loop-func": 1, 48 | "no-redeclare": 2, 49 | "no-return-assign": 2, 50 | "no-sequences": 2, 51 | "no-with": 2, 52 | "radix": 2, 53 | "wrap-iife": 2, 54 | "yoda": 2, 55 | 56 | // Variables 57 | // 58 | "no-delete-var": 2, 59 | "no-undef": 2, 60 | 61 | 62 | // Style 63 | // 64 | "camelcase": 1, 65 | "indent": [1, "tab"], 66 | "comma-spacing": [1, {"before": false, "after": true}], 67 | "comma-style": [1, "last"], 68 | "consistent-this": [1, "self"], 69 | "eol-last": 1, 70 | "key-spacing": [1, {"beforeColon": false, "afterColon": true}], 71 | "new-parens": 1, 72 | "no-lonely-if": 1, 73 | "no-nested-ternary": 1, 74 | "no-spaced-func": 1, 75 | "no-trailing-spaces": 1, 76 | "no-underscore-dangle": 1, 77 | "operator-assignment": [2, "always"], 78 | "operator-linebreak": [2, "after"], 79 | "quote-props": [1, "as-needed"], 80 | "quotes": [2, "single"], 81 | "semi": [1, "always"], 82 | "semi-spacing": [2, {"before": false, "after": true}], 83 | "space-infix-ops": 1, 84 | "keyword-spacing": [2, { "overrides": { 85 | "if": { "after": false }, 86 | "for": { "after": false }, 87 | "while": { "after": false } 88 | } }], 89 | "space-unary-ops": [1, {"words": true, "nonwords": false}], 90 | "wrap-regex": 2, 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: 'ci' 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: 'Build on Node v${{ matrix.node }} ${{ matrix.os.name }} ${{ matrix.arch }}' 8 | runs-on: ${{ matrix.os.name }}-${{ matrix.os.version }} 9 | 10 | strategy: 11 | matrix: 12 | os: 13 | - name: Ubuntu 14 | version: latest 15 | - name: macOS 16 | version: latest 17 | - name: Windows 18 | # Using the `windows-2019` runner instead of latest(`windows-2022`) 19 | # because the build was last known working on that version of runner and 20 | # `prebuild` fails to find the 2022 Visual Studio install because 21 | # it's internally using an old version of `node-gyp`, see 22 | # https://github.com/prebuild/prebuild/issues/286 23 | # 24 | # Also some more context around this problem in this internal issue, 25 | # https://github.com/MadLittleMods/node-usb-detection/issues/164 26 | version: 2019 27 | arch: [x64] 28 | node: ['12', '14', '16'] 29 | include: 30 | - os: 31 | name: Windows 32 | version: 2019 33 | arch: x86 34 | node: '12' 35 | - os: 36 | name: Windows 37 | version: 2019 38 | arch: x86 39 | node: '14' 40 | - os: 41 | name: Windows 42 | version: 2019 43 | arch: x86 44 | node: '16' 45 | 46 | steps: 47 | - name: 'Checkout repository' 48 | uses: actions/checkout@v2 49 | 50 | - name: 'Install Node.js v${{ matrix.node }} ${{ matrix.arch }}' 51 | uses: actions/setup-node@v2 52 | with: 53 | node-version: ${{ matrix.node }} 54 | architecture: ${{ matrix.arch }} 55 | 56 | # FIXME: Install `npm@^8` (latest v8) once 57 | # https://github.com/actions/setup-node/issues/411#issuecomment-1025543081 58 | # is fixed. 59 | # 60 | # Even though we install a new version of node-gyp locally in 61 | # the project, the CI always seems to use the npm bundled version of 62 | # node-gyp. Even following the instructions from the docs, I could get it working 63 | # on all other platforms except for Windows and npm@6, see 64 | # - https://github.com/nodejs/node-gyp/blob/245cd5bbe4441d4f05e88f2fa20a86425419b6af/docs/Updating-npm-bundled-node-gyp.md 65 | # - https://github.com/nodejs/node-gyp/blob/245cd5bbe4441d4f05e88f2fa20a86425419b6af/docs/Force-npm-to-use-global-node-gyp.md 66 | - name: 'Install npm@^8 to get a more up to date bundled node-gyp' 67 | run: npm install --global npm@8.3.1 68 | 69 | # Fix `Error: Could not find any Visual Studio installation to use` 70 | # See https://github.com/nodejs/node-gyp/tree/245cd5bbe4441d4f05e88f2fa20a86425419b6af/docs#issues-finding-the-installed-visual-studio 71 | # 72 | # This is commented out because we're using `windows-2019` runners atm 73 | # - name: 'Set msvs_version so node-gyp can find the Visual Studio install' 74 | # if: ${{ matrix.os.name == 'Windows' }} 75 | # run: npm config set msvs_version 2022 76 | 77 | - name: 'Install Linux dependencies' 78 | if: ${{ matrix.os.name == 'Ubuntu' }} 79 | run: | 80 | sudo apt-get update 81 | sudo apt-get install libudev-dev 82 | 83 | - name: 'Node.js version' 84 | run: node --version 85 | 86 | - name: 'npm version' 87 | run: npm --version 88 | 89 | - name: 'node-gyp version' 90 | run: npm run node-gyp -- --version 91 | 92 | - name: 'Install dependencies and build from source' 93 | run: npm ci --build-from-source 94 | 95 | # We run this after `npm ci` so that `nan` is installed probably 96 | - name: 'node-gyp configuration (for debugging)' 97 | if: ${{ always() }} 98 | run: npm run node-gyp -- configure --verbose 99 | 100 | # TODO: Tests are disabled until we have tests which are 101 | # suitable for CI and don't require manual interaction. 102 | - name: 'Test' 103 | run: echo "Skipping tests on CI, as they currently require manual interaction." 104 | 105 | # Prebuilds should only be generated once per OS + arch 106 | # Running from Node LTS will generate prebuilds for all ABIs 107 | - name: 'Prebuild binaries for all ABIs' 108 | if: ${{ matrix.node == '16' }} 109 | run: npm run prebuild 110 | 111 | - name: 'Upload prebuilt binaries' 112 | if: ${{ matrix.node == '16' }} 113 | uses: actions/upload-artifact@v2 114 | with: 115 | name: prebuilds-${{ matrix.os.name }}-${{ matrix.arch }} 116 | path: prebuilds 117 | 118 | check: 119 | name: 'Check JavaScript code' 120 | runs-on: ubuntu-latest 121 | 122 | steps: 123 | - name: 'Checkout repository' 124 | uses: actions/checkout@v2 125 | 126 | - name: 'Install Node.js' 127 | uses: actions/setup-node@v2 128 | with: 129 | node-version: '14' 130 | 131 | - name: 'Install dependencies without building' 132 | run: npm ci --ignore-scripts 133 | 134 | - name: 'Lint' 135 | run: npm run lint 136 | 137 | publish: 138 | name: 'Publish release' 139 | if: ${{ startsWith(github.ref, 'refs/tags/v') }} 140 | needs: [check, build] 141 | runs-on: ubuntu-latest 142 | 143 | steps: 144 | - name: 'Checkout repository' 145 | uses: actions/checkout@v2 146 | 147 | - name: 'Install Node.js with registry configured for publish' 148 | uses: actions/setup-node@v2 149 | with: 150 | node-version: '14' 151 | registry-url: 'https://registry.npmjs.org' 152 | 153 | - name: 'Install dependencies without building' 154 | run: npm ci --ignore-scripts 155 | 156 | - name: 'Download prebuilt binaries' 157 | uses: actions/download-artifact@v2 158 | with: 159 | path: prebuilds 160 | 161 | - name: 'Publish to npm' 162 | # We use `--ignore-scripts` to skip the linting/testing `prepublishOnly` hook 163 | run: npm publish --ignore-scripts 164 | env: 165 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 166 | 167 | - name: 'Upload prebuilt artifacts to GitHub release' 168 | run: npm run prebuild-upload -- ${{ secrets.GITHUB_TOKEN }} 169 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings.xml 2 | .monitor 3 | .DS_Store 4 | 5 | npm-debug.log 6 | node_modules 7 | build 8 | 9 | # Ignore `prebuild` output 10 | prebuilds 11 | 12 | # From `segfault-handler` 13 | stacktrace-*.log 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build 2 | test 3 | prebuilds 4 | .github 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 4.14.2 - 2023-02-06 - Deprecated 🛑 4 | 5 | - Library is now deprecated. For help migrating, refer to the [documentation for `usb`](https://github.com/node-usb/node-usb#migrating-from-node-usb-detection) 6 | 7 | 8 | ## 4.14.1 - 2022-03-10 9 | 10 | - Attempt to get more build more prebuild binaries. Still missing a lot! 11 | - Prebuild binaries from new Node.js v16 LTS but this didn't help, https://github.com/MadLittleMods/node-usb-detection/commit/c246d9fa85adeda8846317fefb6c1e9a7c63995e 12 | - The problem stems from a fix to skip Node.js v17.0.0 which fails to build, https://github.com/MadLittleMods/node-usb-detection/pull/165#discussion_r823362667 13 | 14 | 15 | ## 4.14.0 - 2022-03-10 16 | 17 | - Fill in `device.deviceAddress`(as `devnum`) and `device.locationId`(as `busnum`) on Linux which were previously always hard-coded as `0` 18 | - Thanks to [@efuturetoday](https://github.com/efuturetoday) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/149) 19 | - Fix segmentation fault (segfault) crash after `stopMonitoring` on Linux 20 | - Thanks to [@umbernhard](https://github.com/umbernhard), [@antelle](https://github.com/antelle) and [@Julusian](https://github.com/Julusian) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/162) 21 | 22 | Developer facing: 23 | 24 | - Fix GitHub Actions CI failing on Windows runners (`windows-2022`) (prebuild failing to build binaries), https://github.com/MadLittleMods/node-usb-detection/pull/165 25 | - Explain why test can fail when not enough devices from different vendors, https://github.com/MadLittleMods/node-usb-detection/pull/166 26 | 27 | 28 | ## 4.13.0 - 2021-10-14 29 | 30 | - Skipping linting/testing when publishing to npm via CI 31 | 32 | 33 | ## 4.12.0 - 2021-10-14 34 | 35 | - Add specific instructions for rebuilding for Electron, https://github.com/MadLittleMods/node-usb-detection/pull/133 36 | - Replace TravisCI and AppVeyor with GitHub Actions 37 | - Thanks to [@mcous](https://github.com/mcous) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/152) 38 | - Bumped Node.js in CI to 12, 14, and 16 39 | - Bumped recommended Node.js version to 14 40 | - Dropped Linux x86 from CI, since Node.js stopped officially publishing Linux x86 builds after v8 41 | - Update and simplify development section in README as follows, 42 | - Thanks to [@mcous](https://github.com/mcous) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/152) 43 | - Remove Python 2 requirement, because `node-gyp` now only supports Python 3 and Python 2 has been EOL'd 44 | - Remove some outdated links and instructions in favor of linking to `node-gyp` documentation 45 | 46 | 47 | ## 4.11.0 - 2021-03-04 48 | 49 | - Use SPDX identifier for MIT in `package.json` license field 50 | - Thanks to [@mcous](https://github.com/mcous) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/121) 51 | - Add install for Electron instructions to readme 52 | - Thanks to [@mcous](https://github.com/mcous) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/120) 53 | - Add prebuilds for latest node and Electron versions 54 | - Thanks to [@NoahAndrews](https://github.com/NoahAndrews) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/125) 55 | 56 | ## 4.10.0 - 2020-07-21 57 | 58 | - Update `node-gyp@6.1.0`, https://github.com/MadLittleMods/node-usb-detection/pull/109 59 | - Update `prebuild-install@5.3.5` and `node-abi@2.18.0` to support Electron 9 60 | - Thanks to [@NoahAndrews](https://github.com/NoahAndrews) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/117) 61 | 62 | ## 4.9.0 - 2020-04-23 63 | 64 | - Update dependencies (`npm audit fix`), https://github.com/MadLittleMods/node-usb-detection/pull/108 65 | - Remove `fprintf` logging from macOS and add conditional `--debug` flag when building if you want logs 66 | - Thanks to [@DomiR](https://github.com/DomiR) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/101) 67 | 68 | ## 4.8.0 - 2020-04-23 69 | 70 | - Update `prebuild` dependencies to latest 71 | - Thanks to [@mcous](https://github.com/mcous) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/106) 72 | 73 | ## 4.7.0 - 2019-11-24 74 | 75 | - Fix deprecation warnings for `Nan::Callback:Call()` 76 | - Thanks to [@kryops](https://github.com/kryops) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/93) 77 | - Update TypeScript definitions 78 | - Thanks to [@RicoBrase](https://github.com/RicoBrase) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/90) 79 | 80 | ## 4.6.0 - 2019-11-24 81 | 82 | - Add support for Node.js v13 83 | - Thanks to [@kryops](https://github.com/kryops) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/92) 84 | 85 | ## 4.5.0 - 2019-10-06 86 | 87 | - Unbind ATL lib on Windows (`atlstr.h`) 88 | - Thanks to [@sarakusha](https://github.com/sarakusha) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/84) 89 | 90 | ## 4.4.0 - 2019-10-06 91 | 92 | - Fix `deviceName` garbled in Windows 93 | - Thanks to [@nononoone](https://github.com/nononoone) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/86) 94 | 95 | ## 4.3.0 - 2019-05-27 96 | 97 | - Add support for Node.js v12 98 | - Thanks to [@kryops](https://github.com/kryops) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/81) 99 | 100 | ## 4.2.0 - 2019-05-09 101 | 102 | - Add note about `usbDetect.find()` requiring `usbDetect.startMonitoring()` for proper results after insert/remove 103 | - Thanks to [@mikew](https://github.com/mikew) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/79) 104 | - Fix `usbDetect.find(...)` promise syntax/callback not working 105 | - Thanks to [@erikkallen](https://github.com/erikkallen) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/74) 106 | 107 | ## 4.1.0 - 2018-11-24 108 | 109 | - Add Windows backslash normalization for instance id's to fix certain USB devices not being detected 110 | - Thanks to [@LanetheGreat](https://github.com/LanetheGreat) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/73) 111 | 112 | ## v4.0.0 - 2018-09-27 113 | 114 | - Use native Promises, https://github.com/MadLittleMods/node-usb-detection/pull/69 115 | - Now requires Node.js >=4 116 | - Update dependencies based on npm security audit, https://github.com/MadLittleMods/node-usb-detection/pull/70 117 | 118 | ## v3.2.0 - 2018-07-23 119 | 120 | - Add TypeScript declarations/definitions 121 | - Thanks to [@thegecko](https://github.com/thegecko) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/66) 122 | 123 | ## v3.1.0 - 2018-06-04 124 | 125 | - Add serial number support to Windows 126 | - Thanks to [@doganmurat](https://github.com/doganmurat) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/62) 127 | 128 | ## v3.0.0 - 2018-05-13 129 | 130 | - Show multiple/duplicate USB devices on Windows 131 | - Thanks to [@doganmurat](https://github.com/doganmurat) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/54) 132 | - Update all dependencies 133 | 134 | ## v2.1.0 - 2018-02-06 135 | 136 | - Add npm `install` hook that will use our prebuilt binaries instead of having to compile from source with node-gyp. 137 | - Remove side-effects when you `require('usb-detection')` so the process won't hang from just requiring. 138 | - Fix 100% CPU usage on Linux, https://github.com/MadLittleMods/node-usb-detection/issues/2 139 | - Thanks to [@sarakusha](https://github.com/sarakusha) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/21) 140 | - Ensure the process will exit gracefully across all platforms, https://github.com/MadLittleMods/node-usb-detection/issues/35 141 | 142 | ## v2.0.1 - 2017-12-27 143 | 144 | - Remove npm `install` hook to prevent hanging the install process caused by `prebuild-install` verify require and our side-effects. 145 | - Thanks to [@Lange](https://github.com/Lange) for noticing an issue. 146 | 147 | ## v2.0.0 - 2017-12-19 148 | 149 | - ~~Remove side-effects when you `require('usb-detection')` so the process won't hang from just requiring.~~ 150 | Now requires an explicit call to `usbDetect.startMonitoring()` to begin listening to USB add/remove/change events. 151 | - Add npm `install` hook that will use our prebuilt binaries instead of having to compile from source with node-gyp. 152 | 153 | ## v1.4.2 - 2017-11-11 154 | 155 | - Remove npm `install` hook to prevent hanging the install process caused by `prebuild-install` verify require and our side-effects. 156 | - Thanks to [@Lange](https://github.com/Lange) for [figuring out the root cause](https://github.com/MadLittleMods/node-usb-detection/pull/47#issuecomment-343714022). 157 | 158 | ## v1.4.1 - 2017-11-11 159 | 160 | - Add check for null before notifying of addition/removal 161 | - Thanks to [@reidmweber](https://github.com/reidmweber) for [this contribution](https://github.com/MadLittleMods/node-usb-detection/pull/32) via [#37](https://github.com/MadLittleMods/node-usb-detection/pull/37) 162 | - Create prebuilt binaries on tagged releases 163 | - Thanks to [@jayalfredprufrock](https://github.com/jayalfredprufrock) for the PoC and [@Lange](https://github.com/Lange) for the [contribution](https://github.com/MadLittleMods/node-usb-detection/pull/47) 164 | 165 | ## v1.4.0 - 2016-03-20 166 | 167 | - Add compatibility for `node@0.10.x` by using more `nan` types and methods. 168 | - Thanks to [@apla](https://github.com/apla) for [this contribution](https://github.com/MadLittleMods/node-usb-detection/pull/26)! 169 | 170 | ## v1.3.0 - 2015-10-11 171 | 172 | - Add compatibility for Node 4 173 | - Upgrade [`nan`](https://www.npmjs.com/package/nan) dependency nan@2.x. Thank you [@lorenc-tomasz](https://github.com/lorenc-tomasz) 174 | 175 | ## v1.2.0 - 2015-06-12 176 | 177 | - New maintainer/owner [@MadLittleMods](https://github.com/MadLittleMods). Previously maintained by [@adrai](https://github.com/adrai) :+1: 178 | - Add tests `npm test` 179 | - `find` now also returns a promise 180 | - Format js and c++ 181 | - Added eslint file, linter code style guidelines 182 | - Alias `insert` as the `add` event name for `.on` 183 | - Update readme 184 | - Fix usage section `.on` callbacks which do not actually have a `err` parameter passed to the callback 185 | - Add API section to document clearly all of the methods and events emitted 186 | - Add test instructions 187 | 188 | ## v1.1.0 189 | 190 | - Add support for Node v0.12.x 191 | 192 | ## v1.0.3 193 | 194 | - Revert "ready for node >= 0.11.4" 195 | 196 | ## v1.0.2 197 | 198 | - Fix issues found via cppcheck 199 | 200 | ## v1.0.1 201 | 202 | - Ready for node >= 0.11.4 203 | 204 | ## v1.0.0 205 | 206 | - First release 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛑🛑 Deprecated 🛑🛑 2 | 3 | This library is now deprecated. We recommend using [`usb`](https://github.com/node-usb/node-usb#migrating-from-node-usb-detection) for hotplug detection instead. It supports a wider range of platforms, using a more proven codebase. 4 | 5 | For help migrating, refer to the [documentation for `usb`](https://github.com/node-usb/node-usb#migrating-from-node-usb-detection) 6 | 7 | --- 8 | 9 | [![npm version](https://badge.fury.io/js/usb-detection.svg)](http://badge.fury.io/js/usb-detection) [![Gitter](https://badges.gitter.im/MadLittleMods/node-usb-detection.svg)](https://gitter.im/MadLittleMods/node-usb-detection?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 10 | 11 | 12 | # usb-detection 13 | 14 | `usb-detection` allows you to listen for insert/remove events of USB devices on your system. 15 | 16 | 17 | ### [Changelog](https://github.com/MadLittleMods/node-usb-detection/blob/master/CHANGELOG.md) 18 | 19 | 20 | # Install 21 | 22 | ```sh 23 | npm install usb-detection 24 | ``` 25 | 26 | ## Install for Electron 27 | 28 | This module uses native extensions and needs to be compiled for your target version of Electron. Precompiled binaries for recent Node.js and Electron versions are built and published using [prebuild][] and can be installed automatically using [electron-rebuild][]. 29 | 30 | See the [Electron docs for using native modules][electron-native-modules] to ensure your project is set up to correctly use the prebuilt binaries for your version of Electron. 31 | 32 | [prebuild]: https://github.com/prebuild/prebuild 33 | [electron-rebuild]: https://github.com/electron/electron-rebuild 34 | [electron-native-modules]: https://www.electronjs.org/docs/tutorial/using-native-node-modules 35 | 36 | --- 37 | 38 | If you run into the following error, here are the exact steps you can use: 39 | 40 | ``` 41 | detection.node was compiled against a different Node.js version using NODE_MODULE_VERSION 72. This version of Node.js requires NODE_MODULE_VERSION 80. Please try re-compiling or re-installing 42 | ``` 43 | 44 | 1. `npm i electron-rebuild --save-dev` 45 | 1. `./node_modules/.bin/electron-rebuild` 46 | 47 | 48 | # Usage 49 | 50 | ```js 51 | var usbDetect = require('usb-detection'); 52 | 53 | usbDetect.startMonitoring(); 54 | 55 | // Detect add/insert 56 | usbDetect.on('add', function(device) { console.log('add', device); }); 57 | usbDetect.on('add:vid', function(device) { console.log('add', device); }); 58 | usbDetect.on('add:vid:pid', function(device) { console.log('add', device); }); 59 | 60 | // Detect remove 61 | usbDetect.on('remove', function(device) { console.log('remove', device); }); 62 | usbDetect.on('remove:vid', function(device) { console.log('remove', device); }); 63 | usbDetect.on('remove:vid:pid', function(device) { console.log('remove', device); }); 64 | 65 | // Detect add or remove (change) 66 | usbDetect.on('change', function(device) { console.log('change', device); }); 67 | usbDetect.on('change:vid', function(device) { console.log('change', device); }); 68 | usbDetect.on('change:vid:pid', function(device) { console.log('change', device); }); 69 | 70 | // Get a list of USB devices on your system, optionally filtered by `vid` or `pid` 71 | usbDetect.find(function(err, devices) { console.log('find', devices, err); }); 72 | usbDetect.find(vid, function(err, devices) { console.log('find', devices, err); }); 73 | usbDetect.find(vid, pid, function(err, devices) { console.log('find', devices, err); }); 74 | // Promise version of `find`: 75 | usbDetect.find().then(function(devices) { console.log(devices); }).catch(function(err) { console.log(err); }); 76 | 77 | // Allow the process to exit 78 | //usbDetect.stopMonitoring() 79 | ``` 80 | 81 | 82 | # API 83 | 84 | ## `usbDetect.startMonitoring()` 85 | 86 | Start listening for USB add/remove/change events. This will cause the Node.js process to stay open until you call `usbDetect.stopMonitoring()` (see below). 87 | 88 | 89 | ## `usbDetect.stopMonitoring()` 90 | 91 | Stop listening for USB add/remove/change events. This will also allow the Node.js process to exit. 92 | 93 | This is really only meant to be called once on exit. No guarantees if you start/stop monitoring multiple times, see https://github.com/MadLittleMods/node-usb-detection/issues/53 94 | 95 | 96 | ## `usbDetect.on(eventName, callback)` 97 | 98 | - `eventName` 99 | - `add`: also aliased as `insert` 100 | - `add:vid` 101 | - `add:vid:pid` 102 | - `remove` 103 | - `remove:vid` 104 | - `remove:vid:pid` 105 | - `change` 106 | - `change:vid` 107 | - `change:vid:pid` 108 | - `callback`: Function that is called whenever the event occurs 109 | - Takes a `device` 110 | 111 | 112 | ```js 113 | var usbDetect = require('usb-detection'); 114 | usbDetect.startMonitoring(); 115 | 116 | usbDetect.on('add', function(device) { 117 | console.log(device); 118 | }); 119 | 120 | /* Console output: 121 | { 122 | locationId: 0, 123 | vendorId: 5824, 124 | productId: 1155, 125 | deviceName: 'Teensy USB Serial (COM3)', 126 | manufacturer: 'PJRC.COM, LLC.', 127 | serialNumber: '', 128 | deviceAddress: 11 129 | } 130 | */ 131 | ``` 132 | 133 | 134 | ## `usbDetect.find(vid, pid, callback)` 135 | 136 | **Note:** All `find` calls return a promise even with the node-style callback flavors. 137 | 138 | - `find()` 139 | - `find(vid)` 140 | - `find(vid, pid)` 141 | - `find(callback)` 142 | - `find(vid, callback)` 143 | - `find(vid, pid, callback)` 144 | 145 | Parameters: 146 | 147 | - `vid`: restrict search to a certain vendor id 148 | - `pid`: restrict search to s certain product id 149 | - `callback`: Function that is called whenever the event occurs 150 | - Takes a `err` and `devices` parameter. 151 | 152 | 153 | ```js 154 | var usbDetect = require('usb-detection'); 155 | usbDetect.startMonitoring(); 156 | 157 | usbDetect.find(function(err, devices) { 158 | console.log(devices, err); 159 | }); 160 | // Equivalent to: 161 | // usbDetect.find().then(function(devices) { console.log(devices); }).catch(function(err) { console.log(err); }); 162 | 163 | /* Console output: 164 | [ 165 | { 166 | locationId: 0, 167 | vendorId: 0, 168 | productId: 0, 169 | deviceName: 'USB Root Hub', 170 | manufacturer: '(Standard USB Host Controller)', 171 | serialNumber: '', 172 | deviceAddress: 2 173 | }, 174 | { 175 | locationId: 0, 176 | vendorId: 5824, 177 | productId: 1155, 178 | deviceName: 'Teensy USB Serial (COM3)', 179 | manufacturer: 'PJRC.COM, LLC.', 180 | serialNumber: '', 181 | deviceAddress: 11 182 | } 183 | ] 184 | */ 185 | ``` 186 | 187 | 188 | 189 | 190 | # FAQ 191 | 192 | ### The script/process is not exiting/quiting 193 | 194 | ```js 195 | var usbDetect = require('usb-detection'); 196 | 197 | // Do some detection 198 | usbDetect.startMonitoring(); 199 | 200 | // After this call, the process will be able to quit 201 | usbDetect.stopMonitoring(); 202 | ``` 203 | 204 | ### `usbDetect.find()` always returns the same list of devices, even after removal. 205 | 206 | Make sure you call `usbDetect.startMonitoring()` before any calls to `usbDetect.find()`. 207 | 208 | 209 | ### `npm run rebuild` -> `The system cannot find the path specified.` 210 | 211 | If you are running into the `The system cannot find the path specified.` error when running `npm run rebuild`, 212 | make sure you have Python installed and on your PATH. 213 | 214 | You can verify `node-gyp` is configured correctly by looking at the output of `node-gyp configure --verbose`. 215 | 216 | 217 | ### To build a debug version with error outputs use: 218 | 219 | ```sh 220 | $ npm run rebuild --debug 221 | ``` 222 | 223 | 224 | # Development (compile from source) 225 | 226 | This assumes you also have everything on your system necessary to compile ANY native module for Node.js using [node-gyp](https://github.com/nodejs/node-gyp). This may not be the case, though, so please ensure the following requirements are satisfied before filing an issue about "does not install". 227 | 228 | If you are developing locally, you should use [Node.js v14](https://nodejs.org), but if you are just trying to install usb-detection, you should be able to compile from source using any supported version of Node. 229 | 230 | ### Windows 231 | 232 | See [node-gyp's Windows installation instructions](https://github.com/nodejs/node-gyp#on-windows). 233 | 234 | ### macOS 235 | 236 | See [node-gyp's macOS installation instructions](https://github.com/nodejs/node-gyp#on-windows). 237 | 238 | ### Linux 239 | 240 | You know what you need for you system, basically your appropriate analog of build-essential. Keep rocking! See [node-gyp's Unix installation instructions](https://github.com/nodejs/node-gyp#on-unix) for more details. 241 | 242 | ```sh 243 | sudo apt-get install build-essential 244 | ``` 245 | 246 | You will also need to install `libudev-dev`. 247 | 248 | ```sh 249 | sudo apt-get install libudev-dev 250 | ``` 251 | 252 | # Testing 253 | 254 | We have a suite of Mocha/Chai tests. 255 | 256 | The tests require some manual interaction of plugging/unplugging a USB device. Follow the cyan background text instructions. 257 | 258 | ```sh 259 | npm test 260 | ``` 261 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "detection", 5 | "sources": [ 6 | "src/detection.cpp", 7 | "src/detection.h", 8 | "src/deviceList.cpp" 9 | ], 10 | "include_dirs" : [ 11 | " 4 | // Rico Brase 5 | 6 | export interface Device { 7 | locationId: number; 8 | vendorId: number; 9 | productId: number; 10 | deviceName: string; 11 | manufacturer: string; 12 | serialNumber: string; 13 | deviceAddress: number; 14 | } 15 | 16 | export function find(vid: number, pid: number, callback: (error: any, devices: Device[]) => any): void; 17 | export function find(vid: number, pid: number): Promise; 18 | export function find(vid: number, callback: (error: any, devices: Device[]) => any): void; 19 | export function find(vid: number): Promise; 20 | export function find(callback: (error: any, devices: Device[]) => any): void; 21 | export function find(): Promise; 22 | 23 | export function startMonitoring(): void; 24 | export function stopMonitoring(): void; 25 | export function on(event: string, callback: (device: Device) => void): void; 26 | 27 | export const version: number; 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | //var SegfaultHandler = require('segfault-handler'); 2 | //SegfaultHandler.registerHandler(); 3 | 4 | var index = require('./package.json'); 5 | 6 | function isFunction(functionToCheck) { 7 | return typeof functionToCheck === 'function'; 8 | } 9 | 10 | if(global[index.name] && global[index.name].version === index.version) { 11 | module.exports = global[index.name]; 12 | } else { 13 | var detection = require('bindings')('detection.node'); 14 | var EventEmitter2 = require('eventemitter2').EventEmitter2; 15 | 16 | var detector = new EventEmitter2({ 17 | wildcard: true, 18 | delimiter: ':', 19 | maxListeners: 1000 // default would be 10! 20 | }); 21 | 22 | //detector.find = detection.find; 23 | detector.find = function(vid, pid, callback) { 24 | // Suss out the optional parameters 25 | if(isFunction(vid) && !pid && !callback) { 26 | callback = vid; 27 | vid = undefined; 28 | } else if(isFunction(pid) && !callback) { 29 | callback = pid; 30 | pid = undefined; 31 | } 32 | 33 | return new Promise(function(resolve, reject) { 34 | // Assemble the optional args into something we can use with `apply` 35 | var args = []; 36 | if(vid) { 37 | args = args.concat(vid); 38 | } 39 | if(pid) { 40 | args = args.concat(pid); 41 | } 42 | 43 | // Tack on our own callback that takes care of things 44 | args = args.concat(function(err, devices) { 45 | 46 | // We call the callback if they passed one 47 | if(callback) { 48 | callback.call(callback, err, devices); 49 | } 50 | 51 | // But also do the promise stuff 52 | if(err) { 53 | reject(err); 54 | return; 55 | } 56 | resolve(devices); 57 | }); 58 | 59 | // Fire off the `find` function that actually does all of the work 60 | detection.find.apply(detection, args); 61 | }); 62 | }; 63 | 64 | detection.registerAdded(function(device) { 65 | detector.emit('add:' + device.vendorId + ':' + device.productId, device); 66 | detector.emit('insert:' + device.vendorId + ':' + device.productId, device); 67 | detector.emit('add:' + device.vendorId, device); 68 | detector.emit('insert:' + device.vendorId, device); 69 | detector.emit('add', device); 70 | detector.emit('insert', device); 71 | 72 | detector.emit('change:' + device.vendorId + ':' + device.productId, device); 73 | detector.emit('change:' + device.vendorId, device); 74 | detector.emit('change', device); 75 | }); 76 | 77 | detection.registerRemoved(function(device) { 78 | detector.emit('remove:' + device.vendorId + ':' + device.productId, device); 79 | detector.emit('remove:' + device.vendorId, device); 80 | detector.emit('remove', device); 81 | 82 | detector.emit('change:' + device.vendorId + ':' + device.productId, device); 83 | detector.emit('change:' + device.vendorId, device); 84 | detector.emit('change', device); 85 | }); 86 | 87 | var started = false; 88 | 89 | detector.startMonitoring = function() { 90 | if(started) { 91 | return; 92 | } 93 | 94 | started = true; 95 | detection.startMonitoring(); 96 | }; 97 | 98 | detector.stopMonitoring = function() { 99 | if(!started) { 100 | return; 101 | } 102 | 103 | started = false; 104 | detection.stopMonitoring(); 105 | }; 106 | 107 | detector.version = index.version; 108 | global[index.name] = detector; 109 | 110 | module.exports = detector; 111 | } 112 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Kaba AG 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "usb-detection", 3 | "version": "4.14.2", 4 | "description": "Listen to USB devices and detect changes on them.", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "gypfile": true, 8 | "scripts": { 9 | "node-gyp": "node-gyp", 10 | "install": "prebuild-install || node-gyp rebuild", 11 | "prepublishOnly": "npm run validate", 12 | "lint": "eslint **/*.js", 13 | "validate": "npm run lint && npm test", 14 | "test": "jasmine ./test/test.js", 15 | "prebuild": "npm run prebuild-node && npm run prebuild-electron", 16 | "prebuild-node": "prebuild --force --strip --verbose -t 12.0.0 -t 14.0.0 -t 16.0.0", 17 | "prebuild-electron": "prebuild --force --strip --verbose -r electron -t 13.0.0 -t 14.0.0 -t 15.0.0 -t 16.0.0", 18 | "prebuild-upload": "prebuild --upload-all", 19 | "rebuild": "node-gyp rebuild" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/MadLittleMods/node-usb-detection.git" 24 | }, 25 | "keywords": [ 26 | "usb", 27 | "device", 28 | "hardware", 29 | "list", 30 | "insert", 31 | "add", 32 | "remove", 33 | "change", 34 | "plug", 35 | "unplug", 36 | "notification" 37 | ], 38 | "homepage": "https://github.com/MadLittleMods/node-usb-detection", 39 | "bugs": { 40 | "url": "https://github.com/MadLittleMods/node-usb-detection/issues" 41 | }, 42 | "license": "MIT", 43 | "engines": { 44 | "node": ">=4" 45 | }, 46 | "dependencies": { 47 | "bindings": "^1.5.0", 48 | "eventemitter2": "^5.0.1", 49 | "nan": "^2.15.0", 50 | "prebuild-install": "^7.0.1" 51 | }, 52 | "devDependencies": { 53 | "chai": "^4.1.2", 54 | "chalk": "^2.4.1", 55 | "eslint": "^4.19.1", 56 | "jasmine": "^3.1.0", 57 | "node-abi": "^3.8.0", 58 | "node-gyp": "^8.4.0", 59 | "prebuild": "^11.0.3" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/detection.cpp: -------------------------------------------------------------------------------- 1 | #include "detection.h" 2 | 3 | 4 | #define OBJECT_ITEM_LOCATION_ID "locationId" 5 | #define OBJECT_ITEM_VENDOR_ID "vendorId" 6 | #define OBJECT_ITEM_PRODUCT_ID "productId" 7 | #define OBJECT_ITEM_DEVICE_NAME "deviceName" 8 | #define OBJECT_ITEM_MANUFACTURER "manufacturer" 9 | #define OBJECT_ITEM_SERIAL_NUMBER "serialNumber" 10 | #define OBJECT_ITEM_DEVICE_ADDRESS "deviceAddress" 11 | 12 | 13 | Nan::Callback* addedCallback; 14 | bool isAddedRegistered = false; 15 | 16 | Nan::Callback* removedCallback; 17 | bool isRemovedRegistered = false; 18 | 19 | void RegisterAdded(const Nan::FunctionCallbackInfo& args) { 20 | Nan::HandleScope scope; 21 | 22 | v8::Local callback; 23 | 24 | if (args.Length() == 0) { 25 | return Nan::ThrowTypeError("First argument must be a function"); 26 | } 27 | 28 | if (args.Length() == 1) { 29 | // callback 30 | if(!args[0]->IsFunction()) { 31 | return Nan::ThrowTypeError("First argument must be a function"); 32 | } 33 | 34 | callback = args[0].As(); 35 | } 36 | 37 | addedCallback = new Nan::Callback(callback); 38 | isAddedRegistered = true; 39 | } 40 | 41 | void NotifyAdded(ListResultItem_t* it) { 42 | Nan::HandleScope scope; 43 | 44 | if (it == NULL) { 45 | return; 46 | } 47 | 48 | if (isAddedRegistered){ 49 | v8::Local argv[1]; 50 | v8::Local item = Nan::New(); 51 | Nan::Set(item, Nan::New(OBJECT_ITEM_LOCATION_ID).ToLocalChecked(), Nan::New(it->locationId)); 52 | Nan::Set(item, Nan::New(OBJECT_ITEM_VENDOR_ID).ToLocalChecked(), Nan::New(it->vendorId)); 53 | Nan::Set(item, Nan::New(OBJECT_ITEM_PRODUCT_ID).ToLocalChecked(), Nan::New(it->productId)); 54 | Nan::Set(item, Nan::New(OBJECT_ITEM_DEVICE_NAME).ToLocalChecked(), Nan::New(it->deviceName.c_str()).ToLocalChecked()); 55 | Nan::Set(item, Nan::New(OBJECT_ITEM_MANUFACTURER).ToLocalChecked(), Nan::New(it->manufacturer.c_str()).ToLocalChecked()); 56 | Nan::Set(item, Nan::New(OBJECT_ITEM_SERIAL_NUMBER).ToLocalChecked(), Nan::New(it->serialNumber.c_str()).ToLocalChecked()); 57 | Nan::Set(item, Nan::New(OBJECT_ITEM_DEVICE_ADDRESS).ToLocalChecked(), Nan::New(it->deviceAddress)); 58 | argv[0] = item; 59 | 60 | Nan::AsyncResource resource("usb-detection:NotifyAdded"); 61 | addedCallback->Call(1, argv, &resource); 62 | } 63 | } 64 | 65 | void RegisterRemoved(const Nan::FunctionCallbackInfo& args) { 66 | Nan::HandleScope scope; 67 | 68 | v8::Local callback; 69 | 70 | if (args.Length() == 0) { 71 | return Nan::ThrowTypeError("First argument must be a function"); 72 | } 73 | 74 | if (args.Length() == 1) { 75 | // callback 76 | if(!args[0]->IsFunction()) { 77 | return Nan::ThrowTypeError("First argument must be a function"); 78 | } 79 | 80 | callback = args[0].As(); 81 | } 82 | 83 | removedCallback = new Nan::Callback(callback); 84 | isRemovedRegistered = true; 85 | } 86 | 87 | void NotifyRemoved(ListResultItem_t* it) { 88 | Nan::HandleScope scope; 89 | 90 | if (it == NULL) { 91 | return; 92 | } 93 | 94 | if (isRemovedRegistered) { 95 | v8::Local argv[1]; 96 | v8::Local item = Nan::New(); 97 | Nan::Set(item, Nan::New(OBJECT_ITEM_LOCATION_ID).ToLocalChecked(), Nan::New(it->locationId)); 98 | Nan::Set(item, Nan::New(OBJECT_ITEM_VENDOR_ID).ToLocalChecked(), Nan::New(it->vendorId)); 99 | Nan::Set(item, Nan::New(OBJECT_ITEM_PRODUCT_ID).ToLocalChecked(), Nan::New(it->productId)); 100 | Nan::Set(item, Nan::New(OBJECT_ITEM_DEVICE_NAME).ToLocalChecked(), Nan::New(it->deviceName.c_str()).ToLocalChecked()); 101 | Nan::Set(item, Nan::New(OBJECT_ITEM_MANUFACTURER).ToLocalChecked(), Nan::New(it->manufacturer.c_str()).ToLocalChecked()); 102 | Nan::Set(item, Nan::New(OBJECT_ITEM_SERIAL_NUMBER).ToLocalChecked(), Nan::New(it->serialNumber.c_str()).ToLocalChecked()); 103 | Nan::Set(item, Nan::New(OBJECT_ITEM_DEVICE_ADDRESS).ToLocalChecked(), Nan::New(it->deviceAddress)); 104 | argv[0] = item; 105 | 106 | Nan::AsyncResource resource("usb-detection:NotifyRemoved"); 107 | removedCallback->Call(1, argv, &resource); 108 | } 109 | } 110 | 111 | void Find(const Nan::FunctionCallbackInfo& args) { 112 | Nan::HandleScope scope; 113 | 114 | int vid = 0; 115 | int pid = 0; 116 | v8::Local callback; 117 | 118 | if (args.Length() == 0) { 119 | return Nan::ThrowTypeError("First argument must be a function"); 120 | } 121 | 122 | if (args.Length() == 3) { 123 | if (args[0]->IsNumber() && args[1]->IsNumber()) { 124 | vid = (int) Nan::To(args[0]).FromJust(); 125 | pid = (int) Nan::To(args[1]).FromJust(); 126 | } 127 | 128 | // callback 129 | if(!args[2]->IsFunction()) { 130 | return Nan::ThrowTypeError("Third argument must be a function"); 131 | } 132 | 133 | callback = args[2].As(); 134 | } 135 | 136 | if (args.Length() == 2) { 137 | if (args[0]->IsNumber()) { 138 | vid = (int) Nan::To(args[0]).FromJust(); 139 | } 140 | 141 | // callback 142 | if(!args[1]->IsFunction()) { 143 | return Nan::ThrowTypeError("Second argument must be a function"); 144 | } 145 | 146 | callback = args[1].As(); 147 | } 148 | 149 | if (args.Length() == 1) { 150 | // callback 151 | if(!args[0]->IsFunction()) { 152 | return Nan::ThrowTypeError("First argument must be a function"); 153 | } 154 | 155 | callback = args[0].As(); 156 | } 157 | 158 | ListBaton* baton = new ListBaton(); 159 | strcpy(baton->errorString, ""); 160 | baton->callback = new Nan::Callback(callback); 161 | baton->vid = vid; 162 | baton->pid = pid; 163 | 164 | uv_work_t* req = new uv_work_t(); 165 | req->data = baton; 166 | uv_queue_work(uv_default_loop(), req, EIO_Find, (uv_after_work_cb)EIO_AfterFind); 167 | } 168 | 169 | void EIO_AfterFind(uv_work_t* req) { 170 | Nan::HandleScope scope; 171 | 172 | ListBaton* data = static_cast(req->data); 173 | 174 | v8::Local argv[2]; 175 | if(data->errorString[0]) { 176 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 177 | argv[1] = Nan::Undefined(); 178 | } 179 | else { 180 | v8::Local results = Nan::New(); 181 | int i = 0; 182 | for(std::list::iterator it = data->results.begin(); it != data->results.end(); it++, i++) { 183 | v8::Local item = Nan::New(); 184 | Nan::Set(item, Nan::New(OBJECT_ITEM_LOCATION_ID).ToLocalChecked(), Nan::New((*it)->locationId)); 185 | Nan::Set(item, Nan::New(OBJECT_ITEM_VENDOR_ID).ToLocalChecked(), Nan::New((*it)->vendorId)); 186 | Nan::Set(item, Nan::New(OBJECT_ITEM_PRODUCT_ID).ToLocalChecked(), Nan::New((*it)->productId)); 187 | Nan::Set(item, Nan::New(OBJECT_ITEM_DEVICE_NAME).ToLocalChecked(), Nan::New((*it)->deviceName.c_str()).ToLocalChecked()); 188 | Nan::Set(item, Nan::New(OBJECT_ITEM_MANUFACTURER).ToLocalChecked(), Nan::New((*it)->manufacturer.c_str()).ToLocalChecked()); 189 | Nan::Set(item, Nan::New(OBJECT_ITEM_SERIAL_NUMBER).ToLocalChecked(), Nan::New((*it)->serialNumber.c_str()).ToLocalChecked()); 190 | Nan::Set(item, Nan::New(OBJECT_ITEM_DEVICE_ADDRESS).ToLocalChecked(), Nan::New((*it)->deviceAddress)); 191 | Nan::Set(results, i, item); 192 | } 193 | argv[0] = Nan::Undefined(); 194 | argv[1] = results; 195 | } 196 | 197 | Nan::AsyncResource resource("usb-detection:EIO_AfterFind"); 198 | data->callback->Call(2, argv, &resource); 199 | 200 | for(std::list::iterator it = data->results.begin(); it != data->results.end(); it++) { 201 | delete *it; 202 | } 203 | delete data; 204 | delete req; 205 | } 206 | 207 | void StartMonitoring(const Nan::FunctionCallbackInfo& args) { 208 | Start(); 209 | } 210 | 211 | void StopMonitoring(const Nan::FunctionCallbackInfo& args) { 212 | Stop(); 213 | } 214 | 215 | extern "C" { 216 | void init (v8::Local target) { 217 | Nan::SetMethod(target, "find", Find); 218 | Nan::SetMethod(target, "registerAdded", RegisterAdded); 219 | Nan::SetMethod(target, "registerRemoved", RegisterRemoved); 220 | Nan::SetMethod(target, "startMonitoring", StartMonitoring); 221 | Nan::SetMethod(target, "stopMonitoring", StopMonitoring); 222 | InitDetection(); 223 | } 224 | } 225 | 226 | NODE_MODULE(detection, init); 227 | -------------------------------------------------------------------------------- /src/detection.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _USB_DETECTION_H 3 | #define _USB_DETECTION_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "deviceList.h" 16 | 17 | void Find(const Nan::FunctionCallbackInfo& args); 18 | void EIO_Find(uv_work_t* req); 19 | void EIO_AfterFind(uv_work_t* req); 20 | void InitDetection(); 21 | void StartMonitoring(const Nan::FunctionCallbackInfo& args); 22 | void Start(); 23 | void StopMonitoring(const Nan::FunctionCallbackInfo& args); 24 | void Stop(); 25 | 26 | 27 | struct ListBaton { 28 | public: 29 | //v8::Persistent callback; 30 | Nan::Callback* callback; 31 | std::list results; 32 | char errorString[1024]; 33 | int vid; 34 | int pid; 35 | }; 36 | 37 | void RegisterAdded(const Nan::FunctionCallbackInfo& args); 38 | void NotifyAdded(ListResultItem_t* it); 39 | void RegisterRemoved(const Nan::FunctionCallbackInfo& args); 40 | void NotifyRemoved(ListResultItem_t* it); 41 | 42 | #endif 43 | 44 | #ifdef DEBUG 45 | #define DEBUG_HEADER fprintf(stderr, "node-usb-detection [%s:%s() %d]: ", __FILE__, __FUNCTION__, __LINE__); 46 | #define DEBUG_FOOTER fprintf(stderr, "\n"); 47 | #define DEBUG_LOG(...) DEBUG_HEADER fprintf(stderr, __VA_ARGS__); DEBUG_FOOTER 48 | #else 49 | #define DEBUG_LOG(...) 50 | #endif -------------------------------------------------------------------------------- /src/detection_linux.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "detection.h" 5 | #include "deviceList.h" 6 | 7 | using namespace std; 8 | 9 | 10 | 11 | /********************************** 12 | * Local defines 13 | **********************************/ 14 | #define DEVICE_ACTION_ADDED "add" 15 | #define DEVICE_ACTION_REMOVED "remove" 16 | 17 | #define DEVICE_TYPE_DEVICE "usb_device" 18 | 19 | #define DEVICE_PROPERTY_NAME "ID_MODEL" 20 | #define DEVICE_PROPERTY_SERIAL "ID_SERIAL_SHORT" 21 | #define DEVICE_PROPERTY_VENDOR "ID_VENDOR" 22 | 23 | 24 | /********************************** 25 | * Local typedefs 26 | **********************************/ 27 | 28 | 29 | 30 | /********************************** 31 | * Local Variables 32 | **********************************/ 33 | static ListResultItem_t* currentItem; 34 | static bool isAdded; 35 | 36 | static udev *udev; 37 | static udev_enumerate *enumerate; 38 | static udev_list_entry *devices, *dev_list_entry; 39 | static udev_device *dev; 40 | 41 | static udev_monitor *mon; 42 | static int fd; 43 | 44 | static uv_work_t work_req; 45 | static uv_signal_t term_signal; 46 | static uv_signal_t int_signal; 47 | 48 | static uv_async_t async_handler; 49 | static uv_mutex_t notify_mutex; 50 | static uv_cond_t notifyDeviceHandled; 51 | 52 | static bool deviceHandled = true; 53 | 54 | static bool isRunning = false; 55 | 56 | /********************************** 57 | * Local Helper Functions protoypes 58 | **********************************/ 59 | static void BuildInitialDeviceList(); 60 | 61 | static void WaitForDeviceHandled(); 62 | static void SignalDeviceHandled(); 63 | static void cbTerminate(uv_signal_t *handle, int signum); 64 | static void cbWork(uv_work_t *req); 65 | static void cbAfter(uv_work_t *req, int status); 66 | static void cbAsync(uv_async_t *handle); 67 | 68 | /********************************** 69 | * Public Functions 70 | **********************************/ 71 | void Start() { 72 | if(isRunning) { 73 | return; 74 | } 75 | 76 | isRunning = true; 77 | 78 | uv_mutex_init(¬ify_mutex); 79 | uv_async_init(uv_default_loop(), &async_handler, cbAsync); 80 | uv_signal_init(uv_default_loop(), &term_signal); 81 | uv_signal_init(uv_default_loop(), &int_signal); 82 | uv_cond_init(¬ifyDeviceHandled); 83 | 84 | uv_queue_work(uv_default_loop(), &work_req, cbWork, cbAfter); 85 | } 86 | 87 | void Stop() { 88 | if(!isRunning) { 89 | return; 90 | } 91 | 92 | isRunning = false; 93 | } 94 | 95 | void InitDetection() { 96 | /* Create the udev object */ 97 | udev = udev_new(); 98 | if (!udev) 99 | { 100 | printf("Can't create udev\n"); 101 | return; 102 | } 103 | 104 | /* Set up a monitor to monitor devices */ 105 | mon = udev_monitor_new_from_netlink(udev, "udev"); 106 | udev_monitor_enable_receiving(mon); 107 | 108 | /* Get the file descriptor (fd) for the monitor. 109 | This fd will get passed to select() */ 110 | fd = udev_monitor_get_fd(mon); 111 | 112 | BuildInitialDeviceList(); 113 | } 114 | 115 | 116 | void EIO_Find(uv_work_t* req) { 117 | ListBaton* data = static_cast(req->data); 118 | 119 | CreateFilteredList(&data->results, data->vid, data->pid); 120 | } 121 | 122 | /********************************** 123 | * Local Functions 124 | **********************************/ 125 | static void WaitForDeviceHandled() { 126 | uv_mutex_lock(¬ify_mutex); 127 | if(deviceHandled == false) { 128 | uv_cond_wait(¬ifyDeviceHandled, ¬ify_mutex); 129 | } 130 | deviceHandled = false; 131 | uv_mutex_unlock(¬ify_mutex); 132 | } 133 | 134 | static void SignalDeviceHandled() { 135 | uv_mutex_lock(¬ify_mutex); 136 | deviceHandled = true; 137 | uv_cond_signal(¬ifyDeviceHandled); 138 | uv_mutex_unlock(¬ify_mutex); 139 | } 140 | 141 | static ListResultItem_t* GetProperties(struct udev_device* dev, ListResultItem_t* item) { 142 | struct udev_list_entry* sysattrs; 143 | struct udev_list_entry* entry; 144 | sysattrs = udev_device_get_properties_list_entry(dev); 145 | udev_list_entry_foreach(entry, sysattrs) { 146 | const char *name, *value; 147 | name = udev_list_entry_get_name(entry); 148 | value = udev_list_entry_get_value(entry); 149 | 150 | if(strcmp(name, DEVICE_PROPERTY_NAME) == 0) { 151 | item->deviceName = value; 152 | } 153 | else if(strcmp(name, DEVICE_PROPERTY_SERIAL) == 0) { 154 | item->serialNumber = value; 155 | } 156 | else if(strcmp(name, DEVICE_PROPERTY_VENDOR) == 0) { 157 | item->manufacturer = value; 158 | } 159 | } 160 | item->vendorId = strtol(udev_device_get_sysattr_value(dev,"idVendor"), NULL, 16); 161 | item->productId = strtol(udev_device_get_sysattr_value(dev,"idProduct"), NULL, 16); 162 | item->deviceAddress = strtol(udev_device_get_sysattr_value(dev,"devnum"), NULL, 10); 163 | item->locationId = strtol(udev_device_get_sysattr_value(dev,"busnum"), NULL, 10); 164 | 165 | return item; 166 | } 167 | 168 | static void DeviceAdded(struct udev_device* dev) { 169 | DeviceItem_t* item = new DeviceItem_t(); 170 | GetProperties(dev, &item->deviceParams); 171 | 172 | AddItemToList((char *)udev_device_get_devnode(dev), item); 173 | 174 | currentItem = &item->deviceParams; 175 | isAdded = true; 176 | 177 | uv_async_send(&async_handler); 178 | } 179 | 180 | static void DeviceRemoved(struct udev_device* dev) { 181 | ListResultItem_t* item = NULL; 182 | 183 | if(IsItemAlreadyStored((char *)udev_device_get_devnode(dev))) { 184 | DeviceItem_t* deviceItem = GetItemFromList((char *)udev_device_get_devnode(dev)); 185 | if(deviceItem) { 186 | item = CopyElement(&deviceItem->deviceParams); 187 | } 188 | RemoveItemFromList(deviceItem); 189 | delete deviceItem; 190 | } 191 | 192 | if(item == NULL) { 193 | item = new ListResultItem_t(); 194 | GetProperties(dev, item); 195 | } 196 | 197 | currentItem = item; 198 | isAdded = false; 199 | 200 | uv_async_send(&async_handler); 201 | } 202 | 203 | 204 | static void cbWork(uv_work_t *req) { 205 | // We have this check in case we `Stop` before this thread starts, 206 | // otherwise the process will hang 207 | if(!isRunning) { 208 | return; 209 | } 210 | 211 | uv_signal_start(&int_signal, cbTerminate, SIGINT); 212 | uv_signal_start(&term_signal, cbTerminate, SIGTERM); 213 | 214 | pollfd fds = {fd, POLLIN, 0}; 215 | while (isRunning) { 216 | int ret = poll(&fds, 1, 100); 217 | if (!ret) continue; 218 | if (ret < 0) break; 219 | 220 | dev = udev_monitor_receive_device(mon); 221 | if (dev) { 222 | if(udev_device_get_devtype(dev) && strcmp(udev_device_get_devtype(dev), DEVICE_TYPE_DEVICE) == 0) { 223 | if(strcmp(udev_device_get_action(dev), DEVICE_ACTION_ADDED) == 0) { 224 | WaitForDeviceHandled(); 225 | DeviceAdded(dev); 226 | } 227 | else if(strcmp(udev_device_get_action(dev), DEVICE_ACTION_REMOVED) == 0) { 228 | WaitForDeviceHandled(); 229 | DeviceRemoved(dev); 230 | } 231 | } 232 | udev_device_unref(dev); 233 | } 234 | } 235 | 236 | // After the loop stops running, clean up all of our references and close gracefully 237 | udev_monitor_unref(mon); 238 | udev_unref(udev); 239 | uv_mutex_destroy(¬ify_mutex); 240 | uv_signal_stop(&int_signal); 241 | uv_signal_stop(&term_signal); 242 | uv_close((uv_handle_t *) &async_handler, NULL); 243 | uv_cond_destroy(¬ifyDeviceHandled); 244 | } 245 | 246 | static void cbAfter(uv_work_t *req, int status) { 247 | Stop(); 248 | } 249 | 250 | static void cbAsync(uv_async_t *handle) { 251 | if(!isRunning) { 252 | return; 253 | } 254 | 255 | if (isAdded) { 256 | NotifyAdded(currentItem); 257 | } 258 | else { 259 | NotifyRemoved(currentItem); 260 | } 261 | 262 | // Delete Item in case of removal 263 | if(isAdded == false) { 264 | delete currentItem; 265 | } 266 | 267 | SignalDeviceHandled(); 268 | } 269 | 270 | 271 | static void cbTerminate(uv_signal_t *handle, int signum) { 272 | Stop(); 273 | } 274 | 275 | 276 | static void BuildInitialDeviceList() { 277 | /* Create a list of the devices */ 278 | enumerate = udev_enumerate_new(udev); 279 | udev_enumerate_scan_devices(enumerate); 280 | devices = udev_enumerate_get_list_entry(enumerate); 281 | /* For each item enumerated, print out its information. 282 | udev_list_entry_foreach is a macro which expands to 283 | a loop. The loop will be executed for each member in 284 | devices, setting dev_list_entry to a list entry 285 | which contains the device's path in /sys. */ 286 | udev_list_entry_foreach(dev_list_entry, devices) { 287 | const char *path; 288 | 289 | /* Get the filename of the /sys entry for the device 290 | and create a udev_device object (dev) representing it */ 291 | path = udev_list_entry_get_name(dev_list_entry); 292 | dev = udev_device_new_from_syspath(udev, path); 293 | 294 | /* usb_device_get_devnode() returns the path to the device node 295 | itself in /dev. */ 296 | if(udev_device_get_devnode(dev) == NULL || udev_device_get_sysattr_value(dev,"idVendor") == NULL) { 297 | continue; 298 | } 299 | 300 | /* From here, we can call get_sysattr_value() for each file 301 | in the device's /sys entry. The strings passed into these 302 | functions (idProduct, idVendor, serial, etc.) correspond 303 | directly to the files in the /sys directory which 304 | represents the USB device. Note that USB strings are 305 | Unicode, UCS2 encoded, but the strings returned from 306 | udev_device_get_sysattr_value() are UTF-8 encoded. */ 307 | 308 | DeviceItem_t* item = new DeviceItem_t(); 309 | item->deviceParams.vendorId = strtol (udev_device_get_sysattr_value(dev,"idVendor"), NULL, 16); 310 | item->deviceParams.productId = strtol (udev_device_get_sysattr_value(dev,"idProduct"), NULL, 16); 311 | if(udev_device_get_sysattr_value(dev,"product") != NULL) { 312 | item->deviceParams.deviceName = udev_device_get_sysattr_value(dev,"product"); 313 | } 314 | if(udev_device_get_sysattr_value(dev,"manufacturer") != NULL) { 315 | item->deviceParams.manufacturer = udev_device_get_sysattr_value(dev,"manufacturer"); 316 | } 317 | if(udev_device_get_sysattr_value(dev,"serial") != NULL) { 318 | item->deviceParams.serialNumber = udev_device_get_sysattr_value(dev, "serial"); 319 | } 320 | item->deviceParams.deviceAddress = strtol(udev_device_get_sysattr_value(dev,"devnum"), NULL, 10); 321 | item->deviceParams.locationId = strtol(udev_device_get_sysattr_value(dev,"busnum"), NULL, 10); 322 | 323 | item->deviceState = DeviceState_Connect; 324 | 325 | AddItemToList((char *)udev_device_get_devnode(dev), item); 326 | 327 | udev_device_unref(dev); 328 | } 329 | /* Free the enumerator object */ 330 | udev_enumerate_unref(enumerate); 331 | } 332 | -------------------------------------------------------------------------------- /src/detection_mac.cpp: -------------------------------------------------------------------------------- 1 | #include "detection.h" 2 | #include "deviceList.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | /********************************** 17 | * Local typedefs 18 | **********************************/ 19 | typedef struct DeviceListItem { 20 | io_object_t notification; 21 | IOUSBDeviceInterface** deviceInterface; 22 | DeviceItem_t* deviceItem; 23 | } stDeviceListItem; 24 | 25 | /********************************** 26 | * Local Variables 27 | **********************************/ 28 | static ListResultItem_t* currentItem; 29 | static bool isAdded = false; 30 | static bool initialDeviceImport = true; 31 | 32 | static IONotificationPortRef gNotifyPort; 33 | static io_iterator_t gAddedIter; 34 | static CFRunLoopRef gRunLoop; 35 | 36 | static CFMutableDictionaryRef matchingDict; 37 | static CFRunLoopSourceRef runLoopSource; 38 | 39 | static uv_work_t work_req; 40 | static uv_signal_t term_signal; 41 | static uv_signal_t int_signal; 42 | 43 | static uv_async_t async_handler; 44 | static uv_mutex_t notify_mutex; 45 | static uv_cond_t notifyDeviceHandled; 46 | 47 | static bool deviceHandled = true; 48 | 49 | static bool isRunning = false; 50 | 51 | /********************************** 52 | * Local Helper Functions protoypes 53 | **********************************/ 54 | 55 | static void WaitForDeviceHandled(); 56 | static void SignalDeviceHandled(); 57 | static void cbTerminate(uv_signal_t *handle, int signum); 58 | static void cbWork(uv_work_t *req); 59 | static void cbAfter(uv_work_t *req, int status); 60 | static void cbAsync(uv_async_t *handle); 61 | 62 | 63 | /********************************** 64 | * Public Functions 65 | **********************************/ 66 | //================================================================================================ 67 | // 68 | // DeviceRemoved 69 | // 70 | // This routine will get called whenever any kIOGeneralInterest notification happens. We are 71 | // interested in the kIOMessageServiceIsTerminated message so that's what we look for. Other 72 | // messages are defined in IOMessage.h. 73 | // 74 | //================================================================================================ 75 | static void DeviceRemoved(void *refCon, io_service_t service, natural_t messageType, void *messageArgument) { 76 | kern_return_t kr; 77 | stDeviceListItem* deviceListItem = (stDeviceListItem *) refCon; 78 | DeviceItem_t* deviceItem = deviceListItem->deviceItem; 79 | 80 | if(messageType == kIOMessageServiceIsTerminated) { 81 | if(deviceListItem->deviceInterface) { 82 | kr = (*deviceListItem->deviceInterface)->Release(deviceListItem->deviceInterface); 83 | } 84 | 85 | kr = IOObjectRelease(deviceListItem->notification); 86 | 87 | 88 | ListResultItem_t* item = NULL; 89 | if(deviceItem) { 90 | item = CopyElement(&deviceItem->deviceParams); 91 | RemoveItemFromList(deviceItem); 92 | delete deviceItem; 93 | } 94 | else { 95 | item = new ListResultItem_t(); 96 | } 97 | 98 | WaitForDeviceHandled(); 99 | currentItem = item; 100 | isAdded = false; 101 | uv_async_send(&async_handler); 102 | } 103 | } 104 | 105 | //================================================================================================ 106 | // 107 | // DeviceAdded 108 | // 109 | // This routine is the callback for our IOServiceAddMatchingNotification. When we get called 110 | // we will look at all the devices that were added and we will: 111 | // 112 | // 1. Create some private data to relate to each device (in this case we use the service's name 113 | // and the location ID of the device 114 | // 2. Submit an IOServiceAddInterestNotification of type kIOGeneralInterest for this device, 115 | // using the refCon field to store a pointer to our private data. When we get called with 116 | // this interest notification, we can grab the refCon and access our private data. 117 | // 118 | //================================================================================================ 119 | static void DeviceAdded(void *refCon, io_iterator_t iterator) { 120 | kern_return_t kr; 121 | io_service_t usbDevice; 122 | IOCFPlugInInterface **plugInInterface = NULL; 123 | SInt32 score; 124 | HRESULT res; 125 | 126 | while((usbDevice = IOIteratorNext(iterator))) { 127 | io_name_t deviceName; 128 | CFStringRef deviceNameAsCFString; 129 | UInt32 locationID; 130 | UInt16 vendorId; 131 | UInt16 productId; 132 | UInt16 addr; 133 | 134 | DeviceItem_t* deviceItem = new DeviceItem_t(); 135 | 136 | // Get the USB device's name. 137 | kr = IORegistryEntryGetName(usbDevice, deviceName); 138 | if(KERN_SUCCESS != kr) { 139 | deviceName[0] = '\0'; 140 | } 141 | 142 | deviceNameAsCFString = CFStringCreateWithCString(kCFAllocatorDefault, deviceName, kCFStringEncodingASCII); 143 | 144 | 145 | if(deviceNameAsCFString) { 146 | Boolean result; 147 | char deviceName[MAXPATHLEN]; 148 | 149 | // Convert from a CFString to a C (NUL-terminated) 150 | result = CFStringGetCString(deviceNameAsCFString, 151 | deviceName, 152 | sizeof(deviceName), 153 | kCFStringEncodingUTF8); 154 | 155 | if(result) { 156 | deviceItem->deviceParams.deviceName = deviceName; 157 | } 158 | 159 | CFRelease(deviceNameAsCFString); 160 | } 161 | 162 | CFStringRef manufacturerAsCFString = (CFStringRef)IORegistryEntrySearchCFProperty( 163 | usbDevice, 164 | kIOServicePlane, 165 | CFSTR(kUSBVendorString), 166 | kCFAllocatorDefault, 167 | kIORegistryIterateRecursively 168 | ); 169 | 170 | if(manufacturerAsCFString) { 171 | Boolean result; 172 | char manufacturer[MAXPATHLEN]; 173 | 174 | // Convert from a CFString to a C (NUL-terminated) 175 | result = CFStringGetCString( 176 | manufacturerAsCFString, 177 | manufacturer, 178 | sizeof(manufacturer), 179 | kCFStringEncodingUTF8 180 | ); 181 | 182 | if(result) { 183 | deviceItem->deviceParams.manufacturer = manufacturer; 184 | } 185 | 186 | CFRelease(manufacturerAsCFString); 187 | } 188 | 189 | CFStringRef serialNumberAsCFString = (CFStringRef) IORegistryEntrySearchCFProperty( 190 | usbDevice, 191 | kIOServicePlane, 192 | CFSTR(kUSBSerialNumberString), 193 | kCFAllocatorDefault, 194 | kIORegistryIterateRecursively 195 | ); 196 | 197 | if(serialNumberAsCFString) { 198 | Boolean result; 199 | char serialNumber[MAXPATHLEN]; 200 | 201 | // Convert from a CFString to a C (NUL-terminated) 202 | result = CFStringGetCString( 203 | serialNumberAsCFString, 204 | serialNumber, 205 | sizeof(serialNumber), 206 | kCFStringEncodingUTF8 207 | ); 208 | 209 | if(result) { 210 | deviceItem->deviceParams.serialNumber = serialNumber; 211 | } 212 | 213 | CFRelease(serialNumberAsCFString); 214 | } 215 | 216 | 217 | // Now, get the locationID of this device. In order to do this, we need to create an IOUSBDeviceInterface 218 | // for our device. This will create the necessary connections between our userland application and the 219 | // kernel object for the USB Device. 220 | kr = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); 221 | 222 | if((kIOReturnSuccess != kr) || !plugInInterface) { 223 | DEBUG_LOG("IOCreatePlugInInterfaceForService returned 0x%08x.\n", kr); 224 | continue; 225 | } 226 | 227 | stDeviceListItem *deviceListItem = new stDeviceListItem(); 228 | 229 | // Use the plugin interface to retrieve the device interface. 230 | res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*) &deviceListItem->deviceInterface); 231 | 232 | // Now done with the plugin interface. 233 | (*plugInInterface)->Release(plugInInterface); 234 | 235 | if(res || deviceListItem->deviceInterface == NULL) { 236 | DEBUG_LOG("QueryInterface returned %d.\n", (int) res); 237 | continue; 238 | } 239 | 240 | // Now that we have the IOUSBDeviceInterface, we can call the routines in IOUSBLib.h. 241 | // In this case, fetch the locationID. The locationID uniquely identifies the device 242 | // and will remain the same, even across reboots, so long as the bus topology doesn't change. 243 | 244 | kr = (*deviceListItem->deviceInterface)->GetLocationID(deviceListItem->deviceInterface, &locationID); 245 | if(KERN_SUCCESS != kr) { 246 | DEBUG_LOG("GetLocationID returned 0x%08x.\n", kr); 247 | continue; 248 | } 249 | deviceItem->deviceParams.locationId = locationID; 250 | 251 | 252 | kr = (*deviceListItem->deviceInterface)->GetDeviceAddress(deviceListItem->deviceInterface, &addr); 253 | if(KERN_SUCCESS != kr) { 254 | DEBUG_LOG("GetDeviceAddress returned 0x%08x.\n", kr); 255 | continue; 256 | } 257 | deviceItem->deviceParams.deviceAddress = addr; 258 | 259 | 260 | kr = (*deviceListItem->deviceInterface)->GetDeviceVendor(deviceListItem->deviceInterface, &vendorId); 261 | if(KERN_SUCCESS != kr) { 262 | DEBUG_LOG("GetDeviceVendor returned 0x%08x.\n", kr); 263 | continue; 264 | } 265 | deviceItem->deviceParams.vendorId = vendorId; 266 | 267 | kr = (*deviceListItem->deviceInterface)->GetDeviceProduct(deviceListItem->deviceInterface, &productId); 268 | if(KERN_SUCCESS != kr) { 269 | DEBUG_LOG("GetDeviceProduct returned 0x%08x.\n", kr); 270 | continue; 271 | } 272 | deviceItem->deviceParams.productId = productId; 273 | 274 | 275 | // Extract path name as unique key 276 | io_string_t pathName; 277 | IORegistryEntryGetPath(usbDevice, kIOServicePlane, pathName); 278 | deviceNameAsCFString = CFStringCreateWithCString(kCFAllocatorDefault, pathName, kCFStringEncodingASCII); 279 | char cPathName[MAXPATHLEN]; 280 | 281 | if(deviceNameAsCFString) { 282 | Boolean result; 283 | 284 | // Convert from a CFString to a C (NUL-terminated) 285 | result = CFStringGetCString( 286 | deviceNameAsCFString, 287 | cPathName, 288 | sizeof(cPathName), 289 | kCFStringEncodingUTF8 290 | ); 291 | 292 | 293 | CFRelease(deviceNameAsCFString); 294 | } 295 | 296 | AddItemToList(cPathName, deviceItem); 297 | deviceListItem->deviceItem = deviceItem; 298 | 299 | if(initialDeviceImport == false) { 300 | WaitForDeviceHandled(); 301 | currentItem = &deviceItem->deviceParams; 302 | isAdded = true; 303 | uv_async_send(&async_handler); 304 | } 305 | 306 | // Register for an interest notification of this device being removed. Use a reference to our 307 | // private data as the refCon which will be passed to the notification callback. 308 | kr = IOServiceAddInterestNotification( 309 | gNotifyPort, // notifyPort 310 | usbDevice, // service 311 | kIOGeneralInterest, // interestType 312 | DeviceRemoved, // callback 313 | deviceListItem, // refCon 314 | &(deviceListItem->notification) // notification 315 | ); 316 | 317 | if(KERN_SUCCESS != kr) { 318 | DEBUG_LOG("IOServiceAddInterestNotification returned 0x%08x.\n", kr); 319 | } 320 | 321 | // Done with this USB device; release the reference added by IOIteratorNext 322 | kr = IOObjectRelease(usbDevice); 323 | } 324 | } 325 | 326 | void Start() { 327 | if(isRunning) { 328 | return; 329 | } 330 | 331 | isRunning = true; 332 | 333 | uv_mutex_init(¬ify_mutex); 334 | uv_async_init(uv_default_loop(), &async_handler, cbAsync); 335 | uv_signal_init(uv_default_loop(), &term_signal); 336 | uv_signal_init(uv_default_loop(), &int_signal); 337 | uv_cond_init(¬ifyDeviceHandled); 338 | 339 | uv_queue_work(uv_default_loop(), &work_req, cbWork, cbAfter); 340 | } 341 | 342 | void Stop() { 343 | if(!isRunning) { 344 | return; 345 | } 346 | 347 | isRunning = false; 348 | 349 | uv_mutex_destroy(¬ify_mutex); 350 | uv_signal_stop(&int_signal); 351 | uv_signal_stop(&term_signal); 352 | uv_close((uv_handle_t *) &async_handler, NULL); 353 | uv_cond_destroy(¬ifyDeviceHandled); 354 | 355 | if (gRunLoop) { 356 | CFRunLoopStop(gRunLoop); 357 | } 358 | } 359 | 360 | void InitDetection() { 361 | kern_return_t kr; 362 | 363 | // Set up the matching criteria for the devices we're interested in. The matching criteria needs to follow 364 | // the same rules as kernel drivers: mainly it needs to follow the USB Common Class Specification, pp. 6-7. 365 | // See also Technical Q&A QA1076 "Tips on USB driver matching on Mac OS X" 366 | // . 367 | // One exception is that you can use the matching dictionary "as is", i.e. without adding any matching 368 | // criteria to it and it will match every IOUSBDevice in the system. IOServiceAddMatchingNotification will 369 | // consume this dictionary reference, so there is no need to release it later on. 370 | 371 | // Interested in instances of class 372 | // IOUSBDevice and its subclasses 373 | matchingDict = IOServiceMatching(kIOUSBDeviceClassName); 374 | 375 | if (matchingDict == NULL) { 376 | DEBUG_LOG("IOServiceMatching returned NULL.\n"); 377 | } 378 | 379 | // Create a notification port and add its run loop event source to our run loop 380 | // This is how async notifications get set up. 381 | 382 | gNotifyPort = IONotificationPortCreate(kIOMasterPortDefault); 383 | 384 | // Now set up a notification to be called when a device is first matched by I/O Kit. 385 | kr = IOServiceAddMatchingNotification( 386 | gNotifyPort, // notifyPort 387 | kIOFirstMatchNotification, // notificationType 388 | matchingDict, // matching 389 | DeviceAdded, // callback 390 | NULL, // refCon 391 | &gAddedIter // notification 392 | ); 393 | 394 | if (KERN_SUCCESS != kr) { 395 | DEBUG_LOG("IOServiceAddMatchingNotification returned 0x%08x.\n", kr); 396 | } 397 | 398 | // Iterate once to get already-present devices and arm the notification 399 | DeviceAdded(NULL, gAddedIter); 400 | initialDeviceImport = false; 401 | } 402 | 403 | void EIO_Find(uv_work_t* req) { 404 | ListBaton* data = static_cast(req->data); 405 | 406 | CreateFilteredList(&data->results, data->vid, data->pid); 407 | } 408 | 409 | static void WaitForDeviceHandled() { 410 | uv_mutex_lock(¬ify_mutex); 411 | if(deviceHandled == false) { 412 | uv_cond_wait(¬ifyDeviceHandled, ¬ify_mutex); 413 | } 414 | deviceHandled = false; 415 | uv_mutex_unlock(¬ify_mutex); 416 | } 417 | 418 | static void SignalDeviceHandled() { 419 | uv_mutex_lock(¬ify_mutex); 420 | deviceHandled = true; 421 | uv_cond_signal(¬ifyDeviceHandled); 422 | uv_mutex_unlock(¬ify_mutex); 423 | } 424 | 425 | static void cbWork(uv_work_t *req) { 426 | // We have this check in case we `Stop` before this thread starts, 427 | // otherwise the process will hang 428 | if(!isRunning) { 429 | return; 430 | } 431 | 432 | uv_signal_start(&int_signal, cbTerminate, SIGINT); 433 | uv_signal_start(&term_signal, cbTerminate, SIGTERM); 434 | 435 | runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort); 436 | 437 | gRunLoop = CFRunLoopGetCurrent(); 438 | CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode); 439 | 440 | // Creating `gRunLoop` can take some cycles so we also need this second 441 | // `isRunning` check here because it happens at a future time 442 | if(isRunning) { 443 | // Start the run loop. Now we'll receive notifications. 444 | CFRunLoopRun(); 445 | } 446 | 447 | // The `CFRunLoopRun` is a blocking call so we also need this second 448 | // `isRunning` check here because it happens at a future time 449 | if(isRunning) { 450 | // We should never get here while running 451 | DEBUG_LOG("Unexpectedly back from CFRunLoopRun()!\n"); 452 | } 453 | } 454 | 455 | static void cbAfter(uv_work_t *req, int status) { 456 | Stop(); 457 | } 458 | 459 | static void cbAsync(uv_async_t *handle) { 460 | if(!isRunning) { 461 | return; 462 | } 463 | 464 | if(isAdded) { 465 | NotifyAdded(currentItem); 466 | } 467 | else { 468 | NotifyRemoved(currentItem); 469 | } 470 | 471 | // Delete Item in case of removal 472 | if(isAdded == false) { 473 | delete currentItem; 474 | } 475 | 476 | SignalDeviceHandled(); 477 | } 478 | 479 | 480 | static void cbTerminate(uv_signal_t *handle, int signum) { 481 | Stop(); 482 | } 483 | -------------------------------------------------------------------------------- /src/detection_win.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Include Windows headers 10 | #include 11 | // Include `CM_DEVCAP_UNIQUEID` 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "detection.h" 19 | #include "deviceList.h" 20 | 21 | using namespace std; 22 | 23 | typedef std::basic_string tstring; 24 | 25 | /********************************** 26 | * Local defines 27 | **********************************/ 28 | #define VID_TAG "VID_" 29 | #define PID_TAG "PID_" 30 | 31 | #define LIBRARY_NAME ("setupapi.dll") 32 | 33 | 34 | #define DllImport __declspec(dllimport) 35 | 36 | #define MAX_THREAD_WINDOW_NAME 64 37 | 38 | /********************************** 39 | * Local typedefs 40 | **********************************/ 41 | 42 | 43 | 44 | /********************************** 45 | * Local Variables 46 | **********************************/ 47 | GUID GUID_DEVINTERFACE_USB_DEVICE = { 48 | 0xA5DCBF10L, 49 | 0x6530, 50 | 0x11D2, 51 | 0x90, 52 | 0x1F, 53 | 0x00, 54 | 0xC0, 55 | 0x4F, 56 | 0xB9, 57 | 0x51, 58 | 0xED 59 | }; 60 | 61 | DWORD threadId; 62 | HANDLE threadHandle; 63 | 64 | HANDLE deviceChangedRegisteredEvent; 65 | HANDLE deviceChangedSentEvent; 66 | 67 | uv_signal_t term_signal; 68 | uv_signal_t int_signal; 69 | 70 | ListResultItem_t* currentDevice; 71 | bool isAdded; 72 | bool isRunning = false; 73 | 74 | HINSTANCE hinstLib; 75 | 76 | 77 | typedef BOOL (WINAPI *_SetupDiEnumDeviceInfo) (HDEVINFO DeviceInfoSet, DWORD MemberIndex, PSP_DEVINFO_DATA DeviceInfoData); 78 | typedef HDEVINFO (WINAPI *_SetupDiGetClassDevs) (const GUID *ClassGuid, PCTSTR Enumerator, HWND hwndParent, DWORD Flags); 79 | typedef BOOL (WINAPI *_SetupDiDestroyDeviceInfoList) (HDEVINFO DeviceInfoSet); 80 | typedef BOOL (WINAPI *_SetupDiGetDeviceInstanceId) (HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, PTSTR DeviceInstanceId, DWORD DeviceInstanceIdSize, PDWORD RequiredSize); 81 | typedef BOOL (WINAPI *_SetupDiGetDeviceRegistryProperty) (HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize); 82 | 83 | 84 | _SetupDiEnumDeviceInfo DllSetupDiEnumDeviceInfo; 85 | _SetupDiGetClassDevs DllSetupDiGetClassDevs; 86 | _SetupDiDestroyDeviceInfoList DllSetupDiDestroyDeviceInfoList; 87 | _SetupDiGetDeviceInstanceId DllSetupDiGetDeviceInstanceId; 88 | _SetupDiGetDeviceRegistryProperty DllSetupDiGetDeviceRegistryProperty; 89 | 90 | 91 | /********************************** 92 | * Local Helper Functions protoypes 93 | **********************************/ 94 | void UpdateDevice(PDEV_BROADCAST_DEVICEINTERFACE pDevInf, WPARAM wParam, DeviceState_t state); 95 | std::string Utf8Encode(const std::string &str); 96 | DWORD WINAPI ListenerThread(LPVOID lpParam); 97 | 98 | void BuildInitialDeviceList(); 99 | 100 | void cbWork(uv_work_t* req); 101 | void cbAfter(uv_work_t* req); 102 | void cbTerminate(uv_signal_t *handle, int signum); 103 | 104 | void ExtractDeviceInfo(HDEVINFO hDevInfo, SP_DEVINFO_DATA* pspDevInfoData, TCHAR* buf, DWORD buffSize, ListResultItem_t* resultItem); 105 | bool CheckValidity(ListResultItem_t* item); 106 | 107 | 108 | /********************************** 109 | * Public Functions 110 | **********************************/ 111 | void cbWork(uv_work_t* req) { 112 | // We have this check in case we `Stop` before this thread starts, 113 | // otherwise the process will hang 114 | if(!isRunning) { 115 | return; 116 | } 117 | 118 | uv_signal_start(&int_signal, cbTerminate, SIGINT); 119 | uv_signal_start(&term_signal, cbTerminate, SIGTERM); 120 | 121 | WaitForSingleObject(deviceChangedRegisteredEvent, INFINITE); 122 | } 123 | 124 | 125 | void cbAfter(uv_work_t* req) { 126 | if(!isRunning) { 127 | return; 128 | } 129 | 130 | if(isAdded) { 131 | NotifyAdded(currentDevice); 132 | } 133 | else { 134 | NotifyRemoved(currentDevice); 135 | } 136 | 137 | // Delete Item in case of removal 138 | if(!isAdded) { 139 | delete currentDevice; 140 | } 141 | 142 | SetEvent(deviceChangedSentEvent); 143 | 144 | currentDevice = NULL; 145 | 146 | uv_queue_work(uv_default_loop(), req, cbWork, (uv_after_work_cb)cbAfter); 147 | } 148 | 149 | void cbTerminate(uv_signal_t *handle, int signum) { 150 | Stop(); 151 | } 152 | 153 | void LoadFunctions() { 154 | 155 | bool success; 156 | 157 | hinstLib = LoadLibrary(LIBRARY_NAME); 158 | 159 | if (hinstLib != NULL) { 160 | DllSetupDiEnumDeviceInfo = (_SetupDiEnumDeviceInfo) GetProcAddress(hinstLib, "SetupDiEnumDeviceInfo"); 161 | 162 | DllSetupDiGetClassDevs = (_SetupDiGetClassDevs) GetProcAddress(hinstLib, "SetupDiGetClassDevsA"); 163 | 164 | DllSetupDiDestroyDeviceInfoList = (_SetupDiDestroyDeviceInfoList) GetProcAddress(hinstLib, "SetupDiDestroyDeviceInfoList"); 165 | 166 | DllSetupDiGetDeviceInstanceId = (_SetupDiGetDeviceInstanceId) GetProcAddress(hinstLib, "SetupDiGetDeviceInstanceIdA"); 167 | 168 | DllSetupDiGetDeviceRegistryProperty = (_SetupDiGetDeviceRegistryProperty) GetProcAddress(hinstLib, "SetupDiGetDeviceRegistryPropertyA"); 169 | 170 | success = ( 171 | DllSetupDiEnumDeviceInfo != NULL && 172 | DllSetupDiGetClassDevs != NULL && 173 | DllSetupDiDestroyDeviceInfoList != NULL && 174 | DllSetupDiGetDeviceInstanceId != NULL && 175 | DllSetupDiGetDeviceRegistryProperty != NULL 176 | ); 177 | } 178 | else { 179 | success = false; 180 | } 181 | 182 | if(!success) { 183 | printf("Could not load library functions from dll -> abort (Check if %s is available)\r\n", LIBRARY_NAME); 184 | exit(1); 185 | } 186 | } 187 | 188 | void Start() { 189 | if(isRunning) { 190 | return; 191 | } 192 | 193 | isRunning = true; 194 | 195 | // Start listening for the Windows API events 196 | threadHandle = CreateThread( 197 | NULL, // default security attributes 198 | 0, // use default stack size 199 | ListenerThread, // thread function name 200 | NULL, // argument to thread function 201 | 0, // use default creation flags 202 | &threadId 203 | ); 204 | 205 | uv_signal_init(uv_default_loop(), &term_signal); 206 | uv_signal_init(uv_default_loop(), &int_signal); 207 | 208 | uv_work_t* req = new uv_work_t(); 209 | uv_queue_work(uv_default_loop(), req, cbWork, (uv_after_work_cb)cbAfter); 210 | } 211 | 212 | void Stop() { 213 | if(!isRunning) { 214 | return; 215 | } 216 | 217 | isRunning = false; 218 | 219 | uv_signal_stop(&int_signal); 220 | uv_signal_stop(&term_signal); 221 | 222 | SetEvent(deviceChangedRegisteredEvent); 223 | } 224 | 225 | void InitDetection() { 226 | LoadFunctions(); 227 | 228 | deviceChangedRegisteredEvent = CreateEvent( 229 | NULL, 230 | false, // auto-reset event 231 | false, // non-signalled state 232 | "" 233 | ); 234 | deviceChangedSentEvent = CreateEvent( 235 | NULL, 236 | false, // auto-reset event 237 | true, // non-signalled state 238 | "" 239 | ); 240 | 241 | BuildInitialDeviceList(); 242 | } 243 | 244 | 245 | void EIO_Find(uv_work_t* req) { 246 | 247 | ListBaton* data = static_cast(req->data); 248 | 249 | CreateFilteredList(&data->results, data->vid, data->pid); 250 | } 251 | 252 | 253 | /********************************** 254 | * Local Functions 255 | **********************************/ 256 | void ToUpper(char * buf) { 257 | char* c = buf; 258 | while (*c != '\0') { 259 | *c = toupper((unsigned char)*c); 260 | c++; 261 | } 262 | } 263 | 264 | void NormalizeSlashes(char* buf) { 265 | char* c = buf; 266 | while (*c != '\0') { 267 | if(*c == '/') 268 | *c = '\\'; 269 | c++; 270 | } 271 | } 272 | 273 | void extractVidPid(char * buf, ListResultItem_t * item) { 274 | if(buf == NULL) { 275 | return; 276 | } 277 | 278 | ToUpper(buf); 279 | 280 | char* string; 281 | char* temp; 282 | char* pidStr, *vidStr; 283 | int vid = 0; 284 | int pid = 0; 285 | 286 | string = new char[strlen(buf) + 1]; 287 | memcpy(string, buf, strlen(buf) + 1); 288 | 289 | vidStr = strstr(string, VID_TAG); 290 | pidStr = strstr(string, PID_TAG); 291 | 292 | if(vidStr != NULL) { 293 | temp = (char*) (vidStr + strlen(VID_TAG)); 294 | temp[4] = '\0'; 295 | vid = strtol (temp, NULL, 16); 296 | } 297 | 298 | if(pidStr != NULL) { 299 | temp = (char*) (pidStr + strlen(PID_TAG)); 300 | temp[4] = '\0'; 301 | pid = strtol (temp, NULL, 16); 302 | } 303 | item->vendorId = vid; 304 | item->productId = pid; 305 | 306 | delete string; 307 | } 308 | 309 | 310 | LRESULT CALLBACK DetectCallback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 311 | if (msg == WM_DEVICECHANGE) { 312 | if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) { 313 | PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam; 314 | PDEV_BROADCAST_DEVICEINTERFACE pDevInf; 315 | 316 | if(pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { 317 | pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr; 318 | UpdateDevice(pDevInf, wParam, (DBT_DEVICEARRIVAL == wParam) ? DeviceState_Connect : DeviceState_Disconnect); 319 | } 320 | } 321 | } 322 | 323 | return 1; 324 | } 325 | 326 | 327 | DWORD WINAPI ListenerThread( LPVOID lpParam ) { 328 | char className[MAX_THREAD_WINDOW_NAME]; 329 | _snprintf_s(className, MAX_THREAD_WINDOW_NAME, "ListnerThreadUsbDetection_%d", GetCurrentThreadId()); 330 | 331 | WNDCLASSA wincl = {0}; 332 | wincl.hInstance = GetModuleHandle(0); 333 | wincl.lpszClassName = className; 334 | wincl.lpfnWndProc = DetectCallback; 335 | 336 | if (!RegisterClassA(&wincl)) { 337 | DWORD le = GetLastError(); 338 | printf("RegisterClassA() failed [Error: %x]\r\n", le); 339 | return 1; 340 | } 341 | 342 | 343 | HWND hwnd = CreateWindowExA(WS_EX_TOPMOST, className, className, 0, 0, 0, 0, 0, NULL, 0, 0, 0); 344 | if (!hwnd) { 345 | DWORD le = GetLastError(); 346 | printf("CreateWindowExA() failed [Error: %x]\r\n", le); 347 | return 1; 348 | } 349 | 350 | DEV_BROADCAST_DEVICEINTERFACE_A notifyFilter = {0}; 351 | notifyFilter.dbcc_size = sizeof(notifyFilter); 352 | notifyFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; 353 | notifyFilter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE; 354 | 355 | HDEVNOTIFY hDevNotify = RegisterDeviceNotificationA(hwnd, ¬ifyFilter, DEVICE_NOTIFY_WINDOW_HANDLE); 356 | if (!hDevNotify) { 357 | DWORD le = GetLastError(); 358 | printf("RegisterDeviceNotificationA() failed [Error: %x]\r\n", le); 359 | return 1; 360 | } 361 | 362 | MSG msg; 363 | while(TRUE) { 364 | BOOL bRet = GetMessage(&msg, hwnd, 0, 0); 365 | if ((bRet == 0) || (bRet == -1)) { 366 | break; 367 | } 368 | 369 | TranslateMessage(&msg); 370 | DispatchMessage(&msg); 371 | } 372 | 373 | return 0; 374 | } 375 | 376 | 377 | void BuildInitialDeviceList() { 378 | DWORD dwFlag = (DIGCF_ALLCLASSES | DIGCF_PRESENT); 379 | HDEVINFO hDevInfo = DllSetupDiGetClassDevs(NULL, "USB", NULL, dwFlag); 380 | 381 | if(INVALID_HANDLE_VALUE == hDevInfo) { 382 | return; 383 | } 384 | 385 | SP_DEVINFO_DATA* pspDevInfoData = (SP_DEVINFO_DATA*) HeapAlloc(GetProcessHeap(), 0, sizeof(SP_DEVINFO_DATA)); 386 | if (pspDevInfoData) { 387 | pspDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA); 388 | for(int i=0; DllSetupDiEnumDeviceInfo(hDevInfo, i, pspDevInfoData); i++) { 389 | DWORD nSize=0 ; 390 | TCHAR buf[MAX_PATH]; 391 | 392 | if (!DllSetupDiGetDeviceInstanceId(hDevInfo, pspDevInfoData, buf, sizeof(buf), &nSize)) { 393 | break; 394 | } 395 | NormalizeSlashes(buf); 396 | 397 | DeviceItem_t* item = new DeviceItem_t(); 398 | item->deviceState = DeviceState_Connect; 399 | 400 | DWORD DataT; 401 | DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_LOCATION_INFORMATION, &DataT, (PBYTE)buf, MAX_PATH, &nSize); 402 | DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_HARDWAREID, &DataT, (PBYTE)(buf + nSize - 1), MAX_PATH - nSize, &nSize); 403 | 404 | AddItemToList(buf, item); 405 | ExtractDeviceInfo(hDevInfo, pspDevInfoData, buf, MAX_PATH, &item->deviceParams); 406 | } 407 | 408 | HeapFree(GetProcessHeap(), 0, pspDevInfoData); 409 | } 410 | 411 | if(hDevInfo) { 412 | DllSetupDiDestroyDeviceInfoList(hDevInfo); 413 | } 414 | } 415 | 416 | 417 | void ExtractDeviceInfo(HDEVINFO hDevInfo, SP_DEVINFO_DATA* pspDevInfoData, TCHAR* buf, DWORD buffSize, ListResultItem_t* resultItem) { 418 | 419 | DWORD DataT; 420 | DWORD nSize; 421 | static int dummy = 1; 422 | 423 | resultItem->locationId = 0; 424 | resultItem->deviceAddress = dummy++; 425 | 426 | // device found 427 | if (DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_FRIENDLYNAME, &DataT, (PBYTE)buf, buffSize, &nSize)) { 428 | resultItem->deviceName = Utf8Encode(buf); 429 | } 430 | else if ( DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_DEVICEDESC, &DataT, (PBYTE)buf, buffSize, &nSize)) 431 | { 432 | resultItem->deviceName = Utf8Encode(buf); 433 | } 434 | if (DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_MFG, &DataT, (PBYTE)buf, buffSize, &nSize)) { 435 | resultItem->manufacturer = Utf8Encode(buf); 436 | } 437 | if (DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_HARDWAREID, &DataT, (PBYTE)buf, buffSize, &nSize)) { 438 | // Use this to extract VID / PID 439 | extractVidPid(buf, resultItem); 440 | } 441 | 442 | // Extract Serial Number 443 | // 444 | // Format: \ 445 | // 446 | // Ex. `USB\VID_2109&PID_8110\5&376ABA2D&0&21` 447 | // - ``: `USB\VID_2109&PID_8110` 448 | // - ``: `5&376ABA2D&0&21` 449 | // 450 | // [Device instance IDs](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/device-instance-ids) -> 451 | // - [Device IDs](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/device-ids) -> [Hardware IDs](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/hardware-ids) -> [Device identifier formats](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/device-identifier-formats) -> [Identifiers for USB devices](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-usb-devices) 452 | // - [Standard USB Identifiers](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/standard-usb-identifiers) 453 | // - [Special USB Identifiers](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/special-usb-identifiers) 454 | // - [Instance specific ID](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/instance-ids) 455 | DWORD dwCapabilities = 0x0; 456 | if (DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_CAPABILITIES, &DataT, (PBYTE)&dwCapabilities, sizeof(dwCapabilities), &nSize)) { 457 | if ((dwCapabilities & CM_DEVCAP_UNIQUEID) == CM_DEVCAP_UNIQUEID) { 458 | if (DllSetupDiGetDeviceInstanceId(hDevInfo, pspDevInfoData, buf, buffSize, &nSize)) { 459 | string deviceInstanceId = buf; 460 | size_t serialNumberIndex = deviceInstanceId.find_last_of("\\"); 461 | if (serialNumberIndex != string::npos) { 462 | resultItem->serialNumber = deviceInstanceId.substr(serialNumberIndex + 1); 463 | } 464 | } 465 | } 466 | } 467 | } 468 | 469 | 470 | void UpdateDevice(PDEV_BROADCAST_DEVICEINTERFACE pDevInf, WPARAM wParam, DeviceState_t state) { 471 | // dbcc_name: 472 | // \\?\USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed} 473 | // convert to 474 | // USB\Vid_04e8&Pid_503b\0002F9A9828E0F06 475 | tstring szDevId = pDevInf->dbcc_name+4; 476 | auto idx = szDevId.rfind(_T('#')); 477 | 478 | if (idx != tstring::npos) szDevId.resize(idx); 479 | std::replace(begin(szDevId), end(szDevId), _T('#'), _T('\\')); 480 | auto to_upper = [] (TCHAR ch) { return std::use_facet>(std::locale()).toupper(ch); }; 481 | transform(begin(szDevId), end(szDevId), begin(szDevId), to_upper); 482 | 483 | tstring szClass; 484 | idx = szDevId.find(_T('\\')); 485 | if (idx != tstring::npos) szClass = szDevId.substr(0, idx); 486 | // if we are adding device, we only need present devices 487 | // otherwise, we need all devices 488 | DWORD dwFlag = DBT_DEVICEARRIVAL != wParam ? DIGCF_ALLCLASSES : (DIGCF_ALLCLASSES | DIGCF_PRESENT); 489 | HDEVINFO hDevInfo = DllSetupDiGetClassDevs(NULL, szClass.c_str(), NULL, dwFlag); 490 | if(INVALID_HANDLE_VALUE == hDevInfo) { 491 | return; 492 | } 493 | 494 | SP_DEVINFO_DATA* pspDevInfoData = (SP_DEVINFO_DATA*) HeapAlloc(GetProcessHeap(), 0, sizeof(SP_DEVINFO_DATA)); 495 | if (pspDevInfoData) { 496 | pspDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA); 497 | for (int i = 0; DllSetupDiEnumDeviceInfo(hDevInfo, i, pspDevInfoData); i++) { 498 | DWORD nSize = 0; 499 | TCHAR buf[MAX_PATH]; 500 | 501 | if (!DllSetupDiGetDeviceInstanceId(hDevInfo, pspDevInfoData, buf, sizeof(buf), &nSize)) { 502 | break; 503 | } 504 | NormalizeSlashes(buf); 505 | 506 | if (szDevId == buf) { 507 | WaitForSingleObject(deviceChangedSentEvent, INFINITE); 508 | 509 | DWORD DataT; 510 | DWORD nSize; 511 | DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_LOCATION_INFORMATION, &DataT, (PBYTE) buf, MAX_PATH, &nSize); 512 | DllSetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData, SPDRP_HARDWAREID, &DataT, (PBYTE)(buf + nSize - 1), MAX_PATH - nSize, &nSize); 513 | 514 | if (state == DeviceState_Connect) { 515 | DeviceItem_t *device = new DeviceItem_t(); 516 | 517 | AddItemToList(buf, device); 518 | ExtractDeviceInfo(hDevInfo, pspDevInfoData, buf, MAX_PATH, &device->deviceParams); 519 | 520 | currentDevice = &device->deviceParams; 521 | isAdded = true; 522 | } else { 523 | 524 | ListResultItem_t *item = NULL; 525 | if (IsItemAlreadyStored(buf)) { 526 | DeviceItem_t *deviceItem = GetItemFromList(buf); 527 | if (deviceItem) { 528 | item = CopyElement(&deviceItem->deviceParams); 529 | } 530 | RemoveItemFromList(deviceItem); 531 | delete deviceItem; 532 | } 533 | 534 | if (item == NULL) { 535 | item = new ListResultItem_t(); 536 | ExtractDeviceInfo(hDevInfo, pspDevInfoData, buf, MAX_PATH, item); 537 | } 538 | currentDevice = item; 539 | isAdded = false; 540 | } 541 | 542 | break; 543 | } 544 | } 545 | 546 | HeapFree(GetProcessHeap(), 0, pspDevInfoData); 547 | } 548 | 549 | if(hDevInfo) { 550 | DllSetupDiDestroyDeviceInfoList(hDevInfo); 551 | } 552 | 553 | SetEvent(deviceChangedRegisteredEvent); 554 | } 555 | 556 | std::string Utf8Encode(const std::string &str) 557 | { 558 | if (str.empty()) { 559 | return std::string(); 560 | } 561 | 562 | //System default code page to wide character 563 | int wstr_size = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0); 564 | std::wstring wstr_tmp(wstr_size, 0); 565 | MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, &wstr_tmp[0], wstr_size); 566 | 567 | //Wide character to Utf8 568 | int str_size = WideCharToMultiByte(CP_UTF8, 0, &wstr_tmp[0], (int)wstr_tmp.size(), NULL, 0, NULL, NULL); 569 | std::string str_utf8(str_size, 0); 570 | WideCharToMultiByte(CP_UTF8, 0, &wstr_tmp[0], (int)wstr_tmp.size(), &str_utf8[0], str_size, NULL, NULL); 571 | 572 | return str_utf8; 573 | } 574 | -------------------------------------------------------------------------------- /src/deviceList.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "deviceList.h" 6 | 7 | 8 | using namespace std; 9 | 10 | map deviceMap; 11 | 12 | void AddItemToList(char* key, DeviceItem_t * item) { 13 | item->SetKey(key); 14 | deviceMap.insert(pair(item->GetKey(), item)); 15 | } 16 | 17 | void RemoveItemFromList(DeviceItem_t* item) { 18 | deviceMap.erase(item->GetKey()); 19 | } 20 | 21 | DeviceItem_t* GetItemFromList(char* key) { 22 | map::iterator it; 23 | 24 | it = deviceMap.find(key); 25 | if(it == deviceMap.end()) { 26 | return NULL; 27 | } 28 | else { 29 | return it->second; 30 | } 31 | } 32 | 33 | bool IsItemAlreadyStored(char* key) { 34 | map::iterator it; 35 | 36 | it = deviceMap.find(key); 37 | if(it == deviceMap.end()) { 38 | return false; 39 | } 40 | else { 41 | return true; 42 | } 43 | 44 | return true; 45 | } 46 | 47 | ListResultItem_t* CopyElement(ListResultItem_t* item) { 48 | ListResultItem_t* dst = new ListResultItem_t(); 49 | dst->locationId = item->locationId; 50 | dst->vendorId = item->vendorId; 51 | dst->productId = item->productId; 52 | dst->deviceName = item->deviceName; 53 | dst->manufacturer = item->manufacturer; 54 | dst->serialNumber = item->serialNumber; 55 | dst->deviceAddress = item->deviceAddress; 56 | 57 | return dst; 58 | } 59 | 60 | void CreateFilteredList(list *filteredList, int vid, int pid) { 61 | map::iterator it; 62 | 63 | for (it = deviceMap.begin(); it != deviceMap.end(); ++it) { 64 | DeviceItem_t* item = it->second; 65 | 66 | if ( 67 | (( vid != 0 && pid != 0) && (vid == item->deviceParams.vendorId && pid == item->deviceParams.productId)) 68 | || ((vid != 0 && pid == 0) && vid == item->deviceParams.vendorId) 69 | || (vid == 0 && pid == 0) 70 | ) { 71 | (*filteredList).push_back(CopyElement(&item->deviceParams)); 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/deviceList.h: -------------------------------------------------------------------------------- 1 | #ifndef _DEVICE_LIST_H 2 | #define _DEVICE_LIST_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | public: 9 | int locationId; 10 | int vendorId; 11 | int productId; 12 | std::string deviceName; 13 | std::string manufacturer; 14 | std::string serialNumber; 15 | int deviceAddress; 16 | } ListResultItem_t; 17 | 18 | typedef enum _DeviceState_t { 19 | DeviceState_Connect, 20 | DeviceState_Disconnect, 21 | } DeviceState_t; 22 | 23 | typedef struct _DeviceItem_t { 24 | ListResultItem_t deviceParams; 25 | DeviceState_t deviceState; 26 | 27 | private: 28 | char* key; 29 | 30 | 31 | public: 32 | _DeviceItem_t() { 33 | key = NULL; 34 | } 35 | 36 | ~_DeviceItem_t() { 37 | if(this->key != NULL) { 38 | delete this->key; 39 | } 40 | } 41 | 42 | void SetKey(char* key) { 43 | if(this->key != NULL) { 44 | delete this->key; 45 | } 46 | this->key = new char[strlen(key) + 1]; 47 | memcpy(this->key, key, strlen(key) + 1); 48 | } 49 | 50 | char* GetKey() { 51 | return this->key; 52 | } 53 | } DeviceItem_t; 54 | 55 | 56 | void AddItemToList(char* key, DeviceItem_t * item); 57 | void RemoveItemFromList(DeviceItem_t* item); 58 | bool IsItemAlreadyStored(char* identifier); 59 | DeviceItem_t* GetItemFromList(char* key); 60 | ListResultItem_t* CopyElement(ListResultItem_t* item); 61 | void CreateFilteredList(std::list* filteredList, int vid, int pid); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true, 5 | "jasmine": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2018 9 | }, 10 | "rules": { 11 | "no-console": 0, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/dev-helper-test.js: -------------------------------------------------------------------------------- 1 | // This is just used to debug the package while you develop things 2 | 3 | var usbDetect = require('../'); 4 | 5 | /* */ 6 | console.log('startMonitoring'); 7 | usbDetect.startMonitoring(); 8 | /* */ 9 | 10 | /* */ 11 | usbDetect.find() 12 | .then(function(devices) { 13 | console.log('find', devices.length, devices.sort((a, b) => { 14 | if(a.vendorId !== b.vendorId) { 15 | return a.vendorId - b.vendorId; 16 | } 17 | else if(a.productId !== b.productId) { 18 | return a.productId - b.productId; 19 | } 20 | 21 | return a.deviceName.localeCompare(b.deviceName); 22 | })); 23 | }); 24 | 25 | 26 | usbDetect.on('add', function(device) { 27 | console.log('add', device); 28 | 29 | usbDetect.find() 30 | .then(function(devices) { 31 | console.log('find', devices.length, devices); 32 | }); 33 | }); 34 | 35 | usbDetect.on('remove', function(device) { 36 | console.log('remove', device); 37 | }); 38 | 39 | usbDetect.on('change', function(device) { 40 | console.log('change', device); 41 | }); 42 | /* */ 43 | 44 | /* * / 45 | setTimeout(() => { 46 | console.log('stopMonitoring'); 47 | usbDetect.stopMonitoring(); 48 | }, 1000); 49 | /* */ 50 | 51 | /* * / 52 | //console.log('stopMonitoring'); 53 | usbDetect.stopMonitoring(); 54 | /* */ 55 | 56 | 57 | /* * / 58 | console.log('startMonitoring0'); 59 | usbDetect.startMonitoring(); 60 | 61 | var addIndex = 0; 62 | usbDetect.on('add', function(device) { 63 | console.log('add', addIndex); 64 | 65 | if(addIndex === 0) { 66 | console.log('stopMonitoring0 (after add)'); 67 | usbDetect.stopMonitoring(); 68 | 69 | setTimeout(function() { 70 | console.log('startMonitoring1'); 71 | usbDetect.startMonitoring(); 72 | }, 250); 73 | } 74 | else if(addIndex === 1) { 75 | console.log('stopMonitoring1 (after add)'); 76 | usbDetect.stopMonitoring(); 77 | } 78 | 79 | addIndex++; 80 | }); 81 | /* */ 82 | -------------------------------------------------------------------------------- /test/fixtures/requiring-exit-gracefully.js: -------------------------------------------------------------------------------- 1 | var usbDetect = require('../../'); 2 | -------------------------------------------------------------------------------- /test/fixtures/sigint-after-start-monitoring-exit-gracefully.js: -------------------------------------------------------------------------------- 1 | var usbDetect = require('../../'); 2 | 3 | usbDetect.startMonitoring(); 4 | -------------------------------------------------------------------------------- /test/fixtures/start-delayed-stop-monitoring-exit-gracefully.js: -------------------------------------------------------------------------------- 1 | var usbDetect = require('../../'); 2 | 3 | usbDetect.startMonitoring(); 4 | 5 | setTimeout(function() { 6 | usbDetect.stopMonitoring(); 7 | }, 100); 8 | -------------------------------------------------------------------------------- /test/fixtures/start-stop-monitoring-exit-gracefully.js: -------------------------------------------------------------------------------- 1 | var usbDetect = require('../../'); 2 | 3 | usbDetect.startMonitoring(); 4 | 5 | usbDetect.stopMonitoring(); 6 | -------------------------------------------------------------------------------- /test/lib/child-executor.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn; 2 | 3 | class ChildExecutor { 4 | constructor() { 5 | this.child = null; 6 | } 7 | 8 | exec(command) { 9 | var commandParts = command.split(' '); 10 | return new Promise((resolve, reject) => { 11 | var stdout = ''; 12 | var stderr = ''; 13 | 14 | // We use `child` so we can manually send a kill 15 | // ex. 16 | // var executor = new ChildExecutor(); 17 | // executor.exec('SOMECOMMAND').then((resultInfo) => { console.log('resultInfo'); }); 18 | // executor.child.kill('SIGINT'); 19 | // 20 | // We have to use `spawn` instead of `exec` because you can't kill it 21 | this.child = spawn(commandParts[0], commandParts.slice(1)); 22 | 23 | this.child.stdout.on('data', (data) => { 24 | stdout += data; 25 | }); 26 | 27 | this.child.stderr.on('data', (data) => { 28 | stderr += data; 29 | }); 30 | 31 | this.child.on('close', (code) => { 32 | resolve({ 33 | command: command, 34 | stdout: stdout, 35 | stderr: stderr 36 | }); 37 | }); 38 | 39 | this.child.on('error', (err) => { 40 | reject({ 41 | command: command, 42 | stdout: stdout, 43 | stderr: stderr, 44 | error: err 45 | }); 46 | }); 47 | 48 | //this.child.stdout.pipe(process.stdout); 49 | //this.child.stderr.pipe(process.stderr); 50 | }); 51 | } 52 | } 53 | 54 | module.exports = ChildExecutor; 55 | -------------------------------------------------------------------------------- /test/lib/command-runner.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | 3 | var commandRunner = function(command) { 4 | return new Promise(function(resolve, reject) { 5 | var child = exec(command, function(err, stdout, stderr) { 6 | var resultInfo = { 7 | command: command, 8 | stdout: stdout, 9 | stderr: stderr, 10 | error: err 11 | }; 12 | 13 | if(err) { 14 | reject(resultInfo); 15 | } 16 | else { 17 | resolve(resultInfo); 18 | } 19 | }); 20 | 21 | //child.stdout.pipe(process.stdout); 22 | //child.stderr.pipe(process.stderr); 23 | }); 24 | }; 25 | 26 | module.exports = commandRunner; 27 | -------------------------------------------------------------------------------- /test/lib/set-timeout-promise-helper.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(time = 0) { 3 | return new Promise((resolve) => { 4 | setTimeout(resolve, time); 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var chai = require('chai'); 4 | var expect = require('chai').expect; 5 | var chalk = require('chalk'); 6 | var commandRunner = require('./lib/command-runner'); 7 | var ChildExecutor = require('./lib/child-executor'); 8 | var getSetTimeoutPromise = require('./lib/set-timeout-promise-helper'); 9 | 10 | // The plugin to test 11 | var usbDetect = require('../'); 12 | 13 | const MANUAL_INTERACTION_TIMEOUT = 10000; 14 | 15 | // We just look at the keys of this device object 16 | var DEVICE_OBJECT_FIXTURE = { 17 | locationId: 0, 18 | vendorId: 5824, 19 | productId: 1155, 20 | deviceName: 'Teensy USB Serial (COM3)', 21 | manufacturer: 'PJRC.COM, LLC.', 22 | serialNumber: '', 23 | deviceAddress: 11 24 | }; 25 | 26 | function once(eventName) { 27 | return new Promise(function(resolve) { 28 | usbDetect.on(eventName, function(device) { 29 | resolve(device); 30 | }); 31 | }); 32 | } 33 | 34 | function testDeviceShape(device) { 35 | expect(device) 36 | .to.have.all.keys(DEVICE_OBJECT_FIXTURE) 37 | .that.is.an('object'); 38 | } 39 | 40 | describe('usb-detection', function() { 41 | describe('API', function() { 42 | beforeAll(function() { 43 | usbDetect.startMonitoring(); 44 | }); 45 | 46 | afterAll(function() { 47 | usbDetect.stopMonitoring(); 48 | }); 49 | 50 | describe('`.find`', function() { 51 | var testArrayOfDevicesShape = function(devices) { 52 | expect(devices.length).to.be.greaterThan(0); 53 | devices.forEach(function(device) { 54 | testDeviceShape(device); 55 | }); 56 | }; 57 | 58 | it('should find some usb devices', function(done) { 59 | usbDetect.find(function(err, devices) { 60 | testArrayOfDevicesShape(devices); 61 | expect(err).to.equal(undefined); 62 | done(); 63 | }); 64 | }); 65 | 66 | it('should return a promise when vid and pid are given', async function(done) { 67 | const devices = await usbDetect.find(); 68 | usbDetect.find(devices[0].vendorId, devices[0].productId) 69 | .then(function(devicesFromTestedFunction) { 70 | testArrayOfDevicesShape(devicesFromTestedFunction); 71 | expect(devicesFromTestedFunction.length).to.be.greaterThan(0); 72 | // Should find a subset of USB devices. We assume you have many USB devices connected 73 | expect(devicesFromTestedFunction.length).to.be.lessThan(devices.length); 74 | }) 75 | .then(done) 76 | .catch(done.fail); 77 | }); 78 | 79 | it('should return a promise when vid is given', async function(done) { 80 | const devices = await usbDetect.find(); 81 | 82 | const vendorIdMap = {}; 83 | devices.forEach((device => { 84 | vendorIdMap[device.vendorId] = device.vendorId; 85 | })); 86 | // We assume you have many USB devices connected from different vendors (at least 2) 87 | expect(Object.keys(vendorIdMap).length).to.be.greaterThan(1, 'Unable to get correct result from test because USB devices connected to host all have the same vendorId (need at least 2 devices from different vendors).'); 88 | 89 | usbDetect.find(devices[0].vendorId) 90 | .then(function(devicesFromTestedFunction) { 91 | testArrayOfDevicesShape(devicesFromTestedFunction); 92 | expect(devicesFromTestedFunction.length).to.be.greaterThan(0); 93 | // Should find a subset of USB devices. We assume you have many USB devices connected 94 | expect(devicesFromTestedFunction.length).to.be.lessThan(devices.length); 95 | }) 96 | .then(done) 97 | .catch(done.fail); 98 | }); 99 | 100 | 101 | it('should return a promise', function(done) { 102 | usbDetect.find() 103 | .then(function(devices) { 104 | testArrayOfDevicesShape(devices); 105 | }) 106 | .then(done) 107 | .catch(done.fail); 108 | }); 109 | }); 110 | 111 | describe('Events `.on`', function() { 112 | it('should listen to device add/insert', function(done) { 113 | console.log(chalk.black.bgCyan('Add/Insert a USB device')); 114 | once('add') 115 | .then(function(device) { 116 | testDeviceShape(device); 117 | }) 118 | .then(done) 119 | .catch(done.fail); 120 | }, MANUAL_INTERACTION_TIMEOUT); 121 | 122 | it('should listen to device remove', function(done) { 123 | console.log(chalk.black.bgCyan('Remove a USB device')); 124 | once('remove') 125 | .then(function(device) { 126 | testDeviceShape(device); 127 | }) 128 | .then(done) 129 | .catch(done.fail); 130 | }, MANUAL_INTERACTION_TIMEOUT); 131 | 132 | it('should listen to device change', function(done) { 133 | console.log(chalk.black.bgCyan('Add/Insert or Remove a USB device')); 134 | once('change') 135 | .then(function(device) { 136 | testDeviceShape(device); 137 | }) 138 | .then(done) 139 | .catch(done.fail); 140 | }, MANUAL_INTERACTION_TIMEOUT); 141 | }); 142 | }); 143 | 144 | describe('can exit gracefully', () => { 145 | it('when requiring package (no side-effects)', (done) => { 146 | commandRunner(`node ${path.join(__dirname, './fixtures/requiring-exit-gracefully.js')}`) 147 | .then(done) 148 | .catch((resultInfo) => { 149 | done.fail(resultInfo.err); 150 | }); 151 | }); 152 | 153 | it('after `startMonitoring` then `stopMonitoring`', (done) => { 154 | commandRunner(`node ${path.join(__dirname, './fixtures/start-stop-monitoring-exit-gracefully.js')}`) 155 | .then(done) 156 | .catch((resultInfo) => { 157 | done.fail(resultInfo.err); 158 | }); 159 | }); 160 | 161 | it('after `startMonitoring` then an async delayed `stopMonitoring`', (done) => { 162 | commandRunner(`node ${path.join(__dirname, './fixtures/start-delayed-stop-monitoring-exit-gracefully.js')}`) 163 | .then(done) 164 | .catch((resultInfo) => { 165 | done.fail(resultInfo.err); 166 | }); 167 | }); 168 | 169 | it('when SIGINT (Ctrl + c) after `startMonitoring`', (done) => { 170 | const executor = new ChildExecutor(); 171 | 172 | executor.exec(`node ${path.join(__dirname, './fixtures/sigint-after-start-monitoring-exit-gracefully.js')}`) 173 | .then(done) 174 | .catch((resultInfo) => { 175 | done.fail(resultInfo.err); 176 | }); 177 | 178 | getSetTimeoutPromise(100) 179 | .then(() => { 180 | executor.child.kill('SIGINT'); 181 | }) 182 | .catch(done.fail); 183 | }); 184 | }); 185 | }); 186 | --------------------------------------------------------------------------------