/* * The Helium Toolkit * * Copyright (C) 1996-2000 Edouard Thiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License; see http://www.lif-sud.univ-mrs.fr/~thiel/helium/LICENSE */ /* * gui/event.c - 17/06/1996 * * Gestion des evenements de X11 */ #include static int he_souris_b = 0; /* Etat boutons souris */ static He_mess_proc he_event_mess_proc = NULL; static Window he_mess_window = 0; /*------------------- I N T E R F A C E - P U B L I Q U E --------------------*/ /* * Fixe mess_proc() * appele' pour un ClientMessage genere' par HeSendClientMessage(). * */ void HeSetClientMessageProc ( He_mess_proc mess_proc ) { he_event_mess_proc = mess_proc; } /* * Envoie un ClientMessage a l'application, qui sera traite' par la boucle * d'evenements X de HeMainLoop(). * * USAGES * - Dans main(), entre HeInit() et HeMainLoop(), on envoie un message qui * sera traite' dans HeMainLoop(), ou on n'a plus la main. * - message de mise a jour d'objets. * - etc * * RQ : revoir ce mécanisme avec HeIOLoop ?? */ void HeSendClientMessage (long l[5]) { HeSendLongMessage (l, he_atom_mess); } /*-------------------- F O N C T I O N S - P R I V E E S ---------------------*/ /* * Envoie un évènement Expose à un Window. */ void HeSendExpose (Window win) { XEvent event; /* Envoie un evt Expose a hn */ event.type = Expose; event.xexpose.window = win; event.xexpose.count = 0; XSendEvent (he_display, win, False, 0L, &event); } /* * Envoie un ClientMessage. * * Appelé par HeSendClientMessage et par HeSignalNotify */ void HeSendLongMessage (long l[5], Atom message_type) { XEvent event; /* L'evenement doit etre envoye' a un window de l'application. * La premiere fois on cherche donc un window. */ if (he_mess_window == 0) { if (he_main_frame != 0) he_mess_window = HeGetWindow (he_main_frame); else { int i; He_node *hop; for (i = 0; (hop = HeNodeScan(i)) != NULL; i++) if (HeIsFrame(hop)) { he_mess_window = hop->win; break; } } if (he_mess_window == 0) { HeError ("HeSend*Message: can't find a Window for sending" " ClientMessage. Please create a frame before" " calling HeSend*Message.\n"); return; } } /* Le window he_mess_window existe ; * on fabrique le message et on le lui envoie. */ event.type = ClientMessage; event.xclient.window = he_mess_window; event.xclient.message_type = message_type; event.xclient.format = 32; memcpy (event.xclient.data.l, l, sizeof(long) * 5); if (XSendEvent (he_display, he_mess_window, False, 0L, &event) != 0) XFlush (he_display); } /* * Masque d'evenements acceptes sur un window */ void HeEventMask (Window win) { XSelectInput (he_display, win, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | StructureNotifyMask | ExposureMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask); } /* void HeEventMask_Grab (Window win) { XSelectInput (he_display, win, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | StructureNotifyMask | ExposureMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | OwnerGrabButtonMask); } */ /* * Init : enregistrement du fd lié au display X11 pour HeIOLoop() */ void HeEventIOInit () { HeAddIO (ConnectionNumber(he_display), HE_IO_READ, HeEventIOProc, NULL); } /* * Lecture et traitement de tous les event X disponibles dans la Queue. * * Le fonctionnement est le suivant : * - HeIOPoll() fait un select() bloquant sur fdX (le fd de X) ; il sort donc * dés qu'il y a quelque chose à lire sur fdX. * - On lit ensuite la Queue d'évènements sur fdX avec XEventsQueued(). * - Il faut traiter toute la Queue, car des event non traités mais présents * dans la Queue seront "en suspend" jusqu'à ce qu'il y ait du nouveau sur * fdX. * - Le but est de redonner la main rapidement à HeIOPoll() ; on évite donc de * provoquer la venue de nouvelles données sur fdX, en particulier de faire * un Xflush sur le buffer de requètes. */ void HeEventIOProc (int handle, Ulong rcond, void *data) { XEvent report; int i, n, m = 0; /* Nb d'evènements après lecture sur fdX, sans faire de Xflush */ n = XEventsQueued (he_display, QueuedAfterReading); while (n > 0) { /* Sécurité contre boucle longue ou infinie */ if (++m > 20) { HeError ("HeEventIOProc: long XEvent loop detected.\n"); break; } /* On lit exactement les n évènements dans la queue ; donc XNextEvent est non-bloquant et ne fait pas de Xflush */ for (i = 0; i < n; i++) { XNextEvent (he_display, &report); HeEventDispatch (&report); } /* Nb d'evènements restants, sans lecture sur fdX ni Xflush (ces évènements sont soit générés localement par Xlib, soit provoqués par du code utilisateur) ; on les lit pour éviter qu'ils ne restent en suspend dans HeIOPoll(). */ n = XEventsQueued (he_display, QueuedAlready); } /* Le XFlush est réalisé à la fin de HeIOLoop() */ } /* * Fonction de predicat pour XCheckIfEvent, qui examine toute la queue * des evts et s'arrete au premier evt sur lequel cette fct renvoie TRUE. * * Ici, on veut savoir s'il y a un Expose sur le meme window dans la suite, * mais sans le retirer de la queue. On renvoie donc toujours FALSE (toute * la queue sera examinee, tant pis) et on change he_expose_later = TRUE. */ static int he_predi_expose_later = FALSE; static Window he_predi_expose_win = 0; Bool HeEventExposePredicate (Display *display, XEvent *event, char *arg) { if (event->type == Expose && event->xexpose.window == he_predi_expose_win) he_predi_expose_later = TRUE; return False; } /* * Repartition des evenements X11 * */ void HeEventDispatch (XEvent *xev) { He_event hev; He_node *hn = NULL; /* Initialisation de hev */ hev.type = xev->type; hev.sx = hev.sy = hev.sb = hev.time = 0; hev.sb = he_souris_b; hev.win = 0; hev.xev = xev; /* * Remplis les champs de hev selon le type d'evenement */ switch (xev->type) { /* Touche du clavier enfonce'e/relache'e*/ case KeyPress : case KeyRelease : hev.win = xev->xkey.window; hev.sx = xev->xkey.x; hev.sy = xev->xkey.y; hev.time = xev->xkey.time; hev.len = XLookupString (&xev->xkey, hev.str, 256, &hev.sym, NULL); hev.str[(hev.len < 256) ? hev.len : 256-1] = 0; break; /* Boutons souris enfonce' */ case ButtonPress : /* Filtre : si un bouton est deja actif, on ignore l'evt */ if (he_souris_b != 0) return; /* Le nouveau bouton actif */ he_souris_b = xev->xbutton.button; hev.win = xev->xbutton.window; hev.sx = xev->xbutton.x; hev.sy = xev->xbutton.y; hev.sb = he_souris_b; hev.time = xev->xbutton.time; break; /* Boutons souris relache' */ case ButtonRelease : /* Filtre : si bout relache' != bout actif, on ignore l'evt */ if (he_souris_b != xev->xbutton.button) return; hev.win = xev->xbutton.window; hev.sx = xev->xbutton.x; hev.sy = xev->xbutton.y; hev.sb = he_souris_b; hev.time = xev->xbutton.time; /* Il n'y a plus de bouton actif */ he_souris_b = 0; break; /* Deplacement de la souris */ case MotionNotify : hev.win = xev->xmotion.window; hev.sx = xev->xmotion.x; hev.sy = xev->xmotion.y; hev.time = xev->xmotion.time; break; /* Entre-sortie de la souris dans un window */ case EnterNotify : case LeaveNotify : /* printf ("HeEvent %s win 0x%lx mode %d detail %d\n", HeGetXEventName (hev.xev->type), hev.xev->xcrossing.window, hev.xev->xcrossing.mode, hev.xev->xcrossing.detail); */ /* On filtre tous les Enter/Leave virtuels, c'est-à-dire transmis * aux parents intermédiaires dans la hiérarchie des windows * (Xlib v.1 p.656) ; ces évènements provoquent des erreurs dans * la gestion interne du focus. */ if (hev.xev->xcrossing.detail == NotifyVirtual || hev.xev->xcrossing.detail == NotifyNonlinearVirtual) return; hev.win = xev->xcrossing.window; hev.sx = xev->xcrossing.x; hev.sy = xev->xcrossing.y; hev.time = xev->xcrossing.time; break; /* Changement de Focus */ case FocusIn : case FocusOut : hev.win = xev->xfocus.window; break; /* Refresh d'un Window : le but est d'eviter tous les Expose inutiles, * qui interviennent par exemple lors d'un "opaque move", et qui * peuvent saturer la charge CPU pendant un long moment. * * Dans GLUT (src-glut/glut_event.c "case Expose:"), la methode * utilisee ne permet de savoir que si il y a des Expose CONSECUTIFS, * or en cas de "opaque move", chaque subwindow recoit un Expose, * et donc il n'y a pas d'Expose consecutif sur un meme Window !! * * Une autre methode a ete testee ici : * while ( XCheckTypedWindowEvent (he_display, xev->xexpose.window, * Expose, xev) == True ); * qui supprime tous les expose de la queue, et sort de la boucle * avec le dernier Expose. INCONVENIENT : on realise ce "dernier" * expose au moment du "premier" Expose, ce qui change la chronologie * des evenements. * * SOLUTION : on examine la queue d'evenements avec XCheckIfEvent * pour savoir si il y a plus tard dans la queue un autre evt Expose * sur le meme window ; si c'est le cas, on ignore le Expose courant, * et on LAISSE le Expose ulterieur dans la queue. * Avantage : on CONSERVE la chronologie des evts. */ case Expose : if (xev->xexpose.count == 0) { XEvent ev_return; /* variables globales */ he_predi_expose_later = FALSE; he_predi_expose_win = xev->xexpose.window; /* Examine toute la queue sans l'alterer, car la fonction de predicat HeEventExposePredicate renvoie toujours FALSE */ XCheckIfEvent (he_display, &ev_return, HeEventExposePredicate, NULL); /* Il n'y a pas d'Expose plus tard dans la queue */ if (he_predi_expose_later == FALSE) hev.win = xev->xexpose.window; } break; /* Avis de destruction : arrive apres des UnmapNotify et LeavNotify, * ce qui peut provoquer des BadWindow et erreur Get_Property sur * un window qui n'existe plus ; c'est pourquoi les fonctions * HeGetNodeFromWin et HeNonFatalXErrorHandler sont * silencieuses dans ce cas. */ case DestroyNotify : /* on ne fait rien */ break; /* Masquage, affichage d'un Window */ case UnmapNotify : hev.win = xev->xunmap.window; break; case MapNotify : hev.win = xev->xmap.window; break; /* Habillage par le Window Manager avec un parent-window * pour la decoration ; utilise' dans frame.c */ case ReparentNotify : hev.win = xev->xreparent.window; break; /* Changement de la geometrie d'un Window */ case ConfigureNotify : hev.win = xev->xconfigure.window; break; /* Message d'un client ou du WM */ case ClientMessage : if (xev->xclient.message_type == he_atom_mess) { if (he_event_mess_proc == NULL) HeError ("HeEventDispatch: you have sent a ClientMessage" " with HeSendClientMessage, but there is no proc" " to receive it. Please set the proc with" " HeSetClientMessageProc( He_mess_proc proc );\n"); else he_event_mess_proc (xev->xclient.data.l); } /* Cet event est envoyé par HeSignalNotify pour faire sortir du select(), cf RQ1 dans HeSignalInstall() */ else if (xev->xclient.message_type == he_atom_sig) { /* on ne fait rien */ } /* Intercepte la fermeture d'un Window */ else if (xev->xclient.message_type == he_atom_protoc && xev->xclient.data.l[0] == he_atom_delwin) { hev.win = xev->xclient.window; } else HeError ("HeEventDispatch: ClientMessage unknown\n" " type: %d data: (%d %d %d %d %d)\n", xev->xclient.message_type, xev->xclient.data.l[0], xev->xclient.data.l[1], xev->xclient.data.l[2], xev->xclient.data.l[3], xev->xclient.data.l[4]); break; /* Changement de la correspondance du clavier */ case MappingNotify : XRefreshKeyboardMapping (&xev->xmapping); break; /* Perte d'une selection */ case SelectionClear : HeXSelClear (&xev->xselectionclear); break; /* Une autre appli demande la selection */ case SelectionRequest : HeXSelRequest (&xev->xselectionrequest); break; /* Recoit la selection demandee a une autre appli */ case SelectionNotify : HeXSelNotify (&xev->xselection); break; /* Apparait en cas de XCopyArea ou XCopyPlane */ case NoExpose : break; /* Certains deplacements de fenetres */ case GravityNotify: break; /* Evenement non pris en compte. */ default : /* HeError ("HeEventDispatch: unexpected event %s\n", HeGetXEventName (xev->type)); */ break; } /* switch */ /* * Recherche du handle id et du noeud hn a partir du win */ if (hev.win == 0) return; /* Certains evts sont post-mortem, on s'en apercoit ici */ hn = HeGetNodeFromWin (hev.win); if (hn == NULL) return; /* if (xev->type != MotionNotify) printf ("HeEventDispatch: %d %s\n", hn->id, HeGetXEventName (xev->type)); */ /* Sécurité */ if (hn->meth->magic != HE_METH_MAGIC) { HeError ("HeEventDispatch: meth has a bad magic number\n"); return; } /* Filtre : ferme les menus */ if (HeMenuFilterEvents (hn, &hev)) { he_souris_b = 0; /* plus de bouton souris actif */ return; } /* Appel méthode */ (hn->meth->event)(hn, &hev); } /* * Redistribution des He_event dans un conteneur à ses fils * Renvoie -1 si erreur. * * On redistribue les evts aux gadgets comme s'ils venaient directement du WM. * (Les widgets ont directement reçu ces evts du WM, sans passer par ici, * donc il n'y a rien a tester). * * La difficulte est pour le drag (hev->sb > 0) : * - le gadget recoit autant de Leave/Enter que de survols. * - les autres gadgets sont masqués * - le Leave/Enter sur le conteneur est masqué * - si la fin du drag se fait en dehors du gadget on lui envoie un * Leave supplementaire. * * ATTENTION, toujours appeler les callback en dernier, car dedans on a pu * supprimer des gadgets. * * Le flag HE_STA_MOUSE_IN est mis si la souris est dans le pointed * * Appele' par les méthodes He*Event */ int HeEventToGadgets (He_node *hn, He_event *hev, He_kbd_proc kbd_proc, int *pointed) { He_node *ga; switch (hev->type) { /* * Touche clavier enfonce'e/relache'e. */ case KeyPress : case KeyRelease : /* Appelle kbd_proc ; intercepte l'evt si elle renvoie FALSE */ if (kbd_proc != NULL) if ((kbd_proc)(hn, hev) == FALSE) break; /* Envoie hev au focus */ HeSendEventToFocus (hn, hev); break; /* * Debut du drag : bouton souris enfonce' */ case ButtonPress : ga = HeGetNodeFromId (*pointed); if (ga != NULL) ga->meth->event (ga, hev); break; /* * Fin du drag : bouton souris relaché */ case ButtonRelease : ga = HeGetNodeFromId (*pointed); /* On envoie ButtonRelease au pointed */ if (ga != NULL) ga->meth->event (ga, hev); /* Si le drag finit dans l'item on a termine' */ if (ga != NULL && HE_BIT_ISSET (hn->status, HE_STA_MOUSE_IN)) break; /* On envoie un LeaveNotify supplementaire a pointed * (specifs de fin de drag en dehors de l'item). */ *pointed = 0; /* perte */ HE_BIT_CLR (hn->status, HE_STA_MOUSE_IN); if (ga != NULL) { hev->type = LeaveNotify; ga->meth->event (ga, hev); } /* On cherche le nouveau pointed puis on lui envoie EnterNotify * (le drag avait pu le masquer). */ ga = HeFindClic (hn->sub_node, hev->sx, hev->sy, TRUE); if (ga != NULL) { *pointed = ga->id; HE_BIT_SET (hn->status, HE_STA_MOUSE_IN); hev->type = EnterNotify; ga->meth->event (ga, hev); } break; /* * On bouge dans le conteneur * - on calcule les Enter/Motion/Leave dans les sub_items * et on leur envoie un hev synthétique. * - on gere MOUSE_IN et le drag */ case MotionNotify : ga = HeGetNodeFromId (*pointed); if (hev->sb > 0) { /* drag : on conserve pointed */ int test; /* drag a vide : on masque l'evt */ if (ga == NULL) break; /* on conserve pointed et on calcule Enter/Motion/Leave */ test = HeIsClic (ga, hev->sx, hev->sy); if (!HE_BIT_ISSET (hn->status, HE_STA_MOUSE_IN) && test) { HE_BIT_SET (hn->status, HE_STA_MOUSE_IN); hev->type = EnterNotify; } else if (HE_BIT_ISSET (hn->status, HE_STA_MOUSE_IN) && !test) { HE_BIT_CLR (hn->status, HE_STA_MOUSE_IN); hev->type = LeaveNotify; } /* sinon MotionNotify */ ga->meth->event (ga, hev); } else { /* pas de drag : on recalcule pointed */ /* Si on est encore sur pointed, * on envoie MotionNotify et c'est fini */ if (HeIsClic (ga, hev->sx, hev->sy)) { ga->meth->event (ga, hev); break; } /* On envoie LeaveNotify a pointed si il existe */ *pointed = 0; /* perte */ HE_BIT_CLR (hn->status, HE_STA_MOUSE_IN); if (ga != NULL) { hev->type = LeaveNotify; ga->meth->event (ga, hev); } /* On cherche le nouveau pointed puis envoie EnterNotify */ ga = HeFindClic (hn->sub_node, hev->sx, hev->sy, TRUE); if (ga != NULL) { *pointed = ga->id; HE_BIT_SET (hn->status, HE_STA_MOUSE_IN); hev->type = EnterNotify; ga->meth->event (ga, hev); } } break; /* * On entre dans le conteneur */ case EnterNotify : /* Si drag, on masque EnterNotify */ if (hev->sb > 0) break; /* Sinon on cherche le nouveau pointed * et on lui envoie EnterNotify */ ga = HeFindClic (hn->sub_node, hev->sx, hev->sy, TRUE); if (ga != NULL) { *pointed = ga->id; HE_BIT_SET (hn->status, HE_STA_MOUSE_IN); ga->meth->event (ga, hev); } break; /* * On sort du conteneur */ case LeaveNotify : /* Si drag, on masque LeaveNotify */ if (hev->sb > 0) break; /* Sinon on envoie LeaveNotify au pointed restant */ ga = HeGetNodeFromId (*pointed); *pointed = 0; /* perte */ HE_BIT_CLR (hn->status, HE_STA_MOUSE_IN); if (ga != NULL) ga->meth->event (ga, hev); break; /* * Expose : refresh general */ case Expose : (hn->meth->draw)(hn); /* On transmet Expose aux sub_nodes */ for (ga = hn->sub_node; ga != NULL; ga = ga->next_node) ga->meth->event (ga, hev); break; } /* switch */ return 0; }