Development/Tutorials/D-Bus/Creating Interfaces: Difference between revisions
m (Fix typo in file name) |
|||
(28 intermediate revisions by 14 users not shown) | |||
Line 1: | Line 1: | ||
{{TutorialBrowser| | {{TutorialBrowser| | ||
Line 16: | Line 17: | ||
== Lights: Defining The Interface == | == 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 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 {{qt|QDBusAbstractAdaptor}} 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 [http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format D-Bus specification]. | An interface as seen on the bus can be described using a standard XML format that is described in the [http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format D-Bus specification]. | ||
Line 22: | Line 23: | ||
Such XML might look like this example: | Such XML might look like this example: | ||
< | <syntaxhighlight lang="xml"> | ||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> | <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> | ||
<node> | <node> | ||
Line 38: | Line 39: | ||
</method> | </method> | ||
</interface> | </interface> | ||
</node></ | </node></syntaxhighlight> | ||
If one has used a | If one has used a D-Bus application such as <tt>qdbus</tt> (CLI) or <tt>qdbusviewer</tt> (Graphical) to explore the <tt>org.freedesktop.DBus.Introspectable.Introspect</tt> method, the above might look familiar. | ||
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 {{qt|QDBusAbstractAdaptor}} | 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 {{qt|QDBusAbstractAdaptor}} | ||
Line 50: | Line 51: | ||
We will be using the (fictitious) 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: | We will be using the (fictitious) 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: | ||
< | <syntaxhighlight lang="cpp-qt" line> | ||
#include <QObject> | #include <QObject> | ||
Line 75: | Line 76: | ||
void dbusCanNotSeeMe(); | void dbusCanNotSeeMe(); | ||
}; | }; | ||
</ | </syntaxhighlight> | ||
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: | 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: | ||
Line 88: | Line 89: | ||
We can also combine the above as we wish. To achieve the desired results in the above example then, we might adjust the class definition thusly: | We can also combine the above as we wish. To achieve the desired results in the above example then, we might adjust the class definition thusly: | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
#include <QObject> | #include <QObject> | ||
Line 112: | Line 113: | ||
void dbusCanNotSeeMe(); | void dbusCanNotSeeMe(); | ||
}; | }; | ||
</ | </syntaxhighlight> | ||
Note how we moved the methods we wish to export to to be public slots and marked the signal we want to export with <tt>Q_SCRIPTABLE</tt>. We will later then choose to create | Note how we moved the methods we wish to export to to be public slots and marked the signal we want to export with <tt>Q_SCRIPTABLE</tt>. We will later then 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. | We would then go about creating an implementation of this class as defined above. | ||
Line 126: | Line 127: | ||
Therefore, we may choose to call our interface example <tt>org.foo.Background</tt>. The easiest way to define this is to add a <tt>Q_CLASSINFO</tt> macro entry to our class definition: | Therefore, we may choose to call our interface example <tt>org.foo.Background</tt>. The easiest way to define this is to add a <tt>Q_CLASSINFO</tt> macro entry to our class definition: | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
class Background : QObject | class Background : QObject | ||
{ | { | ||
Q_OBJECT | Q_OBJECT | ||
Q_CLASSINFO("D-Bus Interface", "org.foo.Background") | Q_CLASSINFO("D-Bus Interface", "org.foo.Background") | ||
</ | </syntaxhighlight> | ||
The interface will now be known as <tt>org.foo.Background</tt>. | The interface will now be known as <tt>org.foo.Background</tt>. | ||
Line 141: | Line 142: | ||
=== qdbuscpp2xml === | === qdbuscpp2xml === | ||
To generate the XML we will be using the <tt>qdbuscpp2xml</tt> 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 | To generate the XML we will be using the <tt>qdbuscpp2xml</tt> 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 to define which methods to export using the following command line switches: | ||
{| | {| | ||
|- | |- | ||
|+ qdbuscpp2xml switches | |+ qdbuscpp2xml switches | ||
! Switch !! Exports | ! Switch !! Exports | ||
|- | |- | ||
| -S || all signals | | -S || all signals | ||
Line 167: | Line 168: | ||
In our example above we want to export all the public slots but only scriptable signals. Therefore we would use this command line: | In our example above we want to export all the public slots but only scriptable signals. Therefore we would use this command line: | ||
< | <syntaxhighlight lang="bash"> | ||
$> qdbuscpp2xml -M -s background.h -o org.foo.Background.xml | $> qdbuscpp2xml -M -s background.h -o org.foo.Background.xml | ||
</ | </syntaxhighlight> | ||
This produces a file named {{path|org.foo. | This produces a file named {{path|org.foo.Background.xml}} which contains this: | ||
< | <syntaxhighlight lang="xml"> | ||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> | |||
<node> | <node> | ||
<interface name="org.foo.Background"> | <interface name="org.foo.Background"> | ||
Line 188: | Line 190: | ||
</method> | </method> | ||
</interface> | </interface> | ||
</node></ | </node></syntaxhighlight> | ||
This file should be shipped with your project's source distribution. | This file should be shipped with your project's source distribution. | ||
=== CMake === | === Call qdbuscpp2xml using CMake === | ||
<syntaxhighlight lang="bash"> | |||
set(my_nice_project_SRC | |||
${my_nice_project_SRC} | |||
${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.xml | |||
) | |||
qt5_generate_dbus_interface( | |||
my_awesome_header_file.h | |||
org.kde.my_cool_interface.xml | |||
OPTIONS -a | |||
) | |||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.my_cool_interface.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR}) | |||
</syntaxhighlight> | |||
This will generate org.kde.my_cool_interface.xml from my_awesome_header_file.h and install it in ${DBUS_INTERFACES_INSTALL_DIR} (wich usally means /usr/share/dbus-1/interfaces). | |||
Note: Adding the xml as a source of your project (in first line, assuming your project sources are in a variable called my_nice_project_SRC) is a workaround to cause the xml file to be a dependency and thus to be generated. | |||
=== 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: | Next we add this XML file to our project. This is done by adding the following line to the CMakeLists.txt file: | ||
< | <syntaxhighlight lang="bash"> | ||
qt5_add_dbus_adaptor(foo_SRCS org.foo.Background.xml | |||
background.h Background) | background.h Background) | ||
</ | </syntaxhighlight> | ||
This will cause two files, in this case {{path|backgroundadaptor.h}} and {{path|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. | This will cause two files, in this case {{path|backgroundadaptor.h}} and {{path|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. | ||
Line 211: | Line 232: | ||
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: | 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: | ||
< | <syntaxhighlight lang="cpp-qt" line> | ||
#include "background.h" | #include "background.h" | ||
#include "backgroundadaptor.h" | #include "backgroundadaptor.h" | ||
Line 221: | Line 242: | ||
QDBusConnection dbus = QDBusConnection::sessionBus(); | QDBusConnection dbus = QDBusConnection::sessionBus(); | ||
dbus.registerObject("/Background", this); | dbus.registerObject("/Background", this); | ||
dbus.registerService("org.foo.Background"); | |||
} | } | ||
</ | </syntaxhighlight> | ||
It's that simple. Since the generated adaptor is a {{qt|QObject}} when we pass it <tt>this</tt> it not only will be deleted when our <tt>Background</tt> object is deleted but it will bind itself to <tt>this</tt> for the purposes of forwarding D-Bus calls. | It's that simple. Since the generated adaptor is a {{qt|QObject}} when we pass it <tt>this</tt> it not only will be deleted when our <tt>Background</tt> object is deleted but it will bind itself to <tt>this</tt> for the purposes of forwarding D-Bus calls. | ||
We then need to register our object on the bus by calling <tt>{qt|QDBusConnection}}::registerObject</tt> | We then need to register our object on the bus by calling <tt>{{qt|QDBusConnection}}::registerObject</tt> and expose the interface for others to use by calling <tt>{{qt|QDBusConnection}}::registerService</tt>. | ||
{{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 <tt>this</tt> pointer may come in handy.}} | {{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 <tt>this</tt> pointer may come in handy.}} | ||
== See also == | |||
* [http://websvn.kde.org/?view=rev&revision=706966 commit adding dbus to ktimetracker] | |||
* [http://websvn.kde.org/?view=rev&revision=772290 adding dbus to krep], [http://websvn.kde.org/trunk/playground/utils/krep/krep.cpp?r1=772290&r2=772472 addition] | |||
[[Category:C++]] | [[Category:C++]] |
Latest revision as of 11:15, 17 April 2020
Tutorial Series | D-Bus |
Previous | Introduction |
What's Next | Autostart Services |
Further Reading | n/a |
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 QDBusAbstractAdaptor 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 D-Bus specification.
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 such as qdbus (CLI) or qdbusviewer (Graphical) to explore the org.freedesktop.DBus.Introspectable.Introspect method, the above might look familiar.
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 QDBusAbstractAdaptor
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 (fictitious) 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 in the above example then, we might adjust the class definition thusly:
#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 then 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.
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 so as to prevent name collisions. Therefore if the domain for your project website is http://foo.org you should prefix your interface names with org.foo. This is a very common approach to namespacing such exported components.
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
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 outset of this tutorial.
qdbuscpp2xml
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 to 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. Therefore we would use this command line:
$> 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.
Call qdbuscpp2xml using CMake
set(my_nice_project_SRC
${my_nice_project_SRC}
${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.xml
)
qt5_generate_dbus_interface(
my_awesome_header_file.h
org.kde.my_cool_interface.xml
OPTIONS -a
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.my_cool_interface.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR})
This will generate org.kde.my_cool_interface.xml from my_awesome_header_file.h and install it in ${DBUS_INTERFACES_INSTALL_DIR} (wich usally means /usr/share/dbus-1/interfaces).
Note: Adding the xml as a source of your project (in first line, assuming your project sources are in a variable called my_nice_project_SRC) is a workaround to cause the xml file to be a dependency and thus to be generated.
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(foo_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.
The generated class will take a Background* in its constructor and will include background.h.
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 accessing D-Bus interfaces.
Action: Instantiating the Interface At Runtime
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");
}
It's that simple. Since the generated adaptor is a QObject when we pass it 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.