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

commit6e077d5ca1dab1aea0a35e4050c5c64daf547da9
parentd444a0e94c
authorLucas Galante <[email protected]>
date2026-07-01 12:11
Support all built-in commands via cce-ctl IPC, remove server direct action parser mapping

 src/cce_ctl.rs               |  13 +++++
 src/server/config.rs         |  92 +++------------------------------
 src/server/window_manager.rs | 118 +++++++++++++++++++++++++++++++++++--------
 3 files changed, 117 insertions(+), 106 deletions(-)

diff --git a/src/cce_ctl.rs b/src/cce_ctl.rs
index bd0a428..191b9cc 100644
--- a/src/cce_ctl.rs
+++ b/src/cce_ctl.rs
@@ -36,6 +36,19 @@ fn usage(name: &str, to_stderr: bool) {
     print("  close");
     print("  minimize");
     print("  focus-next");
+    print("  focus-prev");
+    print("  fullscreen");
+    print("  mode-next");
+    print("  mode-next-shared");
+    print("  zoom-in");
+    print("  zoom-out");
+    print("  zoom-reset");
+    print("  pan-left");
+    print("  pan-right");
+    print("  pan-up");
+    print("  pan-down");
+    print("  overlay-left");
+    print("  overlay-right");
     print("  focus-window <app_id>");
     print("  expose");
     print("  windows");
diff --git a/src/server/config.rs b/src/server/config.rs
index 803b4f0..580c7c2 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -588,93 +588,17 @@ pub fn parse_button(s: &str) -> u32 {
 }
 
 pub fn parse_action(s: &str) -> Action {
-    if s == "close" {
-        Action::Close
-    } else if s == "exit" {
-        Action::Exit
-    } else if s == "reload" {
-        Action::Reload
-    } else if s == "focus-next" {
-        Action::FocusNext
-    } else if s == "focus-prev" {
-        Action::FocusPrev
-    } else if s == "move" {
-        Action::Move
-    } else if s == "resize" {
-        Action::Resize
-    } else if s == "layout-next" {
-        Action::LayoutNext
-    } else if s == "mode-next" {
-        Action::ModeNext
-    } else if s == "mode-next-shared" {
-        Action::ModeNextShared
-    } else if s == "fullscreen" {
-        Action::Fullscreen
-    } else if s == "minimize" {
-        Action::Minimize
-    } else if s.starts_with("spawn")
-        && (s.len() == 5 || s.as_bytes()[5] == b' ' || s.as_bytes()[5] == b'-')
+    let trimmed = s.trim();
+    if trimmed.starts_with("spawn")
+        && (trimmed.len() == 5 || trimmed.as_bytes()[5] == b' ' || trimmed.as_bytes()[5] == b'-')
     {
         Action::Spawn
-    } else if s.starts_with("view") {
-        let rest = &s[4..];
-        let tag_str = rest
-            .strip_prefix('-')
-            .or_else(|| rest.strip_prefix(' '))
-            .unwrap_or(rest);
-        if let Ok(tag) = tag_str.parse::<i32>() {
-            if tag >= 1 && tag <= 4 {
-                return match tag {
-                    1 => Action::View1,
-                    2 => Action::View2,
-                    3 => Action::View3,
-                    4 => Action::View4,
-                    _ => Action::None,
-                };
-            }
-        }
-        Action::None
-    } else if s == "toggle" {
+    } else if trimmed == "toggle" {
         Action::Toggle
-    } else if s.starts_with("set-viewport") || s.starts_with("set-tag") {
-        let is_viewport = s.starts_with("set-viewport");
-        let rest = if is_viewport { &s[12..] } else { &s[7..] };
-        let tag_str = rest
-            .strip_prefix('-')
-            .or_else(|| rest.strip_prefix(' '))
-            .unwrap_or(rest);
-        if let Ok(tag) = tag_str.parse::<i32>() {
-            if tag >= 1 && tag <= 4 {
-                return match tag {
-                    1 => Action::SetViewport1,
-                    2 => Action::SetViewport2,
-                    3 => Action::SetViewport3,
-                    4 => Action::SetViewport4,
-                    _ => Action::None,
-                };
-            }
-        }
-        Action::None
-    } else if s == "expose" {
-        Action::Expose
-    } else if s == "side-panel-left" || s == "pinned-left" || s == "overlay-left" {
-        Action::OverlayLeft
-    } else if s == "side-panel-right" || s == "pinned-right" || s == "overlay-right" {
-        Action::OverlayRight
-    } else if s == "zoom-in" {
-        Action::ZoomIn
-    } else if s == "zoom-out" {
-        Action::ZoomOut
-    } else if s == "zoom-reset" {
-        Action::ZoomReset
-    } else if s == "pan-left" {
-        Action::PanLeft
-    } else if s == "pan-right" {
-        Action::PanRight
-    } else if s == "pan-up" {
-        Action::PanUp
-    } else if s == "pan-down" {
-        Action::PanDown
+    } else if trimmed == "move" {
+        Action::Move
+    } else if trimmed == "resize" {
+        Action::Resize
     } else {
         Action::None
     }
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 1982bc1..9994ac5 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -697,6 +697,20 @@ impl WindowManager {
             }
         }
 
+        let has_wallpaper = self.windows.iter().any(|&w| !w.is_null() && !(*w).closed && (*w).get_app_id_string().as_deref() == Some("cce-wallpaper"));
+        let outputs_list = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
+        let mut curr_out = (*outputs_list).next;
+        while curr_out != outputs_list {
+            let next_out = (*curr_out).next;
+            let output = crate::container_of!(curr_out, crate::output::Output, link);
+            if (*output).sent.state == crate::output::OutputStateValue::Enabled {
+                if !(*output).background_rect.is_null() {
+                    ffi::wlr_scene_node_set_enabled((*output).background_rect as *mut ffi::wlr_scene_node, !has_wallpaper);
+                }
+            }
+            curr_out = next_out;
+        }
+
         self.keep_status_bar_on_top();
 
         let mut hasher = std::collections::hash_map::DefaultHasher::new();
@@ -723,6 +737,18 @@ impl WindowManager {
         let new_order_hash = hasher.finish();
         let reorder = self.rendering_requested.order_hash != new_order_hash;
         self.rendering_requested.order_hash = new_order_hash;
+        log::info!("[render_finish_loop] START - render_list={:p}", render_list);
+        let mut debug_curr = (*render_list).next;
+        let mut debug_idx = 0;
+        while debug_curr != render_list {
+            let node = crate::container_of!(debug_curr, crate::wm_node::WmNode, link);
+            if let crate::wm_node::WmNodeType::Window(window) = (*node).get() {
+                let app_id = (*window).get_app_id_string().unwrap_or_default();
+                log::info!("[render_finish_loop] List[{}] = app_id={} state={:?}", debug_idx, app_id, (*window).state);
+            }
+            debug_curr = (*debug_curr).next;
+            debug_idx += 1;
+        }
 
         let mut found_fullscreen = false;
         curr = (*render_list).next;
@@ -1420,13 +1446,13 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
 
             left_status.sort_by_key(|&w| unsafe {
                 let app_id = (*w).get_app_id_string().unwrap_or_default();
-                let name = app_id.trim_start_matches("cce-status-left-");
+                let name = app_id.strip_prefix("cce-status-left-").unwrap_or(&app_id);
                 LEFT_ORDER.iter().position(|&m| m == name).unwrap_or(99)
             });
 
             right_status.sort_by_key(|&w| unsafe {
                 let app_id = (*w).get_app_id_string().unwrap_or_default();
-                let name = app_id.trim_start_matches("cce-status-right-");
+                let name = app_id.strip_prefix("cce-status-right-").unwrap_or(&app_id);
                 RIGHT_ORDER.iter().position(|&m| m == name).unwrap_or(99)
             });
 
@@ -1456,10 +1482,11 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
             // Layout Right status windows (right-to-left)
             let mut cur_right_x = wlr_box.x + wlr_box.width - margin;
             for win_ptr in right_status.into_iter().rev() {
-                let w = if (*win_ptr).box_geom.width > 0 { (*win_ptr).box_geom.width as u32 } else { 100 };
+                let actual_w = (*win_ptr).box_geom.width;
+                let w = if actual_w > 0 { actual_w as u32 } else { 100 };
                 let x = cur_right_x - w as i32;
                 let app_id = (*win_ptr).get_app_id_string().unwrap_or_default();
-                log::info!("[ArrangeStatus] Right module app_id={} x={} y={} w={}", app_id, x, wlr_box.y, w);
+                log::info!("[ArrangeStatus] Right module app_id={} actual_box_w={} x={} y={} w={}", app_id, actual_w, x, wlr_box.y, w);
                 (*win_ptr).rendering_requested.x = x;
                 (*win_ptr).rendering_requested.y = wlr_box.y;
                 (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
@@ -1683,25 +1710,26 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
             if win_ptr.is_null() || (*win_ptr).closed {
                 continue;
             }
-            if !matches!((*win_ptr).state, crate::window::WindowState::Mapped) {
-                continue;
-            }
             if let Some(app_id) = (*win_ptr).get_app_id_string() {
                 if app_id.starts_with("cce-status") {
-                    status_bar_windows.push(win_ptr);
+                    if matches!((*win_ptr).state, crate::window::WindowState::Mapped) {
+                        status_bar_windows.push(win_ptr);
+                    }
                 }
             }
         }
         for win_ptr in status_bar_windows {
             let node_link = &mut (*win_ptr).node.link as *mut ffi::wl_list as *mut WlList;
             let list_head = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
-            if !node_link.is_null() && !list_head.is_null() && (*node_link).next != list_head {
-                if !(*node_link).prev.is_null() && !(*node_link).next.is_null() {
-                    crate::server::wl_list_remove(node_link);
-                }
-                let prev_node = (*list_head).prev;
-                if !prev_node.is_null() && (*prev_node).next == list_head {
-                    crate::server::wl_list_insert(prev_node, node_link);
+            if !node_link.is_null() && !list_head.is_null() {
+                if (*node_link).next != list_head {
+                    if (*win_ptr).is_linked() {
+                        crate::server::wl_list_remove_and_reinit(node_link);
+                    }
+                    let last = (*list_head).prev;
+                    if !last.is_null() {
+                        crate::server::wl_list_insert(last, node_link);
+                    }
                 }
             }
         }
@@ -1713,13 +1741,15 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
         }
         let node_link = &mut (*window).node.link as *mut ffi::wl_list as *mut WlList;
         let list_head = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
-        if !node_link.is_null() && !list_head.is_null() && (*node_link).next != list_head {
-            if !(*node_link).prev.is_null() && !(*node_link).next.is_null() {
-                crate::server::wl_list_remove(node_link);
-            }
-            let prev_node = (*list_head).prev;
-            if !prev_node.is_null() && (*prev_node).next == list_head {
-                crate::server::wl_list_insert(prev_node, node_link);
+        if !node_link.is_null() && !list_head.is_null() {
+            if (*node_link).next != list_head {
+                if (*window).is_linked() {
+                    crate::server::wl_list_remove_and_reinit(node_link);
+                }
+                let last = (*list_head).prev;
+                if !last.is_null() {
+                    crate::server::wl_list_insert(last, node_link);
+                }
             }
         }
         self.keep_status_bar_on_top();
@@ -2278,6 +2308,34 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                 self.execute_action(&crate::config::Action::ZoomOut, None);
                 "ok\n".to_string()
             }
+            "zoom-reset" => {
+                self.execute_action(&crate::config::Action::ZoomReset, None);
+                "ok\n".to_string()
+            }
+            "pan-left" => {
+                self.execute_action(&crate::config::Action::PanLeft, None);
+                "ok\n".to_string()
+            }
+            "pan-right" => {
+                self.execute_action(&crate::config::Action::PanRight, None);
+                "ok\n".to_string()
+            }
+            "pan-up" => {
+                self.execute_action(&crate::config::Action::PanUp, None);
+                "ok\n".to_string()
+            }
+            "pan-down" => {
+                self.execute_action(&crate::config::Action::PanDown, None);
+                "ok\n".to_string()
+            }
+            "overlay-left" => {
+                self.execute_action(&crate::config::Action::OverlayLeft, None);
+                "ok\n".to_string()
+            }
+            "overlay-right" => {
+                self.execute_action(&crate::config::Action::OverlayRight, None);
+                "ok\n".to_string()
+            }
             "set-zoom" => {
                 if parts.len() < 2 { return "error: missing zoom factor\n".to_string(); }
                 if let Ok(factor) = parts[1].parse::<f64>() {
@@ -2380,6 +2438,22 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                 self.execute_action(&crate::config::Action::FocusNext, None);
                 "ok\n".to_string()
             }
+            "focus-prev" => {
+                self.execute_action(&crate::config::Action::FocusPrev, None);
+                "ok\n".to_string()
+            }
+            "fullscreen" => {
+                self.execute_action(&crate::config::Action::Fullscreen, None);
+                "ok\n".to_string()
+            }
+            "mode-next" => {
+                self.execute_action(&crate::config::Action::ModeNext, None);
+                "ok\n".to_string()
+            }
+            "mode-next-shared" => {
+                self.execute_action(&crate::config::Action::ModeNextShared, None);
+                "ok\n".to_string()
+            }
             "focus-window" => {
                 if parts.len() < 2 { return "error: missing app_id/id\n".to_string(); }
                 let query = parts[1..].join(" ").to_lowercase();