spnw*

Automatic dark mode with OpenBSD and dwm

Lately I've been on a quest to use OpenBSD for as much of my computing as possible. Prior to this I spent several years using macOS, so one of my goals is to recreate some of the "quality of life" features I've come to appreciate from that world — like the automatic use of light mode in the daytime and dark mode at night.

A composite screenshot of my desktop, showing both light and dark modes.

dwm

Things might be easier with a full desktop environment, but on BSD and Linux I tend to prefer just running a simple window manager like dwm.

I wrote a patch for dwm 6.5 to make it look for a ~/.lightmode file on startup — if it exists, dwm will use a light theme, otherwise it defaults to dark. This method allows the setting to persist when restarting dwm. To switch themes while dwm is running, just send it SIGHUP and it will check again.

xsettingsd

To update the appearance of Firefox and such, you'll need an XSettings daemon. xsettingsd is a simple one for those who don't use GNOME. All you have to do is make an ~/.xsettingsd file and run xsettingsd in the background. The file can be reloaded as needed by sending SIGHUP to xsettingsd.

You'll need to at least set your GTK theme. For example:

Net/ThemeName "Adwaita-dark"

(On OpenBSD, you can get Adwaita-dark by installing gnome-themes-extra.)

~/bin/set-theme

This is a little script I wrote to change the theme of dwm, GTK, and Emacs, and set an appropriate wallpaper. Take it as an example to be tweaked according to your needs.

#!/bin/sh

set_wallpaper() {
    feh --bg-fill "$1" &
}

set_gtk_theme() {
    # sed -i does not work when invoked by Redshift hook script
    ( tmp=$(mktemp) &&
      sed "s/^\(Net\/ThemeName\) .*/\1 \"$1\"/" ~/.xsettingsd >"$tmp" &&
      mv "$tmp" ~/.xsettingsd &&
      pkill -HUP -x xsettingsd ) &
}

# (defun load-theme-exclusively (theme)
#   (mapc #'disable-theme custom-enabled-themes)
#   (load-theme theme t))
set_emacs_theme() {
    emacsclient -ue "(load-theme-exclusively '$1)" &
}

case $1 in
    dark)
        rm -f ~/.lightmode
        set_wallpaper ~/wallpapers/dark.jpg
        set_gtk_theme Adwaita-dark
        set_emacs_theme leuven-dark
        ;;
    light)
        touch ~/.lightmode
        set_wallpaper ~/wallpapers/light.jpg
        set_gtk_theme Adwaita
        set_emacs_theme leuven
        ;;
    *)
        echo "usage: set-theme <dark|light>"
        exit 1
        ;;
esac

pkill -HUP -x dwm &

Redshift

To automate the switching, we need something that can call our set-theme script at sunrise and sunset. There are several tools dedicated to this, but I opted to use Redshift, which also adjusts the color temperature of your screen throughout the day to make it easier on the eyes.

You'll need a ~/.config/redshift.conf file:

[redshift]
location-provider=manual

[manual]
lat=51.51
lon=-0.13

And a hook script in ~/.config/redshift/hooks/:

#!/bin/sh

[ $1 != period-changed ] && exit
case $3 in
    daytime) set-theme light ;;
    night) set-theme dark ;;
esac

Note that if you launch Redshift during a transition period (i.e. dusk or dawn), this script might not be called right away.

Parting thoughts

I'm just scratching the surface here, and I doubt I've covered all the programs and use cases you're likely to have.

A perk of the .lightmode system is that it's not too hard to make other programs use it. For example, whenever I restart Emacs, my config checks for it to ensure that it always enables the right theme.

As a bonus, I recommend the excellent Stylus browser extension together with dark themes for Wikipedia, Stack Overflow, etc. When you install them, you'll have the option to enable them in dark mode only.