Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
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});
}
},