Разработка для 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» и т.п. В случае ошибки при вызове метода, пользователю показывается соответствующий диалог с сообщением об ошибке. Сама страница выглядит следующим образом:
061315b0fcc949b6b51c846b1f302a41.png

Список путей сервиса


По нажатию на сервис из списка происходит переход на страницу со список всех возможных путей данного сервиса. Для этого, так же как и для получения списка интерфейсов, используется интерфейс 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 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. Сделано это для того, чтобы выводить только те пути, по которым доступны интерфейсы, которые могут быть действительно полезны для пользователя.

Страница списков путей выглядит следующим образом:

58a9e70d765249f9b79b62c50f1bde9d.png

Список интерфейсов


При нажатии на какой-либо путь открывается следующая страница со списком интерфейсов реализованных данным сервисом по выбранному пути. Список этот так же как и список путей составляется по 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;
}

Сам список выглядит так:
3ed9ef97420846df9a4d5469531ccd12.png

При нажатии на интерфейс открывается еще одна страница с отображением сигналов методов и свойств данного интерфейса:
b9e113ae3293486592cd7a8119924eee.png

Все эти параметры получаются так же с помощью парсинга xml, который был получен в результате вызова метода Introspect. Однако тут, для упрощения работы, мы выделили отдельный класс InterfaceMember, который по сути представляет собой структуру для хранения всех параметров конкретного члена интерфейса. Сделано это было для того, чтобы такие объекты было легко представлять с в QML коде в виде невизуальных элементов.

Вызов методов и изменение свойств интерфейса


Последние две страницы приложения — это страницы изменения свойства интерфейса и вызова метода интерфейса. Первая реализована достаточно просто и выглядит следующим образом:
6b1ef18c44f542bc9e48016d899c0865.png

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

Страница вызова метода выглядит следующим образом:

12ed9b2eb3cf44bc831f47ee1074d0d4.png

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

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

Заключение


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

Автор: Денис Лаурэ

Комментарии (0)

© Habrahabr.ru