#include "qml.hpp" #include #include #include #include #include #include #include #include "connection.hpp" #include "link.hpp" #include "metadata.hpp" #include "node.hpp" #include "registry.hpp" namespace qs::service::pipewire { void PwObjectIface::ref() { this->refcount++; if (this->refcount == 1) { this->object->ref(); } } void PwObjectIface::unref() { if (this->refcount == 0) return; this->refcount--; if (this->refcount == 0) { this->object->unref(); } } Pipewire::Pipewire(QObject* parent): QObject(parent) { auto* connection = PwConnection::instance(); for (auto* node: connection->registry.nodes.values()) { this->onNodeAdded(node); } QObject::connect(&connection->registry, &PwRegistry::nodeAdded, this, &Pipewire::onNodeAdded); for (auto* link: connection->registry.links.values()) { this->onLinkAdded(link); } QObject::connect(&connection->registry, &PwRegistry::linkAdded, this, &Pipewire::onLinkAdded); for (auto* group: connection->registry.linkGroups) { this->onLinkGroupAdded(group); } QObject::connect( &connection->registry, &PwRegistry::linkGroupAdded, this, &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 } QQmlListProperty Pipewire::nodes() { return QQmlListProperty(this, nullptr, &Pipewire::nodesCount, &Pipewire::nodeAt); } qsizetype Pipewire::nodesCount(QQmlListProperty* property) { return static_cast(property->object)->mNodes.count(); // NOLINT } PwNodeIface* Pipewire::nodeAt(QQmlListProperty* property, qsizetype index) { return static_cast(property->object)->mNodes.at(index); // NOLINT } void Pipewire::onNodeAdded(PwNode* node) { auto* iface = PwNodeIface::instance(node); QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onNodeRemoved); this->mNodes.push_back(iface); emit this->nodesChanged(); } void Pipewire::onNodeRemoved(QObject* object) { auto* iface = static_cast(object); // NOLINT this->mNodes.removeOne(iface); emit this->nodesChanged(); } QQmlListProperty Pipewire::links() { return QQmlListProperty(this, nullptr, &Pipewire::linksCount, &Pipewire::linkAt); } qsizetype Pipewire::linksCount(QQmlListProperty* property) { return static_cast(property->object)->mLinks.count(); // NOLINT } PwLinkIface* Pipewire::linkAt(QQmlListProperty* property, qsizetype index) { return static_cast(property->object)->mLinks.at(index); // NOLINT } void Pipewire::onLinkAdded(PwLink* link) { auto* iface = PwLinkIface::instance(link); QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onLinkRemoved); this->mLinks.push_back(iface); emit this->linksChanged(); } void Pipewire::onLinkRemoved(QObject* object) { auto* iface = static_cast(object); // NOLINT this->mLinks.removeOne(iface); emit this->linksChanged(); } QQmlListProperty Pipewire::linkGroups() { return QQmlListProperty( this, nullptr, &Pipewire::linkGroupsCount, &Pipewire::linkGroupAt ); } qsizetype Pipewire::linkGroupsCount(QQmlListProperty* property) { return static_cast(property->object)->mLinkGroups.count(); // NOLINT } PwLinkGroupIface* Pipewire::linkGroupAt(QQmlListProperty* property, qsizetype index) { return static_cast(property->object)->mLinkGroups.at(index); // NOLINT } void Pipewire::onLinkGroupAdded(PwLinkGroup* linkGroup) { auto* iface = PwLinkGroupIface::instance(linkGroup); QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onLinkGroupRemoved); this->mLinkGroups.push_back(iface); emit this->linkGroupsChanged(); } void Pipewire::onLinkGroupRemoved(QObject* object) { auto* iface = static_cast(object); // NOLINT this->mLinkGroups.removeOne(iface); emit this->linkGroupsChanged(); } 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; } PwNodeIface* Pipewire::defaultAudioSource() const { // NOLINT auto* connection = PwConnection::instance(); auto name = connection->defaults.defaultSource(); for (auto* node: connection->registry.nodes.values()) { if (name == node->name) { return PwNodeIface::instance(node); } } return nullptr; } PwNodeIface* PwNodeLinkTracker::node() const { return this->mNode; } void PwNodeLinkTracker::setNode(PwNodeIface* node) { if (node == this->mNode) return; if (this->mNode != nullptr) { if (node == nullptr) { QObject::disconnect(&PwConnection::instance()->registry, nullptr, this, nullptr); } QObject::disconnect(this->mNode, nullptr, this, nullptr); } if (node != nullptr) { if (this->mNode == nullptr) { QObject::connect( &PwConnection::instance()->registry, &PwRegistry::linkGroupAdded, this, &PwNodeLinkTracker::onLinkGroupCreated ); } QObject::connect(node, &QObject::destroyed, this, &PwNodeLinkTracker::onNodeDestroyed); } this->mNode = node; this->updateLinks(); emit this->nodeChanged(); } void PwNodeLinkTracker::updateLinks() { // done first to avoid unref->reref of nodes auto newLinks = QVector(); if (this->mNode != nullptr) { auto* connection = PwConnection::instance(); for (auto* link: connection->registry.linkGroups) { if ((!this->mNode->isSink() && link->outputNode() == this->mNode->id()) || (this->mNode->isSink() && link->inputNode() == this->mNode->id())) { auto* iface = PwLinkGroupIface::instance(link); // do not connect twice if (!this->mLinkGroups.contains(iface)) { QObject::connect( iface, &QObject::destroyed, this, &PwNodeLinkTracker::onLinkGroupDestroyed ); } newLinks.push_back(iface); } } } for (auto* iface: this->mLinkGroups) { // only disconnect no longer used nodes if (!newLinks.contains(iface)) { QObject::disconnect(iface, nullptr, this, nullptr); } } this->mLinkGroups = newLinks; emit this->linkGroupsChanged(); } QQmlListProperty PwNodeLinkTracker::linkGroups() { return QQmlListProperty( this, nullptr, &PwNodeLinkTracker::linkGroupsCount, &PwNodeLinkTracker::linkGroupAt ); } qsizetype PwNodeLinkTracker::linkGroupsCount(QQmlListProperty* property) { return static_cast(property->object)->mLinkGroups.count(); // NOLINT } PwLinkGroupIface* PwNodeLinkTracker::linkGroupAt(QQmlListProperty* property, qsizetype index) { return static_cast(property->object)->mLinkGroups.at(index); // NOLINT } void PwNodeLinkTracker::onNodeDestroyed() { this->mNode = nullptr; this->updateLinks(); emit this->nodeChanged(); } void PwNodeLinkTracker::onLinkGroupCreated(PwLinkGroup* linkGroup) { if ((!this->mNode->isSink() && linkGroup->outputNode() == this->mNode->id()) || (this->mNode->isSink() && linkGroup->inputNode() == this->mNode->id())) { auto* iface = PwLinkGroupIface::instance(linkGroup); QObject::connect(iface, &QObject::destroyed, this, &PwNodeLinkTracker::onLinkGroupDestroyed); this->mLinkGroups.push_back(iface); emit this->linkGroupsChanged(); } } void PwNodeLinkTracker::onLinkGroupDestroyed(QObject* object) { if (this->mLinkGroups.removeOne(object)) { emit this->linkGroupsChanged(); } } PwNodeAudioIface::PwNodeAudioIface(PwNodeBoundAudio* boundData, QObject* parent) : QObject(parent) , boundData(boundData) { // clang-format off QObject::connect(boundData, &PwNodeBoundAudio::mutedChanged, this, &PwNodeAudioIface::mutedChanged); QObject::connect(boundData, &PwNodeBoundAudio::channelsChanged, this, &PwNodeAudioIface::channelsChanged); QObject::connect(boundData, &PwNodeBoundAudio::volumesChanged, this, &PwNodeAudioIface::volumesChanged); // clang-format on } bool PwNodeAudioIface::isMuted() const { return this->boundData->isMuted(); } void PwNodeAudioIface::setMuted(bool muted) { this->boundData->setMuted(muted); } float PwNodeAudioIface::averageVolume() const { return this->boundData->averageVolume(); } void PwNodeAudioIface::setAverageVolume(float volume) { this->boundData->setAverageVolume(volume); } QVector PwNodeAudioIface::channels() const { return this->boundData->channels(); } QVector PwNodeAudioIface::volumes() const { return this->boundData->volumes(); } void PwNodeAudioIface::setVolumes(const QVector& volumes) { this->boundData->setVolumes(volumes); } PwNodeIface::PwNodeIface(PwNode* node): PwObjectIface(node), mNode(node) { QObject::connect(node, &PwNode::propertiesChanged, this, &PwNodeIface::propertiesChanged); if (auto* audioBoundData = dynamic_cast(node->boundData)) { this->audioIface = new PwNodeAudioIface(audioBoundData, this); } } PwNode* PwNodeIface::node() const { return this->mNode; } QString PwNodeIface::name() const { return this->mNode->name; } quint32 PwNodeIface::id() const { return this->mNode->id; } QString PwNodeIface::description() const { return this->mNode->description; } QString PwNodeIface::nickname() const { return this->mNode->nick; } bool PwNodeIface::isSink() const { return this->mNode->isSink; } bool PwNodeIface::isStream() const { return this->mNode->isStream; } QVariantMap PwNodeIface::properties() const { auto map = QVariantMap(); for (auto [k, v]: this->mNode->properties.asKeyValueRange()) { map.insert(k, QVariant::fromValue(v)); } return map; } PwNodeAudioIface* PwNodeIface::audio() const { return this->audioIface; } PwNodeIface* PwNodeIface::instance(PwNode* node) { auto v = node->property("iface"); if (v.canConvert()) { return v.value(); } auto* instance = new PwNodeIface(node); node->setProperty("iface", QVariant::fromValue(instance)); return instance; } PwLinkIface::PwLinkIface(PwLink* link): PwObjectIface(link), mLink(link) { QObject::connect(link, &PwLink::stateChanged, this, &PwLinkIface::stateChanged); } PwLink* PwLinkIface::link() const { return this->mLink; } quint32 PwLinkIface::id() const { return this->mLink->id; } PwNodeIface* PwLinkIface::target() const { return PwNodeIface::instance( PwConnection::instance()->registry.nodes.value(this->mLink->inputNode()) ); } PwNodeIface* PwLinkIface::source() const { return PwNodeIface::instance( PwConnection::instance()->registry.nodes.value(this->mLink->outputNode()) ); } PwLinkState::Enum PwLinkIface::state() const { return this->mLink->state(); } PwLinkIface* PwLinkIface::instance(PwLink* link) { auto v = link->property("iface"); if (v.canConvert()) { return v.value(); } auto* instance = new PwLinkIface(link); link->setProperty("iface", QVariant::fromValue(instance)); return instance; } PwLinkGroupIface::PwLinkGroupIface(PwLinkGroup* group): QObject(group), mGroup(group) { QObject::connect(group, &PwLinkGroup::stateChanged, this, &PwLinkGroupIface::stateChanged); QObject::connect(group, &QObject::destroyed, this, [this]() { delete this; }); } void PwLinkGroupIface::ref() { this->mGroup->ref(); } void PwLinkGroupIface::unref() { this->mGroup->unref(); } PwLinkGroup* PwLinkGroupIface::group() const { return this->mGroup; } PwNodeIface* PwLinkGroupIface::target() const { return PwNodeIface::instance( PwConnection::instance()->registry.nodes.value(this->mGroup->inputNode()) ); } PwNodeIface* PwLinkGroupIface::source() const { return PwNodeIface::instance( PwConnection::instance()->registry.nodes.value(this->mGroup->outputNode()) ); } PwLinkState::Enum PwLinkGroupIface::state() const { return this->mGroup->state(); } PwLinkGroupIface* PwLinkGroupIface::instance(PwLinkGroup* group) { auto v = group->property("iface"); if (v.canConvert()) { return v.value(); } auto* instance = new PwLinkGroupIface(group); group->setProperty("iface", QVariant::fromValue(instance)); return instance; } PwObjectTracker::~PwObjectTracker() { this->clearList(); } QList PwObjectTracker::objects() const { return this->trackedObjects; } void PwObjectTracker::setObjects(const QList& objects) { // +1 ref before removing old refs to avoid an unbind->bind. for (auto* object: objects) { if (auto* pwObject = dynamic_cast(object)) { pwObject->ref(); } } this->clearList(); // connect destroy for (auto* object: objects) { if (auto* pwObject = dynamic_cast(object)) { QObject::connect(object, &QObject::destroyed, this, &PwObjectTracker::objectDestroyed); } } this->trackedObjects = objects; } void PwObjectTracker::clearList() { for (auto* object: this->trackedObjects) { if (auto* pwObject = dynamic_cast(object)) { pwObject->unref(); QObject::disconnect(object, nullptr, this, nullptr); } } this->trackedObjects.clear(); } void PwObjectTracker::objectDestroyed(QObject* object) { this->trackedObjects.removeOne(object); emit this->objectsChanged(); } } // namespace qs::service::pipewire