uiohook-napi

Native N-API bindings for libuiohook, delivering cross-platform global keyboard and mouse hooks straight to Node.js.

Library
npm
v1.5.5
242stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
37/100Needs Attention
Development Activity4
Maintenance0
Community64
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
56/100Fair
Architecture78
Code Quality45
Innovation55
Learning Curve45

uiohook-napi wraps the C libuiohook project in a native Node.js N-API addon, giving JavaScript and TypeScript applications a direct, low-level channel into the operating system’s global keyboard and mouse hook APIs — the same low-level input feed used by remote-control tools, accessibility software, and automation frameworks. Instead of reimplementing platform-specific hook code for Win32, X11, and macOS Carbon/IOKit for every project, developers install a single npm package that ships prebuilt native binaries for each OS/architecture combination.

Events surface through a familiar Node EventEmitter interface (uIOhook.on(‘keydown’, …), ‘mouseup’, ‘wheel’, etc.), with a background worker thread bridging OS-level callbacks to the JS event loop via a napi_threadsafe_function so hook events never block or get dropped. The library also exposes keyTap/keyToggle helpers for synthesizing key presses, making it useful for both input-capture and input-injection use cases such as global hotkey managers, screen-recording/annotation tools, and remote-desktop clients.

What You Get

  • Cross-platform global hooks — a single API surface backed by native implementations for Windows (SetWindowsHookEx), Linux/X11 (XRecord), and macOS (Carbon/IOKit)
  • Prebuilt native binaries via prebuildify/node-gyp-build, so consumers install without needing a C toolchain
  • A typed EventEmitter API (keydown, keyup, mousedown, mouseup, mousemove, click, wheel, input) with TypeScript definitions included
  • Key synthesis helpers (keyTap, keyToggle) for programmatically triggering key presses with modifier support

Common Use Cases

  • Building global hotkey / shortcut managers that need to react to key combinations even when the app isn’t focused
  • Recording user input for screen-recording, tutorial, or macro-replay tools
  • Powering remote-desktop or remote-control software that needs to both observe and inject input events
  • Implementing accessibility or automation tooling that requires OS-level input monitoring outside the browser sandbox

Under The Hood

Architecture The project is a thin layered design: a TypeScript wrapper (src/index.ts) around a native N-API addon (src/lib/addon.c) that spawns and stops a worker (src/lib/uiohook_worker.c/h) running the vendored libuiohook C library — pulled in as a git submodule and compiled as a separate static_library target in binding.gyp — on a background thread. Raw uiohook_event structs are bridged back to JS through a napi_threadsafe_function (dispatch_proc, tsfn_to_js_proxy), normalized into plain JS objects (uiohook_to_js_event), and re-emitted through a Node EventEmitter subclass (UiohookNapi) as typed sub-events (keydown, mouseup, wheel, etc.). Changing the threadsafe-function/worker-thread lifecycle is the highest-risk spot: AddonCleanUp explicitly guards against native callbacks firing into a torn-down JS environment.

Tech Stack The only runtime dependency is node-gyp-build (^4.8.4), which loads the correct prebuilt native binary per platform/arch; devDependencies are prebuildify (^6.0.1) for producing those prebuilds, typescript (^5.9.3) with @types/node (^18.19.130) for the TS wrapper, and ts-node (^10.9.2) to run the demo script. The native side is plain C (C99) compiled through node-gyp/binding.gyp with per-OS conditions — Win32 SetWindowsHookEx APIs, X11 XRecord (-lX11 -lXrandr -lXtst -lXt) on Linux, and Carbon/IOKit/ApplicationServices/AppKit frameworks on macOS. There is no web/ORM framework or database involved — it’s a low-level native systems binding distributed as prebuilt binaries so consumers skip a C toolchain at install time.

Code Quality No test files or test framework exist in the repository — the package.json “test” script points at dist/prebuild-test-noop.ts, a stub prebuildify uses only to confirm the compiled binary loads, not a real test suite. There is no ESLint or Prettier configuration. The TypeScript wrapper does use strict mode (tsconfig.json: strict, alwaysStrict) and typed interfaces for every event shape, and the C addon consistently uses NAPI_THROW/NAPI_FATAL_IF_FAILED macros for explicit native error handling rather than swallowing failures. CI (.github/workflows/ci.yml) builds and packages cross-platform prebuilds (Windows/Linux/macOS, x64/arm64, plus loong64 on Linux) but does not run any automated tests.

API Design The public surface is intentionally small: a single uIOhook singleton extending EventEmitter, with on(‘keydown’|‘keyup’|‘mousedown’|‘mouseup’|‘mousemove’|‘click’|‘wheel’|‘input’, …) plus keyTap/keyToggle for synthesis. This keeps the getting-started boilerplate minimal — start(), listen, done — at the cost of exposing raw platform scan-code integers (UiohookKey) rather than a normalized cross-platform key-name abstraction, so consumers must map codes back to names themselves via the exported constant map.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search