forked from quickshell/quickshell
342 lines
10 KiB
C++
342 lines
10 KiB
C++
#include "properties.hpp"
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include <qcontainerfwd.h>
|
|
#include <qdbusabstractinterface.h>
|
|
#include <qdbusargument.h>
|
|
#include <qdbuserror.h>
|
|
#include <qdbusextratypes.h>
|
|
#include <qdbusmessage.h>
|
|
#include <qdbusmetatype.h>
|
|
#include <qdbuspendingcall.h>
|
|
#include <qdbuspendingreply.h>
|
|
#include <qdebug.h>
|
|
#include <qlogging.h>
|
|
#include <qloggingcategory.h>
|
|
#include <qmetatype.h>
|
|
#include <qobject.h>
|
|
#include <qtmetamacros.h>
|
|
#include <qtversionchecks.h>
|
|
#include <qvariant.h>
|
|
|
|
#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<QVariant*>(slot) = variant;
|
|
} else {
|
|
type.destruct(slot);
|
|
type.construct(slot, variant.constData());
|
|
}
|
|
} else if (variant.metaType() == QMetaType::fromType<QDBusArgument>()) {
|
|
auto arg = qvariant_cast<QDBusArgument>(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<void(std::function<QDBusError(void*)>)> 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<QDBusVariant> 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<DBusPropertyCore*> 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<QVariantMap> 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<QDBusVariant> reply = *call;
|
|
|
|
if (reply.isError()) {
|
|
if (!property->isRequired() && reply.error().type() == QDBusError::InvalidArgs) {
|
|
qCDebug(logDbusProperties) << "Error updating non-required property" << propStr;
|
|
qCDebug(logDbusProperties) << reply.error();
|
|
} else {
|
|
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
|