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;