Skip to main content

max / alloy

swayosd: enable the backend the caps-lock bind needs, drop the preset line that named nothing Two defects in the same package's wiring, found by checking whether `exec swayosd-server` in the sway config was redundant the way `exec xdg-user-dirs-update` turned out to be. It is not: Fedora's SwayOSD 0.3.2 ships three binaries, one system unit and a D-Bus service file, and no user unit at all. The sway `exec` is the mechanism, not a workaround. So the user preset's `enable swayosd.service` named a unit that does not exist. It has been a no-op reading as configuration since it was written. Removed, with the reason recorded next to the alloy-hinged note above it, which was removed on 2026-07-19 for exactly the same reason. The other two lines in that file name units that do exist; checked rather than assumed. The half that does ship a unit was never enabled by anything, so the sway config's `bindsym --release Caps_Lock` had no daemon behind it. Enabling it turned out to need more than a preset line: SwayOSD installs swayosd-libinput-backend.service into /usr/lib64/systemd/system, and systemd's search path is /etc/systemd/system, /usr/local/lib/systemd/system and /usr/lib/systemd/system. /usr/lib64 is a real directory here, not a symlink to /usr/lib, so the unit is invisible: `systemctl enable` answers "Unit swayosd-libinput-backend.service does not exist" and `preset-all` matches nothing and says nothing, which is the same silent failure the alloy-debug-shell@ comment in this file already warns about. The Containerfile now links the unit into the path systemd reads, before preset-all runs. Guarded both ways: if a later SwayOSD ships the unit at the canonical path the shim stands aside rather than replacing a real unit file with a link to a file that moved, and if the unit is at neither path the build fails instead of quietly shipping the dead bind again. Verified in the rebuilt image: the unit resolves, and preset-all creates graphical.target.wants/swayosd-libinput-backend.service. Not yet verified at runtime, which is a different claim: that the daemon starts in a session and the bind draws an overlay needs a boot.
Co-Authored-By
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-22 03:18 UTC
Signed with PGP, not checked
Commit: c1cd25ef5eeda30fc88183bb1e6b4f6e4bef7fa0
Parent: 7b977b2
3 files changed, +54 insertions, -3 deletions
@@ -382,6 +382,35 @@
382 382 # preset-all` reads every preset file and applies it — this must
383 383 # run after the config tree is in place, so it lives here.
384 384 # =====================================================================
385 +
386 + # SwayOSD's system unit is not where systemd looks for units. Fedora's
387 + # SwayOSD 0.3.2 installs swayosd-libinput-backend.service into
388 + # /usr/lib64/systemd/system, and systemd's search path is /etc/systemd/system,
389 + # /usr/local/lib/systemd/system and /usr/lib/systemd/system. On this base
390 + # /usr/lib64 is a real directory rather than a symlink to /usr/lib, so the
391 + # unit is invisible: `systemctl enable` answers "Unit
392 + # swayosd-libinput-backend.service does not exist", and the preset line for it
393 + # matches nothing and says nothing. That is a packaging bug upstream, not a
394 + # choice Alloy gets to make, so link the unit into the path systemd reads.
395 + #
396 + # Without this the caps-lock and num-lock overlays have no daemon, and the
397 + # sway config's `bindsym --release Caps_Lock` does nothing at all.
398 + #
399 + # Written to survive the fix rather than assume the bug is permanent: if a
400 + # later SwayOSD ships the unit at the canonical path, this leaves it alone
401 + # instead of replacing a real unit file with a link to a file that moved. If
402 + # the unit is at neither path the build stops, because the alternative is
403 + # shipping the dead bind again and not knowing.
404 + RUN canon=/usr/lib/systemd/system/swayosd-libinput-backend.service; \
405 + real=/usr/lib64/systemd/system/swayosd-libinput-backend.service; \
406 + if [ -e "$canon" ]; then \
407 + echo "swayosd unit is already in the search path; the shim can go"; \
408 + elif [ -e "$real" ]; then \
409 + ln -s "$real" "$canon"; \
410 + else \
411 + echo "swayosd-libinput-backend.service is at neither path" >&2; exit 1; \
412 + fi
413 +
385 414 RUN systemctl preset-all
386 415
387 416 # Template instances have to be enabled by name. `preset-all` iterates over
@@ -4,6 +4,7 @@
4 4 # units are enabled or disabled by default. Rationale for each entry:
5 5 #
6 6 # - greetd: boot lands on the tuigreet TUI on VT1.
7 + # - swayosd-libinput-backend: the caps-lock and num-lock overlays.
7 8 # - tailscaled: opt-in only. Enrollment happens via `alloy tail`
8 9 # (see docs/CONTINUITY.md — Alloy does not phone home
9 10 # before the user says yes).
@@ -26,4 +27,18 @@
26 27 # without a word, which reads exactly like the feature being broken. Its
27 28 # instances are enabled by name in the Containerfile instead.
28 29
30 + # Volume and brightness overlays need no daemon beyond `swayosd-server`,
31 + # which the sway config exec's, but the lock-key ones do: reading caps-lock
32 + # and num-lock state means watching the input devices, which a user session
33 + # cannot do. That is why this half of SwayOSD ships as a system unit, and
34 + # it is the only part of the package that has a unit at all. Without it the
35 + # sway config's `bindsym --release Caps_Lock` has nothing behind it, which
36 + # was the state until 2026-07-21.
37 + #
38 + # The privilege is real and worth naming: the daemon opens the libinput
39 + # devices to watch modifier state. Accepted because the alternative is a
40 + # bind that silently does nothing, and because it reads state rather than
41 + # injecting it.
42 + enable swayosd-libinput-backend.service
43 +
29 44 disable tailscaled.service
@@ -3,18 +3,25 @@
3 3 # Applied on first user login for services that run under the user
4 4 # session (not system-wide). Rationale for each entry:
5 5 #
6 - # - swayosd: Fn keys need the daemon live to render OSD overlays.
7 6 # - syncthing: opt-in only. Enrollment via `alloy sync`
8 7 # (see docs/CONTINUITY.md).
9 8 # - gammastep: opt-in only. User needs to set their location
10 9 # before enabling makes sense.
11 10 #
11 + # Both units above exist in the image; checked, because two lines here
12 + # have named units that do not.
13 + #
12 14 # alloy-hinged was enabled here until 2026-07-19. The fold-to-notes
13 15 # daemon was shelved in the 2026-07-17 pivot (see
14 16 # docs/HARDWARE-FW12.md#fold-to-notes-shelved) and no unit was ever
15 17 # shipped, so the line only produced a failed enable at first login.
16 -
17 - enable swayosd.service
18 + #
19 + # swayosd.service was enabled here until 2026-07-21 and was the same
20 + # mistake. Fedora's SwayOSD 0.3.2 ships three binaries, one SYSTEM unit
21 + # (swayosd-libinput-backend.service, now in the system preset) and a
22 + # D-Bus service file. There is no user unit, so this line named nothing.
23 + # The server is started by `exec swayosd-server` in the sway config,
24 + # which is the mechanism rather than a workaround for this line failing.
18 25
19 26 disable syncthing.service
20 27 disable gammastep.service