INTRODUCCIÓN A LA PROGRAMACIÓN EN X WINDOW

Artículo 3: Introduccion a los eventos en X Window

Autor: (c) Santiago Romero.
Revista: Programación Actual (Prensa Técnica) nº 21, Diciembre-1998


La programación en los sistemas multitarea (como X Window o Windows) se basa en la espera, recogida y análisis de mensajes enviados por el sistema a las diferentes aplicaciones como respuesta a un evento ocurrido en el mismo, lo cual hace a este evento un importante objeto de estudio.

Como se comentó en el curso de programación de Windows95/98, un error habitual de los programadores de DOS que pasan a programar en sistemas multitarea es pensar que dos programas ejecutados simultáneamente en la misma máquina (la cual constará generalmente de un sólo procesador), cada uno de ellos se ejecutará a la mitad de la velocidad original de los programas por sí solos. Esto sería cierto si los programas se construyeran con la misma filosofía con que está pensada la programación de MSDOS, en la que los programas son sucesiones lineales de sentencias que deben ser ejecutadas una tras otra (con la excepción de saltos, funciones, o entradas en interrupciones, etc., que no constituyen sino simples cambios en la posición de esta linealidad). Es cierto que esta filosofía de la programación es muy simple y debido a esto es la primera que se aborda y aprende, pues muestra de una forma muy eficiente la forma de pensar de un computador, que no es más que un ejecutor de instrucciones.

En cambio, la manera de funcionar de los sistemas multitarea como X Window es totalmente distinta. En vez de ejecutar el programa instrucción por instrucción, estos programas se apoyan en el hecho de que resulta más eficiente el basar la programación multitarea en lo que se conoce como programación orientada a eventos (o, como se le suele llamar en Windows, orientado a mensajes), los cuales son enviados por el servidor y recibidos por el programa cliente (nuestro programa), y de los cuales deberemos hacernos cargo para realizar las acciones oportunas ante cada uno de ellos.


ORIENTACION A EVENTOS

Un evento se puede definir de una manera sencilla como un cambio de estado de la máquina, como pueda ser la pulsación de una tecla, movimiento o pulsación del mouse (estos primeros llamados eventos hardware), o un evento que indique a nuestra aplicación que se terminó de reproducir un archivo de sonido o que la ventana donde se ejecuta el programa debe de ser redibujada al ser movida o destapada (eventos software).


 SISTEMA     COMUNICACIóN       APLICACIóN
------------------------------------------------
 EVENTOS <->   MENSAJES  <-> GESTIóN DE EVENTOS

A modo de ejemplo, en lugar de tratar de detectar continuamente el estado del teclado para una determinada aplicación y obrar en función de cada tecla pulsada (con lo que requerimos atención por parte de nuestro programa a la detección de pulsación y liberación de teclas, incluso en los momentos en que ninguna está siendo pulsada), en los sistemas orientados a eventos es el servidor (o el propio Sistema Operativo, como en el caso de Windows) quien se dedica a la detección del dispositivo y envío de eventos (hardware) a los programas clientes que especificaron dicho servicio. Es decir, nuestro programa se dedicaría a recoger todos los eventos recibidos desde el servidor y sólo en caso de recibir un evento de teclado se procesaría la función encargada de manipular dicho código de teclado para realizar una acción determinada.

Cliente/Servidor

De una manera simplista, y para terminar de definir el esqueleto de un programa básico en X Window, un programa de X constará de las siguientes partes diferenciadas:

 1. Apertura del display: Mediante XOpenDisplay() se abrirá el display
deseado, o, de una manera más genérica, se abrirá el
display 0 o NULL de tal modo que sea el usuario quien decida el lugar de
visualización de nuestra aplicación. También puede ser
necesario obtener el número de la pantalla actual para determinadas
acciones sobre la misma (usando DefaultScreen()). Esto fue tratado en el
primero número de nuestro curso.

2. Creación de una (o más) ventanas de trabajo: Utilizando
funciones especificas para ello (XCreateSimpleWindow() o XCreateWindow()),
creamos la ventana principal de nuestra aplicación (si la necesita) y
todas las ventanas hijas que ésta pueda necesitar (primer y segundo
capítulo).

 3. Mapeado de la ventana: Con el fin de que la ventana sea visible, la
mapeamos (las funcionas de mapeado y desmapeado fueron comentadas en la
anterior entrega).

 4. Bucle de eventos: El bucle de eventos es una sección de
código que se encarga de detectar, clasificar e identificar eventos de
manera que la aplicación defina su propio comportamiento ante cada
evento. Una pulsación de teclado la gestionará de forma
diferente un programa de dibujo que un juego, por ejemplo, así que de
las acciones que se realicen como respuesta a estos mensajes dependerá
la función de nuestra aplicación. Este es el objetivo
didáctico de este mes.

 5. Cierre de la aplicación: Aquí se realizan todas las
funciones necesarias para cerrar correctamente nuestra aplicación
(XCloseDisplay(), etc.)

Al crear un programa X Window habremos, pues, de inicializar las ventanas de la aplicación (tal y como hicimos en el primer ejemplo del curso y como se comentará en esta entrega), y de programar un bucle cerrado que recoja todos los mensajes enviados por el servidor (eventos), cada uno de ellos con un nombre concreto (KeyPress, ButtonPress, Expose), de tal modo que la aplicación no sigue un flujo lineal, sino que se queda esperando mensajes desde el servidor, y contestando a éstos de manera que la aplicación haga lo deseado.


EJEMPLOS PRACTICOS

En la próxima entrega se analizarán las funciones de X Window asociadas a la recogida de los eventos (la estructura XEvent, como veremos, enorme, y función XNextEvent) y los eventos más sencillos (teclado, ratón, redibujado), pero para ir asimilando la nueva filosofía de trabajo a continuación veremos varios programas ejemplo sobre cómo el método de organización del código. Un ejemplo de programación orientada a eventos en lenguaje algorítmico sería el siguiente:


Programa:
{
  AbreDisplay();
  ObténInformación();
  CreaVentana();
  MuestraVentana();

  REPETIR Espera_EVENTO
  {
    Si llega PULSACION_DE_TECLADO:
    {
      Si la tecla es ESCAPE: Salir();
      Si la tecla es F1: ayuda();
      En el resto de casos: nada();
    }

    Si llega PULSACION_DE_RATON:
      Pintar_Punto_donde_raton();

    Resto_de_eventos: nada();
  }
}
Este "programa" es en sí mismo mucho más eficiente de lo que parece. ¿Porqué? Muy sencillo: mientras no ocurra un evento para nuestra aplicación, nuestro programa no hará nada, es decir, no robará tiempo de CPU a otros programas que hayan recibido la atención del usuario, y en el caso de que la reciba nuestro programa, el resto de programas estarán esperando eventos con lo que nuestro programa no tiene porqué verse ralentizado por otros programas que se ejecuten en el sistema operativo. Por supuesto, nada nos impide realizar acciones aunque no hayamos recibido un evento, pero por ahora nos centraremos en las bases para que más adelante cada lector profundice en una parte u otra de las Xlib según sus necesidades de programación. Por otra parte, el bucle que recoge los mensajes debe ser la parte principal del programa, es decir, la única parte de él que se ejecute (debe ser un bucle infinito), de forma que todas las funciones que sean llamadas lo sean desde él, y que no se salga del programa hasta que se reciba un mensaje que indique el cierre de la aplicación.

En el listado 1 disponemos de un sencillo programa de X Window que detecta la pulsación de teclas y botones del mouse y avisa de ello en la consola xterm desde donde sea lanzado, programa que vamos a utilizar para introducir el sistema de eventos en X Window.

 LISTADO 1: Ejemplo: eventos de raton y teclado.

/* Ejemplo del tutorial de Xintro.html */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>

 Display *dis;
 int screen;
 Window win;
 GC gc;

void init_x() {
  /* Para coger los colores blanco y negro */
  unsigned long black,white;

  dis=XOpenDisplay((char *)0);
  screen=DefaultScreen(dis);
  black=BlackPixel(dis,screen);
  white=WhitePixel(dis,screen);

  /* creamos la ventana */
  win=XCreateSimpleWindow(dis,DefaultRootWindow(dis),0,0,  
                          400, 300, 5, white, black);

  /* propiedades de la ventana */
  XSetStandardProperties(dis,win,"Hola Mundo :)","Hola!",None,NULL,0,NULL);

  /* seleccionamos qué tipos de eventos deseamos recibir */
  XSelectInput(dis, win, ExposureMask|ButtonPressMask|KeyPressMask);

  gc=XCreateGC(dis, win, 0,0);        
  XSetBackground(dis,gc,white);
  XSetForeground(dis,gc,black);
  XClearWindow(dis, win);
  XMapRaised(dis, win);
}

void close_xwin() {
  XFreeGC(dis, gc);
  XDestroyWindow(dis,win);
  XCloseDisplay(dis);
}

/*--- Funcion principal main -----------------*/
void main()
{

 XEvent event;    /* donde guardar los eventos. */
  char key;       /* para códigos de teclado */

 init_x();        /* creamos ventana, etc. */

  /* 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(dis, &event);

    switch(event.type)      /* ¿que evento ha ocurrido? */
    {  

    case Expose:
            /* si es necesario, redibujar la pantalla */
            if( event.xexpose.count == 0 ) ;
              ; /*  redraw(); */
            break;
    
    case KeyPress:    /* ¿fue una tecla? */
            key = XKeycodeToKeysym(dis, event.xkey.keycode, 0);
            if(key == 'q') close_xwin();
            printf("has pulsado la tecla %c !\n",key);
            break;
    
    case ButtonPress:    /* ¿boton del raton? */
      printf("Has pulsado un boton en (%i,%i)\n",
             event.xbutton.x,event.xbutton.y);
      break;
      }
    }
 close_xwin();
 
}


COMENTARIO DE TEST.C

En este programa se ha separado de la función main el código de inicialización y cierre de las variables asociadas al sistema de ventanas (Display, ventana, etc.) en 2 funciones (init_x() y close_x()), y se ha dejado en ella únicamente el bucle de mensajes.

De la función init_x(), los únicos aspectos necesarios de comentario son los siguientes (el resto ya han sido tratados aquí):


/* seleccionamos qué tipos de eventos deseamos recibir */
XSelectInput(dis, win, ExposureMask|ButtonPressMask|KeyPressMask);

Mediante XSelectInput(display, ventana, máscaras), es posible indicarle al servidor qué tipo de eventos queremos recibir en nuestra aplicación, con lo que si no se va a usar el mouse o teclado, nos ahorraremos la recepción de eventos de ese tipo (avisos a nuestra aplicación del estilo: "tecla pulsada", "botón pulsado", "ratón en 100,200", etc.), con la consecuente reducción del bucle de mensajes y la eliminación del procesamiento de estos mensajes (ya que no nos interesan). En este caso le pedimos al servidor que nos envíe todos los mensajes relacionados con eventos de pulsaciones de teclado (KeyPressMask), con pulsaciones del botón del ratón (ButtonPressMask) y que nos avise cuando sea necesario redibujar la ventana (ExposureMask).


XEvent event;    /* donde guardar los eventos. */

XEvent es, como ya se ha comentado, la estructura donde se almacena la información sobre los eventos que nos envía el servidor, y que veremos en el próximo número.


/* Bucle infinito de eventos */
while(1)
{
 // bucle de eventos
}

Esta sección de código de la función main() se encarga de la recogida y respuesta ante los distintos eventos que nuestra aplicación pueda recibir.


/* Coger el evento siguiente en nuestra variable de eventos */
XNextEvent(dis, &event);

Mediante la anterior línea se recoge la información del servidor en la estructura event, de tal modo que podamos discernir qué evento ha ocurrido:


 switch(event.type)      /* ¿que evento ha ocurrido? */
 {  

   case Expose:
      /* si es necesario, redibujar la pantalla */
      if( event.xexpose.count == 0 ) ;
         /*  redraw(); */
      break;
    
   case KeyPress:    /* ¿fue una tecla? */
      key = XKeycodeToKeysym(dis, event.xkey.keycode, 0);
      if(key == 'q') close_xwin();
      printf("has pulsado la tecla %c !\n",key);
      break;
    
    case ButtonPress:    /* ¿boton del raton? */
      printf("Has pulsado un boton en (%i,%i)\n",
             event.xbutton.x,event.xbutton.y);
      break;
   }
 }

La anterior sección de código utiliza el campo type de la estructura event para saber qué evento ha ocurrido y actuar en consecuencia.

Como otro ejemplo de programa que gestiona los eventos tenemos win.c, uno de los ejemplos incluidos en la documentación de X Window en Internet (muy abundante y a la que se puede llegar mediante cualquier buscador como www.yahoo.com). Este programa de ejemplo no sólo gestiona los eventos (como puede leerse en los comentarios incluidos con el programa), sino que también muestra la manera de dibujar frases y formas gráficas en pantalla, ademas de controlar los posibles errores devueltos por las funciones, que hasta ahora no habíamos comentado. En este listado se puede observar cómo el bucle de eventos es un bucle infinito que sólo terminará cuando se cierre la aplicación, y dentro del cual se realizan las acciones pertinentes a nuestro programa. En el mismo sólo se comprueban y reacciona ante 2 eventos:

Expose: Evento enviado por el servidor para que redibujemos la ventana
(similar a un WM_PAINT de Windows), ya que esta ha sido pasada a primer plano,
se ha redimensionado, etc.

ButtonPress: Si pulsamos el botón del ratón, nuestra
aplicación recibirá este mensaje y se ejecutará el
exit(0), saliendo de la aplicación.

El listado completo del programa es el número 2 (compilable mediante gcc -o win.exe win.c -lX11 -L/usr/X11R6/lib), y por ahora no vamos a comentarlo porque incorpora funciones que todavía no han sido abordadas, pero que cumple perfectamente su función de ilustrar cómo se debe de organizar el bucle principal de mensajes de un programa cualquiera. En este caso el programa simplemente dibuja datos en pantalla (un mensaje, recuadro y una elipse rellena.
LISTADO 2: Ejemplo win de la documentacion de XWindow.

/* win.c --- a very basic Xlib application,    */
/* Jeff Pitchers, LUT 1993                     */
/* Extraido de Internet, documentación de X11  */
/* Set básico de ficheros de cabecera para X11 */
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/keysym.h>

/* Variables del programa: */
/* display: Display del servidor al que conectar */
/* screen: Nº de pantalla a usar en ese display */
/* win: La única ventana de la aplicación */
/* event: La estructura de eventos */
/* gc1, gc2: Contextos de gráficos */
/* gs1, gs2: Valores de los GCs */
/* message: Mensaje a imprimir en la ventana */
Display *display;
int screen;  
Window win;
XEvent event;      
GC gc1, gc2;       
XGCValues gs1,gs2; 
char *message;     
static char progDefaultMessage[] = {"A Simple Xlib Program"};

/* Función principal main, como en todo programa C */
main(argc, argv)
int argc; char **argv;
{

  /* Tratando de conectar al servidor X */
  if ((display = XOpenDisplay(NULL)) == NULL)
  {
    perror("Can't connect to server");
    exit(1);
  }
  screen = DefaultScreen(display);

  /* Crear una ventana */
  win = XcreateSimpleWindow(display,
          RootWindow(display, screen),
          100, 200, 400, 300, 1,
          BlackPixel(display, screen),
          WhitePixel(display, screen));

  /* Seleccionar qué eventos se desea que la aplicación gestione */
  /* Se selecciona Redibujado + Pulsación de botón */
  XSelectInput(display, win, ExposureMask | ButtonPressMask);

  /* Iniciar contextos graficos */
  setUpGCs();

 /* Tratar de obtener un mensaje de la fuente de */ 
 /* recursos definidos por el usuario. Esto es similar */
 /* a los ficheros de recursos de Windows */
 /* (en los que se basó luego dicho S.O.), y mediante */
 /* XgetDefault es posible leer del fichero .Xresources */
 /* para obtener valores por defecto para determinadas variables. */
  if ((message = XGetDefault(display, "win", "message")) == NULL)
    message = progDefaultMessage;

  /* Mapear la ventana para que sea visible. */
  XMapWindow(display, win);

  /* Bucle de mensajes. Este bucle es el que le da sentido */
  /* a la aplicación, y consiste en la captura de los mensajes */
  /* o eventos enviados por el servidor y actuación en consecuencia */
  /* con ellos. Esto será explicado en la próxima entrega */
  while (1) {

    /* Coger el evento de la cola */
    XNextEvent(display, &event);
    /* dependiendo del tipo de evento... */
    switch (event.type) {
      /* si nos piden redibujado, redibujar */
      case Expose:
            drawInToWindow(); break;
      /* si se pulsa un botón, salir */
      case ButtonPress:
            exit(0); break;
      /* resto de eventos: nada */
      default: break;
    }
  }
}

/* Función que inicializa algunos contextos gráficos para */
/* ser utilizados en el programa */
setUpGCs()
/* gc1 se usa para el mensage y rectangulo */
/* gc2 se usa para la ellipse */
{
/* Definir un bitmap para rellenar la elipse */
#define stipple_width 3
#define stipple_height 3
  static char stipple_bits[] = {0x02, 0x05, 0x02};
  Pixmap stipple;

  if ((stipple = XCreateBitmapFromData(display, RootWindow(display, screen),
    stipple_bits, stipple_width, stipple_height)) == 0) {
    perror("Can't create bitmap");
    exit(1);
  }

  gc1 = XCreateGC(display, win, 0, &gs1); /* 0 -> use defaults */
  gc2 = XCreateGC(display, win, 0, &gs2); /* 0 -> use defaults */
  XSetForeground(display, gc1, BlackPixel(display, screen));
  XSetBackground(display, gc1, WhitePixel(display, screen));
  XSetLineAttributes(display, gc1, 2, LineSolid, CapRound, JoinRound);
  XSetStipple(display, gc2, stipple);
  XSetFillStyle(display, gc2, FillOpaqueStippled);
}

/* Función que dibuja en la ventana */
drawInToWindow()
{
  /* Imprimir mensaje y dibujar rectángulo */ 
  /* Dibujar elipse rellena definida en el GC */
  XDrawString(display, win, gc1, 130,
              20, message, strlen(message));
  XDrawRectangle(display, win, gc1,
              40, 40, 320, 220);
  XFillArc(display, win, gc2,
              50, 50, 300, 200, 0, 360*64);
}
/* Fin del programa */


SIMILITUD CON WINDOWS

Quienes ya hayan programado en Windows no tendrán ningún problema para adaptar su manera de pensar a X Window, pues la similitud entre los programas X Window y Windows, y entre estos dos y el ejemplo de pseudocódigo anterior es grande, cambiando simplemente la nomenclatura de las diferentes estructuras, funciones y tipos de datos. Veamos un ejemplo de función procesadora de mensajes de Windows para mostrar la semejanza con X Window:


//=== Función procesadora de mensajes. ===============
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, 
WPARAM wParam, LPARAM lParam )
{
 HDC hdc;
 PAINTSTRUCT ps;
 RECT rect;

 switch( message )
 {
  // mensaje producido en la creación de la ventana
  case WM_CREATE:     
       break;

  // mensaje producido cuando hay que redibujar
  case WM_PAINT:
       hdc = BeginPaint( hwnd, &ps );
       GetClientRect( hwnd, &rect );
       DrawText( hdc, "¡Hola Mundo!", -1, &rect,
                 DT_SINGLELINE | DT_CENTER | DT_VCENTER );
       EndPaint( hwnd, &ps );
       break;
        
  // mensaje producido al cerrar la ventana
  case WM_DESTROY:
       PostQuitMessage( 0 );
       break;

  // resto de mensajes, dar una respuesta estándar.
  // dejamos que el propio windows los responda :
     default:
      return( DefWindowProc( hwnd, message, wParam, lParam ) );
   }

  return(0);
}
Como puede verse, aprender a programar en Windows facilita el aprendizaje en X Window y viceversa, de modo que en general cualquier libro de programación orientada a eventos (preferentemente alguno de los escasos manuales sobre X Window) puede ayudarnos en la metodología, mientras que nuestro curso proporciona ejemplos y definiciones de las funciones específicas de X.


EN LA PROXIMA ENTREGA

El mes que viene desgranaremos la estructura XEvent, las funciones asociadas a la gestión de los eventos, y los nombres de los diferentes eventos en X Window, de forma que podremos crear nuestros primeros programas antes de pasar al tema de salida de gráficos y textos por pantalla.

Pulse aquí para bajarse los ejemplos y listados del artículo (8 Kb).

Figura 1: "Arquitectura cliente-servidor."

Santiago Romero


Volver a la tabla de contenidos.