Implement parsing for mouse_map config

This commit is contained in:
Kovid Goyal
2021-05-08 09:06:45 +05:30
parent 212c653165
commit 417c81de60
4 changed files with 157 additions and 34 deletions

View File

@@ -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()}

View File

@@ -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

View File

@@ -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'),
)
)

View File

@@ -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]