diff options
| author | divens <divens@users.noreply.github.com> | 2026-04-03 10:04:41 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-03 10:04:41 +0200 |
| commit | cbe0237b1e4d36bec7cb51103179f0473920f3a0 (patch) | |
| tree | 8b70edb5c3cef5481a83d00486ca276f33dcdcaf | |
| parent | fix(web-client): use base URL when switching sessions (#4981) (diff) | |
| download | sl0ppij-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.rs | 51 |
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, |
