#!/usr/bin/env bash # Display configuration. # # This is the only setting in Panama that can leave the user unable to SEE the # screen well enough to undo it: a mode the panel cannot show, or a scale that # makes everything unreadable, is not recoverable through the UI that caused it. # # So the property under test is not "can it change the resolution" but "does an # unconfirmed change always come back". A regression here is not a broken # feature, it is a user staring at a blank monitor. # # * an unconfirmed change reverts on its own, and stores nothing # * a confirmed change is what writes to the settings store # * a mode, scale, rotation, or output the compositor did not offer is refused # before anything is applied # # The compositor is the live one -- there is no way to test this otherwise -- # but preferences are isolated, and every path restores the display it started # from. set -euo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" harness="$repo_dir/config/dot/quickshell/displays-harness.qml" config_home="$(mktemp -d /tmp/panama-displays-config.XXXXXX)" fail() { printf 'displays contract: %s\n' "$1" >&2 exit 1 } run() { XDG_CONFIG_HOME="$config_home" qs -p "$harness" "$@"; } status() { run ipc call displays-test status; } original_mode="" original_scale="" original_transform="" restore() { # Through hyprctl rather than the harness: if the harness apply path is what # is broken, the daily-driver display must still come back. if [[ -n "$original_mode" ]]; then hyprctl eval "hl.monitor({ output = \"$monitor_name\", mode = \"$original_mode\", scale = $original_scale, transform = $original_transform })" >/dev/null 2>&1 || true fi # Kill by PID, never `pkill -f displays-harness`: that pattern also matches # any shell whose command line contains this script's text, which includes # the invoking shell itself. [[ -n "${harness_pid:-}" ]] && kill "$harness_pid" >/dev/null 2>&1 || true rm -rf "$config_home" } trap restore EXIT XDG_CONFIG_HOME="$config_home" qs -p "$harness" --daemonize >/dev/null harness_pid="" for _ in $(seq 1 40); do run ipc show 2>/dev/null | rg -q '^target displays-test$' && break sleep 0.1 done run ipc show 2>/dev/null | rg -q '^target displays-test$' || fail 'test IPC target did not start' harness_pid="$(run list | awk '/Process ID:/ { print $3; exit }')" for _ in $(seq 1 50); do [[ "$(status | jq -r .count)" != "0" ]] && break sleep 0.1 done state="$(status)" monitor_name="$(jq -r .name <<<"$state")" [[ -n "$monitor_name" ]] || fail "no display was detected: $state" original_mode="$(jq -r '"\(.width)x\(.height)@\(.refresh)"' <<<"$state")" original_scale="$(jq -r .scale <<<"$state")" original_transform="$(jq -r .transform <<<"$state")" [[ "$(jq -r .modes <<<"$state")" -gt 0 ]] || fail 'the display reported no usable modes' # ── Anything the compositor did not offer is refused before applying ───────── while IFS= read -r kind; do [[ "$(run ipc call displays-test applyBad "$kind")" == "false" ]] \ || fail "an invalid $kind was accepted" [[ "$(status | jq -r .awaiting)" == "false" ]] \ || fail "an invalid $kind left a change pending" done <<'KINDS' mode scale transform output KINDS # The display must not have moved for any of those. now="$(status)" [[ "$(jq -r .scale <<<"$now")" == "$original_scale" ]] || fail 'a refused change still altered the scale' # ── An unconfirmed change reverts on its own and stores nothing ────────────── target_scale=$(awk -v s="$original_scale" 'BEGIN { print (s == 1.25) ? 1.5 : 1.25 }') [[ "$(run ipc call displays-test applyScale "$target_scale")" == "true" ]] \ || fail 'a valid scale change was refused' applied=false for _ in $(seq 1 30); do [[ "$(hyprctl -j monitors | jq -r '.[0].scale')" == "$target_scale" ]] && { applied=true; break; } sleep 0.2 done [[ "$applied" == true ]] || fail 'the scale change never reached the compositor' [[ "$(status | jq -r .awaiting)" == "true" ]] || fail 'an applied change is not awaiting confirmation' # Wait out the countdown. This is the whole point of the contract. reverted=false for _ in $(seq 1 120); do if [[ "$(hyprctl -j monitors | jq -r '.[0].scale')" == "$original_scale" ]]; then reverted=true break fi sleep 0.5 done [[ "$reverted" == true ]] || fail 'an unconfirmed change did NOT revert -- this would strand a user on an unreadable display' [[ "$(status | jq -r .awaiting)" == "false" ]] || fail 'the pending state survived the revert' [[ "$(status | jq -r .overridden)" == "false" ]] || fail 'an unconfirmed change was written to the settings store' # ── A confirmed change is what writes ──────────────────────────────────────── run ipc call displays-test applyScale "$target_scale" >/dev/null sleep 1 run ipc call displays-test confirmChange >/dev/null sleep 0.6 [[ "$(status | jq -r .awaiting)" == "false" ]] || fail 'confirming did not clear the pending state' [[ "$(status | jq -r .overridden)" == "true" ]] || fail 'confirming did not store the change' store="$config_home/panama/settings.json" jq -e --arg m "$monitor_name" '.displays[$m].scale != null' "$store" >/dev/null \ || fail 'the confirmed change is not in the settings store' # ── Forgetting clears it ───────────────────────────────────────────────────── run ipc call displays-test forget >/dev/null sleep 0.6 [[ "$(status | jq -r .overridden)" == "false" ]] || fail 'forget did not clear the stored display setting' trap - EXIT restore printf 'displays contract: PASS\n'