diff --git a/docs/changelog.rst b/docs/changelog.rst index 7be494c44..e528d8332 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -43,6 +43,11 @@ The :doc:`ssh kitten ` is redesigned with powerful new features: Detailed list of changes ------------------------------------- +0.32.0 [future] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- A new option :opt:`notify_on_cmd_finish` to show a desktop notification when a long running command finishes (:pull:`6817`) + 0.31.0 [2023-11-08] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 3058cfb84..32d051a70 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -3149,10 +3149,8 @@ ''' ) -opt('notify_on_cmd_finish', 'never', - choices=('never', 'unfocused', 'invisible', 'always'), - long_text=''' -Whether to send a desktop notification when a long-running command finishes +opt('notify_on_cmd_finish', 'never', option_type='notify_on_cmd_finish', long_text=''' +Show a desktop notification when a long-running command finishes (needs :opt:`shell_integration`). The possible values are: @@ -3168,17 +3166,13 @@ is not currently active. :code:`always` - Always send a notification, even if the window is focused. -''' - ) + Always send a notification, regardless of window state. -opt('notify_on_cmd_finish_min_duration', '5.0', - option_type="float", - long_text=''' -Only send a notification if a command takes more than specified seconds of time. -It's not recommended to set a value too small, as you probably don't want a -notification when a command launches a new window and exit immediately (e.g. the -`code` command from vscode). +Furthermore, you can set the minimum duration for what is considered a +long running command. The default is 5 seconds. Specify a second argument +to set the duration. For example: :code:`invisible 15`. +Do not set the value too small, otherwise a command that launches a new OS Window +and exits will spam a notification. ''' ) diff --git a/kitty/options/parse.py b/kitty/options/parse.py index e07040f83..19515b32b 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -13,12 +13,12 @@ deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias, deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations, macos_option_as_alt, macos_titlebar_color, menu_map, modify_font, narrow_symbols, - optional_edge_width, parse_map, parse_mouse_map, paste_actions, remote_control_password, - resize_debounce_time, scrollback_lines, scrollback_pager_history_size, shell_integration, - store_multiple, 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, titlebar_color, - to_cursor_shape, to_font_size, to_layout_names, to_modifiers, url_prefixes, url_style, - visual_window_select_characters, window_border_width, window_size + notify_on_cmd_finish, optional_edge_width, parse_map, parse_mouse_map, paste_actions, + remote_control_password, resize_debounce_time, scrollback_lines, scrollback_pager_history_size, + shell_integration, store_multiple, 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, titlebar_color, to_cursor_shape, to_font_size, to_layout_names, to_modifiers, + url_prefixes, url_style, visual_window_select_characters, window_border_width, window_size ) @@ -1121,15 +1121,7 @@ def narrow_symbols(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans["narrow_symbols"][k] = v def notify_on_cmd_finish(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - val = val.lower() - if val not in self.choices_for_notify_on_cmd_finish: - raise ValueError(f"The value {val} is not a valid choice for notify_on_cmd_finish") - ans["notify_on_cmd_finish"] = val - - choices_for_notify_on_cmd_finish = frozenset(('never', 'unfocused', 'invisible', 'always')) - - def notify_on_cmd_finish_min_duration(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - ans['notify_on_cmd_finish_min_duration'] = float(val) + ans['notify_on_cmd_finish'] = notify_on_cmd_finish(val) def open_url_with(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['open_url_with'] = to_cmdline(val) diff --git a/kitty/options/types.py b/kitty/options/types.py index 41b80c118..7ba4c34b3 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -20,7 +20,6 @@ choices_for_linux_display_server = typing.Literal['auto', 'wayland', 'x11'] choices_for_macos_colorspace = typing.Literal['srgb', 'default', 'displayp3'] choices_for_macos_show_window_title_in = typing.Literal['all', 'menubar', 'none', 'window'] -choices_for_notify_on_cmd_finish = typing.Literal['never', 'unfocused', 'invisible', 'always'] choices_for_placement_strategy = typing.Literal['center', 'top-left'] choices_for_pointer_shape_when_dragging = choices_for_default_pointer_shape choices_for_pointer_shape_when_grabbed = choices_for_default_pointer_shape @@ -388,7 +387,6 @@ 'mouse_map', 'narrow_symbols', 'notify_on_cmd_finish', - 'notify_on_cmd_finish_min_duration', 'open_url_with', 'paste_actions', 'placement_strategy', @@ -548,8 +546,7 @@ class Options: mark3_background: Color = Color(242, 116, 188) mark3_foreground: Color = Color(0, 0, 0) mouse_hide_wait: float = 0.0 if is_macos else 3.0 - notify_on_cmd_finish: choices_for_notify_on_cmd_finish = 'never' - notify_on_cmd_finish_min_duration: float = 5.0 + notify_on_cmd_finish: typing.Tuple[str, float] = ('never', 5.0) open_url_with: typing.List[str] = ['default'] paste_actions: typing.FrozenSet[str] = frozenset({'confirm', 'quote-urls-at-prompt'}) placement_strategy: choices_for_placement_strategy = 'center' diff --git a/kitty/options/utils.py b/kitty/options/utils.py index db9468b75..b816125ef 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -702,6 +702,16 @@ def active_tab_title_template(x: str) -> Optional[str]: return None if x == 'none' else x +def notify_on_cmd_finish(x: str) -> Tuple[str, float]: + parts = x.split() + if parts[0] not in ('never', 'unfocused', 'invisible', 'always'): + raise ValueError(f'Unknown notify_on_cmd_finish value: {x}') + duration = 5.0 + if len(parts) > 1: + duration = float(parts[1]) + return parts[0], duration + + def config_or_absolute_path(x: str, env: Optional[Dict[str, str]] = None) -> Optional[str]: if not x or x.lower() == 'none': return None diff --git a/kitty/window.py b/kitty/window.py index 68460e9c0..5669253b8 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -543,7 +543,7 @@ def __init__( self.current_mouse_event_button = 0 self.current_clipboard_read_ask: Optional[bool] = None self.prev_osc99_cmd = NotificationCommand() - self.last_cmd_output_start_time = 0 + self.last_cmd_output_start_time = 0. self.actions_on_close: List[Callable[['Window'], None]] = [] self.actions_on_focus_change: List[Callable[['Window', bool], None]] = [] self.actions_on_removal: List[Callable[['Window'], None]] = [] @@ -1331,19 +1331,17 @@ def cmd_output_marking(self, is_start: int) -> None: self.call_watchers(self.watchers.on_cmd_startstop, {"is_start": True, "time": start_time}) else: - if self.last_cmd_output_start_time > 0: + if self.last_cmd_output_start_time > 0.: end_time = monotonic() last_cmd_output_duration = end_time - self.last_cmd_output_start_time - self.last_cmd_output_start_time = 0 + self.last_cmd_output_start_time = 0. self.call_watchers(self.watchers.on_cmd_startstop, {"is_start": False, "time": end_time}) opts = get_options() - mode = opts.notify_on_cmd_finish - if mode == 'never': - return + mode, duration = opts.notify_on_cmd_finish - if last_cmd_output_duration >= opts.notify_on_cmd_finish_min_duration: + if last_cmd_output_duration >= duration and mode != 'never': cmd = NotificationCommand() cmd.title = 'kitty' cmd.body = 'Command finished in a background window.\nClick to focus.'