aboutsummaryrefslogtreecommitdiff
path: root/src/protocol/surface_reuse.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-16 00:58:23 +0300
committerGitHub <noreply@github.com>2026-09-16 00:58:23 +0300
commit18061191fdc019498610aee81f0df93f6c2ebd31 (patch)
treeab44d1c6939f1e9b5657512e831c6e50969941a2 /src/protocol/surface_reuse.rs
parentfc548768db4771fcc68590f56713a20afa53850a (diff)
fix: keep remote surface updates incremental (#4209)
refs #3769 refs #3745
Diffstat (limited to 'src/protocol/surface_reuse.rs')
-rw-r--r--src/protocol/surface_reuse.rs99
1 files changed, 99 insertions, 0 deletions
diff --git a/src/protocol/surface_reuse.rs b/src/protocol/surface_reuse.rs
index bba85824..aa2187d4 100644
--- a/src/protocol/surface_reuse.rs
+++ b/src/protocol/surface_reuse.rs
@@ -50,6 +50,23 @@ struct CellBaseline {
width: u16,
height: u16,
cells: Vec<CellData>,
+ popup: Option<PopupBaseline>,
+}
+
+struct PopupBaseline {
+ terminal_id: String,
+ width: u16,
+ height: u16,
+ cells: Vec<CellData>,
+}
+
+fn popup_baseline(surface: &PaneSurfaceFrame) -> Option<PopupBaseline> {
+ surface.popup.as_ref().map(|popup| PopupBaseline {
+ terminal_id: popup.terminal_id.clone(),
+ width: popup.frame.width,
+ height: popup.frame.height,
+ cells: popup.frame.cells.clone(),
+ })
}
/// Connection-local decoding happens before activation and presentation filtering, so
@@ -57,11 +74,27 @@ struct CellBaseline {
#[derive(Default)]
pub(crate) struct Decoder {
baseline: Option<CellBaseline>,
+ surface_delta: bool,
}
impl Decoder {
+ pub(crate) fn new(surface_delta: bool) -> Self {
+ Self {
+ baseline: None,
+ surface_delta,
+ }
+ }
+
pub(crate) fn decode(&mut self, message: ServerMessage) -> Result<ServerMessage, String> {
let message = match message {
+ ServerMessage::EndpointControl { kind, data }
+ if kind == super::surface_delta::MESSAGE_KIND =>
+ {
+ if !self.surface_delta {
+ return Err("surface delta was not negotiated".into());
+ }
+ return self.decode_delta(&data).map(ServerMessage::PaneSurface);
+ }
ServerMessage::EndpointControl { kind, data } if kind == MESSAGE_KIND => {
let reuse: SurfaceReuse<PaneSurfaceFrame> = serde_json::from_str(&data)
.map_err(|error| format!("invalid surface reuse: {error}"))?;
@@ -81,6 +114,9 @@ impl Decoder {
surface.frame.cells.clone_from(&base.cells);
base.projection_revision = surface.projection_revision;
base.surface_revision = surface.surface_revision;
+ if self.surface_delta {
+ base.popup = popup_baseline(&surface);
+ }
return Ok(ServerMessage::PaneSurface(surface));
}
message => message,
@@ -94,6 +130,9 @@ impl Decoder {
base.width = surface.frame.width;
base.height = surface.frame.height;
base.cells.clone_from(&surface.frame.cells);
+ if self.surface_delta {
+ base.popup = popup_baseline(surface);
+ }
}
ServerMessage::PaneSurfacePatch(patch) => {
if let Some(base) = &mut self.baseline {
@@ -124,4 +163,64 @@ impl Decoder {
}
Ok(message)
}
+
+ fn decode_delta(&mut self, data: &str) -> Result<PaneSurfaceFrame, String> {
+ use super::surface_delta::{self, GridUpdate};
+ let Some(base) = &mut self.baseline else {
+ return Err("surface delta without a baseline".into());
+ };
+ let delta = surface_delta::decode_for(data, (base.width, base.height))?;
+ let mut surface = delta.surface;
+ if surface.boot_id != base.boot_id
+ || delta.base_projection_revision != base.projection_revision
+ || delta.base_surface_revision != base.surface_revision
+ || surface.surface_revision != base.surface_revision.saturating_add(1)
+ || surface.projection_revision < base.projection_revision
+ || base.cells.len() != usize::from(base.width) * usize::from(base.height)
+ {
+ return Err("surface delta does not match its baseline".into());
+ }
+ surface.frame.cells.clone_from(&base.cells);
+ surface_delta::apply_rows(&mut surface.frame.cells, base.width, &delta.rows);
+ match (&mut surface.popup, delta.popup_cells) {
+ (None, None) => {}
+ (Some(popup), Some(update)) => {
+ let count = usize::from(popup.frame.width) * usize::from(popup.frame.height);
+ match update {
+ GridUpdate::Replace(cells) => popup.frame.cells = cells,
+ GridUpdate::Patch(rows) => {
+ let previous = base
+ .popup
+ .as_ref()
+ .filter(|previous| {
+ previous.terminal_id == popup.terminal_id
+ && previous.width == popup.frame.width
+ && previous.height == popup.frame.height
+ && previous.cells.len() == count
+ })
+ .ok_or("popup delta does not match its baseline")?;
+ popup.frame.cells.clone_from(&previous.cells);
+ surface_delta::apply_rows(&mut popup.frame.cells, previous.width, &rows);
+ }
+ }
+ }
+ _ => return Err("popup delta is missing or unexpected".into()),
+ }
+ for frame in
+ std::iter::once(&surface.frame).chain(surface.popup.as_ref().map(|popup| &popup.frame))
+ {
+ if frame.cells.iter().any(|cell| {
+ cell.hyperlink
+ .is_some_and(|index| index as usize >= frame.hyperlinks.len())
+ }) {
+ return Err("surface delta has an invalid hyperlink index".into());
+ }
+ }
+ // Validate the entire update before advancing either grid or revision.
+ surface_delta::apply_rows(&mut base.cells, base.width, &delta.rows);
+ base.popup = popup_baseline(&surface);
+ base.projection_revision = surface.projection_revision;
+ base.surface_revision = surface.surface_revision;
+ Ok(surface)
+ }
}