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

commit03f18788d363a20da5c0f8af6b539f052c66589a
parentb4a26b4350
authorIsaac Freund <[email protected]>
date2023-01-06 18:51
session-lock: wait for present before locking

Currently we send the locked event after rendering and commit of blank
or lock surfaces buffers on all outputs. However, this is technically
not enough to ensure that the buffers have been presented.

Instead, listen to the wlr_output present event to ensure that no
normal, "unlocked" content is possibly visible.

 river/LockManager.zig |  2 +-
 river/Output.zig      | 41 +++++++++++++++++++++++++++++++++++++++++
 river/render.zig      |  7 ++++---
 3 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/river/LockManager.zig b/river/LockManager.zig
index cc4b56a..6723bd4 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -149,7 +149,7 @@ pub fn maybeLock(manager: *LockManager) void {
         while (it) |node| : (it = node.next) {
             const output = &node.data;
             switch (output.lock_render_state) {
-                .unlocked => {
+                .unlocked, .pending_blank, .pending_lock_surface => {
                     all_outputs_blanked = false;
                     all_outputs_rendered_lock_surface = false;
                 },
diff --git a/river/Output.zig b/river/Output.zig
index d1469d9..0d5a541 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -70,8 +70,17 @@ views: ViewStack(View) = .{},
 
 lock_surface: ?*LockSurface = null,
 lock_render_state: enum {
+    /// Normal, "unlocked" content may be visible.
     unlocked,
+    /// Submitted a blank buffer but the buffer has not yet been presented.
+    /// Normal, "unlocked" content may be visible.
+    pending_blank,
+    /// A blank buffer has been presented.
     blanked,
+    /// Submitted the lock surface buffer but the buffer has not yet been presented.
+    /// Normal, "unlocked" content may be visible.
+    pending_lock_surface,
+    /// The lock surface buffer has been presented.
     lock_surface,
 } = .unlocked,
 
@@ -102,6 +111,7 @@ status_trackers: std.SinglyLinkedList(OutputStatus) = .{},
 destroy: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleDestroy),
 enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable),
 mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode),
+present: wl.Listener(*wlr.Output.event.Present) = wl.Listener(*wlr.Output.event.Present).init(handlePresent),
 frame: wl.Listener(*wlr.OutputDamage) = wl.Listener(*wlr.OutputDamage).init(handleFrame),
 damage_destroy: wl.Listener(*wlr.OutputDamage) = wl.Listener(*wlr.OutputDamage).init(handleDamageDestroy),
 
@@ -140,6 +150,7 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void {
     wlr_output.events.destroy.add(&self.destroy);
     wlr_output.events.enable.add(&self.enable);
     wlr_output.events.mode.add(&self.mode);
+    wlr_output.events.present.add(&self.present);
 
     self.damage.?.events.frame.add(&self.frame);
     self.damage.?.events.destroy.add(&self.damage_destroy);
@@ -478,6 +489,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
     self.enable.link.remove();
     self.frame.link.remove();
     self.mode.link.remove();
+    self.present.link.remove();
 
     // Free all memory and clean up the wlr.Output
     if (self.layout_demand) |demand| demand.deinit();
@@ -511,6 +523,35 @@ fn handleMode(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
     server.root.startTransaction();
 }
 
+fn handlePresent(
+    listener: *wl.Listener(*wlr.Output.event.Present),
+    event: *wlr.Output.event.Present,
+) void {
+    const self = @fieldParentPtr(Self, "present", listener);
+
+    switch (self.lock_render_state) {
+        .unlocked => return,
+        .pending_blank, .pending_lock_surface => {
+            if (!event.presented) {
+                self.lock_render_state = .unlocked;
+                self.damage.?.addWhole();
+                return;
+            }
+
+            self.lock_render_state = switch (self.lock_render_state) {
+                .pending_blank => .blanked,
+                .pending_lock_surface => .lock_surface,
+                .unlocked, .blanked, .lock_surface => unreachable,
+            };
+
+            if (server.lock_manager.state != .locked) {
+                server.lock_manager.maybeLock();
+            }
+        },
+        .blanked, .lock_surface => unreachable,
+    }
+}
+
 fn setTitle(self: Self) void {
     const title = fmt.allocPrintZ(util.gpa, "river - {s}", .{self.wlr_output.name}) catch return;
     defer util.gpa.free(title);
diff --git a/river/render.zig b/river/render.zig
index adef939..a238cd9 100644
--- a/river/render.zig
+++ b/river/render.zig
@@ -98,9 +98,10 @@ pub fn renderOutput(output: *Output) void {
             return;
         };
 
-        output.lock_render_state = if (output.lock_surface != null) .lock_surface else .blanked;
-        if (server.lock_manager.state != .locked) {
-            server.lock_manager.maybeLock();
+        if (output.lock_surface == null) {
+            output.lock_render_state = .pending_blank;
+        } else {
+            output.lock_render_state = .pending_lock_surface;
         }
 
         return;