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

commita85207132188f4a1475b2ac1b20db06c4f9fec93
parent7a1ae811b6
authorIsaac Freund <[email protected]>
date2023-03-01 10:49
Root: centralize focus(null) calls in applyPending()

 river/Cursor.zig                    |  5 ++---
 river/LayerSurface.zig              |  2 +-
 river/LockManager.zig               |  4 ++--
 river/Output.zig                    |  1 +
 river/Root.zig                      | 11 +++++++++--
 river/Seat.zig                      |  1 +
 river/View.zig                      | 10 ----------
 river/XwaylandOverrideRedirect.zig  | 14 +++++---------
 river/command/output.zig            |  3 ---
 river/command/tags.zig              |  6 ------
 river/command/toggle_fullscreen.zig |  3 ---
 11 files changed, 21 insertions(+), 39 deletions(-)

diff --git a/river/Cursor.zig b/river/Cursor.zig
index 84cda6a..829afe7 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -337,6 +337,7 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
     server.root.applyPending();
 }
 
+/// Requires a call to Root.applyPending()
 fn updateKeyboardFocus(self: Self, result: Root.AtResult) void {
     switch (result.node) {
         .view => |view| {
@@ -348,8 +349,6 @@ fn updateKeyboardFocus(self: Self, result: Root.AtResult) void {
             // give it keyboard focus.
             if (layer_surface.wlr_layer_surface.current.keyboard_interactive != .none) {
                 self.seat.setFocusRaw(.{ .layer = layer_surface });
-            } else {
-                self.seat.focus(null);
             }
         },
         .lock_surface => |lock_surface| {
@@ -364,11 +363,11 @@ fn updateKeyboardFocus(self: Self, result: Root.AtResult) void {
 }
 
 /// Focus the output at the given layout coordinates, if any
+/// Requires a call to Root.applyPending()
 fn updateOutputFocus(self: Self, lx: f64, ly: f64) void {
     if (server.root.output_layout.outputAt(lx, ly)) |wlr_output| {
         const output = @intToPtr(*Output, wlr_output.data);
         self.seat.focusOutput(output);
-        self.seat.focus(null);
     }
 }
 
diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig
index b3cdec3..fd294f2 100644
--- a/river/LayerSurface.zig
+++ b/river/LayerSurface.zig
@@ -134,6 +134,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
     }
 }
 
+/// Requires a call to Root.applyPending()
 fn handleKeyboardInteractiveExclusive(output: *Output) void {
     if (server.lock_manager.state != .unlocked) return;
 
@@ -174,7 +175,6 @@ fn handleKeyboardInteractiveExclusive(output: *Output) void {
             // without keyboard interactivity, stop focusing that layer surface.
             if (!current_focus.mapped or current_focus.current.keyboard_interactive == .none) {
                 seat.setFocusRaw(.{ .none = {} });
-                seat.focus(null);
             }
         }
     }
diff --git a/river/LockManager.zig b/river/LockManager.zig
index 9757d93..e5467ac 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -223,8 +223,6 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
         while (it) |node| : (it = node.next) {
             const seat = &node.data;
             seat.setFocusRaw(.none);
-            seat.focus(null);
-            seat.cursor.updateState();
 
             // Exit locked mode
             seat.enterMode(seat.prev_mode_id);
@@ -232,6 +230,8 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
     }
 
     handleDestroy(&manager.destroy);
+
+    server.root.applyPending();
 }
 
 fn handleDestroy(listener: *wl.Listener(void)) void {
diff --git a/river/Output.zig b/river/Output.zig
index adbded4..95dde75 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -293,6 +293,7 @@ pub fn layerSurfaceTree(self: Self, layer: zwlr.LayerShellV1.Layer) *wlr.SceneTr
 
 /// Arrange all layer surfaces of this output and adjust the usable area.
 /// Will arrange views as well if the usable area changes.
+/// Requires a call to Root.applyPending()
 pub fn arrangeLayers(self: *Self) void {
     var full_box: wlr.Box = .{
         .x = 0,
diff --git a/river/Root.zig b/river/Root.zig
index 4f08d38..7603705 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -294,7 +294,6 @@ pub fn removeOutput(root: *Self, output: *Output) void {
         const seat = &seat_node.data;
         if (seat.focused_output == output) {
             seat.focusOutput(fallback_output);
-            seat.focus(null);
         }
     }
 
@@ -340,7 +339,6 @@ pub fn addOutput(root: *Self, output: *Output) void {
             while (it) |seat_node| : (it = seat_node.next) {
                 const seat = &seat_node.data;
                 seat.focusOutput(output);
-                seat.focus(null);
             }
         }
         root.applyPending();
@@ -352,6 +350,15 @@ pub fn addOutput(root: *Self, output: *Output) void {
 /// generates a new layout for all outputs and all affected clients ack a
 /// configure and commit a new buffer.
 pub fn applyPending(root: *Self) void {
+    {
+        // Changes to the pending state may require a focus update to keep
+        // state consistent. Instead of having focus(null) calls spread all
+        // around the codebase and risk forgetting one, always ensure focus
+        // state is synchronized here.
+        var it = server.input_manager.seats.first;
+        while (it) |node| : (it = node.next) node.data.focus(null);
+    }
+
     // If there is already a transaction inflight, wait until it completes.
     if (root.inflight_layout_demands > 0 or root.inflight_configures > 0) {
         root.pending_state_dirty = true;
diff --git a/river/Seat.zig b/river/Seat.zig
index d3c22d9..802a5ce 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -138,6 +138,7 @@ pub fn deinit(self: *Self) void {
 
 /// Set the current focus. If a visible view is passed it will be focused.
 /// If null is passed, the top view in the stack of the focused output will be focused.
+/// Requires a call to Root.applyPending()
 pub fn focus(self: *Self, _target: ?*View) void {
     var target = _target;
 
diff --git a/river/View.zig b/river/View.zig
index 399dcdd..45d8906 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -478,16 +478,6 @@ pub fn unmap(view: *Self) void {
         server.root.hidden.pending.wm_stack.prepend(view);
     }
 
-    {
-        var it = server.input_manager.seats.first;
-        while (it) |node| : (it = node.next) {
-            const seat = &node.data;
-            if (seat.focused == .view and seat.focused.view == view) {
-                seat.focus(null);
-            }
-        }
-    }
-
     view.request_activate.link.remove();
 
     server.root.applyPending();
diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig
index e87ce77..cb0686a 100644
--- a/river/XwaylandOverrideRedirect.zig
+++ b/river/XwaylandOverrideRedirect.zig
@@ -138,15 +138,11 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSur
     var seat_it = server.input_manager.seats.first;
     while (seat_it) |seat_node| : (seat_it = seat_node.next) {
         const seat = &seat_node.data;
-        switch (seat.focused) {
-            .view => |focused| if (focused.impl == .xwayland_view and
-                focused.impl.xwayland_view.xwayland_surface.pid == self.xwayland_surface.pid and
-                seat.wlr_seat.keyboard_state.focused_surface == self.xwayland_surface.surface)
-            {
-                seat.keyboardEnterOrLeave(focused.rootSurface());
-            },
-            .xwayland_override_redirect => |focused| if (focused == self) seat.focus(null),
-            .layer, .lock_surface, .none => {},
+        if (seat.focused == .view and seat.focused.view.impl == .xwayland_view and
+            seat.focused.view.impl.xwayland_view.xwayland_surface.pid == self.xwayland_surface.pid and
+            seat.wlr_seat.keyboard_state.focused_surface == self.xwayland_surface.surface)
+        {
+            seat.keyboardEnterOrLeave(seat.focused.view.rootSurface());
         }
     }
 
diff --git a/river/command/output.zig b/river/command/output.zig
index dc72323..64d021e 100644
--- a/river/command/output.zig
+++ b/river/command/output.zig
@@ -43,7 +43,6 @@ pub fn focusOutput(
     }
 
     seat.focusOutput((try getOutput(seat, args[1])) orelse return);
-    seat.focus(null);
     server.root.applyPending();
 }
 
@@ -68,8 +67,6 @@ pub fn sendToOutput(
         if (seat.focused.view.pending.output == destination_output) return;
         seat.focused.view.setPendingOutput(destination_output);
 
-        // Handle the change and focus whatever's next in the focus stack
-        seat.focus(null);
         server.root.applyPending();
     }
 }
diff --git a/river/command/tags.zig b/river/command/tags.zig
index 3eac000..d1ac8da 100644
--- a/river/command/tags.zig
+++ b/river/command/tags.zig
@@ -34,7 +34,6 @@ pub fn setFocusedTags(
     if (output.pending.tags != tags) {
         output.previous_tags = output.pending.tags;
         output.pending.tags = tags;
-        seat.focus(null);
         server.root.applyPending();
     }
 }
@@ -58,7 +57,6 @@ pub fn setViewTags(
     if (seat.focused == .view) {
         const view = seat.focused.view;
         view.pending.tags = tags;
-        seat.focus(null);
         server.root.applyPending();
     }
 }
@@ -75,7 +73,6 @@ pub fn toggleFocusedTags(
     if (new_focused_tags != 0) {
         output.previous_tags = output.pending.tags;
         output.pending.tags = new_focused_tags;
-        seat.focus(null);
         server.root.applyPending();
     }
 }
@@ -92,7 +89,6 @@ pub fn toggleViewTags(
         if (new_tags != 0) {
             const view = seat.focused.view;
             view.pending.tags = new_tags;
-            seat.focus(null);
             server.root.applyPending();
         }
     }
@@ -110,7 +106,6 @@ pub fn focusPreviousTags(
     if (output.pending.tags != previous_tags) {
         output.previous_tags = output.pending.tags;
         output.pending.tags = previous_tags;
-        seat.focus(null);
         server.root.applyPending();
     }
 }
@@ -127,7 +122,6 @@ pub fn sendToPreviousTags(
     if (seat.focused == .view) {
         const view = seat.focused.view;
         view.pending.tags = output.previous_tags;
-        seat.focus(null);
         server.root.applyPending();
     }
 }
diff --git a/river/command/toggle_fullscreen.zig b/river/command/toggle_fullscreen.zig
index 402cce3..5dfef41 100644
--- a/river/command/toggle_fullscreen.zig
+++ b/river/command/toggle_fullscreen.zig
@@ -33,9 +33,6 @@ pub fn toggleFullscreen(
         const view = seat.focused.view;
 
         view.pending.fullscreen = !view.pending.fullscreen;
-        // It is possible to end up with multiple fullscreen views in which
-        // case making one non-fullscreen should switch focus to the next.
-        seat.focus(null);
         server.root.applyPending();
     }
 }