forked from quickshell/quickshell
		
	service/pipewire: add a way to set preferred default nodes
This commit is contained in:
		
							parent
							
								
									f889f08901
								
							
						
					
					
						commit
						fdc78ae16f
					
				
					 4 changed files with 107 additions and 13 deletions
				
			
		| 
						 | 
					@ -2,9 +2,12 @@
 | 
				
			||||||
#include <array>
 | 
					#include <array>
 | 
				
			||||||
#include <cstring>
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qjsondocument.h>
 | 
				
			||||||
 | 
					#include <qjsonobject.h>
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qloggingcategory.h>
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qstringview.h>
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
#include <spa/utils/json.h>
 | 
					#include <spa/utils/json.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,7 +66,7 @@ void PwDefaultTracker::onMetadataProperty(const char* key, const char* type, con
 | 
				
			||||||
	} else return;
 | 
						} else return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString name;
 | 
						QString name;
 | 
				
			||||||
	if (strcmp(type, "Spa:String:JSON") == 0) {
 | 
						if (type != nullptr && value != nullptr && strcmp(type, "Spa:String:JSON") == 0) {
 | 
				
			||||||
		auto failed = true;
 | 
							auto failed = true;
 | 
				
			||||||
		auto iter = std::array<spa_json, 2>();
 | 
							auto iter = std::array<spa_json, 2>();
 | 
				
			||||||
		spa_json_init(&iter[0], value, strlen(value));
 | 
							spa_json_init(&iter[0], value, strlen(value));
 | 
				
			||||||
| 
						 | 
					@ -138,6 +141,70 @@ void PwDefaultTracker::onNodeDestroyed(QObject* node) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PwDefaultTracker::changeConfiguredSink(PwNode* node) {
 | 
				
			||||||
 | 
						if (node != nullptr) {
 | 
				
			||||||
 | 
							if (!node->isSink) {
 | 
				
			||||||
 | 
								qCCritical(logDefaults) << "Cannot change default sink to a node that is not a sink.";
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->changeConfiguredSinkName(node->name);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							this->changeConfiguredSinkName("");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PwDefaultTracker::changeConfiguredSinkName(const QString& sink) {
 | 
				
			||||||
 | 
						if (sink == this->mDefaultConfiguredSinkName) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->setConfiguredDefault("default.configured.audio.sink", sink)) {
 | 
				
			||||||
 | 
							this->mDefaultConfiguredSinkName = sink;
 | 
				
			||||||
 | 
							qCInfo(logDefaults) << "Set default configured sink to" << sink;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PwDefaultTracker::changeConfiguredSource(PwNode* node) {
 | 
				
			||||||
 | 
						if (node != nullptr) {
 | 
				
			||||||
 | 
							if (node->isSink) {
 | 
				
			||||||
 | 
								qCCritical(logDefaults) << "Cannot change default source to a node that is not a source.";
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->changeConfiguredSourceName(node->name);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							this->changeConfiguredSourceName("");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PwDefaultTracker::changeConfiguredSourceName(const QString& source) {
 | 
				
			||||||
 | 
						if (source == this->mDefaultConfiguredSourceName) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->setConfiguredDefault("default.configured.audio.source", source)) {
 | 
				
			||||||
 | 
							this->mDefaultConfiguredSourceName = source;
 | 
				
			||||||
 | 
							qCInfo(logDefaults) << "Set default configured source to" << source;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool PwDefaultTracker::setConfiguredDefault(const char* key, const QString& value) {
 | 
				
			||||||
 | 
						auto* meta = this->defaultsMetadata.object();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!meta || !meta->proxy()) {
 | 
				
			||||||
 | 
							qCCritical(logDefaults) << "Cannot set default node as metadata is not ready.";
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (value.isEmpty()) {
 | 
				
			||||||
 | 
							meta->setProperty(key, "Spa:String:JSON", nullptr);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Spa json is a superset of json so we can avoid the awful spa json api when serializing.
 | 
				
			||||||
 | 
							auto json = QJsonDocument({{"name", value}}).toJson(QJsonDocument::Compact);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							meta->setProperty(key, "Spa:String:JSON", json.toStdString().c_str());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void PwDefaultTracker::setDefaultSink(PwNode* node) {
 | 
					void PwDefaultTracker::setDefaultSink(PwNode* node) {
 | 
				
			||||||
	if (node == this->mDefaultSink) return;
 | 
						if (node == this->mDefaultSink) return;
 | 
				
			||||||
	qCInfo(logDefaults) << "Default sink changed to" << node;
 | 
						qCInfo(logDefaults) << "Default sink changed to" << node;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,9 +18,13 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] PwNode* defaultConfiguredSink() const;
 | 
						[[nodiscard]] PwNode* defaultConfiguredSink() const;
 | 
				
			||||||
	[[nodiscard]] const QString& defaultConfiguredSinkName() const;
 | 
						[[nodiscard]] const QString& defaultConfiguredSinkName() const;
 | 
				
			||||||
 | 
						void changeConfiguredSink(PwNode* node);
 | 
				
			||||||
 | 
						void changeConfiguredSinkName(const QString& sink);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] PwNode* defaultConfiguredSource() const;
 | 
						[[nodiscard]] PwNode* defaultConfiguredSource() const;
 | 
				
			||||||
	[[nodiscard]] const QString& defaultConfiguredSourceName() const;
 | 
						[[nodiscard]] const QString& defaultConfiguredSourceName() const;
 | 
				
			||||||
 | 
						void changeConfiguredSource(PwNode* node);
 | 
				
			||||||
 | 
						void changeConfiguredSourceName(const QString& source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signals:
 | 
					signals:
 | 
				
			||||||
	void defaultSinkChanged();
 | 
						void defaultSinkChanged();
 | 
				
			||||||
| 
						 | 
					@ -54,6 +58,8 @@ private:
 | 
				
			||||||
	void setDefaultConfiguredSource(PwNode* node);
 | 
						void setDefaultConfiguredSource(PwNode* node);
 | 
				
			||||||
	void setDefaultConfiguredSourceName(const QString& name);
 | 
						void setDefaultConfiguredSourceName(const QString& name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool setConfiguredDefault(const char* key, const QString& value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	PwRegistry* registry;
 | 
						PwRegistry* registry;
 | 
				
			||||||
	PwBindableRef<PwMetadata> defaultsMetadata;
 | 
						PwBindableRef<PwMetadata> defaultsMetadata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -143,11 +143,19 @@ PwNodeIface* Pipewire::defaultConfiguredAudioSink() const { // NOLINT
 | 
				
			||||||
	return PwNodeIface::instance(node);
 | 
						return PwNodeIface::instance(node);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Pipewire::setDefaultConfiguredAudioSink(PwNodeIface* node) {
 | 
				
			||||||
 | 
						PwConnection::instance()->defaults.changeConfiguredSink(node ? node->node() : nullptr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PwNodeIface* Pipewire::defaultConfiguredAudioSource() const { // NOLINT
 | 
					PwNodeIface* Pipewire::defaultConfiguredAudioSource() const { // NOLINT
 | 
				
			||||||
	auto* node = PwConnection::instance()->defaults.defaultConfiguredSource();
 | 
						auto* node = PwConnection::instance()->defaults.defaultConfiguredSource();
 | 
				
			||||||
	return PwNodeIface::instance(node);
 | 
						return PwNodeIface::instance(node);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Pipewire::setDefaultConfiguredAudioSource(PwNodeIface* node) {
 | 
				
			||||||
 | 
						PwConnection::instance()->defaults.changeConfiguredSource(node ? node->node() : nullptr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PwNodeIface* PwNodeLinkTracker::node() const { return this->mNode; }
 | 
					PwNodeIface* PwNodeLinkTracker::node() const { return this->mNode; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void PwNodeLinkTracker::setNode(PwNodeIface* node) {
 | 
					void PwNodeLinkTracker::setNode(PwNodeIface* node) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,28 +60,38 @@ class Pipewire: public QObject {
 | 
				
			||||||
	Q_PROPERTY(ObjectModel<PwLinkGroupIface>* linkGroups READ linkGroups CONSTANT);
 | 
						Q_PROPERTY(ObjectModel<PwLinkGroupIface>* linkGroups READ linkGroups CONSTANT);
 | 
				
			||||||
	/// The default audio sink (output) or `null`.
 | 
						/// The default audio sink (output) or `null`.
 | 
				
			||||||
	///
 | 
						///
 | 
				
			||||||
 | 
						/// This is the default sink currently in use by pipewire, and the one applications
 | 
				
			||||||
 | 
						/// are currently using.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// To set the default sink, use @@preferredDefaultAudioSink.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
	/// > [!INFO] When the default sink changes, this property may breifly become null.
 | 
						/// > [!INFO] When the default sink changes, this property may breifly become null.
 | 
				
			||||||
	/// > This depends on your hardware.
 | 
						/// > This depends on your hardware.
 | 
				
			||||||
	Q_PROPERTY(PwNodeIface* defaultAudioSink READ defaultAudioSink NOTIFY defaultAudioSinkChanged);
 | 
						Q_PROPERTY(PwNodeIface* defaultAudioSink READ defaultAudioSink NOTIFY defaultAudioSinkChanged);
 | 
				
			||||||
	/// The default audio source (input) or `null`.
 | 
						/// The default audio source (input) or `null`.
 | 
				
			||||||
	///
 | 
						///
 | 
				
			||||||
 | 
						/// This is the default source currently in use by pipewire, and the one applications
 | 
				
			||||||
 | 
						/// are currently using.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// To set the default source, use @@preferredDefaultAudioSource.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
	/// > [!INFO] When the default source changes, this property may breifly become null.
 | 
						/// > [!INFO] When the default source changes, this property may breifly become null.
 | 
				
			||||||
	/// > This depends on your hardware.
 | 
						/// > This depends on your hardware.
 | 
				
			||||||
	Q_PROPERTY(PwNodeIface* defaultAudioSource READ defaultAudioSource NOTIFY defaultAudioSourceChanged);
 | 
						Q_PROPERTY(PwNodeIface* defaultAudioSource READ defaultAudioSource NOTIFY defaultAudioSourceChanged);
 | 
				
			||||||
	/// The configured default audio sink (output) or `null`.
 | 
						/// The preferred default audio sink (output) or `null`.
 | 
				
			||||||
	///
 | 
						///
 | 
				
			||||||
	/// This is not the same as @@defaultAudioSink. While @@defaultAudioSink is the
 | 
						/// This is a hint to pipewire telling it which sink should be the default when possible.
 | 
				
			||||||
	/// sink that will be used by applications, @@defaultConfiguredAudioSink is the
 | 
						/// @@defaultAudioSink may differ when it is not possible for pipewire to pick this node.
 | 
				
			||||||
	/// 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
 | 
						/// See @@defaultAudioSink for the current default sink, regardless of preference.
 | 
				
			||||||
	/// source that will be used by applications, @@defaultConfiguredAudioSource is the
 | 
						Q_PROPERTY(PwNodeIface* preferredDefaultAudioSink READ defaultConfiguredAudioSink WRITE setDefaultConfiguredAudioSink NOTIFY defaultConfiguredAudioSinkChanged);
 | 
				
			||||||
	/// source requested to be the default by quickshell or another configuration tool,
 | 
						/// The preferred default audio source (input) or `null`.
 | 
				
			||||||
	/// which might not exist or be valid.
 | 
						///
 | 
				
			||||||
	Q_PROPERTY(PwNodeIface* defaultConfiguredAudioSource READ defaultConfiguredAudioSource NOTIFY defaultConfiguredAudioSourceChanged);
 | 
						/// This is a hint to pipewire telling it which source should be the default when possible.
 | 
				
			||||||
 | 
						/// @@defaultAudioSource may differ when it is not possible for pipewire to pick this node.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// See @@defaultAudioSource for the current default source, regardless of preference.
 | 
				
			||||||
 | 
						Q_PROPERTY(PwNodeIface* preferredDefaultAudioSource READ defaultConfiguredAudioSource WRITE setDefaultConfiguredAudioSource NOTIFY defaultConfiguredAudioSourceChanged);
 | 
				
			||||||
	// clang-format on
 | 
						// clang-format on
 | 
				
			||||||
	QML_ELEMENT;
 | 
						QML_ELEMENT;
 | 
				
			||||||
	QML_SINGLETON;
 | 
						QML_SINGLETON;
 | 
				
			||||||
| 
						 | 
					@ -97,7 +107,10 @@ public:
 | 
				
			||||||
	[[nodiscard]] PwNodeIface* defaultAudioSource() const;
 | 
						[[nodiscard]] PwNodeIface* defaultAudioSource() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] PwNodeIface* defaultConfiguredAudioSink() const;
 | 
						[[nodiscard]] PwNodeIface* defaultConfiguredAudioSink() const;
 | 
				
			||||||
 | 
						static void setDefaultConfiguredAudioSink(PwNodeIface* node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] PwNodeIface* defaultConfiguredAudioSource() const;
 | 
						[[nodiscard]] PwNodeIface* defaultConfiguredAudioSource() const;
 | 
				
			||||||
 | 
						static void setDefaultConfiguredAudioSource(PwNodeIface* node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signals:
 | 
					signals:
 | 
				
			||||||
	void defaultAudioSinkChanged();
 | 
						void defaultAudioSinkChanged();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue