aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordivens <divens@users.noreply.github.com>2026-04-03 10:04:41 +0200
committerGitHub <noreply@github.com>2026-04-03 10:04:41 +0200
commitcbe0237b1e4d36bec7cb51103179f0473920f3a0 (patch)
tree8b70edb5c3cef5481a83d00486ca276f33dcdcaf
parentfix(web-client): use base URL when switching sessions (#4981) (diff)
downloadsl0ppij-cbe0237b1e4d36bec7cb51103179f0473920f3a0.tar.gz
sl0ppij-cbe0237b1e4d36bec7cb51103179f0473920f3a0.tar.xz
sl0ppij-cbe0237b1e4d36bec7cb51103179f0473920f3a0.zip
fix(windows): encode modifiers in raw bytes for non-Char keys (#4967)
On the native console input path, crossterm captures modifier flags from INPUT_RECORD but the raw bytes written to the PTY don't include them. This causes inner applications to never see ALT, SHIFT, or CTRL modifiers for keys like Enter, Tab, Backspace, arrows, and function keys. Encode modifiers using standard VT conventions: - Simple keys (Enter, Tab, Backspace): ALT uses ESC prefix, other modifiers use CSI u encoding - CSI letter-final keys (arrows, Home, End): xterm modifier parameter - CSI tilde keys (PageUp, Delete, F5+): modifier before tilde - SS3 keys (F1-F4): convert to CSI with modifier Co-authored-by: divens <divens.dev@gmail.com>
-rw-r--r--zellij-utils/src/input/mod.rs51
1 files changed, 51 insertions, 0 deletions
diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs
index fecc958f..c8ac1e91 100644
--- a/zellij-utils/src/input/mod.rs
+++ b/zellij-utils/src/input/mod.rs
@@ -402,6 +402,57 @@ mod not_wasm {
_ => return None,
};
+ // Encode modifiers in VT raw bytes for non-Char keys.
+ //
+ // On the native console path (Windows), crossterm captures modifier
+ // flags from INPUT_RECORD but the raw bytes don't include them.
+ // Without encoding, the inner application never sees the modifiers.
+ let has_shift = modifiers.contains(&KeyModifier::Shift);
+ let has_any_modifier = has_alt || has_ctrl || has_shift;
+ let raw_bytes = if has_any_modifier && !raw_bytes.is_empty() {
+ let modifier_code = 1
+ + if has_shift { 1 } else { 0 }
+ + if has_alt { 2 } else { 0 }
+ + if has_ctrl { 4 } else { 0 };
+ let first = raw_bytes.get(0).copied();
+ let second = raw_bytes.get(1).copied();
+ let third = raw_bytes.get(2).copied();
+ let last = raw_bytes.last().copied();
+ match (first, second, third, last) {
+ // Simple keys (Enter=0x0d, Tab=0x09, Backspace=0x7f):
+ // ALT only uses ESC prefix, other modifiers use CSI u.
+ (Some(b), _, _, _) if b != 0x1b => {
+ if modifier_code == 3 {
+ let mut alt_bytes = vec![0x1b];
+ alt_bytes.extend_from_slice(&raw_bytes);
+ alt_bytes
+ } else {
+ format!("\x1b[{};{}u", b as u32, modifier_code).into_bytes()
+ }
+ },
+ // CSI letter-final (\x1b[A, \x1b[D, \x1b[H, \x1b[F):
+ // → \x1b[1;{mod}A
+ (Some(0x1b), Some(b'['), Some(final_byte), _) if raw_bytes.len() == 3 => {
+ format!("\x1b[1;{}{}", modifier_code, final_byte as char).into_bytes()
+ },
+ // CSI tilde (\x1b[5~, \x1b[3~, \x1b[15~):
+ // → \x1b[5;{mod}~
+ (Some(0x1b), Some(b'['), _, Some(b'~')) if raw_bytes.len() >= 4 => {
+ let num_part = &raw_bytes[2..raw_bytes.len() - 1];
+ let num_str = std::str::from_utf8(num_part).unwrap_or("1");
+ format!("\x1b[{};{}~", num_str, modifier_code).into_bytes()
+ },
+ // SS3 (\x1bOP, \x1bOQ, etc. for F1-F4):
+ // → \x1b[1;{mod}P
+ (Some(0x1b), Some(b'O'), Some(final_byte), _) if raw_bytes.len() == 3 => {
+ format!("\x1b[1;{}{}", modifier_code, final_byte as char).into_bytes()
+ },
+ _ => raw_bytes,
+ }
+ } else {
+ raw_bytes
+ };
+
Some((
KeyWithModifier::new_with_modifiers(bare_key, modifiers),
raw_bytes,