diff --git a/src/services/pipewire/defaults.cpp b/src/services/pipewire/defaults.cpp index 86e50f50..7cc6d171 100644 --- a/src/services/pipewire/defaults.cpp +++ b/src/services/pipewire/defaults.cpp @@ -2,9 +2,12 @@ #include #include +#include +#include #include #include #include +#include #include #include @@ -63,7 +66,7 @@ void PwDefaultTracker::onMetadataProperty(const char* key, const char* type, con } else return; QString name; - if (strcmp(type, "Spa:String:JSON") == 0) { + if (type != nullptr && value != nullptr && strcmp(type, "Spa:String:JSON") == 0) { auto failed = true; auto iter = std::array(); 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) { if (node == this->mDefaultSink) return; qCInfo(logDefaults) << "Default sink changed to" << node; diff --git a/src/services/pipewire/defaults.hpp b/src/services/pipewire/defaults.hpp index 9544514e..f3a8e3f9 100644 --- a/src/services/pipewire/defaults.hpp +++ b/src/services/pipewire/defaults.hpp @@ -18,9 +18,13 @@ public: [[nodiscard]] PwNode* defaultConfiguredSink() const; [[nodiscard]] const QString& defaultConfiguredSinkName() const; + void changeConfiguredSink(PwNode* node); + void changeConfiguredSinkName(const QString& sink); [[nodiscard]] PwNode* defaultConfiguredSource() const; [[nodiscard]] const QString& defaultConfiguredSourceName() const; + void changeConfiguredSource(PwNode* node); + void changeConfiguredSourceName(const QString& source); signals: void defaultSinkChanged(); @@ -54,6 +58,8 @@ private: void setDefaultConfiguredSource(PwNode* node); void setDefaultConfiguredSourceName(const QString& name); + bool setConfiguredDefault(const char* key, const QString& value); + PwRegistry* registry; PwBindableRef defaultsMetadata; diff --git a/src/services/pipewire/qml.cpp b/src/services/pipewire/qml.cpp index aad58a57..a8186ea3 100644 --- a/src/services/pipewire/qml.cpp +++ b/src/services/pipewire/qml.cpp @@ -143,11 +143,19 @@ PwNodeIface* Pipewire::defaultConfiguredAudioSink() const { // NOLINT return PwNodeIface::instance(node); } +void Pipewire::setDefaultConfiguredAudioSink(PwNodeIface* node) { + PwConnection::instance()->defaults.changeConfiguredSink(node ? node->node() : nullptr); +} + PwNodeIface* Pipewire::defaultConfiguredAudioSource() const { // NOLINT auto* node = PwConnection::instance()->defaults.defaultConfiguredSource(); return PwNodeIface::instance(node); } +void Pipewire::setDefaultConfiguredAudioSource(PwNodeIface* node) { + PwConnection::instance()->defaults.changeConfiguredSource(node ? node->node() : nullptr); +} + PwNodeIface* PwNodeLinkTracker::node() const { return this->mNode; } void PwNodeLinkTracker::setNode(PwNodeIface* node) { diff --git a/src/services/pipewire/qml.hpp b/src/services/pipewire/qml.hpp index 261770c9..80049ddc 100644 --- a/src/services/pipewire/qml.hpp +++ b/src/services/pipewire/qml.hpp @@ -60,28 +60,38 @@ class Pipewire: public QObject { Q_PROPERTY(ObjectModel* linkGroups READ linkGroups CONSTANT); /// 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. /// > This depends on your hardware. Q_PROPERTY(PwNodeIface* defaultAudioSink READ defaultAudioSink NOTIFY defaultAudioSinkChanged); /// 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. /// > This depends on your hardware. 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 - /// 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 a hint to pipewire telling it which sink should be the default when possible. + /// @@defaultAudioSink may differ when it is not possible for pipewire to pick this node. /// - /// 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); + /// See @@defaultAudioSink for the current default sink, regardless of preference. + Q_PROPERTY(PwNodeIface* preferredDefaultAudioSink READ defaultConfiguredAudioSink WRITE setDefaultConfiguredAudioSink NOTIFY defaultConfiguredAudioSinkChanged); + /// The preferred default audio source (input) or `null`. + /// + /// 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 QML_ELEMENT; QML_SINGLETON; @@ -97,7 +107,10 @@ public: [[nodiscard]] PwNodeIface* defaultAudioSource() const; [[nodiscard]] PwNodeIface* defaultConfiguredAudioSink() const; + static void setDefaultConfiguredAudioSink(PwNodeIface* node); + [[nodiscard]] PwNodeIface* defaultConfiguredAudioSource() const; + static void setDefaultConfiguredAudioSource(PwNodeIface* node); signals: void defaultAudioSinkChanged();