mercredi 1 janvier 2014

Xlib, C magnifique (avec l'exemple de WM_NAME)

Et oui, je suis membre de la petite minorité de personne aimant programmer directement en X11 avec Xlib. Je ne suis pas un foudre de programmation, mais plutôt un dilettante de la programmation C. Le fait d'utiliser Xlib directement est pour moi comparable à faire un Sudoku: C'est long, parfois ennuyeux, toujours sources d'erreur, magnifiquement vain, mais tellement distrayant. On peut ainsi passer une journée entière pour parvenir simplement à afficher une simple fenêtre à l'écran avec Xlib. Pour résumer, Xlib est une API (liste de fonctions) permettant de programmer pour X Windows et dont la dernière version est la version nommée X11.

Xlib est pour distrayante, car elle oblige à devoir passer un temps fou sur la toile pour glaner quelques infos ou tutoriels, car contrairement aux librairies graphiques plus évoluées (exemple GTK, Xt, Qt), on doit lire des pages et des pages de sites ou de documentation avant de comprendre le début d'une explication pour les choses les plus simples.

Prenons l'exemple par exemple d'une fonction permettant de déterminer le nom d'une fenêtre X (au sens X11 évidement). Cette simple fonctionnalité est déjà toute une histoire, car on doit s'intéresser à un élément périphérique à X11: le Window Manager (WM pour les initiés ou pour les amis les plus proches). Le WM est le chef d'orchestre des fenêtres dans X11. Il possède évidement des privilèges spécifiques lui permettant d’exécuter les tâches qui lui sont dévouées, comme par exemple gérer la barre de titres et différentes "décorations" associées à une fenêtre (cette fois au sens du WM).

Dans mon cas personnel, j'utilise le WM le plus simple, celui qui est inclus à Lubuntu: OpenBox. Normalement, l'usage des nom des fenêtres (ceux qui apparaissent par exemple dans le gestionnaire de taches par exemple), n'est pas tributaire de votre WM. En théorie, l'exemple suivant devrait fonctionner quelques soit votre WM.

Donc voici le résultat de mes recherches: une fonction C permettant de récupérer les noms de chaque fenêtre actives dans X:

/*----------------------------------------------
 * Fonction récupérant le nom d'une fenêtre
 * retour=0 si problème
 * sinon retour=1
 * Le nom_fentre est un buffer fourni par l'appelant
 * dont la longueur est spécifié en argument
 * --------------------------------------------*/
int X11vb_recuperer_nom_fenetre( Display * d, Window w, char * nom_fenetre, int nb_c_nom_fenetre)
{
char **liste;
int i=0;
XTextProperty text_property;

if (nb_c_nom_fenetre<1 class="Apple-tab-span" span="" style="white-space: pre;">
return 0; nom_fenetre[0]=0;

//---- récupération de la property texte WMName
if(!XGetWMName (d, w, &text_property))
{
return 0;
}
if (!XTextPropertyToStringList( &text_property, &liste, &i))
{
printf("XStringListToTextProperty - out of memory\n");
return 0;
}
if (!i)
{
printf("XTextPropertyToStringList=>liste=0\n");
return 0;
}
if (!*liste)
{
printf("XTextPropertyToStringList=>liste[0]=vide\n");
return 0;
}
//------ copie du résultat
strncpy(nom_fenetre, *liste, nb_c_nom_fenetre);
nom_fenetre[nb_c_nom_fenetre-1]=0;
XFreeStringList(liste);
return 1;
}

Pour l'utiliser, voici un source additionnel pour par exemple lister les fenêtres qui possède un nom (et oui, toutes les fenêtre n'ont pas de nom, et pourtant nullement orpheline).

#include
#include
#include
#include
#include
#include



int X11vb_recuperer_nom_fenetre( Display * d, Window w, char * nom_fenetre, int nb_c_nom_fenetre);

// ERROR HANDLER, GENERIC
static int ErrorHandler (Display *display, XErrorEvent *error)
{
   //printf ("\r\n error! \r\n");
   return 0;
}
// END ERROR HANDLER



void ListerWindows (Display *display, Window window, int niveau, char * filtre)
{
static int nb_w=0;
Window parent;
Window root;
Window *enfant;
XWindowAttributes windowattr; 
int nb_enfant=0;
int i=0, h=0, l=0, x=0, y=0;
char nom_fenetre[40]; //---le nom de la fenêtre affiché sera limité à 39 caract.
   
if (filtre) if (!*filtre) filtre=NULL;

//-----récupérer le nom de la fenêtre
if (!X11vb_recuperer_nom_fenetre(display, window, nom_fenetre, sizeof(nom_fenetre)))
*nom_fenetre=0; ;
//---- attributs de la fenêtre
if (XGetWindowAttributes(display, window,   &windowattr) == 0)
printf("failed to get window attributes");
else
{
x= windowattr.x;
y= windowattr.y;
l= windowattr.width;
h= windowattr.height;
}
// les enfants de cette fenêtre----
if (!XQueryTree (display, window, &root, &parent, &enfant, &nb_enfant))
nb_enfant=0;
if (*nom_fenetre)
{
if (!filtre || strstr(nom_fenetre, filtre)) 
printf ("%03i (niv.=%i; enfant=%i) - window: '%s' (x=%i,y=%i;L=%i,H=%i) w=%i\r\n", ++nb_w, niveau,nb_enfant, nom_fenetre, x,y,l,h,(int)window);
}

for (i=0; i < nb_enfant; i++)
ListerWindows (display, enfant[i], niveau+1, filtre);
   
XFree ((char*) enfant);
}


int main(int argc, char *argv[])
{
   // CONNECT TO THE XSERVER
   Display *display;
   int depth;
   int screen;
   int connection;
   char *filtre=NULL;
   Window rootWindow;
      
   display= XOpenDisplay (NULL);
   screen= DefaultScreen (display);
   depth= DefaultDepth (display, screen);
   connection= ConnectionNumber (display);
   XSetErrorHandler (ErrorHandler);
   
   printf ("Lister X11 window ayant un nom pour le Window Manager (WMName)\r\n");
   printf ("--------------------------------------------------------------\r\n");
   printf ("Display: %s\r\n", XDisplayName((char*)display));
   printf ("Width: %d\r\n", DisplayWidth(display, screen));
   printf ("Height: %d\r\n", DisplayHeight(display, screen));
   printf ("Connection: %d\r\n", connection);
   printf ("Color Depth: %d\r\n", depth);
  
   
if (argc>1)
filtre= argv[1];
if (filtre)
printf("=====>filtre sur le nom des fenêtres contenant:%s\n",filtre);
else
printf ("--en option:\r\n--mettre en argument un filtre sur le nom\r\n");
rootWindow = RootWindow (display, screen);  
ListerWindows (display, rootWindow, 0, filtre);
XCloseDisplay (display);

return 0;
}


Pour ne rien oublier, voici le makefile que j'utilise. Vous pourrez donc le tester en réel sur votre machine linux, si votre gcc (compilateur C) est opérationnel.

liste_w: lister_w.c
gcc -o lister_w lister_w.c -L/usr/X11R6/lib -lX11

On peut remarquer à quel point cet exemple est délicieusement complexe pour une opération aussi élémentaire. Pour être complet sur la question, vous avez un utilitaire permettant globalement de faire la même chose en ligne de commande sous linux: xdotool. Un petit tour sur la page d'aide de cet utilistaire vous permettra d'en comprendre toute la puissance. Il peut par exemple vous permettre d'envoyer des touches 'clavier' vers une X window. xprop et xwinfo sont deux utilitaires qui complète xdotool.

Liens:
Excellente page Wiki sur Xlib
Livre important: Xlib programming manual Volume 1.
Page du source que j'ai adapté et adopté