diff --git a/src/core/util.hpp b/src/core/util.hpp index 3c1a5ac6..3ca095e4 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include // NOLINTBEGIN #define DROP_EMIT(object, func) \ @@ -211,3 +213,34 @@ public: GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); } }; + +template +class SimpleObjectHandleOps { + using Traits = MemberPointerTraits; + +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 +bool setSimpleObjectHandle(auto* parent, auto* value) { + return SimpleObjectHandleOps::setObject(parent, value); +} diff --git a/src/services/pipewire/CMakeLists.txt b/src/services/pipewire/CMakeLists.txt index 51c9fec8..6996eff7 100644 --- a/src/services/pipewire/CMakeLists.txt +++ b/src/services/pipewire/CMakeLists.txt @@ -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 diff --git a/src/services/pipewire/connection.hpp b/src/services/pipewire/connection.hpp index fa270356..2b3e860e 100644 --- a/src/services/pipewire/connection.hpp +++ b/src/services/pipewire/connection.hpp @@ -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(); diff --git a/src/services/pipewire/defaults.cpp b/src/services/pipewire/defaults.cpp new file mode 100644 index 00000000..86e50f50 --- /dev/null +++ b/src/services/pipewire/defaults.cpp @@ -0,0 +1,224 @@ +#include "defaults.hpp" +#include +#include + +#include +#include +#include +#include +#include + +#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_init(&iter[0], value, strlen(value)); + + if (spa_json_enter_object(&iter[0], &iter[1]) > 0) { + auto buf = std::array(); + + 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 diff --git a/src/services/pipewire/defaults.hpp b/src/services/pipewire/defaults.hpp new file mode 100644 index 00000000..9544514e --- /dev/null +++ b/src/services/pipewire/defaults.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#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 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 diff --git a/src/services/pipewire/metadata.cpp b/src/services/pipewire/metadata.cpp index db0e4dd2..582e9ac6 100644 --- a/src/services/pipewire/metadata.cpp +++ b/src/services/pipewire/metadata.cpp @@ -1,14 +1,15 @@ #include "metadata.hpp" -#include -#include +#include #include #include #include #include +#include #include #include -#include +#include +#include #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_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(); - 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 diff --git a/src/services/pipewire/metadata.hpp b/src/services/pipewire/metadata.hpp index f57c9c58..812a8534 100644 --- a/src/services/pipewire/metadata.hpp +++ b/src/services/pipewire/metadata.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #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 defaultSinkHolder; - PwBindableRef defaultSourceHolder; - - QString mDefaultSink; - QString mDefaultSource; -}; - } // namespace qs::service::pipewire diff --git a/src/services/pipewire/qml.cpp b/src/services/pipewire/qml.cpp index 7092eb4a..aad58a57 100644 --- a/src/services/pipewire/qml.cpp +++ b/src/services/pipewire/qml.cpp @@ -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* 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()) { return v.value(); diff --git a/src/services/pipewire/qml.hpp b/src/services/pipewire/qml.hpp index 2c2c1d60..261770c9 100644 --- a/src/services/pipewire/qml.hpp +++ b/src/services/pipewire/qml.hpp @@ -58,10 +58,30 @@ class Pipewire: public QObject { Q_PROPERTY(ObjectModel* links READ links CONSTANT); /// All pipewire link groups. Q_PROPERTY(ObjectModel* 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* nodes(); [[nodiscard]] ObjectModel* links(); [[nodiscard]] ObjectModel* 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); diff --git a/src/services/pipewire/registry.cpp b/src/services/pipewire/registry.cpp index 55cfb276..1370fa10 100644 --- a/src/services/pipewire/registry.cpp +++ b/src/services/pipewire/registry.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -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 diff --git a/src/services/pipewire/registry.hpp b/src/services/pipewire/registry.hpp index 6ccd7148..c61773b2 100644 --- a/src/services/pipewire/registry.hpp +++ b/src/services/pipewire/registry.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -105,9 +106,8 @@ class PwBindableRef: public PwBindableObjectRef { public: explicit PwBindableRef(T* object = nullptr): PwBindableObjectRef(object) {} + T* object() { return static_cast(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);