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

commit51c1a1e07e45322dbd4f46dfd4fad009d8d67bde
parentb54d917f71
authorLucas Galante <[email protected]>
date2026-08-20 14:05
overview: a world window spawning during overview exits onto it

Mapping a Floating/Tiled/Fullscreen window while overview is up snaps
the camera out of overview (zoom 1, centered on the new window, mode
Normal) before the map-time focus runs — the user asked for the window
(launcher pick, spawn keybind), so the session lands on it. Chrome
(Popup/Overlay via resolved mode), status, wallpaper and the grid
spawn without disturbing the overview; settle-phase/blocked-focus
spawns are excluded by the existing should_focus gates.

exit_overview_to_window is the factored map-time variant of the camera
dance the overview click-release path does inline in cursor.rs.

Shadow-verified: overview -> launcher spawn stays in overview ->
cce-files spawn exits to normal with the new window focused.

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

 src/server/window.rs         | 21 +++++++++++++++++++++
 src/server/window_manager.rs | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+)

diff --git a/src/server/window.rs b/src/server/window.rs
index 496b44e..42484e8 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -1259,6 +1259,27 @@ impl Window {
                 }
             }
 
+            // A WORLD window spawning during overview pulls the session
+            // out of it, landing at zoom 1 on the new window — the user
+            // asked for it (launcher pick, spawn keybind). Chrome
+            // (Popup/Overlay), status, wallpaper and the grid spawn without
+            // disturbing the overview. Before the focus loop, so the
+            // focus-follow pan sees the settled zoom-1 camera and no-ops.
+            if should_focus
+                && (*self.server).wm.mode == crate::window_manager::WindowManagerMode::Overview
+                && !self.is_grid()
+                && !self.is_status_bar()
+                && !self.is_wallpaper()
+            {
+                let resolved = (*self.server).wm.get_mode_for_window(self as *mut Window);
+                if !matches!(
+                    resolved,
+                    crate::tiling::TilingMode::Popup | crate::tiling::TilingMode::Overlay
+                ) {
+                    (*self.server).wm.exit_overview_to_window(self as *mut Window);
+                }
+            }
+
             // The grid layer never takes focus — it is desktop furniture,
             // not a window (it is also input-transparent, so focus here
             // would be unreachable-by-click and unswitchable-away for
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index a2a65ed..c1ceb53 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -2010,6 +2010,43 @@ impl WindowManager {
 
 
 
+    /// Snap the camera out of overview and onto `win`: zoom 1, centered,
+    /// mode Normal. The overview click-release path in cursor.rs does the
+    /// same dance inline (plus its focus/seat-event bookkeeping); this is
+    /// the map-time variant for windows SPAWNED during overview.
+    pub unsafe fn exit_overview_to_window(&mut self, win: *mut Window) {
+        if win.is_null() {
+            return;
+        }
+        let (mut viewport_w, mut viewport_h) = (1920.0_f64, 1080.0_f64);
+        let outputs_list = &(*self.server).om.outputs as *const ffi::wl_list as *mut WlList;
+        let mut curr_out = (*outputs_list).next;
+        while curr_out != outputs_list {
+            let output = crate::container_of!(curr_out, crate::output::Output, link);
+            if (*output).sent.state == crate::output::OutputStateValue::Enabled {
+                let wlr_box = (*output).sent.box_layout();
+                viewport_w = wlr_box.width as f64;
+                viewport_h = wlr_box.height as f64;
+                break;
+            }
+            curr_out = (*curr_out).next;
+        }
+        let win_w = if (*win).box_geom.width > 0 { (*win).box_geom.width as f64 } else { 800.0 };
+        let win_h = if (*win).box_geom.height > 0 { (*win).box_geom.height as f64 } else { 600.0 };
+        let center_x = (*win).virtual_x + win_w / 2.0;
+        let center_y = (*win).virtual_y + win_h / 2.0;
+        self.desk_zoom = 1.0;
+        self.mode = WindowManagerMode::Normal;
+        self.desk_pan_x = center_x - viewport_w / 2.0;
+        self.desk_pan_y = center_y - viewport_h / 2.0;
+        self.stop_panning_animation();
+        if matches!(self.state, WindowManagerState::Idle) {
+            self.update_viewport_local();
+        } else {
+            self.dirty_windowing();
+        }
+    }
+
     /// Issue grid_patch events to grid clients whose current patch no
     /// longer comfortably covers the viewport (or whose buffer resolution
     /// has drifted more than 2x from the zoom). One patch in flight per