pragma Singleton // Shared PipeWire device discovery and default selection. Quick Settings and // Panama Settings intentionally use this same boundary so they cannot disagree // about what counts as an input or which node should become the default. import Quickshell import Quickshell.Services.Pipewire import QtQuick import "AudioStreams.js" as AudioStreams Singleton { id: root readonly property var outputs: Pipewire.nodes.values.filter(node => !node.isStream && node.isSink) readonly property var inputs: Pipewire.nodes.values.filter(node => !node.isStream && (node.type & PwNodeType.AudioSource) === PwNodeType.AudioSource) readonly property var playbackStreams: Pipewire.nodes.values.filter(node => node.ready && node.audio && (node.type & PwNodeType.AudioOutStream) === PwNodeType.AudioOutStream) readonly property var applications: AudioStreams.group( root.playbackStreams, PwNodeType.AudioOutStream) // The capture side of the same story: which applications currently hold the // microphone open. Same grouping rules as playback, so an application that // records on three streams reads as one entry with one mute. readonly property var captureStreams: Pipewire.nodes.values.filter(node => node.ready && node.audio && (node.type & PwNodeType.AudioInStream) === PwNodeType.AudioInStream) readonly property var captureApplications: AudioStreams.group( root.captureStreams, PwNodeType.AudioInStream) function nodes(output: bool): var { return output ? root.outputs : root.inputs; } function current(output: bool): var { return output ? Pipewire.defaultAudioSink : Pipewire.defaultAudioSource; } function select(output: bool, node: var): void { if (!node) return; if (output) Pipewire.preferredDefaultAudioSink = node; else Pipewire.preferredDefaultAudioSource = node; } function label(node: var): string { if (!node) return "Unknown device"; return node.description || node.nickname || node.name || "Unknown device"; } function applicationVolume(application: var): real { return AudioStreams.volume(application); } function applicationMuted(application: var): bool { return AudioStreams.muted(application); } // `max` is the clamp ceiling; omitting it means 1, and anything that is not // a positive number is treated as omitted. Callers that honor the // over-amplification setting pass 1.5. function setApplicationVolume(application: var, value: real, max: real): bool { return AudioStreams.setVolume(application, value, max); } function setApplicationMuted(application: var, muted: bool): bool { return AudioStreams.setMuted(application, muted); } }