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); void desktop_focus_view(struct view *view, bool raise);
/** /**
* desktop_focus_view_or_surface() - like desktop_focus_view() but can * desktop_focus_for_cursor_update() - like desktop_focus_view() but can
* also focus other (e.g. xwayland-unmanaged) surfaces * 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); struct wlr_surface *surface, bool raise);
void desktop_arrange_all_views(struct server *server); void desktop_arrange_all_views(struct server *server);

View file

@ -58,8 +58,8 @@ set_or_offer_focus(struct view *view)
} }
} }
void static void
desktop_focus_view(struct view *view, bool raise) _desktop_focus_view(struct view *view, bool raise, bool for_cursor_update)
{ {
assert(view); assert(view);
/* /*
@ -100,6 +100,28 @@ desktop_focus_view(struct view *view, bool raise)
view_move_to_front(view); 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 * If any child/sibling of the view is a modal dialog, focus
* the dialog instead. It does not need to be raised separately * 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); 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? */ /* TODO: focus layer-shell surfaces also? */
void 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) struct wlr_surface *surface, bool raise)
{ {
assert(view || surface); assert(view || surface);
if (view) { if (view) {
desktop_focus_view(view, raise); _desktop_focus_view(view, raise, /*for_cursor_update*/ true);
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
} else { } else {
struct wlr_xwayland_surface *xsurface = 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 * If followMouse=yes, update the keyboard focus when the
* cursor enters a surface * cursor enters a surface
*/ */
desktop_focus_view_or_surface(seat, desktop_focus_for_cursor_update(seat,
view_from_wlr_surface(new_focused_surface), view_from_wlr_surface(new_focused_surface),
new_focused_surface, rc.raise_on_focus); 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 * Always focus the surface below the cursor when
* followMouse=yes and followMouseRequiresMovement=no. * 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); 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 #ifdef HAVE_XWAYLAND
} else if (ctx.type == LAB_SSD_UNMANAGED) { } 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); /*raise*/ false);
#endif #endif
} }

View file

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

View file

@ -217,8 +217,18 @@ handle_commit(struct wl_listener *listener, void *data)
} }
if (update_required) { if (update_required) {
bool was_empty = wlr_box_empty(current);
view_impl_apply_geometry(view, size.width, size.height); 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 * Some views (e.g., terminals that scale as multiples of rows
* and columns, or windows that impose a fixed aspect ratio), * 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 * If the view was focused (on the xwayland server side) before
* being mapped, update the seat focus now. Note that this only * being mapped, just update the seat focus. Otherwise, try to
* really matters in the case of Globally Active input windows. * focus the view now.
* In all other cases, it's redundant since view_impl_map()
* results in the view being focused anyway.
*/ */
if (xwayland_view->focused_before_map) { if (xwayland_view->focused_before_map) {
xwayland_view->focused_before_map = false; xwayland_view->focused_before_map = false;
seat_focus_surface(&view->server->seat, view->surface); seat_focus_surface(&view->server->seat, view->surface);
} else {
desktop_focus_view(view, /*raise*/ true);
} }
view_impl_map(view); view_impl_map(view);