Commit a9dc983

Patrik Sundberg  ·  2026-03-17 13:29:14 -0400 EDT
parent 46b02ee
fix(attach): reject combined intentional modifiers for Ctrl+\ detach

The parser accepted any modifier combination that included ctrl
(ctrl+shift, ctrl+alt, ctrl+super, etc.) because it only checked
the ctrl bit. This was over-permissive: ctrl+shift+\ is a different
key combination and should not trigger detach.

Tighten the check to require ctrl as the ONLY intentional modifier.
Lock modifiers (caps_lock, num_lock) remain tolerated since they are
ambient state, not deliberate key combinations.
1 files changed,  +17, -6
+17, -6
......@@ -246,8 +246,11 @@ fn parseKittyCtrlBackslash(buf: []const u8) bool {
246246 if (mod_encoded < 1) return false;
247247 const mod_raw = mod_encoded - 1;
248248
249- // 5. Ctrl bit (0b100 = 4) must be set.
250- if (mod_raw & 0b100 == 0) return false;
249+ // 5. Ctrl must be the only intentional modifier. Lock modifiers
250+ // (caps_lock=0b1000000, num_lock=0b10000000) are tolerated because
251+ // they are ambient state, not deliberate key combinations.
252+ const intentional_mods = mod_raw & 0b00111111;
253+ if (intentional_mods != 0b100) return false;
251254
252255 // 6. Parse optional event type after ':'.
253256 if (pos < buf.len and buf[pos] == ':') {
......@@ -634,14 +637,22 @@ test "isKittyCtrlBackslash" {
634637 // ctrl + caps_lock + num_lock = 1 + (4 + 64 + 128) = 197
635638 try expect(isKittyCtrlBackslash("\x1b[92;197u"));
636639
637- // Combined modifiers: ctrl + shift = 1 + (4 + 1) = 6
638- try expect(isKittyCtrlBackslash("\x1b[92;6u"));
640+ // Combined intentional modifiers — must NOT match (ctrl+\ is the
641+ // detach key, not ctrl+shift+\ or ctrl+alt+\)
642+ // ctrl + shift = 1 + (4 + 1) = 6
643+ try expect(!isKittyCtrlBackslash("\x1b[92;6u"));
639644
640645 // ctrl + alt = 1 + (4 + 2) = 7
641- try expect(isKittyCtrlBackslash("\x1b[92;7u"));
646+ try expect(!isKittyCtrlBackslash("\x1b[92;7u"));
642647
643648 // ctrl + super = 1 + (4 + 8) = 13
644- try expect(isKittyCtrlBackslash("\x1b[92;13u"));
649+ try expect(!isKittyCtrlBackslash("\x1b[92;13u"));
650+
651+ // ctrl + shift + caps_lock = 1 + (1 + 4 + 64) = 70 — shift is intentional
652+ try expect(!isKittyCtrlBackslash("\x1b[92;70u"));
653+
654+ // ctrl + shift + num_lock = 1 + (1 + 4 + 128) = 134 — shift is intentional
655+ try expect(!isKittyCtrlBackslash("\x1b[92;134u"));
645656
646657 // Modifier without ctrl bit — must NOT match
647658 // shift only = 1 + 1 = 2