Tiling WMs and systemd: Two Great Tastes
One thing I’ve always found cumbersome about configuring a tiling window manager is dealing with all the extra programs you need to launch on startup. Most WMs have a config setting for running one-off commands to start your status bar, set your background, and so on. This feels like an afterthought and is a pain to debug when one of those programs inevitably crashes.
The solution? systemd. I was browsing the documentation for niri the
other day and noticed this
handy page. It struck me: for years, I’ve been making this process so
much more painful than it needed to be. Turns out, systemd has a target
just for launching things on desktop startup:
graphical-session.target
.
Maybe this is already obvious to folks. I had definitely come across it before, but never stopped to consider that I could configure all my startup programs like this. Check this out:
# /home/ciaran/.config/systemd/user/swaybg.service
[Install]
WantedBy=graphical-session.target
[Service]
ExecStart=swaybg -i /home/ciaran/desktop.png
Restart=on-failure
[Unit]
After=graphical-session.target
ConditionEnvironment=WAYLAND_DISPLAY
Description=Wallpaper tool for Wayland compositors
PartOf=graphical-session.target
And there, with a single service file, you get:
- Portability between window managers
- The ability to trivially inspect logs when something unexpected happens
- A way to restart your services when they crash without having to look up their CLI flags in your dotfiles
Please note: I don’t claim to be an expert in configuring systemd. The service config above is adapted from the built-in waybar config in home-manager. There may be room for improvement here.
As you can see from the comment at the top of the snippet, this is a
user service, not a system service. Don’t forget to use --user
, e.g.
systemctl cat --user swaybg.service
.
One more thing: in reality, I’m configuring these services via Nix with home-manager. The actual config looks like this:
systemd.user.services.swaybg = {
Install = {
WantedBy = ["graphical-session.target"];
};
Service = {
ExecStart = "${lib.getExe pkgs.swaybg} -i ${cfg.wallpaperPath}";
Restart = "on-failure";
};
Unit = {
After = ["graphical-session.target"];
ConditionEnvironment = "WAYLAND_DISPLAY";
Description = "Wallpaper tool for Wayland compositors";
PartOf = ["graphical-session.target"];
};
};