📜 ⬆️ ⬇️

QtDbus - darkness, covered with mystery. Part 1

Our journey began with the Qt Graphics Framework, we were recruited by its bright side, and then we received a long rake in different parts of the body.

This article is a spin-off of the main plot. It tells the tale of QtDBus. This Qt module appeared in the fourth version and was at least somehow documented and supplied with examples. But Qt 5.0 struck out, and I don’t know why, but this led to the above mentioned dock. .
Case number 1. How and with what to cook

Trying to understand the logic of working with DBus on Qt dock is a thankless task. There is something much better - this is a tutorial from the dbus developers themselves. And although I will not say anything new, but for the sake of the integrity of the article I will give a general scheme of how it all works.
So, the concept itself:
image Each D-Bus message sent over the bus has its own sender. If the message is not a broadcast signal, then it also has a recipient. Addresses of senders and recipients are called object paths, since D-Bus assumes that each application consists of a set of objects, and messages are sent not between applications, but between objects of these same applications.

So, to open access to the object, in the simplest case, you need
  1. Connect to the daemon on the bus. For this we have to use QDBusConnection , note that for standard buses there are static methods.
  2. Register your name there. This is if we want to have a normal, readable, and most importantly, a fixed name by which we can be connected to. For this is the QDBusConnection :: registerService () method .
  3. And register the object in one path ( QDBusConnection :: registerObject () ).
  4. To pick up an interface to it, well, he also needs to give some name.

The use does not seem so complicated. The question remains with debugging.
')
For this there is a whole range of programs and methods:

Case number 2. Attempting to establish a connection.
So get ready, the gates of hell are opening. The first thing we will try to do based on this example is to establish a connection. Create two projects: Ping and Pong (similar to this example), which will interact.
Ping Project:
main.cpp
#include <stdio.h> #include <QObject> #include <QCoreApplication> #include <QDBusConnection> #include <QDBusConnectionInterface> #include <QDBusServiceWatcher> #include <QDebug> #include "Ping.h" #include "../serviceNameAndProperty.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); if (!QDBusConnection::sessionBus().isConnected()) { fprintf(stderr, "Cannot connect to the D-Bus session bus.\n" "To start it, run:\n" "\teval `dbus-launch --auto-syntax`\n"); return 1; } qDebug()<<"Ping connected to D-bus"; Ping ping; QDBusConnectionInterface *iface = QDBusConnection::sessionBus().interface(); QObject::connect(iface, SIGNAL(serviceRegistered(QString)), &ping, SLOT(connectToService(QString))); QObject::connect(iface, SIGNAL(serviceUnregistered(QString)), &ping, SLOT(disconnect(QString))); QStringList registedServices = iface->registeredServiceNames(); if(registedServices.contains(ping.m_aviableServiceName)) ping.connectToService(ping.m_aviableServiceName); return a.exec(); } 
Ping.h
 #ifndef PING_H #define PING_H #include <QObject> #include <QDBusAbstractInterface> #include <qdbusinterface.h> class Ping : public QObject { Q_OBJECT public: explicit Ping(QObject *parent = 0); public slots: void connectToService(const QString &name); void disconnect(const QString &name); public: QString m_aviableServiceName; private: QDBusInterface *m_interface; QString m_interfaceName; static const QString _propertyName; }; #endif // PING_H 
Ping.cpp
 #include "Ping.h" #include "../serviceNameAndProperty.h" #include <QDBusConnectionInterface> #include <QDebug> const QString Ping::_propertyName(QUIOTING(IMAGE_DATA_SHARED_ID)); Ping::Ping(QObject *parent) : QObject(parent) { m_interface = NULL; m_interfaceName = QString(BUFFER_NAME); m_aviableServiceName = QString(SERVICE_NAME); } void Ping::connectToService(const QString &name) { if(name != m_aviableServiceName) return; qDebug()<<"Connceting"; m_interface = new QDBusInterface(name, "/", m_interfaceName, QDBusConnection::sessionBus(), this); if(!m_interface->isValid()){ qDebug()<<"Invalid interface"<<m_interface->lastError(); delete m_interface; m_interface = NULL; return; } qDebug()<<m_interface->interface(); QVariant var("ku"); var = m_interface->property("imageDataSharedId"); qDebug()<<var; } void Ping::disconnect(const QString &name) { if(name != m_aviableServiceName) return; if(name != m_interface->service()) return; delete m_interface; m_interface = NULL; qDebug()<<"Disconnect"; } 

Pong Project:
main.cpp
 #include <QCoreApplication> #include <QDBusConnection> #include <QDBusError> #include <QDebug> #include "Pong.h" #include "../serviceNameAndProperty.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QObject obj; Pong *pong = new Pong(&obj); if( ! QDBusConnection::sessionBus().registerObject("/", &obj)){ fprintf(stderr, "%s\n", qPrintable("Can't register object")); exit(1); } qDebug()<<"Pong connected to D-bus"; if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME)) { fprintf(stderr, "%s\n", qPrintable(QDBusConnection::sessionBus().lastError().message())); exit(1); } qDebug()<<"Test service start"; return a.exec(); } 
Pong.h
 #ifndef PONG_H #define PONG_H #include <QDBusAbstractAdaptor> #include <QDBusVariant> #include "../serviceNameAndProperty.h" class Pong : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", BUFFER_NAME) Q_PROPERTY(QString IMAGE_DATA_SHARED_ID READ imageDataSharedId) public: explicit Pong(QObject *parent = nullptr); QString imageDataSharedId(); private: QString m_imageDataSharedId; }; #endif // PONG_H 
Pong.cpp
 #include "Pong.h" Pong::Pong(QObject *parent) : QDBusAbstractAdaptor(parent) { m_imageDataSharedId = "testImageBufferId"; } QString Pong::imageDataSharedId() { return m_imageDataSharedId; } 

General serviceNameAndProperty.h file
 #ifndef SERVICENAMEANDPROPERTY_H #define SERVICENAMEANDPROPERTY_H #define SERVICE_NAME "ru.sonarh.dbus.pong" #define BUFFER_NAME "buffer" #define IMAGE_DATA_SHARED_ID imageDataSharedId #define QUIOTING(text) #text #endif // SERVICENAMEANDPROPERTY_H 

We collect projects and starts ping first and then ping. But the result is unexpected:

In other words, Ping does not know about the appearance of pong. In neponyatka refer to the code qdbusviewer:
 QDBusConnectionInterface *iface = c.interface(); connect(iface, SIGNAL(serviceRegistered(QString)), this, SLOT(serviceRegistered(QString))); connect(iface, SIGNAL(serviceUnregistered(QString)), this, SLOT(serviceUnregistered(QString))); connect(iface, SIGNAL(serviceOwnerChanged(QString,QString,QString)), this, SLOT(serviceOwnerChanged(QString,QString,QString))); 

It seems the same. But no, they have a completely different slots:
 void QDBusViewer::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { QModelIndex hit = findItem(servicesModel, name); if (!hit.isValid() && oldOwner.isEmpty() && !newOwner.isEmpty()) serviceRegistered(name); else if (hit.isValid() && !oldOwner.isEmpty() && newOwner.isEmpty()) servicesModel->removeRows(hit.row(), 1); else if (hit.isValid() && !oldOwner.isEmpty() && !newOwner.isEmpty()) { servicesModel->removeRows(hit.row(), 1); serviceRegistered(name); } } 

Suddenly, the code looks like an example from the docks. Okay, we write something similar in our:
 void Ping::manageConnection(const QString& name, const QString &oldVAlue, const QString &newValue) { if(name != m_aviableServiceName) return; if(newValue.isEmpty()) disconnect(name); else connectToService(name); } 

And we are convinced that only serviceOwnerChanged works . But that's not all, the trolls warn us that this signal is deprecated. Well, then we write this code:
 QDBusServiceWatcher watcher; watcher.setConnection(QDBusConnection::sessionBus());; QObject::connect(&watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)),&ping, SLOT(manageConnection(QString,QString,QString))); 
Compile, run. Doesn't work ... Hey, trolls, you're trolling too fat! Tell me, how should this be used? No, I understand if I add a line
  watcher.addWatchedService(ping.m_aviableServiceName); 
That everything earns money and we even begin to receive signals of registration and deregistration of the service, but what if I don’t know the exact name, and I only know the mask?

Case number 3. Attempt to work.
So, we overcame the first round. But right after him comes the second! And it looks like this:

Those. we cannot create an interface. Again we climb into qdbusviewer and see the following lines there:
  QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get")); QList<QVariant> arguments; arguments << sig.mInterface << sig.mName; message.setArguments(arguments); c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage))); 

An interesting option, yes, it works. But the dock promises us something more, softer, more abstract. If now replace these lines on
  QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface,c); if( !iface.isValid()) qDebug()<<(QDBusError(iface.lastError()).message()); else qDebug()<<iface.property(sig.mName.toLatin1().data()); 
That problem that stopped us will repeat.
So, there is a problem, but the reason is not clear. The first desire is to get into the sources of Qt. The solution is head-on, but in an hour I did not come to success, and my brain was strained. The decision came from the outside: watching how the KDE interfaces are built, I realized that
In order to facilitate the use of:

Service nameNetwork hostnamesDot-separated ("looks like a hostname")
Object pathURL path componentSlash-separated ("looks like a path")
InterfacePlugin identifierDot-separated
it is not a recommendation or an association, but a binding agreement. And indeed, if you replace BUFFER_NAME with an unintelligible fdgfsgf.buffer, then everything will work.
If you study the D-Bus dock more carefully, you will find that the presence of a dot in the interface name is necessary, but why does the version proposed in qdbusviewer work?

Bonus level
If in pong, in main.cpp, make the first lines like this:
 Pong pong; if( ! QDBusConnection::sessionBus().registerObject("/", &pong)){ 
then my program crashes with Segmentation fault;

Conclusion

Initially, the article was planned to be single, but as I studied the issue, more and more questions accumulated and the article grew and grew. And it grew to its current size only after learning the basics, what will happen next?

Links

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


All Articles