Por ejemplo, las terminales de texto en X utilizan siempre el CutBuffer 0, y almacenan simplemente caracteres ASCII con retornos de carro, tabuladores, etc. Si en nuestra aplicación detectamos la pulsación del botón central del mouse (o de la tecla que deseemos asignar a la función de pegado) y leemos los datos contenidos en el CutBuffer 0, insertándolos en nuestra aplicación, tendremos implementada una función de pegado que nos permitirá pegar texto en nuestra aplicación copiado desde cualquier programa de X. Análogamente, si introducimos datos en dicho CutBuffer estaremos posibilitando que en cualquier aplicación pueda pegarse texto copiado desde la nuestra.
Las funciones para la inserción de datos en los CutBuffers son las siguientes:
XStoreBytes( Display *display, char *bytes, int nbytes );
Esta función almacena un vector de bytes (que puede representar cualquier cosa) dentro del CutBuffer 0. Se le pasa como parámetro el display del servidor en el que almacenarlo, un puntero a los datos a almacenar, y el número de bytes que ocupan dichos datos.
XStoreBuffer( Display *display, char *bytes, int nbytes, int buffer );
Esta función almacena un vector de bytes (que puede representar cualquier cosa) dentro del CutBuffer buffer. Utiliza los mismos parámetros que la función anterior seguidos del buffer donde almacenar los datos.
La manera de vaciar los CutBuffers consiste en almacenar en ellos un dato de cero bytes, es decir, llamando a esta función con un valor de nbytes igual a cero.
Las funciones para la lectura de datos desde los CutBuffers son las siguientes:
char *bytes = XFetchBytes( Display *display, int * nbytes );
Esta función lee los datos almacenados en el CutBuffer 0 del servidor X, devolviendo un puntero a dichos datos y en nbytes el tamaño de los mismos. Para devolver los datos la función pide memoria internamente que tendremos que liberar cuando hayamos finalizado con su uso. Ejemplo:
char *datos; int nbytes; datos = XFetchBytes( display, &nbytes ); /* más código */ if( datos != NULL ) XFree( datos );También existe otra función para recuperar datos de cualquier buffer de corte (no sólo del cero):
char *bytes = XFetchBuffer( Display *display, int * nbytes, int buffer );
Esta función realiza el proceso equivalente a la función anterior pero para el buffer especificado.
Por último, los 8 CutBuffers pueden considerarse como una lista circular de buffers la cual podemos rotar (hacer que el buffer que antes era el 0 ahora sea el 1, etc.). Esto puede ser muy útil para ciertas aplicaciones, y se hace mediante XRotateBuffers( Display *display, int rotate ), donde rotate es la cantida de veces a rotar los buffers (cero los deja como están). Es una buena idea poner todos los buffers con longitud cero antes de comenzar a utilizar esta función.
Supongamos por ejemplo que disponemos de un programa que dispone de muchas funciones a las que se accede a través del teclado. Sería perfectamente posible, gracias al envío de eventos X entre aplicaciones, diseñar un programa con botones que hagan las tareas más habituales del primer programa, de forma que cuando pulsemos en un botón, envío eventos KeyPress al otro programa con las teclas apropiadas para realizar determinadas funciones (es decir, hemos creado un programa de macros). Hay otras muchas aplicaciones para el envío de eventos, de modo que vamos a ver la manera de comunicar nuestras aplicaciones mediante este lazo.
Para ello se utiliza la función XSendEvent:
Status XSendEvent(display, window_dest, propagate, event_mask, event_send) Display *display; Window window_dest; Bool propagate; long event_mask; XEvent *event_send;Es necesario rellenar correctamente la estructura XEvent (con todos los campos asociados al evento que vamos a enviar) , y la máscara del evento. El evento será enviado a la ventana window_dest, y la función devolverá 0 en caso de error, y distinto de cero en caso de éxito. La propagación se refiere a si el evento se debe propagar a las subventanas de una ventana en el caso de que dicha ventana no esté interesada en el tipo de eventos que enviamos.
Para saber a qué ventana enviar el evento es posible usar las constantes PointerWindow (enviar el evento a la ventana que tenga el cursor del ratón sobre ella) o InputFocus (aquella ventana que tenga el foco del teclado).
Veamos un ejemplo de envío de eventos KeyPress a otra aplicación:
int EnviarCodigo( Display *display, Window window, int keycode, int estado ) { int miestado; XKeyEvent evento; evento.display = display; evento.window = window; evento.root = RootWindow( display, DefaultScreen(display)); evento.time = CurrentTime; evento.state = estado; evento.type = KeyPress; evento.keycode = keycode; evento.x = evento.y = evento.x_root = evento.y_root = 0; evento.same_screen = 1; evento.subwindow = (Window) NULL; miestado = XSendEvent( display, window, 0, KeyPressMask, &evento ); if( miestado != 0 ) { evento.time = CurrentTime; evento.type = KeyRelease; miestado = XSendEvent( display, window, 1, KeyReleaseMask, &evento ); } return(miestado); }En esta función rellenamos todos los parámetros de la estructura Evento y enviamos el evento. En caso de éxito enviamos el evento de liberación de tecla y salimos de la función.
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window window; Atom message_type; int format; /* campos para intercambio de datos: */ union { char b[20]; short s[10]; long l[5]; } data; } XClientMessageEvent;El resultado dentro de nuestra aplicación para recepción sería algo como:
switch( evento.tipo ) { case ClientMessage: /* trabajar con los campos deseados */ break; etc.. }Para el envío de estos eventos a otras aplicaciones ya conocemos XSendEvent(), de modo que podemos establecer métodos de comunicación entre todas las aplicaciones que programemos.
Lo primero que veremos es la manera de conocer la altura absoluta de una fuente de letras, de forma que dejemos como mínimo dicho espacio entre 2 líneas de texto consecutivas para que no monte una sobre la otra. Para ello haremos uso de la estructura XFontStruct, parámetro devuelto por la función de informació sobre fuentes XQueryFont( display, nombre), que podremos utilizar sobre una fuente tras su carga. Esta estructura contiene campos que nos informarán de determinadas características de la fuente:
XFontStruct *font_struct; Font font; int altura_texto; font = XLoadFont( display, "9x15" ); font_struct = XQueryFont( display, font ); altura_texto = font_struct->ascent + font_struct->descent;Mediante la variable altura_texto ya podemos calcular las alturas correctas de líneas en nuestro programa.
Para las anchuras en pixels de las fuentes disponemos de la útil función XTextWidth():
int XTextWidth( XFontStruct *font_struct, char *string, int count);
Esta función, dado una estructura de fuente, una cadena, y el número de caracteres de la misma, nos devuelve la anchura en pixels que ocuparía dicha cadena si la escribiésemos en pantalla o en un pixmap, cosa que ya sabemos hacer mediante lo visto en anteriores entregas (XDrawText, XDrawString, XDrawImageString, etc.).
Sobre la carga de fuentes, un consejo que puede leerse en la documentación de muchos programas y en muchos libros sobre X es que se debe intentar cargar primero la fuente que el usuario especifique en línea de comandos mediante -fontname <nombre_de_fuente>. Si dicho intento falla (porque la fuente no se encuentre disponible en ese sistema), hay que intentar cargar la fuente por defecto que deseemos que use nuestro programa (por ejemplo, "9x15" es bastante estándar), y en último extremo en caso de fallo, tratar de cargar la fuente "fixed", que se encuentra en prácticamente todos los sistemas.
A esta función de ejemplo que hemos visto se le pueden hacer multitud de ampliaciones. Podríamos crear un modificador \k (por ejemplo) que a partir de ese momento cambie la fuente a itálica (cursiva), otro para negrita, modificadores para cambiar el color, etc. Esto haría que mediante esa función se pudiese trazar cualquier tipo de texto y en cualquier estilo simplemente formateando correctamente la cadena que se desea imprimir, en lugar de realizar en el código llamadas para cambiar el GC cada vez que se traza texto en pantalla.LISTADO 1: PrintfText()
/*======================================== Imprime cadenas formateadas con \n. Modificable para ampliaciones con colores, estilos, etc. =======================================*/ PrintfText( Display *display, Window window, GC gc, int x, int y, int altura_fuente, char *cadena, int longitud ) { char buff[81]; int i=0, j=0, caracter; while( i<longitud ) { j = 0; caracter = cadena[i]; while( (j<80) && (i<longitud) && (caracter!='\n') ) { buff[j] = cadena[i]; i++; j++; if( i<longitud ) caracter = cadena[i]; } if( j > 0 ) { buff[j] = '; XDrawString( display, window, gc, buff, strlen(buff) ); } i++; y+=altura_fuente; } XFlush(display); }
int XGrabPointer( Display display, Window grab_window, Bool owner_events, int event_mask, int pointer_mode, int keyboard_mode, Window confine_to, Cursor cursor, Time time);
Mediante XGrabPointer() se realiza la captura del puntero del ratón. Para ello se le especifica el display y la ventana a la que limitar el ratón (grab_window normalmente será la ventana raíz o sólo nuestra ventana), el modo de puntero y de teclado (que para la captura del ratón suelen valor pointer_mode = GrabModeSync y keyboard_mode=GrabModeAsync), la variable confine_to que puede utilizarse para confinar el puntero a una determinada ventana (y que no pueda salir de ella) y el cursor que deseamos definir para el ratón mientras dure el grab. Esta función devuelve GrabSuccess si el puntero ha sido capturado con éxito.
Una vez capturado el puntero, debemos activar los eventos con XAllowEvents() que permitirá el procesado normal de los eventos hasta que llegue el primer ButtonPress o ButtonRelease. Podemos con XWindowEvent() chequear únicamente aquellos eventos que nos interesen, por ejemplo, y tomar las acciones apropiadas para ellos.
Para deshacer el grab y dejar la situación del mouse como anteriormente, basta con utilizar XUngrabPointer( display, time );
Como ejemplo:
status = XGrabPointer(display, RootWindow(display,screen), False, ButtonPressMask, GrabModeSync, GrabModeAsync, False, cursor, CurrentTime ); if( status == GrabSuccess ) { /* usar el ratón aquí */ XAllowEvents(display, SyncPointer, CurrentTime ); XWindowEvent(display, RootWindow(display,screen), ButtonPressMask, &evento ); if( evento.type == ButtonPress ) { /* detectada pulsación del mouse */ hacer_lo_que_sea(); } XUnGrabPointer( display, CurrentTime ); } else Error();Mediante estas funciones podemos controlar las coordenadas del raton o sus pulsaciones aunque éstas caiga fuera de nuestra ventana, gestionando así completamente el ratón si nuestra aplicación lo requiere (por ejemplo para juegos controlados mediante el mouse).
Mediante la función que vamos a ver a continuación no es necesario estar dentro del bucle de eventos ni mirar dentro de las estructuras de eventos para conocer información sobre el estado del dispositivo apuntador:
Bool XQueryPointer( Display *display, Window window, Window *root_return, Window *child_return, int *root_x_return, int *root_y_return, int * win_x_return, int *win_y_return, unsigned it *mask_return);Esta función acepta como parámetros el display sobre el que realizar la consulta y la ventana que la realiza, y recibe en el resto de parámetros (usados para recoger los valores devueltos) toda la información requerida:
root_return = Ventana raíz del display indicado.
child_return = Ventana sobre la que está el puntero del ratón.
root_x_return = Coordenada X del ratón relativa a la ventana raíz (escritorio).
root_y_return = Coordenada Y del ratón relativa a la ventana raíz (escritorio).
win_x_return = Coordenada X del ratón relativa a la ventana del programa.
win_y_return = Coordenada Y del ratón relativa a la ventana del programa.
mask_return = estado actual de los botones del mouse.
Mediante la máscara mask_return podemos saber el estado de cualquiera de los botones del mouse utilizando las constantes definidas en <X11/X.h>. Mediante estas constantes no sólo podemos saber el estado de pulsación de los botones sino que también son capaces de informarnos de si durante dicha pulsación o movimiento está siendo pulsada Mayúsculas, Control, Alt, etc. Dichas constantes son:
ShiftMask ControlMask CapsLockMask Mod1Mask (tecla META o COMPOSE). Button1Mask Button2Mask etc.Un ejemplo del uso de esta función sería:
XQueryPointer( display, window, &rootwin, &childwin, &rx, &ry, &wx, &wy, &botones );
Esto nos daría toda la información sobre el estado del mouse en cualquier parte de la aplicación, y no sólo en el bucle de eventos (lo cual puede ampliar en mucho la sencillez de funcionamiento del programa), aunque esta función es de muy lento acceso (mucho más lenta que el simple acceso a través de los eventos).
Ejemplo: ralentizar a la mitad la velocidad de movimiento del mouse:
Display *display; int acc_num, acc_denom, thresh; XGetPointerControl( display, &acc_num, &acc_denom, &thresh ); acc_num = acc_num/2; XChangePointerControl( display, 1, 0, acc_num, acc_denom, thresh );Por último, una función muy útil para aquellos ordenadores en donde no se dispone de ratón es XWarpPointer, porque hace a nuestra aplicación capaz de mover el puntero del ratón mediante teclas a nuestra elección. Esta función tiene el siguiente prototipo:
XWarpPointer(display, src_w, dest_w, src_x, src_y, src_width, src_height, dest_x, dest_y) Display *display; Window src_w, dest_w; int src_x, src_y; unsigned int src_width, src_height; int dest_x, dest_y;Esta función mueve el puntero del ratón desde la ventana origen src_w, con dimensiones src_width y src_height a la ventana destino especificada y en las coordenadas destino que se le pasan en dest_x, dest_y. Si se desea, se puede especificar dest_x y dest_y relativamente a la ventana de destino y especificar como 0 src_w, src_x, src_y, src_width y src_height. Esto nos permitiría mover el cursor del ratón incondicionalmente por toda la pantalla (dst_w=rootwindow) de una manera absoluta. Si dest_w se especifica como 0 (None), entonces el movimiento es relativo (es decir, se mueve el cursor desde la posición actual una cantidad de pixels determinada por dst_x, dst_y).
La correcta utilización de esta función nos permitirá que en nuestras aplicaciones, por ejemplo, el puntero del mouse se pueda mover también mediante ciertas teclas, lo cual beneficiaría a los usuarios sin ratón.
La captura del teclado se realiza mediante la función XGrabKeyboard(), y se finaliza de varias posibles maneras: bien utilizando la función XUngrabKeyboard(), bien mediante XAllowEvents(), bien haciendo la ventana invisible o, finalmente, terminando la ejecución de nuestra aplicación (o, al menos, la comunicación de ésta con el servidor X).
int XGrabKeyboard(display, grab_window, owner_events, pointer_mode, keyboard_mode, time) Display *display; Window grab_window; Bool owner_events; int pointer_mode, keyboard_mode; Time time;El parámetro owner_events especifica, si está a 1, que los eventos que ya están en la cola de eventos (ocurridos durante el grab) se reporten de manera normal, mientras que a 0 se tratan como si hubiesen sido capturados. Pointer_mode normalmente valdrá GrabModeAsync, de forma que el procesador de eventos del puntero sean realizados normalmente. Keyboard_mode normalmente valdrá el mismo valor. Para time suele utilizarse el valor time devuelto por las funciones de evento.
El grab o captura se finaliza mediante la función int XUngrabKeyboard( display, time );.
El modo de uso de XAllowEvents() es el siguiente:
XAllowEvents(display, event_mode, time) Display *display; int event_mode; Time time;De estos parámetros, event_mode especifica el modo de permiso, entre AsyncPointer, SyncPointer, AsyncKeyboard, SyncKeyboard, ReplayPointer, ReplayKeyboard, AsyncBoth, or SyncBoth, es decir, permitiendo acceso síncrono o asíncrono al teclado, al ratón, o a ambos.
XQueryKeymap( Display *display, char keys_return[32]);
Esta función devuelve en el array de 32 elementos de 8 bytes (total = 32*8 = 256 elementos) el estado de las teclas del teclado, con un 1 si está pulsada o un 0 si no lo está. Simplemente tendremos que testear el bit que corresponda a cada tecla para saber su estado.
Mediante esta función podemos desarrollar otra función de gran utilidad, KeyPressed, la cual nos devolverá el estado de una tecla determinada según el código de tecla que deseemos testear:
int KeyPressed( Display *display, int keycode ) { char vector[32]; int bit, codigo; unsigned int byteindex, bitindex; byteindex = keycode / 8; bitindex = keycode & 7; XQueryKeymap( display, vector ); bit = ( 1 & (vector[byteindex] >> bitindex ) ); return(bit); }Esta función permitirá realizar en cualquier parte del programa sentencias como:
#define ESCAPE 9 if( KeyPressed( ESCAPE ) == 1 ) Salir();Si vamos a utilizar esta función de forma muy seguida es recomendable llamar una sola vez a XQueryKeymap() y obtener una sola vez el mapa de bits del teclado, y testear luego cada vez los bits deseados en lugar de realizar varias peticiones al servidor X.
#include <sys/time.h> #include <unistd.h> int gettimeofday(struct timeval *tv, struct timezone *tz);La estructura timezone está obsoleta y no nos interesa, de modo que al llamar a esta función pondremos el segundo parámetro a NULL. La estructura timeval, por contra, sí que es útil:
struct timeval { long tv_sec; /* segundos */ long tv_usec; /* microsegundos */ };Los valores numéricos de esta estructura cambian con el tiempo (tv_sec según los segundos y tv_usec según los microsegundos transcurridos). En el caso de tv_usec, va desde cero hasta 1.000.000 momento en que se resetea a cero e incrementa tv_sec. Hay definidas funciones de temporizacion usando estos parámetros (ver man gettimeofday).
#include <unistd.h> void usleep(unsigned long usec);Esta función suspende la ejecución del proceso llamante durante un número determinado de microsegundos. La pausa puede prolongarse ligeramente por cualquier actividad en el sistema o por el tiempo gastado procesando la llamada. Otras funciones relacionadas y que permiten el uso de temporizadores en Linux mediante señales (3 por aplicación) son setitimer() y getitimer() (ver páginas man).