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

commit8fa815647f8fc43c442a495fe3a047a37638380d
parent984a28e309
authorLucas Galante <[email protected]>
date2026-06-26 12:29
feat: implement WindowManagerMode (Normal, Overview), add wm-mode IPC command, and integrate mode checking

 src/server/cursor.rs         | 10 ++++---
 src/server/seat.rs           | 19 +++++++++++---
 src/server/status_server.rs  |  2 +-
 src/server/window.rs         | 10 +++++++
 src/server/window_manager.rs | 35 ++++++++++++++++++++++++-
 src/server/xdg_toplevel.rs   | 62 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 128 insertions(+), 10 deletions(-)

diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index d1ba139..10fad50 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -461,7 +461,7 @@ impl Cursor {
                 if (*window).tiling_mode != crate::tiling::TilingMode::Popup
                     && (*window).tiling_mode != crate::tiling::TilingMode::Fullscreen
                     && (*window).tiling_mode != crate::tiling::TilingMode::Status
-                    && (*server).wm.desk_zoom >= 0.999
+                    && (*server).wm.mode == crate::window_manager::WindowManagerMode::Normal
                 {
                     match get_border_zone(window, lx, ly) {
                         BorderZone::Resize(edges) => {
@@ -563,7 +563,7 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
         }
 
         // --- ZOOMED OUT CLICK HANDLING ---
-        if (*event).button == 0x110 && (*(*seat).server).wm.desk_zoom < 0.999 {
+        if (*event).button == 0x110 && (*(*seat).server).wm.mode == crate::window_manager::WindowManagerMode::Overview {
             let lx = cursor.x();
             let ly = cursor.y();
             let server = seat.server;
@@ -597,6 +597,7 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                 let center_y = (*clicked_win).virtual_y + win_h / 2.0;
 
                 (*server).wm.desk_zoom = 1.0;
+                (*server).wm.mode = crate::window_manager::WindowManagerMode::Normal;
                 (*server).wm.desk_pan_x = center_x - viewport_w / 2.0;
                 (*server).wm.desk_pan_y = center_y - viewport_h / 2.0;
 
@@ -911,6 +912,7 @@ unsafe extern "C" fn handle_axis(listener: *mut ffi::wl_listener, data: *mut std
                     wm.desk_pan_x += (cx - phys_x) * (1.0 / old_zoom - 1.0 / new_zoom);
                     wm.desk_pan_y += (cy - phys_y) * (1.0 / old_zoom - 1.0 / new_zoom);
                     wm.desk_zoom = new_zoom;
+                    wm.mode = if new_zoom < 0.999 { crate::window_manager::WindowManagerMode::Overview } else { crate::window_manager::WindowManagerMode::Normal };
                     wm.dirty_windowing();
                 }
             }
@@ -1239,7 +1241,7 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
         log::info!("Swipe gesture matched action: {:?}", matched_action);
         cursor.gesture_triggered = true;
 
-        if matched_action == crate::config::Action::Expose && (*seat.server).wm.desk_zoom < 0.999 {
+        if matched_action == crate::config::Action::Expose && (*seat.server).wm.mode == crate::window_manager::WindowManagerMode::Overview {
             let lx = cursor.x();
             let ly = cursor.y();
             let mut hovered_win: *mut crate::window::Window = std::ptr::null_mut();
@@ -1470,7 +1472,7 @@ pub enum BorderZone {
 }
 
 pub unsafe fn get_border_zone(window: *mut crate::window::Window, lx: f64, ly: f64) -> BorderZone {
-    if (*(*window).server).wm.desk_zoom < 0.999 {
+    if (*(*window).server).wm.mode == crate::window_manager::WindowManagerMode::Overview {
         return BorderZone::None;
     }
     if (*window).tiling_mode == crate::tiling::TilingMode::Popup
diff --git a/src/server/seat.rs b/src/server/seat.rs
index f3c8312..d542213 100644
--- a/src/server/seat.rs
+++ b/src/server/seat.rs
@@ -995,20 +995,31 @@ impl Seat {
                         let mut vx = op.start_win_virtual_x;
                         let mut vy = op.start_win_virtual_y;
 
+                        if edges.left {
+                            vx = (*win).virtual_x;
+                        }
+                        if edges.top {
+                            vy = (*win).virtual_y;
+                        }
+
+                        if (*win).resize_edges != Some(edges) {
+                            (*win).resize_start_vx = op.start_win_virtual_x;
+                            (*win).resize_start_vy = op.start_win_virtual_y;
+                            (*win).resize_start_w = op.start_win_w;
+                            (*win).resize_start_h = op.start_win_h;
+                            (*win).resize_edges = Some(edges);
+                        }
+
                         if edges.left {
                             let w = std::cmp::max(50, (op.start_win_w as f64 - virtual_dx) as i32) as u32;
-                            let dw = w as i32 - op.start_win_w as i32;
                             new_w = w;
-                            vx = op.start_win_virtual_x - dw as f64;
                         } else if edges.right {
                             new_w = std::cmp::max(50, (op.start_win_w as f64 + virtual_dx) as i32) as u32;
                         }
 
                         if edges.top {
                             let h = std::cmp::max(50, (op.start_win_h as f64 - virtual_dy) as i32) as u32;
-                            let dh = h as i32 - op.start_win_h as i32;
                             new_h = h;
-                            vy = op.start_win_virtual_y - dh as f64;
                         } else if edges.bottom {
                             new_h = std::cmp::max(50, (op.start_win_h as f64 + virtual_dy) as i32) as u32;
                         }
diff --git a/src/server/status_server.rs b/src/server/status_server.rs
index 091c878..1ba3fb3 100644
--- a/src/server/status_server.rs
+++ b/src/server/status_server.rs
@@ -239,7 +239,7 @@ fn format_for_subscription(sub: Subscription, update: &StatusUpdate) -> String {
 pub unsafe fn build_status_update(wm: &crate::window_manager::WindowManager) -> StatusUpdate {
     let focused_window = wm.focused_window();
 
-    let text = format!("Zoom: {:.2} | Pan: ({:.0}, {:.0})", wm.desk_zoom, wm.desk_pan_x, wm.desk_pan_y);
+    let text = format!("Mode: {:?} | Zoom: {:.2} | Pan: ({:.0}, {:.0})", wm.mode, wm.desk_zoom, wm.desk_pan_x, wm.desk_pan_y);
     let escaped = text.replace('\\', "\\\\").replace('"', "\\\"");
     let viewport_json = format!("{{\"text\": \"{}\", \"tooltip\": \"Camera State\"}}", escaped);
 
diff --git a/src/server/window.rs b/src/server/window.rs
index 421a796..6480e47 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -224,6 +224,11 @@ pub struct Window {
     pub scale: f64,
     pub virtual_x: f64,
     pub virtual_y: f64,
+    pub resize_start_vx: f64,
+    pub resize_start_vy: f64,
+    pub resize_start_w: u32,
+    pub resize_start_h: u32,
+    pub resize_edges: Option<Edges>,
     pub commit: ffi::wl_listener,
 
     pub wm_scheduled: WmScheduledState,
@@ -381,6 +386,11 @@ impl Window {
             scale: 1.0,
             virtual_x: unsafe { (*server).wm.desk_pan_x + 100.0 },
             virtual_y: unsafe { (*server).wm.desk_pan_y + 100.0 },
+            resize_start_vx: 0.0,
+            resize_start_vy: 0.0,
+            resize_start_w: 0,
+            resize_start_h: 0,
+            resize_edges: None,
             commit: std::mem::zeroed(),
             wm_scheduled: WmScheduledState {
                 dimensions_hint: DimensionsHint { min_width: 0, min_height: 0, max_width: 0, max_height: 0 },
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 0257afc..ee43d49 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -19,6 +19,12 @@ pub enum WindowManagerState {
     Render,
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum WindowManagerMode {
+    Normal,
+    Overview,
+}
+
 pub struct WindowManagerScheduled {
     pub dirty: bool,
     pub dirty_lazy: bool,
@@ -83,6 +89,7 @@ pub struct WindowManager {
     pub desk_pan_x: f64,
     pub desk_pan_y: f64,
     pub desk_zoom: f64,
+    pub mode: WindowManagerMode,
     pub global_layout: crate::tiling::TilingMode,
     pub layout: crate::config::Layout,
     pub mode_rules: Vec<crate::config::ModeRule>,
@@ -117,6 +124,7 @@ impl WindowManager {
         self.output_scale = 1.0;
         self.input_rules = Vec::new();
         self.input_config = crate::config::InputConfig::default();
+        self.mode = WindowManagerMode::Normal;
         Ok(())
     }
 
@@ -152,6 +160,7 @@ impl WindowManager {
         self.target_desk_pan_y = None;
         self.animation_timer = std::ptr::null_mut();
         self.desk_zoom = 1.0;
+        self.mode = WindowManagerMode::Normal;
         self.global_layout = crate::tiling::TilingMode::Cascade;
         self.restore_queue = Vec::new();
         self.shutting_down = false;
@@ -215,6 +224,7 @@ impl WindowManager {
                 self.desk_pan_x = state.desk_pan_x;
                 self.desk_pan_y = state.desk_pan_y;
                 self.desk_zoom = state.desk_zoom;
+                self.mode = if state.desk_zoom < 0.999 { WindowManagerMode::Overview } else { WindowManagerMode::Normal };
                 self.global_layout = state.global_layout;
                 self.restore_queue = state.windows;
                 self.has_restored_focused_window = self.restore_queue.iter().any(|w| w.focused);
@@ -1640,6 +1650,7 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                 self.desk_pan_x = cx - (viewport_w / 2.0) / new_zoom;
                 self.desk_pan_y = cy - (viewport_h / 2.0) / new_zoom;
                 self.desk_zoom = new_zoom;
+                self.mode = if new_zoom < 0.999 { WindowManagerMode::Overview } else { WindowManagerMode::Normal };
                 self.dirty_windowing();
             }
             Action::PanLeft | Action::PanRight | Action::PanUp | Action::PanDown => {
@@ -1662,7 +1673,7 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                 self.dirty_windowing();
             }
             Action::Expose => {
-                if self.desk_zoom < 0.999 {
+                if self.mode == WindowManagerMode::Overview {
                     let mut viewport_w = 1920.0;
                     let mut viewport_h = 1080.0;
                     let outputs_list = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
@@ -1686,6 +1697,7 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                                 let center_x = (*fw).virtual_x + win_w / 2.0;
                                 let center_y = (*fw).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();
@@ -1695,6 +1707,7 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                         }
                     }
                     self.desk_zoom = 1.0;
+                    self.mode = WindowManagerMode::Normal;
                     self.desk_pan_x = 0.0;
                     self.desk_pan_y = 0.0;
                     self.stop_panning_animation();
@@ -1771,6 +1784,7 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                         let center_y = min_vy + box_h / 2.0;
 
                         self.desk_zoom = new_zoom;
+                        self.mode = WindowManagerMode::Overview;
                         self.desk_pan_x = center_x - (viewport_w / 2.0) / new_zoom;
                         self.desk_pan_y = center_y - (viewport_h / 2.0) / new_zoom;
                         self.dirty_windowing();
@@ -1877,6 +1891,7 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                     self.desk_pan_x = cx - (viewport_w / 2.0) / new_zoom;
                     self.desk_pan_y = cy - (viewport_h / 2.0) / new_zoom;
                     self.desk_zoom = new_zoom;
+                    self.mode = if new_zoom < 0.999 { WindowManagerMode::Overview } else { WindowManagerMode::Normal };
                     self.dirty_windowing();
                     return "ok\n".to_string();
                 }
@@ -1930,6 +1945,24 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                 self.execute_action(&crate::config::Action::Expose, None);
                 "ok\n".to_string()
             }
+            "wm-mode" => {
+                if parts.len() < 2 {
+                    return format!("{:?}\n", self.mode).to_lowercase();
+                }
+                let target = parts[1].to_lowercase();
+                if target == "normal" {
+                    if self.mode == WindowManagerMode::Overview {
+                        self.execute_action(&crate::config::Action::Expose, None);
+                    }
+                    return "ok\n".to_string();
+                } else if target == "overview" {
+                    if self.mode == WindowManagerMode::Normal {
+                        self.execute_action(&crate::config::Action::Expose, None);
+                    }
+                    return "ok\n".to_string();
+                }
+                "error: invalid mode, specify 'normal' or 'overview'\n".to_string()
+            }
             "minimize" => {
                 self.execute_action(&crate::config::Action::Minimize, None);
                 "ok\n".to_string()
diff --git a/src/server/xdg_toplevel.rs b/src/server/xdg_toplevel.rs
index 4cbb018..c569178 100644
--- a/src/server/xdg_toplevel.rs
+++ b/src/server/xdg_toplevel.rs
@@ -514,6 +514,68 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
             }
         }
     }
+
+    if let Some(edges) = (*window).resize_edges {
+        let geometry = (*toplevel).geometry;
+        let mut resize_active = false;
+        let server = (*window).server;
+        let seats_list = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+        let mut curr_seat = (*seats_list).next;
+        while curr_seat != seats_list {
+            let seat = crate::container_of!(curr_seat, crate::seat::Seat, link);
+            if let Some(ref op) = (*seat).op {
+                if op.window_ptr == window {
+                    if let crate::seat::PointerOpType::Resize { .. } = op.op_type {
+                        resize_active = true;
+                        break;
+                    }
+                }
+            }
+            curr_seat = (*curr_seat).next;
+        }
+
+        let mut new_vx = (*window).virtual_x;
+        let mut new_vy = (*window).virtual_y;
+        if edges.left {
+            new_vx = (*window).resize_start_vx + ((*window).resize_start_w as f64 - geometry.width as f64);
+        }
+        if edges.top {
+            new_vy = (*window).resize_start_vy + ((*window).resize_start_h as f64 - geometry.height as f64);
+        }
+        (*window).virtual_x = new_vx;
+        (*window).virtual_y = new_vy;
+
+        let scale = (*server).wm.desk_zoom;
+        let pan_x = (*server).wm.desk_pan_x;
+        let pan_y = (*server).wm.desk_pan_y;
+
+        let mut out_x = 0;
+        let mut out_y = 0;
+        let outputs_list = &mut (*server).om.outputs as *mut 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();
+                out_x = wlr_box.x;
+                out_y = wlr_box.y;
+                break;
+            }
+            curr_out = (*curr_out).next;
+        }
+
+        let final_x = out_x + ((new_vx - pan_x) * scale) as i32;
+        let final_y = out_y + ((new_vy - pan_y) * scale) as i32;
+
+        (*window).rendering_requested.x = final_x;
+        (*window).rendering_requested.y = final_y;
+        (*window).box_geom.x = final_x;
+        (*window).box_geom.y = final_y;
+
+        if !resize_active {
+            (*window).resize_edges = None;
+        }
+    }
 }
 
 unsafe extern "C" fn handle_request_show_window_menu(