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 28af6cc6..7e646dc5 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -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 = diff --git a/src/input/cursor.c b/src/input/cursor.c index 217f6be2..8db5e655 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -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 } 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..4e1f0530 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() 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), diff --git a/src/xwayland.c b/src/xwayland.c index 0185e61d..77c46479 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);