Make QGenericArgument/Q_ARG from QVariant

760 views Asked by At

I wrote an interface for systemd-timedated:

#include <QtDBus>

#include <dbus/dbus.h>

Q_DECLARE_LOGGING_CATEGORY(timeDateInterfaceCategory)

#define TIMEDATE_DBUS_SERVICE "org.freedesktop.timedate1"
#define TIMEDATE_DBUS_PATH "/org/freedesktop/timedate1"
#define TIMEDATE_DBUS_INTERFACE "org.freedesktop.timedate1"

class TimeDateInterface
        : public QDBusAbstractInterface
{

    Q_OBJECT

    Q_PROPERTY(bool CanNTP MEMBER CanNTP NOTIFY CanNTPChanged)
    Q_PROPERTY(bool LocalRTC MEMBER LocalRTC NOTIFY LocalRTCChanged)
    Q_PROPERTY(bool NTP MEMBER NTP NOTIFY NTPChanged)
    Q_PROPERTY(bool NTPSynchronized MEMBER NTPSynchronized NOTIFY NTPSynchronizedChanged)
    Q_PROPERTY(qulonglong RTCTimeUSec MEMBER RTCTimeUSec NOTIFY RTCTimeUSecChanged)
    Q_PROPERTY(qulonglong TimeUSec MEMBER TimeUSec NOTIFY TimeUSecChanged)
    Q_PROPERTY(QString Timezone MEMBER Timezone NOTIFY TimezoneChanged)

public :

    explicit
    TimeDateInterface(QObject * const parent = Q_NULLPTR)
        : QDBusAbstractInterface{{TIMEDATE_DBUS_SERVICE}, {TIMEDATE_DBUS_PATH}, TIMEDATE_DBUS_INTERFACE,
                                 QDBusConnection::systemBus(),
                                 parent}
    {
        qDBusRegisterMetaType< QVariantMap >();

        if (!isValid()) {
            qCCritical(timeDateInterfaceCategory).noquote()
                    << tr("Unable to create interface %1: %2")
                       .arg(service(), lastError().message());
            return;
        }

        if (!connection().connect({service()}, path(), {DBUS_INTERFACE_PROPERTIES}, {"PropertiesChanged"},
                                  //QString::fromLatin1(QMetaObject::normalizedSignature("PropertiesChanged(QString,QVariantMap,QStringList)")),
                                  this, SLOT(propertiesChanged(QString, QVariantMap, QStringList)))) {
            Q_ASSERT(false);
        }
    }

public Q_SLOTS :

    Q_SCRIPTABLE
    void SetLocalRTC(bool localRtc, bool fixSystem, bool userInteraction)
    {
        const auto message = call(QDBus::BlockWithGui, {"SetLocalRTC"},
                                  QVariant::fromValue(localRtc),
                                  QVariant::fromValue(fixSystem),
                                  QVariant::fromValue(userInteraction));
        QDBusPendingReply<> pendingReply = message;
        Q_ASSERT(pendingReply.isFinished());
        if (pendingReply.isError()) {
            qCWarning(timeDateInterfaceCategory).noquote()
                    << tr("Asynchronous call finished with error: %1")
                       .arg(pendingReply.error().message());
            return;
        }
    }

    Q_SCRIPTABLE
    void SetNTP(bool useNtp, bool userInteraction)
    {
        const auto message = call(QDBus::BlockWithGui, {"SetNTP"},
                                  QVariant::fromValue(useNtp),
                                  QVariant::fromValue(userInteraction));
        QDBusPendingReply<> pendingReply = message;
        Q_ASSERT(pendingReply.isFinished());
        if (pendingReply.isError()) {
            qCWarning(timeDateInterfaceCategory).noquote()
                    << tr("Asynchronous call finished with error: %1")
                       .arg(pendingReply.error().message());
            return;
        }
    }

    Q_SCRIPTABLE
    void SetTime(qlonglong usecUtc, bool relative, bool userInteraction)
    {
        const auto message = call(QDBus::BlockWithGui, {"SetTime"},
                                  QVariant::fromValue(usecUtc),
                                  QVariant::fromValue(relative),
                                  QVariant::fromValue(userInteraction));
        QDBusPendingReply<> pendingReply = message;
        Q_ASSERT(pendingReply.isFinished());
        if (pendingReply.isError()) {
            qCWarning(timeDateInterfaceCategory).noquote()
                    << tr("Asynchronous call finished with error: %1")
                       .arg(pendingReply.error().message());
            return;
        }
    }

    Q_SCRIPTABLE
    void SetTimezone(QString timezone, bool userInteraction)
    {
        const auto message = call(QDBus::BlockWithGui, {"SetTimezone"},
                                  QVariant::fromValue(timezone),
                                  QVariant::fromValue(userInteraction));
        QDBusPendingReply<> pendingReply = message;
        Q_ASSERT(pendingReply.isFinished());
        if (pendingReply.isError()) {
            qCWarning(timeDateInterfaceCategory).noquote()
                    << tr("Asynchronous call finished with error: %1")
                       .arg(pendingReply.error().message());
            return;
        }
    }

private Q_SLOTS :

    void propertyChanged(QString const & propertyName)
    {
        const auto signature = QStringLiteral("%1Changed()").arg(propertyName);
        const int signalIndex = staticMetaObject.indexOfSignal(QMetaObject::normalizedSignature(qUtf8Printable(signature)).constData());
        if (signalIndex < 0) {
            qCCritical(timeDateInterfaceCategory).noquote()
                    << tr("There is no signal with %1 signature")
                       .arg(signature);
            return;
        }
        const auto signal = staticMetaObject.method(signalIndex);
        if (!signal.invoke(this, Qt::DirectConnection)) {
            qCCritical(timeDateInterfaceCategory).noquote()
                    << tr("Unable to emit %1 signal for %2 property")
                       .arg(signature, propertyName);
        }
    }

    void propertiesChanged(QString interfaceName, QVariantMap changedProperties, QStringList invalidatedProperties)
    {
        if (interfaceName != interface()) {
            return;
        }
        QMapIterator< QString, QVariant > i{changedProperties};
        while (i.hasNext()) {
            propertyChanged(i.next().key());
        }
        for (QString const & invalidatedProperty : invalidatedProperties) {
            propertyChanged(invalidatedProperty);
        }
    }

Q_SIGNALS :

    void CanNTPChanged();
    void LocalRTCChanged();
    void NTPChanged();
    void NTPSynchronizedChanged();
    void RTCTimeUSecChanged();
    void TimeUSecChanged();
    void TimezoneChanged();

private :

    bool CanNTP;
    bool LocalRTC;
    bool NTP;
    bool NTPSynchronized;
    qulonglong RTCTimeUSec;
    qulonglong TimeUSec;
    QString Timezone;

};

On my system qdbus --system org.freedesktop.timedate1 /org/freedesktop/timedate1 gives:

method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()
method QString org.freedesktop.DBus.Introspectable.Introspect()
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface, QVariantMap changed_properties, QStringList invalidated_properties)
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface, QString property)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface)
method void org.freedesktop.DBus.Properties.Set(QString interface, QString property, QDBusVariant value)
property read bool org.freedesktop.timedate1.CanNTP
property read bool org.freedesktop.timedate1.LocalRTC
property read bool org.freedesktop.timedate1.NTP
property read bool org.freedesktop.timedate1.NTPSynchronized
property read qulonglong org.freedesktop.timedate1.RTCTimeUSec
property read qulonglong org.freedesktop.timedate1.TimeUSec
property read QString org.freedesktop.timedate1.Timezone
method void org.freedesktop.timedate1.SetLocalRTC(bool, bool, bool)
method void org.freedesktop.timedate1.SetNTP(bool, bool)
method void org.freedesktop.timedate1.SetTime(qlonglong, bool, bool)
method void org.freedesktop.timedate1.SetTimezone(QString, bool)

I consider it is a natural to use PropertiesChanged D-Bus signal to emit somePropertyChanged signals per property. It just connected to propertiesChanged dispatcher slot. I need it to emit somePropertyChanged signals from QML-side singletone (which, in turn, just translates D-Bus properties names to QML properties names (decapitalizing, conversion from usecs since Epoch to QDateTime etc)).

There is requirements in Qt Property System to NOTIFY signal prototype:

NOTIFY signals for MEMBER variables must take zero or one parameter, which must be of the same type as the property.

In QML-side handler of somePropertyChanged signal:

Connections {
    target: CppSingleton
    onSomePropertyChanged: {
        if (someProperty) {
            //
        }
    }
}

symbol someProperty is accessible only if somePropertyChanged signal is declared unary, not nullary. It is handy to have someProperty accessible in signal handler.

When I dispatch PropertiesChanged D-Bus signal, I have a name of a property. It allows to create signal's prototype string "somePropertyChanged()" to get corresponding QObject's method index. Also I have a new value for the property changed. But it is enclosed into QVariant. QMetaMethod::invoke, in turn, accepts Q_ARG values. Therefore I have to use only nullary *Changed signals.

Under the hood Q_ARG is just a QArgument: a pair of typename (const char *) and type-erased value (void *). Evidently it is variant-concept-like thing. I want to convert QVariant to QGenericArgument. Is it possible in general case?

QVariant has similar to QGenericArgument's constructor: QVariant::QVariant(int typeId, const void *copy). But I want to extract both these values. I think it is enough (with aid of some Qt's RTTI, e.g. QVariant's const char *typeToName(int typeId)) to achieve desired.

ADDITIONAL:

There are undocumented methods in QVariant:

void *data();
const void *constData() const;
inline const void *data() const { return constData(); }

Maybe I can use them. But problem of ownership is arose in the case.

ADDITIONAL 2:

Seems QGenericArgument is non-owning being. In conjunction with Qt::DirectConnection in-place-like method call I can use QVariant::data to get acces to QVaraint's internals.

ADDITIONAL 3:

QDBus has a mess with QDBusVariant/QVariant and QDBusArgument internal conversions/representation. It seems unpredictable which "typeinfo" for each property is correctly extracted. (It can be solved using undocumented qdbus_cast< T >(const QVariant &) cast).

0

There are 0 answers