kitty/gen/cursors.py
Kovid Goyal 406a92fc69
handle unknown pointer names in OSC 22 more gracefully
Also, allow using beam and arrow as aliases of text and default when
setting pointer shapes. These are the names kitty itself reports, so
they should be allowed in addition to the canonical list from the spec.

Fixes #10372
2026-08-19 04:56:27 +05:30

171 lines
9.2 KiB
Python
Executable file

#!/usr/bin/env python
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
import os
import subprocess
import sys
if __name__ == '__main__' and not __package__:
import __main__
__main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from .key_constants import patch_file
# References for these names:
# CSS:choices_for_{option.name} 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
cell cell cell,!plus,!cross cell cell: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,size-bdiag nesw_resize _windowResizeNorthEastSouthWestCursor
nwse-resize nwse-resize nwse-resize,size_fdiag,size-fdiag 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 = []
css_names = []
glfw_xc_map = {}
glfw_xfont_map = []
kitty_to_enum_map = {}
enum_to_glfw_map = {}
enum_to_css_map = {}
glfw_cocoa_map = {}
glfw_css_map = {}
css_to_enum = {}
xc_to_enum = {}
glfw_wayland = {}
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
enum_to_css_map[enum_name] = css
glfw_css_map[glfw_name] = css
css_to_enum[css] = enum_name
css_names.append(css)
glfw_wayland[glfw_name] = 'WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_' + wayland.replace('-', '_').upper()
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)});')
for x in xc:
x = x.lstrip('!')
xc_to_enum[x] = enum_name
parts = cocoa.split(':', 1)
if len(parts) == 1:
if parts[0].startswith('_'):
glfw_cocoa_map[glfw_name] = f'U({glfw_name}, {parts[0]});'
else:
glfw_cocoa_map[glfw_name] = f'C({glfw_name}, {parts[0]});'
else:
glfw_cocoa_map[glfw_name] = f'S({glfw_name}, {parts[0]}, {parts[1]});'
for x, v in xc_to_enum.items():
if x not in css_to_enum:
css_to_enum[x] = v
for x, v in kitty_to_enum_map.items():
if x not in css_to_enum:
css_to_enum[x] = v
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 wayland mapping', '\n'.join(f' C({g}, {x});' for g, x in glfw_wayland.items()))
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/utils.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())
)
patch_file('kitty/glfw.c', 'glfw to css', '\n'.join(f' case {g}: return "{c}";' for g, c in glfw_css_map.items()))
patch_file('kitty/screen.c', 'enum to css', '\n'.join(f' case {e}: ans = "{c}"; break;' for e, c in enum_to_css_map.items()))
patch_file('kitty/screen.c', 'css to enum', '\n'.join(f' else if (strcmp("{c}", css_name) == 0) s = {e};' for c, e in css_to_enum.items()))
patch_file('glfw/cocoa_window.m', 'glfw to cocoa', '\n'.join(f' {x}' for x in glfw_cocoa_map.values()))
patch_file(
'docs/pointer-shapes.rst',
'list of shape css names',
'\n'.join(f'#. {x}' if x else '' for x in [''] + sorted(css_names) + ['']),
start_marker='.. ',
end_marker='',
)
patch_file(
'tools/tui/loop/mouse.go',
'pointer shape enum',
'\n'.join(f'\t{x} PointerShape = {i}' for i, x in enumerate(enum_to_glfw_map)),
start_marker='// ',
end_marker='',
)
patch_file(
'tools/tui/loop/mouse.go',
'pointer shape tostring',
'\n'.join(f'''\tcase {x}: return "{x.lower().rpartition('_')[0].replace('_', '-')}"''' for x in enum_to_glfw_map),
start_marker='// ',
end_marker='',
)
patch_file('tools/cmd/mouse_demo/main.go', 'all pointer shapes', '\n'.join(f'\tloop.{x},' for x in enum_to_glfw_map), start_marker='// ', end_marker='')
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'])