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).