4

Les événements :  principe général

 

 

4.1. Les événements

                  Les événements sont des structures envoyées aux clients par le serveur pour leur communiquer diverses informations. Il existe trois grandes catégories d'événements :

 

• les événements résultant d'actions de l'utilisateur sur les dispositifs d'entrée, par exemple l'enfoncement d'une touche.

• les événements avertissant de modifications survenues sur des structures internes : table de couleurs, table des clés, etc. Ces transformations concernent aussi les fenêtres :  changement de visibilité, besoin de réaffichage, etc.

• les événements permettant de communiquer entre clients. En particulier toute une série d'événements transmettent les requêtes des clients au window manager. D'autres événements plus spécifiques permet­tront aux clients de communiquer entre eux (par exemple, le méca­nisme de sélection permet de faire du couper/coller).

 

                  Avant d'entrer dans les détails, nous allons d'abord présenter quelques événements pour que le lecteur en ait une image plus précise. Nous énoncerons ensuite des principes généraux concernant tous les types d'événements : structures associées, sélection et propagation des événements, structuration du programme. Les événements particuliers seront ensuite détaillés au fur et à mesure dans les chapitres suivants.

 

                  Il existe des événements permettant de savoir si l'utilisateur a enfoncé une touche du clavier ou s'il a déplacé la souris.

KeyPress

KeyRelease

Ces événements indiquent l'enfoncement et le relâchement d'une touche du clavier.

 

ButtonPress

ButtonRelease

 

Ces événements indiquent l'enfoncement et le relâchement d'un des boutons de la souris.

 

MotionNotify

 

 

Cet événement indique un déplacement de la souris. 

                  Il existe aussi des événements qui concernent les fenêtres, mais qui sont en rapport avec la position du pointeur de souris :

 

EnterNotify

LeaveNotify

 

Ces événements traduisent les entrées et sorties de la souris dans les fenêtres.  La ou les fenêtres dans lesquelles pénètre la souris recevront un événement de type EnterNotify et celles qui sont quittées par ce même mouvement recevront un événement de type LeaveNotify.

 

                  Ces événements sont reçus par toute la hiérarchie des fenêtres concernées. En effet, l'entrée dans une sous-fenêtre implique également l'entrée dans la fenêtre mère puisque les filles sont toujours disposées à l'intérieur de leur parent.

 

                  La question de savoir à qui est destiné un événement est une question centrale à laquelle doit répondre le serveur (cf. figure 4.1.). Le fait qu'une fenêtre ait sélectionné un événement est en général une condition nécessaire pour que l'événement soit envoyé, mais toutes les fenêtres ayant sélectionné un événement ne le recevront pas nécessairement. En effet, l'utilisateur qui enfonce une touche du clavier a l'intention d'envoyer cet événement à une seule des applications tournant à l'écran (son éditeur de texte par exemple). De même, les mouvements et les clics de souris n'intéressent qu'une seule application à la fois.

 

Le partage des dispositifs d'entrée clavier/souris, s'effectue selon le principe suivant : à un instant donné une seule fenêtre reçoit les événements du clavier. On dit qu'elle a le focus du clavier;. De même, à un instant donné une seule fenêtre (pas nécessairement la même) reçoit les événements de souris. Par défaut, la fenêtre dans laquelle se trouve la souris a le focus du clavier et reçoit les événements souris (sauf si des mécanismes de monopolisation du clavier ou de la souris ont été mis en œuvre[1]).

 

                  fig. 4.1. A quelles fenêtres et applications sont destinés

les événements survenant sur le clavier, la souris  ?

 

                  Pour l'instant notons qu'il existe des événements permettant d'avertir l'application que ses fenêtres saisissent ou perdent le focus du clavier :

 

FocusIn

FocusOut

 

Ces événements indiquent les changements de focus du clavier. Ils seront reçus par la ou les fenêtres qui récupèrent le focus (FocusIn) et par celles qui le perdent (FocusOut). Ces événements sont envoyés après l'appel d'un client à la requête XSetInputFocus.

 

                  La plupart des window manager tâchent de rendre visible la fenêtre pouvant recevoir les entrées en soulignant la barre de titre ou son bord par une couleur brillante. Ainsi, l'utilisateur a une perception directe de la fenêtre qui peut recevoir les entrées. Cette fenêtre s'allume  quand la souris entre dans la fenêtre (ou quand l'utilisateur clique dessus) et s'éteint quand la souris en sort. Ce phénomène est implanté par le window manager à l'aide des événements EnterWindow, LeaveWindow, FocusIn et FocusOut.

 

                  Il existe également des événements informant l'application de modifications survenues dans l'affichage ou la configuration des fenêtres (sur sa propre demande ou sur celle du window manager) :

 

Expose

 

 

Les événements de type Expose  indiquent que tout ou partie de la fenêtre se trouve exposée, c'est-à-dire rendue visible à l'écran. Etre exposée, c'est ici être exposée aux regards de l'utilisateur. Ces événements permettent de récupérer les zones rectangulaires exposées et indiquent qu'il faut dessiner (ou redessiner) le contenu des fenêtres.

 

MapNotify

 

 

informe l'application que la fenêtre est (potentiellement) affichée à l'écran. Cela ne garantit pas que la fenêtre soit visible car elle peut être cachée par ses sœurs. Pour être assurer qu'une partie de la fenêtre est visible, il faut avoir reçu un événement de type Expose[2].

 

ConfigureNotify

 

 

Les événements de type ConfigureNotify préviennent l'application des changements survenus dans la configuration géométrique des fenêtres, comme un changement de dimension.

 

                                   Concernant la communication entre clients, on peut citer l'événement de type ClientMessage :

 

ClientMessage

 

 

est envoyé "directement" par une application à une autre au moyen de la requête XSendEvent. En réalité, le serveur sert bien entendu d'intermédiaire. Cet événement permet d'envoyer un message à un autre client et nécessite l'utilisation d'une convention entre les clients.

 

                  Nous reviendrons plus loin sur tous ces événements, mais avant de le faire, nous allons expliquer les principes généraux gouvernant l'envoi et la réception des événements.

4.2. Types d'événements

                  La nature d'un événement envoyé par le serveur ne pouvant être connue par avance des clients qui le reçoivent, la librairie utilise le type XEvent qui est une union de structures de différents types (chaque type correspondant à la description d'un événement particulier). Le type XEvent (cf. figure 4.2. page suivante) est assez grand pour contenir n'importe quelle structure de l'union et il contient un champ type permettant d'identifier le type de la structure qui a été effectivement reçue.

 

                   Une fois le type connu, on peut accéder aux champs d'une structure particulière via un membre de l'union correspondant à un événement de ce type. Ainsi,  aux événements de type ButtonPress et ButtonRelease correspond une structure de type XButtonEvent qui sera accessible via le membre xbutton de l'union et dont la définition est donnée figure 4.3. page 63.

 

                  A chaque type d'événement correspond une structure particulière dont les champs contiennent les informations relatives à un événement de ce type.  Il y a 33 noms ou types d'événements différents et 31 types de structures associées. Le type XEvent est une union de ces 31 types. Le nom des champs d'accès coïncide généralement avec le nom des types d'événements. Au type Name d'événement correspond une structure C de type XNameEvent dont le champ d'accès dans l'union XEvent est le membre xname. Par exemple, à l'événement de type Expose correspond une structure de type XExposeEvent adressable via le champ xexpose d'un événement de type générique XEvent.

 

                  Cependant, il n'y a que 31 noms de types de structures, car certains types d'événements sont décrits par une même structure. Ainsi, la structure permettant d'accéder aux informations relatives aux événements de type ButtonPress et ButtonRelease est une structure de type XButtonEvent adressable par le champ xbutton d'un événement générique XEvent. De même aux événements de type KeyPress ou KeyRelease est associée une structure de type XKeyEvent adressable via le champ xkey[3].

typedef union _XEvent {

   int                          type;

   XAnyEvent                    xany;

   XKeyEvent                    xkey;

   XButtonEvent                 xbutton;

   XMotionEvent                 xmotion;

   XCrossingEvent               xcrossing;

   XFocusChangeEvent      xfocus;

   XExposeEvent                 xexpose;

   XGraphicsExposeEvent         xgraphicsexpose;

   XNoExposeEvent               xnoexpose;

   XVisibilityEvent             xvisibility;

   XCreateWindowEvent           xcreatewindow;

   XDestroyWindowEvent          xdestroywinodw;

   XUnmapEvent            xunmap;

   XMapEvent                    xmap;

   XMapRequestEvent             xmaprequest;

   XReparentEvent               xreparent;

   XConfigureEvent              xconfigure;

   XGravityEvent                xgravity;

   XResizeRequestEvent          xresizerequest;

   XConfigureRequestEvent       xconfigurerequest;   XCirculateEvent              xcirculate;

   XCirculateRequestEvent       xcirculaterequest;

   XPropertyEvent               xproperty;

   XSelectionClearEvent         xselectionclear;   XSelectionRequestEvent       xselectionrequest;

   XSelectionEvent              xselection;

   XColormapEvent               xcolormap;

   XClientMessageEvent          xclient;

   XMappingEvent                xmapping;

   XErrorEvent            xerror;

   XKeymapEvent                 xkeymap;

}  XEvent ;

 

fig. 4.2. L’union de type XEvent

 

                  Sur réception d'un événement, les programmes effectuent un branchement conditionnel sur le type de l'événement reçu avant d'accéder aux champs particuliers de la structure associée à l'événement.

typedef struct {

          int type;                                                                      /* le type de l'événement */

         unsigned long serial;                                /* dernière requête traitée */

         Bool send_event;                               /* vrai si envoyé par XSendEvent */

         Display *display;                                 /* où l'événement s'est produit */

         Window window;                                          /* fenêtre recevant l'événement */

         Window root;                                                                         /* racine d’origine */

          Window subwindow;  /* sous-fenêtre dans laquelle l'ev. s'est produit*/      Time time;     /* heure de l’événement */                                                                                      int x, y;      /* position relative à window */

         int x_root, y_root;                                       /* position relative à root */      unsigned int state;                                                                /* état (masque) boutons + modifieurs*/

         unsigned int button;                         /* bouton ayant provoqué l’ev. */

         Bool same_screen;                 /* si le pointeur est toujours sur l’écran */

} XButtonEvent;

typedef XButtonEvent XButtonPressedEvent;

typedef XButtonEvent XButtonReleaseEvent;

 

fig. 4.3. La structure XButtonEvent

associée aux événements ButtonPress et ButtonRelease

                  L'allure générale d'une boucle de réception d'événements est donc la suivante :

 

events() {

XEvent ev;

   for (  ;  ;  )  {

         /* récupération de l'événement suivant */

         XNextEvent(dpy, &ev);

         switch (ev.type) {

               case ButtonRelease:

               case ButtonPress: /* traitement */ 

                           pos_x = ev.xbutton.x;

                           pos_y = ev.xbutton.y;

                                 /* etc.*/

                           break;

               case Expose :          /* traitement */ 

                           pos_x = ev.xexpose.x;

                           pos_y = ev.xexpose.y;

                                 /* etc. */

                           break;

         }

   }

}

                  Les événements récupérés dans cette boucle porteront sur n'importe quelle fenêtre de l'application mais la structure de l'événement permettra toujours de récupérer la fenêtre ayant reçu l'événement (champ window).

 

                  On pourra cependant tester le champ fenêtre avant le champ type. Il existe en effet des champs communs à toutes les structures d'événements. La structure XAnyEvent contient ces champs (figure 4.4.) qui seront accessibles dans tout événement de type XEvent par le membre xany. Ces (cinq) champs sont les suivants :

 

type                                            Le champ type indique le type de l'événement. C'est également le premier champ de l'union XEvent. C'est un entier dont le nom symbolique est défini dans <X11/Xlib.h>[4]. Les types d'événements sont énumérés figure 4.5.

 

window                                      Le champ window indique l'identificateur de la fenêtre qui a reçu l'événement.

 

Les autres champs, quoique également importants, sont moins fréquemment utilisés.

 

serial                                      Le champ serial indique le numéro de la dernière requête traitée par le serveur, les requêtes étant numérotées à partir du début du programme. Quand une erreur se produit, un événement de type XError est émis et le champ serial est affiché par une fonction du système permettant ainsi au programmeur de connaître le rang de la dernière requête traitée.

 

send_event                          Le booléen send_event permet de savoir si l'événement provient du serveur ou s'il a été envoyé par un client à l'aide de la requête XSendEvent.

 

display                                   Le champ display permet d'identifier le serveur qui envoie l'événement (et donc la station sur laquelle l'événement s'est produit). On ne testera ce champ que si l'on a ouvert plusieurs connexions à différents serveurs.

typedef struct {

         int type;                                           /* le type de l'événement */

         unsigned long serial;       /* dernière requête traitée */

         Bool send_event;   /* vrai si envoyé par XSendEvent */

          Display *display;                 /* où l'événement s'est produit */

         Window window;                            /* fenêtre recevant l'événement */

} XAnyEvent;

 

fig. 4.4. Les cinq champs communs à toute structure d'événement

 

 

                  Les structures associées aux événements comportent donc toujours ces cinq champs, suivis d’autres, plus spécifiques à l'événement considéré : position de la souris pour les événement de type XButtonEvent, détail sur les touches pour les événements de type XKeyEvent, etc. Nous détaillerons ces champs plus spécifiques au moment de la description de ces événements particuliers. En cas de besoin, le lecteur pourra consulter l'index sur les événements fourni en annexe. Les événements y sont présentés par ordre alphabétique sur le type, et on y trouvera la description complète de la structure associée.

 

 

      KeyPress                MapNotify

      KeyRelease              MapRequest

      ButtonPress             ReparentNotify

      ButtonRelease           ConfigureNotify

      MotionNotify            ConfigureRequest

      EnterNotify             GravityNotify

      LeaveNotify             ResizeRequest

      FocusIn                 CirculateNotify    

      FocusOut                CirculateRequest

      KeymapNotify            PropertyNotify

      Expose                  SelectionClear

      GraphicsExpose          SelectionRequest

      NoExpose                SelectionNotify

      VisibilityNotify        ColormapNotify

      CreateNotify            ClientMessage

      DestroyNotify           UnmapNotify  

      MappingNotify

 

fig. 4.5. Les différents types d’événement

4.3. Sélection et masques d'événements

                  Nous avons vu que la récupération des événements envoyés par le serveur pouvait s'effectuer au moyen de la fonction XNextEvent. Cependant, l'appel à XNextEvent n'est pas suffisant pour recevoir les événements car la plupart des événements ne sont envoyés à l'application que si celle-ci en a fait la demande préalable au moyen d'une opération appelée sélection. La sélection des événements est indiqué au serveur par la valeur de l'attribut event_mask des fenêtres. Par défaut, l'attribut event_mask d'une fenêtre est positionné à NoEventMask, ce qui signifie que par défaut, une fenêtre ne reçoit aucun événement. La sélection des événements doit être effectuée par l'application au niveau de chaque fenêtre, soit par un appel à la fonction

 

XSelectInput (dpy, win, event_mask)

 

soit par une modification directe de l'attribut event_mask de la fenêtre concernée (cf. XCreateWindow ou XChangeWindowAttributes). L'application indique ainsi au serveur qu'elle veut recevoir certains types d'événements s'ils se produisent sur une fenêtre donnée.

 

Pour recevoir un événement, il faut donc l'avoir préalablement sélectionné sur une fenêtre. En outre, les fenêtres doivent être affichées pour recevoir des événements. On sélectionnera cependant les événements avant d'afficher les fenêtres pour que le serveur n'ignore pas d'événements.

 

                  Différents masques permettent de spécifier le type des événements intéressant une fenêtre. Le champ event_mask de la fenêtre (ou l'argument de la requête XSelectInput) pourront recevoir une combinaison quelconque de ces différents masques. Les masques de sélection sont énumérés figure 4.6. (page suivante). Par exemple, les masques KeyPressMask et KeyReleaseMask permettent de sélectionner les événements de types KeyPress et KeyRelease avertissant de l'enfoncement et du relâchement des touches du clavier. De même, ButtonPressMask et ButtonReleaseMask permettent de sélectionner les événements de types ButtonPress et ButtonRelease qui correspondent à une pression ou un relâchement de bouton de souris. Il est naturel de spécifier la fenêtre devant recevoir ces événements, car ils ne sauraient intéresser toutes les fenêtres d'une application bien conçue.

 

              NoEventMask;               Button5MotionMask

     KeyPressMask              ButtonMotionMask

     KeyReleaseMask            KeymapStateMask

     ButtonPressMask           ExposureMask

     ButtonReleaseMask         VisibilityChangeMask

     EnterWindowMask           StructureNotifyMask

     LeaveWindowMask           ResizeRedirectMask

     PointerMotionMask         SubstructureNotifyMask

     PointerMotionHintMask     SubstructureRedirectMask

     Button1MotionMask         FocusChangeMask

     Button2MotionMask         PropertyChangeMask

     Button3MotionMask         ColormapChangeMask

     Button4MotionMask         OwnerGrabButtonMask

 

fig. 4.6. Les différents masques d’événement

 

                  La structure générale d'un programme d'interface sous X-Window est donc toujours plus ou moins celle-ci :

 

main() {

 

  /* ouverture de la connexion au serveur */

dpy           =     XOpenDisplay(NULL);

 

   /* Initialisations diverses */

screen        =     DefaultScreen(dpy);

root          =     RootWindow(dpy, screen);

pixel_noir    =     BlackPixel(dpy,screen);

pixel_blanc   =     WhitePixel(dpy, screen);

 

   /* Création de fenêtres */

   /* et sélection d'événements associés */

win           =     XCreateSimpleWindow(..);

XSelectInput(dpy, win, ButtonPressMask

                           | ButtonReleaseMask

                           | Button3MotionMask

                           | KeyPressMask

                           | ExposureMask);

 

   /* puis affichage des fenêtres initiales  */

XMapWindow(dpy, win);

 

   /* suivi de la boucle destinée au traitement des

    * événements et qui fera des appels à XNextEvent

    */

 

events();

}

i.organisation du programme;Les masques d'événements servent à sélectionner le type des événements reçus par une fenêtre. Il existe cependant des exceptions inventoriées figure 4.7. :

                  • Certains événements ne sont pas sélectionnés par des masques. Ainsi, les événements de type ClientMessage et MappingNotify sont toujours envoyés aux clients concernés.

                  • Certains masques ne sélectionnent aucun événement mais donnent des précisions sur les événements qui seront envoyés. Ainsi PointerMotionHintMask restreint le nombre d'événements de mouvement de souris sélectionnés mais ne sélectionne pas en lui-même d'événement souris (cf. l'exemple de programmation traité dans le chapitre suivant).

      

                           Masques                                                                       Evénements

 

                      NoEventMask                                                                             

                      OwnerGrabButtonMask                      ne sélectionnent pas d’ev.

                      PointerMotionHintMask                              

                                                                                                    ClientMessage

                                                                                                    GraphicsExpose

                                                                                                    NoExpose

                      pas de masque associé                                 MappingNotify

                                                                                                    SelectionClear

                                                                                                    SelectionNotify

                                                                                                    SelectionRequest

 

fig. 4.7. La non-correspondance masque / événement

 De plus, pour les masques permettant de sélectionner des événements, la correspondance entre masque et événement n'est pas toujours univoque, car, comme le montre la figure 4.8. ci-contre : 

                  • plusieurs masques peuvent sélectionner l'envoi d'un même type d'événement. Il existe en effet de nombreux masques permettant de sélectionner les mouvements du pointeur de souris.

                  • un même masque peut sélectionner plusieurs types d'événements.

 

       Masques                          Evénements

 

     KeyPressMask                   KeyPress

     KeyReleaseMask                 KeyRelease

     ButtonPressMask                ButtonPress

     ButtonReleaseMask              ButtonRelease

     EnterWindowMask                EnterNotify

     LeaveWindowMask                LeaveNotify

     PointerMotionMask              MotionNotify

     Button1MotionMask              ibid

     Button2MotionMask              ibid

     Button3MotionMask              ibid

     Button4MotionMask              ibid

     Button5MotionMask              ibid

     ButtonMotionMask               ibid

     KeymapStateMask                KeymapNotify

     ExposureMask                   Expose

     VisibilityChangeMask           VisibilityNotify

     StructureNotifyMask            CirculateNotify

                                    ConfigureNotify

                                    DestroyNotify (suite de StructureNotifyMask)                GravityNotify

                                    MapNotify

                                    UnmapNotify

                                    ReparentNotify      ResizeRedirectMask             ResizeRequest

     SubstructureNotifyMask         identiques à ceux

                                                                                                           sélectionnés par

                                                                                                           StructureNotifyMask

                                                                                                           + CreateNotify

     SubstructureRedirectMask       CirculateRequest

                                    MapRequest

                                    ConfigureRequest

     FocusChangeMask                FocusIn

                                    FocusOut

     PropertyChangeMask             PropertyNotify

     ColormapChangeMask             ColormapNotify

 

fig. 4.8. La correspondance masque / événement

                 On peut noter cependant que la correspondance est univoque en ce qui concerne la plupart des événements sur les dispositifs d'entrée. Seul l'événement MotionNotify peut être sélectionné par plusieurs masques. Dans ce cas, le masque permet de spécifier des événements de mouvements particuliers. Par exemple, une sélection de mouvement effectuée avec le masque ButtonMotionMask limite l'envoi des mouvements à ceux qui se produisent avec un bouton de souris enfoncé.

 

                  Inversement, un seul masque peut permettre de sélectionner plusieurs événements. Par exemple, on note qu'un seul masque suffit à sélectionner tous les événements de modifications sur une fenêtre (le masque StructureNotifyMask). De même, un seul masque permet de sélectionner ces informations sur toutes les sous-fenêtres (SubstructureNotifyMask).

 

                  Nous expliquerons l'usage des masques au fur et à mesure des descriptions d’événement particulier. Retenons pour l'instant que les masques d'événements servent principalement à sélectionner des événements sur les fenêtres. Mais ils permettent également d'indiquer des types d'événements dans d'autres requêtes. Par exemple, ils sont utilisés par des requêtes permettant de récupérer des événements. Nous allons voir maintenant qu'ils permettent aussi de spécifier quels événements doivent être propagés ou bloqués dans la hiérarchie des fenêtres concernées par un événement.

 

 

4.4. Propagation des événements

                  On a vu que pour qu'une application reçoive un événement, il faut qu'il ait été sélectionné par l’une de ses fenêtres. L'application qui reçoit un événement détermine ensuite, grâce au champ window, la fenêtre à laquelle est rapporté l'événement. Généralement, cette fenêtre est aussi celle dans laquelle l'événement s'est produit. Cependant pour les événements concernant les dispositifs d'entrée, la fenêtre à laquelle est rapporté un événement n'est pas nécessairement la fenêtre dans laquelle l'événement s'est produit[5]. Si l'événement s'est produit dans une sous-fenêtre, c'est le champ subwindow qui contiendra en fait l'identificateur de la fenêtre d'origine.

 

                  Dans le cas d'un événement ButtonPress par exemple, la fenêtre dans laquelle l'événement se produit est conventionnellement celle située sous le pointeur de souris et la plus en avant à l'écran. Supposons par exemple que l'on ait deux fenêtres, F1 et F2, F2 étant fille de F1 (cf. figure 4.9.).

 

 

fig. 4.9. La propagation d’un événement ButtonPress

 

 

                  F2 sera toujours affichée dans le cadre de F1. Si un événement se produit dans F2, il se produira donc aussi, en un certain sens, dans F1, puisque F2 est toujours contenue dans F1. Si l'événement n'a pas été sélectionné par F2 mais a été sélectionné par F1, c'est F1 qui recevra l'événement. On dira dans ce cas que l'événement s'est produit dans F2 mais a été propagé à F1. Le champ subwindow de la structure associée à l'événement permettra d'identifier F2 comme origine de l'événement et le champ window spécifiera F1 comme receveur. Les événements concernés par ce mécanisme de propagation sont les événements concernant le clavier et la souris.

                  Si l'événement peut être envoyé à plusieurs fenêtres (comme LeaveNotify), il sera propagé vers les ancêtres et reçu par plusieurs fenêtres (si, bien entendu, ces fenêtres ont sélectionné ce type d'événement). Par contre, si l'événement ne peut être reçu que par une fenêtre (comme un événement ButtonPress[6]), il sera propagé en partant de la fenêtre la plus en avant sur l'écran, et transmis parmi ses ancêtres à la première fenêtre qui l'aura sélectionné.

                  Mais, en réalité, pour déterminer si un événement doit être propagé vers un ancêtre, le serveur consulte l'attribut do_not_propagate_mask. Cet attribut permet d'empêcher la propagation des événements vers les ancêtres. Ainsi, dans la situation décrite par la figure 4.9. où F2 est fille de F1, on peut avoir ou ne pas avoir propagation d'un événement ButtonPress qui se produit dans F2 selon les valeurs des attributs event_mask et do_not_propagate_mask de F2 et F1.

 

                  Si la fenêtre la plus en avant a sélectionné l'événement, l'événement lui sera transmis. Sinon le serveur testera son champ do_not_propagate_mask pour savoir si la fenêtre accepte de propager l'événement. Si le champ do_not_propagate_mask n'empêche pas la propagation, l'événement sera d'abord propagé à la mère pour laquelle on testera à nouveau le champ event_mask puis le champ do_not_propagate_mask. Et ainsi de suite, jusqu'à trouver parmi les ancêtres une fenêtre intéressée par l'événement, ou une fenêtre bloquant le processus de propagation. Dans le cas où l'événement est finalement transmis à un ancêtre de la fenêtre d'origine, le champ subwindow  en indiquera l'origine.

                 

                  Voici un contexte pour la figure 4.9. où il y a propagation d'un événement de type ButtonPress qui se produit initialement dans F2 :

 

   F2    event_mask = 0

         do_not_propagate_mask = 0

 

   F1    event_mask = KeyPressMask

         do_not_propagate_mask = 0

 

   F     event_mask = ButtonPressMask

         do_not_propagate_mask = 0

 

                  En effet, F2 n’a pas sélectionné l'événement, ni demandé à ce qu’il soit bloqué. L’événement se propage donc vers F1. Pour les mêmes raisons, l’événement se propage vers F. Cette fois, F a bien sélectionné l'événement et le recevra avec un champ window à F et un champ subwindow à F2. Par contre, dans le contexte suivant :

 

   F2    event_mask = 0

         do_not_propagate_mask = 0

 

   F1    event_mask = KeyPressMask

         do_not_propagate_mask = ButtonPressMask

 

   F     event_mask = ButtonPressMask

         do_not_propagate_mask = 0

 

                  L’événement ButtonPress, propagé de F2 vers F1, est cependant bloqué par F1 à cause de la valeur de son attribut do_not_propagate_mask. Cet événement, bien que sélectionné par F et non sélectionné par F1, ne sera pas envoyé.

 

                  On utilisera souvent le mécanisme de propagation pour recevoir tous les événements sur une même fenêtre de fond. Cela permet dans certains cas de simplifier la programmation (cf. l'exemple de la barre de défilement). A l'inverse, on bloquera aussi parfois la propagation de certains événements. Par exemple, si l'on fait apparaître une boîte de dialogue dans un éditeur de texte, on ne souhaite pas voir propager les entrées du clavier ou les clics de souris à travers la boite, car on risquerait alors d'insérer malencontreusement du texte sans que l'utilisateur s'en rende compte.

 

4.5. Piles d'événements et piles de requêtes

                  On a déjà mentionné dans l'introduction la présence d'une pile de requêtes. En réalité, il y a un double empilement des requêtes. Les appels au protocole sont d'abord empilés localement (par la librairie Xlib) au niveau du client, puis transmis ensuite au serveur. Le serveur empile également les requêtes provenant des différents clients avant de les exécuter tour à tour. Bien entendu, certaines requêtes sont exécutées immédiatement et forcent la communication avec le serveur[7]. Pour envoyer la pile locale de requêtes au serveur on utilise la fonction

 

XFlush (dpy)

 

                  Cette fonction ne garantit pas en elle‑même que les requêtes soient exécutées par le serveur. Elle ne fait que vider la pile locale et garantit simplement que les requêtes sont envoyées. On l'utilisera pour accélérer l'envoi de requêtes particulières, comme les requêtes d'affichage ou de dessin.

 

                  Les événements sont eux aussi empilés. Quand un événement se produit, le serveur en recherche le ou les destinataires et, s'il en existe, empile alors l'événement dans une pile propre à chaque client. De leur côté, les clients font appel à des requêtes de récupération d'événement permettant d'extraire des événements de leur pile. Les événements à analyser s'accumulent donc parallèlement aux requêtes en attendant d'être traités par l'application (cf. figure 4.10.).

 

 

 

fig. 4.10. L'empilement des événements et des requêtes

côté client et côté serveur

 

 

                  Il est souvent utile pour déboguer d'empêcher l'empilement des requêtes afin d'être synchrone avec le serveur. Sur les systèmes POSIX, il suffira de positionner la variable globale _Xdebug à une valeur non nulle pour obtenir un comportement synchrone sous débogueur. Cependant, la baisse des performances entre le mode synchrone et le mode asynchrone peut parfois être de l'ordre d'un facteur 30 et il vaut mieux pouvoir localiser l'erreur pour ne passer en mode synchrone que dans une portion restreinte du programme.

 

                  La fonction XSynchronize;[8] permet de passer dans le programme lui-même du mode synchrone au mode asynchrone et vice versa :

 

int (*XSynchronize (dpy, on_off) ) ( )

Bool                  on_off ;

 

                 Après chaque exécution d'une fonction de la Xlib qui a engendré une requête au protocole X, un appel à une fonction spéciale appelée the after function est effectué. La fonction XSynchronize retourne la dernière after function et permet de passer en mode synchrone ou asynchrone grâce au paramètre on_off qui peut être nul (asynchrone) ou non nul (synchrone). On peut modifier la procédure déclenchée après une requête protocolaire grâce à la fonction

 

int (*XSetAfterFunction (dpy, procedure)) ( )

int (*procedure (Display *))  ( ) ;

 

                  La fonction XSetAfterFunction permet de modifier une fonction post‑protocolaire et retourne la fonction qui était déclenchée précédemment. La librairie n'utilise pas ce mécanisme pour elle‑même et la fonction déclenchée est par défaut une fonction qui ne fait rien (NoOp). Ce mécanisme peut donc être utilisé par les programmeurs pour déclencher des impressions ou produire un effet de bord après l'exécution de certaines requêtes.   

 

4.6. Réception des événements

                  La récupération des événements envoyés par le serveur s'effectue dans presque tous les cas au moyen de la fonction XNextEvent.

 

XNextEvent (dpy, &event)

 

      XNextEvent retourne l'événement le plus ancien dans la pile et le retire de la pile. Si la pile d'événement est vide, cette fonction vide la pile de requête (en faisant un appel à XFlush) et attend qu'un événement se produise. Elle peut donc être bloquante si l'application ne reçoit pas d'événement.

                  On utilisera presque toujours XNextEvent pour récupérer les événements, mais on dispose de toute une série de fonctions permettant de regarder ou d'extraire des événements de la pile. Ainsi les fonctions

 

int XPending (dpy)

int XEventsQueued (dpy, mode)

int     mode ;

 

permettent de retourner le nombre d'événements en attente de traitement dans la pile. De même la fonction

 

XPeekEvent (dpy,&event)

 

permet de regarder sans l'extraire (mais avec attente) le prochain événement de la pile.

                 

                  Il y a également moyen de faire du filtrage sur les événements. On peut ainsi regarder, avec ou sans attente, si la pile contient des événements particuliers grâce à des fonctions permettant de spécifier quels événements intéressent l'application. On peut filtrer des événements par divers procédés : par masque, par fenêtre, par type ou par procédure.

 

                  Dans le cas du filtrage par procédure, la procédure doit retourner un booléen. L’adresse de la procédure et la liste de ses arguments sont alors passées en paramètre à la requête, qui examine si un événement de la pile satisfait la procédure booléenne.

XIfEvent (dpy,&event,procedure, string_args)

Bool XCheckIfEvent (dpy,&event,procedure, string_args)

Display*                           dpy ;

XEvent                              event ;

Bool (*) (  Display*,

               XEvent*,

               char*

             )                        procedure ;

char*                                  string_args ;

 

                  Ces fonctions existent en deux formules, l'une bloquante (XIfEvent) qui attend qu’un événement convenable se produise, et l'autre non bloquante (XCheckIfEvent), qui teste simplement l'existence d’un tel événement. De même, on peut tester avec ou sans attente la présence d’événement satisfaisant un certain masque de types

XMaskEvent (dpy, event_mask, &ev)                                 

Bool XCheckMaskEvent (dpy, event_mask, &ev)

 

ou bien encore, satisfaisant un masque de types et correspondant à une certaine fenêtre :

 

XWindowEvent (dpy, win, event_mask, &ev)                                  

Bool XCheckWindowEvent (dpy, win, event_mask, &ev)

 

                  On peut aussi simplement tester la présence d'un événement d'un certain type

 

Bool XCheckTypeEvent (dpy, event_type, &ev)

 

ou encore satisfaisant un certain type et se produisant sur une certaine fenêtre :

 

Bool XCheckTypeWindowEvent (dpy, win, event_type, &ev)

 

                  Il existe également une fonction qui permet de remettre un événement dans la queue :

 

XPutBackEvent (dpy, &ev).

 

 

4.7. La gestion des fenêtres

                  On a vu qu’on pouvait interdire la redirection des requêtes vers le window manager en mettant l’attribut override_redirect des fenêtres filles de la racine à True. Ce n’est cependant pas souhaitable, car on prive alors l’utilisateur d'une interface homogène. Les applications doivent au contraire prévoir la présence d’un window manager quelconque (et doivent même être préparées à fonctionner sans window manager). En présence d'un window manager, les applications peuvent lui communiquer des préférences au moyen de propriétés. Il existe un grand nombre de requêtes permettant d'affecter des propriétés standards qui sont lues par les window manager. Ces fonctions et les propriétés associées sont énumérées dans le chapitre 10 sur la communication entre clients.

                  De manière générale, l’application sera informée des différents changements survenus sur ces fenêtres par des événements — que ces changements proviennent de requêtes émises par le window manager ou par l’application elle-même.

 

                  La plupart des événements en Notify rapportent une modification qui a eu lieu, tandis que les événements en Request rapportent des appels de requêtes effectués par les clients sur ces fenêtres, alors que les modifications demandées par ces requêtes n'ont pas encore été satisfaites.

 

                  Les événements en Request ne sont sélectionnés que par un seul client (en général le window manager) et n’intéresseront pas le programmeur d’une application (sauf s'il écrit un window manager). Le window manager est le premier client qui sélectionne certains types d'événements[9] s'attribuant ainsi la redirection des requêtes, qui lui permet de récupérer les événements de type CirculateRequest, ConfigureRequest, MapRequest et ResizeRequest.

                  Les applications, quant à elles, s’intéressent aux événements sélectionnables par StructureNotifyMask qui traduisent des modifications effectives survenues sur les fenêtres. Les événements sélectionnables par StructureNotifyMask sur une fenêtre peuvent être sélectionnés globalement pour toutes les fenêtres sœurs en sélectionnant SubstructureNotifyMask sur la fenêtre parent. Ainsi les applications peuvent récupérer les événements de type :

ConfigureNotify           qui ;annonce tout changement dans la configuration géométrique d'une fenêtre (taille, position, bord, ordre d'empilement).

 

MapNotify

UnmapNotify

qui sont envoyés par le serveur quand une fenêtre passe de l'état Map à l’état Unmap et vice versa.

 

GravityNotify                 qui ;est émis quand un déplacement de fenêtre est causé par un changement de la taille de son parent (cf. la description de l’attribut win_gravity des fenêtres).

 

ReparentNotify              qui est émis pour indiquer que le parent de la fenêtre a changé. Il est important de sélectionner cet événement si on s'intéresse à la position d'une fenêtre fille de la racine, car celle-ci est susceptible d'être "reparentée" par un window manager.

 

CirculateNotify           qui ;indique qu’une fenêtre a été déplacée par XCirculateWindowUp ou Down. Si le gestionnaire de fenêtre empêche cette opération, l'événement ne sera pas envoyé.

 

                  Signalons en outre les événements de type  VisibilityNotify ;qui rapportent tout changement de visibilité dans une fenêtre de type InputOutput, ainsi que les événements de type PropertyNotify, ;qui indiquent qu'une propriété de la fenêtre a été modifiée[10]. Ces événements rapportent l'heure de la modification dans le champ time de la structure associée.

 

                  Pour plus d’informations sur ces événements, le lecteur pourra se référer à l’index sur les événements figurant en annexe.

4.8. Le traitement des erreurs

                  Il y a deux gestions des erreurs dans la librairie Xlib : les erreurs fatales  (après lesquelles il est impossible de maintenir en vie le processus), et celles repérées par le serveur. On peut modifier la procédure déclenchée par ces deux types d'erreurs. Les premières avec XSetIOErrorHandler (mais on devra logiquement quitter l'application ensuite) et les secondes avec

 

int (*XSetErrorHandler (handler))()

int (*handler)(Display *, XErrorEvent *)

 

                  Cette fonction retourne la procédure ayant déclenchée l'erreur précédente. La procédure par défaut imprime les différents champs de l'événement de type XErrorEvent correspondant à l'erreur produite et quitte le programe. Les erreurs déclenchant cette procédure n'étant pas considérées comme fatales, le client peut en fait modifier la procédure associée et ne pas quitter le programme. Cependant, la procédure associée ne doit pas appeler directement ou indirectement des fonctions qui utiliseraient des requêtes au protocole ou qui demanderait à lire un événement du display.

                  Un événement de type XError contient un nombre codant la procédure qui a échoué (selon les définitions de <X11/Xproto.h>) et un code caractérisant l'erreur. Ce code peut être traduit en une chaîne de caractères décrivant l'erreur avec la fonction XGetErrorText et un message d'erreur pourra être obtenu par la fonction XGetErrorDatabaseText. A partir de la Release 5, ces fonctions seront affectées par la localité courante (cf. chapitre 8).

 

XGetErrorText (dpy, code, buffer_return, lenght)

int                      code ;

char*                 buffer_return ;

int                      lenght ;

 

XGetErrorDatabaseText (dpy, name, message, default_string, buffer_retrun,

                                                                                lenght)

const char*     name, message, default_string ;

 

                  Signalons au passage une fonction utile pour indiquer à l'utilisateur qu'il a commis une erreur de manipulation quelconque :

 

XBell (dpy, pourcent)

int                      pourcent ;

 

                  Cette fonction émet un son dont le volume est relatif au volume de base du clavier. Le volume de base est l'un des paramètres de contrôle du clavier (modifiable par l'utilisateur avec la commande xset). On se servira de XBell pour signaler de manière sonore des erreurs à l'utilisateur. L'argument pourcent doit être compris entre -100 et +100 (ou une erreur se produira). Quand l'argument n'est pas négatif, le son est émis au volume base - [(base*pourcent)/100] + pourcent, et si l'argument est négatif, au volume base + [(base*pourcent)/100].

 

                  Pour changer la valeur du volume de base du clavier, il faut utiliser la fonction XChangeKeyboardControl. Cette fonction permet d'ajuster divers paramètres contrôlant le signal sonore émis par le clavier (volume, fréquence, durée) et des paramètres liés aux touches (répétition, etc.). Les valeurs actuelles de ces paramètres peuvent être récupérés avec la fonction XGetKeyboardControl[11].

 

4.9. Bien structurer le programme :  les contextes

                  Pour conclure ce chapitre général sur les événements, nous aimerions insister sur l'importance d'une bonne structuration du programme et introduire une notion, celle de contexte, facilitant cette structuration. Les contextes d'association (type XContext) fournissent un moyen très efficace pour associer des données à une fenêtre. Les contextes sont un outil précieux de structuration de programme. Un gestionnaire de contexte a en effet été défini pour les besoins internes de la librairie et les fonctions associées sont efficaces. Ces fonctions permettent d'associer des données à une fenêtre dans un contexte (via une adresse du programme de l'utilisateur). On peut ensuite grâce au contexte retrouver l'adresse des données associées à partir de l'identificateur de la fenêtre. On pourra donc modifier ces données dynamiquement puisqu'on mémorise leurs adresses.

 

XContext XUniqueContext ()

 

int XSaveContext (dpy, win, xcontext, pdata)

int XFindContext (dpy, win, xcontext, &pdata)

int XDeleteContext (dpy, win, xcontext)

Window           win ;

XContext        xcontext ;

caddr_t             pdata ;                      /* pdata est une adresse de données */

 

                  La fonction XUniqueContext permet de créer un contexte d'association global. La terminologie est peut-être mal choisie, car on peut créer plusieurs contextes. La fonction XSavecontext permet d'établir une liaison entre un pointeur sur des données pdata et une fenêtre. La fonction XFindContext permet ensuite de retrouver l'adresse des données associées à une fenêtre et la fonction XDeleteContext permet de supprimer une association de données à une fenêtre[12].

 

                  Les contextes d'association sont très utiles. Comme nous l'avons vu, le contrôle du flot d'un programme X est entièrement dirigé par la réception des événements. La boucle principale du programme récupère un à un les événements qui se produisent indépendamment de l'objet (la fenêtre) qui les reçoit. Ce mode de contrôle est donc antinomique de celui de la programmation par objets puisqu'il met l'accent sur les événements qui se produisent au lieu de mettre l'accent sur les objets qui reçoivent ces événements.

                  Grâce aux associations par contextes, on va pouvoir retrouver un style de programmation par objets. En effet, un événement contient toujours l'indication de la fenêtre source dans laquelle l'événement s'est produit. On peut donc récupérer cette fenêtre à partir de l'événement, et ensuite, à partir du contexte,  n'importe quel type de données qui lui aura été associé.

 

 

 

fig. 4.11. L’organisation du contrôle grâce aux

contextes d’associations de données aux fenêtres

 

 

                  On peut ainsi traiter les fenêtres comme des objets comportant un certain nombre d'attributs et de méthodes (puisque le langage C permet de stocker des adresses de fonctions) — attributs modifiables et fonctions susceptibles d'être déclenchées quand certains événements se produisent (cf. figure 4.11).

 

                  Les contextes d'association permettent ainsi un traitement plus générique des fenêtres.  On peut en effet définir différents types de fenêtres relativement à des comportements attendus en réponse aux événements. Dans certains cas, on pourra définir un contexte global permettant de retrouver pour toute fenêtre, la ou les fonctions de traitement des événements. De telles fonctions s'appellent des handlers d'événements car elles prennent en charge le traitement des événements.

                  Les contextes d’association permettent également d'implanter une variante intéressante de cette architecture.  On peut en effet les utiliser pour avoir à la fois un traitement générique des fenêtres, et un traitement modulaire des événements. Le schéma de la figure 4.12. en résume le principe.

Type d’événement    ->    Contexte (+ Fenêtre)    ->    Traitement

fig. 4.12. Un schéma de contrôle par contextes d’événements

i.organisation du programme;On peut associer, à chaque type d’événement, un contexte d’association particulier qui permet de retrouver, pour chaque fenêtre, la fonction de traitement d'un événement de ce type (son propre handler d'événement). Chaque handler d’événement est alors en charge de traiter les réactions aux événements d’un type donné, pour un objet (fenêtre) d’un type donné.

 

                  La principale qualité de ce schéma de contrôle, outre sa clarté, est sa grande modularité. Il permet de bien organiser les fichiers et facilite la maintenance des programmes. On évite en effet d'avoir à modifier la boucle centrale de traitement des événements chaque fois que l'on modifie le comportement d'un objet ou d'une fenêtre et on peut adopter une disposition arborescente des fichiers reflétant l’arborescence des objets de l’interface. Pour une très petite application, ce type d'organisation peut paraître un peu lourd, mais dès que l'application est un tant soit peu conséquente, ce schéma de structuration devient très avantageux. Les lignes suivantes, inspirées d'une note d'Yves Berteaud sur la programmation par handlers d'événements[13], en indiquent l'implantation dans un programme :

i.organisation du programme;

/*

 * Definition du type procedure (qui recoit un pointeur

 * d'evenement et le traite)

 */

 

typedef void (*TraiteEv) ( /* &XEvent */ ) ;

 

/*

 * Tableau de contextes pour stocker le traitement de

 * chaque type d'evenement

 */

 

XContext ContexteEv [LASTEvent] ;

/* LASTEvent est une constante egale au nombre

/* de types d'evenements

 

 * La fonction permettant d'enregistrer une fonction de

 * traitement pour une fenetre et un evenement donne

 */

 

void EnregistreHandler (w, ev_type, fonction)

Window      w;

int         ev_type ;

TraiteEv fonction ;

{

  TraiteEv f ;

 

  /*

   * Si le contexte n'existe pas encore pour ce type

   * d'evenement, le creer

   */

 

  XContext context = ContexteEv [ev_type] ;

  if (context == 0)

       context   = ContexteEv [ev_type]

                 = XUniqueContext () ;

  /*

   * Si une fonction est deja associee, la supprimer

   */

  if (XFindContext (dpy, w context, &f) == 0)

       XDeleteContext (dpy, w, context) ;

 

  /*

   * Associer la fonction a la fenetre dans ce contexte

   */

  XSaveContext (dpy, w, context, fonction) ;

}

 

 

void MainLoop ()

{

 

  while (True) {

 

     XEvent    ev ;

     XContext  context ;

     TraiteEv  fonction ;

 

     XNextEvent (dpy, &ev) ;

 

     /*

      * Recuperer le contexte associe a ce type d'evenement

      */

 

     context = ContexteEv [ev.type] ;

 

     /*

      * Recuperer et invoquer la fonction associee a la

      * fenetre qui a recu l'evenement

      */

 

     if (context != 0 &&

         XFindContext (dpy, ev.xany.window, context,

                             &fonction) == 0)

          (*fonction) (&ev) ;

     else

         fprintf(stderr, "evenement non traite") ;

  }

}

 

 

 

/*

 * Squelette des applications concues sur ce modele

 */

 

main ()

{

 

  /*

   * Initialisations diverses des objets de l'interface

   */

 

  /*

   * Enregistrement des fonctions de traitement

   * apres selection des evenements sur les fenetres

   * avec EnregistreHandler (win, ev_type, fonction)

   */

 

  MainLoop () ;

}


 

 

 

 

       Les fonctions importantes

 

   XSelectInput (dpy, window, event_mask)

          XNextEvent (dpy, &event)

 

          XFlush (dpy)

          XPending (dpy)

          XPeekEvent (dpy, &event)

          XPutBackEvent (pdy, &event)

 

          XBell (dpy, pourcent)

 

          XContext XUniqueContext ()

          int XSaveContext (dpy, win, xcontext, pdata)

          int XFindContext (dpy, win, xcontext, &pdata)

          int XDeleteContext (dpy, win, xcontext)

 

          Dans ce chapitre, les notions importantes qui ont été introduites sont celles de types d'événements, de sélection et de propagation. Les contextes d'association sont également importants pour une bonne structuration des programmes.

 

 


Exercices sur les événements

Les corrigés des exercices sur les événements sont regroupés pages 279 et suivantes.

 

Exercice 4 : (Impression du type des événements)

Ecrire un programme permettant d'imprimer, pour chaque événement reçu, le type de l'événement reçu. Créer une fenêtre, l'afficher et lui faire subir un certain nombre de modifications à l'aide du window manager pour observer les événements reçus.

 

Exercice 5 : (Variante avec les événements souris)

Etude de ButtonPress, ButtonRelease et MotionNotify. On fait une variante à partir de l’exercice précédent : on sélectionne uniquement les trois événements ci-dessus et on imprime pour chaque événement reçu, l’identificateur de la fenêtre ayant reçu l’événement et le numéro du bouton enfoncé. Pour l'événement MotionNotify, on fera varier le masque de sélection pour ne recevoir que les mouvements se produisant le bouton2 étant enfoncé.

 

Exercice 6 :  (Propagation des événements souris)

Le but de cet exercice est de recevoir les événements de type ButtonPress ButtonRelease et MotionNotify et d’observer la propagation de ces événements. Pour cela, on va créer une fenêtre principale de fond noir contenant une rangée de sous-fenêtres de fond blanc. On rendra  sensible la fenêtre principale et deux des cinq sous-fenêtres aux événements de type ButtonPress et aux mouvements de souris survenant avec un bouton enfoncé. On empêchera par contre la propagation de ces événements dans les sous-fenêtres qui n’auront pas sélectionné ces événements. On réalisera ensuite une boucle d'événement qui affiche pour chaque événement reçu l’identificateur de la fenêtre source et le numéro du bouton enfoncé, ainsi que la position du pointeur au moment de l’événement, relativement à la fenêtre source et relativement à la racine.

 

Exercice 7 : (Un pop-up menu de pattern de fond d'écran)

Faire un pop-up menu de fonds d'écran disposés trois par trois en pavés sur chaque ligne du menu. Pour cela, on créera une fenêtre principale dans laquelle ce menu apparaîtra sur enfoncement du bouton gauche de la souris. Une fois affiché, on maintient la souris enfoncée et on passe de fond en fond. Chaque entrée dans une des petites fenêtres constituant un fond d’écran provoque l’impression d’une chaîne de caractère (par printf) donnant le nom du fichier bitmap servant de fond. On doit pouvoir relâcher le bouton sur un fond d’écran et voir alors s’afficher le nom du fond sélectionné.

 

 

Indications : 1. on pourra utiliser un XContext pour associer la chaîne de caractères à imprimer à chaque sous-fenêtre.

                  2. Comment faire pour empêcher la fenêtre principale de saisir la souris sur l’enfoncement du ButtonPress ?

                  3. Comment faire pour que le menu pende en dehors du cadre de la fenêtre quand on clique très près d’un bord ?  Inversement, comment faire pour que le menu apparaisse toujours en entier quand on se trouve près du bord de l’écran  ?

 

 

 

 

 

 



[1] Un tel mécanisme est déclenché automatiquement sur l'enfoncement d'un bouton de souris.

[2] Ou un événement de type VisibilityNotify si le champ state est positionné à VisibilityUnobscured.

[3]  Même chose pour les événements concernant le focus du clavier : à FocusIn et FocusOut correspond une structure de type XFocusChangeEvent adressable via le membre xfocus. De même les événements d'entrée et sortie de la souris EnterNotify et LeaveNotify sont décrits par une même structure de type XCrossingEvent. On a donc 4 types de structures de moins correspondant aux quatre cas énumérés précédemment. A l'inverse, il y a 2 types de structures supplémentaires, les types  XErrorEvent et XAnyEvent, d'où le décompte final  : 33  ‑ 4 + 2 = 31.

[4] Précisément dans le fichier <X11/X.h> inclus (par #include) dans <X11/Xlib.h>.

[5] Il peut se produire un phénomène de propagation, ou encore, comme dans le cas des événements clavier, la fenêtre source peut avoir perdu le focus du clavier.

[6] Les événements concernant les dispositifs d'entrée sont reçus par une seule fenêtre, mais il n'y a pas d'objection à ce que plusieurs clients les sélectionnent. L'événement ButtonPress ne peut être reçu que par une application car il  déclenche une monopolisation de la souris. Sinon, la plupart des événements peuvent être envoyés à plusieurs clients (s'ils sont plusieurs à l'avoir sélectionné pour la même fenêtre).  Il y a d'autres exceptions, comme les événements destinés au window manager, qui ne peuvent être sélectionnés que par un seul client afin qu'un seul window manager puisse être présent à l'écran simultanément.

[7] Avec attente éventuelle dans l'application.

[8] A ne pas confondre avec XSync (dpy, discard).  La fonction XSync synchronise l’application avec le point de programme où elle est appelée en vidant le buffer de requêtes (XFlush) et en attendant que le serveur les aient traitées. En outre, si on lui passe un argument discard positionné à False, la pile locale des événements n'est pas vidée et elle contiendra éventuellement des événements antérieur à l'appel à XSync. Si on lui passe un argument discard positionné à True, la pile locale est alors vidée et des événements non encore traités par l'application peuvent être perdus.

[9] En positionnant les masques SubstructureRedirectMask sur la fenêtre racine puis ResizeRedirectMask sur les fenêtres créées avec l’attribut override_redirect à False.

[10]  A tout le moins qu'il y a été ajouté une chaîne de longueur zéro

[11] De manière analogue, les fonctions XChangePointerControl et XGetPointerControl permettent de modifier et récupérer les paramètres de contrôle de la souris (vitesse et seuil d'accélération).

[12] Ici aussi, le terme est un peu mal choisi, car il ne s'agit pas de détruire le contexte, mais simplement de supprimer une association de donnée particulière.

[13] Yves Berteaud, Conception et programmation sous Xlib par "Handlers d'événements", NSL, Paris, Juin 1991.