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

commitbfd35898eb26cd99847505bbf32d4c7699fada3f
parent60dabae451
authorLucas Galante <[email protected]>
date2026-08-13 17:52
fix: the tiled hard-snap gate was inert — ask what the window WAS at grab

seat.rs's move branch tested the window's CURRENT mode to decide between the
hard square snap and the magnetic one, but op_update un-tiles a tiled window
on the first motion event so the drag can follow the pointer. By the time any
motion arrives the mode always reads Floating, so the tiled branch never ran
and a dragged tiled window still landed off-grid and demoted itself — the
exact behavior the snap was added to fix.

start_tiling_mode looks like the answer but is not: every op-construction site
un-tiles the window before building the struct, so it records the demoted
Floating too (it feeds the overview click-cancel restore, so its meaning is
left alone). Added start_was_tiled, captured before the un-tile at all op
sites — the border sites already had initial_mode for this, the pointer-
binding site gets a new local, and the sites that never un-tile (status bars,
client-initiated moves) read the live mode.

Verified by injected drag (ccectl pointer-press + pointer-move-by): dragging
a tiled window 300px moved it by exactly one grid period (vx 1060 -> 1588,
C-7:E-6 -> D-7:F-6) and it was Tiled again on release. Before: it ended
Floating at vx 1360, off-grid.

 src/server/cursor.rs       |  9 +++++++++
 src/server/seat.rs         | 17 ++++++++++++++---
 src/server/xdg_toplevel.rs |  2 ++
 3 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 44edd6a..75ff343 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -925,6 +925,7 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                     start_win_h: (*clicked_status).box_geom.height as u32,
                     start_win_virtual_x: (*clicked_status).virtual_x,
                     start_win_virtual_y: (*clicked_status).virtual_y,
+                    start_was_tiled: false,
                     start_pan_x: (*server).wm.desk_pan_x,
                     start_pan_y: (*server).wm.desk_pan_y,
                     start_tiling_mode: (*clicked_status).tiling_mode,
@@ -978,6 +979,7 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                     start_win_h: (*clicked_win).box_geom.height as u32,
                     start_win_virtual_x: (*clicked_win).virtual_x,
                     start_win_virtual_y: (*clicked_win).virtual_y,
+                    start_was_tiled: (*clicked_win).tiling_mode == crate::tiling::TilingMode::Tiled,
                     start_pan_x: (*server).wm.desk_pan_x,
                     start_pan_y: (*server).wm.desk_pan_y,
                     start_tiling_mode: (*clicked_win).tiling_mode,
@@ -1053,6 +1055,10 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
             }
             
             if !target_win.is_null() && !(*target_win).is_status_bar() && !(*target_win).is_wallpaper() {
+                // Before the un-tile below: a tiled window's drag snaps hard
+                // to whole squares, and by op_update the mode reads Floating.
+                let grabbed_tiled =
+                    (*target_win).tiling_mode == crate::tiling::TilingMode::Tiled;
                 if (*target_win).tiling_mode != crate::tiling::TilingMode::Floating
                     && (*target_win).tiling_mode != crate::tiling::TilingMode::Popup
                     && (*target_win).tiling_mode != crate::tiling::TilingMode::Fullscreen
@@ -1096,6 +1102,7 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                         start_win_virtual_x: (*target_win).virtual_x,
                         start_win_virtual_y: (*target_win).virtual_y,
                         start_tiling_mode: (*target_win).tiling_mode,
+                        start_was_tiled: grabbed_tiled,
                         start_mode_locked: (*target_win).mode_locked,
                         start_pan_x: (*(*seat).server).wm.desk_pan_x,
                         start_pan_y: (*(*seat).server).wm.desk_pan_y,
@@ -1180,6 +1187,7 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                             start_win_virtual_x: (*border_target_win).virtual_x,
                             start_win_virtual_y: (*border_target_win).virtual_y,
                             start_tiling_mode: (*border_target_win).tiling_mode,
+                            start_was_tiled: initial_mode == crate::tiling::TilingMode::Tiled,
                             start_mode_locked: (*border_target_win).mode_locked,
                             start_pan_x: (*(*seat).server).wm.desk_pan_x,
                         start_pan_y: (*(*seat).server).wm.desk_pan_y,
@@ -1250,6 +1258,7 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
                             start_win_virtual_x: (*border_target_win).virtual_x,
                             start_win_virtual_y: (*border_target_win).virtual_y,
                             start_tiling_mode: (*border_target_win).tiling_mode,
+                            start_was_tiled: initial_mode == crate::tiling::TilingMode::Tiled,
                             start_mode_locked: (*border_target_win).mode_locked,
                             start_pan_x: (*(*seat).server).wm.desk_pan_x,
                         start_pan_y: (*(*seat).server).wm.desk_pan_y,
diff --git a/src/server/seat.rs b/src/server/seat.rs
index 031e92e..fdedefb 100644
--- a/src/server/seat.rs
+++ b/src/server/seat.rs
@@ -36,6 +36,12 @@ pub struct SeatOp {
     pub start_pan_x: f64,
     pub start_pan_y: f64,
     pub start_tiling_mode: crate::tiling::TilingMode,
+    /// Was the window Tiled when the drag was GRABBED? Every op site un-tiles
+    /// a tiled window before building this struct (the drag needs it floating
+    /// to follow the pointer), so `start_tiling_mode` already reads Floating
+    /// and cannot answer this. A tiled window's move snaps hard to whole
+    /// squares, so the motion handler has to know.
+    pub start_was_tiled: bool,
     pub start_mode_locked: bool,
     pub started_in_overview: bool,
 }
@@ -1190,9 +1196,13 @@ impl Seat {
                             // magnetic snap below is for Floating windows,
                             // which use it to decide whether they land aligned
                             // (and so become Tiled) at op_end.
-                            let (vx, vy) = if (*self.server).wm.get_mode_for_window(win)
-                                == crate::tiling::TilingMode::Tiled
-                            {
+                            //
+                            // This asks what the window was when GRABBED, not
+                            // what it is now: op_update un-tiles a tiled window
+                            // on the first motion event so the drag can follow
+                            // the pointer, so the live mode is always Floating
+                            // here and a test against it never fires.
+                            let (vx, vy) = if op.start_was_tiled {
                                 crate::policy::snap::snap_move_tiled(vx, vy, &sp)
                             } else {
                                 crate::policy::snap::snap_move(
@@ -1734,6 +1744,7 @@ unsafe extern "C" fn seat_op_start_pointer(
             start_pan_x: (*(*seat).server).wm.desk_pan_x,
             start_pan_y: (*(*seat).server).wm.desk_pan_y,
             start_tiling_mode: crate::tiling::TilingMode::Floating,
+            start_was_tiled: false,
             start_mode_locked: false,
             started_in_overview: false,
         });
diff --git a/src/server/xdg_toplevel.rs b/src/server/xdg_toplevel.rs
index 2c2fe54..21a9855 100644
--- a/src/server/xdg_toplevel.rs
+++ b/src/server/xdg_toplevel.rs
@@ -962,6 +962,7 @@ unsafe extern "C" fn handle_request_move(
             start_win_virtual_x: (*window).virtual_x,
             start_win_virtual_y: (*window).virtual_y,
             start_tiling_mode: (*window).tiling_mode,
+            start_was_tiled: (*window).tiling_mode == crate::tiling::TilingMode::Tiled,
             start_mode_locked: (*window).mode_locked,
             start_pan_x: (*(*window).server).wm.desk_pan_x,
             start_pan_y: (*(*window).server).wm.desk_pan_y,
@@ -1019,6 +1020,7 @@ unsafe extern "C" fn handle_request_resize(
             start_win_virtual_x: (*window).virtual_x,
             start_win_virtual_y: (*window).virtual_y,
             start_tiling_mode: (*window).tiling_mode,
+            start_was_tiled: (*window).tiling_mode == crate::tiling::TilingMode::Tiled,
             start_mode_locked: (*window).mode_locked,
             start_pan_x: (*(*window).server).wm.desk_pan_x,
             start_pan_y: (*(*window).server).wm.desk_pan_y,