From c8e13ad3938d68635693d0b0f4bf1054628e8387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 17:25:57 +0200 Subject: [PATCH] cursor-shape: add support for server side cursor shapes This implements support for the new cursor-shape-v1 protocol. When available, we use it, instead of client-side cursor surfaces, to select the xcursor shape. Note that we still need to keep client side pointers, for: * backward compatibility * to be able to "hide" the cursor Closes #1379 --- CHANGELOG.md | 4 ++ cursor-shape-v1.xml | 147 ++++++++++++++++++++++++++++++++++++++++++++ cursor-shape.c | 25 +++++++- cursor-shape.h | 13 +++- meson.build | 6 ++ render.c | 47 +++++++++----- wayland.c | 46 ++++++++++++++ wayland.h | 12 +++- 8 files changed, 280 insertions(+), 20 deletions(-) create mode 100644 cursor-shape-v1.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b47f09..6625c56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,10 @@ * Support for the new fractional-scaling-v1 Wayland protocol. This brings true fractional scaling to Wayland in general, and with this release, foot. +* Support for the new `cursor-shape-v1` Wayland protocol, i.e. server + side cursor shapes ([#1379][1379]). + +[1379]: https://codeberg.org/dnkl/foot/issues/1379 ### Changed diff --git a/cursor-shape-v1.xml b/cursor-shape-v1.xml new file mode 100644 index 00000000..56f6a1a6 --- /dev/null +++ b/cursor-shape-v1.xml @@ -0,0 +1,147 @@ + + + + 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. + + + + + + + + Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object. + + + + + + + + + This interface advertises the list of supported cursor shapes for a + device, and 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/cursor-shape.c b/cursor-shape.c index 152f176f..cd9ba221 100644 --- a/cursor-shape.c +++ b/cursor-shape.c @@ -7,7 +7,7 @@ const char * cursor_shape_to_string(enum cursor_shape shape) { - static const char *const table[] = { + static const char *const table[CURSOR_SHAPE_COUNT] = { [CURSOR_SHAPE_NONE] = NULL, [CURSOR_SHAPE_HIDDEN] = "hidden", [CURSOR_SHAPE_LEFT_PTR] = "left_ptr", @@ -27,3 +27,26 @@ cursor_shape_to_string(enum cursor_shape shape) xassert(shape <= ALEN(table)); return table[shape]; } + +#if defined(HAVE_CURSOR_SHAPE) +enum wp_cursor_shape_device_v1_shape +cursor_shape_to_server_shape(enum cursor_shape shape) +{ + static const enum wp_cursor_shape_device_v1_shape table[CURSOR_SHAPE_COUNT] = { + [CURSOR_SHAPE_LEFT_PTR] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT, + [CURSOR_SHAPE_TEXT] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, + [CURSOR_SHAPE_TEXT_FALLBACK] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, + [CURSOR_SHAPE_TOP_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE, + [CURSOR_SHAPE_TOP_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE, + [CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE, + [CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE, + [CURSOR_SHAPE_LEFT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE, + [CURSOR_SHAPE_RIGHT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE, + [CURSOR_SHAPE_TOP_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE, + [CURSOR_SHAPE_BOTTOM_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE, + }; + + xassert(shape <= ALEN(table)); + return table[shape]; +} +#endif diff --git a/cursor-shape.h b/cursor-shape.h index fb79e45a..0cb0b4d8 100644 --- a/cursor-shape.h +++ b/cursor-shape.h @@ -1,10 +1,14 @@ #pragma once +#if defined(HAVE_CURSOR_SHAPE) +#include +#endif + enum cursor_shape { CURSOR_SHAPE_NONE, CURSOR_SHAPE_CUSTOM, - CURSOR_SHAPE_HIDDEN, + CURSOR_SHAPE_LEFT_PTR, CURSOR_SHAPE_TEXT, CURSOR_SHAPE_TEXT_FALLBACK, @@ -16,6 +20,13 @@ enum cursor_shape { CURSOR_SHAPE_RIGHT_SIDE, CURSOR_SHAPE_TOP_SIDE, CURSOR_SHAPE_BOTTOM_SIDE, + + CURSOR_SHAPE_COUNT, }; const char *cursor_shape_to_string(enum cursor_shape shape); + +#if defined(HAVE_CURSOR_SHAPE) +enum wp_cursor_shape_device_v1_shape cursor_shape_to_server_shape( + enum cursor_shape shape); +#endif diff --git a/meson.build b/meson.build index 9560504f..ed0bf4a0 100644 --- a/meson.build +++ b/meson.build @@ -170,6 +170,12 @@ else fractional_scale = false endif +# TODO: check wayland-protocols version +wl_proto_xml += [wayland_protocols_datadir + '/unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1 + 'cursor-shape-v1.xml', # TODO: use wayland-protocols + ] +add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') + foreach prot : wl_proto_xml wl_proto_headers += custom_target( prot.underscorify() + '-client-header', diff --git a/render.c b/render.c index fedc3467..ab1ddc51 100644 --- a/render.c +++ b/render.c @@ -31,6 +31,7 @@ #include "box-drawing.h" #include "char32.h" #include "config.h" +#include "cursor-shape.h" #include "grid.h" #include "hsl.h" #include "ime.h" @@ -4259,35 +4260,46 @@ render_xcursor_update(struct seat *seat) if (seat->pointer.shape == CURSOR_SHAPE_HIDDEN) { /* Hide cursor */ wl_surface_attach(seat->pointer.surface.surf, NULL, 0, 0); + wl_pointer_set_cursor( + seat->wl_pointer, seat->pointer.serial, seat->pointer.surface.surf, + 0, 0); wl_surface_commit(seat->pointer.surface.surf); return; } xassert(seat->pointer.cursor != NULL); - const float scale = seat->pointer.scale; - struct wl_cursor_image *image = seat->pointer.cursor->images[0]; - struct wl_buffer *buf = wl_cursor_image_get_buffer(image); +#if defined(HAVE_CURSOR_SHAPE) + if (seat->pointer.shape_device != NULL) { + wp_cursor_shape_device_v1_set_shape( + seat->pointer.shape_device, + seat->pointer.serial, + cursor_shape_to_server_shape(seat->pointer.shape)); + } else +#endif + { + const int scale = seat->pointer.scale; + struct wl_cursor_image *image = seat->pointer.cursor->images[0]; - wayl_surface_scale_explicit_width_height( - seat->mouse_focus->window, - &seat->pointer.surface, image->width, image->height, scale); + wl_surface_attach( + seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); - wl_surface_attach(seat->pointer.surface.surf, buf, 0, 0); + wl_pointer_set_cursor( + seat->wl_pointer, seat->pointer.serial, + seat->pointer.surface.surf, + image->hotspot_x / scale, image->hotspot_y / scale); - wl_pointer_set_cursor( - seat->wl_pointer, seat->pointer.serial, - seat->pointer.surface.surf, - image->hotspot_x / scale, image->hotspot_y / scale); + wl_surface_damage_buffer( + seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); - wl_surface_damage_buffer( - seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_set_buffer_scale(seat->pointer.surface.surf, scale); - xassert(seat->pointer.xcursor_callback == NULL); - seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf); - wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); + xassert(seat->pointer.xcursor_callback == NULL); + seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface.surf); + wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat); - wl_surface_commit(seat->pointer.surface.surf); + wl_surface_commit(seat->pointer.surface.surf); + } } static void @@ -4452,6 +4464,7 @@ render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape s if (seat->pointer.shape == shape) return true; + /* TODO: skip this when using server-side cursors */ if (shape != CURSOR_SHAPE_HIDDEN) { const char *const xcursor = cursor_shape_to_string(shape); const char *const fallback = diff --git a/wayland.c b/wayland.c index 5160240b..f3a5619d 100644 --- a/wayland.c +++ b/wayland.c @@ -14,6 +14,10 @@ #include #include +#if defined(HAVE_CURSOR_SHAPE) +#include +#endif + #include #define LOG_MODULE "wayland" @@ -209,6 +213,11 @@ seat_destroy(struct seat *seat) if (seat->data_device != NULL) wl_data_device_release(seat->data_device); +#if defined(HAVE_CURSOR_SHAPE) + if (seat->pointer.shape_device != NULL) + wp_cursor_shape_device_v1_destroy(seat->pointer.shape_device); +#endif + if (seat->wl_keyboard != NULL) wl_keyboard_release(seat->wl_keyboard); if (seat->wl_pointer != NULL) @@ -316,9 +325,22 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, seat->wl_pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); + +#if defined(HAVE_CURSOR_SHAPE) + if (seat->wayl->cursor_shape_manager != NULL) { + xassert(seat->pointer.shape_device == NULL); + seat->pointer.shape_device = wp_cursor_shape_manager_v1_get_pointer( + seat->wayl->cursor_shape_manager, seat->wl_pointer); + } +#endif } } else { if (seat->wl_pointer != NULL) { +#if defined(HAVE_CURSOR_SHAPE) + wp_cursor_shape_device_v1_destroy(seat->pointer.shape_device); + seat->pointer.shape_device = NULL; +#endif + wl_pointer_release(seat->wl_pointer); wl_surface_destroy(seat->pointer.surface.surf); @@ -1167,6 +1189,17 @@ handle_global(void *data, struct wl_registry *registry, } #endif +#if defined(HAVE_CURSOR_SHAPE) + else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->cursor_shape_manager = wl_registry_bind( + wayl->registry, name, &wp_cursor_shape_manager_v1_interface, required); + } +#endif + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { const uint32_t required = 1; @@ -1401,6 +1434,15 @@ wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager, LOG_WARN("fractional scaling not available"); } +#if defined(HAVE_CURSOR_SHAPE) + if (wayl->cursor_shape_manager == NULL) { +#else + if (true) { +#endif + LOG_WARN("no server-side cursors available, " + "falling back to client-side cursors"); + } + if (presentation_timings && wayl->presentation == NULL) { LOG_ERR("presentation time interface not implemented by compositor"); goto out; @@ -1495,6 +1537,10 @@ wayl_destroy(struct wayland *wayl) if (wayl->viewporter != NULL) wp_viewporter_destroy(wayl->viewporter); #endif +#if defined(HAVE_CURSOR_SHAPE) + if (wayl->cursor_shape_manager != NULL) + wp_cursor_shape_manager_v1_destroy(wayl->cursor_shape_manager); +#endif #if defined(HAVE_XDG_ACTIVATION) if (wayl->xdg_activation != NULL) xdg_activation_v1_destroy(wayl->xdg_activation); diff --git a/wayland.h b/wayland.h index af1bcb3f..b30ad307 100644 --- a/wayland.h +++ b/wayland.h @@ -146,12 +146,18 @@ struct seat { struct { uint32_t serial; + /* Client-side cursor */ struct wayl_surface surface; struct wl_cursor_theme *theme; struct wl_cursor *cursor; + + /* Server-side cursor */ +#if defined(HAVE_CURSOR_SHAPE) + struct wp_cursor_shape_device_v1 *shape_device; +#endif + float scale; bool hidden; - enum cursor_shape shape; struct wl_callback *xcursor_callback; bool xcursor_pending; @@ -426,6 +432,10 @@ struct wayland { struct xdg_activation_v1 *xdg_activation; #endif +#if defined(HAVE_CURSOR_SHAPE) + struct wp_cursor_shape_manager_v1 *cursor_shape_manager; +#endif + bool presentation_timings; struct wp_presentation *presentation; uint32_t presentation_clock_id;