aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-29 19:48:55 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-29 19:48:55 +0300
commit77242943ba65a80cbc1180258dd64f06635233f9 (patch)
treeaf094d9f591f7886578e109fc9820d7516a17b1a /src/server
parent324e625c60658ab0de46928be300fc1804af48df (diff)
fix: keep split host color replies out of panes
refs #344 refs #279
Diffstat (limited to 'src/server')
-rw-r--r--src/server/clients.rs3
-rw-r--r--src/server/headless.rs98
2 files changed, 100 insertions, 1 deletions
diff --git a/src/server/clients.rs b/src/server/clients.rs
index 4ee1ff09..026dd378 100644
--- a/src/server/clients.rs
+++ b/src/server/clients.rs
@@ -33,6 +33,8 @@ pub(crate) struct ClientConnection {
pub(crate) host_terminal_theme: crate::terminal_theme::TerminalTheme,
/// Last reported focus state for this client's outer terminal.
pub(crate) outer_terminal_focus: Option<bool>,
+ /// Stateful parser for app-client input split across transport reads.
+ pub(crate) raw_input: crate::raw_input::RawInputFramer,
/// Monotonic activity stamp used to choose the fallback foreground client.
pub(crate) last_activity: u64,
/// Render baseline for the negotiated client encoding.
@@ -93,6 +95,7 @@ impl ClientConnection {
cell_size,
host_terminal_theme,
outer_terminal_focus,
+ raw_input: crate::raw_input::RawInputFramer::default(),
last_activity,
render_state: ClientRenderState::new(render_encoding),
graphics_cache: crate::kitty_graphics::HostGraphicsCache::default(),
diff --git a/src/server/headless.rs b/src/server/headless.rs
index e6576df6..f59201e0 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -1740,7 +1740,16 @@ impl HeadlessServer {
}
return true;
}
- let events = crate::raw_input::parse_raw_input_bytes_sync(&data);
+ let events = if let Some(client) = self.clients.get_mut(&client_id) {
+ let mut events = client.raw_input.push(&data);
+ // The thin client only forwards a bare ESC after its local input timeout.
+ if data.as_slice() == b"\x1b" {
+ events.extend(client.raw_input.flush_timeout());
+ }
+ events
+ } else {
+ Vec::new()
+ };
let host_surface_redraw = crate::raw_input::events_require_host_surface_redraw(
&events,
self.app.state.redraw_on_focus_gained,
@@ -4080,6 +4089,93 @@ next_tab = ""
}
#[test]
+ fn app_client_lone_escape_closes_navigate_mode() {
+ let mut server = test_headless_server();
+ server.app.state.workspaces = vec![crate::workspace::Workspace::test_new("test")];
+ server.app.state.active = Some(0);
+ server.app.state.selected = 0;
+ server.app.state.mode = crate::app::Mode::Navigate;
+ server.clients.insert(
+ 1,
+ ClientConnection::new(
+ (80, 24),
+ crate::kitty_graphics::HostCellSize::default(),
+ crate::terminal_theme::TerminalTheme::default(),
+ Some(true),
+ 1,
+ RenderEncoding::SemanticFrame,
+ None,
+ ),
+ );
+ server.foreground_client_id = Some(1);
+ server.sync_foreground_client_state();
+
+ assert!(server.handle_server_event(ServerEvent::ClientInput {
+ client_id: 1,
+ data: b"\x1b".to_vec(),
+ }));
+
+ assert_eq!(server.app.state.mode, crate::app::Mode::Terminal);
+ }
+
+ #[tokio::test]
+ async fn split_default_background_response_updates_theme_without_forwarding_tail() {
+ let mut server = test_headless_server();
+ let mut workspace = crate::workspace::Workspace::test_new("test");
+ let focused = workspace.focused_pane_id().unwrap();
+ let (runtime, mut rx) =
+ crate::terminal::TerminalRuntime::test_with_channel_capacity(80, 24, 1);
+ workspace.tabs[0].runtimes.insert(focused, runtime);
+ server.app.state.workspaces = vec![workspace];
+ server.app.state.active = Some(0);
+ server.app.state.selected = 0;
+ server.app.state.mode = crate::app::Mode::Terminal;
+ server.clients.insert(
+ 1,
+ ClientConnection::new(
+ (80, 24),
+ crate::kitty_graphics::HostCellSize::default(),
+ crate::terminal_theme::TerminalTheme::default(),
+ Some(true),
+ 1,
+ RenderEncoding::SemanticFrame,
+ None,
+ ),
+ );
+ server.foreground_client_id = Some(1);
+ server.sync_foreground_client_state();
+
+ let _ = server.handle_server_event(ServerEvent::ClientInput {
+ client_id: 1,
+ data: b"\x1b]".to_vec(),
+ });
+ assert!(rx.try_recv().is_err());
+
+ assert!(server.handle_server_event(ServerEvent::ClientInput {
+ client_id: 1,
+ data: b"11;#123456\x07".to_vec(),
+ }));
+
+ assert!(rx.try_recv().is_err());
+ assert_eq!(
+ server.clients[&1].host_terminal_theme.background,
+ Some(crate::terminal_theme::RgbColor {
+ r: 0x12,
+ g: 0x34,
+ b: 0x56,
+ })
+ );
+ assert_eq!(
+ server.app.state.host_terminal_theme.background,
+ Some(crate::terminal_theme::RgbColor {
+ r: 0x12,
+ g: 0x34,
+ b: 0x56,
+ })
+ );
+ }
+
+ #[test]
fn render_and_stream_uses_each_client_terminal_size() {
let mut server = test_headless_server();
server.app.state.workspaces = vec![crate::workspace::Workspace::test_new("test")];