#001

Dependency-less notifications in dwm

Last updated:

For some time now, I’ve been trying to reduce the number of dotfiles I keep track of, mainly to make them easier to maintain and deploy. This usually means having to learn program defaults or just installing less crap.

One of the programs that felt a little extra in my setup was dunst, which in the words of it’s author is a lightweight and customizable notification daemon. I rarely need or get notifications, yet I kept a full blown configuration to make them pretty. So, to get rid of dunst without losing notifications completely, I came up with a setup that makes use of the status bar of suckless’ dynamic window manager (dwm), my window manager of choice, to display notifications.

Configuring the status bar

For context, the dwm’s status bar uses the name of the X11 root window1 as it’s source of text to display in the status bar. This property can be set to anything (usually the output of a command e.g. date(1)) and updated indefinite times. For example, the following shell script displays the date and time in the status bar and updates it every second:

$ cat statusbar
#!/bin/sh

while true; do
    xsetroot -name "$(date)"
    sleep 1
done

This script can now be run in the background before starting dwm from an X11 startup script to have it continuously display the date in the status bar for the entire session.

Displaying notifications

To display notifications in the status bar (if any), the above example can be updated as follows:

$ cat statusbar
#!/bin/sh

while true; do
    if [ -s "/tmp/nqueue" ]; then
        printf "1p\n1d\nw\n" | ed -s /tmp/nqueue
        touch /tmp/nlock
    else
        date
    fi | xargs -I{} xsetroot -name '{}'

    if [ -f "/tmp/nlock" ]; then
        sleep 10
        rm -f /tmp/nlock
    else
        sleep 1
    fi
done

The script monitors the file /tmp/nqueue, which acts as a queue of notifications (one message per line). The queue follows the First in, First out (FIFO) principle. That is, the oldest message (first line) is displayed in the status bar and removed from the queue, after which the second message becomes the first and the cycle is repeated until there are no more messages. When the queue is empty, the status bar reverts to displaying the output of the date(1) command.

In addition, when a notification is displayed in the status bar, a lock file (/tmp/nlock) is created to prevent new notifications from overwriting the current one (see the notify script below). However, the frequency at which the status bar is updated to show the next available notification or to update the output of the date(1) command is also regulated by the presence or absence of this file.

Finally, to add a notification to the queue, a script like the following can be used:

$ cat notify
#!/bin/sh

echo "$1" >> "/tmp/nqueue"
[ ! -e "/tmp/nlock" ] && sbreset

This will append the first argument passed to the script (the notification message) to the queue, and force the status bar to be updated with the following sbreset script if a lock file doesn’t exist:

$ cat sbreset
#!/bin/sh

PID=$(pgrep -f statusbar)
[ -n "$PID" ] && pkill -P "$PID"

The script simply finds the PID of the statusbar script and kills its child processes, causing a new loop iteration to begin. That’s it! Once dwm has been restarted with the updated statusbar script, notifications can be queued as follows:

$ notify "Example notification"

Bonus

The right mouse button can be mapped to execute the sbreset script to force a status bar update in dwm’s config.h file:

static const char *sbreset[] = {"sbreset", NULL};
static Button buttons[] = {
    ...
    {ClkStatusText, 0, Button3,  spawn, {.v = sbreset}},
    ...
};

The update causes any notifications that are being displayed to be cleared before their time expires, mimicking the behaviour of dunst.


  1. The WM_NAME X11 property of the root window.