Build the Panama Hyprland desktop

This commit is contained in:
Gabriel Brown
2026-08-17 10:32:55 -04:00
parent 67033f2a31
commit 5248883e4b
190 changed files with 18554 additions and 34 deletions
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
fail() {
printf 'activity-indicator contract: %s\n' "$1" >&2
exit 1
}
cleanup() {
qs ipc call activity close >/dev/null 2>&1 || true
qs ipc call activity clear >/dev/null 2>&1 || true
qs ipc call status-events reset >/dev/null 2>&1 || true
}
trap cleanup EXIT
[[ -f "$repo_dir/config/dot/quickshell/modules/bar/ActivityIndicator.qml" ]] || fail 'bar indicator is missing'
[[ -f "$repo_dir/config/dot/quickshell/modules/bar/ActivityPanel.qml" ]] || fail 'activity detail panel is missing'
rg -q 'ActivityIndicator \{' "$repo_dir/config/dot/quickshell/modules/bar/Bar.qml" || fail 'bar does not instantiate the activity indicator'
! rg -q '^[[:space:]]*RecordingIndicator \{\}' "$repo_dir/config/dot/quickshell/shell.qml" || fail 'legacy recording popup is still instantiated'
qs ipc call activity fixture microphone-on >/dev/null
qs ipc call activity open >/dev/null
for _ in $(seq 1 20); do
if hyprctl layers | rg -q 'namespace: qs-popover-activity'; then
printf 'activity-indicator contract: PASS\n'
exit 0
fi
sleep 0.1
done
fail 'active privacy state did not map its detail panel'
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
fail() {
printf 'activity-state contract: %s\n' "$1" >&2
exit 1
}
activity() {
qs ipc call activity status | jq -r ".${1}"
}
event() {
qs ipc call status-events status | jq -r ".${1}"
}
cleanup() {
qs ipc call activity clear >/dev/null 2>&1 || true
qs ipc call status-events reset >/dev/null 2>&1 || true
}
trap cleanup EXIT
qs ipc show | rg -q '^target activity$' || fail 'activity IPC target is missing'
rg -q 'StatusEvents.publish' "$repo_dir/config/dot/quickshell/services/Capture.qml" || fail 'capture does not publish recording transitions'
rg -q 'status-events screenshot' "$repo_dir/config/dot/quickshell/services/Capture.qml" || fail 'successful screenshot path does not publish an event'
rg -q 'StatusEvents.publish' "$repo_dir/config/dot/quickshell/services/NightLight.qml" || fail 'Night Light does not publish transitions'
qs ipc call status-events reset >/dev/null
qs ipc call activity fixture microphone-on >/dev/null
[[ "$(activity microphoneActive)" == "true" ]] || fail 'microphone state did not become persistent'
[[ "$(event activeKey)" == "privacy-microphone" ]] || fail 'microphone transition did not publish Signal Glass event'
qs ipc call status-events dismiss >/dev/null
[[ "$(activity microphoneActive)" == "true" ]] || fail 'capsule dismissal cleared persistent privacy state'
qs ipc call activity fixture all-off >/dev/null
[[ "$(activity microphoneActive)" == "false" ]] || fail 'microphone state did not clear'
[[ "$(event activeKey)" == "privacy-microphone" ]] || fail 'microphone release did not publish a transition'
[[ "$(event title)" == "Microphone released" ]] || fail 'microphone release copy is incorrect'
trap - EXIT
cleanup
printf 'activity-state contract: PASS\n'
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
bluetooth="$repo_dir/config/dot/quickshell/modules/quicksettings/BluetoothList.qml"
rg -q 'const shouldDiscover = root\.active && root\.adapter\.enabled' "$bluetooth" || {
printf 'bluetooth discovery contract: desired state is not calculated idempotently\n' >&2
exit 1
}
rg -q 'shouldDiscover && !root\.adapter\.discovering' "$bluetooth" || {
printf 'bluetooth discovery contract: redundant BlueZ starts are not guarded\n' >&2
exit 1
}
rg -q '!shouldDiscover && root\.discoveryOwned && root\.adapter\.discovering' "$bluetooth" || {
printf 'bluetooth discovery contract: the picker may stop discovery it does not own\n' >&2
exit 1
}
! rg -q 'Component\.onCompleted: root\.syncDiscovery' "$bluetooth" || {
printf 'bluetooth discovery contract: inactive construction still writes BlueZ state\n' >&2
exit 1
}
rg -Uq 'onAdapterChanged:[^{\n]*\{[^}]*if \(root\.active\)' "$bluetooth" || {
printf 'bluetooth discovery contract: adapter changes are not gated by an open panel\n' >&2
exit 1
}
rg -Uq 'Component\.onDestruction:[^{\n]*\{[^}]*root\.discoveryOwned' "$bluetooth" || {
printf 'bluetooth discovery contract: owned discovery is not released on destruction\n' >&2
exit 1
}
printf 'bluetooth discovery contract: PASS\n'
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
events="$repo_dir/config/dot/quickshell/services/StatusEvents.qml"
focus="$repo_dir/config/dot/quickshell/services/FocusSession.qml"
devices="$repo_dir/config/dot/quickshell/services/DeviceEvents.qml"
privacy="$repo_dir/config/dot/quickshell/services/PrivacyState.qml"
fail() {
printf 'curated-events policy: %s\n' "$1" >&2
exit 1
}
rg -q 'action === "open-workspace"' "$events" || fail 'broker cannot route a retained workspace action'
rg -q 'actionId: "open-workspace"' "$focus" || fail 'focus completion does not retain an actionable workspace destination'
rg -q 'actionData: String\(finishedWorkspaceId\)' "$focus" || fail 'focus completion omits its workspace id'
rg -q 'id: discoverySettle' "$devices" || fail 'device discovery has no silent initialization window'
! rg -Uq 'Component\.onCompleted:[^{\n]*\{[^}]*outputInitialized = true' "$devices" || fail 'output monitoring announces initial discovery'
rg -Uq 'function clearFixture\(\): void \{[^}]*initialized = false' "$privacy" || fail 'leaving a fixture can emit a false privacy transition'
printf 'curated-events policy: PASS\n'
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'focus-session contract: %s\n' "$1" >&2
exit 1
}
read_prop() {
qs ipc call focus status | jq -r ".${1}"
}
qs ipc show | rg -q '^target focus$' || fail 'focus IPC target is missing'
# Keep this contract reversible even if an assertion fails halfway through.
cleanup() {
qs ipc call focus end >/dev/null 2>&1 || true
}
trap cleanup EXIT
qs ipc call focus start >/dev/null
[[ "$(read_prop active)" == "true" ]] || fail 'start did not activate a session'
[[ "$(read_prop paused)" == "false" ]] || fail 'new session started paused'
[[ "$(read_prop workspaceId)" =~ ^[1-9][0-9]*$ ]] || fail 'session did not bind a positive workspace'
qs ipc call focus dismiss >/dev/null
[[ "$(read_prop capsuleVisible)" == "false" ]] || fail 'dismiss did not hide the capsule'
qs ipc call focus pause >/dev/null
[[ "$(read_prop paused)" == "true" ]] || fail 'pause did not freeze the session'
qs ipc call focus reveal >/dev/null
[[ "$(read_prop capsuleVisible)" == "true" ]] || fail 'reveal did not show the capsule'
qs ipc call focus overview >/dev/null
sleep 0.2
hyprctl layers | rg -q 'namespace: qs-overview' || fail 'focus overview did not map Mission Control'
qs ipc call overview close >/dev/null
qs ipc call focus end >/dev/null
[[ "$(read_prop active)" == "false" ]] || fail 'end did not clear the session'
trap - EXIT
printf 'focus-session contract: PASS\n'
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'focus-session expiry: %s\n' "$1" >&2
exit 1
}
cleanup() {
qs ipc call focus end >/dev/null 2>&1 || true
}
trap cleanup EXIT
qs ipc call focus end >/dev/null 2>&1 || true
qs ipc call focus start >/dev/null
state_file=$(find "${XDG_STATE_HOME:-$HOME/.local/state}/quickshell/by-shell" -name focus-session.json -print -quit)
[[ -n "$state_file" ]] || fail 'persistent state file was not created'
expired=$(mktemp "${state_file}.expired.XXXXXX")
jq '.paused = false | .deadlineMs = 1' "$state_file" >"$expired"
chmod --reference="$state_file" "$expired"
mv "$expired" "$state_file"
qs kill >/dev/null
qs --daemonize >/dev/null
status=""
for _ in $(seq 1 60); do
status=$(qs ipc call focus status 2>/dev/null || true)
if jq -e '.active == false' <<<"$status" >/dev/null 2>&1; then
break
fi
sleep 0.2
done
jq -e '.active == false and .caffeine == false and .doNotDisturb == false' <<<"$status" >/dev/null || fail 'expired session was not completed and released'
trap - EXIT
printf 'focus-session expiry: PASS\n'
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'focus-session restart: %s\n' "$1" >&2
exit 1
}
cleanup() {
qs ipc call focus end >/dev/null 2>&1 || true
}
trap cleanup EXIT
qs ipc call focus end >/dev/null 2>&1 || true
qs ipc call focus start >/dev/null
qs ipc call focus pause >/dev/null
before=$(qs ipc call focus status)
qs kill >/dev/null
qs --daemonize >/dev/null
status=""
for _ in $(seq 1 60); do
status=$(qs ipc call focus status 2>/dev/null || true)
if jq -e '.active == true and .paused == true' <<<"$status" >/dev/null 2>&1; then
break
fi
sleep 0.2
done
jq -e '.active == true' <<<"$status" >/dev/null || fail 'active session was not restored'
jq -e '.paused == true' <<<"$status" >/dev/null || fail 'paused state was not restored'
[[ "$(jq -r .remaining <<<"$status")" == "$(jq -r .remaining <<<"$before")" ]] || fail 'paused remainder changed across restart'
jq -e '.doNotDisturb == true' <<<"$status" >/dev/null || fail 'Do Not Disturb was not reclaimed'
jq -e '.caffeine == true' <<<"$status" >/dev/null || fail 'Caffeine was not reclaimed'
cleanup
trap - EXIT
printf 'focus-session restart: PASS\n'
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
body="$repo_dir/config/dot/quickshell/modules/overview/OverviewBody.qml"
thumbnail="$repo_dir/config/dot/quickshell/modules/overview/WindowThumbnail.qml"
fail() {
printf 'overview-keyboard contract: %s\n' "$1" >&2
exit 1
}
rg -q 'Keys\.onLeftPressed' "$body" || fail 'Left does not navigate workspaces'
rg -q 'Keys\.onRightPressed' "$body" || fail 'Right does not navigate workspaces'
rg -q 'Keys\.onUpPressed' "$body" || fail 'Up does not navigate search results'
rg -q 'Keys\.onDownPressed' "$body" || fail 'Down does not navigate search results'
rg -q 'Keys\.onDeletePressed' "$body" || fail 'Delete cannot close the selected search result'
rg -q 'selectedResultIndex' "$body" || fail 'search results have no keyboard selection model'
rg -q 'property bool selected' "$thumbnail" || fail 'thumbnail cannot render keyboard selection'
printf 'overview-keyboard contract: PASS\n'
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'overview live actions: %s\n' "$1" >&2
exit 1
}
original_workspace="$(hyprctl -j activeworkspace | jq -r '.id')"
address=""
cleanup() {
qs ipc call overview close >/dev/null 2>&1 || true
if [[ -n "$address" ]] && hyprctl -j clients | jq -e --arg address "$address" '.[] | select(.address == $address)' >/dev/null; then
hyprctl dispatch "hl.dsp.window.close({ window = \"address:$address\" })" >/dev/null 2>&1 || true
fi
hyprctl dispatch "hl.dsp.focus({ workspace = $original_workspace })" >/dev/null 2>&1 || true
}
trap cleanup EXIT
kitty --class panama-overview-contract --title "Panama Overview Contract" sh -c 'exec sleep 300' >/dev/null 2>&1 &
for _ in $(seq 1 40); do
address="$(hyprctl -j clients | jq -r '.[] | select(.class == "panama-overview-contract") | .address' | head -1)"
[[ -n "$address" ]] && break
sleep 0.1
done
[[ -n "$address" ]] || fail 'disposable window did not appear'
target_workspace="$(hyprctl -j workspaces | jq -r --argjson current "$original_workspace" '[.[] | select(.id > 0 and .id != $current)][0].id // empty')"
if [[ -z "$target_workspace" ]]; then
target_workspace=$((original_workspace + 1))
fi
qs ipc call overview-actions move "$address" "$target_workspace" >/dev/null
sleep 0.25
[[ "$(hyprctl -j clients | jq -r --arg address "$address" '.[] | select(.address == $address) | .workspace.name')" == "$target_workspace" ]] \
|| fail 'exact-address workspace move failed'
qs ipc call overview-actions move "$address" special:scratch >/dev/null
sleep 0.25
[[ "$(hyprctl -j clients | jq -r --arg address "$address" '.[] | select(.address == $address) | .workspace.name')" == "special:scratch" ]] \
|| fail 'exact-address scratchpad move failed'
qs ipc call overview open >/dev/null
sleep 0.2
qs ipc call overview-actions restore "$address" >/dev/null
sleep 0.3
[[ "$(hyprctl -j clients | jq -r --arg address "$address" '.[] | select(.address == $address) | .workspace.id')" == "$original_workspace" ]] \
|| fail 'scratchpad restore did not return to the selected workspace'
qs ipc call overview-actions close "$address" >/dev/null
for _ in $(seq 1 30); do
if ! hyprctl -j clients | jq -e --arg address "$address" '.[] | select(.address == $address)' >/dev/null; then
address=""
trap - EXIT
cleanup
printf 'overview live actions: PASS\n'
exit 0
fi
sleep 0.1
done
fail 'exact-address close did not close the disposable window'
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
qs ipc call overview close >/dev/null 2>&1 || true
}
trap cleanup EXIT
qs ipc show | rg -A8 '^target overview$' | rg -q 'function search\(query: string\): void'
qs ipc call overview search ChatGPT >/dev/null
for _ in $(seq 1 20); do
if hyprctl layers | rg -q 'namespace: qs-overview'; then
printf 'overview-search contract: PASS\n'
exit 0
fi
sleep 0.1
done
printf 'overview-search contract: overview did not map\n' >&2
exit 1
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
body="$repo_dir/config/dot/quickshell/modules/overview/OverviewBody.qml"
thumbnail="$repo_dir/config/dot/quickshell/modules/overview/WindowThumbnail.qml"
workspace="$repo_dir/config/dot/quickshell/modules/overview/WorkspaceCard.qml"
fail() {
printf 'overview-window-actions contract: %s\n' "$1" >&2
exit 1
}
rg -q 'function addressOf\(toplevel: var\): string' "$body" || fail 'address normalization boundary is missing'
rg -q 'function closeToplevel\(toplevel: var\): void' "$body" || fail 'address-scoped close boundary is missing'
rg -q 'function moveToplevel\(toplevel: var, workspaceName: string\): void' "$body" || fail 'address-scoped move boundary is missing'
rg -q 'function restoreToplevel\(toplevel: var\): void' "$body" || fail 'scratchpad restore boundary is missing'
rg -q 'hl\.dsp\.window\.close\(\{ window = ' "$body" || fail 'close does not use the modern exact-window dispatcher'
rg -q 'hl\.dsp\.window\.move\(\{ workspace = ' "$body" || fail 'move does not use the modern exact-window dispatcher'
rg -q 'signal closeRequested' "$thumbnail" || fail 'thumbnail has no close intent'
rg -q 'Drag\.active:' "$thumbnail" || fail 'thumbnail has no drag source'
rg -q 'DropArea \{' "$workspace" || fail 'workspace card has no drop target'
qs ipc show | rg -q '^target overview-actions$' || fail 'live action contract surface is missing'
printf 'overview-window-actions contract: PASS\n'
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
shelf="$repo_dir/config/dot/quickshell/modules/overview/ScratchpadShelf.qml"
body="$repo_dir/config/dot/quickshell/modules/overview/OverviewBody.qml"
keys="$repo_dir/config/dot/hypr/keybinds.lua"
fail() {
printf 'scratchpad-shelf contract: %s\n' "$1" >&2
exit 1
}
[[ -f "$shelf" ]] || fail 'global scratchpad shelf is missing'
rg -q 'special:scratch' "$body" || fail 'overview does not discover the global scratch workspace'
rg -q 'ScratchpadShelf \{' "$body" || fail 'overview does not instantiate the shelf'
rg -q 'DropArea \{' "$shelf" || fail 'shelf is not a drag destination'
rg -q 'onRestoreRequested' "$body" || fail 'shelf cards cannot restore to the selected workspace'
rg -q 'SUPER.*X|mod .*\+ X' "$keys" || fail 'Super+X scratchpad toggle disappeared'
rg -q 'SHIFT \+ X' "$keys" || fail 'Super+Shift+X send-to-scratchpad binding disappeared'
qs ipc call overview open >/dev/null
for _ in $(seq 1 20); do
if hyprctl layers | rg -q 'namespace: qs-overview'; then
qs ipc call overview close >/dev/null
printf 'scratchpad-shelf contract: PASS\n'
exit 0
fi
sleep 0.1
done
fail 'overview did not map with the shelf present'
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'screen intelligence contract: %s\n' "$1" >&2
exit 1
}
fixture_dir="$(mktemp -d)"
cleanup() {
qs ipc call screen-intelligence close >/dev/null 2>&1 || true
qs ipc call capture close >/dev/null 2>&1 || true
rm -rf "$fixture_dir"
}
trap cleanup EXIT
qs ipc show | rg -q '^target screen-intelligence$' || fail 'screen-intelligence IPC target is missing'
status="$(qs ipc call screen-intelligence status)"
jq -e '.phase == "idle" and (.ocrReady | type == "boolean") and (.codeReady | type == "boolean")' <<<"$status" >/dev/null \
|| fail 'readiness status is malformed'
fixture="$fixture_dir/panama-live-ocr.png"
magick -size 1600x260 xc:white \
-gravity center -fill black -font DejaVu-Sans -pointsize 72 \
-annotate 0 'PANAMA LIVE OCR' "$fixture"
qs ipc call screen-intelligence analyzeFile "$fixture" >/dev/null
for _ in $(seq 1 100); do
status="$(qs ipc call screen-intelligence status)"
phase="$(jq -r .phase <<<"$status")"
[[ "$phase" == "ready" || "$phase" == "error" ]] && break
sleep 0.1
done
[[ "$(jq -r .phase <<<"$status")" == "ready" ]] \
|| fail "fixture recognition did not finish successfully: $(jq -r .error <<<"$status")"
[[ "$(jq -r .text <<<"$status" | tr '\n' ' ')" == *'PANAMA LIVE OCR'* ]] \
|| fail 'live service did not expose recognized fixture text'
qs ipc call screen-intelligence close >/dev/null
[[ "$(qs ipc call screen-intelligence status | jq -r .phase)" == "idle" ]] \
|| fail 'close did not reset recognition state'
qs ipc call screen-intelligence open >/dev/null
for _ in $(seq 1 40); do
capture_status="$(qs ipc call capture status 2>/dev/null || true)"
if jq -e '.open == true' <<<"$capture_status" >/dev/null 2>&1; then
break
fi
sleep 0.1
done
jq -e '.open == true and .mode == "selection" and .intelligenceMode == true' <<<"$capture_status" >/dev/null \
|| fail 'direct open did not select Selection + Read mode'
printf 'screen intelligence contract: PASS\n'
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'screen intelligence helper contract: %s\n' "$1" >&2
exit 1
}
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
helper="$project_root/config/dot/quickshell/scripts/screen-intelligence"
fixture_dir="$(mktemp -d)"
trap 'rm -rf "$fixture_dir"' EXIT
[[ -x "$helper" ]] || fail 'recognition helper is missing or not executable'
command -v magick >/dev/null || fail 'ImageMagick is required to build the OCR fixture'
probe="$($helper probe)"
jq -e '.tesseract == true and .zbar == true' <<<"$probe" >/dev/null \
|| fail 'Tesseract and ZBar are not ready'
fixture="$fixture_dir/panama-ocr.png"
magick -size 1600x260 xc:white \
-gravity center -fill black -font DejaVu-Sans -pointsize 72 \
-annotate 0 'PANAMA SCREEN INTELLIGENCE' "$fixture"
result="$($helper analyze-file "$fixture")"
jq -e '.ok == true' <<<"$result" >/dev/null || fail "recognition failed: $(jq -r .error <<<"$result")"
text="$(jq -r .text <<<"$result" | tr '\n' ' ' | tr -s ' ')"
[[ "$text" == *'PANAMA SCREEN INTELLIGENCE'* ]] \
|| fail "literal fixture text was not recognized: $text"
printf 'screen intelligence helper contract: PASS\n'
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'settings pages contract: %s\n' "$1" >&2
exit 1
}
cleanup() {
qs ipc call settings close >/dev/null 2>&1 || true
}
trap cleanup EXIT
pages=(home appearance displays connectivity desktop sound notifications screen-intelligence shortcuts services about)
for page in "${pages[@]}"; do
qs ipc call settings page "$page" >/dev/null
for _ in $(seq 1 20); do
[[ "$(qs ipc call settings status | jq -r .page)" == "$page" ]] && break
sleep 0.1
done
[[ "$(qs ipc call settings status | jq -r .page)" == "$page" ]] || fail "$page did not route"
hyprctl -j clients | jq -e '[.[] | select(.title == "Panama Settings")] | length == 1' >/dev/null \
|| fail "$page created a missing or duplicate Settings window"
done
qs ipc call settings page '__unsupported__' >/dev/null
[[ "$(qs ipc call settings status | jq -r .page)" == "home" ]] || fail 'unsupported page did not fall back to Home'
hyprctl -j binds | jq -e '.[] | select(.description == "Panama Settings" and .key == "I" and .modmask == 64)' >/dev/null \
|| fail 'Super+I is not registered as Panama Settings'
hyprctl -j binds | jq -e '.[] | select(.description == "Screen Intelligence" and .key == "S" and .modmask == 65)' >/dev/null \
|| fail 'Super+Shift+S is not registered as Screen Intelligence'
desktop_file="$HOME/.local/share/applications/panama-settings.desktop"
[[ -L "$desktop_file" || -f "$desktop_file" ]] || fail 'searchable desktop entry is not installed'
desktop-file-validate "$desktop_file" >/dev/null || fail 'desktop entry is invalid'
gnome_desktop_file="$HOME/.local/share/applications/gnome-settings-hyprland.desktop"
[[ -L "$gnome_desktop_file" || -f "$gnome_desktop_file" ]] || fail 'searchable GNOME Settings fallback is not installed'
desktop-file-validate "$gnome_desktop_file" >/dev/null || fail 'GNOME Settings fallback entry is invalid'
intelligence_desktop_file="$HOME/.local/share/applications/panama-screen-intelligence.desktop"
[[ -L "$intelligence_desktop_file" || -f "$intelligence_desktop_file" ]] || fail 'Screen Intelligence desktop entry is not installed'
desktop-file-validate "$intelligence_desktop_file" >/dev/null || fail 'Screen Intelligence desktop entry is invalid'
trap - EXIT
cleanup
printf 'settings pages contract: PASS\n'
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
harness="$repo_dir/config/dot/quickshell/settings-preferences-harness.qml"
state_home="$(mktemp -d /tmp/panama-settings-state.XXXXXX)"
fail() {
printf 'settings preferences contract: %s\n' "$1" >&2
exit 1
}
qs_for_harness() {
XDG_STATE_HOME="$state_home" qs -p "$harness" "$@"
}
cleanup() {
qs_for_harness kill >/dev/null 2>&1 || true
}
trap cleanup EXIT
start_harness() {
XDG_STATE_HOME="$state_home" qs -p "$harness" --daemonize >/dev/null
for _ in $(seq 1 40); do
if qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-pref-test$'; then
return
fi
sleep 0.1
done
fail 'test IPC target did not start'
}
stop_harness() {
qs_for_harness kill >/dev/null 2>&1 || true
for _ in $(seq 1 40); do
if ! qs_for_harness ipc show >/dev/null 2>&1; then
return
fi
sleep 0.1
done
fail 'test shell did not stop cleanly'
}
start_harness
qs_for_harness ipc call settings-pref-test applyFixture >/dev/null
expected='{"use24Hour":true,"showSeconds":false,"dockAutohide":false,"focusDurationMinutes":70,"autoHdr":false,"vrrPolicy":0,"directScanoutPolicy":0,"nightLightEnabled":true,"nightLightAutomatic":true,"nightLightTemperature":4100,"lastPage":"desktop"}'
before="$(qs_for_harness ipc call settings-pref-test status | jq -c 'del(.stateDir)')"
[[ "$before" == "$expected" ]] || fail "fixture did not apply: $before"
state_file=""
for _ in $(seq 1 40); do
state_file="$(find "$state_home" -name panama-settings.json -print -quit)"
if [[ -n "$state_file" ]] && jq -e '.lastPage == "desktop" and .focusDurationMinutes == 70' "$state_file" >/dev/null 2>&1; then
break
fi
sleep 0.1
done
[[ -n "$state_file" ]] || fail 'preferences file was not created'
jq -e '.use24Hour == true and .showSeconds == false and .dockAutohide == false and .autoHdr == false and .directScanoutPolicy == 0 and .nightLightEnabled == true and .nightLightAutomatic == true and .nightLightTemperature == 4100' "$state_file" >/dev/null \
|| fail 'preferences file did not contain the complete atomic update'
# The contract is specifically restart persistence, not merely live QML state.
stop_harness
start_harness
after=""
for _ in $(seq 1 40); do
after="$(qs_for_harness ipc call settings-pref-test status | jq -c 'del(.stateDir)')"
[[ "$after" == "$expected" ]] && break
sleep 0.1
done
[[ "$after" == "$expected" ]] || fail "values did not survive restart: $after"
trap - EXIT
cleanup
printf 'settings preferences contract: PASS\n'
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
harness="$repo_dir/config/dot/quickshell/settings-system-harness.qml"
fail() {
printf 'settings system contract: %s\n' "$1" >&2
exit 1
}
qs_for_harness() {
qs -p "$harness" "$@"
}
cleanup() {
qs_for_harness kill >/dev/null 2>&1 || true
}
trap cleanup EXIT
original_auto_hdr="$(hyprctl -j getoption render:cm_auto_hdr | jq -r .int)"
original_vrr="$(hyprctl -j getoption misc:vrr | jq -r .int)"
original_direct="$(hyprctl -j getoption render:direct_scanout | jq -r .int)"
qs_for_harness --daemonize >/dev/null
for _ in $(seq 1 40); do
if qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-system-test$'; then
break
fi
sleep 0.1
done
qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-system-test$' || fail 'test IPC target did not start'
qs_for_harness ipc call settings-system-test refresh >/dev/null
status='{}'
for _ in $(seq 1 40); do
status="$(qs_for_harness ipc call settings-system-test status | jq -c .)"
if jq -e '.monitorName != "" and .width > 0 and .height > 0 and .busy == false' <<<"$status" >/dev/null; then
break
fi
sleep 0.1
done
jq -e '.monitorName == "DP-2" and .width == 4500 and .height == 3000 and .scale == 1.5 and .format == "XRGB2101010"' <<<"$status" >/dev/null \
|| fail "live monitor data was not normalized: $status"
[[ "$(qs_for_harness ipc call settings-system-test panelAllowed '__definitely_not_a_panel__' | jq -r .)" == "false" ]] \
|| fail 'unsupported GNOME panel was accepted'
[[ "$(qs_for_harness ipc call settings-system-test panelAllowed sound | jq -r .)" == "true" ]] \
|| fail 'supported GNOME sound panel was rejected'
# Apply the exact current values: this exercises the real allow-listed command
# without changing the daily-driver compositor state.
qs_for_harness ipc call settings-system-test apply "$([[ "$original_auto_hdr" == 1 ]] && printf true || printf false)" "$original_vrr" "$original_direct" >/dev/null
sleep 0.3
[[ "$(hyprctl -j getoption render:cm_auto_hdr | jq -r .int)" == "$original_auto_hdr" ]] || fail 'Auto HDR changed unexpectedly'
[[ "$(hyprctl -j getoption misc:vrr | jq -r .int)" == "$original_vrr" ]] || fail 'VRR changed unexpectedly'
[[ "$(hyprctl -j getoption render:direct_scanout | jq -r .int)" == "$original_direct" ]] || fail 'direct scanout changed unexpectedly'
trap - EXIT
cleanup
printf 'settings system contract: PASS\n'
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'settings window contract: %s\n' "$1" >&2
exit 1
}
cleanup() {
qs ipc call settings close >/dev/null 2>&1 || true
}
trap cleanup EXIT
qs ipc show | rg -q '^target settings$' || fail 'settings IPC target is missing'
qs ipc call settings open >/dev/null
for _ in $(seq 1 40); do
if hyprctl -j clients | jq -e '[.[] | select(.title == "Panama Settings")] | length == 1' >/dev/null; then
break
fi
sleep 0.1
done
hyprctl -j clients | jq -e '[.[] | select(.title == "Panama Settings")] | length == 1' >/dev/null \
|| fail 'exactly one Settings window did not map'
hyprctl -j clients | jq -e '.[] | select(.title == "Panama Settings" and .floating == false)' >/dev/null \
|| fail 'Settings window is not tiled'
qs ipc call settings page displays >/dev/null
[[ "$(qs ipc call settings status | jq -r .page)" == "displays" ]] || fail 'Displays page did not route'
qs ipc call settings page desktop >/dev/null
[[ "$(qs ipc call settings status | jq -r .page)" == "desktop" ]] || fail 'Desktop page did not route'
address="$(hyprctl -j clients | jq -r '.[] | select(.title == "Panama Settings") | .address')"
hyprctl dispatch "hl.dsp.window.close({ window = \"address:$address\" })" >/dev/null
for _ in $(seq 1 40); do
[[ "$(qs ipc call settings status | jq -r .open)" == "false" ]] && break
sleep 0.1
done
[[ "$(qs ipc call settings status | jq -r .open)" == "false" ]] || fail 'compositor close did not synchronize shell state'
qs ipc call settings open >/dev/null
for _ in $(seq 1 40); do
hyprctl -j clients | jq -e '.[] | select(.title == "Panama Settings")' >/dev/null && break
sleep 0.1
done
qs ipc call settings close >/dev/null
for _ in $(seq 1 40); do
if ! hyprctl -j clients | jq -e '.[] | select(.title == "Panama Settings")' >/dev/null; then
trap - EXIT
printf 'settings window contract: PASS\n'
exit 0
fi
sleep 0.1
done
fail 'Settings window did not close'
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
shell_file="$repo_dir/config/dot/quickshell/shell.qml"
rules_file="$repo_dir/config/dot/hypr/rules.lua"
fail() {
printf 'signal-glass contract: %s\n' "$1" >&2
exit 1
}
cleanup() {
qs ipc call status-events reset >/dev/null 2>&1 || true
qs ipc call focus end >/dev/null 2>&1 || true
}
trap cleanup EXIT
[[ -f "$repo_dir/config/dot/quickshell/modules/signals/SignalGlass.qml" ]] || fail 'shared host is missing'
[[ -f "$repo_dir/config/dot/quickshell/modules/signals/EventCard.qml" ]] || fail 'event presentation is missing'
[[ -f "$repo_dir/config/dot/quickshell/modules/signals/FocusCard.qml" ]] || fail 'focus presentation is missing'
[[ "$(rg -c 'SignalGlass \{\}' "$shell_file")" == "1" ]] || fail 'shell does not instantiate exactly one shared host delegate'
! rg -q 'FocusCapsule \{\}' "$shell_file" || fail 'legacy focus-only host is still instantiated'
rg -q 'namespace = "\^qs-signal-glass\$"' "$rules_file" || fail 'Hyprland layer rules do not match the shared namespace'
qs ipc call status-events reset >/dev/null
qs ipc call status-events fixture critical >/dev/null
for _ in $(seq 1 20); do
if hyprctl layers | rg -q 'namespace: qs-signal-glass'; then
printf 'signal-glass contract: PASS\n'
exit 0
fi
sleep 0.1
done
fail 'event did not map the shared Signal Glass layer'
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'status-events contract: %s\n' "$1" >&2
exit 1
}
status() {
qs ipc call status-events status | jq -r ".${1}"
}
cleanup() {
qs ipc call status-events reset >/dev/null 2>&1 || true
if [[ "${restore_dnd:-false}" == "true" ]]; then
qs ipc call notifications dnd >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
qs ipc show | rg -q '^target status-events$' || fail 'IPC target is missing'
qs ipc call status-events reset >/dev/null
qs ipc call status-events fixture ambient >/dev/null
[[ "$(status activeKey)" == "device-output" ]] || fail 'ambient fixture did not become active'
[[ "$(status queueLength)" == "0" ]] || fail 'first event unexpectedly queued'
qs ipc call status-events fixture ambient-replacement >/dev/null
[[ "$(status activeKey)" == "device-output" ]] || fail 'equivalent event changed its stable key'
[[ "$(status title)" == "Studio Display" ]] || fail 'equivalent event did not replace active content'
[[ "$(status queueLength)" == "0" ]] || fail 'equivalent event stacked instead of replacing'
qs ipc call status-events fixture critical >/dev/null
[[ "$(status activeKey)" == "privacy-microphone" ]] || fail 'critical event did not preempt ambient event'
[[ "$(status priority)" == "90" ]] || fail 'critical event priority was not preserved'
[[ "$(status queueLength)" == "1" ]] || fail 'preempted event was not retained once'
qs ipc call status-events dismiss >/dev/null
[[ "$(status activeKey)" == "device-output" ]] || fail 'dismiss did not reveal the queued ambient event'
qs ipc call status-events reset >/dev/null
dnd_now="$(qs ipc call notifications dnd)"
if [[ "$dnd_now" == "true" ]]; then
restore_dnd=true
fi
qs ipc call status-events fixture ambient >/dev/null
[[ "$(status active)" == "false" ]] || fail 'DND did not suppress an ambient event'
qs ipc call status-events fixture critical >/dev/null
[[ "$(status activeKey)" == "privacy-microphone" ]] || fail 'DND suppressed a privacy event'
trap - EXIT
cleanup
printf 'status-events contract: PASS\n'