Files
Gabriel Brown e723fabed3 Stop the session you started, so the next one can begin
Logging out of plain Hyprland left the desktop running. autostart.lua starts
about ten user units, none of them children of the compositor, and the shutdown
handler that was meant to stop them stopped hyprland-session.target -- a unit
Fedora does not ship. systemctl says 'Unit not found', the handler reports
nothing, and hyprpaper, hypridle, vicinae and the polkit agent keep running
after logout. Each of them Requires=graphical-session.target, so that target
stayed active with no session behind it.

The symptom was not a broken Hyprland. Both uwsm and gnome-session refuse to
start into a session that is already running, so 'Hyprland (uwsm-managed)' and
GNOME both bounced back to the login screen, and plain Hyprland -- which makes
no such check -- was the only one that worked. The session that looked healthy
was the one leaving the mess, and it was the one Panama tells people not to use.

Now stops graphical-session.target, which every one of those units is PartOf,
so a unit added to the start handler later cannot be forgotten here.

panama-crash-watch had the same leak and no PartOf at all: a journal follower
per login, and a 'once per program per session' dedup that had quietly become
once per boot.
2026-08-23 13:55:47 -04:00

119 lines
7.4 KiB
Lua

-- ─────────────────────────────────────────────────────────────────────────────
-- Autostart
--
-- `exec-once` no longer exists in 0.56; startup is an event handler.
--
-- Session: Fedora installs two sessions, "Hyprland" and "Hyprland
-- (uwsm-managed)". Panama targets the uwsm one, because
-- xdg-desktop-portal-hyprland's systemd unit requires graphical-session.target
-- and Fedora ships no hyprland-session.target -- without uwsm, screen sharing
-- in OBS/Sunshine/Zoom silently fails. The target is started explicitly below
-- so the plain session works too.
--
-- Anything that ships a systemd user unit is started as a unit rather than as
-- a compositor child. That is not cosmetic: under uwsm, units get the correct
-- activation environment (see uwsm/env), they restart on failure, and they
-- shut down in order. Panama's `change-settings` script enables them once;
-- the explicit starts here make a fresh clone work before that has run.
-- ─────────────────────────────────────────────────────────────────────────────
hl.on("hyprland.start", function()
-- Publish the session environment to systemd and D-Bus so user units and
-- the portals can see WAYLAND_DISPLAY. Cheap, and essential without uwsm.
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=Hyprland")
hl.exec_cmd("systemctl --user start hyprland-session.target")
-- Units: polkit prompts, wallpaper, launcher daemon, idle/lock. All four
-- carry `ConditionEnvironment=WAYLAND_DISPLAY`, and hl.exec_cmd fires
-- commands without waiting for them to finish, so the dbus-update call
-- above racing this one is not safe to assume complete -- a lost race
-- leaves the Condition unmet and the unit silently never starts (exit 0,
-- no error). hypridle is the only listener for the logind Lock signal,
-- so that failure mode is "lock-session goes to nobody". Re-import
-- synchronously in the same shell invocation first so the Condition
-- always sees it, regardless of how the dbus-update call above scheduled.
-- panama-polkit-agent replaces hyprpolkitagent, whose prompt is compiled
-- into its binary and cannot be themed. Only one agent may register per
-- session, so they must not both start. hyprpolkitagent stays INSTALLED as
-- the fallback: `systemctl --user start hyprpolkitagent` restores the stock
-- prompt if Panama's ever fails to come up.
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP && systemctl --user start panama-polkit-agent.service hyprpaper.service vicinae.service hypridle.service")
-- Pending repairs for this machine, if any. Oneshot: it checks, tells the
-- user when there is something to tell, and exits. Started here rather
-- than enabled so it belongs to the Hyprland session; see the unit.
hl.exec_cmd("systemctl --user start panama-migrate-notify.service")
-- Notices when a program dumps core and says so once per session. Under
-- GNOME, ABRT does this; here nothing did, and applications died silently.
hl.exec_cmd("systemctl --user start panama-crash-watch.service")
-- Text expansion. change-settings runs `espanso service register`, which
-- writes and enables espanso's own user unit; the explicit start makes the
-- first Hyprland login after a fresh install work rather than the second.
-- Started on its own line: unlike the units above it carries no
-- ConditionEnvironment, and a missing unit (espanso not yet registered)
-- must not muddy the start of the four that lock and wallpaper depend on.
hl.exec_cmd("systemctl --user start espanso.service")
-- The shell: bar, dock, overview, quick settings, notifications, capture.
-- A supervised unit rather than a compositor child, for the one property
-- a child cannot have: if the shell crashes, systemd restarts it in two
-- seconds instead of leaving a desktop with no bar until someone knows
-- what to type. The unit ships in config/local/share/systemd/user and is
-- started per-session here like every other Panama unit.
hl.exec_cmd("systemctl --user start panama-quickshell.service")
-- Removable-media automounting. GNOME did this invisibly via gvfs+udisks;
-- outside GNOME something has to ask udisks to mount. No tray icon: the
-- Quickshell bar already has a tray, and udiskie's own icon would be
-- redundant. -f opens Nautilus when you click the mount notification.
hl.exec_cmd("udiskie --automount --notify --no-tray --file-manager nautilus")
-- Keyring unlock, for Nextcloud and Bitwarden credential storage.
hl.exec_cmd("/usr/bin/gnome-keyring-daemon --start --components=secrets,ssh,pkcs11")
-- Nextcloud and Bitwarden are NOT started here. Both ship a
-- ~/.config/autostart/*.desktop entry, and systemd's own
-- systemd-xdg-autostart-generator turns every such entry into a
-- `PartOf=graphical-session.target` unit (`[email protected]`,
-- `[email protected]`) that fires once the uwsm
-- session brings up graphical-session.target -- confirmed live via
-- `systemctl --user list-units 'app-*@autostart.service'`. An explicit
-- second launch here used to duplicate that: for Bitwarden specifically,
-- each `flatpak run` gets its own sandbox instance, so the two starts
-- didn't just race, they left two competing processes fighting over the
-- app's single-instance lock, with neither reliably owning a usable
-- window. Trust the generator instead of re-launching.
--
-- RustDesk is deliberately absent too: it ships an enabled *system*
-- service (`rustdesk --service`) that spawns --server and --tray for the
-- session on its own. Starting it here as well would give you two trays.
end)
-- Tear down what the start handler brought up.
--
-- This stopped `hyprland-session.target`, which does not exist on Fedora --
-- systemctl reports "Unit not found" and the handler achieves nothing. The
-- units started above are not children of the compositor, so nothing else
-- stopped them either: hyprpaper, hypridle, vicinae and the polkit agent kept
-- running after logout, and each one Requires=graphical-session.target, so
-- that target stayed active with no session behind it.
--
-- The next login then failed. Both uwsm and gnome-session refuse to start into
-- a session that is already running ("A compositor or graphical-session*
-- target is already active!"), so "Hyprland (uwsm-managed)" and GNOME both
-- bounced straight back to the login screen while plain Hyprland -- which
-- makes no such check -- kept working. The desktop appeared to be the only one
-- that functioned, when it was the one leaving the mess.
--
-- Stopping graphical-session.target is enough on its own: every unit above is
-- PartOf= it, so they come down with it, and it carries StopWhenUnneeded=yes.
-- It is stopped rather than the units listed individually so that a unit added
-- to the start handler later cannot be forgotten here.
hl.on("hyprland.shutdown", function()
hl.exec_cmd("systemctl --user stop graphical-session.target")
end)
return true