Keep Settings recovery isolated and display-safe

This commit is contained in:
Gabriel Brown
2026-08-18 03:19:45 -04:00
parent 6d3f888784
commit 7b8270fdd6
5 changed files with 64 additions and 37 deletions
+8 -4
View File
@@ -79,16 +79,20 @@ absent_result="$(run restore "$absent_name")" || fail 'restore failed for a snap
jq -e '.home.present == false and (.home | has("data") | not)' <<<"$absent_result" >/dev/null \
|| fail 'restore did not return absent Home state for the live service to reload'
# Desktop absence is symmetric: a Home-only snapshot removes a desktop file
# created later and restores the Home store.
# Desktop absence is symmetric for ordinary preferences, but confirmed display
# geometry is protected state: it must survive even a Home-only snapshot.
rm -f "$settings"
printf '{"initialized":true,"favorites":[{"id":"light.porch","alias":"Porch"}]}' >"$home"
run save >/dev/null || fail 'save failed when desktop settings were absent'
desktop_absent_name="$(run list | jq -r '.[0].name')"
printf '{"gapsOut":47}' >"$settings"
printf '{"gapsOut":47,"windowRounding":9,"displays":{"DP-2":{"mode":"4500x3000@60","scale":1.5,"transform":0}}}' >"$settings"
printf '{"initialized":false,"favorites":[]}' >"$home"
run restore "$desktop_absent_name" >/dev/null || fail 'Home-only snapshot restore failed'
[[ ! -e "$settings" ]] || fail 'restore did not preserve the snapshots absent desktop state'
[[ -e "$settings" ]] || fail 'Home-only restore discarded confirmed display geometry'
[[ "$(jq -r '.displays["DP-2"].scale' "$settings")" == "1.5" ]] \
|| fail 'Home-only restore changed confirmed display geometry'
[[ "$(jq 'keys == ["displays"]' "$settings")" == "true" ]] \
|| fail 'Home-only restore retained ordinary desktop preferences'
[[ "$(jq -r '.favorites[0].id' "$home")" == "light.porch" ]] \
|| fail 'Home-only snapshot did not restore Home state'
assert_transaction_clean
@@ -24,7 +24,8 @@ wallpaper_service="$repo_dir/config/dot/quickshell/services/Wallpaper.qml"
# Preferences are committed to $XDG_CONFIG_HOME, and the Home store lives under
# $XDG_STATE_HOME. Both are isolated so this contract cannot touch the real
# desktop's settings; the compositor is the live one and is restored below.
# desktop's settings. The harness replaces the compositor write seam as well,
# so interruption cannot leave the daily desktop modified.
config_home="$(mktemp -d /tmp/panama-commit-config.XXXXXX)"
state_home="$(mktemp -d /tmp/panama-commit-state.XXXXXX)"
@@ -53,11 +54,7 @@ qs_for_harness() {
XDG_CONFIG_HOME="$config_home" XDG_STATE_HOME="$state_home" qs -p "$harness" "$@"
}
original_rounding="$(hyprctl -j getoption decoration:rounding | jq -r .int)"
original_gaps="$(hyprctl -j getoption general:gaps_out | jq -r .css | awk '{print $1}')"
restore() {
hyprctl eval "hl.config({ decoration = { rounding = $original_rounding }, general = { gaps_out = $original_gaps } })" >/dev/null 2>&1 || true
qs_for_harness kill >/dev/null 2>&1 || true
rm -rf "$config_home" "$state_home"
}
@@ -76,17 +73,13 @@ qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-system-test$' || f
[[ "$(qs_for_harness ipc call settings-system-test stored showSeconds)" == "false" ]] \
|| fail 'a local key was not stored'
# ── A compositor key reaches Hyprland, then is stored ────────────────────────
target_rounding=$(( original_rounding == 11 ? 13 : 11 ))
# ── A compositor key reaches the verified apply boundary, then is stored ────
target_rounding=11
[[ "$(qs_for_harness ipc call settings-system-test commit windowRounding "$target_rounding")" == "true" ]] \
|| fail 'commitPreference refused a compositor key'
for _ in $(seq 1 40); do
[[ "$(hyprctl -j getoption decoration:rounding | jq -r .int)" == "$target_rounding" ]] && break
sleep 0.1
done
[[ "$(hyprctl -j getoption decoration:rounding | jq -r .int)" == "$target_rounding" ]] \
|| fail "a compositor-backed commit did not reach Hyprland (rounding=$(hyprctl -j getoption decoration:rounding | jq -r .int))"
apply_state="$(qs_for_harness ipc call settings-system-test applyState)"
jq -e '.[-1].windowRounding == 11' <<<"$apply_state" >/dev/null \
|| fail "a compositor-backed commit did not reach the apply boundary: $apply_state"
[[ "$(qs_for_harness ipc call settings-system-test stored windowRounding)" == "$target_rounding" ]] \
|| fail 'a verified compositor commit was not stored'
@@ -137,14 +130,10 @@ home_after="$(qs_for_harness ipc call settings-system-test homeState)"
jq -e '.count == 0 and .initialized == false' <<<"$home_after" >/dev/null \
|| fail "reset left the Home accessory store customised: $home_after"
# Resetting a stored value does not by itself tell Hyprland anything, so the
# reset must re-apply compositor-backed defaults too.
for _ in $(seq 1 40); do
[[ "$(hyprctl -j getoption decoration:rounding | jq -r .int)" == "18" ]] && break
sleep 0.1
done
[[ "$(hyprctl -j getoption decoration:rounding | jq -r .int)" == "18" ]] \
|| fail "reset did not re-apply the compositor default (rounding=$(hyprctl -j getoption decoration:rounding | jq -r .int))"
# Resetting a stored value does not itself apply compositor policy, so the last
# isolated batch must contain the shipped default.
jq -e '.appliedBatches[-1].windowRounding == 18' <<<"$reset_state" >/dev/null \
|| fail "reset did not re-apply the compositor default: $reset_state"
trap - EXIT
restore