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

commit0fc38cbbb1046cebae65a357786388cdc317c6c3
parentf4c67a49de
authorIsaac Freund <[email protected]>
date2023-11-17 19:48
session-lock: fix pointer focus handling on map

 river/LockManager.zig |  1 -
 river/LockSurface.zig | 35 +++++++++++++++++++++++++++--------
 2 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/river/LockManager.zig b/river/LockManager.zig
index 8da7778..a500260 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -111,7 +111,6 @@ fn handleLock(listener: *wl.Listener(*wlr.SessionLockV1), lock: *wlr.SessionLock
             while (it) |node| : (it = node.next) {
                 const seat = &node.data;
                 seat.setFocusRaw(.none);
-                seat.cursor.updateState();
 
                 // Enter locked mode
                 seat.prev_mode_id = seat.mode_id;
diff --git a/river/LockSurface.zig b/river/LockSurface.zig
index c1bbfdd..2de43ee 100644
--- a/river/LockSurface.zig
+++ b/river/LockSurface.zig
@@ -31,6 +31,8 @@ const SceneNodeData = @import("SceneNodeData.zig");
 wlr_lock_surface: *wlr.SessionLockSurfaceV1,
 lock: *wlr.SessionLockV1,
 
+idle_update_focus: ?*wl.EventSource = null,
+
 output_mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleOutputMode),
 map: wl.Listener(void) = wl.Listener(void).init(handleMap),
 surface_destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
@@ -78,6 +80,10 @@ pub fn destroy(lock_surface: *LockSurface) void {
         }
     }
 
+    if (lock_surface.idle_update_focus) |event_source| {
+        event_source.remove();
+    }
+
     lock_surface.output_mode.link.remove();
     lock_surface.map.link.remove();
     lock_surface.surface_destroy.link.remove();
@@ -105,16 +111,29 @@ fn handleMap(listener: *wl.Listener(void)) void {
     output.normal_content.node.setEnabled(false);
     output.locked_content.node.setEnabled(true);
 
-    {
-        var it = server.input_manager.seats.first;
-        while (it) |node| : (it = node.next) {
-            const seat = &node.data;
-            if (seat.focused != .lock_surface) {
-                seat.setFocusRaw(.{ .lock_surface = lock_surface });
-            }
-            seat.cursor.updateState();
+    // Unfortunately the surface commit handlers for the scene subsurface tree corresponding to
+    // this lock surface won't be called until after this function returns, which means that we cannot
+    // update pointer focus yet as the nodes in the scene graph representing this lock surface are still
+    // 0x0 in size. To work around this, use an idle callback.
+    const event_loop = server.wl_server.getEventLoop();
+    assert(lock_surface.idle_update_focus == null);
+    lock_surface.idle_update_focus = event_loop.addIdle(*LockSurface, updateFocus, lock_surface) catch {
+        std.log.err("out of memory", .{});
+        return;
+    };
+}
+
+fn updateFocus(lock_surface: *LockSurface) void {
+    var it = server.input_manager.seats.first;
+    while (it) |node| : (it = node.next) {
+        const seat = &node.data;
+        if (seat.focused != .lock_surface) {
+            seat.setFocusRaw(.{ .lock_surface = lock_surface });
         }
+        seat.cursor.updateState();
     }
+
+    lock_surface.idle_update_focus = null;
 }
 
 fn handleDestroy(listener: *wl.Listener(void)) void {