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

commitdcb277751f3d8bf9a6f4bd3f9a3caea54f083714
parenteb679313c3
authorIsaac Freund <[email protected]>
date2025-12-04 22:00
OutputManager: tighten asserts, resolve XXX comments

 river/Output.zig        |  5 +++--
 river/OutputManager.zig | 43 ++++++++++++++++++++++---------------------
 2 files changed, 25 insertions(+), 23 deletions(-)

diff --git a/river/Output.zig b/river/Output.zig
index 7300a8f..d453df8 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -122,7 +122,6 @@ layer_shell: LayerShellOutput = .{},
 /// The output is initially considered blanked:
 /// If using the DRM backend it will be blanked with the initial modeset.
 /// If using the Wayland or X11 backend nothing will be visible until the first frame is rendered.
-/// XXX set this to blanked on enabled->disabled transition
 lock_render_state: enum {
     /// Submitted an unlocked buffer but the buffer has not yet been presented.
     pending_unlock,
@@ -211,7 +210,7 @@ pub fn create(wlr_output: *wlr.Output) !void {
 fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
     const output: *Output = @fieldParentPtr("destroy", listener);
 
-    log.debug("output '{s}' destroyed", .{wlr_output.name});
+    log.debug("wlr_output '{s}' destroyed", .{wlr_output.name});
 
     {
         var it = server.layer_shell.surfaces.iterator();
@@ -285,6 +284,7 @@ pub fn manageStart(output: *Output) void {
             }
 
             output.sent = output.scheduled;
+
             output.link_sent.remove();
             server.wm.sent.outputs.append(output);
         },
@@ -299,6 +299,7 @@ pub fn manageStart(output: *Output) void {
             output.sent = output.scheduled;
 
             if (output.scheduled.state == .destroying) {
+                assert(output.wlr_output == null);
                 {
                     var it = server.wm.windows.iterator();
                     while (it.next()) |window| {
diff --git a/river/OutputManager.zig b/river/OutputManager.zig
index beb393e..7b09086 100644
--- a/river/OutputManager.zig
+++ b/river/OutputManager.zig
@@ -259,11 +259,10 @@ pub fn commitOutputState(om: *OutputManager) void {
     {
         var it = wm.sent.outputs.iterator(.forward);
         while (it.next()) |output| {
-            const wlr_output = output.wlr_output orelse continue;
             switch (output.sent.state) {
                 .enabled, .disabled_soft => {
                     output.scene_output.?.setPosition(output.sent.x, output.sent.y);
-                    _ = om.output_layout.add(wlr_output, output.sent.x, output.sent.y) catch {
+                    _ = om.output_layout.add(output.wlr_output.?, output.sent.x, output.sent.y) catch {
                         log.err("out of memory", .{});
                         continue; // Try again next time
                     };
@@ -271,10 +270,11 @@ pub fn commitOutputState(om: *OutputManager) void {
                         lock_surface.tree.node.setPosition(output.sent.x, output.sent.y);
                     }
                 },
-                .disabled_hard, .destroying => {
-                    om.output_layout.remove(wlr_output);
+                .disabled_hard => {
+                    om.output_layout.remove(output.wlr_output.?);
                     output.sent_wl_output = false;
                 },
+                .destroying => unreachable,
             }
         }
     }
@@ -284,14 +284,11 @@ pub fn commitOutputState(om: *OutputManager) void {
     const need_modeset = blk: {
         var it = wm.sent.outputs.iterator(.forward);
         while (it.next()) |output| {
-            const wlr_output = output.wlr_output orelse continue;
+            const wlr_output = output.wlr_output.?;
             switch (output.sent.state) {
                 .enabled => if (!wlr_output.enabled) break :blk true,
-                // Technically disabling an output does not require a modeset,
-                // but handling both enabled and disable here simplifies
-                // lock_render_state tracking.
                 .disabled_soft, .disabled_hard => if (wlr_output.enabled) break :blk true,
-                .destroying => unreachable, // output.wlr_output must be null.
+                .destroying => unreachable,
             }
             switch (output.sent.mode) {
                 .standard => |mode| {
@@ -321,13 +318,12 @@ pub fn commitOutputState(om: *OutputManager) void {
         {
             var it = wm.sent.outputs.iterator(.forward);
             while (it.next()) |output| {
-                const wlr_output = output.wlr_output orelse continue;
                 const state = states.addOne(util.gpa) catch {
                     log.err("out of memory", .{});
                     return;
                 };
 
-                state.output = wlr_output;
+                state.output = output.wlr_output.?;
                 state.base = wlr.Output.State.init();
 
                 output.sent.applyModeset(&state.base);
@@ -402,22 +398,25 @@ pub fn commitOutputState(om: *OutputManager) void {
     {
         var it = wm.sent.outputs.safeIterator(.forward);
         while (it.next()) |output| {
-            if (output.wlr_output) |wlr_output| {
-                if (wlr_output.enabled) {
-                    wlr_output.scheduleFrame();
-                } else {
+            switch (output.sent.state) {
+                .enabled => {
+                    assert(output.wlr_output.?.enabled);
+                    output.wlr_output.?.scheduleFrame();
+                },
+                .disabled_soft, .disabled_hard => {
+                    assert(!output.wlr_output.?.enabled);
                     output.lock_render_state = .blanked;
-                }
-            }
-            if (output.sent.state == .disabled_hard) {
-                output.link_sent.remove();
-                output.link_sent.init();
+                    if (output.sent.state == .disabled_hard) {
+                        output.link_sent.remove();
+                        output.link_sent.init();
+                    }
+                },
+                .destroying => unreachable,
             }
             output.current = output.sent;
         }
     }
 
-    // XXX sending this every transaction is too noisy
     om.sendConfig() catch {
         log.err("out of memory", .{});
     };
@@ -447,5 +446,7 @@ fn sendConfig(om: *OutputManager) !void {
         head.state.y = output.current.y;
     }
 
+    // wlroots won't send events to clients unless something has changed
+    // compared to the last config set.
     om.wlr_output_manager.setConfiguration(config);
 }