#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