aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-13 18:53:37 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-13 18:53:53 +0300
commitc542fc871743f96f4e665e80f9274a262421d847 (patch)
treec3e131449c2fb1d2efc1eb823cfd0123a6e32cd1 /src/server
parentb02a9a81d2316822822c81cbb806012ee278fb68 (diff)
fix: gate kitty graphics behind config
Diffstat (limited to 'src/server')
-rw-r--r--src/server/client_transport.rs8
-rw-r--r--src/server/headless.rs80
-rw-r--r--src/server/protocol.rs15
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,
},