Development for Sailfish OS: working with D-Bus
Good day to all! This article continues the series of articles devoted to the development for the mobile platform Sailfish OS. Since the Linux kernel is at the heart of the operating system, Sailfish OS initially has some “goodies” that come from the Linux world. One of these “goodies” is the D-Bus interprocess communication system. For this article, I will assume that the reader is already familiar with what kind of system it is, why it is needed and how to use it (otherwise, information about this is quite easy to find on the network, for example, on the official website or on opennet ).
Despite the fact that D-Bus is initially supported in Sailfish OS, it is possible to manage it only from the terminal or from applications (if it is already embedded in them). That is why the idea arose of creating a visual client for the D-Bus system for Sailfish OS, which would allow viewing services registered in the system and interacting with them using a graphical interface. In other words, create an analogue of D-Feet or Qt D-Bus Viewer for Sailfish OS.
Any application in the system can create its own service (or service) that will implement one or more interfaces that are described by methods, signals and properties. An application can also interact with other applications through their services registered in D-Bus. For example, net.connman is registered in Sailfish OSimplementing the net.connman.Technology interface at / net / connman / technology / bluetooth . This interface also contains the SetProperty () method . By calling it as follows - SetProperty ("Powered", true) - you can enable Bluetooth on the device.
Actually, the application functionality should repeat that for analogues. Those. the application should allow viewing the list of services registered in the D-Bus (both session and system), for each such service, viewing the list of possible paths, for each path a list of interfaces, and for each interface showing lists of methods, properties and signals. In addition, the application should also allow calling these same methods and reading / changing properties.
The Sailfish SDK provides two options for interacting with D-Bus. Firstly, it is Nemo QML D-Bus Plugin , which allows you to interact with D-Bus directly from QML code. Secondly, it is Qt D-Bus - a standard Qt mechanism that provides C ++ classes for interacting with D-Bus. The difference between the two is that the former is fairly easy to use, while the latter provides more options. The application described in this article will describe both methods.
To get a list of services registered in D-Bus, use the DBusInterface element :
And the ListNames method of the above interface is called :
Upon successful call of the method, the list of services is populated. At the same time, filtering occurs to avoid displaying services with names of the form ": 1.44", etc. In case of an error when calling the method, the user is shown the corresponding dialog with the error message. The page itself looks like this:

By clicking on a service from the list, you go to a page with a list of all the possible paths for this service. To do this, as well as to obtain a list of interfaces, the org.freedesktop.DBus.Introspectable interface and its Introspect method are used . This method returns information about interfaces and nested service paths for one path in the form of xml. However, for our purposes it is necessary to obtain a list of all possible service paths. In other words, you need to call the Introspect methodalong the root path ("/"), and then recursively along all nested paths (which are described in the method response). Since this process is recursive, and the method response is obtained in xml format, it was not possible to implement such a format using QML code alone (the XmlListModel element simply does not represent the necessary functionality).
Therefore, it was decided to implement the list of paths in C ++. It looks like this:
This code implements the recursive algorithm described above. It is worth noting that the paths that only two interfaces are accessible from are removed from the results: org.freedesktop.DBus.Introspectable and org.freedesktop.DBus.Peer . This is done in order to display only those paths along which interfaces are available that can be really useful to the user.
The path list page is as follows:

When you click on any path, the next page opens with a list of interfaces implemented by this service for the selected path. This list, as well as the list of paths, is compiled using xml obtained using the Introspect method , but without any recursions, calling it once for a specific path. This functionality could be made using Nemo QML Plugin D-Bus, however, we decided to implement it in C ++:
The list itself looks like this:

When you click on an interface, another page opens showing the signals of the methods and properties of this interface:

All these parameters are also obtained using the xml parsing, which was obtained as a result of calling the Introspect method . However, here, to simplify the work, we have allocated a separate class InterfaceMember , which in essence is a structure for storing all the parameters of a specific member of the interface. This was done so that such objects can be easily represented with in QML code as non-visual elements.
The last two pages of the application are the pages for changing the interface property and invoking the interface method. The first is implemented quite simply and looks like this:

If the property is read-only, you cannot change it. If the property is also writable, the field for entering a new value on the page will be changeable and a new value can be entered there. Reading and writing property values is done using the property () and setProperty () methods of the QDBusInterface class . Although it is also possible to implement it using the Get () and Set methods of the org.freedesktop.DBus.Properties interface .
The method call page is as follows:

The invocation of the interface method itself is just as easily implemented using the call () or callWithArgumentList () methods of the QDBusInterface class .
However, here we had a difficulty in converting method arguments from those obtained from QML to those that are understandable for D-Bus itself. To implement this functionality, it was decided to take a ready-made solution, which was implemented in Qt D-Bus Viewer. The source code for this project can be viewed on GitHub .
This application was published in the application store for the Sailfish OS platform - Jolla Harbor under the name Visual D-Bus and is available for download from there. The source code for the project can be found on GitHub .
Posted by: Denis Laure
Despite the fact that D-Bus is initially supported in Sailfish OS, it is possible to manage it only from the terminal or from applications (if it is already embedded in them). That is why the idea arose of creating a visual client for the D-Bus system for Sailfish OS, which would allow viewing services registered in the system and interacting with them using a graphical interface. In other words, create an analogue of D-Feet or Qt D-Bus Viewer for Sailfish OS.
Any application in the system can create its own service (or service) that will implement one or more interfaces that are described by methods, signals and properties. An application can also interact with other applications through their services registered in D-Bus. For example, net.connman is registered in Sailfish OSimplementing the net.connman.Technology interface at / net / connman / technology / bluetooth . This interface also contains the SetProperty () method . By calling it as follows - SetProperty ("Powered", true) - you can enable Bluetooth on the device.
Actually, the application functionality should repeat that for analogues. Those. the application should allow viewing the list of services registered in the D-Bus (both session and system), for each such service, viewing the list of possible paths, for each path a list of interfaces, and for each interface showing lists of methods, properties and signals. In addition, the application should also allow calling these same methods and reading / changing properties.
The Sailfish SDK provides two options for interacting with D-Bus. Firstly, it is Nemo QML D-Bus Plugin , which allows you to interact with D-Bus directly from QML code. Secondly, it is Qt D-Bus - a standard Qt mechanism that provides C ++ classes for interacting with D-Bus. The difference between the two is that the former is fairly easy to use, while the latter provides more options. The application described in this article will describe both methods.
List of services
To get a list of services registered in D-Bus, use the DBusInterface element :
DBusInterface {
id: dbusList
service: 'org.freedesktop.DBus'
path: '/org/freedesktop/DBus'
iface: 'org.freedesktop.DBus'
bus: DBus.SessionBus
}
And the ListNames method of the above interface is called :
dbusList.typedCall('ListNames', undefined,
function(result) {
sessionServices = result.filter(function(value) {return value[0] !== ':'}).sort();
}, function() {
pageStack.push(Qt.resolvedUrl("FailedToRecieveServicesDialog.qml"));
});
Upon successful call of the method, the list of services is populated. At the same time, filtering occurs to avoid displaying services with names of the form ": 1.44", etc. In case of an error when calling the method, the user is shown the corresponding dialog with the error message. The page itself looks like this:

List of Service Paths
By clicking on a service from the list, you go to a page with a list of all the possible paths for this service. To do this, as well as to obtain a list of interfaces, the org.freedesktop.DBus.Introspectable interface and its Introspect method are used . This method returns information about interfaces and nested service paths for one path in the form of xml. However, for our purposes it is necessary to obtain a list of all possible service paths. In other words, you need to call the Introspect methodalong the root path ("/"), and then recursively along all nested paths (which are described in the method response). Since this process is recursive, and the method response is obtained in xml format, it was not possible to implement such a format using QML code alone (the XmlListModel element simply does not represent the necessary functionality).
Therefore, it was decided to implement the list of paths in C ++. It looks like this:
QStringList DBusServiceInspector::getPathsList(QString serviceName, bool isSystemBus) {
this->serviceName = serviceName;
dDusConnection = isSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus();
return extractPaths(introspectService("/"), "/");
}
QString DBusServiceInspector::introspectService(QString path) {
QDBusInterface interface(serviceName, path, "org.freedesktop.DBus.Introspectable", dDusConnection);
QDBusReply xmlReply = interface.call("Introspect");
if (xmlReply.isValid()) return xmlReply.value();
return "";
}
QStringList DBusServiceInspector::extractPaths(QString xml, QString pathPrefix) {
QXmlStreamReader xmlReader(xml);
QStringList pathsList;
while (!xmlReader.atEnd() && !xmlReader.hasError()) {
QXmlStreamReader::TokenType token = xmlReader.readNext();
if (token == QXmlStreamReader::StartDocument)
continue;
if (token == QXmlStreamReader::StartElement) {
if (xmlReader.name() == "interface") {
QXmlStreamAttributes attributes = xmlReader.attributes();
if (attributes.hasAttribute("name") &&
attributes.value("name") != "org.freedesktop.DBus.Introspectable" &&
attributes.value("name") != "org.freedesktop.DBus.Peer")
if (!pathsList.contains(pathPrefix)) pathsList.append(pathPrefix);
} else if (xmlReader.name() == "node") {
QXmlStreamAttributes attributes = xmlReader.attributes();
if (attributes.hasAttribute("name") && attributes.value("name") != pathPrefix) {
QString path = attributes.value("name").toString();
if (path.at(0) == '/' || pathPrefix.at(pathPrefix.length() - 1) == '/') {
path = pathPrefix + path;
} else {
path = pathPrefix + "/" + path;
}
pathsList.append(extractPaths(introspectService(path), path));
}
}
}
}
return pathsList;
}
This code implements the recursive algorithm described above. It is worth noting that the paths that only two interfaces are accessible from are removed from the results: org.freedesktop.DBus.Introspectable and org.freedesktop.DBus.Peer . This is done in order to display only those paths along which interfaces are available that can be really useful to the user.
The path list page is as follows:

Interface List
When you click on any path, the next page opens with a list of interfaces implemented by this service for the selected path. This list, as well as the list of paths, is compiled using xml obtained using the Introspect method , but without any recursions, calling it once for a specific path. This functionality could be made using Nemo QML Plugin D-Bus, however, we decided to implement it in C ++:
QStringList DBusServiceInspector::getInterfacesList(QString serviceName, QString path, bool isSystemBus) {
this->serviceName = serviceName;
dDusConnection = isSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus();
QXmlStreamReader xmlReader(introspectService(path));
QStringList interfacesList;
while(!xmlReader.atEnd() && !xmlReader.hasError()) {
QXmlStreamReader::TokenType token = xmlReader.readNext();
if (token == QXmlStreamReader::StartElement) {
if (xmlReader.name() == "interface") {
QXmlStreamAttributes attributes = xmlReader.attributes();
if (attributes.hasAttribute("name"))
interfacesList.append(attributes.value("name").toString());
}
}
}
return interfacesList;
}
The list itself looks like this:

When you click on an interface, another page opens showing the signals of the methods and properties of this interface:

All these parameters are also obtained using the xml parsing, which was obtained as a result of calling the Introspect method . However, here, to simplify the work, we have allocated a separate class InterfaceMember , which in essence is a structure for storing all the parameters of a specific member of the interface. This was done so that such objects can be easily represented with in QML code as non-visual elements.
Calling methods and changing interface properties
The last two pages of the application are the pages for changing the interface property and invoking the interface method. The first is implemented quite simply and looks like this:

If the property is read-only, you cannot change it. If the property is also writable, the field for entering a new value on the page will be changeable and a new value can be entered there. Reading and writing property values is done using the property () and setProperty () methods of the QDBusInterface class . Although it is also possible to implement it using the Get () and Set methods of the org.freedesktop.DBus.Properties interface .
The method call page is as follows:

The invocation of the interface method itself is just as easily implemented using the call () or callWithArgumentList () methods of the QDBusInterface class .
However, here we had a difficulty in converting method arguments from those obtained from QML to those that are understandable for D-Bus itself. To implement this functionality, it was decided to take a ready-made solution, which was implemented in Qt D-Bus Viewer. The source code for this project can be viewed on GitHub .
Conclusion
This application was published in the application store for the Sailfish OS platform - Jolla Harbor under the name Visual D-Bus and is available for download from there. The source code for the project can be found on GitHub .
Posted by: Denis Laure