mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-08 21:25:32 +02:00
Implement parsing for mouse_map config
This commit is contained in:
50
kitty/cli.py
50
kitty/cli.py
@@ -7,16 +7,16 @@ import re
|
||||
import sys
|
||||
from collections import deque
|
||||
from typing import (
|
||||
Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, Match, Optional,
|
||||
Sequence, Set, Tuple, Type, TypeVar, Union, cast
|
||||
Any, Callable, Dict, FrozenSet, Generator, Iterable, Iterator, List, Match,
|
||||
Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast
|
||||
)
|
||||
|
||||
from .cli_stub import CLIOptions
|
||||
from .conf.utils import resolve_config
|
||||
from .config import KeyAction
|
||||
from .config import KeyAction, MouseMap
|
||||
from .constants import appname, defconf, is_macos, is_wayland, str_version
|
||||
from .options_stub import Options as OptionsStub
|
||||
from .types import SingleKey
|
||||
from .types import MouseEvent, SingleKey
|
||||
from .typing import BadLineType, SequenceMap, TypedDict
|
||||
|
||||
|
||||
@@ -759,21 +759,25 @@ SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
|
||||
ShortcutMap = Dict[Tuple[SingleKey, ...], KeyAction]
|
||||
|
||||
|
||||
def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None:
|
||||
def mod_to_names(mods: int) -> Generator[str, None, None]:
|
||||
from .fast_data_types import (
|
||||
GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER,
|
||||
GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER,
|
||||
glfw_get_key_name
|
||||
GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER
|
||||
)
|
||||
modmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER,
|
||||
'hyper': GLFW_MOD_HYPER, 'meta': GLFW_MOD_META, 'num_lock': GLFW_MOD_NUM_LOCK, 'caps_lock': GLFW_MOD_CAPS_LOCK}
|
||||
for name, val in modmap.items():
|
||||
if mods & val:
|
||||
yield name
|
||||
|
||||
|
||||
def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None:
|
||||
from .fast_data_types import glfw_get_key_name
|
||||
keys = []
|
||||
for key_spec in key_sequence:
|
||||
names = []
|
||||
mods, is_native, key = key_spec
|
||||
for name, val in modmap.items():
|
||||
if mods & val:
|
||||
names.append(name)
|
||||
names = list(mod_to_names(mods))
|
||||
if key:
|
||||
kname = glfw_get_key_name(0, key) if is_native else glfw_get_key_name(key, 0)
|
||||
names.append(kname or f'{key}')
|
||||
@@ -807,19 +811,43 @@ def flatten_sequence_map(m: SequenceMap) -> ShortcutMap:
|
||||
return ans
|
||||
|
||||
|
||||
def compare_mousemaps(final: MouseMap, initial: MouseMap) -> None:
|
||||
added = set(final) - set(initial)
|
||||
removed = set(initial) - set(final)
|
||||
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
|
||||
|
||||
def print_mouse_action(trigger: MouseEvent, action: KeyAction) -> None:
|
||||
names = list(mod_to_names(trigger.mods)) + [f'b{trigger.button+1}']
|
||||
when = {-1: 'repeat', 1: 'press', 2: 'doublepress', 3: 'triplepress'}.get(trigger.repeat_count, trigger.repeat_count)
|
||||
grabbed = 'grabbed' if trigger.grabbed else 'ungrabbed'
|
||||
print('\t', '+'.join(names), when, grabbed, action)
|
||||
|
||||
def print_changes(defns: MouseMap, changes: Set[MouseEvent], text: str) -> None:
|
||||
if changes:
|
||||
print(title(text))
|
||||
for k in sorted(changes):
|
||||
print_mouse_action(k, defns[k])
|
||||
|
||||
print_changes(final, added, 'Added mouse actions:')
|
||||
print_changes(initial, removed, 'Removed mouse actions:')
|
||||
print_changes(final, changed, 'Changed mouse actions:')
|
||||
|
||||
|
||||
def compare_opts(opts: OptionsStub) -> None:
|
||||
from .config import defaults, load_config
|
||||
print('\nConfig options different from defaults:')
|
||||
default_opts = load_config()
|
||||
ignored = ('key_definitions', 'keymap', 'sequence_map', 'mousemap', 'mouse_mappings')
|
||||
changed_opts = [
|
||||
f for f in sorted(defaults._fields) # type: ignore
|
||||
if f not in ('key_definitions', 'keymap', 'sequence_map') and getattr(opts, f) != getattr(defaults, f)
|
||||
if f not in ignored and getattr(opts, f) != getattr(defaults, f)
|
||||
]
|
||||
field_len = max(map(len, changed_opts)) if changed_opts else 20
|
||||
fmt = '{{:{:d}s}}'.format(field_len)
|
||||
for f in changed_opts:
|
||||
print(title(fmt.format(f)), getattr(opts, f))
|
||||
|
||||
compare_mousemaps(opts.mousemap, default_opts.mousemap)
|
||||
final_, initial_ = opts.keymap, default_opts.keymap
|
||||
final: ShortcutMap = {(k,): v for k, v in final_.items()}
|
||||
initial: ShortcutMap = {(k,): v for k, v in initial_.items()}
|
||||
|
||||
131
kitty/config.py
131
kitty/config.py
@@ -19,15 +19,18 @@ from .conf.utils import (
|
||||
BadLine, init_config, key_func, load_config as _load_config, merge_dicts,
|
||||
parse_config_base, python_string, to_bool, to_cmdline
|
||||
)
|
||||
from .config_data import InvalidMods, all_options, parse_shortcut, type_convert
|
||||
from .config_data import (
|
||||
InvalidMods, all_options, parse_mods, parse_shortcut, type_convert
|
||||
)
|
||||
from .constants import cache_dir, defconf, is_macos
|
||||
from .fonts import FontFeature
|
||||
from .options_stub import Options as OptionsStub
|
||||
from .types import SingleKey
|
||||
from .types import SingleKey, MouseEvent
|
||||
from .typing import TypedDict
|
||||
from .utils import expandvars, log_error
|
||||
|
||||
KeyMap = Dict[SingleKey, 'KeyAction']
|
||||
MouseMap = Dict[MouseEvent, 'KeyAction']
|
||||
KeySequence = Tuple[SingleKey, ...]
|
||||
SubSequenceMap = Dict[KeySequence, 'KeyAction']
|
||||
SequenceMap = Dict[SingleKey, SubSequenceMap]
|
||||
@@ -344,7 +347,43 @@ all_key_actions: Set[str] = set()
|
||||
sequence_sep = '>'
|
||||
|
||||
|
||||
class KeyDefinition:
|
||||
class BaseDefinition:
|
||||
action: KeyAction
|
||||
|
||||
def resolve_kitten_aliases(self, aliases: Dict[str, Sequence[str]]) -> None:
|
||||
if not self.action.args:
|
||||
return
|
||||
kitten = self.action.args[0]
|
||||
rest = self.action.args[1] if len(self.action.args) > 1 else ''
|
||||
changed = False
|
||||
for key, expanded in aliases.items():
|
||||
if key == kitten:
|
||||
changed = True
|
||||
kitten = expanded[0]
|
||||
if len(expanded) > 1:
|
||||
rest = expanded[1] + ' ' + rest
|
||||
if changed:
|
||||
self.action = self.action._replace(args=[kitten, rest.rstrip()])
|
||||
|
||||
|
||||
class MouseMapping(BaseDefinition):
|
||||
|
||||
def __init__(self, button: int, mods: int, repeat_count: int, grabbed: bool, action: KeyAction):
|
||||
self.button = button
|
||||
self.mods = mods
|
||||
self.repeat_count = repeat_count
|
||||
self.grabbed = grabbed
|
||||
self.action = action
|
||||
|
||||
def resolve(self, kitty_mod: int) -> None:
|
||||
self.mods = defines.resolve_key_mods(kitty_mod, self.mods)
|
||||
|
||||
@property
|
||||
def trigger(self) -> MouseEvent:
|
||||
return MouseEvent(self.button, self.mods, self.repeat_count, self.grabbed)
|
||||
|
||||
|
||||
class KeyDefinition(BaseDefinition):
|
||||
|
||||
def __init__(self, is_sequence: bool, action: KeyAction, mods: int, is_native: bool, key: int, rest: Tuple[SingleKey, ...] = ()):
|
||||
self.is_sequence = is_sequence
|
||||
@@ -363,21 +402,6 @@ class KeyDefinition:
|
||||
self.trigger = r(self.trigger)
|
||||
self.rest = tuple(map(r, self.rest))
|
||||
|
||||
def resolve_kitten_aliases(self, aliases: Dict[str, Sequence[str]]) -> None:
|
||||
if not self.action.args:
|
||||
return
|
||||
kitten = self.action.args[0]
|
||||
rest = self.action.args[1] if len(self.action.args) > 1 else ''
|
||||
changed = False
|
||||
for key, expanded in aliases.items():
|
||||
if key == kitten:
|
||||
changed = True
|
||||
kitten = expanded[0]
|
||||
if len(expanded) > 1:
|
||||
rest = expanded[1] + ' ' + rest
|
||||
if changed:
|
||||
self.action = self.action._replace(args=[kitten, rest.rstrip()])
|
||||
|
||||
|
||||
def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
|
||||
parts = val.split(maxsplit=1)
|
||||
@@ -430,8 +454,47 @@ def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
|
||||
key_definitions.append(KeyDefinition(False, paction, mods, is_native, key))
|
||||
|
||||
|
||||
def parse_mouse_action(val: str, mouse_mappings: List['MouseMapping']) -> None:
|
||||
pass
|
||||
def parse_mouse_action(val: str, mouse_mappings: List[MouseMapping]) -> None:
|
||||
parts = val.split(maxsplit=3)
|
||||
if len(parts) != 4:
|
||||
log_error(f'Ignoring invalid mouse action: {val}')
|
||||
return
|
||||
xbutton, event, modes, action = parts
|
||||
kparts = xbutton.split('+')
|
||||
if len(kparts) > 1:
|
||||
mparts, obutton = parts[:-1], parts[-1].lower()
|
||||
mods = parse_mods(mparts, obutton)
|
||||
if mods is None:
|
||||
return
|
||||
else:
|
||||
obutton = parts[0].lower()
|
||||
mods = 0
|
||||
obutton = xbutton.lower()
|
||||
try:
|
||||
b = {'left': 'b1', 'middle': 'b3', 'right': 'b2'}.get(obutton, obutton)[1:]
|
||||
button = getattr(defines, f'GLFW_MOUSE_BUTTON_{b}')
|
||||
except Exception:
|
||||
log_error(f'Mouse button: {xbutton} not recognized, ignoring')
|
||||
return
|
||||
try:
|
||||
count = {'release': -1, 'press': 1, 'doublepress': 2, 'triplepress': 3}[event.lower()]
|
||||
except KeyError:
|
||||
log_error(f'Mouse event type: {event} not recognized, ignoring')
|
||||
return
|
||||
specified_modes = frozenset(modes.lower().split(','))
|
||||
if specified_modes - {'grabbed', 'ungrabbed'}:
|
||||
log_error(f'Mouse modes: {modes} not recognized, ignoring')
|
||||
return
|
||||
try:
|
||||
paction = parse_key_action(action)
|
||||
except Exception:
|
||||
log_error(f'Invalid mouse action: {action}. Ignoring.')
|
||||
return
|
||||
if paction is None:
|
||||
log_error(f'Ignoring unknown mouse action: {action}')
|
||||
return
|
||||
for mode in specified_modes:
|
||||
mouse_mappings.append(MouseMapping(button, mods, count, mode == 'grabbed', paction))
|
||||
|
||||
|
||||
def parse_symbol_map(val: str) -> Dict[Tuple[int, int], str]:
|
||||
@@ -606,6 +669,7 @@ def parse_config(lines: Iterable[str], check_keys: bool = True, accumulate_bad_l
|
||||
ans: Dict[str, Any] = {
|
||||
'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [],
|
||||
'env': {}, 'kitten_aliases': {}, 'font_features': {}, 'mouse_mappings': [],
|
||||
'mousemap': {}
|
||||
}
|
||||
defs: Optional[OptionsStub] = None
|
||||
if check_keys:
|
||||
@@ -624,8 +688,7 @@ def parse_config(lines: Iterable[str], check_keys: bool = True, accumulate_bad_l
|
||||
|
||||
|
||||
def parse_defaults(lines: Iterable[str], check_keys: bool = False) -> Dict[str, Any]:
|
||||
ans = parse_config(lines, check_keys)
|
||||
return ans
|
||||
return parse_config(lines, check_keys)
|
||||
|
||||
|
||||
xc = init_config(config_lines(all_options), parse_defaults)
|
||||
@@ -757,12 +820,36 @@ def finalize_keys(opts: OptionsStub) -> None:
|
||||
opts.sequence_map = sequence_map
|
||||
|
||||
|
||||
def finalize_mouse_mappings(opts: OptionsStub) -> None:
|
||||
defns: List[MouseMapping] = []
|
||||
for d in getattr(opts, 'mouse_mappings'):
|
||||
if d is None: # clear_all_shortcuts
|
||||
defns = []
|
||||
else:
|
||||
defns.append(d)
|
||||
kitten_aliases: List[Dict[str, Sequence[str]]] = getattr(opts, 'kitten_aliases')
|
||||
for d in defns:
|
||||
d.resolve(opts.kitty_mod)
|
||||
if kitten_aliases and d.action.func == 'kitten':
|
||||
d.resolve_kitten_aliases(kitten_aliases)
|
||||
|
||||
mousemap: MouseMap = {}
|
||||
for defn in defns:
|
||||
is_no_op = defn.action.func in no_op_actions
|
||||
if is_no_op:
|
||||
mousemap.pop(defn.trigger, None)
|
||||
else:
|
||||
mousemap[defn.trigger] = defn.action
|
||||
opts.mousemap = mousemap
|
||||
|
||||
|
||||
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumulate_bad_lines: Optional[List[BadLine]] = None) -> OptionsStub:
|
||||
parser = parse_config
|
||||
if accumulate_bad_lines is not None:
|
||||
parser = partial(parse_config, accumulate_bad_lines=accumulate_bad_lines)
|
||||
opts = _load_config(Options, defaults, parser, merge_configs, *paths, overrides=overrides)
|
||||
finalize_keys(opts)
|
||||
finalize_mouse_mappings(opts)
|
||||
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
|
||||
log_error('Cannot use both macos_titlebar_color and background_opacity')
|
||||
opts.macos_titlebar_color = 0
|
||||
|
||||
@@ -21,11 +21,12 @@ def generate_stub():
|
||||
},
|
||||
preamble_lines=(
|
||||
'from kitty.types import SingleKey',
|
||||
'from kitty.config import KeyAction, KeyMap, SequenceMap',
|
||||
'from kitty.config import KeyAction, KeyMap, SequenceMap, MouseMap',
|
||||
),
|
||||
extra_fields=(
|
||||
('keymap', 'KeyMap'),
|
||||
('sequence_map', 'SequenceMap'),
|
||||
('mousemap', 'MouseMap'),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -52,6 +52,13 @@ class SingleKey(NamedTuple):
|
||||
key: int = -1
|
||||
|
||||
|
||||
class MouseEvent(NamedTuple):
|
||||
button: int = 0
|
||||
mods: int = 0
|
||||
repeat_count: int = 1
|
||||
grabbed: bool = False
|
||||
|
||||
|
||||
ConvertibleToNumbers = Union[str, bytes, int, float]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user