Tag Archives: qt4

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.

Qt4 et le design pattern Command

Bonjour,  ce petit tutorial va tenter de vous expliquer comment réaliser une opération longue tout en restant réactif dans une application Qt4.
Il existe plusieurs moyens mais je ne vais en décrire qu’un seul qui à mon sens est le plus propre. Comme cas pratique, j’ai choisi d’implémenter le Design patterns “Command”.
La première étape utilise des thread.

Communication entre un thread et une application Qt

Pas de solution miracle, pour faire “deux choses à la fois” dans une application, il faut passer par du multi-threading. Nous allons créer une classe thread qui hérite de QThread. Cette classe sera le support du traitement long à effectuer. Voyez ça comme une sorte encapsulation.

//.h de la classe

#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include "command.h"
class Thread : public QThread
{
    Q_ObJECT
        COMMAND* mycommand;
        bool undo;
public:

    Thread(COMMAND* mycommand,bool undo);

    protected:
        void run();
};

#endif // THREAD_H

Comme vous le voyez, c’est très simple. Nous héritons de QThread, nous avons deux membres dans cette classe: un pointeur vers une instance de la classe COMMAND (utile pour l’implémentation du patterns du même nom) et un booléen qui nous permet de déterminer le sens de l’action “annuler” (undo) ou “normal” (undo == false). bien entendu, il est possible d’avoir autant de paramètre que vous le souhaitez. Il ne faut pas oublié de redéfinir la fonction run().

// l’implémentation

#include "thread.h"

Thread::Thread(COMMAND* _mycommand,bool _undo)
        : mycommand(_mycommand),undo(_undo)
{

}
void Thread::run()
{
    if(undo)
        mycommand->undo();
    else
        mycommand->doCommand();
}

Dans le constructeur, je définis les données membres. La fonction run exécute la commande en fonction du paramètre undo. Il faut savoir que tout le code appelé dans run sera exécuté dans un thread différent.
À ce stade, nous avons une classe Thread qui est prête à recevoir et exécuter une “command”.

Nous allons voir maintenant comment créer un design pattern command.

Implémenter le design pattern command

Ce pattern est très utile pour gèrer l’annulation d’une action ou par exemple refaire la dernière action. Il consiste à créer une classe pour chaque action (du moins toutes les actions que vous voulez pouvoir modifier ou dans notre cas, exécuter dans un thread). Qt fournit des outils pour faire ça. Dans un cadre formateur, je préfère l’implémenter entièrement.
Il faut tout d’abord écrire la classe abstraite qui définit une commande. Cela permettra à notre classe thread de bien intéragir avec la commande.

#ifndef COMMAND_H
#define COMMAND_H
#include <QObject>
class COMMAND : public QObject
{
        Q_ObJECT

public:
    virtual void doCommand()=0;
    virtual void undo()=0;
signals:
        void Maximum(int M);
        void Minimum(int m);
        void valueChanged(int v);
        void done();
};

#endif // COMMAND_H

Il y a deux méthodes abstraites pures: l’une pour faire la commande, l’autre pour l’annuler. J’ai ajouté quelques signaux pour que la commande communique avec le thread principal pour informer l’utilisateur de l’avancée du la tâche en cours. Toutes les futures commandes de notre application doivent être des sous-classes de COMMAND. Vous l’avez deviné, il faut implémenter une commande, maintenant.
Notre commande sera vraiment basique, c’est une commande d’attente (so useless).

//wait.h

#ifndef WAIT_H
#define WAIT_H
#include "command.h"
class WAIT : public COMMAND
{
public:
    WAIT();

    virtual void doCommand();
    virtual void undo();

};

#endif // WAIT_H

Rien de particulier, juste la re-définition des fontions virtuelles pures. Voici leurs implémentations

#include "wait.h"
WAIT::WAIT()
{

}

void WAIT::doCommand()
{

int step = 10;
        emit Maximum(100);
        int i = 0;
        int k = 0;
        emit Minimum(i);
    for(int j = 0 ; j< 1000 ; j++)
    {
        sleep(0.5); //fake statement
        if(i>=step)
        {
                emit valueChanged(++k);
                i = 0;
         }
        i++;

    }
    emit valueChanged(++k);
    emit done();
}

void WAIT::undo()
{

}

Dans notre “doCommand”, nous calculons le pas de la notification de l’application principale. Ici, j’ai arbitrairement choisi 10 mais dans un contexte utile, le pas est égal à la taille de vos données à traiter divisé par le nombre de notification que vous voulez.
Nous émettons la valeur maximale. Initialisation des variables temporaires (i et k). On émet i (0). Nous faisons une bouble sur chaque élément à traiter, on fait le traitement et on calcule un peu pour savoir s’il faut ou pas prévenir l’application principale.
Il est bon de ne pas prévenir à chaque fois, car si vous travaillez sur plusieurs milliers ou millions de données le traitement des signaux ralentira un peu votre application. Comme exemple, imaginez que vous travaillez sur chaque pixel d’une grosse photo.

Il ne reste plus qu’a écrire la fenêtre principale qui affichera à l’utisateur l’avancée de notre traitement.

QProgressBar et QThread.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui/QMainWindow>
#include <QProgressBar>
#include <QDockWidget>
#include "thread.h"
namespace Ui
{
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QProgressBar *workinprogress;
    QDockWidget *Progressdock;
    Thread* myThread;
};

#endif // MAINWINDOW_H

Rien d’original, une mainwindow avec juste trois membres. Une QProgressBar qui affichera la progression.
Un DockWidget pour afficher la progesse bar, je ne l’ai pas implémenté dans cet exemple mais il peut être amusant d’afficher le dock quand une opération est en cours et la cacher quand c’est fini. Le dernier membre est une instance de notre classe Thread.

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "wait.h"



MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    workinprogress = new QProgressBar;
    Progressdock = new QDockWidget(tr("progress panel"));
    workinprogress->setValue(0);
    Progressdock->setWidget(workinprogress);
    Progressdock->setAllowedAreas(Qt::BottomDockWidgetArea);
    addDockWidget(Qt::BottomDockWidgetArea,Progressdock);
    WAIT* mywait = new WAIT();

    myThread = new Thread(mywait,false);
    connect(mywait,SIGNAL(Maximum(int)),workinprogress,SLOT(setMaximum(int)));
    connect(mywait,SIGNAL(Minimum(int)),workinprogress,SLOT(setMinimum(int)));
    connect(mywait,SIGNAL(valueChanged(int)),workinprogress,SLOT(setValue(int)));

    connect(ui->pushButton,SIGNAL(clicked()),myThread,SLOT(start()));

}

MainWindow::~MainWindow()
{
    delete ui;
}

Nous initialisons la QProgressbar et le QDockwidget. En suite, je paramètre un peu nos deux instances. Je crée alors une instance de WAIT et j’initialise le thread avec en paramètre l’adresse de notre commande.
Je connecte maintenant les signaux de la commande au slot de la QProgressBar.
Finalement, je connecte le clique sur “pushButton” sur le démarrage du Thread.
Ainsi, à chaque clique, il demarrera la commande wait.

Aller plus loin

Il serait préférable de créer une instance de WAIT à chaque clique et de l’ajouter dans une pile (dans la mainwindow). Cela est nécessaire pour finir le design pattern command. En haut de la pile, se trouve la dernière commande, si elle est annulée alors il faut la dépiler et exécuter undo().

Je proposerai en téléchargement ce petit exemple, d’ici quelques jours. En attendant, une petite capture d’écran. Capture d'écran Design Pattern Command, QThread et Qt.