6.0.0-rc4Source

Using Nt D-Bus

Connecting to a bus, exporting an object with an adaptor, calling it, receiving its signals, and the types that travel over D-Bus.

Link Nt::DBus (or Nt6::DBus from an installed package) and include <QtDBus>:

find_package(Nt6 6 REQUIRED)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE Nt6::DBus)

Nt D-Bus speaks the wire protocol itself, so it needs neither libdbus nor a Qt installation. It works with any D-Bus implementation: dbus-daemon, dbus-broker, GLib's GDBus and sd-bus.

Connecting

QDBusConnection::sessionBus() and QDBusConnection::systemBus() return the connections to the standard buses, which are opened the first time. The session bus is found through DBUS_SESSION_BUS_ADDRESS, or $XDG_RUNTIME_DIR/bus. Check the connection before using it:

QDBusConnection bus = QDBusConnection::sessionBus();
if (!bus.isConnected()) {
    std::fprintf(stderr, "No session bus: %s\n", qPrintable(bus.lastError().message()));
    return 1;
}
auto *clock = new Clock;
new ClockAdaptor(clock);
bus.registerObject("/clock", clock);
if (!bus.registerService("org.example.Clock")) {
    std::fprintf(stderr, "org.example.Clock is taken: %s\n", qPrintable(bus.lastError().message()));
    return 1;
}

Nt starts one thread that reads and writes the sockets of all connections. Replies and signals are delivered to objects through the event loops of the threads they live in, so run QCoreApplication::exec(), or a QEventLoop, in those threads.

Exporting an Object

An adaptor is a small class that a QObject owns. Its public slots, signals and properties form one D-Bus interface, named by the "D-Bus Interface" class information:

class Clock : public QObject {
    Q_OBJECT
public:
    QString now() const { return QDateTime::currentDateTime().toString(Qt::ISODate); }
signals:
    void alarm(const QString &message);
};

class ClockAdaptor : public QDBusAbstractAdaptor {
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.example.Clock")
    Q_PROPERTY(int volume READ volume WRITE setVolume)
public:
    explicit ClockAdaptor(Clock *clock) : QDBusAbstractAdaptor(clock), m_clock(clock) {
        setAutoRelaySignals(true); // Clock::alarm() is emitted as ClockAdaptor::alarm()
    }
    int volume() const { return m_volume; }
    void setVolume(int volume) { m_volume = volume; }

public slots:
    QString Now() { return m_clock->now(); }
    void SetAlarm(int seconds, const QString &message) {
        QTimer::singleShot(seconds * 1000, m_clock, [this, message] { emit m_clock->alarm(message); });
    }

signals:
    void alarm(const QString &message);

private:
    Clock *m_clock;
    int m_volume = 5;
};

Register the object with QDBusConnection::registerObject() and claim a name with QDBusConnection::registerService(). The bus then routes calls to the object, and the connection answers org.freedesktop.DBus.Introspectable, org.freedesktop.DBus.Properties and org.freedesktop.DBus.Peer for it. A slot that inherits QDBusContext can reply later or send an error.

A slot's parameters are its input arguments; non-const references are output arguments, after the return value. A last parameter of the type QDBusMessage receives the call.

Calling an Object

QDBusInterface calls methods by name, and reads and writes properties:

QDBusConnection bus = QDBusConnection::sessionBus();
QDBusInterface clock("org.example.Clock", "/clock", "org.example.Clock", bus);
QDBusReply<QString> now = clock.call("Now");
if (!now.isValid()) {
    std::fprintf(stderr, "Cannot reach the clock: %s\n", qPrintable(now.error().message()));
    return 1;
}
std::printf("It is %s, volume %d\n", qPrintable(now.value()), clock.property("volume").toInt());

For an interface used often, a subclass of QDBusAbstractInterface gives typed methods, and its signals are emitted when the remote object emits them:

class ClockInterface : public QDBusAbstractInterface {
    Q_OBJECT
public:
    explicit ClockInterface(const QDBusConnection &connection, QObject *parent = nullptr)
        : QDBusAbstractInterface("org.example.Clock", "/clock", "org.example.Clock", connection, parent) {}

    QDBusPendingReply<QString> Now() { return asyncCall("Now"); }
    QDBusPendingReply<> SetAlarm(int seconds, const QString &message) {
        return asyncCall("SetAlarm", seconds, message);
    }

signals:
    void alarm(const QString &message); // emitted when the remote clock emits it
};

call() blocks; asyncCall() returns a QDBusPendingCall, which a QDBusPendingCallWatcher reports on. QDBusReply and QDBusPendingReply convert the reply to C++ types and hold the error if the call failed.

Signals

QDBusConnection::connect() delivers a D-Bus signal to a slot, named with SLOT():

QDBusConnection::sessionBus().connect("org.example.Clock", "/clock", "org.example.Clock",
                                      "alarm", &window, SLOT(showAlarm(QString)));

QDBusServiceWatcher reports when names appear and disappear on the bus.

Types

The basic D-Bus types map to uchar, bool, short, ushort, int, uint, qlonglong, qulonglong, double, QString, QDBusObjectPath, QDBusSignature and QDBusUnixFileDescriptor. A QDBusVariant is a D-Bus variant. QStringList, QByteArray, QVariantList and QVariantMap are arrays and dictionaries. Structures and other containers need stream operators and qDBusRegisterMetaType(); see QDBusArgument.

Peer-to-Peer

QDBusServer listens on a socket of its own, and QDBusConnection::connectToPeer() connects to it. There is no bus in between, so there are no names and no QDBusConnectionInterface.

What Is Missing

Only Unix domain sockets are supported, with the EXTERNAL and ANONYMOUS authentication mechanisms. There are no qdbusxml2cpp, qdbuscpp2xml, qdbus or qdbusviewer tools: use gdbus or busctl to look at a bus. QDBusInterface has no meta-object built from introspection data.