Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
river: render a window with the rwm protocol
river/Output.zig | 48 ++++++++++++++++++++++++++----------------------
river/Window.zig | 8 +++-----
river/WindowManager.zig | 22 ++++++++++++++++------
river/XdgToplevel.zig | 16 ++++------------
4 files changed, 49 insertions(+), 45 deletions(-)
diff --git a/river/Output.zig b/river/Output.zig
index 91296f1..a9d2d09 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -67,7 +67,7 @@ pub const State = struct {
} = .none,
scale: f32 = 1,
transform: wl.Output.Transform = .normal,
- adaptive_sync: bool = false,
+ adaptive_sync: bool = true,
auto_layout: bool = true,
/// Width in the logical coordinate space
@@ -242,32 +242,34 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
pub fn sendDirty(output: *Output) !void {
switch (output.pending.state) {
.enabled, .disabled_soft => {
- const wm_v1 = server.wm.object.?;
- const new = output.object == null;
- const output_v1 = output.object orelse blk: {
- const output_v1 = try river.OutputV1.create(wm_v1.getClient(), wm_v1.getVersion(), 0);
- output.object = output_v1;
-
- output_v1.setHandler(*Output, handleRequest, null, output);
- wm_v1.sendOutput(output_v1);
- output.link_sent.remove();
- server.wm.sent.outputs.append(output);
-
- break :blk output_v1;
- };
- errdefer comptime unreachable;
+ if (server.wm.object) |wm_v1| {
+ const new = output.object == null;
+ const output_v1 = output.object orelse blk: {
+ const output_v1 = try river.OutputV1.create(wm_v1.getClient(), wm_v1.getVersion(), 0);
+ output.object = output_v1;
- const pending = &output.pending;
- const sent = &output.sent;
+ output_v1.setHandler(*Output, handleRequest, null, output);
+ wm_v1.sendOutput(output_v1);
- if (new or pending.width() != sent.width() or pending.height() != sent.height()) {
- output_v1.sendDimensions(pending.width(), pending.height());
- }
- if (new or pending.x != sent.x or pending.y != sent.y) {
- output_v1.sendPosition(pending.x, pending.y);
+ break :blk output_v1;
+ };
+ errdefer comptime unreachable;
+
+ const pending = &output.pending;
+ const sent = &output.sent;
+
+ if (new or pending.width() != sent.width() or pending.height() != sent.height()) {
+ output_v1.sendDimensions(pending.width(), pending.height());
+ }
+ if (new or pending.x != sent.x or pending.y != sent.y) {
+ output_v1.sendPosition(pending.x, pending.y);
+ }
}
output.sent = output.pending;
+
+ output.link_sent.remove();
+ server.wm.sent.outputs.append(output);
},
.disabled_hard, .destroying => {
if (output.object) |output_v1| {
@@ -319,6 +321,8 @@ fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), ev
return;
}
+ log.debug("backend requested new mode", .{});
+
if (event.state.mode) |mode| {
output.pending.mode = .{ .standard = mode };
} else {
diff --git a/river/Window.zig b/river/Window.zig
index 16d5ff5..d914810 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -323,7 +323,7 @@ pub fn sendDirty(window: *Window) !void {
}
},
.ready => {
- const wm_v1 = server.wm.object.?;
+ const wm_v1 = server.wm.object orelse return;
const new = window.object == null;
const window_v1 = window.object orelse blk: {
const window_v1 = try river.WindowV1.create(wm_v1.getClient(), wm_v1.getVersion(), 0);
@@ -604,7 +604,7 @@ pub fn updateSceneState(window: *Window) void {
pub fn configure(window: *Window) bool {
if (!window.initialized) return false;
- assert(window.mapped and !window.destroying);
+ assert(!window.destroying);
const committed = &window.committed;
window.inflight = .{
@@ -631,7 +631,7 @@ pub fn configure(window: *Window) bool {
.none => unreachable,
};
- if (track_configure) {
+ if (track_configure and window.mapped) {
window.saveSurfaceTree();
window.sendFrameDone();
}
@@ -744,8 +744,6 @@ pub fn map(window: *Window) !void {
window.mapped = true;
window.foreign_toplevel_handle.map();
-
- window.ready();
}
/// Called by the impl when the surface will no longer be displayed
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index 0ad7693..f80b888 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -220,6 +220,7 @@ pub fn dirtyPending(wm: *WindowManager) void {
fn handleDirtyPending(wm: *WindowManager) void {
assert(wm.pending.dirty);
+ wm.dirty_idle = null;
switch (wm.state) {
.idle => {
assert(!wm.committed.dirty);
@@ -233,7 +234,7 @@ fn sendUpdate(wm: *WindowManager) void {
assert(wm.state == .idle);
assert(wm.pending.dirty);
- const wm_v1 = wm.object orelse return;
+ log.debug("sending update to window manager", .{});
// XXX send all dirty pending state
@@ -245,7 +246,6 @@ fn sendUpdate(wm: *WindowManager) void {
log.err("out of memory", .{});
continue; // Try again next update
};
- output.link_pending.remove();
}
}
@@ -265,11 +265,17 @@ fn sendUpdate(wm: *WindowManager) void {
wm.pending.dirty = false;
- const serial = server.wl_server.nextSerial();
- wm_v1.sendUpdate(serial);
- wm.state = .{ .update_sent = serial };
+ if (wm.object) |wm_v1| {
+ const serial = server.wl_server.nextSerial();
+ wm_v1.sendUpdate(serial);
+ wm.state = .{ .update_sent = serial };
- wm.startTimeoutTimer();
+ wm.startTimeoutTimer();
+ } else {
+ // Pretend that the non-existent wm client made an empty commit.
+ wm.committed.dirty = true;
+ wm.sendConfigures();
+ }
}
fn autoLayoutOutputs(wm: *WindowManager) void {
@@ -306,6 +312,7 @@ fn sendConfigures(wm: *WindowManager) void {
.idle, .update_acked => {},
.update_sent, .inflight_configures => unreachable,
}
+
assert(wm.committed.dirty);
wm.committed.dirty = false;
@@ -382,6 +389,7 @@ fn commitTransaction(wm: *WindowManager) void {
.window => |window| {
window.commitTransaction();
+ window.tree.node.reparent(server.scene.layers.wm);
window.tree.node.setEnabled(true);
window.popup_tree.node.setEnabled(true);
},
@@ -411,6 +419,8 @@ fn commitTransaction(wm: *WindowManager) void {
server.idle_inhibit_manager.checkActive();
+ log.debug("finished committing transaction", .{});
+
if (wm.committed.dirty) {
wm.sendConfigures();
} else if (wm.pending.dirty) {
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 7025dca..63f931d 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -60,6 +60,8 @@ configure_state: union(enum) {
// Listeners that are always active over the window's lifetime
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
+ack_configure: wl.Listener(*wlr.XdgSurface.Configure) =
+ wl.Listener(*wlr.XdgSurface.Configure).init(handleAckConfigure),
map: wl.Listener(void) = wl.Listener(void).init(handleMap),
unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
@@ -72,10 +74,6 @@ request_resize: wl.Listener(*wlr.XdgToplevel.event.Resize) =
set_title: wl.Listener(void) = wl.Listener(void).init(handleSetTitle),
set_app_id: wl.Listener(void) = wl.Listener(void).init(handleSetAppId),
-// Listeners that are only active while the window is mapped
-ack_configure: wl.Listener(*wlr.XdgSurface.Configure) =
- wl.Listener(*wlr.XdgSurface.Configure).init(handleAckConfigure),
-
pub fn create(wlr_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
const window = try Window.create(.{ .toplevel = .{
.window = undefined,
@@ -103,8 +101,8 @@ pub fn create(wlr_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
wlr_toplevel.base.data = @intFromPtr(toplevel);
wlr_toplevel.base.surface.data = @intFromPtr(&window.tree.node);
- // Add listeners that are active over the toplevel's entire lifetime
wlr_toplevel.events.destroy.add(&toplevel.destroy);
+ wlr_toplevel.base.events.ack_configure.add(&toplevel.ack_configure);
wlr_toplevel.base.surface.events.map.add(&toplevel.map);
wlr_toplevel.base.surface.events.commit.add(&toplevel.commit);
wlr_toplevel.base.events.new_popup.add(&toplevel.new_popup);
@@ -233,8 +231,8 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
}
assert(toplevel.decoration == null);
- // Remove listeners that are active for the entire lifetime of the toplevel
toplevel.destroy.link.remove();
+ toplevel.ack_configure.link.remove();
toplevel.map.link.remove();
toplevel.unmap.link.remove();
toplevel.commit.link.remove();
@@ -257,9 +255,6 @@ fn handleMap(listener: *wl.Listener(void)) void {
const toplevel: *XdgToplevel = @fieldParentPtr("map", listener);
const window = toplevel.window;
- // Add listeners that are only active while mapped
- toplevel.wlr_toplevel.base.events.ack_configure.add(&toplevel.ack_configure);
-
toplevel.wlr_toplevel.base.getGeometry(&toplevel.geometry);
// XXX this seems like it should be deleted/moved to handleCommit()
@@ -282,9 +277,6 @@ fn handleMap(listener: *wl.Listener(void)) void {
fn handleUnmap(listener: *wl.Listener(void)) void {
const toplevel: *XdgToplevel = @fieldParentPtr("unmap", listener);
- // Remove listeners that are only active while mapped
- toplevel.ack_configure.link.remove();
-
toplevel.window.unmap();
}