From 34892c35be7dc750bde7e506360c9d43558f71b9 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Thu, 12 Jun 2025 10:55:56 -0400 Subject: [PATCH] desktop: enforce followMouse focus policy more strictly With followMouse=yes in the config, a client activation request was still allowed to focus a view that wasn't under the mouse cursor, but it would then be immediately unfocused again. With some clients using modal dialogs (e.g. Scribus), this could lead to an infinite cycle of the client and labwc continually switching the focus back and forth. To break the cycle, only allow focusing a view under the cursor when when followMouse=yes. 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 Fixes: #2722 --- include/labwc.h | 8 ++++-- src/desktop.c | 63 +++++++++++++++++++++++++++++------------- src/input/cursor.c | 6 ++-- src/view-impl-common.c | 1 - src/xdg.c | 10 +++++++ src/xwayland.c | 8 +++--- 6 files changed, 66 insertions(+), 30 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index af451140..90dea7cd 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -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); diff --git a/src/desktop.c b/src/desktop.c index df179eb2..70a376fa 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -39,8 +39,27 @@ desktop_arrange_all_views(struct server *server) } } -void -desktop_focus_view(struct view *view, bool raise) +static void +set_or_offer_focus(struct view *view) +{ + struct seat *seat = &view->server->seat; + switch (view_wants_focus(view)) { + case VIEW_WANTS_FOCUS_ALWAYS: + if (view->surface != seat->seat->keyboard_state.focused_surface) { + seat_focus_surface(seat, view->surface); + } + break; + case VIEW_WANTS_FOCUS_LIKELY: + case VIEW_WANTS_FOCUS_UNLIKELY: + view_offer_focus(view); + break; + case VIEW_WANTS_FOCUS_NEVER: + break; + } +} + +static void +_desktop_focus_view(struct view *view, bool raise, bool for_cursor_update) { assert(view); /* @@ -77,34 +96,40 @@ desktop_focus_view(struct view *view, bool raise) workspaces_switch_to(view->workspace, /*update_focus*/ false); } - struct seat *seat = &view->server->seat; - switch (view_wants_focus(view)) { - case VIEW_WANTS_FOCUS_ALWAYS: - if (view->surface != seat->seat->keyboard_state.focused_surface) { - seat_focus_surface(seat, view->surface); - } - break; - case VIEW_WANTS_FOCUS_LIKELY: - case VIEW_WANTS_FOCUS_UNLIKELY: - view_offer_focus(view); - break; - case VIEW_WANTS_FOCUS_NEVER: - break; - } - if (raise) { view_move_to_front(view); } + + /* + * When followMouse=yes, only allow focusing a view that's under + * the cursor (after unminimizing and potentially raising it). + * Note that view_move_to_front() may also have raised sub- or + * sibling views. In that case, focus the one 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)) { + set_or_offer_focus(ctx.view); + } + } else { + set_or_offer_focus(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 = diff --git a/src/input/cursor.c b/src/input/cursor.c index d97b93ab..547f5fa9 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -629,7 +629,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); } @@ -649,7 +649,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); } @@ -1092,7 +1092,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 } diff --git a/src/view-impl-common.c b/src/view-impl-common.c index 3357367b..f395a426 100644 --- a/src/view-impl-common.c +++ b/src/view-impl-common.c @@ -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) { diff --git a/src/xdg.c b/src/xdg.c index a31550e5..87d1fb11 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -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() fail with focus-follows-mouse. + */ + 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), diff --git a/src/xwayland.c b/src/xwayland.c index dfc38add..1c1581ea 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -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);