Managing Sway tools as systemd services

Posted by Alex D'Andrea on 27 October 2023

I recently took some time to set up some of the accompanying services of Sway (the window manager) as systemd user services. Those services are dunst, blueman-applet, kanshi - amongst others.

The most simple way to run those applications are to exec them when Sway starts. This is simple, but it lacks everything from automatic restart, reloading configuration and status inspection.

So, let’s look at kanshi, the service that magically manages different monitors that get detected on the system at any time; you’d run it within sway as:

exec kanshi

# or, to restart it on re-reading the sway configuration
exec_always kanshi

# or, to have its output be sent to a specific systemd category
# and be distinguishable from a whole lot of other outputs
exec_always systemd-cat -t kanshi kanshi

… where exec_always will start the command line on config reload, whereas exec only does at sway start. systemd-cat forwards all output on stdout, stderr from an invoked command to the journal given by -t. The problem with this is:

  • it is only started once (or once after re-reading the configuration)
  • there is no visibility, traceability of the application
  • it will not be re-started when it fails, there will be no error reporting in that case
  • when you kill it, you can only find out the actual command line for restarting it by searching through your sway configuration files

To solve these issues, this can be rewritten as a systemd unit, and put into the ~/.config/systemd/user/kanshi.service file:

[Unit]
Description=Kanshi to manage outputs
Documentation=man:kanshi(1)
PartOf=graphical-session.target
After=sway.service

[Service]
Type=simple
Restart=on-failure
ExecStart=/usr/bin/kanshi

[Install]
WantedBy=graphical-session.target

These systemd units in that directory are user units, in this case specific to the user (as opposed to shared amongst all users on the system). The unit is bound to the graphical-session target and started after sway was started. Sway will not be started as systemd unit, but rather as a default command. Together with the setup from Systemd integration for Sway (AUR), sway then also binds specific units to the graphical-session target.

After the unit has been created, it needs to be loaded and activated:

$ systemctl --user daemon-reload
$ systemctl enable --now --user kanshi.service

Finally the helpers all are managed systemd units, can be stopped, started and reloaded.

Side hassle: waiting for dbus tray

I have blogged about this before, and I had hoped that this very problem would have also been solved by this approach. It turns out that sway-systemd in fact tries to solve it, but the solution did not work for me.

I have now solved it like this:

  1. Create a systemd unit tray-startup-delay.service:

    [Unit]
    Description=Tray startup delay
    PartOf=graphical-session.target
    BindsTo=waybar.service
    After=waybar.service
    
    [Service]
    Type=oneshot
    ExecStart=tray-service-startup.sh
    TimeoutStartSec=60
    
    [Install]
    WantedBy=graphical-session.target
    

    (the referenced script is the one from the other blog post)

  2. Change the units that have problem with late arrival of the tray dbus unit (?) and add a Wants/After combination in the [Unit] section:

    [Unit]
    [...]
    Wants=tray-startup-delay.service
    After=tray-startup-delay.service
    [...]
    

Conclusion: using systemd units to manager sway tools does require some time to set up, but is worth it - in my opinion. For the effort, you get proper service management, restarts and a better introspection and operability of your whole system.

In addition, I have learned more details about systemd whilst setting it up. If you know about the history and the controversy that systemd caused in the Linux world, especially in the Debian community, it is remarkable that such a powerful system had been rejected so fiercly by a group of the community. But that is a topic for another post…