Build complete PipeWire sound settings
This commit is contained in:
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# The Sound page is a first-class PipeWire control surface, not a launcher for
|
||||
# another settings app. This contract keeps the real device plumbing shared
|
||||
# with Quick Settings and verifies the controls that must remain available.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
sound_page="$repo_dir/config/dot/quickshell/modules/settings/SoundPage.qml"
|
||||
device_list="$repo_dir/config/dot/quickshell/modules/settings/SoundDeviceList.qml"
|
||||
device_row="$repo_dir/config/dot/quickshell/modules/settings/SoundDeviceRow.qml"
|
||||
balance="$repo_dir/config/dot/quickshell/modules/settings/AudioBalance.qml"
|
||||
audio_devices="$repo_dir/config/dot/quickshell/services/AudioDevices.qml"
|
||||
sound_feedback="$repo_dir/config/dot/quickshell/services/SoundFeedback.qml"
|
||||
quick_devices="$repo_dir/config/dot/quickshell/modules/quicksettings/AudioDeviceList.qml"
|
||||
harness="$repo_dir/config/dot/quickshell/sound-page-harness.qml"
|
||||
config_home="$(mktemp -d /tmp/panama-sound-config.XXXXXX)"
|
||||
state_home="$(mktemp -d /tmp/panama-sound-state.XXXXXX)"
|
||||
|
||||
fail() {
|
||||
printf 'sound page contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
qs_for_harness() {
|
||||
XDG_CONFIG_HOME="$config_home" XDG_STATE_HOME="$state_home" qs -p "$harness" "$@"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
qs_for_harness kill >/dev/null 2>&1 || true
|
||||
rm -rf "$config_home" "$state_home"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
for file in "$sound_page" "$device_list" "$device_row" "$balance" "$audio_devices" "$sound_feedback" "$quick_devices"; do
|
||||
[[ -f "$file" ]] || fail "missing ${file#"$repo_dir/"}"
|
||||
done
|
||||
|
||||
# One shared source of truth owns device discovery and default selection.
|
||||
rg -Fq 'pragma Singleton' "$audio_devices" || fail 'audio device service is not a singleton'
|
||||
rg -Fq 'Singleton {' "$audio_devices" || fail 'audio device service has no singleton root'
|
||||
rg -Fq 'PwNodeType.AudioSource' "$audio_devices" || fail 'audio sources are not filtered by PipeWire type'
|
||||
rg -Fq 'Pipewire.preferredDefaultAudioSink = node;' "$audio_devices" || fail 'output selection does not reach PipeWire'
|
||||
rg -Fq 'Pipewire.preferredDefaultAudioSource = node;' "$audio_devices" || fail 'input selection does not reach PipeWire'
|
||||
rg -Fq 'AudioDevices.outputs' "$quick_devices" || fail 'Quick Settings does not share output discovery'
|
||||
rg -Fq 'AudioDevices.inputs' "$quick_devices" || fail 'Quick Settings does not share input discovery'
|
||||
rg -Fq 'AudioDevices.select(root.output, node)' "$quick_devices" || fail 'Quick Settings does not share device selection'
|
||||
|
||||
# Every hardware row binds its node before reading/writing audio state.
|
||||
rg -Fq 'PwObjectTracker {' "$device_row" || fail 'device rows do not bind PipeWire objects'
|
||||
rg -Fq 'root.node.audio.muted = !root.node.audio.muted;' "$device_row" || fail 'device mute is not writable'
|
||||
rg -Fq 'root.node.audio.volume = value;' "$device_row" || fail 'per-device volume is not writable'
|
||||
rg -Fq 'PwNodePeakMonitor {' "$device_row" || fail 'input rows have no level monitor'
|
||||
rg -Fq 'enabled: !root.output && root.selected' "$device_row" || fail 'input monitoring is not scoped to the selected source'
|
||||
|
||||
# Stereo hardware gets a real channel balance control.
|
||||
rg -Fq 'PwAudioChannel.FrontLeft' "$balance" || fail 'balance does not identify the left channel'
|
||||
rg -Fq 'PwAudioChannel.FrontRight' "$balance" || fail 'balance does not identify the right channel'
|
||||
rg -Fq 'root.node.audio.volumes = next;' "$balance" || fail 'balance does not write per-channel volume'
|
||||
|
||||
[[ "$(rg -c 'SoundDeviceList \{' "$sound_page")" -eq 2 ]] || fail 'Sound page does not expose output and input device lists'
|
||||
rg -Fq 'AudioBalance {' "$sound_page" || fail 'Sound page has no output balance control'
|
||||
rg -Fq 'SystemSettings.openGnomePanel("sound")' "$sound_page" || fail 'advanced GNOME Sound handoff was removed'
|
||||
rg -Fq 'SoundFeedback.setEventSounds(checked)' "$sound_page" || fail 'event sounds are not controllable'
|
||||
rg -Fq 'SoundFeedback.setInputFeedback(checked)' "$sound_page" || fail 'input feedback sounds are not controllable'
|
||||
rg -Fq 'org.gnome.desktop.sound' "$sound_feedback" || fail 'sound feedback does not use the desktop sound schema'
|
||||
|
||||
# Native bindings are the supported path. Shelling out would race the service
|
||||
# that owns these same objects and regress Quick Settings coherence.
|
||||
if rg -q '\b(Process|pactl|wpctl)\b' "$audio_devices" "$device_list" "$device_row" "$balance"; then
|
||||
fail 'Sound controls bypass the Quickshell PipeWire service'
|
||||
fi
|
||||
|
||||
printf 'sound page static contract: PASS\n'
|
||||
|
||||
# Instantiate the complete page against the real, read-only PipeWire graph.
|
||||
# Merely constructing these controls must never change a default or volume.
|
||||
qs_for_harness --daemonize >/dev/null
|
||||
for _ in $(seq 1 60); do
|
||||
qs_for_harness ipc show 2>/dev/null | rg -q '^target sound-page-test$' && break
|
||||
sleep 0.1
|
||||
done
|
||||
qs_for_harness ipc show 2>/dev/null | rg -q '^target sound-page-test$' \
|
||||
|| fail 'Sound page harness did not start'
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
status="$(qs_for_harness ipc call sound-page-test status)"
|
||||
[[ "$(jq -r .ready <<<"$status")" == "true" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.ready == true and .outputs > 0 and .inputs > 0
|
||||
and (.defaultOutput | length > 0) and (.defaultInput | length > 0)' \
|
||||
<<<"$status" >/dev/null \
|
||||
|| fail "real PipeWire graph was not represented: $status"
|
||||
|
||||
trap - EXIT
|
||||
cleanup
|
||||
printf 'sound page runtime: PASS (%s outputs, %s inputs)\n' \
|
||||
"$(jq -r .outputs <<<"$status")" "$(jq -r .inputs <<<"$status")"
|
||||
Reference in New Issue
Block a user