Nova Flow OS
KDE Developer Platform
KDE Developer Platform
  • KDE Developer Platform
    • Getting started
      • Building KDE software
        • KDE software
        • Where to find the development team
        • Learning more
        • Choose what to work on
        • Source code cross-referencing
        • Installing build dependencies
        • Set up a development environment
        • Building KDE software with kdesrc-build
        • Basic troubleshooting
        • Tips and tricks
        • IDE Configuration
          • Setting up an IDE for KDE development
          • Visual Studio Code
          • Qt Creator
          • Kate
          • KDevelop
          • CLion
          • Sublime Text
        • Building KDE software manually
        • Building KDE software with distrobox and podman
      • Kirigami
        • KDE is ours
        • Setting up and getting started
        • Explaining pages
        • Layouts, ListViews, and Cards
        • Adding actions
        • Adding a dialog
        • Using separate files
        • Next steps
        • Colors and themes in Kirigami
        • Typography
        • Actions based components
        • Page rows and page stacks
        • Scrollable pages and list views
        • Cards
        • Drawers
        • Chips
        • Dialog types
        • Controls and interactive elements
        • Form layouts
        • Inline messages
        • Action toolbars
        • Progress bars and indicators
        • List views
        • Understanding CMakeLists
        • Figuring out main.cpp
        • Connect logic to your QML user interface
        • Connect models to your QML user interface
        • About page
        • Introduction to Kirigami Addons
        • FormCard About pages
        • Form delegates in your settings pages
      • KXmlGui
        • Getting started with KXmlGui
        • Hello World!
        • Creating the main window
        • Using actions
        • Saving and loading
        • Command line interface
      • Python with Kirigami
        • Apps with QML and Python
        • Your first Python + Kirigami application
        • Creating a Python package
        • Creating a Flatpak
      • Common programming mistakes
      • Adding a new KDE project
    • Features
      • Icons
      • Configuration
        • The KConfig Framework
        • Introduction to KConfig
        • Using KConfig XT
        • KDE Frameworks 6 porting guide
        • Settings module (KCM) development
        • KConfigDialog
      • D-Bus
        • What is D-Bus practically useful for?
        • Introduction to D-Bus
        • Accessing D-Bus interfaces
        • Intermediate D-Bus
        • Creating D-Bus interfaces
        • Using custom types with D-Bus
        • D-Bus autostart services
      • Create your own mouse cursor theme
      • Session management
      • Archives
      • Desktop file
      • KAuth
        • Privilege Escalation
        • Using actions in your applications
      • KIdleTime
      • Akonadi: personal information management
        • Debugging Akonadi Resources
        • Using Akonadi in applications
      • Concurrent programming
      • Solid
      • Sonnet
    • Plasma themes and plugins
      • Getting started
      • Plasma Widget tutorial
        • How to create a plasmoid
        • Setup
        • Porting Plasmoids to KF6
        • Testing
        • QML
        • Plasma's QML API
        • Widget Properties
        • Configuration
        • Translations / i18n
        • Examples
        • C++ API
      • KWin Effects
      • Plasma Desktop scripting
        • Javascript Interaction With Plasma Shells
        • Templates
        • Examples
        • API documentation
        • Configuration keys
      • Plasma Style tutorial
        • Creating a Plasma Style quickstart
        • Understanding Plasma Styles
        • SVG elements and Inkscape
        • Background SVG format
        • System and accent colors
        • Theme elements reference
        • Porting themes to Plasma 5
        • Porting themes to Plasma 6
      • Aurorae window decorations
      • KWin scripting tutorial
        • Quick start
        • KWin scripting API
      • Wallpapers
      • Plasma comic
        • Tutorial
        • Testing and debugging
        • Examples
      • Create a custom Window Switcher
      • KRunner C++ Plugin
        • Basic Anatomy of a Runner
        • KRunner metadata format
    • Applications
      • Creating sensor faces
      • Dolphin
        • Creating Dolphin service menus
      • Kate
        • Kate plugin tutorial
      • KMines
        • Making a KMines theme
      • Writing tests
        • Appium automation testing
    • Packaging
      • Android
        • KDE on Android
        • Building applications for Android
        • Packaging and publishing applications for Android
        • Publishing on Google Play
          • Introduction
          • Packaging your app
          • Adding your app to Google Play
          • Publishing your app
          • Releasing new versions of old apps
        • Porting applications to Android
          • Basic porting
          • Making applications run well on Android
          • Metadata
      • Windows
        • Packaging and publishing applications for Windows
        • Publish your app in the Microsoft Store
          • Packaging your app for the Microsoft Store
          • Submitting your app to the Microsoft Store
      • Plasma Mobile
        • KDE on mobile devices
        • Porting a new device to Plasma Mobile
        • KDE Telephony stack
          • General Overview
          • Kernel layer
          • System daemons
            • General overview
            • Developing Telephony functionality
            • ModemManager Telephony functions
          • Session daemons
          • QML declarative plugin layer
          • KDE application layer
        • Execute applications
      • Distributing KDE software as Flatpak
        • Your first Flatpak
        • Extending your package
        • Nightly Flatpaks and Flathub
        • Testing your Flatpak
    • System administration
      • Shell scripting with KDE dialogs
      • Kiosk: Simple configuration management for large deployment
        • Abstract
        • Introduction to Kiosk
        • Kiosk keys
    • Contribute to the documentation
    • About
      • Readme
      • License
        • Creative Commons Attribution-ShareAlike 4.0 International
        • GNU General Public License 3.0 or later
Powered by GitBook
On this page
  • Abstract
  • Lights: Defining The Interface
  • Camera: Generating the Interface
  • Action: Instantiating the Interface At Runtime
  1. KDE Developer Platform
  2. Features
  3. D-Bus

Creating D-Bus interfaces

Learn how to expose functionality in your application by creating and using custom D-Bus interfaces. Covers generating the XML descriptions, instantiating interfaces at run time and setting up the bui

PreviousIntermediate D-BusNextUsing custom types with D-Bus

Last updated 8 months ago

Abstract

D-Bus allows applications to expose internal API to the outside world by means of remotely callable interfaces. This tutorial shows how to create and implement such interfaces in your applications.

Lights: Defining The Interface

D-Bus interfaces generally reflect the API of one or more classes in the providing application. Bridging this API over to D-Bus is done using by creating a subclass that reacts to DBus messages and takes action directly. Usually, however, this simply results in one line methods that call similarly named methods in another object. This repetitive work can almost always be avoided by generating a D-Bus XML description file.

An interface as seen on the bus can be described using a standard XML format that is described in the .

Such XML might look like this example:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.foo.Background">
    <signal name="backgroundChanged">
    </signal>
    <method name="refreshBackground">
    </method>
    <method name="currentBackground">
      <arg type="s" direction="out"/>
    </method>
    <method name="setBackground">
      <arg type="b" direction="out"/>
      <arg name="name" type="s" direction="in"/>
    </method>
  </interface>
</node>

If one has used a D-Bus application like qdbus (terminal) or qdbusviewer (graphical) to explore the org.freedesktop.DBus.Introspectable.Introspect method, the above might look familiar.

Fortunately there are ways to automate the process so that it's hardly noticeable, namely: creating a class that includes the methods we wish to expose via D-Bus and using tools that come with Qt to do the rest for us.

Defining Methods

We will be using the example of an interface that lets the user set the background wallpaper and query the current settings. We will be providing three methods in this interface, which can be seen in the following class definition:

#include <QObject>

class Background : QObject
{
    Q_OBJECT

    public:
        Background(QObject* parent);

        void doNotExportToDBus();
        
        void refreshBackground();
        QString currentBackground();

    Q_SIGNALS:
        void doNotExportThisSignal();
        void backgroundChanged();

    public Q_SLOTS:
        bool setBackground(QString name);

    protected Q_SLOTS:
        void dbusCanNotSeeMe();
};

Next we need to mark which of the above methods we wish to expose via D-Bus. Fortunately, this is quite simple with the following options available to us:

  • export all signals

  • export all public slots

  • export all properties

  • export only scriptable signals

  • export only scriptable public slots

  • export only scriptable properties

We can also combine the above as we wish. To achieve the desired results from the above example, we need to adjust the class definition accordingly:

#include <QObject>
class Background : QObject
{
    Q_OBJECT

    public:
        Background(QObject* parent);

        void doNotExportToDBus();

    Q_SIGNALS:
        void doNotExportThisSignal();
        Q_SCRIPTABLE void backgroundChanged();

    public Q_SLOTS:
        void refreshBackground();
        QString currentBackground();
        bool setBackground(QString name);

    protected Q_SLOTS:
        void dbusCanNotSeeMe();
};

Note how we moved the methods we wish to export to to be public slots and marked the signal we want to export with Q_SCRIPTABLE. We will later choose to create an interface that exports all the public slots and all scriptable signals.

We would then go about creating an implementation of this class as defined above.

Tip

When exposing an API to other applications via D-Bus, other applications and users, via scripting, may come to rely on the calls available in the interface. Changing the D-Bus interface can therefore cause breakage for others. For this reason it is recommended to keep compatibility with publicly advertised D-Bus APIs over the lifespan of a major release of your application.

Naming The Interface

The next step after having defined our interface is to come up with a name that it will appear as on the bus. These names by convention take on the form of reverse domain names to prevent name collisions. Therefore if the domain for your project website is foo.org you should prefix your interface names with org.foo.

Therefore, we may choose to call our interface example org.foo.Background. The easiest way to define this is to add a Q_CLASSINFO macro entry to our class definition:

class Background : QObject
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.foo.Background")

The interface will now be known as org.foo.Background.

Camera: Generating the Interface

The Simple Way

The Complex But Portable Way

This way requires a more complicated build procedure, but if your project will be used by many other applications, this is probably the better way. It installs an XML file in the system, which other applications can use to generate their own adapter class. Anyone with your project installed can refer to this file without having to go to your project source.

Now that we have set up the interface in our class, we will want to generate an adaptor class that mediates between D-Bus and our application's objects. The first step is to generate the XML seen at the beginning of this tutorial.

You can generate the xml file manually by calling qdbuscpp2xml in a terminal, and ship the generated xml as part of the your project's source.

Another option is to make CMake to do it for you in compile time. This approach has the advantages of keeping your source code clean and not needing to remember to regenerate the xml file after each edit of the class.

Solution one: call qdbuscpp2xml manually

To generate the XML we will be using the qdbuscpp2xml command line tool that comes with Qt. This program takes a C++ source file and generates a D-Bus XML definition of the interface for us. It lets us define which methods to export using the following command line switches:

Switch
Exports

-S

all signals

-M

all public slots

-P

all properties

-A

all exportable items

-s

scriptable signals

-m

scriptable public slots

-p

scriptable properties

-a

all scriptable items

In our example above we want to export all the public slots but only scriptable signals. Therefor we would use this command:

$> qdbuscpp2xml -M -s background.h -o org.foo.Background.xml

This produces a file named org.foo.Background.xml which contains this:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.foo.Background">
    <signal name="backgroundChanged">
    </signal>
    <method name="refreshBackground">
    </method>
    <method name="currentBackground">
      <arg type="s" direction="out"/>
    </method>
    <method name="setBackground">
      <arg type="b" direction="out"/>
      <arg name="name" type="s" direction="in"/>
    </method>
  </interface>
</node>

This file should be shipped with your project's source distribution.

Solution two: Call qdbuscpp2xml using CMake (Preferred)

Add the following code to your project's CMakeLists.txt:

set(my_nice_project_SRCS 
    ${my_nice_project_SRCS}
    ${CMAKE_CURRENT_BINARY_DIR}/org.foo.Background.xml
)
qt5_generate_dbus_interface(
    background.h
    org.foo.Background.xml
    OPTIONS -a
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.foo.Background.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR})

This will generate org.foo.Background.xml from background.h and install it in ${DBUS_INTERFACES_INSTALL_DIR} (which usually means /usr/share/dbus-1/interfaces).

Export the interface using CMake

Next we add this XML file to our project. This is done by adding the following line to the CMakeLists.txt file:

qt5_add_dbus_adaptor(my_nice_project_SRCS org.foo.Background.xml
                     background.h Background)

This will cause two files, in this case backgroundadaptor.h and backgroundadaptor.cpp, to be generated in the build directory, built and added to the application at build time. You should not ship these files with your project's source distribution.

Action: Instantiating the Interface At Runtime

The Simple Way

#include <QDBusConnection>
Background::Background(QObject* parent)
    : QObject(parent)
{
    // register DBus object at org.kde.myapp/foobar
    QDBusConnection::sessionBus().registerService("org.kde.myapp");
    QDBusConnection::sessionBus().registerObject("/foobar", this, QDBusConnection::ExportScriptableContents);

    ... // the rest of constructor
} 

In line 6 we register the service path with DBus. This name should not be used by any other project. Note that if you have

    KLocalizedString::setApplicationDomain("myapp");
    KDBusService service(KDBusService::Unique);

in your main function, the service name org.kde.myapp is auto registered and you can safely omit line 6.

However, if you have multiple instances of this class, we need to edit above example to avoid path conflict. Replace QDBusConnection::sessionBus().registerObject("/foobar", this, QDBusConnection::ExportScriptableContents); with QDBusConnection::sessionBus().registerObject("/foobar/" + QString("YOUR UNIQUE INSTANCE IDENTIFIER"), this, QDBusConnection::ExportScriptableContents);

The Complex Way

Now that we have our interface created for us, all we need to do is create it at runtime. We do this by including the generated header file and instantiating an object, as seen in this example:

#include "background.h"
#include "backgroundadaptor.h"

Background::Background(QObject* parent)
    : QObject(parent)
{
    new BackgroundAdaptor(this);
    QDBusConnection dbus = QDBusConnection::sessionBus();
    dbus.registerObject("/Background", this);
    dbus.registerService("org.foo.Background");
}

Since the generated adaptor is a QObject, when we pass the constructor this it not only will be deleted when our Background object is deleted but it will bind itself to this for the purposes of forwarding D-Bus calls.

We then need to register our object on the bus by calling QDBusConnection::registerObject and expose the interface for others to use by calling QDBusConnection::registerService.

Tip

If there will be more than one of the same object created in your application then you will need to register each object with a unique path. If there is no well defined, unique naming scheme for your objects the this pointer may come in handy.

One can construct this XML by hand and manually map it to the API of a given class, but not only is this error prone and time consuming, it's not much fun. If it weren't for the fact that this XML can be used by other applications wishing to consume your D-Bus interface, one may as well write their own

The simplest way of generating interfaces is to use your class as interface directly. Just make sure your class has Q_CLASSINFO("D-Bus Interface", "org.foo.Background") under the Q_OBJECT macro. Now you can skip the next section and head to

The D-Bus XML description file will also be installed. This allows users to examine it as a reference and other applications to use this file to generate interface classes using qdbusxml2cpp as seen in the tutorial on .

You can use the generated adaptor to

You can use to register your class. Normally you'd do this in the constructor of your class, but if you have more than one instance of the same class, you need to ensure there is no DBus path conflict. For a singleton class, you may have something like this in the constructor:

In line 7 we register this class as an object at org.kde.myapp/foobar/org.foo.Background. Note that other than the case of / which is the root path, the first argument should never end with /. The second argument is the pointer to the class you want to expose to DBus, this class should be a subclass of QObject and have Q_CLASSINFO("D-Bus Interface", "org.foo.Background"). The third argument is the methods you want to exposed to DBus, for detailed information, head to .

QDBusAbstractAdaptor
D-Bus specification
QDBusAbstractAdaptor
accessing D-Bus interfaces
QDBusConnection::registerObject
Qt's documentation on QDBusConnection
Instantiating the Interface At Runtime
Instantiating the Interface At Runtime