diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-24 01:30:05 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-24 01:31:06 +0300 |
| commit | 36de78dd4fa4e2c203ee955f18caf82a6bca7e80 (patch) | |
| tree | 2cf7fca12c9c1eeaeb13ae6ad010526d03ccd6fa /src/client | |
| parent | 503613c8ecbea752a234daf30cb0ad5a8bf93e4e (diff) | |
fix: preserve kitty graphics during host repaints
refs #1628
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/mod.rs | 34 |
1 files changed, 22 insertions, 12 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs index b1c363f2..23e331e8 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -87,6 +87,8 @@ struct ClientState { remote_image_paste_key: Option<(crossterm::event::KeyCode, crossterm::event::KeyModifiers)>, /// Whether outer focus gain should force a full host-terminal redraw. redraw_on_focus_gained: bool, + /// Whether the next semantic frame must repaint every cell without clearing the surface. + repaint_pending: bool, /// Whether this client draws the cursor into frame cells instead of using the host cursor. draw_host_cursor: bool, } @@ -220,8 +222,8 @@ fn attach_scroll_action( } impl ClientState { - fn request_full_redraw(&mut self) { - self.blit_encoder = render_ansi::BlitEncoder::new(); + fn request_repaint(&mut self) { + self.repaint_pending = true; } } @@ -1302,6 +1304,7 @@ async fn run_client_loop( #[cfg(unix)] remote_image_paste_key: config.remote_image_paste_key, redraw_on_focus_gained: config.redraw_on_focus_gained, + repaint_pending: false, draw_host_cursor, }; debug!(?negotiated_encoding, "client render encoding active"); @@ -1417,7 +1420,7 @@ async fn run_client_loop( &events, state.redraw_on_focus_gained, ) { - state.request_full_redraw(); + state.request_repaint(); } if crate::raw_input::events_require_host_terminal_theme_query(&events) { query_host_terminal_theme(); @@ -1489,7 +1492,7 @@ async fn run_client_loop( &raw_events, state.redraw_on_focus_gained, ) { - state.request_full_redraw(); + state.request_repaint(); } let msg = ClientMessage::InputEvents { events }; if let Err(e) = write_to_server(&mut write_stream, &msg) { @@ -1516,11 +1519,14 @@ async fn run_client_loop( frame_data }; let encoded = if state.draw_host_cursor { + state.blit_encoder.encode_with_suppressed_visible_cursor( + &frame_data, + state.repaint_pending, + ) + } else { state .blit_encoder - .encode_with_suppressed_visible_cursor(&frame_data, false) - } else { - state.blit_encoder.encode(&frame_data, false) + .encode(&frame_data, state.repaint_pending) }; let mut stdout = io::stdout(); let graphics = if state.kitty_graphics_enabled { @@ -1532,6 +1538,7 @@ async fn run_client_loop( write_encoded_frame_with_graphics(&mut stdout, &encoded.bytes, graphics); let _ = stdout.flush(); state.blit_encoder.commit(frame_data, encoded); + state.repaint_pending = false; } ServerMessage::Terminal(frame) => { if state.kitty_graphics_enabled && contains_kitty_graphics_bytes(&frame.bytes) { @@ -1983,15 +1990,18 @@ fn write_encoded_frame_with_graphics( encoded: &[u8], graphics: &[u8], ) -> io::Result<()> { - writer.write_all(encoded)?; if graphics.is_empty() { - return Ok(()); + return writer.write_all(encoded); } + let insertion = render_ansi::final_sync_output_end(encoded).unwrap_or(encoded.len()); + + writer.write_all(&encoded[..insertion])?; record_received_kitty_graphics(graphics); writer.write_all(b"\x1b7")?; writer.write_all(graphics)?; - writer.write_all(b"\x1b8") + writer.write_all(b"\x1b8")?; + writer.write_all(&encoded[insertion..]) } fn contains_kitty_graphics_bytes(bytes: &[u8]) -> bool { @@ -2360,7 +2370,7 @@ mod tests { } #[test] - fn graphics_bytes_are_written_after_blit_with_saved_cursor() { + fn graphics_bytes_are_written_inside_synchronized_blit_with_saved_cursor() { let mut output = Vec::new(); write_encoded_frame_with_graphics( &mut output, @@ -2371,7 +2381,7 @@ mod tests { assert_eq!( output, - b"\x1b[?2026htext\x1b[?2026lcursor\x1b7graphics\x1b8" + b"\x1b[?2026htext\x1b7graphics\x1b8\x1b[?2026lcursor" ); } |
