diff --git a/docs/reference/gdk/images/all_resize_cursor.png b/docs/reference/gdk/images/all_resize_cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..6a8fbcd22bbc498ef422423e05dfc65dccaf233d Binary files /dev/null and b/docs/reference/gdk/images/all_resize_cursor.png differ diff --git a/docs/reference/gdk/images/dnd_ask_cursor.png b/docs/reference/gdk/images/dnd_ask_cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..0b626c97e5dea71bc484fb61e6a9ca30ed5bb122 Binary files /dev/null and b/docs/reference/gdk/images/dnd_ask_cursor.png differ diff --git a/docs/reference/gdk/meson.build b/docs/reference/gdk/meson.build index 96a993e066839a81b7e4cb98fd5c26a94df28bf2..a2c312e5e31189cb4eddc5c4c9d49c01c4e87147 100644 --- a/docs/reference/gdk/meson.build +++ b/docs/reference/gdk/meson.build @@ -142,6 +142,8 @@ images = [ 'images/X_cursor.png', 'images/zoom_in_cursor.png', 'images/zoom_out_cursor.png', + 'images/dnd_ask_cursor.png', + 'images/all_resize_cursor.png', ] src_dir = [ gdkinc ] diff --git a/gdk/gdkcursor.c b/gdk/gdkcursor.c index 367c531be8c9e43f69e624477972074c1b5f4a89..537003cb1070f1f8f741ba95be02e1a47c25ece2 100644 --- a/gdk/gdkcursor.c +++ b/gdk/gdkcursor.c @@ -285,6 +285,11 @@ gdk_cursor_new_for_display (GdkDisplay *display, * - ![](zoom_in_cursor.png) "zoom-in" * - ![](zoom_out_cursor.png) "zoom-out" * + * Additionally, the following cursor names are supported, which are + * not in the CSS specification: + * - ![](dnd_ask_cursor.png) "dnd-ask" + * - ![](all_resize_cursor.png) "all-resize" + * * * Returns: (nullable): a new #GdkCursor, or %NULL if there is no * cursor with the given name diff --git a/gdk/wayland/gdkcursor-wayland.c b/gdk/wayland/gdkcursor-wayland.c index 7e02be9be481b4fa7ae83dc37f68e0995dff2b71..37e169a1daf655fa77fb4fa4b2759f9fb8bdd54d 100644 --- a/gdk/wayland/gdkcursor-wayland.c +++ b/gdk/wayland/gdkcursor-wayland.c @@ -122,6 +122,7 @@ static const struct { { "nwse-resize", "bd_double_arrow" }, { "zoom-in", "left_ptr" }, { "zoom-out", "left_ptr" }, + { "all-resize", "move" }, /* not CSS, but we want to guarantee it anyway */ { NULL, NULL } }; @@ -210,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 0fc3ec3e15c416bad9cb2fd021df7edc12adad9c..0b0c966225faf8c25dfdbc241a6dc6d8420fa2c1 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,76 @@ 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 }, +}; + +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 || + g_str_equal (cursor_name, "none")) + 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 +489,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) + if (!pointer->cursor) { - buffer = _gdk_wayland_cursor_get_buffer (pointer->cursor, - pointer->cursor_image_index, - &x, &y, &w, &h, &scale); + pointer->cursor_timeout_id = 0; + return G_SOURCE_REMOVE; } - else + + if (tablet && !tablet->current_tool) { pointer->cursor_timeout_id = 0; return G_SOURCE_REMOVE; } - if (tablet) + if (GDK_WAYLAND_DISPLAY (seat->display)->cursor_shape) { - if (!tablet->current_tool) + 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) { - pointer->cursor_timeout_id = 0; - return G_SOURCE_REMOVE; + 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); + } + + if (tablet) + { zwp_tablet_tool_v2_set_cursor (tablet->current_tool->wp_tablet_tool, pointer->enter_serial, pointer->pointer_surface, @@ -2856,6 +2950,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 +3281,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 +3333,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 +4729,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 +4739,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 8b4c3496b3f82eeff252993aa6087a94d49c5e05..7da4ed01bf4466df06f7ebe7895a722e1bf01dec 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 eb5e4b3b31060228ab1734294ce4e42bee4ea7eb..d379d1c0d56fd6ef64006c0796b4e6caa69a5d65 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 64aa708b2d3c0909936cfc7a576491c7925ba4f0..1916a1283c199218b8ec6645447597580cf588f3 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 af7ea1f69819d342af3d0ff55aebfa4e0bc0f168..ef07d135decb4bbbc17c443d6e222bc19240013b 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 0000000000000000000000000000000000000000..ea55c4d12af4e38054eed7ced88afc3ca5c21bb9 --- /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. + + + + + + diff --git a/gdk/x11/gdkcursor-x11.c b/gdk/x11/gdkcursor-x11.c index 5555ea01b2a07304cc89136a0cbcb76ec2c090dd..7f0bb8d805f02e519e315699b98a35b415ba2c7c 100644 --- a/gdk/x11/gdkcursor-x11.c +++ b/gdk/x11/gdkcursor-x11.c @@ -650,6 +650,7 @@ static const struct { { "nwse-resize", "bd_double_arrow" }, { "zoom-in", "left_ptr" }, { "zoom-out", "left_ptr" }, + { "all-resize", "move" }, /* not CSS, but we want to guarantee it anyway */ { NULL, NULL } };