5

La souris et le clavier

 

 

 

            Dans ce chapitre nous allons dŽtailler tous les ŽvŽnements concernant la souris et le clavier.

 

5.1. La saisie des ŽvŽnements clavier et souris

            Nous avons vu dans le chapitre prŽcŽdent que pour recevoir un ŽvŽnement il fallait en avoir fait la demande auprs du serveur en sŽlectionnant cet ŽvŽnement sur une fentre. En rŽalitŽ cette condition n'est pas suffisante pour les ŽvŽnements sur les dispositifs dÕentrŽe. Comme nous l'avons dŽjˆ ŽnoncŽ dans le chapitre prŽcŽdent, une seule fentre, ˆ un instant donnŽ, recevra les ŽvŽnements concernant le clavier, et une seule fentre (pas nŽcessairement la mme), recevra les ŽvŽnements de mouvements et boutons de souris.

            En effet, les dispositifs d'entrŽe sont partagŽs par toutes les applications tournant ˆ l'Žcran mais ne concernent ˆ un instant donnŽ qu'une seule fentre. Par exemple, lÕenfoncement dÕune touche du clavier opŽrŽ par lÕutilisateur ne concerne ˆ un instant donnŽ quÕune seule application.

 

            Le problme de la destination des actions de l'utilisateur sur le clavier et la souris est rŽsolu ˆ lÕaide dÕun certain nombre de rgles. La propagation des ŽvŽnements (gouvernŽe par les attributs event_mask et do_not_propagate_mask des fentres) est l'une de ces rgles, mais il y a d'autres notions qui interviennent, que nous allons dŽtailler dans ce chapitre. Dans cette premire section, nous allons d'abord dŽcrire le comportement par dŽfaut du systme, tout en introduisant deux notions fondamentales permettant de comprendre l'attribution des dispositifs d'entrŽe.

Le focus du clavier

La fentre recevant les entrŽes des touches du clavier est dite avoir le focus du clavier[1].  La convention par dŽfaut du systme est que c'est la fentre racine qui a le focus. Une fentre qui a le focus en fait bŽnŽficier ses descendantes et il s'ensuit que c'est la fentre situŽe sous le curseur de souris qui bŽnŽficie par dŽfaut du focus du clavier.

            Concernant le focus, les window manager mettent en Ļuvre deux conventions : la convention par dŽfaut,  selon laquelle la fentre ayant le focus est la fentre situŽe sous le pointeur de souris, et une deuxime convention, selon laquelle c'est l'utilisateur qui dŽtermine le focus en cliquant prŽalablement sur la fentre concernŽe ou sur sa bannire. Pour implanter cette deuxime convention, les window manager utilisent la fonction XSetInputFocus qui permet dÕattribuer le focus du clavier ˆ une fentre autre que la racine.

 

XSetInputFocus (dpy, focus_win, revert_to, time)

Display*      dpy ;

Window       focus_win, revert_to ;

Time           time ;

 

            XSetInputFocus attribue le focus ˆ la fentre focus_win. Il sÕensuit que seule cette fentre et ses descendantes pourront ensuite recevoir les ŽvŽnements KeyPress et KeyRelease. La fentre qui reoit le focus doit tre visible, sinon une erreur de type BadMatch est gŽnŽrŽe au moment de l'exŽcution de la requte.

 

            On peut passer pour focus_win un identificateur de fentre ou les constantes PointerRoot (pour dŽsigner la racine contenant le pointeur ˆ ce moment) ou None pour annihiler tout ŽvŽnement sur le clavier jusqu'au prochain changement de focus. L'argument revert_to indique vers quelle fentre rediriger le focus si la fentre accaparante devenait invisible. On peut indiquer l'une des constantes RevertToParent, RevertToPointerRoot ou RevertToNone. Cet argument n'est consultŽ que si l'on a passŽ un vŽritable identificateur de fentre (sinon, on aura passŽ PointerRoot ou None, et dans les deux cas, un changement de focus ne pourra se produire que par un nouvel appel ˆ XSetInputFocus). Quant ˆ l'attribut time, il prŽcise quand le changement de focus doit prendre effet. On peut passer une estampille du temps en millisecondes ou la constante CurrentTime.

 

            Un appel ˆ XSetInputFocus gŽnre lÕenvoi dՎvŽnements de type FocusIn et FocusOut aux fentres acquŽrant et perdant respectivement le focus.

 

 

 

La saisie de la souris

            La fentre recevant les ŽvŽnements souris est par dŽfaut la fentre situŽe sous le pointeur de souris, sauf en cas de saisie du pointeur.

 

            On appelle saisie (grab en anglais) la monopolisation d'un dispositif d'entrŽe par une fentre.  En cas de saisie, les ŽvŽnements concernant le dispositif sont uniquement acheminŽs vers la fentre accaparante. Autrement dit, les autres fentres ne peuvent plus recevoir d'ŽvŽnement concernant ce dispositif, et c'est pourquoi l'on dit que le dispositif a ŽtŽ saisi. Contrairement ˆ ce que l'on pourrait croire, les cas de saisie sont frŽquents car le systme dŽclenche automatiquement une saisie du pointeur sur l'occurrence d'un ŽvŽnement de type ButtonPress. Entre deux ŽvŽnements de type ButtonPress et ButtonRelease les ŽvŽnements souris sont acheminŽs uniquement pour la fentre ayant sŽlectionnŽ lÕenfoncement de bouton, et ce, jusquՈ ce que tous les boutons de la souris soient rel‰chŽs. On parle dans ce cas de saisie automatique du pointeur.

 

En cas de saisie, la fentre recevant les ŽvŽnements souris nÕest plus nŽcessairement celle dans laquelle se trouve le pointeur. En effet, c'est la fentre qui a reu l'ŽvŽnement ButtonPress qui reoit alors les ŽvŽnements souris, mme si le pointeur sort de cette fentre. En outre, aucune autre fentre ne reoit plus dՎvŽnement souris, mme si la fentre accaparante n'en a pas sŽlectionnŽ d'autre.

            Les rgles que nous venons d'Žnoncer dŽcrivent le comportement par dŽfaut du systme. En rŽalitŽ, tout client peut sortir de ces conventions et demander au serveur la saisie d'un dispositif d'entrŽe ou la modification des paramtres de la saisie dŽclenchŽe automatiquement par le systme sur lՎvŽnement ButtonPress. Mais avant de dŽtailler les phŽnomnes de saisie, nous allons prŽsenter les ŽvŽnements souris et donner un exemple simple de programmation.

           

 

5.2. ButtonPress, ButtonRelease et MotionNotify

ButtonPress

ButtonRelease

Ces ŽvŽnements sont envoyŽs si un bouton de la souris est enfoncŽ ou rel‰chŽ. On les sŽlectionne avec les masques ButtonPressMask et ButtonReleaseMask. Ces ŽvŽnements sont envoyŽs ˆ la fentre dans laquelle se trouvait la souris au moment de l'enfoncement du bouton, sauf en cas d'appropriation de la souris. Un ŽvŽnement de type XButtonEvent contient un champ button permettant de conna”tre le numŽro du bouton qui est enfoncŽ ou rel‰chŽ.

            Comme nous l'avons dŽjˆ indiquŽ, l'enfoncement d'un bouton de souris dŽclenche automatiquement une saisie de la souris par la fentre ayant sŽlectionnŽ cet ŽvŽnement. Cette fentre reoit ensuite les ŽvŽnements souris qu'elle a sŽlectionnŽs, quelque soit la position de la souris et jusqu'ˆ ce que tous les boutons soient rel‰chŽs. Durant toute cette pŽriode, les autres fentres ne reoivent plus d'ŽvŽnement souris. Pour prŽvenir l'utilisateur, le curseur de souris garde la forme qui lui Žtait attribuŽ dans la fentre accaparante, mme s'il se trouve ˆ l'extŽrieur.

 

            Les avantages de la saisie automatique sont nombreux. Sans ce mŽcanisme, il serait impossible de faire glisser un objet sur l'Žcran en l'entra”nant avec la souris, car cet objet cesserait de recevoir les ŽvŽnements ds que la souris sortirait de la fentre.  Il y a cependant des cas o ce comportement n'est pas avantageux pour l'application.

 

            On peut supprimer cette redirection des ŽvŽnements vers la fentre accaparante au niveau de l'application en ajoutant le masque OwnerGrabButtonMask[2] au masque de sŽlection de l'ŽvŽnement bouton. Dans ce cas, les ŽvŽnements souris sont envoyŽs aux fentres de l'application se trouvant sous le curseur jusqu'ˆ ce que la saisie prenne fin.

 

            La saisie du pointeur reste cependant active dans les fentres des autres clients, et ces derniers ne reoivent plus d'ŽvŽnement souris. L'intŽrt de ce mŽcanisme est que l'application qui a sŽlectionnŽ un ŽvŽnement de type ButtonPress est toujours assurŽe de pouvoir rŽcupŽrer l'ŽvŽnement ButtonRelease correspondant, mme s'il se produit dans la fentre dÕun autre client. CÕest pourquoi, la saisie nÕest pas entirement dŽsactivŽe par le masque OwnerGrabButtonMask et elle continue d'affecter les fentres des autres clients.i.Souris:saisie des ŽvŽnements;

 

            DŽtaillons pour ces premiers ŽvŽnements, les champs accessibles dans la structure XButtonEvent associŽe[3]. Une structure d'ŽvŽnement contient quel que soit son type les cinq champs de la structure XAnyEvent que nous avons dŽjˆ commentŽs dans le chapitre prŽcŽdent. On aura donc accs ici (comme pour n'importe quel type d'ŽvŽnement) aux champs type, window, serial, display et send_event. Les autres champs disponibles sont les suivants :

 

 

Window root                 Ce champ indique la racine sur laquelle s'est produit l'ŽvŽnement.

 

Window subwindow       Ce champ indique la sous-fentre dans laquelle s'est produit l'ŽvŽnement quand elle diffre de la fentre qui l'a reu (champ window).

 

Time time                     Le champ time indique l'heure de l'ŽvŽnement en millisecondes. Le type Time correspond en rŽalitŽ au type unsigned long. Ce champ devient par consŽquent faux quand sa valeur dŽpasse 32 bits, ce qui se produit environ tous les 49,7 jours[4].

 

int x,y                           Les champs x et y fournissent les coordonnŽes du pointeur au moment de l'ŽvŽnement, relativement ˆ la fentre ayant reu l'ŽvŽnement (champ window).

 

int x_root, y_root     Ces champs fournissent les coordonnŽes du pointeur au moment de l'ŽvŽnement relativement ˆ la racine sur laquelle l'ŽvŽnement s'est produit (champ root).

 

unsigned int state          L'entier state est un masque qui fournit l'Žtat des modifieurs[5] et des boutons juste avant l'ŽvŽnement.  On pourra tester le champ state gr‰ce aux entiers symboliques ŽnumŽrŽs figure 5.1.

 

                 Mod1Mask       Button1Mask

ShiftMask        Mod2Mask       Button2Mask

LockMask         Mod3Mask       Button3Mask

ControlMask      Mod4Mask       Button4Mask

                 Mod5Mask       Button5Mask

 

fig. 5.1. Les masques d'Žtat des boutons et modifieurs

 

unsigned int button        DŽtail indiquant le numŽro du bouton qui a ŽtŽ enfoncŽ ou rel‰chŽ. Les valeurs possibles sont Button1, Button2, etc. (ˆ ne pas confondre avec les masques de la figure 5.1).

 

Bool same_screen        Le boolŽen same_screen indique si le pointeur est toujours sur le mme Žcran. Ce champ sert dans les cas de saisie du pointeur quand la souris peut changer dՎcran.

 

            La plupart de ces champs sont Žgalement disponibles dans les ŽvŽnements de mouvement de souris que nous allons dŽcrire maintenant.

 

 

Les mouvements de souris

 

MotionNotify

La souris a bougŽ. Ce type d'ŽvŽnement est envoyŽ pour tous les mouvements de souris ; mais les mouvements de souris Žtant trs nombreux, on peut restreindre le nombre des ŽvŽnements envoyŽs en indiquant des contraintes par des masques de sŽlection spŽcifiques. 

             Les masques permettant de sŽlectionner des mouvements souris sont PointerMotionMask, ButtonMotionMask et Button<n>MotionMask, o n est un entier compris entre 1 et 5. Les mouvements ne seront tous envoyŽs que s'ils ont ŽtŽ sŽlectionnŽs avec PointerMotionMask.  ButtonMotionMask limite l'envoi des ŽvŽnements aux mouvements se produisant lorsqu'un bouton de souris est enfoncŽ. On peut mme restreindre encore l'envoi des ŽvŽnements ˆ ceux se produisant lorsquÕun bouton particulier de la souris est enfoncŽ, commele deuxime bouton avec Button2MotionMask. Ces masques de sŽlection peuvent tre combinŽs entre eux.

 

            La structure associŽe ˆ un ŽvŽnement de type MotionNotify est de type XMotionEvent et on y accde via le membre xmotion de lÕunion XEvent. Elle contient les cinq champs de la structure XAnyEvent et les neufs champs que nous avons dŽjˆ commentŽs pour les ŽvŽnements ButtonPress et ButtonRelease.  Ces neufs champs supplŽmentaires sont en rŽalitŽ communs ˆ tous les ŽvŽnements sur les entrŽes  (i.e. aux ŽvŽnements de type ButtonPress, ButtonRelease, KeyPress, KeyRelease, MotionNotify, EnterWindow  et LeaveWindow) et ont ŽtŽ regroupŽs figure 5.2.

 

    Window root;                        /* racine sur laquelle l'ev.  s'est produit */

    Window subwindow;                    /* fentre fille o s'est produit l'ev.*/

    Time time;                                            /* horaire en millisecondes */

    int x, y;                                     /* coordonnŽs relatives ˆ  window */

    int x_root, y_root;                       /* coordonnŽs relatives ˆ root */

    unsigned int state;     /* Žtat des boutons et modifieurs (masque) */

    Bool same_screen;                /* si le pointeur est sur le mme Žcran*/

 

fig. 5.2. Neuf champs communs aux ŽvŽnements dÕentrŽe

(en sus de ceux communs ˆ tous les ŽvŽnements)

            En outre, un champ supplŽmentaire is_hint  (de type char) indique si l'ŽvŽnement MotionNotify a ŽtŽ envoyŽ normalement ou selon un mode spŽcial, prŽconisŽ par l'emploi du masque PointerMotionHintMask.

 

 

L'envoi de mouvements ŅsynchronisŽsÓ

 

i.Souris:saisie des ŽvŽnements;            Le masque PointerMotionHintMask, (de lÕanglais Hint, indication, trace ou piste) permet de rŽduire l'envoi des ŽvŽnements de mouvement et de mieux synchroniser leur traitement par l'application. Ce masque ne sŽlectionne pas en lui‑mme  de mouvement mais peut tre utilisŽ en association avec les masques de sŽlection (restreint ou non) ŽnumŽrŽs dans la section prŽcŽdente. Le masque PointerMotionHintMask demande en rŽalitŽ l'envoi dÕun seul ŽvŽnement de mouvement — un autre mouvement de souris n'Žtant envoyŽ ˆ lÕapplication que si le client en rŽitre explicitement la demande[6] via un appel ˆ XQueryPointer ou ˆ XGetMotionEvents.

            Ce mŽcanisme permet dÕignorer les mouvements qui se produisent pendant que lÕapplication traite les ŽvŽnements de mouvement quÕelle reoit. Sur rŽception d'un premier mouvement de souris, lÕapplication peut traiter cet ŽvŽnement et appeler alors XQueryPointer. Cette fonction lui permet ˆ la fois de rŽcupŽrer la position actuelle de la souris et de resŽlectionner lÕenvoi dÕun nouvel ŽvŽnement. La position du pointeur de souris qui est rŽcupŽrŽe par XQueryPointer est plus rŽcente que celle fournie par l'ŽvŽnement reu et donc mieux synchronisŽe avec lÕutilisateur. En outre, lÕapplication demandant les ŽvŽnements un ˆ un se trouve elle-mme mieux synchronisŽe avec le serveur car on Žvite d'accumuler du retard en ayant trop d'ŽvŽnements de mouvement ˆ traiter. On utilisera cette technique pour optimiser le suivi de la souris chaque fois que les positions intermŽdiaires de la souris peuvent tre ignorŽes[7]. LÕexemple dŽveloppŽ dans la section suivante utilise cette technique.

 

             La fonction XQueryPointer prend les arguments suivants :

 

Bool XQueryPointer ( dpy,  win,   &root_return,   &child_return,                                                                &root_x, &root_y, &win_x, &win_y, &state)

Display*                  dpy ;

Window                   win,

                              root_return, child_return;

int                          root_x_return, root_y_return ,win_x_return, win_y_return ;

unsigned int              state ;

 

            Grossirement, cette fonction permet de rŽcupŽrer la position de la souris relativement ˆ une fentre quelconque. L'argument win est une fentre choisie par le programmeur, et qui servira d'origine pour rapporter la position actuelle de la souris (dans win_x et win_y), sous rŽserve que la souris se trouve encore sur le mme Žcran. Dans ce cas, la fonction retourne True. Sinon, la fonction retourne False mais permet (dans tous les cas) de rŽcupŽrer la racine sur laquelle se trouve la souris (dans root_return) et la position de la souris relativement ˆ la racine (dans root_x et root_y). L'argument child_return rapporte l'identificateur de la fentre dans laquelle se trouve la souris et le masque state rapporte l'Žtat des boutons et des modifieurs.

 

5.3. Un exemple simple de programmation

            Dans cette section nous allons dŽtailler la programmation d'une barre de dŽfilement. La figure 5.3. donne quelques indications sur les noms des variables que lÕon va utiliser. Pour matŽrialiser la barre de dŽfilement, on va crŽer une fentre rectangulaire assez longue appelŽe barre. Pour matŽrialiser l'ascenseur nous utiliserons une seconde fentre plus petite que nous appellerons tirette. La seconde fentre Žtant toujours incluse dans la premire, nous pourrons la crŽer fille de la premire.

 

 

fig. 5.3. La programmation dÕune barre de dŽfilement

 

            Les contraintes matŽrielles sont que les deux fentres aient toujours mme hauteur et que la position en x de la tirette reste comprise entre 0 et LONGUEUR moins LARGEUR pour que la tirette ne sorte pas de la barre.

 

            Les ŽvŽnements ˆ sŽlectionner dŽpendent du fonctionnement de la barre de dŽfilement. Supposons ici que :

            „ quand on clique dans la barre, le centre de la tirette est amenŽ ˆ l'endroit o l'on vient de cliquer.

            „ quand on clique et que lÕon tire la souris en maintenant le bouton enfoncŽ, la tirette (c'est-ˆ-dire son centre) suit le mouvement de la souris sur l'axe de la barre.

            „ si on modifie la taille de la barre, la hauteur de la tirette s'adapte ˆ celle de la barre mais sa largeur initiale reste constante.

            Il rŽsulte de cette description que l'on doit sŽlectionner les ŽvŽnements ButtonPress sur la barre pour capter l'enfoncement du bouton, et sŽlectionner les mouvements de souris avec le masque ButtonMotionMask pour capter les mouvements qui se produisent quand un bouton de la souris est enfoncŽ. On doit Žgalement sŽlectionner les ŽvŽnements ConfigureNotify (ˆ l'aide du masque StructureNotifyMask) sur la barre pour capter les changements de taille de la barre et rŽajuster la hauteur de la tirette en consŽquence. On aurait pu sŽlectionner aussi les ŽvŽnements souris sur la tirette, mais cela n'est gure nŽcessaire ici du fait de la propagation des ŽvŽnements. Un ŽvŽnement qui se produit sur la tirette sera de toute faon propagŽ ˆ la barre[8].

 

            Pour rŽaliser un suivi rapide de la souris, on va demander ˆ ne recevoir qu'une trace des mouvements de souris. On utilisera donc le masque PointerMotionHintMask en combinaison avec ButtonMotionMask  pour sŽlectionner les mouvements. On fera ensuite appel ˆ XQueryPointer chaque fois qu'on recevra un ŽvŽnement de mouvement pour rŽcupŽrer la position actuelle de la souris et demander l'envoi du prochain mouvement au serveur. Dans notre exemple, on peut considŽrer que notre dispositif d'affichage n'a qu'un Žcran et rŽcupŽrer les coordonnŽes du pointeur relativement ˆ la barre avec XQueryPointer sans en tester la valeur de retour.

 

            Avant de nous lancer dans la programmation, reste encore ˆ se poser la question suivante[9] : Doit-on lever ou non la saisie de la souris dŽclenchŽe par lՎvŽnement ButtonPress ? (Si oui, il suffit de rajouter le masque OwnerGrabButtonMask lors de la sŽlection de l'enfoncement du bouton.i.Souris:saisie des ŽvŽnements;)

 

            Ici, la rŽponse est non. Au contraire, on se trouve dans un bon cas vis-ˆ-vis du comportement par dŽfaut. Il est souhaitable en effet que les ŽvŽnements souris soient toujours acheminŽs vers la barre, mme si la souris sort de la fentre. Sinon, on ne recevra plus d'ŽvŽnement quand la souris quitterait un peu la barre, et cela pourrait gner l'utilisateur si la barre est Žtroite. Par contre, il faut faire bien attention ˆ bloquer la tirette ˆ l'intŽrieur de la barre.

            Voici finalement une programmation possible de cette barre de dŽfilement, o l'on note l'abscisse de la tirette relativement ˆ la barre dans la variable tire_x, et l'abscisse de la souris relativement ˆ la  barre dans pt_x  :

 

#include <stdio.h>

#include <X11/Xlib.h>

#include <X11/bitmaps/gray3>         /* Pour le fond de la tirette */

 

#define         LONGUEUR 600         /* Les dimensions initiales de la barre */

#define         LARGEUR        50

 

char*                nom_de_station = NULL;

unsigned long     pixel_blanc, pixel_noir;

Display*           dpy;

Window            root,

                       barre,       /* la barre de defilement, ici fenetre principale */

                       tirette;      /* l'ascenseur proprement dit */

int               screen_n;     

 

/*

 * une procŽdure de sortie qui imprime un message d'erreur

 */

 

void erreur (message_format, a1, a2, a3)

char* message_format;

{

     printf (message_format, a1, a2, a3);

     exit (1);

}

 

/*

 * La boucle d'ŽvŽnements

 */

events ()  {

     XEvent                       ev;

     int                         tire_x,          /* position courante de la tirette */

                                      pt_x,           /* position de la souris */

                                      i_bidon;        /* entier bidon */

     int                         width,          /* les dimensions */

                                      height;          /* de la barre */

     Window                      w_bidon;       /* fentre bidon */

     XWindowAttributes xwa;

 

     tire_x = 0;              /* position courante de la tirette relativement ˆ la barre */

 

     /*

      * La largeur et la hauteur de la barre peuvent avoir ŽtŽ modifiŽes par l'intervention

      * du window manager et de l'utilisateur. Il faut rŽcupŽrer ces donnŽes avant de

      * procŽder au traitement des ŽvŽnements.

      */

     XGetWindowAttributes (dpy, barre, &xwa);

     width =   xwa.width;

     height      =   xwa.height;

    

     for (;;) {

 

         XNextEvent (dpy, &ev);

 

         switch (ev.type) {

 

                   case ButtonPress:

                       /*

                        * Si on est dans la barre il faut dŽplacer la tirette la o on a cliquŽ

                        */

                       if (ev.xbutton.subwindow != tirette) {

                            /*

                             * on doit tenir compte de la  position limite ˆ droite

                             */

                            if (ev.xbutton.x > width - LARGEUR/2)

                                      tire_x = width - LARGEUR;

                            else

                                      tire_x = ev.xbutton.x - LARGEUR/2;

                            if (tire_x  <  0)

                                      tire_x = 0;

                            XMoveWindow (dpy, tirette, tire_x, 0);

                       }

                       break;

 

                   case MotionNotify:

                       /*

                        * RŽcupŽrer la position actuelle de la  souris relativement ˆ la barre

                        */

                       XQueryPointer (dpy, barre, &w_bidon, &w_bidon, &i_bidon,                                                                                                                                                &i_bidon, &pt_x, &i_bidon, &i_bidon);

                       /*

                        * Un effet de bord de cette fonction (avec PointerMotionHintMask)

                        * est de sŽlectionner l'envoi du prochain mouvement de souris

                        *

                        * DŽplacer le centre de la tirette ˆ l'emplacement pointŽ en tenant

                        * compte des positions limites

                        */

                       tire_x = pt_x - LARGEUR/2;

 

                       if (tire_x < 0)

                                 tire_x = 0;

                       if (tire_x > width - LARGEUR)

                                 tire_x = width - LARGEUR;

                       XMoveWindow (dpy, tirette, tire_x, 0);

                       break;

 

                   case ConfigureNotify:

                       /*

                        * La barre a changŽ de taille, il faut modifier la hauteur de la tirette

                        */

                       height = ev.xconfigure.height;

                       XResizeWindow (dpy, tirette, LARGEUR, height);

                       break;

              }

         }

}

 

 

main (argc, argv) char **argv;

{

     Pixmap                           fond_gris;

     XSetWindowAttributes xsw;

     /*

      * Ouvrir la connexion

      */

     dpy = XOpenDisplay (nom_de_station);

     if (dpy == NULL)

              erreur ("%s: ne peut ouvrir la connexion a %s\n", argv[0],

                                 XDisplayName(nom_de_station));

     /*

      * Initialisations globales

      */

     screen_n       =   DefaultScreen (dpy);

     root             =   RootWindow (dpy, screen_n);

     pixel_blanc   =   WhitePixel (dpy, screen_n);

     pixel_noir     =   BlackPixel (dpy, screen_n);

 

     /*

      * CrŽer la barre de dŽfilement

      */

     barre = XCreateSimpleWindow (dpy, root, 200, 200, LONGUEUR,

                                                    LARGEUR, 2, pixel_blanc, pixel_noir);

    

     /*

      * SŽlectionner ButtonPress et ConfigureNotify sur la barre et les mouvements

      * souris restreints aux mouvements se produisant bouton enfoncŽ, plus le masque

      * PointerMotionHintMask pour nÕavoir trace que dÕun ŽvŽnement ˆ la fois

      */

     XSelectInput (dpy, barre, ButtonPressMask | ButtonMotionMask |

                                          PointerMotionHintMask | StructureNotifyMask);

    

     /*

      * CrŽer le fond de la tirette

      */

     fond_gris = XCreateBitmapFromData (dpy, root, gray3_bits, gray3_width,

                                                         gray3_height);

     xsw.background_pixmap = fond_gris;

    

     /*

      * Puis crŽer la tirette ˆ gauche dans la barre

      */

     tirette = XCreateWindow (dpy, barre, 0, 0, LARGEUR, LARGEUR, 0,

                                          CopyFromParent, InputOutput, CopyFromParent,

                                          CWBackPixmap, &xsw);

 

     /*

      * Afficher les fentres avant de rŽcupŽrer les ŽvŽnements 

      */

     XMapSubwindows (dpy, barre);

     XMapWindow (dpy, barre);

 

     events ();

}

 

5.4. Monopolisation du clavier ou de la souris

            De manire gŽnŽrale, on peut s'approprier la souris, le clavier, un bouton, ou une combinaison de touches du clavier et mme le serveur[10]. Des conventions rŽgissent lÕusage des fonctions dÕappropriation mais en dernire instance, ce sera le premier client en ayant exprimŽ la demande qui sera rendu ma”tre du dispositif.

            On a vu dans la section 5.1. que le terme de saisie dŽsignait l'appropriation des ŽvŽnements relatifs ˆ un dispositif par une fentre appelŽe alors fentre accaparante (ou grab-window). Les ŽvŽnements concernant le dispositif saisi ne suivent plus alors le mŽcanisme standard de propagation et sont systŽmatiquement envoyŽs ˆ la fentre accaparante quelle que soit la position de la souris[11].

 

 

Monopolisation de la souris

 

            La fonction permettant dÕaccaparer la souris est la fonction XGrabPointer.

i.Souris:saisie des ŽvŽnements;

int XGrabPointer(dpy, grab_window, owner_events, event_mask, pointer_mode,

                                    keyboard_mode, confine_to, cursor, time)

Window                   grab_window ;

Bool                        owner_events ;

unsigned int              event_mask ;

int                          pointer_mode ;

int                          keyboard_mode  ;

Window                   confine_to ;

Cursor                     cursor ;

Time                       time ;

 

            LÕargument grab_window spŽcifie la fentre accaparante, le boolŽen owner_events prŽcise si les ŽvŽnements doivent tre acheminŽs normalement pour les fentres de lÕapplication. (S'il est positionnŽ ˆ True, le monopole dŽclenchŽ est analogue au monopole automatiquement dŽclenchŽ aprs sŽlection de l'ŽvŽnement ButtonPress par ButtonPressMask | OwnerGrabButtonMask.) Le masque event_mask permet de spŽcifier les ŽvŽnements sŽlectionnŽs durant le processus de saisie, annulant ceux prŽcŽdemment sŽlectionnŽs par la fentre ; pointer_mode indique si les ŽvŽnements doivent tre envoyŽs de manire synchrone ou asynchrone (GrabModeSync ou GrabModeAsync) ; confine_to spŽcifie si le pointeur doit rester bloquer dans une fentre particulire durant la saisie (passer None sinon) ; cursor indique la forme du pointeur pendant la durŽe de la saisie et time spŽcifie quand doit tre dŽclenchŽ le processus de saisie. On peut passer la constante CurrentTime. La constante de retour de cette fonction peut valoir GrabSuccess, GrabNotViewable, AlreadyGrabbed, GrabFrozen ou GrabInvalidTime. Cette requte gŽnre des ŽvŽnements EnterNotify et LeaveNotify .

i.Souris:saisie des ŽvŽnements;

            Le dŽclenchement de la saisie automatique sur l'ŽvŽnement ButtonPress est en fait Žquivalent ˆ un appel ˆ XGrabPointer en mode asynchrone, avec le masque de sŽlection d'ŽvŽnements event_mask de la fentre. La prŽsence o lÕabsence du masque OwnerGrabButtonMask dans ce masque de sŽlection revient ˆ dŽclencher la requte avec le masque owner_events ˆ True ou False.

 

            Signalons Žgalement ˆ propos de la souris, la fonction XWarpPointer[12] qui permet de placer la souris n'importe o et de rŽcupŽrer le focus.

                      

 

 

Monopolisation du clavier

 

            Pour sÕapproprier le clavier on utilisera la fonction XGrabKeyboard.

int XGrabKeyboard(dpy, grab_window, owner_events, pointer_mode,

                                               keyboard_mode, time)

 

            Cette fonction gŽnre elle aussi lÕenvoi dՎvŽnements FocusIn et FocusOut aux applications intŽressŽes, le champ mode indiquant quÕil s'agit dÕune saisie;. Les arguments sont similaires ˆ ceux de XGrabPointer. Selon la valeur de owner_events, on peut ici encore limiter la saisie et garder un acheminement normal des ŽvŽnements ˆ l'intŽrieur de l'application.

 

Les saisies passives

 

            De manire gŽnŽrale, une saisie peut tre activŽe par :

            „ L'enfoncement d'un bouton de souris. C'est le comportement par dŽfaut du systme (automatic grab).

            „ Un appel ˆ XGrabPointer ou XGrabKeyboard. C'est une saisie brutale et les applications ne doivent pas en abuser.

            „ La rŽalisation dÕactions dŽfinies lors d'une appropriation passive. Une telle appropriation conditionnelle aura ŽtŽ prŽcŽdemment demandŽe par un appel ˆ XGrabButton ou ˆ XGrabKey.

i.Souris:saisie des ŽvŽnements;

            Les saisies passives sont des demandes de saisies qui ne sont pas immŽdiatement dŽclenchŽes. On parle alors de saisies passives, par opposition aux saisies actives, o la saisie est directement activŽe par la requte[13].

            Les fonctions suivantes permettent de demander une saisie qui ne sera activŽe que si certains ŽvŽnements particuliers se produisent :

XGrabButton(dpy, button, modifiers, grab_window, owner_events, event_mask,

                              pointer_mode, keyboard_mode, confine_to, cursor)

XGrabKey;(dpy, keycode, modifiers, grab_window, owner_events, pointer_mode,

                              keyboard_mode)

unsigned int  button, keycode ;

unsigned int  modifiers ;

Window       grab_window ;

Bool            owner_events ;

unsigned int  event_mask ;

int               pointer_mode,

                  keyboard_mode ;

Window       confine_to ;

Cursor         cursor ;

            Ainsi, certains programmes autorisent lÕutilisateur ˆ choisir une combinaison de touches pour faire appara”tre un menu n'importe o. En rŽalitŽ, le clavier est alors lÕobjet dÕune saisie passive effectuŽe avec XGrabKey, saisie qui n'est activŽe que par lÕenfoncement de cette combinaison de touches. Une saisie passive a alors gain de cause sur la saisie active dŽclenchŽe par un simple enfoncement du bouton (sans la combinaison de touches).

            Des fonctions symŽtriques permettent d'interrompre ces processus d'appropriation :

 

XUngrabPointer(dpy, time)

XUngrabKeyboard(dpy, time)

XUngrabKey(dpy, keycode, modifiers, grab_window)

XUngrabButton(dpy, button, modifiers, grab_window)

            i.Souris:saisie des ŽvŽnements;

            Il existe en outre une fonction qui permet de modifier les paramtres dÕune saisie active de la souris :

 

XChangeActivePointerGrab(dpy, event_mask, cursor, time)

 

 

5.5. EnterNotify et LeaveNotify

            Les ŽvŽnements d'entrŽe et sortie des fentres sont de type EnterNotify et LeaveNotify. Ces ŽvŽnements concernent la souris, mais traduisent en rŽalitŽ autant une propriŽtŽ des fentres franchies qu'un ŽvŽnement souris. En voici une brve description.

 

EnterNotify

LeaveNotify

La souris est entrŽe ou sortie d'une fentre. On sŽlectionne ces ŽvŽnements avec les masques EnterWindowMask et ;LeaveWindowMask. Ces ŽvŽnements se produisent lorsqu'on dŽplace la souris d'une fentre ˆ l'autre ou lorsqu'on affiche (ou retire) une fentre sous la souris.

           

            En effet, ces ŽvŽnements traduisent tous les changements de position du pointeur relativement aux empilements des fentres. Ils peuvent donc se produire non seulement quand la souris bouge et franchit une frontire, mais Žgalement quand les fentres se trouvent dŽplacŽes par une requte dans la pile. Le but est d'avertir les applications de la position de leurs fentres vis-ˆ-vis de la rŽception des ŽvŽnements concernant les entrŽes.

 

            La structure associŽe ˆ ce type d'ŽvŽnement dans l'union XEvent a le type XCrossingEvent et est accessible par le membre xcrossing de l'union. Cette structure partage de nombreux champs avec les ŽvŽnements souris ŽtudiŽs prŽcŽdemment. On retrouve en effet les neufs champs dŽcrits pour les ŽvŽnements boutons (cf. fig. 5.2.). En outre elle partage un certain nombre de champs avec les structures de type XFocusEvent correspondant aux ŽvŽnements de type FocusIn et FocuOut. Les champs supplŽmentaires sont les suivants : focus, detail et mode.

 

Bool focus                    Ce boolŽen indique si la fentre concernŽe a le focus du clavier.

 

            La fentre qui reoit EnterNotify est normalement avertie quÕelle va recevoir ensuite les ŽvŽnements souris. Normalement, elle reoit aussi le focus du clavier, sauf en cas dÕappropriation du focus. Pour savoir si la fentre dans laquelle entre la souris a le focus, il faut consulter le champs focus. Il est ˆ True si cette fentre a le focus (ou compte la fentre ayant le focus parmi ses anctres), et ˆ False sinon.

 

Les champs detail et mode

 

            Nous recommandons au lecteur de sauter cette section s'il n'a pas immŽdiatement besoin d'informations prŽcises sur les ŽvŽnements EnterNotify et LeaveNotify.

           

int detail                     Cet entier peut prendre les valeurs NotifyAncestor, NotifyVirtual, NotifyInferior, NotifyNonlinear ou NotifyNonlinearVirtual. Il indique la position de la fentre qui reoit l'ŽvŽnement relativement au mouvement du pointeur.

 

            Dans les cas simples o les deux fentres sont dans une relation de parentŽ directe, le champ detail indique la destination du mouvement pour un ŽvŽnement de sortie et son origine pour une entrŽe. Par exemple, si lÕon passe de F1 ˆ F2 (F2 Žtant fille de F1) selon le mouvement indiquŽ par la flche A sur la figure 5.4. (page 108), F1 recevra un ŽvŽnement LeaveNotify de dŽtail NotifyInferior (avec un champ subwindow ˆ F2). SymŽtriquement, F2 recevra un EnterNotify de dŽtail NotifyAncestor. Le mouvement inverse de F2 ˆ F1 donnerait un LeaveNotify de dŽtail NotifyAncestor et un EnterNotify de detail NotifyInferior.

 

            La situation se complique un peu quand on passe d'une fentre ˆ une autre, sans que les deux fentres soient dans un rapport de parentŽ directe. En effet, des ŽvŽnements EnterNotify et LeaveNotify sont Žgalement envoyŽs aux fentres qui se trouvent sur le chemin entre la fentre d'origine et la fentre destination dans la hiŽrarchie. On dit alors que ces fentres intermŽdiaires sont traversŽes virtuellement.

 

fig. 5.4. Les mouvements d'entrŽe/sortie de fentres

 

 

            Dans le cas o l'origine et la destination sont situŽes sur une mme branche (cas de parentŽ verticale), le champ detail de la structure event.xcrossing contiendra la valeur NotifyVirtual pour les fentres intermŽdiaires, l'origine et la destination recevant comme prŽcŽdemment un champ detail ˆ NotifyAncestor ou NotifyInferiror selon le sens du mouvement.

            Par contre, si l'origine et la destination du mouvement sont dans un rapport de parentŽ non vertical, elles recevront seulement une indication de dŽtail NotifyNonlinear et les fentres intermŽdiaires situŽes sur le chemin joignant l'une ˆ l'autre (l'anctre commun exclu) recevront un dŽtail ˆ NotifyNonlinearVirtual. La situation est la mme si l'on change d'Žcran : les fentres intermŽdiaires (jusqu'aux deux racines) recevront un dŽtail NotifyNonlinearVirtual et l'origine et la destination, un dŽtail NotifyNonlinear.

 

            Par exemple, un passage de F2 ˆ G2 illustrŽ par le mouvement de flche B sur la figure 5.4. provoquera un LeaveNotify sur F2 de dŽtail NotifyNonlinear, un LeaveNotify sur F1 de dŽtail NotifyNonlinearVirtual, un EnterNotify sur G1 de dŽtail NotifyNonlinearVitual avec subwindow G2 et un EnterNotify sur G2 de dŽtail NotifyNonlinear.

 

 

int mode                         Cet entier peut prendre les valeurs NotifyNormal, NotifyGrab ou NotifyUngrab. Il permet de savoir si l'ŽvŽnement provient  d'un mouvement normal ou s'il rŽsulte du dŽbut ou de la fin d'un processus de saisie.

 

            Des ŽvŽnements de type EnterNotify et LeaveNotify sont en effet envoyŽs quand la souris est saisie et que le pointeur ne se trouvait pas dŽjˆ dans la fentre accaparante.  SymŽtriquement, des ŽvŽnements seront envoyŽs aux applications pour avertir de la fin du processus de saisie (par exemple, sur le rel‰chement d'un bouton). CÕest le champs mode qui permet de savoir sÕil sÕagit dÕun cas simple (NotifyNormal) ou sÕil sÕagit dÕune notification de dŽbut ou de fin de saisie (NotifyGrab, ou NotifyUngrab).

            Par exemple, dans le cas o l'on passe de F2 ˆ F1, mais aprs enfoncement de bouton dans F2, on recevra les ŽvŽnements suivants : un LeaveNotify sur F2 de dŽtail NotifyAncestor, mode NotifyNormal, pour annoncer la sortie de fentre. Ensuite, ce n'est qu'aprs le rel‰chement du bouton que l'on recevra les ŽvŽnements souris sur F1 (car dans l'intervalle, la saisie est automatiquement activŽe sur F2). Quand le bouton sera rel‰chŽ sur F1, on recevra un LeaveNotify sur F2, de mode NotifyUngrab et dŽtail NotifyAncestor pour annoncer la fin de la saisie, et un EnterNotify sur F1, de mode NotifyUngrab avec detail NotifyInferior pour annoncer l'entrŽe dans la fentre F1 due ˆ la fin du processus de saisie. Les composantes du pointeur ˆ ce moment seront celles qu'il avait au moment o le bouton a ŽtŽ rel‰chŽ dans F1 (et non pas celles qu'il avait au moment du franchissement du bord). Ce n'est en effet qu'ˆ partir de ce moment lˆ que la souris est considŽrŽe comme Žtant dans F1 du point de vue de l'envoi des ŽvŽnements souris. Auparavant, c'est F2 qui recevait les ŽvŽnements.

 

 

            Une fin de saisie peut ainsi provoquer une cascade d'ŽvŽnements EnterNotify et LeaveNotify, en particulier quand on aura positionnŽ le masque OwnerGrabButtonMask. En effet, ce masque, qui permet aux fentres de recevoir les ŽvŽnements souris, ne supprime pas pour autant le phŽnomne de saisie, et il y a quand mme Žmission d'ŽvŽnements avertissant les fentres de la saisie.

i.Souris:saisie des ŽvŽnements;

            Ainsi , un passage de F2 ˆ G2, avec saisie (enfoncement de bouton dans F2, rel‰cher dans G2) et sŽlection du masque OwnerGrabButtonMask[14], dŽclenchera l'envoi des ŽvŽnements suivants :

 

LeaveNotify sur F2

       mode NotifyNormal, detail NotifyNonlinear

LeaveNotify sur F1

       mode NotifyNormal,  detail NotifyNonlinearVirtual

EnterNotify sur G1, subwindow G2

       mode NotifyNormal,  detail NotifyNonlinearVirtual

 EnterNotify sur G2

       mode NotifyNormal, detail NotifyNonlinear

ButtonRelease sur G2

LeaveNotify sur F2

       mode NotifyUngrab, detail NotifyNonlinear

LeaveNotify sur F1

       mode NotifyUngrab, detail NotifyNonlinearVirtual

EnterNotify sur G1, subwindow G2

       mode NotifyUngrab, detail NotifyNonlinearVirtual

EnterNotify sur G2

       mode NotifyUngrab, detail NotifyNonlinear

 

 

5.6. FocusIn et FocusOut

            On a vu que le modle dŽveloppŽ par la librairie Xlib comporte la notion de fentre ayant le focus du clavier. Par dŽfaut, c'est la fentre racine qui a le focus, ce qui a pour effet de transmettre le focus ˆ toute fentre dans laquelle se trouve la souris. Ainsi, les ŽvŽnements EnterNotify et LeaveNotify qui indiquent ˆ lÕapplication que la souris entre ou sort d'une fentre lui indiqueront aussi normalement si elle peut recevoir les entrŽes du clavier. Cependant, on l'a vu, la rŽception d'un ŽvŽnement  de type EnterNotify ne garantit pas toujours que la fentre qui le reoit ait le focus du clavier, ˆ cause des divers mŽcanismes de saisie. En particulier, pour conna”tre lՎtat du focus du clavier au moment o la souris entre dans une fentre, il faut consulter le champs focus de l'ŽvŽnement.

            En effet, plusieurs fonctions du systme peuvent perturber la convention par dŽfaut. Par exemple, la fonction  XSetInputFocus permet dÕattribuer le focus ˆ une fentre pourvu quÕelle soit affichŽe. Les ŽvŽnements FocusIn et FocusOut sont lˆ pour avertir les applications des changements de focus survenant ainsi, suite ˆ lÕappel dÕun client ˆ XSetInputFocus ou ˆ une fonction accaparant le clavier comme XGrabKeyboard ou XGrabKey.

 

 

FocusIn,  FocusOut          Ces ŽvŽnements sont sŽlectionnables par FocusChangeMask. Le champ dÕaccs de la structure dans une union XEvent est le membre xfocus. L'ŽvŽnement FocusIn informe l'application qu'une de ses fentres bŽnŽficie du focus du clavier et FocusOut qu'elle vient de le perdre.

 

            Ces ŽvŽnements sont assez analogues aux ŽvŽnements d'entrŽe/sortie des fentres. Ils traduisent en quelque sorte un mouvement du focus (les ŽvŽnements d'entrŽe/sortie traduisant un mouvement du pointeur) et permettent de dŽterminer si l'application peut ou non recevoir des entrŽes.

 

 

Quand une application a-t-elle le focus du clavier ?

 

            Pour rŽsumer ces diverses considŽrations sur le focus du clavier et son accessibilitŽ, voici une boucle de programme indiquant par un boolŽen nommŽ Clavier_accessible,  si lÕapplication peut ou non recevoir les entrŽes du clavier :

Bool     Clavier_accessible,

         Focus_saisi = False;

 

for (;;) {

   XNextEvent (dpy, &ev);

   switch (ev.type) {

 

         case FocusIn:

         /* On est sžr dÕavoir le focus du clavier */

               Focus_saisi = True;

               Clavier_accessible = True;

               break;

 

         case FocusOut:   

         /* On est sžr dÕavoir perdu le focus */

               Clavier_accessible = False;

               Focus_saisi = False;

               break;

 

         case EnterNotify:

         /* Il faut tester le champ focus de lÕev. */

               if (ev.xcrossing.focus)

                     then  Clavier_accessible = True ;

               else  Clavier_accessible = False;

               break;

 

         case LeaveNotify:

         /* Normalement, on nÕa pas le focus,

          * sauf si on a saisi le clavier

          */

               if (Focus_saisi)

                     then Clavier_accessible = True ;

               else Clavier_accessible = False;

               break;

   }

}

 

 

 

Les champs mode et dŽtail d'un changement de focus

 

             Nous invitons ici encore le lecteur ˆ sauter la fin de cette section s'il n'a pas rŽellement besoin de dŽtail sur les ŽvŽnements de changement de focus.

 

            Outre les cinq champs communs ˆ tous les types d'ŽvŽnements, les ŽvŽnements de changement de focus contiennent deux autres champs : un champ mode et un champ detail. (Il n'y a pas de champ focus car le type de l'ŽvŽnement indique en lui-mme si la fentre peut recevoir des entrŽes).

 

int mode                         Le champ mode est tout-ˆ-fait analogue ˆ celui des ŽvŽnements de type EnterNotify. Il indique si l'avertissement provient d'un simple changement de focus (i.e. d'un appel ˆ XSetInputFocus ) ou s'il rŽsulte d'un mŽcanisme de saisie (par exemple, un appel ˆ XGrabKeyboard). Il peut prendre les valeurs NotifyNormal, NotifyGrab, ou NotifyUngrab.

 

int detail                     Le champ detail des ŽvŽnements de type FocusIn et FocusOut peut prendre les valeurs suivantes :

                                     NotifyAncestor, NotifyVirtual, NotifyInferior, NotifyNonLinear, NotifyNonLinearVirtual,  NotifyPointer, NotifyPointerRoot, NotifyDetailNone.

 

              Son sens est trs voisin de celui des ŽvŽnements d'entrŽe/sortie de fentres, ˆ ceci prs qu'il s'agit d'un mouvement du focus au lieu d'un mouvement de souris. Comme dans le cas de EnterNotify et LeaveNotify, de nombreuses fentres sont susceptibles de recevoir ces ŽvŽnements. La valeur du champ detail est basŽe sur les distinctions suivantes :  fentre d'origine (ancien focus), fentre de destination (nouveau focus) et fentre du pointeur contenant la souris au moment du changement de focus.

            Dans les cas simples o l'origine et la destination sont dans un rapport de parentŽ vertical, le champ detail prendra la valeur NotifyAncestor et NotifyInferior pour les extrmes, et NotifyVirtual pour les fentres intermŽdiaires. Dans les cas o l'origine et la destination ne sont pas anctres l'une de l'autre, il prendra les valeurs NotifyNonlinear pour les extrŽmitŽs et NotifyNonlinearVirtual pour les fentres situŽes entres les deux, l'anctre commun Žtant exclu. Ce sera Žgalement le cas pour les changements de focus survenant sur des racines diffŽrentes.

 

            La prŽsence de la fentre contenant le pointeur complique un peu la situation. En effet, cette fois, des ŽvŽnements de changement de focus seront envoyŽs non seulement ˆ toutes les fentres situŽes entre la source et la destination dans la hiŽrarchie, mais Žgalement ˆ toutes les fentres situŽes entre la fentre contenant le pointeur et la destination, si cette dernire n'est pas une des extrŽmitŽs. Ainsi, la fentre contenant le pointeur et toutes les ascendantes concernŽes par ce changement de focus recevront Žgalement des ŽvŽnements de type FocusIn (ou FocusOut, selon les cas) avec un champ detail ˆ NotifyPointer.

 

            De plus, si une requte de saisie indique une attribution de focus ˆ PointerRoot ou None, toutes les fentres des racines concernŽes par l'acquisition recevront des ŽvŽnements FocusIn (et toutes les fentres concernŽes par la perte des ŽvŽnements de type FocusOut) avec detail ˆ NotifyPointerRoot ou NotifyDetailNone. Dans ce cas, les fentres origine et destination auront un champ detail ˆ NotifyNonlinear, NotifyPointerRoot ou NotifyDetailNone, selon la valeur passŽe en argument ˆ la requte de saisie.

 

            Si une fentre donnŽe perd le focus, le champ detail sera  NotifyNonlinearVirtual  pour toutes les fentres intermŽdiaires entre cette fentre et sa racine. En outre toutes les fentres (de toutes les racines) recevront un ŽvŽnement de dŽtail NotifyPointerRoot ou NotifyDetailNone indiquant l'acquisition (ou la perte) gŽnŽrale de focus. On voit bien dans ce cas qu'une mme fentre peut recevoir plusieurs ŽvŽnements de type FocusIn et FocusOut correspondant ˆ un mme changement de focus. Une fentre peut en effet tre avertie ˆ la fois de la perte de focus relative ˆ la perte de focus d'une fentre, et de l'acquisition de focus, relative ˆ l'acquisition gŽnŽrale, par passage de PointerRoot ˆ la requte de saisie.

           

 

5.7. La saisie de texte

             Dans cette section, nous allons commenter les diffŽrents ŽvŽnements qui concernent le clavier et donner un aperu de la saisie de texte au clavier (par des programmes ignorant la notion de localitŽ ou antŽrieurs ˆ la Release 5[15]).

 

 

L'ŽvŽnement KeymapNotify

 

            LՎvŽnement de type KeymapNotify permet de conna”tre lՎtat du clavier avant dÕen recevoir les entrŽes.

 

KeymapNotify             Cet ŽvŽnement est Žmis juste aprs un EnterNotify ou un FocusIn  car ces deux types dՎvŽnements sont susceptibles dÕindiquer ˆ lÕapplication quÕelle va recevoir des entrŽes. Il donne l'Žtat du clavier et sera utilisŽ par les applications qui souhaitent savoir dans quel Žtat se trouvait le clavier avant d'interprŽter des entrŽes.

 

            En fait l'ŽvŽnement KeymapNotify est rarement utilisŽ, car l'Žtat des modifieurs est dŽjˆ fourni par les ŽvŽnements EnterNotify et FocusIn, qui sont Žmis juste avant.  Le type de cet ŽvŽnement est XKeymapEvent. On pourra le sŽlectionner avec le masque KeymapStateMask et on accŽdera ˆ la structure associŽe dans une union XEvent via le champs xkeymap. Son principal champ est le champ key_vector qui donne un vecteur dՎtat des touches sous la forme dÕun masque de 32 caractres (256 bits). Le keycode dÕune touche donnŽe est le rang du bit signifiant dans le vecteur des touches. La fonction

 

XQueryKeymap(dpy, tableau)

Display*                  dpy ;

char                         tableau [32] ;

 

permet Žgalement de lire lՎtat du clavier. Cependant, elle est plus cožteuse, (comme toutes les fonctions en XQuery), quÕun appel ˆ XNextEvent - d'o l'intŽrt de cet ŽvŽnement.

 

 

Les ŽvŽnements KeyPress et KeyRelease

 

            On rŽcupre les entrŽes tapŽes au clavier au moyen des ŽvŽnements KeyPress et KeyRelease.

 

KeyPress

KeyRelease

Ces ŽvŽnements traduisent le fait quÕune touche du clavier a ŽtŽ enfoncŽe ou rel‰chŽe. Ces ŽvŽnements sont de type XKeyEvent et on y accde dans lÕunion XEvent par le membre xkey.

 

            La structure associŽe contient principa­lement le code Žmis par la touche (champ key­code).  Pour recevoir ces ŽvŽnements, il faut bŽnŽficier du focus du clavier et avoir sŽlectionnŽ ces ŽvŽnements par KeyPressMask (ou KeyReleaseMask). Les autres champs de la structure de type XKeyEvent sont les mmes que ceux que nous avons dŽjˆ dŽtaillŽs pour les ŽvŽnements souris (cf. figure 5.2). On pourra donc accŽder aux champs window, root, subwindow, x, y, x_root, y_root, time, state, same_screen, etc.

Remarques :

            1. LՎvŽnement KeyRelease nÕexiste pas sur certaines machines bas de gamme. On Žvitera donc d'en faire usage si lÕon veut que l'application soit portable sur ces machines.

            2. Il ne peut y avoir de saisie du clavier entre un ŽvŽnement de type KeyPress et un ŽvŽnement de type KeyRelease.

            Le champ keycode peut tre assimilŽ ˆ un code physique directement Žmis par le clavier de par la position de la touche. On ne peut pas modifier les liens entre les touches et les keycode. Cependant, les claviers Žtant loin d'tre standardisŽs, le systme X a prŽvu un second niveau de codage permettant d'associer au code Žmis par la touche, et compte tenu de l'Žtat du clavier, un code symbolique ou keysym. C'est ce code symbolique qui va tre rŽcupŽrŽ et interprŽtŽ par les applications gr‰ce ˆ la fonction XLookupString.

 

 

La fonction XLookupString

 

            On nÕinterprte jamais directement le code Žmis par une touche ˆ partir du keycode. CÕest un code symbolique keysym, rŽcupŽrŽ par une fonction en XLookupString[17] qui permet vŽritablement dÕinterprŽter le code Žmis par une touche.

            Le code symbolique keysym est un entier dŽcrit par des constantes dŽfinies dans .h;le fichier <X11/keysym.h>. Les constantes symboliques prŽsentent l'avantage de permettre d'interprŽter directement des associations de touches physiques avec des modifieurs. Par exemple, le code symbolique de la touche "a" enfoncŽe simultanŽment avec le modifieur Shift gauche sur le clavier fournit le code symbolique XK_Shift_A.  Pour ces associations, le serveur utilise des tables de codes symboliques associŽs ˆ un keycode donnŽ en association avec des modifieurs[18].

 

            L'ŽvŽnement de type KeyPress rŽcupŽrŽ par une application ne comporte que le champ keycode. On rŽcupre le code symbolique keysym qui lui est associŽ et sa traduction (si elle existe) sous forme de cha”ne de caractres ASCII gr‰ce ˆ la fonction[19] XLookupString.

 

int XLookupString ( &ev, buffer, n_buf, &keysym_return, &status_in_out )

XEvent                    ev ;       /* doit tre de type XKeyEvent */ 

char             buffer [] ;

int                          n_buf ;

KeySym                  keysym_return ;

XComposeStatus       status_in_out ;

 

             Pour utiliser XLookupString, il faut inclure le fichier <X11/Xutil.h>. Cette fonction permet de rŽcupŽrer, dans un tableau de caractres buffer (allouŽ par lÕapplication qui en fournit lÕadresse et la taille dans n_buf), la traduction du code Žmis par la touche sous la forme d'une cha”ne de caractres ASCII, ainsi que la valeur du code symbolique keysym associŽ, compte tenu de l'Žtat des modifieurs Shift, Lock, Control et Meta (le code Žmis par la touche Žtant rŽcupŽrŽ par le serveur gr‰ce au champ keycode de lՎvŽnement)..h; L'ŽvŽnement dont on aura passŽ lÕadresse est supposŽ tre de type XKeyEvent. XLookupString retourne le nombre de caractres ASCII placŽs dans le buffer. On ne rŽcupre gŽnŽralement qu'un seul caractre ˆ la fois, mais il arrive qu'une touche ait ŽtŽ rŽassociŽe ˆ une cha”ne de plusieurs caractres, suite ˆ un appel ˆ XRebindKeysym.

           

            On peut en effet modifier les cha”nes de caractres ASCII associŽes par XLookupString (dans le buffer) au code symbolique keysym gr‰ce ˆ la fonction XRebindKeysym. Cette redŽfinition est locale au client. Elle ne modifie que les rŽponses aux futurs appels ˆ XLookupString Žmis par le client (cf. figure 5.5.).

 

 

fig. 5.5. Le codage des touches du clavier

retournŽ par XLookupString

 

L'ŽvŽnement MappingNotify

 

            On peut aussi dans certaines implantations modifier les liens entre les keycode et les keysym. Mais cela n'est gure recommandŽ, car, cette fois, la modification ne sera plus locale au client. Si on modifie ces liens, les autres clients recevront des ŽvŽnements de type MappingNotify.

 

MappingNotify           Cet ŽvŽnement informe qu'un changement d'association a ŽtŽ effectuŽ par un autre client sur les ressources concernant le clavier ou la souris. Ce type dՎvŽnement nÕa pas besoin dՐtre sŽlectionnŽ pour tre reu.

 

            Un ŽvŽnement de type MappingNotify annoncera par exemple :

            „ un appel ˆ XChangeKeyboardMapping (qui modifie les liens entre touches physiques et touches symboliques),

            „ un appel ˆ XSetModifierMapping (qui modifie les liens entre touches modifieurs et modifieurs logiques), 

            „ ou encore un appel ˆ XSetPointerMapping (qui modifie les liens entre les boutons physiques et les bouton logiques de la souris).

La rŽaction normale ˆ un changement sur les touches est un appel ˆ XRefreshKeyboardMapping (tester le champ request de l'ŽvŽnement) pour remettre ˆ jour les tables locales.

 


 

 

       Les fonctions importantes

 

       Bool  XQueryPointer ( dpy,  win,   &root_return,   &child_return,

                                           &root_x, &root_y, &win_x, &win_y, &state)

 

       int XLookupString ( &ev, buffer, n_buf, &keysym_return,

                                          &status_in_out )

 

 

       Des ŽvŽnements importants sont traitŽs dans ce chapitre : ButtonPress, ButtonRelease, MotionNotify, KeyPress, EnterNotify, LeaveNotify, FocusIn et FocusOut.

 

       La notion de saisie d'un dispositif d'entrŽe est une notion importante puisqu'une saisie du pointeur est automatiquement dŽclenchŽe par la rŽception d'un ŽvŽnement de type ButtonPress. On retiendra l'importance des masques de sŽlection d'ŽvŽnements souris OwnerGrabButtonMask et PointerMotionHintMask, et l'exemple de programmation de la section 5.3. On s'attardera Žgalement sur les sections 5.5. ˆ 5.7. pour savoir quand et comment saisir les entrŽes du clavier.

      

 

 

Exercices sur les ŽvŽnements clavier/souris

Les corrigŽs de ces exercices figurent pages 292 et suivantes.

 

Exercice 8: (RŽception des caractres lus dans une fentre X)

Imprimer (sur la console de lancement du programme), les caractres frappŽs au clavier dans une fentre X,  par exemple avec la commande fprintf(stderr, ...).

 

Exercice 9 : (Etude des ŽvŽnements EnterNotify et LeaveNotify)

CrŽer deux fentres ayant chacune une sous-fentre et les placer ˆ l'aide du window manager selon la disposition de la figure 5.5. Faire varier les masques de sŽlection des ŽvŽnements boutons pour avoir (ou non) saisie de la souris ; afficher les champs mode et dŽtail des ŽvŽnements d'entrŽe/sortie des fentres.



[1] Cette fentre pourra tre dŽsignŽe comme argument de certaines fonctions par la constante InputFocus.

[2]  Ce masque s'appelle OwnerGrabButtonMask, de Owner, propriŽtaire et Grab, saisie, pour indiquer que le serveur ne libre la souris que pour les fentres de l'application propriŽtaire de la saisie.

[3]  Ici via le membre xbutton de l'union XEvent. Par exemple, on Žcrira

                  if (ev.xbutton.subwindow == FenetreMenu) etc...

[4] Cela signifie que l'on a intŽrt ˆ relancer pŽriodiquement le serveur si l'on veut faire usage de cette information.

[5] Les modifieurs sont les touches qui produisent une modification de l'interprŽtation des autres touches enfoncŽes simultanŽment  avec elles. Par exemple Shift, Alternate, Meta, etc.

[6] Il y a deux exceptions : ;si le bouton change d'Žtat ou si la souris sort de la fentre. Mais dans ces deux cas, il faudra qu'un nouveau mouvement se produise et que l'application soit encore en mesure de recevoir cet ŽvŽnement pour qu'elle le rŽcupre.

[7] Si l'on souhaite rŽcupŽrer tous les ŽvŽnements, on pourra faire appel sur certaines machines ˆ une mŽmoire appelŽe motion history buffer, permettant de rŽcupŽrer simultanŽment tous les ŽvŽnements de mouvements qui ont suivi lÕunique mouvement envoyŽ spontanŽment par le serveur. Pour savoir si le serveur possde une telle capacitŽ, on testera la valeur de retour de la fonction

                  int XDisplayMotionBufferSize(dpy)

qui doit tre strictement positive. On procde alors ˆ une sŽlection de mouvements modulŽe par PointerMotionHintMask mais on appelle cette fois XGetMotionEvents ˆ lÕintŽrieur de la boucle dՎvŽnements.

[8] Sous rŽserve que la tirette n'ait pas sŽlectionnŽ ni bloquŽ l'ŽvŽnement. RŽcupŽrer les ŽvŽnements sur la mre n'entraine d'ailleurs aucune perte d'information car on peut tester si l'ŽvŽnement provient de la tirette ou de la barre gr‰ce au champ subwindow.

[9] Question qui se pose chaque fois que l'on sŽlectionne un ŽvŽnement de type ButtonPress.

[10] Dans certains cas, il peut sembler nŽcessaire de monopoliser le serveur lui-mme, pour empcher, par exemple, qu'un dessin extŽrieur ne vienne modifier l'Žtat de l'Žcran entre deux requtes. Dans ces cas trs particuliers, (que les conventions entre clients recommandent par ailleurs d'exclure), les programmes feront appel ˆ la fonction XGrabServer.

[11] En outre, si la fentre accaparante n'a pas sŽlectionnŽ ces ŽvŽnements souris, ces derniers sont perdus.

[12] Cependant, les conventions entre clients recommandent de ne pas utiliser cette fonction.

[13] Les requtes prŽcŽdentes peuvent dŽclencher des saisies actives ou passives selon la valeur de l'argument time .

[14] Qui empche la saisie d'tre active pour les fentres de l'application mais ne supprime pas la saisie.

[15] A partir de la Release 5, on dispose d'un outil trs puissant, les mŽthodes de lecture d'entrŽe, qui permet de s'adapter ˆ la langue de l'utilisateur. Ces mŽthodes de lecture utilisent Žventuellement de larges dictionnaires et des algorithmes complexes pour interprŽter les cha”nes de caractres tapŽes au clavier. Elles ont ŽtŽ dŽfinies dans le but de permettre l'internationalisation des programmes c'est-ˆ-dire la portabilitŽ des programmes indŽpendamment des langues utilisŽes (cf. chapitre 8).

[16] A partir de la Release 5, on dispose d'un outil trs puissant, les mŽthodes de lecture d'entrŽe, qui permet de s'adapter ˆ la langue de l'utilisateur. Ces mŽthodes de lecture utilisent Žventuellement de larges dictionnaires et des algorithmes complexes pour interprŽter les cha”nes de caractres tapŽes au clavier. Elles ont ŽtŽ dŽfinies dans le but de permettre l'internationalisation des programmes c'est-ˆ-dire la portabilitŽ des programmes indŽpendamment des langues utilisŽes (cf. chapitre 8).

[17] Dans la Release 5 on pourra utiliser les fonctions XwcLookupString ou XmbLookupString qui interprtent les cha”nes relativement ˆ la localitŽ courante avec une mŽthode d'entrŽe spŽcifique.

[18] En fait seulement quatre symboles keysym peuvent tre associŽs dans la Release 4 ˆ un keycode donnŽ (les rgles dÕassociation standards nÕutilisant que les deux premiers groupes). Le premier groupe est utilisŽ quand les modifieurs sont tous rel‰chŽs et le deuxime groupe, quand les modifieurs sont actifs. Si cette question intŽresse le lecteur, quÕil se reporte dans la documentation aux fonctions XGetKeyboardMapping ou XChangeKeyboardMapping. Cependant, ces fonctions ne sont pas disponibles dans toutes les implantations. Dans la Release 5, c'est la mŽthode d'entrŽes qui gŽrera les associations de touches et modifieurs.

[19] XwcLookupString et XmbLookupString dans la Release 5.