2021-09-24 21:45:48 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2021-12-28 15:06:33 +00:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2025-07-28 01:02:01 -04:00
|
|
|
#include "input/cursor.h"
|
2020-12-23 18:52:46 +00:00
|
|
|
#include <assert.h>
|
2021-08-02 17:30:34 +01:00
|
|
|
#include <time.h>
|
2025-04-25 14:21:30 +02:00
|
|
|
#include <wlr/backend/libinput.h>
|
2025-09-06 20:06:32 -04:00
|
|
|
#include <wlr/types/wlr_cursor.h>
|
2023-12-02 17:56:32 +03:00
|
|
|
#include <wlr/types/wlr_cursor_shape_v1.h>
|
2025-07-28 01:22:10 -04:00
|
|
|
#include <wlr/types/wlr_data_device.h>
|
2025-09-06 20:06:32 -04:00
|
|
|
#include <wlr/types/wlr_layer_shell_v1.h>
|
2025-07-28 01:22:10 -04:00
|
|
|
#include <wlr/types/wlr_pointer_constraints_v1.h>
|
2021-11-28 22:14:23 +00:00
|
|
|
#include <wlr/types/wlr_primary_selection.h>
|
2025-07-28 01:22:10 -04:00
|
|
|
#include <wlr/types/wlr_relative_pointer_v1.h>
|
|
|
|
|
#include <wlr/types/wlr_scene.h>
|
|
|
|
|
#include <wlr/types/wlr_subcompositor.h>
|
|
|
|
|
#include <wlr/types/wlr_xcursor_manager.h>
|
|
|
|
|
#include <wlr/types/wlr_xdg_shell.h>
|
2022-10-13 00:35:25 +03:00
|
|
|
#include <wlr/util/region.h>
|
2022-01-05 21:27:47 +00:00
|
|
|
#include "action.h"
|
2023-10-20 18:34:14 -04:00
|
|
|
#include "common/macros.h"
|
2022-09-16 18:41:02 -04:00
|
|
|
#include "common/mem.h"
|
|
|
|
|
#include "config/mousebind.h"
|
2025-08-17 16:01:50 -04:00
|
|
|
#include "config/rcxml.h"
|
2022-09-18 05:40:52 +02:00
|
|
|
#include "dnd.h"
|
2023-07-08 18:27:40 +02:00
|
|
|
#include "idle.h"
|
2023-09-03 18:56:20 +02:00
|
|
|
#include "input/gestures.h"
|
2025-01-10 11:02:58 +01:00
|
|
|
#include "input/keyboard.h"
|
2024-08-09 09:25:00 +02:00
|
|
|
#include "input/tablet.h"
|
2025-01-10 11:02:58 +01:00
|
|
|
#include "input/touch.h"
|
2020-05-29 21:36:12 +01:00
|
|
|
#include "labwc.h"
|
2024-03-09 10:48:52 +00:00
|
|
|
#include "layers.h"
|
2020-10-19 22:14:17 +01:00
|
|
|
#include "menu/menu.h"
|
2025-07-26 15:34:45 -04:00
|
|
|
#include "output.h"
|
2021-10-24 21:31:05 -04:00
|
|
|
#include "resistance.h"
|
2024-05-29 11:06:39 +09:00
|
|
|
#include "resize-outlines.h"
|
2021-03-21 20:54:55 +00:00
|
|
|
#include "ssd.h"
|
2022-11-21 10:10:39 -05:00
|
|
|
#include "view.h"
|
2024-06-10 00:01:54 +02:00
|
|
|
#include "xwayland.h"
|
2022-09-16 02:23:44 +02:00
|
|
|
|
2023-12-02 17:56:32 +03:00
|
|
|
#define LAB_CURSOR_SHAPE_V1_VERSION 1
|
|
|
|
|
|
2025-07-26 16:23:02 -04:00
|
|
|
struct constraint {
|
|
|
|
|
struct seat *seat;
|
|
|
|
|
struct wlr_pointer_constraint_v1 *constraint;
|
|
|
|
|
struct wl_listener destroy;
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-03 19:58:21 +00:00
|
|
|
static const char * const *cursor_names = NULL;
|
2022-09-16 02:23:44 +02:00
|
|
|
|
|
|
|
|
/* Usual cursor names */
|
2022-11-03 19:58:21 +00:00
|
|
|
static const char * const cursors_xdg[] = {
|
2022-09-16 02:23:44 +02:00
|
|
|
NULL,
|
|
|
|
|
"default",
|
|
|
|
|
"grab",
|
|
|
|
|
"nw-resize",
|
|
|
|
|
"n-resize",
|
|
|
|
|
"ne-resize",
|
|
|
|
|
"e-resize",
|
|
|
|
|
"se-resize",
|
|
|
|
|
"s-resize",
|
|
|
|
|
"sw-resize",
|
|
|
|
|
"w-resize"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* XCursor fallbacks */
|
2022-11-03 19:58:21 +00:00
|
|
|
static const char * const cursors_x11[] = {
|
2022-09-16 02:23:44 +02:00
|
|
|
NULL,
|
|
|
|
|
"left_ptr",
|
|
|
|
|
"grabbing",
|
|
|
|
|
"top_left_corner",
|
|
|
|
|
"top_side",
|
|
|
|
|
"top_right_corner",
|
|
|
|
|
"right_side",
|
|
|
|
|
"bottom_right_corner",
|
|
|
|
|
"bottom_side",
|
|
|
|
|
"bottom_left_corner",
|
|
|
|
|
"left_side"
|
2022-09-10 23:53:35 +02:00
|
|
|
};
|
|
|
|
|
|
2022-09-16 02:23:44 +02:00
|
|
|
static_assert(
|
|
|
|
|
ARRAY_SIZE(cursors_xdg) == LAB_CURSOR_COUNT,
|
2022-11-09 20:27:47 +00:00
|
|
|
"XDG cursor names are out of sync");
|
2022-09-16 02:23:44 +02:00
|
|
|
static_assert(
|
|
|
|
|
ARRAY_SIZE(cursors_x11) == LAB_CURSOR_COUNT,
|
2022-11-09 20:27:47 +00:00
|
|
|
"X11 cursor names are out of sync");
|
2022-09-16 02:23:44 +02:00
|
|
|
|
|
|
|
|
enum lab_cursors
|
2025-08-26 20:27:34 -04:00
|
|
|
cursor_get_from_edge(enum lab_edge resize_edges)
|
2022-09-16 02:23:44 +02:00
|
|
|
{
|
|
|
|
|
switch (resize_edges) {
|
2025-08-26 20:27:34 -04:00
|
|
|
case LAB_EDGES_TOP_LEFT:
|
2022-09-16 02:23:44 +02:00
|
|
|
return LAB_CURSOR_RESIZE_NW;
|
2025-08-26 20:27:34 -04:00
|
|
|
case LAB_EDGE_TOP:
|
2022-09-16 02:23:44 +02:00
|
|
|
return LAB_CURSOR_RESIZE_N;
|
2025-08-26 20:27:34 -04:00
|
|
|
case LAB_EDGES_TOP_RIGHT:
|
2022-09-16 02:23:44 +02:00
|
|
|
return LAB_CURSOR_RESIZE_NE;
|
2025-08-26 20:27:34 -04:00
|
|
|
case LAB_EDGE_RIGHT:
|
2022-09-16 02:23:44 +02:00
|
|
|
return LAB_CURSOR_RESIZE_E;
|
2025-08-26 20:27:34 -04:00
|
|
|
case LAB_EDGES_BOTTOM_RIGHT:
|
2022-09-16 02:23:44 +02:00
|
|
|
return LAB_CURSOR_RESIZE_SE;
|
2025-08-26 20:27:34 -04:00
|
|
|
case LAB_EDGE_BOTTOM:
|
2022-09-16 02:23:44 +02:00
|
|
|
return LAB_CURSOR_RESIZE_S;
|
2025-08-26 20:27:34 -04:00
|
|
|
case LAB_EDGES_BOTTOM_LEFT:
|
2022-09-16 02:23:44 +02:00
|
|
|
return LAB_CURSOR_RESIZE_SW;
|
2025-08-26 20:27:34 -04:00
|
|
|
case LAB_EDGE_LEFT:
|
2022-09-16 02:23:44 +02:00
|
|
|
return LAB_CURSOR_RESIZE_W;
|
|
|
|
|
default:
|
2025-08-26 20:27:34 -04:00
|
|
|
return LAB_CURSOR_DEFAULT;
|
2022-09-16 02:23:44 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum lab_cursors
|
2025-09-03 05:08:52 -04:00
|
|
|
cursor_get_from_ssd(enum lab_node_type view_area)
|
2022-09-16 02:23:44 +02:00
|
|
|
{
|
2025-09-03 05:08:52 -04:00
|
|
|
enum lab_edge resize_edges = node_type_to_edges(view_area);
|
2022-09-16 02:23:44 +02:00
|
|
|
return cursor_get_from_edge(resize_edges);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-10 01:57:39 -04:00
|
|
|
static struct wlr_surface *
|
|
|
|
|
get_toplevel(struct wlr_surface *surface)
|
|
|
|
|
{
|
2023-02-03 14:53:26 -05:00
|
|
|
while (surface) {
|
2022-09-10 01:57:39 -04:00
|
|
|
struct wlr_xdg_surface *xdg_surface =
|
2023-02-03 14:53:26 -05:00
|
|
|
wlr_xdg_surface_try_from_wlr_surface(surface);
|
2022-09-10 01:57:39 -04:00
|
|
|
if (!xdg_surface) {
|
2023-02-03 14:53:26 -05:00
|
|
|
break;
|
2022-09-10 01:57:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (xdg_surface->role) {
|
|
|
|
|
case WLR_XDG_SURFACE_ROLE_NONE:
|
|
|
|
|
return NULL;
|
|
|
|
|
case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
|
|
|
|
|
return surface;
|
|
|
|
|
case WLR_XDG_SURFACE_ROLE_POPUP:
|
|
|
|
|
surface = xdg_surface->popup->parent;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-02-03 14:53:26 -05:00
|
|
|
if (surface && wlr_layer_surface_v1_try_from_wlr_surface(surface)) {
|
2022-09-10 01:57:39 -04:00
|
|
|
return surface;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-02 21:19:56 +01:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_request_set_cursor(struct wl_listener *listener, void *data)
|
2020-10-02 21:19:56 +01:00
|
|
|
{
|
2025-04-15 21:43:00 +02:00
|
|
|
struct seat *seat = wl_container_of(listener, seat, request_set_cursor);
|
2022-09-10 19:19:02 +02:00
|
|
|
|
|
|
|
|
if (seat->server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
|
|
|
|
|
/* Prevent setting a cursor image when moving or resizing */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 15:59:33 +01:00
|
|
|
/*
|
|
|
|
|
* Omit cursor notifications when the current cursor is
|
|
|
|
|
* invisible, e.g. on touch input.
|
|
|
|
|
*/
|
|
|
|
|
if (!seat->cursor_visible) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-28 20:50:05 +02:00
|
|
|
/*
|
|
|
|
|
* Omit cursor notifications from a pointer when a tablet
|
|
|
|
|
* tool (stylus/pen) is in proximity. We expect to get cursor
|
|
|
|
|
* notifications from the tablet tool instead.
|
|
|
|
|
* Receiving cursor notifications from pointer and tablet tool at
|
|
|
|
|
* the same time is a side effect of also setting pointer focus
|
|
|
|
|
* when a tablet tool enters proximity on a tablet-capable surface.
|
|
|
|
|
* See also `notify_motion()` in `input/tablet.c`.
|
|
|
|
|
*/
|
2024-06-04 20:11:32 +02:00
|
|
|
if (tablet_tool_has_focused_surface(seat)) {
|
|
|
|
|
return;
|
2024-05-28 20:50:05 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-02 21:19:56 +01:00
|
|
|
/*
|
2022-02-19 02:05:38 +01:00
|
|
|
* This event is raised by the seat when a client provides a cursor
|
2020-10-02 21:19:56 +01:00
|
|
|
* image
|
|
|
|
|
*/
|
|
|
|
|
struct wlr_seat_pointer_request_set_cursor_event *event = data;
|
|
|
|
|
struct wlr_seat_client *focused_client =
|
|
|
|
|
seat->seat->pointer_state.focused_client;
|
|
|
|
|
|
2021-09-21 22:05:56 +01:00
|
|
|
/*
|
2023-12-05 11:56:10 +03:00
|
|
|
* This can be sent by any client, so we check to make sure this one
|
2021-09-21 22:05:56 +01:00
|
|
|
* actually has pointer focus first.
|
|
|
|
|
*/
|
2020-10-02 21:19:56 +01:00
|
|
|
if (focused_client == event->seat_client) {
|
2021-09-21 22:05:56 +01:00
|
|
|
/*
|
|
|
|
|
* Once we've vetted the client, we can tell the cursor to use
|
2020-10-02 21:19:56 +01:00
|
|
|
* the provided surface as the cursor image. It will set the
|
|
|
|
|
* hardware cursor on the output that it's currently on and
|
2021-09-21 22:05:56 +01:00
|
|
|
* continue to do so as the cursor moves between outputs.
|
|
|
|
|
*/
|
2022-09-10 19:48:49 +02:00
|
|
|
|
2020-10-02 21:19:56 +01:00
|
|
|
wlr_cursor_set_surface(seat->cursor, event->surface,
|
2022-09-10 19:48:49 +02:00
|
|
|
event->hotspot_x, event->hotspot_y);
|
2020-10-02 21:19:56 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-02 17:56:32 +03:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_request_set_shape(struct wl_listener *listener, void *data)
|
2023-12-02 17:56:32 +03:00
|
|
|
{
|
|
|
|
|
struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data;
|
|
|
|
|
const char *shape_name = wlr_cursor_shape_v1_name(event->shape);
|
|
|
|
|
struct seat *seat = wl_container_of(listener, seat, request_set_shape);
|
|
|
|
|
struct wlr_seat_client *focused_client = seat->seat->pointer_state.focused_client;
|
|
|
|
|
|
|
|
|
|
/* Prevent setting a cursor image when moving or resizing */
|
|
|
|
|
if (seat->server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 15:59:33 +01:00
|
|
|
/*
|
|
|
|
|
* Omit set shape when the current cursor is
|
|
|
|
|
* invisible, e.g. on touch input.
|
|
|
|
|
*/
|
|
|
|
|
if (!seat->cursor_visible) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-02 17:56:32 +03:00
|
|
|
/*
|
|
|
|
|
* This can be sent by any client, so we check to make sure this one
|
|
|
|
|
* actually has pointer focus first.
|
|
|
|
|
*/
|
|
|
|
|
if (event->seat_client != focused_client) {
|
|
|
|
|
wlr_log(WLR_INFO, "seat client %p != focused client %p",
|
|
|
|
|
event->seat_client, focused_client);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-08 11:38:25 +02:00
|
|
|
/*
|
|
|
|
|
* Omit cursor notifications from a pointer when a tablet
|
|
|
|
|
* tool (stylus/pen) is in proximity.
|
|
|
|
|
*/
|
|
|
|
|
if (tablet_tool_has_focused_surface(seat)
|
|
|
|
|
&& event->device_type
|
|
|
|
|
!= WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_TABLET_TOOL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-02 17:56:32 +03:00
|
|
|
wlr_log(WLR_DEBUG, "set xcursor to shape %s", shape_name);
|
|
|
|
|
wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, shape_name);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-02 21:19:56 +01:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_request_set_selection(struct wl_listener *listener, void *data)
|
2020-10-02 21:19:56 +01:00
|
|
|
{
|
|
|
|
|
struct seat *seat = wl_container_of(
|
|
|
|
|
listener, seat, request_set_selection);
|
|
|
|
|
struct wlr_seat_request_set_selection_event *event = data;
|
|
|
|
|
wlr_seat_set_selection(seat->seat, event->source,
|
|
|
|
|
event->serial);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 23:41:07 +01:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_request_set_primary_selection(struct wl_listener *listener, void *data)
|
2021-08-18 23:41:07 +01:00
|
|
|
{
|
|
|
|
|
struct seat *seat = wl_container_of(
|
|
|
|
|
listener, seat, request_set_primary_selection);
|
|
|
|
|
struct wlr_seat_request_set_primary_selection_event *event = data;
|
|
|
|
|
wlr_seat_set_primary_selection(seat->seat, event->source,
|
|
|
|
|
event->serial);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
process_cursor_move(struct server *server, uint32_t time)
|
2020-05-29 21:36:12 +01:00
|
|
|
{
|
2024-07-20 16:58:39 +09:00
|
|
|
struct view *view = server->grabbed_view;
|
|
|
|
|
|
2024-08-07 09:17:25 +09:00
|
|
|
int x = server->grab_box.x + (server->seat.cursor->x - server->grab_x);
|
|
|
|
|
int y = server->grab_box.y + (server->seat.cursor->y - server->grab_y);
|
2024-07-20 16:58:39 +09:00
|
|
|
|
2024-08-07 09:17:25 +09:00
|
|
|
/* Apply resistance for maximized/tiled view */
|
|
|
|
|
bool needs_untile = resistance_unsnap_apply(view, &x, &y);
|
|
|
|
|
if (needs_untile) {
|
|
|
|
|
/*
|
|
|
|
|
* When the view needs to be un-tiled, resize it to natural
|
|
|
|
|
* geometry while anchoring it to cursor. If the natural
|
|
|
|
|
* geometry is unknown (possible with xdg-shell views), then
|
|
|
|
|
* we set a size of 0x0 here and determine the correct geometry
|
|
|
|
|
* later. See do_late_positioning() in xdg.c.
|
|
|
|
|
*/
|
|
|
|
|
struct wlr_box new_geo = {
|
|
|
|
|
.width = view->natural_geometry.width,
|
|
|
|
|
.height = view->natural_geometry.height,
|
|
|
|
|
};
|
|
|
|
|
interactive_anchor_to_cursor(server, &new_geo);
|
2024-08-27 10:10:53 -04:00
|
|
|
/* Shaded clients will not process resize events until unshaded */
|
|
|
|
|
view_set_shade(view, false);
|
2025-09-01 11:49:24 -04:00
|
|
|
view_set_maximized(view, VIEW_AXIS_NONE);
|
2024-08-07 09:17:25 +09:00
|
|
|
view_set_untiled(view);
|
2025-09-01 11:49:24 -04:00
|
|
|
view_move_resize(view, new_geo);
|
2024-08-07 09:17:25 +09:00
|
|
|
x = new_geo.x;
|
|
|
|
|
y = new_geo.y;
|
|
|
|
|
}
|
2021-07-23 21:15:55 +01:00
|
|
|
|
2024-08-07 09:17:25 +09:00
|
|
|
/* Then apply window & edge resistance */
|
|
|
|
|
resistance_move_apply(view, &x, &y);
|
2022-07-06 07:19:28 +02:00
|
|
|
|
2024-08-07 09:17:25 +09:00
|
|
|
view_move(view, x, y);
|
2024-04-05 11:35:31 +09:00
|
|
|
overlay_update(&server->seat);
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
process_cursor_resize(struct server *server, uint32_t time)
|
2020-05-29 21:36:12 +01:00
|
|
|
{
|
2024-05-31 15:10:39 +09:00
|
|
|
/* Rate-limit resize events respecting monitor refresh rate */
|
|
|
|
|
static uint32_t last_resize_time = 0;
|
|
|
|
|
static struct view *last_resize_view = NULL;
|
|
|
|
|
|
2025-07-28 13:54:10 +09:00
|
|
|
assert(server->grabbed_view);
|
2024-05-31 15:10:39 +09:00
|
|
|
if (server->grabbed_view == last_resize_view) {
|
|
|
|
|
int32_t refresh = 0;
|
|
|
|
|
if (output_is_usable(last_resize_view->output)) {
|
|
|
|
|
refresh = last_resize_view->output->wlr_output->refresh;
|
|
|
|
|
}
|
|
|
|
|
/* Limit to 250Hz if refresh rate is not available */
|
|
|
|
|
if (refresh <= 0) {
|
|
|
|
|
refresh = 250000;
|
|
|
|
|
}
|
|
|
|
|
/* Not caring overflow, but it won't be observable */
|
|
|
|
|
if (time - last_resize_time < 1000000 / (uint32_t)refresh) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
last_resize_time = time;
|
|
|
|
|
last_resize_view = server->grabbed_view;
|
|
|
|
|
|
2020-10-02 21:19:56 +01:00
|
|
|
double dx = server->seat.cursor->x - server->grab_x;
|
|
|
|
|
double dy = server->seat.cursor->y - server->grab_y;
|
2020-05-29 21:36:12 +01:00
|
|
|
|
|
|
|
|
struct view *view = server->grabbed_view;
|
2023-02-08 23:19:14 -05:00
|
|
|
struct wlr_box new_view_geo = view->current;
|
2020-05-29 21:36:12 +01:00
|
|
|
|
2025-08-26 20:27:34 -04:00
|
|
|
if (server->resize_edges & LAB_EDGE_TOP) {
|
2024-01-20 21:59:46 -05:00
|
|
|
/* Shift y to anchor bottom edge when resizing top */
|
|
|
|
|
new_view_geo.y = server->grab_box.y + dy;
|
2020-05-29 21:36:12 +01:00
|
|
|
new_view_geo.height = server->grab_box.height - dy;
|
2025-08-26 20:27:34 -04:00
|
|
|
} else if (server->resize_edges & LAB_EDGE_BOTTOM) {
|
2020-05-29 21:36:12 +01:00
|
|
|
new_view_geo.height = server->grab_box.height + dy;
|
2022-07-17 23:38:11 -04:00
|
|
|
}
|
2022-04-02 21:34:51 -04:00
|
|
|
|
2025-08-26 20:27:34 -04:00
|
|
|
if (server->resize_edges & LAB_EDGE_LEFT) {
|
2024-01-20 21:59:46 -05:00
|
|
|
/* Shift x to anchor right edge when resizing left */
|
|
|
|
|
new_view_geo.x = server->grab_box.x + dx;
|
2020-05-29 21:36:12 +01:00
|
|
|
new_view_geo.width = server->grab_box.width - dx;
|
2025-08-26 20:27:34 -04:00
|
|
|
} else if (server->resize_edges & LAB_EDGE_RIGHT) {
|
2020-05-29 21:36:12 +01:00
|
|
|
new_view_geo.width = server->grab_box.width + dx;
|
2022-07-17 23:38:11 -04:00
|
|
|
}
|
2022-04-02 21:34:51 -04:00
|
|
|
|
2022-07-17 23:38:11 -04:00
|
|
|
resistance_resize_apply(view, &new_view_geo);
|
2022-04-02 21:34:51 -04:00
|
|
|
view_adjust_size(view, &new_view_geo.width, &new_view_geo.height);
|
|
|
|
|
|
2025-08-26 20:27:34 -04:00
|
|
|
if (server->resize_edges & LAB_EDGE_TOP) {
|
2024-01-20 21:59:46 -05:00
|
|
|
/* After size adjustments, make sure to anchor bottom edge */
|
2022-04-02 21:34:51 -04:00
|
|
|
new_view_geo.y = server->grab_box.y +
|
|
|
|
|
server->grab_box.height - new_view_geo.height;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 20:27:34 -04:00
|
|
|
if (server->resize_edges & LAB_EDGE_LEFT) {
|
2024-01-20 21:59:46 -05:00
|
|
|
/* After size adjustments, make sure to anchor bottom right */
|
2022-04-02 21:34:51 -04:00
|
|
|
new_view_geo.x = server->grab_box.x +
|
|
|
|
|
server->grab_box.width - new_view_geo.width;
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-29 11:06:39 +09:00
|
|
|
if (rc.resize_draw_contents) {
|
|
|
|
|
view_move_resize(view, new_view_geo);
|
|
|
|
|
} else {
|
|
|
|
|
resize_outlines_update(view, new_view_geo);
|
|
|
|
|
}
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-28 21:47:24 +00:00
|
|
|
void
|
2022-09-16 02:23:44 +02:00
|
|
|
cursor_set(struct seat *seat, enum lab_cursors cursor)
|
2021-07-30 14:26:54 +01:00
|
|
|
{
|
2022-09-16 02:23:44 +02:00
|
|
|
assert(cursor > LAB_CURSOR_CLIENT && cursor < LAB_CURSOR_COUNT);
|
2022-09-10 23:53:35 +02:00
|
|
|
|
|
|
|
|
/* Prevent setting the same cursor image twice */
|
2022-09-16 02:23:44 +02:00
|
|
|
if (seat->server_cursor == cursor) {
|
2022-09-12 13:14:18 -04:00
|
|
|
return;
|
2022-09-10 19:48:49 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-02 15:59:33 +01:00
|
|
|
if (seat->cursor_visible) {
|
|
|
|
|
wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager,
|
|
|
|
|
cursor_names[cursor]);
|
|
|
|
|
}
|
2022-09-16 02:23:44 +02:00
|
|
|
seat->server_cursor = cursor;
|
2021-07-30 14:26:54 +01:00
|
|
|
}
|
|
|
|
|
|
2024-11-02 15:59:33 +01:00
|
|
|
void
|
|
|
|
|
cursor_set_visible(struct seat *seat, bool visible)
|
|
|
|
|
{
|
|
|
|
|
if (seat->cursor_visible == visible) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seat->cursor_visible = visible;
|
|
|
|
|
cursor_update_image(seat);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-26 01:21:43 +01:00
|
|
|
void
|
|
|
|
|
cursor_update_image(struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
enum lab_cursors cursor = seat->server_cursor;
|
2024-11-02 15:59:33 +01:00
|
|
|
|
|
|
|
|
if (!seat->cursor_visible) {
|
|
|
|
|
wlr_cursor_unset_image(seat->cursor);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-26 01:21:43 +01:00
|
|
|
if (cursor == LAB_CURSOR_CLIENT) {
|
2023-03-28 00:58:42 +02:00
|
|
|
/*
|
|
|
|
|
* When we loose the output cursor while over a client
|
|
|
|
|
* surface (e.g. output was destroyed and we now deal with
|
|
|
|
|
* a new output instance), we have to force a re-enter of
|
|
|
|
|
* the surface so the client sets its own cursor again.
|
|
|
|
|
*/
|
|
|
|
|
if (seat->seat->pointer_state.focused_surface) {
|
|
|
|
|
seat->server_cursor = LAB_CURSOR_DEFAULT;
|
2024-05-03 14:18:43 +02:00
|
|
|
wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, "");
|
2025-01-06 23:49:08 +09:00
|
|
|
wlr_seat_pointer_clear_focus(seat->seat);
|
2023-03-28 00:58:42 +02:00
|
|
|
cursor_update_focus(seat->server);
|
|
|
|
|
}
|
2023-01-26 01:21:43 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2023-07-17 02:01:02 -04:00
|
|
|
/*
|
|
|
|
|
* Call wlr_cursor_unset_image() first to force wlroots to
|
|
|
|
|
* update the cursor (e.g. for a new output). Otherwise,
|
|
|
|
|
* wlr_cursor_set_xcursor() may detect that we are setting the
|
|
|
|
|
* same cursor as before, and do nothing.
|
|
|
|
|
*/
|
|
|
|
|
wlr_cursor_unset_image(seat->cursor);
|
|
|
|
|
wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager,
|
|
|
|
|
cursor_names[cursor]);
|
2023-01-26 01:21:43 +01:00
|
|
|
}
|
|
|
|
|
|
2022-09-10 01:57:39 -04:00
|
|
|
static bool
|
2022-09-12 04:54:00 -04:00
|
|
|
update_pressed_surface(struct seat *seat, struct cursor_context *ctx)
|
2022-09-10 01:57:39 -04:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* In most cases, we don't want to leave one surface and enter
|
2022-11-15 14:20:05 -05:00
|
|
|
* another while a button is pressed. We only do so when
|
|
|
|
|
* (1) there is a pointer grab active (e.g. XDG popup grab) and
|
|
|
|
|
* (2) both surfaces belong to the same XDG toplevel.
|
|
|
|
|
*
|
2022-11-26 21:35:51 +00:00
|
|
|
* GTK/Wayland menus are known to use an XDG popup grab and to
|
2022-11-15 14:20:05 -05:00
|
|
|
* rely on the leave/enter events to work properly. Firefox
|
|
|
|
|
* context menus (in contrast) do not use an XDG popup grab and
|
|
|
|
|
* do not work properly if we send leave/enter events.
|
2022-09-10 01:57:39 -04:00
|
|
|
*/
|
2023-08-28 16:43:51 +02:00
|
|
|
if (!wlr_seat_pointer_has_grab(seat->seat)) {
|
2022-11-15 14:20:05 -05:00
|
|
|
return false;
|
|
|
|
|
}
|
2022-09-12 04:54:00 -04:00
|
|
|
if (seat->pressed.surface && ctx->surface != seat->pressed.surface) {
|
|
|
|
|
struct wlr_surface *toplevel = get_toplevel(ctx->surface);
|
2024-09-21 01:11:27 +09:00
|
|
|
if (toplevel && toplevel == get_toplevel(seat->pressed.surface)) {
|
|
|
|
|
seat_set_pressed(seat, ctx);
|
2022-09-10 01:57:39 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
static bool
|
|
|
|
|
process_cursor_motion_out_of_surface(struct server *server,
|
|
|
|
|
double *sx, double *sy)
|
2022-09-01 17:50:28 -04:00
|
|
|
{
|
|
|
|
|
struct view *view = server->seat.pressed.view;
|
|
|
|
|
struct wlr_scene_node *node = server->seat.pressed.node;
|
|
|
|
|
struct wlr_surface *surface = server->seat.pressed.surface;
|
|
|
|
|
assert(surface);
|
|
|
|
|
int lx, ly;
|
|
|
|
|
|
2025-01-25 21:45:54 +01:00
|
|
|
if (node && wlr_subsurface_try_from_wlr_surface(surface)) {
|
|
|
|
|
wlr_scene_node_coords(node, &lx, &ly);
|
|
|
|
|
} else if (view) {
|
2023-02-08 23:19:14 -05:00
|
|
|
lx = view->current.x;
|
|
|
|
|
ly = view->current.y;
|
2025-01-25 21:45:54 +01:00
|
|
|
/* Take into account invisible xdg-shell CSD borders */
|
|
|
|
|
if (view->type == LAB_XDG_SHELL_VIEW) {
|
2024-11-27 04:14:08 +01:00
|
|
|
struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view);
|
|
|
|
|
lx -= xdg_surface->geometry.x;
|
|
|
|
|
ly -= xdg_surface->geometry.y;
|
2025-01-25 21:45:54 +01:00
|
|
|
}
|
2023-02-03 14:53:26 -05:00
|
|
|
} else if (node && wlr_layer_surface_v1_try_from_wlr_surface(surface)) {
|
2022-09-01 17:50:28 -04:00
|
|
|
wlr_scene_node_coords(node, &lx, &ly);
|
|
|
|
|
#if HAVE_XWAYLAND
|
|
|
|
|
} else if (node && node->parent == server->unmanaged_tree) {
|
|
|
|
|
wlr_scene_node_coords(node, &lx, &ly);
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
|
|
|
|
wlr_log(WLR_ERROR, "Can't detect surface for out-of-surface movement");
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2022-09-01 17:50:28 -04:00
|
|
|
}
|
|
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
*sx = server->seat.cursor->x - lx;
|
|
|
|
|
*sy = server->seat.cursor->y - ly;
|
2022-09-01 17:50:28 -04:00
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
return true;
|
2022-09-01 17:50:28 -04:00
|
|
|
}
|
|
|
|
|
|
2022-09-12 13:14:18 -04:00
|
|
|
/*
|
2024-04-20 11:33:04 +02:00
|
|
|
* Common logic shared by cursor_update_focus(), process_cursor_motion()
|
|
|
|
|
* and cursor_axis()
|
2022-09-12 13:14:18 -04:00
|
|
|
*/
|
2024-04-20 11:33:04 +02:00
|
|
|
static bool
|
2022-09-12 04:54:00 -04:00
|
|
|
cursor_update_common(struct server *server, struct cursor_context *ctx,
|
2025-04-16 18:25:43 +02:00
|
|
|
bool cursor_has_moved, double *sx, double *sy)
|
2020-05-29 21:36:12 +01:00
|
|
|
{
|
2022-09-12 13:14:18 -04:00
|
|
|
struct seat *seat = &server->seat;
|
|
|
|
|
struct wlr_seat *wlr_seat = seat->seat;
|
2021-02-27 19:26:13 +00:00
|
|
|
|
2025-09-03 01:20:53 -04:00
|
|
|
ssd_update_hovered_button(server, ctx->node);
|
2022-09-12 13:14:18 -04:00
|
|
|
|
|
|
|
|
if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
|
|
|
|
|
/*
|
|
|
|
|
* Prevent updating focus/cursor image during
|
Clear keyboard/pointer focus on Move/Resize, window switcher and menu
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.
2024-12-29 00:48:55 +09:00
|
|
|
* interactive move/resize, window switcher and
|
|
|
|
|
* menu interaction.
|
2022-09-12 13:14:18 -04:00
|
|
|
*/
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2022-09-12 13:14:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* TODO: verify drag_icon logic */
|
2022-09-12 04:54:00 -04:00
|
|
|
if (seat->pressed.surface && ctx->surface != seat->pressed.surface
|
|
|
|
|
&& !update_pressed_surface(seat, ctx)
|
2022-09-18 05:40:52 +02:00
|
|
|
&& !seat->drag.active) {
|
2022-09-12 13:14:18 -04:00
|
|
|
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.
|
|
|
|
|
*/
|
2024-04-20 11:33:04 +02:00
|
|
|
return process_cursor_motion_out_of_surface(server, sx, sy);
|
2022-09-12 13:14:18 -04:00
|
|
|
}
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2022-09-12 13:14:18 -04:00
|
|
|
}
|
|
|
|
|
|
2024-03-19 00:32:47 +01:00
|
|
|
if (ctx->surface) {
|
2022-09-12 13:14:18 -04:00
|
|
|
/*
|
|
|
|
|
* Cursor is over an input-enabled client surface. The
|
|
|
|
|
* cursor image will be set by request_cursor_notify()
|
|
|
|
|
* in response to the enter event.
|
|
|
|
|
*/
|
Clear keyboard/pointer focus on Move/Resize, window switcher and menu
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.
2024-12-29 00:48:55 +09:00
|
|
|
wlr_seat_pointer_notify_enter(wlr_seat, ctx->surface,
|
|
|
|
|
ctx->sx, ctx->sy);
|
|
|
|
|
seat->server_cursor = LAB_CURSOR_CLIENT;
|
2022-09-12 13:14:18 -04:00
|
|
|
if (cursor_has_moved) {
|
2024-04-20 11:33:04 +02:00
|
|
|
*sx = ctx->sx;
|
|
|
|
|
*sy = ctx->sy;
|
|
|
|
|
return true;
|
2022-09-12 13:14:18 -04:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/*
|
|
|
|
|
* Cursor is over a server (labwc) surface. Clear focus
|
|
|
|
|
* from the focused client (if any, no-op otherwise) and
|
2022-09-21 07:42:34 +02:00
|
|
|
* set the cursor image ourselves when not currently in
|
|
|
|
|
* a drag operation.
|
2022-09-12 13:14:18 -04:00
|
|
|
*/
|
|
|
|
|
wlr_seat_pointer_notify_clear_focus(wlr_seat);
|
2022-09-18 05:40:52 +02:00
|
|
|
if (!seat->drag.active) {
|
2023-08-08 03:39:35 +02:00
|
|
|
enum lab_cursors cursor = cursor_get_from_ssd(ctx->type);
|
|
|
|
|
if (ctx->view && ctx->view->shaded && cursor > LAB_CURSOR_GRAB) {
|
|
|
|
|
/* Prevent resize cursor on borders for shaded SSD */
|
|
|
|
|
cursor = LAB_CURSOR_DEFAULT;
|
|
|
|
|
}
|
|
|
|
|
cursor_set(seat, cursor);
|
2022-09-21 07:42:34 +02:00
|
|
|
}
|
2022-09-12 13:14:18 -04:00
|
|
|
}
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2022-09-12 13:14:18 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-26 20:27:34 -04:00
|
|
|
enum lab_edge
|
2022-09-15 08:31:37 -04:00
|
|
|
cursor_get_resize_edges(struct wlr_cursor *cursor, struct cursor_context *ctx)
|
2022-09-12 04:54:00 -04:00
|
|
|
{
|
2025-09-03 05:08:52 -04:00
|
|
|
enum lab_edge resize_edges = node_type_to_edges(ctx->type);
|
2022-09-12 04:54:00 -04:00
|
|
|
if (ctx->view && !resize_edges) {
|
2023-02-08 23:19:14 -05:00
|
|
|
struct wlr_box box = ctx->view->current;
|
2022-09-12 04:54:00 -04:00
|
|
|
resize_edges |=
|
2023-02-08 23:19:14 -05:00
|
|
|
(int)cursor->x < box.x + box.width / 2 ?
|
2025-08-26 20:27:34 -04:00
|
|
|
LAB_EDGE_LEFT : LAB_EDGE_RIGHT;
|
2022-09-12 04:54:00 -04:00
|
|
|
resize_edges |=
|
2023-02-08 23:19:14 -05:00
|
|
|
(int)cursor->y < box.y + box.height / 2 ?
|
2025-08-26 20:27:34 -04:00
|
|
|
LAB_EDGE_TOP : LAB_EDGE_BOTTOM;
|
2022-09-12 04:54:00 -04:00
|
|
|
}
|
|
|
|
|
return resize_edges;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
bool
|
|
|
|
|
cursor_process_motion(struct server *server, uint32_t time, double *sx, double *sy)
|
2022-09-12 13:14:18 -04:00
|
|
|
{
|
2020-05-29 21:36:12 +01:00
|
|
|
/* If the mode is non-passthrough, delegate to those functions. */
|
2020-10-21 20:30:06 +01:00
|
|
|
if (server->input_mode == LAB_INPUT_STATE_MOVE) {
|
2020-05-29 21:36:12 +01:00
|
|
|
process_cursor_move(server, time);
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2020-10-21 20:30:06 +01:00
|
|
|
} else if (server->input_mode == LAB_INPUT_STATE_RESIZE) {
|
2020-05-29 21:36:12 +01:00
|
|
|
process_cursor_resize(server, time);
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-25 10:04:37 +01:00
|
|
|
/* Otherwise, find view under the pointer and send the event along */
|
2022-09-12 04:54:00 -04:00
|
|
|
struct cursor_context ctx = get_cursor_context(server);
|
|
|
|
|
struct seat *seat = &server->seat;
|
2022-02-18 00:07:37 +01:00
|
|
|
|
2025-09-03 05:32:44 -04:00
|
|
|
if (ctx.type == LAB_NODE_MENUITEM) {
|
2022-09-12 04:54:00 -04:00
|
|
|
menu_process_cursor_motion(ctx.node);
|
2022-09-16 02:23:44 +02:00
|
|
|
cursor_set(&server->seat, LAB_CURSOR_DEFAULT);
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2022-02-19 02:05:38 +01:00
|
|
|
}
|
2021-07-30 14:26:54 +01:00
|
|
|
|
2022-09-18 05:40:52 +02:00
|
|
|
if (seat->drag.active) {
|
|
|
|
|
dnd_icons_move(seat, seat->cursor->x, seat->cursor->y);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 18:30:17 -05:00
|
|
|
struct mousebind *mousebind;
|
|
|
|
|
wl_list_for_each(mousebind, &rc.mousebinds, link) {
|
2025-09-03 05:08:52 -04:00
|
|
|
if (ctx.type == LAB_NODE_CLIENT
|
2025-07-24 11:08:03 +09:00
|
|
|
&& view_inhibits_actions(ctx.view, &mousebind->actions)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-01-06 18:30:17 -05:00
|
|
|
if (mousebind->mouse_event == MOUSE_ACTION_DRAG
|
|
|
|
|
&& mousebind->pressed_in_context) {
|
2022-09-14 23:09:36 -04:00
|
|
|
/*
|
|
|
|
|
* Use view and resize edges from the press
|
|
|
|
|
* event (not the motion event) to prevent
|
|
|
|
|
* moving/resizing the wrong view
|
|
|
|
|
*/
|
2022-01-06 18:30:17 -05:00
|
|
|
mousebind->pressed_in_context = false;
|
2024-09-21 01:11:27 +09:00
|
|
|
actions_run(seat->pressed.view, server,
|
|
|
|
|
&mousebind->actions, &seat->pressed);
|
2022-01-06 18:30:17 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-01 21:32:31 +09:00
|
|
|
struct wlr_surface *old_focused_surface =
|
|
|
|
|
seat->seat->pointer_state.focused_surface;
|
|
|
|
|
|
2025-04-16 18:25:43 +02:00
|
|
|
bool notify = cursor_update_common(server, &ctx,
|
2024-04-20 11:33:04 +02:00
|
|
|
/* cursor_has_moved */ true, sx, sy);
|
2025-04-01 21:32:31 +09:00
|
|
|
|
|
|
|
|
struct wlr_surface *new_focused_surface =
|
|
|
|
|
seat->seat->pointer_state.focused_surface;
|
|
|
|
|
|
|
|
|
|
if (rc.focus_follow_mouse && new_focused_surface
|
|
|
|
|
&& old_focused_surface != new_focused_surface) {
|
|
|
|
|
/*
|
|
|
|
|
* If followMouse=yes, update the keyboard focus when the
|
|
|
|
|
* cursor enters a surface
|
|
|
|
|
*/
|
|
|
|
|
desktop_focus_view_or_surface(seat,
|
|
|
|
|
view_from_wlr_surface(new_focused_surface),
|
|
|
|
|
new_focused_surface, rc.raise_on_focus);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return notify;
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2023-04-01 14:06:52 -04:00
|
|
|
static void
|
|
|
|
|
_cursor_update_focus(struct server *server)
|
2021-12-26 22:09:41 +00:00
|
|
|
{
|
2022-02-27 20:36:49 +01:00
|
|
|
/* Focus surface under cursor if it isn't already focused */
|
2022-09-12 04:54:00 -04:00
|
|
|
struct cursor_context ctx = get_cursor_context(server);
|
2023-03-23 21:39:49 +00:00
|
|
|
|
2023-11-01 23:01:19 -04:00
|
|
|
if ((ctx.view || ctx.surface) && rc.focus_follow_mouse
|
2025-02-24 21:40:46 +09:00
|
|
|
&& !rc.focus_follow_mouse_requires_movement) {
|
2024-12-31 13:00:32 +09:00
|
|
|
/*
|
|
|
|
|
* Always focus the surface below the cursor when
|
|
|
|
|
* followMouse=yes and followMouseRequiresMovement=no.
|
|
|
|
|
*/
|
2023-11-01 23:01:19 -04:00
|
|
|
desktop_focus_view_or_surface(&server->seat, ctx.view,
|
|
|
|
|
ctx.surface, rc.raise_on_focus);
|
2023-03-23 21:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
double sx, sy;
|
2025-04-16 18:25:43 +02:00
|
|
|
cursor_update_common(server, &ctx, /*cursor_has_moved*/ false, &sx, &sy);
|
2021-12-26 22:09:41 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-01 14:06:52 -04:00
|
|
|
void
|
|
|
|
|
cursor_update_focus(struct server *server)
|
|
|
|
|
{
|
|
|
|
|
/* Prevent recursion via view_move_to_front() */
|
|
|
|
|
static bool updating_focus = false;
|
|
|
|
|
if (!updating_focus) {
|
|
|
|
|
updating_focus = true;
|
|
|
|
|
_cursor_update_focus(server);
|
|
|
|
|
updating_focus = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 16:42:32 +10:00
|
|
|
static void
|
|
|
|
|
warp_cursor_to_constraint_hint(struct seat *seat,
|
|
|
|
|
struct wlr_pointer_constraint_v1 *constraint)
|
|
|
|
|
{
|
2023-12-19 17:45:11 +00:00
|
|
|
if (!seat->server->active_view) {
|
2023-04-12 16:42:32 +10:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (constraint->current.committed
|
|
|
|
|
& WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) {
|
|
|
|
|
double sx = constraint->current.cursor_hint.x;
|
|
|
|
|
double sy = constraint->current.cursor_hint.y;
|
|
|
|
|
wlr_cursor_warp(seat->cursor, NULL,
|
2023-12-19 17:45:11 +00:00
|
|
|
seat->server->active_view->current.x + sx,
|
|
|
|
|
seat->server->active_view->current.y + sy);
|
2023-04-12 16:42:32 +10:00
|
|
|
|
|
|
|
|
/* Make sure we are not sending unnecessary surface movements */
|
|
|
|
|
wlr_seat_pointer_warp(seat->seat, sx, sy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-31 03:35:13 +01:00
|
|
|
static void
|
2021-10-17 16:54:35 -04:00
|
|
|
handle_constraint_commit(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct seat *seat = wl_container_of(listener, seat, constraint_commit);
|
|
|
|
|
struct wlr_pointer_constraint_v1 *constraint = seat->current_constraint;
|
2024-02-10 18:02:30 +01:00
|
|
|
/* Prevents unused variable warning when compiled without asserts */
|
|
|
|
|
(void)constraint;
|
2025-08-14 14:35:44 -04:00
|
|
|
assert(constraint->surface == data);
|
2021-10-17 16:54:35 -04:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 03:35:13 +01:00
|
|
|
static void
|
2025-06-26 18:23:09 +09:00
|
|
|
handle_constraint_destroy(struct wl_listener *listener, void *data)
|
2021-10-17 16:54:35 -04:00
|
|
|
{
|
|
|
|
|
struct constraint *constraint = wl_container_of(listener, constraint,
|
|
|
|
|
destroy);
|
|
|
|
|
struct wlr_pointer_constraint_v1 *wlr_constraint = data;
|
|
|
|
|
struct seat *seat = constraint->seat;
|
|
|
|
|
|
|
|
|
|
wl_list_remove(&constraint->destroy.link);
|
|
|
|
|
if (seat->current_constraint == wlr_constraint) {
|
2023-04-12 16:42:32 +10:00
|
|
|
warp_cursor_to_constraint_hint(seat, wlr_constraint);
|
|
|
|
|
|
2022-04-04 20:53:36 +01:00
|
|
|
if (seat->constraint_commit.link.next) {
|
2021-10-17 16:54:35 -04:00
|
|
|
wl_list_remove(&seat->constraint_commit.link);
|
|
|
|
|
}
|
|
|
|
|
wl_list_init(&seat->constraint_commit.link);
|
|
|
|
|
seat->current_constraint = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(constraint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
create_constraint(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct wlr_pointer_constraint_v1 *wlr_constraint = data;
|
|
|
|
|
struct server *server = wl_container_of(listener, server,
|
|
|
|
|
new_constraint);
|
2022-09-18 15:22:26 -04:00
|
|
|
struct constraint *constraint = znew(*constraint);
|
2021-10-17 16:54:35 -04:00
|
|
|
|
|
|
|
|
constraint->constraint = wlr_constraint;
|
|
|
|
|
constraint->seat = &server->seat;
|
2025-06-26 18:23:09 +09:00
|
|
|
constraint->destroy.notify = handle_constraint_destroy;
|
2021-10-17 16:54:35 -04:00
|
|
|
wl_signal_add(&wlr_constraint->events.destroy, &constraint->destroy);
|
|
|
|
|
|
2023-12-19 17:45:11 +00:00
|
|
|
struct view *view = server->active_view;
|
2022-07-14 02:19:36 +00:00
|
|
|
if (view && view->surface == wlr_constraint->surface) {
|
2021-10-17 16:54:35 -04:00
|
|
|
constrain_cursor(server, wlr_constraint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
constrain_cursor(struct server *server, struct wlr_pointer_constraint_v1
|
|
|
|
|
*constraint)
|
|
|
|
|
{
|
|
|
|
|
struct seat *seat = &server->seat;
|
|
|
|
|
if (seat->current_constraint == constraint) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
wl_list_remove(&seat->constraint_commit.link);
|
|
|
|
|
if (seat->current_constraint) {
|
2023-04-12 16:42:32 +10:00
|
|
|
if (!constraint) {
|
|
|
|
|
warp_cursor_to_constraint_hint(seat, seat->current_constraint);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-17 16:54:35 -04:00
|
|
|
wlr_pointer_constraint_v1_send_deactivated(
|
|
|
|
|
seat->current_constraint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seat->current_constraint = constraint;
|
|
|
|
|
|
2022-04-04 20:53:36 +01:00
|
|
|
if (!constraint) {
|
2021-10-17 16:54:35 -04:00
|
|
|
wl_list_init(&seat->constraint_commit.link);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wlr_pointer_constraint_v1_send_activated(constraint);
|
|
|
|
|
seat->constraint_commit.notify = handle_constraint_commit;
|
|
|
|
|
wl_signal_add(&constraint->surface->events.commit,
|
|
|
|
|
&seat->constraint_commit);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-13 00:35:25 +03:00
|
|
|
static void
|
|
|
|
|
apply_constraint(struct seat *seat, struct wlr_pointer *pointer, double *x, double *y)
|
|
|
|
|
{
|
2024-10-19 19:44:50 +01:00
|
|
|
if (!seat->server->active_view) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-10-13 00:35:25 +03:00
|
|
|
if (!seat->current_constraint || pointer->base.type != WLR_INPUT_DEVICE_POINTER) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-02-24 10:20:14 +01:00
|
|
|
assert(seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED);
|
2022-10-13 00:35:25 +03:00
|
|
|
|
|
|
|
|
double sx = seat->cursor->x;
|
|
|
|
|
double sy = seat->cursor->y;
|
|
|
|
|
|
2023-12-19 17:45:11 +00:00
|
|
|
sx -= seat->server->active_view->current.x;
|
|
|
|
|
sy -= seat->server->active_view->current.y;
|
2022-10-13 00:35:25 +03:00
|
|
|
|
|
|
|
|
double sx_confined, sy_confined;
|
|
|
|
|
if (!wlr_region_confine(&seat->current_constraint->region, sx, sy,
|
|
|
|
|
sx + *x, sy + *y, &sx_confined, &sy_confined)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*x = sx_confined - sx;
|
|
|
|
|
*y = sy_confined - sy;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-24 10:20:14 +01:00
|
|
|
static bool
|
|
|
|
|
cursor_locked(struct seat *seat, struct wlr_pointer *pointer)
|
|
|
|
|
{
|
|
|
|
|
return seat->current_constraint
|
|
|
|
|
&& pointer->base.type == WLR_INPUT_DEVICE_POINTER
|
|
|
|
|
&& seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-13 00:35:25 +03:00
|
|
|
static void
|
|
|
|
|
preprocess_cursor_motion(struct seat *seat, struct wlr_pointer *pointer,
|
|
|
|
|
uint32_t time_msec, double dx, double dy)
|
|
|
|
|
{
|
2023-02-24 10:20:14 +01:00
|
|
|
if (cursor_locked(seat, pointer)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-10-13 00:35:25 +03:00
|
|
|
apply_constraint(seat, pointer, &dx, &dy);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The cursor doesn't move unless we tell it to. The cursor
|
|
|
|
|
* automatically handles constraining the motion to the output
|
|
|
|
|
* layout, as well as any special configuration applied for the
|
|
|
|
|
* specific input device which generated the event. You can pass
|
|
|
|
|
* NULL for the device if you want to move the cursor around
|
|
|
|
|
* without any input.
|
|
|
|
|
*/
|
|
|
|
|
wlr_cursor_move(seat->cursor, &pointer->base, dx, dy);
|
2024-04-20 11:33:04 +02:00
|
|
|
double sx, sy;
|
|
|
|
|
bool notify = cursor_process_motion(seat->server, time_msec, &sx, &sy);
|
|
|
|
|
if (notify) {
|
|
|
|
|
wlr_seat_pointer_notify_motion(seat->seat, time_msec, sx, sy);
|
|
|
|
|
}
|
2022-10-13 00:35:25 +03:00
|
|
|
}
|
|
|
|
|
|
2025-04-25 14:21:30 +02:00
|
|
|
static double get_natural_scroll_factor(struct wlr_input_device *wlr_input_device)
|
|
|
|
|
{
|
|
|
|
|
if (wlr_input_device_is_libinput(wlr_input_device)) {
|
|
|
|
|
struct libinput_device *libinput_device =
|
|
|
|
|
wlr_libinput_get_device_handle(wlr_input_device);
|
|
|
|
|
if (libinput_device_config_scroll_get_natural_scroll_enabled(libinput_device)) {
|
|
|
|
|
return -1.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1.0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-24 20:53:22 +01:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_motion(struct wl_listener *listener, void *data)
|
2020-05-29 21:36:12 +01:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* This event is forwarded by the cursor when a pointer emits a
|
|
|
|
|
* _relative_ pointer motion event (i.e. a delta)
|
|
|
|
|
*/
|
2025-04-15 21:43:00 +02:00
|
|
|
struct seat *seat = wl_container_of(listener, seat, on_cursor.motion);
|
2021-10-17 16:54:35 -04:00
|
|
|
struct server *server = seat->server;
|
2022-03-19 11:34:11 +00:00
|
|
|
struct wlr_pointer_motion_event *event = data;
|
2023-07-08 18:27:40 +02:00
|
|
|
idle_manager_notify_activity(seat->seat);
|
2024-11-02 15:59:33 +01:00
|
|
|
cursor_set_visible(seat, /* visible */ true);
|
2020-05-29 21:36:12 +01:00
|
|
|
|
2025-04-25 14:21:30 +02:00
|
|
|
if (seat->cursor_scroll_wheel_emulation) {
|
2025-08-26 23:48:05 -04:00
|
|
|
enum wl_pointer_axis orientation;
|
2025-04-25 14:21:30 +02:00
|
|
|
double delta;
|
|
|
|
|
if (fabs(event->delta_x) > fabs(event->delta_y)) {
|
|
|
|
|
orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL;
|
|
|
|
|
delta = event->delta_x;
|
|
|
|
|
} else {
|
|
|
|
|
orientation = WL_POINTER_AXIS_VERTICAL_SCROLL;
|
|
|
|
|
delta = event->delta_y;
|
|
|
|
|
}
|
2022-10-13 00:35:25 +03:00
|
|
|
|
2025-04-25 14:21:30 +02:00
|
|
|
/*
|
|
|
|
|
* arbitrary factor that should give reasonable speed
|
|
|
|
|
* with the default configured scroll factor of 1.0
|
|
|
|
|
*/
|
|
|
|
|
double motion_to_scroll_factor = 0.04;
|
|
|
|
|
double scroll_factor = motion_to_scroll_factor *
|
|
|
|
|
get_natural_scroll_factor(&event->pointer->base);
|
|
|
|
|
|
|
|
|
|
/* The delta of a single step for mouse wheel emulation */
|
|
|
|
|
double pointer_axis_step = 15.0;
|
|
|
|
|
|
|
|
|
|
cursor_emulate_axis(seat, &event->pointer->base,
|
|
|
|
|
orientation,
|
|
|
|
|
pointer_axis_step * scroll_factor * delta, 0,
|
|
|
|
|
WL_POINTER_AXIS_SOURCE_CONTINUOUS, event->time_msec);
|
|
|
|
|
} else {
|
|
|
|
|
wlr_relative_pointer_manager_v1_send_relative_motion(
|
|
|
|
|
server->relative_pointer_manager,
|
|
|
|
|
seat->seat, (uint64_t)event->time_msec * 1000,
|
|
|
|
|
event->delta_x, event->delta_y, event->unaccel_dx,
|
|
|
|
|
event->unaccel_dy);
|
|
|
|
|
|
|
|
|
|
preprocess_cursor_motion(seat, event->pointer,
|
|
|
|
|
event->time_msec, event->delta_x, event->delta_y);
|
|
|
|
|
}
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 03:35:13 +01:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_motion_absolute(struct wl_listener *listener, void *data)
|
2020-05-29 21:36:12 +01:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* This event is forwarded by the cursor when a pointer emits an
|
|
|
|
|
* _absolute_ motion event, from 0..1 on each axis. This happens, for
|
|
|
|
|
* example, when wlroots is running under a Wayland window rather than
|
|
|
|
|
* KMS+DRM, and you move the mouse over the window. You could enter the
|
|
|
|
|
* window from any edge, so we have to warp the mouse there. There is
|
|
|
|
|
* also some hardware which emits these events.
|
|
|
|
|
*/
|
2025-04-15 21:43:00 +02:00
|
|
|
struct seat *seat = wl_container_of(listener, seat, on_cursor.motion_absolute);
|
2022-03-19 11:34:11 +00:00
|
|
|
struct wlr_pointer_motion_absolute_event *event = data;
|
2023-07-08 18:27:40 +02:00
|
|
|
idle_manager_notify_activity(seat->seat);
|
2024-11-02 15:59:33 +01:00
|
|
|
cursor_set_visible(seat, /* visible */ true);
|
2021-10-17 21:57:10 +00:00
|
|
|
|
|
|
|
|
double lx, ly;
|
2022-03-19 11:34:11 +00:00
|
|
|
wlr_cursor_absolute_to_layout_coords(seat->cursor,
|
|
|
|
|
&event->pointer->base, event->x, event->y, &lx, &ly);
|
2021-10-17 21:57:10 +00:00
|
|
|
|
|
|
|
|
double dx = lx - seat->cursor->x;
|
|
|
|
|
double dy = ly - seat->cursor->y;
|
|
|
|
|
|
|
|
|
|
wlr_relative_pointer_manager_v1_send_relative_motion(
|
|
|
|
|
seat->server->relative_pointer_manager,
|
|
|
|
|
seat->seat, (uint64_t)event->time_msec * 1000,
|
|
|
|
|
dx, dy, dx, dy);
|
|
|
|
|
|
2022-10-13 00:35:25 +03:00
|
|
|
preprocess_cursor_motion(seat, event->pointer,
|
|
|
|
|
event->time_msec, dx, dy);
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2024-10-07 09:20:58 +09:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
process_release_mousebinding(struct server *server,
|
2022-09-14 14:49:09 -04:00
|
|
|
struct cursor_context *ctx, uint32_t button)
|
2021-11-01 20:32:14 -04:00
|
|
|
{
|
2024-12-28 17:42:26 +09:00
|
|
|
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) {
|
2024-10-07 09:20:58 +09:00
|
|
|
return;
|
2024-03-29 02:53:53 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-01 20:32:14 -04:00
|
|
|
struct mousebind *mousebind;
|
2025-01-10 11:02:58 +01:00
|
|
|
uint32_t modifiers = keyboard_get_all_modifiers(&server->seat);
|
2022-09-14 14:49:09 -04:00
|
|
|
|
2022-08-29 23:06:53 +02:00
|
|
|
wl_list_for_each(mousebind, &rc.mousebinds, link) {
|
2025-09-03 05:08:52 -04:00
|
|
|
if (ctx->type == LAB_NODE_CLIENT
|
2025-07-24 11:08:03 +09:00
|
|
|
&& view_inhibits_actions(ctx->view, &mousebind->actions)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-09-03 05:08:52 -04:00
|
|
|
if (node_type_contains(mousebind->context, ctx->type)
|
2021-12-01 02:32:24 +00:00
|
|
|
&& mousebind->button == button
|
|
|
|
|
&& modifiers == mousebind->modifiers) {
|
2021-12-01 02:44:55 +00:00
|
|
|
switch (mousebind->mouse_event) {
|
2021-12-03 23:17:12 +00:00
|
|
|
case MOUSE_ACTION_RELEASE:
|
|
|
|
|
break;
|
|
|
|
|
case MOUSE_ACTION_CLICK:
|
2022-01-09 05:48:27 +01:00
|
|
|
if (mousebind->pressed_in_context) {
|
2021-12-01 02:44:55 +00:00
|
|
|
break;
|
2022-01-09 05:48:27 +01:00
|
|
|
}
|
2021-12-03 23:17:12 +00:00
|
|
|
continue;
|
|
|
|
|
default:
|
|
|
|
|
continue;
|
2021-11-01 20:32:14 -04:00
|
|
|
}
|
2024-09-21 01:11:27 +09:00
|
|
|
actions_run(ctx->view, server, &mousebind->actions, ctx);
|
2021-11-01 20:32:14 -04:00
|
|
|
}
|
2022-01-08 02:47:30 -05:00
|
|
|
}
|
2021-11-01 20:32:14 -04:00
|
|
|
}
|
|
|
|
|
|
2021-08-02 17:30:34 +01:00
|
|
|
static bool
|
2024-03-25 21:49:46 +00:00
|
|
|
is_double_click(long double_click_speed, uint32_t button,
|
|
|
|
|
struct cursor_context *ctx)
|
2021-08-02 17:30:34 +01:00
|
|
|
{
|
2025-09-03 05:08:52 -04:00
|
|
|
static enum lab_node_type last_type;
|
2021-12-01 02:42:01 +00:00
|
|
|
static uint32_t last_button;
|
2022-04-28 16:26:48 +08:00
|
|
|
static struct view *last_view;
|
2021-12-01 02:42:01 +00:00
|
|
|
static struct timespec last_click;
|
2021-08-02 17:30:34 +01:00
|
|
|
struct timespec now;
|
|
|
|
|
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
2021-12-01 02:42:01 +00:00
|
|
|
long ms = (now.tv_sec - last_click.tv_sec) * 1000 +
|
|
|
|
|
(now.tv_nsec - last_click.tv_nsec) / 1000000;
|
|
|
|
|
last_click = now;
|
2024-03-25 21:49:46 +00:00
|
|
|
if (last_button != button || last_view != ctx->view
|
|
|
|
|
|| last_type != ctx->type) {
|
2021-12-01 02:42:01 +00:00
|
|
|
last_button = button;
|
2024-03-25 21:49:46 +00:00
|
|
|
last_view = ctx->view;
|
|
|
|
|
last_type = ctx->type;
|
2021-12-01 02:42:01 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (ms < double_click_speed && ms >= 0) {
|
2021-12-26 23:29:01 +00:00
|
|
|
/*
|
|
|
|
|
* End sequence so that third click is not considered a
|
|
|
|
|
* double-click
|
|
|
|
|
*/
|
2021-12-01 02:42:01 +00:00
|
|
|
last_button = 0;
|
2022-04-28 16:26:48 +08:00
|
|
|
last_view = NULL;
|
2025-09-03 05:08:52 -04:00
|
|
|
last_type = LAB_NODE_NONE;
|
2021-12-01 02:42:01 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2021-08-02 17:30:34 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-01 20:32:14 -04:00
|
|
|
static bool
|
2025-04-15 21:43:00 +02:00
|
|
|
process_press_mousebinding(struct server *server, struct cursor_context *ctx,
|
2024-09-21 01:11:27 +09:00
|
|
|
uint32_t button)
|
2021-11-01 20:32:14 -04:00
|
|
|
{
|
2024-12-28 17:42:26 +09:00
|
|
|
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) {
|
2024-03-29 02:53:53 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-01 20:32:14 -04:00
|
|
|
struct mousebind *mousebind;
|
2024-03-25 21:49:46 +00:00
|
|
|
bool double_click = is_double_click(rc.doubleclick_time, button, ctx);
|
2022-11-07 20:26:37 +00:00
|
|
|
bool consumed_by_frame_context = false;
|
2025-01-10 11:02:58 +01:00
|
|
|
uint32_t modifiers = keyboard_get_all_modifiers(&server->seat);
|
2022-09-14 14:49:09 -04:00
|
|
|
|
2022-08-29 23:06:53 +02:00
|
|
|
wl_list_for_each(mousebind, &rc.mousebinds, link) {
|
2025-09-03 05:08:52 -04:00
|
|
|
if (ctx->type == LAB_NODE_CLIENT
|
2025-07-24 11:08:03 +09:00
|
|
|
&& view_inhibits_actions(ctx->view, &mousebind->actions)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-09-03 05:08:52 -04:00
|
|
|
if (node_type_contains(mousebind->context, ctx->type)
|
2021-12-01 02:32:24 +00:00
|
|
|
&& mousebind->button == button
|
|
|
|
|
&& modifiers == mousebind->modifiers) {
|
2021-12-01 02:44:55 +00:00
|
|
|
switch (mousebind->mouse_event) {
|
2022-04-04 20:53:36 +01:00
|
|
|
case MOUSE_ACTION_DRAG: /* fallthrough */
|
2021-12-03 23:17:12 +00:00
|
|
|
case MOUSE_ACTION_CLICK:
|
2022-01-08 02:01:14 -05:00
|
|
|
/*
|
|
|
|
|
* DRAG and CLICK actions will be processed on
|
|
|
|
|
* the release event, unless the press event is
|
|
|
|
|
* counted as a DOUBLECLICK.
|
|
|
|
|
*/
|
2022-01-09 05:48:27 +01:00
|
|
|
if (!double_click) {
|
2024-10-07 09:20:58 +09:00
|
|
|
/* Swallow the press event */
|
2022-11-07 20:26:37 +00:00
|
|
|
consumed_by_frame_context |=
|
2025-09-03 05:08:52 -04:00
|
|
|
mousebind->context == LAB_NODE_FRAME;
|
2024-05-02 11:00:23 +01:00
|
|
|
consumed_by_frame_context |=
|
2025-09-03 05:08:52 -04:00
|
|
|
mousebind->context == LAB_NODE_ALL;
|
2022-01-08 02:01:14 -05:00
|
|
|
mousebind->pressed_in_context = true;
|
2022-01-09 05:48:27 +01:00
|
|
|
}
|
2021-12-03 23:17:12 +00:00
|
|
|
continue;
|
|
|
|
|
case MOUSE_ACTION_DOUBLECLICK:
|
2022-01-09 05:48:27 +01:00
|
|
|
if (!double_click) {
|
2021-12-01 02:44:55 +00:00
|
|
|
continue;
|
2022-01-09 05:48:27 +01:00
|
|
|
}
|
2021-12-03 23:17:12 +00:00
|
|
|
break;
|
|
|
|
|
case MOUSE_ACTION_PRESS:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
continue;
|
2021-11-01 20:32:14 -04:00
|
|
|
}
|
2025-09-03 05:08:52 -04:00
|
|
|
consumed_by_frame_context |= mousebind->context == LAB_NODE_FRAME;
|
|
|
|
|
consumed_by_frame_context |= mousebind->context == LAB_NODE_ALL;
|
2024-09-21 01:11:27 +09:00
|
|
|
actions_run(ctx->view, server, &mousebind->actions, ctx);
|
2021-11-01 20:32:14 -04:00
|
|
|
}
|
|
|
|
|
}
|
2022-11-07 20:26:37 +00:00
|
|
|
return consumed_by_frame_context;
|
2021-11-01 20:32:14 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 02:51:33 +09:00
|
|
|
static struct wlr_layer_surface_v1 *
|
|
|
|
|
get_root_layer(struct wlr_surface *wlr_surface)
|
|
|
|
|
{
|
|
|
|
|
assert(wlr_surface);
|
|
|
|
|
struct wlr_subsurface *subsurface =
|
|
|
|
|
wlr_subsurface_try_from_wlr_surface(wlr_surface);
|
|
|
|
|
if (subsurface) {
|
|
|
|
|
if (subsurface->parent) {
|
|
|
|
|
return get_root_layer(subsurface->parent);
|
|
|
|
|
} else {
|
|
|
|
|
/* never reached? */
|
|
|
|
|
wlr_log(WLR_ERROR, "subsurface without parent");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 09:03:17 +09:00
|
|
|
static uint32_t press_msec;
|
|
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
bool
|
|
|
|
|
cursor_process_button_press(struct seat *seat, uint32_t button, uint32_t time_msec)
|
2020-05-29 21:36:12 +01:00
|
|
|
{
|
2020-10-02 21:19:56 +01:00
|
|
|
struct server *server = seat->server;
|
2022-09-12 04:54:00 -04:00
|
|
|
struct cursor_context ctx = get_cursor_context(server);
|
2022-02-18 00:07:37 +01:00
|
|
|
|
2024-04-26 09:03:17 +09:00
|
|
|
/* Used on next button release to check if it can close menu or select menu item */
|
|
|
|
|
press_msec = time_msec;
|
|
|
|
|
|
2022-09-14 23:09:36 -04:00
|
|
|
if (ctx.view || ctx.surface) {
|
2024-09-21 01:11:27 +09:00
|
|
|
/* Store cursor context for later action processing */
|
|
|
|
|
seat_set_pressed(seat, &ctx);
|
2022-02-26 01:21:39 +01:00
|
|
|
}
|
|
|
|
|
|
2020-10-21 20:30:06 +01:00
|
|
|
if (server->input_mode == LAB_INPUT_STATE_MENU) {
|
2024-04-26 09:03:17 +09:00
|
|
|
/*
|
|
|
|
|
* If menu was already opened on press, set a very small value
|
|
|
|
|
* so subsequent release always closes menu or selects menu item.
|
|
|
|
|
*/
|
|
|
|
|
press_msec = 0;
|
2024-10-07 09:20:58 +09:00
|
|
|
lab_set_add(&seat->bound_buttons, button);
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2020-10-19 22:14:17 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-01 23:01:19 -04:00
|
|
|
/*
|
|
|
|
|
* On press, set focus to a non-view surface that wants it.
|
|
|
|
|
* Action processing does not run for these surfaces and thus
|
|
|
|
|
* the Focus action (used for normal views) does not work.
|
|
|
|
|
*/
|
2025-09-03 05:08:52 -04:00
|
|
|
if (ctx.type == LAB_NODE_LAYER_SURFACE) {
|
2025-09-09 02:51:33 +09:00
|
|
|
wlr_log(WLR_DEBUG, "press on layer-(sub)surface");
|
|
|
|
|
struct wlr_layer_surface_v1 *layer = get_root_layer(ctx.surface);
|
2024-03-07 19:15:02 +00:00
|
|
|
if (layer && layer->current.keyboard_interactive) {
|
2024-03-09 10:48:52 +00:00
|
|
|
layer_try_set_focus(seat, layer);
|
2024-03-07 19:15:02 +00:00
|
|
|
}
|
2023-11-01 23:01:19 -04:00
|
|
|
#ifdef HAVE_XWAYLAND
|
2025-09-03 05:08:52 -04:00
|
|
|
} else if (ctx.type == LAB_NODE_UNMANAGED) {
|
2023-11-01 23:01:19 -04:00
|
|
|
desktop_focus_view_or_surface(seat, NULL, ctx.surface,
|
|
|
|
|
/*raise*/ false);
|
|
|
|
|
#endif
|
2022-02-18 00:07:37 +01:00
|
|
|
}
|
2023-10-02 22:04:40 +01:00
|
|
|
|
2025-09-09 02:51:33 +09:00
|
|
|
if (ctx.type != LAB_NODE_CLIENT && ctx.type != LAB_NODE_LAYER_SURFACE
|
2023-08-14 23:38:55 +01:00
|
|
|
&& wlr_seat_pointer_has_grab(seat->seat)) {
|
2023-08-09 16:58:18 +01:00
|
|
|
/*
|
|
|
|
|
* If we have an active popup grab (an open popup) we want to
|
|
|
|
|
* cancel that grab whenever the user presses on anything that
|
|
|
|
|
* is not the client itself, for example the desktop or any
|
|
|
|
|
* part of the server side decoration.
|
|
|
|
|
*
|
|
|
|
|
* Note: This does not work for XWayland clients
|
|
|
|
|
*/
|
2023-08-06 12:19:58 +02:00
|
|
|
wlr_seat_pointer_end_grab(seat->seat);
|
2024-10-07 09:20:58 +09:00
|
|
|
lab_set_add(&seat->bound_buttons, button);
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2023-08-06 12:19:58 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-14 14:49:09 -04:00
|
|
|
/* Bindings to the Frame context swallow mouse events if activated */
|
2022-11-07 20:26:37 +00:00
|
|
|
bool consumed_by_frame_context =
|
2025-04-15 21:43:00 +02:00
|
|
|
process_press_mousebinding(server, &ctx, button);
|
2022-09-14 14:49:09 -04:00
|
|
|
|
2022-11-07 20:26:37 +00:00
|
|
|
if (ctx.surface && !consumed_by_frame_context) {
|
2021-12-01 02:44:55 +00:00
|
|
|
/* Notify client with pointer focus of button press */
|
2024-04-20 11:33:04 +02:00
|
|
|
return true;
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
2024-04-20 11:33:04 +02:00
|
|
|
|
2024-10-07 09:20:58 +09:00
|
|
|
lab_set_add(&seat->bound_buttons, button);
|
2024-04-20 11:33:04 +02:00
|
|
|
return false;
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
bool
|
|
|
|
|
cursor_process_button_release(struct seat *seat, uint32_t button,
|
|
|
|
|
uint32_t time_msec)
|
2022-09-14 14:49:09 -04:00
|
|
|
{
|
|
|
|
|
struct server *server = seat->server;
|
|
|
|
|
struct cursor_context ctx = get_cursor_context(server);
|
|
|
|
|
struct wlr_surface *pressed_surface = seat->pressed.surface;
|
|
|
|
|
|
2024-10-07 09:20:58 +09:00
|
|
|
/* Always notify button release event when it's not bound */
|
|
|
|
|
const bool notify = !lab_set_contains(&seat->bound_buttons, button);
|
|
|
|
|
|
2022-09-14 14:49:09 -04:00
|
|
|
seat_reset_pressed(seat);
|
|
|
|
|
|
|
|
|
|
if (server->input_mode == LAB_INPUT_STATE_MENU) {
|
2024-04-26 09:03:17 +09:00
|
|
|
/* TODO: take into account overflow of time_msec */
|
|
|
|
|
if (time_msec - press_msec > rc.menu_ignore_button_release_period) {
|
2025-09-03 05:32:44 -04:00
|
|
|
if (ctx.type == LAB_NODE_MENUITEM) {
|
2024-04-26 09:03:17 +09:00
|
|
|
menu_call_selected_actions(server);
|
|
|
|
|
} else {
|
|
|
|
|
menu_close_root(server);
|
2024-12-19 17:04:10 +09:00
|
|
|
cursor_update_focus(server);
|
2024-04-26 09:03:17 +09:00
|
|
|
}
|
2022-09-14 14:49:09 -04:00
|
|
|
}
|
2024-10-07 09:20:58 +09:00
|
|
|
return notify;
|
2022-09-14 14:49:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
|
2024-10-07 09:20:58 +09:00
|
|
|
return notify;
|
2023-08-28 16:43:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pressed_surface && ctx.surface != pressed_surface) {
|
|
|
|
|
/*
|
|
|
|
|
* Button released but originally pressed over a different surface.
|
|
|
|
|
* Just send the release event to the still focused surface.
|
|
|
|
|
*/
|
2024-10-07 09:20:58 +09:00
|
|
|
return notify;
|
2022-09-14 14:49:09 -04:00
|
|
|
}
|
|
|
|
|
|
2025-04-15 21:43:00 +02:00
|
|
|
process_release_mousebinding(server, &ctx, button);
|
2022-09-14 14:49:09 -04:00
|
|
|
|
2024-10-07 09:20:58 +09:00
|
|
|
return notify;
|
2024-04-20 11:33:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2024-09-24 04:49:08 +09:00
|
|
|
cursor_finish_button_release(struct seat *seat, uint32_t button)
|
2024-04-20 11:33:04 +02:00
|
|
|
{
|
|
|
|
|
struct server *server = seat->server;
|
|
|
|
|
|
2024-09-24 04:49:08 +09:00
|
|
|
/* Clear "pressed" status for all bindings of this mouse button */
|
|
|
|
|
struct mousebind *mousebind;
|
|
|
|
|
wl_list_for_each(mousebind, &rc.mousebinds, link) {
|
|
|
|
|
if (mousebind->button == button) {
|
|
|
|
|
mousebind->pressed_in_context = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 09:20:58 +09:00
|
|
|
lab_set_remove(&seat->bound_buttons, button);
|
|
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
if (server->input_mode == LAB_INPUT_STATE_MOVE
|
|
|
|
|
|| server->input_mode == LAB_INPUT_STATE_RESIZE) {
|
2024-05-29 11:06:39 +09:00
|
|
|
if (resize_outlines_enabled(server->grabbed_view)) {
|
|
|
|
|
resize_outlines_finish(server->grabbed_view);
|
|
|
|
|
}
|
2024-04-20 11:33:04 +02:00
|
|
|
/* Exit interactive move/resize mode */
|
|
|
|
|
interactive_finish(server->grabbed_view);
|
|
|
|
|
return true;
|
2022-09-14 14:49:09 -04:00
|
|
|
}
|
2024-04-20 11:33:04 +02:00
|
|
|
|
|
|
|
|
return false;
|
2022-09-14 14:49:09 -04:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 03:35:13 +01:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_button(struct wl_listener *listener, void *data)
|
2022-09-14 14:49:09 -04:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* This event is forwarded by the cursor when a pointer emits a button
|
|
|
|
|
* event.
|
|
|
|
|
*/
|
2025-04-15 21:43:00 +02:00
|
|
|
struct seat *seat = wl_container_of(listener, seat, on_cursor.button);
|
2022-09-14 14:49:09 -04:00
|
|
|
struct wlr_pointer_button_event *event = data;
|
2023-07-08 18:27:40 +02:00
|
|
|
idle_manager_notify_activity(seat->seat);
|
2024-11-02 15:59:33 +01:00
|
|
|
cursor_set_visible(seat, /* visible */ true);
|
2022-09-14 14:49:09 -04:00
|
|
|
|
2024-04-20 11:33:04 +02:00
|
|
|
bool notify;
|
2022-09-14 14:49:09 -04:00
|
|
|
switch (event->state) {
|
2024-03-19 01:32:05 +01:00
|
|
|
case WL_POINTER_BUTTON_STATE_PRESSED:
|
2024-04-20 11:33:04 +02:00
|
|
|
notify = cursor_process_button_press(seat, event->button,
|
|
|
|
|
event->time_msec);
|
|
|
|
|
if (notify) {
|
|
|
|
|
wlr_seat_pointer_notify_button(seat->seat, event->time_msec,
|
|
|
|
|
event->button, event->state);
|
|
|
|
|
}
|
2022-09-14 14:49:09 -04:00
|
|
|
break;
|
2024-03-19 01:32:05 +01:00
|
|
|
case WL_POINTER_BUTTON_STATE_RELEASED:
|
2024-04-20 11:33:04 +02:00
|
|
|
notify = cursor_process_button_release(seat, event->button,
|
|
|
|
|
event->time_msec);
|
|
|
|
|
if (notify) {
|
|
|
|
|
wlr_seat_pointer_notify_button(seat->seat, event->time_msec,
|
|
|
|
|
event->button, event->state);
|
|
|
|
|
}
|
2024-09-24 04:49:08 +09:00
|
|
|
cursor_finish_button_release(seat, event->button);
|
2023-12-01 22:10:08 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-11 23:36:58 +09:00
|
|
|
struct scroll_info {
|
|
|
|
|
int direction;
|
|
|
|
|
bool run_action;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct scroll_info
|
2025-07-22 00:19:22 +09:00
|
|
|
compare_delta(double delta, double delta_discrete, struct accumulated_scroll *accum)
|
2022-11-09 05:18:14 +00:00
|
|
|
{
|
2025-01-11 23:36:58 +09:00
|
|
|
struct scroll_info info = {0};
|
|
|
|
|
|
2025-07-22 00:19:22 +09:00
|
|
|
if (delta_discrete) {
|
|
|
|
|
/* mice */
|
|
|
|
|
info.direction = delta_discrete > 0 ? 1 : -1;
|
|
|
|
|
accum->delta_discrete += delta_discrete;
|
|
|
|
|
/*
|
|
|
|
|
* Non-hi-res mice produce delta_discrete of ±120 for every
|
|
|
|
|
* "click", so it always triggers actions. But for hi-res mice
|
|
|
|
|
* that produce smaller delta_discrete, we accumulate it and
|
|
|
|
|
* run actions after it exceeds 120(= 1 click).
|
|
|
|
|
*/
|
|
|
|
|
if (fabs(accum->delta_discrete) >= 120.0) {
|
|
|
|
|
accum->delta_discrete = fmod(accum->delta_discrete, 120.0);
|
|
|
|
|
info.run_action = true;
|
|
|
|
|
}
|
2022-11-09 05:18:14 +00:00
|
|
|
} else {
|
2025-07-22 00:19:22 +09:00
|
|
|
/* 2-finger scrolling on touchpads */
|
|
|
|
|
if (delta == 0) {
|
|
|
|
|
/* delta=0 marks the end of a scroll */
|
|
|
|
|
accum->delta = 0;
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
info.direction = delta > 0 ? 1 : -1;
|
|
|
|
|
accum->delta += delta;
|
|
|
|
|
/*
|
|
|
|
|
* The threshold of 10 is inherited from various historic
|
|
|
|
|
* projects including weston.
|
|
|
|
|
*
|
|
|
|
|
* For historic context, see:
|
|
|
|
|
* https://lists.freedesktop.org/archives/wayland-devel/2019-April/040377.html
|
|
|
|
|
*/
|
|
|
|
|
if (fabs(accum->delta) >= 10.0) {
|
|
|
|
|
accum->delta = fmod(accum->delta, 10.0);
|
|
|
|
|
info.run_action = true;
|
|
|
|
|
}
|
2022-11-09 05:18:14 +00:00
|
|
|
}
|
2025-01-11 23:36:58 +09:00
|
|
|
|
|
|
|
|
return info;
|
2022-11-09 05:18:14 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 03:35:13 +01:00
|
|
|
static bool
|
2025-04-21 18:18:21 +02:00
|
|
|
process_cursor_axis(struct server *server, enum wl_pointer_axis orientation,
|
|
|
|
|
double delta, double delta_discrete)
|
2022-10-25 21:46:46 +02:00
|
|
|
{
|
2025-04-21 18:18:21 +02:00
|
|
|
struct cursor_context ctx = get_cursor_context(server);
|
2025-01-10 11:02:58 +01:00
|
|
|
uint32_t modifiers = keyboard_get_all_modifiers(&server->seat);
|
2022-10-25 21:46:46 +02:00
|
|
|
|
2022-11-09 05:18:14 +00:00
|
|
|
enum direction direction = LAB_DIRECTION_INVALID;
|
2025-07-22 00:19:22 +09:00
|
|
|
struct scroll_info info = compare_delta(delta, delta_discrete,
|
|
|
|
|
&server->seat.accumulated_scrolls[orientation]);
|
2025-01-11 23:36:58 +09:00
|
|
|
|
2025-04-21 18:18:21 +02:00
|
|
|
if (orientation == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
|
2025-01-11 23:36:58 +09:00
|
|
|
if (info.direction < 0) {
|
2022-11-09 05:18:14 +00:00
|
|
|
direction = LAB_DIRECTION_LEFT;
|
2025-01-11 23:36:58 +09:00
|
|
|
} else if (info.direction > 0) {
|
2022-11-09 05:18:14 +00:00
|
|
|
direction = LAB_DIRECTION_RIGHT;
|
|
|
|
|
}
|
2025-04-21 18:18:21 +02:00
|
|
|
} else if (orientation == WL_POINTER_AXIS_VERTICAL_SCROLL) {
|
2025-01-11 23:36:58 +09:00
|
|
|
if (info.direction < 0) {
|
2022-11-09 05:18:14 +00:00
|
|
|
direction = LAB_DIRECTION_UP;
|
2025-01-11 23:36:58 +09:00
|
|
|
} else if (info.direction > 0) {
|
2022-11-09 05:18:14 +00:00
|
|
|
direction = LAB_DIRECTION_DOWN;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
wlr_log(WLR_DEBUG, "Failed to handle cursor axis event");
|
2022-10-25 21:46:46 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-21 18:18:21 +02:00
|
|
|
bool handled = false;
|
|
|
|
|
if (direction != LAB_DIRECTION_INVALID) {
|
|
|
|
|
struct mousebind *mousebind;
|
|
|
|
|
wl_list_for_each(mousebind, &rc.mousebinds, link) {
|
2025-09-03 05:08:52 -04:00
|
|
|
if (ctx.type == LAB_NODE_CLIENT
|
2025-08-02 20:10:09 +09:00
|
|
|
&& view_inhibits_actions(ctx.view, &mousebind->actions)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-09-03 05:08:52 -04:00
|
|
|
if (node_type_contains(mousebind->context, ctx.type)
|
2025-04-21 18:18:21 +02:00
|
|
|
&& mousebind->direction == direction
|
|
|
|
|
&& modifiers == mousebind->modifiers
|
|
|
|
|
&& mousebind->mouse_event == MOUSE_ACTION_SCROLL) {
|
|
|
|
|
handled = true;
|
|
|
|
|
/*
|
2025-07-22 00:19:22 +09:00
|
|
|
* Action may not be executed if the accumulated scroll delta
|
|
|
|
|
* on touchpads or hi-res mice doesn't exceed the threshold
|
2025-04-21 18:18:21 +02:00
|
|
|
*/
|
|
|
|
|
if (info.run_action) {
|
|
|
|
|
actions_run(ctx.view, server, &mousebind->actions, &ctx);
|
|
|
|
|
}
|
2025-01-11 23:36:58 +09:00
|
|
|
}
|
2022-10-25 21:46:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-21 18:18:21 +02:00
|
|
|
/* Bindings swallow mouse events if activated */
|
|
|
|
|
if (ctx.surface && !handled) {
|
|
|
|
|
/* Make sure we are sending the events to the surface under the cursor */
|
|
|
|
|
double sx, sy;
|
|
|
|
|
cursor_update_common(server, &ctx, /*cursor_has_moved*/ false, &sx, &sy);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2022-10-25 21:46:46 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 03:35:13 +01:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_axis(struct wl_listener *listener, void *data)
|
2020-05-29 21:36:12 +01:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* This event is forwarded by the cursor when a pointer emits an axis
|
|
|
|
|
* event, for example when you move the scroll wheel.
|
|
|
|
|
*/
|
2025-04-15 21:43:00 +02:00
|
|
|
struct seat *seat = wl_container_of(listener, seat, on_cursor.axis);
|
2022-10-25 21:46:46 +02:00
|
|
|
struct server *server = seat->server;
|
2025-04-21 18:18:21 +02:00
|
|
|
struct wlr_pointer_axis_event *event = data;
|
|
|
|
|
idle_manager_notify_activity(seat->seat);
|
|
|
|
|
cursor_set_visible(seat, /* visible */ true);
|
2024-08-08 17:29:43 +09:00
|
|
|
|
|
|
|
|
/* input->scroll_factor is set for pointer/touch devices */
|
|
|
|
|
assert(event->pointer->base.type == WLR_INPUT_DEVICE_POINTER
|
|
|
|
|
|| event->pointer->base.type == WLR_INPUT_DEVICE_TOUCH);
|
|
|
|
|
struct input *input = event->pointer->base.data;
|
|
|
|
|
double scroll_factor = input->scroll_factor;
|
|
|
|
|
|
2025-04-21 18:18:21 +02:00
|
|
|
bool notify = process_cursor_axis(server, event->orientation,
|
|
|
|
|
event->delta, event->delta_discrete);
|
2022-09-08 01:30:29 +02:00
|
|
|
|
2025-04-21 18:18:21 +02:00
|
|
|
if (notify) {
|
2022-10-25 21:46:46 +02:00
|
|
|
/* Notify the client with pointer focus of the axis event. */
|
|
|
|
|
wlr_seat_pointer_notify_axis(seat->seat, event->time_msec,
|
2024-08-08 17:29:43 +09:00
|
|
|
event->orientation, scroll_factor * event->delta,
|
|
|
|
|
round(scroll_factor * event->delta_discrete),
|
2024-03-19 01:05:35 +01:00
|
|
|
event->source, event->relative_direction);
|
2022-10-25 21:46:46 +02:00
|
|
|
}
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 03:35:13 +01:00
|
|
|
static void
|
2025-04-15 21:43:00 +02:00
|
|
|
handle_frame(struct wl_listener *listener, void *data)
|
2020-05-29 21:36:12 +01:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* This event is forwarded by the cursor when a pointer emits an frame
|
|
|
|
|
* event. Frame events are sent after regular pointer events to group
|
|
|
|
|
* multiple events together. For instance, two axis events may happen
|
|
|
|
|
* at the same time, in which case a frame event won't be sent in
|
|
|
|
|
* between.
|
|
|
|
|
*/
|
2025-04-15 21:43:00 +02:00
|
|
|
struct seat *seat = wl_container_of(listener, seat, on_cursor.frame);
|
2020-05-29 21:36:12 +01:00
|
|
|
/* Notify the client with pointer focus of the frame event. */
|
2020-10-02 21:19:56 +01:00
|
|
|
wlr_seat_pointer_notify_frame(seat->seat);
|
2020-05-29 21:36:12 +01:00
|
|
|
}
|
2020-05-29 22:18:03 +01:00
|
|
|
|
2025-04-21 18:18:21 +02:00
|
|
|
void
|
|
|
|
|
cursor_emulate_axis(struct seat *seat, struct wlr_input_device *device,
|
|
|
|
|
enum wl_pointer_axis orientation, double delta, double delta_discrete,
|
|
|
|
|
enum wl_pointer_axis_source source, uint32_t time_msec)
|
|
|
|
|
{
|
|
|
|
|
struct server *server = seat->server;
|
|
|
|
|
struct input *input = device->data;
|
|
|
|
|
|
|
|
|
|
double scroll_factor = 1.0;
|
|
|
|
|
/* input->scroll_factor is set for pointer/touch devices */
|
|
|
|
|
if (device->type == WLR_INPUT_DEVICE_POINTER
|
|
|
|
|
|| device->type == WLR_INPUT_DEVICE_TOUCH) {
|
|
|
|
|
scroll_factor = input->scroll_factor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool notify = process_cursor_axis(server, orientation, delta, delta_discrete);
|
|
|
|
|
if (notify) {
|
|
|
|
|
/* Notify the client with pointer focus of the axis event. */
|
|
|
|
|
wlr_seat_pointer_notify_axis(seat->seat, time_msec,
|
|
|
|
|
orientation, scroll_factor * delta,
|
|
|
|
|
round(scroll_factor * delta_discrete),
|
|
|
|
|
source, WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL);
|
|
|
|
|
}
|
|
|
|
|
wlr_seat_pointer_notify_frame(seat->seat);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-02 14:19:08 +02:00
|
|
|
void
|
|
|
|
|
cursor_emulate_move(struct seat *seat, struct wlr_input_device *device,
|
|
|
|
|
double dx, double dy, uint32_t time_msec)
|
|
|
|
|
{
|
|
|
|
|
if (!dx && !dy) {
|
|
|
|
|
wlr_log(WLR_DEBUG, "dropping useless cursor_emulate: %.10f,%.10f", dx, dy);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wlr_relative_pointer_manager_v1_send_relative_motion(
|
|
|
|
|
seat->server->relative_pointer_manager,
|
|
|
|
|
seat->seat, (uint64_t)time_msec * 1000,
|
|
|
|
|
dx, dy, dx, dy);
|
|
|
|
|
|
|
|
|
|
wlr_cursor_move(seat->cursor, device, dx, dy);
|
|
|
|
|
double sx, sy;
|
|
|
|
|
bool notify = cursor_process_motion(seat->server, time_msec, &sx, &sy);
|
|
|
|
|
if (notify) {
|
|
|
|
|
wlr_seat_pointer_notify_motion(seat->seat, time_msec, sx, sy);
|
|
|
|
|
}
|
|
|
|
|
wlr_seat_pointer_notify_frame(seat->seat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
cursor_emulate_move_absolute(struct seat *seat, struct wlr_input_device *device,
|
|
|
|
|
double x, double y, uint32_t time_msec)
|
|
|
|
|
{
|
|
|
|
|
double lx, ly;
|
|
|
|
|
wlr_cursor_absolute_to_layout_coords(seat->cursor,
|
|
|
|
|
device, x, y, &lx, &ly);
|
|
|
|
|
|
|
|
|
|
double dx = lx - seat->cursor->x;
|
|
|
|
|
double dy = ly - seat->cursor->y;
|
|
|
|
|
|
|
|
|
|
cursor_emulate_move(seat, device, dx, dy, time_msec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
cursor_emulate_button(struct seat *seat, uint32_t button,
|
|
|
|
|
enum wl_pointer_button_state state, uint32_t time_msec)
|
|
|
|
|
{
|
|
|
|
|
bool notify;
|
|
|
|
|
switch (state) {
|
|
|
|
|
case WL_POINTER_BUTTON_STATE_PRESSED:
|
|
|
|
|
notify = cursor_process_button_press(seat, button, time_msec);
|
|
|
|
|
if (notify) {
|
|
|
|
|
wlr_seat_pointer_notify_button(seat->seat, time_msec, button, state);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case WL_POINTER_BUTTON_STATE_RELEASED:
|
|
|
|
|
notify = cursor_process_button_release(seat, button, time_msec);
|
|
|
|
|
if (notify) {
|
|
|
|
|
wlr_seat_pointer_notify_button(seat->seat, time_msec, button, state);
|
|
|
|
|
}
|
|
|
|
|
cursor_finish_button_release(seat, button);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
wlr_seat_pointer_notify_frame(seat->seat);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-03 14:18:43 +02:00
|
|
|
static void
|
2024-03-12 19:53:41 +00:00
|
|
|
cursor_load(struct seat *seat)
|
2020-05-29 22:18:03 +01:00
|
|
|
{
|
2021-11-28 22:50:37 +00:00
|
|
|
const char *xcursor_theme = getenv("XCURSOR_THEME");
|
|
|
|
|
const char *xcursor_size = getenv("XCURSOR_SIZE");
|
|
|
|
|
uint32_t size = xcursor_size ? atoi(xcursor_size) : 24;
|
|
|
|
|
|
2024-03-12 19:53:41 +00:00
|
|
|
if (seat->xcursor_manager) {
|
|
|
|
|
wlr_xcursor_manager_destroy(seat->xcursor_manager);
|
|
|
|
|
}
|
2021-11-28 22:50:37 +00:00
|
|
|
seat->xcursor_manager = wlr_xcursor_manager_create(xcursor_theme, size);
|
2020-10-02 21:19:56 +01:00
|
|
|
wlr_xcursor_manager_load(seat->xcursor_manager, 1);
|
|
|
|
|
|
2023-02-12 00:45:12 +01:00
|
|
|
/*
|
|
|
|
|
* Wlroots provides integrated fallback cursor icons using
|
|
|
|
|
* old-style X11 cursor names (cursors_x11) and additionally
|
|
|
|
|
* (since wlroots 0.16.2) aliases them to cursor-spec names
|
|
|
|
|
* (cursors_xdg).
|
|
|
|
|
*
|
|
|
|
|
* However, the aliasing does not include the "grab" cursor
|
|
|
|
|
* icon which labwc uses when dragging a window. To fix that,
|
|
|
|
|
* try to get the grab cursor icon from wlroots. If the user
|
|
|
|
|
* supplied an appropriate cursor theme which includes the
|
|
|
|
|
* "grab" cursor icon, we will keep using it.
|
|
|
|
|
*
|
|
|
|
|
* If no "grab" icon can be found we will fall back to the
|
|
|
|
|
* old style cursor names and use "grabbing" instead which
|
|
|
|
|
* is part of the X11 fallbacks and thus always available.
|
|
|
|
|
*
|
|
|
|
|
* Shipping the complete alias table for X11 cursor names
|
|
|
|
|
* (and not just the "grab" cursor alias) makes sure that
|
|
|
|
|
* this also works for wlroots versions before 0.16.2.
|
|
|
|
|
*
|
|
|
|
|
* See the cursor name alias table on the top of this file
|
|
|
|
|
* for the actual cursor names used.
|
|
|
|
|
*/
|
2022-09-16 02:23:44 +02:00
|
|
|
if (wlr_xcursor_manager_get_xcursor(seat->xcursor_manager,
|
2023-02-11 10:38:43 +01:00
|
|
|
cursors_xdg[LAB_CURSOR_GRAB], 1)) {
|
2022-09-16 02:23:44 +02:00
|
|
|
cursor_names = cursors_xdg;
|
|
|
|
|
} else {
|
2022-09-10 23:53:35 +02:00
|
|
|
wlr_log(WLR_INFO,
|
2022-09-16 02:23:44 +02:00
|
|
|
"Cursor theme is missing cursor names, using fallback");
|
|
|
|
|
cursor_names = cursors_x11;
|
2022-09-10 23:53:35 +02:00
|
|
|
}
|
2024-03-12 19:53:41 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-03 14:18:43 +02:00
|
|
|
void
|
|
|
|
|
cursor_reload(struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
cursor_load(seat);
|
2024-06-10 00:01:54 +02:00
|
|
|
#if HAVE_XWAYLAND
|
|
|
|
|
xwayland_reset_cursor(seat->server);
|
|
|
|
|
#endif
|
2024-05-03 14:18:43 +02:00
|
|
|
cursor_update_image(seat);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 19:53:41 +00:00
|
|
|
void
|
|
|
|
|
cursor_init(struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
cursor_load(seat);
|
2022-09-10 23:53:35 +02:00
|
|
|
|
2023-03-28 19:23:49 +02:00
|
|
|
/* Set the initial cursor image so the cursor is visible right away */
|
|
|
|
|
cursor_set(seat, LAB_CURSOR_DEFAULT);
|
|
|
|
|
|
2022-09-18 05:40:52 +02:00
|
|
|
dnd_init(seat);
|
|
|
|
|
|
2025-04-15 21:43:00 +02:00
|
|
|
CONNECT_SIGNAL(seat->cursor, &seat->on_cursor, motion);
|
|
|
|
|
CONNECT_SIGNAL(seat->cursor, &seat->on_cursor, motion_absolute);
|
|
|
|
|
CONNECT_SIGNAL(seat->cursor, &seat->on_cursor, button);
|
|
|
|
|
CONNECT_SIGNAL(seat->cursor, &seat->on_cursor, axis);
|
|
|
|
|
CONNECT_SIGNAL(seat->cursor, &seat->on_cursor, frame);
|
2020-10-02 21:19:56 +01:00
|
|
|
|
2023-09-03 18:56:20 +02:00
|
|
|
gestures_init(seat);
|
2023-09-03 19:07:55 +02:00
|
|
|
touch_init(seat);
|
2024-08-09 09:25:00 +02:00
|
|
|
tablet_init(seat);
|
|
|
|
|
|
2025-04-15 21:43:00 +02:00
|
|
|
CONNECT_SIGNAL(seat->seat, seat, request_set_cursor);
|
2023-12-02 17:56:32 +03:00
|
|
|
|
|
|
|
|
struct wlr_cursor_shape_manager_v1 *cursor_shape_manager =
|
|
|
|
|
wlr_cursor_shape_manager_v1_create(seat->server->wl_display,
|
|
|
|
|
LAB_CURSOR_SHAPE_V1_VERSION);
|
|
|
|
|
if (!cursor_shape_manager) {
|
|
|
|
|
wlr_log(WLR_ERROR, "unable to create cursor_shape interface");
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
2020-10-02 21:19:56 +01:00
|
|
|
|
2025-04-15 21:43:00 +02:00
|
|
|
CONNECT_SIGNAL(cursor_shape_manager, seat, request_set_shape);
|
|
|
|
|
CONNECT_SIGNAL(seat->seat, seat, request_set_selection);
|
|
|
|
|
CONNECT_SIGNAL(seat->seat, seat, request_set_primary_selection);
|
2020-05-29 22:18:03 +01:00
|
|
|
}
|
2021-12-31 23:30:25 +00:00
|
|
|
|
2022-01-02 15:51:40 +00:00
|
|
|
void cursor_finish(struct seat *seat)
|
|
|
|
|
{
|
2025-04-15 21:43:00 +02:00
|
|
|
wl_list_remove(&seat->on_cursor.motion.link);
|
|
|
|
|
wl_list_remove(&seat->on_cursor.motion_absolute.link);
|
|
|
|
|
wl_list_remove(&seat->on_cursor.button.link);
|
|
|
|
|
wl_list_remove(&seat->on_cursor.axis.link);
|
|
|
|
|
wl_list_remove(&seat->on_cursor.frame.link);
|
2021-12-31 23:30:25 +00:00
|
|
|
|
2023-09-03 18:56:20 +02:00
|
|
|
gestures_finish(seat);
|
2023-09-03 19:07:55 +02:00
|
|
|
touch_finish(seat);
|
2021-12-31 23:34:08 +00:00
|
|
|
|
2024-08-09 09:25:00 +02:00
|
|
|
tablet_finish(seat);
|
|
|
|
|
|
2025-04-15 21:43:00 +02:00
|
|
|
wl_list_remove(&seat->request_set_cursor.link);
|
2023-12-02 17:56:32 +03:00
|
|
|
wl_list_remove(&seat->request_set_shape.link);
|
2021-12-31 23:30:25 +00:00
|
|
|
wl_list_remove(&seat->request_set_selection.link);
|
2025-02-08 15:48:50 +01:00
|
|
|
wl_list_remove(&seat->request_set_primary_selection.link);
|
2021-12-31 23:30:25 +00:00
|
|
|
|
|
|
|
|
wlr_xcursor_manager_destroy(seat->xcursor_manager);
|
|
|
|
|
wlr_cursor_destroy(seat->cursor);
|
2022-09-18 05:40:52 +02:00
|
|
|
|
|
|
|
|
dnd_finish(seat);
|
2021-12-31 23:30:25 +00:00
|
|
|
}
|