Розробка для Sailfish OS: робота з D-Bus

Всім доброго часу доби! Дана стаття продовжує цикл статей, присвячених розробки для мобільної платформи Sailfish OS. Оскільки в основі операційної системи лежить ядро Linux, то в Sailfish OS спочатку доступні деякі «смакоту», які прийшли зі світу Linux. Однією з таких переваг є система межпроцессного взаємодії D-Bus. Для цієї статті я буду вважати, що читач вже знайомий з тим, що це за система, для чого вона потрібна і як нею користуватися (в іншому випадку, інформацію про це досить легко знайти в мережі, наприклад, на офіційному сайті або opennet).

Незважаючи на те, що D-Bus підтримується в Sailfish OS, керувати ним можливе лише з терміналу або з додатків (якщо в них це вже закладено). Саме тому виникла ідея створення візуального клієнта до системи D-Bus для Sailfish OS, які дозволить переглядати сервіси, зареєстровані в системі і взаємодіяти з ними за допомогою графічного інтерфейсу. Іншими словами, створити аналог D-Feet або Qt D-Bus Viewer для Sailfish OS.

Будь-який додаток в системі, може створити свій власний сервіс (або послугу), який буде реалізовувати один або декілька інтерфейсів, які описуються методами, сигналами і властивостями. Так само програма може взаємодіяти з іншими додатками через їх сервіси, зареєстровані в D-Bus. Наприклад, в Sailfish OS зареєстрований сервіс net.connman, який реалізує інтерфейс net.connman.Technology за адресою /net/connman/technology/bluetooth. Даний інтерфейс містить, в тому числі, і метод SetProperty(). Викликавши його наступним чином SetProperty(«Powered», true) — можна включити Bluetooth на вашому пристрої.

Власне, функціонал програми повинен повторювати такий для аналогів. Тобто програма повинна дозволяти переглядати список сервісів зареєстрованих в D-Bus (як сесійних, так і сервісних), для кожного такого сервісу переглядати список можливих шляхів, для кожного шляху список інтерфейсів, а для кожного інтерфейсу показувати списки методів, властивостей і сигналів. На додаток додаток так само повинно дозволяти викликати ці самі методи і читати/змінювати властивості.

У Sailfish SDK передбачено два варіанти взаємодії з D-Bus. По-перше, це Nemo QML Plugin D-Bus, який дозволяє взаємодіяти з D-Bus безпосередньо з QML коду. По-друге, це Qt D-Bus — стандартний механізм Qt, С++ надає класи для взаємодії з D-Bus. Різниця між ними в тому, що перший досить легкий у використанні, а другий надає більше можливостей. В описаному в даній статті додатку буде описано обидва способи.

Перелік сервісів

Для отримання списку сервісів, зареєстрованих в D-Bus використовується елемент DBusInterface:

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

І викликається метод ListNames описаного вище інтерфейсу:

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

При успішному виклику методу, заповнюється список сервісів. При цьому відбувається фільтрація, щоб уникнути виводу сервісів з іменами виду ":1.44" і т. п. В разі помилки при виклику методу, користувачеві показується відповідний діалог з повідомленням про помилку. Сама сторінка виглядає наступним чином:


Перелік шляхів сервісу

По натисненню на сервіс зі списку відбувається перехід на сторінку зі списку всіх можливих шляхів даного сервісу. Для цього, так само як і для отримання списку інтерфейсів, використовується інтерфейс org.freedesktop.DBus.Introspectable і його метод Introspect. Даний метод повертає інформацію про інтерфейсах і вкладених шляхах сервісу для одного якогось шляху у вигляді xml. Однак, для наших цілей необхідно отримати список всіх можливих шляхів сервісу. Іншими словами, потрібно викликати метод Introspect кореневого шляху ("/"), а потім рекурсивно по всіх вкладених шляхів (які описані у відповіді методу). Оскільки даний процес рекурсивен, а відповідь методу виходить у форматі xml, то реалізувати такий формат засобами одного лише QML коду не представлялося можливим елемент XmlListModel просто не уявляє потрібного функціоналу).

Тому, отримання списку шляхів було вирішено реалізовувати на С++. Виглядає це так:

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;
}

В даному коді реалізований рекурсивний алгоритм, описаний вище. Тут варто зазначити, що з результатів убираються ті шляхи, по яких доступні тільки два інтерфейсу: org.freedesktop.DBus.Introspectable org.freedesktop.DBus.Peer. Зроблено це для того, щоб виводити тільки ті шляхи, по яких доступні інтерфейси, які можуть бути дійсно корисними для користувача.

Сторінка списків шляхів виглядає наступним чином:


Перелік інтерфейсів

При натисканні на який-небудь шлях відкривається наступна сторінка зі списком інтерфейсів реалізованих даним сервісом по обраному шляху. Список цей так само як і перелік шляхів складається з xml, отриманого за допомогою методу Introspect, але вже без всяких рекурсий, викликавши його раз для конкретного шляху. Даний функціонал можна було зробити і використовуючи Nemo QML Plugin D-Bus, однак ми вирішили реалізувати його на З++:

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;
}

Сам список виглядає так:


При натисканні на інтерфейс відкривається ще одна сторінка з відображенням сигналів методів і властивостей даного інтерфейсу:


Всі ці параметри виходять за допомогою парсингу xml, який був отриманий в результаті виклику методу Introspect. Однак тут, для спрощення роботи, ми виділили окремий клас InterfaceMember, який по суті являє собою структуру для зберігання всіх параметрів конкретного члена інтерфейсу. Зроблено це було для того, щоб такі об'єкти було легко представляти з QML коді у вигляді невізуальні елементів.

Виклик методів і зміна властивостей інтерфейсу

Останні дві сторінки, додатки — це сторінки зміни властивості інтерфейсу і виклику методу інтерфейсу. Перша реалізована досить просто і виглядає наступним чином:


Якщо властивість доступна тільки для читання, то змінити його не можна. Якщо ж властивість доступна і для запису, то поле для введення нового значення на сторінці буде изменяемо і туди можна буде ввести нове значення. Читання і запис значень властивості здійснюються з допомогою методів property() і setProperty() класу QDBusInterface. Хоча це так само можливо реалізувати використовуючи методи Get() Set інтерфейсу org.freedesktop.DBus.Properties.

Сторінка виклику методу виглядає наступним чином:


Сам виклик методу інтерфейсу так само легко реалізується за допомогою методів call() або callWithArgumentList() класу QDBusInterface.

Однак тут у нас виникла складність при конвертації аргументів методу отриманих з QML до зрозумілим для самого D-Bus. Для реалізації даного функціоналу було вирішено взяти вже готове рішення, яке було реалізовано в Qt D-Bus Viewer. Вихідні матеріали даного проекту можна подивитися на GitHub.

Висновок

Цей додаток було опубліковано в магазині додатків для платформи Sailfish OS — Jolla Harbour під назвою Visual D-Bus і доступний звідти для скачування. Вихідні коди проекту можна знайти на GitHub.

Автор: Денис Лаурэ
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.