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

commit3b8b9bd84aa6194d8fa592b72e71e5ced202392f
parent9b5e71f87e
authorIsaac Freund <[email protected]>
date2025-03-12 09:50
rivercompat: implement new rwm update sequence

The client side ergonomics of the update sequence changes seem like they
will be fine in practice. Most of the refactoring needed would have been
necessary anyways to make rivercompat actually useful.

This commit compiles but is untested as the new update sequence has not
yet been implemented in river itself.

 rivercompat/Seat.zig          | 94 +++++++++++++++++++++++++++----------------
 rivercompat/ShellSurface.zig  | 45 +++++++++++++--------
 rivercompat/Window.zig        | 87 ++++++++++++++++++++++-----------------
 rivercompat/WindowManager.zig | 38 +++++++++++++----
 rivercompat/XkbBinding.zig    |  5 ++-
 5 files changed, 171 insertions(+), 98 deletions(-)

diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index 5583d31..388300a 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -32,8 +32,16 @@ const PointerBinding = @import("PointerBinding.zig");
 
 const gpa = std.heap.c_allocator;
 
+const State = struct {
+    new: bool = false,
+    action: ?Action = null,
+    window_interaction: ?*Window = null,
+    shell_surface_interaction: ?*river.ShellSurfaceV1 = null,
+};
+
 wm: *WindowManager,
 seat_v1: *river.SeatV1,
+pending: State = .{},
 focused: ?*Window = null,
 link: wl.list.Link,
 
@@ -42,43 +50,12 @@ pub fn create(wm: *WindowManager, seat_v1: *river.SeatV1) void {
     seat.* = .{
         .wm = wm,
         .seat_v1 = seat_v1,
+        .pending = .{ .new = true },
         .link = undefined,
     };
     wm.seats.append(seat);
 
     seat_v1.setListener(*Seat, handleEvent, seat);
-
-    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.k, .{ .mod4 = true }, .close_focused);
-    XkbBinding.create(seat, xkb.Keysym.s, .{ .mod4 = true }, .show_all);
-    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);
-}
-
-pub fn focus(seat: *Seat, target: ?*Window) void {
-    if (target) |window| {
-        seat.seat_v1.focusWindow(window.window_v1);
-        seat.focused = window;
-
-        window.link.remove();
-        seat.wm.windows.prepend(window);
-
-        window.node_v1.placeTop();
-    } else {
-        seat.seat_v1.clearFocus();
-    }
-}
-
-pub fn focusNext(seat: *Seat) void {
-    if (seat.focused != null) {
-        if (seat.wm.windows.length() >= 2) {
-            seat.focus(seat.wm.windows.last().?);
-        }
-    } else {
-        seat.focus(seat.wm.windows.first());
-    }
 }
 
 fn handleEvent(seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) void {
@@ -94,15 +71,38 @@ fn handleEvent(seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) v
         .window_interaction => |args| {
             const window_v1 = args.window orelse return;
             const window: *Window = @ptrCast(@alignCast(window_v1.getUserData()));
-            seat.focus(window);
+            assert(seat.pending.window_interaction == null);
+            seat.pending.window_interaction = window;
         },
         .shell_surface_interaction => |args| {
-            const shell_surface_v1 = args.shell_surface orelse return;
-            seat_v1.focusShellSurface(shell_surface_v1);
+            assert(seat.pending.shell_surface_interaction == null);
+            seat.pending.shell_surface_interaction = args.shell_surface orelse return;
         },
     }
 }
 
+pub fn updateWindowing(seat: *Seat) void {
+    if (seat.pending.new) {
+        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.k, .{ .mod4 = true }, .close_focused);
+        XkbBinding.create(seat, xkb.Keysym.s, .{ .mod4 = true }, .show_all);
+        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);
+    }
+    if (seat.pending.window_interaction) |window| {
+        seat.focus(window);
+    }
+    if (seat.pending.shell_surface_interaction) |shell_surface| {
+        seat.seat_v1.focusShellSurface(shell_surface);
+    }
+    if (seat.pending.action) |action| {
+        seat.execute(action);
+    }
+    seat.pending = .{};
+}
+
 pub const Action = enum {
     focus_next,
     close_focused,
@@ -144,3 +144,27 @@ pub fn execute(seat: *Seat, action: Action) void {
         .op_end => seat.seat_v1.opEnd(),
     }
 }
+
+pub fn focus(seat: *Seat, target: ?*Window) void {
+    if (target) |window| {
+        seat.seat_v1.focusWindow(window.window_v1);
+        seat.focused = window;
+
+        window.link.remove();
+        seat.wm.windows.prepend(window);
+
+        window.node_v1.placeTop();
+    } else {
+        seat.seat_v1.clearFocus();
+    }
+}
+
+pub fn focusNext(seat: *Seat) void {
+    if (seat.focused != null) {
+        if (seat.wm.windows.length() >= 2) {
+            seat.focus(seat.wm.windows.last().?);
+        }
+    } else {
+        seat.focus(seat.wm.windows.first());
+    }
+}
diff --git a/rivercompat/ShellSurface.zig b/rivercompat/ShellSurface.zig
index 2e8b425..605e3c7 100644
--- a/rivercompat/ShellSurface.zig
+++ b/rivercompat/ShellSurface.zig
@@ -28,11 +28,16 @@ const WindowManager = @import("WindowManager.zig");
 
 const gpa = std.heap.c_allocator;
 
-wm: *WindowManager,
+const State = struct {
+    new: bool = false,
+};
+
 surface: *wl.Surface,
 viewport: *wp.Viewport,
 shell_surface_v1: *river.ShellSurfaceV1,
 node: *river.NodeV1,
+pending: State,
+link: wl.list.Link,
 
 pub fn create(wm: *WindowManager) void {
     const shell_surface = gpa.create(ShellSurface) catch @panic("OOM");
@@ -41,30 +46,36 @@ pub fn create(wm: *WindowManager) void {
     const shell_surface_v1 = wm.wm_v1.getShellSurface(surface) catch @panic("OOM");
 
     shell_surface.* = .{
-        .wm = wm,
         .surface = surface,
         .viewport = viewport,
         .shell_surface_v1 = shell_surface_v1,
         .node = shell_surface_v1.getNode() catch @panic("OOM"),
+        .pending = .{ .new = true },
+        .link = undefined,
     };
+    wm.shell_surfaces.append(shell_surface);
+}
 
-    shell_surface.node.placeBottom();
-    shell_surface.node.setPosition(0, 0);
-    shell_surface_v1.syncNextCommit();
+pub fn updateWindowing(shell_surface: *ShellSurface, wm: *WindowManager) void {
+    if (shell_surface.pending.new) {
+        shell_surface.node.placeBottom();
+        shell_surface.node.setPosition(0, 0);
+        shell_surface.shell_surface_v1.syncNextCommit();
 
-    const rgb = 0xfdf6e3;
+        const rgb = 0xfdf6e3;
 
-    const buffer = wm.single_pixel.createU32RgbaBuffer(
-        @as(u32, (rgb >> 16) & 0xff) * (0xffff_ffff / 0xff),
-        @as(u32, (rgb >> 8) & 0xff) * (0xffff_ffff / 0xff),
-        @as(u32, (rgb >> 0) & 0xff) * (0xffff_ffff / 0xff),
-        0xffff_ffff,
-    ) catch @panic("OOM");
-    defer buffer.destroy();
+        const buffer = wm.single_pixel.createU32RgbaBuffer(
+            @as(u32, (rgb >> 16) & 0xff) * (0xffff_ffff / 0xff),
+            @as(u32, (rgb >> 8) & 0xff) * (0xffff_ffff / 0xff),
+            @as(u32, (rgb >> 0) & 0xff) * (0xffff_ffff / 0xff),
+            0xffff_ffff,
+        ) catch @panic("OOM");
+        defer buffer.destroy();
 
-    surface.attach(buffer, 0, 0);
+        shell_surface.surface.attach(buffer, 0, 0);
 
-    surface.damageBuffer(0, 0, math.maxInt(i32), math.maxInt(i32));
-    viewport.setDestination(math.maxInt(i32) / 2, math.maxInt(i32) / 2);
-    surface.commit();
+        shell_surface.surface.damageBuffer(0, 0, math.maxInt(i32), math.maxInt(i32));
+        shell_surface.viewport.setDestination(math.maxInt(i32) / 2, math.maxInt(i32) / 2);
+        shell_surface.surface.commit();
+    }
 }
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
index 99f51b7..d25f8e2 100644
--- a/rivercompat/Window.zig
+++ b/rivercompat/Window.zig
@@ -26,61 +26,32 @@ const WindowManager = @import("WindowManager.zig");
 
 const gpa = std.heap.c_allocator;
 
-wm: *WindowManager,
+const State = struct {
+    new: bool = false,
+    closed: bool = false,
+};
+
 window_v1: *river.WindowV1,
 node_v1: *river.NodeV1,
+pending: State,
 link: wl.list.Link,
 
 pub fn create(window_v1: *river.WindowV1, wm: *WindowManager) void {
     const window = gpa.create(Window) catch @panic("OOM");
     window.* = .{
-        .wm = wm,
         .window_v1 = window_v1,
         .node_v1 = window_v1.getNode() catch @panic("OOM"),
+        .pending = .{ .new = true },
         .link = undefined,
     };
     wm.windows.append(window);
     window_v1.setListener(*Window, handleEvent, window);
-    window.node_v1.placeTop();
-    window_v1.useSsd();
-
-    const rgb = 0x586e75;
-    window_v1.setBorders(
-        .{ .left = true, .bottom = true, .top = false, .right = true },
-        6, // width
-        @as(u32, (rgb >> 16) & 0xff) * (0xffff_ffff / 0xff),
-        @as(u32, (rgb >> 8) & 0xff) * (0xffff_ffff / 0xff),
-        @as(u32, (rgb >> 0) & 0xff) * (0xffff_ffff / 0xff),
-        0xffff_ffff,
-    );
-
-    {
-        var it = wm.seats.iterator(.forward);
-        while (it.next()) |seat| {
-            seat.focus(window);
-        }
-    }
 }
 
 fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window: *Window) void {
     assert(window.window_v1 == window_v1);
     switch (event) {
-        .closed => {
-            window_v1.destroy();
-
-            window.link.remove();
-            {
-                var it = window.wm.seats.iterator(.forward);
-                while (it.next()) |seat| {
-                    if (seat.focused == window) {
-                        seat.focused = null;
-                        seat.focusNext();
-                    }
-                }
-            }
-
-            gpa.destroy(window);
-        },
+        .closed => window.pending.closed = true,
         .dimensions_hint => {},
         .dimensions => {},
         .app_id => {},
@@ -97,3 +68,45 @@ fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window:
         .minimize_requested => {},
     }
 }
+
+pub fn updateWindowing(window: *Window, wm: *WindowManager) void {
+    if (window.pending.closed) {
+        window.window_v1.destroy();
+        window.link.remove();
+        {
+            var it = wm.seats.iterator(.forward);
+            while (it.next()) |seat| {
+                if (seat.focused == window) {
+                    seat.focused = null;
+                    seat.focusNext();
+                }
+            }
+        }
+        gpa.destroy(window);
+        return;
+    }
+
+    if (window.pending.new) {
+        window.node_v1.placeTop();
+        window.window_v1.useSsd();
+
+        const rgb = 0x586e75;
+        window.window_v1.setBorders(
+            .{ .left = true, .bottom = true, .top = false, .right = true },
+            6, // width
+            @as(u32, (rgb >> 16) & 0xff) * (0xffff_ffff / 0xff),
+            @as(u32, (rgb >> 8) & 0xff) * (0xffff_ffff / 0xff),
+            @as(u32, (rgb >> 0) & 0xff) * (0xffff_ffff / 0xff),
+            0xffff_ffff,
+        );
+
+        {
+            var it = wm.seats.iterator(.forward);
+            while (it.next()) |seat| {
+                seat.focus(window);
+            }
+        }
+    }
+
+    window.pending = .{};
+}
diff --git a/rivercompat/WindowManager.zig b/rivercompat/WindowManager.zig
index 6ef5727..83fac77 100644
--- a/rivercompat/WindowManager.zig
+++ b/rivercompat/WindowManager.zig
@@ -35,6 +35,7 @@ single_pixel: *wp.SinglePixelBufferManagerV1,
 
 windows: wl.list.Head(Window, .link),
 seats: wl.list.Head(Seat, .link),
+shell_surfaces: wl.list.Head(ShellSurface, .link),
 
 pub fn init(
     wm: *WindowManager,
@@ -50,9 +51,11 @@ pub fn init(
         .single_pixel = single_pixel,
         .windows = undefined,
         .seats = undefined,
+        .shell_surfaces = undefined,
     };
     wm.windows.init();
     wm.seats.init();
+    wm.shell_surfaces.init();
 
     wm_v1.setListener(*WindowManager, handleEvent, wm);
 
@@ -64,16 +67,17 @@ fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event
     switch (event) {
         .unavailable => main.fatal("another window manager is already running", .{}),
         .finished => unreachable, // We never send river_window_manager_v1.stop
-        .update => |args| {
-            wm_v1.ackUpdate(args.serial);
-            wm_v1.commit();
+        .update_windowing_start => {
+            wm.updateWindowing();
+            wm_v1.updateWindowingFinish();
+        },
+        .update_rendering_start => {
+            wm.updateRendering();
+            wm_v1.updateWindowingFinish();
         },
         .session_locked => {},
         .session_unlocked => {},
-        .window => |args| {
-            Window.create(args.id, wm);
-            wm.arrange();
-        },
+        .window => |args| Window.create(args.id, wm),
         .output => |args| {
             _ = args;
         },
@@ -83,16 +87,34 @@ fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event
     }
 }
 
-pub fn arrange(wm: *WindowManager) void {
+fn updateWindowing(wm: *WindowManager) void {
+    {
+        var it = wm.seats.iterator(.forward);
+        while (it.next()) |seat| {
+            seat.updateWindowing();
+        }
+    }
     {
         var x: i32 = 0;
         var y: i32 = 0;
         var it = wm.windows.iterator(.forward);
         while (it.next()) |window| {
+            window.updateWindowing(wm);
+
             window.node_v1.setPosition(x, y);
             window.window_v1.proposeDimensions(400, 400);
             x += 40;
             y += 40;
         }
     }
+    {
+        var it = wm.shell_surfaces.iterator(.forward);
+        while (it.next()) |shell_surface| {
+            shell_surface.updateWindowing(wm);
+        }
+    }
+}
+
+fn updateRendering(wm: *WindowManager) void {
+    _ = wm;
 }
diff --git a/rivercompat/XkbBinding.zig b/rivercompat/XkbBinding.zig
index 0abbf2b..0b19e83 100644
--- a/rivercompat/XkbBinding.zig
+++ b/rivercompat/XkbBinding.zig
@@ -51,7 +51,10 @@ pub fn create(
 fn handleEvent(xkb_binding_v1: *river.XkbBindingV1, event: river.XkbBindingV1.Event, binding: *XkbBinding) void {
     assert(binding.xkb_binding_v1 == xkb_binding_v1);
     switch (event) {
-        .pressed => binding.seat.execute(binding.action),
+        .pressed => {
+            assert(binding.seat.pending.action == null);
+            binding.seat.pending.action = binding.action;
+        },
         .released => {},
     }
 }