This implements part of the new 's' (sound) parameter; the 'silent'
value. When s=silent, we set the ${muted} template argument to
"true". It is intended to set the 'suppress-sound' hint:
notify-send --hint BOOLEAN:suppress-sound:${muted}
* Don't store a list of unfinished notifications. Use a single one. If
the notification ID of the 'current' notification doesn't match the
previous, unfinished one, the 'current' notification replaces the
previous one, instead of updating it.
* Update xstrjoin() to take an optional delimiter (for example ','),
and use that when joining categories and 'alive IDs'.
* Rename ${action-arg} to ${action-argument}
* Update handling of the 'n' parameter (symbolic icon name); the spec
allows it to be used multiple times, and the terminal is supposed to
pick the first one it can resolve. Foot can't resolve icons at all,
neither can 'notify-send' or 'fyi' (which is what foot typically
executes to display a notification); it's the notification daemon that
resolves icons.
The spec _could_ be interpreted to mean the terminal should lookup
.desktop files, and use the value of the 'Icon' key from the first
matching .desktop files. But foot doesn't read .desktop files, and I
don't intend to implement XDG directory scanning and parsing of
.desktop files just to figure out which icon to use.
Instead, use a simple heuristics; use the *shortest* symbolic
names. The idea is pretty simple: plain icon names are typically
shorter than .desktop file IDs.
First, icons have been finalized in the specification. There were only
three things we needed to adjust:
* symbolic names are base64 encoded
* there are a couple of OSC-99 defined symbolic names that need to be
translated to the corresponding XDG icon name.
* allow in-band icons without a cache ID (that is, allow applications
to use p=icon without having to cache the icon first).
Second, add support for the following new additions to the protocol:
* 'f': custom app-name, overrides the terminal's app-id
* 't': categories
* 'p=alive': lets applications poll for currently active notifications
* 'id' is now 'unset' by default, rather than "0"
* 'w': expire time (i.e. notification timeout)
* "buttons": aka actions. This lets applications add additional (to
the terminal defined "default" action) actions. The 'activated' event
has been updated to report which button/action was used to activate
the notification.
To support button/actions, desktop-notifications.command had to be
reworked a bit.
There's now a new config option:
desktop-notifications.command-action-arg. It has two template
arguments ${action-name} and ${action-label}.
command-action-arg gets expanded for *each* action.
${action-name} and ${action-label} has been replaced by ${action-arg}
in command. This is a somewhat special template, in that it gets
replaced by *all* instances of the expanded actions.
If the user hasn't configured a custom 'desktop-notifications.close'
command, try to close the notification by sending SIGINT to the
notification helper.
This is best-effort:
* If there's no helper running, we do nothing (except warn)
* We don't verify, in any way, the notification is actually closed
* We don't send any other signals, under any circumstances. That is,
no SIGTERM, no SIGKILL. Ever.
Add a new config option, desktop-notifications.close, defining what to
execute to close a notification. It has a single template parameter,
${id}, that is expanded to the external notification ID foot may have
picked up from the notification helper.
notify-send does not support closing notifications, and it appears
impossible to pass an *unsigned* integer as argument to gdbus. Hence
no default value for the new 'close' option.
Example:
printf '\e]99;i=123;this is a notification\e\\'
printf '\e]99;i=123:p=close;\e\\'
Application can now request to receive a 'close' event when the
notification is closed (but not necessarily activated), by adding
'c=1' to the notification request.
We always prefer the symbolic name. Thus, there's no need to write raw
PNG data to disk if we have a symbolic name.
Furthermore, keep the file open until the cache entry is evicted.
Only do it when the notification was activated.
Here, activated means the 'click to activate' notification action was
triggered.
How do we tie everything together?
First, we add a new template parameter, ${action}. It's intended to be
used with e.g. notify-send's --action option.
When the action is triggered, notify-send prints its name on stdout,
on a separate line. Look for this in stdout. Only if we've seen it do
we focus/report the notification.
This fixes an issue where it wasn't possible to trigger multiple
notifications with the same kitty notification ID. This is something
that works in kitty, and there's no reason why it shouldn't work.
The issue was that we track stdout, and the notification helper's PID
in the notification struct. Thus, when a notification is being
displayed, we can't re-use the same notification struct instance for
another notification.
This patch fixes this by adding a new notification list,
'active_notifications'. Whenever we detect that we need to track the
helper (notification want's to either focus the window on activation,
or send an event to the application), we add a copy of the
notification to the 'active' list.
The notification can then be removed from the 'kitty' list, allowing
kitty notifications to re-use the same ID over and over again, even if
old notifications are still being displayed.
This implements the suggested protocol discussed in
https://github.com/kovidgoyal/kitty/issues/7657.
Icons are handled by loading a cache. Both in-band PNG data, and
symbolic names are allowed.
Applications use a graphical ID to reference the icon both when
loading the cache, and when showing a notification.
* 'g' is the graphical ID
* 'n' is optional, and assigns a symbolic name to the icon
* 'p=icon' - the payload is icon PNG data. It needs to be base64
encoded, but this is *not* implied. I.e. the application *must* use
e=1 explicitly.
To load an icon (in-band PNG data):
printf '\e]99;g=123:p=icon;<base64-encoded-png-data>\e\\'
or (symbolic name)
printf '\e]99;g=123:n=firefox:p=icon;\e\\'
Of course, we can combine the two, assigning *both* a symbolic
name, *and* PNG data:
printf '\e]99;g=123:n=firefox:p=icon;<base64-encoded-png>\e\\'
Then, to use the icon in a notification:
printf '\e]99;g=123;this is a notification\e\\'
Foot also allows a *symbolic* icon to be defined and used at the same
time:
printf '\e]99;g=123:n=firefox;this is a notification\e\\'
This obviously won't work with PNG data, since it uses the payload
portion of the escape sequence.
This patch modifies our stdout reader to consume input as we go,
instead of all at once when stdout is closed. This will make it easier
to add support for reading e.g. the daemon assigned notification ID in
the future, and also ensures we see the XDG activation token "as soon
as possible".
Furthermore, to be more future proof, require the XDG activation token
to be prefixed with 'xdgtoken=', and ignore other lines.
Thus, instead of treating *all* of stdout as the XDG activation token,
parse stdout line-by-line, and ignore everything that does not begin
with 'xdgtoken='. Everything (on that line) following 'xdgtoken=' is
treated as the activation token.
This patch adds support for window focusing, and sending events back
to the client application when a notification is closed.
* Refactor notification related configuration options:
- add desktop-notifications sub-section
- deprecate 'notify' in favor of 'desktop-notifications.command'
- deprecate 'notify-focus-inhibit' in favor of
'desktop-notifications.inhibit-when-focused'
* Refactor: rename 'struct kitty_notification' to 'struct
notification'
* Pass a 'struct notification' to notify_notify(), instead of many
arguments.
* notify_notify() now registers a reaper callback. When the notifier
process has terminated, the notification is considered closed, and we
either try to focus (activate) the window, or send an event to the
client application, depending on the notification setting.
* For the window activation, we need an XDG activation token. For now,
assume *everything* written on stdout is part of the token.
* Refactor: remove much of the warnings from OSC-99; we don't
typically log anything when an OSC/CSI has invalid values.
* Add icon support to OSC-99. This isn't part of the upstream
spec. Foot's implementation:
- uses the 'I' parameter
- the value is expected to be a symbolic icon name
- a quick check for absolute paths is done, and such icon requests
are ignored.
* Added ${icon} to the 'desktop-notifications.command' template. Uses
the icon specified in the notification, or ${app-id} if not set.
This adds limited support for OSC-99, kitty desktop notifications[^1]. We
support everything defined by the "protocol", except:
* 'a': action to perform on notification activation. Since we don't
trigger the notification ourselves (over D-Bus), we don't know a)
which ID the notification got, or b) when it is clicked.
* ... and that's it. Everything else is supported
To be explicit, we *do* support:
* Chunked notifications (d=0|1), allowing the application to append
data to a notification in chunks, before it's finally displayed.
* Plain UTF-8, or base64-encoded UTF-8 payload (e=0|1).
* Notification identifier (i=xyz).
* Payload type (p=title|body).
* When to honor the notification (o=always|unfocused|invisible), with
the following quirks:
- we don't know when the window is invisible, thus it's treated as
'unfocused'.
- the foot option 'notify-focus-inhibit' overrides 'always'
* Urgency (u=0|1|2)
[^1]: https://sw.kovidgoyal.net/kitty/desktop-notifications/