#include "properties.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../core/logcat.hpp" #include "dbus_properties.h" QS_LOGGING_CATEGORY(logDbusProperties, "quickshell.dbus.properties", QtWarningMsg); namespace qs::dbus { QDBusError demarshallVariant(const QVariant& variant, const QMetaType& type, void* slot) { const char* expectedSignature = "v"; if (type.id() != QMetaType::QVariant) { expectedSignature = QDBusMetaType::typeToSignature(type); if (expectedSignature == nullptr) { qFatal() << "failed to demarshall unregistered dbus meta-type" << type << "with" << variant; } } if (variant.metaType() == type) { if (type.id() == QMetaType::QVariant) { *reinterpret_cast(slot) = variant; } else { type.destruct(slot); type.construct(slot, variant.constData()); } } else if (variant.metaType() == QMetaType::fromType()) { auto arg = qvariant_cast(variant); auto signature = arg.currentSignature(); if (signature == expectedSignature) { if (!QDBusMetaType::demarshall(arg, type, slot)) { QString error; QDebug(&error) << "failed to deserialize dbus value" << variant << "into" << type; return QDBusError(QDBusError::InvalidArgs, error); } } else { QString error; QDebug(&error) << "mismatched signature while trying to demarshall" << variant << "into" << type << "expected" << expectedSignature << "got" << signature; return QDBusError(QDBusError::InvalidArgs, error); } } else { QString error; QDebug(&error) << "failed to deserialize variant" << variant << "into" << type; return QDBusError(QDBusError::InvalidArgs, error); } return QDBusError(); } void asyncReadPropertyInternal( const QMetaType& type, QDBusAbstractInterface& interface, const QString& property, std::function)> callback // NOLINT ) { if (type.id() != QMetaType::QVariant) { const char* expectedSignature = QDBusMetaType::typeToSignature(type); if (expectedSignature == nullptr) { qFatal() << "qs::dbus::asyncReadPropertyInternal called with unregistered dbus meta-type" << type; } } auto callMessage = QDBusMessage::createMethodCall( interface.service(), interface.path(), "org.freedesktop.DBus.Properties", "Get" ); callMessage << interface.interface() << property; auto pendingCall = interface.connection().asyncCall(callMessage); auto* call = new QDBusPendingCallWatcher(pendingCall, &interface); auto responseCallback = [type, callback](QDBusPendingCallWatcher* call) { QDBusPendingReply reply = *call; callback([&](void* slot) { if (reply.isError()) { return reply.error(); } else { return demarshallVariant(reply.value().variant(), type, slot); } }); delete call; }; QObject::connect(call, &QDBusPendingCallWatcher::finished, &interface, responseCallback); } DBusPropertyGroup::DBusPropertyGroup(QVector properties, QObject* parent) : QObject(parent) , properties(std::move(properties)) {} void DBusPropertyGroup::setInterface(QDBusAbstractInterface* interface) { if (this->interface != nullptr) { delete this->propertyInterface; this->propertyInterface = nullptr; } if (interface != nullptr) { this->interface = interface; this->propertyInterface = new DBusPropertiesInterface( interface->service(), interface->path(), interface->connection(), this ); QObject::connect( this->propertyInterface, &DBusPropertiesInterface::PropertiesChanged, this, &DBusPropertyGroup::onPropertiesChanged ); } } void DBusPropertyGroup::attachProperty(DBusPropertyCore* property) { this->properties.append(property); } void DBusPropertyGroup::updateAllDirect() { qCDebug(logDbusProperties).noquote() << "Updating all properties of" << this->toString() << "via individual queries"; if (this->interface == nullptr) { qFatal() << "Attempted to update properties of disconnected property group"; } for (auto* property: this->properties) { this->requestPropertyUpdate(property); } } void DBusPropertyGroup::updateAllViaGetAll() { qCDebug(logDbusProperties).noquote() << "Updating all properties of" << this->toString() << "via GetAll"; if (this->interface == nullptr) { qFatal() << "Attempted to update properties of disconnected property group"; } auto pendingCall = this->propertyInterface->GetAll(this->interface->interface()); auto* call = new QDBusPendingCallWatcher(pendingCall, this); auto responseCallback = [this](QDBusPendingCallWatcher* call) { const QDBusPendingReply reply = *call; if (reply.isError()) { qCWarning(logDbusProperties).noquote() << "Error updating properties of" << this->toString() << "via GetAll"; qCWarning(logDbusProperties) << reply.error(); emit this->getAllFailed(reply.error()); } else { qCDebug(logDbusProperties).noquote() << "Received GetAll property set for" << this->toString(); this->updatePropertySet(reply.value(), true); emit this->getAllFinished(); } delete call; }; QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); } void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties, bool complainMissing) { for (const auto [name, value]: properties.asKeyValueRange()) { auto prop = std::ranges::find_if(this->properties, [&name](DBusPropertyCore* prop) { return prop->nameRef() == name; }); if (prop == this->properties.end()) { qCDebug(logDbusProperties) << "Ignoring untracked property update" << name << "for" << this->toString(); } else { this->tryUpdateProperty(*prop, value); } } if (complainMissing) { for (const auto* prop: this->properties) { if (prop->isRequired() && !properties.contains(prop->name())) { qCWarning(logDbusProperties) << prop->nameRef() << "missing from property set for" << this->toString(); } } } } void DBusPropertyGroup::tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant) const { property->mExists = true; auto error = property->store(variant); if (error.isValid()) { qCWarning(logDbusProperties).noquote() << "Error demarshalling property update for" << this->propertyString(property); qCWarning(logDbusProperties) << error; } else { qCDebug(logDbusProperties).noquote() << "Updated property" << this->propertyString(property) << "to" << property->valueString(); } } void DBusPropertyGroup::requestPropertyUpdate(DBusPropertyCore* property) { const QString propStr = this->propertyString(property); if (this->interface == nullptr) { qFatal(logDbusProperties).noquote() << "Tried to update property" << propStr << "of a disconnected interface"; } qCDebug(logDbusProperties).noquote() << "Updating property" << propStr; auto pendingCall = this->propertyInterface->Get(this->interface->interface(), property->name()); auto* call = new QDBusPendingCallWatcher(pendingCall, this); auto responseCallback = [this, propStr, property](QDBusPendingCallWatcher* call) { const QDBusPendingReply reply = *call; if (reply.isError()) { qCWarning(logDbusProperties).noquote() << "Error updating property" << propStr; qCWarning(logDbusProperties) << reply.error(); } else { this->tryUpdateProperty(property, reply.value().variant()); } delete call; }; QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); } void DBusPropertyGroup::pushPropertyUpdate(DBusPropertyCore* property) { const QString propStr = this->propertyString(property); if (this->interface == nullptr) { qFatal(logDbusProperties).noquote() << "Tried to write property" << propStr << "of a disconnected interface"; } qCDebug(logDbusProperties).noquote() << "Writing property" << propStr; auto pendingCall = this->propertyInterface->Set( this->interface->interface(), property->name(), QDBusVariant(property->serialize()) ); auto* call = new QDBusPendingCallWatcher(pendingCall, this); auto responseCallback = [propStr](QDBusPendingCallWatcher* call) { const QDBusPendingReply<> reply = *call; if (reply.isError()) { qCWarning(logDbusProperties).noquote() << "Error writing property" << propStr; qCWarning(logDbusProperties) << reply.error(); } delete call; }; QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); } QString DBusPropertyGroup::toString() const { if (this->interface == nullptr) { return "{ DISCONNECTED }"; } else { return this->interface->service() + this->interface->path() + "/" + this->interface->interface(); } } QString DBusPropertyGroup::propertyString(const DBusPropertyCore* property) const { return this->toString() % ':' % property->nameRef(); } void DBusPropertyGroup::onPropertiesChanged( const QString& interfaceName, const QVariantMap& changedProperties, const QStringList& invalidatedProperties ) { if (interfaceName != this->interface->interface()) return; qCDebug(logDbusProperties).noquote() << "Received property change set and invalidations for" << this->toString(); for (const auto& name: invalidatedProperties) { auto prop = std::ranges::find_if(this->properties, [&name](DBusPropertyCore* prop) { return prop->nameRef() == name; }); if (prop == this->properties.end()) { qCDebug(logDbusProperties) << "Ignoring untracked property invalidation" << name << "for" << this; } else { this->requestPropertyUpdate(*prop); } } this->updatePropertySet(changedProperties, false); } } // namespace qs::dbus #if QT_VERSION < QT_VERSION_CHECK(6, 8, 0) QDebug operator<<(QDebug debug, const QDBusObjectPath& path) { debug.nospace() << "QDBusObjectPath(" << path.path() << ")"; return debug; } #endif