Pause Hyprland reloads during settings updates

This commit is contained in:
Ryan Hughes
2026-06-09 14:20:36 -04:00
parent 9b8c9fd537
commit f79a231cc7
7 changed files with 244 additions and 8 deletions
+138
View File
@@ -0,0 +1,138 @@
#!/bin/bash
# omarchy:summary=Pause or resume Hyprland config auto-reload around package transactions.
# omarchy:hidden=true
set -euo pipefail
command="${1:-}"
case "$command" in
pause | resume) ;;
*)
echo "Usage: omarchy-hyprland-reload-guard pause|resume" >&2
exit 1
;;
esac
run_root="${OMARCHY_HYPRLAND_RELOAD_GUARD_RUN_ROOT:-/run/user}"
state_dir="${OMARCHY_HYPRLAND_RELOAD_GUARD_STATE_DIR:-/run/omarchy/hyprland-reload-guard}"
hyprctl_bin="${HYPRCTL:-}"
if [[ -z $hyprctl_bin ]]; then
hyprctl_bin=/usr/bin/hyprctl
[[ -x $hyprctl_bin ]] || hyprctl_bin=$(command -v hyprctl || true)
fi
[[ -n $hyprctl_bin && -x $hyprctl_bin ]] || exit 0
hyprctl_instance() {
local runtime_dir="$1"
local signature="$2"
shift 2
XDG_RUNTIME_DIR="$runtime_dir" \
HYPRLAND_INSTANCE_SIGNATURE="$signature" \
"$hyprctl_bin" --instance "$signature" "$@"
}
option_bool() {
local runtime_dir="$1"
local signature="$2"
local option="$3"
local output=""
output=$(hyprctl_instance "$runtime_dir" "$signature" -j getoption "$option" 2>/dev/null) || return 1
if command -v jq >/dev/null 2>&1; then
jq -r 'if .bool == true then "true" else "false" end' <<<"$output"
elif [[ $output == *'"bool": true'* ]]; then
printf 'true\n'
else
printf 'false\n'
fi
}
instances() {
[[ -d $run_root ]] || return 0
find "$run_root" -mindepth 3 -maxdepth 3 -type d -path "$run_root/*/hypr/*" -print 2>/dev/null |
while IFS= read -r instance_dir; do
local signature hypr_dir runtime_dir uid
signature="${instance_dir##*/}"
hypr_dir="${instance_dir%/*}"
runtime_dir="${hypr_dir%/*}"
uid="${runtime_dir##*/}"
[[ $uid =~ ^[0-9]+$ ]] || continue
[[ -n $signature ]] || continue
printf '%s\t%s\t%s\n' "$runtime_dir" "$uid" "$signature"
done
}
state_file_for() {
local signature="$1"
printf '%s/%s\n' "$state_dir" "$signature"
}
pause_instance() {
local runtime_dir="$1"
local uid="$2"
local signature="$3"
local disable_autoreload suppress_errors state_file
mkdir -p "$state_dir"
state_file=$(state_file_for "$signature")
if [[ ! -f $state_file ]]; then
disable_autoreload=$(option_bool "$runtime_dir" "$signature" misc.disable_autoreload) || return 0
suppress_errors=$(option_bool "$runtime_dir" "$signature" debug.suppress_errors) || return 0
printf '%s\t%s\t%s\n' "$uid" "$disable_autoreload" "$suppress_errors" >"$state_file"
chmod 0600 "$state_file" 2>/dev/null || true
fi
hyprctl_instance "$runtime_dir" "$signature" eval \
'hl.config({ misc = { disable_autoreload = true }, debug = { suppress_errors = true } })' \
>/dev/null 2>&1 || true
}
resume_instance() {
local state_file="$1"
local signature uid disable_autoreload suppress_errors runtime_dir
signature="${state_file##*/}"
IFS=$'\t' read -r uid disable_autoreload suppress_errors <"$state_file" || return 0
runtime_dir="$run_root/$uid"
if [[ -d $runtime_dir ]]; then
hyprctl_instance "$runtime_dir" "$signature" eval \
"hl.config({ debug = { suppress_errors = $suppress_errors } })" \
>/dev/null 2>&1 || true
hyprctl_instance "$runtime_dir" "$signature" reload >/dev/null 2>&1 || true
hyprctl_instance "$runtime_dir" "$signature" eval \
"hl.config({ misc = { disable_autoreload = $disable_autoreload }, debug = { suppress_errors = $suppress_errors } })" \
>/dev/null 2>&1 || true
fi
rm -f "$state_file"
}
case "$command" in
pause)
while IFS=$'\t' read -r runtime_dir uid signature; do
pause_instance "$runtime_dir" "$uid" "$signature"
done < <(instances)
;;
resume)
[[ -d $state_dir ]] || exit 0
find "$state_dir" -mindepth 1 -maxdepth 1 -type f -print 2>/dev/null |
while IFS= read -r state_file; do
resume_instance "$state_file"
done
rmdir "$state_dir" 2>/dev/null || true
;;
esac
@@ -0,0 +1,12 @@
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = omarchy-settings
Target = omarchy-settings-dev
[Action]
Description = Pausing Hyprland config auto-reload for Omarchy settings update...
When = PreTransaction
Depends = omarchy
Exec = /usr/bin/omarchy-hyprland-reload-guard pause
@@ -0,0 +1,12 @@
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = omarchy-settings
Target = omarchy-settings-dev
[Action]
Description = Reloading Hyprland after Omarchy settings update...
When = PostTransaction
Depends = omarchy
Exec = /usr/bin/omarchy-hyprland-reload-guard resume
+2 -2
View File
@@ -54,8 +54,8 @@ bin/omarchy-debug,
bin/omarchy-debug-idle,
bin/omarchy-upload-log ──► omarchy-settings /usr/bin/ (needed before omarchy is installed)
default/libalpm/hooks/00-omarchy-update-guard.hook
──► omarchy /usr/share/libalpm/hooks/00-omarchy-update-guard.hook
default/libalpm/hooks/*.hook
──► omarchy /usr/share/libalpm/hooks/*.hook
install/** ──► omarchy /usr/share/omarchy/install/
migrations/system/** ──► omarchy /usr/share/omarchy/migrations/system/
+8
View File
@@ -151,6 +151,14 @@ sudo env OMARCHY_ALLOW_DIRECT_PACMAN=1 pacman -Syu
The guard does not start `omarchy update` itself because pacman is already in a
transaction setup path; it only aborts with instructions.
The `omarchy` package also installs ALPM hooks for `omarchy-settings` /
`omarchy-settings-dev` installs and upgrades. The pre-transaction hook runs
`omarchy-hyprland-reload-guard pause` to disable live Hyprland config reloads
while `/usr/share/omarchy/default/hypr/**` is replaced. The post-transaction
hook runs `omarchy-hyprland-reload-guard resume`, forces one `hyprctl reload`,
and restores the session's previous `misc.disable_autoreload` and
`debug.suppress_errors` values.
## Path 1: `omarchy update`
High-level flow:
+12 -6
View File
@@ -127,12 +127,18 @@ for source, destination, legacy in package_defaults:
if destination and (source not in pkgbuild or destination not in pkgbuild):
errors.append(f"PKGBUILD does not explicitly install {source} -> {destination}")
guard_hook = "default/libalpm/hooks/00-omarchy-update-guard.hook"
guard_destination = "/usr/share/libalpm/hooks/00-omarchy-update-guard.hook"
if not (root / guard_hook).exists():
errors.append(f"missing package default source: {guard_hook}")
if guard_hook not in omarchy_pkgbuild or guard_destination not in omarchy_pkgbuild:
errors.append(f"omarchy PKGBUILD does not install {guard_hook} -> {guard_destination}")
alpm_hooks = [
"00-omarchy-update-guard.hook",
"10-omarchy-hyprland-reload-pause.hook",
"90-omarchy-hyprland-reload-resume.hook",
]
for hook in alpm_hooks:
source = f"default/libalpm/hooks/{hook}"
destination = f"/usr/share/libalpm/hooks/{hook}"
if not (root / source).exists():
errors.append(f"missing package default source: {source}")
if source not in omarchy_pkgbuild or destination not in omarchy_pkgbuild:
errors.append(f"omarchy PKGBUILD does not install {source} -> {destination}")
if errors:
print("\n".join(errors), file=sys.stderr)
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
set -euo pipefail
source "$(dirname "$0")/base-test.sh"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
run_root="$test_tmp/run-user"
state_dir="$test_tmp/state"
hyprctl_log="$test_tmp/hyprctl.log"
fake_hyprctl="$test_tmp/hyprctl"
signature="test-signature"
runtime_dir="$run_root/1000"
mkdir -p "$runtime_dir/hypr/$signature"
cat >"$fake_hyprctl" <<'BASH'
#!/bin/bash
printf '%s\t%s\t%s\n' "$XDG_RUNTIME_DIR" "$HYPRLAND_INSTANCE_SIGNATURE" "$*" >>"$FAKE_HYPRCTL_LOG"
case "$*" in
*'getoption misc.disable_autoreload'*)
printf '{"option":"misc.disable_autoreload","bool":%s,"set":true}\n' "${FAKE_DISABLE_AUTORELOAD:-false}"
;;
*'getoption debug.suppress_errors'*)
printf '{"option":"debug.suppress_errors","bool":%s,"set":true}\n' "${FAKE_SUPPRESS_ERRORS:-false}"
;;
*)
printf 'ok\n'
;;
esac
BASH
chmod 0755 "$fake_hyprctl"
FAKE_HYPRCTL_LOG="$hyprctl_log" \
HYPRCTL="$fake_hyprctl" \
OMARCHY_HYPRLAND_RELOAD_GUARD_RUN_ROOT="$run_root" \
OMARCHY_HYPRLAND_RELOAD_GUARD_STATE_DIR="$state_dir" \
"$ROOT/bin/omarchy-hyprland-reload-guard" pause
state_file="$state_dir/$signature"
[[ -f $state_file ]] || fail "reload guard stores Hyprland state on pause"
grep -Fx $'1000 false false' "$state_file" >/dev/null || fail "reload guard records previous Hyprland reload settings"
grep -F 'hl.config({ misc = { disable_autoreload = true }, debug = { suppress_errors = true } })' "$hyprctl_log" >/dev/null || fail "reload guard pauses autoreload with hyprctl eval"
pass "reload guard pauses live Hyprland reloads"
: >"$hyprctl_log"
FAKE_HYPRCTL_LOG="$hyprctl_log" \
HYPRCTL="$fake_hyprctl" \
OMARCHY_HYPRLAND_RELOAD_GUARD_RUN_ROOT="$run_root" \
OMARCHY_HYPRLAND_RELOAD_GUARD_STATE_DIR="$state_dir" \
"$ROOT/bin/omarchy-hyprland-reload-guard" resume
[[ ! -e $state_file ]] || fail "reload guard clears Hyprland state after resume"
grep -F $'test-signature --instance test-signature reload' "$hyprctl_log" >/dev/null || fail "reload guard forces one Hyprland reload after package transaction"
grep -F 'hl.config({ misc = { disable_autoreload = false }, debug = { suppress_errors = false } })' "$hyprctl_log" >/dev/null || fail "reload guard restores previous Hyprland reload settings"
pass "reload guard resumes live Hyprland reloads"