Make cmd finish notification clear behavior configurable

This commit is contained in:
Kovid Goyal
2024-12-05 10:27:30 +05:30
parent ecb607c31d
commit 512bea3d43
5 changed files with 51 additions and 30 deletions

View File

@@ -89,6 +89,8 @@ Detailed list of changes
- Allow :ref:`specifying individual color themes <auto_color_scheme>` to use so that kitty changes colors automatically following the OS dark/light mode
- :opt:`notify_on_cmd_finish`: Automatically remove notifications when the window gains focus or the next notification is shown. Clearing behavior can be configured (:pull:`8100`)
- Discard OSC 9 notifications that start with :code:`4;` because some misguided software is using it for "progress reporting" (:iss:`8011`)
- Wayland GNOME: Workaround bug in mutter causing double tap on titlebar to not always work (:iss:`8054`)

View File

@@ -3304,7 +3304,13 @@ and exits will spam a notification.
Second, the action to perform. The default is :code:`notify`. The possible values are:
:code:`notify`
Send a desktop notification.
Send a desktop notification. The subsequent arguments are optional and specify when
the notification is automatically cleared. The set of possible events when the notification is
cleared are: :code:`focus` and :code:`next`. :code:`focus` means that when the notification
policy is :code:`unfocused` or :code:`invisible` the notification is automatically cleared
when the window regains focus. The value of :code:`next` means that the previous notification
is cleared when the next notification is shown. The default when no arguments are specified
is: :code:`focus next`.
:code:`bell`
Ring the terminal bell.
@@ -3323,6 +3329,9 @@ Some more examples::
# Run 'notify-send' when a command takes more than 10 seconds in a invisible window
# Here %c is replaced by the current command line and %s by the job exit code
notify_on_cmd_finish invisible 10.0 command notify-send "job finished with status: %s" %c
# Do not clear previous notification when next command finishes or window regains focus
notify_on_cmd_finish invisible 5.0 notify
'''
)

View File

@@ -565,7 +565,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: NotifyOnCmdFinish = NotifyOnCmdFinish(when='never', duration=5.0, action='notify', cmdline=())
notify_on_cmd_finish: NotifyOnCmdFinish = NotifyOnCmdFinish(when='never', duration=5.0, action='notify', cmdline=(), clear_on=('focus', 'next'))
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'

View File

@@ -26,6 +26,7 @@ from typing import (
Tuple,
TypeVar,
Union,
cast,
get_args,
)
@@ -763,11 +764,17 @@ def active_tab_title_template(x: str) -> Optional[str]:
return None if x == 'none' else x
ClearOn = Literal['next', 'focus']
default_clear_on: tuple[ClearOn, ...] = 'focus', 'next'
all_clear_on = get_args(ClearOn)
class NotifyOnCmdFinish(NamedTuple):
when: str
duration: float
action: str
cmdline: Tuple[str, ...]
when: str = 'never'
duration: float = 5.0
action: str = 'notify'
cmdline: Tuple[str, ...] = ()
clear_on: tuple[ClearOn, ...] = default_clear_on
def notify_on_cmd_finish(x: str) -> NotifyOnCmdFinish:
@@ -780,16 +787,26 @@ def notify_on_cmd_finish(x: str) -> NotifyOnCmdFinish:
duration = float(parts[1])
action = 'notify'
cmdline: Tuple[str, ...] = ()
clear_on = default_clear_on
if len(parts) > 2:
if parts[2] not in ('notify', 'bell', 'command'):
raise ValueError(f'Unknown notify_on_cmd_finish action: {parts[2]}')
action = parts[2]
if action == 'command':
if action == 'notify':
if len(parts) > 3:
con: list[ClearOn] = []
for x in parts[3].split():
if x not in all_clear_on:
raise ValueError(
f'notify_on_cmd_finish: notify clear_on value "{x}" is invalid. Valid values are: {", ".join(all_clear_on)}')
con.append(cast(ClearOn, x))
clear_on = tuple(con)
elif action == 'command':
if len(parts) > 3:
cmdline = tuple(to_cmdline(parts[3]))
else:
raise ValueError('notify_on_cmd_finish `command` action needs a command line')
return NotifyOnCmdFinish(when, duration, action, cmdline)
return NotifyOnCmdFinish(when, duration, action, cmdline, clear_on)
def config_or_absolute_path(x: str, env: Optional[Dict[str, str]] = None) -> Optional[str]:

View File

@@ -117,6 +117,7 @@ if TYPE_CHECKING:
from .fast_data_types import MousePosition
from .file_transmission import FileTransmission
from .notifications import OnlyWhen
class CwdRequestType(Enum):
@@ -630,7 +631,7 @@ class Window:
self.current_mouse_event_button = 0
self.current_clipboard_read_ask: Optional[bool] = None
self.last_cmd_output_start_time = 0.
self.last_notification_id: Optional[int] = None
self.last_cmd_end_notification: Optional[tuple[int, 'OnlyWhen']] = None
self.open_url_handler: 'OpenUrlHandler' = None
self.last_cmd_cmdline = ''
self.last_cmd_exit_status = 0
@@ -1214,13 +1215,12 @@ class Window:
tab = self.tabref()
if tab is not None:
tab.relayout_borders()
if self.last_notification_id:
# Notification id is saved withing handle_cmd_end so it
# configured to be close upon focus is gained and visibility
# change. When window is focused, it is visible for sure.
nm = get_boss().notification_manager
nm.close_notification(self.last_notification_id)
self.last_notification_id = None
if self.last_cmd_end_notification is not None:
from .notifications import OnlyWhen
opts = get_options()
if self.last_cmd_end_notification[1] in (OnlyWhen.unfocused, OnlyWhen.invisible) and 'focus' in opts.notify_on_cmd_finish.clear_on:
get_boss().notification_manager.close_notification(self.last_cmd_end_notification[0])
self.last_cmd_end_notification = None
elif self.os_window_id == current_focused_os_window_id():
# Cancel IME composition after loses focus
update_ime_position_for_window(self.id, False, -1)
@@ -1518,7 +1518,7 @@ class Window:
"is_start": False, "time": end_time, 'cmdline': self.last_cmd_cmdline, 'exit_status': self.last_cmd_exit_status})
opts = get_options()
when, duration, action, notify_cmdline = opts.notify_on_cmd_finish
when, duration, action, notify_cmdline, _ = opts.notify_on_cmd_finish
if last_cmd_output_duration >= duration and when != 'never':
from .notifications import OnlyWhen
@@ -1531,20 +1531,13 @@ class Window:
if not nm.is_notification_allowed(cmd, self.id):
return
if action == 'notify':
# Notification id is saved, so configuration was checked on
# previous pass and saved to be cleared upon focus. But that
# action was missed somehow and we should clear it here.
if self.last_notification_id:
nm.close_notification(self.last_notification_id)
self.last_notification_id = None
if self.last_cmd_end_notification is not None:
if 'next' in opts.notify_on_cmd_finish.clear_on:
nm.close_notification(self.last_cmd_end_notification[0])
self.last_cmd_end_notification = None
notification_id = nm.notify_with_command(cmd, self.id)
# Saving notification id only when we are going to close it in
# future.
# TODO(Shvedov): We should close notification not only when
# gather focus, but when window become visible if `when` equals
# to "invisible".
if cmd.only_when is OnlyWhen.unfocused or cmd.only_when is OnlyWhen.invisible:
self.last_notification_id = notification_id
if notification_id is not None:
self.last_cmd_end_notification = notification_id, cmd.only_when
elif action == 'bell':
self.screen.bell()
elif action == 'command':