forked from quickshell/quickshell
		
	services/pipewire: update volume props from device for device nodes
Yet another device node edge case. In addition to only writing via a pw_device when present, now we only read from one as well. This fixes missing state changes not conveyed by the pw_node. Fixes #35
This commit is contained in:
		
							parent
							
								
									579d589290
								
							
						
					
					
						commit
						95d0af8113
					
				
					 5 changed files with 61 additions and 19 deletions
				
			
		| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
#include <array>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include <pipewire/device.h>
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +21,7 @@
 | 
			
		|||
#include <spa/utils/type.h>
 | 
			
		||||
 | 
			
		||||
#include "core.hpp"
 | 
			
		||||
#include "node.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::service::pipewire {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -104,30 +106,43 @@ void PwDevice::addDeviceIndexPairs(const spa_pod* param) {
 | 
			
		|||
	qint32 device = 0;
 | 
			
		||||
	qint32 index = 0;
 | 
			
		||||
 | 
			
		||||
	spa_pod* props = nullptr;
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	quint32 id = SPA_PARAM_Route;
 | 
			
		||||
	spa_pod_parser_get_object(
 | 
			
		||||
			&parser, SPA_TYPE_OBJECT_ParamRoute, &id,
 | 
			
		||||
			SPA_PARAM_ROUTE_device, SPA_POD_Int(&device),
 | 
			
		||||
			SPA_PARAM_ROUTE_index, SPA_POD_Int(&index)
 | 
			
		||||
			SPA_PARAM_ROUTE_index, SPA_POD_Int(&index),
 | 
			
		||||
			SPA_PARAM_ROUTE_props, SPA_POD_PodObject(&props)
 | 
			
		||||
	);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
	this->stagingIndexes.insert(device, index);
 | 
			
		||||
	auto volumeProps = PwVolumeProps::parseSpaPod(props);
 | 
			
		||||
 | 
			
		||||
	this->stagingIndexes.append(device);
 | 
			
		||||
	// Insert into the main map as well, staging's purpose is to remove old entries.
 | 
			
		||||
	this->routeDeviceIndexes.insert(device, index);
 | 
			
		||||
 | 
			
		||||
	qCDebug(logDevice).nospace() << "Registered device/index pair for " << this
 | 
			
		||||
	                             << ": [device: " << device << ", index: " << index << ']';
 | 
			
		||||
 | 
			
		||||
	emit this->routeVolumesChanged(device, volumeProps);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDevice::polled() {
 | 
			
		||||
	// It is far more likely that the list content has not come in yet than it having no entries,
 | 
			
		||||
	// and there isn't a way to check in the case that there *aren't* actually any entries.
 | 
			
		||||
	if (!this->stagingIndexes.isEmpty() && this->stagingIndexes != this->routeDeviceIndexes) {
 | 
			
		||||
		this->routeDeviceIndexes = this->stagingIndexes;
 | 
			
		||||
		qCDebug(logDevice) << "Updated device/index pair list for" << this << "to"
 | 
			
		||||
		                   << this->routeDeviceIndexes;
 | 
			
		||||
	if (!this->stagingIndexes.isEmpty()) {
 | 
			
		||||
		this->routeDeviceIndexes.removeIf([&](const std::pair<qint32, qint32>& entry) {
 | 
			
		||||
			if (!stagingIndexes.contains(entry.first)) {
 | 
			
		||||
				qCDebug(logDevice).nospace() << "Removed device/index pair [device: " << entry.first
 | 
			
		||||
				                             << ", index: " << entry.second << "] for" << this;
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,9 +8,11 @@
 | 
			
		|||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qhash.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
#include <spa/pod/builder.h>
 | 
			
		||||
 | 
			
		||||
#include "core.hpp"
 | 
			
		||||
#include "node.hpp"
 | 
			
		||||
#include "registry.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::service::pipewire {
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +34,7 @@ public:
 | 
			
		|||
 | 
			
		||||
signals:
 | 
			
		||||
	void deviceReady();
 | 
			
		||||
	void routeVolumesChanged(qint32 routeDevice, const PwVolumeProps& volumeProps);
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void polled();
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +46,7 @@ private:
 | 
			
		|||
	onParam(void* data, qint32 seq, quint32 id, quint32 index, quint32 next, const spa_pod* param);
 | 
			
		||||
 | 
			
		||||
	QHash<qint32, qint32> routeDeviceIndexes;
 | 
			
		||||
	QHash<qint32, qint32> stagingIndexes;
 | 
			
		||||
	QList<qint32> stagingIndexes;
 | 
			
		||||
	void addDeviceIndexPairs(const spa_pod* param);
 | 
			
		||||
 | 
			
		||||
	bool
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -255,6 +255,13 @@ void PwNode::onParam(
 | 
			
		|||
PwNodeBoundAudio::PwNodeBoundAudio(PwNode* node): node(node) {
 | 
			
		||||
	if (node->device) {
 | 
			
		||||
		QObject::connect(node->device, &PwDevice::deviceReady, this, &PwNodeBoundAudio::onDeviceReady);
 | 
			
		||||
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    node->device,
 | 
			
		||||
		    &PwDevice::routeVolumesChanged,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &PwNodeBoundAudio::onDeviceVolumesChanged
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -278,13 +285,17 @@ void PwNodeBoundAudio::onInfo(const pw_node_info* info) {
 | 
			
		|||
 | 
			
		||||
void PwNodeBoundAudio::onSpaParam(quint32 id, quint32 index, const spa_pod* param) {
 | 
			
		||||
	if (id == SPA_PARAM_Props && index == 0) {
 | 
			
		||||
		this->updateVolumeProps(param);
 | 
			
		||||
		if (this->node->device) {
 | 
			
		||||
			qCDebug(logNode) << "Skipping node volume props update for" << this->node
 | 
			
		||||
			                 << "in favor of device updates.";
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->updateVolumeProps(PwVolumeProps::parseSpaPod(param));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwNodeBoundAudio::updateVolumeProps(const spa_pod* param) {
 | 
			
		||||
	auto volumeProps = PwVolumeProps::parseSpaPod(param);
 | 
			
		||||
 | 
			
		||||
void PwNodeBoundAudio::updateVolumeProps(const PwVolumeProps& volumeProps) {
 | 
			
		||||
	if (volumeProps.volumes.size() != volumeProps.channels.size()) {
 | 
			
		||||
		qCWarning(logNode) << "Cannot update volume props of" << this->node
 | 
			
		||||
		                   << "- channelVolumes and channelMap are not the same size. Sizes:"
 | 
			
		||||
| 
						 | 
				
			
			@ -489,6 +500,18 @@ void PwNodeBoundAudio::onDeviceReady() {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwNodeBoundAudio::onDeviceVolumesChanged(
 | 
			
		||||
    qint32 routeDevice,
 | 
			
		||||
    const PwVolumeProps& volumeProps
 | 
			
		||||
) {
 | 
			
		||||
	if (this->node->device && this->node->routeDevice == routeDevice) {
 | 
			
		||||
		qCDebug(logNode) << "Got updated device volume props for" << this->node << "via"
 | 
			
		||||
		                 << this->node->device;
 | 
			
		||||
 | 
			
		||||
		this->updateVolumeProps(volumeProps);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
 | 
			
		||||
	auto props = PwVolumeProps();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -203,9 +203,10 @@ signals:
 | 
			
		|||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onDeviceReady();
 | 
			
		||||
	void onDeviceVolumesChanged(qint32 routeDevice, const PwVolumeProps& props);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void updateVolumeProps(const spa_pod* param);
 | 
			
		||||
	void updateVolumeProps(const PwVolumeProps& volumeProps);
 | 
			
		||||
 | 
			
		||||
	bool mMuted = false;
 | 
			
		||||
	QVector<PwAudioChannel::Enum> mChannels;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -219,22 +219,22 @@ class PwNodeAudioIface: public QObject {
 | 
			
		|||
	// clang-format off
 | 
			
		||||
	/// If the node is currently muted. Setting this property changes the mute state.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
 | 
			
		||||
	Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged);
 | 
			
		||||
	/// The average volume over all channels of the node.
 | 
			
		||||
	/// Setting this property modifies the volume of all channels proportionately.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
 | 
			
		||||
	Q_PROPERTY(float volume READ averageVolume WRITE setAverageVolume NOTIFY volumesChanged);
 | 
			
		||||
	/// The audio channels present on the node.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
 | 
			
		||||
	Q_PROPERTY(QVector<qs::service::pipewire::PwAudioChannel::Enum> channels READ channels NOTIFY channelsChanged);
 | 
			
		||||
	/// The volumes of each audio channel individually. Each entry corrosponds to
 | 
			
		||||
	/// the volume of the channel at the same index in @@channels. @@volumes and @@channels
 | 
			
		||||
	/// will always be the same length.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
 | 
			
		||||
	Q_PROPERTY(QVector<float> volumes READ volumes WRITE setVolumes NOTIFY volumesChanged);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	QML_NAMED_ELEMENT(PwNodeAudio);
 | 
			
		||||
| 
						 | 
				
			
			@ -300,7 +300,7 @@ class PwNodeIface: public PwObjectIface {
 | 
			
		|||
	/// - `media.title` - The title of the currently playing media.
 | 
			
		||||
	/// - `media.artist` - The artist of the currently playing media.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
 | 
			
		||||
	Q_PROPERTY(QVariantMap properties READ properties NOTIFY propertiesChanged);
 | 
			
		||||
	/// Extra information present only if the node sends or receives audio.
 | 
			
		||||
	///
 | 
			
		||||
| 
						 | 
				
			
			@ -357,7 +357,7 @@ class PwLinkIface: public PwObjectIface {
 | 
			
		|||
	Q_PROPERTY(qs::service::pipewire::PwNodeIface* source READ source CONSTANT);
 | 
			
		||||
	/// The current state of the link.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
 | 
			
		||||
	Q_PROPERTY(PwLinkState::Enum state READ state NOTIFY stateChanged);
 | 
			
		||||
	QML_NAMED_ELEMENT(PwLink);
 | 
			
		||||
	QML_UNCREATABLE("PwLinks cannot be created directly");
 | 
			
		||||
| 
						 | 
				
			
			@ -392,7 +392,7 @@ class PwLinkGroupIface
 | 
			
		|||
	Q_PROPERTY(qs::service::pipewire::PwNodeIface* source READ source CONSTANT);
 | 
			
		||||
	/// The current state of the link group.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
 | 
			
		||||
	/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
 | 
			
		||||
	Q_PROPERTY(qs::service::pipewire::PwLinkState::Enum state READ state NOTIFY stateChanged);
 | 
			
		||||
	QML_NAMED_ELEMENT(PwLinkGroup);
 | 
			
		||||
	QML_UNCREATABLE("PwLinkGroups cannot be created directly");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue