Como ya se comentó, los eventos enviados por el servidor se almacenan en una cola de entrada desde donde el cliente debe leerlos y procesarlos. El servidor envía al cliente una estructura de datos que contiene tanto el tipo de evento ocurrido como otra información útil relativa a ese evento, como por ejemplo la posición del mouse si el evento era un ButtonPress, o la tecla pulsada ante un KeyPress. Ante tal cantidad de eventos disponibles se hacen necesarias muchas estructuras de datos (una que devuelva información ante una pulsación de teclado, otra para información sobre el ratón, etc.), pero X Window las ha fundido todas en una mediante el operador union, de forma que todos los eventos envían la misma estructura al programa cliente, un estructura que contiene los datos de cualquier posible evento, de tal modo que es la aplicación la encargada de mirar el campo apropiado para recuperar esta información.
Explicado de una manera simplista, supongamos que se dispone de sólo 2 eventos: pulsación de tecla y pulsación de botón del mouse. Serían necesarias 2 estructuras distintas para dotar de información extra al cliente, algo similar a:
struct EventoTecla { char tecla_pulsada; }; struct EventoBoton; { char boton; int x, y; };Lo que hace X Window es crear una sóla estructura como unión de ambas y pasar al cliente siempre esa estructura (sea cual sea el evento, pues ya se encargará la aplicación de utilizar uno u otro campo):
struct Evento { char QueEvento; /* 0=teclado, 1=raton */ EventoTecla datos_teclado; EventoBoton datos_boton; }
Evento Estructura Utilizada Campo utilizado ----------------------------------------------------------- KeyPress XKeyEvent xkey KeyRelease XKeyEvent xkey KeymapNotify XKeyMapEvent xkeymap ButtonPress XButtonEvent xbutton ButtonRelease XButtonEvent xbutton EnterNotify XCrossingEvent xcrossing LeaveNotify XCrossingEvent xcrossing MotionNotify XMotionEvent xmotion CreateNotify XCreateWindowEvent xcreatewindow DestroyNotify XDestroyWindowEvent xdestroywindow GravityNotify XGravityEvent xgravity UnmapNotify XUnmapEvent xunmap MapNotify XMapEvent xmap CirculateNotify XCirculateEvent xcirculate ConfigureNotify XConfigureEvent xconfigure ReparentNotify XReparentEvent xreparent FocusIn XFocusChangeEvent xfocus FocusOut XFocusChangeEvent xfocus MapRequest XMapRequestEvent xmaprequest ConfigureReq. XConfigureRequestEvent xconfigurerequest ResizeRequest XResizeRequestEvent xresizerequest CirculateReq. XCirculateRequestEvent xcirculaterequest PropertyNotify XPropertyEvent xproperty Expose XExposeEvent xexpose ColormapNotify XColormapEvent xcolormap SelectionClear XSelectionClearEvent xselectionclear SelectionReq. XSelectionRequestEvent xselectionrequest SelectionNotify XSelectionEvent xselectionSi el lector todavía no ha entendido nada sobre el tema de las estructuras y los eventos no debe preocuparse, pues aún es necesario mostrar y desarrollar la estructura XEvent para comprender todo el proceso
Veamos el contenido de la estructura Xevent:
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 xdestroywindow; 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; long pad[24]; } XEvent;Antes de ver ejemplos sobre el tema, cabe decir que el bucle de mensajes deberá:
1.- Saber extraer la primera estructura XEvent disponible en la cola de eventos (recogiendo por tanto el primer evento de la cola)
2.- Saber identificar el evento ocurrido. Esto se hace, como veremos a continuación, mediante el campo type de la estructura
3.- Dependiendo del tipo de evento, actuar en consecuencia y recoger de la estructura XEvent los datos necesarios a partir de sus diferentes campos. Si, por ejemplo, recibimos un evento KeyPress, accederemos a (como se verá en la definición de la estructura) xevent.xkey para informarnos acerca de dicha pulsación de teclado. De hay que dentro de esta estructura XEvent hayan definidas diferentes estructuras, útiles o no dependiendo del evento recibido
Veamos en pseudocódigo nuestra aplicación X Window:
Programa: Declarar variables Inicializar display, ventanas, etc Seleccionar eventos deseados (XSelectInput) Bucle de Eventos: Coger el primer evento de la cola. Si evento.type es KeyPress Leer tecla de xevent.xkey.keycode Si evento.type es ButtonPress Leer datos de xevent.xbutton.x y de xevent.xbutton.y (etc.) Fin Bucle EventosOtra estructura útil es XAnyEvent, que incluye los 5 campos comunes a toda las estructuras de evento:
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window window; } XAnyEvent;El parámetro type es el tipo de evento, serial es el número de la última petición atendida, send_event es verdadero si procede de un SendEvent y falso en caso contrario, y display y window son el display y ventana de la que procede el evento.
Mediante todos los datos vistos hasta ahora es posible averiguar el tipo de evento ocurrido y datos acerca del mismo, como se puede ver en el siguiente ejemplo:
XEvent evento; while(1) { CogerSiguienteEvento(evento); switch( evento.type ) { case KeyPress: tecla = evento.xkey.xkeycode; break; case ButtonPress: etc etc etc... } }De este modo, ante cada evento se ejecutará el código correspondiente a lo que nosotros queremos que nuestra aplicación haga ante esa situación. Si se realizara un sencillo juego de naves, el control sería algo similar a lo siguiente:
case KeyPress: tecla = evento.xkey.xkeycode; if( tecla == DERECHA ) x_nave++; else if (tecla = IZQUIERDA) x_nave--; etc...Ahora la pregunta principal debe centrarse en: ¿cómo se obtienen los eventos desde la cola de eventos hasta nuestra estructura Xevent?
XNextEvent(Display *display, XEvent *evento);
Recoge el siguiente evento de la cola en modo bloqueante, es decir, recoge cualquier evento de cualquier ventana de nuestra aplicación y lo introduce en la estructura XEvent que se le pasa como parámetro. Es la más sencilla y más utilizada.
XWindowEvent(Display *display, Window ventana, long Máscara, XEvent *evento);
XMaskEvent(Display *display, long máscara, XEvent *evento);
Recogida de los eventos sólo de la ventana especificada o de la máscara de evento especificado (por si sólo quisieramos recoger eventos relacionados con ButtonPressMask, por ejemplo).
XCheckMaskEvent(Display *display, long máscara, XEvent *evento)
XCheckWindowEvent(Display *display, Window ventana, long máscara, XEvent *evento)
XCheckTypeEvent( Display *display, int tipo, XEvent *evento )
XCheckTypedWindowEvent(Display *display, Window ventana, int tipo, XEvent *evento);
Conjunto de rutinas no bloqueantes, que requieren algunas condiciones concretas. Su utilidad se basa, por ejemplo, en recoger sólo los eventos de un tipo en concreto (XCheckMaskEvent/XCheckTypeEvent y la máscara de eventos que se comentó en la anterior entrega o directamente el tipo de evento), sólo los eventos de una ventana en concreto (XCheckWindowEvent()), o los que cumplan ambas condiciones, ser de un tipo en concreto y pertenecer a la ventana especificada (XCheckTypedWindowEvent()). Como ejemplo, mediante esta última función sería posible recuperar únicamente (por si fueran los únicos que nos interesaran) los eventos de un tipo determinado y de una ventana en concreto (ej: sólo procesar el teclado en una ventana).
XPeekEvent(Display *display, XEvent *evento);
Resultado similar al de XnextEvent, pero no elimina el evento de la cola al recogerlo, simplemente lo lee e introduce sus datos en la estructura.
La diferencia entre un método bloqueante y uno que no lo es radica en que los primeros vacían el buffer de peticiones y esperan a que llegue un evento mientras que los segundos cogen el evento si éste está presente, regresando sin esperar si no lo está.
A todas estas funciones se les pasa un parámetro XEvent que es donde estas introducirán los datos del evento leidos desde la cola.
Otras funciones existentes para la lectura son las siguientes (aunque no serán comentadas al no ser generalmente necesarias): XIfEvent(), XCheckIfEvent() y XPeekIfEvent(), que corresponden a eventos con condiciones definidas por el usuario (para dotar de más posibilidades al total de 11 funciones disponibles para la recuperación de eventos).
Veamos un ejemplo de recogida de eventos (recordemos que la compilación se realiza mediante gcc test1.c -o test1.exe -lX11 -L/usr/X11R6/lib):
#include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xos.h> void main() { /* declaración de variables */ Display *display; Window window; int pantalla; unsigned long white, black; XEvent evento; char tecla; /* inicializaciones */ display = XOpenDisplay( NULL ); pantalla = DefaultScreen( display ); black=BlackPixel( display, pantalla ); white=WhitePixel( display, pantalla ); /* creamos la ventana */ window=XCreateSimpleWindow(display, DefaultRootWindow(display), 0,0,400,300,5,white,black); /* seleccionamos los eventos deseados */ XSelectInput(display, window, ButtonPressMask|KeyPressMask); XMapWindow( display, window ); /* Bucle infinito de eventos */ while(1) { /* Coger el evento siguiente en nuestra variable de eventos */ /* Solo nos son enviados eventos que pedimos en XselectInput */ XNextEvent(display, &evento); switch(evento.type) /* ¿que evento ha ocurrido? */ { case ButtonPress: /* ¿boton del raton? */ printf("Boton pulsado en (%i,%i)\n", evento.xbutton.x, evento.xbutton.y); break; case KeyPress: /* ¿fue una tecla? */ exit(0); /* salir */ } } }Ahora puede observarse como encaja todo lo explicado en los 3 últimos meses: los eventos son enviados por el servidor, y nuestro programa cliente ya es capaz de recogerlos, identificarlos y actuar en consecuencia, como se muestra en la figura 1. Si se conocieran funciones gráficas, ahora sería posible hacer sencillos programas de dibujo que tracen pixels ante pulsaciones del mouse, hacer sencillas utilidades o programas de texto. Es posible crear para ello funciones que emulen un sistema de texto virtual en la ventana gráfica.
En el listado 1 podemos observar otro ejemplo de uso de eventos, esta vez con escritura en pantalla mediante XSelectFont, XSetFont y XDrawString. En este ejemplo se dibuja una X en la coordenada del mouse cada vez que un botón del mismo es pulsado, y se sale al pulsar cualquier tecla.
/* Ejemplo de eventos y escritura en pantalla */ #include <X11/Xlib.h> #include <X11/Xutil.h> void main() { Display *display; Window window; Font font; GC gc; XEvent evento; int pantalla; unsigned long negro, blanco; display = XOpenDisplay( NULL ); font = XLoadFont( display, "8x16" ); pantalla = DefaultScreen( display ); negro = BlackPixel( display, pantalla ); blanco = WhitePixel( display, pantalla ); window = XCreateSimpleWindow( display, DefaultRootWindow( display ), 350, 0, 500, 400, 1, negro, blanco ); XSelectInput( display, window, ButtonPressMask | KeyPressMask ); XMapWindow( display, window ); gc = XCreateGC( display, window, 0, NULL ); XSetFont( display, gc, font ); while( 1 ) { XNextEvent( display, &evento ); switch( evento.type ) { case ButtonPress: XDrawString( display, window, gc, evento.xbutton.x, evento.xbutton.y, "X", 1); break; case KeyPress: exit(0); } } }
typedef struct { int type; /* KeyPress or KeyRelease */ unsigned long serial; /* # of last request processed */ Bool send_event; /* true if this came from a SendEvent */ Display *display; /* Display the event was read from */ Window window; /* event window it is reported relative to */ Window root; /* root window that the event occurred on */ Window subwindow; /* child window */ Time time; /* milliseconds */ int x, y; /* pointer x, y coordinates in event window */ int x_root, y_root; /* coordinates relative to root */ unsigned int state; /* key or button mask */ unsigned int keycode; /* detail */ Bool same_screen; /* same screen flag */ } XKeyEvent; typedef XKeyEvent XKeyPressedEvent; typedef XKeyEvent XKeyReleasedEvent;
Pulse aquí para bajarse los ejemplos y listados del artículo (2 Kb).
Figura 1: "Encolado y recogida de eventos."
Figura 2: "Aplicacion orientada a eventos."
Santiago Romero