runst
is a dead simple notification daemon 🦡 In this post, I'm introducing the project and giving different usage examples that will improve your Linux desktop experience.
https://github.com/orhun/runst
Notifications
It is undeniable that notifications became an inescapable part of our overly techy lives in the past decade. Most of the time, it is overlooked how a simple alert dialog can fire different synapses in the human brain and let us feel a certain way. It is truly magnificent that we commonly don't see the fact that "notifying" is a form of communication and it is deeply integrated into our lives at this point.
So let's talk about Linux desktop and how we can handle notifications.
What? I hear you say "What the hell was that intro?". Well, just stay tight. It was definitely not a trick to hook you to read this blog post.
As in every interactive system, GNU/Linux also has different ways of handling notifications. First, let's try to understand what a notification is and what we need to actually work with it.
Desktop notifications are small, passive popup dialogs that notify the user of particular events in an asynchronous manner.
In other words, notifications are dialogs that are either shown on the screen for a certain amount of time and disappear or persist until user interaction. Cool!
D-Bus
Now, how do we interact with notifications? We clearly need some kind of internal communication protocol between processes to handle messages from different applications. Today, most Linux desktops depend on D-Bus for that purpose.
D-Bus is a message bus system, a simple way for applications to talk to one another. In addition to interprocess communication, D-Bus helps coordinate process lifecycle; it makes it simple and reliable to code a "single instance" application or daemon, and to launch applications and daemons on demand when their services are needed.
D-Bus has Desktop Notifications Specification which defines the standard implementation details for notification-related applications. On the other hand, libnotify is widely used for sending desktop notifications to a "notification daemon". Every system needs a running notification daemon for handling notifications.
If you are running D-Bus, you can create a desktop notification using the notify-send(1)
command of libnotify
:
$ notify-send "runst" "A dead simple notification daemon 🦡" --expire-time 3000
And you can query the desktop notifications real-time with running dbus-monitor(1)
:
$ dbus-monitor "interface='org.freedesktop.Notifications'"
This will result in:
method call time=1677934706.650704 sender=:1.2392 -> destination=:1.1673 serial=8 path=/org/freedesktop/Notifications; interface=org.freedesktop.Notifications; member=Notify
string "notify-send"
uint32 0
string ""
string "runst"
string "A dead simple notification daemon 🦡"
array [
]
array [
dict entry(
string "urgency"
variant byte 1
)
dict entry(
string "sender-pid"
variant int64 442049
)
]
int32 3000
With these in mind, let's take a look at different notification daemon implementations.
Notification Daemons
Desktop environments have a built-in feature for handling the notifications:
Cinnamon, Deepin, Enlightenment, GNOME, GNOME Flashback and KDE Plasma use their own implementations to display notifications, and they cannot be replaced. Their notification servers are started automatically on login to receive notifications from applications via D-Bus.
In that case, if we want to use a custom notification daemon, we need something standalone. This means they might require a manual setup to work with Xorg/Wayland. Here are a few projects:
- wired - Lightweight notification daemon with highly customizable layout blocks.
- mako - Lightweight notification daemon for Wayland.
- dunst - Lightweight and customizable notification daemon.
(Editor's note: wow, they are all lightweight!)
Among those projects, by far the most popular notification daemon is dunst
. Needless to say, I WAS a user of dunst
. Also, the reason why I wrote runst
is basically... dunst
.
So, what is runst
?
runst
- a dead simple notification daemon 🦡
runst
is yet another notification daemon implementation in Rust. It aims to be as simple as possible while providing customizable features. The reason why I wrote runst
is simply that:
I have been a user of dunst for a long time. However, they made some uncool breaking changes in v1.7.0 and it completely broke my configuration. That day, I refused to update
dunst
(I was too lazy to re-configure) and decided to write my own notification server using Rust.
Hence the name. (dunst + rust = runst
)
runst
is initially designed to show a simple notification window. On top of that, it combines customization-oriented and semi-innovative features. The way that I use runst
is pretty simple, it's just an overlay on top of i3status bar:
However, there are many cool things that you can do with runst
.
Let's go through its features with usage examples.
(runst
is configured with a single configuration file. You can check out the defaults here.)
Custom window
You can customize the notification popup text, color and dimensions. For example:
[global]
geometry = "312x184+1078+354" # `slop -k`
font = "Monospace 10"
template = """
[{{app_name}}] <b>{{summary}}</b>\
{% if body %} {{body}}{% endif %} \
{% if now(timestamp=true) - timestamp > 60 %} \
({{ (now(timestamp=true) - timestamp) | humantime }} ago)\
{% endif %}\
{% if unread_count > 1 %} ({{unread_count}}){% endif %}
"""
[urgency_normal]
background = "#000000"
foreground = "#aaaaaa"
text = "normal"
In the configuration above, we configured the notification window dimensions, colors (for the normal urgency), font, and the message template.
Now let's take a closer look at the template
:
[{{app_name}}] <b>{{summary}}</b>\
{% if body %} {{body}}{% endif %} \
{% if now(timestamp=true) - timestamp > 60 %} \
({{ (now(timestamp=true) - timestamp) | humantime }} ago)\
{% endif %}\
{% if unread_count > 1 %} ({{unread_count}}){% endif %}
runst
uses the following context for the template variables:
{
"app_name": "runst",
"summary": "example",
"body": "this is a notification 🦡",
"urgency": "normal",
"unread_count": 1,
"timestamp": 1672426610
}
So we will end up with notifications like this:
-
[runst] example this is a notification 🦡
- (default)
-
[runst] example this is a notification 🦡 (3m 2s ago)
- (while viewing an old notification)
-
[runst] example this is a notification 🦡 (2)
- (when there are 2 more unread notifications)
-
[runst] example
- (if there is no
body
)
- (if there is no
With this Tera-powered template
, you can customize the notification text as you like.
Custom commands
You can run custom OS commands based on urgency levels and the notification contents.
Imagine you want to perform a task when you receive a critical notification from a specific application:
[urgency_critical]
custom_commands = [
{ filter = '{ "app_name":"important-app","body":"^delete.*" }', command = 'delete-everything.sh' },
]
Or maybe you simply want to play a custom notification sound:
[urgency_normal]
custom_commands = [
{ command = 'aplay notification.wav' },
]
Or maybe you want a Gotify notification on your different devices when someone says hi in any chatting application matched by the regex:
[urgency_normal]
custom_commands = [
{ filter = '{ "app_name":"telegram|discord|.*chat$","body":"^hello.*" }', command = 'gotify push -t "{{app_name}}" "someone said hi!"' },
{ filter = '{ "app_name":"matrix","body":"^hey.*" }', command = 'gotify push -t "{{app_name}}" "{{body}}"' }
]
See more examples here.
Auto clear
Normally, if you want the notifications to disappear after e.g. 3 seconds, you can do this:
[urgency_low]
timeout = 3
If the timeout is not specified by the sender, the low urgency notifications will be closed after 3 seconds. You can set timeout
to 0 for unexpiring notifications.
On the other hand, if you want the notifications to disappear as you finish reading them, you can use the following option:
[urgency_low]
auto_clear = true
If this option is set to true
, the estimated read time of the notification contents is calculated and it is used as the timeout for closing the notification. So if it takes 5 seconds to read the notification, it will disappear after 5 seconds.
Endnote
There are other features that are not mentioned in this post so definitely check out the project's home page: https://github.com/orhun/runst
I'm using runst
daily and adding new features as new ideas rush through my mind so feedback is very welcome! It is still a bit barebone :>
See you in the next project! 🦡