#!/bin/bash set -euo pipefail repo_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" helper="$repo_dir/config/dot/quickshell/scripts/panama-osd" scratch="$(mktemp -d)" trap 'rm -rf "$scratch"' EXIT mkdir -p "$scratch/bin" log="$scratch/calls" cat >"$scratch/bin/wpctl" <<'SH' #!/bin/bash printf 'wpctl' >>"$OSD_TEST_LOG" printf ' <%s>' "$@" >>"$OSD_TEST_LOG" printf '\n' >>"$OSD_TEST_LOG" if [[ $1 == "get-volume" ]]; then printf '%s\n' "${WPCTL_OUTPUT:-Volume: 0.58}" fi SH cat >"$scratch/bin/brightnessctl" <<'SH' #!/bin/bash printf 'brightnessctl' >>"$OSD_TEST_LOG" printf ' <%s>' "$@" >>"$OSD_TEST_LOG" printf '\n' >>"$OSD_TEST_LOG" if [[ " $* " == *" -m "* && " $* " != *" set "* ]]; then [[ ${BACKLIGHT_AVAILABLE:-true} == true ]] || exit 1 printf '%s\n' "${BRIGHTNESS_OUTPUT:-intel_backlight,backlight,500,50%,1000}" fi SH cat >"$scratch/bin/panama-brightness" <<'SH' #!/bin/bash printf 'panama-brightness' >>"$OSD_TEST_LOG" printf ' <%s>' "$@" >>"$OSD_TEST_LOG" printf '\n' >>"$OSD_TEST_LOG" case "${1:-}" in list) if [[ -n ${DDC_LIST_JSON:-} ]]; then printf '%s\n' "$DDC_LIST_JSON" else printf '%s\n' '{"displays":[],"error":"No displays"}' fi ;; get) [[ ${DDC_FAIL_GET_BUS:-} != "${2:-}" ]] || exit 1 if [[ -s $OSD_DDC_STATE ]]; then cat "$OSD_DDC_STATE" else printf '%s\n' "${DDC_GET_VALUE:-40}" fi ;; set) if [[ -n ${DDC_SET_DELAY:-} ]]; then if ! mkdir "$OSD_DDC_PROBE" 2>/dev/null; then printf 'ddc-overlap\n' >>"$OSD_TEST_LOG" fi sleep "$DDC_SET_DELAY" rmdir "$OSD_DDC_PROBE" 2>/dev/null || true fi printf '%s\n' "${3:-0}" >"$OSD_DDC_STATE" ;; *) exit 2 ;; esac SH cat >"$scratch/bin/hyprctl" <<'SH' #!/bin/bash printf 'hyprctl' >>"$OSD_TEST_LOG" printf ' <%s>' "$@" >>"$OSD_TEST_LOG" printf '\n' >>"$OSD_TEST_LOG" printf '[{"name":"%s","focused":true}]\n' "${FOCUSED_MONITOR:-DP-2}" SH cat >"$scratch/bin/notify-send" <<'SH' #!/bin/bash printf 'notify-send' >>"$OSD_TEST_LOG" printf ' <%s>' "$@" >>"$OSD_TEST_LOG" printf '\n' >>"$OSD_TEST_LOG" SH cat >"$scratch/bin/playerctl" <<'SH' #!/bin/bash printf 'playerctl' >>"$OSD_TEST_LOG" printf ' <%s>' "$@" >>"$OSD_TEST_LOG" printf '\n' >>"$OSD_TEST_LOG" if [[ $1 == "metadata" ]]; then printf '%s\n' "${PLAYER_OUTPUT:-Horizon — Tycho}" elif [[ $1 == "status" ]]; then printf '%s\n' "${PLAYER_STATUS:-Playing}" fi SH cat >"$scratch/bin/pw-play" <<'SH' #!/bin/bash printf 'pw-play' >>"$OSD_TEST_LOG" printf ' <%s>' "$@" >>"$OSD_TEST_LOG" printf '\n' >>"$OSD_TEST_LOG" SH cat >"$scratch/bin/qs" <<'SH' #!/bin/bash printf 'qs' >>"$OSD_TEST_LOG" printf ' <%s>' "$@" >>"$OSD_TEST_LOG" printf '\n' >>"$OSD_TEST_LOG" [[ ${OSD_TEST_FAIL_QS:-false} != true ]] SH chmod +x "$scratch/bin/"* # The blip and the over-amplification limit are both read out of Panama's own # settings.json, so every run below gets its own config root. Without this the # helper would read Gabriel's real preferences and the assertions would pass or # fail depending on which switches he happens to have on. blip_sound="$scratch/blip.oga" : >"$blip_sound" mkdir -p "$scratch/config-default" # Write a settings.json for one run. No arguments means no file at all, which # is what a fresh install looks like: the helper must fall back to the schema # defaults rather than treating an absent file as an error. settings_root() { local name="$1" body="${2:-}" root="$scratch/config-$name" rm -rf "$root" mkdir -p "$root/panama" [[ -n "$body" ]] && printf '%s\n' "$body" >"$root/panama/settings.json" printf '%s' "$root" } run_helper() { local runtime="${OSD_RUNTIME_DIR:-$scratch/runtime-default}" mkdir -p "$runtime" PATH="$scratch/bin:$PATH" OSD_TEST_LOG="$log" \ XDG_CONFIG_HOME="${OSD_CONFIG_HOME:-$scratch/config-default}" \ PANAMA_OSD_BLIP_SOUND="${OSD_BLIP_SOUND:-$blip_sound}" \ OSD_TEST_FAIL_QS="${OSD_TEST_FAIL_QS:-false}" \ PANAMA_OSD_STRICT="${PANAMA_OSD_STRICT:-false}" \ PANAMA_OSD_BRIGHTNESS_HELPER="$scratch/bin/panama-brightness" \ PANAMA_OSD_RUNTIME_DIR="$runtime" \ OSD_DDC_STATE="$runtime/ddc-state" \ OSD_DDC_PROBE="$runtime/ddc-probe" \ BACKLIGHT_AVAILABLE="${BACKLIGHT_AVAILABLE:-true}" \ DDC_LIST_JSON="${DDC_LIST_JSON:-}" \ DDC_GET_VALUE="${DDC_GET_VALUE:-40}" \ DDC_FAIL_GET_BUS="${DDC_FAIL_GET_BUS:-}" \ DDC_SET_DELAY="${DDC_SET_DELAY:-}" \ FOCUSED_MONITOR="${FOCUSED_MONITOR:-DP-2}" \ "$helper" "$@" } assert_line() { local expected="$1" grep -Fqx -- "$expected" "$log" || { printf 'osd helper contract: missing call\n%s\nactual:\n' "$expected" >&2 cat "$log" >&2 exit 1 } } # The blip is backgrounded and never waited on -- deliberately, so it cannot sit # between the key press and the OSD. Which means the line may land after the # helper has already exited, and asserting it needs a moment rather than an # instant. Waiting here also keeps a slow blip from bleeding into the next # case's freshly truncated log. await_line() { local expected="$1" for _ in $(seq 1 60); do grep -Fqx -- "$expected" "$log" && return 0 sleep 0.05 done printf 'osd helper contract: missing call\n%s\nactual:\n' "$expected" >&2 cat "$log" >&2 exit 1 } refute_line() { local unexpected="$1" why="$2" if grep -Fq -- "$unexpected" "$log"; then printf 'osd helper contract: %s\nunexpected: %s\nactual:\n' "$why" "$unexpected" >&2 cat "$log" >&2 exit 1 fi } : >"$log" run_helper volume up 6 assert_line 'wpctl <-l> <1> <@DEFAULT_AUDIO_SINK@> <6%+>' assert_line 'wpctl <@DEFAULT_AUDIO_SINK@>' assert_line 'qs <58> <100> <58%>' : >"$log" WPCTL_OUTPUT='Volume: 0.58 [MUTED]' run_helper volume toggle assert_line 'wpctl <@DEFAULT_AUDIO_SINK@> ' assert_line 'qs <58> <100> ' : >"$log" WPCTL_OUTPUT='Volume: 0.72 [MUTED]' run_helper microphone toggle assert_line 'wpctl <@DEFAULT_AUDIO_SOURCE@> ' assert_line 'qs <72> <100> ' # ── Over-amplification: the ceiling is a setting, not a constant ───────────── # `wpctl set-volume` clamps to 1.0 unless told otherwise, and it clamps the # *result* -- so the limit has to be on the down step too. Without it, coming # down from 130% would snap to 100% instead of stepping to 124%, which reads as # the slider jumping on its own. : >"$log" OSD_CONFIG_HOME="$(settings_root overamp '{"overAmplification": true}')" \ run_helper volume up 6 assert_line 'wpctl <-l> <1.5> <@DEFAULT_AUDIO_SINK@> <6%+>' : >"$log" OSD_CONFIG_HOME="$(settings_root overamp '{"overAmplification": true}')" \ run_helper volume down 6 assert_line 'wpctl <-l> <1.5> <@DEFAULT_AUDIO_SINK@> <6%->' # The microphone is not part of the bargain: no amount of gain past 100% makes # a capture device better, and the Sound page's input slider stays 0-100 to # match. : >"$log" OSD_CONFIG_HOME="$(settings_root overamp '{"overAmplification": true}')" \ run_helper microphone up 6 assert_line 'wpctl <-l> <1> <@DEFAULT_AUDIO_SOURCE@> <6%+>' refute_line '<-l> <1.5> <@DEFAULT_AUDIO_SOURCE@>' \ 'over-amplification leaked onto the microphone' : >"$log" OSD_CONFIG_HOME="$(settings_root plain '{"overAmplification": false}')" \ run_helper volume up 6 assert_line 'wpctl <-l> <1> <@DEFAULT_AUDIO_SINK@> <6%+>' # A settings file that predates the key, and a settings file that is not JSON # at all, both mean "off" rather than "no volume keys work today". : >"$log" OSD_CONFIG_HOME="$(settings_root nokey '{"barBackdrop": true}')" run_helper volume up 6 assert_line 'wpctl <-l> <1> <@DEFAULT_AUDIO_SINK@> <6%+>' : >"$log" OSD_CONFIG_HOME="$(settings_root broken '{not json')" run_helper volume up 6 assert_line 'wpctl <-l> <1> <@DEFAULT_AUDIO_SINK@> <6%+>' assert_line 'qs <58> <100> <58%>' # ── The volume blip ───────────────────────────────────────────────────────── # Default on, per the schema, so a settings.json that has never been written # still clicks. Fire-and-forget: the blip must never gate the OSD. : >"$log" run_helper volume up 6 await_line "pw-play <$blip_sound>" assert_line 'qs <58> <100> <58%>' : >"$log" run_helper volume down 6 await_line "pw-play <$blip_sound>" : >"$log" WPCTL_OUTPUT='Volume: 0.58' run_helper volume toggle await_line "pw-play <$blip_sound>" : >"$log" OSD_CONFIG_HOME="$(settings_root silent '{"volumeChangeBlip": false}')" \ run_helper volume up 6 refute_line 'pw-play' 'the blip played with volumeChangeBlip off' assert_line 'qs <58> <100> <58%>' # Brightness and the microphone are not volume changes. : >"$log" run_helper brightness up 5 refute_line 'pw-play' 'a brightness step played the volume blip' : >"$log" WPCTL_OUTPUT='Volume: 0.72' run_helper microphone up 6 refute_line 'pw-play' 'a microphone step played the volume blip' # A distribution without the freedesktop sound theme has no file to play. That # is a silent desktop, not a broken volume key. : >"$log" OSD_BLIP_SOUND="$scratch/nothing-here.oga" run_helper volume up 6 refute_line 'pw-play' 'the blip ran against a file that does not exist' assert_line 'wpctl <-l> <1> <@DEFAULT_AUDIO_SINK@> <6%+>' assert_line 'qs <58> <100> <58%>' : >"$log" run_helper brightness up 5 assert_line 'brightnessctl <-m> <-c> ' assert_line 'brightnessctl <-e4> <-n2> <-c> <5%+>' assert_line 'qs <50> <100> <50%>' if grep -Fq 'panama-brightness' "$log"; then printf 'osd helper contract: DDC fallback ran despite a native backlight\n' >&2 exit 1 fi : >"$log" OSD_RUNTIME_DIR="$scratch/runtime-ddc" \ BACKLIGHT_AVAILABLE=false \ DDC_LIST_JSON='{"displays":[{"bus":3,"connector":"HDMI-A-1","value":35},{"bus":5,"connector":"DP-2","value":40}],"error":""}' \ run_helper brightness up 5 assert_line 'hyprctl <-j> ' assert_line 'panama-brightness ' assert_line 'panama-brightness <5>' assert_line 'panama-brightness <5> <45>' assert_line 'qs <45> <100> <45%>' # A cached bus avoids the expensive display scan on subsequent key presses. : >"$log" OSD_RUNTIME_DIR="$scratch/runtime-ddc" \ BACKLIGHT_AVAILABLE=false \ DDC_LIST_JSON='{"displays":[{"bus":3,"connector":"HDMI-A-1","value":35},{"bus":5,"connector":"DP-2","value":45}],"error":""}' \ run_helper brightness down 5 assert_line 'panama-brightness <5>' assert_line 'panama-brightness <5> <40>' assert_line 'qs <40> <100> <40%>' if grep -Fq 'panama-brightness ' "$log"; then printf 'osd helper contract: cached DDC bus triggered another display scan\n' >&2 exit 1 fi # A disconnected cached monitor is discarded and rediscovered once. mkdir -p "$scratch/runtime-ddc-stale" printf '9\tDP-9\n' >"$scratch/runtime-ddc-stale/brightness-bus" : >"$log" OSD_RUNTIME_DIR="$scratch/runtime-ddc-stale" \ BACKLIGHT_AVAILABLE=false \ DDC_FAIL_GET_BUS=9 \ DDC_LIST_JSON='{"displays":[{"bus":5,"connector":"DP-2","value":40}],"error":""}' \ run_helper brightness up 5 assert_line 'panama-brightness <9>' assert_line 'panama-brightness ' assert_line 'panama-brightness <5>' assert_line 'panama-brightness <5> <45>' # If the focused output is not DDC-capable, use the first discovered display. : >"$log" OSD_RUNTIME_DIR="$scratch/runtime-ddc-first" \ BACKLIGHT_AVAILABLE=false \ FOCUSED_MONITOR='eDP-1' \ DDC_GET_VALUE=35 \ DDC_LIST_JSON='{"displays":[{"bus":3,"connector":"HDMI-A-1","value":35},{"bus":5,"connector":"DP-2","value":40}],"error":""}' \ run_helper brightness down 10 assert_line 'panama-brightness <3>' assert_line 'panama-brightness <3> <25>' assert_line 'qs <25> <100> <25%>' # Permission and discovery errors must be visible, never masquerade as 0%. : >"$log" OSD_RUNTIME_DIR="$scratch/runtime-ddc-error" \ BACKLIGHT_AVAILABLE=false \ DDC_LIST_JSON='{"displays":[],"error":"Run sudo udevadm control --reload-rules && sudo udevadm trigger --subsystem-match=i2c-dev --subsystem-match=drm"}' \ run_helper brightness up 5 assert_line 'qs ' assert_line 'notify-send <--app-name=Panama> <--icon=display-brightness-symbolic> ' if grep -Fq 'osd> ' "$log"; then printf 'osd helper contract: unavailable brightness rendered a false percentage\n' >&2 exit 1 fi # Separate key-repeat processes must not overlap their DDC transactions. : >"$log" OSD_RUNTIME_DIR="$scratch/runtime-ddc-lock" \ BACKLIGHT_AVAILABLE=false \ DDC_SET_DELAY=0.15 \ DDC_LIST_JSON='{"displays":[{"bus":5,"connector":"DP-2","value":40}],"error":""}' \ run_helper brightness up 5 & first_pid=$! OSD_RUNTIME_DIR="$scratch/runtime-ddc-lock" \ BACKLIGHT_AVAILABLE=false \ DDC_SET_DELAY=0.15 \ DDC_LIST_JSON='{"displays":[{"bus":5,"connector":"DP-2","value":40}],"error":""}' \ run_helper brightness up 5 & second_pid=$! wait "$first_pid" wait "$second_pid" if grep -Fqx 'ddc-overlap' "$log"; then printf 'osd helper contract: concurrent DDC transactions overlapped\n' >&2 exit 1 fi if [[ $(<"$scratch/runtime-ddc-lock/ddc-state") != 50 ]]; then printf 'osd helper contract: serialized key repeats did not both apply\n' >&2 exit 1 fi : >"$log" run_helper media next assert_line 'playerctl ' assert_line 'qs ' : >"$log" run_helper message notifications-disabled-symbolic 'Do Not Disturb On' assert_line 'qs ' : >"$log" if OSD_TEST_FAIL_QS=true PANAMA_OSD_STRICT=1 run_helper message dialog-error-symbolic 'Strict failure'; then printf 'osd helper contract: strict delivery swallowed an IPC failure\n' >&2 exit 1 fi : >"$log" OSD_TEST_FAIL_QS=true run_helper message dialog-information-symbolic 'Best effort' printf 'osd helper contract: PASS\n'