1

Architecture et concepts de base du système X‑Window

 

1.1. Modèle client/serveur

                  La librairie de fonctions Xlib est une librairie de requêtes faites auprès d'un autre programme, le serveur X. L'exécution d'un programme utilisant cette librairie nécessite le lancement préliminaire du serveur (commande X ou xinit) à moins qu'on ne se trouve directement connecté à un terminal spécialisé (terminal X). Une fois le serveur lancé, différentes applications peuvent effectuer une connexion à ce serveur et recourir à ses services au moyen de la librairie Xlib en demandant par exemple la création de fenêtres et divers affichages de textes et de dessins.

                  Le serveur X est chargé d'administrer des ressources partagées entre plusieurs programmes s'affichant sur une même station mais s'exécutant sur différentes machines d'un réseau. Le terme de serveur est peut-être ambigu dans ce contexte de réseau, car il donne à penser que le serveur peut être distant du terminal sur lequel on est connecté alors que c’est justement le contraire : le serveur tourne sur le terminal (ou sur la machine à laquelle est connecté le terminal) et n'est donc pas a priori distant de l'utilisateur.

 

                  Cependant, le terme de serveur est bien justifié, car n’importe quelle application tournant sur une machine du réseau peut se connecter au serveur et utiliser ses ressources. Ces ressources partagées sont typiquement l'écran, le clavier, la souris, mais aussi des structures internes plus abstraites comme des palettes de couleurs, des fontes ou des propriétés associées aux fenêtres contenant des informations quelconques. Par exemple, les applications tournant sur les écrans représentés figure 1.1. pourraient très bien avoir toutes été lancées sur un super calculateur auquel ne serait relié aucun terminal graphique.

 

 

fig. 1.1. Un Réseau de machines utilisant X-Window

 

                  Ces applications peuvent donc utiliser les ressources de calcul d’une machine puissante, tout en utilisant par ailleurs un serveur local pour gérer les informations graphiques, l'affichage, l'utilisation de la souris et du clavier. De telles applications seront lancées sur le super ordinateur, mais demanderont en début de programme l'ouverture d'une connexion au serveur de la station graphique sur laquelle elles souhaitent afficher l'interface.

 

                  Plusieurs programmes, tournant sur différentes machines, peuvent ainsi effectuer des requêtes auprès du même serveur graphique et afficher des fenêtres sur le même terminal. On appelle habituellement ces programmes des clients car le modèle de communication sous-jacent est le modèle communément appelé modèle client/serveur (cf. figure 1.2.).i.Communication:modèle client/serveur;

 

fig. 1.2. La communication client/serveur

 

                  La métaphore utilisée est celle d'un restaurant dans lequel les clients effectuent des commandes auprès d'un serveur, et où le serveur satisfait chaque client en rapportant les plats dans l'ordre des commandes. Ici, les programmes effectuent des demandes auprès du serveur X, qui traite ces requêtes en respectant l'ordre dans lequel elles ont été formulées dans chaque programme client.

i.Communication:modèle client/serveur;

                  Pour communiquer avec le serveur, les clients doivent utiliser un certain protocole : le protocole X. La librairie Xlib est une librairie de fonctions dont l’implantation effectue des appels aux fonctions du protocole X ; c'est une interface au protocole X plus facile à utiliser. Elle utilise deux notions fondamentales : la notion de fenêtre et celle d'événement.

 

 

fig. 1.3. L'architecture client/serveur

 

 

 

1.2. Les événements

                  En pratique, les clients communiquent avec le serveur au moyen d'appels fonctionnels à la librairie, et inversement le serveur retourne des informations au travers d'unités appelées événements (cf. figure 1.3.) ou plus généralement au moyen de structures dont les adresses sont passées en argument.

                  Les événements décrivent des actions de l'utilisateur sur les dispositifs d'entrée (souris, clavier), mais également des états résultant de l'exécution d'autres programmes (affichages graphiques, destructions de fenêtres, modifications de ressources partagées, appels à une fonction particulière, etc.). Il existe de nombreux types d'événements permettant au client de prévoir la nature des informations qui lui sont communiquées par le serveur. Par exemple, il existe un événement de type ButtonPress qui traduit l'enfoncement d'un bouton de souris et un événement de type KeyPress qui traduit l'enfoncement d'une touche du clavier. Il y a également des événements informant les clients de modifications survenues sur les fenêtres comme les événements ConfigureNotify ou VisibilityNotify.

 

                  Grâce aux événements, les clients peuvent également communiquer entre eux, mais toujours bien entendu par l’intermédiaire du serveur en respectant des conventions de communication. Par exemple, l'événement de type ClientMessage permet d'échanger une information quelconque et l'événement de type PropertyNotify permet d'indiquer qu'une modification a été effectuée sur une propriété associée à une fenêtre.

 

 

1.3. Les fenêtres

                  Les fenêtres sont la base de l'architecture du système X-Window. Ce sont les objets qui permettent de récupérer les événements provenant des dispositifs d'entrée, et l'on dessine toujours dans leurs cadres. Les fenêtres sont rectangulaires. Elles sont hiérarchisées en arbre, en fenêtres parents et fenêtres filles.

 

                   Toutes les fenêtres ont un ancêtre commun : la fenêtre racine ou root window. Une fenêtre détermine un cadre en dehors duquel ses filles restent invisibles. Entre sœurs, les fenêtres sont rangées selon un certain ordre d'empilement rendu visible lorsque leurs positions se recouvrent partiellement (cf. figure 1.4). Les fenêtres possè-dent un certain nombre d'attributs, parmi lesquels des attributs géométriques : position du coin haut le plus à gauche (dans le repère de la fenêtre parent), largeur, hauteur. On peut par ailleurs leur associer des propriétés de format quelconque.

 

                  La plupart des requêtes de la librairie porteront sur les fenêtres. On aura ainsi des requêtes de création, d'affichage et de modification d'attributs des fenêtres. Une fois les fenêtres créées et affichées, on pourra dessiner sur leur fond grâce à d'autres requêtes : demandes d'affichage de points, de rectangles, d'ellipses ou de textes. C'est aussi par l'intermédiaire de propriétés associées aux fenêtres que les différents clients pourront échanger des informations.

 

 

fig. 1.4. La double hiérarchie des fenêtres à l'écran :

entre ancêtres (inclusion) et entre sœurs (recouvrement)

 

 

1.4Bitmap et Pixmap

                  Les points de l'écran sont regroupés par le serveur dans des structures de type Pixmap permettant de spécifier la couleur de chaque point. Un Pixmap est une carte rectangulaire représentant une portion d’écran, de sorte que chaque point d’écran se trouve en correspondance avec une couleur particulière (ou pixel). Un pixel est un entier représentant un indice d'entrée dans une table de couleurs (ou Colormap) permettant de coder les couleurs[1].

                 

                  Le terme Pixmap s'oppose à bitmap. Un bitmap représente également un rectangle de points d’écran, mais il associe à chaque point de l’écran une valeur de {0,1}. Pour des écrans noir et blanc, ou en général, dans un contexte bicolore, un bitmap suffit à caractériser une portion d’écran, car on peut coder le noir et le blanc sur un seul bit (cf. figure 1.5.). Mais dès que l’on possède plus de deux couleurs, il devient nécessaire d’associer à chaque point d’écran une valeur entière virtuellement plus grande. Cette valeur s'appelle un pixel, d'où le nom de Pixmap au lieu de bitmap.

 

 

fig. 1.5. Un bitmap

 

                  Un Pixmap est constitué de n plans de bits. Le nombre de plans s'appelle la profondeur du Pixmap (depth) et correspond au nombre de bits nécessaires pour coder la couleur d'un point.  Si chaque couleur est codée par un entier sur n bits, un pixel  est identifiable à un n-uplet de {0,1}n. Ainsi, on a besoin de n plans de bitmap pour définir un Pixmap de profondeur n (cf. figure 1.6.);. Le type bitmap n'existe pas dans la librairie car un bitmap est simplement un Pixmap de profondeur 1.

 

 

 

fig. 1.6. Codage d’un rectangle de points

par un Pixmap constitué de 8 plans

                 

 

                  Les Pixmap sont des structures très utilisées. On peut associer à chaque fenêtre un Pixmap pour paver le fond de la fenêtre. C'est une alternative à une couleur uniforme pour le fond ou les bords d'une fenêtre (cf. figure 1.7.).

 

fig. 1.7. Le pavage d'un fond de fenêtre avec un Pixmap

 

                  On utilise également des Pixmap dans les contextes graphiques qui servent de paramètres aux primitives de dessin. Les Pixmap utilisés comme couleur de fond permettront de simuler des niveaux de gris sur des écrans noir et blanc. Les curseurs sont également représentés par des Pixmap. Enfin, grâce aux fonctions de copie, les Pixmap pourront servir à mémoriser des portions d'écran.

 

                  Dans la librairie, le type Pixmap correspond à une structure de données opaque au programmeur. Beaucoup d'objets de la librairie ont un type opaque. Le programmeur déclare des objets de ce type, mais il les initialise et les modifie par l'intermédiaire de fonctions fournies par la librairie. Ainsi, la cohérence des structures utilisées est préservée directement par la librairie. Les fonctions de création de Pixmap seront détaillées dans ce livre à la fin du chapitre sur les fenêtres, dans la section 3.8. intitulée Création de fonds de fenêtres.

 

 

1.5. Structure des programmes

                  Dans une telle architecture, le déroulement d'un programme est principalement contrôlé par la récupération des événements. Un programme commence généralement par effectuer des requêtes de création de fenêtres. L'application indique ensuite au serveur les événements qui peuvent intéresser chaque fenêtre, puis pénètre dans une boucle permettant de récupérer les événements envoyés par le serveur. Dans cette boucle, le programme traite les événements au fur et à mesure qu'ils se produisent. Ce schéma de programme est tout-à-fait général et est résumé figure 1.8.

 

1. Connexion au serveur

2. Créations de fenêtres (objets graphiques)

         utilisées par l'interface

3. Sélection d’événements intéressant l’application

4. Boucle principale {

         récupération d'un événement

               -> traitement de l'événement récupéré

   }    

 

fig. 1.8. Schéma général d’un programme d'interface sous X-Window

 

Dans un modèle client/serveur la communication est asynchrone, c'est-à-dire que l'on observe un certain décalage entre la formulation d'une demande au serveur et son exécution effective. Les programmes communiquent avec le serveur en faisant appel aux requêtes de la librairie, lesquelles appellent des fonctions du protocole. Mais ces requêtes ne sont, comme le mot l'indique, que des demandes et leur exécution en tant que fonction C du programme client (au niveau de la librairie Xlib) peut consister :i.Communication:modèle client/serveur;

• en l'empilement local d'une requête au protocole (l'envoi sera effectué plus tard lors d'un appel à une fonction plus impérative).

• en l'envoi pour traitement d'une requête au serveur (mais sans attendre le résultat du traitement).

• en l'envoi et traitement immédiat d'une requête par le serveur (cas général des fonctions ramenant des valeurs). Dans ce cas, la fonction est bien synchronisée avec le reste de l'application.

En tout état de cause, la librairie garantit que le traitement des requêtes sera effectué dans l'ordre des appels du client, mais le traitement de ces requêtes peut s'effectuer de manière asynchrone par rapport au reste du programme. Cependant, certaines requêtes sont toujours bien synchronisées. Par exemple, les requêtes commençant par XQuery (ou comportant les mots Fetch ou Get) font partie de cette catégorie car elles rapportent des valeurs. On notera au passage qu'elles garantissent aussi que les requêtes précédentes ont été traitées par le serveur, puisque le serveur garantit l’ordre de traitement des requêtes.

1.6. Un client particulier : le window manager

                  Le modèle sous‑jacent au système de fenêtrage X a prévu la présence d'un client particulier, le gestionnaire de fenêtres ou window manager, qui rend bien des services mais risque de troubler le programmeur débutant dans sa compréhension de la communication avec le serveur.

 

                  Le window manager offre interactivement à l'utilisateur le service de déplacer les fenêtres, de changer leur taille, etc., en leur ajoutant une décoration qui lui est propre et qui permet d'uniformiser l'apparence des différentes applications présentes à l'écran.  Ainsi la manière dont les fenêtres sont déplacées, fermées, etc. se trouve rendue indépendante des applications et peut même être adaptée aux goûts de chaque utilisateur[2]. La contrepartie à payer par le programmeur va être que les fenêtres pourront être modifiées en dehors de son programme. Il faudra donc prévoir quoi faire si des modifications surviennent et agir en conséquence — par exemple prévoir le réaffichage sur réception d'un événement indiquant un changement de taille de fenêtre.

 

La redirection des requêtes concernant les fenêtres

 

                  Le window manager joue un rôle privilégié dans l'organisation de l'écran. Il est important de noter qu'il ne s'intéresse cependant qu'aux fenêtres les plus hautes dans la hiérarchie des fenêtres, c'est-à-dire celles qui sont directement filles de la racine[3]. Le window manager impose sa politique en ce qui concerne leur taille, position, couleur et épaisseur de bord, etc. Pour cette mise en œuvre, un mécanisme interne prévoit (par défaut[4]) que les requêtes portant sur la création, l'affichage et plus généralement la géométrie des fenêtres, soient interceptées par le window manager qui modifie alors ces requêtes avant qu'elles ne soient effectivement exécutées sur sa demande par le serveur. Ce phénomène est qualifié de redirection car les requêtes des clients sont redirigées sous forme d'événements au window manager (cf. figure 1.9.). Un seul client à la fois peut sélectionner l'événement déclenchant ce mécanisme de redirection, de sorte qu'il ne peut y avoir qu'un seul window manager à la fois connecté à un même serveur.

i.Communication:modèle client/serveur;

 

fig. 1.9. La redirection des requêtes d’un client vers le window manager

 

Ce mécanisme de redirection des requêtes trouble souvent les programmeurs débutants sous X‑Window. Ils pourront être surpris, par exemple, s'ils cherchent simplement à déplacer une fenêtre principale de leur programme via un appel à XMoveWindow, car le résultat peut dépendre du window manager en présence. Une requête comme XMoveWindow est en réalité redirigée sous la forme d’un événement de demande de reconfiguration au window manager. Le window manager est alors libre de reformuler à sa manière la requête avant qu’elle ne soit effectuée par le serveur[5].

i.Communication:modèle client/serveur;

                  On remarque qu'ainsi un programme donné n'a pas toujours le même comportement. Ce comportement varie selon le contexte dans lequel il est exécuté (il dépend par exemple ici du window manager en présence).

 

                  Cette situation peu courante en programmation ne fait que souligner un aspect important de la programmation sous X-Window : les programmes partagent des ressources. Il ne faut donc plus penser un programme comme définissant un processus isolé, mais comme l'exécution d'un processus dans un contexte. Connaître la librairie Xlib, c'est en réalité connaître le modèle "sémantique" sous-jacent. C'est ce modèle que nous allons développer peu à peu au cours des différents chapitres de ce livre.

 

 

1.7. Quelques clients et options standards

                  L’objet de ce livre étant de traiter de la programmation sous X-Window, nous renvoyons le lecteur à la documentation utilisateur pour les descriptions détaillées des commandes standards du système X-Window. Nous citons cependant ici à titre indicatif quelques clients intéressants :

twm et uwm                                Ce sont les window manager distribués en standard. Il existe aussi d’autres window manager qui font partie de contributions quasi standards, par exemple gwm ou les window manager proposés par les toolkits : mwm, olwm, etc.

 

xterm                                         L’émulateur de terminal. Il propose une fenêtre réagissant comme un terminal Tektronix ou VT100.

 

bitmap                                      L’éditeur de bitmap. Cette commande permet de créer interactivement des motifs permettant de réaliser des icônes ou de paver le fond des fenêtres.

 

et d'autres commandes bien utiles :

 

xrefresh                                Cette commande rafraîchit tout l’écran. On l’utilisera chaque fois que l’affichage sera perturbé par une impression extérieure.

 

xset                                            Cette commande permet de modifier diverses caractéristiques du serveur comme le répertoire où charger les fontes, la vitesse de la souris et certaines caractéristiques de l’écran.

 

xsetroot                                Cette commande permet de modifier le fond d’écran.

 

xhost                                         Ce client gère les droits de connexion au serveur des programmes tournant sur les autres machines.

                                                                    

xwininfo                                Cette commande donne interactivement des renseignements sur les fenêtres affichées à l’écran. xwininfo peut être utile pour déboguer un programme, ou pour connaître rapidement les valeurs des attributs de la racine d’un écran.

 

xfd                                               Cette commande permet de visualiser les différentes fontes accessibles au serveur. En particulier, elle permet de visualiser la fonte cursor et de choisir ainsi un curseur.

 

xlsfonts                                Cette commande imprime la liste des noms des fontes disponibles.

 

xrdb                                            Cette commande permet de modifier la propriété RESOURCE_MANAGER associée à la racine. C'est cette propriété qui est utilisée par les applications pour créer une base de ressources. De cette manière, on peut charger un fichier de définition de ressources sur la racine.

                  Une base de ressources est une base de données permettant à l’utilisateur de définir un certain nombre de valeurs pour des paramètres fréquemment utilisés par une ou plusieurs applications. A l'origine, les bases de ressources ont été conçues pour permettre aux applications qui utilisent une même toolkit de partager les mêmes options pour les attributs des objets interactifs (couleurs de fond, largeur de bord, etc.). Ainsi la cohérence des différentes interfaces se trouve augmentée et l'utilisateur peut indiquer lui-même les valeurs d'options qu'il préfère.

 

                  Une base de ressources est constituée par la fusion de données stockées dans des fichiers de ressources ou dans des propriétés[6] associées à la racine. Les valeurs d'attributs d'objets de l'interface pourront donc être fournies par l’utilisateur de différentes manières :

                  • elles pourront être lues dans des fichiers que l'application chargera dans la base de ressources. (Nous reviendrons dans le chapitre 10 sur les fonctions permettant d'accéder aux ressources et sur l’algorithme de chargement des ressources.)

                  • elles pourront être indiquées en option au moment même du lancement de la commande.

 

                  Retenons simplement pour l'instant que, du fait de l'existence de ressources partagées, certaines options sont devenues standards et sont reconnues par de nombreuses applications. Il est donc souhaitable d’utiliser la même syntaxe si l’on veut fournir à l’utilisateur des options similaires sur la ligne de commande[7].

 

                  Par exemple, les options qui suivent sont des options standards :

-d ou -display str          Cette option introduit une chaîne indiquant dans un certain format le serveur auquel l’application veut se connecter.

 

-fg couleur                             Le nom de la couleur d’avant-plan (foreground).

 

-bg couleur                             Le nom de la couleur d’arrière-plan (background).

 

-rv                                               Pour afficher l’application en inverse vidéo (reverse video).

 

-fn fonte                                  Le nom de la fonte normalement utilisée (font normal).

 

-fb fonte                                  Le nom de la fonte grasse utilisée (font bold).

 

-synchronous                    Pour synchroniser les requêtes.

 

-title str                              Le titre de la fenêtre principale (pour le window manager).

 

-geometry format             La taille et/ou position de la fenêtre principale spécifiés dans un certain format.

 

                  Le format d'un argument de type geometry satisfait la syntaxe suivante :

 

[<largeur>x<hauteur>][{+-}<abscisse>{+-}<ordonnée>]

 

                  On peut parfois aussi indiquer la position géométrique des fenêtres directement après un signe = (qui est équivalent à l’option -geometry, sauf que les arguments sont alors collés derrière le signe d’égalité, sans espace). Selon les programmes, les arguments largeur et hauteur seront mesurées en points d'écran ou en taille de caractères. Ainsi, on peut lancer la commande xterm avec les options suivantes :

 

% xterm  -geometry 80x24+50+150

 

et la commande xclock avec

 

% xclock -geometry 600x400+50+150

 

 

fig. 1.10. Le repérage géométrique des fenêtres

 

 

                  Le programme xterm affichera alors une fenêtre de 24 lignes et 80 colonnes, en position (50,150) sur l'écran, et le programme xclock affichera une fenêtre large de 600 points et haute de 400 points.

 

                  On notera que le repérage des coordonnées sur la racine s'effectue par rapport au point de gauche le plus haut de l'écran. L'abscisse des points est orientée de gauche à droite comme dans un repère cartésien habituel, mais les ordonnées sont orientées vers le bas (cf. illustration figure 1.10.).



[1] Le terme de pixel est en fait ambigu. Dans les contextes de dimension, un pixel désigne la taille occupée par un point de l'écran. Dans les contextes où l'on parle de couleurs, un pixel désigne une couleur par un indice dans une table de couleurs.

[2] En effet, la plupart des window manager permettent à l'utilisateur d'indiquer ses préférences, soit interactivement, soit dans un fichier de démarrage.

[3] La racine est la fenêtre constituant le fond de l'écran.

[4] Bien qu'il soit possible en principe de se libérer de ce mécanisme (via l'attribut override_redirect des fenêtres), on ne le fera que dans certains cas bien particuliers (cas d'un menu déroulant par exemple).

[5] En outre, dans ce cas particulier, même si le window manager ne modifie pas les paramètres de la requête du client, il risque d’y avoir ambiguïté de la demande elle-même. En effet, XMoveWindow demande à ce que l’on déplace la fenêtre en un point donné relativement à l'origine de son parent. Or, beaucoup de window manager "reparentent" les fenêtres — ce qui a la fâcheuse conséquence de modifier l'origine par rapport à laquelle est repérée la fenêtre. Ainsi, au lieu d'être déplacée relativement au fond d'écran (parent initial indiqué dans la requête de création), la fenêtre sera placée relativement à la fenêtre englobante,  nouveau parent créé par le window manager pour lui permettre d’afficher ses décorations.

        Cet état de choses, s'il inquiète le novice, n'est en réalité pas très gênant car le client peut s’il le demande, être informé de tous les changements opérés sur ces fenêtres au travers d’événements. Il pourra donc faire en sorte que son programme s’adapte à toutes les situations. Il existe par exemple un événement de type ReparentNotify qui permet de savoir si le parent d’une fenêtre a été modifié. Le tout est d'être averti de ce qui peut se produire et de préparer l'application en conséquence.

[6] La propriété RESOURCE_MANAGER peut par exemple être modifiée avec la commande xrdb qui permet de charger un fichier de ressources.

[7] En outre, ces options pourront être lues relativement facilement avec des fonctions spéciales qui seront présentées chapitre 10. En particulier XParseGeometry permet de lire une option de positionnement géométrique et XParseCommand permet d'intégrer les options de la ligne de commande à une base de ressources.