The notcat
notification server
Notcat is a D-Bus notification server, similar in spirit to dunst or (in particular) statnot, but with output more akin to a netcat.
It is small, fast, low-dependency, and tries hard to do just one thing well.
Notcat can do one of two things when it receives a notification. It can either print the notification to standard output when invoked like:
notcat %s %B
or it can run subcommands, with notification details passed to those commands either as arguments or as environment variables (with the -e
flag, as follows):
notcat -e --on-notify=notcat-notify.es --on-empty=notcat-empty.es
Notcat is built on its own notification server library called notlib.
Both are written in C99 and notlib depends on GLib's D-Bus bindings.
The notcat repository and notlib repository are both hosted on GitHub and are licensed under the GPLv3 and LGPLv3, respectively.
Format arguments
Notcat, by default, prints received notifications to standard output.
How these notifications are printed can be controlled by a number of different format arguments.
%%
- A literal “%”
%i
- Notification ID
%a
- App name
%s
- Summary
%b
- Body text
%B
- Body text, “cooked” to remove markup and convert newlines to spaces
%t
- Expiration timeout in milliseconds; 0 indicates no timeout, and -1 indicates timeout is up to the notification server
%u
- Urgency; either
LOW
, NORMAL
, or CRITICAL
%n
- Type of notcat event; either
notify
, close
, or empty
%c
- Category; often of the form
class.specific
, but may be simply class
%(h:NAME)
- Hint value with the given NAME
%(A:KEY)
- Action name with the given KEY
Notcat also supports simple conditionals of the form %(?K:expr)
, which evaluates and prints expr if and only if the key K is set and not a default value.
An example of using a few of these features together:
notcat '%(?u:Urgency %u: )%s%(?B: - %B)'
On certain notifications, this would print
Urgency CRITICAL: Hi - It's time for lunch
and on others, it might only print Lunch time
.
Running subcommands
It is not hard to end up needing more sophisticated behavior than just printing to standard output.
To support that, notcat provides the arguments --on-notify
, --on-close
, and --on-empty
.
--on-notify
is triggered whenever a notification is created. --on-close
is triggered whenever a notification is closed. --on-empty
is triggered after --on-close
whenever there are no more open notifications.
The default values for these are
notcat --on-notify=echo --on-close= --on-empty=
A value of exactly echo
refers to notcat's internal notification-printing logic.
Any other value is understood to be an external command, which is run via posix_spawnp(3)
with the format arguments filled in and passed as arguments to the command.
For example,
notcat --on-notify=my-script.es %s
will search for my-script.es
in $PATH
, and run the resulting binary with a single argument set to the notification's summary.
If the -s
argument is passed to notcat, then a shell is used to interpret the command.
For example, one could just count the characters in the formatted notification with the following:
notcat -s --on-notify='echo -n $* | wc -c' %s
As an alternative to positional arguments, notcat also provides an -e
flag which instead provides notification details to subcommands using named environment variables.
Using this, the equivalent to the previous command would be:
notcat -se --on-notify='echo -n $NOTE_SUMMARY | wc -c'
This can be a nice way to add legibility to subcommands like as shell scripts.
Because the environment variables aren't formatted like the format arguments are, it also allows those subcommands to do their own formatting.
Client commands
The notcat binary can act as a notification client, capable of sending notifications, invoking actions (on notification servers which support it, essentially only notcat itself right now), or invoking any of the other methods that exist on a standard-compliant notification server.
Initially this was written as a way to manually test the notcat server, and notcat send
in particular was fleshed out because the standard notify-send
command at the time lacked support for configuring notifications in a number of ways.
notify-send
has since closed those gaps, but notcat still works well as a client within a “scriptable” notification server system.
close ID
-
Closes the notification with ID ID.
getcapabilities
-
Prints the capabilities advertised by the notification server.
getserverinfo
-
Prints the server information advertised by the notification server.
invoke ID [key]
-
Invokes an action with the key key on the notification ID.
listen
-
Listens for signals from the notification server and prints them to standard output as they arrive.
send [-aAchiItU value]... [-p] [--sync] [--] [summary] [body]
-
Sends a notification to the notification server.
The summary and body arguments, as well as the aAchiItU
flags, configure the notification (the man page actually describes what each of those flags does).
-p
specifies printing the ID of the created notification to standard output.
--sync
specifies waiting to exit until the notification is closed. If --sync
is given and an action is invoked on the notification, then notcat will also print the key of any action that was invoked.
Remote actions
A particular feature of notcat, on both the client and server side, is support for remote actions, advertised as the x-notlib-remote-actions
capability.
This extension to the typical Desktop Notifications API allows action invocation to happen through the same API as other notification-related methods.
The following example includes the notcat invoke
command, which relies on this capability.
Example usage
The following describes notcat used in practice on my laptop.
This setup is to display notifications within waybar.
The waybar config to define the notcat server is:
"custom/notcat": {
"exec": "notcat --capabilities=body-hyperlinks --on-notify=tee-note.es --on-empty=tee-note.es '%i' '%s%(?B: - %b)'"
"on-click": "act-note.es"
}
Let's take this "exec"
snippet from right to left.
The '%i' '%s%(?B: - %b)'
format arguments tell notcat that each notification should be formatted as two arguments. The first is the notification's ID. The second is the notification summary, followed—if the body is non-empty when “cooked”—with a hyphen and the raw text contents of the body.
These two arguments are passed to tee-note.es
on notify and empty events.
This file is an es script which looks like:
echo $1 > /tmp/notcat.id
echo <={%split \n $2}
This script writes the first argument, the notification ID, to the file /tmp/notcat.id
, and then writes the second argument, with \n
s replaced with spaces, to standard output, where it is displayed by waybar.
Note that waybar interprets Pango markup, which includes all the basic markup elements required by the notifications standard.
This is why we use %b
: so that notcat does not try to handle or strip markup, instead passing it through to waybar to render.
Because the format arguments include the markup-aware %B
in a conditional, notcat advertises the body-markup
capability automatically. Because waybar also correctly handles links marked up with <a href="">
, we manually advertise the body-hyperlinks
capability, since notcat cannot automatically detect that support.
And that's all the configuration we give notcat.
Moving on to the next line: what is the "on-click": "act-note.es"
?
Recall that when notifications are received, tee-note.es
writes the notification ID to /tmp/notcat.id
.
act-note.es
, which reads as follows,
id = <={%read < /tmp/notcat.id}
if {!~ $id () && !~ $id ''} {
notcat invoke $id
}
reads the ID from /tmp/notcat.id
, and, if it's non-empty, calls notcat invoke $id
in order to invoke the default action for the notification.
Limitations and TODOs
Many applications depend on libnotify while notcat doesn't want or need it, so sometimes notcat seems to not work just because libnotify isn't installed on the system and clients are confused.
(This has bitten me in particular with web notifications from Firefox.)
Because notcat isn't a graphical application, certain things like icons and images are unlikely to ever be supported in a real way.
Sound is also pretty unlikely.
However, there are other things which notcat could support and it doesn't now.
-
More format sequences and environment variables, especially around hints, actions, and conditionals
-
More facilities for escaping characters within things like conditionals
-
Better body-markup
support, since notcat only currently handles stripping tags and un-escaping & codes
-
Support for other capabilities as well — and it might be nice to write up doing so, given much of this stuff is more or less completely undocumented.
body-hyperlinks
(<a href> markup)
synchronous
/private-synchronous
/x-canonical-private-synchronous
/x-dunst-stack-tag
persistence
-
Better D-Bus error reporting