#include "metadata.hpp" #include #include #include #include #include #include #include #include #include #include "registry.hpp" namespace qs::service::pipewire { Q_LOGGING_CATEGORY(logMeta, "quickshell.service.pipewire.metadata", QtWarningMsg); void PwMetadata::bindHooks() { pw_metadata_add_listener(this->proxy(), &this->listener.hook, &PwMetadata::EVENTS, this); } void PwMetadata::unbindHooks() { this->listener.remove(); } const pw_metadata_events PwMetadata::EVENTS = { .version = PW_VERSION_METADATA_EVENTS, .property = &PwMetadata::onProperty, }; int PwMetadata::onProperty( void* data, quint32 subject, const char* key, const char* type, const char* value ) { auto* self = static_cast(data); qCDebug(logMeta) << "Received metadata for" << self << "- subject:" << subject << "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. 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 ); } 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; // non "configured" sinks and sources have lower priority as wireplumber seems to only change // the "configured" ones. bool sink = false; if (strcmp(key, "default.configured.audio.sink") == 0) { sink = true; this->sinkConfigured = true; } else if ((!this->sinkConfigured && strcmp(key, "default.audio.sink") == 0)) { sink = true; } if (sink) { this->defaultSinkHolder.setObject(metadata); auto newSink = PwDefaultsMetadata::parseNameSpaJson(value); qCInfo(logMeta) << "Got default sink" << newSink << "configured:" << this->sinkConfigured; if (newSink == this->mDefaultSink) return; this->mDefaultSink = newSink; emit this->defaultSinkChanged(); return; } bool source = false; if (strcmp(key, "default.configured.audio.source") == 0) { source = true; this->sourceConfigured = true; } else if ((!this->sourceConfigured && strcmp(key, "default.audio.source") == 0)) { source = true; } if (source) { this->defaultSourceHolder.setObject(metadata); auto newSource = PwDefaultsMetadata::parseNameSpaJson(value); qCInfo(logMeta) << "Got default source" << newSource << "configured:" << this->sourceConfigured; 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 ""; } 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 ""; } } // namespace qs::service::pipewire