kitty-keyboard
v0.1.0
Published
DOM-like keyboard events for the terminal over the Kitty keyboard protocol, with a keydown-only legacy fallback. Zero dependencies.
Maintainers
Readme
kitty-keyboard
DOM-like keyboard events for the terminal over the Kitty keyboard protocol, with a keydown-only legacy fallback. Zero runtime dependencies.
Terminals normally hand a program a stream of raw bytes on stdin. You get a keydown for a printable character and nothing else. No key release, no auto repeat, and modifier-only presses are invisible. The Kitty keyboard protocol fixes that. On a terminal that speaks it (kitty, ghostty, foot, WezTerm) you get real keyup events, key-repeat events, the full modifier set (Ctrl, Shift, Alt, Meta/Super/Hyper), and the associated text a key produced. On terminals without it, the library degrades to keydown-only events parsed from the legacy byte stream, so your code runs everywhere.
Install
npm install kitty-keyboardQuick start
import { createTerminalKeyboard } from "kitty-keyboard";
const keyboard = await createTerminalKeyboard();
const held = new Set<string>();
keyboard.on("keydown", (event) => {
// Ctrl-C arrives as a key event, not a signal, so handle exit yourself.
if (event.key === "q" || (event.ctrlKey && event.key.toLowerCase() === "c")) {
keyboard.dispose();
process.exit(0);
}
held.add(event.code);
console.log(`down ${event.key} (${event.code}) held: ${[...held].join(", ")}`);
});
keyboard.on("keyup", (event) => {
held.delete(event.code);
});
if (!keyboard.protocolEnabled) {
console.log("legacy terminal: keydown only, no keyup or repeat");
}keyboard.protocolEnabled tells you whether the enhanced protocol is active. It
is true only on a real TTY whose terminal supports the protocol.
Events
Each listener receives a TerminalKeyboardEvent, shaped after the DOM
KeyboardEvent.
type-"keydown"or"keyup".key- the typed value, for example"a","A","ArrowUp", or"Enter".code- the physical key, for example"KeyA","ArrowUp", or"ShiftLeft". Stable across keyboard layouts.repeat-truewhen the event is an auto-repeat (protocol terminals only).ctrlKey,shiftKey,altKey,metaKey- whether each modifier was held. Super and Hyper collapse intometaKey, matching the DOM.getModifierState(name)- per-modifier query for"Shift","Control","Alt","Meta","Super","Hyper","CapsLock", and"NumLock".
Use key when you care about the character the user typed and code when you
care about the physical key position (for example WASD or arrow movement).
Graceful degradation
The parser handles both the disambiguated Kitty form and legacy byte sequences,
so keydown events arrive on every terminal. Keyup and key-repeat events arrive
only where the protocol is active. Check keyboard.protocolEnabled to decide
which model to use.
- Protocol on: track held keys with a Set, add on
keydown, remove onkeyup, and drive continuous motion from the held set. - Protocol off: no keyup ever arrives, so treat each
keydownas one discrete key press and act on it immediately.
Terminal state
Constructing a keyboard puts stdin into raw mode and, when the protocol is
supported, writes the enable sequence. Call keyboard.dispose() to detach the
input listener, pop the protocol flags, and restore stdin to its prior raw and
paused state. dispose() is idempotent.
There is no auto-dispose. The app owns its exit, including Ctrl-C, which arrives
as a key event (event.ctrlKey with event.key === "c") rather than a signal.
Handle it in your keydown listener, call dispose(), then exit.
TerminalKeyboard implements Symbol.dispose, so a using binding restores the
terminal when the scope ends.
using keyboard = await createTerminalKeyboard();
// ... raw mode is restored automatically when this scope exitsAPI
TerminalKeyboard/createTerminalKeyboard()- the event source. Prefer the async factory, which runs the support probe before constructing.TerminalKeyboardEvent- the DOM-shaped event passed to listeners.toKeyboardEvent()maps aRawKeyEventto one directly.detectKittyKeyboardSupport()/getKittyKeyboardSupported()/resetKittyKeyboardDetection()- the protocol support probe trio.parseKeyEvents()- the pure byte-to-event parser, usable on its own.buildKittyKeyboardEnableSequence()/buildKittyKeyboardDisableSequence()/buildKittyKeyboardQuery()- protocol escape-sequence builders, plus theKITTY_KB_*andKITTY_KEYBOARD_FLAGS_FULLflag constants.
Further reading
- The Kitty keyboard protocol specification - the escape sequences, flags, and event encoding this library implements.
