Skip to content

Commit

Permalink
Seat: keep parent Xwayland view of a focused OR surface activated
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
zakvf committed Jan 11, 2023
1 parent f20692e commit 63610d9
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 2 deletions.
36 changes: 34 additions & 2 deletions river/Seat.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) = .{},
Expand Down Expand Up @@ -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 => {},
}
Expand All @@ -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;
},
Expand Down
10 changes: 10 additions & 0 deletions river/XwaylandView.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 63610d9

Please sign in to comment.