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

commitee1aac5edc378481829bfa9186b5ba29f202f1a9
parent2056ac0224
authorIsaac Freund <[email protected]>
date2024-12-26 17:51
river: fix rendering order

Clicking on windows to raise them to the top and give keyboard focus now
works with rivercompat.

 build.zig                     | 17 +++--------
 build.zig.zon                 |  4 +--
 river/Server.zig              |  4 +--
 river/WindowManager.zig       |  4 +++
 river/WmNode.zig              |  4 +--
 rivercompat/Seat.zig          | 56 +++++++++++++++++++++++++++++++++++
 rivercompat/Window.zig        | 68 +++++++++++++++++++++++++++++++++++++++++++
 rivercompat/WindowManager.zig | 30 +++++++++++++++++--
 rivercompat/main.zig          |  2 +-
 riverctl/main.zig             |  4 +--
 rivertile/main.zig            |  4 +--
 11 files changed, 170 insertions(+), 27 deletions(-)

diff --git a/build.zig b/build.zig
index 858e170..250874a 100644
--- a/build.zig
+++ b/build.zig
@@ -99,10 +99,10 @@ pub fn build(b: *Build) !void {
     scanner.addSystemProtocol("unstable/tablet/tablet-unstable-v2.xml");
     scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml");
 
-    scanner.addCustomProtocol("protocol/river-window-management-v1.xml");
-    scanner.addCustomProtocol("protocol/river-control-unstable-v1.xml");
-    scanner.addCustomProtocol("protocol/river-layout-v3.xml");
-    scanner.addCustomProtocol("protocol/wlr-output-power-management-unstable-v1.xml");
+    scanner.addCustomProtocol(b.path("protocol/river-window-management-v1.xml"));
+    scanner.addCustomProtocol(b.path("protocol/river-control-unstable-v1.xml"));
+    scanner.addCustomProtocol(b.path("protocol/river-layout-v3.xml"));
+    scanner.addCustomProtocol(b.path("protocol/wlr-output-power-management-unstable-v1.xml"));
 
     // Some of these versions may be out of date with what wlroots implements.
     // This is not a problem in practice though as long as river successfully compiles.
@@ -182,9 +182,6 @@ pub fn build(b: *Build) !void {
             .flags = &.{ "-std=c99", "-O2" },
         });
 
-        // TODO: remove when zig issue #131 is implemented
-        scanner.addCSource(river);
-
         river.pie = pie;
         river.root_module.omit_frame_pointer = omit_frame_pointer;
 
@@ -208,8 +205,6 @@ pub fn build(b: *Build) !void {
         rivercompat.linkLibC();
         rivercompat.linkSystemLibrary("wayland-client");
 
-        scanner.addCSource(rivercompat);
-
         rivercompat.pie = pie;
         rivercompat.root_module.omit_frame_pointer = omit_frame_pointer;
 
@@ -233,8 +228,6 @@ pub fn build(b: *Build) !void {
         riverctl.linkLibC();
         riverctl.linkSystemLibrary("wayland-client");
 
-        scanner.addCSource(riverctl);
-
         riverctl.pie = pie;
         riverctl.root_module.omit_frame_pointer = omit_frame_pointer;
 
@@ -258,8 +251,6 @@ pub fn build(b: *Build) !void {
         rivertile.linkLibC();
         rivertile.linkSystemLibrary("wayland-client");
 
-        scanner.addCSource(rivertile);
-
         rivertile.pie = pie;
         rivertile.root_module.omit_frame_pointer = omit_frame_pointer;
 
diff --git a/build.zig.zon b/build.zig.zon
index 8aa09e0..e967d71 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -8,8 +8,8 @@
             .hash = "12209db20ce873af176138b76632931def33a10539387cba745db72933c43d274d56",
         },
         .@"zig-wayland" = .{
-            .url = "https://codeberg.org/ifreund/zig-wayland/archive/89afa3baecf7ebfd76e3730c3381b33098f3af42.tar.gz",
-            .hash = "12206015b1750462788de094b8154eab733a36523639124ace437d4631ca01b5ba18",
+            .url = "https://codeberg.org/ifreund/zig-wayland/archive/2500fc313608be05370ebb67426a05604f2fd937.tar.gz",
+            .hash = "12200e8929e6f520bab7c6229a5d550f818bd6109077d2b99dee4aa79aa6f4835344",
         },
         .@"zig-wlroots" = .{
             .url = "https://codeberg.org/ifreund/zig-wlroots/archive/70a4de5cd6886003e54f55239d2753b025271bc9.tar.gz",
diff --git a/river/Server.zig b/river/Server.zig
index dcaf36e..eee2557 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -280,8 +280,8 @@ fn allowlist(server: *Server, global: *const wl.Global) bool {
     // For other globals I like the current pointer comparison approach as it
     // should catch river accidentally exposing multiple copies of e.g. wl_shm
     // with an assertion failure.
-    return global.getInterface() == wl.Output.getInterface() or
-        global.getInterface() == wl.Seat.getInterface() or
+    return global.getInterface() == wl.Output.interface or
+        global.getInterface() == wl.Seat.interface or
         global == server.shm.global or
         global == server.single_pixel_buffer_manager.global or
         global == server.viewporter.global or
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index b698204..ce0978d 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -74,6 +74,7 @@ sent: struct {
 
 /// State sent by the wm but not yet committed with a commit request.
 uncommitted: struct {
+    /// The list is in rendering order, the last node in the list is rendered on top.
     render_list: wl.list.Head(WmNode, .link_uncommitted),
 },
 
@@ -81,12 +82,14 @@ uncommitted: struct {
 committed: struct {
     // The wm has committed state since state was last sent to windows.
     dirty: bool = false,
+    /// The list is in rendering order, the last node in the list is rendered on top.
     render_list: wl.list.Head(WmNode, .link_committed),
 },
 
 /// State committed by the wm that has been sent to windows as part of the
 /// current transaction.
 inflight: struct {
+    /// The list is in rendering order, the last node in the list is rendered on top.
     render_list: wl.list.Head(WmNode, .link_inflight),
 },
 
@@ -415,6 +418,7 @@ fn commitTransaction(wm: *WindowManager) void {
                     window.commitTransaction();
 
                     window.tree.node.reparent(server.scene.layers.wm);
+                    window.tree.node.raiseToTop();
                     window.tree.node.setEnabled(true);
                     window.popup_tree.node.setEnabled(true);
                 },
diff --git a/river/WmNode.zig b/river/WmNode.zig
index c8c7f88..a2170bd 100644
--- a/river/WmNode.zig
+++ b/river/WmNode.zig
@@ -112,11 +112,11 @@ fn handleRequest(
         },
         .place_top => {
             node.link_uncommitted.remove();
-            server.wm.uncommitted.render_list.prepend(node);
+            server.wm.uncommitted.render_list.append(node);
         },
         .place_bottom => {
             node.link_uncommitted.remove();
-            server.wm.uncommitted.render_list.append(node);
+            server.wm.uncommitted.render_list.prepend(node);
         },
         .place_above => |args| {
             const other_data = args.other.getUserData() orelse return;
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
new file mode 100644
index 0000000..72d1d15
--- /dev/null
+++ b/rivercompat/Seat.zig
@@ -0,0 +1,56 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2024 The River Developers
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const Seat = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const wayland = @import("wayland");
+const wl = wayland.client.wl;
+const river = wayland.client.river;
+
+const Window = @import("Window.zig");
+
+const gpa = std.heap.c_allocator;
+
+seat_v1: *river.SeatV1,
+
+pub fn create(seat_v1: *river.SeatV1) void {
+    const seat = gpa.create(Seat) catch @panic("OOM");
+    seat.* = .{
+        .seat_v1 = seat_v1,
+    };
+    seat_v1.setListener(*Seat, handleEvent, seat);
+}
+
+fn handleEvent(seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) void {
+    assert(seat.seat_v1 == seat_v1);
+    switch (event) {
+        .removed => {
+            seat_v1.destroy();
+            gpa.destroy(seat);
+        },
+        .pointer_enter => {},
+        .pointer_leave => {},
+        .pointer_activity => {},
+        .window_interaction => |args| {
+            const window_v1 = args.window orelse return;
+            const window: *Window = @ptrCast(@alignCast(window_v1.getUserData()));
+            seat_v1.focusWindow(window_v1);
+            window.node_v1.placeTop();
+        },
+    }
+}
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
new file mode 100644
index 0000000..17b51d6
--- /dev/null
+++ b/rivercompat/Window.zig
@@ -0,0 +1,68 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2024 The River Developers
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const Window = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const wayland = @import("wayland");
+const wl = wayland.client.wl;
+const river = wayland.client.river;
+
+const WindowManager = @import("WindowManager.zig");
+
+const gpa = std.heap.c_allocator;
+
+wm: *WindowManager,
+window_v1: *river.WindowV1,
+node_v1: *river.NodeV1,
+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"),
+        .link = undefined,
+    };
+    wm.windows.append(window);
+    window_v1.setListener(*Window, handleEvent, window);
+    window.node_v1.placeTop();
+}
+
+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();
+            gpa.destroy(window);
+        },
+        .dimensions => {},
+        .app_id => {},
+        .title => {},
+        .parent => {},
+        .decoration_hint => {},
+        .pointer_move_requested => {},
+        .pointer_resize_requested => {},
+        .show_window_menu_requested => {},
+        .maximize_requested => {},
+        .unmaximize_requested => {},
+        .fullscreen_requested => {},
+        .exit_fullscreen_requested => {},
+        .minimize_requested => {},
+    }
+}
diff --git a/rivercompat/WindowManager.zig b/rivercompat/WindowManager.zig
index ce7d5b8..1e3b00f 100644
--- a/rivercompat/WindowManager.zig
+++ b/rivercompat/WindowManager.zig
@@ -16,22 +16,31 @@
 
 const WindowManager = @This();
 
+const std = @import("std");
+const assert = std.debug.assert;
 const main = @import("main.zig");
 const wayland = @import("wayland");
 const wl = wayland.client.wl;
 const river = wayland.client.river;
 
+const Seat = @import("Seat.zig");
+const Window = @import("Window.zig");
+
 wm_v1: *river.WindowManagerV1,
+windows: wl.list.Head(Window, .link),
 
 pub fn init(wm: *WindowManager, wm_v1: *river.WindowManagerV1) void {
     wm.* = .{
         .wm_v1 = wm_v1,
+        .windows = undefined,
     };
+    wm.windows.init();
 
     wm_v1.setListener(*WindowManager, handleEvent, wm);
 }
 
-fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event, _: *WindowManager) void {
+fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event, wm: *WindowManager) void {
+    assert(wm.wm_v1 == wm_v1);
     switch (event) {
         .unavailable => main.fatal("another window manager is already running", .{}),
         .finished => unreachable, // We never send river_window_manager_v1.stop
@@ -42,13 +51,28 @@ fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event
         .session_locked => {},
         .session_unlocked => {},
         .window => |args| {
-            args.id.proposeDimensions(400, 400);
+            Window.create(args.id, wm);
+            wm.arrange();
         },
         .output => |args| {
             _ = args;
         },
         .seat => |args| {
-            _ = args;
+            Seat.create(args.id);
         },
     }
 }
+
+pub fn arrange(wm: *WindowManager) void {
+    {
+        var x: i32 = 0;
+        var y: i32 = 0;
+        var it = wm.windows.iterator(.forward);
+        while (it.next()) |window| {
+            window.node_v1.setPosition(x, y);
+            window.window_v1.proposeDimensions(400, 400);
+            x += 40;
+            y += 40;
+        }
+    }
+}
diff --git a/rivercompat/main.zig b/rivercompat/main.zig
index 1d1b32c..1d6666a 100644
--- a/rivercompat/main.zig
+++ b/rivercompat/main.zig
@@ -44,7 +44,7 @@ const Globals = struct {
     fn handleEvent(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
         switch (event) {
             .global => |global| {
-                if (mem.orderZ(u8, global.interface, river.WindowManagerV1.getInterface().name) == .eq) {
+                if (mem.orderZ(u8, global.interface, river.WindowManagerV1.interface.name) == .eq) {
                     globals.wm_v1 = registry.bind(global.name, river.WindowManagerV1, 1) catch return;
                 }
             },
diff --git a/riverctl/main.zig b/riverctl/main.zig
index 13a36ae..06d083b 100644
--- a/riverctl/main.zig
+++ b/riverctl/main.zig
@@ -110,10 +110,10 @@ fn _main() !void {
 fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
     switch (event) {
         .global => |global| {
-            if (mem.orderZ(u8, global.interface, wl.Seat.getInterface().name) == .eq) {
+            if (mem.orderZ(u8, global.interface, wl.Seat.interface.name) == .eq) {
                 assert(globals.seat == null); // TODO: support multiple seats
                 globals.seat = registry.bind(global.name, wl.Seat, 1) catch @panic("out of memory");
-            } else if (mem.orderZ(u8, global.interface, zriver.ControlV1.getInterface().name) == .eq) {
+            } else if (mem.orderZ(u8, global.interface, zriver.ControlV1.interface.name) == .eq) {
                 globals.control = registry.bind(global.name, zriver.ControlV1, 1) catch @panic("out of memory");
             }
         },
diff --git a/rivertile/main.zig b/rivertile/main.zig
index 2590e7e..10dc6cc 100644
--- a/rivertile/main.zig
+++ b/rivertile/main.zig
@@ -382,9 +382,9 @@ pub fn main() !void {
 fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
     switch (event) {
         .global => |global| {
-            if (mem.orderZ(u8, global.interface, river.LayoutManagerV3.getInterface().name) == .eq) {
+            if (mem.orderZ(u8, global.interface, river.LayoutManagerV3.interface.name) == .eq) {
                 context.layout_manager = registry.bind(global.name, river.LayoutManagerV3, 1) catch return;
-            } else if (mem.orderZ(u8, global.interface, wl.Output.getInterface().name) == .eq) {
+            } else if (mem.orderZ(u8, global.interface, wl.Output.interface.name) == .eq) {
                 context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
             }
         },