Introduction à Dbus avec Qt4

Dbus

Dbus est un bus système de messagerie. C’est un moyen simple pour des applications de communiquer entre elles. En plus de la communication interprocessus, Dbus est aussi un outil aidant au cycle de vie du processus. Il peut vous permettre de n’autoriser qu’une seule instance de votre application ou de votre daemon. Il peut vous permettre de démarrer votre application ou daemon à la demande quand elles sont nécessaires. Il est possible d’établir deux types de connexions: point à point ou point d’accès (daemon central). Grâce au daemon, vos applications peuvent être prévenues du branchement d’un nouvel composant matériel. Le bus de session (un par utilisateur connecté) est le canal générique de communication. Dbus est destiné à la communication entre application d’un même ordinateur. Il est cependant possible de communiquer par TCP/IP crypté avec un dossier home partagé par NFS. Ce genre d’usage reste rare et expérimental.

Il est possible d’utiliser Dbus dans de nombreux langages: C/C++, python, ruby, perl, java, C#. Je vais me concenter sur l’implémentation de Dbus dans Qt4, dans cet article.

Dbus dans Qt4

Qt4 implement sa propre API Dbus. Le support de Dbus sur windows étant en cours (d’après le site internet), Qt a été sage de regrouper toutes les fonctionnalités dans un module. Pour des environnements linux, il suffit d’installer la bibliothèque et à l’édition des liens, ajouter

QT += dbus

Dans le fichier pro pour inclure le module dbus à votre projet.

Les deux classes importantes sont QDbusMessage et QDbusInterface. La première permet d’envoyer facilement un message vers un service Dbus. C’est un moyen rapide pour communiquer une information vers une application dont vous ne maitrisez rien. Le QBusInterface permet une communication plus transparente, car tous les signaux/slots de votre application (d’une classe, pour être précis) seront mis sur Dbus.

Monitorer Dbus

Vous ne le savez peut-être pas mais votre système GNU/Linux (s’il est assez moderne) utilise probablement DBUS en continue. Pour se donner une idée, il existe deux utilitaires assez intéressant: dbus-monitor et qdbusviewer.

capture de qdbusviewer

 

Connexion à un service : Pidgin

Nous allons maintenant implémenté une solution pour utiliser un service d’une application. Nous voulons mettre à jour la petite phrase de status de pidgin. Ainsi votre logiciel de lecture audio pourra mettre en petite phrase la chanson actuellement jouée.

La première étape est d’identifié la fonction (et son chemin) qui nous intéresse. En général, les logiciels libres fournissent dans leur documentation l’API dbus qu’ils offrent. Cependant, l’outil qdbusviewer vous permet de voir toutes ces informations.

En terme de code, cela reste très simple. Vous déclarez un message Dbus. Il faut définir les bonnes valeurs: chemin, destinataire, noms..

QDBusConnection sessionbus = QDBusConnection::sessionBus();

if ( !sessionbus.isConnected() )
{
    qDebug() << "Could not connect to session bus";
}
    QDBusMessage m = 
    QDBusMessage::createMethodCall("im.pidgin.purple.PurpleService",
                                   "/im/pidgin/purple/PurpleObject",
                                   "im.pidgin.purple.PurpleInterface",
                                   "PurpleUtilSetCurrentSong");
    QList<QVariant> args;
    args << m_p->Title << m_p->Artist << m_p->album;
    m.setArguments(args);
    QDBusMessage metadatamsg = sessionbus.call(m);
    if(metadatamsg.type() != QDBusMessage::ReplyMessage)
        qDebug() << "Error its not a message " << metadatamsg.type() << metadatamsg.errorMessage ();
Connexion à un service dbus
La première étape est de définir quel bus vous allez utiliser : le sessionBus ou le systembus? Dans notre cas, c'est le sessionBus. On verifie que tout se passe bien niveau connection au bus. Il convient ensuite de créer un message. Les paramètres du messages sont les suivants :

  • Le nom du service (la colonne de gauche de qdbusviewer)
  • Le chemin vers l'objet (concaténation de tous les noeuds de l'arbre sauf celui de l'interface).
  • Le chemin vers l'interface (noeud en italique dans qdbusviewer).
  • Une liste d'arguments peut être passée à l'appel de la méthode du service. Dans notre cas, nous passons le titre de la chanson, l'artiste et le nom de l'album.
  • Dernière étape, appel de la méthode suivit d'une vérification d'erreur.

La voie royale pour faire communiquer deux applications via dbus grâce à Qt est l'utilisation des adaptors. Ils traduisent les signaux et slots d'une classe Qt en message Dbus. C'est totalement transparent. Pour faire un exemple simple, je vais faire une application qui propose un service "dbus_example_Service", de définition de brigthness et les clients peuvent définir le nouveau de volume. Plusieurs applications peuvent envoyer le volume au service et elles peuvent également recevoir la brightness. Une seule et unique application peut faire office de service. Deux instance de l'application "dbus_example_Service" vont entrainé des comportements inconnus.

qdbuscpp2xml -S -M mainwindow.h -o org.homelinux.renaudguezennec.xml

qdbusxml2cpp -c GuiAdaptor -a guiadaptor.h:guiadaptor.cpp org.homelinux.renaudguezennec.xml

qdbusxml2cpp -c GuiAdaptor -p test.h:test.cpp org.homelinux.renaudguezennec.xml

La première commande vous permet de générer un fichier xml: org.homelinux.renaudguezennec.xml qui contiendra l'ensemble de l'API de votre fichier .h.
Le resultat est de la forme suivante:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.homelinux.renaudguezennec">
    <signal name="brightnessChanged">
      <arg type="i" direction="out"/>
    </signal>
    <method name="setVolumeLevel">
      <arg type="i" direction="in"/>
    </method>
  </interface>
</node>
Xml généré

La deuxième commande crée une classe GuiAdaptor (dans les fichiers: guiadaptor.h et guiadaptor.cpp) qui expose l'API d'écrire dans le xml. Cette classe sera utile coté service.

La troisième commande crée une classe GuiAdaptor (dans les fichiers: test.h et test.cpp) que sera capable de contacter l'API décrite dans le xml. Nous allons utiliser cette classe dans le client pour l'API décrite dans le xml.

L'étape suivante consiste à générer une classe Adaptor qui transformera vos signaux/Slots Qt en message dbus. Il est utilisé côté service. Dans le main de votre programme, il convient d'instancier un adaptor généré (ici: ControlPanelAdaptor), comme cela:

#include <QtGui/QApplication>
#include "mainwindow.h"
#include "guiadaptor.h"
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    new GuiAdaptor(&w);
    w.show();

    QDBusConnection connection = QDBusConnection::sessionBus();
    bool rel = connection.registerService("org.homelinux.renaudguezennec");
    rel = connection.registerObject("/",&w);

    return a.exec();
}
Creation de service

A ce stade, votre application peut-être un service dbus. Il fournit une API à travers DBus. Nous allons voir maintenant comment créer des clients qui vont utiliser cette API.

Nous allons donc construire un nouveau projet : dbus_example_client. Il contiendra la réciproque du service. Le service est capable d'envoyer le signal brightnessChanged et reçoit son volume par l'appel à setVolumeLevel. Le client lui sera capable d'appeler setVolumeLevel du service et de recevoir la valeur de brightnessChanged. Pour se faire, nous allons utilisé la class GuiAdaptor (test.h et test.cpp) dans notre application cliente.

#include <QtGui/QApplication>
#include "mainwindow.h"
#include "test.h"

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   MainWindow w;

    GuiAdaptor* adapteur = new GuiAdaptor("org.homelinux.renaudguezennec", 
    "/",QDBusConnection::sessionBus(),0);

    QObject::connect(&w,SIGNAL(volumeChanged(int)),adapteur,
    SLOT(setVolumeLevel(int)));

    QObject::connect(adapteur,SIGNAL(brightnessChanged(int)),&w,
    SLOT(setBrightnessChanged(int)));
    w.show();

    return a.exec();
}
Client Qt

Comme vous pouvez le voir, il suffit d'instancier une classe GuiAdaptor (#include "test.h") et de connecter les signals et les slots de notre application avec cette instance. En l'occurence, notre application emet le signal volumeChanged(int), nous le lions au slot setVolumeLevel(int) de notre adapteur. Le signal émit par l'adapteur est récupéré dans un slot de notre application.

Leave a Reply

Your email address will not be published. Required fields are marked *