]> git.mdlowis.com Git - proto/labwc.git/commitdiff
Clear keyboard/pointer focus on Move/Resize, window switcher and menu
authortokyo4j <hrak1529@gmail.com>
Sat, 28 Dec 2024 15:48:55 +0000 (00:48 +0900)
committerHiroaki Yamamoto <hrak1529@gmail.com>
Sun, 29 Dec 2024 07:27:34 +0000 (16:27 +0900)
The previous revert fixed the problem of stuck modifier keys with
keybinds in Blender, but made Firefox show its menu bar with Alt-*
keybinds. This is fundamentally inevitable due to the limitation of
wayland protocol, but at least for the default Alt-Tab keybind for
window switcher, we can mitigate this problem by clearing the keyboard
focus when the window switcher is activated. This is what KWin does, and
we decided to follow that.

So in this commit, keyboard and pointer focus are temporarily cleared
while Move/Resize, window switcher and menu interactions and restored
after them. We slightly deviate from KWin as KWin doesn't clear the
keyboard focus while Move/Resize, but it solves our existing problem
that Firefox shows its menu bar after dragging it with default Alt-Drag
mousebind, and this is what Mutter does.

We considered other solutions, but they don't work well:
1. Send wl_keyboard.{leave,enter} every time keybinds/mousebinds are
   triggered. This solves the Firefox's menu bar problem, but that
   sounds like a workaround and sending unnecessary events every time is
   not desirable.
2. Send release events for both modifiers and keys even when they are
   bound to keybinds. This is what Mutter is doing, but it looks like an
   implementation issue and violates wayland protocol.

include/labwc.h
src/action.c
src/input/cursor.c
src/input/keyboard.c
src/interactive.c
src/menu/menu.c
src/osd.c
src/seat.c
src/view.c

index 783560185e67d4427a4c548a469d56a18bd51dc4..06729f8361a0aeb78a9fca72f94bf51ced65e868 100644 (file)
@@ -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.
index b95dd9df5a63fdc07b0448c1c84b98919ab07172..121f4b0cb89e2108ca2991cefd99f971ce674ff2 100644 (file)
@@ -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);
 }
 
index dc6774c158cc38be3c605c97826275094f4adf8d..2cf3da124bbbeb3dbc077f64bd698ad2b65801e7 100644 (file)
@@ -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;
index d9769443a60ef87b3de65cdf9250e4987b396466..1a8eebf676491913c2bd5320f39ab75003159c86 100644 (file)
@@ -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 *
index 91081fd3cd6d5a8cbe5ab89091bd182aad1f3a71..f552de8fb0e743a6376db7fb238f8f544342e1af 100644 (file)
@@ -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 <unSnapThreshold> 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);
 }
index 91a401780bd659d10a9972cbe9632ae1ee6c2bba..157ffae5a7b3570ba06d6842e31f9cbd3d958e8a 100644 (file)
@@ -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
index 3e061c297a0e655db9263b6187937b738c1b5660..1f58fb496d723d390d010656d6a8234eebfc781c 100644 (file)
--- 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;
 
index f87cc6e96e55ef35f7a7bdf615c2af8fd914663a..40d370e5926b1724790358cee27340ab5f97d68f 100644 (file)
@@ -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);
+}
index a896cb40e6e1154a8e3684bb209e207f113b0aad..6047dd05ce5c50e3303190cfff377c3b23531ffb 100644 (file)
@@ -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) {