Add bell and command option to notify_on_cmd_finish

Instead of sending a desktop notification, it can be set to
ring the terminal bell or run a user-specified command.
This commit is contained in:
Jin Liu 2023-11-14 17:55:33 +08:00
parent 04506975e5
commit a0d5f7a07b
6 changed files with 69 additions and 10 deletions

View file

@ -1210,6 +1210,7 @@ class Screen:
def current_pointer_shape(self) -> str: ...
def change_pointer_shape(self, op: str, name: str) -> None: ...
def bell(self) -> None: ...
def set_tab_bar_render_data(
os_window_id: int, screen: Screen, left: int, top: int, right: int, bottom: int

View file

@ -3168,11 +3168,35 @@
:code:`always`
Always send a notification, regardless of window state.
Furthermore, you can set the minimum duration for what is considered a
Furthermore, you can set two optional arguments:
1. 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.
2. The action to perform. The default is :code:`notify`. The possible values are:
:code:`notify`
Send a desktop notification.
:code:`bell`
Ring the terminal bell.
:code:`command`
Run a custom command. All subsequent arguments are the cmdline to run.
Some more examples::
# Send a notification when a command takes more than 5 seconds in an unfocused window
notify_on_cmd_finish unfocused
# Send a notification when a command takes more than 10 seconds in a invisible window
notify_on_cmd_finish invisible 10.0
# Ring a bell when a command takes more than 10 seconds in a invisible window
notify_on_cmd_finish invisible 10.0 bell
# Run 'notify-send' when a command takes more than 10 seconds in a invisible window
notify_on_cmd_finish invisible 10.0 command notify-send command finished
'''
)

View file

@ -8,7 +8,10 @@
from kitty.fast_data_types import Color, SingleKey
import kitty.fast_data_types
import kitty.fonts
from kitty.options.utils import AliasMap, KeyDefinition, KeyMap, MouseMap, MouseMapping, SequenceMap, TabBarMarginHeight
from kitty.options.utils import (
AliasMap, KeyDefinition, KeyMap, MouseMap, MouseMapping, NotifyOnCmdFinish, SequenceMap,
TabBarMarginHeight
)
import kitty.options.utils
from kitty.types import FloatEdges
import kitty.types
@ -546,7 +549,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: typing.Tuple[str, float] = ('never', 5.0)
notify_on_cmd_finish: NotifyOnCmdFinish = NotifyOnCmdFinish(when='never', duration=5.0, action='notify', cmdline=[])
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

@ -702,14 +702,32 @@ 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]:
class NotifyOnCmdFinish(NamedTuple):
when: str
duration: float
action: str
cmdline: List[str]
def notify_on_cmd_finish(x: str) -> NotifyOnCmdFinish:
parts = x.split()
if parts[0] not in ('never', 'unfocused', 'invisible', 'always'):
raise ValueError(f'Unknown notify_on_cmd_finish value: {x}')
raise ValueError(f'Unknown notify_on_cmd_finish value: {parts[0]}')
when = parts[0]
duration = 5.0
if len(parts) > 1:
duration = float(parts[1])
return parts[0], duration
action = 'notify'
cmdline = []
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 len(parts) > 3:
cmdline = parts[3:]
else:
raise ValueError('notify_on_cmd_finish `command` action needs a command line')
return NotifyOnCmdFinish(when, duration, action, cmdline)
def config_or_absolute_path(x: str, env: Optional[Dict[str, str]] = None) -> Optional[str]:

View file

@ -4519,6 +4519,7 @@ line_edge_colors(Screen *self, PyObject *a UNUSED) {
}
WRAP0(update_only_line_graphics_data)
WRAP0(bell)
#define MND(name, args) {#name, (PyCFunction)name, args, #name},
@ -4609,6 +4610,7 @@ static PyMethodDef methods[] = {
MND(marked_cells, METH_NOARGS)
MND(scroll_to_next_mark, METH_VARARGS)
MND(update_only_line_graphics_data, METH_NOARGS)
MND(bell, METH_NOARGS)
{"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},
{NULL} /* Sentinel */

View file

@ -1339,15 +1339,26 @@ def cmd_output_marking(self, is_start: int) -> None:
self.call_watchers(self.watchers.on_cmd_startstop, {"is_start": False, "time": end_time})
opts = get_options()
mode, duration = opts.notify_on_cmd_finish
when, duration, action, cmdline = opts.notify_on_cmd_finish
if last_cmd_output_duration >= duration and mode != 'never':
if last_cmd_output_duration >= duration and when != 'never':
cmd = NotificationCommand()
cmd.title = 'kitty'
cmd.body = 'Command finished in a background window.\nClick to focus.'
cmd.actions = 'focus'
cmd.only_when = OnlyWhen(mode)
notify_with_command(cmd, self.id)
cmd.only_when = OnlyWhen(when)
if action == 'notify':
notify_with_command(cmd, self.id)
elif action == 'bell':
def bell(title: str, body: str, identifier: str) -> None:
self.screen.bell()
notify_with_command(cmd, self.id, notify_implementation=bell)
elif action == 'command':
def run_command(title: str, body: str, identifier: str) -> None:
open_cmd(cmdline)
notify_with_command(cmd, self.id, notify_implementation=run_command)
else:
raise ValueError(f'Unknown action in option `notify_on_cmd_finish`: {action}')
# }}}
# mouse actions {{{