This post is part of the Wayland Wayfinders series.

Melding systemd and foot for rapid configuration updates and reduced memory consumption in sway with persistent tmux state

Background

Since I often work in strong sunlight, I often want to reconfigure my terminal to use a light or a dark scheme depending on the time of day. In the process, I decided to fiddle around with custom systemd targets, since sway isn’t really good at managing long running processes like my terminal (foot) server.

Starting point

Some years ago I ended up with a rather ad-hoc scratchpad configuration, reproduced for clarity (current config here):

 1# The main scratchpad in the upper left
 2for_window [app_id="mS"] {
 3    move scratchpad
 4    move position 0 0
 5    resize set 50 ppt 35 ppt
 6    floating enable
 7    border pixel 1
 8}
 9bindsym F1 [app_id="mS"] scratchpad show; move position 0 0, resize set 50 ppt 35 ppt
10exec_always foot --app-id="mS" -e tmux new -A -s mS
11
12# The secondary scratchpad in the center
13for_window [app_id="subFloat"] {
14    move scratchpad
15    move position center
16    floating enable
17    border pixel 1
18}
19bindsym F5 exec swaymsg [app_id="subFloat"] scratchpad show || exec foot --app-id="subFloat" -e tmux new -A -s subFloat

Which essentially boils down to:

  • Put a scratchpad in the upper left bound to F1 and resize it to a long thin window
    • Attach or start a tmux session here called mS
  • Put another scratchpad in the center, bound to F5
    • Attach or start a tmux session called subFloat

When everything works (e.g. at start-up) it essentially leads to a scratchpad setup like so:

Additionally, I also have some additional setp for managing terminals:

1set $term_server foot -s
2set $term footclient
3exec_always $term_server
4bindsym $mod+Return exec $term

Which ideally starts a foot server, and subsequently spawns footclient instances on demand.

Annoyances

This setup served me quite well, for several years I had no major issues whatsover, though there were minor annoyances, some of which eventually broke the camel’s back.

Memory consumption

The problem is perhaps immediately evident in the narrowed down specification:

1exec_always foot --app-id="mS" -e tmux new -A -s mS
2# ...
3exec_always foot --app-id="subFloat" -e tmux new -A -s subFloat

Right of the bat, it is rather evident that this configuration will spawn two instances of foot, one for each scratchpad.

Figure 1: Snapshot of memory consumption (from btop), clients take around 10x less memory

Figure 1: Snapshot of memory consumption (from btop), clients take around 10x less memory

On its own, given that I mostly use relatively high-end machines, this was not really a problem, more an aesthetic annoyance.

Applying foot configurations

If the main (sway) server is restarted to apply a configuration update it will not propagate to the scratchpad unless those are also restarted, and sway isn’t meant to handle long running processes. Essentially, applying a change of theme via foot.ini (crrent config here):

1# include = ~/.config/foot/foot.d/theme-acario-light.ini
2include = ~/.config/foot/foot.d/theme-solarized-dark-normal-brights.ini

Would entail a whole killall -9 foot song and dance which was also rather annoying, especially under the current configuration, the tmux sessions would also die with the foot instances.

exec_always doesn’t restart failed processes

Nor should it, as the documentation explicitly mentions, it is basically like exec but it also gets re-executed after a reload. For something like a terminal server service, this isn’t exactly what is required at all. Afterall:

  • It is independent of the sway configuration

Or it really should be. On the other hand, some of the settings for the scratchpads were baked into the configuration. Having to reload the sway configuration (and redo the tmux state) just because aesthetic changes needed to be propagated is asinine, and finally annoyed me enough to make the switch to a real service manager.

Using systemd to offload management

The solution is to abstract the configuration for the main and helper scratchpads out of the configuration and also offload management of the process lifetime to systemd. User configurations for systemd live in $HOME/.config/systemd/user and require some standard commands for interacting with them:

1# Reload user units, every time files are changed
2systemctl --user daemon-reload
3# Start and enable a service
4systemctl --user --now foot-server.service
5# Reload or restart, also when files are changed
6systemctl --user reload-or-restart foot-server.service
7# Check the status
8systemctl --user status foot-server.service

There are a few common stanzas which are needed for a minimal service file:

1[Unit]
2Description=
3[Service]
4ExecStart=
5Restart=on-failure
6[Install]
7WantedBy=default.target

With much more extensive documentation found in the man pages (or online, here).

Terminal Server

The ordering of the services are also fairly obvious, both the scratchpads (and indeed, the sway terminals) need one primary foot-server instance to be running at all times. The easiest approach is to reuse XDG_RUNTIME_DIR which is typically the same as /run/user/$(id -u $USER)/, so the server runs via:

1/usr/bin/foot --server=/run/user/%U/foot-server.sock

Since foot -s doesn’t fork itself, the default Type=simple will suffice.

Additionally, we will need to ensure that all the footclient instances we call use the same server socket (as shown in Sway Scratchpads and Config).

Scratchpad clients

The clients themselves are equally simple, except that the [UNIT] stanza will specify they require foot-server.service and will be run after foot-server.service.

1/usr/bin/footclient --server-socket=/run/user/%U/foot-server.sock --app-id="mS" -e tmux new -A -s mS

The full configurations are reproduced in the next section.

tmux sessions

Additionally, since the goal is to reload foot often via simple killall -9 foot to apply new configurations, it makes sense to add a tmux server into the mix, which will also be part of the requires and after keys for the scratchpad clients.

For this we need to consider some additional options:

1[Service]
2Type= # simple (default), forking, oneshot
3ExecStop=
4RemainAfterExit=yes

Some notes on service types:

  • The server will be a forking service, since tmux start-server essentially puts itself in the background.
  • The clients should be oneshot, since they should complete before other dependent services start1
  • We need to set remain after exit since we have a “rollback” for our service (ExecStop) and since we modify system state, i.e. we don’t want to re-run the service actions without stopping2

False starts

I thought I wanted to use sockets (online man-page), but for a constantly running set of terminals there is very little point. On my machine, the foot server takes around 20MB-31MB while each client instance is below 1.5MB, so having a a socket for either the server or the clients made little to no sense.

Complete configurations3

Figure 2: Final single server many client view (via btop)

Figure 2: Final single server many client view (via btop)

Sway Scratchpads and Config

The final sway scratchpad setup is essentially:

 1# Settings
 2set {
 3 $ms_pos border none, move position 0 0, resize set 50 ppt 35 ppt
 4 $sf_pos border pixel 1, move position center
 5 $start_ms systemctl --user start foot-ms.service
 6 $start_sf systemctl --user start foot-subfloat.service
 7}
 8# The main scratchpad in the upper left
 9for_window [app_id="mS"] {
10    move to scratchpad
11    floating enable
12    $ms_pos
13}
14bindsym F1 exec swaymsg [app_id="mS"] scratchpad show || $start_ms, $ms_pos
15
16# The secondary scratchpad in the center
17for_window [app_id="subFloat"] {
18    move to scratchpad
19    floating enable
20    border pixel 1
21}
22bindsym F5 exec swaymsg [app_id="subFloat"] scratchpad show || $start_sf, $sf_pos

With the bindings changed to point to the same socket as the process:

1# Remove exec_always $term_server, infact get rid of it all together, since systemd handles it now
2set $term footclient --server-socket=$XDG_RUNTIME_DIR/foot-server.sock

Systemd services

foot setup

1[Unit]
2Description=Foot terminal server
3
4[Service]
5ExecStart=/usr/bin/foot --server=/run/user/%U/foot-server.sock
6Restart=on-failure
7
8[Install]
9WantedBy=default.target
 1[Unit]
 2Description=Foot client for tmux session mS
 3After=foot-server.service tmux-session-ms.service
 4Requires=foot-server.service tmux-session-ms.service
 5
 6[Service]
 7ExecStart=/usr/bin/footclient --server-socket=/run/user/%U/foot-server.sock --app-id="mS" -e tmux new -A -s mS
 8Restart=on-failure
 9
10[Install]
11WantedBy=default.target
 1[Unit]
 2Description=Foot client for tmux session subFloat
 3After=foot-server.service tmux-session-subfloat.service
 4Requires=foot-server.service tmux-session-subfloat.service
 5
 6[Service]
 7ExecStart=/usr/bin/footclient --server-socket=/run/user/%U/foot-server.sock --app-id="subFloat" -e tmux new -A -s subFloat
 8Restart=on-failure
 9
10[Install]
11WantedBy=default.target

tmux setup

 1[Unit]
 2Description=Tmux server
 3After=network.target
 4
 5[Service]
 6Type=forking
 7ExecStart=/usr/bin/tmux start-server
 8ExecStop=/usr/bin/tmux kill-server
 9RemainAfterExit=yes
10
11[Install]
12WantedBy=default.target
 1[Unit]
 2Description=Tmux session mS
 3After=tmux-server.service
 4Requires=tmux-server.service
 5
 6[Service]
 7Type=oneshot
 8RemainAfterExit=yes
 9ExecStart=/usr/bin/tmux new-session -s mS -d
10ExecStop=/usr/bin/tmux kill-session -t mS
11
12[Install]
13WantedBy=default.target
 1[Unit]
 2Description=Tmux session subFloat
 3After=tmux-server.service
 4Requires=tmux-server.service
 5
 6[Service]
 7Type=oneshot
 8RemainAfterExit=yes
 9ExecStart=/usr/bin/tmux new-session -s subFloat -d
10ExecStop=/usr/bin/tmux kill-session -t subFloat
11
12[Install]
13WantedBy=default.target

Conclusions

Perhaps on modern machines, with many gigabytes of RAM, there is no real benefit to this approach, however, given that I use a lot of terminals and don’t always reach for tmux, for me this works out very nicely. It also helps handling configuration updates to foot, since all instances use the same server.

Moreover, this setup greatly facilitates changing themes, a feature that is particularly useful for those who work in varying lighting conditions, like moving between indoor and outdoor environments. For people who consistently work in the same environment, this might not be a significant advantage. However, the persistent tmux services is likely to be useful in any case.

Nevertheless, the minor annoyances have been quelled, and I had never really worked with systemd services before barring a few backup setups, so this was pretty gratifying.


  1. There is an excellent visual explanation of the differences b/w simple and oneshot types here by Thomas Stringer ↩︎

  2. Aside from the official documentation, this SO answer is quite nice ↩︎

  3. for now, updates will probably appear only on my Dotfiles repo ↩︎


Series info

Wayland Wayfinders series

  1. Revisiting Wayland for ArchLinux
  2. Lowering resource usage with foot and systemd <-- You are here!