forked from quickshell/quickshell
		
	service/pipewire: refactor defaults and metadata handling
This commit is contained in:
		
							parent
							
								
									7f9762be53
								
							
						
					
					
						commit
						f889f08901
					
				
					 11 changed files with 455 additions and 144 deletions
				
			
		| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtclasshelpermacros.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
// NOLINTBEGIN
 | 
			
		||||
#define DROP_EMIT(object, func)                                                                    \
 | 
			
		||||
| 
						 | 
				
			
			@ -211,3 +213,34 @@ public:
 | 
			
		|||
 | 
			
		||||
	GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <auto member, auto destroyedSlot, auto changedSignal>
 | 
			
		||||
class SimpleObjectHandleOps {
 | 
			
		||||
	using Traits = MemberPointerTraits<decltype(member)>;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	static bool setObject(Traits::Class* parent, Traits::Type value) {
 | 
			
		||||
		if (value == parent->*member) return false;
 | 
			
		||||
 | 
			
		||||
		if (parent->*member != nullptr) {
 | 
			
		||||
			QObject::disconnect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		parent->*member = value;
 | 
			
		||||
 | 
			
		||||
		if (value != nullptr) {
 | 
			
		||||
			QObject::connect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if constexpr (changedSignal != nullptr) {
 | 
			
		||||
			emit(parent->*changedSignal)();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <auto member, auto destroyedSlot, auto changedSignal = nullptr>
 | 
			
		||||
bool setSimpleObjectHandle(auto* parent, auto* value) {
 | 
			
		||||
	return SimpleObjectHandleOps<member, destroyedSlot, changedSignal>::setObject(parent, value);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ qt_add_library(quickshell-service-pipewire STATIC
 | 
			
		|||
	metadata.cpp
 | 
			
		||||
	link.cpp
 | 
			
		||||
	device.cpp
 | 
			
		||||
	defaults.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_qml_module(quickshell-service-pipewire
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "core.hpp"
 | 
			
		||||
#include "metadata.hpp"
 | 
			
		||||
#include "defaults.hpp"
 | 
			
		||||
#include "registry.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::service::pipewire {
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +13,7 @@ public:
 | 
			
		|||
	explicit PwConnection(QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	PwRegistry registry;
 | 
			
		||||
	PwDefaultsMetadata defaults {&this->registry};
 | 
			
		||||
	PwDefaultTracker defaults {&this->registry};
 | 
			
		||||
 | 
			
		||||
	static PwConnection* instance();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										224
									
								
								src/services/pipewire/defaults.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								src/services/pipewire/defaults.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,224 @@
 | 
			
		|||
#include "defaults.hpp"
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <spa/utils/json.h>
 | 
			
		||||
 | 
			
		||||
#include "../../core/util.hpp"
 | 
			
		||||
#include "metadata.hpp"
 | 
			
		||||
#include "node.hpp"
 | 
			
		||||
#include "registry.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::service::pipewire {
 | 
			
		||||
 | 
			
		||||
Q_LOGGING_CATEGORY(logDefaults, "quickshell.service.pipewire.defaults", QtWarningMsg);
 | 
			
		||||
 | 
			
		||||
PwDefaultTracker::PwDefaultTracker(PwRegistry* registry): registry(registry) {
 | 
			
		||||
	QObject::connect(registry, &PwRegistry::metadataAdded, this, &PwDefaultTracker::onMetadataAdded);
 | 
			
		||||
	QObject::connect(registry, &PwRegistry::nodeAdded, this, &PwDefaultTracker::onNodeAdded);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::onMetadataAdded(PwMetadata* metadata) {
 | 
			
		||||
	if (metadata->name() == "default") {
 | 
			
		||||
		qCDebug(logDefaults) << "Got new defaults metadata object" << metadata;
 | 
			
		||||
 | 
			
		||||
		if (this->defaultsMetadata.object()) {
 | 
			
		||||
			QObject::disconnect(this->defaultsMetadata.object(), nullptr, this, nullptr);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    metadata,
 | 
			
		||||
		    &PwMetadata::propertyChanged,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &PwDefaultTracker::onMetadataProperty
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		this->defaultsMetadata.setObject(metadata);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::onMetadataProperty(const char* key, const char* type, const char* value) {
 | 
			
		||||
	void (PwDefaultTracker::*nodeSetter)(PwNode*) = nullptr;
 | 
			
		||||
	void (PwDefaultTracker::*nameSetter)(const QString&) = nullptr;
 | 
			
		||||
 | 
			
		||||
	qCDebug(logDefaults).nospace() << "Got default metadata update for " << key << ": "
 | 
			
		||||
	                               << QString(value);
 | 
			
		||||
 | 
			
		||||
	if (strcmp(key, "default.audio.sink") == 0) {
 | 
			
		||||
		nodeSetter = &PwDefaultTracker::setDefaultSink;
 | 
			
		||||
		nameSetter = &PwDefaultTracker::setDefaultSinkName;
 | 
			
		||||
	} else if (strcmp(key, "default.audio.source") == 0) {
 | 
			
		||||
		nodeSetter = &PwDefaultTracker::setDefaultSource;
 | 
			
		||||
		nameSetter = &PwDefaultTracker::setDefaultSourceName;
 | 
			
		||||
	} else if (strcmp(key, "default.configured.audio.sink") == 0) {
 | 
			
		||||
		nodeSetter = &PwDefaultTracker::setDefaultConfiguredSink;
 | 
			
		||||
		nameSetter = &PwDefaultTracker::setDefaultConfiguredSinkName;
 | 
			
		||||
	} else if (strcmp(key, "default.configured.audio.source") == 0) {
 | 
			
		||||
		nodeSetter = &PwDefaultTracker::setDefaultConfiguredSource;
 | 
			
		||||
		nameSetter = &PwDefaultTracker::setDefaultConfiguredSourceName;
 | 
			
		||||
	} else return;
 | 
			
		||||
 | 
			
		||||
	QString name;
 | 
			
		||||
	if (strcmp(type, "Spa:String:JSON") == 0) {
 | 
			
		||||
		auto failed = true;
 | 
			
		||||
		auto iter = std::array<spa_json, 2>();
 | 
			
		||||
		spa_json_init(&iter[0], value, strlen(value));
 | 
			
		||||
 | 
			
		||||
		if (spa_json_enter_object(&iter[0], &iter[1]) > 0) {
 | 
			
		||||
			auto buf = std::array<char, 1024>();
 | 
			
		||||
 | 
			
		||||
			if (spa_json_get_string(&iter[1], buf.data(), buf.size()) > 0) {
 | 
			
		||||
				if (strcmp(buf.data(), "name") == 0) {
 | 
			
		||||
					if (spa_json_get_string(&iter[1], buf.data(), buf.size()) > 0) {
 | 
			
		||||
						name = buf.data();
 | 
			
		||||
						failed = false;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (failed) {
 | 
			
		||||
			qCWarning(logDefaults) << "Failed to parse SPA default json:"
 | 
			
		||||
			                       << QString::fromLocal8Bit(value);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	(this->*nameSetter)(name);
 | 
			
		||||
	(this->*nodeSetter)(this->registry->findNodeByName(name));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::onNodeAdded(PwNode* node) {
 | 
			
		||||
	if (node->name.isEmpty()) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mDefaultSink == nullptr && node->name == this->mDefaultSinkName) {
 | 
			
		||||
		this->setDefaultSink(node);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (this->mDefaultSource == nullptr && node->name == this->mDefaultSourceName) {
 | 
			
		||||
		this->setDefaultSource(node);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (this->mDefaultConfiguredSink == nullptr && node->name == this->mDefaultConfiguredSinkName) {
 | 
			
		||||
		this->setDefaultConfiguredSink(node);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (this->mDefaultConfiguredSource == nullptr && node->name == this->mDefaultConfiguredSourceName)
 | 
			
		||||
	{
 | 
			
		||||
		this->setDefaultConfiguredSource(node);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::onNodeDestroyed(QObject* node) {
 | 
			
		||||
	if (node == this->mDefaultSink) {
 | 
			
		||||
		qCInfo(logDefaults) << "Default sink destroyed.";
 | 
			
		||||
		this->mDefaultSink = nullptr;
 | 
			
		||||
		emit this->defaultSinkChanged();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (node == this->mDefaultSource) {
 | 
			
		||||
		qCInfo(logDefaults) << "Default source destroyed.";
 | 
			
		||||
		this->mDefaultSource = nullptr;
 | 
			
		||||
		emit this->defaultSourceChanged();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (node == this->mDefaultConfiguredSink) {
 | 
			
		||||
		qCInfo(logDefaults) << "Default configured sink destroyed.";
 | 
			
		||||
		this->mDefaultConfiguredSink = nullptr;
 | 
			
		||||
		emit this->defaultConfiguredSinkChanged();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (node == this->mDefaultConfiguredSource) {
 | 
			
		||||
		qCInfo(logDefaults) << "Default configured source destroyed.";
 | 
			
		||||
		this->mDefaultConfiguredSource = nullptr;
 | 
			
		||||
		emit this->defaultConfiguredSourceChanged();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::setDefaultSink(PwNode* node) {
 | 
			
		||||
	if (node == this->mDefaultSink) return;
 | 
			
		||||
	qCInfo(logDefaults) << "Default sink changed to" << node;
 | 
			
		||||
 | 
			
		||||
	setSimpleObjectHandle<
 | 
			
		||||
	    &PwDefaultTracker::mDefaultSink,
 | 
			
		||||
	    &PwDefaultTracker::onNodeDestroyed,
 | 
			
		||||
	    &PwDefaultTracker::defaultSinkChanged>(this, node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::setDefaultSinkName(const QString& name) {
 | 
			
		||||
	if (name == this->mDefaultSinkName) return;
 | 
			
		||||
	qCInfo(logDefaults) << "Default sink name changed to" << name;
 | 
			
		||||
	this->mDefaultSinkName = name;
 | 
			
		||||
	emit this->defaultSinkNameChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::setDefaultSource(PwNode* node) {
 | 
			
		||||
	if (node == this->mDefaultSource) return;
 | 
			
		||||
	qCInfo(logDefaults) << "Default source changed to" << node;
 | 
			
		||||
 | 
			
		||||
	setSimpleObjectHandle<
 | 
			
		||||
	    &PwDefaultTracker::mDefaultSource,
 | 
			
		||||
	    &PwDefaultTracker::onNodeDestroyed,
 | 
			
		||||
	    &PwDefaultTracker::defaultSourceChanged>(this, node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::setDefaultSourceName(const QString& name) {
 | 
			
		||||
	if (name == this->mDefaultSourceName) return;
 | 
			
		||||
	qCInfo(logDefaults) << "Default source name changed to" << name;
 | 
			
		||||
	this->mDefaultSourceName = name;
 | 
			
		||||
	emit this->defaultSourceNameChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) {
 | 
			
		||||
	if (node == this->mDefaultConfiguredSink) return;
 | 
			
		||||
	qCInfo(logDefaults) << "Default configured sink changed to" << node;
 | 
			
		||||
 | 
			
		||||
	setSimpleObjectHandle<
 | 
			
		||||
	    &PwDefaultTracker::mDefaultConfiguredSink,
 | 
			
		||||
	    &PwDefaultTracker::onNodeDestroyed,
 | 
			
		||||
	    &PwDefaultTracker::defaultConfiguredSinkChanged>(this, node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::setDefaultConfiguredSinkName(const QString& name) {
 | 
			
		||||
	if (name == this->mDefaultConfiguredSinkName) return;
 | 
			
		||||
	qCInfo(logDefaults) << "Default configured sink name changed to" << name;
 | 
			
		||||
	this->mDefaultConfiguredSinkName = name;
 | 
			
		||||
	emit this->defaultConfiguredSinkNameChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) {
 | 
			
		||||
	if (node == this->mDefaultConfiguredSource) return;
 | 
			
		||||
	qCInfo(logDefaults) << "Default configured source changed to" << node;
 | 
			
		||||
 | 
			
		||||
	setSimpleObjectHandle<
 | 
			
		||||
	    &PwDefaultTracker::mDefaultConfiguredSource,
 | 
			
		||||
	    &PwDefaultTracker::onNodeDestroyed,
 | 
			
		||||
	    &PwDefaultTracker::defaultConfiguredSourceChanged>(this, node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PwDefaultTracker::setDefaultConfiguredSourceName(const QString& name) {
 | 
			
		||||
	if (name == this->mDefaultConfiguredSourceName) return;
 | 
			
		||||
	qCInfo(logDefaults) << "Default configured source name changed to" << name;
 | 
			
		||||
	this->mDefaultConfiguredSourceName = name;
 | 
			
		||||
	emit this->defaultConfiguredSourceNameChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PwNode* PwDefaultTracker::defaultSink() const { return this->mDefaultSink; }
 | 
			
		||||
PwNode* PwDefaultTracker::defaultSource() const { return this->mDefaultSource; }
 | 
			
		||||
 | 
			
		||||
PwNode* PwDefaultTracker::defaultConfiguredSink() const { return this->mDefaultConfiguredSink; }
 | 
			
		||||
 | 
			
		||||
const QString& PwDefaultTracker::defaultConfiguredSinkName() const {
 | 
			
		||||
	return this->mDefaultConfiguredSinkName;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PwNode* PwDefaultTracker::defaultConfiguredSource() const { return this->mDefaultConfiguredSource; }
 | 
			
		||||
 | 
			
		||||
const QString& PwDefaultTracker::defaultConfiguredSourceName() const {
 | 
			
		||||
	return this->mDefaultConfiguredSourceName;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::pipewire
 | 
			
		||||
							
								
								
									
										73
									
								
								src/services/pipewire/defaults.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/services/pipewire/defaults.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,73 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
#include "registry.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::service::pipewire {
 | 
			
		||||
 | 
			
		||||
class PwDefaultTracker: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit PwDefaultTracker(PwRegistry* registry);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PwNode* defaultSink() const;
 | 
			
		||||
	[[nodiscard]] PwNode* defaultSource() const;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PwNode* defaultConfiguredSink() const;
 | 
			
		||||
	[[nodiscard]] const QString& defaultConfiguredSinkName() const;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PwNode* defaultConfiguredSource() const;
 | 
			
		||||
	[[nodiscard]] const QString& defaultConfiguredSourceName() const;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void defaultSinkChanged();
 | 
			
		||||
	void defaultSinkNameChanged();
 | 
			
		||||
 | 
			
		||||
	void defaultSourceChanged();
 | 
			
		||||
	void defaultSourceNameChanged();
 | 
			
		||||
 | 
			
		||||
	void defaultConfiguredSinkChanged();
 | 
			
		||||
	void defaultConfiguredSinkNameChanged();
 | 
			
		||||
 | 
			
		||||
	void defaultConfiguredSourceChanged();
 | 
			
		||||
	void defaultConfiguredSourceNameChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onMetadataAdded(PwMetadata* metadata);
 | 
			
		||||
	void onMetadataProperty(const char* key, const char* type, const char* value);
 | 
			
		||||
	void onNodeAdded(PwNode* node);
 | 
			
		||||
	void onNodeDestroyed(QObject* node);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void setDefaultSink(PwNode* node);
 | 
			
		||||
	void setDefaultSinkName(const QString& name);
 | 
			
		||||
 | 
			
		||||
	void setDefaultSource(PwNode* node);
 | 
			
		||||
	void setDefaultSourceName(const QString& name);
 | 
			
		||||
 | 
			
		||||
	void setDefaultConfiguredSink(PwNode* node);
 | 
			
		||||
	void setDefaultConfiguredSinkName(const QString& name);
 | 
			
		||||
 | 
			
		||||
	void setDefaultConfiguredSource(PwNode* node);
 | 
			
		||||
	void setDefaultConfiguredSourceName(const QString& name);
 | 
			
		||||
 | 
			
		||||
	PwRegistry* registry;
 | 
			
		||||
	PwBindableRef<PwMetadata> defaultsMetadata;
 | 
			
		||||
 | 
			
		||||
	PwNode* mDefaultSink = nullptr;
 | 
			
		||||
	QString mDefaultSinkName;
 | 
			
		||||
 | 
			
		||||
	PwNode* mDefaultSource = nullptr;
 | 
			
		||||
	QString mDefaultSourceName;
 | 
			
		||||
 | 
			
		||||
	PwNode* mDefaultConfiguredSink = nullptr;
 | 
			
		||||
	QString mDefaultConfiguredSinkName;
 | 
			
		||||
 | 
			
		||||
	PwNode* mDefaultConfiguredSource = nullptr;
 | 
			
		||||
	QString mDefaultConfiguredSourceName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::pipewire
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +1,15 @@
 | 
			
		|||
#include "metadata.hpp"
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
#include <pipewire/core.h>
 | 
			
		||||
#include <pipewire/extensions/metadata.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qstringview.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
#include <spa/utils/json.h>
 | 
			
		||||
#include <spa/param/param.h>
 | 
			
		||||
#include <spa/utils/dict.h>
 | 
			
		||||
 | 
			
		||||
#include "registry.hpp"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +23,14 @@ void PwMetadata::bindHooks() {
 | 
			
		|||
 | 
			
		||||
void PwMetadata::unbindHooks() { this->listener.remove(); }
 | 
			
		||||
 | 
			
		||||
void PwMetadata::initProps(const spa_dict* props) {
 | 
			
		||||
	if (const auto* name = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) {
 | 
			
		||||
		this->mName = name;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const QString& PwMetadata::name() const { return this->mName; }
 | 
			
		||||
 | 
			
		||||
const pw_metadata_events PwMetadata::EVENTS = {
 | 
			
		||||
    .version = PW_VERSION_METADATA_EVENTS,
 | 
			
		||||
    .property = &PwMetadata::onProperty,
 | 
			
		||||
| 
						 | 
				
			
			@ -39,89 +48,26 @@ int PwMetadata::onProperty(
 | 
			
		|||
	                 << "key:" << QString(key) << "type:" << QString(type)
 | 
			
		||||
	                 << "value:" << QString(value);
 | 
			
		||||
 | 
			
		||||
	emit self->registry->metadataUpdate(self, subject, key, type, value);
 | 
			
		||||
 | 
			
		||||
	// ideally we'd dealloc metadata that wasn't picked up but there's no information
 | 
			
		||||
	// available about if updates can come in later, so I assume they can.
 | 
			
		||||
	emit self->propertyChanged(key, type, value);
 | 
			
		||||
 | 
			
		||||
	return 0; // ??? - no docs and no reason for a callback to return an int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PwDefaultsMetadata::PwDefaultsMetadata(PwRegistry* registry) {
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    registry,
 | 
			
		||||
	    &PwRegistry::metadataUpdate,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &PwDefaultsMetadata::onMetadataUpdate
 | 
			
		||||
	);
 | 
			
		||||
bool PwMetadata::hasSetPermission() const {
 | 
			
		||||
	return (this->perms & SPA_PARAM_INFO_WRITE) == SPA_PARAM_INFO_WRITE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString PwDefaultsMetadata::defaultSink() const { return this->mDefaultSink; }
 | 
			
		||||
 | 
			
		||||
QString PwDefaultsMetadata::defaultSource() const { return this->mDefaultSource; }
 | 
			
		||||
 | 
			
		||||
// we don't really care if the metadata objects are destroyed, but try to ref them so we get property updates
 | 
			
		||||
void PwDefaultsMetadata::onMetadataUpdate(
 | 
			
		||||
    PwMetadata* metadata,
 | 
			
		||||
    quint32 subject,
 | 
			
		||||
    const char* key,
 | 
			
		||||
    const char* /*type*/,
 | 
			
		||||
    const char* value
 | 
			
		||||
) {
 | 
			
		||||
	if (subject != 0) return;
 | 
			
		||||
 | 
			
		||||
	if (strcmp(key, "default.audio.sink") == 0) {
 | 
			
		||||
		this->defaultSinkHolder.setObject(metadata);
 | 
			
		||||
 | 
			
		||||
		auto newSink = PwDefaultsMetadata::parseNameSpaJson(value);
 | 
			
		||||
		qCInfo(logMeta) << "Got default sink" << newSink;
 | 
			
		||||
		if (newSink == this->mDefaultSink) return;
 | 
			
		||||
 | 
			
		||||
		this->mDefaultSink = newSink;
 | 
			
		||||
		emit this->defaultSinkChanged();
 | 
			
		||||
void PwMetadata::setProperty(const char* key, const char* type, const char* value) {
 | 
			
		||||
	if (this->proxy() == nullptr) {
 | 
			
		||||
		qCCritical(logMeta) << "Tried to change property of" << this << "which is not bound.";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (strcmp(key, "default.audio.source") == 0) {
 | 
			
		||||
		this->defaultSourceHolder.setObject(metadata);
 | 
			
		||||
 | 
			
		||||
		auto newSource = PwDefaultsMetadata::parseNameSpaJson(value);
 | 
			
		||||
		qCInfo(logMeta) << "Got default source" << newSource;
 | 
			
		||||
		if (newSource == this->mDefaultSource) return;
 | 
			
		||||
 | 
			
		||||
		this->mDefaultSource = newSource;
 | 
			
		||||
		emit this->defaultSourceChanged();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString PwDefaultsMetadata::parseNameSpaJson(const char* spaJson) {
 | 
			
		||||
	auto iter = std::array<spa_json, 2>();
 | 
			
		||||
	spa_json_init(&iter[0], spaJson, strlen(spaJson));
 | 
			
		||||
 | 
			
		||||
	if (spa_json_enter_object(&iter[0], &iter[1]) < 0) {
 | 
			
		||||
		qCWarning(logMeta) << "Failed to parse source/sink SPA json - failed to enter object of"
 | 
			
		||||
		                   << QString(spaJson);
 | 
			
		||||
		return "";
 | 
			
		||||
	if (!this->hasSetPermission()) {
 | 
			
		||||
		qCCritical(logMeta) << "Tried to change property of" << this << "which is read-only.";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto buf = std::array<char, 512>();
 | 
			
		||||
	while (spa_json_get_string(&iter[1], buf.data(), buf.size()) > 0) {
 | 
			
		||||
		if (strcmp(buf.data(), "name") != 0) continue;
 | 
			
		||||
 | 
			
		||||
		if (spa_json_get_string(&iter[1], buf.data(), buf.size()) < 0) {
 | 
			
		||||
			qCWarning(logMeta
 | 
			
		||||
			) << "Failed to parse source/sink SPA json - failed to read value of name property"
 | 
			
		||||
			  << QString(spaJson);
 | 
			
		||||
			return "";
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return QString(buf.data());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	qCWarning(logMeta) << "Failed to parse source/sink SPA json - failed to find name property of"
 | 
			
		||||
	                   << QString(spaJson);
 | 
			
		||||
	return "";
 | 
			
		||||
	pw_metadata_set_property(this->proxy(), PW_ID_CORE, key, type, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::pipewire
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
#include <pipewire/extensions/metadata.h>
 | 
			
		||||
#include <pipewire/type.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qstringview.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
#include "core.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -18,45 +19,25 @@ class PwMetadata
 | 
			
		|||
public:
 | 
			
		||||
	void bindHooks() override;
 | 
			
		||||
	void unbindHooks() override;
 | 
			
		||||
	void initProps(const spa_dict* props) override;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] const QString& name() const;
 | 
			
		||||
	[[nodiscard]] bool hasSetPermission() const;
 | 
			
		||||
 | 
			
		||||
	// null value clears
 | 
			
		||||
	void setProperty(const char* key, const char* type, const char* value);
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void propertyChanged(const char* key, const char* type, const char* value);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static const pw_metadata_events EVENTS;
 | 
			
		||||
	static int
 | 
			
		||||
	onProperty(void* data, quint32 subject, const char* key, const char* type, const char* value);
 | 
			
		||||
 | 
			
		||||
	QString mName;
 | 
			
		||||
 | 
			
		||||
	SpaHook listener;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PwDefaultsMetadata: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit PwDefaultsMetadata(PwRegistry* registry);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QString defaultSource() const;
 | 
			
		||||
	[[nodiscard]] QString defaultSink() const;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void defaultSourceChanged();
 | 
			
		||||
	void defaultSinkChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onMetadataUpdate(
 | 
			
		||||
	    PwMetadata* metadata,
 | 
			
		||||
	    quint32 subject,
 | 
			
		||||
	    const char* key,
 | 
			
		||||
	    const char* type,
 | 
			
		||||
	    const char* value
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static QString parseNameSpaJson(const char* spaJson);
 | 
			
		||||
 | 
			
		||||
	PwBindableRef<PwMetadata> defaultSinkHolder;
 | 
			
		||||
	PwBindableRef<PwMetadata> defaultSourceHolder;
 | 
			
		||||
 | 
			
		||||
	QString mDefaultSink;
 | 
			
		||||
	QString mDefaultSource;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::pipewire
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,8 +10,8 @@
 | 
			
		|||
 | 
			
		||||
#include "../../core/model.hpp"
 | 
			
		||||
#include "connection.hpp"
 | 
			
		||||
#include "defaults.hpp"
 | 
			
		||||
#include "link.hpp"
 | 
			
		||||
#include "metadata.hpp"
 | 
			
		||||
#include "node.hpp"
 | 
			
		||||
#include "registry.hpp"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -60,10 +60,33 @@ Pipewire::Pipewire(QObject* parent): QObject(parent) {
 | 
			
		|||
	    &Pipewire::onLinkGroupAdded
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(&connection->defaults, &PwDefaultsMetadata::defaultSinkChanged, this, &Pipewire::defaultAudioSinkChanged);
 | 
			
		||||
	QObject::connect(&connection->defaults, &PwDefaultsMetadata::defaultSourceChanged, this, &Pipewire::defaultAudioSourceChanged);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &connection->defaults,
 | 
			
		||||
	    &PwDefaultTracker::defaultSinkChanged,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &Pipewire::defaultAudioSinkChanged
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &connection->defaults,
 | 
			
		||||
	    &PwDefaultTracker::defaultSourceChanged,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &Pipewire::defaultAudioSourceChanged
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &connection->defaults,
 | 
			
		||||
	    &PwDefaultTracker::defaultConfiguredSinkChanged,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &Pipewire::defaultConfiguredAudioSinkChanged
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &connection->defaults,
 | 
			
		||||
	    &PwDefaultTracker::defaultConfiguredSourceChanged,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &Pipewire::defaultConfiguredAudioSourceChanged
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ObjectModel<PwNodeIface>* Pipewire::nodes() { return &this->mNodes; }
 | 
			
		||||
| 
						 | 
				
			
			@ -106,29 +129,23 @@ void Pipewire::onLinkGroupRemoved(QObject* object) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
PwNodeIface* Pipewire::defaultAudioSink() const { // NOLINT
 | 
			
		||||
	auto* connection = PwConnection::instance();
 | 
			
		||||
	auto name = connection->defaults.defaultSink();
 | 
			
		||||
 | 
			
		||||
	for (auto* node: connection->registry.nodes.values()) {
 | 
			
		||||
		if (name == node->name) {
 | 
			
		||||
			return PwNodeIface::instance(node);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nullptr;
 | 
			
		||||
	auto* node = PwConnection::instance()->defaults.defaultSink();
 | 
			
		||||
	return PwNodeIface::instance(node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PwNodeIface* Pipewire::defaultAudioSource() const { // NOLINT
 | 
			
		||||
	auto* connection = PwConnection::instance();
 | 
			
		||||
	auto name = connection->defaults.defaultSource();
 | 
			
		||||
	auto* node = PwConnection::instance()->defaults.defaultSource();
 | 
			
		||||
	return PwNodeIface::instance(node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	for (auto* node: connection->registry.nodes.values()) {
 | 
			
		||||
		if (name == node->name) {
 | 
			
		||||
			return PwNodeIface::instance(node);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
PwNodeIface* Pipewire::defaultConfiguredAudioSink() const { // NOLINT
 | 
			
		||||
	auto* node = PwConnection::instance()->defaults.defaultConfiguredSink();
 | 
			
		||||
	return PwNodeIface::instance(node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	return nullptr;
 | 
			
		||||
PwNodeIface* Pipewire::defaultConfiguredAudioSource() const { // NOLINT
 | 
			
		||||
	auto* node = PwConnection::instance()->defaults.defaultConfiguredSource();
 | 
			
		||||
	return PwNodeIface::instance(node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PwNodeIface* PwNodeLinkTracker::node() const { return this->mNode; }
 | 
			
		||||
| 
						 | 
				
			
			@ -305,6 +322,8 @@ QVariantMap PwNodeIface::properties() const {
 | 
			
		|||
PwNodeAudioIface* PwNodeIface::audio() const { return this->audioIface; }
 | 
			
		||||
 | 
			
		||||
PwNodeIface* PwNodeIface::instance(PwNode* node) {
 | 
			
		||||
	if (node == nullptr) return nullptr;
 | 
			
		||||
 | 
			
		||||
	auto v = node->property("iface");
 | 
			
		||||
	if (v.canConvert<PwNodeIface*>()) {
 | 
			
		||||
		return v.value<PwNodeIface*>();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,10 +58,30 @@ class Pipewire: public QObject {
 | 
			
		|||
	Q_PROPERTY(ObjectModel<PwLinkIface>* links READ links CONSTANT);
 | 
			
		||||
	/// All pipewire link groups.
 | 
			
		||||
	Q_PROPERTY(ObjectModel<PwLinkGroupIface>* linkGroups READ linkGroups CONSTANT);
 | 
			
		||||
	/// The default audio sink or `null`.
 | 
			
		||||
	/// The default audio sink (output) or `null`.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!INFO] When the default sink changes, this property may breifly become null.
 | 
			
		||||
	/// > This depends on your hardware.
 | 
			
		||||
	Q_PROPERTY(PwNodeIface* defaultAudioSink READ defaultAudioSink NOTIFY defaultAudioSinkChanged);
 | 
			
		||||
	/// The default audio source or `null`.
 | 
			
		||||
	/// The default audio source (input) or `null`.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!INFO] When the default source changes, this property may breifly become null.
 | 
			
		||||
	/// > This depends on your hardware.
 | 
			
		||||
	Q_PROPERTY(PwNodeIface* defaultAudioSource READ defaultAudioSource NOTIFY defaultAudioSourceChanged);
 | 
			
		||||
	/// The configured default audio sink (output) or `null`.
 | 
			
		||||
	///
 | 
			
		||||
	/// This is not the same as @@defaultAudioSink. While @@defaultAudioSink is the
 | 
			
		||||
	/// sink that will be used by applications, @@defaultConfiguredAudioSink is the
 | 
			
		||||
	/// sink requested to be the default by quickshell or another configuration tool,
 | 
			
		||||
	/// which might not exist or be valid.
 | 
			
		||||
	Q_PROPERTY(PwNodeIface* defaultConfiguredAudioSink READ defaultConfiguredAudioSink NOTIFY defaultConfiguredAudioSinkChanged);
 | 
			
		||||
	/// The configured default audio source (input) or `null`.
 | 
			
		||||
	///
 | 
			
		||||
	/// This is not the same as @@defaultAudioSource. While @@defaultAudioSource is the
 | 
			
		||||
	/// source that will be used by applications, @@defaultConfiguredAudioSource is the
 | 
			
		||||
	/// source requested to be the default by quickshell or another configuration tool,
 | 
			
		||||
	/// which might not exist or be valid.
 | 
			
		||||
	Q_PROPERTY(PwNodeIface* defaultConfiguredAudioSource READ defaultConfiguredAudioSource NOTIFY defaultConfiguredAudioSourceChanged);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_SINGLETON;
 | 
			
		||||
| 
						 | 
				
			
			@ -72,13 +92,20 @@ public:
 | 
			
		|||
	[[nodiscard]] ObjectModel<PwNodeIface>* nodes();
 | 
			
		||||
	[[nodiscard]] ObjectModel<PwLinkIface>* links();
 | 
			
		||||
	[[nodiscard]] ObjectModel<PwLinkGroupIface>* linkGroups();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PwNodeIface* defaultAudioSink() const;
 | 
			
		||||
	[[nodiscard]] PwNodeIface* defaultAudioSource() const;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PwNodeIface* defaultConfiguredAudioSink() const;
 | 
			
		||||
	[[nodiscard]] PwNodeIface* defaultConfiguredAudioSource() const;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void defaultAudioSinkChanged();
 | 
			
		||||
	void defaultAudioSourceChanged();
 | 
			
		||||
 | 
			
		||||
	void defaultConfiguredAudioSinkChanged();
 | 
			
		||||
	void defaultConfiguredAudioSourceChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onNodeAdded(PwNode* node);
 | 
			
		||||
	void onNodeRemoved(QObject* object);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@
 | 
			
		|||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qstringview.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +144,7 @@ void PwRegistry::onGlobal(
 | 
			
		|||
		meta->initProps(props);
 | 
			
		||||
 | 
			
		||||
		self->metadata.emplace(id, meta);
 | 
			
		||||
		meta->bind();
 | 
			
		||||
		emit self->metadataAdded(meta);
 | 
			
		||||
	} else if (strcmp(type, PW_TYPE_INTERFACE_Link) == 0) {
 | 
			
		||||
		auto* link = new PwLink();
 | 
			
		||||
		link->init(self, id, permissions);
 | 
			
		||||
| 
						 | 
				
			
			@ -199,4 +200,14 @@ void PwRegistry::onLinkGroupDestroyed(QObject* object) {
 | 
			
		|||
	this->linkGroups.removeOne(group);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PwNode* PwRegistry::findNodeByName(QStringView name) const {
 | 
			
		||||
	if (name.isEmpty()) return nullptr;
 | 
			
		||||
 | 
			
		||||
	for (auto* node: this->nodes.values()) {
 | 
			
		||||
		if (node->name == name) return node;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::pipewire
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@
 | 
			
		|||
#include <qhash.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qstringview.h>
 | 
			
		||||
#include <qtclasshelpermacros.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -105,9 +106,8 @@ class PwBindableRef: public PwBindableObjectRef {
 | 
			
		|||
public:
 | 
			
		||||
	explicit PwBindableRef(T* object = nullptr): PwBindableObjectRef(object) {}
 | 
			
		||||
 | 
			
		||||
	T* object() { return static_cast<T*>(this->mObject); }
 | 
			
		||||
	void setObject(T* object) { this->PwBindableObjectRef::setObject(object); }
 | 
			
		||||
 | 
			
		||||
	T* object() { return this->mObject; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PwRegistry
 | 
			
		||||
| 
						 | 
				
			
			@ -127,17 +127,13 @@ public:
 | 
			
		|||
 | 
			
		||||
	PwCore* core = nullptr;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PwNode* findNodeByName(QStringView name) const;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void nodeAdded(PwNode* node);
 | 
			
		||||
	void linkAdded(PwLink* link);
 | 
			
		||||
	void linkGroupAdded(PwLinkGroup* group);
 | 
			
		||||
	void metadataUpdate(
 | 
			
		||||
	    PwMetadata* owner,
 | 
			
		||||
	    quint32 subject,
 | 
			
		||||
	    const char* key,
 | 
			
		||||
	    const char* type,
 | 
			
		||||
	    const char* value
 | 
			
		||||
	);
 | 
			
		||||
	void metadataAdded(PwMetadata* metadata);
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onLinkGroupDestroyed(QObject* object);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue