desktop: enforce followMouse focus policy more strictly

When followMouse=yes, prevent stealing focus from another view under
the cursor. The requested view *is* allowed to take focus if it ends up
under the cursor after raising, or if there is no view under the cursor
at all. (Non-view surfaces aren't currently considered.)

v2: allow focusing a new/raised view appearing under the cursor
v3: allow focusing a sub-view/sibling of the requested view
v4: fix new xdg-shell views under mouse not being focused
v5: allow focus if no other view is focused; rebase
This commit is contained in:
John Lindgren 2025-06-14 11:34:32 -04:00
parent 145de91932
commit 4025d009de
6 changed files with 54 additions and 15 deletions

View file

@ -476,10 +476,12 @@ void xdg_shell_finish(struct server *server);
void desktop_focus_view(struct view *view, bool raise);
/**
* desktop_focus_view_or_surface() - like desktop_focus_view() but can
* also focus other (e.g. xwayland-unmanaged) surfaces
* desktop_focus_for_cursor_update() - like desktop_focus_view() but can
* also focus other (e.g. xwayland-unmanaged) surfaces.
*
* Used only for cursor-driven focus updates.
*/
void desktop_focus_view_or_surface(struct seat *seat, struct view *view,
void desktop_focus_for_cursor_update(struct seat *seat, struct view *view,
struct wlr_surface *surface, bool raise);
void desktop_arrange_all_views(struct server *server);

View file

@ -58,8 +58,8 @@ set_or_offer_focus(struct view *view)
}
}
void
desktop_focus_view(struct view *view, bool raise)
static void
_desktop_focus_view(struct view *view, bool raise, bool for_cursor_update)
{
assert(view);
/*
@ -100,6 +100,28 @@ desktop_focus_view(struct view *view, bool raise)
view_move_to_front(view);
}
/*
* When followMouse=yes, prevent stealing focus from another
* view under the cursor. The requested view *is* allowed to
* take focus if it ends up under the cursor after raising, or
* if there is no view under the cursor at all.
*
* If view_move_to_front() also raised child/sibling views,
* then focus the sibling that's under the cursor.
*/
if (rc.focus_follow_mouse && !for_cursor_update) {
struct cursor_context ctx = get_cursor_context(view->server);
if (ctx.view && view_get_root(ctx.view) == view_get_root(view)) {
view = ctx.view;
} else if (ctx.view) {
/*
* Do not steal focus from an unrelated view.
* TODO: consider non-view surfaces also?
*/
return;
}
}
/*
* If any child/sibling of the view is a modal dialog, focus
* the dialog instead. It does not need to be raised separately
@ -109,14 +131,20 @@ desktop_focus_view(struct view *view, bool raise)
set_or_offer_focus(dialog ? dialog : view);
}
void
desktop_focus_view(struct view *view, bool raise)
{
_desktop_focus_view(view, raise, /*for_cursor_update*/ false);
}
/* TODO: focus layer-shell surfaces also? */
void
desktop_focus_view_or_surface(struct seat *seat, struct view *view,
desktop_focus_for_cursor_update(struct seat *seat, struct view *view,
struct wlr_surface *surface, bool raise)
{
assert(view || surface);
if (view) {
desktop_focus_view(view, raise);
_desktop_focus_view(view, raise, /*for_cursor_update*/ true);
#if HAVE_XWAYLAND
} else {
struct wlr_xwayland_surface *xsurface =

View file

@ -639,7 +639,7 @@ cursor_process_motion(struct server *server, uint32_t time, double *sx, double *
* If followMouse=yes, update the keyboard focus when the
* cursor enters a surface
*/
desktop_focus_view_or_surface(seat,
desktop_focus_for_cursor_update(seat,
view_from_wlr_surface(new_focused_surface),
new_focused_surface, rc.raise_on_focus);
}
@ -659,7 +659,7 @@ _cursor_update_focus(struct server *server)
* Always focus the surface below the cursor when
* followMouse=yes and followMouseRequiresMovement=no.
*/
desktop_focus_view_or_surface(&server->seat, ctx.view,
desktop_focus_for_cursor_update(&server->seat, ctx.view,
ctx.surface, rc.raise_on_focus);
}
@ -1102,7 +1102,7 @@ cursor_process_button_press(struct seat *seat, uint32_t button, uint32_t time_ms
}
#ifdef HAVE_XWAYLAND
} else if (ctx.type == LAB_SSD_UNMANAGED) {
desktop_focus_view_or_surface(seat, NULL, ctx.surface,
desktop_focus_for_cursor_update(seat, NULL, ctx.surface,
/*raise*/ false);
#endif
}

View file

@ -11,7 +11,6 @@
void
view_impl_map(struct view *view)
{
desktop_focus_view(view, /*raise*/ true);
view_update_title(view);
view_update_app_id(view);
if (!view->been_mapped) {

View file

@ -217,8 +217,18 @@ handle_commit(struct wl_listener *listener, void *data)
}
if (update_required) {
bool was_empty = wlr_box_empty(current);
view_impl_apply_geometry(view, size.width, size.height);
/*
* Try to focus the view once it has a valid surface
* size. Before this point, the under-mouse checks in
* desktop_focus_view() may fail.
*/
if (was_empty && !wlr_box_empty(&size)) {
desktop_focus_view(view, /*raise*/ true);
}
/*
* Some views (e.g., terminals that scale as multiples of rows
* and columns, or windows that impose a fixed aspect ratio),

View file

@ -826,14 +826,14 @@ xwayland_view_map(struct view *view)
/*
* If the view was focused (on the xwayland server side) before
* being mapped, update the seat focus now. Note that this only
* really matters in the case of Globally Active input windows.
* In all other cases, it's redundant since view_impl_map()
* results in the view being focused anyway.
* being mapped, just update the seat focus. Otherwise, try to
* focus the view now.
*/
if (xwayland_view->focused_before_map) {
xwayland_view->focused_before_map = false;
seat_focus_surface(&view->server->seat, view->surface);
} else {
desktop_focus_view(view, /*raise*/ true);
}
view_impl_map(view);