aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/client_shell_graphics.rs5
-rw-r--r--src/server/headless/retained_surface.rs13
-rw-r--r--src/server/headless/tests/pane_graphics.rs78
3 files changed, 78 insertions, 18 deletions
diff --git a/src/server/client_shell_graphics.rs b/src/server/client_shell_graphics.rs
index d0af03a9..8499b964 100644
--- a/src/server/client_shell_graphics.rs
+++ b/src/server/client_shell_graphics.rs
@@ -3,7 +3,7 @@ use crate::protocol::{ClientShellPopupSurface, SurfaceGraphicsScene};
pub(crate) fn collect_retained(
app: &crate::app::App,
- surface: &crate::protocol::PaneSurfaceFrame,
+ panes: &[crate::protocol::PaneSurfacePane],
target: crate::ui::TabSurfaceTarget,
cell_size: crate::kitty_graphics::HostCellSize,
delivered: &DeliveryCache,
@@ -12,8 +12,7 @@ pub(crate) fn collect_retained(
let rect = |rect: crate::protocol::SurfaceRect| {
ratatui::layout::Rect::new(rect.x, rect.y, rect.width, rect.height)
};
- let pane_infos = surface
- .panes
+ let pane_infos = panes
.iter()
.map(|pane| {
let (workspace_index, id) = app.parse_pane_id(&pane.pane_id)?;
diff --git a/src/server/headless/retained_surface.rs b/src/server/headless/retained_surface.rs
index bf98bc3e..2a98536f 100644
--- a/src/server/headless/retained_surface.rs
+++ b/src/server/headless/retained_surface.rs
@@ -445,12 +445,10 @@ impl HeadlessServer {
fallback!("graphics_target");
};
let client = &self.clients[&client_id];
- let mut next_surface = surface.clone();
- crate::server::render_stream::apply_pane_surface_patch(&mut next_surface, &patch);
let Some((graphics, delivery, sources)) =
crate::server::client_shell_graphics::collect_retained(
&self.app,
- &next_surface,
+ &panes,
target,
client.cell_size,
&client.shell_graphics_delivery,
@@ -460,14 +458,19 @@ impl HeadlessServer {
fallback!("graphics_geometry");
};
graphics_changed = graphics != surface.graphics;
- next_surface.graphics = graphics;
- Some((next_surface, delivery, sources))
+ Some((graphics, delivery, sources))
} else {
None
};
if patch.rows.is_empty() && !cursor_changed && !metadata_changed && !graphics_changed {
continue;
}
+ let graphics = graphics.map(|(graphics, delivery, sources)| {
+ let mut next_surface = surface.clone();
+ crate::server::render_stream::apply_pane_surface_patch(&mut next_surface, &patch);
+ next_surface.graphics = graphics;
+ (next_surface, delivery, sources)
+ });
updates.push(RetainedRecipientUpdate {
client_id,
patch,
diff --git a/src/server/headless/tests/pane_graphics.rs b/src/server/headless/tests/pane_graphics.rs
index 1255f4eb..70904fd5 100644
--- a/src/server/headless/tests/pane_graphics.rs
+++ b/src/server/headless/tests/pane_graphics.rs
@@ -5,6 +5,62 @@ fn receive_render(receiver: &std::sync::mpsc::Receiver<Vec<u8>>, timeout: Durati
}
#[tokio::test]
+async fn unchanged_retained_graphics_leave_the_committed_surface_and_delivery_untouched() {
+ let (mut server, _control_rx, client_rx, pane_id) = retained_test_server_with_control(
+ b"populated terminal\r\n\x1b_Ga=T,f=32,t=d,i=7,p=3,s=1,v=1,c=1,r=1,q=2;/wAA/w==\x1b\\",
+ );
+ let client = server.clients.get_mut(&1).unwrap();
+ client.mode = ClientConnectionMode::ClientShell;
+ client.cell_size = crate::kitty_graphics::HostCellSize {
+ width_px: 10,
+ height_px: 20,
+ };
+ server.render_and_stream();
+ let _ = receive_render(&client_rx, Duration::from_millis(100));
+
+ let before = server.clients[&1]
+ .render_state
+ .last_pane_surface()
+ .unwrap()
+ .clone();
+ assert_eq!(before.graphics.placements.len(), 1);
+ assert!(before.graphics.assets.is_empty());
+ let cells = server.clients[&1]
+ .render_state
+ .last_pane_surface()
+ .unwrap()
+ .frame
+ .cells
+ .as_ptr();
+ let mut hidden = crate::workspace::Workspace::test_new("hidden");
+ let hidden_pane = hidden.tabs[0].root_pane;
+ hidden.insert_test_runtime(
+ hidden_pane,
+ crate::terminal::TerminalRuntime::test_with_screen_bytes(80, 24, b"hidden output"),
+ );
+ server.app.state.workspaces.push(hidden);
+ for source in [pane_id, hidden_pane] {
+ assert!(server.render_retained_pane_surface_and_stream(&HashSet::from([source])));
+ assert!(client_rx.try_recv().is_err());
+ let after = server.clients[&1].render_state.last_pane_surface().unwrap();
+ assert_eq!(after, &before);
+ assert_eq!(after.frame.cells.as_ptr(), cells);
+ }
+
+ // Skipping the no-op must not discard the upload cache for the next real update.
+ write_shared_test_pane(&mut server, pane_id, b"\x1b[Hchanged");
+ assert!(server.render_retained_pane_surface_and_stream(&HashSet::from([pane_id])));
+ let ServerMessage::PaneSurface(updated) =
+ read_server_message(receive_render(&client_rx, Duration::from_millis(100)))
+ else {
+ panic!("expected retained image with changed text");
+ };
+ assert!(updated.graphics.assets.is_empty());
+ assert_eq!(updated.graphics.placements, before.graphics.placements);
+ assert!(frame_text(&updated.frame).contains("changed"));
+}
+
+#[tokio::test]
async fn first_kitty_image_updates_retained_surface_without_full_redraw() {
let (mut server, _control_rx, client_rx, pane_id) =
retained_test_server_with_control(b"text before image");
@@ -241,7 +297,7 @@ async fn retained_unicode_image_arrives_after_fragmented_upload_without_reupload
async fn render_scale_profile_retained_graphics() {
use ratatui::layout::Direction;
for retained in [false, true] {
- for image in ["none", "visible", "offscreen"] {
+ for image in ["none", "visible", "offscreen", "unchanged-image"] {
for count in [1, 15] {
let (mut server, _control_rx, client_rx, root) =
retained_test_server_with_control(b"populated terminal\r\n");
@@ -289,12 +345,14 @@ async fn render_scale_profile_retained_graphics() {
let sources = pane_ids.iter().copied().collect();
let mut samples = Vec::new();
for sample in 0..110 {
- for id in &pane_ids {
- write_shared_test_pane(
- &mut server,
- *id,
- format!("\x1b[H{sample:03}").as_bytes(),
- );
+ if image != "unchanged-image" {
+ for id in &pane_ids {
+ write_shared_test_pane(
+ &mut server,
+ *id,
+ format!("\x1b[H{sample:03}").as_bytes(),
+ );
+ }
}
let started = Instant::now();
if retained {
@@ -314,9 +372,9 @@ async fn render_scale_profile_retained_graphics() {
}
samples.sort_unstable();
println!(
- "retained={retained} 80x24 panes={count} image={image} median_us={} p95_us={}",
- samples[50].as_micros(),
- samples[94].as_micros()
+ "retained={retained} 80x24 panes={count} image={image} median_us={:.3} p95_us={:.3}",
+ samples[50].as_secs_f64() * 1e6,
+ samples[94].as_secs_f64() * 1e6
);
}
}