From 4f1971c4802447341ea7684f0bb41b7f3f672e46 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Oct 2023 09:05:05 +0530 Subject: [PATCH] Rationalize mouse cursor shape handling Now can use the full range of standard mouse cursor shapes similar to those supported by browsers via the CSS cursor property. Still needs to be fully implemented for cocoa backend. --- docs/changelog.rst | 2 + gen/__main__.py | 3 + gen/cursors.py | 103 +++++++++++++++++++++++++++ glfw/glfw3.h | 37 +++++++--- glfw/wl_init.c | 12 ++-- glfw/wl_window.c | 43 +++++++++--- glfw/x11_window.c | 86 +++++++++++++++++------ kitty/data-types.h | 35 +++++++++- kitty/fast_data_types.pyi | 3 +- kitty/glfw-wrapper.h | 37 +++++++--- kitty/glfw.c | 135 +++++++++++++++++++++++++++--------- kitty/main.py | 3 +- kitty/mouse.c | 12 ++-- kitty/options/definition.py | 50 ++++++++++--- kitty/options/parse.py | 6 +- kitty/options/to-c.h | 42 +++++++++-- kitty/options/types.py | 6 +- 17 files changed, 493 insertions(+), 122 deletions(-) create mode 100755 gen/cursors.py diff --git a/docs/changelog.rst b/docs/changelog.rst index f8980c94a..7f3044232 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -50,6 +50,8 @@ Detailed list of changes - A new mouse action ``mouse_selection word_and_line_from_point`` to select the current word under the mouse cursor and extend to end of line (:pull:`6663`) +- Allow using the full range of standard mouse cursor shapes when customizing the mouse cursor + - macOS: When running the default shell with the login program fix :file:`~/.hushlogin` not being respected when opening windows not in the home directory (:iss:`6689`) - ``kitten @ set-background-opacity`` - add a new ``--toggle`` flag to easily switch opacity between the specified value and the default (:iss:`6691`) diff --git a/gen/__main__.py b/gen/__main__.py index 8184f91fb..9945693a5 100644 --- a/gen/__main__.py +++ b/gen/__main__.py @@ -32,6 +32,9 @@ def main(args: List[str]=sys.argv) -> None: elif which == 'wcwidth': from gen.wcwidth import main main(args) + elif which == 'cursors': + from gen.cursors import main + main(args) else: raise SystemExit(f'Unknown which: {which}') diff --git a/gen/cursors.py b/gen/cursors.py new file mode 100755 index 000000000..21753aeb0 --- /dev/null +++ b/gen/cursors.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + +import os +import subprocess +import sys +from typing import List + +from .key_constants import patch_file + +# References for these names: +# CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor +# XCursor: https://tronche.com/gui/x/xlib/appendix/b/ + Absolute chaos +# Wayland: https://wayland.app/protocols/cursor-shape-v1 +# Cocoa: https://developer.apple.com/documentation/appkit/nscursor + secret apple selectors + SDL_cocoamouse.m + +# kitty_names CSS_name XCursor_names Wayland_name Cocoa_name +cursors = '''\ +arrow default default,!left_ptr default arrowCursor +beam,text text text,!xterm,ibeam text IBeamCursor +pointer,hand pointer pointing_hand,pointer,!hand2,hand pointer pointingHandCursor +help help help,!question_arrow,whats_this help help:arrowCursor +wait wait wait,!clock,watch wait busybutclickable:arrowCursor +progress progress progress,half-busy,left_ptr_watch progress busybutclickable:arrowCursor +crosshair crosshair crosshair,!tcross crosshair crosshairCursor +vertical-text vertical-text vertical-text vertical-text IBeamCursorForVerticalLayout +move move move,!fleur,pointer-move move move:openHandCursor + +e-resize e-resize e-resize,!right_side e_resize resizeRightCursor +ne-resize ne-resize ne-resize,!top_right_corner ne_resize resizenortheast:_windowResizeNorthEastSouthWestCursor +nw-resize nw-resize nw-resize,!top_left_corner nw_resize resizenorthwest:_windowResizeNorthWestSouthEastCursor +n-resize n-resize n-resize,!top_side n_resize resizeUpCursor +se-resize se-resize se-resize,!bottom_right_corner se_resize resizesoutheast:_windowResizeNorthWestSouthEastCursor +sw-resize sw-resize sw-resize,!bottom_left_corner sw_resize resizesouthwest:_windowResizeNorthEastSouthWestCursor +s-resize s-resize s-resize,!bottom_side s_resize resizeDownCursor +w-resize w-resize w-resize,!left_side w_resize resizeLeftCursor + +ew-resize ew-resize ew-resize,!sb_h_double_arrow,split_h ew_resize resizeLeftRightCursor +ns-resize ns-resize ns-resize,!sb_v_double_arrow,split_v ns_resize resizeUpDownCursor +nesw-resize nesw-resize nesw-resize,size_bdiag,!sb_v_double_arrow nesw_resize _windowResizeNorthEastSouthWestCursor +nwse-resize nwse-resize nwse-resize,size_fdiag,!sb_h_double_arrow nwse_resize _windowResizeNorthWestSouthEastCursor + +zoom-in zoom-in zoom-in,zoom_in zoom_in zoomin:arrowCursor +zoom-out zoom-out zoom-out,zoom_out zoom_out zoomout:arrowCursor + +alias alias dnd-link alias dragLinkCursor +copy copy dnd-copy copy dragCopyCursor +not-allowed not-allowed not-allowed,forbidden,crossed_circle not_allowed operationNotAllowedCursor +no-drop no-drop no-drop,dnd-no-drop no_drop operationNotAllowedCursor +grab grab grab,openhand,!hand1 grab openHandCursor +grabbing grabbing grabbing,closedhand,dnd-none grabbing closedHandCursor +''' + + +def main(args: List[str]=sys.argv) -> None: + glfw_enum = [] + glfw_xc_map = {} + glfw_xfont_map = [] + kitty_to_enum_map = {} + enum_to_glfw_map = {} + for line in cursors.splitlines(): + line = line.strip() + if line: + names_, css, xc_, wayland, cocoa = line.split() + names, xc = names_.split(','), xc_.split(',') + base = css.replace('-', '_').upper() + glfw_name = 'GLFW_' + base + '_CURSOR' + enum_name = base + '_POINTER' + enum_to_glfw_map[enum_name] = glfw_name + for n in names: + kitty_to_enum_map[n] = enum_name + glfw_enum.append(glfw_name) + glfw_xc_map[glfw_name] = ', '.join(f'''"{x.replace('!', '')}"''' for x in xc) + for x in xc: + if x.startswith('!'): + glfw_xfont_map.append(f"case {glfw_name}: return set_cursor_from_font(cursor, {'XC_' + x[1:]});") + break + else: + items = tuple('"' + x.replace('!', '') + '"' for x in xc) + glfw_xfont_map.append(f'case {glfw_name}: return try_cursor_names(cursor, {len(items)}, {", ".join(items)});') + + glfw_enum.append('GLFW_INVALID_CURSOR') + patch_file('glfw/glfw3.h', 'mouse cursor shapes', '\n'.join(f' {x},' for x in glfw_enum)) + patch_file('glfw/wl_window.c', 'glfw to xc mapping', '\n'.join(f' C({g}, {x});' for g, x in glfw_xc_map.items())) + patch_file('glfw/x11_window.c', 'glfw to xc mapping', '\n'.join(f' {x}' for x in glfw_xfont_map)) + patch_file('kitty/data-types.h', 'mouse shapes', '\n'.join(f' {x},' for x in enum_to_glfw_map)) + patch_file( + 'kitty/options/definition.py', 'pointer shape names', '\n'.join(f' {x!r},' for x in kitty_to_enum_map), + start_marker='# ', end_marker='', + ) + patch_file('kitty/options/to-c.h', 'pointer shapes', '\n'.join( + f' else if (strcmp(name, "{k}") == 0) return {v};' for k, v in kitty_to_enum_map.items())) + patch_file('kitty/glfw.c', 'enum to glfw', '\n'.join( + f' case {k}: set_glfw_mouse_cursor(w, {v}); break;' for k, v in enum_to_glfw_map.items())) + patch_file('kitty/glfw.c', 'name to glfw', '\n'.join( + f' if (strcmp(name, "{k}") == 0) return {enum_to_glfw_map[v]};' for k, v in kitty_to_enum_map.items())) + subprocess.check_call(['glfw/glfw.py']) + + +if __name__ == '__main__': + import runpy + m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) + m['main']([sys.executable, 'cursors']) diff --git a/glfw/glfw3.h b/glfw/glfw3.h index ae78fcc71..7e5cde34c 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1099,17 +1099,38 @@ typedef enum { * @{ */ typedef enum { - GLFW_ARROW_CURSOR, - GLFW_IBEAM_CURSOR, +/* start mouse cursor shapes (auto generated by gen-key-constants.py do not edit) */ + GLFW_DEFAULT_CURSOR, + GLFW_TEXT_CURSOR, + GLFW_POINTER_CURSOR, + GLFW_HELP_CURSOR, + GLFW_WAIT_CURSOR, + GLFW_PROGRESS_CURSOR, GLFW_CROSSHAIR_CURSOR, - GLFW_HAND_CURSOR, - GLFW_HRESIZE_CURSOR, - GLFW_VRESIZE_CURSOR, - GLFW_NW_RESIZE_CURSOR, + GLFW_VERTICAL_TEXT_CURSOR, + GLFW_MOVE_CURSOR, + GLFW_E_RESIZE_CURSOR, GLFW_NE_RESIZE_CURSOR, - GLFW_SW_RESIZE_CURSOR, + GLFW_NW_RESIZE_CURSOR, + GLFW_N_RESIZE_CURSOR, GLFW_SE_RESIZE_CURSOR, - GLFW_INVALID_CURSOR + GLFW_SW_RESIZE_CURSOR, + GLFW_S_RESIZE_CURSOR, + GLFW_W_RESIZE_CURSOR, + GLFW_EW_RESIZE_CURSOR, + GLFW_NS_RESIZE_CURSOR, + GLFW_NESW_RESIZE_CURSOR, + GLFW_NWSE_RESIZE_CURSOR, + GLFW_ZOOM_IN_CURSOR, + GLFW_ZOOM_OUT_CURSOR, + GLFW_ALIAS_CURSOR, + GLFW_COPY_CURSOR, + GLFW_NOT_ALLOWED_CURSOR, + GLFW_NO_DROP_CURSOR, + GLFW_GRAB_CURSOR, + GLFW_GRABBING_CURSOR, + GLFW_INVALID_CURSOR, +/* end mouse cursor shapes */ } GLFWCursorShape; /*! @} */ diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 1d6b54d61..acff040ae 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -177,7 +177,7 @@ static void pointerHandleMotion(void* data UNUSED, wl_fixed_t sy) { _GLFWwindow* window = _glfw.wl.pointerFocus; - GLFWCursorShape cursorShape = GLFW_ARROW_CURSOR; + GLFWCursorShape cursorShape = GLFW_DEFAULT_CURSOR; if (!window) return; @@ -197,21 +197,21 @@ static void pointerHandleMotion(void* data UNUSED, return; case TOP_DECORATION: if (y < window->wl.decorations.metrics.width) - cursorShape = GLFW_VRESIZE_CURSOR; + cursorShape = GLFW_N_RESIZE_CURSOR; else - cursorShape = GLFW_ARROW_CURSOR; + cursorShape = GLFW_DEFAULT_CURSOR; break; case LEFT_DECORATION: if (y < window->wl.decorations.metrics.width) cursorShape = GLFW_NW_RESIZE_CURSOR; else - cursorShape = GLFW_HRESIZE_CURSOR; + cursorShape = GLFW_W_RESIZE_CURSOR; break; case RIGHT_DECORATION: if (y < window->wl.decorations.metrics.width) cursorShape = GLFW_NE_RESIZE_CURSOR; else - cursorShape = GLFW_HRESIZE_CURSOR; + cursorShape = GLFW_E_RESIZE_CURSOR; break; case BOTTOM_DECORATION: if (x < window->wl.decorations.metrics.width) @@ -219,7 +219,7 @@ static void pointerHandleMotion(void* data UNUSED, else if (x > window->wl.width + window->wl.decorations.metrics.width) cursorShape = GLFW_SE_RESIZE_CURSOR; else - cursorShape = GLFW_VRESIZE_CURSOR; + cursorShape = GLFW_S_RESIZE_CURSOR; break; default: assert(0); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 1674e8b75..bb9752fd6 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -154,7 +154,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque, static void setCursorImage(_GLFWwindow* window, bool on_theme_change) { - _GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR}; + _GLFWcursorWayland defaultCursor = {.shape = GLFW_DEFAULT_CURSOR}; _GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor; struct wl_cursor_image* image = NULL; struct wl_buffer* buffer = NULL; @@ -831,16 +831,37 @@ struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape, struct wl_cursor_theme* struct wl_cursor* ans = NULL; switch (shape) { - C(GLFW_ARROW_CURSOR, "left_ptr", "arrow", "default") - C(GLFW_IBEAM_CURSOR, "xterm", "ibeam", "text") - C(GLFW_CROSSHAIR_CURSOR, "crosshair", "cross") - C(GLFW_HAND_CURSOR, "hand2", "grab", "grabbing", "closedhand") - C(GLFW_HRESIZE_CURSOR, "sb_h_double_arrow", "h_double_arrow", "col-resize") - C(GLFW_VRESIZE_CURSOR, "sb_v_double_arrow", "v_double_arrow", "row-resize") - C(GLFW_NW_RESIZE_CURSOR, "top_left_corner", "nw-resize") - C(GLFW_NE_RESIZE_CURSOR, "top_right_corner", "ne-resize") - C(GLFW_SW_RESIZE_CURSOR, "bottom_left_corner", "sw-resize") - C(GLFW_SE_RESIZE_CURSOR, "bottom_right_corner", "se-resize") + /* start glfw to xc mapping (auto generated by gen-key-constants.py do not edit) */ + C(GLFW_DEFAULT_CURSOR, "default", "left_ptr"); + C(GLFW_TEXT_CURSOR, "text", "xterm", "ibeam"); + C(GLFW_POINTER_CURSOR, "pointing_hand", "pointer", "hand2", "hand"); + C(GLFW_HELP_CURSOR, "help", "question_arrow", "whats_this"); + C(GLFW_WAIT_CURSOR, "wait", "clock", "watch"); + C(GLFW_PROGRESS_CURSOR, "progress", "half-busy", "left_ptr_watch"); + C(GLFW_CROSSHAIR_CURSOR, "crosshair", "tcross"); + C(GLFW_VERTICAL_TEXT_CURSOR, "vertical-text"); + C(GLFW_MOVE_CURSOR, "move", "fleur", "pointer-move"); + C(GLFW_E_RESIZE_CURSOR, "e-resize", "right_side"); + C(GLFW_NE_RESIZE_CURSOR, "ne-resize", "top_right_corner"); + C(GLFW_NW_RESIZE_CURSOR, "nw-resize", "top_left_corner"); + C(GLFW_N_RESIZE_CURSOR, "n-resize", "top_side"); + C(GLFW_SE_RESIZE_CURSOR, "se-resize", "bottom_right_corner"); + C(GLFW_SW_RESIZE_CURSOR, "sw-resize", "bottom_left_corner"); + C(GLFW_S_RESIZE_CURSOR, "s-resize", "bottom_side"); + C(GLFW_W_RESIZE_CURSOR, "w-resize", "left_side"); + C(GLFW_EW_RESIZE_CURSOR, "ew-resize", "sb_h_double_arrow", "split_h"); + C(GLFW_NS_RESIZE_CURSOR, "ns-resize", "sb_v_double_arrow", "split_v"); + C(GLFW_NESW_RESIZE_CURSOR, "nesw-resize", "size_bdiag", "sb_v_double_arrow"); + C(GLFW_NWSE_RESIZE_CURSOR, "nwse-resize", "size_fdiag", "sb_h_double_arrow"); + C(GLFW_ZOOM_IN_CURSOR, "zoom-in", "zoom_in"); + C(GLFW_ZOOM_OUT_CURSOR, "zoom-out", "zoom_out"); + C(GLFW_ALIAS_CURSOR, "dnd-link"); + C(GLFW_COPY_CURSOR, "dnd-copy"); + C(GLFW_NOT_ALLOWED_CURSOR, "not-allowed", "forbidden", "crossed_circle"); + C(GLFW_NO_DROP_CURSOR, "no-drop", "dnd-no-drop"); + C(GLFW_GRAB_CURSOR, "grab", "openhand", "hand1"); + C(GLFW_GRABBING_CURSOR, "grabbing", "closedhand", "dnd-none"); +/* end glfw to xc mapping */ case GLFW_INVALID_CURSOR: break; } diff --git a/glfw/x11_window.c b/glfw/x11_window.c index f4f95224a..e4fb5cbfc 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -2811,37 +2811,77 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor, return true; } -int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) -{ - int native = 0; -#define C(name, val) case name: native = val; break; - switch(shape) { - C(GLFW_ARROW_CURSOR, XC_left_ptr); - C(GLFW_IBEAM_CURSOR, XC_xterm); - C(GLFW_CROSSHAIR_CURSOR, XC_crosshair); - C(GLFW_HAND_CURSOR, XC_hand2); - C(GLFW_HRESIZE_CURSOR, XC_sb_h_double_arrow); - C(GLFW_VRESIZE_CURSOR, XC_sb_v_double_arrow); - C(GLFW_NW_RESIZE_CURSOR, XC_top_left_corner); - C(GLFW_NE_RESIZE_CURSOR, XC_top_right_corner); - C(GLFW_SW_RESIZE_CURSOR, XC_bottom_left_corner); - C(GLFW_SE_RESIZE_CURSOR, XC_bottom_right_corner); - case GLFW_INVALID_CURSOR: - return false; - } -#undef C - +static int +set_cursor_from_font(_GLFWcursor* cursor, int native) { cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native); - if (!cursor->x11.handle) - { + if (!cursor->x11.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to create standard cursor"); return false; } - return true; } +static bool +try_cursor_names(_GLFWcursor *cursor, int arg_count, ...) { + va_list ap; + va_start(ap, arg_count); + const char *first_name = ""; + for (int i = 0; i < arg_count; i++) { + const char *name = va_arg(ap, const char *); + first_name = name; + cursor->x11.handle = XcursorLibraryLoadCursor(_glfw.x11.display, name); + if (cursor->x11.handle) break; + } + va_end(ap); + if (!cursor->x11.handle) { + _glfwInputError(GLFW_PLATFORM_ERROR, + "X11: Failed to load standard cursor: %s with %d aliases via Xcursor library", first_name, arg_count); + return false; + } + return true; +} + + +int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) +{ + switch(shape) { + /* start glfw to xc mapping (auto generated by gen-key-constants.py do not edit) */ + case GLFW_DEFAULT_CURSOR: return set_cursor_from_font(cursor, XC_left_ptr); + case GLFW_TEXT_CURSOR: return set_cursor_from_font(cursor, XC_xterm); + case GLFW_POINTER_CURSOR: return set_cursor_from_font(cursor, XC_hand2); + case GLFW_HELP_CURSOR: return set_cursor_from_font(cursor, XC_question_arrow); + case GLFW_WAIT_CURSOR: return set_cursor_from_font(cursor, XC_clock); + case GLFW_PROGRESS_CURSOR: return try_cursor_names(cursor, 3, "progress", "half-busy", "left_ptr_watch"); + case GLFW_CROSSHAIR_CURSOR: return set_cursor_from_font(cursor, XC_tcross); + case GLFW_VERTICAL_TEXT_CURSOR: return try_cursor_names(cursor, 1, "vertical-text"); + case GLFW_MOVE_CURSOR: return set_cursor_from_font(cursor, XC_fleur); + case GLFW_E_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_right_side); + case GLFW_NE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_right_corner); + case GLFW_NW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_left_corner); + case GLFW_N_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_side); + case GLFW_SE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_right_corner); + case GLFW_SW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_left_corner); + case GLFW_S_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_side); + case GLFW_W_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_left_side); + case GLFW_EW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_h_double_arrow); + case GLFW_NS_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_v_double_arrow); + case GLFW_NESW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_v_double_arrow); + case GLFW_NWSE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_h_double_arrow); + case GLFW_ZOOM_IN_CURSOR: return try_cursor_names(cursor, 2, "zoom-in", "zoom_in"); + case GLFW_ZOOM_OUT_CURSOR: return try_cursor_names(cursor, 2, "zoom-out", "zoom_out"); + case GLFW_ALIAS_CURSOR: return try_cursor_names(cursor, 1, "dnd-link"); + case GLFW_COPY_CURSOR: return try_cursor_names(cursor, 1, "dnd-copy"); + case GLFW_NOT_ALLOWED_CURSOR: return try_cursor_names(cursor, 3, "not-allowed", "forbidden", "crossed_circle"); + case GLFW_NO_DROP_CURSOR: return try_cursor_names(cursor, 2, "no-drop", "dnd-no-drop"); + case GLFW_GRAB_CURSOR: return set_cursor_from_font(cursor, XC_hand1); + case GLFW_GRABBING_CURSOR: return try_cursor_names(cursor, 3, "grabbing", "closedhand", "dnd-none"); +/* end glfw to xc mapping */ + case GLFW_INVALID_CURSOR: return false; + } + return false; +} + void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) { if (cursor->x11.handle) diff --git a/kitty/data-types.h b/kitty/data-types.h index ab04467c5..23fc17f27 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -68,7 +68,40 @@ typedef enum { DISABLE_LIGATURES_NEVER, DISABLE_LIGATURES_CURSOR, DISABLE_LIGATU #define ERROR_PREFIX "[PARSE ERROR]" typedef enum MouseTrackingModes { NO_TRACKING, BUTTON_MODE, MOTION_MODE, ANY_MODE } MouseTrackingMode; typedef enum MouseTrackingProtocols { NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL, SGR_PIXEL_PROTOCOL} MouseTrackingProtocol; -typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape; +typedef enum MouseShapes { + INVALID_POINTER, + /* start mouse shapes (auto generated by gen-key-constants.py do not edit) */ + DEFAULT_POINTER, + TEXT_POINTER, + POINTER_POINTER, + HELP_POINTER, + WAIT_POINTER, + PROGRESS_POINTER, + CROSSHAIR_POINTER, + VERTICAL_TEXT_POINTER, + MOVE_POINTER, + E_RESIZE_POINTER, + NE_RESIZE_POINTER, + NW_RESIZE_POINTER, + N_RESIZE_POINTER, + SE_RESIZE_POINTER, + SW_RESIZE_POINTER, + S_RESIZE_POINTER, + W_RESIZE_POINTER, + EW_RESIZE_POINTER, + NS_RESIZE_POINTER, + NESW_RESIZE_POINTER, + NWSE_RESIZE_POINTER, + ZOOM_IN_POINTER, + ZOOM_OUT_POINTER, + ALIAS_POINTER, + COPY_POINTER, + NOT_ALLOWED_POINTER, + NO_DROP_POINTER, + GRAB_POINTER, + GRABBING_POINTER, +/* end mouse shapes */ +} MouseShape; typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn; typedef enum { TILING, SCALED, MIRRORED, CLAMPED, CENTER_CLAMPED, CENTER_SCALED } BackgroundImageLayout; typedef struct ImageAnchorPosition { diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index e32e7e880..ba820ed04 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -43,7 +43,6 @@ IMPERATIVE_CLOSE_REQUESTED: int CLOSE_BEING_CONFIRMED: int ERROR_PREFIX: str GLSL_VERSION: int -GLFW_IBEAM_CURSOR: int # start glfw functional keys (auto generated by gen-key-constants.py do not edit) GLFW_FKEY_ESCAPE: int GLFW_FKEY_ENTER: int @@ -565,7 +564,7 @@ def set_default_window_icon(path: str) -> None: def set_custom_cursor( - cursor_type: int, + cursor_shape: str, images: Tuple[Tuple[bytes, int, int], ...], x: int = 0, y: int = 0 diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index f6baec851..941a164f3 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -837,17 +837,38 @@ typedef enum { * @{ */ typedef enum { - GLFW_ARROW_CURSOR, - GLFW_IBEAM_CURSOR, +/* start mouse cursor shapes (auto generated by gen-key-constants.py do not edit) */ + GLFW_DEFAULT_CURSOR, + GLFW_TEXT_CURSOR, + GLFW_POINTER_CURSOR, + GLFW_HELP_CURSOR, + GLFW_WAIT_CURSOR, + GLFW_PROGRESS_CURSOR, GLFW_CROSSHAIR_CURSOR, - GLFW_HAND_CURSOR, - GLFW_HRESIZE_CURSOR, - GLFW_VRESIZE_CURSOR, - GLFW_NW_RESIZE_CURSOR, + GLFW_VERTICAL_TEXT_CURSOR, + GLFW_MOVE_CURSOR, + GLFW_E_RESIZE_CURSOR, GLFW_NE_RESIZE_CURSOR, - GLFW_SW_RESIZE_CURSOR, + GLFW_NW_RESIZE_CURSOR, + GLFW_N_RESIZE_CURSOR, GLFW_SE_RESIZE_CURSOR, - GLFW_INVALID_CURSOR + GLFW_SW_RESIZE_CURSOR, + GLFW_S_RESIZE_CURSOR, + GLFW_W_RESIZE_CURSOR, + GLFW_EW_RESIZE_CURSOR, + GLFW_NS_RESIZE_CURSOR, + GLFW_NESW_RESIZE_CURSOR, + GLFW_NWSE_RESIZE_CURSOR, + GLFW_ZOOM_IN_CURSOR, + GLFW_ZOOM_OUT_CURSOR, + GLFW_ALIAS_CURSOR, + GLFW_COPY_CURSOR, + GLFW_NOT_ALLOWED_CURSOR, + GLFW_NO_DROP_CURSOR, + GLFW_GRAB_CURSOR, + GLFW_GRABBING_CURSOR, + GLFW_INVALID_CURSOR, +/* end mouse cursor shapes */ } GLFWCursorShape; /*! @} */ diff --git a/kitty/glfw.c b/kitty/glfw.c index f00cc9344..0ab247773 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -33,7 +33,12 @@ extern size_t cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t arr extern monotonic_t cocoa_cursor_blink_interval(void); -static GLFWcursor *standard_cursor = NULL, *click_cursor = NULL, *arrow_cursor = NULL; +typedef struct mouse_cursor { + GLFWcursor *glfw; + bool initialized, is_custom; +} mouse_cursor; + +static mouse_cursor cursors[GLFW_INVALID_CURSOR+1] = {0}; static void set_os_window_dpi(OSWindow *w); @@ -675,20 +680,52 @@ draw_single_ascii_char(const char ch, size_t *result_width, size_t *result_heigh #endif // }}} +static void +set_glfw_mouse_cursor(GLFWwindow *w, GLFWCursorShape shape) { + if (!cursors[shape].initialized) { + cursors[shape].initialized = true; + cursors[shape].glfw = glfwCreateStandardCursor(shape); + } + if (cursors[shape].glfw) glfwSetCursor(w, cursors[shape].glfw); +} + void set_mouse_cursor(MouseShape type) { if (global_state.callback_os_window) { GLFWwindow *w = (GLFWwindow*)global_state.callback_os_window->handle; switch(type) { - case HAND: - glfwSetCursor(w, click_cursor); - break; - case ARROW: - glfwSetCursor(w, arrow_cursor); - break; - default: - glfwSetCursor(w, standard_cursor); - break; + case INVALID_POINTER: break; + /* start enum to glfw (auto generated by gen-key-constants.py do not edit) */ + case DEFAULT_POINTER: set_glfw_mouse_cursor(w, GLFW_DEFAULT_CURSOR); break; + case TEXT_POINTER: set_glfw_mouse_cursor(w, GLFW_TEXT_CURSOR); break; + case POINTER_POINTER: set_glfw_mouse_cursor(w, GLFW_POINTER_CURSOR); break; + case HELP_POINTER: set_glfw_mouse_cursor(w, GLFW_HELP_CURSOR); break; + case WAIT_POINTER: set_glfw_mouse_cursor(w, GLFW_WAIT_CURSOR); break; + case PROGRESS_POINTER: set_glfw_mouse_cursor(w, GLFW_PROGRESS_CURSOR); break; + case CROSSHAIR_POINTER: set_glfw_mouse_cursor(w, GLFW_CROSSHAIR_CURSOR); break; + case VERTICAL_TEXT_POINTER: set_glfw_mouse_cursor(w, GLFW_VERTICAL_TEXT_CURSOR); break; + case MOVE_POINTER: set_glfw_mouse_cursor(w, GLFW_MOVE_CURSOR); break; + case E_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_E_RESIZE_CURSOR); break; + case NE_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NE_RESIZE_CURSOR); break; + case NW_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NW_RESIZE_CURSOR); break; + case N_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_N_RESIZE_CURSOR); break; + case SE_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_SE_RESIZE_CURSOR); break; + case SW_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_SW_RESIZE_CURSOR); break; + case S_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_S_RESIZE_CURSOR); break; + case W_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_W_RESIZE_CURSOR); break; + case EW_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_EW_RESIZE_CURSOR); break; + case NS_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NS_RESIZE_CURSOR); break; + case NESW_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NESW_RESIZE_CURSOR); break; + case NWSE_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NWSE_RESIZE_CURSOR); break; + case ZOOM_IN_POINTER: set_glfw_mouse_cursor(w, GLFW_ZOOM_IN_CURSOR); break; + case ZOOM_OUT_POINTER: set_glfw_mouse_cursor(w, GLFW_ZOOM_OUT_CURSOR); break; + case ALIAS_POINTER: set_glfw_mouse_cursor(w, GLFW_ALIAS_CURSOR); break; + case COPY_POINTER: set_glfw_mouse_cursor(w, GLFW_COPY_CURSOR); break; + case NOT_ALLOWED_POINTER: set_glfw_mouse_cursor(w, GLFW_NOT_ALLOWED_CURSOR); break; + case NO_DROP_POINTER: set_glfw_mouse_cursor(w, GLFW_NO_DROP_CURSOR); break; + case GRAB_POINTER: set_glfw_mouse_cursor(w, GLFW_GRAB_CURSOR); break; + case GRABBING_POINTER: set_glfw_mouse_cursor(w, GLFW_GRABBING_CURSOR); break; +/* end enum to glfw */ } } } @@ -1090,13 +1127,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { PyObject *ret = PyObject_CallFunction(load_programs, "O", is_semi_transparent ? Py_True : Py_False); if (ret == NULL) return NULL; Py_DECREF(ret); -#define CC(dest, shape) {\ - if (!dest##_cursor) { \ - dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \ - if (dest##_cursor == NULL) { log_error("Failed to create the %s mouse cursor, using default cursor.", #shape); } \ -}} - CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW); -#undef CC get_platform_dependent_config_values(glfw_window); is_first_window = false; } @@ -1121,7 +1151,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { #endif send_prerendered_sprites_for_window(w); if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo); - glfwSetCursor(glfw_window, standard_cursor); + set_glfw_mouse_cursor(glfw_window, GLFW_TEXT_CURSOR); update_os_window_viewport(w, false); glfwSetWindowPosCallback(glfw_window, window_pos_callback); // missing size callback @@ -1310,6 +1340,12 @@ glfw_init(PyObject UNUSED *self, PyObject *args) { static PyObject* glfw_terminate(PYNOARG) { + for (size_t i = 0; i < arraysz(cursors); i++) { + if (cursors[i].is_custom && cursors[i].glfw) { + glfwDestroyCursor(cursors[i].glfw); + cursors[i] = (mouse_cursor){0}; + } + } glfwTerminate(); Py_RETURN_NONE; } @@ -1692,13 +1728,51 @@ cocoa_window_id(PyObject UNUSED *self, PyObject *os_wid) { #endif } +static GLFWCursorShape +pointer_name_to_glfw_name(const char *name) { + /* start name to glfw (auto generated by gen-key-constants.py do not edit) */ + if (strcmp(name, "arrow") == 0) return GLFW_DEFAULT_CURSOR; + if (strcmp(name, "beam") == 0) return GLFW_TEXT_CURSOR; + if (strcmp(name, "text") == 0) return GLFW_TEXT_CURSOR; + if (strcmp(name, "pointer") == 0) return GLFW_POINTER_CURSOR; + if (strcmp(name, "hand") == 0) return GLFW_POINTER_CURSOR; + if (strcmp(name, "help") == 0) return GLFW_HELP_CURSOR; + if (strcmp(name, "wait") == 0) return GLFW_WAIT_CURSOR; + if (strcmp(name, "progress") == 0) return GLFW_PROGRESS_CURSOR; + if (strcmp(name, "crosshair") == 0) return GLFW_CROSSHAIR_CURSOR; + if (strcmp(name, "vertical-text") == 0) return GLFW_VERTICAL_TEXT_CURSOR; + if (strcmp(name, "move") == 0) return GLFW_MOVE_CURSOR; + if (strcmp(name, "e-resize") == 0) return GLFW_E_RESIZE_CURSOR; + if (strcmp(name, "ne-resize") == 0) return GLFW_NE_RESIZE_CURSOR; + if (strcmp(name, "nw-resize") == 0) return GLFW_NW_RESIZE_CURSOR; + if (strcmp(name, "n-resize") == 0) return GLFW_N_RESIZE_CURSOR; + if (strcmp(name, "se-resize") == 0) return GLFW_SE_RESIZE_CURSOR; + if (strcmp(name, "sw-resize") == 0) return GLFW_SW_RESIZE_CURSOR; + if (strcmp(name, "s-resize") == 0) return GLFW_S_RESIZE_CURSOR; + if (strcmp(name, "w-resize") == 0) return GLFW_W_RESIZE_CURSOR; + if (strcmp(name, "ew-resize") == 0) return GLFW_EW_RESIZE_CURSOR; + if (strcmp(name, "ns-resize") == 0) return GLFW_NS_RESIZE_CURSOR; + if (strcmp(name, "nesw-resize") == 0) return GLFW_NESW_RESIZE_CURSOR; + if (strcmp(name, "nwse-resize") == 0) return GLFW_NWSE_RESIZE_CURSOR; + if (strcmp(name, "zoom-in") == 0) return GLFW_ZOOM_IN_CURSOR; + if (strcmp(name, "zoom-out") == 0) return GLFW_ZOOM_OUT_CURSOR; + if (strcmp(name, "alias") == 0) return GLFW_ALIAS_CURSOR; + if (strcmp(name, "copy") == 0) return GLFW_COPY_CURSOR; + if (strcmp(name, "not-allowed") == 0) return GLFW_NOT_ALLOWED_CURSOR; + if (strcmp(name, "no-drop") == 0) return GLFW_NO_DROP_CURSOR; + if (strcmp(name, "grab") == 0) return GLFW_GRAB_CURSOR; + if (strcmp(name, "grabbing") == 0) return GLFW_GRABBING_CURSOR; +/* end name to glfw */ + return GLFW_INVALID_CURSOR; +} + static PyObject* set_custom_cursor(PyObject *self UNUSED, PyObject *args) { - int shape; int x=0, y=0; Py_ssize_t sz; PyObject *images; - if (!PyArg_ParseTuple(args, "iO!|ii", &shape, &PyTuple_Type, &images, &x, &y)) return NULL; + const char *shape; + if (!PyArg_ParseTuple(args, "sO!|ii", &shape, &PyTuple_Type, &images, &x, &y)) return NULL; static GLFWimage gimages[16] = {{0}}; size_t count = MIN((size_t)PyTuple_GET_SIZE(images), arraysz(gimages)); for (size_t i = 0; i < count; i++) { @@ -1708,20 +1782,14 @@ set_custom_cursor(PyObject *self UNUSED, PyObject *args) { return NULL; } } -#define CASE(which, dest) {\ - case which: \ - dest = glfwCreateCursor(gimages, x, y, count); \ - if (dest == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create custom cursor"); return NULL; } \ - break; \ -} - switch(shape) { - CASE(GLFW_IBEAM_CURSOR, standard_cursor); - CASE(GLFW_HAND_CURSOR, click_cursor); - CASE(GLFW_ARROW_CURSOR, arrow_cursor); - default: - PyErr_SetString(PyExc_ValueError, "Unknown cursor shape"); - return NULL; + GLFWCursorShape gshape = pointer_name_to_glfw_name(shape); + if (gshape == GLFW_INVALID_CURSOR) { PyErr_Format(PyExc_KeyError, "Unknown pointer shape: %s", shape); return NULL; } + GLFWcursor *c = glfwCreateCursor(gimages, x, y, count); + if (c == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create custom cursor from specified images"); return NULL; } + if (cursors[gshape].initialized && cursors[gshape].is_custom && cursors[gshape].glfw) { + glfwDestroyCursor(cursors[gshape].glfw); } + cursors[gshape].initialized = true; cursors[gshape].is_custom = true; cursors[gshape].glfw = c; Py_RETURN_NONE; } @@ -2002,7 +2070,6 @@ init_glfw(PyObject *m) { ADDC(GLFW_PRESS); ADDC(GLFW_REPEAT); ADDC(true); ADDC(false); - ADDC(GLFW_IBEAM_CURSOR); ADDC(GLFW_HAND_CURSOR); ADDC(GLFW_ARROW_CURSOR); ADDC(GLFW_PRIMARY_SELECTION); ADDC(GLFW_CLIPBOARD); /* start glfw functional keys (auto generated by gen-key-constants.py do not edit) */ diff --git a/kitty/main.py b/kitty/main.py index c984ae591..dab82ba9f 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -30,7 +30,6 @@ from .constants import ( website_url, ) from .fast_data_types import ( - GLFW_IBEAM_CURSOR, GLFW_MOD_ALT, GLFW_MOD_SHIFT, SingleKey, @@ -76,7 +75,7 @@ def set_custom_ibeam_cursor() -> None: rgba_data2, width2, height2 = load_png_data(data) images = (rgba_data, width, height), (rgba_data2, width2, height2) try: - set_custom_cursor(GLFW_IBEAM_CURSOR, images, 4, 8) + set_custom_cursor("beam", images, 4, 8) except Exception as e: log_error(f'Failed to set custom beam cursor with error: {e}') diff --git a/kitty/mouse.c b/kitty/mouse.c index 3860cfce4..ca38eda54 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -17,7 +17,7 @@ extern PyTypeObject Screen_Type; -static MouseShape mouse_cursor_shape = BEAM; +static MouseShape mouse_cursor_shape = TEXT_POINTER; typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction; #define debug(...) if (OPT(debug_keyboard)) printf(__VA_ARGS__); @@ -288,8 +288,8 @@ do_drag_scroll(Window *w, bool upwards) { if (screen->linebuf == screen->main_linebuf) { screen_history_scroll(screen, SCROLL_LINE, upwards); update_drag(w); - if (mouse_cursor_shape != ARROW) { - mouse_cursor_shape = ARROW; + if (mouse_cursor_shape != DEFAULT_POINTER) { + mouse_cursor_shape = DEFAULT_POINTER; set_mouse_cursor(mouse_cursor_shape); } return true; @@ -347,7 +347,7 @@ detect_url(Screen *screen, unsigned int x, unsigned int y) { int hid = screen_detect_url(screen, x, y); screen->current_hyperlink_under_mouse.id = 0; if (hid != 0) { - mouse_cursor_shape = HAND; + mouse_cursor_shape = POINTER_POINTER; if (hid > 0) { screen->current_hyperlink_under_mouse.id = (hyperlink_id_type)hid; screen->current_hyperlink_under_mouse.x = x; @@ -655,7 +655,7 @@ focus_in_event(void) { // Ensure that no URL is highlighted and the mouse cursor is in default shape bool in_tab_bar; unsigned int window_idx = 0; - mouse_cursor_shape = BEAM; + mouse_cursor_shape = TEXT_POINTER; Window *w = window_for_event(&window_idx, &in_tab_bar); if (w && w->render_data.screen) { screen_mark_url(w->render_data.screen, 0, 0, 0, 0); @@ -817,7 +817,7 @@ mouse_event(const int button, int modifiers, int action) { } w = window_for_event(&window_idx, &in_tab_bar); if (in_tab_bar) { - mouse_cursor_shape = HAND; + mouse_cursor_shape = POINTER_POINTER; handle_tab_bar_mouse(button, modifiers, action); debug("handled by tab bar\n"); } else if (w) { diff --git a/kitty/options/definition.py b/kitty/options/definition.py index f29110110..92a61bcdd 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -588,27 +588,61 @@ Set the active window to the window under the mouse when moving the mouse around ''' ) +pointer_shape_names = ( +# start pointer shape names (auto generated by gen-key-constants.py do not edit) + 'arrow', + 'beam', + 'text', + 'pointer', + 'hand', + 'help', + 'wait', + 'progress', + 'crosshair', + 'vertical-text', + 'move', + 'e-resize', + 'ne-resize', + 'nw-resize', + 'n-resize', + 'se-resize', + 'sw-resize', + 's-resize', + 'w-resize', + 'ew-resize', + 'ns-resize', + 'nesw-resize', + 'nwse-resize', + 'zoom-in', + 'zoom-out', + 'alias', + 'copy', + 'not-allowed', + 'no-drop', + 'grab', + 'grabbing', +# end pointer shape names +) + opt('pointer_shape_when_grabbed', 'arrow', - choices=('arrow', 'beam', 'hand'), ctype='pointer_shape', + choices=pointer_shape_names, ctype='pointer_shape', long_text=''' The shape of the mouse pointer when the program running in the terminal grabs -the mouse. Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`. +the mouse. ''' ) opt('default_pointer_shape', 'beam', - choices=('arrow', 'beam', 'hand'), ctype='pointer_shape', + choices=pointer_shape_names, ctype='pointer_shape', long_text=''' -The default shape of the mouse pointer. Valid values are: :code:`arrow`, -:code:`beam` and :code:`hand`. +The default shape of the mouse pointer. ''' ) opt('pointer_shape_when_dragging', 'beam', - choices=('arrow', 'beam', 'hand'), ctype='pointer_shape', + choices=pointer_shape_names, ctype='pointer_shape', long_text=''' -The default shape of the mouse pointer when dragging across text. Valid values -are: :code:`arrow`, :code:`beam` and :code:`hand`. +The default shape of the mouse pointer when dragging across text. ''' ) diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 60a0bea56..fae488ca1 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -934,7 +934,7 @@ class Parser: raise ValueError(f"The value {val} is not a valid choice for default_pointer_shape") ans["default_pointer_shape"] = val - choices_for_default_pointer_shape = frozenset(('arrow', 'beam', 'hand')) + choices_for_default_pointer_shape = frozenset(('arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing')) def detect_urls(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['detect_urls'] = to_bool(val) @@ -1140,7 +1140,7 @@ class Parser: raise ValueError(f"The value {val} is not a valid choice for pointer_shape_when_dragging") ans["pointer_shape_when_dragging"] = val - choices_for_pointer_shape_when_dragging = frozenset(('arrow', 'beam', 'hand')) + choices_for_pointer_shape_when_dragging = frozenset(('arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing')) def pointer_shape_when_grabbed(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: val = val.lower() @@ -1148,7 +1148,7 @@ class Parser: raise ValueError(f"The value {val} is not a valid choice for pointer_shape_when_grabbed") ans["pointer_shape_when_grabbed"] = val - choices_for_pointer_shape_when_grabbed = frozenset(('arrow', 'beam', 'hand')) + choices_for_pointer_shape_when_grabbed = frozenset(('arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing')) def remember_window_size(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['remember_window_size'] = to_bool(val) diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index abf78e933..9356751ee 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -133,13 +133,41 @@ modify_font(PyObject *mf, Options *opts) { static MouseShape pointer_shape(PyObject *shape_name) { const char *name = PyUnicode_AsUTF8(shape_name); - switch(name[0]) { - case 'a': return ARROW; - case 'h': return HAND; - case 'b': return BEAM; - default: break; - } - return BEAM; + if (!name) return TEXT_POINTER; + /* start pointer shapes (auto generated by gen-key-constants.py do not edit) */ + else if (strcmp(name, "arrow") == 0) return DEFAULT_POINTER; + else if (strcmp(name, "beam") == 0) return TEXT_POINTER; + else if (strcmp(name, "text") == 0) return TEXT_POINTER; + else if (strcmp(name, "pointer") == 0) return POINTER_POINTER; + else if (strcmp(name, "hand") == 0) return POINTER_POINTER; + else if (strcmp(name, "help") == 0) return HELP_POINTER; + else if (strcmp(name, "wait") == 0) return WAIT_POINTER; + else if (strcmp(name, "progress") == 0) return PROGRESS_POINTER; + else if (strcmp(name, "crosshair") == 0) return CROSSHAIR_POINTER; + else if (strcmp(name, "vertical-text") == 0) return VERTICAL_TEXT_POINTER; + else if (strcmp(name, "move") == 0) return MOVE_POINTER; + else if (strcmp(name, "e-resize") == 0) return E_RESIZE_POINTER; + else if (strcmp(name, "ne-resize") == 0) return NE_RESIZE_POINTER; + else if (strcmp(name, "nw-resize") == 0) return NW_RESIZE_POINTER; + else if (strcmp(name, "n-resize") == 0) return N_RESIZE_POINTER; + else if (strcmp(name, "se-resize") == 0) return SE_RESIZE_POINTER; + else if (strcmp(name, "sw-resize") == 0) return SW_RESIZE_POINTER; + else if (strcmp(name, "s-resize") == 0) return S_RESIZE_POINTER; + else if (strcmp(name, "w-resize") == 0) return W_RESIZE_POINTER; + else if (strcmp(name, "ew-resize") == 0) return EW_RESIZE_POINTER; + else if (strcmp(name, "ns-resize") == 0) return NS_RESIZE_POINTER; + else if (strcmp(name, "nesw-resize") == 0) return NESW_RESIZE_POINTER; + else if (strcmp(name, "nwse-resize") == 0) return NWSE_RESIZE_POINTER; + else if (strcmp(name, "zoom-in") == 0) return ZOOM_IN_POINTER; + else if (strcmp(name, "zoom-out") == 0) return ZOOM_OUT_POINTER; + else if (strcmp(name, "alias") == 0) return ALIAS_POINTER; + else if (strcmp(name, "copy") == 0) return COPY_POINTER; + else if (strcmp(name, "not-allowed") == 0) return NOT_ALLOWED_POINTER; + else if (strcmp(name, "no-drop") == 0) return NO_DROP_POINTER; + else if (strcmp(name, "grab") == 0) return GRAB_POINTER; + else if (strcmp(name, "grabbing") == 0) return GRABBING_POINTER; +/* end pointer shapes */ + return TEXT_POINTER; } static int diff --git a/kitty/options/types.py b/kitty/options/types.py index 01155fb01..c520e7910 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -17,13 +17,13 @@ if typing.TYPE_CHECKING: choices_for_allow_cloning = typing.Literal['yes', 'y', 'true', 'no', 'n', 'false', 'ask'] choices_for_allow_remote_control = typing.Literal['password', 'socket-only', 'socket', 'no', 'n', 'false', 'yes', 'y', 'true'] choices_for_background_image_layout = typing.Literal['mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered', 'cscaled'] - choices_for_default_pointer_shape = typing.Literal['arrow', 'beam', 'hand'] + choices_for_default_pointer_shape = typing.Literal['arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing'] choices_for_linux_display_server = typing.Literal['auto', 'wayland', 'x11'] choices_for_macos_colorspace = typing.Literal['srgb', 'default', 'displayp3'] choices_for_macos_show_window_title_in = typing.Literal['all', 'menubar', 'none', 'window'] choices_for_placement_strategy = typing.Literal['center', 'top-left'] - choices_for_pointer_shape_when_dragging = typing.Literal['arrow', 'beam', 'hand'] - choices_for_pointer_shape_when_grabbed = typing.Literal['arrow', 'beam', 'hand'] + choices_for_pointer_shape_when_dragging = typing.Literal['arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing'] + choices_for_pointer_shape_when_grabbed = typing.Literal['arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing'] choices_for_strip_trailing_spaces = typing.Literal['always', 'never', 'smart'] choices_for_tab_bar_align = typing.Literal['left', 'center', 'right'] choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator', 'slant', 'custom']