forked from quickshell/quickshell
dbus: create property helper classes
Handles asynchronous property updates, part of the work for StatusNotifierItems.
This commit is contained in:
parent
8529a2eb22
commit
8e530b6b77
|
@ -73,6 +73,11 @@ if (WAYLAND)
|
|||
list(APPEND QT_FPDEPS WaylandClient)
|
||||
endif()
|
||||
|
||||
if (DBUS)
|
||||
list(APPEND QT_DEPS Qt6::DBus)
|
||||
list(APPEND QT_FPDEPS DBus)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS})
|
||||
|
||||
qt_standard_project_setup(REQUIRES 6.6)
|
||||
|
|
|
@ -5,6 +5,10 @@ install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
|||
add_subdirectory(core)
|
||||
add_subdirectory(io)
|
||||
|
||||
if (DBUS)
|
||||
add_subdirectory(dbus)
|
||||
endif()
|
||||
|
||||
if (WAYLAND)
|
||||
add_subdirectory(wayland)
|
||||
endif ()
|
||||
|
|
22
src/dbus/CMakeLists.txt
Normal file
22
src/dbus/CMakeLists.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
set_source_files_properties(org.freedesktop.DBus.Properties.xml PROPERTIES
|
||||
CLASSNAME DBusPropertiesInterface
|
||||
#INCLUDE dbus_properties_types.hpp
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(DBUS_INTERFACES
|
||||
org.freedesktop.DBus.Properties.xml
|
||||
dbus_properties
|
||||
)
|
||||
|
||||
qt_add_library(quickshell-dbus STATIC
|
||||
dbusutil.cpp
|
||||
${DBUS_INTERFACES}
|
||||
)
|
||||
|
||||
# dbus headers
|
||||
target_include_directories(quickshell-dbus PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_link_libraries(quickshell-dbus PRIVATE ${QT_DEPS})
|
||||
|
||||
qs_pch(quickshell-dbus)
|
||||
#qs_pch(quickshell-dbusplugin)
|
284
src/dbus/dbusutil.cpp
Normal file
284
src/dbus/dbusutil.cpp
Normal file
|
@ -0,0 +1,284 @@
|
|||
#include "dbusutil.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 <qpolygon.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
#include "dbus_properties.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(logDbus, "quickshell.dbus", 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; // NOLINT
|
||||
} 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) << "failed to deserialize variant" << variant
|
||||
<< "which is not a primitive type or a dbus argument (what?)";
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, &interface, responseCallback);
|
||||
}
|
||||
|
||||
void AbstractDBusProperty::tryUpdate(const QVariant& variant) {
|
||||
auto error = this->read(variant);
|
||||
if (error.isValid()) {
|
||||
qCWarning(logDbus).noquote() << "Error demarshalling property update for" << this->toString();
|
||||
qCWarning(logDbus) << error;
|
||||
} else {
|
||||
qCDebug(logDbus).noquote() << "Updated property" << this->toString() << "to"
|
||||
<< this->valueString();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractDBusProperty::update() {
|
||||
if (this->group == nullptr) {
|
||||
qFatal(logDbus) << "Tried to update dbus property" << this->name
|
||||
<< "which is not attached to a group";
|
||||
} else {
|
||||
const QString propStr = this->toString();
|
||||
|
||||
if (this->group->interface == nullptr) {
|
||||
qFatal(logDbus).noquote() << "Tried to update property" << propStr
|
||||
<< "of a disconnected interface";
|
||||
}
|
||||
|
||||
qCDebug(logDbus).noquote() << "Updating property" << propStr;
|
||||
|
||||
auto pendingCall =
|
||||
this->group->propertyInterface->Get(this->group->interface->interface(), this->name);
|
||||
|
||||
auto* call = new QDBusPendingCallWatcher(pendingCall, this);
|
||||
|
||||
auto responseCallback = [this, propStr](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QDBusVariant> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logDbus).noquote() << "Error updating property" << propStr;
|
||||
qCWarning(logDbus) << reply.error();
|
||||
} else {
|
||||
this->tryUpdate(reply.value().variant());
|
||||
}
|
||||
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
}
|
||||
|
||||
QString AbstractDBusProperty::toString() const {
|
||||
const QString group = this->group == nullptr ? "{ NO GROUP }" : this->group->toString();
|
||||
return group + ':' + this->name;
|
||||
}
|
||||
|
||||
DBusPropertyGroup::DBusPropertyGroup(QVector<AbstractDBusProperty*> 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(AbstractDBusProperty* property) {
|
||||
this->properties.append(property);
|
||||
property->group = this;
|
||||
}
|
||||
|
||||
void DBusPropertyGroup::updateAllDirect() {
|
||||
qCDebug(logDbus).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) {
|
||||
property->update();
|
||||
}
|
||||
}
|
||||
|
||||
void DBusPropertyGroup::updateAllViaGetAll() {
|
||||
qCDebug(logDbus).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(logDbus).noquote()
|
||||
<< "Error updating properties of" << this->toString() << "via GetAll";
|
||||
qCWarning(logDbus) << reply.error();
|
||||
} else {
|
||||
qCDebug(logDbus).noquote() << "Received GetAll property set for" << this->toString();
|
||||
this->updatePropertySet(reply.value());
|
||||
}
|
||||
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties) {
|
||||
for (const auto [name, value]: properties.asKeyValueRange()) {
|
||||
auto prop = std::find_if(
|
||||
this->properties.begin(),
|
||||
this->properties.end(),
|
||||
[&name](AbstractDBusProperty* prop) { return prop->name == name; }
|
||||
);
|
||||
|
||||
if (prop == this->properties.end()) {
|
||||
qCDebug(logDbus) << "Ignoring untracked property update" << name << "for" << this;
|
||||
} else {
|
||||
(*prop)->tryUpdate(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString DBusPropertyGroup::toString() const {
|
||||
if (this->interface == nullptr) {
|
||||
return "{ DISCONNECTED }";
|
||||
} else {
|
||||
return this->interface->service() + this->interface->path() + "/"
|
||||
+ this->interface->interface();
|
||||
}
|
||||
}
|
||||
|
||||
void DBusPropertyGroup::onPropertiesChanged(
|
||||
const QString& interfaceName,
|
||||
const QVariantMap& changedProperties,
|
||||
const QStringList& invalidatedProperties
|
||||
) {
|
||||
if (interfaceName != this->interface->interface()) return;
|
||||
qCDebug(logDbus) << "Received property change set and invalidations for" << this->toString();
|
||||
|
||||
for (const auto& name: invalidatedProperties) {
|
||||
auto prop = std::find_if(
|
||||
this->properties.begin(),
|
||||
this->properties.end(),
|
||||
[&name](AbstractDBusProperty* prop) { return prop->name == name; }
|
||||
);
|
||||
|
||||
if (prop == this->properties.end()) {
|
||||
qCDebug(logDbus) << "Ignoring untracked property invalidation" << name << "for" << this;
|
||||
} else {
|
||||
(*prop)->update();
|
||||
}
|
||||
}
|
||||
|
||||
this->updatePropertySet(changedProperties);
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
189
src/dbus/dbusutil.hpp
Normal file
189
src/dbus/dbusutil.hpp
Normal file
|
@ -0,0 +1,189 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusabstractinterface.h>
|
||||
#include <qdbuserror.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbusreply.h>
|
||||
#include <qdbusservicewatcher.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
class DBusPropertiesInterface;
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(logDbus);
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
QDBusError demarshallVariant(const QVariant& variant, const QMetaType& type, void* slot);
|
||||
|
||||
template <typename T>
|
||||
class DBusResult {
|
||||
public:
|
||||
explicit DBusResult() = default;
|
||||
explicit DBusResult(T value): value(std::move(value)) {}
|
||||
explicit DBusResult(QDBusError error): error(std::move(error)) {}
|
||||
explicit DBusResult(T value, QDBusError error)
|
||||
: value(std::move(value))
|
||||
, error(std::move(error)) {}
|
||||
|
||||
bool isValid() { return !this->error.isValid(); }
|
||||
|
||||
T value;
|
||||
QDBusError error;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
DBusResult<T> demarshallVariant(const QVariant& variant) {
|
||||
T value;
|
||||
auto error = demarshallVariant(variant, QMetaType::fromType<T>(), &value);
|
||||
return DBusResult<T>(value, error);
|
||||
}
|
||||
|
||||
void asyncReadPropertyInternal(
|
||||
const QMetaType& type,
|
||||
QDBusAbstractInterface& interface,
|
||||
const QString& property,
|
||||
std::function<void(std::function<QDBusError(void*)>)> callback
|
||||
);
|
||||
|
||||
template <typename T>
|
||||
void asyncReadProperty(
|
||||
QDBusAbstractInterface& interface,
|
||||
const QString& property,
|
||||
std::function<void(T, QDBusError)> callback
|
||||
) {
|
||||
asyncReadPropertyInternal(
|
||||
QMetaType::fromType<T>(),
|
||||
interface,
|
||||
property,
|
||||
[callback](std::function<QDBusError(void*)> internalCallback) { // NOLINT
|
||||
T slot;
|
||||
auto error = internalCallback(static_cast<void*>(&slot));
|
||||
callback(slot, error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class DBusPropertyGroup;
|
||||
|
||||
class AbstractDBusProperty: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit AbstractDBusProperty(QString name, const QMetaType& type, QObject* parent = nullptr)
|
||||
: QObject(parent)
|
||||
, name(std::move(name))
|
||||
, type(type) {}
|
||||
|
||||
[[nodiscard]] QString toString() const;
|
||||
[[nodiscard]] virtual QString valueString() = 0;
|
||||
|
||||
public slots:
|
||||
void update();
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
protected:
|
||||
virtual QDBusError read(const QVariant& variant) = 0;
|
||||
|
||||
private:
|
||||
void tryUpdate(const QVariant& variant);
|
||||
|
||||
DBusPropertyGroup* group = nullptr;
|
||||
|
||||
QString name;
|
||||
QMetaType type;
|
||||
|
||||
friend class DBusPropertyGroup;
|
||||
};
|
||||
|
||||
class DBusPropertyGroup: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit DBusPropertyGroup(
|
||||
QVector<AbstractDBusProperty*> properties = QVector<AbstractDBusProperty*>(),
|
||||
QObject* parent = nullptr
|
||||
);
|
||||
|
||||
void setInterface(QDBusAbstractInterface* interface);
|
||||
void attachProperty(AbstractDBusProperty* property);
|
||||
void updateAllDirect();
|
||||
void updateAllViaGetAll();
|
||||
[[nodiscard]] QString toString() const;
|
||||
|
||||
private slots:
|
||||
void onPropertiesChanged(
|
||||
const QString& interfaceName,
|
||||
const QVariantMap& changedProperties,
|
||||
const QStringList& invalidatedProperties
|
||||
);
|
||||
|
||||
private:
|
||||
void updatePropertySet(const QVariantMap& properties);
|
||||
|
||||
DBusPropertiesInterface* propertyInterface = nullptr;
|
||||
QDBusAbstractInterface* interface = nullptr;
|
||||
QVector<AbstractDBusProperty*> properties;
|
||||
|
||||
friend class AbstractDBusProperty;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class DBusProperty: public AbstractDBusProperty {
|
||||
public:
|
||||
explicit DBusProperty(QString name, QObject* parent = nullptr, T value = T())
|
||||
: AbstractDBusProperty(std::move(name), QMetaType::fromType<T>(), parent)
|
||||
, value(std::move(value)) {}
|
||||
|
||||
explicit DBusProperty(
|
||||
DBusPropertyGroup& group,
|
||||
QString name,
|
||||
QObject* parent = nullptr,
|
||||
T value = T()
|
||||
)
|
||||
: DBusProperty(std::move(name), parent, std::move(value)) {
|
||||
group.attachProperty(this);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString valueString() override {
|
||||
QString str;
|
||||
QDebug(&str) << this->value;
|
||||
return str;
|
||||
}
|
||||
|
||||
[[nodiscard]] T get() const { return this->value; }
|
||||
|
||||
void set(T value) {
|
||||
this->value = std::move(value);
|
||||
emit this->changed();
|
||||
}
|
||||
|
||||
protected:
|
||||
QDBusError read(const QVariant& variant) override {
|
||||
auto result = demarshallVariant<T>(variant);
|
||||
|
||||
if (result.isValid()) {
|
||||
this->set(std::move(result.value));
|
||||
}
|
||||
|
||||
return result.error;
|
||||
}
|
||||
|
||||
private:
|
||||
T value;
|
||||
|
||||
friend class DBusPropertyGroup;
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
26
src/dbus/org.freedesktop.DBus.Properties.xml
Normal file
26
src/dbus/org.freedesktop.DBus.Properties.xml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface_name" direction="in" type="s"/>
|
||||
<arg name="property_name" direction="in" type="s"/>
|
||||
<arg name="value" direction="out" type="v"/>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg name="interface_name" direction="in" type="s"/>
|
||||
<arg name="property_name" direction="in" type="s"/>
|
||||
<arg name="value" direction="in" type="v"/>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface_name" direction="in" type="s"/>
|
||||
<arg name="props" direction="out" type="a{sv}"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
|
||||
</method>
|
||||
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="s" name="interface_name"/>
|
||||
<arg type="a{sv}" name="changed_properties"/>
|
||||
<arg type="as" name="invalidated_properties"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap" />
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
Loading…
Reference in a new issue