diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-29 19:48:55 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-29 19:48:55 +0300 |
| commit | 77242943ba65a80cbc1180258dd64f06635233f9 (patch) | |
| tree | af094d9f591f7886578e109fc9820d7516a17b1a /src/server | |
| parent | 324e625c60658ab0de46928be300fc1804af48df (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.rs | 3 | ||||
| -rw-r--r-- | src/server/headless.rs | 98 |
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")]; |
