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

commit1d59135e42e5774dc9119884a5cc9c82050f996e
parentb6a42b33b8
authorIsaac Freund <[email protected]>
date2025-01-02 12:54
Seat: implement resize operations

 river/Cursor.zig      |  2 +-
 river/Seat.zig        | 57 ++++++++++++++++++++++++++++++++++++++++++++-------
 river/Window.zig      | 46 ++++++++++++++++++++++++++---------------
 river/XdgToplevel.zig |  4 ----
 rivercompat/Seat.zig  | 18 +++++++++++++---
 5 files changed, 95 insertions(+), 32 deletions(-)

diff --git a/river/Cursor.zig b/river/Cursor.zig
index 08e2d4d..826adac 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -355,7 +355,7 @@ pub fn processMotionRelative(cursor: *Cursor, event: *const wlr.Pointer.event.Mo
             data.delta_y = dy - @trunc(dy);
 
             cursor.wlr_cursor.move(event.device, dx, dy);
-            cursor.seat.updateOp(@intFromFloat(dx), @intFromFloat(dy));
+            cursor.seat.updateOp(@intFromFloat(cursor.wlr_cursor.x), @intFromFloat(cursor.wlr_cursor.y));
         },
     }
 }
diff --git a/river/Seat.zig b/river/Seat.zig
index 679253c..ba7339f 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -448,7 +448,14 @@ fn handleRequest(
                 .seat = seat,
             } };
         },
-        .op_add_resize_window => {},
+        .op_add_resize_window => |args| {
+            const data = args.window.getUserData() orelse return;
+            const window: *Window = @ptrCast(@alignCast(data));
+            window.uncommitted.op = .{ .resize = .{
+                .seat = seat,
+                .edges = args.edges,
+            } };
+        },
         .op_end => seat.uncommitted.op = .end,
 
         .pointer_confine_to_region => {},
@@ -539,7 +546,18 @@ pub fn applyCommitted(seat: *Seat) void {
                                 };
                             }
                         },
-                        .resize => {}, // TODO
+                        .resize => |data| {
+                            if (data.seat == seat) {
+                                assert(window.inflight.op == .none);
+                                window.inflight.op = .{
+                                    .resize = .{
+                                        .seat = seat,
+                                        .edges = data.edges,
+                                        .start_box = window.pending.box,
+                                    },
+                                };
+                            }
+                        },
                     }
                 }
             }
@@ -741,8 +759,12 @@ pub fn handleSwitchMapping(
     }
 }
 
-pub fn updateOp(seat: *Seat, dx: i32, dy: i32) void {
-    assert(seat.op != null);
+pub fn updateOp(seat: *Seat, x: i32, y: i32) void {
+    const op = seat.op.?;
+
+    // Total dx/dy since operation start
+    const dx = x - op.start_x;
+    const dy = y - op.start_y;
 
     {
         var it = server.wm.windows.iterator(.forward);
@@ -752,12 +774,33 @@ pub fn updateOp(seat: *Seat, dx: i32, dy: i32) void {
                 .move => |data| {
                     if (data.seat != seat) continue;
 
-                    window.pending.box.x += dx;
-                    window.pending.box.y += dy;
+                    window.pending.box.x = data.start_x + dx;
+                    window.pending.box.y = data.start_y + dy;
+
+                    seat.op.?.dirty = true;
+                },
+                .resize => |data| {
+                    if (data.seat != seat) continue;
+
+                    // For resize, position is not updated until the window has committed
+                    // its new dimensions. The client may not commit exactly the dimensions
+                    // we request and we need to know the actual committed dimensions to
+                    // correctly place the top left corner in the case of a resize from
+                    // the top or left edge.
+                    if (data.edges.left) {
+                        window.pending.box.width = @max(1, data.start_box.width - dx);
+                    } else if (data.edges.right) {
+                        window.pending.box.width = @max(1, data.start_box.width + dx);
+                    }
+
+                    if (data.edges.top) {
+                        window.pending.box.height = @max(1, data.start_box.height - dy);
+                    } else if (data.edges.bottom) {
+                        window.pending.box.height = @max(1, data.start_box.height + dy);
+                    }
 
                     seat.op.?.dirty = true;
                 },
-                .resize => {}, // TODO
             }
         }
     }
diff --git a/river/Window.zig b/river/Window.zig
index 5ad0f3e..3836e7d 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -284,15 +284,6 @@ pub fn setDimensionsHint(window: *Window, hint: DimensionsHint) void {
     }
 }
 
-pub fn setPosition(window: *Window, x: i32, y: i32) void {
-    window.pending.box.x = x;
-    window.pending.box.y = y;
-
-    if (x != window.sent.box.x or y != window.sent.box.y) {
-        server.wm.dirtyPending();
-    }
-}
-
 pub fn setDimensions(window: *Window, width: i32, height: i32) void {
     window.pending.box.width = width;
     window.pending.box.height = height;
@@ -300,8 +291,34 @@ pub fn setDimensions(window: *Window, width: i32, height: i32) void {
     window.inflight.box.width = width;
     window.inflight.box.height = height;
 
-    if (window.sent.dimensions == null or
-        width != window.sent.dimensions.?.width or height != window.sent.dimensions.?.height)
+    switch (window.inflight.op) {
+        .none => {},
+        .move => |data| assert(data.seat.op != null),
+        .resize => |data| {
+            assert(data.seat.op != null);
+
+            if (data.edges.left) {
+                window.pending.box.x = data.start_box.x + data.start_box.width - width;
+            } else if (data.edges.right) {
+                window.pending.box.x = data.start_box.x;
+            }
+
+            if (data.edges.top) {
+                window.pending.box.y = data.start_box.y + data.start_box.height - height;
+            } else if (data.edges.bottom) {
+                window.pending.box.y = data.start_box.y;
+            }
+
+            window.inflight.box.x = window.pending.box.x;
+            window.inflight.box.y = window.pending.box.y;
+        },
+    }
+
+    if (window.sent.dimensions == null or window.sent.position == null or
+        width != window.sent.dimensions.?.width or
+        height != window.sent.dimensions.?.height or
+        window.pending.box.x != window.sent.position.?.x or
+        window.pending.box.y != window.sent.position.?.y)
     {
         server.wm.dirtyPending();
     }
@@ -642,13 +659,8 @@ pub fn commitTransaction(window: *Window) void {
                     // If we did not use the current geometry of the toplevel at this point
                     // we would be rendering the SSD border at initial size X but the surface
                     // would be rendered at size Y.
-                    if (false and window.inflight.resizing) {
-                        window.resizeUpdatePosition(toplevel.geometry.width, toplevel.geometry.height);
-                    }
-
+                    window.setDimensions(toplevel.geometry.width, toplevel.geometry.height);
                     window.current = window.inflight;
-                    window.current.box.width = toplevel.geometry.width;
-                    window.current.box.height = toplevel.geometry.height;
                 },
                 .idle, .committed => {
                     toplevel.configure_state = .idle;
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 769993a..f8ab16d 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -362,10 +362,6 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
         .acked, .timed_out_acked => {
             toplevel.wlr_toplevel.base.getGeometry(&toplevel.geometry);
 
-            if (false and window.inflight.resizing) {
-                window.resizeUpdatePosition(toplevel.geometry.width, toplevel.geometry.height);
-            }
-
             window.setDimensions(toplevel.geometry.width, toplevel.geometry.height);
 
             switch (toplevel.configure_state) {
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index 8439975..20094ee 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -51,7 +51,8 @@ pub fn create(wm: *WindowManager, seat_v1: *river.SeatV1) void {
     XkbBinding.create(seat, xkb.Keysym.n, .{ .mod4 = true }, .focus_next);
     XkbBinding.create(seat, xkb.Keysym.h, .{ .mod4 = true }, .hide_focused);
     XkbBinding.create(seat, xkb.Keysym.s, .{ .mod4 = true }, .show_all);
-    PointerBinding.create(seat, c.BTN_LEFT, .{ .mod4 = true }, .move_start, .move_end);
+    PointerBinding.create(seat, c.BTN_LEFT, .{ .mod4 = true }, .move_start, .op_end);
+    PointerBinding.create(seat, c.BTN_RIGHT, .{ .mod4 = true }, .resize_start, .op_end);
     PointerBinding.create(seat, c.BTN_MIDDLE, .{ .mod4 = true }, .close_focused, null);
 }
 
@@ -103,7 +104,8 @@ pub const Action = enum {
     hide_focused,
     show_all,
     move_start,
-    move_end,
+    resize_start,
+    op_end,
 };
 
 pub fn execute(seat: *Seat, action: Action) void {
@@ -124,6 +126,16 @@ pub fn execute(seat: *Seat, action: Action) void {
                 seat.seat_v1.opAddMoveWindow(window.window_v1);
             }
         },
-        .move_end => seat.seat_v1.opEnd(),
+        .resize_start => {
+            seat.seat_v1.opStartPointer();
+            var it = seat.wm.windows.iterator(.forward);
+            while (it.next()) |window| {
+                seat.seat_v1.opAddResizeWindow(window.window_v1, .{
+                    .top = true,
+                    .left = true,
+                });
+            }
+        },
+        .op_end => seat.seat_v1.opEnd(),
     }
 }