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

commit25043107a4c8053a2ec91b085478251303ff9842
parenteef5c884cc
authorLucas Galante <[email protected]>
date2026-06-15 15:18
Implement dragging and resizing for server-side borders on floating windows

 src/server/cursor.rs | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/server/seat.rs   |  34 +++++++--
 2 files changed, 223 insertions(+), 5 deletions(-)

diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 6931d84..85724ba 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -447,6 +447,25 @@ impl Cursor {
                 }
             }
 
+            if let SceneNodeDataVal::Window(window) = result.data {
+                if (*window).tiling_mode == crate::tiling::TilingMode::Floating {
+                    match get_border_zone(window, lx, ly) {
+                        BorderZone::Resize(edges) => {
+                            ffi::wlr_seat_pointer_notify_clear_focus((*self.seat).wlr_seat);
+                            let cursor_name = get_resize_cursor_name(edges);
+                            self.set_xcursor(cursor_name.as_ptr() as *const _);
+                            return;
+                        }
+                        BorderZone::Move => {
+                            ffi::wlr_seat_pointer_notify_clear_focus((*self.seat).wlr_seat);
+                            self.set_xcursor(b"grab\0".as_ptr() as *const _);
+                            return;
+                        }
+                        BorderZone::None => {}
+                    }
+                }
+            }
+
             if !result.surface.is_null() {
                 ffi::wlr_seat_pointer_notify_enter((*self.seat).wlr_seat, result.surface, result.sx, result.sy);
                 ffi::wlr_seat_pointer_notify_motion((*self.seat).wlr_seat, time_msec, result.sx, result.sy);
@@ -560,7 +579,10 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                 
                 let op_type = match pb.action {
                     crate::config::Action::Move => Some(crate::seat::PointerOpType::Move),
-                    crate::config::Action::Resize => Some(crate::seat::PointerOpType::Resize),
+                    crate::config::Action::Resize => {
+                        let edges = get_closest_edges(target_win, lx, ly);
+                        Some(crate::seat::PointerOpType::Resize { edges })
+                    }
                     _ => None,
                 };
                 
@@ -583,11 +605,90 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                     });
                     cursor.op_start_pointer();
                     cursor.pressed.insert((*event).button, None);
+
+                    match ot {
+                        crate::seat::PointerOpType::Resize { edges } => {
+                            let cursor_name = get_resize_cursor_name(edges);
+                            cursor.set_xcursor(cursor_name.as_ptr() as *const _);
+                        }
+                        crate::seat::PointerOpType::Move => {
+                            cursor.set_xcursor(b"grab\0".as_ptr() as *const _);
+                        }
+                    }
                     return;
                 }
             }
         }
 
+        let lx = cursor.x();
+        let ly = cursor.y();
+        let server = seat.server;
+        let mut border_target_win: *mut crate::window::Window = std::ptr::null_mut();
+        if let Some(result) = (*server).scene.at(lx, ly) {
+            if let SceneNodeDataVal::Window(window) = result.data {
+                border_target_win = window;
+            }
+        }
+
+        if !border_target_win.is_null() && (*border_target_win).tiling_mode == crate::tiling::TilingMode::Floating {
+            match get_border_zone(border_target_win, lx, ly) {
+                BorderZone::Resize(edges) => {
+                    if (*event).button == 0x110 { // BTN_LEFT
+                        seat.focus(Focus::Window(border_target_win));
+                        let cursor_x = (*cursor.wlr_cursor).x;
+                        let cursor_y = (*cursor.wlr_cursor).y;
+                        seat.op = Some(crate::seat::SeatOp {
+                            sent_release: false,
+                            input: crate::seat::SeatOpInput::Pointer,
+                            start_x: cursor_x as i32,
+                            start_y: cursor_y as i32,
+                            x: cursor_x as i32,
+                            y: cursor_y as i32,
+                            window_ptr: border_target_win,
+                            op_type: crate::seat::PointerOpType::Resize { edges },
+                            start_win_x: (*border_target_win).box_geom.x,
+                            start_win_y: (*border_target_win).box_geom.y,
+                            start_win_w: (*border_target_win).box_geom.width as u32,
+                            start_win_h: (*border_target_win).box_geom.height as u32,
+                        });
+                        cursor.op_start_pointer();
+                        cursor.pressed.insert((*event).button, None);
+
+                        let cursor_name = get_resize_cursor_name(edges);
+                        cursor.set_xcursor(cursor_name.as_ptr() as *const _);
+                        return;
+                    }
+                }
+                BorderZone::Move => {
+                    if (*event).button == 0x110 { // BTN_LEFT
+                        seat.focus(Focus::Window(border_target_win));
+                        let cursor_x = (*cursor.wlr_cursor).x;
+                        let cursor_y = (*cursor.wlr_cursor).y;
+                        seat.op = Some(crate::seat::SeatOp {
+                            sent_release: false,
+                            input: crate::seat::SeatOpInput::Pointer,
+                            start_x: cursor_x as i32,
+                            start_y: cursor_y as i32,
+                            x: cursor_x as i32,
+                            y: cursor_y as i32,
+                            window_ptr: border_target_win,
+                            op_type: crate::seat::PointerOpType::Move,
+                            start_win_x: (*border_target_win).box_geom.x,
+                            start_win_y: (*border_target_win).box_geom.y,
+                            start_win_w: (*border_target_win).box_geom.width as u32,
+                            start_win_h: (*border_target_win).box_geom.height as u32,
+                        });
+                        cursor.op_start_pointer();
+                        cursor.pressed.insert((*event).button, None);
+
+                        cursor.set_xcursor(b"grab\0".as_ptr() as *const _);
+                        return;
+                    }
+                }
+                BorderZone::None => {}
+            }
+        }
+
         if let Some(binding) = seat.match_pointer_binding((*event).button) {
             cursor.pressed.insert((*event).button, Some(binding));
             (*binding).pressed();
@@ -1059,3 +1160,94 @@ unsafe extern "C" fn handle_hold_end(listener: *mut ffi::wl_listener, data: *mut
     }
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum BorderZone {
+    None,
+    Move,
+    Resize(crate::window::Edges),
+}
+
+pub unsafe fn get_border_zone(window: *mut crate::window::Window, lx: f64, ly: f64) -> BorderZone {
+    if (*window).tiling_mode != crate::tiling::TilingMode::Floating {
+        return BorderZone::None;
+    }
+    
+    let bw = (*window).rendering_requested.border.width as f64;
+    if bw <= 0.0 {
+        return BorderZone::None;
+    }
+
+    let geom = (*window).box_geom;
+    let rx = lx - geom.x as f64;
+    let ry = ly - geom.y as f64;
+
+    let content_w = geom.width as f64;
+    let content_h = geom.height as f64;
+
+    if rx >= 0.0 && rx < content_w && ry >= 0.0 && ry < content_h {
+        return BorderZone::None;
+    }
+
+    if rx >= -bw && rx < content_w + bw && ry >= -bw && ry < content_h + bw {
+        let threshold = if bw <= 3.0 { bw / 2.0 } else { 3.0 };
+
+        let dist_left = rx + bw;
+        let dist_right = (content_w + bw) - rx;
+        let dist_top = ry + bw;
+        let dist_bottom = (content_h + bw) - ry;
+
+        let min_dist = dist_left.min(dist_right).min(dist_top).min(dist_bottom);
+
+        if min_dist >= 0.0 && min_dist < threshold {
+            let delta = threshold + 1.0;
+            let left = dist_left < delta;
+            let right = dist_right < delta;
+            let top = dist_top < delta;
+            let bottom = dist_bottom < delta;
+
+            return BorderZone::Resize(crate::window::Edges { top, bottom, left, right });
+        } else {
+            return BorderZone::Move;
+        }
+    }
+
+    BorderZone::None
+}
+
+pub fn get_resize_cursor_name(edges: crate::window::Edges) -> &'static [u8] {
+    if edges.top && edges.left {
+        b"nw-resize\0"
+    } else if edges.top && edges.right {
+        b"ne-resize\0"
+    } else if edges.bottom && edges.left {
+        b"sw-resize\0"
+    } else if edges.bottom && edges.right {
+        b"se-resize\0"
+    } else if edges.top {
+        b"n-resize\0"
+    } else if edges.bottom {
+        b"s-resize\0"
+    } else if edges.left {
+        b"w-resize\0"
+    } else if edges.right {
+        b"e-resize\0"
+    } else {
+        b"default\0"
+    }
+}
+
+pub unsafe fn get_closest_edges(window: *mut crate::window::Window, lx: f64, ly: f64) -> crate::window::Edges {
+    let geom = (*window).box_geom;
+    let rx = lx - geom.x as f64;
+    let ry = ly - geom.y as f64;
+    let w = geom.width as f64;
+    let h = geom.height as f64;
+
+    let left = rx < w / 2.0;
+    let right = !left;
+    let top = ry < h / 2.0;
+    let bottom = !top;
+
+    crate::window::Edges { top, bottom, left, right }
+}
+
diff --git a/src/server/seat.rs b/src/server/seat.rs
index 28d3204..38b1b30 100644
--- a/src/server/seat.rs
+++ b/src/server/seat.rs
@@ -10,7 +10,7 @@ pub enum SeatOpInput {
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum PointerOpType {
     Move,
-    Resize,
+    Resize { edges: crate::window::Edges },
 }
 
 #[derive(Clone, Copy, Debug)]
@@ -827,9 +827,35 @@ impl Seat {
                         (*win).box_geom.x = op.start_win_x + dx;
                         (*win).box_geom.y = op.start_win_y + dy;
                     }
-                    PointerOpType::Resize => {
-                        let new_w = std::cmp::max(50, op.start_win_w as i32 + dx) as u32;
-                        let new_h = std::cmp::max(50, op.start_win_h as i32 + dy) as u32;
+                    PointerOpType::Resize { edges } => {
+                        let mut new_w = op.start_win_w;
+                        let mut new_h = op.start_win_h;
+                        let mut new_x = op.start_win_x;
+                        let mut new_y = op.start_win_y;
+
+                        if edges.left {
+                            let w = std::cmp::max(50, op.start_win_w as i32 - dx) as u32;
+                            let dw = w as i32 - op.start_win_w as i32;
+                            new_w = w;
+                            new_x = op.start_win_x - dw;
+                        } else if edges.right {
+                            new_w = std::cmp::max(50, op.start_win_w as i32 + dx) as u32;
+                        }
+
+                        if edges.top {
+                            let h = std::cmp::max(50, op.start_win_h as i32 - dy) as u32;
+                            let dh = h as i32 - op.start_win_h as i32;
+                            new_h = h;
+                            new_y = op.start_win_y - dh;
+                        } else if edges.bottom {
+                            new_h = std::cmp::max(50, op.start_win_h as i32 + dy) as u32;
+                        }
+
+                        (*win).rendering_requested.x = new_x;
+                        (*win).rendering_requested.y = new_y;
+                        (*win).box_geom.x = new_x;
+                        (*win).box_geom.y = new_y;
+
                         (*win).wm_requested.dimensions = Some(crate::window::Dimensions {
                             width: new_w,
                             height: new_h,