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:
parent
7a16ef2cc4
commit
166ea9deb9
@ -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
|
- Allow the user to supply a custom Python function to draw tab bar. See
|
||||||
:opt:`tab_bar_style`
|
: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
|
- Add support for reporting mouse events with pixel co-ordinates using the
|
||||||
``SGR_PIXEL_PROTOCOL`` introduced in xterm 359
|
``SGR_PIXEL_PROTOCOL`` introduced in xterm 359
|
||||||
|
|
||||||
|
|||||||
@ -89,6 +89,8 @@ For example::
|
|||||||
map f1 launch my-program @active-kitty-window-id
|
map f1 launch my-program @active-kitty-window-id
|
||||||
|
|
||||||
|
|
||||||
|
.. _watchers:
|
||||||
|
|
||||||
Watching launched windows
|
Watching launched windows
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
|||||||
15
kitty/cli.py
15
kitty/cli.py
@ -571,15 +571,6 @@ def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: Optional
|
|||||||
return leftover_args
|
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:
|
def options_spec() -> str:
|
||||||
if not hasattr(options_spec, 'ans'):
|
if not hasattr(options_spec, 'ans'):
|
||||||
OPTIONS = '''
|
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.
|
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
|
--hold
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Remain open after child process exits. Note that this only affects the first
|
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(
|
setattr(options_spec, 'ans', OPTIONS.format(
|
||||||
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname),
|
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname),
|
||||||
watcher=WATCHER_DEFINITION
|
|
||||||
))
|
))
|
||||||
ans: str = getattr(options_spec, 'ans')
|
ans: str = getattr(options_spec, 'ans')
|
||||||
return ans
|
return ans
|
||||||
|
|||||||
@ -44,6 +44,7 @@ def expand_opt_references(conf_name: str, text: str) -> str:
|
|||||||
def remove_markup(text: str) -> str:
|
def remove_markup(text: str) -> str:
|
||||||
ref_map = {
|
ref_map = {
|
||||||
'layouts': f'{website_url("overview")}#layouts',
|
'layouts': f'{website_url("overview")}#layouts',
|
||||||
|
'watchers': f'{website_url("launch")}#watchers',
|
||||||
'sessions': f'{website_url("overview")}#startup-sessions',
|
'sessions': f'{website_url("overview")}#startup-sessions',
|
||||||
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
|
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
|
||||||
'action-select_tab': f'{website_url("actions")}#select-tab',
|
'action-select_tab': f'{website_url("actions")}#select-tab',
|
||||||
|
|||||||
@ -3,21 +3,21 @@
|
|||||||
# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
|
# 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 .boss import Boss
|
||||||
from .child import Child
|
from .child import Child
|
||||||
from .cli import WATCHER_DEFINITION, parse_args
|
from .cli import parse_args
|
||||||
from .cli_stub import LaunchCLIOptions
|
from .cli_stub import LaunchCLIOptions
|
||||||
from .constants import resolve_custom_file
|
from .constants import resolve_custom_file
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
get_options, patch_color_profiles, set_clipboard_string
|
get_options, patch_color_profiles, set_clipboard_string
|
||||||
)
|
)
|
||||||
|
from .options.utils import env as parse_env
|
||||||
from .tabs import Tab
|
from .tabs import Tab
|
||||||
from .types import run_once
|
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 .window import Watchers, Window
|
||||||
from .options.utils import env as parse_env
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from typing import TypedDict
|
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``
|
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:
|
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
|
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:
|
if not watchers:
|
||||||
return None
|
return None
|
||||||
import runpy
|
import runpy
|
||||||
ans = Watchers()
|
ans = Watchers()
|
||||||
for path in watchers:
|
for path in watchers:
|
||||||
path = resolve_custom_file(path)
|
path = resolve_custom_file(path)
|
||||||
m = runpy.run_path(path, run_name='__kitty_watcher__')
|
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')
|
w = m.get('on_close')
|
||||||
if callable(w):
|
if callable(w):
|
||||||
ans.on_close.append(w)
|
ans.on_close.append(w)
|
||||||
|
|||||||
@ -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',
|
opt('update_check_interval', '24',
|
||||||
option_type='float',
|
option_type='float',
|
||||||
long_text='''
|
long_text='''
|
||||||
|
|||||||
7
kitty/options/parse.py
generated
7
kitty/options/parse.py
generated
@ -16,7 +16,7 @@ from kitty.options.utils import (
|
|||||||
parse_mouse_map, resize_draw_strategy, scrollback_lines, scrollback_pager_history_size, symbol_map,
|
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_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,
|
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:
|
def visual_bell_duration(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||||
ans['visual_bell_duration'] = positive_float(val)
|
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:
|
def wayland_titlebar_color(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||||
ans['wayland_titlebar_color'] = macos_titlebar_color(val)
|
ans['wayland_titlebar_color'] = macos_titlebar_color(val)
|
||||||
|
|
||||||
@ -1292,6 +1296,7 @@ def create_result_dict() -> typing.Dict[str, typing.Any]:
|
|||||||
'font_features': {},
|
'font_features': {},
|
||||||
'kitten_alias': {},
|
'kitten_alias': {},
|
||||||
'symbol_map': {},
|
'symbol_map': {},
|
||||||
|
'watcher': {},
|
||||||
'map': [],
|
'map': [],
|
||||||
'mouse_map': [],
|
'mouse_map': [],
|
||||||
}
|
}
|
||||||
|
|||||||
3
kitty/options/types.py
generated
3
kitty/options/types.py
generated
@ -429,6 +429,7 @@ option_names = ( # {{{
|
|||||||
'url_prefixes',
|
'url_prefixes',
|
||||||
'url_style',
|
'url_style',
|
||||||
'visual_bell_duration',
|
'visual_bell_duration',
|
||||||
|
'watcher',
|
||||||
'wayland_titlebar_color',
|
'wayland_titlebar_color',
|
||||||
'wheel_scroll_multiplier',
|
'wheel_scroll_multiplier',
|
||||||
'window_alert_on_bell',
|
'window_alert_on_bell',
|
||||||
@ -577,6 +578,7 @@ class Options:
|
|||||||
font_features: typing.Dict[str, typing.Tuple[kitty.fonts.FontFeature, ...]] = {}
|
font_features: typing.Dict[str, typing.Tuple[kitty.fonts.FontFeature, ...]] = {}
|
||||||
kitten_alias: typing.Dict[str, typing.List[str]] = {}
|
kitten_alias: typing.Dict[str, typing.List[str]] = {}
|
||||||
symbol_map: typing.Dict[typing.Tuple[int, int], str] = {}
|
symbol_map: typing.Dict[typing.Tuple[int, int], str] = {}
|
||||||
|
watcher: typing.Dict[str, str] = {}
|
||||||
map: typing.List[kitty.options.utils.KeyDefinition] = []
|
map: typing.List[kitty.options.utils.KeyDefinition] = []
|
||||||
keymap: KeyMap = {}
|
keymap: KeyMap = {}
|
||||||
sequence_map: SequenceMap = {}
|
sequence_map: SequenceMap = {}
|
||||||
@ -691,6 +693,7 @@ defaults.env = {}
|
|||||||
defaults.font_features = {}
|
defaults.font_features = {}
|
||||||
defaults.kitten_alias = {}
|
defaults.kitten_alias = {}
|
||||||
defaults.symbol_map = {}
|
defaults.symbol_map = {}
|
||||||
|
defaults.watcher = {}
|
||||||
defaults.map = [
|
defaults.map = [
|
||||||
# copy_to_clipboard
|
# copy_to_clipboard
|
||||||
KeyDefinition(False, KeyAction('copy_to_clipboard'), 1024, False, 99, ()),
|
KeyDefinition(False, KeyAction('copy_to_clipboard'), 1024, False, 99, ()),
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, Callable, Dict, Iterable, List, NamedTuple, Optional, Sequence, Tuple,
|
Any, Callable, Container, Dict, Iterable, List, NamedTuple, Optional,
|
||||||
Union
|
Sequence, Tuple, Union
|
||||||
)
|
)
|
||||||
|
|
||||||
import kitty.fast_data_types as defines
|
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
|
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]]]:
|
def kitten_alias(val: str) -> Iterable[Tuple[str, List[str]]]:
|
||||||
parts = val.split(maxsplit=2)
|
parts = val.split(maxsplit=2)
|
||||||
if len(parts) >= 2:
|
if len(parts) >= 2:
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
from typing import Generator, List, Optional, Sequence, Union
|
from typing import Generator, List, Optional, Union
|
||||||
|
|
||||||
from .cli_stub import CLIOptions
|
from .cli_stub import CLIOptions
|
||||||
from .options.utils import to_layout_names, window_size
|
from .options.utils import to_layout_names, window_size
|
||||||
@ -39,9 +39,8 @@ class Tab:
|
|||||||
|
|
||||||
class Session:
|
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.tabs: List[Tab] = []
|
||||||
self.default_watchers = list(default_watchers)
|
|
||||||
self.active_tab_idx = 0
|
self.active_tab_idx = 0
|
||||||
self.default_title = default_title
|
self.default_title = default_title
|
||||||
self.os_window_size: Optional[WindowSizes] = None
|
self.os_window_size: Optional[WindowSizes] = None
|
||||||
@ -65,8 +64,6 @@ class Session:
|
|||||||
if isinstance(cmd, str):
|
if isinstance(cmd, str):
|
||||||
cmd = shlex.split(cmd)
|
cmd = shlex.split(cmd)
|
||||||
spec = parse_launch_args(cmd)
|
spec = parse_launch_args(cmd)
|
||||||
if self.default_watchers:
|
|
||||||
spec.opts.watcher = list(spec.opts.watcher) + self.default_watchers
|
|
||||||
t = self.tabs[-1]
|
t = self.tabs[-1]
|
||||||
if t.next_title and not spec.opts.window_title:
|
if t.next_title and not spec.opts.window_title:
|
||||||
spec.opts.window_title = t.next_title
|
spec.opts.window_title = t.next_title
|
||||||
@ -175,8 +172,7 @@ def create_sessions(
|
|||||||
else:
|
else:
|
||||||
yield from parse_session(session_data, opts)
|
yield from parse_session(session_data, opts)
|
||||||
return
|
return
|
||||||
default_watchers = args.watcher if args else ()
|
ans = Session()
|
||||||
ans = Session(default_watchers=default_watchers)
|
|
||||||
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
||||||
ans.add_tab(opts)
|
ans.add_tab(opts)
|
||||||
ans.tabs[-1].layout = current_layout
|
ans.tabs[-1].layout = current_layout
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from gettext import gettext as _
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING, Any, Callable, Deque, Dict, Iterable, List, NamedTuple,
|
TYPE_CHECKING, Any, Callable, Deque, Dict, Iterable, List, NamedTuple,
|
||||||
Optional, Pattern, Sequence, Tuple, Union
|
Optional, Pattern, Sequence, Tuple, Union, cast
|
||||||
)
|
)
|
||||||
|
|
||||||
from .child import ProcessDesc
|
from .child import ProcessDesc
|
||||||
@ -315,6 +315,17 @@ class EdgeWidths:
|
|||||||
return {'left': self.left, 'right': self.right, 'top': self.top, 'bottom': self.bottom}
|
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:
|
class Window:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -326,7 +337,11 @@ class Window:
|
|||||||
copy_colors_from: Optional['Window'] = None,
|
copy_colors_from: Optional['Window'] = None,
|
||||||
watchers: Optional[Watchers] = 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_mouse_event_button = 0
|
||||||
self.current_clipboard_read_ask: Optional[bool] = None
|
self.current_clipboard_read_ask: Optional[bool] = None
|
||||||
self.prev_osc99_cmd = NotificationCommand()
|
self.prev_osc99_cmd = NotificationCommand()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user