diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-16 00:58:23 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-16 00:58:23 +0300 |
| commit | 18061191fdc019498610aee81f0df93f6c2ebd31 (patch) | |
| tree | ab44d1c6939f1e9b5657512e831c6e50969941a2 /src/protocol/surface_reuse.rs | |
| parent | fc548768db4771fcc68590f56713a20afa53850a (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.rs | 99 |
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) + } } |
