From ee794a121e4603d01fb662ad6d1f3b18b6937d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 16:57:33 +0200 Subject: [PATCH 01/15] refactor: track current xcursor using an enum, instead of a char pointer --- cursor-shape.c | 29 +++++++++++++++++++++++++++++ cursor-shape.h | 21 +++++++++++++++++++++ input.c | 22 +++++++++++----------- input.h | 5 +++-- meson.build | 1 + render.c | 24 +++++++++++++++--------- render.h | 2 +- terminal.c | 34 ++++++++++------------------------ terminal.h | 14 -------------- wayland.h | 3 ++- 10 files changed, 93 insertions(+), 62 deletions(-) create mode 100644 cursor-shape.c create mode 100644 cursor-shape.h diff --git a/cursor-shape.c b/cursor-shape.c new file mode 100644 index 00000000..152f176f --- /dev/null +++ b/cursor-shape.c @@ -0,0 +1,29 @@ +#include + +#include "cursor-shape.h" +#include "debug.h" +#include "util.h" + +const char * +cursor_shape_to_string(enum cursor_shape shape) +{ + static const char *const table[] = { + [CURSOR_SHAPE_NONE] = NULL, + [CURSOR_SHAPE_HIDDEN] = "hidden", + [CURSOR_SHAPE_LEFT_PTR] = "left_ptr", + [CURSOR_SHAPE_TEXT] = "text", + [CURSOR_SHAPE_TEXT_FALLBACK] = "xterm", + [CURSOR_SHAPE_TOP_LEFT_CORNER] = "top_left_corner", + [CURSOR_SHAPE_TOP_RIGHT_CORNER] = "top_right_corner", + [CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = "bottom_left_corner", + [CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = "bottom_right_corner", + [CURSOR_SHAPE_LEFT_SIDE] = "left_side", + [CURSOR_SHAPE_RIGHT_SIDE] = "right_side", + [CURSOR_SHAPE_TOP_SIDE] = "top_side", + [CURSOR_SHAPE_BOTTOM_SIDE] = "bottom_side", + + }; + + xassert(shape <= ALEN(table)); + return table[shape]; +} diff --git a/cursor-shape.h b/cursor-shape.h new file mode 100644 index 00000000..fb79e45a --- /dev/null +++ b/cursor-shape.h @@ -0,0 +1,21 @@ +#pragma once + +enum cursor_shape { + CURSOR_SHAPE_NONE, + CURSOR_SHAPE_CUSTOM, + + CURSOR_SHAPE_HIDDEN, + CURSOR_SHAPE_LEFT_PTR, + CURSOR_SHAPE_TEXT, + CURSOR_SHAPE_TEXT_FALLBACK, + CURSOR_SHAPE_TOP_LEFT_CORNER, + CURSOR_SHAPE_TOP_RIGHT_CORNER, + CURSOR_SHAPE_BOTTOM_LEFT_CORNER, + CURSOR_SHAPE_BOTTOM_RIGHT_CORNER, + CURSOR_SHAPE_LEFT_SIDE, + CURSOR_SHAPE_RIGHT_SIDE, + CURSOR_SHAPE_TOP_SIDE, + CURSOR_SHAPE_BOTTOM_SIDE, +}; + +const char *cursor_shape_to_string(enum cursor_shape shape); diff --git a/input.c b/input.c index 7e5d204d..0f638cc1 100644 --- a/input.c +++ b/input.c @@ -1704,20 +1704,20 @@ is_bottom_right(const struct terminal *term, int x, int y) (term->active_surface == TERM_SURF_BORDER_BOTTOM && x > term->width + 1 * csd_border_size * term->scale - 10 * term->scale))); } -const char * +enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y) { - if (is_top_left(term, x, y)) return XCURSOR_TOP_LEFT_CORNER; - else if (is_top_right(term, x, y)) return XCURSOR_TOP_RIGHT_CORNER; - else if (is_bottom_left(term, x, y)) return XCURSOR_BOTTOM_LEFT_CORNER; - else if (is_bottom_right(term, x, y)) return XCURSOR_BOTTOM_RIGHT_CORNER; - else if (term->active_surface == TERM_SURF_BORDER_LEFT) return XCURSOR_LEFT_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_RIGHT) return XCURSOR_RIGHT_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_TOP) return XCURSOR_TOP_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) return XCURSOR_BOTTOM_SIDE; + if (is_top_left(term, x, y)) return CURSOR_SHAPE_TOP_LEFT_CORNER; + else if (is_top_right(term, x, y)) return CURSOR_SHAPE_TOP_RIGHT_CORNER; + else if (is_bottom_left(term, x, y)) return CURSOR_SHAPE_BOTTOM_LEFT_CORNER; + else if (is_bottom_right(term, x, y)) return CURSOR_SHAPE_BOTTOM_RIGHT_CORNER; + else if (term->active_surface == TERM_SURF_BORDER_LEFT) return CURSOR_SHAPE_LEFT_SIDE; + else if (term->active_surface == TERM_SURF_BORDER_RIGHT) return CURSOR_SHAPE_RIGHT_SIDE; + else if (term->active_surface == TERM_SURF_BORDER_TOP) return CURSOR_SHAPE_TOP_SIDE; + else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) return CURSOR_SHAPE_BOTTOM_SIDE; else { BUG("Unreachable"); - return NULL; + return CURSOR_SHAPE_NONE; } } @@ -1819,7 +1819,7 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, } /* Reset last-set-xcursor, to ensure we update it on a pointer-enter event */ - seat->pointer.xcursor = NULL; + seat->pointer.shape = CURSOR_SHAPE_NONE; /* Reset mouse state */ seat->mouse.x = seat->mouse.y = 0; diff --git a/input.h b/input.h index ea488a86..825dc3be 100644 --- a/input.h +++ b/input.h @@ -3,8 +3,9 @@ #include #include -#include "wayland.h" +#include "cursor-shape.h" #include "misc.h" +#include "wayland.h" /* * Custom defines for mouse wheel left/right buttons. @@ -33,4 +34,4 @@ void get_current_modifiers(const struct seat *seat, xkb_mod_mask_t *consumed, uint32_t key); -const char *xcursor_for_csd_border(struct terminal *term, int x, int y); +enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y); diff --git a/meson.build b/meson.build index 6e219caf..9560504f 100644 --- a/meson.build +++ b/meson.build @@ -261,6 +261,7 @@ executable( 'box-drawing.c', 'box-drawing.h', 'config.c', 'config.h', 'commands.c', 'commands.h', + 'cursor-shape.c', 'cursor-shape.h', 'extract.c', 'extract.h', 'fdm.c', 'fdm.h', 'foot-features.h', diff --git a/render.c b/render.c index 4498290e..fedc3467 100644 --- a/render.c +++ b/render.c @@ -4254,9 +4254,9 @@ render_xcursor_update(struct seat *seat) if (!seat->mouse_focus) return; - xassert(seat->pointer.xcursor != NULL); + xassert(seat->pointer.shape != CURSOR_SHAPE_NONE); - if (seat->pointer.xcursor == XCURSOR_HIDDEN) { + if (seat->pointer.shape == CURSOR_SHAPE_HIDDEN) { /* Hide cursor */ wl_surface_attach(seat->pointer.surface.surf, NULL, 0, 0); wl_surface_commit(seat->pointer.surface.surf); @@ -4434,13 +4434,13 @@ render_refresh_urls(struct terminal *term) } bool -render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor) +render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape) { if (seat->pointer.theme == NULL) return false; if (seat->mouse_focus == NULL) { - seat->pointer.xcursor = NULL; + seat->pointer.shape = CURSOR_SHAPE_NONE; return true; } @@ -4449,18 +4449,24 @@ render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor return true; } - if (seat->pointer.xcursor == xcursor) + if (seat->pointer.shape == shape) return true; - if (xcursor != XCURSOR_HIDDEN) { + if (shape != CURSOR_SHAPE_HIDDEN) { + const char *const xcursor = cursor_shape_to_string(shape); + const char *const fallback = + cursor_shape_to_string(CURSOR_SHAPE_TEXT_FALLBACK); + seat->pointer.cursor = wl_cursor_theme_get_cursor( seat->pointer.theme, xcursor); if (seat->pointer.cursor == NULL) { seat->pointer.cursor = wl_cursor_theme_get_cursor( - seat->pointer.theme, XCURSOR_TEXT_FALLBACK ); + seat->pointer.theme, fallback); + if (seat->pointer.cursor == NULL) { - LOG_ERR("failed to load xcursor pointer '%s', and fallback '%s'", xcursor, XCURSOR_TEXT_FALLBACK); + LOG_ERR("failed to load xcursor pointer " + "'%s', and fallback '%s'", xcursor, fallback); return false; } } @@ -4468,7 +4474,7 @@ render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor seat->pointer.cursor = NULL; /* FDM hook takes care of actual rendering */ - seat->pointer.xcursor = xcursor; + seat->pointer.shape = shape; seat->pointer.xcursor_pending = true; return true; } diff --git a/render.h b/render.h index d2c673ee..f038ffb0 100644 --- a/render.h +++ b/render.h @@ -19,7 +19,7 @@ void render_refresh_search(struct terminal *term); void render_refresh_title(struct terminal *term); void render_refresh_urls(struct terminal *term); bool render_xcursor_set( - struct seat *seat, struct terminal *term, const char *xcursor); + struct seat *seat, struct terminal *term, enum cursor_shape shape); bool render_xcursor_is_valid(const struct seat *seat, const char *cursor); struct render_worker_context { diff --git a/terminal.c b/terminal.c index df77d2b9..3591040b 100644 --- a/terminal.c +++ b/terminal.c @@ -47,20 +47,6 @@ #define PTMX_TIMING 0 -const char *const XCURSOR_HIDDEN = "hidden"; -const char *const XCURSOR_LEFT_PTR = "left_ptr"; -const char *const XCURSOR_TEXT = "text"; -const char *const XCURSOR_TEXT_FALLBACK = "xterm"; -//const char *const XCURSOR_HAND2 = "hand2"; -const char *const XCURSOR_TOP_LEFT_CORNER = "top_left_corner"; -const char *const XCURSOR_TOP_RIGHT_CORNER = "top_right_corner"; -const char *const XCURSOR_BOTTOM_LEFT_CORNER = "bottom_left_corner"; -const char *const XCURSOR_BOTTOM_RIGHT_CORNER = "bottom_right_corner"; -const char *const XCURSOR_LEFT_SIDE = "left_side"; -const char *const XCURSOR_RIGHT_SIDE = "right_side"; -const char *const XCURSOR_TOP_SIDE = "top_side"; -const char *const XCURSOR_BOTTOM_SIDE = "bottom_side"; - static void enqueue_data_for_slave(const void *data, size_t len, size_t offset, ptmx_buffer_list_t *buffer_list) @@ -3137,44 +3123,44 @@ term_mouse_motion(struct terminal *term, int button, int row, int col, void term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) { - const char *xcursor = NULL; + enum cursor_shape shape = CURSOR_SHAPE_NONE; switch (term->active_surface) { case TERM_SURF_GRID: { bool have_custom_cursor = render_xcursor_is_valid(seat, term->mouse_user_cursor); - xcursor = seat->pointer.hidden ? XCURSOR_HIDDEN - : have_custom_cursor ? term->mouse_user_cursor - : term->is_searching ? XCURSOR_LEFT_PTR + shape = seat->pointer.hidden ? CURSOR_SHAPE_HIDDEN + : have_custom_cursor ? CURSOR_SHAPE_CUSTOM //term->mouse_user_cursor + : term->is_searching ? CURSOR_SHAPE_LEFT_PTR : (seat->mouse.col >= 0 && seat->mouse.row >= 0 && - term_mouse_grabbed(term, seat)) ? XCURSOR_TEXT - : XCURSOR_LEFT_PTR; + term_mouse_grabbed(term, seat)) ? CURSOR_SHAPE_TEXT + : CURSOR_SHAPE_LEFT_PTR; break; } case TERM_SURF_TITLE: case TERM_SURF_BUTTON_MINIMIZE: case TERM_SURF_BUTTON_MAXIMIZE: case TERM_SURF_BUTTON_CLOSE: - xcursor = XCURSOR_LEFT_PTR; + shape = CURSOR_SHAPE_LEFT_PTR; break; case TERM_SURF_BORDER_LEFT: case TERM_SURF_BORDER_RIGHT: case TERM_SURF_BORDER_TOP: case TERM_SURF_BORDER_BOTTOM: - xcursor = xcursor_for_csd_border(term, seat->mouse.x, seat->mouse.y); + shape = xcursor_for_csd_border(term, seat->mouse.x, seat->mouse.y); break; case TERM_SURF_NONE: return; } - if (xcursor == NULL) + if (shape == CURSOR_SHAPE_NONE) BUG("xcursor not set"); - render_xcursor_set(seat, term, xcursor); + render_xcursor_set(seat, term, shape); } void diff --git a/terminal.h b/terminal.h index 1ccb3219..6dace7ac 100644 --- a/terminal.h +++ b/terminal.h @@ -718,20 +718,6 @@ struct terminal { char *cwd; }; -extern const char *const XCURSOR_HIDDEN; -extern const char *const XCURSOR_LEFT_PTR; -extern const char *const XCURSOR_TEXT; -extern const char *const XCURSOR_TEXT_FALLBACK; -//extern const char *const XCURSOR_HAND2; -extern const char *const XCURSOR_TOP_LEFT_CORNER; -extern const char *const XCURSOR_TOP_RIGHT_CORNER; -extern const char *const XCURSOR_BOTTOM_LEFT_CORNER; -extern const char *const XCURSOR_BOTTOM_RIGHT_CORNER; -extern const char *const XCURSOR_LEFT_SIDE; -extern const char *const XCURSOR_RIGHT_SIDE; -extern const char *const XCURSOR_TOP_SIDE; -extern const char *const XCURSOR_BOTTOM_SIDE; - struct config; struct terminal *term_init( const struct config *conf, struct fdm *fdm, struct reaper *reaper, diff --git a/wayland.h b/wayland.h index e2d22031..af1bcb3f 100644 --- a/wayland.h +++ b/wayland.h @@ -28,6 +28,7 @@ #include #include +#include "cursor-shape.h" #include "fdm.h" /* Forward declarations */ @@ -151,7 +152,7 @@ struct seat { float scale; bool hidden; - const char *xcursor; + enum cursor_shape shape; struct wl_callback *xcursor_callback; bool xcursor_pending; } pointer; 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 02/15] 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; From 6ed5dce5ab5651c1ee345d0315c1538900282f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 17:42:47 +0200 Subject: [PATCH 03/15] render: debug log which method we use to set the xcursor --- render.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/render.c b/render.c index ab1ddc51..d7cc9dfb 100644 --- a/render.c +++ b/render.c @@ -4259,6 +4259,7 @@ render_xcursor_update(struct seat *seat) if (seat->pointer.shape == CURSOR_SHAPE_HIDDEN) { /* Hide cursor */ + LOG_DBG("hiding cursor using client-side NULL-surface"); wl_surface_attach(seat->pointer.surface.surf, NULL, 0, 0); wl_pointer_set_cursor( seat->wl_pointer, seat->pointer.serial, seat->pointer.surface.surf, @@ -4271,6 +4272,7 @@ render_xcursor_update(struct seat *seat) #if defined(HAVE_CURSOR_SHAPE) if (seat->pointer.shape_device != NULL) { + LOG_DBG("setting cursor shape using cursor-shape-v1"); wp_cursor_shape_device_v1_set_shape( seat->pointer.shape_device, seat->pointer.serial, @@ -4278,6 +4280,7 @@ render_xcursor_update(struct seat *seat) } else #endif { + LOG_DBG("setting cursor shape using a client-side cursor surface"); const int scale = seat->pointer.scale; struct wl_cursor_image *image = seat->pointer.cursor->images[0]; From 803b250652d766cf08721e334215585f4fd72fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 18:16:33 +0200 Subject: [PATCH 04/15] pgo: update xcursor stubs to use enum instead of char pointer --- pgo/pgo.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pgo/pgo.c b/pgo/pgo.c index d9ee5855..6be60363 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -76,15 +76,15 @@ render_xcursor_is_valid(const struct seat *seat, const char *cursor) } bool -render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor) +render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape) { return true; } -const char * +enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y) { - return XCURSOR_LEFT_PTR; + return CURSOR_SHAPE_LEFT_PTR; } struct wl_window * From 9155948ac8f2902acae107e9ff765630295ca6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 18:40:25 +0200 Subject: [PATCH 05/15] cursor-shape: assert lookup succeeded --- cursor-shape.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cursor-shape.c b/cursor-shape.c index cd9ba221..48f7419f 100644 --- a/cursor-shape.c +++ b/cursor-shape.c @@ -25,6 +25,7 @@ cursor_shape_to_string(enum cursor_shape shape) }; xassert(shape <= ALEN(table)); + xassert(table[shape] != NULL); return table[shape]; } @@ -47,6 +48,7 @@ cursor_shape_to_server_shape(enum cursor_shape shape) }; xassert(shape <= ALEN(table)); + xassert(table[shape] != 0); return table[shape]; } #endif From ddd6004b275dd9a24f26e03dead90f0fffb0cc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 18:40:44 +0200 Subject: [PATCH 06/15] =?UTF-8?q?render:=20don=E2=80=99t=20(can=E2=80=99t)?= =?UTF-8?q?=20use=20cursor-shape-v1=20when=20user=20has=20set=20a=20custom?= =?UTF-8?q?=20cursor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Well, we _could_, but we’d have to reverse map the string to a cursor-shape-v1 enum value. Let’s not do that, for now at least. --- render.c | 11 ++++++++--- terminal.c | 27 ++++++++++++++++----------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/render.c b/render.c index d7cc9dfb..b0b1ec32 100644 --- a/render.c +++ b/render.c @@ -4271,7 +4271,9 @@ render_xcursor_update(struct seat *seat) xassert(seat->pointer.cursor != NULL); #if defined(HAVE_CURSOR_SHAPE) - if (seat->pointer.shape_device != NULL) { + if (seat->pointer.shape_device != NULL && + seat->pointer.shape != CURSOR_SHAPE_CUSTOM) + { LOG_DBG("setting cursor shape using cursor-shape-v1"); wp_cursor_shape_device_v1_set_shape( seat->pointer.shape_device, @@ -4449,7 +4451,8 @@ render_refresh_urls(struct terminal *term) } bool -render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape) +render_xcursor_set(struct seat *seat, struct terminal *term, + enum cursor_shape shape) { if (seat->pointer.theme == NULL) return false; @@ -4469,7 +4472,9 @@ render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape s /* 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 xcursor = shape == CURSOR_SHAPE_CUSTOM + ? term->mouse_user_cursor + : cursor_shape_to_string(shape); const char *const fallback = cursor_shape_to_string(CURSOR_SHAPE_TEXT_FALLBACK); diff --git a/terminal.c b/terminal.c index 3591040b..3f2c9095 100644 --- a/terminal.c +++ b/terminal.c @@ -3126,19 +3126,24 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) enum cursor_shape shape = CURSOR_SHAPE_NONE; switch (term->active_surface) { - case TERM_SURF_GRID: { - bool have_custom_cursor = - render_xcursor_is_valid(seat, term->mouse_user_cursor); + case TERM_SURF_GRID: + if (seat->pointer.hidden) + shape = CURSOR_SHAPE_HIDDEN; - shape = seat->pointer.hidden ? CURSOR_SHAPE_HIDDEN - : have_custom_cursor ? CURSOR_SHAPE_CUSTOM //term->mouse_user_cursor - : term->is_searching ? CURSOR_SHAPE_LEFT_PTR - : (seat->mouse.col >= 0 && - seat->mouse.row >= 0 && - term_mouse_grabbed(term, seat)) ? CURSOR_SHAPE_TEXT - : CURSOR_SHAPE_LEFT_PTR; + else if (render_xcursor_is_valid(seat, term->mouse_user_cursor)) + shape = CURSOR_SHAPE_CUSTOM; + + else if (seat->mouse.col >= 0 && + seat->mouse.row >= 0 && + term_mouse_grabbed(term, seat)) + { + shape = CURSOR_SHAPE_TEXT; + } + + else + shape = CURSOR_SHAPE_LEFT_PTR; break; - } + case TERM_SURF_TITLE: case TERM_SURF_BUTTON_MINIMIZE: case TERM_SURF_BUTTON_MAXIMIZE: From bf83a0b2bdb2299c95b6296fe8fd93bf0b99b743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Jun 2023 08:38:20 +0200 Subject: [PATCH 07/15] meson: cursor-shape: use .xml from wayland-protocols This patch assumes a git snapshot of wayland-protocols are installed. We need to bump the version number as soon as the next version of wayland-protocols have been released. --- cursor-shape-v1.xml | 147 -------------------------------------------- meson.build | 12 ++-- 2 files changed, 7 insertions(+), 152 deletions(-) delete mode 100644 cursor-shape-v1.xml diff --git a/cursor-shape-v1.xml b/cursor-shape-v1.xml deleted file mode 100644 index 56f6a1a6..00000000 --- a/cursor-shape-v1.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - 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/meson.build b/meson.build index ed0bf4a0..8535a4d1 100644 --- a/meson.build +++ b/meson.build @@ -170,11 +170,13 @@ 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') +if wayland_protocols.version().version_compare('>=1.31') # TODO: 1.32 + wl_proto_xml += [ + wayland_protocols_datadir + '/unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1 + wayland_protocols_datadir + '/staging/cursor-shape/cursor-shape-v1.xml', + ] + add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') +endif foreach prot : wl_proto_xml wl_proto_headers += custom_target( From c2baaff3c1311335b46e75db27bfd79d4f7b623b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Jun 2023 13:25:08 +0200 Subject: [PATCH 08/15] cursor-shape: use server-side cursors for custom (OSC-22), if possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using a lookup table, try to map the user-provided xcursor string to a cursor-shape-v1 known shape. If we succeed, set the user’s custom cursor using server side cursors (i.e. using cursor-shape-v1). If not, fallback to trying to load the image ourselves (using wl_cursor_theme_get_cursor()), and set it using the legacy wl_pointer_set_cursor(). --- cursor-shape.c | 63 +++++++++++++++++++++++++++++++++- cursor-shape.h | 2 ++ render.c | 92 ++++++++++++++++++++++++++++++++++---------------- terminal.c | 13 +++++-- wayland.c | 1 + wayland.h | 2 ++ 6 files changed, 141 insertions(+), 32 deletions(-) diff --git a/cursor-shape.c b/cursor-shape.c index 48f7419f..aafeae8b 100644 --- a/cursor-shape.c +++ b/cursor-shape.c @@ -1,4 +1,9 @@ #include +#include + +#define LOG_MODULE "cursor-shape" +#define LOG_ENABLE_DBG 0 +#include "log.h" #include "cursor-shape.h" #include "debug.h" @@ -30,6 +35,7 @@ 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) { @@ -51,4 +57,59 @@ cursor_shape_to_server_shape(enum cursor_shape shape) xassert(table[shape] != 0); return table[shape]; } -#endif + +enum wp_cursor_shape_device_v1_shape +cursor_string_to_server_shape(const char *xcursor) +{ + if (xcursor == NULL) + return 0; + + static const char *const table[][2] = { + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT] = {"default", "left_ptr"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU] = {"context-menu"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP] = {"help", "question_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER] = {"pointer", "hand"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS] = {"progress", "left_ptr_watch"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT] = {"wait", "watch"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL] = {"cell"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR] = {"crosshair", "cross"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT] = {"text", "xterm"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT] = {"vertical-text"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS] = {"alias", "dnd-link"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY] = {"copy", "dnd-copy"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = {"move"}, /* dnd-move? */ + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP] = {"no-drop", "dnd-no-drop"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED] = {"not-allowed", "crossed_circle"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB] = {"grab", "hand1"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING] = {"grabbing"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE] = {"e-resize", "right_side"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE] = {"n-resize", "top_side"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE] = {"ne-resize", "top_right_corner"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE] = {"nw-resize", "top_left_corner"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE] = {"s-resize", "bottom_side"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE] = {"se-resize", "bottom_right_corner"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE] = {"sw-resize", "bottom_left_corner"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE] = {"w-resize", "left_side"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE] = {"ew-resize", "sb_h_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE] = {"ns-resize", "sb_v_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE] = {"nesw-resize", "fd_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE] = {"nwse-resize", "bd_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE] = {"col-resize", "sb_h_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE] = {"row-resize", "sb_v_double_arrow"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = {"all-scroll", "fleur"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = {"zoom-in"}, + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = {"zoom-out"}, + }; + + for (size_t i = 0; i < ALEN(table); i++) { + for (size_t j = 0; j < ALEN(table[i]); j++) { + if (table[i][j] != NULL && strcmp(xcursor, table[i][j]) == 0) { + return i; + } + } + } + + return 0; +} + +#endif /* HAVE_CURSOR_SHAPE */ diff --git a/cursor-shape.h b/cursor-shape.h index 0cb0b4d8..a9619553 100644 --- a/cursor-shape.h +++ b/cursor-shape.h @@ -29,4 +29,6 @@ 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); +enum wp_cursor_shape_device_v1_shape cursor_string_to_server_shape( + const char *xcursor); #endif diff --git a/render.c b/render.c index b0b1ec32..df7a43f9 100644 --- a/render.c +++ b/render.c @@ -4270,41 +4270,62 @@ render_xcursor_update(struct seat *seat) xassert(seat->pointer.cursor != NULL); + const enum cursor_shape shape = seat->pointer.shape; + const char *const xcursor = seat->pointer.last_custom_xcursor; + #if defined(HAVE_CURSOR_SHAPE) - if (seat->pointer.shape_device != NULL && - seat->pointer.shape != CURSOR_SHAPE_CUSTOM) - { - LOG_DBG("setting cursor shape using cursor-shape-v1"); - wp_cursor_shape_device_v1_set_shape( - seat->pointer.shape_device, - seat->pointer.serial, - cursor_shape_to_server_shape(seat->pointer.shape)); - } else -#endif - { - LOG_DBG("setting cursor shape using a client-side cursor surface"); - const int scale = seat->pointer.scale; - struct wl_cursor_image *image = seat->pointer.cursor->images[0]; + if (seat->pointer.shape_device != NULL) { + xassert(shape != CURSOR_SHAPE_CUSTOM || xcursor != NULL); - wl_surface_attach( - seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 0, 0); + const enum wp_cursor_shape_device_v1_shape custom_shape = + (shape == CURSOR_SHAPE_CUSTOM && xcursor != NULL + ? cursor_string_to_server_shape(xcursor) + : 0); - wl_pointer_set_cursor( - seat->wl_pointer, seat->pointer.serial, - seat->pointer.surface.surf, - image->hotspot_x / scale, image->hotspot_y / scale); + if (shape != CURSOR_SHAPE_CUSTOM || custom_shape != 0) { + xassert(custom_shape == 0 || shape == CURSOR_SHAPE_CUSTOM); - wl_surface_damage_buffer( - seat->pointer.surface.surf, 0, 0, INT32_MAX, INT32_MAX); + const enum wp_cursor_shape_device_v1_shape wp_shape = custom_shape != 0 + ? custom_shape + : cursor_shape_to_server_shape(shape); - wl_surface_set_buffer_scale(seat->pointer.surface.surf, scale); + LOG_DBG("setting %scursor shape using cursor-shape-v1", + custom_shape != 0 ? "custom " : ""); - 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); + wp_cursor_shape_device_v1_set_shape( + seat->pointer.shape_device, + seat->pointer.serial, + wp_shape); - wl_surface_commit(seat->pointer.surface.surf); + return; + } } +#endif + + LOG_DBG("setting %scursor shape using a client-side cursor surface", + shape == CURSOR_SHAPE_CUSTOM ? "custom " : ""); + + const int scale = seat->pointer.scale; + struct wl_cursor_image *image = seat->pointer.cursor->images[0]; + + wl_surface_attach( + seat->pointer.surface.surf, wl_cursor_image_get_buffer(image), 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_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); + + wl_surface_commit(seat->pointer.surface.surf); } static void @@ -4467,8 +4488,13 @@ render_xcursor_set(struct seat *seat, struct terminal *term, return true; } - if (seat->pointer.shape == shape) + if (seat->pointer.shape == shape && + !(shape == CURSOR_SHAPE_CUSTOM && + strcmp(seat->pointer.last_custom_xcursor, + term->mouse_user_cursor) != 0)) + { return true; + } /* TODO: skip this when using server-side cursors */ if (shape != CURSOR_SHAPE_HIDDEN) { @@ -4491,8 +4517,16 @@ render_xcursor_set(struct seat *seat, struct terminal *term, return false; } } - } else + + if (shape == CURSOR_SHAPE_CUSTOM) { + free(seat->pointer.last_custom_xcursor); + seat->pointer.last_custom_xcursor = xstrdup(term->mouse_user_cursor); + } + } else { seat->pointer.cursor = NULL; + free(seat->pointer.last_custom_xcursor); + seat->pointer.last_custom_xcursor = NULL; + } /* FDM hook takes care of actual rendering */ seat->pointer.shape = shape; diff --git a/terminal.c b/terminal.c index 3f2c9095..4a0d99cd 100644 --- a/terminal.c +++ b/terminal.c @@ -3130,8 +3130,15 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) if (seat->pointer.hidden) shape = CURSOR_SHAPE_HIDDEN; - else if (render_xcursor_is_valid(seat, term->mouse_user_cursor)) +#if defined(HAVE_CURSOR_SHAPE) + else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 +#elif + else if (true +#endif + || render_xcursor_is_valid(seat, term->mouse_user_cursor)) + { shape = CURSOR_SHAPE_CUSTOM; + } else if (seat->mouse.col >= 0 && seat->mouse.row >= 0 && @@ -3716,6 +3723,8 @@ void term_set_user_mouse_cursor(struct terminal *term, const char *cursor) { free(term->mouse_user_cursor); - term->mouse_user_cursor = cursor != NULL ? xstrdup(cursor) : NULL; + term->mouse_user_cursor = cursor != NULL && strlen(cursor) > 0 + ? xstrdup(cursor) + : NULL; term_xcursor_update(term); } diff --git a/wayland.c b/wayland.c index f3a5619d..e862b5e8 100644 --- a/wayland.c +++ b/wayland.c @@ -234,6 +234,7 @@ seat_destroy(struct seat *seat) ime_reset_pending(seat); free(seat->clipboard.text); free(seat->primary.text); + free(seat->pointer.last_custom_xcursor); free(seat->name); } diff --git a/wayland.h b/wayland.h index b30ad307..d2c1ead1 100644 --- a/wayland.h +++ b/wayland.h @@ -159,6 +159,8 @@ struct seat { float scale; bool hidden; enum cursor_shape shape; + char *last_custom_xcursor; + struct wl_callback *xcursor_callback; bool xcursor_pending; } pointer; From c2e481fb6af7a2b9c7e3237436aee67019a32878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 16:06:01 +0200 Subject: [PATCH 09/15] meson: bump wayland-protocols version required for cursor-shape to 1.32 --- meson.build | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 8535a4d1..10746d9d 100644 --- a/meson.build +++ b/meson.build @@ -169,8 +169,7 @@ if wayland_protocols.version().version_compare('>=1.31') else fractional_scale = false endif - -if wayland_protocols.version().version_compare('>=1.31') # TODO: 1.32 +if wayland_protocols.version().version_compare('>=1.32') wl_proto_xml += [ wayland_protocols_datadir + '/unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1 wayland_protocols_datadir + '/staging/cursor-shape/cursor-shape-v1.xml', From 7bfa700c556c529036646e3224c6969e845840f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 16:06:52 +0200 Subject: [PATCH 10/15] terminal: #elif -> #else --- terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index 4a0d99cd..2d1313c1 100644 --- a/terminal.c +++ b/terminal.c @@ -3132,7 +3132,7 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) #if defined(HAVE_CURSOR_SHAPE) else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 -#elif +#else else if (true #endif || render_xcursor_is_valid(seat, term->mouse_user_cursor)) From 6388954e8f908a8cfd2f170c6a4f2c05370de762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 16:07:56 +0200 Subject: [PATCH 11/15] =?UTF-8?q?render:=20move=20variables=20inside=20#if?= =?UTF-8?q?def,=20as=20they=E2=80=99re=20not=20used=20outside=20of=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index df7a43f9..5a757a88 100644 --- a/render.c +++ b/render.c @@ -4270,10 +4270,10 @@ render_xcursor_update(struct seat *seat) xassert(seat->pointer.cursor != NULL); +#if defined(HAVE_CURSOR_SHAPE) const enum cursor_shape shape = seat->pointer.shape; const char *const xcursor = seat->pointer.last_custom_xcursor; -#if defined(HAVE_CURSOR_SHAPE) if (seat->pointer.shape_device != NULL) { xassert(shape != CURSOR_SHAPE_CUSTOM || xcursor != NULL); From a361d7917b311cad2def1ef42e740670cbddd7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Jun 2023 16:12:54 +0200 Subject: [PATCH 12/15] main/client: add a version feature flag for cursor-shape --- client.c | 3 ++- foot-features.h | 9 +++++++++ main.c | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/client.c b/client.c index 84bfb2c3..99c7c1e8 100644 --- a/client.c +++ b/client.c @@ -67,12 +67,13 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %cassertions", + "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %ccursor-shape %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', feature_fractional_scaling() ? '+' : ':', + feature_cursor_shape() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } diff --git a/foot-features.h b/foot-features.h index 77923aaf..f8043c12 100644 --- a/foot-features.h +++ b/foot-features.h @@ -46,3 +46,12 @@ static inline bool feature_fractional_scaling(void) return false; #endif } + +static inline bool feature_cursor_shape(void) +{ +#if defined(HAVE_CURSOR_SHAPE) + return true; +#else + return false; +#endif +} diff --git a/main.c b/main.c index 6dd9e468..fc329574 100644 --- a/main.c +++ b/main.c @@ -53,12 +53,13 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %cassertions", + "version: %s %cpgo %cime %cgraphemes %cfractional-scaling %ccursor-shape %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', feature_fractional_scaling() ? '+' : '-', + feature_cursor_shape() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } From 8fc43ccd2d80488dc3ef1b73e6a1309c4530e7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Jun 2023 08:29:21 +0200 Subject: [PATCH 13/15] meson: log availability of cursor-shape-v1 --- meson.build | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/meson.build b/meson.build index 10746d9d..0d75e1e4 100644 --- a/meson.build +++ b/meson.build @@ -175,6 +175,9 @@ if wayland_protocols.version().version_compare('>=1.32') wayland_protocols_datadir + '/staging/cursor-shape/cursor-shape-v1.xml', ] add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') + cursor_shape = true +else + cursor_shape = false endif foreach prot : wl_proto_xml @@ -388,6 +391,7 @@ summary( 'Grapheme clustering': utf8proc.found(), 'Wayland: xdg-activation-v1': xdg_activation, 'Wayland: fractional-scale-v1': fractional_scale, + 'Wayland: cursor-shape-v1': cursor_shape, 'utmp backend': utmp_backend, 'utmp helper default path': utmp_default_helper_path, 'Build terminfo': tic.found(), From ba09d55aabb8979a305c13afe8d51f98e5118410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Jul 2023 14:26:01 +0200 Subject: [PATCH 14/15] term_xcursor_update_for_seat(): fix missing evaluation of render_xcursor_is_valid() When compiling *without* cursor-shape-v1 support, term_xcursor_update_for_seat() would incorrectly set shape=CURSOR_SHAPE_CUSTOM, even though no custom cursor had been set by the user. This resulted in a crash in render_xcursor_set(), when trying to use a NULL-string as custom cursor. --- terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index 2d1313c1..fff55019 100644 --- a/terminal.c +++ b/terminal.c @@ -3133,7 +3133,7 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) #if defined(HAVE_CURSOR_SHAPE) else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 #else - else if (true + else if (false #endif || render_xcursor_is_valid(seat, term->mouse_user_cursor)) { From 3800b279d668403699e96c3c7974d63bf985d83b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Jul 2023 14:42:22 +0200 Subject: [PATCH 15/15] =?UTF-8?q?meson:=20move=20cursor-shape.{c,h}=20from?= =?UTF-8?q?=20=E2=80=98foot=E2=80=99=20binary=20to=20vtlib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should fix a build issue when doing partial PGO builds, when cursor-shape-v1 is *available*. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 0d75e1e4..fc2491d2 100644 --- a/meson.build +++ b/meson.build @@ -233,6 +233,7 @@ vtlib = static_library( 'vtlib', 'base64.c', 'base64.h', 'composed.c', 'composed.h', + 'cursor-shape.c', 'cursor-shape.h', 'csi.c', 'csi.h', 'dcs.c', 'dcs.h', 'macros.h', @@ -271,7 +272,6 @@ executable( 'box-drawing.c', 'box-drawing.h', 'config.c', 'config.h', 'commands.c', 'commands.h', - 'cursor-shape.c', 'cursor-shape.h', 'extract.c', 'extract.h', 'fdm.c', 'fdm.h', 'foot-features.h',