A new watcher option for kitty.conf that replaces the old --watcher cli flag

Applies to all windows, not just initial ones.
This commit is contained in:
Kovid Goyal 2021-09-29 14:01:30 +05:30
parent 7a16ef2cc4
commit 166ea9deb9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
11 changed files with 86 additions and 34 deletions

View File

@ -18,6 +18,11 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- Allow the user to supply a custom Python function to draw tab bar. See
:opt:`tab_bar_style`
- **Backward incompatibility**: The command line option ``--watcher`` has been
removed in favor of the :opt:`watcher` option in :file:`kitty.conf`. It can be set
from the command line as: ``kitty -o watcher=/path/to/watcher``. It has the
advantage of applying to all windows, not just the initially created ones.
- Add support for reporting mouse events with pixel co-ordinates using the
``SGR_PIXEL_PROTOCOL`` introduced in xterm 359

View File

@ -89,6 +89,8 @@ For example::
map f1 launch my-program @active-kitty-window-id
.. _watchers:
Watching launched windows
---------------------------

View File

@ -571,15 +571,6 @@ def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: Optional
return leftover_args
WATCHER_DEFINITION = '''
--watcher -w
type=list
Path to a python file. Appropriately named functions in this file will be called
for various events, such as when the window is resized, focused or closed. See the section
on watchers in the launch command documentation :doc:`launch`. Relative paths are
resolved relative to the kitty config directory.'''
def options_spec() -> str:
if not hasattr(options_spec, 'ans'):
OPTIONS = '''
@ -627,11 +618,6 @@ Path to a file containing the startup :italic:`session` (tabs, windows, layout,
Use - to read from STDIN. See the README file for details and an example.
{watcher}
Note that this watcher will be added only to all initially created windows, not new windows
created after startup.
--hold
type=bool-set
Remain open after child process exits. Note that this only affects the first
@ -731,7 +717,6 @@ type=bool-set
'''
setattr(options_spec, 'ans', OPTIONS.format(
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname),
watcher=WATCHER_DEFINITION
))
ans: str = getattr(options_spec, 'ans')
return ans

View File

@ -44,6 +44,7 @@ def expand_opt_references(conf_name: str, text: str) -> str:
def remove_markup(text: str) -> str:
ref_map = {
'layouts': f'{website_url("overview")}#layouts',
'watchers': f'{website_url("launch")}#watchers',
'sessions': f'{website_url("overview")}#startup-sessions',
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
'action-select_tab': f'{website_url("actions")}#select-tab',

View File

@ -3,21 +3,21 @@
# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Any, Dict, List, NamedTuple, Optional, Sequence
from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Sequence
from .boss import Boss
from .child import Child
from .cli import WATCHER_DEFINITION, parse_args
from .cli import parse_args
from .cli_stub import LaunchCLIOptions
from .constants import resolve_custom_file
from .fast_data_types import (
get_options, patch_color_profiles, set_clipboard_string
)
from .options.utils import env as parse_env
from .tabs import Tab
from .types import run_once
from .utils import find_exe, read_shell_environment, set_primary_selection
from .utils import find_exe, read_shell_environment, set_primary_selection, log_error
from .window import Watchers, Window
from .options.utils import env as parse_env
try:
from typing import TypedDict
@ -178,7 +178,14 @@ file with the same syntax as kitty.conf to read the colors from, or specify them
individually, for example: ``--color background=white`` ``--color foreground=red``
''' + WATCHER_DEFINITION
--watcher -w
type=list
Path to a python file. Appropriately named functions in this file will be called
for various events, such as when the window is resized, focused or closed. See the section
on watchers in the launch command documentation: :ref:`watchers`. Relative paths are
resolved relative to the kitty config directory. Global watchers for all windows can be
specified with :opt:`watcher`.
'''
def parse_launch_args(args: Optional[Sequence[str]] = None) -> LaunchSpec:
@ -221,14 +228,29 @@ def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab]
return tab
def load_watch_modules(watchers: Sequence[str]) -> Optional[Watchers]:
watcher_modules: Dict[str, Any] = {}
def load_watch_modules(watchers: Iterable[str]) -> Optional[Watchers]:
if not watchers:
return None
import runpy
ans = Watchers()
for path in watchers:
path = resolve_custom_file(path)
m = watcher_modules.get(path, None)
if m is None:
try:
m = runpy.run_path(path, run_name='__kitty_watcher__')
except Exception as err:
import traceback
log_error(traceback.format_exc())
log_error(f'Failed to load watcher from {path} with error: {err}')
watcher_modules[path] = False
continue
watcher_modules[path] = m
if m is False:
continue
w = m.get('on_close')
if callable(w):
ans.on_close.append(w)

View File

@ -2532,6 +2532,18 @@ will delete the variable from the child process' environment.
'''
)
opt('+watcher', '',
option_type='watcher',
add_to_default=False,
long_text='''
Path to python file which will be loaded for :ref:`watchers`.
Can be specified more than once to load multiple watchers.
The watchers will be added to every kitty window. Relative
paths are resolved relative to the kitty config directory.
Note that reloading the config will only affect windows
created after the reload.
''')
opt('update_check_interval', '24',
option_type='float',
long_text='''

View File

@ -16,7 +16,7 @@ from kitty.options.utils import (
parse_mouse_map, resize_draw_strategy, scrollback_lines, scrollback_pager_history_size, symbol_map,
tab_activity_symbol, tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade,
tab_font_style, tab_separator, tab_title_template, to_cursor_shape, to_font_size, to_layout_names,
to_modifiers, url_prefixes, url_style, window_border_width, window_size
to_modifiers, url_prefixes, url_style, watcher, window_border_width, window_size
)
@ -1241,6 +1241,10 @@ class Parser:
def visual_bell_duration(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['visual_bell_duration'] = positive_float(val)
def watcher(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
for k, v in watcher(val, ans["watcher"]):
ans["watcher"][k] = v
def wayland_titlebar_color(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['wayland_titlebar_color'] = macos_titlebar_color(val)
@ -1292,6 +1296,7 @@ def create_result_dict() -> typing.Dict[str, typing.Any]:
'font_features': {},
'kitten_alias': {},
'symbol_map': {},
'watcher': {},
'map': [],
'mouse_map': [],
}

View File

@ -429,6 +429,7 @@ option_names = ( # {{{
'url_prefixes',
'url_style',
'visual_bell_duration',
'watcher',
'wayland_titlebar_color',
'wheel_scroll_multiplier',
'window_alert_on_bell',
@ -577,6 +578,7 @@ class Options:
font_features: typing.Dict[str, typing.Tuple[kitty.fonts.FontFeature, ...]] = {}
kitten_alias: typing.Dict[str, typing.List[str]] = {}
symbol_map: typing.Dict[typing.Tuple[int, int], str] = {}
watcher: typing.Dict[str, str] = {}
map: typing.List[kitty.options.utils.KeyDefinition] = []
keymap: KeyMap = {}
sequence_map: SequenceMap = {}
@ -691,6 +693,7 @@ defaults.env = {}
defaults.font_features = {}
defaults.kitten_alias = {}
defaults.symbol_map = {}
defaults.watcher = {}
defaults.map = [
# copy_to_clipboard
KeyDefinition(False, KeyAction('copy_to_clipboard'), 1024, False, 99, ()),

View File

@ -7,8 +7,8 @@ import os
import re
import sys
from typing import (
Any, Callable, Dict, Iterable, List, NamedTuple, Optional, Sequence, Tuple,
Union
Any, Callable, Container, Dict, Iterable, List, NamedTuple, Optional,
Sequence, Tuple, Union
)
import kitty.fast_data_types as defines
@ -745,6 +745,12 @@ def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]:
yield val, DELETE_ENV_VAR
def watcher(val: str, current_val: Container[str]) -> Iterable[Tuple[str, str]]:
val = val.strip()
if val not in current_val:
yield val, val
def kitten_alias(val: str) -> Iterable[Tuple[str, List[str]]]:
parts = val.split(maxsplit=2)
if len(parts) >= 2:

View File

@ -4,7 +4,7 @@
import shlex
import sys
from typing import Generator, List, Optional, Sequence, Union
from typing import Generator, List, Optional, Union
from .cli_stub import CLIOptions
from .options.utils import to_layout_names, window_size
@ -39,9 +39,8 @@ class Tab:
class Session:
def __init__(self, default_title: Optional[str] = None, default_watchers: Sequence[str] = ()):
def __init__(self, default_title: Optional[str] = None):
self.tabs: List[Tab] = []
self.default_watchers = list(default_watchers)
self.active_tab_idx = 0
self.default_title = default_title
self.os_window_size: Optional[WindowSizes] = None
@ -65,8 +64,6 @@ class Session:
if isinstance(cmd, str):
cmd = shlex.split(cmd)
spec = parse_launch_args(cmd)
if self.default_watchers:
spec.opts.watcher = list(spec.opts.watcher) + self.default_watchers
t = self.tabs[-1]
if t.next_title and not spec.opts.window_title:
spec.opts.window_title = t.next_title
@ -175,8 +172,7 @@ def create_sessions(
else:
yield from parse_session(session_data, opts)
return
default_watchers = args.watcher if args else ()
ans = Session(default_watchers=default_watchers)
ans = Session()
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
ans.add_tab(opts)
ans.tabs[-1].layout = current_layout

View File

@ -13,7 +13,7 @@ from gettext import gettext as _
from itertools import chain
from typing import (
TYPE_CHECKING, Any, Callable, Deque, Dict, Iterable, List, NamedTuple,
Optional, Pattern, Sequence, Tuple, Union
Optional, Pattern, Sequence, Tuple, Union, cast
)
from .child import ProcessDesc
@ -315,6 +315,17 @@ class EdgeWidths:
return {'left': self.left, 'right': self.right, 'top': self.top, 'bottom': self.bottom}
def global_watchers() -> Watchers:
spec = get_options().watcher
if getattr(global_watchers, 'options_spec', None) == spec:
return cast(Watchers, getattr(global_watchers, 'ans'))
from .launch import load_watch_modules
ans = load_watch_modules(spec) or Watchers()
setattr(global_watchers, 'ans', ans)
setattr(global_watchers, 'options_spec', spec)
return ans
class Window:
def __init__(
@ -326,7 +337,11 @@ class Window:
copy_colors_from: Optional['Window'] = None,
watchers: Optional[Watchers] = None
):
self.watchers = watchers or Watchers()
if watchers:
self.watchers = watchers
self.watchers.add(global_watchers())
else:
self.watchers = global_watchers()
self.current_mouse_event_button = 0
self.current_clipboard_read_ask: Optional[bool] = None
self.prev_osc99_cmd = NotificationCommand()