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

commitd4dda2595523e40755558e8d7d771e20ed6a0382
parent6145bb4bde
authorLucas Galante <[email protected]>
date2026-07-28 12:48
refactor: Toggle arm replaced by policy dispatch

execute_action passes the command arg through to Policy::action; the
Toggle arm is deleted (DefaultPolicy decides match-existing-vs-spawn)
and config::extract_program_name moves to the policy crate as
actions::program_name, its only caller. build_action_ctx additionally
captures app_id/title/mapped; Command::Spawn applies by re-entering
execute_action's Spawn arm (fork + sh -c).

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

 src/server/config.rs         | 13 ----------
 src/server/window_manager.rs | 56 ++++++--------------------------------------
 2 files changed, 7 insertions(+), 62 deletions(-)

diff --git a/src/server/config.rs b/src/server/config.rs
index 8da85b4..e3a8b85 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -838,19 +838,6 @@ pub fn default_state_path() -> Option<String> {
     }
 }
 
-pub fn extract_program_name(cmd: &str) -> String {
-    let trimmed = cmd.trim();
-    if trimmed.is_empty() {
-        return String::new();
-    }
-    let first_token = trimmed.split_whitespace().next().unwrap_or("");
-    if let Some(pos) = first_token.rfind('/') {
-        first_token[pos+1..].to_string()
-    } else {
-        first_token.to_string()
-    }
-}
-
 fn expand_env_vars(s: &str) -> String {
     let mut result = String::with_capacity(s.len());
     let chars: Vec<char> = s.chars().collect();
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 45226b4..02c8db9 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -770,6 +770,9 @@ impl WindowManager {
             let focus_cyclable = rendered.contains(&(w as usize)) && !(*w).minimized && !is_status;
             windows.push(ActionWindow {
                 id: WindowId((*w).ref_key),
+                app_id,
+                title: (*w).get_title_string(),
+                mapped: matches!((*w).state, crate::window::WindowState::Mapped),
                 x: (*w).virtual_x,
                 y: (*w).virtual_y,
                 w: if (*w).box_geom.width > 0 { (*w).box_geom.width as f64 } else { 800.0 },
@@ -2057,7 +2060,7 @@ impl WindowManager {
         {
             use crate::policy::api::{Compositor, Policy};
             let ctx = self.build_action_ctx();
-            let cmds = crate::policy::actions::DefaultPolicy.action(&ctx, *action);
+            let cmds = crate::policy::actions::DefaultPolicy.action(&ctx, *action, command);
             if !cmds.is_empty() {
                 for cmd in &cmds {
                     self.apply(cmd);
@@ -2092,54 +2095,6 @@ impl WindowManager {
                     }
                 }
             }
-            Action::Toggle => {
-                if let Some(cmd) = command {
-                    let prog_name = crate::config::extract_program_name(cmd);
-                    let mut matched_win: *mut Window = std::ptr::null_mut();
-                    for &w in self.windows.iter() {
-                        if !w.is_null() && !(*w).closed && matches!((*w).state, crate::window::WindowState::Mapped) {
-                            let aid = (*w).get_app_id_string();
-                            let title = (*w).get_title_string();
-
-                            let mut match_aid = false;
-                            if let Some(ref aid_str) = aid {
-                                let aid_lower = aid_str.to_lowercase();
-                                let prog_lower = prog_name.to_lowercase();
-                                if aid_lower == prog_lower || aid_lower.contains(&prog_lower) || prog_lower.contains(&aid_lower) {
-                                    match_aid = true;
-                                }
-                            } else if let Some(ref title_str) = title {
-                                let title_lower = title_str.to_lowercase();
-                                let prog_lower = prog_name.to_lowercase();
-                                if title_lower.contains(&prog_lower) {
-                                    match_aid = true;
-                                }
-                            }
-                            if match_aid {
-                                matched_win = w;
-                                break;
-                            }
-                        }
-                    }
-
-                    if !matched_win.is_null() {
-                        let aid = (*matched_win).get_app_id_string().unwrap_or_default();
-                        log::info!("toggle: closing window {:?}", aid);
-                        (*matched_win).close();
-                        if let Some(seat) = self.first_seat() {
-                            if let crate::seat::Focus::Window(fw) = (*seat).focused {
-                                if fw == matched_win {
-                                    self.focus_next_visible_window(seat);
-                                }
-                            }
-                        }
-                        self.dirty_windowing();
-                    } else {
-                        log::info!("toggle: spawning {}", cmd);
-                        self.execute_action(&Action::Spawn, Some(cmd));
-                    }
-                }
-            }
             Action::WindowSwitcher => {
                 self.launch_window_switcher(false);
             }
@@ -3696,6 +3651,9 @@ impl crate::policy::api::Compositor for WindowManager {
         use crate::policy::api::Command;
         unsafe {
             match *cmd {
+                Command::Spawn(ref cmdline) => {
+                    self.execute_action(&crate::config::Action::Spawn, Some(cmdline));
+                }
                 Command::SetCamera { camera, overview } => {
                     self.desk_pan_x = camera.pan_x;
                     self.desk_pan_y = camera.pan_y;