La programmation des dockapps

Window Maker est un window manager très à la mode. Son look, sa facilité de configuration et sa rapidité sont autant d'atouts pour devenir un standard. De nombreux utilitaires aussi esthétiques qu'indispensables viennent rapidement prendre place sur le dock, utilitaires qui renseignent l'utilisateur sur l'état du système, l'heure, lui permettent de régler facilement des paramètres comme le volume ou l'assistent dans le montage des systèmes de fichiers.

Ces applications dockables (dockapps) se multiplient à grande vitesse et un site Internet leur est même consacré. Les raisons de cette croissance sont simples : les dockapps rendent de grands services, prennent peu de place sur le bureau et sont faciles à programmer. De plus, Window Maker permet de les intégrer par un simple Drag&Drop sur le dock sans avoir à éditer un quelconque fichier de configuration.

Il est très facile, pour un programmeur ayant des bases en programmation X-Window, de comprendre le fonctionnement des dockapps en explorant les sources d'utilitaires comme wmmon (moniteur système) ou wmtime (horloge).

Cet article fait d'abord quelques rappels sommaires concernant X-Window, décrit ensuite les différentes techniques liées à la programmation des dockapps puis les illustre sur un exemple à la " Hello World ! ". Enfin, il donne quelques conseils et idées pour réaliser des dockapps utiles et jolies !

Rappels sur le système de fenêtrage X-Window

Pour que votre écran affiche une quelconque fenêtre sous X, il faut qu'au moins deux processus soient lancés : le serveur X, qui s'exécute toujours sur la machine à laquelle sont reliés l'écran et la souris, et un client X qui est l'application à proprement parler (par exemple un xterm) et qui peut s'exécuter sur une quelconque machine du réseau local ou de l'Internet. Dans le cas d'un poste isolé, le serveur et le client s'exécutent évidemment sur la même machine.

Le serveur X a trois rôles importants. Il sert d'abord d'interface avec le matériel, c'est-à-dire de pilote de périphérique, et c'est pour cela qu'il existe plusieurs serveurs X (XF86_SVGA, XF86_S3, ...), chacun correspondant à certains types de carte vidéo. Son second rôle est de fournir une API client/serveur permettant à un client X d'afficher des fenêtres à l'écran. Une fenêtre s'entend ici au sens X du terme, c'est-à-dire une zone rectangulaire de l'écran sur laquelle le client va pouvoir dessiner, écrire et intercepter des événements. Enfin, son troisième rôle est de surveiller les dispositifs d'entrée de la machine sur laquelle il s'exécute, généralement le clavier et la souris, de mémoriser leur changement d'état (appui sur une touche, déplacement de la souris, etc.) dans des structures appelées événements et de fournir une API client/serveur afin qu'un client X puisse obtenir les événements qui lui sont destinés.

Un client X est une application qui communique par le réseau avec un serveur X. Elle envoie des requêtes d'affichage au serveur X, comme l'ouverture d'une fenêtre ou l'affichage d'un texte via l'API client/serveur, et doit répondre aux événements qui lui sont envoyés par le serveur X. La plupart du temps, un client X ne fait rien d'autre que d'attendre l'arrivée d'un événement et, une fois reçu, de le traiter.

Comme il a déjà été dit, une fenêtre sous X n'est qu'une zone rectangulaire de l'écran, sans bordure de dimension, d'icônes de fermeture ou d'agrandissement. X ne prend pas en charge la gestion de l'esthétique. Il confit ce rôle à un client X particulier : le window manager ou gestionnaire de fenêtres. Fvwm, twm, olwm, AfterStep et bien sûr Window Maker sont autant de window managers qui sont chargés de donner des looks différents aux clients X. Les window managers nécessitent une programmation spécifique de façon à travailler de concert avec le serveur X. Il est impossible d'exécuter plus d'un window manager au même instant pour un serveur X donné. La nature client/serveur de X permet par contre au window manager de s'exécuter sur une autre machine que celle sur laquelle s'exécute le serveur X ou les applications qu'il gère.

L'API client/serveur permettant au serveur et aux clients de communiquer est la bibliothèque X11, ou Xlib, qui se trouve généralement dans le fichier /usr/X11R6/lib/libX11.so. Cette bibliothèque contient nombre d'appels permettant au client de communiquer avec le serveur. Ces appels commencent généralement par la lettre 'X'. Elle contient aussi des appels qui autorisent le client à dialoguer avec le window manager qui le contrôle. Ces appels contiennent souvent les lettres 'WM'.

La programmation X11 rebute parfois le débutant, car il est nécessaire d'écrire une cinquantaine de lignes pour voir enfin s'afficher une fenêtre vide qui se ferme lorsque l'on clique dessus. Dans le cadre du développement de moyennes ou de grosses applications, il est nécessaire de s'appuyer sur des toolkits, c'est-à-dire des ensembles homogènes de widgets (boutons, barres de défilement, zones d'édition, etc.) comme Motif, Athena, Xforms, Qt, Tk ou Gtk. La programmation des éléments graphiques devient même visuelle avec des outils comme xvis (Athena), fdesign (Xforms), Visual Tk (Tk), kdevelop (Qt) ou glade (Gtk).

Par contre, les dockapps sont des applications généralement très simples car elles n'impliquent pas un haut niveau d'interaction avec l'utilisateur. Il est donc inutile d'utiliser des environnements de développement ou des toolkits. Le programmeur en reste donc au niveau X11. Il est d'ailleurs intéressant de remarquer que l'on peut encore réaliser des choses belles et pratiques en n'utilisant que les appels de base de la Xlib.

Les techniques de base

Regardons ce qui se passe lorsqu'on lance différentes applications X sous Window Maker :

Si on lance xspread, ou xfte, la fenêtre de l'application apparaît. Si on lance xterm ou xload, la fenêtre de l'application ainsi qu'une icône apparaissent. Si on lance wmmon ou wmtime, seule une icône apparaît. Les icônes font partie de l'esthétique et, en ce sens, c'est le window manager qui en assure le contrôle. Pourtant, c'est le client qui a la charge de l'image qu'il veut faire apparaître pour icône. Dans le cas de xspread ou xfte, les programmeurs n'ont pas cru nécessaire d'associer une icône à leur application. Il en résulte qu'aucune icône n'apparaît et qu'il est nécessaire de lancer le créateur d'icônes livré avec Window Maker si on désire docker ces applications. Le cas de xterm et xload est plus classique car les programmeurs ont associé une icône à leur application et les docker revient à un simple Drag&Drop. Enfin, les dockapps n'affichent pas de fenêtre puisque leur essence est de s'exécuter directement dans le dock.

La deuxième remarque que l'on peut faire est que la plupart des applications offrent une icône statique, c'est-à-dire un simple fichier image. Les dockapps, quant à elles, modifient dynamiquement leur icône pour afficher des informations et sont capables de répondre à certains événements comme les clics souris. L'icône des dockapps n'est finalement rien d'autre qu'une fenêtre au sens X du terme, contrôlée par l'application elle-même. Window Maker offre cette possibilité d'associer une fenêtre X en guise d'icône d'application alors que d'autres window managers ne l'autorisent pas. L'application ne pourra pas fonctionner sous forme d'icône sous ces environnements et il sera alors nécessaire de gérer une fenêtre d'application classique.

On peut aussi constater que certaines parties de l'icône des dockapps sont gérées par Window Maker alors que d'autres parties sont gérées par l'application. Dans le cas de wmmon, par exemple, certaines parties de l'icône affichent des informations système, alors que le bord de l'icône ainsi que les lignes séparatrices entre les différents afficheurs sont gérés par Window Maker. C'est pour cela que l'on peut draguer l'icône dans le dock en le saisissant par un bord et que l'aspect de ces zones change lorsque l'on change de jeu d'icônes (menu Apparence).

Lorsque l'on docke certaines applications, Window Maker demande des informations complémentaires, par exemple la ligne de commande permettant de lancer l'application. Comme pour l'icône, il s'agit ici encore d'un oubli du programmeur, qui n'a pas écrit le code envoyant ces indications au window manager.

Enfin, les dockapps sont souvent très esthétiques, à l'image de wmmon, qui présente des afficheurs LCD sophistiqués et des jauges multicolores. Certaines affichent même de petits boutons qui s'enfoncent lorsque l'on clique dessus. Les dockapps utiliseraient-elles une nouvelle toolkit dédiée qu'il faudrait apprendre à maîtriser ? La réalité est beaucoup plus simple qu'il n'y paraît. Les dockapps, de par leur petite taille, mettent généralement à profit l'utilisation des pixmaps, c'est-à-dire des dessins stockés en mémoire, et les changements d'apparence se réalisent par de simples copies de morceaux de pixmaps vers la fenêtre d'affichage.

En résumé, voici les points essentiels qui conditionnent la programmation d'une dockapp :

1. Créer une fenêtre principale et une fenêtre icône.

2. Informer le window manager que l'icône de l'application est une fenêtre X gérée par l'application. Lui signaler que la fenêtre principale ne doit pas être affichée s'il sait gérer les icônes dynamiques. Sinon, il affichera la fenêtre principale.

3. Fournir au window manager les informations nécessaires à sa mise dans le dock : la classe de la fenêtre et sa ligne de commande.

4. Mettre en place la gestion de la transparence, qui permettra de définir les zones de la dockapp qui seront gérées par l'application et celles gérées par le window manager.

5. Charger les pixmaps nécessaires au dessin de l'application en mémoire.

6. Sélectionner les événements auxquels l'application doit répondre.

7. Afficher la fenêtre (sous Window Maker, cette dernière ne s'affichera pas, seule l'icône sera visible).

8. Attendre les événements et réagir à ceux-ci de manière adéquate comme dans toute application X.

Exemple de programmation

La dockapp que nous allons réaliser ne fait rien d'autre qu'afficher un pixmap. Quand l'utilisateur clique dessus, elle affiche un autre pixmap. Elle met en pratique toutes les techniques exposées précédemment.

Pour la gestion de la transparence, il est nécessaire de définir un bitmap où chaque point blanc représente un pixel transparent et chaque point noir un pixel opaque. Les pixels transparents seront gérés par le window manager alors que les pixels opaques seront gérés par l'application. Cette fonctionnalité n'est spécifique ni aux dockapps ni à Window Maker et peut être utilisée en programmation X classique. On doit créer un fichier bitmap (*.xbm) à l'aide d'un outil graphique quelconque. Pour cet exemple, le fichier a été créé avec l'utilitaire bitmap. Il doit avoir une taille de 64x64. Tous les pixels sont noirs sauf une rangée de 4 pixels sur chaque côté. Ceci signifie que l'application gérera la partie centrale alors que Window Maker gérera les bords. Si vous ne laissez pas de place à Window Maker, c'est-à-dire si tous les pixels sont noirs, vous ne pourrez pas déplacer votre icône. Ce fichier doit porter le nom " fond.xbm ".

On doit créer deux pixmaps qui seront affichés en alternance, au gré des clics souris de l'utilisateur. Ils doivent aussi avoir une taille de 64x64. Attention, seule la partie centrale de 56x56 sera affichée à cause de la transparence. Ils doivent s'appeller " image1.xpm " et " image2.xpm ". Utilisez xpaint ou gimp pour les dessiner. Vous pouvez aussi convertir un gif en xpm avec l'utilitaire xv.

Fabriquer un Makefile pour compiler l'exemple :

WMCoucou : WMCoucou.c fond.xbm image1.xpm image2.xpm Makefile

gcc -Wall -O2 -o WMCoucou WMCoucou.c \

-L/usr/X11R6/lib -lXpm -lXext -lX11 -Xlinker -s

Les options ­Wall, -O2 et ­Xlinker ­s sont optionnelles. La première concerne les warnings, la seconde permet d'optimiser le code et la troisième supprime tous les symboles de l'exécutable. L'option ­L indique où trouver les différentes bibliothèques. Les options ­l permettent de spécifier les bibliothèques à utiliser.

Enfin, voici le source commenté de WMCoucou.c :

/* Il faut tout d'abord inclure les fichiers d'entetes apropries */

#include <X11/Xlib.h> // Pour les fonctions de base

#include <X11/xpm.h> // Pour la gestion des pixmaps

#include <X11/Xutil.h> // Pour la communication avec le window manager

#include <X11/extensions/shape.h> // Pour la gestion de la transparence

/* On inclut directement dans le code les donnees decrivant

les bitmaps et pixmaps necessaires a l'application */

#include "fond.xbm" // Le masque de fond pour la transparence

#include "image1.xpm" // Le message Coucou en LCD

#include "image2.xpm" // Le message Salut en LCD

/* Declaration des variables necessaires a la gestion d'un client X */

Display *dpy; // La voie de communication avec le serveur

Window root; // La fenetre fond d'ecran (le "bureau")

int screen; // Le # de l'ecran (0 si un seul ecran)

GC gc; // Le contexte graphique permettant de dessiner

unsigned long p_blanc; // Le # de la couleur blanche

unsigned long p_noir; // Le # de la couleur noire

/* Les fenetres associees a l'application */

Window win; // Notre fenetre principale

Window iconwin; // Notre fenetre icone

unsigned int borderwidth = 1; // La largeur du bord des fenetres

/* Indications a faire passer au window manager */

XWMHints wmhints; // L'icone et l'etat de la fenetre

XSizeHints sizehints; // La taille et la position

XClassHint classHint; // La classe de la fenetre

/* Les pixmaps associes a l'application */

Pixmap pixmask; // Gestion de la transparence

Pixmap XPM[2]; // Nos deux messages (Coucou et Salut)

int nXPM = 0; // Le # du pixmap a afficher

/* Gestionnaire de l'evenement Expose (demande de reaffichage) */

void DoExpose() {

/* Copie du pixmap nXPM dans la fenetre et dans l'icone */

XCopyArea(dpy, XPM[nXPM], win, gc, 0, 0, 64, 64, 0, 0);

XCopyArea(dpy, XPM[nXPM], iconwin, gc, 0, 0, 64, 64, 0, 0);

}

/* Gestionnaire de l'evenement ButtonPress (bouton souris enfonce) */

void DoButtonPress() {

/* Changement du # du pixmap */

nXPM = 1 - nXPM;

/* Demande de reaffichage */

DoExpose();

}

/* Boucle evenementielle principale */

void DoEvents() {

XEvent ev; // Structure pour recuperer l'evenement

do { // Boucle sans fin

XNextEvent(dpy, &ev); // Attente d'un evenement et recuperation

switch (ev.type) { // Traitement de l'evenement

case Expose:

DoExpose(); // Si X demande le reaffichage

break;

case ButtonPress:

DoButtonPress(); // Si l'utilisateur clique sur l'icone

break; // (ou sur la fenetre)

}

} while (1);

}

/* Initialisations de la dockapp */

void main(int argc, char *argv[]) {

char *wname = argv[0]; // Le nom de l'application

/* Initialisations X classiques */

dpy = XOpenDisplay(NULL); // Ouvre une communication

// avec le serveur X local

screen = DefaultScreen(dpy); // Recupere l'ecran par defaut

root = RootWindow(dpy, screen); // Recupere la fenetre de fond

p_blanc = WhitePixel(dpy, screen); // # de couleur du blanc

p_noir = BlackPixel(dpy, screen); // # de couleur du noir

gc = XDefaultGC(dpy, screen); // Recupere le contexte graphique

/* Definition de la taille des fenetres */

sizehints.x = 0;

sizehints.y = 0;

sizehints.width = 64;

sizehints.height = 64;

/* Creation de la fenetre principale et de la fenetre icone */

win = XCreateSimpleWindow(dpy, root, sizehints.x, sizehints.y,

sizehints.width, sizehints.height, borderwidth, p_noir, p_blanc);

iconwin = XCreateSimpleWindow(dpy, root, sizehints.x, sizehints.y,

sizehints.width, sizehints.height, borderwidth, p_noir, p_blanc);

/* Indique au window manager la position et la taille de la fenetre */

sizehints.flags = USSize | USPosition;

XSetWMNormalHints(dpy, win, &sizehints);

/* Associe une icone a la fenetre principale et l'indique au

window manager */

wmhints.initial_state = WithdrawnState; // Pour ne voir que l'icone

wmhints.icon_window = iconwin; // L'icone est une fenetre

wmhints.icon_x = sizehints.x;

wmhints.icon_y = sizehints.y;

wmhints.window_group = win;

wmhints.flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;

XSetWMHints(dpy, win, &wmhints);

/* Indique au window manager la classe de la fenetre */

classHint.res_name = wname;

classHint.res_class = wname;

XSetClassHint(dpy, win, &classHint);

/* Indique au window manager la ligne de commande */

XSetCommand(dpy, win, argv, argc);

/* Chargement du pixmap definissant la transparence */

pixmask = XCreateBitmapFromData(dpy, win, fond_bits, fond_width, fond_height);

/* Definition du masque de transparence pour la fenetre et l'icone */

XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, pixmask, ShapeSet);

XShapeCombineMask(dpy, iconwin, ShapeBounding, 0, 0, pixmask, ShapeSet);

/* Chargement des deux pixmaps a afficher */

XpmCreatePixmapFromData(dpy, root, image1_xpm, &XPM[0], NULL, NULL);

XpmCreatePixmapFromData(dpy, root, image2_xpm, &XPM[1], NULL, NULL);

/* Selection des evenements pour la fenetre et l'icone */

XSelectInput(dpy, win, ExposureMask | ButtonPressMask);

XSelectInput(dpy, iconwin, ExposureMask | ButtonPressMask);

/* Affichage de la fenetre (Window Maker ne l'affichera pas grace

a l'option WithdrawnState de wmhints) */

XMapWindow(dpy,win) ;

/* Attente et traitement des evenements */

DoEvents();

}

Pour aller plus loin

Bien sûr, cette application ne fait pas beaucoup de chose. Comment faire pour lui ajouter tout ce qui lui manque, à savoir des informations pertinentes, des jauges, des afficheurs LCD ?

Pour ce qui est du contenu, un grand nombre d'informations systèmes se trouvent dans le pseudo système de fichiers /proc. Allez y faire un tour et faites un cat (pas un less) sur les fichiers qui s'y trouvent. Vous trouverez une foule de données intéressantes sur la configuration, les processus, la mémoire, le réseau, etc. Un man proc vous apprendra beaucoup aussi. La technique de récupération de ces infos est très simple : il suffit d'ouvrir un fichier se trouvant dans proc et de lire les données textuelles qu'il contient. L'emploi des fonctions sur les chaînes de caractères et les expressions régulières vous permettra de récupérer les champs qui vous semblent pertinents. La documentation de ces fonctions et de toutes celles de la glibc est accessible par info.

Pour aller chercher ces informations à intervalles réguliers, l'utilisation des signaux (man signal) et des fonctions alarm ou setitimer devrait vous aider. Là encore, la lecture du manuel et de la documentation info est recommandée.

Pour afficher des jauges à la manière de wmmon, il suffit de créer un pixmap de jauge vide et un pixmap de jauge pleine avec toutes les couleurs que vous désirez. Imaginons que votre jauge fait 32 pixels de long. Par une simple règle de trois, ramenez d'abord la valeur numérique que vous voulez représenter par la jauge à un nombre n compris entre 0 et 31. Afficher ensuite les n premières colonnes de la jauge vide grâce à XCopyArea. Afficher ensuite les (32-n) dernières colonnes de la jauge pleine toujours grâce à XCopyArea.

Note : Il est généralement plus efficace de créer un seul pixmap assez grand pour contenir toutes les images de l'application.

Pour les afficheurs LCD, c'est la même chose. Créez un pixmap contenant le dessin point par point des chiffres de 0 à 9 en ordre croissant (ou récupérez celui de wmmon!). Lorsque vous devez afficher un nombre, décomposez-le en chiffre par divisions successives ou à l'aide de atoi. Pour chaque chiffre, réalisez un XCopyArea qui ne copiera que le morceau de pixmap voulu (celui qui correspond au chiffre). Le même raisonnement peut être appliqué aux lettres et symboles.

Bon amusement...

Alain BASTY

Alain.Basty@accesinter.com


© Copyright 2000 Diamond Editions/Linux magazine France. - Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1or any later version published by the Free Software Foundation; A copy of the license is included in the section entitled "GNU Free Documentation License".