diff --git a/packages/runtime/src/composables/useInput.ts b/packages/runtime/src/composables/useInput.ts index 6119f96..0bb96bf 100644 --- a/packages/runtime/src/composables/useInput.ts +++ b/packages/runtime/src/composables/useInput.ts @@ -19,6 +19,11 @@ export interface Key { backspace: boolean; delete: boolean; meta: boolean; + super: boolean; + hyper: boolean; + capsLock: boolean; + numLock: boolean; + eventType?: "press" | "repeat" | "release"; } export interface UseInputOptions { @@ -37,6 +42,7 @@ export function useInput( function listener(data: string) { const keypress = parseKeypress(data); + if (keypress.ignore) return; const key: Key = { upArrow: keypress.name === "up", @@ -55,20 +61,32 @@ export function useInput( backspace: keypress.name === "backspace", delete: keypress.name === "delete", meta: keypress.meta, + super: keypress.super ?? false, + hyper: keypress.hyper ?? false, + capsLock: keypress.capsLock ?? false, + numLock: keypress.numLock ?? false, + eventType: keypress.eventType, }; let input: string; - if (keypress.ctrl) { + if (keypress.isKittyProtocol) { + if (keypress.isPrintable) { + input = keypress.text ?? keypress.name; + } else if (keypress.ctrl && keypress.name.length === 1) { + input = keypress.name; + } else { + input = ""; + } + } else if (keypress.ctrl) { input = keypress.name ?? ""; } else { input = keypress.sequence; } - if (nonAlphanumericKeys.includes(keypress.name)) { + if (!keypress.isKittyProtocol && nonAlphanumericKeys.includes(keypress.name)) { input = ""; } - // Strip escape prefix from incomplete sequences if (input.startsWith("\x1b")) { input = input.slice(1); } @@ -77,8 +95,10 @@ export function useInput( key.shift = true; } - // exitOnCtrlC skip: don't call user handler for Ctrl+C when intercepted + // exitOnCtrlC: intercept kitty Ctrl+C (\x1b[3;5u). Legacy \x03 is caught + // by emitInput in createStdinController and never reaches useInput. if (input === "c" && key.ctrl && stdin?.internal_exitOnCtrlC) { + app!.exit(); return; } diff --git a/packages/runtime/src/io/parse-keypress.ts b/packages/runtime/src/io/parse-keypress.ts index 070e6bf..b2f0702 100644 --- a/packages/runtime/src/io/parse-keypress.ts +++ b/packages/runtime/src/io/parse-keypress.ts @@ -7,6 +7,8 @@ const metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/; const fnKeyRe = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/; +const kittyQueryResponseRe = /^\x1b\[\?\d+u$/; + const keyName: Record = { /* xterm/gnome ESC O letter */ OP: "f1", @@ -131,6 +133,7 @@ export interface Keypress { * Only set by the kitty protocol parser. */ isPrintable?: boolean; + ignore?: boolean; } // Kitty keyboard protocol modifier bits. @@ -421,6 +424,10 @@ export function parseKeypress(s: Uint8Array | string = ""): Keypress { s = ""; } + if (kittyQueryResponseRe.test(s)) { + return { name: "", ctrl: false, meta: false, shift: false, sequence: s, raw: s, ignore: true }; + } + // Try kitty keyboard protocol parsers first const kittyResult = parseKittyKeypress(s); if (kittyResult) return kittyResult;