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.
By default, it will print the notification to standard output.
This can be controlled by a number of format arguments, which are filled with particular details of the notification.
For example, when a notification is received, the following notcat invocation prints the notification's summary (%s), a space, and then the notification's body (%B):
notcat %s %B
Notcat can also run subcommands, with notification details passed to those commands as arguments, or as environment variables when notcat is given the -e flag.
Subcommands can be run when a notification is received, when a notification is closed, or when all notifications have closed and the notification queue is “empty”.
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 depend 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
The format arguments available for notcat are as follows.
%%
- 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 is not its 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 sent to notcat.
--on-close is triggered whenever a notification is closed, either manually or because it expired.
--on-empty is triggered after --on-close if there are no more open notifications left.
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 invoked using 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 the command is invoked using $SHELL -c.
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 an easy way to add legibility to larger subcommands like shell scripts.
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 describes the meaning of each of these flags).
-p configures notcat to print the ID of the created notification to standard output.
--sync configures notcat to wait until the notification is closed before exiting. Notcat will also print the key of any actions invoked on the notification waiting for it to be closed.
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 as it can be used in practice.
This setup displays notifications within waybar.
The waybar config which runs the notcat server is this:
"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" from right to left.
The '%i' '%s%(?B: - %b)' format arguments tell notcat that each notification should be formatted as two arguments. The first argument 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 \ns 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 as the body format argument: so that notcat does not try to handle or strip markup, instead passing it straight 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 use --capabilities=body-hyperlinks to manually advertise the body-hyperlinks capability, since notcat cannot automatically detect that support.
And that's all the configuration we give the notcat server.
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
While notcat doesn't want or need libnotify, many other applications do, so sometimes notcat seems to be broken 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 unlikely.
However, there are other things which notcat could support and doesn't now.
-
More format sequences and environment variables, especially around hints, actions, and conditionals
-
More robust behavior in conditionals, especially allowing character escaping and better control over argument-splitting
-
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)
persistence
-
Better D-Bus error reporting