From 6c6e406507bfb0e9a809b74029b5b05b5a095768 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Mon, 12 Sep 2022 13:14:18 -0400 Subject: [PATCH] cursor: Factor out cursor_update_common() and fix some glitches Fix a couple of glitches seen when exiting interactive move/resize: - Cursor briefly set to left_ptr rather than the correct cursor image - Cursor not updated if the view being moved/resized is destroyed Also make sure to exit interactive mode if the view is going fullscreen (labwc gets very confused otherwise). Code changes in detail: - Factor out set_server_cursor() which will set the correct cursor image for non-client areas (either XCURSOR_DEFAULT or one of the resize cursors). - Unify the logic from cursor_rebase() and process_cursor_motion by factoring out cursor_update_common(). This corrects some logic discrepancies between the two, which should be a good thing(TM). - Remove the extra cursor_set(XCURSOR_DEFAULT) from interactive_end() and instead rely on cursor_update_focus() to do the right thing. - Simplify cursor_button() by just calling interactive_end() when we want to exit interactive mode. - Call cursor_update_focus() from view_destroy() if the view had mouse focus or was being interactively moved/resized. v2: Eliminate force_reenter parameters and figure out automatically when we need to re-enter the surface. v3: Rename wlseat -> wlr_seat. v4: Simplify client/server cursor logic. --- include/labwc.h | 11 +- src/cursor.c | 254 ++++++++++++++++----------------------- src/desktop.c | 2 +- src/interactive.c | 11 +- src/view.c | 10 +- src/xwayland-unmanaged.c | 8 +- 6 files changed, 129 insertions(+), 167 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 0466e9a1..2de216f9 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -80,6 +80,12 @@ struct seat { struct wlr_keyboard_group *keyboard_group; bool cursor_requires_fallback; + /* + * Name of most recent server-side cursor image. Set by + * cursor_set(). Cleared when a client surface is entered + * (in that case the client is expected to set a cursor image). + */ + char *cursor_set_by_server; struct wlr_cursor *cursor; struct wlr_xcursor_manager *xcursor_manager; @@ -533,13 +539,12 @@ void cursor_set(struct seat *seat, const char *cursor_name); /** * cursor_update_focus - update cursor focus, may update the cursor icon * @server - server - * @force_reenter - re-enter a surface if it already owns the cursor focus * * This can be used to give the mouse focus to the surface under the cursor * or to force an update of the cursor icon by sending an exit and enter - * event to an already focused surface when @force_reenter is true. + * event to an already focused surface. */ -void cursor_update_focus(struct server *server, bool force_reenter); +void cursor_update_focus(struct server *server); void cursor_init(struct seat *seat); void cursor_finish(struct seat *seat); diff --git a/src/cursor.c b/src/cursor.c index d535ba05..cf7d01ba 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -14,10 +14,6 @@ #include "common/scene-helpers.h" #include "common/zfree.h" -/* Used to prevent setting the same cursor image twice */ -static char *last_cursor_image = NULL; - - static const struct cursor_alias { const char *name, *alias; } cursor_aliases[] = { @@ -49,52 +45,6 @@ is_surface(enum ssd_part_type view_area) ; } -/* - * cursor_rebase() for internal use: reuses node, surface, sx and sy - * For a public variant use cursor_update_focus() - */ -static void -cursor_rebase(struct seat *seat, struct wlr_scene_node *node, - struct wlr_surface *surface, double sx, double sy, uint32_t time_msec, - bool force) -{ - if (seat->pressed.surface && surface != seat->pressed.surface) { - /* Don't leave surface when a button was pressed over another surface */ - return; - } - - if (seat->server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { - /* Prevent resetting focus / cursor image when moving or resizing */ - return; - } - - struct wlr_surface *focused_surface = - seat->seat->pointer_state.focused_surface; - - if (surface) { - if (!force && surface == focused_surface) { - /* - * Usually we prevent re-entering an already focused surface - * because it sends useless leave and enter events. - * - * They may also seriously confuse clients if sent between - * connected events like a double click (#258) or fast scroll - * events caused by a touchpad (#225). - * - * If we just want to update the cursor image though - * the @force argument may be used to allow re-entering. - */ - return; - } - wlr_seat_pointer_notify_clear_focus(seat->seat); - wlr_seat_pointer_notify_enter(seat->seat, surface, sx, sy); - wlr_seat_pointer_notify_motion(seat->seat, time_msec, sx, sy); - } else if (focused_surface) { - cursor_set(seat, XCURSOR_DEFAULT); - wlr_seat_pointer_notify_clear_focus(seat->seat); - } -} - static void request_cursor_notify(struct wl_listener *listener, void *data) { @@ -127,10 +77,6 @@ request_cursor_notify(struct wl_listener *listener, void *data) wlr_cursor_set_surface(seat->cursor, event->surface, event->hotspot_x, event->hotspot_y); - - if (last_cursor_image) { - zfree(last_cursor_image); - } } } @@ -250,16 +196,26 @@ cursor_set(struct seat *seat, const char *cursor_name) } /* Prevent setting the same cursor image twice */ - if (last_cursor_image) { - if (!strcmp(last_cursor_image, cursor_name)) { - return; - } - free(last_cursor_image); + if (seat->cursor_set_by_server && !strcmp(cursor_name, + seat->cursor_set_by_server)) { + return; } - last_cursor_image = strdup(cursor_name); wlr_xcursor_manager_set_cursor_image( seat->xcursor_manager, cursor_name, seat->cursor); + zfree(seat->cursor_set_by_server); + seat->cursor_set_by_server = strdup(cursor_name); +} + +static void +set_server_cursor(struct seat *seat, enum ssd_part_type view_area) +{ + uint32_t resize_edges = ssd_resize_edges(view_area); + if (resize_edges) { + cursor_set(seat, wlr_xcursor_get_resize_name(resize_edges)); + } else { + cursor_set(seat, XCURSOR_DEFAULT); + } } bool @@ -307,11 +263,86 @@ process_cursor_motion_out_of_surface(struct server *server, uint32_t time) wlr_seat_pointer_notify_motion(server->seat.seat, time, sx, sy); } +/* + * Common logic shared by cursor_update_focus() and process_cursor_motion() + */ +static void +cursor_update_common(struct server *server, struct wlr_scene_node *node, + struct wlr_surface *surface, double sx, double sy, + enum ssd_part_type view_area, uint32_t time_msec, + bool cursor_has_moved) +{ + struct seat *seat = &server->seat; + struct wlr_seat *wlr_seat = seat->seat; + + ssd_update_button_hover(node, &server->ssd_hover_state); + + if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { + /* + * Prevent updating focus/cursor image during + * interactive move/resize + */ + return; + } + + /* TODO: verify drag_icon logic */ + if (seat->pressed.surface && surface != seat->pressed.surface + && !seat->drag_icon) { + if (cursor_has_moved) { + /* + * Button has been pressed while over another + * surface and is still held down. Just send + * the motion events to the focused surface so + * we can keep scrolling or selecting text even + * if the cursor moves outside of the surface. + */ + process_cursor_motion_out_of_surface(server, time_msec); + } + return; + } + + if (surface && !input_inhibit_blocks_surface(seat, surface->resource)) { + /* + * Cursor is over an input-enabled client surface. The + * cursor image will be set by request_cursor_notify() + * in response to the enter event. + */ + if (surface != wlr_seat->pointer_state.focused_surface + || seat->cursor_set_by_server) { + /* + * 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. + */ + wlr_seat_pointer_notify_clear_focus(wlr_seat); + wlr_seat_pointer_notify_enter(wlr_seat, surface, + sx, sy); + zfree(seat->cursor_set_by_server); + } + if (cursor_has_moved) { + wlr_seat_pointer_notify_motion(wlr_seat, time_msec, + sx, sy); + } + } else { + /* + * Cursor is over a server (labwc) surface. Clear focus + * from the focused client (if any, no-op otherwise) and + * set the cursor image ourselves. + */ + wlr_seat_pointer_notify_clear_focus(wlr_seat); + set_server_cursor(seat, view_area); + } +} + static void process_cursor_motion(struct server *server, uint32_t time) { - static bool cursor_name_set_by_server; - /* If the mode is non-passthrough, delegate to those functions. */ if (server->input_mode == LAB_INPUT_STATE_MOVE) { process_cursor_move(server, time); @@ -323,7 +354,6 @@ process_cursor_motion(struct server *server, uint32_t time) /* Otherwise, find view under the pointer and send the event along */ double sx, sy; - struct wlr_seat *wlr_seat = server->seat.seat; struct wlr_scene_node *node; enum ssd_part_type view_area = LAB_SSD_NONE; struct view *view = desktop_node_and_view_at(server, @@ -335,29 +365,9 @@ process_cursor_motion(struct server *server, uint32_t time) surface = lab_wlr_surface_from_node(node); } - /* resize handles */ - uint32_t resize_edges = ssd_resize_edges(view_area); - - /* Set cursor */ - if (view_area == LAB_SSD_ROOT || view_area == LAB_SSD_MENU) { - cursor_set(&server->seat, XCURSOR_DEFAULT); - } else if (resize_edges) { - cursor_name_set_by_server = true; - cursor_set(&server->seat, - wlr_xcursor_get_resize_name(resize_edges)); - } else if (ssd_part_contains(LAB_SSD_PART_TITLEBAR, view_area)) { - /* title and buttons */ - cursor_set(&server->seat, XCURSOR_DEFAULT); - cursor_name_set_by_server = true; - } else if (cursor_name_set_by_server) { - /* xdg/xwindow window content */ - /* layershell or unmanaged */ - cursor_set(&server->seat, XCURSOR_DEFAULT); - cursor_name_set_by_server = false; - } - if (view_area == LAB_SSD_MENU) { menu_process_cursor_motion(node); + set_server_cursor(&server->seat, view_area); return; } @@ -373,6 +383,7 @@ process_cursor_motion(struct server *server, uint32_t time) if (mousebind->mouse_event == MOUSE_ACTION_DRAG && mousebind->pressed_in_context) { /* Find closest resize edges in case action is Resize */ + uint32_t resize_edges = ssd_resize_edges(view_area); if (view && !resize_edges) { resize_edges |= server->seat.cursor->x < view->x + view->w / 2 ? WLR_EDGE_LEFT @@ -387,50 +398,8 @@ process_cursor_motion(struct server *server, uint32_t time) } } - /* TODO: ssd_hover_state should likely be located in server->seat */ - ssd_update_button_hover(node, &server->ssd_hover_state); - - if (server->seat.pressed.surface && - server->seat.pressed.surface != surface && - !server->seat.drag_icon) { - /* - * Button has been pressed while over another surface - * and is still held down. Just send the adjusted motion - * events to the focused surface so we can keep scrolling - * or selecting text even if the cursor moves outside of - * the surface. - */ - process_cursor_motion_out_of_surface(server, time); - } else if (surface && !input_inhibit_blocks_surface( - &server->seat, surface->resource)) { - bool focus_changed = - wlr_seat->pointer_state.focused_surface != surface; - /* - * "Enter" the surface if necessary. This lets the client know - * that the cursor has entered one of its surfaces. - * - * Note that this gives the surface "pointer focus", which is - * distinct from keyboard focus. You get pointer focus by moving - * the pointer over a window. - */ - wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); - if (!focus_changed || server->seat.drag_icon) { - /* - * The enter event contains coordinates, so we only need - * to notify on motion if the focus did not change. - */ - wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy); - } - } else { - /* - * Clear pointer focus so future button events and such are not - * sent to the last client to have the cursor over it. - * - * Except if we started pressing a button on the last client and - * are not currently in drag and drop mode. - */ - wlr_seat_pointer_clear_focus(wlr_seat); - } + cursor_update_common(server, node, surface, sx, sy, view_area, time, + /*cursor_has_moved*/ true); } static uint32_t @@ -440,7 +409,7 @@ msec(const struct timespec *t) } void -cursor_update_focus(struct server *server, bool force_reenter) +cursor_update_focus(struct server *server) { double sx, sy; struct wlr_scene_node *node = NULL; @@ -458,10 +427,9 @@ cursor_update_focus(struct server *server, bool force_reenter) surface = lab_wlr_surface_from_node(node); } - ssd_update_button_hover(node, &seat->server->ssd_hover_state); - /* Focus surface under cursor if it isn't already focused */ - cursor_rebase(seat, node, surface, sx, sy, msec(&now), force_reenter); + cursor_update_common(server, node, surface, sx, sy, view_area, + msec(&now), /*cursor_has_moved*/ false); } void @@ -830,27 +798,15 @@ cursor_button(struct wl_listener *listener, void *data) if (server->input_mode == LAB_INPUT_STATE_MENU) { if (close_menu) { menu_close_root(server); - cursor_rebase(&server->seat, node, surface, sx, sy, - event->time_msec, false); + cursor_update_common(server, node, surface, sx, sy, + view_area, event->time_msec, false); close_menu = false; } return; } if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { - /* Exit interactive move/resize/menu mode. */ - if (server->grabbed_view == view) { - interactive_end(view); - } else { - server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; - server->grabbed_view = NULL; - } - - /* - * Focus surface under cursor and force updating the - * cursor icon - */ - cursor_rebase(&server->seat, node, surface, sx, sy, - event->time_msec, true); + /* Exit interactive move/resize mode */ + interactive_end(server->grabbed_view); return; } goto mousebindings; @@ -923,7 +879,7 @@ cursor_axis(struct wl_listener *listener, void *data) wlr_idle_notify_activity(seat->wlr_idle, seat->seat); /* Make sure we are sending the events to the surface under the cursor */ - cursor_update_focus(seat->server, false); + cursor_update_focus(seat->server); /* Notify the client with pointer focus of the axis event. */ wlr_seat_pointer_notify_axis(seat->seat, event->time_msec, diff --git a/src/desktop.c b/src/desktop.c index 20e73ff9..e6d09080 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -65,7 +65,7 @@ desktop_move_to_front(struct view *view) #if HAVE_XWAYLAND move_xwayland_sub_views_to_front(view); #endif - cursor_update_focus(view->server, false); + cursor_update_focus(view->server); } static void diff --git a/src/interactive.c b/src/interactive.c index c56a17d7..02ee9ee6 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -134,14 +134,7 @@ interactive_end(struct view *view) view_snap_to_edge(view, "down"); } } - /* - * First set the cursor image in case we moved / resized via SSD. - * Then allow an application to set its own image in case there - * is a surface below the cursor (e.g. moved / resized via 'Alt' - * modifier). If there is no surface below the cursor the second - * call is a no-op. - */ - cursor_set(&view->server->seat, XCURSOR_DEFAULT); - cursor_update_focus(view->server, true); + /* Update focus/cursor image */ + cursor_update_focus(view->server); } } diff --git a/src/view.c b/src/view.c index 088d3647..0ee9c317 100644 --- a/src/view.c +++ b/src/view.c @@ -149,7 +149,7 @@ view_moved(struct view *view) wlr_scene_node_set_position(&view->scene_tree->node, view->x, view->y); view_discover_output(view); ssd_update_geometry(view); - cursor_update_focus(view->server, false); + cursor_update_focus(view->server); } /* N.B. Use view_move() if not resizing. */ @@ -533,6 +533,7 @@ view_set_fullscreen(struct view *view, bool fullscreen, wlr_output = view_wlr_output(view); } if (fullscreen) { + interactive_end(view); if (!view->maximized && !view->tiled) { view_store_natural_geometry(view); } @@ -801,6 +802,7 @@ void view_destroy(struct view *view) { struct server *server = view->server; + bool need_cursor_update = false; if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_destroy(view->toplevel_handle); @@ -810,6 +812,7 @@ view_destroy(struct view *view) /* Application got killed while moving around */ server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; server->grabbed_view = NULL; + need_cursor_update = true; } if (server->seat.pressed.view == view) { @@ -819,6 +822,7 @@ view_destroy(struct view *view) if (server->focused_view == view) { server->focused_view = NULL; + need_cursor_update = true; } osd_on_view_destroy(view); @@ -850,4 +854,8 @@ view_destroy(struct view *view) /* Remove view from server->views */ wl_list_remove(&view->link); free(view); + + if (need_cursor_update) { + cursor_update_focus(server); + } } diff --git a/src/xwayland-unmanaged.c b/src/xwayland-unmanaged.c index db6022d6..b09b6196 100644 --- a/src/xwayland-unmanaged.c +++ b/src/xwayland-unmanaged.c @@ -11,7 +11,7 @@ unmanaged_handle_request_configure(struct wl_listener *listener, void *data) wlr_xwayland_surface_configure(xsurface, ev->x, ev->y, ev->width, ev->height); if (unmanaged->node) { wlr_scene_node_set_position(unmanaged->node, ev->x, ev->y); - cursor_update_focus(unmanaged->server, false); + cursor_update_focus(unmanaged->server); } } @@ -23,7 +23,7 @@ unmanaged_handle_set_geometry(struct wl_listener *listener, void *data) struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; if (unmanaged->node) { wlr_scene_node_set_position(unmanaged->node, xsurface->x, xsurface->y); - cursor_update_focus(unmanaged->server, false); + cursor_update_focus(unmanaged->server); } } @@ -51,7 +51,7 @@ unmanaged_handle_map(struct wl_listener *listener, void *data) unmanaged->server->unmanaged_tree, xsurface->surface)->buffer->node; wlr_scene_node_set_position(unmanaged->node, xsurface->x, xsurface->y); - cursor_update_focus(unmanaged->server, false); + cursor_update_focus(unmanaged->server); } static void @@ -105,7 +105,7 @@ unmanaged_handle_unmap(struct wl_listener *listener, void *data) seat_reset_pressed(seat); } unmanaged->node = NULL; - cursor_update_focus(unmanaged->server, false); + cursor_update_focus(unmanaged->server); if (seat->seat->keyboard_state.focused_surface == xsurface->surface) { focus_next_surface(unmanaged->server, xsurface);