Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Keep a fullscreen window on the desk in overview
In overview a focused fullscreen window (hover focuses in overview, or
one on top when overview opens) still owned the top of the stack and
pinned to its output at full size, covering the desk overview exists to
show.
fullscreen_on_top is now false in overview, so the window stacks behind
every window there like a stepped-aside one, and place_fullscreen_windows
draws it at its desk spot at the overview zoom. Through a camera flight
(the overview ramp, or an eased zoom) it flies with the desk; an exit
onto it lands exactly on its spot, since exit_onto_window centres its
output-sized rect, and only then pins. set_mode dirties windowing while a
fullscreen window exists so the restack runs. A fullscreen toggle made in
overview animates into the desk slab rather than across the output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 9 ++++++++-
src/server/window.rs | 13 ++++++++++++-
src/server/window_manager.rs | 36 +++++++++++++++++++++++++++++-------
3 files changed, 49 insertions(+), 9 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 0761fcee..8dff9f6e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -955,7 +955,14 @@ zoom, so the camera pans away from it. Focusing it again pans the camera to
exactly that spot (`Window::fullscreen_anchor_pan`, from `focus_follow_pan`)
and it rides the desk until the ease lands, then pins — no jump at either
end. `save_state` records `saved_virtual_x/y` for a fullscreen window, as it
-did before the spot existed.
+did before the spot existed. **Overview treats it the same way**: there a
+fullscreen window is never on top (`fullscreen_on_top`), so it is a slab on
+the desk stacked behind every window even while focused (hover focuses it
+in overview, and pinning it then would cover the desk), and through any
+camera flight (`camera_ramp_anim`, an eased zoom) it flies with the desk.
+An overview exit onto it lands exactly on its spot — `exit_onto_window`
+centres its output-sized rect — and only then pins; `set_mode` dirties
+windowing while a fullscreen window exists so the restack runs.
### xdg-activation
diff --git a/src/server/window.rs b/src/server/window.rs
index 1b3f35cb..76a6fafc 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -4442,7 +4442,18 @@ impl Window {
return true;
}
let (w, h) = (*output).sent.dimensions();
- ((*output).sent.x as f64, (*output).sent.y as f64, w as f64, h as f64)
+ if self.fs_on_desk {
+ // Toggled in overview: it grows into its slab on the desk
+ // (`place_fullscreen_windows`), not over the whole output.
+ (
+ self.rendering_requested.x as f64,
+ self.rendering_requested.y as f64,
+ w as f64 * self.scale,
+ h as f64 * self.scale,
+ )
+ } else {
+ ((*output).sent.x as f64, (*output).sent.y as f64, w as f64, h as f64)
+ }
} else {
let w = self.configure_sent.width.map(|w| w as i32).unwrap_or(self.box_geom.width);
let h = self.configure_sent.height.map(|h| h as i32).unwrap_or(self.box_geom.height);
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index b479e087..753e48a5 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -2509,6 +2509,12 @@ impl WindowManager {
return;
}
self.mode = mode;
+ // Overview decides whether a fullscreen window owns the top of the
+ // stack (`fullscreen_on_top`), and the stacking pass runs only on a
+ // transaction.
+ if self.windows.iter().any(|&w| !w.is_null() && !(*w).closed && (*w).is_fullscreen()) {
+ self.dirty_windowing();
+ }
// The selection belongs to overview: it is made there, and the
// group move it exists for is an overview drag.
if mode != WindowManagerMode::Overview {
@@ -2999,8 +3005,9 @@ impl WindowManager {
(*self.server).scene.layers.fullscreen
} else if rendered_fullscreen(window) {
// Stepped aside for a focused window (alt-tab
- // out): behind every window but above the
- // grid, which the pass below re-asserts. It
+ // out), or in overview: behind every window
+ // but above the grid, which the pass below
+ // re-asserts. It
// sits on the desk there
// (`place_fullscreen_windows`), like the grid
// beneath it.
@@ -4259,7 +4266,8 @@ impl WindowManager {
}
/// A fullscreen window is pinned to its output only while it owns the
- /// top of the stack. Stepped aside (`Window::fullscreen_yields`) it
+ /// top of the stack outside overview. Stepped aside
+ /// (`Window::fullscreen_yields`), or in overview, it
/// sits on the desk at the spot it covered — `virtual_x/y`, at output
/// size, scaled with the zoom — so a focus chord's pan leaves it behind
/// like any other window. Until 2026-10-03 it stayed pinned behind the
@@ -4292,7 +4300,16 @@ impl WindowManager {
(Some((ax, ay)), Some(tx), Some(ty)) => (tx - ax).abs() < 0.5 && (ty - ay).abs() < 0.5,
_ => false,
};
- let on_desk = returning || (*w).fullscreen_yields();
+ // Overview shows the desk, so the window is on it there; and
+ // through a camera flight (the overview ramp, or an eased zoom)
+ // it flies with the desk rather than snapping to the output at
+ // either end — an exit onto it lands exactly on its spot
+ // (`exit_onto_window` centres its output-sized rect).
+ let flying = self.camera_ramp_anim.is_some() || self.target_desk_zoom.is_some();
+ let on_desk = returning
+ || flying
+ || self.mode == WindowManagerMode::Overview
+ || (*w).fullscreen_yields();
if on_desk {
let (sx, sy) = (*w).virtual_to_screen((*w).virtual_x, (*w).virtual_y);
(*w).rendering_requested.x = sx;
@@ -7208,10 +7225,15 @@ unsafe fn rendered_fullscreen(window: *mut Window) -> bool {
(*window).is_fullscreen() && !(*window).rendering_requested.hidden
}
-/// A fullscreen window that owns the top of the stack: rendered fullscreen
-/// and not stepped aside for another focused window (`fullscreen_yields`).
+/// A fullscreen window that owns the top of the stack: rendered fullscreen,
+/// not stepped aside for another focused window (`fullscreen_yields`), and
+/// not in overview — where it is a slab on the desk like a stepped-aside
+/// one (`place_fullscreen_windows`) and stacks behind every window with
+/// it, so it never hides the windows overview is there to show.
unsafe fn fullscreen_on_top(window: *mut Window) -> bool {
- rendered_fullscreen(window) && !(*window).fullscreen_yields()
+ rendered_fullscreen(window)
+ && (*(*window).server).wm.mode != WindowManagerMode::Overview
+ && !(*window).fullscreen_yields()
}
/// Prefer the installed `~/.local/bin/cce-cloud`, falling back to PATH lookup.