Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix(scene): client backgrounds paint between the grid backdrop and the cells
A wlr-layer-shell Background surface (and the cce-wallpaper window) went
into scene.layers.background, the same tree as each output's base rect and
its grid tree — and draw_grid raises the grid tree to the top of that layer
on every redraw, with the backdrop rect as the first node inside it. So a
client background was always under an opaque backdrop: mapped, enabled,
and never seen.
The background layer now has an ordered middle: `layers.background_clients`
hosts the layer-shell Background surfaces and the wallpaper window; each
output's grid backdrop moves out of grid_tree into its own
`grid_backdrop_tree`, placed just above the output's base rect (below the
clients); grid_tree keeps only the cells and rims and stays raised on top.
The backdrop tree is positioned in lockstep with grid_tree (pan shift, Solid
spec, output layout) and destroyed with it. A client wallpaper therefore
replaces the flat backdrop colour and keeps the cell lattice, which is what
the grid-client comment in window_manager.rs already described as the
intended stacking.
Verified in a shadow session on this build: with no client the desktop is
unchanged; a Background-layer surface shows through the gaps between the
cells with the cells on top; a floating window stacks above both.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/server/output.rs | 66 ++++++++++++++++++++++++++++++++++++++++++--
src/server/output_manager.rs | 12 ++++++++
src/server/scene.rs | 13 ++++++++-
src/server/window_manager.rs | 4 ++-
4 files changed, 90 insertions(+), 5 deletions(-)
diff --git a/src/server/output.rs b/src/server/output.rs
index fa2daae..3b90d41 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -159,6 +159,12 @@ pub struct Output {
pub scene_output: *mut ffi::wlr_scene_output,
pub background_rect: *mut ffi::wlr_scene_rect,
pub grid_tree: *mut ffi::wlr_scene_tree,
+ /// The grid's backdrop (gap colour, or the Solid spec's colour), kept in
+ /// its own tree under `scene.layers.background_clients` so a client
+ /// background surface paints over it while the cells in `grid_tree` stay
+ /// on top. Positioned in lockstep with `grid_tree`.
+ pub grid_backdrop_tree: *mut ffi::wlr_scene_tree,
+ pub grid_backdrop_rect: *mut ffi::wlr_scene_rect,
pub adjust_tree: *mut ffi::wlr_scene_tree,
pub adjust_rects: Vec<*mut ffi::wlr_scene_rect>,
pub last_adjust_mode: bool,
@@ -381,6 +387,11 @@ impl Output {
ffi::wlr_scene_node_destroy(self.grid_tree as *mut ffi::wlr_scene_node);
self.grid_tree = std::ptr::null_mut();
}
+ if !self.grid_backdrop_tree.is_null() {
+ ffi::wlr_scene_node_destroy(self.grid_backdrop_tree as *mut ffi::wlr_scene_node);
+ self.grid_backdrop_tree = std::ptr::null_mut();
+ self.grid_backdrop_rect = std::ptr::null_mut();
+ }
self.grid_rect_pool.clear();
// The bevel subtree died with grid_tree above.
self.grid_bevel_pool.clear();
@@ -457,6 +468,8 @@ impl Output {
scene_output,
background_rect: std::ptr::null_mut(),
grid_tree: std::ptr::null_mut(),
+ grid_backdrop_tree: std::ptr::null_mut(),
+ grid_backdrop_rect: std::ptr::null_mut(),
adjust_tree: std::ptr::null_mut(),
adjust_rects: Vec::new(),
last_adjust_mode: false,
@@ -1164,9 +1177,41 @@ impl Output {
// Enable the grid tree.
ffi::wlr_scene_node_set_enabled(self.grid_tree as *mut ffi::wlr_scene_node, true);
- // Keep the grid tree at the top of the background layer to prevent wallpaper windows from overlapping it
+ // Keep the grid tree (cells + rims) at the top of the background layer,
+ // above the client backgrounds in layers.background_clients.
ffi::wlr_scene_node_raise_to_top(self.grid_tree as *mut ffi::wlr_scene_node);
+ // The backdrop goes BELOW the client backgrounds: its own tree, placed
+ // just above this output's base rect (or at the very bottom), so a
+ // layer-shell Background surface or a wallpaper window replaces the flat
+ // colour and keeps the cell lattice.
+ if self.grid_backdrop_tree.is_null() {
+ self.grid_backdrop_tree = ffi::wlr_scene_tree_create((*self.server).scene.layers.background);
+ if self.grid_backdrop_tree.is_null() {
+ return;
+ }
+ if !self.background_rect.is_null() {
+ ffi::wlr_scene_node_lower_to_bottom(self.background_rect as *mut ffi::wlr_scene_node);
+ ffi::wlr_scene_node_place_above(
+ self.grid_backdrop_tree as *mut ffi::wlr_scene_node,
+ self.background_rect as *mut ffi::wlr_scene_node,
+ );
+ } else {
+ ffi::wlr_scene_node_lower_to_bottom(self.grid_backdrop_tree as *mut ffi::wlr_scene_node);
+ }
+ }
+ ffi::wlr_scene_node_set_enabled(self.grid_backdrop_tree as *mut ffi::wlr_scene_node, true);
+ let backdrop_tree = self.grid_backdrop_tree;
+ let backdrop_rect = &mut self.grid_backdrop_rect;
+ let mut set_backdrop = |w: i32, h: i32, color_ptr: *const f32| {
+ if backdrop_rect.is_null() {
+ *backdrop_rect = ffi::wlr_scene_rect_create(backdrop_tree, w, h, color_ptr);
+ } else {
+ ffi::wlr_scene_rect_set_size(*backdrop_rect, w, h);
+ ffi::wlr_scene_rect_set_color(*backdrop_rect, color_ptr);
+ }
+ };
+
let (viewport_w, viewport_h) = self.current.dimensions();
let spec = wm.layout.background_spec();
let zoom = crate::policy::background::sanitized_zoom(wm.desk_zoom);
@@ -1307,6 +1352,11 @@ impl Output {
x,
y,
);
+ ffi::river_scene_node_set_position_if_changed(
+ backdrop_tree as *mut ffi::wlr_scene_node,
+ x,
+ y,
+ );
}
if force {
@@ -1314,7 +1364,7 @@ impl Output {
// backdrop always draws — while a grid client is live it
// is the safety net beyond the patch edges during fast
// pans; the CELLS yield to the client's rendering.
- get_rect(frame.backdrop_w, frame.backdrop_h, grid.gap_color.0.as_ptr(), 0, 0, 0, 0);
+ set_backdrop(frame.backdrop_w, frame.backdrop_h, grid.gap_color.0.as_ptr());
if let Some(cells) = frame.cells.as_ref().filter(|_| wm.grid_cells_enabled) {
// scenefx fade-inset wire encoding: inset px * 1000
@@ -1385,8 +1435,13 @@ impl Output {
self.sent.x,
self.sent.y,
);
+ ffi::river_scene_node_set_position_if_changed(
+ backdrop_tree as *mut ffi::wlr_scene_node,
+ self.sent.x,
+ self.sent.y,
+ );
if force {
- get_rect(viewport_w, viewport_h, color.0.as_ptr(), 0, 0, 0, 0);
+ set_backdrop(viewport_w, viewport_h, color.0.as_ptr());
}
}
}
@@ -1550,6 +1605,11 @@ unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut
ffi::wlr_scene_node_destroy((*output).grid_tree as *mut ffi::wlr_scene_node);
(*output).grid_tree = std::ptr::null_mut();
}
+ if !(*output).grid_backdrop_tree.is_null() {
+ ffi::wlr_scene_node_destroy((*output).grid_backdrop_tree as *mut ffi::wlr_scene_node);
+ (*output).grid_backdrop_tree = std::ptr::null_mut();
+ (*output).grid_backdrop_rect = std::ptr::null_mut();
+ }
if !(*output).adjust_tree.is_null() {
ffi::wlr_scene_node_destroy((*output).adjust_tree as *mut ffi::wlr_scene_node);
diff --git a/src/server/output_manager.rs b/src/server/output_manager.rs
index be1984a..fe038a8 100644
--- a/src/server/output_manager.rs
+++ b/src/server/output_manager.rs
@@ -235,6 +235,13 @@ impl OutputManager {
output.sent.y,
);
}
+ if !output.grid_backdrop_tree.is_null() {
+ ffi::wlr_scene_node_set_position(
+ output.grid_backdrop_tree as *mut ffi::wlr_scene_node,
+ output.sent.x,
+ output.sent.y,
+ );
+ }
if output.adjust_tree.is_null() {
output.adjust_tree = ffi::wlr_scene_tree_create((*server).scene.layers.top);
@@ -258,6 +265,11 @@ impl OutputManager {
output.grid_tree = std::ptr::null_mut();
output.grid_rect_pool.clear();
}
+ if !output.grid_backdrop_tree.is_null() {
+ ffi::wlr_scene_node_destroy(output.grid_backdrop_tree as *mut ffi::wlr_scene_node);
+ output.grid_backdrop_tree = std::ptr::null_mut();
+ output.grid_backdrop_rect = std::ptr::null_mut();
+ }
if !output.adjust_tree.is_null() {
ffi::wlr_scene_node_destroy(output.adjust_tree as *mut ffi::wlr_scene_node);
output.adjust_tree = std::ptr::null_mut();
diff --git a/src/server/scene.rs b/src/server/scene.rs
index f181f53..078145f 100644
--- a/src/server/scene.rs
+++ b/src/server/scene.rs
@@ -6,6 +6,14 @@ use crate::scene_node_data::{SceneNodeData, SceneNodeDataVal};
pub struct SceneLayers {
pub background: *mut ffi::wlr_scene_tree,
+ /// Client-provided backgrounds — wlr-layer-shell Background surfaces and
+ /// the `cce-wallpaper` window — inside `background`, ABOVE each output's
+ /// base rect and grid backdrop and BELOW its grid cells (`Output::draw_grid`
+ /// keeps the backdrop under this tree and the cell tree above it). A client
+ /// wallpaper therefore replaces the flat backdrop colour and keeps the cell
+ /// lattice; before this tree the grid tree, re-raised on every redraw, buried
+ /// every client background under its opaque backdrop.
+ pub background_clients: *mut ffi::wlr_scene_tree,
pub bottom: *mut ffi::wlr_scene_tree,
pub wm: *mut ffi::wlr_scene_tree,
pub top: *mut ffi::wlr_scene_tree,
@@ -42,6 +50,7 @@ impl Scene {
locked_tree: std::ptr::null_mut(),
layers: SceneLayers {
background: std::ptr::null_mut(),
+ background_clients: std::ptr::null_mut(),
bottom: std::ptr::null_mut(),
wm: std::ptr::null_mut(),
top: std::ptr::null_mut(),
@@ -98,6 +107,7 @@ impl Scene {
ffi::wlr_scene_node_set_enabled(locked_tree as *mut ffi::wlr_scene_node, false);
self.layers.background = ffi::wlr_scene_tree_create(normal_tree);
+ self.layers.background_clients = ffi::wlr_scene_tree_create(self.layers.background);
self.layers.bottom = ffi::wlr_scene_tree_create(normal_tree);
self.layers.wm = ffi::wlr_scene_tree_create(normal_tree);
self.layers.top = ffi::wlr_scene_tree_create(normal_tree);
@@ -113,6 +123,7 @@ impl Scene {
if self.layers.border_overlay.is_null()
|| self.layers.background.is_null()
+ || self.layers.background_clients.is_null()
|| self.layers.bottom.is_null()
|| self.layers.wm.is_null()
|| self.layers.top.is_null()
@@ -212,7 +223,7 @@ impl Scene {
pub unsafe fn layer_surface_tree(&self, layer: u32) -> *mut ffi::wlr_scene_tree {
// layer is zwlr_layer_shell_v1_layer enum values
match layer {
- ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND => self.layers.background,
+ ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND => self.layers.background_clients,
ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM => self.layers.bottom,
ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_TOP => self.layers.top,
ffi::zwlr_layer_shell_v1_layer_ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY => self.layers.overlay,
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 39c329a..a52a330 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -2114,7 +2114,9 @@ impl WindowManager {
// the next unrelated transaction reparented it (the
// off-screen reveal delay in overview/zoom).
let layer = if (*window).get_app_id_string().as_deref() == Some("cce-wallpaper") {
- (*self.server).scene.layers.background
+ // Between the native backdrop and the fallback
+ // cells, like a layer-shell Background surface.
+ (*self.server).scene.layers.background_clients
} else if (*window).is_grid() {
// The grid client is a desktop fixture: above
// the native backdrop and fallback cells