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

commit9ec88eded4aed6fbcb2550fd7932a86e50dfd1bd
parent861cc11dfb
authorLucas Galante <[email protected]>
date2026-07-28 14:03
refactor: overlay arms to policy dispatch; focus helper delegates to rule

The OverlayLeft/Right legacy arms are deleted (DefaultPolicy emits
SetOverlayPosition, applied as the layout string write) and
focus_next_visible_window keeps only eligibility + the id-to-pointer
round trip, delegating the pick to focus::next_visible_focus.

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

 src/server/window_manager.rs | 72 ++++++++++++++++++++++++--------------------
 1 file changed, 39 insertions(+), 33 deletions(-)

diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 2e33ef4..664f38a 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -1798,33 +1798,41 @@ impl WindowManager {
         self.focus_history.retain(|&w| w != window);
     }
 
+    /// Refocus after the focused window goes away, by the policy crate's
+    /// next-visible rule (most recent eligible history entry, else the last
+    /// eligible window in window order, else clear focus). This side owns
+    /// eligibility (mapped, not minimized, not status/background).
     pub unsafe fn focus_next_visible_window(&mut self, seat: *mut crate::seat::Seat) {
-        let mut next_focus: *mut Window = std::ptr::null_mut();
-        for &w in self.focus_history.iter() {
-            if !w.is_null() && !(*w).closed && !(*w).minimized && matches!((*w).state, crate::window::WindowState::Mapped) {
-                let app_id = (*w).get_app_id_string();
-                let is_status_bar = app_id.as_deref().map_or(false, |id| id.starts_with("cce-status"));
-                let is_wallpaper = app_id.as_deref() == Some("cce-wallpaper");
-                if !is_status_bar && !is_wallpaper {
-                    next_focus = w;
-                    break;
-                }
-            }
-        }
-        if next_focus.is_null() {
-            for &w in self.windows.iter() {
-                if !w.is_null() && !(*w).closed && !(*w).minimized && matches!((*w).state, crate::window::WindowState::Mapped) {
-                    let app_id = (*w).get_app_id_string();
-                    let is_status_bar = app_id.as_deref().map_or(false, |id| id.starts_with("cce-status"));
-                    let is_wallpaper = app_id.as_deref() == Some("cce-wallpaper");
-                    if !is_status_bar && !is_wallpaper {
-                        next_focus = w;
-                    }
-                }
+        let eligible = |w: *mut Window| -> bool {
+            if (*w).closed || (*w).minimized || !matches!((*w).state, crate::window::WindowState::Mapped) {
+                return false;
             }
-        }
-        if !next_focus.is_null() {
-            (*seat).focus(crate::seat::Focus::Window(next_focus));
+            let app_id = (*w).get_app_id_string();
+            let is_status_bar = app_id.as_deref().map_or(false, |id| id.starts_with("cce-status"));
+            let is_wallpaper = app_id.as_deref() == Some("cce-wallpaper");
+            !is_status_bar && !is_wallpaper
+        };
+        let candidate = |w: *mut Window| crate::policy::focus::FocusCandidate {
+            id: crate::policy::api::WindowId((*w).ref_key),
+            eligible: eligible(w),
+        };
+        let history: Vec<_> = self
+            .focus_history
+            .iter()
+            .filter(|&&w| !w.is_null())
+            .map(|&w| candidate(w))
+            .collect();
+        let windows: Vec<_> = self
+            .windows
+            .iter()
+            .filter(|&&w| !w.is_null())
+            .map(|&w| candidate(w))
+            .collect();
+        let next = crate::policy::focus::next_visible_focus(&history, &windows)
+            .and_then(|id| self.windows.get(id.0).copied())
+            .unwrap_or(std::ptr::null_mut());
+        if !next.is_null() {
+            (*seat).focus(crate::seat::Focus::Window(next));
         } else {
             (*seat).focus(crate::seat::Focus::None);
         }
@@ -2123,14 +2131,6 @@ impl WindowManager {
                 self.start_clean_exit();
             }
             Action::LayoutNext => {}
-            Action::OverlayLeft => {
-                self.layout.overlay_position = "left".to_string();
-                self.dirty_windowing();
-            }
-            Action::OverlayRight => {
-                self.layout.overlay_position = "right".to_string();
-                self.dirty_windowing();
-            }
             _ => {}
         }
     }
@@ -3707,6 +3707,12 @@ impl crate::policy::api::Compositor for WindowManager {
                         }
                     }
                 }
+                Command::SetOverlayPosition(side) => {
+                    self.layout.overlay_position = match side {
+                        crate::policy::api::OverlaySide::Left => "left".to_string(),
+                        crate::policy::api::OverlaySide::Right => "right".to_string(),
+                    };
+                }
                 Command::Relayout => self.dirty_windowing(),
                 Command::RefreshCamera => {
                     if matches!(self.state, WindowManagerState::Idle) {