diff --git a/include/labwc.h b/include/labwc.h index 78356018..06729f83 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -116,6 +116,16 @@ struct seat { double x, y; } smooth_scroll_offset; + /* + * The surface whose keyboard focus is temporarily cleared with + * seat_focus_override_begin() and restored with + * seat_focus_override_end(). + */ + struct { + struct wlr_surface *surface; + struct wl_listener surface_destroy; + } focus_override; + struct wlr_pointer_constraint_v1 *current_constraint; /* In support for ToggleKeybinds */ @@ -273,9 +283,13 @@ struct server { * 'active_view' is generally the view with keyboard-focus, updated with * each "focus change". This view is drawn with "active" SSD coloring. * - * The exception is when a layer-shell client takes keyboard-focus in - * which case the currently active view stays active. This is important - * for foreign-toplevel protocol. + * The exceptions are: + * - when a layer-shell client takes keyboard-focus in which case the + * currently active view stays active + * - when keyboard focus is temporarily cleared for server-side + * interactions like Move/Resize, window switcher and menus. + * + * Note that active_view is synced with foreign-toplevel clients. */ struct view *active_view; /* @@ -503,6 +517,20 @@ void seat_set_pressed(struct seat *seat, struct cursor_context *ctx); void seat_reset_pressed(struct seat *seat); void seat_output_layout_changed(struct seat *seat); +/* + * Temporarily clear the pointer/keyboard focus from the client at the + * beginning of interactive move/resize, window switcher or menu interactions. + * The focus is kept cleared until seat_focus_override_end() is called or + * layer-shell/session-lock surfaces are mapped. + */ +void seat_focus_override_begin(struct seat *seat, enum input_mode input_mode, + enum lab_cursors cursor_shape); +/* + * Restore the pointer/keyboard focus which was cleared in + * seat_focus_override_begin(). + */ +void seat_focus_override_end(struct seat *seat); + /** * interactive_anchor_to_cursor() - repositions the geometry to remain * underneath the cursor when its size changes during interactive move. diff --git a/src/action.c b/src/action.c index b95dd9df..121f4b0c 100644 --- a/src/action.c +++ b/src/action.c @@ -810,7 +810,9 @@ start_window_cycling(struct server *server, enum lab_cycle_dir direction) shift_is_pressed(server); server->osd_state.cycle_view = desktop_cycle_view(server, server->osd_state.cycle_view, direction); - server->input_mode = LAB_INPUT_STATE_WINDOW_SWITCHER; + + seat_focus_override_begin(&server->seat, + LAB_INPUT_STATE_WINDOW_SWITCHER, LAB_CURSOR_DEFAULT); osd_update(server); } diff --git a/src/input/cursor.c b/src/input/cursor.c index dc6774c1..2cf3da12 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -496,7 +496,8 @@ cursor_update_common(struct server *server, struct cursor_context *ctx, if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { /* * Prevent updating focus/cursor image during - * interactive move/resize + * interactive move/resize, window switcher and + * menu interaction. */ return false; } @@ -524,28 +525,9 @@ cursor_update_common(struct server *server, struct cursor_context *ctx, * cursor image will be set by request_cursor_notify() * in response to the enter event. */ - bool has_focus = (ctx->surface == - wlr_seat->pointer_state.focused_surface); - - if (!has_focus || seat->server_cursor != LAB_CURSOR_CLIENT) { - /* - * Enter the surface if necessary. Usually we - * prevent re-entering an already focused - * surface, because the extra leave and enter - * events can confuse clients (e.g. break - * double-click detection). - * - * We do however send a leave/enter event pair - * if a server-side cursor was set and we need - * to trigger a cursor image update. - */ - if (has_focus) { - wlr_seat_pointer_notify_clear_focus(wlr_seat); - } - wlr_seat_pointer_notify_enter(wlr_seat, ctx->surface, - ctx->sx, ctx->sy); - seat->server_cursor = LAB_CURSOR_CLIENT; - } + wlr_seat_pointer_notify_enter(wlr_seat, ctx->surface, + ctx->sx, ctx->sy); + seat->server_cursor = LAB_CURSOR_CLIENT; if (cursor_has_moved) { *sx = ctx->sx; *sy = ctx->sy; diff --git a/src/input/keyboard.c b/src/input/keyboard.c index d9769443..1a8eebf6 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -69,15 +69,18 @@ keyboard_any_modifiers_pressed(struct wlr_keyboard *keyboard) static void end_cycling(struct server *server) { - osd_preview_restore(server); - if (server->osd_state.cycle_view) { - desktop_focus_view(server->osd_state.cycle_view, - /*raise*/ true); + should_cancel_cycling_on_next_key_release = false; + + if (server->input_mode != LAB_INPUT_STATE_WINDOW_SWITCHER) { + return; } - /* osd_finish() additionally resets cycle_view to NULL */ + struct view *cycle_view = server->osd_state.cycle_view; + osd_preview_restore(server); + /* FIXME: osd_finish() transiently sets focus to the old surface */ osd_finish(server); - should_cancel_cycling_on_next_key_release = false; + /* Note that server->osd_state.cycle_view is cleared at this point */ + desktop_focus_view(cycle_view, /*raise*/ true); } static struct wlr_seat_client * diff --git a/src/interactive.c b/src/interactive.c index 91081fd3..f552de8f 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -73,6 +73,8 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) return; } + enum lab_cursors cursor_shape = LAB_CURSOR_DEFAULT; + switch (mode) { case LAB_INPUT_STATE_MOVE: if (view->fullscreen) { @@ -96,7 +98,7 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; seat->region_prevent_snap = keyboard_any_modifiers_pressed(keyboard); - cursor_set(seat, LAB_CURSOR_GRAB); + cursor_shape = LAB_CURSOR_GRAB; break; case LAB_INPUT_STATE_RESIZE: if (view->shaded || view->fullscreen || @@ -121,14 +123,13 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) */ view_set_untiled(view); view_restore_to(view, view->pending); - cursor_set(seat, cursor_get_from_edge(edges)); + cursor_shape = cursor_get_from_edge(edges); break; default: /* Should not be reached */ return; } - server->input_mode = mode; server->grabbed_view = view; /* Remember view and cursor positions at start of move/resize */ server->grab_x = seat->cursor->x; @@ -136,6 +137,8 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) server->grab_box = view->current; server->resize_edges = edges; + seat_focus_override_begin(seat, mode, cursor_shape); + /* * Un-tile maximized/tiled view immediately if is * zero. Otherwise, un-tile it later in cursor motion handler. @@ -281,9 +284,8 @@ interactive_cancel(struct view *view) resize_indicator_hide(view); - view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; view->server->grabbed_view = NULL; - /* Update focus/cursor image */ - cursor_update_focus(view->server); + /* Restore keyboard/pointer focus */ + seat_focus_override_end(&view->server->seat); } diff --git a/src/menu/menu.c b/src/menu/menu.c index 91a40178..157ffae5 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -1336,8 +1336,9 @@ menu_open_root(struct menu *menu, int x, int y) menu_configure(menu, (struct wlr_box){.x = x, .y = y}); wlr_scene_node_set_enabled(&menu->scene_tree->node, true); menu->server->menu_current = menu; - menu->server->input_mode = LAB_INPUT_STATE_MENU; selected_item = NULL; + seat_focus_override_begin(&menu->server->seat, + LAB_INPUT_STATE_MENU, LAB_CURSOR_DEFAULT); } static void @@ -1628,8 +1629,7 @@ menu_execute_item(struct menuitem *item) */ struct server *server = item->parent->server; menu_close(server->menu_current); - server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; - cursor_update_focus(server); + seat_focus_override_end(&server->seat); /* * We call the actions after closing the menu so that virtual keyboard @@ -1739,7 +1739,7 @@ menu_close_root(struct server *server) server->menu_current = NULL; destroy_pipemenus(server); } - server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; + seat_focus_override_end(&server->seat); } void diff --git a/src/osd.c b/src/osd.c index 3e061c29..1f58fb49 100644 --- a/src/osd.c +++ b/src/osd.c @@ -105,7 +105,8 @@ osd_on_view_destroy(struct view *view) void osd_finish(struct server *server) { - server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; + seat_focus_override_end(&server->seat); + server->osd_state.preview_node = NULL; server->osd_state.preview_anchor = NULL; diff --git a/src/seat.c b/src/seat.c index f87cc6e9..40d370e5 100644 --- a/src/seat.c +++ b/src/seat.c @@ -507,6 +507,14 @@ focus_change_notify(struct wl_listener *listener, void *data) return; } + /* + * We clear the keyboard focus at the beginning of Move/Resize, window + * switcher and opening menus, but don't want to deactivate the view. + */ + if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { + return; + } + if (view != server->active_view) { if (server->active_view) { view_set_activated(server->active_view, false); @@ -656,8 +664,16 @@ seat_reconfigure(struct server *server) } static void -seat_focus(struct seat *seat, struct wlr_surface *surface, bool is_lock_surface) +seat_focus(struct seat *seat, struct wlr_surface *surface, + bool replace_exclusive_layer, bool is_lock_surface) { + /* Respect layer-shell exclusive keyboard-interactivity. */ + if (seat->focused_layer && seat->focused_layer->current.keyboard_interactive + == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE + && !replace_exclusive_layer) { + return; + } + /* * Respect session lock. This check is critical, DO NOT REMOVE. * It should also come before the !surface condition, or the @@ -708,18 +724,20 @@ seat_focus(struct seat *seat, struct wlr_surface *surface, bool is_lock_surface) void seat_focus_surface(struct seat *seat, struct wlr_surface *surface) { - /* Respect layer-shell exclusive keyboard-interactivity. */ - if (seat->focused_layer && seat->focused_layer->current.keyboard_interactive - == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { + /* Don't update focus while window switcher, Move/Resize and menu interaction */ + if (seat->server->osd_state.cycle_view || seat->server->input_mode + != LAB_INPUT_STATE_PASSTHROUGH) { return; } - seat_focus(seat, surface, /*is_lock_surface*/ false); + seat_focus(seat, surface, /*replace_exclusive_layer*/ false, + /*is_lock_surface*/ false); } void seat_focus_lock_surface(struct seat *seat, struct wlr_surface *surface) { - seat_focus(seat, surface, /*is_lock_surface*/ true); + seat_focus(seat, surface, /*replace_exclusive_layer*/ true, + /*is_lock_surface*/ true); } void @@ -730,7 +748,8 @@ seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer) desktop_focus_topmost_view(seat->server); return; } - seat_focus(seat, layer->surface, /*is_lock_surface*/ false); + seat_focus(seat, layer->surface, /*replace_exclusive_layer*/ true, + /*is_lock_surface*/ false); seat->focused_layer = layer; } @@ -794,3 +813,53 @@ seat_output_layout_changed(struct seat *seat) } } } + +static void +handle_focus_override_surface_destroy(struct wl_listener *listener, void *data) +{ + struct seat *seat = wl_container_of(listener, seat, + focus_override.surface_destroy); + wl_list_remove(&seat->focus_override.surface_destroy.link); + seat->focus_override.surface = NULL; +} + +void +seat_focus_override_begin(struct seat *seat, enum input_mode input_mode, + enum lab_cursors cursor_shape) +{ + assert(!seat->focus_override.surface); + assert(seat->server->input_mode == LAB_INPUT_STATE_PASSTHROUGH); + + seat->server->input_mode = input_mode; + + seat->focus_override.surface = seat->seat->keyboard_state.focused_surface; + if (seat->focus_override.surface) { + seat->focus_override.surface_destroy.notify = + handle_focus_override_surface_destroy; + wl_signal_add(&seat->focus_override.surface->events.destroy, + &seat->focus_override.surface_destroy); + } + + seat_focus(seat, NULL, /*replace_exclusive_layer*/ false, + /*is_lock_surface*/ false); + wlr_seat_pointer_clear_focus(seat->seat); + cursor_set(seat, cursor_shape); +} + +void +seat_focus_override_end(struct seat *seat) +{ + seat->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; + + if (seat->focus_override.surface) { + if (!seat->seat->keyboard_state.focused_surface) { + seat_focus(seat, seat->focus_override.surface, + /*replace_exclusive_layer*/ false, + /*is_lock_surface*/ false); + } + wl_list_remove(&seat->focus_override.surface_destroy.link); + seat->focus_override.surface = NULL; + } + + cursor_update_focus(seat->server); +} diff --git a/src/view.c b/src/view.c index a896cb40..6047dd05 100644 --- a/src/view.c +++ b/src/view.c @@ -2531,9 +2531,7 @@ view_destroy(struct view *view) if (server->grabbed_view == view) { /* Application got killed while moving around */ - server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; - server->grabbed_view = NULL; - overlay_hide(&server->seat); + interactive_cancel(view); } if (server->active_view == view) {