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

commitbcc32cde8a6ef69cdb4d59d727b15ef53872e80b
parent63fa8d9648
authorIsaac Freund <[email protected]>
date2024-12-21 14:02
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();
 }