Work on porting kittens to use new key infrastructure

Also move type definitions into their own module
This commit is contained in:
Kovid Goyal 2021-01-14 21:35:48 +05:30
parent 0714fd376b
commit 027c5a57f1
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
30 changed files with 524 additions and 721 deletions

View File

@ -629,7 +629,8 @@ specification.
* No way to disambiguate :kbd:`Esc` keypresses, other than using 8-bit controls
which are undesirable for other reasons
* Incorrectly claims special keys are sometimes encoded using ``CSI letter`` encodings when it
is actually ``SS3 letter``.
is actually ``SS3 letter`` in all terminals newer than a VT-52, which is
pretty much everything.
* :kbd:`ctrl+shift+tab`` should be ``CSI 9 ; 6 u`` not ``CSI 1 ; 5 Z``
(shift+tab is not a separate key from tab)
* No support for the :kbd:`super` modifier.

View File

@ -9,7 +9,7 @@
from kitty.cli import parse_args
from kitty.cli_stub import BroadcastCLIOptions
from kitty.key_encoding import RELEASE, encode_key_event, key_defs as K
from kitty.key_encoding import encode_key_event
from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION
from kitty.remote_control import create_basic_command, encode_send
from kitty.typing import KeyEventType, ScreenSize
@ -59,15 +59,15 @@ def on_eot(self) -> None:
def on_key(self, key_event: KeyEventType) -> None:
if self.line_edit.on_key(key_event):
self.commit_line()
if key_event.type is not RELEASE and not key_event.mods:
if key_event.key is K['ENTER']:
self.write_broadcast_text('\r')
self.print('')
self.line_edit.clear()
self.write(SAVE_CURSOR)
return
if key_event.matches('enter'):
self.write_broadcast_text('\r')
self.print('')
self.line_edit.clear()
self.write(SAVE_CURSOR)
return
ek = encode_key_event(key_event)
ek = standard_b64encode(ek.encode('utf-8')).decode('ascii')
self.write_broadcast_data('kitty-key:' + ek)
def write_broadcast_text(self, text: str) -> None:

View File

@ -22,7 +22,7 @@
from kitty.conf.utils import KittensKeyAction
from kitty.constants import appname
from kitty.fast_data_types import wcswidth
from kitty.key_encoding import RELEASE, KeyEvent, enter_key, key_defs as K
from kitty.key_encoding import KeyEvent, EventType
from kitty.options_stub import DiffOptions
from kitty.utils import ScreenSize
@ -57,7 +57,6 @@ def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, s
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
ESCAPE = K['ESCAPE']
def generate_diff(collection: Collection, context: int) -> Union[str, Dict[str, Patch]]:
@ -483,48 +482,30 @@ def do_search(self) -> None:
self.message = sanitize(_('No matches found'))
self.cmd.bell()
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
if self.state is COMMAND:
self.line_edit.on_text(text, in_bracketed_paste)
self.draw_status_line()
return
if self.state is MESSAGE:
self.state = DIFFED
self.draw_status_line()
return
action = self.shortcut_action(text)
if action is not None:
return self.perform_action(action)
def on_key(self, key_event: KeyEvent) -> None:
if self.state is MESSAGE:
if key_event.type is not RELEASE:
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
if key_event.text:
if self.state is COMMAND:
self.line_edit.on_text(key_event.text, in_bracketed_paste)
self.draw_status_line()
return
if self.state is MESSAGE:
self.state = DIFFED
self.draw_status_line()
return
if self.state is COMMAND:
if self.line_edit.on_key(key_event):
if not self.line_edit.current_input:
return
else:
if self.state is MESSAGE:
if key_event.type is not EventType.RELEASE:
self.state = DIFFED
self.draw_status_line()
self.draw_status_line()
return
if key_event.type is RELEASE:
return
if self.state is COMMAND:
if key_event.key is ESCAPE:
self.state = DIFFED
self.draw_status_line()
if self.state is COMMAND:
if self.line_edit.on_key(key_event):
if not self.line_edit.current_input:
self.state = DIFFED
self.draw_status_line()
return
if key_event.type is EventType.RELEASE:
return
if key_event is enter_key:
self.state = DIFFED
self.do_search()
self.line_edit.clear()
self.draw_screen()
return
if self.state >= DIFFED and self.current_search is not None and key_event.key is ESCAPE:
self.current_search = None
self.draw_screen()
return
action = self.shortcut_action(key_event)
if action is not None:
return self.perform_action(action)

View File

@ -17,9 +17,7 @@
from kitty.cli import parse_args
from kitty.cli_stub import HintsCLIOptions
from kitty.fast_data_types import set_clipboard_string
from kitty.key_encoding import (
KeyEvent, backspace_key, enter_key, key_defs as K
)
from kitty.key_encoding import KeyEvent
from kitty.typing import BossType, KittyCommonOpts
from kitty.utils import ScreenSize, screen_size_function, set_primary_selection
@ -40,7 +38,6 @@ def kitty_common_opts() -> KittyCommonOpts:
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
ESCAPE = K['ESCAPE']
class Mark:
@ -177,11 +174,11 @@ def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
self.draw_screen()
def on_key(self, key_event: KeyEvent) -> None:
if key_event is backspace_key:
if key_event.matches('backspace'):
self.current_input = self.current_input[:-1]
self.current_text = None
self.draw_screen()
elif key_event is enter_key and self.current_input:
elif key_event.matches('enter') and self.current_input:
try:
idx = decode_hint(self.current_input, self.alphabet)
self.chosen.append(self.index_map[idx])
@ -196,7 +193,7 @@ def on_key(self, key_event: KeyEvent) -> None:
self.draw_screen()
else:
self.quit_loop(0)
elif key_event.key is ESCAPE:
elif key_event.matches('esc'):
self.quit_loop(0 if self.multiple else 1)
def on_interrupt(self) -> None:
@ -288,6 +285,7 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
@postprocessor
def ip(text: str, s: int, e: int) -> Tuple[int, int]:
from ipaddress import ip_address
# Check validity of IPs (or raise InvalidMatch)
ip = text[s:e]

View File

@ -8,6 +8,7 @@
Any, Callable, ContextManager, Dict, Optional, Sequence, Type, Union
)
from kitty.types import ParsedShortcut
from kitty.typing import (
AbstractEventLoop, BossType, Debug, ImageManagerType, KeyEventType,
KittensKeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType
@ -35,6 +36,7 @@ def _initialize(
self.debug = debug
self.cmd = commander(self)
self._image_manager = image_manager
self._key_shortcuts: Dict[ParsedShortcut, KittensKeyActionType] = {}
@property
def image_manager(self) -> ImageManagerType:
@ -45,18 +47,16 @@ def image_manager(self) -> ImageManagerType:
def asyncio_loop(self) -> AbstractEventLoop:
return self._tui_loop.asycio_loop
def add_shortcut(self, action: KittensKeyActionType, key: str, mods: Optional[int] = None, is_text: Optional[bool] = False) -> None:
if not hasattr(self, '_text_shortcuts'):
self._text_shortcuts, self._key_shortcuts = {}, {}
if is_text:
self._text_shortcuts[key] = action
else:
self._key_shortcuts[(key, mods or 0)] = action
def add_shortcut(self, action: KittensKeyActionType, spec: Union[str, ParsedShortcut]) -> None:
if isinstance(spec, str):
from kitty.key_encoding import parse_shortcut
spec = parse_shortcut(spec)
self._key_shortcuts[spec] = action
def shortcut_action(self, key_event_or_text: Union[str, KeyEventType]) -> Optional[KittensKeyActionType]:
if isinstance(key_event_or_text, str):
return self._text_shortcuts.get(key_event_or_text)
return self._key_shortcuts.get((key_event_or_text.key, key_event_or_text.mods))
def shortcut_action(self, key_event: KeyEventType) -> Optional[KittensKeyActionType]:
for sc, action in self._key_shortcuts.items():
if key_event.matches(sc):
return action
def __enter__(self) -> None:
if self._image_manager is not None:
@ -85,6 +85,12 @@ def quit_loop(self, return_code: Optional[int] = None) -> None:
def on_term(self) -> None:
self._tui_loop.quit(1)
def on_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.text:
self.on_text(key_event.text, in_bracketed_paste)
else:
self.on_key(key_event)
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
pass

View File

@ -5,17 +5,10 @@
from typing import Callable, Tuple
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.key_encoding import RELEASE, KeyEvent, key_defs as K
from kitty.key_encoding import EventType, KeyEvent
from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by
HOME = K['HOME']
END = K['END']
BACKSPACE = K['BACKSPACE']
DELETE = K['DELETE']
LEFT = K['LEFT']
RIGHT = K['RIGHT']
class LineEdit:
@ -137,22 +130,22 @@ def end(self) -> bool:
return self.cursor_pos != orig
def on_key(self, key_event: KeyEvent) -> bool:
if key_event.type is RELEASE:
if key_event.type is EventType.RELEASE:
return False
elif key_event.key is HOME:
if key_event.matches('home'):
return self.home()
elif key_event.key is END:
if key_event.matches('end'):
return self.end()
elif key_event.key is BACKSPACE:
if key_event.matches('backspace'):
self.backspace()
return True
elif key_event.key is DELETE:
if key_event.matches('delete'):
self.delete()
return True
elif key_event.key is LEFT:
if key_event.matches('left'):
self.left()
return True
elif key_event.key is RIGHT:
if key_event.matches('right'):
self.right()
return True
return False

View File

@ -21,7 +21,7 @@
)
from kitty.key_encoding import (
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, backspace_key, decode_key_event,
enter_key, key_defs as K
enter_key
)
from kitty.typing import ImageManagerType, KeyEventType, Protocol
from kitty.utils import ScreenSizeGetter, screen_size_function, write_all
@ -29,8 +29,6 @@
from .handler import Handler
from .operations import init_state, reset_state
C, D = K['C'], K['D']
class BinaryWrite(Protocol):
@ -149,7 +147,7 @@ def initialize(self) -> None:
self.write('Press the Enter key to quit')
def on_key(self, key_event: KeyEventType) -> None:
if key_event is enter_key:
if key_event.key == 'ENTER':
self.quit_loop(1)
def on_interrupt(self) -> None:
@ -282,8 +280,23 @@ def _on_csi(self, csi: str) -> None:
elif q == '~':
if csi == '200~':
self.in_bracketed_paste = True
return
elif csi == '201~':
self.in_bracketed_paste = False
return
elif q in 'u~ABCDHFPQRS':
try:
k = decode_key_event(csi[:-1], q)
except Exception:
pass
else:
if k.matches('ctrl+c'):
self.handler.on_interrupt()
return
if k.matches('ctrl+d'):
self.handler.on_eot()
return
self.handler.on_key_event(k)
def _on_pm(self, pm: str) -> None:
pass
@ -300,21 +313,7 @@ def _on_osc(self, osc: str) -> None:
self.handler.on_clipboard_response(standard_b64decode(rest).decode('utf-8'), from_primary)
def _on_apc(self, apc: str) -> None:
if apc.startswith('K'):
try:
k = decode_key_event(apc[1:])
except Exception:
pass
else:
if k.mods is CTRL and k.type is not RELEASE:
if k.key is C:
self.handler.on_interrupt()
return
if k.key is D:
self.handler.on_eot()
return
self.handler.on_key(k)
elif apc.startswith('G'):
if apc.startswith('G'):
if self.handler.image_manager is not None:
self.handler.image_manager.handle_response(apc)
# }}}

View File

@ -40,7 +40,6 @@
MOUSE_URXVT_MODE=(1015, '?'),
ALTERNATE_SCREEN=(1049, '?'),
BRACKETED_PASTE=(2004, '?'),
EXTENDED_KEYBOARD=(2017, '?'),
)
F = TypeVar('F')
@ -270,8 +269,8 @@ def init_state(alternate_screen: bool = True) -> str:
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
SAVE_COLORS +
set_mode('BRACKETED_PASTE') + SAVE_COLORS +
'\033[>31u' + # extended keyboard mode
'\033[*x' # reset DECSACE to default region select
)
if alternate_screen:
@ -287,6 +286,7 @@ def reset_state(normal_screen: bool = True) -> str:
ans += RESTORE_PRIVATE_MODE_VALUES
ans += RESTORE_CURSOR
ans += RESTORE_COLORS
ans += '\033[<u' # restore keyboard mode
return ans

View File

@ -25,8 +25,7 @@
)
from .config_data import MINIMUM_FONT_SIZE
from .constants import (
SingleKey, appname, config_dir, is_macos, kitty_exe,
supports_primary_selection
appname, config_dir, is_macos, kitty_exe, supports_primary_selection
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
@ -49,6 +48,7 @@
from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
)
from .types import SingleKey
from .typing import PopenType, TypedDict
from .utils import (
func_name, get_editor, get_primary_selection, is_path_in_temp_dir,

View File

@ -14,8 +14,9 @@
from .cli_stub import CLIOptions
from .conf.utils import resolve_config
from .config import KeyAction
from .constants import appname, defconf, is_macos, is_wayland, str_version, SingleKey
from .constants import appname, defconf, is_macos, is_wayland, str_version
from .options_stub import Options as OptionsStub
from .types import SingleKey
from .typing import BadLineType, SequenceMap, TypedDict
@ -759,7 +760,10 @@ def parse_args(
def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None:
from .fast_data_types import glfw_get_key_name, GLFW_MOD_ALT, GLFW_MOD_SHIFT, GLFW_MOD_CONTROL, GLFW_MOD_SUPER
from .fast_data_types import (
GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT, GLFW_MOD_SUPER,
glfw_get_key_name
)
mmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER}
keys = []
for key_spec in key_sequence:

View File

@ -11,7 +11,8 @@
)
from ..rgb import Color, to_color as as_color
from ..utils import log_error, expandvars
from ..types import ParsedShortcut
from ..utils import expandvars, log_error
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
T = TypeVar('T')
@ -301,38 +302,6 @@ def w(f: Callable) -> Callable:
return func_with_args, ans
def parse_kittens_shortcut(sc: str) -> Tuple[Optional[int], str, bool]:
from ..key_encoding import config_key_map, config_mod_map, text_match
if sc.endswith('+'):
parts = list(filter(None, sc.rstrip('+').split('+') + ['+']))
else:
parts = sc.split('+')
qmods = parts[:-1]
if qmods:
resolved_mods = 0
for mod in qmods:
m = config_mod_map.get(mod.upper())
if m is None:
raise ValueError('Unknown shortcut modifiers: {}'.format(sc))
resolved_mods |= m
mods: Optional[int] = resolved_mods
else:
mods = None
is_text = False
rkey = parts[-1]
tkey = text_match(rkey)
if tkey is None:
rkey = rkey.upper()
q = config_key_map.get(rkey)
if q is None:
raise ValueError('Unknown shortcut key: {}'.format(sc))
rkey = q
else:
is_text = True
rkey = tkey
return mods, rkey, is_text
KittensKeyAction = Tuple[str, Tuple[str, ...]]
@ -362,15 +331,12 @@ def parse_kittens_func_args(action: str, args_funcs: Dict[str, Callable]) -> Kit
return func, tuple(args)
KittensKey = Tuple[str, Optional[int], bool]
def parse_kittens_key(
val: str, funcs_with_args: Dict[str, Callable]
) -> Optional[Tuple[KittensKeyAction, KittensKey]]:
) -> Optional[Tuple[KittensKeyAction, ParsedShortcut]]:
from ..key_encoding import parse_shortcut
sc, action = val.partition(' ')[::2]
if not sc or not action:
return None
mods, key, is_text = parse_kittens_shortcut(sc)
ans = parse_kittens_func_args(action, funcs_with_args)
return ans, (key, mods, is_text)
return ans, parse_shortcut(sc)

View File

@ -20,9 +20,10 @@
parse_config_base, python_string, to_bool, to_cmdline
)
from .config_data import InvalidMods, all_options, parse_shortcut, type_convert
from .constants import SingleKey, cache_dir, defconf, is_macos
from .constants import cache_dir, defconf, is_macos
from .fonts import FontFeature
from .options_stub import Options as OptionsStub
from .types import SingleKey
from .typing import TypedDict
from .utils import expandvars, log_error

View File

@ -16,13 +16,15 @@
choices, positive_float, positive_int, to_bool, to_cmdline as tc, to_color,
to_color_or_none, unit_float
)
from .constants import (
FloatEdges, SingleKey, config_dir, is_macos
)
from .constants import config_dir, is_macos
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .key_names import get_key_name_lookup, key_name_aliases
from .key_names import (
character_key_name_aliases, functional_key_name_aliases,
get_key_name_lookup
)
from .layout.interface import all_layouts
from .rgb import Color, color_as_int, color_as_sharp, color_from_int
from .types import FloatEdges, SingleKey
from .utils import log_error
@ -60,6 +62,8 @@ def to_modifiers(val: str) -> int:
def parse_shortcut(sc: str) -> SingleKey:
if sc.endswith('+') and len(sc) > 1:
sc = sc[:-1] + 'plus'
parts = sc.split('+')
mods = 0
if len(parts) > 1:
@ -67,6 +71,7 @@ def parse_shortcut(sc: str) -> SingleKey:
if not mods:
raise InvalidMods('Invalid shortcut')
q = parts[-1]
q = character_key_name_aliases.get(q.upper(), q)
is_native = False
if q.startswith('0x'):
try:
@ -80,7 +85,7 @@ def parse_shortcut(sc: str) -> SingleKey:
key = ord(q)
except Exception:
uq = q.upper()
uq = key_name_aliases.get(uq, uq)
uq = functional_key_name_aliases.get(uq, uq)
x: Optional[int] = getattr(defines, f'GLFW_FKEY_{uq}', None)
if x is None:
lf = get_key_name_lookup()

View File

@ -27,45 +27,6 @@ class Version(NamedTuple):
base = os.path.dirname(os.path.abspath(__file__))
class Edges(NamedTuple):
left: int = 0
top: int = 0
right: int = 0
bottom: int = 0
class FloatEdges(NamedTuple):
left: float = 0
top: float = 0
right: float = 0
bottom: float = 0
class ScreenGeometry(NamedTuple):
xstart: float
ystart: float
xnum: int
ynum: int
dx: float
dy: float
class WindowGeometry(NamedTuple):
left: int
top: int
right: int
bottom: int
xnum: int
ynum: int
spaces: Edges = Edges()
class SingleKey(NamedTuple):
mods: int = 0
is_native: bool = False
key: int = -1
@lru_cache(maxsize=2)
def kitty_exe() -> str:
rpath = sys._xoptions.get('bundle_exe_dir')

822
kitty/key_encoding.py generated
View File

@ -2,476 +2,254 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import string
from typing import Dict, NamedTuple, Optional
from enum import IntEnum
from functools import lru_cache
from typing import Dict, NamedTuple, Optional, Tuple, Union
from . import fast_data_types as defines
from .key_names import key_name_aliases
from .fast_data_types import KeyEvent as WindowSystemKeyEvent
from .key_names import character_key_name_aliases, functional_key_name_aliases
from .types import ParsedShortcut
# ENCODING {{{
ENCODING = {
'0': 'G',
'1': 'H',
'2': 'I',
'3': 'J',
'4': 'K',
'5': 'L',
'6': 'M',
'7': 'N',
'8': 'O',
'9': 'P',
'A': 'S',
'APOSTROPHE': 'B',
'B': 'T',
'BACKSLASH': 't',
'BACKSPACE': '1',
'C': 'U',
'CAPS LOCK': ':',
'COMMA': 'C',
'D': 'V',
'DELETE': '3',
'DOWN': '6',
'E': 'W',
'END': '-',
'ENTER': 'z',
'EQUAL': 'R',
'ESCAPE': 'y',
'F': 'X',
'F1': '/',
'F10': ']',
'F11': '{',
'F12': '}',
'F13': '@',
'F14': '%',
'F15': '$',
'F16': '#',
'F17': 'BA',
'F18': 'BB',
'F19': 'BC',
'F2': '*',
'F20': 'BD',
'F21': 'BE',
'F22': 'BF',
'F23': 'BG',
'F24': 'BH',
'F25': 'BI',
'F3': '?',
'F4': '&',
'F5': '<',
'F6': '>',
'F7': '(',
'F8': ')',
'F9': '[',
'G': 'Y',
'GRAVE ACCENT': 'v',
'H': 'Z',
'HOME': '.',
'I': 'a',
'INSERT': '2',
'J': 'b',
'K': 'c',
'KP 0': 'BJ',
'KP 1': 'BK',
'KP 2': 'BL',
'KP 3': 'BM',
'KP 4': 'BN',
'KP 5': 'BO',
'KP 6': 'BP',
'KP 7': 'BQ',
'KP 8': 'BR',
'KP 9': 'BS',
'KP ADD': 'BX',
'KP DECIMAL': 'BT',
'KP DIVIDE': 'BU',
'KP ENTER': 'BY',
'KP EQUAL': 'BZ',
'KP MULTIPLY': 'BV',
'KP SUBTRACT': 'BW',
'L': 'd',
'LEFT': '5',
'LEFT ALT': 'Bc',
'LEFT BRACKET': 's',
'LEFT CONTROL': 'Bb',
'LEFT SHIFT': 'Ba',
'LEFT SUPER': 'Bd',
'M': 'e',
'MINUS': 'D',
'N': 'f',
'NUM LOCK': '=',
'O': 'g',
'P': 'h',
'PAGE DOWN': '9',
'PAGE UP': '8',
'PAUSE': '!',
'PERIOD': 'E',
'PRINT SCREEN': '^',
'Q': 'i',
'R': 'j',
'RIGHT': '4',
'RIGHT ALT': 'Bg',
'RIGHT BRACKET': 'u',
'RIGHT CONTROL': 'Bf',
'RIGHT SHIFT': 'Be',
'RIGHT SUPER': 'Bh',
'S': 'k',
'SCROLL LOCK': '+',
'SEMICOLON': 'Q',
'SLASH': 'F',
'SPACE': 'A',
'T': 'l',
'TAB': '0',
'U': 'm',
'UP': '7',
'V': 'n',
'W': 'o',
'WORLD 1': 'w',
'WORLD 2': 'x',
'X': 'p',
'Y': 'q',
'Z': 'r',
'PLUS': 'Bi',
'UNDERSCORE': 'Bj',
'MENU': 'Bk',
'EXCLAM': 'Bl',
'DOUBLE QUOTE': 'Bm',
'NUMBER SIGN': 'Bn',
'DOLLAR': 'Bo',
'AMPERSAND': 'Bp',
'PARENTHESIS LEFT': 'Bq',
'PARENTHESIS RIGHT': 'Br',
'COLON': 'Bs',
'LESS': 'Bt',
'GREATER': 'Bu',
'AT': 'Bv',
'PARAGRAPH': 'Bw',
'MASCULINE': 'Bx',
'A GRAVE': 'By',
'A DIAERESIS': 'Bz',
'A RING': 'B0',
'AE': 'B1',
'C CEDILLA': 'B2',
'E GRAVE': 'B3',
'E ACUTE': 'B4',
'I GRAVE': 'B5',
'N TILDE': 'B6',
'O GRAVE': 'B7',
'O DIAERESIS': 'B8',
'O SLASH': 'B9',
'U GRAVE': 'B.',
'U DIAERESIS': 'B-',
'S SHARP': 'B:',
'CYRILLIC A': 'B+',
'CYRILLIC BE': 'B=',
'CYRILLIC VE': 'B^',
'CYRILLIC GHE': 'B!',
'CYRILLIC DE': 'B/',
'CYRILLIC IE': 'B*',
'CYRILLIC ZHE': 'B?',
'CYRILLIC ZE': 'B&',
'CYRILLIC I': 'B<',
'CYRILLIC SHORT I': 'B>',
'CYRILLIC KA': 'B(',
'CYRILLIC EL': 'B)',
'CYRILLIC EM': 'B[',
'CYRILLIC EN': 'B]',
'CYRILLIC O': 'B{',
'CYRILLIC PE': 'B}',
'CYRILLIC ER': 'B@',
'CYRILLIC ES': 'B%',
'CYRILLIC TE': 'B$',
'CYRILLIC U': 'B#',
'CYRILLIC EF': 'CA',
'CYRILLIC HA': 'CB',
'CYRILLIC TSE': 'CC',
'CYRILLIC CHE': 'CD',
'CYRILLIC SHA': 'CE',
'CYRILLIC SHCHA': 'CF',
'CYRILLIC HARD SIGN': 'CG',
'CYRILLIC YERU': 'CH',
'CYRILLIC SOFT SIGN': 'CI',
'CYRILLIC E': 'CJ',
'CYRILLIC YU': 'CK',
'CYRILLIC YA': 'CL',
'CYRILLIC IO': 'CM',
'CIRCUMFLEX': 'CN'
}
KEY_MAP = {
32: 'A',
33: 'Bl',
34: 'Bm',
35: 'Bn',
36: 'Bo',
38: 'Bp',
39: 'B',
40: 'Bq',
41: 'Br',
43: 'Bi',
44: 'C',
45: 'D',
46: 'E',
47: 'F',
48: 'G',
49: 'H',
50: 'I',
51: 'J',
52: 'K',
53: 'L',
54: 'M',
55: 'N',
56: 'O',
57: 'P',
58: 'Bs',
59: 'Q',
60: 'Bt',
61: 'R',
62: 'Bu',
64: 'Bv',
65: 'S',
66: 'T',
67: 'U',
68: 'V',
69: 'W',
70: 'X',
71: 'Y',
72: 'Z',
73: 'a',
74: 'b',
75: 'c',
76: 'd',
77: 'e',
78: 'f',
79: 'g',
80: 'h',
81: 'i',
82: 'j',
83: 'k',
84: 'l',
85: 'm',
86: 'n',
87: 'o',
88: 'p',
89: 'q',
90: 'r',
91: 's',
92: 't',
93: 'u',
94: 'CN',
95: 'Bj',
96: 'v',
161: 'w',
162: 'x',
167: 'Bw',
186: 'Bx',
192: 'By',
196: 'Bz',
197: 'B0',
198: 'B1',
199: 'B2',
200: 'B3',
201: 'B4',
204: 'B5',
209: 'B6',
210: 'B7',
214: 'B8',
216: 'B9',
217: 'B.',
220: 'B-',
222: 'B:',
223: 'B+',
224: 'B=',
225: 'B^',
226: 'B!',
227: 'B/',
228: 'B*',
229: 'B?',
230: 'B&',
231: 'B<',
232: 'B>',
233: 'B(',
234: 'B)',
235: 'B[',
236: 'B]',
237: 'B{',
238: 'B}',
239: 'B@',
240: 'B%',
241: 'B$',
242: 'B#',
243: 'CA',
244: 'CB',
245: 'CC',
246: 'CD',
247: 'CE',
248: 'CF',
249: 'CG',
250: 'CH',
251: 'CI',
252: 'CJ',
253: 'CK',
254: 'CL',
255: 'CM',
256: 'y',
257: 'z',
258: '0',
259: '1',
260: '2',
261: '3',
262: '4',
263: '5',
264: '6',
265: '7',
266: '8',
267: '9',
268: '.',
269: '-',
280: ':',
281: '+',
282: '=',
283: '^',
284: '!',
290: '/',
291: '*',
292: '?',
293: '&',
294: '<',
295: '>',
296: '(',
297: ')',
298: '[',
299: ']',
300: '{',
301: '}',
302: '@',
303: '%',
304: '$',
305: '#',
306: 'BA',
307: 'BB',
308: 'BC',
309: 'BD',
310: 'BE',
311: 'BF',
312: 'BG',
313: 'BH',
314: 'BI',
320: 'BJ',
321: 'BK',
322: 'BL',
323: 'BM',
324: 'BN',
325: 'BO',
326: 'BP',
327: 'BQ',
328: 'BR',
329: 'BS',
330: 'BT',
331: 'BU',
332: 'BV',
333: 'BW',
334: 'BX',
335: 'BY',
336: 'BZ',
340: 'Ba',
341: 'Bb',
342: 'Bc',
343: 'Bd',
344: 'Be',
345: 'Bf',
346: 'Bg',
347: 'Bh',
348: 'Bk'
}
# END_ENCODING }}}
text_keys = (
string.ascii_uppercase + string.ascii_lowercase + string.digits +
'`~!@#$%^&*()_-+=[{]}\\|<,>./?;:\'" '
'ÄäÖöÜüߧºàåæçèéìñòøùабвгдежзийклмнопрстуфхцчшщъыьэюяё'
)
# number name mappings {{{
# start csi mapping (auto generated by gen-key-constants.py do not edit)
functional_key_number_to_name_map = {
57344: 'ESCAPE',
57345: 'ENTER',
57346: 'TAB',
57347: 'BACKSPACE',
57348: 'INSERT',
57349: 'DELETE',
57350: 'LEFT',
57351: 'RIGHT',
57352: 'UP',
57353: 'DOWN',
57354: 'PAGE_UP',
57355: 'PAGE_DOWN',
57356: 'HOME',
57357: 'END',
57358: 'CAPS_LOCK',
57359: 'SCROLL_LOCK',
57360: 'NUM_LOCK',
57361: 'PRINT_SCREEN',
57362: 'PAUSE',
57363: 'MENU',
57364: 'F1',
57365: 'F2',
57366: 'F3',
57367: 'F4',
57368: 'F5',
57369: 'F6',
57370: 'F7',
57371: 'F8',
57372: 'F9',
57373: 'F10',
57374: 'F11',
57375: 'F12',
57376: 'F13',
57377: 'F14',
57378: 'F15',
57379: 'F16',
57380: 'F17',
57381: 'F18',
57382: 'F19',
57383: 'F20',
57384: 'F21',
57385: 'F22',
57386: 'F23',
57387: 'F24',
57388: 'F25',
57389: 'F26',
57390: 'F27',
57391: 'F28',
57392: 'F29',
57393: 'F30',
57394: 'F31',
57395: 'F32',
57396: 'F33',
57397: 'F34',
57398: 'F35',
57399: 'KP_0',
57400: 'KP_1',
57401: 'KP_2',
57402: 'KP_3',
57403: 'KP_4',
57404: 'KP_5',
57405: 'KP_6',
57406: 'KP_7',
57407: 'KP_8',
57408: 'KP_9',
57409: 'KP_DECIMAL',
57410: 'KP_DIVIDE',
57411: 'KP_MULTIPLY',
57412: 'KP_SUBTRACT',
57413: 'KP_ADD',
57414: 'KP_ENTER',
57415: 'KP_EQUAL',
57416: 'KP_SEPARATOR',
57417: 'KP_LEFT',
57418: 'KP_RIGHT',
57419: 'KP_UP',
57420: 'KP_DOWN',
57421: 'KP_PAGE_UP',
57422: 'KP_PAGE_DOWN',
57423: 'KP_HOME',
57424: 'KP_END',
57425: 'KP_INSERT',
57426: 'KP_DELETE',
57427: 'LEFT_SHIFT',
57428: 'LEFT_CONTROL',
57429: 'LEFT_ALT',
57430: 'LEFT_SUPER',
57431: 'RIGHT_SHIFT',
57432: 'RIGHT_CONTROL',
57433: 'RIGHT_ALT',
57434: 'RIGHT_SUPER',
57435: 'MEDIA_PLAY',
57436: 'MEDIA_PAUSE',
57437: 'MEDIA_PLAY_PAUSE',
57438: 'MEDIA_REVERSE',
57439: 'MEDIA_STOP',
57440: 'MEDIA_FAST_FORWARD',
57441: 'MEDIA_REWIND',
57442: 'MEDIA_TRACK_NEXT',
57443: 'MEDIA_TRACK_PREVIOUS',
57444: 'MEDIA_RECORD',
57445: 'LOWER_VOLUME',
57446: 'RAISE_VOLUME',
57447: 'MUTE_VOLUME'}
csi_number_to_functional_number_map = {
2: 57348,
3: 57349,
5: 57354,
6: 57355,
7: 57356,
8: 57357,
9: 57346,
11: 57364,
12: 57365,
13: 57345,
14: 57367,
15: 57368,
17: 57369,
18: 57370,
19: 57371,
20: 57372,
21: 57373,
23: 57374,
24: 57375,
27: 57344,
127: 57347}
letter_trailer_to_csi_number_map = {'A': 57352, 'B': 57353, 'C': 57351, 'D': 57350, 'F': 8, 'H': 7, 'P': 11, 'Q': 12, 'R': 13, 'S': 14}
tilde_trailers = {57348, 57349, 57354, 57355, 57368, 57369, 57370, 57371, 57372, 57373, 57374, 57375}
# end csi mapping
# }}}
def text_match(key: str) -> Optional[str]:
if key.upper() == 'SPACE':
return ' '
if key not in text_keys:
return None
return key
@lru_cache(2)
def get_name_to_functional_number_map() -> Dict[str, int]:
return {v: k for k, v in functional_key_number_to_name_map.items()}
def encode(
integer: int,
chars: str = string.ascii_uppercase + string.ascii_lowercase + string.digits +
'.-:+=^!/*?&<>()[]{}@%$#'
) -> str:
ans = ''
d = len(chars)
while True:
integer, remainder = divmod(integer, d)
ans = chars[remainder] + ans
if integer == 0:
break
return ans
@lru_cache(2)
def get_functional_to_csi_number_map() -> Dict[int, int]:
return {v: k for k, v in csi_number_to_functional_number_map.items()}
def symbolic_name(glfw_name: str) -> str:
return glfw_name[9:].replace('_', ' ')
def glfw_key_name(symbolic_name: str) -> str:
return 'GLFW_KEY_' + symbolic_name.replace(' ', '_')
def update_encoding() -> None:
import re
import subprocess
keys = {a for a in dir(defines) if a.startswith('GLFW_KEY_')}
ans = ENCODING
key_map = {}
i = len(ans)
for k in sorted(keys, key=lambda k: int(getattr(defines, k))):
if k in ('GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE'):
continue
val = getattr(defines, k)
name = symbolic_name(k)
if val <= defines.GLFW_KEY_LAST and val != defines.GLFW_KEY_UNKNOWN:
if name not in ans:
ans[name] = encode(i)
i += 1
key_map[val] = ans[name]
with open(__file__, 'r+') as f:
raw = f.read()
nraw = re.sub(
r'^ENCODING = {.+^# END_ENCODING',
'ENCODING = {!r}\nKEY_MAP={!r}\n# END_ENCODING'.format(
ans, key_map
),
raw,
flags=re.MULTILINE | re.DOTALL
)
if raw == nraw:
raise SystemExit('Failed to replace ENCODING dict')
f.seek(0), f.truncate()
f.write(nraw)
subprocess.check_call(['yapf', '-i', __file__])
class KeyEvent(NamedTuple):
type: int
mods: int
key: str
@lru_cache(2)
def get_csi_number_to_letter_trailer_map() -> Dict[int, str]:
return {v: k for k, v in letter_trailer_to_csi_number_map.items()}
PRESS: int = 1
REPEAT: int = 2
RELEASE: int = 4
class EventType(IntEnum):
PRESS = PRESS
REPEAT = REPEAT
RELEASE = RELEASE
@lru_cache(maxsize=128)
def parse_shortcut(spec: str) -> ParsedShortcut:
if spec.endswith('+') and len(spec) > 1:
spec = spec[:-1] + 'plus'
parts = spec.split('+')
key_name = parts[-1]
key_name = functional_key_name_aliases.get(key_name.upper(), key_name)
is_functional_key = key_name.upper() in get_name_to_functional_number_map()
if is_functional_key:
key_name = key_name.upper()
else:
key_name = character_key_name_aliases.get(key_name.upper(), key_name)
mods = tuple(config_mod_map.get(x.upper(), SUPER << 8) for x in parts[:-1])
mod_val = 0
for x in mods:
mod_val |= x
return ParsedShortcut(mod_val, key_name)
class KeyEvent(NamedTuple):
type: EventType = EventType.PRESS
mods: int = 0
key: str = ''
text: str = ''
shifted_key: str = ''
alternate_key: str = ''
shift: bool = False
alt: bool = False
ctrl: bool = False
super: bool = False
def matches(self, spec: Union[str, ParsedShortcut], types: int = EventType.PRESS | EventType.REPEAT) -> bool:
if not self.type & types:
return False
q = self.mods
is_shifted = bool(self.shifted_key and self.shift)
if is_shifted:
q = self.mods & ~SHIFT
kq = self.shifted_key
else:
kq = self.key
if isinstance(spec, str):
spec = parse_shortcut(spec)
if q != spec.mods:
return False
return kq == spec.key_name
def as_window_system_event(self) -> WindowSystemKeyEvent:
action = defines.GLFW_PRESS
if self.type is EventType.REPEAT:
action = defines.GLFW_REPEAT
elif self.type is EventType.RELEASE:
action = defines.GLFW_RELEASE
mods = 0
if self.mods:
if self.shift:
mods |= defines.GLFW_MOD_SHIFT
if self.alt:
mods |= defines.GLFW_MOD_ALT
if self.ctrl:
mods |= defines.GLFW_MOD_CONTROL
if self.super:
mods |= defines.GLFW_MOD_SUPER
fnm = get_name_to_functional_number_map()
def as_num(key: str) -> int:
return (fnm.get(key) or ord(key)) if key else 0
return WindowSystemKeyEvent(
key=as_num(self.key), shifted_key=as_num(self.shifted_key),
alternate_key=as_num(self.alternate_key), mods=mods,
action=action, text=self.text)
SHIFT, ALT, CTRL, SUPER = 1, 2, 4, 8
type_map = {'p': PRESS, 't': REPEAT, 'r': RELEASE}
rtype_map = {v: k for k, v in type_map.items()}
mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
rmod_map = {v: k for k, v in mod_map.items()}
key_rmap = {}
key_defs: Dict[str, str] = {}
config_key_map = {}
enter_key = KeyEvent(key='ENTER')
backspace_key = KeyEvent(key='BACKSPACE')
config_mod_map = {
'SHIFT': SHIFT,
'ALT': ALT,
@ -483,56 +261,102 @@ class KeyEvent(NamedTuple):
'CTRL': CTRL,
'CONTROL': CTRL
}
for key_name, enc in ENCODING.items():
key_name = key_name.replace(' ', '_')
key_defs[key_name] = config_key_map[key_name] = key_name
key_rmap[enc] = key_name
config_key_map.update({k: key_defs[v] for k, v in key_name_aliases.items() if v in key_defs})
enter_key = KeyEvent(PRESS, 0, key_defs['ENTER'])
backspace_key = KeyEvent(PRESS, 0, key_defs['BACKSPACE'])
globals().update(key_defs)
del key_name, enc
def decode_key_event(text: str) -> KeyEvent:
typ = type_map[text[0]]
mods = mod_map[text[1]]
key = key_rmap[text[2:4]]
return KeyEvent(typ, mods, key)
def decode_key_event(csi: str, csi_type: str) -> KeyEvent:
parts = csi.split(';')
def get_sub_sections(x: str, missing: int = 0) -> Tuple[int, ...]:
return tuple(int(y) if y else missing for y in x.split(':'))
first_section = get_sub_sections(parts[0])
second_section = get_sub_sections(parts[1], 1) if len(parts) > 1 else ()
third_section = get_sub_sections(parts[2]) if len(parts) > 2 else ()
mods = (second_section[0] - 1) if second_section else 0
action = second_section[1] if len(second_section) > 1 else 1
keynum = first_section[0]
if csi_type in 'ABCDHFPQRS':
keynum = letter_trailer_to_csi_number_map[csi_type]
def key_name(num: int) -> str:
if not num:
return ''
num = csi_number_to_functional_number_map.get(num, num)
ans = functional_key_number_to_name_map.get(num)
if ans is None:
ans = chr(num)
return ans
return KeyEvent(
mods=mods, shift=bool(mods & SHIFT), alt=bool(mods & ALT),
ctrl=bool(mods & CTRL), super=bool(mods & SUPER),
key=key_name(keynum),
shifted_key=key_name(first_section[1] if len(first_section) > 1 else 0),
alternate_key=key_name(first_section[2] if len(first_section) > 2 else 0),
type={1: EventType.PRESS, 2: EventType.REPEAT, 3: EventType.RELEASE}[action],
text=''.join(map(chr, third_section))
)
def csi_number_for_name(key_name: str) -> int:
if not key_name:
return 0
fn = get_name_to_functional_number_map().get(key_name)
if fn is None:
return ord(key_name)
return get_functional_to_csi_number_map().get(fn, fn)
def encode_key_event(key_event: KeyEvent) -> str:
typ = rtype_map[key_event.type]
mods = rmod_map[key_event.mods]
key = ENCODING[key_event.key.replace('_', ' ')]
return typ + mods + key
class WindowSystemKeyEvent(NamedTuple):
code: int
mods: int
action: int
key = csi_number_for_name(key_event.key)
shifted_key = csi_number_for_name(key_event.shifted_key)
alternate_key = csi_number_for_name(key_event.alternate_key)
lt = get_csi_number_to_letter_trailer_map()
trailer = lt.get(key, 'u')
if trailer != 'u':
key = 1
mods = key_event.mods
text = key_event.text
ans = '\033['
if key != 1 or mods or shifted_key or alternate_key or text:
ans += f'{key}'
if shifted_key or alternate_key:
ans += ':' + (f'{shifted_key}' if shifted_key else '')
if alternate_key:
ans += f':{alternate_key}'
action = 1
if key_event.type is EventType.REPEAT:
action = 2
elif key_event.type is EventType.RELEASE:
action = 3
if mods or action > 1 or text:
m = 0
if key_event.shift:
m |= 1
if key_event.alt:
m |= 2
if key_event.ctrl:
m |= 4
if key_event.super:
m |= 8
if action > 1 or m:
ans += f';{m+1}'
if action > 1:
ans += f':{action}'
elif text:
ans += ';'
if text:
ans += ';' + ':'.join(map(str, map(ord, text)))
fn = get_name_to_functional_number_map().get(key_event.key)
if fn is not None and fn in tilde_trailers:
trailer = '~'
return ans + trailer
def decode_key_event_as_window_system_key(text: str) -> Optional[WindowSystemKeyEvent]:
k = decode_key_event(text)
glfw_name = glfw_key_name(k.key)
glfw_code = getattr(defines, glfw_name, None)
if glfw_code is None:
csi, trailer = text[2:-1], text[-1]
try:
k = decode_key_event(csi, trailer)
except Exception:
return None
action = defines.GLFW_PRESS
if k.type is RELEASE:
action = defines.GLFW_RELEASE
elif k.type is REPEAT:
action = defines.GLFW_REPEAT
mods = 0
if k.mods & CTRL:
mods |= defines.GLFW_MOD_CONTROL
if k.mods & ALT:
mods |= defines.GLFW_MOD_ALT
if k.mods & SUPER:
mods |= defines.GLFW_MOD_SUPER
if k.mods & SHIFT:
mods |= defines.GLFW_MOD_SHIFT
return WindowSystemKeyEvent(glfw_code, mods, action)
return k.as_window_system_event()

View File

@ -9,9 +9,7 @@
from .constants import is_macos
key_name_aliases = {
'SPACE': ' ',
'SPC': ' ',
functional_key_name_aliases = {
'ESC': 'ESCAPE',
'PGUP': 'PAGE_UP',
'PAGEUP': 'PAGE_UP',
@ -21,7 +19,17 @@
'ARROWUP': 'UP',
'ARROWDOWN': 'DOWN',
'ARROWRIGHT': 'RIGHT',
'ARROWLEFT': 'LEFT'
'ARROWLEFT': 'LEFT',
'DEL': 'DELETE',
}
character_key_name_aliases = {
'SPC': ' ',
'SPACE': ' ',
'PLUS': '+',
'MINUS': '-',
'HYPHEN': '-',
}
LookupFunc = Callable[[str, bool], Optional[int]]

View File

@ -5,8 +5,8 @@
from typing import Optional, Union
from .config import KeyAction, KeyMap, SequenceMap, SubSequenceMap
from .constants import SingleKey
from .fast_data_types import KeyEvent
from .types import SingleKey
from .typing import ScreenType

View File

@ -9,14 +9,14 @@
Tuple
)
from kitty.constants import Edges, WindowGeometry
from kitty.borders import BorderColor
from kitty.fast_data_types import (
Region, set_active_window, viewport_for_window
)
from kitty.options_stub import Options
from kitty.types import Edges, WindowGeometry
from kitty.typing import TypedDict, WindowType
from kitty.window_list import WindowGroup, WindowList
from kitty.borders import BorderColor
class BorderLine(NamedTuple):

View File

@ -10,7 +10,7 @@
)
from kitty.borders import BorderColor
from kitty.constants import Edges
from kitty.types import Edges
from kitty.typing import WindowType
from kitty.window_list import WindowGroup, WindowList

View File

@ -8,7 +8,7 @@
)
from kitty.borders import BorderColor
from kitty.constants import Edges, WindowGeometry
from kitty.types import Edges, WindowGeometry
from kitty.typing import EdgeLiteral, WindowType
from kitty.window_list import WindowGroup, WindowList

View File

@ -7,7 +7,7 @@
from kitty.borders import BorderColor
from kitty.conf.utils import to_bool
from kitty.constants import Edges
from kitty.types import Edges
from kitty.typing import EdgeLiteral, WindowType
from kitty.window_list import WindowGroup, WindowList

View File

@ -5,7 +5,7 @@
from typing import Dict, Generator, Iterable, List, Tuple
from kitty.borders import BorderColor
from kitty.constants import Edges
from kitty.types import Edges
from kitty.typing import WindowType
from kitty.window_list import WindowGroup, WindowList

View File

@ -17,7 +17,7 @@
from .conf.utils import BadLine
from .config import cached_values_for
from .constants import (
SingleKey, appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
is_wayland, kitty_exe, logo_data_file, running_in_kitty
)
from .fast_data_types import (
@ -30,6 +30,7 @@
from .options_stub import Options as OptionsStub
from .os_window_size import initial_window_size_func
from .session import get_os_window_sizing_data
from .types import SingleKey
from .utils import (
detach, expandvars, find_exe, log_error, read_shell_environment,
single_instance, startup_notification_handler, unix_socket_paths

View File

@ -34,10 +34,11 @@ def generate_stub():
all_options,
class_name='DiffOptions',
preamble_lines=(
'from kitty.conf.utils import KittensKey, KittensKeyAction',
'from kitty.conf.utils import KittensKeyAction',
'from kitty.types import ParsedShortcut',
),
extra_fields=(
('key_definitions', 'typing.Dict[KittensKey, KittensKeyAction]'),
('key_definitions', 'typing.Dict[ParsedShortcut, KittensKeyAction]'),
)
)

View File

@ -4,7 +4,8 @@
from typing import Any, Callable, Dict, NamedTuple, Tuple
from .constants import FloatEdges, is_macos
from .constants import is_macos
from .types import FloatEdges
from .typing import EdgeLiteral
from .utils import log_error

View File

@ -8,9 +8,9 @@
from typing import TYPE_CHECKING, Dict, Generator, List, Optional
from kitty.config import parse_send_text_bytes
from kitty.key_encoding import (
WindowSystemKeyEvent, decode_key_event_as_window_system_key
)
from kitty.key_encoding import decode_key_event_as_window_system_key
from kitty.fast_data_types import KeyEvent as WindowSystemKeyEvent
from .base import (
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError,
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType,
@ -145,7 +145,8 @@ def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get:
elif encoding == 'base64':
data = base64.standard_b64decode(q)
elif encoding == 'kitty-key':
data = decode_key_event_as_window_system_key(q)
data = base64.standard_b64decode(q)
data = decode_key_event_as_window_system_key(data)
else:
raise TypeError(f'Invalid encoding for send-text data: {encoding}')
exclude_active = payload_get('exclude_active')
@ -153,7 +154,7 @@ def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get:
if window is not None:
if not exclude_active or window is not boss.active_window:
if isinstance(data, WindowSystemKeyEvent):
kdata = window.encoded_key(data.code, mods=data.mods, action=data.action)
kdata = window.encoded_key(data)
if kdata:
window.write_to_child(kdata)
else:

View File

@ -6,7 +6,7 @@
from typing import Any, Dict, NamedTuple, Optional, Sequence, Tuple
from .config import build_ansi_color_table
from .constants import WindowGeometry
from .types import WindowGeometry
from .fast_data_types import (
DECAWM, Screen, cell_size_for_window, pt_to_px, set_tab_bar_render_data,
viewport_for_window

49
kitty/types.py Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
from typing import NamedTuple
class ParsedShortcut(NamedTuple):
mods: int
key_name: str
class Edges(NamedTuple):
left: int = 0
top: int = 0
right: int = 0
bottom: int = 0
class FloatEdges(NamedTuple):
left: float = 0
top: float = 0
right: float = 0
bottom: float = 0
class ScreenGeometry(NamedTuple):
xstart: float
ystart: float
xnum: int
ynum: int
dx: float
dy: float
class WindowGeometry(NamedTuple):
left: int
top: int
right: int
bottom: int
xnum: int
ynum: int
spaces: Edges = Edges()
class SingleKey(NamedTuple):
mods: int = 0
is_native: bool = False
key: int = -1

View File

@ -19,13 +19,13 @@
from .child import ProcessDesc
from .cli_stub import CLIOptions
from .config import build_ansi_color_table
from .constants import ScreenGeometry, WindowGeometry, appname, wakeup
from .constants import appname, wakeup
from .fast_data_types import (
BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM,
CELL_PROGRAM, CELL_SPECIAL_PROGRAM, DCS, DECORATION, DIM, GLFW_MOD_CONTROL,
GLFW_PRESS, GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM,
GRAPHICS_PROGRAM, MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE,
SCROLL_PAGE, STRIKETHROUGH, TINT_PROGRAM, Screen, add_timer, add_window,
GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM,
MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
STRIKETHROUGH, TINT_PROGRAM, KeyEvent, Screen, add_timer, add_window,
cell_size_for_window, compile_program, encode_key_for_tty, get_boss,
get_clipboard_string, init_cell_program, pt_to_px, set_clipboard_string,
set_titlebar_color, set_window_padding, set_window_render_data,
@ -36,6 +36,7 @@
from .options_stub import Options
from .rgb import to_color
from .terminfo import get_capabilities
from .types import ScreenGeometry, WindowGeometry
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
from .utils import (
color_as_int, get_primary_selection, load_shaders, open_cmd, open_url,
@ -881,18 +882,20 @@ def copy_to_clipboard(self) -> None:
if text:
set_clipboard_string(text)
def encoded_key(self, key: int, mods: int = 0, action: int = GLFW_PRESS) -> bytes:
def encoded_key(self, key_event: KeyEvent) -> bytes:
return encode_key_for_tty(
key=key, mods=mods, key_encoding_flags=self.screen.current_key_encoding_flags(),
cursor_key_mode=self.screen.cursor_key_mode, action=action
).encode('ascii')
key=key_event.key, shifted_key=key_event.shifted_key, alternate_key=key_event.alternate_key,
mods=key_event.mods, action=key_event.action, text=key_event.text,
key_encoding_flags=self.screen.current_key_encoding_flags(),
cursor_key_mode=self.screen.cursor_key_mode,
).encode('ascii')
def copy_or_interrupt(self) -> None:
text = self.text_for_selection()
if text:
set_clipboard_string(text)
else:
self.write_to_child(self.encoded_key(ord('c'), mods=GLFW_MOD_CONTROL))
self.write_to_child(self.encoded_key(KeyEvent(key=ord('c'), mods=GLFW_MOD_CONTROL)))
def copy_and_clear_or_interrupt(self) -> None:
self.copy_or_interrupt()

View File

@ -10,7 +10,7 @@
Any, Deque, Dict, Generator, Iterator, List, Optional, Tuple, Union
)
from .constants import WindowGeometry
from .types import WindowGeometry
from .typing import EdgeLiteral, TabType, WindowType
WindowOrId = Union[WindowType, int]