📜 ⬆️ ⬇️

Development for Sailfish OS: Work with D-Bus

Good day to all! This article continues the series of articles devoted to the development of the mobile platform Sailfish OS. Since the operating system is based on the Linux kernel, some “goodies” that come from the Linux world are initially available in Sailfish OS. 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 the system is, what it is for, and how to use it (otherwise, information about this is fairly 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 can only be managed from the terminal or from applications (if they are already embedded in them). That is why the idea arose of creating a visual client for the D-Bus system for Sailfish OS, which will allow you to view the services registered in the system and interact with them using the 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 several interfaces, which are described by methods, signals and properties. The same application can interact with other applications through their services registered in the D-Bus. For example, the net.connman service is registered in Sailfish OS, which implements the net.connman.Technology interface at / net / connman / technology / bluetooth . This interface contains, among other things, the SetProperty () method. Calling it as follows - SetProperty ("Powered", true) - you can turn on Bluetooth on the device.

Actually, the functionality of the application should repeat that for analogues. Those. the application should allow to view the list of services registered in D-Bus (both session and system), for each such service to view the list of possible paths, for each path the list of interfaces, and for each interface to show lists of methods, properties and signals. In addition, the application should also allow to call these same methods and read / modify properties.
')
Sailfish SDK provides two options for interaction with the D-Bus. First, it is the Nemo QML Plugin D-Bus , which allows you to interact with the D-Bus directly from the QML code. Secondly, Qt D-Bus is a standard Qt mechanism, providing C ++ classes for interacting with D-Bus. The difference between them is that the first is fairly easy to use, and the second provides more options. The application described in this article will describe both methods.

List of services


To get the list of services registered in D-Bus, the DBusInterface element is used :

DBusInterface { id: dbusList service: 'org.freedesktop.DBus' path: '/org/freedesktop/DBus' iface: 'org.freedesktop.DBus' bus: DBus.SessionBus } 

And the ListNames method of the interface described above is called:

 dbusList.typedCall('ListNames', undefined, function(result) { sessionServices = result.filter(function(value) {return value[0] !== ':'}).sort(); }, function() { pageStack.push(Qt.resolvedUrl("FailedToRecieveServicesDialog.qml")); }); 

If the method is called successfully, the list of services is filled. When this happens filtering to avoid the withdrawal of services with the names of the form ": 1.44", etc. In case of an error when calling the method, the corresponding dialog is displayed to the user with an error message. The page itself looks like this:


List of service paths


By clicking on the service from the list, you will go to the page with a list of all possible ways of this service. For this, as well as for getting the list of interfaces, the org.freedesktop.DBus.Introspectable interface and its Introspect method are used. This method returns information about the 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 method on the root path ("/"), and then recursively on all nested paths (which are described in the method response). Since this process is recursive, and the method's response is obtained in xml format, it was not possible to implement such a format using only one QML code (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<QString> 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. Here it is worth noting that from the results are removed those ways in which only two interfaces are available: org.freedesktop.DBus.Introspectable and org.freedesktop.DBus.Peer . This is done in order to display only those paths through which interfaces are available that can be really useful for the user.

The path list page looks like this:


Interface list


When you click on any path, the next page opens with a list of interfaces implemented by this service along the selected path. This list as well as the list of paths is made up of xml, obtained using the Introspect method, however, without any recursions, calling it once for a particular path. This functionality could be done 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 the interface, another page opens displaying the signals of the methods and properties of this interface:


All these parameters are obtained in the same way by parsing xml, which was obtained as a result of calling the Introspect method. However, here, to simplify the work, we have identified a separate InterfaceMember class, which in essence is a structure for storing all the parameters of a particular interface member. This was done to ensure that such objects were easy to represent in QML code as non-visual elements.

Calling methods and changing interface properties


The last two pages of the application are the pages of changing the interface property and calling the interface method. The first one is implemented quite simply and looks like this:


If the property is read-only, then it cannot be changed. If the property is also available for recording, 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 ​​are done using the property () and setProperty () methods of the QDBusInterface class. Although it is also possible to implement using the Get () and Set methods of the org.freedesktop.DBus.Properties interface.

The method call page looks like this:


The call to the interface method is also easily implemented using the call () or callWithArgumentList () methods of the QDBusInterface class.

However, here we have a difficulty in converting the 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 sources of this project can be viewed on GitHub .

Conclusion


This application was published in the app store for the platform Sailfish OS - Jolla Harbor called Visual D-Bus and is available for downloading from there. Project source codes can be found on GitHub .

The author: Denis Laure

Source: https://habr.com/ru/post/316462/


All Articles