Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Window: store in SlotMap
This allows us to have weak references to windows that are hard to
misuse.
build.zig | 2 ++
common/slotmap.zig | 8 +++++++-
river/Cursor.zig | 8 ++++----
river/Output.zig | 2 +-
river/Seat.zig | 45 +++++++++++++++++++++++++++------------------
river/Window.zig | 33 ++++++++++++++++++---------------
river/WindowManager.zig | 9 ++++-----
7 files changed, 63 insertions(+), 44 deletions(-)
diff --git a/build.zig b/build.zig
index 08bfcd8..15aa910 100644
--- a/build.zig
+++ b/build.zig
@@ -148,6 +148,7 @@ pub fn build(b: *Build) !void {
const flags = b.createModule(.{ .root_source_file = b.path("common/flags.zig") });
const globber = b.createModule(.{ .root_source_file = b.path("common/globber.zig") });
+ const slotmap = b.createModule(.{ .root_source_file = b.path("common/slotmap.zig") });
{
const river = b.addExecutable(.{
@@ -175,6 +176,7 @@ pub fn build(b: *Build) !void {
river.root_module.addImport("wlroots", wlroots);
river.root_module.addImport("flags", flags);
river.root_module.addImport("globber", globber);
+ river.root_module.addImport("slotmap", slotmap);
river.addCSourceFile(.{
.file = b.path("river/wlroots_log_wrapper.c"),
diff --git a/common/slotmap.zig b/common/slotmap.zig
index 4eb6ed1..9931c52 100644
--- a/common/slotmap.zig
+++ b/common/slotmap.zig
@@ -21,7 +21,8 @@ pub fn SlotMap(comptime T: type) type {
return struct {
const Map = @This();
- pub const Key = struct {
+ /// This is packed just to make == work.
+ pub const Key = packed struct {
generation: u32,
index: u32,
};
@@ -187,6 +188,11 @@ test "iteration" {
var map: SlotMap(u64) = .empty;
defer map.deinit(testing.allocator);
+ {
+ var it = map.iterator();
+ try testing.expectEqual(null, it.next());
+ }
+
const five = try map.put(testing.allocator, 5);
const six = try map.put(testing.allocator, 6);
const seven = try map.put(testing.allocator, 7);
diff --git a/river/Cursor.zig b/river/Cursor.zig
index e4446df..b32f24d 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -377,13 +377,13 @@ fn updateHovered(cursor: *Cursor) void {
// Exclude input regions of the toplevel that extend beyond the window
if (result.surface != null and result.surface.?.getRootSurface() == toplevel.wlr_toplevel.base.surface) {
if (window.box.containsPoint(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) {
- cursor.seat.wm_scheduled.window = window;
+ cursor.seat.wm_scheduled.window = window.ref;
}
} else {
- cursor.seat.wm_scheduled.window = window;
+ cursor.seat.wm_scheduled.window = window.ref;
}
},
- .xwayland => cursor.seat.wm_scheduled.window = window,
+ .xwayland => cursor.seat.wm_scheduled.window = window.ref,
.destroying => {},
}
},
@@ -531,7 +531,7 @@ pub fn processAxis(cursor: *Cursor, event: *const wlr.Pointer.event.Axis) void {
fn interact(cursor: Cursor, result: Scene.AtResult) void {
switch (result.data) {
.window => |window| {
- cursor.seat.wm_scheduled.interaction = .{ .window = window };
+ cursor.seat.wm_scheduled.interaction = .{ .window = window.ref };
server.wm.dirtyWindowing();
},
.shell_surface => |shell_surface| {
diff --git a/river/Output.zig b/river/Output.zig
index 2695631..5f709aa 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -276,7 +276,7 @@ pub fn manageStart(output: *Output) void {
if (output.scheduled.state == .destroying) {
{
- var it = server.wm.windows.iterator(.forward);
+ var it = server.wm.windows.iterator();
while (it.next()) |window| {
switch (window.wm_scheduled.fullscreen_requested) {
.fullscreen => |output_hint| {
diff --git a/river/Seat.zig b/river/Seat.zig
index 5e9bd33..fcb4853 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -80,7 +80,7 @@ pub const Event = union(enum) {
pub const WmFocus = union(enum) {
none,
- window: *Window,
+ window: Window.Ref,
shell_surface: *ShellSurface,
};
@@ -118,7 +118,7 @@ event_queue: EventQueue = EventQueue.init(),
/// State to be sent to the wm in the next manage sequence.
wm_scheduled: struct {
/// The window entered/hovered by the pointer, if any
- window: ?*Window = null,
+ window: ?Window.Ref = null,
/// The window clicked on, touched, etc.
interaction: WmFocus = .none,
op_release: bool = false,
@@ -127,7 +127,7 @@ wm_scheduled: struct {
/// State sent to the wm in the latest manage sequence.
wm_sent: struct {
/// The window entered/hovered by the pointer, if any
- window: ?*Window = null,
+ window: ?Window.Ref = null,
} = .{},
link_sent: wl.list.Link,
@@ -324,10 +324,12 @@ pub fn manageStart(seat: *Seat) void {
}
if (new) {
- if (seat.wm_scheduled.window) |window| {
- if (window.object) |window_v1| {
- seat_v1.sendPointerEnter(window_v1);
- seat.wm_sent.window = seat.wm_scheduled.window;
+ if (seat.wm_scheduled.window) |ref| {
+ if (ref.get()) |window| {
+ if (window.object) |window_v1| {
+ seat_v1.sendPointerEnter(window_v1);
+ seat.wm_sent.window = seat.wm_scheduled.window;
+ }
}
}
} else if (seat.wm_scheduled.window != seat.wm_sent.window) {
@@ -335,27 +337,30 @@ pub fn manageStart(seat: *Seat) void {
seat_v1.sendPointerLeave();
seat.wm_sent.window = null;
}
- if (seat.wm_scheduled.window) |window| {
- if (window.object) |window_v1| {
- seat_v1.sendPointerEnter(window_v1);
- seat.wm_sent.window = window;
+ if (seat.wm_scheduled.window) |ref| {
+ if (ref.get()) |window| {
+ if (window.object) |window_v1| {
+ seat_v1.sendPointerEnter(window_v1);
+ seat.wm_sent.window = seat.wm_scheduled.window;
+ }
}
}
}
switch (seat.wm_scheduled.interaction) {
.none => {},
- .window => |window| {
- if (window.object) |window_v1| {
- seat_v1.sendWindowInteraction(window_v1);
- seat.wm_scheduled.interaction = .none;
+ .window => |ref| {
+ if (ref.get()) |window| {
+ if (window.object) |window_v1| {
+ seat_v1.sendWindowInteraction(window_v1);
+ }
}
},
.shell_surface => |shell_surface| {
seat_v1.sendShellSurfaceInteraction(shell_surface.object);
- seat.wm_scheduled.interaction = .none;
},
}
+ seat.wm_scheduled.interaction = .none;
if (seat.op) |*op| {
seat_v1.sendOpDelta(op.x - op.start_x, op.y - op.start_y);
@@ -436,7 +441,7 @@ fn handleRequest(
if (!server.wm.ensureWindowing()) return;
const data = args.window.getUserData() orelse return;
const window: *Window = @ptrCast(@alignCast(data));
- seat.wm_requested.focus = .{ .window = window };
+ seat.wm_requested.focus = .{ .window = window.ref };
},
.focus_shell_surface => |args| {
if (!server.wm.ensureWindowing()) return;
@@ -494,7 +499,11 @@ pub fn manageFinish(seat: *Seat) void {
switch (seat.wm_requested.focus) {
.none => seat.focus(.none),
- .window => |window| seat.focus(.{ .window = window }),
+ .window => |ref| {
+ if (ref.get()) |window| {
+ seat.focus(.{ .window = window });
+ }
+ },
.shell_surface => |shell_surface| seat.focus(.{ .shell_surface = shell_surface }),
}
diff --git a/river/Window.zig b/river/Window.zig
index ea760cd..ed71001 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -25,6 +25,7 @@ const posix = std.posix;
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const river = @import("wayland").server.river;
+const SlotMap = @import("slotmap").SlotMap;
const server = &@import("main.zig").server;
const util = @import("util.zig");
@@ -85,6 +86,16 @@ pub const Configure = struct {
resizing: bool = false,
};
+pub const Ref = packed struct {
+ key: SlotMap(*Window).Key,
+
+ pub fn get(ref: Ref) ?*Window {
+ return server.wm.windows.get(ref.key);
+ }
+};
+
+ref: Ref,
+
/// The window management protocol object for this window
/// Created in manageStart() when state is .ready
/// Set to null in manageStart() when state is .closing
@@ -134,9 +145,6 @@ decorations_above_tree: *wlr.SceneTree,
popup_tree: *wlr.SceneTree,
-/// WindowManager.windows
-link: wl.list.Link,
-
/// State to be sent to the wm in the next manage sequence.
wm_scheduled: struct {
dimensions_hint: DimensionsHint = .{},
@@ -224,6 +232,9 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
const window = try util.gpa.create(Window);
errdefer util.gpa.destroy(window);
+ const key = try server.wm.windows.put(util.gpa, window);
+ errdefer server.wm.windows.remove(key);
+
const tree = try server.scene.hidden_tree.createSceneTree();
errdefer tree.node.destroy();
@@ -231,6 +242,7 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
errdefer popup_tree.node.destroy();
window.* = .{
+ .ref = .{ .key = key },
.node = undefined,
.impl = impl,
.tree = tree,
@@ -247,7 +259,6 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
.decorations_above = undefined,
.decorations_above_tree = try tree.createSceneTree(),
.popup_tree = popup_tree,
- .link = undefined,
};
window.node.init(.window);
@@ -255,8 +266,6 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
window.decorations_below.init();
window.decorations_above.init();
- server.wm.windows.append(window);
-
window.tree.node.setEnabled(false);
window.popup_tree.node.setEnabled(false);
window.fullscreen_background.node.setEnabled(false);
@@ -290,12 +299,6 @@ pub fn destroy(window: *Window) void {
if (seat.focused == .window and seat.focused.window == window) {
seat.focus(.none);
}
- if (seat.wm_scheduled.window == window) {
- seat.wm_scheduled.window = null;
- }
- if (seat.wm_sent.window == window) {
- seat.wm_sent.window = null;
- }
}
}
@@ -307,10 +310,10 @@ pub fn destroy(window: *Window) void {
window.tree.node.destroy();
window.popup_tree.node.destroy();
- window.link.remove();
-
window.node.deinit();
+ server.wm.windows.remove(window.ref.key);
+
util.gpa.destroy(window);
}
@@ -645,7 +648,7 @@ pub fn manageFinish(window: *Window) bool {
var it = server.wm.wm_sent.seats.iterator(.forward);
while (it.next()) |seat| {
if (seat.wm_requested.focus == .window and
- seat.wm_requested.focus.window == window)
+ seat.wm_requested.focus.window == window.ref)
{
window.configure_scheduled.activated = true;
break;
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index d95776b..991ee13 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -21,6 +21,7 @@ const assert = std.debug.assert;
const wl = @import("wayland").server.wl;
const wlr = @import("wlroots");
const river = @import("wayland").server.river;
+const SlotMap = @import("slotmap").SlotMap;
const server = &@import("main.zig").server;
const util = @import("util.zig");
@@ -50,7 +51,7 @@ state: union(enum) {
render,
} = .idle,
-windows: wl.list.Head(Window, .link),
+windows: SlotMap(*Window) = .empty,
/// State to be sent to the wm in the next manage sequence.
wm_scheduled: struct {
@@ -90,7 +91,6 @@ pub fn init(wm: *WindowManager) !void {
wm.* = .{
.global = try wl.Global.create(server.wl_server, river.WindowManagerV1, 1, *WindowManager, wm, bind),
- .windows = undefined,
.wm_sent = .{
.outputs = undefined,
.seats = undefined,
@@ -100,7 +100,6 @@ pub fn init(wm: *WindowManager) !void {
},
.timeout = timeout,
};
- wm.windows.init();
wm.wm_sent.outputs.init();
wm.wm_sent.seats.init();
wm.rendering_requested.list.init();
@@ -280,7 +279,7 @@ fn manageStart(wm: *WindowManager) void {
wm.wm_scheduled.output_config = null;
{
- var it = wm.windows.safeIterator(.forward);
+ var it = wm.windows.iterator();
while (it.next()) |window| window.manageStart();
}
@@ -399,7 +398,7 @@ fn renderFinish(wm: *WindowManager) void {
log.debug("render sequence finish", .{});
{
- var it = wm.windows.safeIterator(.forward);
+ var it = wm.windows.iterator();
while (it.next()) |window| {
// If a window is unmapped during a render sequence, we need to retain the saved
// buffers until after the next manage sequence (in which the closed event will