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 { | |
| 246 | 246 | if (mod_encoded < 1) return false; | |
| 247 | 247 | const mod_raw = mod_encoded - 1; | |
| 248 | 248 | ||
| 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; | |
| 251 | 254 | ||
| 252 | 255 | // 6. Parse optional event type after ':'. | |
| 253 | 256 | if (pos < buf.len and buf[pos] == ':') { |
| ... | ... | @@ -634,14 +637,22 @@ test "isKittyCtrlBackslash" { | |
| 634 | 637 | // ctrl + caps_lock + num_lock = 1 + (4 + 64 + 128) = 197 | |
| 635 | 638 | try expect(isKittyCtrlBackslash("\x1b[92;197u")); | |
| 636 | 639 | ||
| 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")); | |
| 639 | 644 | ||
| 640 | 645 | // ctrl + alt = 1 + (4 + 2) = 7 | |
| 641 | - | try expect(isKittyCtrlBackslash("\x1b[92;7u")); | |
| 646 | + | try expect(!isKittyCtrlBackslash("\x1b[92;7u")); | |
| 642 | 647 | ||
| 643 | 648 | // 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")); | |
| 645 | 656 | ||
| 646 | 657 | // Modifier without ctrl bit — must NOT match | |
| 647 | 658 | // shift only = 1 + 1 = 2 |