INTRODUCCIÓN A LA PROGRAMACIÓN EN X WINDOW

Artículo 2: Referencia de funciones de inicialización.

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


Vimos en la anterior entrega los conceptos básicos de la arquitectura cliente/servidor y un programa ejemplo cuyas funciones de inicialización veremos en este número, dedicado a las llamadas que se utilizan en el previo al programa en sí.

Como se comentó el mes pasado, un programa X Window debe comenzar por inicializar el sistema de ventanas (o más correctamente, realizar la apertura del display y pantalla y crear la ventana principal de la aplicación) para posteriormente introducir el código del programa propiamente dicho. Veamos dichas funciones de inicialización una a una, con una descripción de cada una de ellas y de los parámetros que estas aceptan (para obtener esta información OnLine, aunque en inglés, pruebe a ejecutar "man <nombre_de_función>" en la línea de comandos, respetando mayúsculas y minúsculas). Las páginas del manual suelen ser muy útiles (aunque es difícil encontrar lo que se busca) cuando se programa en X Window. Por otra parte, cuando se conoce el nombre de una función, lás páginas del manual se convierten en estupendas referencias donde consultar parámetros y nombres de estructuras. A título de ejemplo, en puede observarse la salida del comando "man XOpenDisplay" (función que comentaremos a continuación) ejecutando dicho comando en cualquier consola de Linux. En el presente texto trataremos de ofrecer un manual de referencia en castellano a todas las funciones de inicialización necesarias para nuestros programas, y por supuesto, en castellano.


XOPENDISPLAY


Display *XOpenDisplay( char *display_name );

La función XOpenDisplay devuelve una estructura tipo display que sirve como conexión con el servidor X y que contiene toda la información sobre dicho servidor. Esta función conecta a la aplicación al servidor X a través de protocolos de comunicaciones TCP o DECnet.

Si la llamada tiene éxito, XOpenDisplay devuelve un puntero a una estructura de tipo Display definida en /usr/X11R6/include/X11/Xlib.h, o NULL en caso de no tener éxito. Tras obtener la estructura de tipo Display ya se puede acceder a cualquiera de las pantallas (screens) de dicho display. Como veremos a continuación, es posible obtener información del display (anchura, altura, etc.) a partir de esta estructura, pero usando macros específicas para ello y sin tener que utilizar personalmente los campos de esta estructura.


 Ejemplo: disp = XOpenDisplay( NULL );


XCLOSEDISPLAY

XCloseDisplay(Display *display);

Esta función cierra la comunicación con el servidor X para el display especificado y destruye todas las ventanas , identificadores de recursos (ventanas, fuentes, cursores, etc.) que se crearon en este display (a menos que se cambie el modo de cierre mediante la función XSetCloseDownMode()). Esta función debe ser llamada antes de salir del programa para que se reciban posibles errores en cola ya que la llamada a XCloseDisplay genera una operación XSync.


 Ejemplo: XCloseDisplay( disp );


XSYNC

XSync(Display *display, Bool discard);

Esta función vacía el buffer de salida y espera a que todas las peticiones sean atendidas por el servidor X, de manera que cualquier error generado sea atendido por el manejador de errores del cliente. El parámetro discard especifica si se deben de descartar todos los eventos en la cola (False=no descartar, True=descartar). Es llamada automáticamente por XCloseDisplay al cierre de nuestro programa.


XFLUSH

XFlush( Display *display );

Esta función se utiliza para vaciar el buffer de peticiones de nuestra aplicación de tal modo que sean enviadas al servidor. Como veremos en la sección dedicada al bucle de eventos, en ocasiones no será necesario el uso de esta función pues será llamada automáticamente por funciones utilizadas en dicho bucle, aunque en otros casos nos puede ser necesario forzar el envío de los mensajes al servidor.


XSETCLOSEDOWNMODE

XSetCloseDownMode(Display *display, int close_mode);

La función XSetCloseDownMode() especifica qué debe ocurrir a los recursos del cliente cuando se cierre la conexión con el servidor X. El parámetro close_mode indica el modo de actuación del servidor, entre DestroyAll (modo por defecto, y que especifica que todos los recursos del cliente desaparecerán), y RetainPermanent o RetainTemporary, que los mantendrán longeva o temporalmente (ver las man-pages para más información). Esta función permite especificar qué ocurriría con un icono o sonido de nuestro programa, por ejemplo, si éste fuera cerrado por parte del usuario. Como ejemplo podríamos usar el programa visualizador de gráfocps XV, que al ser cerrado permite dejar la ventana con el gráfico visualizado en nuestro desktop.


XKILLCLIENT

XKillClient(Display *display, XID resource);

Esta función cerrará aquel cliente dueño del recurso especificado mediante XID resource, lo cual puede permitir al Window Manager la muerte de una aplicación cuyo recurso cause un error grave, o para aquellas aplicaciones que usan XSetCloseDownMode con RetainTemporary y mueren por un error, mientras que sus recursos no lo hacen. Esta función asegura que el Window Manager podrá cerrar aplicaciones cuyos recursos dañen la estabilidad del sistema.


OBTENIENDO INFORMACIÓN SOBRE EL DISPLAY

A continuación se presentan una serie de funciones (cuyos nombres comienzan por X) y macros (de uso más sencillo) mediante las cuales es posible obtener información sobre un display previamente abierto con XOpenDisplay.


DEFAULTSCREEN

DefaultScreen( display );
int XDefaultScreen( Display *display );

Esta macro (la primera) o función (la segunda) devuelve el número asociado a la pantalla por defecto en referencia a un display abierto mediante XOpenDisplay. Ejemplo:


Display *display;
int pantalla;

/* primero es necesario abrir el display */
display = XOpenDisplay( NULL );
pantalla = DefaultScreen( display );

Este número de pantalla se utilizará en llamadas a otras funciones, por lo que es aconsejable que sea almacenado en alguna variable global del programa. En general en la programación estructurada suele ser poco recomendable la utilización de variables globales, pero en el caso de la variable display, esta va a ser utilizada por el 95% (tal vez más) de las funciones Xlib de nuestro programa, de modo que declarándola como global (dotándole de un ámbito igual a todo el programa) se obtiene un ahorro en tiempo de ejecución, en escritura (habría que pasar dicha variable a todas las funciones de nuestro programa) y en longitud del código.


SCREENCOUNT

ScreenCount( display );

Devuelve el número de pantallas disponibles en el display.


DISPLAYWIDTH Y DISPLAYHEIGHT

/* macros */
DisplayWidth( display, Pantalla );
DisplayHeight( display, Pantalla );
/* funciones */
int XDisplayWidth( Display *display, int Pantalla );
int XDisplayHeight( Display *display, int Pantalla );

Estas macros (las 2 primeras) y funciones (las 2 últimas) nos permiten conocer el tamaño de la pantalla especificada en los 2 parámetros display y pantalla.


int anchura, altura;
anchura = DisplayWidth( display, pantalla );
altura = DisplayHeight( display, pantalla );
printf("Dimensiones: %dx%d .", anchura, altura );


SERVERVENDOR

ServerVendor( display );
char *XServerVendor( Display *display );

Esta macro/función proporciona información sobre el fabricante o vendedor del servidor que se está ejecutando. Devuelve una cadena de texto que identifica al fabricante (vendor) de X Window. En un sistema Linux esta cadena será probablemente una alusión a XFree86, el servidor free de X Window.


WHITEPIXEL y BLACKPIXEL

/* macros */
WhitePixel( display, Pantalla );
BlackPixel( display, Pantalla );

Como la disponibilidad de colores puede variar entre los distintos displays y pantallas, estas 2 macros nos proporcionan los valores de pixel correspondientes a los colores blanco (WhitePixel) y negro (BlackPixel) para utilizarlos en la creación de la ventana, en rutinas gráficas, etc. Ejemplo:


unsigned long blanco, negro;
blanco  = WhitePixel( display, pantalla );
negro = BlackPixel( display, pantalla );

La utilidad de esta función radica en que cada display, pantalla o modo de vídeo puede disponer de un valor numérico para estos colores, de manera que podemos pedir al propio sistema de que nos informe de estos valores para el display que ejecuta nuestro programa.


DEFAULTDEPTH

DefaultDepth(display, pantalla);

Esta función devuelve el nº de planos o bits por pixel del display actual (8bpp=256 colores, 15bpp=32768 colores, 16bpp=65536 colores, 24/32bpp=16.4 millones de colores), de tal modo que podamos adaptar nuestras rutinas de dibujo (si utilizamos DGA, por ejemplo) a la profundidad de color de la pantalla. Sabiendo este parámetros nuestras rutinas de trazado de gráficos pueden ser optimizadas y nuestros gráficos pueden ser convertidos desde el disco al formato de pantalla actual. Recordemos que el número de bits por pixel indica la forma en que se representa un pixel individual en pantalla, de forma que un programa sea capaz de componer los gráficos con el objetivo de que la pantalla los "comprenda" directamente.


DEFAULTROOTWINDOW Y ROOTWINDOW

DefaultRootWindow( display );
RootWindow( display, Pantalla );

Estas 2 funciones sirven para conocer el identificador de la ventana raíz en la pantalla por defecto o en la pantalla especificada, respectivamente. Su uso es necesario, por ejemplo, para la creación de nuestra ventana principal con el fin de que sea hija de la ventana raíz (como se vio en el ejemplo del mes pasado).


OTRAS FUNCIONES DE INTERÉS

Otras funciones útiles que sería recomendable consultar en el manual de X (mediante man <nombre_de_función>) son las siguientes:


ConnectionNumber(display);
DefaultColormap(display, screen_number);
int *XListDepths(display, screen_number, count_return);
DefaultGC(display, screen_number);
DefaultScreenOfDisplay(display);
DefaultVisual(display, screen_number);
DisplayCells(display, screen_number);
DisplayPlanes(display, screen_number);
DisplayString(display);
ProtocolVersion(display);
ProtocolRevision(display);
VendorRelease(display);
XSetBackground(display, gc, color);
XSetForeground(display, gc, color);
XClearWindow(display, window);

Aparte de estas funciones tenemos la sencilla función XSetStandardProperties que nos permite especificar entre otras cosas el título de la ventana de nuestro programa y el título que esta presentará al estar minimizada:

XSetStandardProperties( display, ventana, "Titulo", "TituloMinimizado", None, NULL, 0, NULL );

A esta función se le pasa el display, ventana y títulos para dicha ventana, así como una serie de parámetros adicionales que por ahora dejaremos en (None, NULL, 0 y NULL).


PROGRAMA DE EJEMPLO

A modo de ejemplo de uso de las funciones de inicialización y descripción del display actual, el siguiente listado (gcc -o info.exe info.c -lX11 -L/usr/X11R6/lib) muestra en la xterm donde es lanzado las características más importantes de la pantalla por defecto del display actual, utilizando las funciones anteriormente descritas.


/* INFO.C : Información sobre el display */

/* headers necesarios para la compilación */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>

main()
{
    /* variables utilizadas en el programas */
    /* Necesitaremos abrir el display */
    Display *display;
    char *vendedor;
    int pantalla, pantallas, anchura, altura, depth;
    unsigned long blanco, negro;

    /* abrimos el display por defecto */
    display = XOpenDisplay( NULL );

    /* tomamos datos acerca de dicho display */
    /* para ello utilizamos las macros comentadas */
    /* en el artículo */
    pantallas = ScreenCount( display );
    pantalla = DefaultScreen( display );
    blanco  = WhitePixel( display, pantalla );
    negro = BlackPixel( display, pantalla );
    anchura  = DisplayWidth( display, pantalla );
    altura  = DisplayHeight( display, pantalla );
    depth = DefaultDepth( display, pantalla );
    vendedor = ServerVendor( display );

    /* mostramos los datos en la xterm */
    printf("\nInformación sobre el display:\n\n");
    printf("vendedor  = %s\n", vendedor );
    printf("pantallas = %d\n", pantallas );
    printf("pantalla  = %d\n", pantalla );
    printf("blanco    = %u\n", blanco );
    printf("negro     = %u\n", negro );
    printf("depth     = %u\n", depth );  
    printf("anchura   = %d\n", anchura );
    printf("altura    = %d\n\n", altura );
}

La salida del proceso de compilación y ejecución del anterior programa (disponible en el CD que acompaña a la revista como info.c) puede observarse en la siguiente figura:

 FIGURA1: Salida del programa INFO.C:
 ====================================

[sromero@compiler codigo]$ ./info.exe

Información sobre el display:

vendedor  = The XFree86 Project, Inc
pantallas = 1
pantalla  = 0
blanco    = 65535
negro     = 0
depth     = 16
anchura   = 1024
altura    = 768

VENTANAS

Cada una de las pantallas puede tener más de una ventana, organizadas en forma de árbol donde el nodo raíz es una ventana principal creada por el servidor X:

Ventana_Raíz:
 Aplicación_1
   \ Hija_Aplic1_1
   \ Hija_Aplic1_2

 Aplicación_2
 (sin hijas)

Es posible observar esta jerarquía mediante el comando "xwininfo -tree" (y pinchando sobre la ventana deseada), o mediante "xwininfo -tree -root" para poder examinar todas las ventanas que cuelgan de la ventana raíz (cuya salida puede verse en la figura 2). Mediante el comando xwininfo sin parámetros simplemente se nos proporcionará información acerca de la ventana sobre la que pulsemos el botón del mouse.



 FIGURA 2: SALIDA DE XWININFO -ROOT:
 ==================================

[sromero@compiler art2]$ xwininfo -root

xwininfo: Window id: 0x25 (the root window) (has no name)

  Absolute upper-left X:  0
  Absolute upper-left Y:  0
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 1024
  Height: 768
  Depth: 16
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x21 (installed)
  Bit Gravity State: NorthWestGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +0+0  -0+0  -0-0  +0-0
  -geometry 1024x768+0+0

Las ventanas en un determinado nivel del árbol se llaman ventanas hermanas (como Hija1 e Hija2 en nuestro ejemplo), y existe un orden en que son trazadas (especificado por la forma en que son apiladas), lo cual quiere decir que existe una pila que especifica el orden de solapamiento de las mismas, de tal modo que una ventana será visible si está mapeada y está en la cima de la pila (y recibe el foco del usuario), o bien pese a no estár en la cima de la pila se da la circunstancia de que no está solapada por ninguna otra ventana.


ATRIBUTOS DE LAS VENTANAS

Entre los atributos que definen a una ventana (y que utilizaremos para la creación de las mismas mediante las funciones apropiadas) tenemos la propia geometría de la ventana, definida por la anchura y altura en pixels (siempre sin incluir el borde) y la posición de la misma en coordenadas de pantalla (en relación a (0,0), como se especificó en el número anterior). Más atributos (aunque no configurables) son la profundidad de color de la ventana (8/15/16/24/32 bits por pixel), o el orden en que ésta es apilada, que no puede ser especificado en la creación ya que ésta siempre aparece encima de la ventana padre y de sus hermanas.

Más características configurables de la ventana son el cursor del apuntador asociado a la misma, el borde (que puede ser un color sólido o un borde a base de patrones de bitmaps o pixmaps), y el fondo (que también puede ser sólido o relleno mediante un patrón).


CREACIÓN DE VENTANAS

Para crear ventanas en Xlib disponemos de 2 funciones, XCreateSimpleWindow y XCreateWindow. Veamos la primera de ellas (más sencilla y cuyo uso bastará por el momento):


Window XCreateSimpleWindow(
 Display *display,
 Window Ventana_Madre,
 int x, int y,
 unsigned int Anchura,
 unsigned int Altura,
 unsigned int Anchura_Borde,
 unsigned long Color_Borde,
 unsigned long Color_Fondo );

De estos parámetros, las coordenadas (x,y) especifican el lugar de creación de la ventana, y Anchura y Altura las dimensiones de la misma. El tamaño y color del borde también pueden especificarse, así como el color del fondo, para los que podemos usar las macros anteriormente descritas WhitePixel y BlackPixel. La variable display se puede obtener de XOpenDisplay, mientras que la ventana madre se puede especificar gracias a las macros DefaultRootWindow o RootWindow para que nuestra ventana cuelgue de la ventana madre o raíz (si es la ventana principal de nuestra aplicación).

Un ejemplo de creación de ventana sería el siguiente, que crearía una ventana de 500x400 en la posición (100,100) de la pantalla:


/* Primero se abre el display por defecto */
display = XOpenDisplay(NULL);

/* a continuación se crea la ventana en dicho display, 
   tomando el desktop como raíz, en 100,100, de 500x400, 
   y de color y fondo blanco y negro */
ventana = XCreateSimpleWindow(display,
          RootWindow( display, 0),
          100, 100, 500, 400, 2,
          WhitePixel(display, pantalla),
          BlackPixel(display, pantalla) );


GEOMETRÍA Y MAPEADO DE LAS VENTANAS

Una vez creada la ventana es posible modificar el tamaño de la misma y de su borde así como su posición en pantalla (geometría de la ventana) mediante las siguientes llamadas Xlib:

XMoveWindow( Display *display, Window ventana, int x, int y ):
Mueve la ventana que se le indica a la posición de pantalla especificada por las coordenadas (x,y) que se le pasen como parámetros.

XResizeWindow( Display *display, Window ventana, unsigned int anchura, unsigned int altura ):
Esta función modifica el tamaño (anchura y altura) de la ventana a los valores deseados y que se le pasan como parámetros.

XSetWindowBorderWidth( Display *display, Window ventana, unsigned int anchura ):
Permite especificar una nueva anchura en pixels del borde.

XMoveResizeWindow( Display *display, Window ventana, int x, int y, int anchura, int altura ):
esta función es la fusión de la 2 primeras y permite modificar las dimensiones y posición simultáneamente en una sóla llamada a XLib.

A continuación se incluyen una serie de funciones para el mapeado y desmapeado de ventanas, ya que una ventana no se mapea automáticamente al crearla y será necesario mapearla manualmente como se ha hecho en los ejemplos. Como se comentó anteriormente, mapear una ventana significa hacerla visible, y para que esté mapeada es imprescindible que ninguna ventana la solape.

XMapWindow( Display *display, Window ventana ):
Mapea la ventana especificada.

XMapSubWindows( Display *display, Window ventana ):
Mapea las ventanas hijas de la ventana que se le indica (pero no dicha ventana).

XUnmapWindow( Display *display, Window ventana ):
Desmapea la ventana especificada.

XUnmapSubWindows( Display *display, Window ventana ):
Similar a la anterior pero con las ventanas hijas.

XMapRaised( Display *display, Window ventana ):
Mapea la ventana colocándola además en la cima de ventanas. Tras la ejecución de esta llamada Xlib la ventana sobre la que es aplicado pasa a estar en primer plano y recibiendo el foco de X Window.

Por otra parte, Xlib nos provee de las siguientes 2 funciones para modificar la posición de nuestra ventana (si deseamos colocarla en el fondo o cima de la pila):

XRaiseWindow( Display *display, Window ventana ):
Esta función eleva la ventana que se le especifica a la cima de la pila de ventanas de forma que queda por encima de todas ellas, de forma similar a lo que hace XMapRaised(), pero con ventanas ya mapeadas.

XLowerWindow( Display *display, Window ventana ):
Mediante esta llamada Xlib se coloca a la ventana especificada en el fondo de la pila (y por tanto bajo todas las demás ventanas de la jerarquía).

Por último con respecto a modificación de geometría y apilamiento, la función XConfigureWindow permite modificar la posición, anchura, altura, borde y orden en la pila de una ventana mediante sola llamada, utilizando una estructura con los datos a pasarle y una variable donde cada bit indica si queremos modificar ese dato o no. Pero veamos las definiciones y un ejemplo de uso para aclarar su utilización:


XConfigureWindow( Display *display,
                  Window ventana,
                  unsigned int mascara,
                  XWindowChanges *Valores );

Símbolos de la variable máscara:


/* Diferentes bits On/Off */
#define CWX            (1<<0)
#define CWY            (1<<1)
#define CWWidth        (1<<2)
#define CWHeight       (1<<3)
#define CWBorderWidth  (1<<4)
#define CWSibling      (1<<5)
#define CWStackMode    (1<<6)

La estructura XWindowChanges es la siguiente:


/* parámetro 4 de la función */
typedef struct {
    int x, y;
    int width, height;
    int border, width;
    Window sibling;
    int stack_mode;
} XWindowChanges;

Un ejemplo de uso donde se modificaría el valor de las coordenadas X e Y y de la anchura de la ventana sería el siguiente:


 /* declaramos la estructura */
 XWindowChanges Valores;

 /* valores que deseamos modificar */
 Valores.x = 100;
 Valores.y = 100;
 Valores.width = 200;

 /* llamada a la función indicando mediante OR (|) */
 /* qué parámetros hay que modificar. */
 XConfigureWindow( display, ventana, CWX | CWY | CWWidth, Valores );

En el anterior ejemplo definimos los diferentes campos a modificar en la estructura XWindowChanges, y llamamos a la función especificando mediante la operación OR los parámetros a modificar tomándolos desde dicha estructura (para más información sobre sibling y stack_mode, los responsables de la modificación de la posición en la pila de la ventana, consultar el manual).


OTRAS FUNCIONES DE VENTANA

Las siguientes funciones también son de interés:

XSetWindowBackground(Display *display, Window ventana, long background_pixel):
Permite modificar el color de fondo de la ventana especificada.

XSetWindowBackgroundPixmap(Display *display, Window ventana, Pixmap background_pixmap):
Permite mapear un bitmap (Pixmap) como fondo de la ventana especificada, utilizándolo como un patrón a repetir.

XSetWindowBorder(Display *display, Window ventana, long border_pixel):
Permite modificar el color del borde de la ventana especificada.

XSetWindowBorderPixmap(Display *display, Window ventana, Pixmap border_pixmap):
Permite mapear un bitmap (Pixmap) en el borde de la ventana especificada.

XSetWindowColormap(Display *display, Window ventana, Colormap colormap):
Aunque por ahora no vamos a utilizar esta función, esta llamada permite modificar el colormap (mapa de colores o paleta) asociado a una ventana. Esto será necesario en modos de 8 bits por pixel o de 16 colores, ya que al disponer de un número limitado de colores, cada aplicación podrá hacer uso de su propia paleta de colores, aunque sólo puede estar activa una cada vez, de modo que la aplicación que reciba el foco del usuario deberá especificar la paleta que desea usar mientras mantenga el foco.


EN LA PRÓXIMA ENTREGA

Este mes hemos desgranado las funciones más importantes de inicialización y gestión de ventanas de Xlib, cubriendo la introducción a la primera parte de cualquier programa (la inicialización del mismo). En el próximo capítulo del curso comenzaremos a ver cómo se realiza la gestión de eventos y los diferentes tipos de eventos/mensajes de los que dispone X Window. Hasta entonces, un buen deporte es hacer man con las funciones aquí presentadas.

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

Santiago Romero


Volver a la tabla de contenidos.