forked from quickshell/quickshell
331 lines
12 KiB
C++
331 lines
12 KiB
C++
#pragma once
|
|
|
|
#include <functional>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include <bit>
|
|
#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 <qoverload.h>
|
|
#include <qstringview.h>
|
|
#include <qtclasshelpermacros.h>
|
|
#include <qtmetamacros.h>
|
|
#include <qvariant.h>
|
|
|
|
#include "../core/util.hpp"
|
|
|
|
class DBusPropertiesInterface;
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(logDbusProperties);
|
|
|
|
namespace qs::dbus {
|
|
|
|
QDBusError demarshallVariant(const QVariant& variant, const QMetaType& type, void* slot);
|
|
|
|
template <typename T>
|
|
class DBusResult {
|
|
public:
|
|
explicit DBusResult() = default;
|
|
DBusResult(T value): value(std::move(value)) {}
|
|
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,
|
|
const 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 DBusPropertyCore {
|
|
public:
|
|
DBusPropertyCore() = default;
|
|
virtual ~DBusPropertyCore() = default;
|
|
Q_DISABLE_COPY_MOVE(DBusPropertyCore);
|
|
|
|
[[nodiscard]] virtual QString name() const = 0;
|
|
[[nodiscard]] virtual QStringView nameRef() const = 0;
|
|
[[nodiscard]] virtual QString valueString() = 0;
|
|
[[nodiscard]] virtual bool isRequired() const = 0;
|
|
[[nodiscard]] bool exists() const { return this->mExists; }
|
|
|
|
protected:
|
|
virtual QDBusError store(const QVariant& variant) = 0;
|
|
[[nodiscard]] virtual QVariant serialize() = 0;
|
|
|
|
private:
|
|
bool mExists : 1 = false;
|
|
|
|
friend class DBusPropertyGroup;
|
|
};
|
|
|
|
// Default implementation with no transformation
|
|
template <typename T>
|
|
struct DBusDataTransform {
|
|
using Wire = T;
|
|
using Data = T;
|
|
};
|
|
|
|
namespace bindable_p {
|
|
|
|
template <typename T>
|
|
struct BindableParams;
|
|
|
|
template <template <typename, typename, auto, auto> class B, typename C, typename T, auto O, auto S>
|
|
struct BindableParams<B<C, T, O, S>> {
|
|
using Class = C;
|
|
using Type = T;
|
|
static constexpr size_t OFFSET = O;
|
|
};
|
|
|
|
template <typename Bindable>
|
|
struct BindableType {
|
|
using Meta = BindableParams<Bindable>;
|
|
using Type = Meta::Type;
|
|
};
|
|
|
|
template <typename T, typename = void>
|
|
struct HasFromWire: std::false_type {};
|
|
|
|
template <typename T>
|
|
struct HasFromWire<T, std::void_t<decltype(T::fromWire(std::declval<typename T::Wire>()))>>
|
|
: std::true_type {};
|
|
|
|
template <typename T, typename = void>
|
|
struct HasToWire: std::false_type {};
|
|
|
|
template <typename T>
|
|
struct HasToWire<T, std::void_t<decltype(T::toWire(std::declval<typename T::Data>()))>>
|
|
: std::true_type {};
|
|
|
|
} // namespace bindable_p
|
|
|
|
template <
|
|
typename T,
|
|
auto offset,
|
|
auto bindablePtr,
|
|
auto updatedPtr,
|
|
auto groupPtr,
|
|
StringLiteral16 Name,
|
|
bool required>
|
|
class DBusBindableProperty: public DBusPropertyCore {
|
|
using PtrMeta = MemberPointerTraits<decltype(bindablePtr)>;
|
|
using Owner = PtrMeta::Class;
|
|
using Bindable = PtrMeta::Type;
|
|
using BindableType = bindable_p::BindableType<Bindable>::Type;
|
|
using BaseType = std::conditional_t<std::is_void_v<T>, BindableType, T>;
|
|
using Transform = DBusDataTransform<BaseType>;
|
|
using DataType = Transform::Data;
|
|
using WireType = Transform::Wire;
|
|
|
|
public:
|
|
explicit DBusBindableProperty() { this->group()->attachProperty(this); }
|
|
|
|
[[nodiscard]] QString name() const override { return Name; };
|
|
[[nodiscard]] QStringView nameRef() const override { return Name; };
|
|
[[nodiscard]] bool isRequired() const override { return required; };
|
|
|
|
[[nodiscard]] QString valueString() override {
|
|
QString str;
|
|
QDebug(&str) << this->bindable()->value();
|
|
return str;
|
|
}
|
|
|
|
void write() { this->group()->pushPropertyUpdate(this); }
|
|
void requestUpdate() { this->group()->requestPropertyUpdate(this); }
|
|
|
|
protected:
|
|
QDBusError store(const QVariant& variant) override {
|
|
DBusResult<DataType> result;
|
|
|
|
if constexpr (bindable_p::HasFromWire<Transform>::value) {
|
|
auto wireResult = demarshallVariant<WireType>(variant);
|
|
if (!wireResult.isValid()) return wireResult.error;
|
|
result = Transform::fromWire(std::move(wireResult.value));
|
|
} else if constexpr (std::is_same_v<WireType, BaseType>) {
|
|
result = demarshallVariant<DataType>(variant);
|
|
} else {
|
|
return QDBusError(QDBusError::NotSupported, "This property cannot be deserialized");
|
|
}
|
|
|
|
if (!result.isValid()) return result.error;
|
|
this->bindable()->setValue(std::move(result.value));
|
|
|
|
if constexpr (updatedPtr != nullptr) {
|
|
(this->owner()->*updatedPtr)();
|
|
}
|
|
|
|
return QDBusError();
|
|
}
|
|
|
|
QVariant serialize() override {
|
|
if constexpr (bindable_p::HasToWire<Transform>::value) {
|
|
return QVariant::fromValue(Transform::toWire(this->bindable()->value()));
|
|
} else if constexpr (std::is_same_v<WireType, BaseType>) {
|
|
return QVariant::fromValue(this->bindable()->value());
|
|
} else {
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
private:
|
|
[[nodiscard]] constexpr Owner* owner() const {
|
|
auto* self = std::bit_cast<char*>(this);
|
|
return std::bit_cast<Owner*>(self - offset()); // NOLINT
|
|
}
|
|
|
|
[[nodiscard]] constexpr DBusPropertyGroup* group() const { return &(this->owner()->*groupPtr); }
|
|
[[nodiscard]] constexpr Bindable* bindable() const { return &(this->owner()->*bindablePtr); }
|
|
};
|
|
|
|
class DBusPropertyGroup: public QObject {
|
|
Q_OBJECT;
|
|
|
|
public:
|
|
explicit DBusPropertyGroup(QVector<DBusPropertyCore*> properties = {}, QObject* parent = nullptr);
|
|
explicit DBusPropertyGroup(QObject* parent): DBusPropertyGroup({}, parent) {}
|
|
|
|
void setInterface(QDBusAbstractInterface* interface);
|
|
void attachProperty(DBusPropertyCore* property);
|
|
void updateAllDirect();
|
|
void updateAllViaGetAll();
|
|
[[nodiscard]] QString toString() const;
|
|
|
|
void pushPropertyUpdate(DBusPropertyCore* property);
|
|
void requestPropertyUpdate(DBusPropertyCore* property);
|
|
|
|
signals:
|
|
void getAllFinished();
|
|
void getAllFailed(QDBusError error);
|
|
|
|
private slots:
|
|
void onPropertiesChanged(
|
|
const QString& interfaceName,
|
|
const QVariantMap& changedProperties,
|
|
const QStringList& invalidatedProperties
|
|
);
|
|
|
|
private:
|
|
void updatePropertySet(const QVariantMap& properties, bool complainMissing);
|
|
void tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant) const;
|
|
[[nodiscard]] QString propertyString(const DBusPropertyCore* property) const;
|
|
|
|
DBusPropertiesInterface* propertyInterface = nullptr;
|
|
QDBusAbstractInterface* interface = nullptr;
|
|
QVector<DBusPropertyCore*> properties;
|
|
|
|
friend class AbstractDBusProperty;
|
|
};
|
|
|
|
} // namespace qs::dbus
|
|
|
|
// NOLINTBEGIN
|
|
#define QS_DBUS_BINDABLE_PROPERTY_GROUP(Class, name) qs::dbus::DBusPropertyGroup name {this};
|
|
|
|
#define QS_DBUS_PROPERTY_BINDING_P( \
|
|
Class, \
|
|
Type, \
|
|
property, \
|
|
bindable, \
|
|
updated, \
|
|
group, \
|
|
name, \
|
|
required \
|
|
) \
|
|
static constexpr size_t _qs_property_##property##_offset() { return offsetof(Class, property); } \
|
|
\
|
|
qs::dbus::DBusBindableProperty< \
|
|
Type, \
|
|
&Class::_qs_property_##property##_offset, \
|
|
&Class::bindable, \
|
|
updated, \
|
|
&Class::group, \
|
|
u##name, \
|
|
required> \
|
|
property;
|
|
|
|
#define QS_DBUS_PROPERTY_BINDING_8( \
|
|
Class, \
|
|
Type, \
|
|
property, \
|
|
bindable, \
|
|
updated, \
|
|
group, \
|
|
name, \
|
|
required \
|
|
) \
|
|
QS_DBUS_PROPERTY_BINDING_P( \
|
|
Class, \
|
|
Type, \
|
|
property, \
|
|
bindable, \
|
|
&Class::updated, \
|
|
group, \
|
|
name, \
|
|
required \
|
|
)
|
|
|
|
#define QS_DBUS_PROPERTY_BINDING_7(Class, Type, property, bindable, group, name, required) \
|
|
QS_DBUS_PROPERTY_BINDING_P(Class, Type, property, bindable, nullptr, group, name, required)
|
|
|
|
#define QS_DBUS_PROPERTY_BINDING_6(Class, property, bindable, group, name, required) \
|
|
QS_DBUS_PROPERTY_BINDING_7(Class, void, property, bindable, group, name, required)
|
|
|
|
#define QS_DBUS_PROPERTY_BINDING_5(Class, property, bindable, group, name) \
|
|
QS_DBUS_PROPERTY_BINDING_6(Class, property, bindable, group, name, true)
|
|
|
|
// Q_OBJECT_BINDABLE_PROPERTY elides the warning for the exact same reason,
|
|
// so we consider it safe to disable the warning.
|
|
// clang-format off
|
|
#define QS_DBUS_PROPERTY_BINDING(...) \
|
|
QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
|
|
QT_OVERLOADED_MACRO(QS_DBUS_PROPERTY_BINDING, __VA_ARGS__) \
|
|
QT_WARNING_POP
|
|
// clang-format on
|
|
// NOLINTEND
|