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

commit34b3ae64f5c33d1c1fae0f07ccbad4b3e5335704
parent1a15b3f815
authorLucas Galante <[email protected]>
date2026-06-16 21:34
feat: adjust CSD geometry calculations and resolve layout gaps on transitions

 src/server/ipc_server.rs     | 12 +++++------
 src/server/run_server.rs     |  4 ++--
 src/server/status_server.rs  | 12 +++++------
 src/server/window.rs         | 50 +++++++++++++++++++++++++++++++++++++-------
 src/server/window_manager.rs | 48 ++++++++++++++++++++++++++++--------------
 src/server/xdg_toplevel.rs   |  7 +++++++
 6 files changed, 96 insertions(+), 37 deletions(-)

diff --git a/src/server/ipc_server.rs b/src/server/ipc_server.rs
index 22491f4..f1b3515 100644
--- a/src/server/ipc_server.rs
+++ b/src/server/ipc_server.rs
@@ -9,29 +9,29 @@ pub struct IpcRequest {
     pub reply_tx: mpsc::Sender<String>,
 }
 
-fn get_ipc_socket_path() -> String {
-    if let Ok(display) = std::env::var("WAYLAND_DISPLAY") {
+fn get_ipc_socket_path(display_socket: Option<&str>) -> String {
+    if let Some(display) = display_socket {
         format!("/tmp/cce-client-{}.sock", display)
     } else {
         "/tmp/cce-client.sock".to_string()
     }
 }
 
-pub fn spawn_ipc_server() -> mpsc::Receiver<IpcRequest> {
+pub fn spawn_ipc_server(display_socket: Option<String>) -> mpsc::Receiver<IpcRequest> {
     let (tx, rx) = mpsc::channel::<IpcRequest>();
     
     thread::Builder::new()
         .name("cce-ipc-server".to_string())
         .spawn(move || {
-            ipc_server_main(tx);
+            ipc_server_main(tx, display_socket);
         })
         .expect("Failed to spawn CCE IPC server thread");
 
     rx
 }
 
-fn ipc_server_main(tx: mpsc::Sender<IpcRequest>) {
-    let socket_path = get_ipc_socket_path();
+fn ipc_server_main(tx: mpsc::Sender<IpcRequest>, display_socket: Option<String>) {
+    let socket_path = get_ipc_socket_path(display_socket.as_deref());
     let _ = std::fs::remove_file(&socket_path);
 
     let listener = match UnixListener::bind(&socket_path) {
diff --git a/src/server/run_server.rs b/src/server/run_server.rs
index f496475..9328a6f 100644
--- a/src/server/run_server.rs
+++ b/src/server/run_server.rs
@@ -169,9 +169,9 @@ pub fn run_server() {
 
     std::env::set_var("WAYLAND_DISPLAY", &socket_str);
 
-    server.wm.start_ipc();
+    server.wm.start_ipc(Some(socket_str.clone()));
 
-    let status_sender = crate::status_server::spawn_status_server();
+    let status_sender = crate::status_server::spawn_status_server(Some(socket_str.clone()));
     server.wm.status_sender = Some(status_sender);
 
 
diff --git a/src/server/status_server.rs b/src/server/status_server.rs
index 334e730..b59e017 100644
--- a/src/server/status_server.rs
+++ b/src/server/status_server.rs
@@ -61,8 +61,8 @@ impl StatusSender {
     }
 }
 
-pub fn get_status_socket_path() -> String {
-    if let Ok(display) = std::env::var("WAYLAND_DISPLAY") {
+pub fn get_status_socket_path(display_socket: Option<&str>) -> String {
+    if let Some(display) = display_socket {
         format!("/tmp/cce-client-status-{}.sock", display)
     } else {
         "/tmp/cce-client-status.sock".to_string()
@@ -70,21 +70,21 @@ pub fn get_status_socket_path() -> String {
 }
 
 /// Spawn the status server thread. Returns a StatusSender for the main loop.
-pub fn spawn_status_server() -> StatusSender {
+pub fn spawn_status_server(display_socket: Option<String>) -> StatusSender {
     let (tx, rx) = mpsc::channel::<StatusUpdate>();
 
     std::thread::Builder::new()
         .name("cce-status-server".into())
         .spawn(move || {
-            status_server_main(rx);
+            status_server_main(rx, display_socket);
         })
         .expect("failed to spawn status server thread");
 
     StatusSender { tx }
 }
 
-fn status_server_main(rx: mpsc::Receiver<StatusUpdate>) {
-    let socket_path = get_status_socket_path();
+fn status_server_main(rx: mpsc::Receiver<StatusUpdate>, display_socket: Option<String>) {
+    let socket_path = get_status_socket_path(display_socket.as_deref());
     // Remove stale socket
     let _ = std::fs::remove_file(&socket_path);
 
diff --git a/src/server/window.rs b/src/server/window.rs
index 5fa49d8..1d4741f 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -234,6 +234,8 @@ pub struct Window {
     pub box_geom: ffi::wlr_box,
     pub margin_x: i32,
     pub margin_y: i32,
+    pub last_decor_w: i32,
+    pub last_decor_h: i32,
     pub foreign_toplevel_handle: *mut ffi::wlr_ext_foreign_toplevel_handle_v1,
     pub wlr_toplevel_handle: *mut ffi::wlr_foreign_toplevel_handle_v1,
 }
@@ -393,6 +395,8 @@ impl Window {
             box_geom: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
             margin_x: 0,
             margin_y: 0,
+            last_decor_w: 0,
+            last_decor_h: 0,
             foreign_toplevel_handle: std::ptr::null_mut(),
             wlr_toplevel_handle: std::ptr::null_mut(),
         });
@@ -739,6 +743,33 @@ impl Window {
         }
     }
 
+    pub unsafe fn get_decorations_size(&self) -> (i32, i32) {
+        if self.wm_requested.ssd {
+            return (0, 0);
+        }
+        let surface = self.root_surface();
+        if surface.is_null() {
+            return (0, 0);
+        }
+        let surf_w = ffi::river_wlr_surface_get_width(surface);
+        let surf_h = ffi::river_wlr_surface_get_height(surface);
+        
+        let (geom_w, geom_h) = match self.impl_type {
+            WindowImpl::Toplevel(toplevel) => {
+                if toplevel.is_null() {
+                    (surf_w, surf_h)
+                } else {
+                    ((*toplevel).geometry.width, (*toplevel).geometry.height)
+                }
+            }
+            _ => (surf_w, surf_h),
+        };
+        
+        let dec_w = (surf_w - geom_w).max(0);
+        let dec_h = (surf_h - geom_h).max(0);
+        (dec_w, dec_h)
+    }
+
     pub unsafe fn send_frame_done(&self) {
         assert_eq!(self.state, WindowState::Mapped);
         if !matches!(self.impl_type, WindowImpl::Destroying) {
@@ -1121,7 +1152,8 @@ impl Window {
 
 
 
-        let is_cascade = (*self.server).wm.get_mode_for_window(self as *mut Window) == crate::tiling::TilingMode::Cascade;
+        let mode = (*self.server).wm.get_mode_for_window(self as *mut Window);
+        let is_maximized_layout = mode == crate::tiling::TilingMode::Cascade || mode == crate::tiling::TilingMode::Grid;
         self.configure_scheduled = Configure {
             width,
             height,
@@ -1130,7 +1162,7 @@ impl Window {
             ssd: self.wm_requested.ssd,
             tiled: self.wm_requested.tiled,
             capabilities: self.wm_requested.capabilities,
-            maximized: self.wm_requested.maximized || is_cascade,
+            maximized: self.wm_requested.maximized || is_maximized_layout,
             inform_fullscreen: self.wm_requested.inform_fullscreen,
             resizing: self.wm_requested.resizing,
         };
@@ -1439,6 +1471,13 @@ impl Window {
                         x = 0;
                         y = 0;
                     }
+                    // For tiled CSD windows, do not shift the surfaces tree
+                    if !self.wm_requested.ssd &&
+                       self.tiling_mode != crate::tiling::TilingMode::Floating &&
+                       self.tiling_mode != crate::tiling::TilingMode::Popup {
+                        x = 0;
+                        y = 0;
+                    }
                     (x, y)
                 }
             }
@@ -1662,7 +1701,7 @@ impl Window {
 
         let children_head = ffi::river_scene_tree_get_children(self.surfaces.tree) as *mut WlList;
         if (*children_head).next != children_head {
-            ffi::wlr_scene_subsurface_tree_set_clip(self.surfaces.tree as *mut ffi::wlr_scene_node, &surface_clip);
+            ffi::wlr_scene_subsurface_tree_set_clip(self.surfaces.tree as *mut ffi::wlr_scene_node, std::ptr::null());
         }
     }
 }
@@ -2444,10 +2483,7 @@ impl Decoration {
 
         let children_head = ffi::river_scene_tree_get_children(self.surfaces.tree) as *mut WlList;
         if (*children_head).next != children_head {
-            let mut clip = *window_clip;
-            clip.x -= self.rendering_requested.offset_x;
-            clip.y -= self.rendering_requested.offset_y;
-            ffi::wlr_scene_subsurface_tree_set_clip(self.surfaces.tree as *mut ffi::wlr_scene_node, &clip);
+            ffi::wlr_scene_subsurface_tree_set_clip(self.surfaces.tree as *mut ffi::wlr_scene_node, std::ptr::null());
         }
     }
 }
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 723d621..7c47de7 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -167,9 +167,9 @@ impl WindowManager {
         Ok(())
     }
 
-    pub fn start_ipc(&mut self) {
+    pub fn start_ipc(&mut self, display_socket: Option<String>) {
         if self.ipc_rx.is_none() {
-            let rx = crate::ipc_server::spawn_ipc_server();
+            let rx = crate::ipc_server::spawn_ipc_server(display_socket);
             self.ipc_rx = Some(rx);
         }
     }
@@ -899,16 +899,24 @@ impl WindowManager {
                 (*win_ptr).rendering_requested.y = final_y;
 
                 if !self.expose_active {
+                    let mut target_w = w;
+                    let mut target_h = h;
+                    if !(*win_ptr).wm_requested.ssd {
+                        let (dec_w, dec_h) = (*win_ptr).get_decorations_size();
+                        target_w = (w - dec_w).max(1);
+                        target_h = (h - dec_h).max(1);
+                    }
                     (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
-                        width: w as u32,
-                        height: h as u32,
+                        width: target_w as u32,
+                        height: target_h as u32,
                     });
                     (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
-                        width: w as u32,
-                        height: h as u32,
+                        width: target_w as u32,
+                        height: target_h as u32,
                     };
-                    let is_cascade = self.get_mode_for_window(win_ptr) == crate::tiling::TilingMode::Cascade;
-                    (*win_ptr).wm_requested.tiled = if is_cascade { 0 } else { 1 | 2 | 4 | 8 };
+                    let mode = self.get_mode_for_window(win_ptr);
+                    let is_maximized_layout = mode == crate::tiling::TilingMode::Cascade || mode == crate::tiling::TilingMode::Grid;
+                    (*win_ptr).wm_requested.tiled = if is_maximized_layout { 0 } else { 1 | 2 | 4 | 8 };
                 }
 
                 let is_focused = win_ptr == focused_window;
@@ -965,13 +973,20 @@ impl WindowManager {
 
                     (*win_ptr).rendering_requested.x = sp_x;
                     (*win_ptr).rendering_requested.y = sp_y;
+                    let mut sp_target_w = sp_w;
+                    let mut sp_target_h = sp_h;
+                    if !(*win_ptr).wm_requested.ssd {
+                        let (dec_w, dec_h) = (*win_ptr).get_decorations_size();
+                        sp_target_w = (sp_w - dec_w).max(1);
+                        sp_target_h = (sp_h - dec_h).max(1);
+                    }
                     (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
-                        width: sp_w as u32,
-                        height: sp_h as u32,
+                        width: sp_target_w as u32,
+                        height: sp_target_h as u32,
                     });
                     (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
-                        width: sp_w as u32,
-                        height: sp_h as u32,
+                        width: sp_target_w as u32,
+                        height: sp_target_h as u32,
                     };
                     (*win_ptr).wm_requested.tiled = 1 | 2 | 4 | 8;
 
@@ -1087,10 +1102,11 @@ impl WindowManager {
                         usable_y + gap_left + fbw + bar_height + gap_top + cascade_offset * idx_floating
                     };
 
-                    let min_x = usable_x + gap_left;
-                    let max_x = usable_x + usable_w - gap_right - fw;
-                    let min_y = usable_y + bar_height + gap_top;
-                    let max_y = usable_y + usable_h - gap_bottom - fh;
+                    let min_visible = 64;
+                    let min_x = usable_x + gap_left - fw + fw.min(min_visible);
+                    let max_x = usable_x + usable_w - gap_right - fw.min(min_visible);
+                    let min_y = usable_y + bar_height + gap_top - fh + fh.min(min_visible);
+                    let max_y = usable_y + usable_h - gap_bottom - fh.min(min_visible);
 
                     fx = fx.clamp(min_x, max_x.max(min_x));
                     fy = fy.clamp(min_y, max_y.max(min_y));
diff --git a/src/server/xdg_toplevel.rs b/src/server/xdg_toplevel.rs
index 3fdaffe..efc2e98 100644
--- a/src/server/xdg_toplevel.rs
+++ b/src/server/xdg_toplevel.rs
@@ -484,6 +484,13 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
             (*window).rendering_scheduled.width = new_geometry.width as u32;
             (*window).rendering_scheduled.height = new_geometry.height as u32;
 
+            let (dec_w, dec_h) = (*window).get_decorations_size();
+            if dec_w != (*window).last_decor_w || dec_h != (*window).last_decor_h {
+                (*window).last_decor_w = dec_w;
+                (*window).last_decor_h = dec_h;
+                (*(*window).server).wm.dirty_windowing();
+            }
+
             match (*toplevel).configure_state {
                 ConfigureState::Acked => {
                     (*toplevel).configure_state = ConfigureState::Committed;