Nous en sommes maintenant au quatrième numéro de ce dossier sur la programmation d'applications Gnome. Au menu ce mois-ci : comment utiliser le CVS de Gnome, LibGlade, l'un des outils les plus excitants du moment et comment écrire une applet pour contrôler votre lecteur mp3.
Après un mois d'août sans Linux Magazine, nous voilà frais et dispos, prêts à aborder des sujets un peu plus périlleux. Ce numéro est donc un peu particulier. En effet, nous verrons comment récupérer les versions de développement de Gnome directement sur le serveur CVS anonyme. Nous verrons ensuite comment utiliser le générateur d'interfaces Glade en conjonction avec LibGlade, l'interpréteur expérimental des fichiers XML générés par Glade. Enfin, nous verrons comment écrire une applet pour le panel de Gnome. A titre d'exemple, nous écrirons une applet de contrôle de Xmms (ex. X11amp), un lecteur de fichiers MP3.
Utiliser le serveur CVS de Gnome
Parce que Gnome est un environnement qui évolue très rapidement, il est parfois utile au développeur de disposer des derniers outils du projet. A l'heure où sont écrites ces lignes, en particulier, les modules "glade" et "libglade" ne sont disponibles que par le biais du serveur CVS. Attention cependant, utiliser les modules disponibles par CVS implique que vous utiliserez du code directement sorti du clavier des développeurs de Gnome et qui, par conséquent, peut ne pas être aussi fiable que celui disponible sur les sites FTP. Vous voilà donc averti et prêt à vous lancer à l'assaut du serveur CVS de Gnome.
Tout d'abord, vous devez avoir installé CVS sur votre machine. Exécutez la commande "cvs -v" pour connaître la version disponible sur votre système. Si cette commande ne répond pas, vous devez installer CVS. Pour Debian ou Redhat, les versions binaires sont disponibles sur les CD d'installation, mais aussi sur de nombreux sites FTP (ftp.jussieu.fr par exemple). Dans le cas où vous ne trouveriez pas de version binaire pour votre système, vous pouvez compiler CVS vous-même ; cela se fait sans aucune difficulté sur un système GNU/Linux.
Une fois CVS prêt à fonctionner, vous devez indiquer à quel serveur vous souhaitez vous connecter et sous quelle identité. Cela se fait via la variable d'environnement CVSROOT. Dans notre cas, le serveur auquel nous nous connecterons est anoncvs.gnome.org car nous utiliserons l'accès anonyme (selon le même principe que pour FTP). Voici donc la ligne de commande à taper :
export CVSROOT=':pserver:anonymous@anoncvs.gnome.org:/cvs/gnome'
sous bash, ou
setenv CVSROOT ':pserver:anonymous@anoncvs.gnome.org:/cvs/gnome'
sous csh.
Ensuite, il faut s'authentifier sur le serveur :
cvs login
Lorsque le serveur vous demande votre mot de passe, appuyez simplement sur la touche entrée, car pour l'accès à anoncvs, il n'y a pas de mot de passe. Vous n'avez besoin de vous authentifier qu'une seule fois sur le serveur, les informations relatives à la connexion sont conservées dans votre répertoire « home » dans ~/.cvspass
Une fois cette étape franchie, vous pouvez récupérer les modules que vous désirez. Par exemple, pour télécharger Glade, vous taperez :
cvs -z3 get glade
Le répertoire glade sera créé à l'endroit où vous aurez tapé la commande. Pour compiler le module, vous devrez ensuite taper les lignes suivantes :
cd glade;
./autogen.sh; # créer les fichiers de configuration et les Makefiles
make; # compiler Glade
make install; # installer Glade
ldconfig; # mettre a jour le cache du loader
Vous pouvez voir la liste des modules disponibles à l'adresse
http://cvs.gnome.org/bonsai
Il est conseillé de ne pas installer les modules en développement dans l'arborescence /usr mais plutôt dans un répertoire dédié, par exemple /opt/gnome. Dans ce cas, il faut préparer l'arborescence. Tapez les lignes suivantes :
mkdir /opt; mkdir /opt/gnome/;
mkdir /opt/gnome/share; mkdir /opt/gnome/share/aclocal
export ACLOCAL_FLAGS=-I /opt/GNOME/share/aclocal/
Attention à bien respecter l'espace entre le -I et le /opt. Si vous comptez utiliser régulièrement l'arborescence de développement, il est conseillé de mettre la ligne concernant ACLOCAL_FLAGS dans votre fichier de démarrage (~/.bashrc si vous utilisez bash).
Ensuite, il faut indiquer en option du script autogen.sh que l'installation du module doit se faire dans l'arborescence /opt/gnome :
./autogen.sh --prefix=/opt/gnome
Après cette étape, vous pouvez lancer le make comme précédemment.
Pour que le loader (ld.so) puisse trouver les librairies partagées de Gnome, vous devez lui indiquer dans quel répertoire les chercher. Vous devez ajouter le chemin /opt/gnome/lib dans votre fichier /etc/ld.so.conf.
Si vous n'êtes pas root sur votre système et que vous vouliez installer Gnome dans un de vos sous-répertoires, vous pouvez tout à fait le faire. Dans ce cas il faut que vous indiquiez au loader où se trouvent les librairies Gnome via la variable d'environnement LD_LIBRARY_PATH.
Par exemple, supposons que vous vouliez installer Gnome dans votre répertoire ~/my-gnome. Après avoir créé l'arborescence gnome comme indiqué ci-dessus, vous taperez les lignes :
export LD_LIBRARY_PATH=~/my-gnome/lib
./autogen.sh --prefix=~/my-gnome
etc.
Si vous désirez de nouveau synchroniser votre copie du module avec celle du serveur CVS, après quelques jours, vous n'avez qu'à vous mettre au-dessus du répertoire contenant votre copie et taper (toujours en utilisant l'exemple de Glade) :
cvs -z3 get glade
Vous n'avez pas besoin d'effacer votre copie locale avant de lancer la commande. CVS déterminera quelles sont les différences entre votre copie locale et la version CVS et ne transférera qu'elles. Cela permet des temps de transfert très réduits et soulage le serveur anoncvs qui est, vous vous en doutez, très sollicité.
Utiliser Glade
Glade est une application permettant de créer des interfaces Gnome de façon graphique. Ce programme est maintenant utilisé par de nombreux développeurs. Jusqu'au mois de juin dernier, il ne permettait de créer que des applications Gtk, sans utiliser les possibilités offertes par les librairies de Gnome. Ce n'est plus le cas. Cependant, il sera probablement nécessaire que vous utilisiez la version la plus récente de Glade.
Une fois Glade lancé, vous pouvez créer la totalité des éléments graphiques de votre application. Décrire Glade en détail dépasse le cadre de ce dossier. Cela dit, nous allons voir comment, de l'interface graphique modelée depuis Glade, on obtient le code final. En fait, le code est sauvegardé au format XML (voir http://www.w3.org/XML) jusqu'à ce que vous demandiez à Glade de le transformer en langage de votre choix (C, C++, Ada95 ou Perl pour l'instant) . La seule chose que vous aurez à coder en langage de programmation est la partie dynamique de l'interface. Dans les cas les plus simples, il s'agira uniquement d'implémenter les callbacks des contrôles graphiques. Nous allons voir maintenant une autre façon d'utiliser Glade.
Utiliser LibGlade
L'idée de LibGlade est la suivante : puisque Glade utilise le format XML pour sauvegarder l'interface créée par l'utilisateur, pourquoi ne pas utiliser directement le fichier XML pour générer l'interface Gtk à la volée, au démarrage du programme, exactement comme Glade le fait lorsque vous ré-ouvrez un projet. Voici les avantages d'une telle méthode :
4 Indépendance totale du langage de programmation.
LibGlade est une toute petite librairie écrite en C et très facile à appeler depuis tous les langages supportés par Gnome (C, C++, Objective C, Ada95, Perl, Python, Scheme...). Comme nous le verrons, il suffit de quelques instructions pour démarrer l'interpréteur XML en début de programme. Une même interface peut donc être utilisée par des programmes écrits dans des langages complètement différents.
4 Séparation accrue du programme et de son interface utilisateur.
L'essentiel du code concernant l'interface graphique se trouve contenu dans le fichier XML, évitant ainsi au développeur non expérimenté la tentation de mélanger le code de l'interface utilisateur avec le coeur de son application.
4 Versatilité de l'interface.
Il n'est absolument pas nécessaire de recompiler le programme pour changer l'aspect de l'interface graphique. En fait, l'utilisateur final peut même charger les fichiers XML dans Glade et modifier l'interface à sa guise. Les modifications seront prises en compte au démarrage suivant, sans que l'utilisateur n'ait à regarder le code de l'application. En fait, le développeur peut même prévoir que l'utilisateur choisisse son interface parmi une liste. Ce genre de choses existe déjà dans certaines applications, c'est ce que l'on appelle les "thèmes" (Gtk, Enlightenement, WindowMaker) ou les "skins" (xmms). Mis à part Gtk, les possibilités de configuration de l'interface sont assez limitées.
D'autre part, le surcoût engendré par l'interprétation du fichier XML est très faible et n'a de toute façon d'impact qu'au moment du démarrage de l'application. Une fois le fichier XML lu, il n'y a plus aucune différence entre l'interface créée par LibGlade et une interface directement écrite en C (ou C++, ou Objective C...).
Nous allons voir maintenant comment utiliser LibGlade pour créer une interface utilisateur complète. Tout d'abord, il faut créer l'interface sous Glade. Dans notre exemple, nous allons créer une fenêtre principale Gnome, c'est-à-dire, en fait, un objet de type GnomeApp. Si la version de Glade dont vous disposez ne contient pas le support Gnome, vous pouvez tout aussi bien créer une fenêtre principale Gtk plutôt qu'une fenêtre Gnome. Tout d'abord, créez un nouveau projet (menu Fichier/Nouveau Projet). Le dialogue vous proposera de sauver le projet dans un fichier avec extension .glade. Appelez ce fichier gnome.glade dans le répertoire où vous ferez vos essais. Ensuite, créez la fenêtre principale Gnome sous Glade : dans la palette d'outils, il y a en haut trois boutons (Gtk, Gtk additionnel, Gnome), cliquez sur le bouton Gnome, les boutons d'outils changeront et celui en haut à gauche représentera une petite fenêtre. Cliquez sur celui-ci. Une fenêtre principale Gnome apparaît, contenant un menu avec les entrées classiques ainsi qu'une barre d'icônes et une barre contextuelle en bas (voir figure 1).
Fig.1 : Une fenêtre Gnome en construction sous Glade
(Cliquez pour agrandir)
Dans la fenêtre de propriétés, sélectionnez l'onglet signaux. Puis, dans la fenêtre principale Gnome que vous avez créée, cliquez sur l'entrée de menu Fichier/Quitter. Le callback correspondant à cette action apparaît dans la fenêtre de propriétés, il doit s'appeler on_exit1_activate (mais vous pourriez tout à fait changer son nom). Dans l'exemple ci-dessous, nous implémenterons ce callback à titre d'exemple.
Une fois le fichier de projet sauvé, il doit apparaître sous le nom gnome.glade dans votre répertoire de travail. Vous pouvez éditer ce fichier, c'est un fichier texte au format XML. Parcourez-le, le XML ressemble beaucoup, dans sa syntaxe, au HTML. Vous reconnaîtrez rapidement la structure de la fenêtre que vous aurez créée.
Place maintenant à l'exemple. Vous allez voir, utiliser LibGlade dans une application est déroutant de simplicité.
/****************************************************/
/* tutoriel GNOME */
/* Bertrand Guiheneuf, 1999 */
/* Bertrand.Guiheneuf@aful.org */
/* */
/* 4/LFM13.c */
/* Pour compiler cet exemple, copiez/collez ces */
/* lignes dans une fenetre shell */
/*
gcc -o LFM13 `gnome-config --cflags gnomeui
libglade` LFM13.c `gnome-config --libs
gnomeui libglade`
*/
/************************************************/
#include <gnome.h>
#include <glade/glade.h>
/* callback appele quand l'utilisateur */
/* selectionne l'item de menu "Quit" */
void
on_exit1_activate (GtkWidget *widget, void *data)
{
gtk_main_quit ();
}
int
main(int argc, char *argv[])
{
GladeXML *xml;
gnome_init ("Bonjour", "1.0", argc, argv);
glade_gnome_init();
xml = glade_xml_new ("gnome.glade", NULL);
if (xml) glade_xml_signal_autoconnect (xml);
gtk_main ();
return 0;
}
Ce morceau de code n'appelle, finalement, que peu de commentaires. La fonction glade_xml_new lit le fichier contenant la description de l'interface. Le deuxième paramètre de cette fonction sert à indiquer à partir de quel niveau de l'arborescence de fenêtre il faut interpréter le code XML. Un exemple dans l'exemple d'applet ci-dessous éclairera un peu le rôle de ce paramètre. La commande glade_xml_signal_autoconnect connecte automatiquement les contrôles aux callbacks définis dans le source. Il faut faire très attention à ne pas déclarer les fonctions de callback en classe static, car LibGlade se sert des symboles publics de l'exécutable pour déterminer quels callbacks sont implémentés. Si vous ne connaissez pas la différence entre les fonctions déclarées en static et celles qui ne le sont pas, retenez juste qu'il ne faut pas déclarer les callbacks utilisés par l'interface Glade en static.
Ecrire une applet
Nous allons mettre à profit ce que nous avons appris concernant LibGlade pour écrire rapidement une applet Gnome. Les applets Gnome sont les petites applications qui apparaissent sur le panel. Ce sont en fait des applications à part entière, la plupart du temps indépendantes du panel, qui communiquent avec lui via CORBA. La technologie CORBA occupe une place très importante dans l'architecture de Gnome et intervient, vous le verrez, à de nombreux endroits. Dans le cas de l'écriture d'applets, l'utilisation de CORBA est invisible au programmeur. Comme exemple d'applet, nous allons écrire une petite barre de commande de Xmms. Xmms est (entre autre) un lecteur de fichier au format MPEG2 layer 3 (http://www.xmms.org). Il est fourni avec une librairie permettant de le commander depuis une application externe. Cette librairie et les headers associés sont installés par défaut donc, pour pouvoir compiler notre exemple d'applet, vous n'aurez qu'à télécharger Xmms et à l'installer. L'interface de l'applet a été, quant à elle, entièrement faite sous Glade. Voir la figure 2
Fig.2 : l'applet en cours de création sous Glade
pour des clichés d'écran de l'interface en cours de construction. Le code correspondant est disponible sur http://perso.cybercable.fr/guiheneu, vous n'aurez pas à refaire vous-même l'interface. Il faut juste retenir que l'interface est simplement un objet GtkWindow contenant un objet GtkHbox appelé applet_zone. applet_zone contient à son tour un objet GtkHbox appelé applet_zone_h, qui sera affiché lorsque le panel sera en position horizontale, et un objet GtkVbox appelé applet_zone_v qui, pour sa part, sera affiché lorsque le panel sera en position verticale. Ces deux objets contiennent des boutons dont les noms et les callbacks associés seront faciles à déduire du code suivant :
/************************************************/
/* tutoriel GNOME */
/* Bertrand Guiheneuf, 1999 */
/* Bertrand.Guiheneuf@aful.org */
/* */
/* 4/LFM14.c - Une applet pour commander Xmms */
/*
gcc -o LFM14 `gnome-config --cflags gnomeui \par libglade applets` LFM14.c `gnome-config \par --libs gnomeui libglade applets` -lxmms
*/
/************************************************/
#include <gnome.h>
#include <glade/glade.h>
#include <applet-widget.h>
#include <xmms/xmmsctrl.h>
gint xmms_session = 0;
static gboolean visible_window = FALSE;
GtkWidget *applet_zone;
GtkWidget *applet_zone_h;
GtkWidget *applet_zone_v;
/* ouvrir une fenetre Xmms si necessaire */
static gboolean
open_xmms_if_needed () {
if (!xmms_remote_is_running(xmms_session)) {
system ("xmms&");
while (!xmms_remote_is_running(xmms_session))
{} ;
visible_window = TRUE;
return TRUE;
} else {
return FALSE;
}
}
/* les fonctions suivantes sont les callbacks */
/* associes aux boutons de controle. NOTEZ */
/* BIEN QUE CES FONTIONS NE SONT PAS STATIC */
void
on_button_xmms_clicked (GtkWidget *widget,
void *data)
{
static gboolean first_call = TRUE;
if (!open_xmms_if_needed ()) {
visible_window = !visible_window;
xmms_remote_main_win_toggle (xmms_session,
visible_window);
}
}
void
on_button_play_clicked (GtkWidget *widget,
void *data)
{
open_xmms_if_needed ();
xmms_remote_play (xmms_session);
}
void
on_button_stop_clicked (GtkWidget *widget,
void *data)
{
open_xmms_if_needed ();
xmms_remote_stop (xmms_session);
}
void
on_button_avance_clicked (GtkWidget *widget,
void *data)
{
open_xmms_if_needed ();
xmms_remote_playlist_next (xmms_session);
}
void
on_button_recule_clicked (GtkWidget *widget,
void *data)
{
open_xmms_if_needed ();
xmms_remote_playlist_prev (xmms_session);
}
/* reponse au signal "change_orient" envoye par */
/* le panel quand il change d'orientation */
static void
applet_change_orient(GtkWidget *w,
PanelOrientType orient, gpointer data)
{
switch(orient) {
case ORIENT_UP:
case ORIENT_DOWN:
gtk_widget_hide (applet_zone_v);
gtk_widget_show (applet_zone_h);
break;
case ORIENT_LEFT:
case ORIENT_RIGHT:
gtk_widget_hide (applet_zone_h);
gtk_widget_show (applet_zone_v);
break;
}
}
/* callback associe au signal "save_session"
envoye par le panel pour donner l'ordre a
l'applet de sauver son etat. (avant de la
fermer, par exemple). */
static gint
applet_save_session(GtkWidget *w,
const char *privcfgpath,
const char *globcfgpath)
{
gint playlist_pos;
gnome_config_push_prefix(privcfgpath);
if (xmms_remote_is_playing (xmms_session)) {
gnome_config_set_bool (
"linuxmag/General/playing=false", TRUE);
} else {
gnome_config_set_bool (
"linuxmag/General/playing=false", FALSE);
}
gnome_config_pop_prefix();
gnome_config_sync();
gnome_config_drop_all();
return FALSE;
}
/* appele au lancement de l'applet pour restaurer
l'etat dans lequel elle etait lorsqu'elle a
ete fermee pour la derniere fois */
static void
applet_restore_session (AppletWidget *applet)
{
gint playlist_pos;
gboolean is_playing;
gnome_config_push_prefix(
APPLET_WIDGET(applet)->privcfgpath);
is_playing = gnome_config_get_bool
("linuxmag/General/playing=false");
gnome_config_pop_prefix();
if (is_playing) {
open_xmms_if_needed ();
xmms_remote_play (xmms_session);
}
}
int
main(int argc, char *argv[])
{
GladeXML *xml;
GtkWidget *applet;
applet_widget_init ("linuxmag_applet",
NULL, argc, argv, NULL,0,NULL);
applet = applet_widget_new ("linuxmag_applet");
if (!applet)
g_error("Can't create applet!");
glade_gnome_init();
/* charger le fichier xml, et n'interpreter */
/* le code que pour les objets contenus dans */
/* "applet_zone" */
xml = glade_xml_new ("applet.glade",
"applet_zone");
if (xml) glade_xml_signal_autoconnect(xml);
applet_zone = glade_xml_get_widget (xml,
"applet_zone");
applet_zone_v = glade_xml_get_widget (xml,
"applet_zone_v");
applet_zone_h = glade_xml_get_widget (xml,
"applet_zone_h");
gtk_signal_connect (GTK_OBJECT(applet),
"change_orient",
GTK_SIGNAL_FUNC(applet_change_orient),
NULL);
gtk_signal_connect(GTK_OBJECT(applet),
"save_session",
GTK_SIGNAL_FUNC(applet_save_session), NULL);
applet_widget_add (APPLET_WIDGET (applet),
applet_zone);
gtk_widget_show (applet);
applet_restore_session (APPLET_WIDGET (applet));
gtk_main ();
return 0;
}
Fig.3 : L'applet de contrôle de Xmms en action
Le résultat est visible dans la figure 3. Les callbacks des boutons ne nécessitent pas d'explications particulières : ils ne font qu'appeler les commandes de contrôle Xmms. Vous pouvez constater, en revanche, que nous n'avons pas utilisé la fonction glade_xml_new de la même façon que dans l'exemple précédent. Dans la ligne
xml=glade_xml_new("applet.glade", "applet_zone");
le deuxième argument précise à partir de quel objet commencer la construction de l'interface. Dans notre exemple d'applet, nous n'avons pas besoin d'afficher l'objet GtkWindow de haut niveau créé dans Glade. En effet, une applet doit utiliser un objet AppletWidget comme conteneur pour afficher quelque chose dans le panel. Avec la fonction glade_xml_new, nous indiquons donc qu'il ne faut commencer la construction qu'à partir de l'objet de nom applet_zone.
Pour ce qui est de la construction d'une applet proprement dite, tout se passe presque comme pour une application Gnome normale. Il suffit d'appeler applet_widget_init() plutôt que gnome_init() et d'utiliser un objet AppletWidget plutôt qu'un objet GnomeApp comme conteneur principal. Une applet doit cependant répondre à certains callbacks particuliers pour être totalement conforme. Le plus important est la réponse au signal change_orient émis par le panel. Dans notre cas, si nous n'y répondions pas, la barre de boutons de l'applet serait toujours horizontale, que le panel soit en position horizontale ou non. Le panel est très tolérant vis-à-vis des applets à cet égard. Il adaptera sa taille à celle de la plus grosse applet, au risque de prendre énormément de place lorsqu'une applet n'est pas prévue pour être utilisée verticalement, par exemple.
Par ailleurs, à l'heure où sont écrites ces lignes, l'utilisateur peut changer la taille du panel avec la version de développement. Les applets devront à l'avenir savoir répondre aux signal change_size. Le callback du signal devra être de la forme :
#ifdef HAVE_PANEL_SIZE
static void
applet_change_size(GtkWidget *w, PanelSizeType o,
gpointer data)
{
switch(o) {
case SIZE_TINY:
/* 24 pixels */ break;
case SIZE_STANDARD:
/* 48 pixels */ break;
case SIZE_LARGE:
/* 64 pixels */ break;
case SIZE_HUGE:
/* 80 pixels */ break;
}
}
#endif
Vous devez absolument entourer le code concernant le changement de taille des balises de compilation conditionnelle ci-dessus ou votre applet ne pourra pas se compiler avec les versions courantes du panel. Il est tout à fait probable, cependant, que la version publique du panel disponible au moment où vous lirez cet article inclut le changement de taille par défaut. Le support de cette caractéristique dans notre applet d'exemple est laissé au lecteur à titre d'exercice.
Enfin, vous pouvez constater que l'applet ci-dessus sait sauvegarder son état via la gestion de sessions de Gnome. Nous verrons ce sujet plus en détail le mois prochain.
Projet Gnome
Bertrand.Guiheneuf@aful.org