Files
moonarch/defaults/xdg/stasis/stasis.rune
T
nevaforget 8d31ff26e0 feat(lock): gtklock replaces swaylock
moonix switched first; this follows so the two distributions agree again
and moonix can drop the two substitute derivations that were rewriting
these configs at deploy time.

Two things made it worth doing. gtklock's modules show an avatar, a clock
and power buttons, none of which swaylock can do at all. And the old
objection is gone: 4.0.0 links libgtk-session-lock.so.0, so it speaks
ext-session-lock-v1 exactly like swaylock and the compositor still holds
the lock if the process dies. Only the <=2.x versions used layer-shell,
where a crash unlocked the screen.

The config location is the real prize. gtklock searches $XDG_CONFIG_DIRS,
so defaults/xdg/gtklock/config.ini rides the generic sweep and takes effect
through the package alone. Everything built for swaylock in 2026-08-18 goes
with it: the explicit install line in the PKGBUILD, the pre_upgrade
retirement of seeded home copies, check_swaylock_config in moonarch-doctor,
the comment in post-install.sh. swaylock needed all of that because its
sysconfdir was not an XDG path.

The blur stays baked into the wallpaper. gtklock cannot blur either — GTK3
has no filter CSS property and it takes no screenshot of the session, so
there is nothing to blur at lock time.

playerctl-module is deliberately not installed; it needs playerctld, which
nothing here starts. The other two are named bare in modules= because
gtklock's compiled-in module directory really is /usr/lib/gtklock on Arch.
All three packages are in extra, so official.txt covers them.

Both callers keep spawning the locker directly. There is no Wayland
protocol for "lock the session" — ext-session-lock-v1 is how a client
becomes the lock — and no portal either; logind's Session.Lock only emits a
signal nothing here subscribes to. Routing the popout through `stasis
trigger lock-screen` would name the locker once instead of twice, at the
price of a Lock button that fails silently whenever stasis is down. The
reason to want it does not exist: the plan continues after a manual lock
anyway, because stasis takes the state from logind's LockedHint rather than
from having started the locker.

The PKGBUILD side of this is a separate commit in moonarch-pkgbuilds, and
it carries the part that fails silently if forgotten: pre_upgrade rewrites
already-seeded ~/.config/stasis/stasis.rune, which is copied once and never
overwritten. Without it an existing install keeps calling a locker that is
no longer there.
2026-09-12 02:25:00 +02:00

145 lines
4.9 KiB
Plaintext

# Stasis idle manager configuration for Moonarch
# Manages screen dimming, DPMS, lock, and suspend timers
@author "Moonarch"
@description "Idle management for Moonarch (Niri + gtklock)"
# TIMEOUTS ARE CUMULATIVE, NOT ABSOLUTE.
#
# Each `timeout` counts from the moment the *previous* step in the same block
# fired, not from the last input event. Only the first step measures raw idle
# time. Verified against a running daemon: with `dpms 120` followed by
# `lock_screen 180`, dpms fired at 18:43:30 and the lock at 18:46:31 -- 181
# seconds later, not 60.
#
# This file previously carried the upstream template's numbers while reading
# them as absolute. The result was a screen that went dark after 2 minutes when
# 6 were intended, and a `brightness 300` step behind a `dpms 120` step that
# could never run, because the display was already off when its turn came.
#
# Step order inside a block is execution order. Lock before DPMS, so the
# session is already secured when the display shuts off, and the display then
# stays off instead of being lit again to show the locker.
default:
# monitor_media: detect active media via PipeWire/PulseAudio sink-inputs
# (pactl), not MPRIS. Non-browser players (mpv, vlc, ...) are counted and
# inhibit idle. Browser audio is excluded by design; browser idle-inhibit is
# expected via D-Bus (enable_dbus_inhibit), which Waterfox/Firefox only send
# in fullscreen -- so windowed browser video is NOT caught by stasis.
# ignore_remote_media false: also count remote players (Spotify-remote,
# KDEConnect, Chromecast); has no effect on windowed browser video.
monitor_media true
ignore_remote_media false
# Both empty on purpose: nothing here should keep the machine awake beyond
# what inhibit_apps already covers. They are set rather than omitted because
# stasis discards any config missing a knob its own template carries, backing
# it up and starting over from that template.
suspend_inhibit_apps [ ]
suspend_inhibit_media [ ]
# App/process inhibit patterns (apps that don't use D-Bus idle-inhibit)
inhibit_apps [
"vlc"
"mpv"
"celluloid"
r"steam_app_.*"
"gamescope"
"virt-manager"
"qemu"
"obs"
"kdenlive"
"handbrake"
"blender"
]
# Lid actions (laptop only). Locks only; the suspend on lid close comes from
# logind's HandleLidSwitch default, not from here.
lid_close_action "gtklock -d"
lid_open_action ""
# Every binary named in a command below has to be on the PATH of the unit
# that starts stasis -- stasis does not run the actions through a login
# shell, so /etc/profile never applies. A missing binary fails silently: the
# step is logged as started, exits immediately, and the plan stops there.
# In moonix that PATH lives in modules/services.nix.
# Desktop plan (machines without a battery)
#
# These blocks sit directly under `default:` on purpose: stasis builds the
# desktop plan from exactly those, with the ac:/battery: containers excluded
# (src/config/mod.rs, parse_plan_block "default"). A machine whose chassis
# reports no battery gets PlanSource::Desktop, and select_plan_source only
# ever falls back *onto* the desktop plan, never from it onto ac: -- so with
# the action blocks living in ac:/battery: alone, a desktop ran a plan of
# disabled steps: no DPMS, no lock, "waiting for idle" forever.
#
# Only brightness is left out: brightnessctl drives an internal panel, and
# external monitors need DDC/CI, which it does not speak. /sys/class/backlight
# is empty on a desktop, so the step would be a no-op.
lock_screen:
timeout 300 # 5 min idle
command "gtklock"
end
dpms:
timeout 60 # 1 min after the lock, so 6 min idle
command "niri msg action power-off-monitors"
resume_command "niri msg action power-on-monitors"
end
suspend:
timeout 1800 # 30 min after the blank, so 36 min idle
command "systemctl suspend"
end
# AC power plan (relaxed)
ac:
brightness:
timeout 300 # 5 min idle
command "brightnessctl set 50%"
end
lock_screen:
timeout 60 # 6 min idle
command "gtklock"
end
dpms:
timeout 60 # 7 min idle
command "niri msg action power-off-monitors"
resume_command "niri msg action power-on-monitors"
end
suspend:
timeout 1800 # 37 min idle
command "systemctl suspend"
end
end
# Battery plan (aggressive)
battery:
brightness:
timeout 60 # 1 min idle
command "brightnessctl set 30%"
end
lock_screen:
timeout 60 # 2 min idle
command "gtklock"
end
dpms:
timeout 30 # 2.5 min idle
command "niri msg action power-off-monitors"
resume_command "niri msg action power-on-monitors"
end
suspend:
timeout 300 # 7.5 min idle
command "systemctl suspend"
end
end
end