From 207be3c771d281baae6e5fa0fb74be9a056e97a2 Mon Sep 17 00:00:00 2001 From: Can Celik Date: Tue, 1 Sep 2026 15:48:22 +0300 Subject: refactor: render the shell in the client (#3487) * feat: add opt-in client-rendered shell * feat: add client-owned shell mouse controls * feat: complete client-owned shell chrome * feat: add client-owned shell settings and notifications * feat: run client shell custom commands * feat: render popup terminals in client shell * feat: add client-owned scrollback and copy mode * fix: preserve client worktree trust defaults * feat: render startup diagnostics in client shell * feat: render onboarding in client shell * feat: render product announcements in client shell * feat: render release notes in client shell * feat: render mobile switcher in client shell * feat: render kitty graphics in client shell * fix: preserve client shell lifecycle coherence * refactor: route client shell commands through endpoint connections * refactor: make the client-rendered shell the default * refactor: remove monolithic launch mode * refactor: complete client-rendered shell cutover * perf: retain client pane surface updates * perf: scale retained pane surface updates * perf: remove remote pane input latency * test: make client shell checks platform-independent * fix: prevent client timer starvation * test: fix client shell platform gates * test: accept retained surfaces in cross-area checks * test: make client mode assertions platform-neutral * fix: badge only outdated integrations --- src/pane/kitty_keyboard.rs | 75 +++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 20 deletions(-) (limited to 'src/pane/kitty_keyboard.rs') diff --git a/src/pane/kitty_keyboard.rs b/src/pane/kitty_keyboard.rs index 30ab62ba..74de2538 100644 --- a/src/pane/kitty_keyboard.rs +++ b/src/pane/kitty_keyboard.rs @@ -3,8 +3,7 @@ pub(crate) struct KittyKeyboardTracker { pending: Vec, stack: Vec, flags: u16, - #[cfg(windows)] - modify_other_keys: bool, + modify_other_keys_level: u8, } impl KittyKeyboardTracker { @@ -32,9 +31,10 @@ impl KittyKeyboardTracker { self.store_pending(&bytes[index..]); break; } - #[cfg(windows)] if bytes[index + 1] == b'c' { - self.modify_other_keys = false; + self.stack.clear(); + self.flags = 0; + self.modify_other_keys_level = 0; } if bytes[index + 1] != b'[' { index += 1; @@ -52,7 +52,6 @@ impl KittyKeyboardTracker { match bytes[end] { b'u' => self.observe_csi_u(&bytes[index + 2..end]), - #[cfg(windows)] b'm' => self.observe_modify_other_keys(&bytes[index + 2..end]), #[cfg(windows)] b'n' if bytes[index + 2..end] @@ -61,7 +60,7 @@ impl KittyKeyboardTracker { !params.contains(&b';') && parse_kitty_keyboard_flags(params) == 4 }) => { - self.modify_other_keys = false; + self.modify_other_keys_level = 0; } _ => {} } @@ -69,18 +68,21 @@ impl KittyKeyboardTracker { } } + pub(crate) fn modify_other_keys_level(&self) -> u8 { + self.modify_other_keys_level + } + #[cfg(windows)] pub(crate) fn modify_other_keys_enabled(&self) -> bool { - self.modify_other_keys + self.modify_other_keys_level > 0 } - #[cfg(windows)] fn observe_modify_other_keys(&mut self, params: &[u8]) { let Some(params) = params.strip_prefix(b">") else { return; }; if params.is_empty() { - self.modify_other_keys = false; + self.modify_other_keys_level = 0; return; } @@ -91,8 +93,8 @@ impl KittyKeyboardTracker { return; } if parse_kitty_keyboard_flags(resource) == 4 { - self.modify_other_keys = - value.is_some_and(|value| parse_kitty_keyboard_flags(value) != 0); + self.modify_other_keys_level = + value.map(parse_kitty_keyboard_flags).unwrap_or(0).min(2) as u8; } } @@ -128,17 +130,22 @@ impl KittyKeyboardTracker { #[cfg(unix)] pub(crate) fn replay_ansi(&self) -> Option { - if self.stack.is_empty() { - return (self.flags != 0).then(|| format!("\x1b[={}u", self.flags)); - } - let mut ansi = String::new(); - let baseline = self.stack[0]; - if baseline != 0 { - ansi.push_str(&format!("\x1b[={baseline}u")); + if self.stack.is_empty() { + if self.flags != 0 { + ansi.push_str(&format!("\x1b[={}u", self.flags)); + } + } else { + let baseline = self.stack[0]; + if baseline != 0 { + ansi.push_str(&format!("\x1b[={baseline}u")); + } + for flags in self.stack.iter().skip(1).copied().chain([self.flags]) { + ansi.push_str(&format!("\x1b[>{flags}u")); + } } - for flags in self.stack.iter().skip(1).copied().chain([self.flags]) { - ansi.push_str(&format!("\x1b[>{flags}u")); + if self.modify_other_keys_level > 0 { + ansi.push_str(&format!("\x1b[>4;{}m", self.modify_other_keys_level)); } (!ansi.is_empty()).then_some(ansi) } @@ -166,6 +173,7 @@ mod tests { assert_eq!(tracker.flags, 1); assert_eq!(tracker.stack, vec![0]); + assert_eq!(tracker.modify_other_keys_level(), 1); #[cfg(windows)] { assert!(tracker.modify_other_keys_enabled()); @@ -175,4 +183,31 @@ mod tests { assert!(!tracker.modify_other_keys_enabled()); } } + + #[test] + fn ris_clears_kitty_flags_stack_and_modify_other_keys() { + let mut tracker = KittyKeyboardTracker::default(); + tracker.observe(b"\x1b[>1u\x1b[>5u\x1b[>4;2m\x1bc"); + + assert_eq!(tracker.flags, 0); + assert!(tracker.stack.is_empty()); + assert_eq!(tracker.modify_other_keys_level(), 0); + #[cfg(unix)] + assert_eq!(tracker.replay_ansi(), None); + } + + #[test] + fn tracks_and_replays_exact_modify_other_keys_level() { + let mut tracker = KittyKeyboardTracker::default(); + + tracker.observe(b"\x1b[>4;1m"); + assert_eq!(tracker.modify_other_keys_level(), 1); + #[cfg(unix)] + assert_eq!(tracker.replay_ansi().as_deref(), Some("\x1b[>4;1m")); + + tracker.observe(b"\x1b[>4;2m"); + assert_eq!(tracker.modify_other_keys_level(), 2); + tracker.observe(b"\x1b[>4;0m"); + assert_eq!(tracker.modify_other_keys_level(), 0); + } } -- cgit v1.2.3