Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix(grid): re-issue the grid client's patch after a config reload
cce-grid is a pure function of (patch, style config) and repaints only
when handed a patch. `reload` re-read the desktop keys into the
compositor's own layout and retiled, but update_grid_patches re-sends a
patch only when the current one no longer COVERS the viewport — and a
cell-width or color change leaves coverage untouched. So the client kept
showing the old grid until the camera happened to travel far enough to
need a fresh patch, which read as a reload that "took too long".
A window-level `grid_patch_stale` flag, set by the new
`invalidate_grid_patches` on reload and on a `layout` change to a
desktop key, makes the next arrange re-issue the patch regardless of
coverage (a covering patch already in flight is left to latch first).
Verified in a headless shadow: reload with grid_cell_width 450 -> 300
logs "sent patch #2 ... (style reload)" and the shot shows the new
width within the same second.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/server/window.rs | 6 ++++++
src/server/window_manager.rs | 36 ++++++++++++++++++++++++++++++++----
2 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/src/server/window.rs b/src/server/window.rs
index 30d6303..42f0a72 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -465,6 +465,11 @@ pub struct Window {
/// The patch the CURRENT buffer covers — what arrange anchors to.
pub grid_patch_current: Option<crate::policy::api::GridPatch>,
pub grid_patch_serial: u32,
+ /// The current patch was rendered under a style config that has since
+ /// changed (reload, or a `layout` change to the desktop keys): re-issue
+ /// it on the next arrange even though its coverage is still fine. See
+ /// `WindowManager::invalidate_grid_patches`.
+ pub grid_patch_stale: bool,
pub saved_floating_width: i32,
pub saved_floating_height: i32,
pub saved_floating_virtual_x: f64,
@@ -723,6 +728,7 @@ impl Window {
grid_patch_acked: None,
grid_patch_current: None,
grid_patch_serial: 0,
+ grid_patch_stale: false,
saved_floating_width: 0,
saved_floating_height: 0,
saved_floating_virtual_x: 0.0,
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 2ac5827..714be91 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -2343,11 +2343,16 @@ impl WindowManager {
if !matches!((*w).state, crate::window::WindowState::Mapped) {
continue;
}
- if (*w).grid_patch_current.as_ref().map_or(false, &covers) {
+ // A stale patch (style changed since it was rendered) is
+ // re-issued regardless of coverage; a covering patch already in
+ // flight is left to latch first, and the flag then re-sends
+ // once it has become current.
+ let stale = (*w).grid_patch_stale;
+ if !stale && (*w).grid_patch_current.as_ref().map_or(false, &covers) {
continue;
}
if let Some(cur) = &(*w).grid_patch_current {
- explain("current", cur);
+ explain(if stale { "stale" } else { "current" }, cur);
}
if let Some((_, pending)) = &(*w).grid_patch_pending {
if covers(pending) {
@@ -2410,15 +2415,32 @@ impl WindowManager {
.cce_window_management
.send_grid_patch((*w).ref_key, serial, patch)
{
- log::info!("[Grid] sent patch #{serial}: {:.0},{:.0} {:.0}x{:.0} @{:.3}",
- patch.x, patch.y, patch.w, patch.h, patch.scale);
+ log::info!("[Grid] sent patch #{serial}: {:.0},{:.0} {:.0}x{:.0} @{:.3}{}",
+ patch.x, patch.y, patch.w, patch.h, patch.scale,
+ if stale { " (style reload)" } else { "" });
(*w).grid_patch_pending = Some((serial, patch));
+ (*w).grid_patch_stale = false;
} else {
log::info!("[Grid] patch #{serial} not sent (no toplevel resource yet)");
}
}
}
+ /// Mark every grid client's rendered patch stale so `update_grid_patches`
+ /// re-issues it on the next arrange even though its coverage is still
+ /// fine. The grid client is a pure function of (patch, style config) and
+ /// repaints only when handed a patch, so after a config reload — or a
+ /// `layout` change to a desktop key — an unmoved viewport kept showing
+ /// the OLD cell size and colors until the camera happened to travel far
+ /// enough to need a fresh patch.
+ pub unsafe fn invalidate_grid_patches(&mut self) {
+ for &w in self.windows.iter() {
+ if !w.is_null() && !(*w).closed && (*w).is_grid() {
+ (*w).grid_patch_stale = true;
+ }
+ }
+ }
+
pub unsafe fn arrange_views(&mut self) {
self.update_grid_patches();
self.update_restore_placeholders();
@@ -4371,6 +4393,9 @@ impl WindowManager {
}
self.retile_for_grid_change(old_sp);
self.remap_saved_entries(&old_sp);
+ if key.starts_with("desktop_") || key.starts_with("grid_cell") {
+ self.invalidate_grid_patches();
+ }
self.dirty_windowing();
"ok\n".to_string()
}
@@ -4827,6 +4852,9 @@ impl WindowManager {
// reload too — closed windows must reopen on their
// squares, not their stale pixels.
self.remap_saved_entries(&old_sp);
+ // The grid client reads the same desktop keys and only
+ // repaints when handed a patch: hand it one.
+ self.invalidate_grid_patches();
self.dirty_windowing();
// Process old PIDs