diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-13 18:53:37 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-13 18:53:53 +0300 |
| commit | c542fc871743f96f4e665e80f9274a262421d847 (patch) | |
| tree | c3e131449c2fb1d2efc1eb823cfd0123a6e32cd1 /src/server | |
| parent | b02a9a81d2316822822c81cbb806012ee278fb68 (diff) | |
fix: gate kitty graphics behind config
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/client_transport.rs | 8 | ||||
| -rw-r--r-- | src/server/headless.rs | 80 | ||||
| -rw-r--r-- | src/server/protocol.rs | 15 |
3 files changed, 80 insertions, 23 deletions
diff --git a/src/server/client_transport.rs b/src/server/client_transport.rs index a74bf7f6..348d00c7 100644 --- a/src/server/client_transport.rs +++ b/src/server/client_transport.rs @@ -146,8 +146,8 @@ pub(crate) fn handle_client_handshake( ( clamped_cols, clamped_rows, - cell_width_px.max(1), - cell_height_px.max(1), + cell_width_px, + cell_height_px, requested_encoding, ) } @@ -349,8 +349,8 @@ fn client_read_loop( client_id, cols: clamped_cols, rows: clamped_rows, - cell_width_px: cell_width_px.max(1), - cell_height_px: cell_height_px.max(1), + cell_width_px, + cell_height_px, } } ClientMessage::Detach => ServerEvent::ClientDetach { client_id }, diff --git a/src/server/headless.rs b/src/server/headless.rs index 3e978d5a..f490ec45 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -505,7 +505,11 @@ impl HeadlessServer { }; let (cols, rows) = self.effective_size; let area = Rect::new(0, 0, cols, rows); - crate::ui::compute_view_with_cell_size(&mut self.app.state, area, client.cell_size); + if self.app.state.kitty_graphics_enabled && client.cell_size.is_known() { + crate::ui::compute_view_with_cell_size(&mut self.app.state, area, client.cell_size); + } else { + crate::ui::compute_view(&mut self.app.state, area); + } // Shared runtime size changes affect pane wrapping and foreground-driven // rendering semantics. Force one fresh frame to every remaining client @@ -1458,12 +1462,21 @@ impl HeadlessServer { let mut broken_clients: Vec<u64> = Vec::new(); for (client_id, (cols, rows), cell_size, is_foreground) in render_targets { let area = Rect::new(0, 0, cols, rows); - let (buffer, cursor) = crate::server::render_stream::render_virtual_with_cell_size( - &mut self.app.state, - area, - is_foreground, - cell_size, - ); + let (buffer, cursor) = if self.app.state.kitty_graphics_enabled && cell_size.is_known() + { + crate::server::render_stream::render_virtual_with_cell_size( + &mut self.app.state, + area, + is_foreground, + cell_size, + ) + } else { + crate::server::render_stream::render_virtual( + &mut self.app.state, + area, + is_foreground, + ) + }; let hyperlinks = crate::server::render_stream::visible_hyperlinks(&self.app.state); let mut frame = FrameData::from_ratatui_buffer_with_hyperlinks(&buffer, cursor, &hyperlinks); @@ -1472,7 +1485,7 @@ impl HeadlessServer { continue; }; let mut next_graphics_cache = client.graphics_cache.clone(); - if self.app.state.kitty_graphics_enabled { + if self.app.state.kitty_graphics_enabled && cell_size.is_known() { frame.graphics = crate::kitty_graphics::encode_local_pane_graphics( &self.app.state, cell_size, @@ -1486,13 +1499,54 @@ impl HeadlessServer { continue; }; - let Some(prepared) = client.render_state.prepare_frame(&frame) else { + let mut commit_graphics_cache = true; + if frame.graphics.len() > MAX_FRAME_SIZE { + warn!( + client_id, + graphics_bytes = frame.graphics.len(), + max = MAX_FRAME_SIZE, + "dropping oversized graphics payload for client frame" + ); + frame.graphics.clear(); + commit_graphics_cache = false; + } + + let Some(mut prepared) = client.render_state.prepare_frame(&frame) else { client.render_pending = false; continue; }; + let mut frame_to_commit = frame.clone(); let serialized = match Self::frame_server_message(prepared.message()) { Ok(framed) => framed, + Err(protocol::FramingError::Oversized { claimed, max }) + if !frame.graphics.is_empty() => + { + warn!( + client_id, + claimed, max, "dropping graphics from oversized frame for client" + ); + let mut text_only_frame = frame.clone(); + text_only_frame.graphics.clear(); + let Some(text_only_prepared) = + client.render_state.prepare_frame(&text_only_frame) + else { + client.render_pending = false; + continue; + }; + let framed = match Self::frame_server_message(text_only_prepared.message()) { + Ok(framed) => framed, + Err(err) => { + warn!(client_id, err = %err, "failed to serialize text-only frame for client"); + broken_clients.push(client_id); + continue; + } + }; + prepared = text_only_prepared; + frame_to_commit = text_only_frame; + commit_graphics_cache = false; + framed + } Err(protocol::FramingError::Oversized { claimed, max }) => { warn!( client_id, @@ -1510,8 +1564,12 @@ impl HeadlessServer { match writer.render.try_send(serialized) { Ok(()) => { client.render_pending = false; - client.graphics_cache = next_graphics_cache; - client.render_state.commit_sent_frame(frame, prepared); + if commit_graphics_cache { + client.graphics_cache = next_graphics_cache; + } + client + .render_state + .commit_sent_frame(frame_to_commit, prepared); } Err(std::sync::mpsc::TrySendError::Full(_)) => { client.render_pending = true; diff --git a/src/server/protocol.rs b/src/server/protocol.rs index 743817ac..8748c41c 100644 --- a/src/server/protocol.rs +++ b/src/server/protocol.rs @@ -15,10 +15,9 @@ use serde::{Deserialize, Serialize}; /// Current protocol version. Bumped when wire format changes incompatibly. pub const PROTOCOL_VERSION: u32 = 5; -/// Maximum allowed frame payload size (32 MB). Frames larger than this are -/// rejected to prevent denial-of-service via oversized length prefixes while -/// leaving room for first-frame Kitty graphics image uploads. -pub const MAX_FRAME_SIZE: usize = 32 * 1024 * 1024; +/// Maximum allowed frame payload size (2 MB). Frames larger than this are +/// rejected to prevent denial-of-service via oversized length prefixes. +pub const MAX_FRAME_SIZE: usize = 2 * 1024 * 1024; /// Length of the u32 little-endian length prefix in bytes. const LENGTH_PREFIX_BYTES: usize = 4; @@ -47,9 +46,9 @@ pub enum ClientMessage { cols: u16, /// Terminal height in rows. rows: u16, - /// Width of a terminal cell in physical pixels. + /// Width of a terminal cell in physical pixels, or 0 when client-side Kitty graphics are disabled. cell_width_px: u32, - /// Height of a terminal cell in physical pixels. + /// Height of a terminal cell in physical pixels, or 0 when client-side Kitty graphics are disabled. cell_height_px: u32, /// Render encoding requested by the client. requested_encoding: RenderEncoding, @@ -67,9 +66,9 @@ pub enum ClientMessage { cols: u16, /// New terminal height in rows. rows: u16, - /// Width of a terminal cell in physical pixels. + /// Width of a terminal cell in physical pixels, or 0 when client-side Kitty graphics are disabled. cell_width_px: u32, - /// Height of a terminal cell in physical pixels. + /// Height of a terminal cell in physical pixels, or 0 when client-side Kitty graphics are disabled. cell_height_px: u32, }, |
