/*SPDX-FileCopyrightText: 2017 Kai Uwe Broulik <kde@privat.broulik.de>SPDX-License-Identifier: GPL-2.0-or-later*/pragma ComponentBehavior: Boundimport QtQuickimport org.kde.plasma.private.volume as PlasmaPaQtObject {id: pulseAudiosignal streamsChanged()readonly property /*PlasmaPa.GlobalConfig*/ QtObject globalConfig: PlasmaPa.GlobalConfig { }// It's a JS object so we can do key lookup and don't need to take care of filtering duplicates.property var pidMatches: new Set()// TODO Evict cache at some point, preferably if all instances of an application closed.function registerPidMatch(appName: string) {if (!hasPidMatch(appName)) {pidMatches.add(appName);// In case this match is new, notify that streams might have changed.// This way we also catch the case when the non-playing instance// shows up first.// Only notify if we changed to avoid infinite recursion.streamsChanged();}}function hasPidMatch(appName: string): bool {return pidMatches.has(appName);}function findStreams(key: string, value: var): /*[QtObject]*/ var {return findStreamsFn(stream => stream[key] === value);}function findStreamsFn(fn: var): var {const streams = [];for (let i = 0, count = instantiator.count; i < count; ++i) {const stream = instantiator.objectAt(i);if (fn(stream)) {streams.push(stream);}}return streams;}function streamsForAppId(appId: string): /*[QtObject]*/ var {return findStreams("portalAppId", appId);}function streamsForAppName(appName: string): /*[QtObject]*/ var {return findStreams("appName", appName);}function streamsForPid(pid: int): /*[QtObject]*/ var {// skip stream that has portalAppId// app using portal may have a sandbox pidconst streams = findStreamsFn(stream => stream.pid === pid && !stream.portalAppId);if (streams.length === 0) {for (let i = 0, length = instantiator.count; i < length; ++i) {const stream = instantiator.objectAt(i) as StreamDelegate;if (stream.parentPid === -1) {stream.parentPid = backend.parentPid(stream.pid);}if (stream.parentPid === pid) {streams.push(stream);}}}return streams;}// QtObject has no default property, hence adding the Instantiator to one explicitly.readonly property Instantiator instantiator: Instantiator {model: PlasmaPa.PulseObjectFilterModel {filters: [ { role: "VirtualStream", value: false } ]sourceModel: PlasmaPa.SinkInputModel {}}component StreamDelegate: QtObject {id: delegaterequired property var modelreadonly property int pid: model.Client?.properties["application.process.id"] ?? 0// Determined on demand.property int parentPid: -1readonly property string appName: model.Client?.properties["application.name"] ?? ""readonly property string portalAppId: model.Client?.properties["pipewire.access.portal.app_id"] ?? ""readonly property bool muted: model.Muted// whether there is nothing actually going on on that streamreadonly property bool corked: model.Corkedreadonly property int volume: model.Volumefunction mute(): void {model.Muted = true;}function unmute(): void {model.Muted = false;}}delegate: StreamDelegate { }onObjectAdded: (index, object) => pulseAudio.streamsChanged()onObjectRemoved: (index, object) => pulseAudio.streamsChanged()}readonly property int minimalVolume: PlasmaPa.PulseAudio.MinimalVolumereadonly property int normalVolume: PlasmaPa.PulseAudio.NormalVolume}