From 49749407fa2d767e908cf90974b17f3f690f84d2 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 4 Mar 2026 18:14:29 +0100 Subject: [PATCH] fix(tui): ignore unsupported Kitty CSI-u modifiers closes #1807 --- packages/tui/CHANGELOG.md | 1 + packages/tui/src/components/editor.ts | 7 ++++++- packages/tui/src/keys.ts | 2 ++ packages/tui/test/editor.test.ts | 10 ++++++++++ packages/tui/test/keys.test.ts | 6 ++++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index ca37f7e2..d39ee8f2 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Fixed TUI width calculation for regional indicator symbols (e.g. partial flag sequences like `🇨` during streaming) to prevent wrap drift and stale character artifacts in differential rendering. +- Fixed Kitty CSI-u handling to ignore unsupported modifiers so modifier-only events do not insert stray printable characters ([#1807](https://github.com/badlogic/pi-mono/issues/1807)) ## [0.55.4] - 2026-03-02 diff --git a/packages/tui/src/components/editor.ts b/packages/tui/src/components/editor.ts index a4551fde..f6182e01 100644 --- a/packages/tui/src/components/editor.ts +++ b/packages/tui/src/components/editor.ts @@ -96,6 +96,8 @@ const KITTY_CSI_U_REGEX = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+ const KITTY_MOD_SHIFT = 1; const KITTY_MOD_ALT = 2; const KITTY_MOD_CTRL = 4; +const KITTY_LOCK_MASK = 64 + 128; // Caps Lock + Num Lock +const KITTY_ALLOWED_MODIFIERS = KITTY_MOD_SHIFT | KITTY_LOCK_MASK; // Decode a printable CSI-u sequence, preferring the shifted key when present. function decodeKittyPrintable(data: string): string | undefined { @@ -111,7 +113,10 @@ function decodeKittyPrintable(data: string): string | undefined { // Modifiers are 1-indexed in CSI-u; normalize to our bitmask. const modifier = Number.isFinite(modValue) ? modValue - 1 : 0; - // Ignore CSI-u sequences used for Alt/Ctrl shortcuts. + // Only accept printable CSI-u input for plain or Shift-modified text keys. + // Reject unsupported modifier bits (e.g. Super/Meta) to avoid inserting + // characters from modifier-only terminal events. + if ((modifier & ~KITTY_ALLOWED_MODIFIERS) !== 0) return undefined; if (modifier & (KITTY_MOD_ALT | KITTY_MOD_CTRL)) return undefined; // Prefer the shifted keycode when Shift is held. diff --git a/packages/tui/src/keys.ts b/packages/tui/src/keys.ts index aba7a5da..829da434 100644 --- a/packages/tui/src/keys.ts +++ b/packages/tui/src/keys.ts @@ -1048,6 +1048,8 @@ export function parseKey(data: string): string | undefined { const { codepoint, baseLayoutKey, modifier } = kitty; const mods: string[] = []; const effectiveMod = modifier & ~LOCK_MASK; + const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt; + if ((effectiveMod & ~supportedModifierMask) !== 0) return undefined; if (effectiveMod & MODIFIERS.shift) mods.push("shift"); if (effectiveMod & MODIFIERS.ctrl) mods.push("ctrl"); if (effectiveMod & MODIFIERS.alt) mods.push("alt"); diff --git a/packages/tui/test/editor.test.ts b/packages/tui/test/editor.test.ts index 842a1666..4d02374c 100644 --- a/packages/tui/test/editor.test.ts +++ b/packages/tui/test/editor.test.ts @@ -361,6 +361,16 @@ describe("Editor component", () => { }); }); + describe("Kitty CSI-u handling", () => { + it("ignores printable CSI-u sequences with unsupported modifiers", () => { + const editor = new Editor(createTestTUI(), defaultEditorTheme); + + editor.handleInput("\x1b[99;9u"); + + assert.strictEqual(editor.getText(), ""); + }); + }); + describe("Unicode text editing behavior", () => { it("inserts mixed ASCII, umlauts, and emojis as literal text", () => { const editor = new Editor(createTestTUI(), defaultEditorTheme); diff --git a/packages/tui/test/keys.test.ts b/packages/tui/test/keys.test.ts index d307c7b9..d31fa7f4 100644 --- a/packages/tui/test/keys.test.ts +++ b/packages/tui/test/keys.test.ts @@ -294,6 +294,12 @@ describe("parseKey", () => { assert.strictEqual(parseKey(latinCtrlC), "ctrl+c"); setKittyProtocolActive(false); }); + + it("should ignore Kitty CSI-u with unsupported modifiers", () => { + setKittyProtocolActive(true); + assert.strictEqual(parseKey("\x1b[99;9u"), undefined); + setKittyProtocolActive(false); + }); }); describe("Legacy key parsing", () => {