Files
Panama/config/dot/quickshell/sound-page-harness.qml

113 lines
3.3 KiB
QML

// Read-only contract harness for the Sound page. It instantiates every device
// row against the real PipeWire graph but exposes no mutating IPC methods.
//
// The two services that read the machine rather than the graph -- SoundDefaults
// and SoundCards -- are fed through their own fixture seams by the contract, so
// the ghost row and the profile card can be measured against canned state
// instead of against whatever hardware is plugged in today.
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
import QtQuick
import qs.services
import qs.modules.settings
ShellRoot {
id: root
QtObject {
id: fixtureAudio
property real volume: 0.42
property bool muted: false
}
QtObject {
id: fixtureNode
property QtObject audio: fixtureAudio
}
readonly property var fixtureApplication: ({
key: "fixture.player",
label: "Fixture Player",
icon: "audio-x-generic-symbolic",
nodes: [fixtureNode]
})
SoundPage {
width: 760
height: 900
}
// A device list of its own, so the ghost row can be read without reaching
// into the page's internals.
SoundDeviceList {
id: outputList
width: 620
visible: false
output: true
}
SoundCaptureRow {
id: captureProbe
width: 620
}
ApplicationMixer {
id: populatedMixer
width: 620
visible: false
applications: [root.fixtureApplication]
pipewireReady: true
}
ApplicationMixer {
id: emptyMixer
width: 620
visible: false
applications: []
pipewireReady: true
}
ApplicationMixer {
id: unavailableMixer
width: 620
visible: false
applications: []
pipewireReady: false
}
PwObjectTracker {
objects: AudioDevices.outputs.concat(AudioDevices.inputs)
}
IpcHandler {
target: "sound-page-test"
function status(): string {
return JSON.stringify({
ready: Pipewire.ready,
outputs: AudioDevices.outputs.length,
inputs: AudioDevices.inputs.length,
applications: AudioDevices.applications.length,
defaultOutput: AudioDevices.label(AudioDevices.current(true)),
defaultInput: AudioDevices.label(AudioDevices.current(false)),
populatedRows: populatedMixer.rowCount,
populatedStatus: populatedMixer.statusText,
emptyRows: emptyMixer.rowCount,
emptyStatus: emptyMixer.statusText,
unavailableRows: unavailableMixer.rowCount,
unavailableStatus: unavailableMixer.statusText,
captureApplications: AudioDevices.captureApplications.length,
captureTypesValid: AudioDevices.captureApplications.every(application =>
application.nodes.every(node =>
(node.type & PwNodeType.AudioInStream)
=== PwNodeType.AudioInStream)),
captureRowVisible: captureProbe.visible,
ghostVisible: !!outputList.absent,
ghostLabel: outputList.absent ? String(outputList.absent.label) : ""
});
}
}
}