Capturing global keyboard shortcuts like Ctrl + C
or Shift + Alt + S
can be incredibly useful for building productivity tools, real-time overlays, or accessibility utilities. But doing it cleanly β without logging noisy or repeated key events β requires careful handling of modifier keys and key state.
This post walks through how to build a global key combo tracker in Node.js on Windows, and explains the logic behind each part of the implementation.
π§© The Challenge
Most global key listeners log every key event β including repeated Ctrl
, Shift
, or Alt
presses β which leads to noisy, unreadable logs like:
LEFT CTRL
LEFT CTRL
LEFT CTRL
A
To build a clean and useful tracker, we need to:
- β Ignore modifier keys when pressed alone
- β
Log only meaningful combinations (e.g.
Ctrl + A
) - β
Normalize key names like
LEFT CTRL
βCtrl
- β Prevent repeated logs while keys are held
- β Support global tracking, even when the app is not focused
βοΈ Implementation
1. Install the Listener
We use node-global-key-listener
, a native module that captures global key events across the OS.
npm install node-global-key-listener
2. Core Logic
const { GlobalKeyboardListener } = require("node-global-key-listener");
const keyboard = new GlobalKeyboardListener();
const heldModifiers = new Set();
const loggedCombos = new Set();
const MODIFIERS = new Set([
"LEFT CTRL", "RIGHT CTRL",
"LEFT SHIFT", "RIGHT SHIFT",
"LEFT ALT", "RIGHT ALT",
"LEFT META", "RIGHT META"
]);
keyboard.addListener((e) => {
const key = e.name.toUpperCase();
if (e.state === "DOWN") {
if (MODIFIERS.has(key)) {
heldModifiers.add(normalizeModifier(key));
} else {
const combo = heldModifiers.size > 0
? [...heldModifiers, key].join(" + ")
: key;
if (!loggedCombos.has(combo)) {
loggedCombos.add(combo);
console.log(combo);
}
}
} else if (e.state === "UP") {
if (MODIFIERS.has(key)) {
heldModifiers.delete(normalizeModifier(key));
} else {
loggedCombos.clear();
}
}
});
function normalizeModifier(key) {
if (key.includes("CTRL")) return "Ctrl";
if (key.includes("SHIFT")) return "Shift";
if (key.includes("ALT")) return "Alt";
if (key.includes("META")) return "Meta";
return key;
}
π§ Why This Works
πΉ Modifier Filtering
We define a set of known modifier keys (LEFT CTRL
, RIGHT SHIFT
, etc.) and track them in heldModifiers
. These keys are ignored when pressed alone β they only contribute to a combo.
πΉ Normalization
The OS reports keys like LEFT CTRL
and RIGHT CTRL
separately. For clarity and consistency, we normalize them to a single label (Ctrl
, Shift
, etc.) using the normalizeModifier()
function.
This ensures that:
-
LEFT CTRL + A
andRIGHT CTRL + A
both log asCtrl + A
- The output is clean and human-readable
πΉ Combo Construction
When a non-modifier key is pressed:
- If modifiers are held β log the full combo (e.g.
Ctrl + A
) - If no modifiers are held β log the key alone (e.g.
A
)
πΉ Duplicate Prevention
We use a loggedCombos
set to ensure each combo is logged only once per press. This avoids repeated logs when holding keys.
πΉ Reset on Key Release
When a non-modifier key is released, we clear the loggedCombos
set so the next press can be logged again.
π§ͺ Example Output
Ctrl + C
Shift + Alt + S
Escape
A
- β
Ctrl
alone β ignored - β
A
β logged - β
Ctrl + A
β logged once - β Holding keys β no spam
π Whatβs Next?
This Node.js core is a clean foundation for more advanced tools. You could extend it by:
- πΌοΈ Wrapping it in an Electron app with a floating overlay
- πΎ Logging combos to a file or clipboard
- π¨ Styling the UI with themes and transparency
- π¦ Packaging it as a cross-platform desktop utility
π§ Conclusion
This project demonstrates how to:
- Handle global keyboard input in Node.js
- Filter and normalize noisy key events
- Design a clean shortcut-tracking system
Itβs a great one-day build with real-world utility β and a perfect launchpad for more advanced desktop tools.
You can find the full source code on GitHub: global-key-logger.
Top comments (0)