Desktop notifications are small, passive popup dialogs that notify the user of particular events in an asynchronous manner. These passive popups can automatically disappear after a short period of time.
runst
is the server implementation of freedesktop.org - Desktop Notifications Specification and it can be used to receive notifications from applications via D-Bus. As of now, only X11 is supported.
- Fully customizable notification window (size, location, text, colors).
- Template-powered (Jinja2/Django) notification text.
- Auto-clear notifications based on a fixed time or estimated read time.
- Run custom OS commands based on the matched notifications.
runst
is initially designed to show a simple notification window. On top of that, it combines customization-oriented and semi-innovative features. In the future, I'm aiming to shape runst
functionality based on new ideas and feedback.
Feel free to submit an issue if you have something in mind or having a problem!
runst
can be installed from crates.io:
$ cargo install runst
The minimum supported Rust version is 1.70.0
.
runst
can be installed from the extra repository using pacman:
$ pacman -S runst
Or you can install the available AUR packages with using an AUR helper. For example:
$ paru -S runst-git
runst
is available for Alpine Edge. It can be installed via apk after enabling the testing repository.
apk add runst
See the available binaries for different operating systems/architectures from the releases page.
Release tarballs are signed with the following PGP key: AEF8C7261F4CEB41A448CBC41B250A9F78535D1A
- Clone the repository.
$ git clone https://github.com/orhun/runst && cd runst/
- Build.
$ CARGO_TARGET_DIR=target cargo build --release
Binary will be located at target/release/runst
.
You can use xinitrc or xprofile for autostarting runst
.
If you are starting Xorg manually with xinit, you can runst
on X server startup via xinitrc:
$HOME/.xinitrc
:
runst &
Long-running programs such as notification daemons should be started before the window manager, so they should either fork themself or be run in the background via appending &
sign. Otherwise, the script would halt and wait for each program to exit before executing the window manager or desktop environment.
In the case of runst
not being available since it's started at a faster manner than the window manager, you can add a delay as shown in the example below:
{ sleep 2; runst; } &
If you are using a display manager, you can utilize an xprofile file which allows you to execute commands at the beginning of the X user session.
The xprofile file, which is ~/.xprofile
or /etc/xprofile
, can be styled similarly to xinitrc.
You can create a D-Bus service to launch runst
automatically on the first notification action. For example, you can create the following service configuration:
/usr/share/dbus-1/services/org.orhun.runst.service
:
[D-BUS Service]
Name=org.freedesktop.Notifications
Exec=/usr/bin/runst
Whenever an application sends a notification by sending a signal to org.freedesktop.Notifications
, D-Bus activates runst
.
Also, see #1 for systemd integration.
runst
can be controlled with sending commands to D-Bus via dbus-send(1)
.
dbus-send --print-reply --dest=org.freedesktop.Notifications /org/freedesktop/Notifications/ctl "org.freedesktop.Notifications.${command}"
Available commands are:
-
History
: show the last notification. -
Close
: close the notification. -
CloseAll
: close all the notifications.
For example:
# show the last notification
dbus-send --print-reply \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications/ctl \
org.freedesktop.Notifications.History
An example usage for i3:
# Notification history
bindsym $mod grave exec dbus-send --print-reply \
--dest=org.freedesktop.Notifications /org/freedesktop/Notifications/ctl org.freedesktop.Notifications.History
# Close notification
bindsym $mod shift grave exec dbus-send --print-reply \
--dest=org.freedesktop.Notifications /org/freedesktop/Notifications/ctl org.freedesktop.Notifications.Close
Additionally, to view the server version:
dbus-send --print-reply --dest=org.freedesktop.Notifications /org/freedesktop/Notifications org.freedesktop.Notifications.GetServerInformation
runst
configuration file supports TOML format and the default configuration values can be found here.
If exists, configuration file is read from the following default locations:
$HOME/.config/runst/runst.toml
$HOME/.runst/runst.toml
You can also specify a path via RUNST_CONFIG
environment variable.
Sets the logging verbosity. Possible values are error
, warn
, info
, debug
and trace
.
Shows a notification at startup if set to true
.
Sets the window geometry. The value format is <width>x<height> <x> <y>
.
For setting this value, I recommend using a tool like slop which helps with querying for a selection and printing the region to stdout.
If set to true
, the window is resized to match the contents.
If the content is larger than the window size, geometry
option is used for maximum width and height.
Sets the font to use for the window.
Sets the template for the notification message. The syntax is based on Jinja2 and Django templates.
Simply, there are 3 kinds of delimiters:
-
{{
and}}
for expressions -
{%
or{%-
and%}
or-%}
for statements -
{#
and#}
for comments
See Tera documentation for more information about control structures, built-in filters, etc.
Context is the model that holds the required data for template rendering. The JSON format is used in the following example for the representation of a context.
{
"app_name": "runst",
"summary": "example",
"body": "this is a notification 🦡",
"urgency": "normal",
"unread_count": 1,
"timestamp": 1672426610
}
Pango is used for text rendering. The markup documentation can be found here.
A few examples would be:
-
<b>bold text</b>
: bold text -
<span foreground="blue">blue text</span>
: blue text -
<tt>monospace text</tt>
: monospace text
There are 3 levels of urgency defined in the Freedesktop specification and they define the importance of the notification.
-
low
: e.g. "joe signed on" -
normal
: e.g. "you got mail" -
critical
: e.g. "your computer is on fire!"
You can configure runst
to act differently based on these urgency levels. For this, there need to be 3 different sections defined in the configuration file. Each of these sections has the following fields:
[urgency_{level}] # urgency_low, urgency_normal or urgency_critical
background = "#000000" # background color
foreground = "#ffffff" # foreground color
timeout = 10
auto_clear = true
text = "normal"
custom_commands = []
This is the default timeout value (in seconds) if the notification has no timeout specified by the sender. If the timeout is 0, the notification is not automatically closed (i.e. it never expires).
If set to true
, the estimated read time of the notification is calculated and it is used as the timeout. This is useful if you want the notifications to disappear as you finish reading them.
This is the custom text for the urgency level and can be used in template context as urgency
. If it is not set, the corresponding urgency level is used (e.g. "low", "normal" or "critical").
With using this option, you can run custom OS commands based on urgency levels and the notification contents. The basic usage is the following:
custom_commands = [
{ command = 'echo "{{app_name}} {{summary}} {{body}}"' } # echoes the notification to stdout
]
As shown in the example above, you can specify an arbitrary command via command
which is also processed through the template engine. This means that you can use the same template context.
The filtering is done by matching the fields in JSON via using filter
along with the command
. For example, if you want to play a custom notification sound for a certain application:
custom_commands = [
{ filter = '{ "app_name":"notify-send" }', command = 'aplay notification.wav' },
{ filter = '{ "app_name":"weechat" }', command = 'aplay irc.wav' }
]
The JSON filter can have the following fields:
-
app_name
: Name of the application that sends the notification. -
summary
: Summary of the notification. -
body
: Body of the notification.
Each of these fields is matched using regex and you can combine them as follows:
custom_commands = [
{ filter = '{ "app_name":"telegram|discord|.*chat$","body":"^hello.*" }', command = 'gotify push -t "{{app_name}}" "someone said hi!"' }
]
In this hypothetical example, we are sending a Gotify notification when someone says hi to us in any chatting application matched by the regex.
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.
I wanted to keep runst
simple since the way I use dunst
was really simple. I was only showing an overlay window on top of i3status as shown below:
And that's how runst
is born.
Licensed under either of Apache License Version 2.0 or The MIT License at your option.
Copyright © 2022-2024, Orhun Parmaksız