diff --git a/gdk/wayland/gdkcursor-wayland.c b/gdk/wayland/gdkcursor-wayland.c index 6a5e2cf53d..37e169a1da 100644 --- a/gdk/wayland/gdkcursor-wayland.c +++ b/gdk/wayland/gdkcursor-wayland.c @@ -211,6 +211,13 @@ gdk_wayland_cursor_get_surface (GdkCursor *cursor, return NULL; } +const char * +_gdk_wayland_cursor_get_name (GdkCursor *cursor) +{ + GdkWaylandCursor *wayland_cursor = GDK_WAYLAND_CURSOR (cursor); + return wayland_cursor->name; +} + struct wl_buffer * _gdk_wayland_cursor_get_buffer (GdkCursor *cursor, guint image_index, diff --git a/gdk/wayland/gdkdevice-wayland.c b/gdk/wayland/gdkdevice-wayland.c index 0fc3ec3e15..52c3e4fa95 100644 --- a/gdk/wayland/gdkdevice-wayland.c +++ b/gdk/wayland/gdkdevice-wayland.c @@ -35,6 +35,7 @@ #include "gdkseatprivate.h" #include "pointer-gestures-unstable-v1-client-protocol.h" #include "tablet-unstable-v2-client-protocol.h" +#include "cursor-shape-v1-client-protocol.h" #include @@ -99,6 +100,7 @@ struct _GdkWaylandPointerData { uint32_t grab_time; struct wl_surface *pointer_surface; + struct wp_cursor_shape_device_v1 *shape_device; GdkCursor *cursor; guint cursor_timeout_id; guint cursor_image_index; @@ -115,6 +117,7 @@ struct _GdkWaylandTabletToolData { GdkSeat *seat; struct zwp_tablet_tool_v2 *wp_tablet_tool; + struct wp_cursor_shape_device_v1 *shape_device; GdkAxisFlags axes; GdkDeviceToolType type; guint64 hardware_serial; @@ -406,6 +409,77 @@ gdk_wayland_device_manager_find_pad (GdkWaylandSeat *seat, } +static const struct +{ + const char *cursor_name; + unsigned int shape; + unsigned int version; +} shape_map[] = { + { "default", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT, 1 }, + { "context-menu", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU, 1 }, + { "help", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP, 1 }, + { "pointer", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER, 1 }, + { "progress", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS }, + { "wait", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT, 1 }, + { "cell", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL, 1 }, + { "crosshair", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR, 1 }, + { "text", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, 1 }, + { "vertical-text", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT, 1 }, + { "alias", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS, 1 }, + { "copy", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY, 1 }, + { "move", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE, 1 }, + { "no-drop", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP, 1 }, + { "not-allowed", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED, 1 }, + { "grab", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB, 1 }, + { "grabbing", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING, 1 }, + { "e-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE, 1 }, + { "n-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE, 1 }, + { "ne-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE, 1 }, + { "nw-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE, 1 }, + { "s-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE, 1 }, + { "se-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE, 1 }, + { "sw-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE, 1 }, + { "w-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE, 1 }, + { "ew-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE, 1 }, + { "ns-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE, 1 }, + { "nesw-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE, 1 }, + { "nwse-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE, 1 }, + { "col-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE, 1 }, + { "row-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE, 1 }, + { "all-scroll", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL, 1 }, + { "zoom-in", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN, 1 }, + { "zoom-out", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT, 1 }, + { "all-scroll", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL, 1 }, + /* the following a v2 additions, with a fallback for v1 */ + { "dnd-ask", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK, 2 }, + { "dnd-ask", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU, 1 }, + { "all-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE, 2 }, + { "all-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE, 1 }, + { "none", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NONE, 2 }, + { "none", 0, 1 }, +}; + +static unsigned int +_gdk_wayland_cursor_get_shape (GdkCursor *cursor, + int version) +{ + gsize i; + const char *cursor_name; + + cursor_name = _gdk_wayland_cursor_get_name (cursor); + if (cursor_name == NULL) + return 0; + + for (i = 0; i < G_N_ELEMENTS (shape_map); i++) + { + if (g_str_equal (shape_map[i].cursor_name, cursor_name) && + version >= shape_map[i].version) + return shape_map[i].shape; + } + + return 0; +} + static gboolean gdk_wayland_device_update_window_cursor (GdkDevice *device) { @@ -416,29 +490,50 @@ gdk_wayland_device_update_window_cursor (GdkDevice *device) guint next_image_index, next_image_delay; gboolean retval = G_SOURCE_REMOVE; GdkWaylandTabletData *tablet; + unsigned int shape; tablet = gdk_wayland_device_manager_find_tablet (seat, device); + if (!pointer->cursor) + { + pointer->cursor_timeout_id = 0; + return G_SOURCE_REMOVE; + } + + if (tablet && !tablet->current_tool) + { + pointer->cursor_timeout_id = 0; + return G_SOURCE_REMOVE; + } + + if (GDK_WAYLAND_DISPLAY (seat->display)->cursor_shape) + { + shape = _gdk_wayland_cursor_get_shape (pointer->cursor, + wp_cursor_shape_manager_v1_get_version (GDK_WAYLAND_DISPLAY (seat->display)->cursor_shape)); + if (shape != 0) + { + if (tablet && tablet->current_tool->shape_device) + { + wp_cursor_shape_device_v1_set_shape (tablet->current_tool->shape_device, pointer->enter_serial, shape); + return G_SOURCE_REMOVE; + } + else if (seat->wl_pointer && pointer->shape_device) + { + wp_cursor_shape_device_v1_set_shape (pointer->shape_device, pointer->enter_serial, shape); + return G_SOURCE_REMOVE; + } + } + } + if (pointer->cursor) { buffer = _gdk_wayland_cursor_get_buffer (pointer->cursor, pointer->cursor_image_index, &x, &y, &w, &h, &scale); } - else - { - pointer->cursor_timeout_id = 0; - return G_SOURCE_REMOVE; - } if (tablet) { - if (!tablet->current_tool) - { - pointer->cursor_timeout_id = 0; - return G_SOURCE_REMOVE; - } - zwp_tablet_tool_v2_set_cursor (tablet->current_tool->wp_tablet_tool, pointer->enter_serial, pointer->pointer_surface, @@ -2856,6 +2951,8 @@ static void _gdk_wayland_seat_remove_tool (GdkWaylandSeat *seat, GdkWaylandTabletToolData *tool) { + g_clear_pointer (&tool->shape_device, wp_cursor_shape_device_v1_destroy); + seat->tablet_tools = g_list_remove (seat->tablet_tools, tool); gdk_seat_tool_removed (GDK_SEAT (seat), tool->tool); @@ -3185,6 +3282,12 @@ seat_handle_capabilities (void *data, &gesture_pinch_listener, seat); } + if (display_wayland->cursor_shape) + { + seat->pointer_info.shape_device = + wp_cursor_shape_manager_v1_get_pointer (display_wayland->cursor_shape, seat->wl_pointer); + } + g_signal_emit_by_name (device_manager, "device-added", seat->pointer); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer) @@ -3231,6 +3334,8 @@ seat_handle_capabilities (void *data, g_signal_emit_by_name (device_manager, "device-removed", seat->continuous_scrolling); g_clear_object (&seat->continuous_scrolling); } + + g_clear_pointer (&seat->pointer_info.shape_device, wp_cursor_shape_device_v1_destroy); } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !seat->wl_keyboard) @@ -4625,6 +4730,7 @@ tablet_seat_handle_tool_added (void *data, { GdkWaylandSeat *seat = data; GdkWaylandTabletToolData *tool; + GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (seat->display); tool = g_new0 (GdkWaylandTabletToolData, 1); tool->wp_tablet_tool = wp_tablet_tool; @@ -4634,6 +4740,13 @@ tablet_seat_handle_tool_added (void *data, zwp_tablet_tool_v2_set_user_data (wp_tablet_tool, tool); seat->tablet_tools = g_list_prepend (seat->tablet_tools, tool); + + if (display->cursor_shape) + { + tool->shape_device = + wp_cursor_shape_manager_v1_get_tablet_tool_v2 ( + display->cursor_shape, tool->wp_tablet_tool); + } } static void diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c index 8b4c3496b3..7da4ed01bf 100644 --- a/gdk/wayland/gdkdisplay-wayland.c +++ b/gdk/wayland/gdkdisplay-wayland.c @@ -553,6 +553,12 @@ gdk_registry_handle_global (void *data, display_wayland->xdg_activation_version); } #endif + else if (strcmp (interface, wp_cursor_shape_manager_v1_interface.name) == 0) + { + display_wayland->cursor_shape = + wl_registry_bind (display_wayland->wl_registry, id, + &wp_cursor_shape_manager_v1_interface, 1); + } g_hash_table_insert (display_wayland->known_globals, GUINT_TO_POINTER (id), g_strdup (interface)); diff --git a/gdk/wayland/gdkdisplay-wayland.h b/gdk/wayland/gdkdisplay-wayland.h index eb5e4b3b31..d379d1c0d5 100644 --- a/gdk/wayland/gdkdisplay-wayland.h +++ b/gdk/wayland/gdkdisplay-wayland.h @@ -40,6 +40,7 @@ #ifdef HAVE_XDG_ACTIVATION #include #endif +#include #include #include @@ -108,6 +109,7 @@ struct _GdkWaylandDisplay #ifdef HAVE_XDG_ACTIVATION struct xdg_activation_v1 *xdg_activation; #endif + struct wp_cursor_shape_manager_v1 *cursor_shape; GList *async_roundtrips; diff --git a/gdk/wayland/gdkprivate-wayland.h b/gdk/wayland/gdkprivate-wayland.h index 64aa708b2d..1916a1283c 100644 --- a/gdk/wayland/gdkprivate-wayland.h +++ b/gdk/wayland/gdkprivate-wayland.h @@ -89,6 +89,8 @@ gboolean _gdk_wayland_display_supports_cursor_color (GdkDisplay *display); void gdk_wayland_display_system_bell (GdkDisplay *display, GdkWindow *window); +const char *_gdk_wayland_cursor_get_name (GdkCursor *cursor); + struct wl_buffer *_gdk_wayland_cursor_get_buffer (GdkCursor *cursor, guint image_index, int *hotspot_x, diff --git a/gdk/wayland/meson.build b/gdk/wayland/meson.build index af7ea1f698..ef07d135de 100644 --- a/gdk/wayland/meson.build +++ b/gdk/wayland/meson.build @@ -59,6 +59,7 @@ proto_sources = [ ['server-decoration', 'private' ], ['xdg-output', 'unstable', 'v1', ], ['primary-selection', 'unstable', 'v1', ], + ['cursor-shape-v1', 'private', ], ] if wlprotocolsdep.version().version_compare('>=1.21') proto_sources += [['xdg-activation', 'staging', 'v1', ]] diff --git a/gdk/wayland/protocol/cursor-shape-v1.xml b/gdk/wayland/protocol/cursor-shape-v1.xml new file mode 100644 index 0000000000..ea55c4d12a --- /dev/null +++ b/gdk/wayland/protocol/cursor-shape-v1.xml @@ -0,0 +1,163 @@ + + + + Copyright 2018 The Chromium Authors + Copyright 2023 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This global offers an alternative, optional way to set cursor images. This + new way uses enumerated cursors instead of a wl_surface like + wl_pointer.set_cursor does. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Destroy the cursor shape manager. + + + + + + Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. + + When the pointer capability is removed from the wl_seat, the + wp_cursor_shape_device_v1 object becomes inert. + + + + + + + + Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object. + + When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1 + object becomes inert. + + + + + + + + + This interface allows clients to set the cursor shape. + + + + + This enum describes cursor shapes. + + The names are taken from the CSS W3C specification: + https://w3c.github.io/csswg-drafts/css-ui/#cursor + with a few additions. + + Note that there are some groups of cursor shapes that are related: + The first group is drag-and-drop cursors which are used to indicate + the selected action during dnd operations. The second group is resize + cursors which are used to indicate resizing and moving possibilities + on window borders. It is recommended that the shapes in these groups + should use visually compatible images and metaphors. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Destroy the cursor shape device. + + The device cursor shape remains unchanged. + + + + + + Sets the device cursor to the specified shape. The compositor will + change the cursor image based on the specified shape. + + The cursor actually changes only if the input device focus is one of + the requesting client's surfaces. If any, the previous cursor image + (surface or shape) is replaced. + + The "shape" argument must be a valid enum entry, otherwise the + invalid_shape protocol error is raised. + + This is similar to the wl_pointer.set_cursor and + zwp_tablet_tool_v2.set_cursor requests, but this request accepts a + shape instead of contents in the form of a surface. Clients can mix + set_cursor and set_shape requests. + + The serial parameter must match the latest wl_pointer.enter or + zwp_tablet_tool_v2.proximity_in serial number sent to the client. + Otherwise the request will be ignored. + + + + + +