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

commit6499690fc57ba95a7b322ed969f1789f9db921d9
parent3772ecd3d2
authorZakariyya Von Forslun <[email protected]>
date2022-05-28 22:19
xwayland-unmanaged: handle focus on map/cursor button

This implements keyboard focus on map for unmanaged Xwayland views
using wlroot's heuristic approach as well as keyboard focus on cursor
button click.

 river/Cursor.zig            |  8 +++++++-
 river/Seat.zig              | 13 ++++++++++---
 river/XwaylandUnmanaged.zig | 19 ++++++++++++++++---
 3 files changed, 33 insertions(+), 7 deletions(-)

diff --git a/river/Cursor.zig b/river/Cursor.zig
index da1b8d6..73f48a4 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -315,7 +315,13 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
                     self.seat.focus(null);
                 }
             },
-            .xwayland_unmanaged => assert(build_options.xwayland),
+            .xwayland_unmanaged => |xwayland_unmanaged| {
+                if (build_options.xwayland) {
+                    self.seat.setFocusRaw(.{ .xwayland_unmanaged = xwayland_unmanaged });
+                } else {
+                    unreachable;
+                }
+            },
         }
         _ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
 
diff --git a/river/Seat.zig b/river/Seat.zig
index ebe23bb..baf1056 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -38,12 +38,14 @@ const Output = @import("Output.zig");
 const SeatStatus = @import("SeatStatus.zig");
 const View = @import("View.zig");
 const ViewStack = @import("view_stack.zig").ViewStack;
+const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig");
 
 const log = std.log.scoped(.seat);
 const PointerConstraint = @import("PointerConstraint.zig");
 
 const FocusTarget = union(enum) {
     view: *View,
+    xwayland_unmanaged: *XwaylandUnmanaged,
     layer: *LayerSurface,
     none: void,
 };
@@ -206,7 +208,8 @@ fn pendingFilter(view: *View, filter_tags: u32) bool {
 }
 
 /// Switch focus to the target, handling unfocus and input inhibition
-/// properly. This should only be called directly if dealing with layers.
+/// properly. This should only be called directly if dealing with layers or
+/// unmanaged xwayland views.
 pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
     // If the target is already focused, do nothing
     if (std.meta.eql(new_focus, self.focused)) return;
@@ -214,6 +217,10 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
     // Obtain the target surface
     const target_surface = switch (new_focus) {
         .view => |target_view| target_view.surface.?,
+        .xwayland_unmanaged => |target_xwayland_unmanaged| blk: {
+            assert(build_options.xwayland);
+            break :blk target_xwayland_unmanaged.xwayland_surface.surface;
+        },
         .layer => |target_layer| target_layer.wlr_layer_surface.surface,
         .none => null,
     };
@@ -228,7 +235,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
                 view.pending.focus -= 1;
                 if (view.pending.focus == 0) view.setActivated(false);
             },
-            .layer, .none => {},
+            .xwayland_unmanaged, .layer, .none => {},
         }
 
         // Set the new focus
@@ -240,7 +247,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
                 target_view.pending.urgent = false;
             },
             .layer => |target_layer| assert(self.focused_output == target_layer.output),
-            .none => {},
+            .xwayland_unmanaged, .none => {},
         }
         self.focused = new_focus;
 
diff --git a/river/XwaylandUnmanaged.zig b/river/XwaylandUnmanaged.zig
index e19d353..96f6f3f 100644
--- a/river/XwaylandUnmanaged.zig
+++ b/river/XwaylandUnmanaged.zig
@@ -78,19 +78,32 @@ fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wl
 
     xwayland_surface.surface.?.events.commit.add(&self.commit);
 
-    // TODO: handle keyboard focus
-    // if (wlr_xwayland_or_surface_wants_focus(self.xwayland_surface)) { ...
+    if (self.xwayland_surface.overrideRedirectWantsFocus()) {
+        server.input_manager.defaultSeat().setFocusRaw(.{ .xwayland_unmanaged = self });
+    }
 }
 
 /// Called when the surface is unmapped and will no longer be displayed.
 fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void {
     const self = @fieldParentPtr(Self, "unmap", listener);
 
-    // Remove self from the list of unmanged views in the root
+    // Remove self from the list of unmanaged views in the root
     const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
     server.root.xwayland_unmanaged_views.remove(node);
 
     self.commit.link.remove();
+
+    // If the unmapped surface is currently focused, reset focus to the most
+    // appropriate view.
+    var seat_it = server.input_manager.seats.first;
+    while (seat_it) |seat_node| : (seat_it = seat_node.next) {
+        const seat = &seat_node.data;
+        if (seat.focused == .xwayland_unmanaged and seat.focused.xwayland_unmanaged == self) {
+            seat.focus(null);
+        }
+    }
+
+    server.root.startTransaction();
 }
 
 fn handleCommit(_: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {