Supervise fcitx5 so XCompose sequences can't silently die
fcitx5 is what turns the CapsLock compose sequences in ~/.XCompose into text for Wayland clients -- CapsLock m s for an emoji, CapsLock space n for your name. It was launched fire-and-forget from Hyprland's autostart via uwsm-app, so nothing supervised it, nothing restarted it, and it logged nowhere. When it went away, every compose sequence stopped working for the rest of the session with no visible symptom beyond "emoji input is busted", and no record of why. That is not hypothetical: it was found dead on a running machine with no coredump, no OOM kill, and nothing in the journal to explain it. Move it to a systemd user service: - Restart=always, not on-failure. fcitx5 exits 0 when it finds another instance already owning org.fcitx.Fcitx5, and a clean exit still leaves the user with no input method. - After=/PartOf=graphical-session.target. It needs WAYLAND_DISPLAY and DISPLAY, which uwsm imports into the user manager before reaching the target, and its wayland connection dies with the compositor. - ConditionEnvironment=WAYLAND_DISPLAY. After= is ordering only and does not stop the unit from being started while the target is inactive. An update over SSH has a live user manager (pam_systemd) and no graphical session, and a fcitx5 started there comes up blind -- then stays active, so the later target activation won't pull in a working one, because Wants= does not restart what is already running. Skipping the start leaves the unit enabled and healthy for the next graphical login. The migration hands over inside a live session only: it enables without --now, and only when graphical-session.target is active does it drop the autostart-launched process and start the unit. Because that kills a fcitx5 that was working a moment ago, a failed start is reported instead of leaving the session mute with the migration marked complete. omarchy-restart-xcompose now drives the unit. It still clears any fcitx5 running outside it first: that process owns the bus name, so the unit's instance would exit on arrival and the stale one would keep serving the old table -- a restart that reports success and changes nothing. Side benefit: fcitx5 now logs to the journal under its own unit, so the next disappearance leaves a record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
03902f2460
commit
6e07fd0e82
@@ -2,4 +2,9 @@
|
||||
|
||||
# omarchy:summary=Restart the XCompose input method service (fcitx5) to apply new compose key settings.
|
||||
|
||||
omarchy-restart-app fcitx5 --disable notificationitem
|
||||
# A fcitx5 running outside the unit owns org.fcitx.Fcitx5, so the unit's
|
||||
# instance would exit on arrival and the stale process would keep serving the
|
||||
# old ~/.XCompose -- a restart that reports success and changes nothing.
|
||||
systemctl --user stop omarchy-fcitx5.service
|
||||
pkill -x fcitx5 >/dev/null 2>&1 || true
|
||||
systemctl --user start omarchy-fcitx5.service
|
||||
|
||||
@@ -4,7 +4,6 @@ hl.on("hyprland.start", function()
|
||||
hl.exec_cmd("dbus-update-activation-environment --systemd --all")
|
||||
|
||||
hl.exec_cmd("quickshell -n -p $OMARCHY_PATH/shell")
|
||||
hl.exec_cmd(o.launch("fcitx5 --disable notificationitem"))
|
||||
hl.exec_cmd("omarchy-first-run")
|
||||
hl.exec_cmd("omarchy-powerprofiles-init")
|
||||
hl.exec_cmd(o.launch("omarchy-hyprland-monitor-watch"))
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[Unit]
|
||||
Description=Fcitx5 input method (XCompose sequences)
|
||||
# fcitx5 turns the CapsLock compose sequences in ~/.XCompose into text for
|
||||
# Wayland clients.
|
||||
#
|
||||
# Wait for the compositor: fcitx5 needs WAYLAND_DISPLAY and DISPLAY, which uwsm
|
||||
# imports into the user manager before it reaches graphical-session.target.
|
||||
After=graphical-session.target
|
||||
# The wayland connection dies with the compositor, so follow the session rather
|
||||
# than linger against a socket that is gone.
|
||||
PartOf=graphical-session.target
|
||||
# After= is ordering only -- it does not stop anything from starting this unit
|
||||
# while the target is inactive. An `omarchy update` over SSH has a live user
|
||||
# manager (pam_systemd) and no graphical session, and a fcitx5 started there
|
||||
# comes up with no WAYLAND_DISPLAY and no way to reach any client. Worse, it
|
||||
# stays active, so the later graphical-session.target activation won't pull in a
|
||||
# working one -- Wants= does not restart what is already running. Skip the start
|
||||
# instead; the unit stays enabled and starts for real at graphical login.
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# notificationitem duplicates the tray entry omarchy already renders itself.
|
||||
ExecStart=/usr/bin/fcitx5 --disable notificationitem
|
||||
# always, not on-failure: fcitx5 exits 0 when it detects another instance owning
|
||||
# its bus name, and a clean exit still leaves the user with no input method.
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
+2
-1
@@ -228,7 +228,8 @@ systemd instance:
|
||||
Voxtype post-update hook.
|
||||
- `install/user/first-run/enable-user-units.sh` — `systemctl --user enable`
|
||||
the shipped user units (`bt-agent`, `omarchy-sleep-lock`,
|
||||
`omarchy-recover-internal-monitor`, `omarchy-migrate-notify.service`).
|
||||
`omarchy-recover-internal-monitor`, `omarchy-migrate-notify.service`,
|
||||
`omarchy-fcitx5.service`).
|
||||
Done here, not at finalize, because
|
||||
the user manager isn't reachable from the ISO chroot; `ConditionPath*`
|
||||
in the unit files keeps services inert when they don't apply.
|
||||
|
||||
@@ -16,4 +16,5 @@ systemctl --user enable --now \
|
||||
bt-agent.service \
|
||||
omarchy-recover-internal-monitor.service \
|
||||
omarchy-sleep-lock.service \
|
||||
omarchy-migrate-notify.service
|
||||
omarchy-migrate-notify.service \
|
||||
omarchy-fcitx5.service
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
echo "Supervise fcitx5 so CapsLock compose sequences can't silently die"
|
||||
|
||||
# fcitx5 was launched fire-and-forget from Hyprland's autostart. Nothing
|
||||
# restarted it and nothing noticed when it went away, so a single lost start
|
||||
# left every ~/.XCompose sequence (CapsLock m s -> emoji) dead for the rest of
|
||||
# the session. It's a systemd user service with Restart=always now.
|
||||
|
||||
systemctl --user daemon-reload >/dev/null 2>&1 || true
|
||||
|
||||
# Enable without --now, and start by hand further down only when there is a
|
||||
# session to start into. `systemctl enable` needs a live user manager, which an
|
||||
# `omarchy update` from a TTY does not have, so fall back to writing exactly the
|
||||
# symlink it would have written rather than silently leaving this unenabled.
|
||||
if ! systemctl --user enable omarchy-fcitx5.service >/dev/null 2>&1; then
|
||||
wants_dir="$HOME/.config/systemd/user/graphical-session.target.wants"
|
||||
mkdir -p "$wants_dir"
|
||||
ln -sfn /usr/lib/systemd/user/omarchy-fcitx5.service \
|
||||
"$wants_dir/omarchy-fcitx5.service"
|
||||
fi
|
||||
|
||||
# Outside a graphical session -- an update over SSH -- there is nothing to hand
|
||||
# over: no autostart-launched fcitx5 to kill, and the unit's own
|
||||
# ConditionEnvironment would skip the start anyway. The enablement above is the
|
||||
# whole job; the next graphical login starts it.
|
||||
if systemctl --user is-active --quiet graphical-session.target; then
|
||||
# The service and the autostart-launched process can't coexist: a second
|
||||
# fcitx5 sees the first owning org.fcitx.Fcitx5 and exits 0, which
|
||||
# Restart=always would turn into a restart loop. Drop the stray first.
|
||||
pkill -x fcitx5 >/dev/null 2>&1 || true
|
||||
|
||||
# Report what systemctl actually said. This kills a fcitx5 that was working a
|
||||
# moment ago, so a start failure here has to be loud instead of leaving the
|
||||
# session with no input method and a migration marked complete.
|
||||
if ! error=$(systemctl --user start omarchy-fcitx5.service 2>&1); then
|
||||
echo "Could not start omarchy-fcitx5.service: $error"
|
||||
echo "Compose sequences (CapsLock m s) will not work until the next login."
|
||||
fi
|
||||
fi
|
||||
@@ -139,6 +139,7 @@ package_defaults = [
|
||||
("default/systemd/user/omarchy-recover-internal-monitor.service", "/usr/lib/systemd/user/omarchy-recover-internal-monitor.service", "systemd/user/omarchy-recover-internal-monitor.service"),
|
||||
("default/systemd/user/omarchy-migrate-notify.service", "/usr/lib/systemd/user/omarchy-migrate-notify.service", "systemd/user/omarchy-migrate-notify.service"),
|
||||
("default/systemd/user/omarchy-tailscale-receive.service", "/usr/lib/systemd/user/omarchy-tailscale-receive.service", "systemd/user/omarchy-tailscale-receive.service"),
|
||||
("default/systemd/user/omarchy-fcitx5.service", "/usr/lib/systemd/user/omarchy-fcitx5.service", "systemd/user/omarchy-fcitx5.service"),
|
||||
("default/systemd/zram-generator.conf.d/90-omarchy.conf", "/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf", "systemd/zram-generator.conf.d/90-omarchy.conf"),
|
||||
("default/fonts/omarchy/omarchy.ttf", "/usr/share/fonts/omarchy/omarchy.ttf", "omarchy.ttf"),
|
||||
("default/snapper/root", "/etc/snapper/config-templates/omarchy", "snapper/root"),
|
||||
|
||||
@@ -46,3 +46,33 @@ grep -F 'omarchy-migrate-notify.service' "$first_run_units" >/dev/null ||
|
||||
grep -F 'omarchy-update-user-notify' "$first_run_units" >/dev/null &&
|
||||
fail "first-run still enables the retired notifier units"
|
||||
pass "first-run enables the login-only migration notifier"
|
||||
|
||||
fcitx_service="$ROOT/default/systemd/user/omarchy-fcitx5.service"
|
||||
grep -Fx 'ExecStart=/usr/bin/fcitx5 --disable notificationitem' "$fcitx_service" >/dev/null
|
||||
grep -Fx 'Restart=always' "$fcitx_service" >/dev/null ||
|
||||
fail "fcitx5 exits 0 on a duplicate bus name, so on-failure would leave the user with no input method"
|
||||
grep -Fx 'After=graphical-session.target' "$fcitx_service" >/dev/null ||
|
||||
fail "fcitx5 needs WAYLAND_DISPLAY, which uwsm imports before it reaches graphical-session.target"
|
||||
grep -Fx 'PartOf=graphical-session.target' "$fcitx_service" >/dev/null ||
|
||||
fail "fcitx5 must stop with the compositor instead of lingering against a dead wayland socket"
|
||||
grep -Fx 'WantedBy=graphical-session.target' "$fcitx_service" >/dev/null ||
|
||||
fail "fcitx5 is never pulled in at login without a WantedBy"
|
||||
grep -Fx 'ConditionEnvironment=WAYLAND_DISPLAY' "$fcitx_service" >/dev/null ||
|
||||
fail "an update over SSH has a live user manager and no display; starting fcitx5 there wedges the unit active-but-blind, and Wants= will not replace it at graphical login"
|
||||
|
||||
fcitx_migration="$ROOT/migrations/1785167800.sh"
|
||||
grep -F 'is-active --quiet graphical-session.target' "$fcitx_migration" >/dev/null ||
|
||||
fail "migration kills fcitx5 and starts the unit outside a graphical session"
|
||||
grep -F 'systemctl --user enable omarchy-fcitx5.service' "$fcitx_migration" >/dev/null ||
|
||||
fail "migration must enable without --now; --now starts the unit before the session-gate check"
|
||||
grep -F 'Could not start omarchy-fcitx5.service' "$fcitx_migration" >/dev/null ||
|
||||
fail "migration pkills a working fcitx5, so a failed handover must be reported instead of marked complete"
|
||||
|
||||
grep -F 'pkill -x fcitx5' "$ROOT/bin/omarchy-restart-xcompose" >/dev/null ||
|
||||
fail "restart-xcompose cannot reload a fcitx5 running outside the unit, so it silently keeps serving the old table"
|
||||
|
||||
grep -F 'omarchy-fcitx5.service' "$first_run_units" >/dev/null ||
|
||||
fail "first-run does not enable the input method, so ~/.XCompose sequences never resolve"
|
||||
grep -F 'fcitx5' "$ROOT/default/hypr/autostart.lua" >/dev/null &&
|
||||
fail "fcitx5 is autostarted from Hyprland; an unsupervised launch dies silently and takes every compose sequence with it"
|
||||
pass "fcitx5 runs supervised, so a lost input method comes back instead of killing XCompose until logout"
|
||||
|
||||
Reference in New Issue
Block a user