aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZakariyya Von Forslun <zakariyyavf@protonmail.com>2023-01-11 06:47:05 +1000
committerZakariyya Von Forslun <zakariyyavf@protonmail.com>2023-01-12 06:56:11 +1000
commit63610d9440e920e9089e51d5d09ca80794226392 (patch)
tree5ffc4abe1141b4cb7099cf4d9be7e38b2408f7a3
parentf20692e329d41baec321a2b022e3279c8a1c6772 (diff)
downloadriver-63610d9440e920e9089e51d5d09ca80794226392.tar.gz
river-63610d9440e920e9089e51d5d09ca80794226392.tar.xz
Seat: keep parent Xwayland view of a focused OR surface activated
Xwayland OR menus may disappear if their parent view is deactivated. The heuristic and ICCCM input model implemented prior, used to determine whether an OR surface may take focus, does not cover all menus, so retaining parent view activation works as a catch-all solution for handling unwanted OR menu focus.
-rw-r--r--river/Seat.zig36
-rw-r--r--river/XwaylandView.zig10
2 files changed, 44 insertions, 2 deletions
diff --git a/river/Seat.zig b/river/Seat.zig
index 627e2f9..441ede3 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -80,6 +80,9 @@ focused_output: *Output,
focused: FocusTarget = .none,
+/// Currently activated Xwayland view (used to handle override redirect menus)
+activated_xwayland_view: if (build_options.xwayland) ?*View else void = if (build_options.xwayland) null else {},
+
/// Stack of views in most recently focused order
/// If there is a currently focused view, it is on top.
focus_stack: ViewStack(*View) = .{},
@@ -230,11 +233,36 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
.none => null,
};
+ if (build_options.xwayland) {
+ // Keep the parent top-level Xwayland view of any override redirect surface
+ // activated while that override redirect surface is focused. This ensures
+ // override redirect menus do not disappear as a result of deactivating
+ // their parent window.
+ if (new_focus == .xwayland_override_redirect and
+ self.focused == .view and
+ self.focused.view.impl == .xwayland_view and
+ self.focused.view.impl.xwayland_view.xwayland_surface.pid == new_focus.xwayland_override_redirect.xwayland_surface.pid)
+ {
+ self.activated_xwayland_view = self.focused.view;
+ } else if (self.activated_xwayland_view) |active_view| {
+ if (!(new_focus == .view and new_focus.view == active_view) and
+ !(new_focus == .xwayland_override_redirect and new_focus.xwayland_override_redirect.xwayland_surface.pid == active_view.impl.xwayland_view.xwayland_surface.pid))
+ {
+ if (active_view.pending.focus == 0) active_view.setActivated(false);
+ self.activated_xwayland_view = null;
+ }
+ }
+ }
+
// First clear the current focus
switch (self.focused) {
.view => |view| {
view.pending.focus -= 1;
- if (view.pending.focus == 0) view.setActivated(false);
+ if (view.pending.focus == 0 and
+ (!build_options.xwayland or view != self.activated_xwayland_view))
+ {
+ view.setActivated(false);
+ }
},
.xwayland_override_redirect, .layer, .lock_surface, .none => {},
}
@@ -244,7 +272,11 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
.view => |target_view| {
assert(server.lock_manager.state == .unlocked);
assert(self.focused_output == target_view.output);
- if (target_view.pending.focus == 0) target_view.setActivated(true);
+ if (target_view.pending.focus == 0 and
+ (!build_options.xwayland or target_view != self.activated_xwayland_view))
+ {
+ target_view.setActivated(true);
+ }
target_view.pending.focus += 1;
target_view.pending.urgent = false;
},
diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig
index 0eef7a4..55681e6 100644
--- a/river/XwaylandView.zig
+++ b/river/XwaylandView.zig
@@ -175,6 +175,16 @@ pub fn getConstraints(self: Self) View.Constraints {
/// Called when the xwayland surface is destroyed
fn handleDestroy(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void {
const self = @fieldParentPtr(Self, "destroy", listener);
+ const view = self.view;
+
+ // Ensure no seat will attempt to access this view after it is destroyed.
+ 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.activated_xwayland_view == view) {
+ seat.activated_xwayland_view = null;
+ }
+ }
// Remove listeners that are active for the entire lifetime of the view
self.destroy.link.remove();