From a672e8a9fdd486b1a2c645aa1135a610124438dc Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Mon, 9 Feb 2026 23:49:42 -0500 Subject: [PATCH] interactive: set grab parameters at cursor press Add interactive_set_grab_context() which is called when the mouse button is first pressed, before interactive_begin(). This fixes two small issues: - The cursor origin position for interactive move/resize was slightly off (depending on mouse resolution), because it was set after the mouse had already moved slightly. Now it's exact. - If app- or keybind-initiated maximize (etc.) happened after the button press but before the mouse was moved, then interactive_begin() would still start move/resize even though the view might now be far away from the cursor. Now interactive_cancel() works as expected, even if called before interactive_begin(). Also, make sure to call interactive_cancel() for un-maximize as well. --- include/labwc.h | 1 + src/action.c | 18 ++++++++++--- src/input/cursor.c | 4 +++ src/interactive.c | 66 +++++++++++++++++++++++++++++++++++++--------- src/view.c | 13 ++++----- src/xdg.c | 12 ++++----- src/xwayland.c | 12 ++++----- 7 files changed, 90 insertions(+), 36 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 5e887ad8..f5c42307 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -414,6 +414,7 @@ void seat_focus_override_end(struct seat *seat, bool restore_focus); */ void interactive_anchor_to_cursor(struct server *server, struct wlr_box *geo); +void interactive_set_grab_context(struct cursor_context *ctx); void interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges); void interactive_finish(struct view *view); diff --git a/src/action.c b/src/action.c index 78c8a28b..2f956d95 100644 --- a/src/action.c +++ b/src/action.c @@ -1262,6 +1262,14 @@ run_action(struct view *view, struct server *server, struct action *action, break; case ACTION_TYPE_MOVE: if (view) { + /* + * If triggered by mousebind, grab context was already + * set by button press handling. For keybind-triggered + * Move, set it now from current cursor position. + */ + if (view != server->seat.pressed.ctx.view) { + interactive_set_grab_context(ctx); + } interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); } @@ -1285,9 +1293,13 @@ run_action(struct view *view, struct server *server, struct action *action, */ enum lab_edge resize_edges = action_get_int(action, "direction", LAB_EDGE_NONE); - if (resize_edges == LAB_EDGE_NONE) { - resize_edges = cursor_get_resize_edges( - server->seat.cursor, ctx); + /* + * If triggered by mousebind, grab context was already + * set by button press handling. For keybind-triggered + * Resize, set it now from current cursor position. + */ + if (view != server->seat.pressed.ctx.view) { + interactive_set_grab_context(ctx); } interactive_begin(view, LAB_INPUT_STATE_RESIZE, resize_edges); diff --git a/src/input/cursor.c b/src/input/cursor.c index 29409bea..98679565 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -1147,6 +1147,7 @@ cursor_process_button_press(struct seat *seat, uint32_t button, uint32_t time_ms if (ctx.view || ctx.surface) { /* Store cursor context for later action processing */ cursor_context_save(&seat->pressed, &ctx); + interactive_set_grab_context(&ctx); } if (server->input_mode == LAB_INPUT_STATE_MENU) { @@ -1277,6 +1278,9 @@ cursor_finish_button_release(struct seat *seat, uint32_t button) /* Exit interactive move/resize mode */ interactive_finish(server->grabbed_view); return true; + } else if (server->grabbed_view) { + /* Button was released without starting move/resize */ + interactive_cancel(server->grabbed_view); } return false; diff --git a/src/interactive.c b/src/interactive.c index 88abdbd9..28f428e7 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -54,9 +54,34 @@ interactive_anchor_to_cursor(struct server *server, struct wlr_box *geo) geo->y = server->grab_box.y + (server->seat.cursor->y - server->grab_y); } +/* + * Called before interactive_begin() to set the initial grab parameters + * (cursor position and view geometry). Once the cursor actually moves, + * then interactive_begin() is called. + */ +void +interactive_set_grab_context(struct cursor_context *ctx) +{ + if (!ctx->view) { + return; + } + struct server *server = ctx->view->server; + if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { + return; + } + + server->grabbed_view = ctx->view; + server->grab_x = server->seat.cursor->x; + server->grab_y = server->seat.cursor->y; + server->grab_box = ctx->view->current; + server->resize_edges = + cursor_get_resize_edges(server->seat.cursor, ctx); +} + void interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) { + assert(view); /* * This function sets up an interactive move or resize operation, where * the compositor stops propagating pointer events to clients and @@ -65,7 +90,8 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) struct server *server = view->server; struct seat *seat = &server->seat; - if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { + if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH + || view != server->grabbed_view) { return; } @@ -108,21 +134,29 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) return; } + /* + * Override resize edges if specified explicitly. + * Otherwise, they were set already from cursor context. + */ + if (edges != LAB_EDGE_NONE) { + server->resize_edges = edges; + } + /* * If tiled or maximized in only one direction, reset * tiled state and un-maximize the relevant axes, but * keep the same geometry as the starting point. */ enum view_axis maximized = view->maximized; - if (edges & LAB_EDGES_LEFT_RIGHT) { + if (server->resize_edges & LAB_EDGES_LEFT_RIGHT) { maximized &= ~VIEW_AXIS_HORIZONTAL; } - if (edges & LAB_EDGES_TOP_BOTTOM) { + if (server->resize_edges & LAB_EDGES_TOP_BOTTOM) { maximized &= ~VIEW_AXIS_VERTICAL; } view_set_maximized(view, maximized); view_set_untiled(view); - cursor_shape = cursor_get_from_edge(edges); + cursor_shape = cursor_get_from_edge(server->resize_edges); break; } default: @@ -130,13 +164,6 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) return; } - server->grabbed_view = view; - /* Remember view and cursor positions at start of move/resize */ - server->grab_x = seat->cursor->x; - server->grab_y = seat->cursor->y; - server->grab_box = view->current; - server->resize_edges = edges; - seat_focus_override_begin(seat, mode, cursor_shape); /* @@ -293,6 +320,8 @@ snap_to_region(struct view *view) void interactive_finish(struct view *view) { + assert(view); + if (view->server->grabbed_view != view) { return; } @@ -314,16 +343,27 @@ interactive_finish(struct view *view) void interactive_cancel(struct view *view) { + assert(view); + if (view->server->grabbed_view != view) { return; } + view->server->grabbed_view = NULL; + + /* + * It's possible that grabbed_view was set but interactive_begin() + * wasn't called yet. In that case, we are done. + */ + if (view->server->input_mode != LAB_INPUT_STATE_MOVE + && view->server->input_mode != LAB_INPUT_STATE_RESIZE) { + return; + } + overlay_finish(&view->server->seat); resize_indicator_hide(view); - view->server->grabbed_view = NULL; - /* Restore keyboard/pointer focus */ seat_focus_override_end(&view->server->seat, /*restore_focus*/ true); } diff --git a/src/view.c b/src/view.c index 212b4581..6d48f55a 100644 --- a/src/view.c +++ b/src/view.c @@ -1433,14 +1433,11 @@ view_maximize(struct view *view, enum view_axis axis) bool store_natural_geometry = !in_interactive_move(view); view_set_shade(view, false); - if (axis != VIEW_AXIS_NONE) { - /* - * Maximize via keybind or client request cancels - * interactive move/resize since we can't move/resize - * a maximized view. - */ - interactive_cancel(view); - } + /* + * Maximize/unmaximize via keybind or client request cancels + * interactive move/resize. + */ + interactive_cancel(view); /* * Update natural geometry for any axis that wasn't already diff --git a/src/xdg.c b/src/xdg.c index 5277cd91..41ae36d0 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -454,11 +454,11 @@ handle_request_move(struct wl_listener *listener, void *data) * the provided serial against a list of button press serials sent to * this client, to prevent the client from requesting this whenever they * want. + * + * Note: interactive_begin() checks that view == server->grabbed_view. */ struct view *view = wl_container_of(listener, view, request_move); - if (view == view->server->seat.pressed.ctx.view) { - interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); - } + interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); } static void @@ -471,12 +471,12 @@ handle_request_resize(struct wl_listener *listener, void *data) * the provided serial against a list of button press serials sent to * this client, to prevent the client from requesting this whenever they * want. + * + * Note: interactive_begin() checks that view == server->grabbed_view. */ struct wlr_xdg_toplevel_resize_event *event = data; struct view *view = wl_container_of(listener, view, request_resize); - if (view == view->server->seat.pressed.ctx.view) { - interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); - } + interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); } static void diff --git a/src/xwayland.c b/src/xwayland.c index 3c848cdd..d8917826 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -288,11 +288,11 @@ handle_request_move(struct wl_listener *listener, void *data) * the provided serial against a list of button press serials sent to * this client, to prevent the client from requesting this whenever they * want. + * + * Note: interactive_begin() checks that view == server->grabbed_view. */ struct view *view = wl_container_of(listener, view, request_move); - if (view == view->server->seat.pressed.ctx.view) { - interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); - } + interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); } static void @@ -305,12 +305,12 @@ handle_request_resize(struct wl_listener *listener, void *data) * the provided serial against a list of button press serials sent to * this client, to prevent the client from requesting this whenever they * want. + * + * Note: interactive_begin() checks that view == server->grabbed_view. */ struct wlr_xwayland_resize_event *event = data; struct view *view = wl_container_of(listener, view, request_resize); - if (view == view->server->seat.pressed.ctx.view) { - interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); - } + interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); } static void