diff --git a/docs/changelog.rst b/docs/changelog.rst index 1ff165a95..af114e0c1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -120,6 +120,8 @@ Detailed list of changes - Allow using backspace to move the cursor onto the previous line in cooked mode. This is indicated by the `bw` propert in kitty's terminfo (:iss:`8841`) +- Watchers: A new event for global watchers corresponding to the tab bar being changed (:disc:`8842`) + 0.42.2 [2025-07-16] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/launch.rst b/docs/launch.rst index e740ba7ae..02290ff39 100644 --- a/docs/launch.rst +++ b/docs/launch.rst @@ -175,6 +175,17 @@ create :file:`~/.config/kitty/mywatcher.py` and use :option:`launch --watcher` = # code received from the program running in the window ... + def on_tab_bar_dirty(boss: Boss, window: Window, data: dict[str, Any]) -> None: + # called when any changes happen to the tab bar, such a new tabs being + # created, tab titles changing, tabs moving, etc. Useful to display the + # tab bar externally to kitty. This is called even if the tab bar is + # hidden. Note that this is called only in *global watchers*, that is + # watchers defined in kitty.conf or using the --watcher command line + # flag. data contains tab_manager which is the object responsible for + # managing all tabs in a single OS Window. + ... + + Every callback is passed a reference to the global ``Boss`` object as well as the ``Window`` object the action is occurring on. The ``data`` object is a dict that contains event dependent data. You have full access to kitty internals in diff --git a/kitty/launch.py b/kitty/launch.py index de7a12a35..3d019ede8 100644 --- a/kitty/launch.py +++ b/kitty/launch.py @@ -529,6 +529,9 @@ def load_watch_modules(watchers: Iterable[str]) -> Watchers | None: w = m.get('on_color_scheme_preference_change') if callable(w): ans.on_color_scheme_preference_change.append(w) + w = m.get('on_tab_bar_dirty') + if callable(w): + ans.on_tab_bar_dirty.append(w) return ans diff --git a/kitty/tabs.py b/kitty/tabs.py index da22cbc06..c605d01fd 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -55,7 +55,7 @@ from .tab_bar import TabBar, TabBarData from .types import ac from .typing_compat import EdgeLiteral, SessionTab, SessionType, TypedDict from .utils import cmdline_for_hold, log_error, platform_window_id, resolved_shell, shlex_split, which -from .window import CwdRequest, Watchers, Window, WindowDict +from .window import CwdRequest, Watchers, Window, WindowDict, global_watchers from .window_list import WindowList @@ -1007,6 +1007,12 @@ class TabManager: # {{{ def mark_tab_bar_dirty(self) -> None: if self.tab_bar_should_be_visible and not self.tab_bar_hidden: mark_tab_bar_dirty(self.os_window_id) + boss = get_boss() + w = self.active_window + data = {'tab_manager': self} + for g in global_watchers(): + for watcher in g.on_tab_bar_dirty: + watcher(boss, w, data) def update_tab_bar_data(self) -> None: self.tab_bar.update(self.tab_bar_data) diff --git a/kitty/window.py b/kitty/window.py index 778cbb57c..2a6297bcf 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -297,6 +297,7 @@ class Watchers: on_title_change: list[Watcher] on_cmd_startstop: list[Watcher] on_color_scheme_preference_change: list[Watcher] + on_tab_bar_dirty: list[Watcher] def __init__(self) -> None: self.on_resize = [] @@ -306,6 +307,7 @@ class Watchers: self.on_title_change = [] self.on_cmd_startstop = [] self.on_color_scheme_preference_change = [] + self.on_tab_bar_dirty = [] def add(self, others: 'Watchers') -> None: def merge(base: list[Watcher], other: list[Watcher]) -> None: @@ -319,11 +321,13 @@ class Watchers: merge(self.on_title_change, others.on_title_change) merge(self.on_cmd_startstop, others.on_cmd_startstop) merge(self.on_color_scheme_preference_change, others.on_color_scheme_preference_change) + merge(self.on_tab_bar_dirty, others.on_tab_bar_dirty) def clear(self) -> None: del self.on_close[:], self.on_resize[:], self.on_focus_change[:] del self.on_set_user_var[:], self.on_title_change[:], self.on_cmd_startstop[:] del self.on_color_scheme_preference_change[:] + del self.on_tab_bar_dirty[:] def copy(self) -> 'Watchers': ans = Watchers() @@ -334,12 +338,13 @@ class Watchers: ans.on_title_change = self.on_title_change[:] ans.on_cmd_startstop = self.on_cmd_startstop[:] ans.on_color_scheme_preference_change = self.on_color_scheme_preference_change[:] + ans.on_tab_bar_dirty = self.on_tab_bar_dirty[:] return ans @property def has_watchers(self) -> bool: return bool(self.on_close or self.on_resize or self.on_focus_change or self.on_color_scheme_preference_change - or self.on_set_user_var or self.on_title_change or self.on_cmd_startstop) + or self.on_set_user_var or self.on_title_change or self.on_cmd_startstop or self.on_tab_bar_dirty) def call_watchers(windowref: Callable[[], Optional['Window']], which: str, data: dict[str, Any]) -> None: