git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit4364e314671c15d22283d746792c424888ee6e5e
parentdd4b71a704
authorLucas Galante <[email protected]>
date2026-08-13 14:51
fix: don't park viewport-hidden windows in hidden_tree — the off-screen reveal delay

Windows whose box is fully outside the viewport get
rendering_requested.hidden=true from the arrange pass, and the WM
render_finish reorder pass parked their trees under the disabled
hidden_tree. But un-hiding happens on camera-motion frames
(update_viewport_local -> arrange -> render_viewport_update), which
deliberately never run a transaction: the motion path re-enabled the
window's node, yet the tree was still a child of the disabled
hidden_tree, so a window scrolling into view during an overview enter or
manual zoom-out rendered nothing until the next unrelated manage
transaction happened to run the reorder pass (status updates made that
~1s — the user-visible pop-in delay; with no transaction it never
appeared at all).

Hidden windows now keep their normal layer parent and stacking slot and
are hidden purely by their disabled scene node — which Window::
render_finish and render_viewport_update both already maintain. The
node-enable flip on a motion frame is then sufficient to reveal them the
frame their box intersects the viewport. Hidden fullscreen windows no
longer set found_fullscreen so shell-surface layering still follows only
visible fullscreen windows. hidden_tree remains in use for
WindowState::Init windows.

Diagnosed with the /tmp/cce-ovdbg scene-state dump: state-side hidden
flipped false the frame the box crossed the viewport edge while the tree
stayed under the disabled hidden_tree until the next reorder.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/server/window_manager.rs | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index a941e42..1c958fe 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -1549,10 +1549,17 @@ impl WindowManager {
                 crate::wm_node::WmNodeType::Window(window) => {
                     (*window).render_finish();
                     if reorder {
-                        if (*window).rendering_requested.hidden {
-                            ffi::wlr_scene_node_reparent((*window).tree as *mut _, (*self.server).scene.hidden_tree);
-                            ffi::wlr_scene_node_reparent((*window).popup_tree as *mut _, (*self.server).scene.hidden_tree);
-                        } else {
+                        {
+                            // Viewport-hidden windows are NOT parked under the
+                            // disabled hidden_tree: they keep their normal layer
+                            // parent and stacking slot, hidden purely by their
+                            // disabled node (render_finish and
+                            // render_viewport_update both own that flag).
+                            // Un-hiding happens on camera-motion frames, which
+                            // never run this reorder pass — a window parked here
+                            // stayed invisible after scrolling into view until
+                            // 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
                             } else if rendered_fullscreen(window) {
@@ -1594,7 +1601,7 @@ impl WindowManager {
                             } else {
                                 ffi::wlr_scene_node_raise_to_top((*window).tree as *mut _);
                             }
-                            if rendered_fullscreen(window) {
+                            if !(*window).rendering_requested.hidden && rendered_fullscreen(window) {
                                 found_fullscreen = true;
                             }