view: try to reduce confusion in focused_view tracking

Our current approach to handling the focused/active view is a bit
confusing. In particular, it's hard to be sure when server->focused_view
is or isn't in sync with the real wlroots keyboard focus.

Try to clean things up a bit. In particular:

- Add comments to server->focused_view and desktop_focused_view() to
  clarify that they should match, but it's not guaranteed.

- desktop_focused_view() now prints a warning if it detects that
  server->focused_view is out of sync. We should keep an eye out for
  this warning, and if we see it, try to figure out why it happened.

- For consistency, use only "focus/defocus" as the verbs in function
  names rather than "activate". This is a bit arbitrary, but the idea is
  that focus is the primary action while the active/inactive state is a
  side effect.

- view_focus/defocus() replace view_set_activated() and now update both
  focus and active/inactive state, to try to keep them in sync.

- Add comments at view_focus/defocus() to warn against calling them
  directly (we should generally call the desktop.c functions).

- desktop_focus_view(NULL) is now forbidden and is no longer handled as
  a special case to clear the focus. This was (at least to me) a
  surprising behavior and caused trouble when working on another change.

- To maintain existing behavior, desktop_focus_topmost_mapped_view() now
  explicitly clears the focus if there are no mapped views.

There should be no behavioral change here.
This commit is contained in:
John Lindgren 2023-09-26 01:35:36 -04:00 committed by Johan Malm
parent 003d1fd494
commit 3022985ba7
11 changed files with 100 additions and 60 deletions

View file

@ -246,6 +246,13 @@ struct server {
uint32_t resize_edges; uint32_t resize_edges;
/* SSD state */ /* SSD state */
/*
* Currently focused view (cached value). This pointer is used
* primarily to track which view is being drawn with "active"
* SSD coloring. We consider it a bug if this gets out of sync
* with the actual keyboard focus (according to wlroots). See
* also desktop_focused_view().
*/
struct view *focused_view; struct view *focused_view;
struct ssd_hover_state *ssd_hover_state; struct ssd_hover_state *ssd_hover_state;
@ -370,7 +377,7 @@ void foreign_toplevel_update_outputs(struct view *view);
* cannot assume this means that the window actually has keyboard * cannot assume this means that the window actually has keyboard
* or pointer focus, in this compositor are they called together. * or pointer focus, in this compositor are they called together.
*/ */
void desktop_focus_and_activate_view(struct seat *seat, struct view *view); void desktop_focus_view(struct view *view);
void desktop_arrange_all_views(struct server *server); void desktop_arrange_all_views(struct server *server);
void desktop_focus_output(struct output *output); void desktop_focus_output(struct output *output);
struct view *desktop_topmost_mapped_view(struct server *server); struct view *desktop_topmost_mapped_view(struct server *server);
@ -388,6 +395,13 @@ enum lab_cycle_dir {
*/ */
struct view *desktop_cycle_view(struct server *server, struct view *start_view, struct view *desktop_cycle_view(struct server *server, struct view *start_view,
enum lab_cycle_dir dir); enum lab_cycle_dir dir);
/**
* desktop_focused_view - return the view that is currently focused
* (determined on-the-fly from the wlroots keyboard focus). The return
* value should ideally match server->focused_view (we consider it a
* bug otherwise) but is guaranteed to be up-to-date even if
* server->focused_view gets out of sync.
*/
struct view *desktop_focused_view(struct server *server); struct view *desktop_focused_view(struct server *server);
void desktop_focus_topmost_mapped_view(struct server *server); void desktop_focus_topmost_mapped_view(struct server *server);

View file

@ -257,7 +257,8 @@ bool view_isfocusable(struct view *view);
bool view_inhibits_keybinds(struct view *view); bool view_inhibits_keybinds(struct view *view);
void view_toggle_keybinds(struct view *view); void view_toggle_keybinds(struct view *view);
void view_set_activated(struct view *view); void view_focus(struct view *view);
void view_defocus(struct view *view);
void view_set_output(struct view *view, struct output *output); void view_set_output(struct view *view, struct output *output);
void view_close(struct view *view); void view_close(struct view *view);

View file

@ -634,7 +634,7 @@ actions_run(struct view *activator, struct server *server,
break; break;
case ACTION_TYPE_FOCUS: case ACTION_TYPE_FOCUS:
if (view) { if (view) {
desktop_focus_and_activate_view(&server->seat, view); desktop_focus_view(view);
} }
break; break;
case ACTION_TYPE_ICONIFY: case ACTION_TYPE_ICONIFY:

View file

@ -478,7 +478,7 @@ process_cursor_motion(struct server *server, uint32_t time)
} }
if (ctx.view && rc.focus_follow_mouse) { if (ctx.view && rc.focus_follow_mouse) {
desktop_focus_and_activate_view(seat, ctx.view); desktop_focus_view(ctx.view);
if (rc.raise_on_focus) { if (rc.raise_on_focus) {
view_move_to_front(ctx.view); view_move_to_front(ctx.view);
} }
@ -522,7 +522,7 @@ _cursor_update_focus(struct server *server)
&& !rc.focus_follow_mouse_requires_movement && !rc.focus_follow_mouse_requires_movement
&& !server->osd_state.cycle_view) { && !server->osd_state.cycle_view) {
/* Prevents changing keyboard focus during A-Tab */ /* Prevents changing keyboard focus during A-Tab */
desktop_focus_and_activate_view(&server->seat, ctx.view); desktop_focus_view(ctx.view);
if (rc.raise_on_focus) { if (rc.raise_on_focus) {
view_move_to_front(ctx.view); view_move_to_front(ctx.view);
} }

View file

@ -35,13 +35,9 @@ desktop_arrange_all_views(struct server *server)
} }
void void
desktop_focus_and_activate_view(struct seat *seat, struct view *view) desktop_focus_view(struct view *view)
{ {
if (!view) { assert(view);
seat_focus_surface(seat, NULL);
return;
}
/* /*
* Guard against views with no mapped surfaces when handling * Guard against views with no mapped surfaces when handling
* 'request_activate' and 'request_minimize'. * 'request_activate' and 'request_minimize'.
@ -51,8 +47,9 @@ desktop_focus_and_activate_view(struct seat *seat, struct view *view)
return; return;
} }
if (input_inhibit_blocks_surface(seat, view->surface->resource) struct server *server = view->server;
|| seat->server->session_lock) { if (input_inhibit_blocks_surface(&server->seat, view->surface->resource)
|| server->session_lock) {
return; return;
} }
@ -69,16 +66,7 @@ desktop_focus_and_activate_view(struct seat *seat, struct view *view)
return; return;
} }
struct wlr_surface *prev_surface; view_focus(view);
prev_surface = seat->seat->keyboard_state.focused_surface;
/* Do not re-focus an already focused surface. */
if (prev_surface == view->surface) {
return;
}
view_set_activated(view);
seat_focus_surface(seat, view->surface);
} }
static struct wl_list * static struct wl_list *
@ -210,28 +198,45 @@ struct view *
desktop_focused_view(struct server *server) desktop_focused_view(struct server *server)
{ {
struct seat *seat = &server->seat; struct seat *seat = &server->seat;
struct wlr_surface *focused_surface; struct wlr_surface *focused_surface =
focused_surface = seat->seat->keyboard_state.focused_surface; seat->seat->keyboard_state.focused_surface;
if (!focused_surface) { struct view *focused_view = NULL;
return NULL;
} if (focused_surface) {
struct view *view; struct view *view;
wl_list_for_each(view, &server->views, link) { wl_list_for_each(view, &server->views, link) {
if (view->surface == focused_surface) { if (view->surface == focused_surface) {
return view; focused_view = view;
break;
}
} }
} }
return NULL; /* warn so we can identify cases where this occurs */
if (focused_view != server->focused_view) {
wlr_log(WLR_ERROR, "server->focused_view is out of sync");
}
return focused_view;
} }
void void
desktop_focus_topmost_mapped_view(struct server *server) desktop_focus_topmost_mapped_view(struct server *server)
{ {
struct view *view = desktop_topmost_mapped_view(server); struct view *view = desktop_topmost_mapped_view(server);
desktop_focus_and_activate_view(&server->seat, view);
if (view) { if (view) {
desktop_focus_view(view);
view_move_to_front(view); view_move_to_front(view);
} else {
/*
* Defocus previous focused surface/view if no longer
* focusable (e.g. unmapped or on a different workspace).
* Note than a non-view surface may have been focused.
*/
seat_focus_surface(&server->seat, NULL);
if (server->focused_view) {
view_defocus(server->focused_view);
}
} }
} }
@ -257,7 +262,7 @@ desktop_focus_output(struct output *output)
} }
if (wlr_output_layout_intersects(layout, if (wlr_output_layout_intersects(layout,
output->wlr_output, &view->current)) { output->wlr_output, &view->current)) {
desktop_focus_and_activate_view(&output->server->seat, view); desktop_focus_view(view);
wlr_cursor_warp(output->server->seat.cursor, NULL, wlr_cursor_warp(output->server->seat.cursor, NULL,
view->current.x + view->current.width / 2, view->current.x + view->current.width / 2,
view->current.y + view->current.height / 2); view->current.y + view->current.height / 2);

View file

@ -37,7 +37,7 @@ handle_request_activate(struct wl_listener *listener, void *data)
if (view->workspace != view->server->workspace_current) { if (view->workspace != view->server->workspace_current) {
workspaces_switch_to(view->workspace); workspaces_switch_to(view->workspace);
} }
desktop_focus_and_activate_view(&view->server->seat, view); desktop_focus_view(view);
view_move_to_front(view); view_move_to_front(view);
} }

View file

@ -41,8 +41,8 @@ keyboard_any_modifiers_pressed(struct wlr_keyboard *keyboard)
static void static void
end_cycling(struct server *server) end_cycling(struct server *server)
{ {
desktop_focus_and_activate_view(&server->seat, server->osd_state.cycle_view);
if (server->osd_state.cycle_view) { if (server->osd_state.cycle_view) {
desktop_focus_view(server->osd_state.cycle_view);
view_move_to_front(server->osd_state.cycle_view); view_move_to_front(server->osd_state.cycle_view);
} }

View file

@ -51,7 +51,7 @@ view_impl_move_sub_views(struct view *parent, enum z_direction z_direction)
void void
view_impl_map(struct view *view) view_impl_map(struct view *view)
{ {
desktop_focus_and_activate_view(&view->server->seat, view); desktop_focus_view(view);
view_move_to_front(view); view_move_to_front(view);
view_update_title(view); view_update_title(view);
view_update_app_id(view); view_update_app_id(view);

View file

@ -187,19 +187,50 @@ _view_set_activated(struct view *view, bool activated)
} }
} }
/*
* Give the view keyboard focus and mark it active. This function should
* only be called by desktop_focus_view(), which contains additional
* checks to make sure it's okay to give focus.
*/
void void
view_set_activated(struct view *view) view_focus(struct view *view)
{ {
assert(view); assert(view);
struct view *last = view->server->focused_view; /* Update seat focus */
if (last == view) { struct seat *seat = &view->server->seat;
return; if (view->surface != seat->seat->keyboard_state.focused_surface) {
seat_focus_surface(seat, view->surface);
} }
if (last) { /* Update active view */
_view_set_activated(last, false); if (view != view->server->focused_view) {
if (view->server->focused_view) {
_view_set_activated(view->server->focused_view, false);
}
_view_set_activated(view, true);
view->server->focused_view = view;
}
}
/*
* Take keyboard focus from the view and mark it inactive. It's rarely
* necessary to call this function directly; usually it's better to
* focus a different view instead by calling something like
* desktop_focus_topmost_mapped_view().
*/
void
view_defocus(struct view *view)
{
assert(view);
/* Update seat focus */
struct seat *seat = &view->server->seat;
if (view->surface == seat->seat->keyboard_state.focused_surface) {
seat_focus_surface(seat, NULL);
}
/* Update active view */
if (view == view->server->focused_view) {
_view_set_activated(view, false);
view->server->focused_view = NULL;
} }
_view_set_activated(view, true);
view->server->focused_view = view;
} }
void void
@ -377,18 +408,7 @@ _minimize(struct view *view, bool minimized)
view->minimized = minimized; view->minimized = minimized;
if (minimized) { if (minimized) {
view->impl->unmap(view, /* client_request */ false); view->impl->unmap(view, /* client_request */ false);
_view_set_activated(view, false); view_defocus(view);
if (view == view->server->focused_view) {
/*
* Prevents clearing the active view when
* we don't actually have keyboard focus.
*
* This may happen when using a custom mouse
* focus configuration or by using the foreign
* toplevel protocol via some panel.
*/
view->server->focused_view = NULL;
}
} else { } else {
view->impl->map(view); view->impl->map(view);
} }

View file

@ -615,7 +615,7 @@ xdg_activation_handle_request(struct wl_listener *listener, void *data)
if (view->workspace != view->server->workspace_current) { if (view->workspace != view->server->workspace_current) {
workspaces_switch_to(view->workspace); workspaces_switch_to(view->workspace);
} }
desktop_focus_and_activate_view(&view->server->seat, view); desktop_focus_view(view);
view_move_to_front(view); view_move_to_front(view);
} }

View file

@ -256,7 +256,7 @@ handle_request_activate(struct wl_listener *listener, void *data)
return; return;
} }
desktop_focus_and_activate_view(&view->server->seat, view); desktop_focus_view(view);
view_move_to_front(view); view_move_to_front(view);
} }