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/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/cursor-shape.c b/cursor-shape.c new file mode 100644 index 00000000..aafeae8b --- /dev/null +++ b/cursor-shape.c @@ -0,0 +1,115 @@ +#include +#include + +#define LOG_MODULE "cursor-shape" +#define LOG_ENABLE_DBG 0 +#include "log.h" + +#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_COUNT] = { + [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)); + xassert(table[shape] != NULL); + 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)); + xassert(table[shape] != 0); + return table[shape]; +} + +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 new file mode 100644 index 00000000..a9619553 --- /dev/null +++ b/cursor-shape.h @@ -0,0 +1,34 @@ +#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, + 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, + + 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); +enum wp_cursor_shape_device_v1_shape cursor_string_to_server_shape( + const char *xcursor); +#endif 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/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/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; } diff --git a/meson.build b/meson.build index 6e219caf..fc2491d2 100644 --- a/meson.build +++ b/meson.build @@ -169,6 +169,16 @@ if wayland_protocols.version().version_compare('>=1.31') else fractional_scale = false endif +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', + ] + add_project_arguments('-DHAVE_CURSOR_SHAPE', language: 'c') + cursor_shape = true +else + cursor_shape = false +endif foreach prot : wl_proto_xml wl_proto_headers += custom_target( @@ -223,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', @@ -380,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(), 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 * diff --git a/render.c b/render.c index 4498290e..5a757a88 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" @@ -4254,26 +4255,61 @@ 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 */ + 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, + 0, 0); wl_surface_commit(seat->pointer.surface.surf); return; } xassert(seat->pointer.cursor != NULL); - const float scale = seat->pointer.scale; +#if defined(HAVE_CURSOR_SHAPE) + const enum cursor_shape shape = seat->pointer.shape; + const char *const xcursor = seat->pointer.last_custom_xcursor; + + if (seat->pointer.shape_device != NULL) { + xassert(shape != CURSOR_SHAPE_CUSTOM || xcursor != NULL); + + const enum wp_cursor_shape_device_v1_shape custom_shape = + (shape == CURSOR_SHAPE_CUSTOM && xcursor != NULL + ? cursor_string_to_server_shape(xcursor) + : 0); + + if (shape != CURSOR_SHAPE_CUSTOM || custom_shape != 0) { + xassert(custom_shape == 0 || shape == CURSOR_SHAPE_CUSTOM); + + const enum wp_cursor_shape_device_v1_shape wp_shape = custom_shape != 0 + ? custom_shape + : cursor_shape_to_server_shape(shape); + + LOG_DBG("setting %scursor shape using cursor-shape-v1", + custom_shape != 0 ? "custom " : ""); + + wp_cursor_shape_device_v1_set_shape( + seat->pointer.shape_device, + seat->pointer.serial, + wp_shape); + + 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]; - struct wl_buffer *buf = wl_cursor_image_get_buffer(image); - 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, buf, 0, 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, @@ -4283,6 +4319,8 @@ render_xcursor_update(struct seat *seat) 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); @@ -4434,13 +4472,14 @@ 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,26 +4488,48 @@ render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor return true; } - if (seat->pointer.xcursor == xcursor) + 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) { + 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); - if (xcursor != XCURSOR_HIDDEN) { 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; } } - } 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.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..fff55019 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,56 @@ 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); + case TERM_SURF_GRID: + if (seat->pointer.hidden) + shape = CURSOR_SHAPE_HIDDEN; - xcursor = seat->pointer.hidden ? XCURSOR_HIDDEN - : have_custom_cursor ? term->mouse_user_cursor - : term->is_searching ? XCURSOR_LEFT_PTR - : (seat->mouse.col >= 0 && - seat->mouse.row >= 0 && - term_mouse_grabbed(term, seat)) ? XCURSOR_TEXT - : XCURSOR_LEFT_PTR; +#if defined(HAVE_CURSOR_SHAPE) + else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 +#else + else if (false +#endif + || 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: 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 @@ -3725,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/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.c b/wayland.c index 5160240b..e862b5e8 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) @@ -225,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); } @@ -316,9 +326,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 +1190,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 +1435,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 +1538,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 e2d22031..d2c1ead1 100644 --- a/wayland.h +++ b/wayland.h @@ -28,6 +28,7 @@ #include #include +#include "cursor-shape.h" #include "fdm.h" /* Forward declarations */ @@ -145,13 +146,21 @@ 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; + char *last_custom_xcursor; - const char *xcursor; struct wl_callback *xcursor_callback; bool xcursor_pending; } pointer; @@ -425,6 +434,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;