Наше путешествие началось Qt Graphics Framework, нас завербовали его светлой стороной, а потом мы долго получали граблями по разным частям тела.
Данная статья — это спин-офф основного сюжета. В ней сказ пойдет о QtDBus. Этот модуль Qt появился еще в четвертой версии и был хоть как-то документирован и снабжен примерами. Но грянул Qt 5.0, и уж не знаю по чему, но это привело к тому, что на сторону тьмы перешла вышеназванная дока. .
Дело №1. Как что и с чем готовить
Пытаться понять логику работы с DBus по доке Qt — дело неблагодарное. Есть что-то гораздо лучше — это туториал от самих разработчиков dbus. И хотя я не скажу ничего нового, но ради целостности статьи приведу общую схему как все это работает.
Итак, сама концепция:
Каждое сообщение D-Bus, передаваемое по шине, имеет своего отправителя. В случае, если сообщение не является широковещательным сигналом, то оно имеет и получателя. Адреса отправителей и получателей называются путями объектов, поскольку D-Bus предполагает, что каждое приложение состоит из набора объектов, а сообщения пересылаются не между приложениями, а между объектами этих самых приложений.
Итак, чтоб открыть доступ к объекту, в простейшем случае надо
- Подключится к демону на шине. Для это мы должны использовать QDBusConnection, заметим, что для стандартных шин есть статические методы.
- Зарегистирировать там свое имя. Это если хотим, иметь нормальное, удобочитаемое, а главное фиксированное имя, по которому к нам могут подключатся.Для это есть метод QDBusConnection::registerService().
- И зарегистрировать объект по некому пути(QDBusConnection::registerObject()).
- Подцепить к нему интерфейс, ну и ему тоже надо задать какое-то имя.
Использование не кажется уж таким вот сложным. Остается вопрос с отладкой.
Для это есть целый комплекс программ и методов:
- Способ из Qt-шной доки.
- qdbus
- qdbusviewer
- dbus-monitor
Дело №2. Попытка установить соединение.
Итак, готовьтесь, врата ада открываются. Первое что мы попытаемся сделать на основе этого примера — Установить соединение. Создадим два проекта: Ping и Pong(аналогично этому примеру), которые будут взаимодействовать.
Проект Ping:
#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();
}
#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
#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:
#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, "%sn",
qPrintable("Can't register object"));
exit(1);
}
qDebug()<<"Pong connected to D-bus";
if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME)) {
fprintf(stderr, "%sn",
qPrintable(QDBusConnection::sessionBus().lastError().message()));
exit(1);
}
qDebug()<<"Test service start";
return a.exec();
}
#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
#include "Pong.h"
Pong::Pong(QObject *parent) :
QDBusAbstractAdaptor(parent)
{
m_imageDataSharedId = "testImageBufferId";
}
QString Pong::imageDataSharedId()
{
return m_imageDataSharedId;
}
#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
Собираем проекты и запускает сначала пинг, а затем понг. Но результат неожиданный:
Иными словами пинг не узнает о появлении понга. В непонятках обращаемся к коду 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)));
Вроде тоже самое. Ан-нет, в слотах у них совсем другое:
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);
}
}
Внезапно код похож на пример из доки. Ладно, пишем что-то аналогичное у себя:
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);
}
И убеждаемся, что работает только serviceOwnerChanged. Но это не все, тролли нас предупреждают, что этот сигнал deprecated. Хорошо, тогда пишем такой код:
QDBusServiceWatcher watcher;
watcher.setConnection(QDBusConnection::sessionBus());;
QObject::connect(&watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)),&ping, SLOT(manageConnection(QString,QString,QString)));
Компилируем, запускаем. Не работает… Эй, тролли, вы троллите слишком жирно! Скажите, как надо юзать сие поделие? Нет, я понимаю, если добавить строку
watcher.addWatchedService(ping.m_aviableServiceName);
Что все зарабаотает и мы даже начнем получать сигналы регистрации и дерегистрации сервиса, ну а если я не знаю точного имени, а знаю лишь маску?
Дело №3. Попытка работы.
Итак, мы преодолели первый круг. Но ведь сразу за ним идет второй! А выглядит он вот так:
Т.е. мы не можем создать интерфейс. Снова лезем в qdbusviewer и видим там следующие строки:
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)));
Интересный вариант, да, он работает. Но дока нам обещает нечто больше, мягче, абстрактнее. Если теперь заменить эти строчки на
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());
То проблема, остановившая нас, повторится.
Итак, проблема есть, но причина ее непонятна. Первое желание — залезть в исходники Qt. Решение лобовое, но за час к успеху я не пришел, а
To facilitate remembering of the naming formats and their purposes, the following table can be used:
Service name Network hostnames Dot-separated («looks like a hostname») Object path URL path component Slash-separated («looks like a path») Interface Plugin identifier Dot-separated
это не рекомендация или ассоциация, а соглашение, обязательное к исполнению. И действительно, если заменить BUFFER_NAME на невразумительное fdgfsgf.buffer, то все заработает.
Если изучить доку D-Bus по-внимательнее, то обнаружится, что наличие точки в имени интерфейса обязательно, но почему тогда работает вариант предложенный в qdbusviewer?
Бонус-левел
Если в понге, в main.cpp первые строчки сделать вот такими:
Pong pong;
if( ! QDBusConnection::sessionBus().registerObject("/", &pong)){
то у меня программа вываливается с Segmentation fault;
Заключение.
Изначально статья планировалась единой, но по мере моего изучения вопроса накапливалось все больше и больше вопросов и статья все разрасталась и разрасталась. И выросла до текущих размеров только после изучения основ, чтоже будет дальше?
Ссылки
Автор: DancingOnWater