├── src ├── prebuild-test-noop.ts ├── lib │ ├── uiohook_worker.h │ ├── napi_helpers.c │ ├── napi_helpers.h │ ├── uiohook_worker.c │ └── addon.c ├── demo.ts ├── index.ts └── libuiohook.patch ├── .gitmodules ├── tsconfig.json ├── .vscode └── c_cpp_properties.json ├── LICENSE ├── package.json ├── README.md ├── .gitignore ├── .github └── workflows │ └── ci.yml ├── binding.gyp └── yarn.lock /src/prebuild-test-noop.ts: -------------------------------------------------------------------------------- 1 | // see https://github.com/prebuild/node-gyp-build/issues/29 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libuiohook"] 2 | path = libuiohook 3 | url = https://github.com/kwhat/libuiohook 4 | ignore = dirty 5 | -------------------------------------------------------------------------------- /src/lib/uiohook_worker.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDON_SRC_UIOHOOK_WORKER_H_ 2 | #define ADDON_SRC_UIOHOOK_WORKER_H_ 3 | 4 | #include 5 | 6 | #define UIOHOOK_ERROR_THREAD_CREATE 0x10 7 | 8 | int uiohook_worker_start(dispatcher_t dispatch_proc); 9 | 10 | int uiohook_worker_stop(); 11 | 12 | #endif // !ADDON_SRC_UIOHOOK_WORKER_H_ 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "alwaysStrict": true, 5 | "strict": true, 6 | "module": "CommonJS", 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "outDir": "dist", 10 | "declaration": true 11 | }, 12 | "include": [ 13 | "src/**/*" 14 | ], 15 | "exclude": [ 16 | "src/demo.ts" 17 | ] 18 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${env:HOME}/.nvm/versions/node/v12.16.1/include/node/**" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/usr/bin/gcc", 11 | "cStandard": "c11", 12 | "cppStandard": "c++17" 13 | } 14 | ], 15 | "version": 4 16 | } -------------------------------------------------------------------------------- /src/demo.ts: -------------------------------------------------------------------------------- 1 | import { uIOhook, UiohookKey } from './' 2 | 3 | const keycodeMap = new Map(Object.entries(UiohookKey).map(_ => [_[1], _[0]])) 4 | 5 | ;(function main () { 6 | uIOhook.on('keydown', (e) => { 7 | console.log( 8 | `${prettyModifier('ctrl', e.ctrlKey)}${prettyModifier('shift', e.shiftKey)}${prettyModifier('alt', e.altKey)}`, 9 | e.keycode, 10 | keycodeMap.get(e.keycode as any) 11 | ) 12 | 13 | if (e.keycode === UiohookKey.Escape) { 14 | process.exit(0) 15 | } 16 | }) 17 | 18 | uIOhook.start() 19 | })() 20 | 21 | function prettyModifier (name: string, state: boolean) { 22 | return state ? `[${name}]` : ` ${name} ` 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexander Drozdov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uiohook-napi", 3 | "version": "1.5.4", 4 | "author": { 5 | "name": "Alexander Drozdov" 6 | }, 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/SnosMe/uiohook-napi.git" 10 | }, 11 | "license": "MIT", 12 | "keywords": [ 13 | "iohook", 14 | "uiohook", 15 | "libuiohook", 16 | "hook", 17 | "input", 18 | "keyboard", 19 | "mouse" 20 | ], 21 | "main": "dist/index.js", 22 | "types": "dist/index.d.ts", 23 | "scripts": { 24 | "install": "node-gyp-build", 25 | "prebuild": "prebuildify --napi", 26 | "build-ts": "tsc", 27 | "demo": "ts-node src/demo.ts", 28 | "make-libuiohook-patch": "git -C ./libuiohook diff --cached > ./src/libuiohook.patch", 29 | "apply-libuiohook-patch": "git -C ./libuiohook apply ../src/libuiohook.patch" 30 | }, 31 | "files": [ 32 | "dist", 33 | "binding.gyp", 34 | "libuiohook/src", 35 | "libuiohook/include", 36 | "src/lib", 37 | "prebuilds" 38 | ], 39 | "devDependencies": { 40 | "@types/node": "18.x.x", 41 | "prebuildify": "5.x.x", 42 | "ts-node": "10.x.x", 43 | "typescript": "4.x.x" 44 | }, 45 | "dependencies": { 46 | "node-gyp-build": "4.x.x" 47 | }, 48 | "prebuild": { 49 | "test": "dist/prebuild-test-noop.js" 50 | }, 51 | "gypfile": true, 52 | "engines": { 53 | "node": ">= 16" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/lib/napi_helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "napi_helpers.h" 3 | 4 | napi_value error_create(napi_env env) { 5 | napi_status status; 6 | napi_value error = NULL; 7 | bool is_exception_pending; 8 | const napi_extended_error_info* info; 9 | 10 | // We must retrieve the last error info before doing anything else, because 11 | // doing anything else will replace the last error info. 12 | status = napi_get_last_error_info(env, &info); 13 | NAPI_FATAL_IF_FAILED(status, "error_create", "napi_get_last_error_info"); 14 | 15 | status = napi_is_exception_pending(env, &is_exception_pending); 16 | NAPI_FATAL_IF_FAILED(status, "error_create", "napi_is_exception_pending"); 17 | 18 | // A pending exception takes precedence over any internal error status. 19 | if (is_exception_pending) { 20 | status = napi_get_and_clear_last_exception(env, &error); 21 | NAPI_FATAL_IF_FAILED(status, "error_create", "napi_get_and_clear_last_exception"); 22 | } 23 | else { 24 | const char* error_message = info->error_message != NULL ? 25 | info->error_message : "Error in native callback"; 26 | 27 | napi_value message; 28 | status = napi_create_string_utf8( 29 | env, 30 | error_message, 31 | strlen(error_message), 32 | &message); 33 | NAPI_FATAL_IF_FAILED(status, "error_create", "napi_create_string_utf8"); 34 | 35 | switch (info->error_code) { 36 | case napi_object_expected: 37 | case napi_string_expected: 38 | case napi_boolean_expected: 39 | case napi_number_expected: 40 | status = napi_create_type_error(env, NULL, message, &error); 41 | NAPI_FATAL_IF_FAILED(status, "error_create", "napi_create_type_error"); 42 | break; 43 | default: 44 | status = napi_create_error(env, NULL, message, &error); 45 | NAPI_FATAL_IF_FAILED(status, "error_create", "napi_create_error"); 46 | break; 47 | } 48 | } 49 | 50 | return error; 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uiohook-napi 2 | 3 | [![](https://img.shields.io/npm/v/uiohook-napi/latest?color=CC3534&label=uiohook-napi&logo=npm&labelColor=212121)](https://www.npmjs.com/package/uiohook-napi) 4 | 5 | N-API C-bindings for [libuiohook](https://github.com/kwhat/libuiohook). 6 | 7 | 8 | ### Usage example 9 | 10 | ```typescript 11 | import { uIOhook, UiohookKey } from 'uiohook-napi' 12 | 13 | uIOhook.on('keydown', (e) => { 14 | if (e.keycode === UiohookKey.Q) { 15 | console.log('Hello!') 16 | } 17 | 18 | if (e.keycode === UiohookKey.Escape) { 19 | process.exit(0) 20 | } 21 | }) 22 | 23 | uIOhook.start() 24 | ``` 25 | 26 | ### API 27 | 28 | ```typescript 29 | interface UiohookNapi { 30 | on(event: 'input', listener: (e: UiohookKeyboardEvent | UiohookMouseEvent | UiohookWheelEvent) => void): this 31 | 32 | on(event: 'keydown', listener: (e: UiohookKeyboardEvent) => void): this 33 | on(event: 'keyup', listener: (e: UiohookKeyboardEvent) => void): this 34 | 35 | on(event: 'mousedown', listener: (e: UiohookMouseEvent) => void): this 36 | on(event: 'mouseup', listener: (e: UiohookMouseEvent) => void): this 37 | on(event: 'mousemove', listener: (e: UiohookMouseEvent) => void): this 38 | on(event: 'click', listener: (e: UiohookMouseEvent) => void): this 39 | 40 | on(event: 'wheel', listener: (e: UiohookWheelEvent) => void): this 41 | 42 | keyTap(key: keycode, modifiers?: keycode[]) 43 | keyToggle(key: keycode, toggle: 'down' | 'up') 44 | } 45 | 46 | export interface UiohookKeyboardEvent { 47 | altKey: boolean 48 | ctrlKey: boolean 49 | metaKey: boolean 50 | shiftKey: boolean 51 | keycode: number 52 | } 53 | 54 | export interface UiohookMouseEvent { 55 | altKey: boolean 56 | ctrlKey: boolean 57 | metaKey: boolean 58 | shiftKey: boolean 59 | x: number 60 | y: number 61 | button: unknown 62 | clicks: number 63 | } 64 | 65 | export interface UiohookWheelEvent { 66 | altKey: boolean 67 | ctrlKey: boolean 68 | metaKey: boolean 69 | shiftKey: boolean 70 | x: number 71 | y: number 72 | clicks: number 73 | amount: number 74 | direction: WheelDirection 75 | rotation: number 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /src/lib/napi_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDON_SRC_HELPERS_H_ 2 | #define ADDON_SRC_HELPERS_H_ 3 | 4 | #include 5 | 6 | #define NAPI_FATAL_IF_FAILED(status, location, message) \ 7 | do { \ 8 | if ((status) != napi_ok) { \ 9 | napi_fatal_error(location, NAPI_AUTO_LENGTH, \ 10 | message, NAPI_AUTO_LENGTH); \ 11 | } \ 12 | } while (0) 13 | 14 | #define NAPI_THROW_IF_FAILED_VOID(env, status) \ 15 | if ((status) != napi_ok) { \ 16 | NAPI_FATAL_IF_FAILED( \ 17 | napi_throw(env, error_create(env)), \ 18 | "NAPI_THROW_IF_FAILED_VOID", \ 19 | "napi_throw"); \ 20 | return; \ 21 | } 22 | 23 | #define NAPI_THROW_IF_FAILED(env, status, ...) \ 24 | if ((status) != napi_ok) { \ 25 | NAPI_FATAL_IF_FAILED( \ 26 | napi_throw(env, error_create(env)), \ 27 | "NAPI_THROW_IF_FAILED_VOID", \ 28 | "napi_throw"); \ 29 | return __VA_ARGS__; \ 30 | } 31 | 32 | #define NAPI_THROW_VOID(env, code, msg) \ 33 | do { \ 34 | NAPI_FATAL_IF_FAILED( \ 35 | napi_throw_error(env, (code), (msg)), \ 36 | "NAPI_THROW_VOID", \ 37 | "napi_throw_error"); \ 38 | return; \ 39 | } while (0) 40 | 41 | #define NAPI_THROW(env, code, msg, ...) \ 42 | do { \ 43 | NAPI_FATAL_IF_FAILED( \ 44 | napi_throw_error(env, (code), (msg)), \ 45 | "NAPI_THROW", \ 46 | "napi_throw_error"); \ 47 | return __VA_ARGS__; \ 48 | } while (0) 49 | 50 | 51 | napi_value error_create(napi_env env); 52 | 53 | #endif // !ADDON_SRC_HELPERS_H_ 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and not Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | # Stores VSCode versions used for testing VSCode extensions 108 | .vscode-test 109 | 110 | # yarn v2 111 | 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .pnp.* 116 | 117 | build 118 | prebuilds 119 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Prebuild 2 | 3 | on: push 4 | 5 | jobs: 6 | prebuild-windows: 7 | runs-on: windows-2019 8 | steps: 9 | - uses: actions/checkout@v3 10 | with: 11 | submodules: true 12 | - uses: actions/setup-node@v3 13 | - run: npm install --global node-gyp 14 | - run: yarn apply-libuiohook-patch 15 | - run: yarn --frozen-lockfile 16 | - run: yarn prebuild --arch x64 17 | # - run: yarn prebuild --arch arm64 18 | - uses: actions/upload-artifact@v3 19 | with: 20 | name: windows 21 | path: prebuilds 22 | 23 | prebuild-linux: 24 | runs-on: ubuntu-20.04 25 | steps: 26 | - uses: actions/checkout@v3 27 | with: 28 | submodules: true 29 | - uses: actions/setup-node@v3 30 | - run: | 31 | sudo apt-get update 32 | sudo apt-get install -y libxrandr-dev libxtst-dev 33 | - run: yarn apply-libuiohook-patch 34 | - run: yarn --frozen-lockfile 35 | - run: yarn prebuild --arch x64 36 | - run: yarn prebuild --arch arm64 37 | - uses: actions/upload-artifact@v3 38 | with: 39 | name: linux 40 | path: prebuilds 41 | 42 | prebuild-darwin: 43 | runs-on: macos-12 44 | steps: 45 | - uses: actions/checkout@v3 46 | with: 47 | submodules: true 48 | - uses: actions/setup-node@v3 49 | - run: yarn apply-libuiohook-patch 50 | - run: yarn --frozen-lockfile 51 | - run: yarn prebuild --arch x64 52 | - run: yarn prebuild --arch arm64 53 | - uses: actions/upload-artifact@v3 54 | with: 55 | name: darwin 56 | path: prebuilds 57 | 58 | build-package: 59 | needs: 60 | - prebuild-windows 61 | - prebuild-linux 62 | - prebuild-darwin 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v3 66 | with: 67 | submodules: true 68 | - uses: actions/download-artifact@v3 69 | with: 70 | name: windows 71 | path: prebuilds 72 | - uses: actions/download-artifact@v3 73 | with: 74 | name: linux 75 | path: prebuilds 76 | - uses: actions/download-artifact@v3 77 | with: 78 | name: darwin 79 | path: prebuilds 80 | - uses: actions/setup-node@v3 81 | - run: yarn apply-libuiohook-patch 82 | - run: yarn --frozen-lockfile --ignore-scripts 83 | - run: yarn tsc 84 | - run: npm pack 85 | - uses: actions/upload-artifact@v3 86 | with: 87 | name: package 88 | path: uiohook-napi-*.tgz 89 | retention-days: 3 90 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'uiohook_napi', 5 | 'dependencies': ['libuiohook'], 6 | 'sources': [ 7 | 'src/lib/addon.c', 8 | 'src/lib/napi_helpers.c', 9 | 'src/lib/uiohook_worker.c', 10 | ], 11 | 'include_dirs': [ 12 | 'libuiohook/include', 13 | 'src/lib', 14 | ] 15 | }, 16 | { 17 | 'target_name': 'libuiohook', 18 | 'type': 'static_library', 19 | 'sources': [ 20 | 'libuiohook/src/logger.c', 21 | ], 22 | 'include_dirs': [ 23 | 'libuiohook/include', 24 | 'libuiohook/src', 25 | ], 26 | "conditions": [ 27 | ['OS=="win"', { 28 | 'sources': [ 29 | 'libuiohook/src/windows/input_helper.c', 30 | 'libuiohook/src/windows/input_hook.c', 31 | 'libuiohook/src/windows/post_event.c', 32 | 'libuiohook/src/windows/system_properties.c' 33 | ], 34 | 'include_dirs': [ 35 | 'libuiohook/src/windows' 36 | ] 37 | }], 38 | ['OS=="linux"', { 39 | 'defines': [ 40 | 'USE_XRANDR', 'USE_EVDEV', 'USE_XT' 41 | ], 42 | 'link_settings': { 43 | 'libraries': [ 44 | '-lX11', '-lXrandr', '-lXtst', '-lpthread', '-lXt' 45 | ], 46 | }, 47 | 'cflags': ['-std=c99', '-pedantic', '-Wall', '-pthread'], 48 | 'sources': [ 49 | 'libuiohook/src/x11/input_helper.c', 50 | 'libuiohook/src/x11/input_hook.c', 51 | 'libuiohook/src/x11/post_event.c', 52 | 'libuiohook/src/x11/system_properties.c' 53 | ], 54 | 'include_dirs': [ 55 | 'libuiohook/src/x11' 56 | ] 57 | }], 58 | ['OS=="mac"', { 59 | "defines":[ 60 | "__MACOSX_CORE__","USE_IOKIT","USE_APPLICATION_SERVICES","USE_OBJC" 61 | ], 62 | "link_settings": { 63 | "libraries": [ 64 | "-framework IOKit", 65 | "-framework Carbon", 66 | "-framework ApplicationServices", 67 | "-framework AppKit", 68 | "-framework CoreFoundation" 69 | ], 70 | }, 71 | 'cflags': ['-std=c99', '-pedantic', '-Wall', '-pthread'], 72 | 'sources': [ 73 | "libuiohook/src/darwin/input_helper.c", 74 | "libuiohook/src/darwin/input_hook.c", 75 | "libuiohook/src/darwin/post_event.c", 76 | "libuiohook/src/darwin/system_properties.c" 77 | ], 78 | 'include_dirs': [ 79 | 'libuiohook/src/darwin' 80 | ] 81 | }] 82 | ] 83 | } 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /src/lib/uiohook_worker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include 8 | #else 9 | #include 10 | #include 11 | #endif 12 | 13 | #include "uiohook_worker.h" 14 | 15 | // Thread and mutex variables. 16 | static uv_thread_t hook_thread; 17 | static int hook_thread_status; 18 | static uv_mutex_t hook_running_mutex; 19 | static uv_mutex_t hook_control_mutex; 20 | static uv_cond_t hook_control_cond; 21 | 22 | static dispatcher_t user_dispatcher = NULL; 23 | 24 | bool logger_proc(unsigned int level, const char* format, ...) { 25 | bool status = false; 26 | 27 | va_list args; 28 | switch (level) { 29 | case LOG_LEVEL_WARN: 30 | case LOG_LEVEL_ERROR: 31 | va_start(args, format); 32 | status = vfprintf(stderr, format, args) >= 0; 33 | va_end(args); 34 | break; 35 | } 36 | 37 | return status; 38 | } 39 | 40 | // NOTE: The following callback executes on the same thread that hook_run() is called 41 | // from. This is important because hook_run() attaches to the operating systems 42 | // event dispatcher and may delay event delivery to the target application. 43 | // Furthermore, some operating systems may choose to disable your hook if it 44 | // takes to long to process. If you need to do any extended processing, please 45 | // do so by copying the event to your own queued dispatch thread. 46 | void worker_dispatch_proc(uiohook_event* const event) { 47 | switch (event->type) { 48 | case EVENT_HOOK_ENABLED: 49 | // Lock the running mutex so we know if the hook is enabled. 50 | uv_mutex_lock(&hook_running_mutex); 51 | 52 | // Signal control cond so hook_enable() can continue. 53 | uv_mutex_lock(&hook_control_mutex); 54 | uv_cond_signal(&hook_control_cond); 55 | uv_mutex_unlock(&hook_control_mutex); 56 | break; 57 | 58 | case EVENT_HOOK_DISABLED: 59 | // Lock the control mutex until we exit. 60 | uv_mutex_lock(&hook_control_mutex); 61 | 62 | // Unlock the running mutex so we know if the hook is disabled. 63 | uv_mutex_unlock(&hook_running_mutex); 64 | break; 65 | 66 | case EVENT_KEY_PRESSED: 67 | case EVENT_KEY_RELEASED: 68 | // case EVENT_KEY_TYPED: 69 | case EVENT_MOUSE_CLICKED: 70 | case EVENT_MOUSE_PRESSED: 71 | case EVENT_MOUSE_RELEASED: 72 | case EVENT_MOUSE_MOVED: 73 | case EVENT_MOUSE_DRAGGED: 74 | case EVENT_MOUSE_WHEEL: { 75 | user_dispatcher(event); 76 | break; 77 | } 78 | 79 | default: 80 | break; 81 | } 82 | } 83 | 84 | void hook_thread_proc(void* arg) { 85 | #ifdef _WIN32 86 | // Attempt to set the thread priority to time critical. 87 | HANDLE this_thread = GetCurrentThread(); 88 | if (SetThreadPriority(this_thread, THREAD_PRIORITY_TIME_CRITICAL) == FALSE) { 89 | logger_proc(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %li for thread %#p! (%#lX)\n", 90 | __FUNCTION__, __LINE__, (long)THREAD_PRIORITY_TIME_CRITICAL, 91 | this_thread, (unsigned long)GetLastError()); 92 | } 93 | #else 94 | // Raise the thread priority 95 | pthread_t this_thread = pthread_self(); 96 | struct sched_param params = { 97 | .sched_priority = (sched_get_priority_max(SCHED_RR) / 2) 98 | }; 99 | if (pthread_setschedparam(this_thread, SCHED_RR, ¶ms) != 0) { 100 | logger_proc(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %i for thread 0x%lX!\n", 101 | __FUNCTION__, __LINE__, params.sched_priority, (unsigned long)this_thread); 102 | } 103 | #endif 104 | 105 | // Set the hook status. 106 | hook_thread_status = hook_run(); 107 | 108 | // Make sure we signal that we have passed any exception throwing code for 109 | // the waiting hook_enable(). 110 | uv_cond_signal(&hook_control_cond); 111 | uv_mutex_unlock(&hook_control_mutex); 112 | } 113 | 114 | int hook_enable() { 115 | // Lock the thread control mutex. This will be unlocked when the 116 | // thread has finished starting, or when it has fully stopped. 117 | uv_mutex_lock(&hook_control_mutex); 118 | 119 | // Set the initial status. 120 | int status = UIOHOOK_FAILURE; 121 | 122 | if (uv_thread_create(&hook_thread, hook_thread_proc, NULL) == 0) { 123 | // Wait for the thread to indicate that it has passed the 124 | // initialization portion by blocking until either a EVENT_HOOK_ENABLED 125 | // event is received or the thread terminates. 126 | uv_cond_wait(&hook_control_cond, &hook_control_mutex); 127 | 128 | if (uv_mutex_trylock(&hook_running_mutex) == 0) { 129 | // Lock Successful; The hook is not running but the hook_control_cond 130 | // was signaled! This indicates that there was a startup problem! 131 | 132 | // Get the status back from the thread. 133 | uv_thread_join(&hook_thread); 134 | status = hook_thread_status; 135 | } 136 | else { 137 | // Lock Failure; The hook is currently running and wait was signaled 138 | // indicating that we have passed all possible start checks. We can 139 | // always assume a successful startup at this point. 140 | status = UIOHOOK_SUCCESS; 141 | } 142 | 143 | logger_proc(LOG_LEVEL_DEBUG, "%s [%u]: Thread Result: (%#X).\n", 144 | __FUNCTION__, __LINE__, status); 145 | } 146 | else { 147 | status = UIOHOOK_ERROR_THREAD_CREATE; 148 | } 149 | 150 | // Make sure the control mutex is unlocked. 151 | uv_mutex_unlock(&hook_control_mutex); 152 | 153 | return status; 154 | } 155 | 156 | 157 | int uiohook_worker_start(dispatcher_t dispatch_proc) { 158 | // Lock the thread control mutex. This will be unlocked when the 159 | // thread has finished starting, or when it has fully stopped. 160 | 161 | // Create event handles for the thread hook. 162 | uv_mutex_init(&hook_running_mutex); 163 | uv_mutex_init(&hook_control_mutex); 164 | uv_cond_init(&hook_control_cond); 165 | 166 | // Set the logger callback for library output. 167 | hook_set_logger_proc(logger_proc); 168 | 169 | // Set the event callback for uiohook events. 170 | hook_set_dispatch_proc(worker_dispatch_proc); 171 | 172 | user_dispatcher = dispatch_proc; 173 | 174 | // Start the hook and block. 175 | // NOTE If EVENT_HOOK_ENABLED was delivered, the status will always succeed. 176 | int status = hook_enable(); 177 | if (status != UIOHOOK_SUCCESS) { 178 | // Close event handles for the thread hook. 179 | uv_mutex_destroy(&hook_running_mutex); 180 | uv_mutex_destroy(&hook_control_mutex); 181 | uv_cond_destroy(&hook_control_cond); 182 | } 183 | 184 | return status; 185 | } 186 | 187 | int uiohook_worker_stop() { 188 | int status = hook_stop(); 189 | 190 | if (status == UIOHOOK_SUCCESS) { 191 | uv_thread_join(&hook_thread); 192 | 193 | // Close event handles for the thread hook. 194 | uv_mutex_destroy(&hook_running_mutex); 195 | uv_mutex_destroy(&hook_control_mutex); 196 | uv_cond_destroy(&hook_control_cond); 197 | } 198 | 199 | return status; 200 | } 201 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events' 2 | import { join } from 'path' 3 | const lib: AddonExports = require('node-gyp-build')(join(__dirname, '..')) 4 | 5 | interface AddonExports { 6 | start (cb: (e: any) => void): void 7 | stop (): void 8 | keyTap (key: number, type: KeyToggle): void 9 | } 10 | 11 | enum KeyToggle { 12 | Tap = 0, 13 | Down = 1, 14 | Up = 2 15 | } 16 | 17 | export enum EventType { 18 | EVENT_KEY_PRESSED = 4, 19 | EVENT_KEY_RELEASED = 5, 20 | EVENT_MOUSE_CLICKED = 6, 21 | EVENT_MOUSE_PRESSED = 7, 22 | EVENT_MOUSE_RELEASED = 8, 23 | EVENT_MOUSE_MOVED = 9, 24 | EVENT_MOUSE_WHEEL = 11 25 | } 26 | 27 | export interface UiohookKeyboardEvent { 28 | type: EventType.EVENT_KEY_PRESSED | EventType.EVENT_KEY_RELEASED 29 | time: number 30 | altKey: boolean 31 | ctrlKey: boolean 32 | metaKey: boolean 33 | shiftKey: boolean 34 | keycode: number 35 | } 36 | 37 | export interface UiohookMouseEvent { 38 | type: EventType.EVENT_MOUSE_CLICKED | 39 | EventType.EVENT_MOUSE_MOVED | 40 | EventType.EVENT_MOUSE_PRESSED | 41 | EventType.EVENT_MOUSE_RELEASED 42 | time: number 43 | altKey: boolean 44 | ctrlKey: boolean 45 | metaKey: boolean 46 | shiftKey: boolean 47 | x: number 48 | y: number 49 | button: unknown 50 | clicks: number 51 | } 52 | 53 | export interface UiohookWheelEvent { 54 | type: EventType.EVENT_MOUSE_WHEEL 55 | time: number 56 | altKey: boolean 57 | ctrlKey: boolean 58 | metaKey: boolean 59 | shiftKey: boolean 60 | x: number 61 | y: number 62 | clicks: number 63 | amount: number 64 | direction: WheelDirection 65 | rotation: number 66 | } 67 | 68 | export enum WheelDirection { 69 | VERTICAL = 3, 70 | HORIZONTAL = 4 71 | } 72 | 73 | export const UiohookKey = { 74 | Backspace: 0x000E, 75 | Tab: 0x000F, 76 | Enter: 0x001C, 77 | CapsLock: 0x003A, 78 | Escape: 0x0001, 79 | Space: 0x0039, 80 | PageUp: 0x0E49, 81 | PageDown: 0x0E51, 82 | End: 0x0E4F, 83 | Home: 0x0E47, 84 | ArrowLeft: 0xE04B, 85 | ArrowUp: 0xE048, 86 | ArrowRight: 0xE04D, 87 | ArrowDown: 0xE050, 88 | Insert: 0x0E52, 89 | Delete: 0x0E53, 90 | 0: 0x000B, 91 | 1: 0x0002, 92 | 2: 0x0003, 93 | 3: 0x0004, 94 | 4: 0x0005, 95 | 5: 0x0006, 96 | 6: 0x0007, 97 | 7: 0x0008, 98 | 8: 0x0009, 99 | 9: 0x000A, 100 | A: 0x001E, 101 | B: 0x0030, 102 | C: 0x002E, 103 | D: 0x0020, 104 | E: 0x0012, 105 | F: 0x0021, 106 | G: 0x0022, 107 | H: 0x0023, 108 | I: 0x0017, 109 | J: 0x0024, 110 | K: 0x0025, 111 | L: 0x0026, 112 | M: 0x0032, 113 | N: 0x0031, 114 | O: 0x0018, 115 | P: 0x0019, 116 | Q: 0x0010, 117 | R: 0x0013, 118 | S: 0x001F, 119 | T: 0x0014, 120 | U: 0x0016, 121 | V: 0x002F, 122 | W: 0x0011, 123 | X: 0x002D, 124 | Y: 0x0015, 125 | Z: 0x002C, 126 | Numpad0: 0x0052, 127 | Numpad1: 0x004F, 128 | Numpad2: 0x0050, 129 | Numpad3: 0x0051, 130 | Numpad4: 0x004B, 131 | Numpad5: 0x004C, 132 | Numpad6: 0x004D, 133 | Numpad7: 0x0047, 134 | Numpad8: 0x0048, 135 | Numpad9: 0x0049, 136 | NumpadMultiply: 0x0037, 137 | NumpadAdd: 0x004E, 138 | NumpadSubtract: 0x004A, 139 | NumpadDecimal: 0x0053, 140 | NumpadDivide: 0x0E35, 141 | NumpadEnter: 0x0E00 | 0x001C, 142 | NumpadEnd: 0xEE00 | 0x004F, 143 | NumpadArrowDown: 0xEE00 | 0x0050, 144 | NumpadPageDown: 0xEE00 | 0x0051, 145 | NumpadArrowLeft: 0xEE00 | 0x004B, 146 | NumpadArrowRight: 0xEE00 | 0x004D, 147 | NumpadHome: 0xEE00 | 0x0047, 148 | NumpadArrowUp: 0xEE00 | 0x0048, 149 | NumpadPageUp: 0xEE00 | 0x0049, 150 | NumpadInsert: 0xEE00 | 0x0052, 151 | NumpadDelete: 0xEE00 | 0x0053, 152 | F1: 0x003B, 153 | F2: 0x003C, 154 | F3: 0x003D, 155 | F4: 0x003E, 156 | F5: 0x003F, 157 | F6: 0x0040, 158 | F7: 0x0041, 159 | F8: 0x0042, 160 | F9: 0x0043, 161 | F10: 0x0044, 162 | F11: 0x0057, 163 | F12: 0x0058, 164 | F13: 0x005B, 165 | F14: 0x005C, 166 | F15: 0x005D, 167 | F16: 0x0063, 168 | F17: 0x0064, 169 | F18: 0x0065, 170 | F19: 0x0066, 171 | F20: 0x0067, 172 | F21: 0x0068, 173 | F22: 0x0069, 174 | F23: 0x006A, 175 | F24: 0x006B, 176 | Semicolon: 0x0027, 177 | Equal: 0x000D, 178 | Comma: 0x0033, 179 | Minus: 0x000C, 180 | Period: 0x0034, 181 | Slash: 0x0035, 182 | Backquote: 0x0029, 183 | BracketLeft: 0x001A, 184 | Backslash: 0x002B, 185 | BracketRight: 0x001B, 186 | Quote: 0x0028, 187 | Ctrl: 0x001D, // Left 188 | CtrlRight: 0x0E1D, 189 | Alt: 0x0038, // Left 190 | AltRight: 0x0E38, 191 | Shift: 0x002A, // Left 192 | ShiftRight: 0x0036, 193 | Meta: 0x0E5B, 194 | MetaRight: 0x0E5C, 195 | NumLock: 0x0045, 196 | ScrollLock: 0x0046, 197 | PrintScreen: 0x0E37, 198 | } as const 199 | 200 | declare interface UiohookNapi { 201 | on(event: 'input', listener: (e: UiohookKeyboardEvent | UiohookMouseEvent | UiohookWheelEvent) => void): this 202 | 203 | on(event: 'keydown', listener: (e: UiohookKeyboardEvent) => void): this 204 | on(event: 'keyup', listener: (e: UiohookKeyboardEvent) => void): this 205 | 206 | on(event: 'mousedown', listener: (e: UiohookMouseEvent) => void): this 207 | on(event: 'mouseup', listener: (e: UiohookMouseEvent) => void): this 208 | on(event: 'mousemove', listener: (e: UiohookMouseEvent) => void): this 209 | on(event: 'click', listener: (e: UiohookMouseEvent) => void): this 210 | 211 | on(event: 'wheel', listener: (e: UiohookWheelEvent) => void): this 212 | } 213 | 214 | class UiohookNapi extends EventEmitter { 215 | private handler (e: UiohookKeyboardEvent | UiohookMouseEvent | UiohookWheelEvent) { 216 | this.emit('input', e) 217 | switch (e.type) { 218 | case EventType.EVENT_KEY_PRESSED: 219 | this.emit('keydown', e) 220 | break 221 | case EventType.EVENT_KEY_RELEASED: 222 | this.emit('keyup', e) 223 | break 224 | case EventType.EVENT_MOUSE_CLICKED: 225 | this.emit('click', e) 226 | break 227 | case EventType.EVENT_MOUSE_MOVED: 228 | this.emit('mousemove', e) 229 | break 230 | case EventType.EVENT_MOUSE_PRESSED: 231 | this.emit('mousedown', e) 232 | break 233 | case EventType.EVENT_MOUSE_RELEASED: 234 | this.emit('mouseup', e) 235 | break 236 | case EventType.EVENT_MOUSE_WHEEL: 237 | this.emit('wheel', e) 238 | break 239 | } 240 | } 241 | 242 | start () { 243 | lib.start(this.handler.bind(this)) 244 | } 245 | 246 | stop () { 247 | lib.stop() 248 | } 249 | 250 | keyTap (key: number, modifiers: number[] = []) { 251 | if (!modifiers.length) { 252 | lib.keyTap(key, KeyToggle.Tap) 253 | return 254 | } 255 | 256 | for (const modKey of modifiers) { 257 | lib.keyTap(modKey, KeyToggle.Down) 258 | } 259 | lib.keyTap(key, KeyToggle.Tap) 260 | let i = modifiers.length 261 | while (i--) { 262 | lib.keyTap(modifiers[i], KeyToggle.Up) 263 | } 264 | } 265 | 266 | keyToggle (key: number, toggle: 'down' | 'up') { 267 | lib.keyTap(key, (toggle === 'down' ? KeyToggle.Down : KeyToggle.Up)) 268 | } 269 | } 270 | 271 | export const uIOhook = new UiohookNapi() 272 | -------------------------------------------------------------------------------- /src/libuiohook.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/windows/input_helper.c b/src/windows/input_helper.c 2 | index e690021..0d383e0 100644 3 | --- a/src/windows/input_helper.c 4 | +++ b/src/windows/input_helper.c 5 | @@ -298,7 +298,7 @@ unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags) { 6 | if (vk_code < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[0])) { 7 | scancode = keycode_scancode_table[vk_code][0]; 8 | 9 | - if (flags & LLKHF_EXTENDED) { 10 | + if (!(flags & LLKHF_EXTENDED)) { 11 | logger(LOG_LEVEL_DEBUG, "%s [%u]: Using extended lookup for vk_code: %li\n", 12 | __FUNCTION__, __LINE__, vk_code); 13 | 14 | @@ -316,14 +316,16 @@ unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags) { 15 | case VK_DELETE: 16 | scancode |= 0xEE00; 17 | break; 18 | + } 19 | + } else { 20 | + logger(LOG_LEVEL_DEBUG, "%s [%u]: Using normal lookup for vk_code: %li\n", 21 | + __FUNCTION__, __LINE__, vk_code); 22 | 23 | + switch (vk_code) { 24 | case VK_RETURN: 25 | scancode |= 0x0E00; 26 | break; 27 | } 28 | - } else { 29 | - logger(LOG_LEVEL_DEBUG, "%s [%u]: Using normal lookup for vk_code: %li\n", 30 | - __FUNCTION__, __LINE__, vk_code); 31 | } 32 | } 33 | 34 | diff --git a/src/windows/input_hook.c b/src/windows/input_hook.c 35 | index db1e79e..264a71b 100644 36 | --- a/src/windows/input_hook.c 37 | +++ b/src/windows/input_hook.c 38 | @@ -23,10 +23,20 @@ 39 | #include "input_helper.h" 40 | #include "logger.h" 41 | 42 | +#ifndef FOREGROUND_TIMER_MS 43 | +#define FOREGROUND_TIMER_MS 83 // 12 fps 44 | +#endif 45 | + 46 | // Thread and hook handles. 47 | static DWORD hook_thread_id = 0; 48 | static HHOOK keyboard_event_hhook = NULL, mouse_event_hhook = NULL; 49 | -static HWINEVENTHOOK win_event_hhook = NULL; 50 | +static HWINEVENTHOOK win_foreground_hhook = NULL, win_minimizeend_hhook = NULL; 51 | +static UINT_PTR foreground_timer = 0; 52 | + 53 | +static HWND foreground_window = NULL; 54 | +static bool is_blocked_by_uipi = true; 55 | + 56 | +static UINT WM_UIOHOOK_UIPI_TEST = WM_NULL; 57 | 58 | // The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH. 59 | extern HINSTANCE hInst; 60 | @@ -66,6 +76,7 @@ static inline void dispatch_event(uiohook_event *const event) { 61 | } 62 | } 63 | 64 | +static void initialize_modifiers(); 65 | 66 | // Set the native modifier mask for future events. 67 | static inline void set_modifier_mask(unsigned short int mask) { 68 | @@ -79,6 +90,10 @@ static inline void unset_modifier_mask(unsigned short int mask) { 69 | 70 | // Get the current native modifier mask state. 71 | static inline unsigned short int get_modifiers() { 72 | + if (is_blocked_by_uipi) { 73 | + initialize_modifiers(); 74 | + is_blocked_by_uipi = false; 75 | + } 76 | return current_modifiers; 77 | } 78 | 79 | @@ -87,31 +102,49 @@ static void initialize_modifiers() { 80 | current_modifiers = 0x0000; 81 | 82 | // NOTE We are checking the high order bit, so it will be < 0 for a singed short. 83 | - if (GetKeyState(VK_LSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_L); } 84 | - if (GetKeyState(VK_RSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_R); } 85 | - if (GetKeyState(VK_LCONTROL) < 0) { set_modifier_mask(MASK_CTRL_L); } 86 | - if (GetKeyState(VK_RCONTROL) < 0) { set_modifier_mask(MASK_CTRL_R); } 87 | - if (GetKeyState(VK_LMENU) < 0) { set_modifier_mask(MASK_ALT_L); } 88 | - if (GetKeyState(VK_RMENU) < 0) { set_modifier_mask(MASK_ALT_R); } 89 | - if (GetKeyState(VK_LWIN) < 0) { set_modifier_mask(MASK_META_L); } 90 | - if (GetKeyState(VK_RWIN) < 0) { set_modifier_mask(MASK_META_R); } 91 | - 92 | - if (GetKeyState(VK_LBUTTON) < 0) { set_modifier_mask(MASK_BUTTON1); } 93 | - if (GetKeyState(VK_RBUTTON) < 0) { set_modifier_mask(MASK_BUTTON2); } 94 | - if (GetKeyState(VK_MBUTTON) < 0) { set_modifier_mask(MASK_BUTTON3); } 95 | - if (GetKeyState(VK_XBUTTON1) < 0) { set_modifier_mask(MASK_BUTTON4); } 96 | - if (GetKeyState(VK_XBUTTON2) < 0) { set_modifier_mask(MASK_BUTTON5); } 97 | - 98 | - if (GetKeyState(VK_NUMLOCK) < 0) { set_modifier_mask(MASK_NUM_LOCK); } 99 | - if (GetKeyState(VK_CAPITAL) < 0) { set_modifier_mask(MASK_CAPS_LOCK); } 100 | - if (GetKeyState(VK_SCROLL) < 0) { set_modifier_mask(MASK_SCROLL_LOCK); } 101 | + if (GetAsyncKeyState(VK_LSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_L); } 102 | + if (GetAsyncKeyState(VK_RSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_R); } 103 | + if (GetAsyncKeyState(VK_LCONTROL) < 0) { set_modifier_mask(MASK_CTRL_L); } 104 | + if (GetAsyncKeyState(VK_RCONTROL) < 0) { set_modifier_mask(MASK_CTRL_R); } 105 | + if (GetAsyncKeyState(VK_LMENU) < 0) { set_modifier_mask(MASK_ALT_L); } 106 | + if (GetAsyncKeyState(VK_RMENU) < 0) { set_modifier_mask(MASK_ALT_R); } 107 | + if (GetAsyncKeyState(VK_LWIN) < 0) { set_modifier_mask(MASK_META_L); } 108 | + if (GetAsyncKeyState(VK_RWIN) < 0) { set_modifier_mask(MASK_META_R); } 109 | + 110 | + if (GetAsyncKeyState(VK_LBUTTON) < 0) { set_modifier_mask(MASK_BUTTON1); } 111 | + if (GetAsyncKeyState(VK_RBUTTON) < 0) { set_modifier_mask(MASK_BUTTON2); } 112 | + if (GetAsyncKeyState(VK_MBUTTON) < 0) { set_modifier_mask(MASK_BUTTON3); } 113 | + if (GetAsyncKeyState(VK_XBUTTON1) < 0) { set_modifier_mask(MASK_BUTTON4); } 114 | + if (GetAsyncKeyState(VK_XBUTTON2) < 0) { set_modifier_mask(MASK_BUTTON5); } 115 | + 116 | + if (GetAsyncKeyState(VK_NUMLOCK) < 0) { set_modifier_mask(MASK_NUM_LOCK); } 117 | + if (GetAsyncKeyState(VK_CAPITAL) < 0) { set_modifier_mask(MASK_CAPS_LOCK); } 118 | + if (GetAsyncKeyState(VK_SCROLL) < 0) { set_modifier_mask(MASK_SCROLL_LOCK); } 119 | +} 120 | + 121 | +void check_and_update_uipi_state(HWND hwnd) { 122 | + SetLastError(ERROR_SUCCESS); 123 | + PostMessage(hwnd, WM_UIOHOOK_UIPI_TEST, 0, 0); 124 | + if (GetLastError() == ERROR_ACCESS_DENIED) { 125 | + is_blocked_by_uipi = true; 126 | + } 127 | } 128 | 129 | void unregister_running_hooks() { 130 | // Stop the event hook and any timer still running. 131 | - if (win_event_hhook != NULL) { 132 | - UnhookWinEvent(win_event_hhook); 133 | - win_event_hhook = NULL; 134 | + if (win_foreground_hhook != NULL) { 135 | + UnhookWinEvent(win_foreground_hhook); 136 | + win_foreground_hhook = NULL; 137 | + } 138 | + 139 | + if (win_minimizeend_hhook != NULL) { 140 | + UnhookWinEvent(win_minimizeend_hhook); 141 | + win_minimizeend_hhook = NULL; 142 | + } 143 | + 144 | + if (foreground_timer != 0) { 145 | + KillTimer(NULL, foreground_timer); 146 | + foreground_timer = 0; 147 | } 148 | 149 | // Destroy the native hooks. 150 | @@ -193,31 +226,14 @@ static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) { 151 | // Populate key pressed event. 152 | dispatch_event(&event); 153 | 154 | + if ((event.mask & (MASK_CTRL)) && 155 | + (event.mask & (MASK_ALT)) && 156 | + (event.data.keyboard.keycode == VC_DELETE)) { 157 | + current_modifiers = 0; 158 | + } 159 | + 160 | // If the pressed event was not consumed... 161 | if (event.reserved ^ 0x01) { 162 | - // Buffer for unicode typed chars. No more than 2 needed. 163 | - WCHAR buffer[2]; // = { WCH_NONE }; 164 | - 165 | - // If the pressed event was not consumed and a unicode char exists... 166 | - SIZE_T count = keycode_to_unicode(kbhook->vkCode, buffer, sizeof(buffer)); 167 | - for (unsigned int i = 0; i < count; i++) { 168 | - // Populate key typed event. 169 | - event.time = kbhook->time; 170 | - event.reserved = 0x00; 171 | - 172 | - event.type = EVENT_KEY_TYPED; 173 | - event.mask = get_modifiers(); 174 | - 175 | - event.data.keyboard.keycode = VC_UNDEFINED; 176 | - event.data.keyboard.rawcode = (uint16_t) kbhook->vkCode; 177 | - event.data.keyboard.keychar = buffer[i]; 178 | - 179 | - logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X typed. (%lc)\n", 180 | - __FUNCTION__, __LINE__, event.data.keyboard.keycode, (wint_t) event.data.keyboard.keychar); 181 | - 182 | - // Fire key typed event. 183 | - dispatch_event(&event); 184 | - } 185 | } 186 | } 187 | 188 | @@ -594,40 +610,17 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) 189 | 190 | // Callback function that handles events. 191 | void CALLBACK win_hook_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { 192 | - switch (event) { 193 | - case EVENT_OBJECT_NAMECHANGE: 194 | - logger(LOG_LEVEL_DEBUG, "%s [%u]: Restarting Windows input hook on window event: %#X.\n", 195 | - __FUNCTION__, __LINE__, event); 196 | - 197 | - // Remove any keyboard or mouse hooks that are still running. 198 | - if (keyboard_event_hhook != NULL) { 199 | - UnhookWindowsHookEx(keyboard_event_hhook); 200 | - } 201 | - 202 | - if (mouse_event_hhook != NULL) { 203 | - UnhookWindowsHookEx(mouse_event_hhook); 204 | - } 205 | - 206 | - // Restart the event hooks. 207 | - keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0); 208 | - mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0); 209 | - 210 | - // Re-initialize modifier masks. 211 | - initialize_modifiers(); 212 | - 213 | - // FIXME We should compare the modifier mask before and after the restart 214 | - // to determine if we should synthesize missing events. 215 | + foreground_window = hWnd; 216 | + check_and_update_uipi_state(hWnd); 217 | +} 218 | 219 | - // Check for event hook error. 220 | - if (keyboard_event_hhook == NULL || mouse_event_hhook == NULL) { 221 | - logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n", 222 | - __FUNCTION__, __LINE__, (unsigned long) GetLastError()); 223 | - } 224 | - break; 225 | +static VOID CALLBACK foreground_timer_proc(HWND _hwnd, UINT msg, UINT_PTR timerId, DWORD dwmsEventTime) 226 | +{ 227 | + HWND system_foreground = GetForegroundWindow(); 228 | 229 | - default: 230 | - logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows window event: %#X.\n", 231 | - __FUNCTION__, __LINE__, event); 232 | + if (foreground_window != system_foreground) { 233 | + foreground_window = system_foreground; 234 | + check_and_update_uipi_state(system_foreground); 235 | } 236 | } 237 | 238 | @@ -660,17 +653,22 @@ UIOHOOK_API int hook_run() { 239 | keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0); 240 | mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0); 241 | 242 | - // Create a window event hook to listen for capture change. 243 | - win_event_hhook = SetWinEventHook( 244 | - EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, 245 | - NULL, 246 | - win_hook_event_proc, 247 | - 0, 0, 248 | - WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); 249 | + win_foreground_hhook = SetWinEventHook( 250 | + EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, 251 | + NULL, win_hook_event_proc, 0, 0, WINEVENT_OUTOFCONTEXT); 252 | + win_minimizeend_hhook = SetWinEventHook( 253 | + EVENT_SYSTEM_MINIMIZEEND, EVENT_SYSTEM_MINIMIZEEND, 254 | + NULL, win_hook_event_proc, 0, 0, WINEVENT_OUTOFCONTEXT); 255 | + foreground_timer = SetTimer(NULL, 0, FOREGROUND_TIMER_MS, foreground_timer_proc); 256 | + 257 | + WM_UIOHOOK_UIPI_TEST = RegisterWindowMessage("UIOHOOK_UIPI_TEST"); 258 | + 259 | + foreground_window = GetForegroundWindow(); 260 | + is_blocked_by_uipi = true; // init modifiers 261 | 262 | // If we did not encounter a problem, start processing events. 263 | if (keyboard_event_hhook != NULL && mouse_event_hhook != NULL) { 264 | - if (win_event_hhook == NULL) { 265 | + if (win_foreground_hhook == NULL || win_minimizeend_hhook == NULL) { 266 | logger(LOG_LEVEL_WARN, "%s [%u]: SetWinEventHook() failed!\n", 267 | __FUNCTION__, __LINE__); 268 | } 269 | @@ -678,9 +676,6 @@ UIOHOOK_API int hook_run() { 270 | logger(LOG_LEVEL_DEBUG, "%s [%u]: SetWindowsHookEx() successful.\n", 271 | __FUNCTION__, __LINE__); 272 | 273 | - // Check and setup modifiers. 274 | - initialize_modifiers(); 275 | - 276 | // Set the exit status. 277 | status = UIOHOOK_SUCCESS; 278 | 279 | diff --git a/src/x11/input_hook.c b/src/x11/input_hook.c 280 | index 15c9b9e..dc2f7b9 100644 281 | --- a/src/x11/input_hook.c 282 | +++ b/src/x11/input_hook.c 283 | @@ -26,8 +26,11 @@ 284 | #include 285 | #include 286 | 287 | +#ifdef USE_XKB_COMMON 288 | #include 289 | +#else 290 | #include 291 | +#endif 292 | 293 | #include 294 | #include 295 | -------------------------------------------------------------------------------- /src/lib/addon.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "napi_helpers.h" 6 | #include "uiohook_worker.h" 7 | 8 | static napi_threadsafe_function threadsafe_fn = NULL; 9 | static bool is_worker_running = false; 10 | 11 | void dispatch_proc(uiohook_event* const event) { 12 | if (threadsafe_fn == NULL) return; 13 | 14 | uiohook_event* copied_event = malloc(sizeof(uiohook_event)); 15 | memcpy(copied_event, event, sizeof(uiohook_event)); 16 | if (copied_event->type == EVENT_MOUSE_DRAGGED) { 17 | copied_event->type = EVENT_MOUSE_MOVED; 18 | } 19 | 20 | napi_status status = napi_call_threadsafe_function(threadsafe_fn, copied_event, napi_tsfn_nonblocking); 21 | if (status == napi_closing) { 22 | threadsafe_fn = NULL; 23 | free(copied_event); 24 | return; 25 | } 26 | NAPI_FATAL_IF_FAILED(status, "dispatch_proc", "napi_call_threadsafe_function"); 27 | } 28 | 29 | napi_value uiohook_to_js_event(napi_env env, uiohook_event* event) { 30 | napi_status status; 31 | 32 | napi_value event_obj; 33 | status = napi_create_object(env, &event_obj); 34 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_object"); 35 | 36 | napi_value e_type; 37 | status = napi_create_uint32(env, event->type, &e_type); 38 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32"); 39 | 40 | napi_value e_altKey; 41 | status = napi_get_boolean(env, (event->mask & (MASK_ALT)), &e_altKey); 42 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_get_boolean"); 43 | 44 | napi_value e_ctrlKey; 45 | status = napi_get_boolean(env, (event->mask & (MASK_CTRL)), &e_ctrlKey); 46 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_get_boolean"); 47 | 48 | napi_value e_metaKey; 49 | status = napi_get_boolean(env, (event->mask & (MASK_META)), &e_metaKey); 50 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_get_boolean"); 51 | 52 | napi_value e_shiftKey; 53 | status = napi_get_boolean(env, (event->mask & (MASK_SHIFT)), &e_shiftKey); 54 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_get_boolean"); 55 | 56 | napi_value e_time; 57 | status = napi_create_double(env, (double)event->time, &e_time); 58 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_double"); 59 | 60 | if (event->type == EVENT_KEY_PRESSED || event->type == EVENT_KEY_RELEASED) { 61 | napi_value e_keycode; 62 | status = napi_create_uint32(env, event->data.keyboard.keycode, &e_keycode); 63 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32"); 64 | 65 | napi_property_descriptor descriptors[] = { 66 | { "type", NULL, NULL, NULL, NULL, e_type, napi_enumerable, NULL }, 67 | { "time", NULL, NULL, NULL, NULL, e_time, napi_enumerable, NULL }, 68 | { "altKey", NULL, NULL, NULL, NULL, e_altKey, napi_enumerable, NULL }, 69 | { "ctrlKey", NULL, NULL, NULL, NULL, e_ctrlKey, napi_enumerable, NULL }, 70 | { "metaKey", NULL, NULL, NULL, NULL, e_metaKey, napi_enumerable, NULL }, 71 | { "shiftKey", NULL, NULL, NULL, NULL, e_shiftKey, napi_enumerable, NULL }, 72 | { "keycode", NULL, NULL, NULL, NULL, e_keycode, napi_enumerable, NULL }, 73 | }; 74 | status = napi_define_properties(env, event_obj, sizeof(descriptors) / sizeof(descriptors[0]), descriptors); 75 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_define_properties"); 76 | return event_obj; 77 | } 78 | else if (event->type == EVENT_MOUSE_MOVED || event->type == EVENT_MOUSE_PRESSED || event->type == EVENT_MOUSE_RELEASED || event->type == EVENT_MOUSE_CLICKED) { 79 | napi_value e_x; 80 | status = napi_create_int32(env, event->data.mouse.x, &e_x); 81 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32"); 82 | 83 | napi_value e_y; 84 | status = napi_create_int32(env, event->data.mouse.y, &e_y); 85 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32"); 86 | 87 | napi_value e_button; 88 | status = napi_create_uint32(env, event->data.mouse.button, &e_button); 89 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32"); 90 | 91 | napi_value e_clicks; 92 | status = napi_create_uint32(env, event->data.mouse.clicks, &e_clicks); 93 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32"); 94 | 95 | napi_property_descriptor descriptors[] = { 96 | { "type", NULL, NULL, NULL, NULL, e_type, napi_enumerable, NULL }, 97 | { "time", NULL, NULL, NULL, NULL, e_time, napi_enumerable, NULL }, 98 | { "altKey", NULL, NULL, NULL, NULL, e_altKey, napi_enumerable, NULL }, 99 | { "ctrlKey", NULL, NULL, NULL, NULL, e_ctrlKey, napi_enumerable, NULL }, 100 | { "metaKey", NULL, NULL, NULL, NULL, e_metaKey, napi_enumerable, NULL }, 101 | { "shiftKey", NULL, NULL, NULL, NULL, e_shiftKey, napi_enumerable, NULL }, 102 | { "x", NULL, NULL, NULL, NULL, e_x, napi_enumerable, NULL }, 103 | { "y", NULL, NULL, NULL, NULL, e_y, napi_enumerable, NULL }, 104 | { "button", NULL, NULL, NULL, NULL, e_button, napi_enumerable, NULL }, 105 | { "clicks", NULL, NULL, NULL, NULL, e_clicks, napi_enumerable, NULL }, 106 | }; 107 | status = napi_define_properties(env, event_obj, sizeof(descriptors) / sizeof(descriptors[0]), descriptors); 108 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_define_properties"); 109 | return event_obj; 110 | } 111 | else if (event->type == EVENT_MOUSE_WHEEL) { 112 | napi_value e_x; 113 | status = napi_create_int32(env, event->data.wheel.x, &e_x); 114 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32"); 115 | 116 | napi_value e_y; 117 | status = napi_create_int32(env, event->data.wheel.y, &e_y); 118 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32"); 119 | 120 | napi_value e_clicks; 121 | status = napi_create_uint32(env, event->data.wheel.clicks, &e_clicks); 122 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32"); 123 | 124 | napi_value e_amount; 125 | status = napi_create_uint32(env, event->data.wheel.amount, &e_amount); 126 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32"); 127 | 128 | napi_value e_direction; 129 | status = napi_create_uint32(env, event->data.wheel.direction, &e_direction); 130 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_uint32"); 131 | 132 | napi_value e_rotation; 133 | status = napi_create_int32(env, event->data.wheel.rotation, &e_rotation); 134 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_create_int32"); 135 | 136 | napi_property_descriptor descriptors[] = { 137 | { "type", NULL, NULL, NULL, NULL, e_type, napi_enumerable, NULL }, 138 | { "time", NULL, NULL, NULL, NULL, e_time, napi_enumerable, NULL }, 139 | { "altKey", NULL, NULL, NULL, NULL, e_altKey, napi_enumerable, NULL }, 140 | { "ctrlKey", NULL, NULL, NULL, NULL, e_ctrlKey, napi_enumerable, NULL }, 141 | { "metaKey", NULL, NULL, NULL, NULL, e_metaKey, napi_enumerable, NULL }, 142 | { "shiftKey", NULL, NULL, NULL, NULL, e_shiftKey, napi_enumerable, NULL }, 143 | { "x", NULL, NULL, NULL, NULL, e_x, napi_enumerable, NULL }, 144 | { "y", NULL, NULL, NULL, NULL, e_y, napi_enumerable, NULL }, 145 | { "clicks", NULL, NULL, NULL, NULL, e_clicks, napi_enumerable, NULL }, 146 | { "amount", NULL, NULL, NULL, NULL, e_amount, napi_enumerable, NULL }, 147 | { "direction", NULL, NULL, NULL, NULL, e_direction, napi_enumerable, NULL }, 148 | { "rotation", NULL, NULL, NULL, NULL, e_rotation, napi_enumerable, NULL }, 149 | }; 150 | status = napi_define_properties(env, event_obj, sizeof(descriptors) / sizeof(descriptors[0]), descriptors); 151 | NAPI_FATAL_IF_FAILED(status, "uiohook_to_js_event", "napi_define_properties"); 152 | return event_obj; 153 | } 154 | 155 | return NULL; // never 156 | } 157 | 158 | void tsfn_to_js_proxy(napi_env env, napi_value js_callback, void* context, void* _event) { 159 | uiohook_event* event = (uiohook_event*)_event; 160 | 161 | if (env == NULL || js_callback == NULL || is_worker_running == false) { 162 | free(event); 163 | return; 164 | } 165 | 166 | napi_status status; 167 | 168 | napi_value event_obj = uiohook_to_js_event(env, event); 169 | 170 | napi_value global; 171 | status = napi_get_global(env, &global); 172 | NAPI_FATAL_IF_FAILED(status, "tsfn_to_js_proxy", "napi_get_global"); 173 | 174 | status = napi_call_function(env, global, js_callback, 1, &event_obj, NULL); 175 | NAPI_FATAL_IF_FAILED(status, "tsfn_to_js_proxy", "napi_call_function"); 176 | 177 | free(event); 178 | } 179 | 180 | napi_value AddonStart(napi_env env, napi_callback_info info) { 181 | if (is_worker_running == true) 182 | return NULL; 183 | 184 | napi_status status; 185 | 186 | size_t info_argc = 1; 187 | napi_value info_argv[1]; 188 | status = napi_get_cb_info(env, info, &info_argc, info_argv, NULL, NULL); 189 | NAPI_THROW_IF_FAILED(env, status, NULL); 190 | 191 | napi_value cb = info_argv[0]; 192 | 193 | napi_value async_resource_name; 194 | status = napi_create_string_utf8(env, "UIOHOOK_NAPI", NAPI_AUTO_LENGTH, &async_resource_name); 195 | NAPI_THROW_IF_FAILED(env, status, NULL); 196 | 197 | status = napi_create_threadsafe_function(env, cb, NULL, async_resource_name, 0, 1, NULL, NULL, NULL, tsfn_to_js_proxy, &threadsafe_fn); 198 | NAPI_THROW_IF_FAILED(env, status, NULL); 199 | 200 | int worker_status = uiohook_worker_start(dispatch_proc); 201 | 202 | if (worker_status != UIOHOOK_SUCCESS) { 203 | napi_release_threadsafe_function(threadsafe_fn, napi_tsfn_release); 204 | threadsafe_fn = NULL; 205 | } 206 | 207 | switch (worker_status) { 208 | case UIOHOOK_SUCCESS: { 209 | is_worker_running = true; 210 | return NULL; 211 | } 212 | case UIOHOOK_ERROR_THREAD_CREATE: 213 | NAPI_THROW(env, "UIOHOOK_ERROR_THREAD_CREATE", "Failed to create worker thread.", NULL); 214 | case UIOHOOK_ERROR_OUT_OF_MEMORY: 215 | NAPI_THROW(env, "UIOHOOK_ERROR_OUT_OF_MEMORY", "Failed to allocate memory.", NULL); 216 | case UIOHOOK_ERROR_X_OPEN_DISPLAY: 217 | NAPI_THROW(env, "UIOHOOK_ERROR_X_OPEN_DISPLAY", "Failed to open X11 display.", NULL); 218 | case UIOHOOK_ERROR_X_RECORD_NOT_FOUND: 219 | NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_NOT_FOUND", "Unable to locate XRecord extension.", NULL); 220 | case UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE: 221 | NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE", "Unable to allocate XRecord range.", NULL); 222 | case UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT: 223 | NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT", "Unable to allocate XRecord context.", NULL); 224 | case UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT: 225 | NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT", "Failed to enable XRecord context.", NULL); 226 | case UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX: 227 | NAPI_THROW(env, "UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX", "Failed to register low level windows hook.", NULL); 228 | case UIOHOOK_ERROR_AXAPI_DISABLED: 229 | NAPI_THROW(env, "UIOHOOK_ERROR_AXAPI_DISABLED", "Failed to enable access for assistive devices.", NULL); 230 | case UIOHOOK_ERROR_CREATE_EVENT_PORT: 231 | NAPI_THROW(env, "UIOHOOK_ERROR_CREATE_EVENT_PORT", "Failed to create apple event port.", NULL); 232 | case UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE: 233 | NAPI_THROW(env, "UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE", "Failed to create apple run loop source.", NULL); 234 | case UIOHOOK_ERROR_GET_RUNLOOP: 235 | NAPI_THROW(env, "UIOHOOK_ERROR_GET_RUNLOOP", "Failed to acquire apple run loop.", NULL); 236 | case UIOHOOK_ERROR_CREATE_OBSERVER: 237 | NAPI_THROW(env, "UIOHOOK_ERROR_CREATE_OBSERVER", "Failed to create apple run loop observer.", NULL); 238 | case UIOHOOK_FAILURE: 239 | default: 240 | NAPI_THROW(env, "UIOHOOK_FAILURE", "An unknown hook error occurred.", NULL); 241 | } 242 | } 243 | 244 | napi_value AddonStop(napi_env env, napi_callback_info info) { 245 | if (is_worker_running == false) 246 | return NULL; 247 | 248 | int status = uiohook_worker_stop(); 249 | 250 | switch (status) { 251 | case UIOHOOK_SUCCESS: { 252 | is_worker_running = false; 253 | napi_release_threadsafe_function(threadsafe_fn, napi_tsfn_release); 254 | threadsafe_fn = NULL; 255 | return NULL; 256 | } 257 | case UIOHOOK_ERROR_OUT_OF_MEMORY: 258 | NAPI_THROW(env, "UIOHOOK_ERROR_OUT_OF_MEMORY", "Failed to allocate memory.", NULL); 259 | case UIOHOOK_ERROR_X_RECORD_GET_CONTEXT: 260 | NAPI_THROW(env, "UIOHOOK_ERROR_X_RECORD_GET_CONTEXT", "Failed to get XRecord context.", NULL); 261 | case UIOHOOK_FAILURE: 262 | default: 263 | NAPI_THROW(env, "UIOHOOK_FAILURE", "An unknown hook error occurred.", NULL); 264 | } 265 | } 266 | 267 | void AddonCleanUp (void* arg) { 268 | if (is_worker_running) { 269 | uiohook_worker_stop(); 270 | } 271 | } 272 | 273 | typedef enum { 274 | key_tap, 275 | key_down, 276 | key_up, 277 | force_uint = 0xFFFFFFFF 278 | } key_tap_type; 279 | 280 | napi_value AddonKeyTap (napi_env env, napi_callback_info info) { 281 | napi_status status; 282 | 283 | size_t info_argc = 2; 284 | napi_value info_argv[2]; 285 | status = napi_get_cb_info(env, info, &info_argc, info_argv, NULL, NULL); 286 | NAPI_THROW_IF_FAILED(env, status, NULL); 287 | 288 | // [0] KeyCode 289 | uint32_t keycode; 290 | status = napi_get_value_uint32(env, info_argv[0], &keycode); 291 | NAPI_THROW_IF_FAILED(env, status, NULL); 292 | 293 | // [1] KeyTapType 294 | key_tap_type tap_type; 295 | status = napi_get_value_uint32(env, info_argv[1], (int32_t*)&tap_type); 296 | NAPI_THROW_IF_FAILED(env, status, NULL); 297 | 298 | uiohook_event event; 299 | memset(&event, 0, sizeof(event)); 300 | event.data.keyboard.keycode = keycode; 301 | 302 | if (tap_type != key_up) { 303 | event.type = EVENT_KEY_PRESSED; 304 | hook_post_event(&event); 305 | } 306 | if (tap_type != key_down) { 307 | event.type = EVENT_KEY_RELEASED; 308 | hook_post_event(&event); 309 | } 310 | 311 | return NULL; 312 | } 313 | 314 | NAPI_MODULE_INIT() { 315 | napi_status status; 316 | napi_value export_fn; 317 | 318 | status = napi_create_function(env, NULL, 0, AddonStart, NULL, &export_fn); 319 | NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_create_function"); 320 | status = napi_set_named_property(env, exports, "start", export_fn); 321 | NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_set_named_property"); 322 | 323 | status = napi_create_function(env, NULL, 0, AddonStop, NULL, &export_fn); 324 | NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_create_function"); 325 | status = napi_set_named_property(env, exports, "stop", export_fn); 326 | NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_set_named_property"); 327 | 328 | status = napi_create_function(env, NULL, 0, AddonKeyTap, NULL, &export_fn); 329 | NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_create_function"); 330 | status = napi_set_named_property(env, exports, "keyTap", export_fn); 331 | NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_set_named_property"); 332 | 333 | status = napi_add_env_cleanup_hook(env, AddonCleanUp, NULL); 334 | NAPI_FATAL_IF_FAILED(status, "NAPI_MODULE_INIT", "napi_add_env_cleanup_hook"); 335 | 336 | return exports; 337 | } 338 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@cspotcode/source-map-support@^0.8.0": 6 | version "0.8.1" 7 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" 8 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== 9 | dependencies: 10 | "@jridgewell/trace-mapping" "0.3.9" 11 | 12 | "@jridgewell/resolve-uri@^3.0.3": 13 | version "3.1.0" 14 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" 15 | integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== 16 | 17 | "@jridgewell/sourcemap-codec@^1.4.10": 18 | version "1.4.14" 19 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" 20 | integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== 21 | 22 | "@jridgewell/trace-mapping@0.3.9": 23 | version "0.3.9" 24 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" 25 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== 26 | dependencies: 27 | "@jridgewell/resolve-uri" "^3.0.3" 28 | "@jridgewell/sourcemap-codec" "^1.4.10" 29 | 30 | "@tsconfig/node10@^1.0.7": 31 | version "1.0.9" 32 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" 33 | integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== 34 | 35 | "@tsconfig/node12@^1.0.7": 36 | version "1.0.11" 37 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" 38 | integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== 39 | 40 | "@tsconfig/node14@^1.0.0": 41 | version "1.0.3" 42 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" 43 | integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== 44 | 45 | "@tsconfig/node16@^1.0.2": 46 | version "1.0.3" 47 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" 48 | integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== 49 | 50 | "@types/node@18.x.x": 51 | version "18.7.1" 52 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.1.tgz#352bee64f93117d867d05f7406642a52685cbca6" 53 | integrity sha512-GKX1Qnqxo4S+Z/+Z8KKPLpH282LD7jLHWJcVryOflnsnH+BtSDfieR6ObwBMwpnNws0bUK8GI7z0unQf9bARNQ== 54 | 55 | acorn-walk@^8.1.1: 56 | version "8.2.0" 57 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" 58 | integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== 59 | 60 | acorn@^8.4.1: 61 | version "8.8.0" 62 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" 63 | integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== 64 | 65 | arg@^4.1.0: 66 | version "4.1.3" 67 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 68 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 69 | 70 | base64-js@^1.3.1: 71 | version "1.5.1" 72 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 73 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 74 | 75 | bl@^4.0.3: 76 | version "4.1.0" 77 | resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" 78 | integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== 79 | dependencies: 80 | buffer "^5.5.0" 81 | inherits "^2.0.4" 82 | readable-stream "^3.4.0" 83 | 84 | buffer@^5.5.0: 85 | version "5.7.1" 86 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" 87 | integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== 88 | dependencies: 89 | base64-js "^1.3.1" 90 | ieee754 "^1.1.13" 91 | 92 | chownr@^1.1.1: 93 | version "1.1.4" 94 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" 95 | integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== 96 | 97 | create-require@^1.1.0: 98 | version "1.1.1" 99 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 100 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 101 | 102 | diff@^4.0.1: 103 | version "4.0.2" 104 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 105 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 106 | 107 | end-of-stream@^1.1.0, end-of-stream@^1.4.1: 108 | version "1.4.4" 109 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 110 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 111 | dependencies: 112 | once "^1.4.0" 113 | 114 | execspawn@^1.0.1: 115 | version "1.0.1" 116 | resolved "https://registry.yarnpkg.com/execspawn/-/execspawn-1.0.1.tgz#8286f9dde7cecde7905fbdc04e24f368f23f8da6" 117 | integrity sha512-s2k06Jy9i8CUkYe0+DxRlvtkZoOkwwfhB+Xxo5HGUtrISVW2m98jO2tr67DGRFxZwkjQqloA3v/tNtjhBRBieg== 118 | dependencies: 119 | util-extend "^1.0.1" 120 | 121 | fs-constants@^1.0.0: 122 | version "1.0.0" 123 | resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" 124 | integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== 125 | 126 | ieee754@^1.1.13: 127 | version "1.2.1" 128 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 129 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 130 | 131 | inherits@^2.0.3, inherits@^2.0.4: 132 | version "2.0.4" 133 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 134 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 135 | 136 | lru-cache@^6.0.0: 137 | version "6.0.0" 138 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 139 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 140 | dependencies: 141 | yallist "^4.0.0" 142 | 143 | make-error@^1.1.1: 144 | version "1.3.6" 145 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 146 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 147 | 148 | minimist@^1.2.5: 149 | version "1.2.6" 150 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" 151 | integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== 152 | 153 | mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: 154 | version "0.5.3" 155 | resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" 156 | integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== 157 | 158 | node-abi@^3.3.0: 159 | version "3.24.0" 160 | resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.24.0.tgz#b9d03393a49f2c7e147d0c99f180e680c27c1599" 161 | integrity sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw== 162 | dependencies: 163 | semver "^7.3.5" 164 | 165 | node-gyp-build@4.x.x: 166 | version "4.5.0" 167 | resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" 168 | integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== 169 | 170 | npm-run-path@^3.1.0: 171 | version "3.1.0" 172 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-3.1.0.tgz#7f91be317f6a466efed3c9f2980ad8a4ee8b0fa5" 173 | integrity sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg== 174 | dependencies: 175 | path-key "^3.0.0" 176 | 177 | once@^1.3.1, once@^1.4.0: 178 | version "1.4.0" 179 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 180 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 181 | dependencies: 182 | wrappy "1" 183 | 184 | path-key@^3.0.0: 185 | version "3.1.1" 186 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 187 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 188 | 189 | prebuildify@5.x.x: 190 | version "5.0.1" 191 | resolved "https://registry.yarnpkg.com/prebuildify/-/prebuildify-5.0.1.tgz#e10bb6e4986c18909185704c806cc06976c30478" 192 | integrity sha512-vXpKLfIEsDCqMJWVIoSrUUBJQIuAk9uHAkLiGJuTdXdqKSJ10sHmWeuNCDkIoRFTV1BDGYMghHVmDFP8NfkA2Q== 193 | dependencies: 194 | execspawn "^1.0.1" 195 | minimist "^1.2.5" 196 | mkdirp-classic "^0.5.3" 197 | node-abi "^3.3.0" 198 | npm-run-path "^3.1.0" 199 | pump "^3.0.0" 200 | tar-fs "^2.1.0" 201 | 202 | pump@^3.0.0: 203 | version "3.0.0" 204 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 205 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 206 | dependencies: 207 | end-of-stream "^1.1.0" 208 | once "^1.3.1" 209 | 210 | readable-stream@^3.1.1, readable-stream@^3.4.0: 211 | version "3.6.0" 212 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 213 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 214 | dependencies: 215 | inherits "^2.0.3" 216 | string_decoder "^1.1.1" 217 | util-deprecate "^1.0.1" 218 | 219 | safe-buffer@~5.2.0: 220 | version "5.2.1" 221 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 222 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 223 | 224 | semver@^7.3.5: 225 | version "7.3.7" 226 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" 227 | integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== 228 | dependencies: 229 | lru-cache "^6.0.0" 230 | 231 | string_decoder@^1.1.1: 232 | version "1.3.0" 233 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 234 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 235 | dependencies: 236 | safe-buffer "~5.2.0" 237 | 238 | tar-fs@^2.1.0: 239 | version "2.1.1" 240 | resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" 241 | integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== 242 | dependencies: 243 | chownr "^1.1.1" 244 | mkdirp-classic "^0.5.2" 245 | pump "^3.0.0" 246 | tar-stream "^2.1.4" 247 | 248 | tar-stream@^2.1.4: 249 | version "2.2.0" 250 | resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" 251 | integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== 252 | dependencies: 253 | bl "^4.0.3" 254 | end-of-stream "^1.4.1" 255 | fs-constants "^1.0.0" 256 | inherits "^2.0.3" 257 | readable-stream "^3.1.1" 258 | 259 | ts-node@10.x.x: 260 | version "10.9.1" 261 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" 262 | integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== 263 | dependencies: 264 | "@cspotcode/source-map-support" "^0.8.0" 265 | "@tsconfig/node10" "^1.0.7" 266 | "@tsconfig/node12" "^1.0.7" 267 | "@tsconfig/node14" "^1.0.0" 268 | "@tsconfig/node16" "^1.0.2" 269 | acorn "^8.4.1" 270 | acorn-walk "^8.1.1" 271 | arg "^4.1.0" 272 | create-require "^1.1.0" 273 | diff "^4.0.1" 274 | make-error "^1.1.1" 275 | v8-compile-cache-lib "^3.0.1" 276 | yn "3.1.1" 277 | 278 | typescript@4.x.x: 279 | version "4.7.4" 280 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" 281 | integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== 282 | 283 | util-deprecate@^1.0.1: 284 | version "1.0.2" 285 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 286 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 287 | 288 | util-extend@^1.0.1: 289 | version "1.0.3" 290 | resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" 291 | integrity sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA== 292 | 293 | v8-compile-cache-lib@^3.0.1: 294 | version "3.0.1" 295 | resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" 296 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== 297 | 298 | wrappy@1: 299 | version "1.0.2" 300 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 301 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 302 | 303 | yallist@^4.0.0: 304 | version "4.0.0" 305 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 306 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 307 | 308 | yn@3.1.1: 309 | version "3.1.1" 310 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 311 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 312 | --------------------------------------------------------------------------------