14.5 Events
Ein Event (Ereignis) ist eine Nachricht vom Server an den Client, womit der Client über eine bestimmte Aktion informiert wird. Meistens handelt es sich dabei um Mausaktionen oder Tastatureingaben. Im Prinzip wird über Events das ganze Programm gesteuert. Die Events sind, um es genau zu nehmen, auch wieder nichts anderes als eine (komplexe) Datenstruktur, die vom Server an den Client geschickt wird. Folgende Dinge müssen immer gemacht werden, damit die Events auch funktionieren:
|
Event-Maske setzen – Es macht keinen Sinn, wenn der Server auf jedes Event eines jeden Speicherobjektes reagiert – von der Überlastung des X-Servers dabei ganz zu schweigen. Welchen Sinn würde es denn ergeben, wenn der Server die Bewegung des Mauscursors, der sich über einem Button befindet, registriert und an den Client schickt? Daher verfügt X über eine Event-Maske, womit es möglich ist, von allen Events, die es gibt, nur eine gewisse Auswahl zu selektieren, die in der Anwendung auch wirklich benötigt wird. |
|
Event-Bearbeitungsschleife – Klar, dass bei regen Zeiten sehr viele Events auftreten, die nicht alle auf einmal abgearbeitet werden können. Daher verwendet man eine Event-Bearbeitungsschleife. In der Schleife holen Sie aus einem Event-Puffer ein Event nach dem anderen ab (sofern eines vorhanden ist) und werten dieses aus – mit einer anschließenden Reaktion, die Sie für das Event vorsehen. |
14.5.1 Event-Maske setzen
Der erste Schritt besteht also darin, eine Event-Maske zu setzen, die man für die Anwendung benötigt. Dazu wird der Befehl XSelectInput() wie folgt verwendet:
XSelectInput(display, win,
ButtonPressMask | ButtonReleaseMask | KeyPressMask |
KeyReleaseMask | EnterWindowMask | ExposureMask |
LeaveWindowMask );
XMapWindow (display, win);
Wie Sie daran erkennen können, werden mehrere Masken des dritten Parameters durch ein bitweises ODER verknüpft. Die ersten beiden Parameter sind so weit nichts Neues mehr. Des Weiteren sollte dieser Befehl noch vor der Funktion XMapWindow() aufgerufen werden, da beim Erzeugen des Fensters schon das Expose-Event verschickt wird. Welche Event-Maske(n) Sie hierfür verwenden sollten, hängt von Ihrer Anwendung ab. Hierzu ein Überblick zu einigen Event-Masken, die auch im Buch angewendet werden.
Tabelle 14.1
Häufig verwendete Event-Masken und deren Events
Event-Maske
|
Bedeutung
|
Events
|
ButtonPressMask
|
Maustaste im Fenster gedrückt
|
ButtonPress
|
ButtonReleaseMask
|
Maustaste im Fenster losgelassen
|
ButtonRelease
|
KeyPressMask
|
Eine Taste niedergedrückt
|
KeyPress
|
KeyReleaseMask
|
Eine Taste losgelassen
|
KeyRelease
|
EnterWindowMask
|
Mauscursor tritt ins Fenster ein
|
EnterNotify
|
LeaveWindowMask
|
Mauscursor verlässt das Fenster
|
LeaveNotify
|
ExposureMask
|
Anwendung muss etwas zeichnen
|
Expose
|
14.5.2 Event-Bearbeitungsschleife
Wenn Sie die Maske für die Events gesetzt haben, können Sie die Event-Bearbeitungsschleife erstellen, die von jetzt an bis zur Beendigung des Programms durchlaufen wird. Diese Schleife wird im Beispiel in der Hauptfunktion mit folgendem Konstrukt ausgeführt:
while (!quit) {
/* +++ Timer Anfang +++ */
gettimeofday (&rt, NULL);
td = time_diff ();
pause = delay (FRAME_LEN - td + pause);
st = rt;
/* +++ Timer Ende */
eventloop ();
}
Der Timer stellt »nur« eine Entlastung für die CPU dar, was bereits im Listing zuvor beschrieben wurde, und hat somit nicht direkt etwas mit der Event-Bearbeitungsschleife zu tun. Die Funktion eventloop(), die in dieser Schleife die Events bearbeitet, sieht so aus:
static void eventloop (void) {
XEvent xev;
int num_events;
XFlush (display);
num_events = XPending (display);
while ((num_events != 0)) {
num_events--;
XNextEvent (display, &xev);
process_event (xev);
}
}
Zuerst flushen Sie den Puffer der X-Aufrufe mit der Funktion XFlush(), der ja ansonsten nur entleert würde, wenn er voll ist. Anschließend überprüfen Sie mit der Funktion XPending(), ob überhaupt ein Event in der Event-Warteschlange vorhanden ist. Wenn nicht, wird der nächste Schleifenaufruf erst gar nicht ausgeführt, und die Anwendung kehrt zur main()-Funktion in die Event-Bearbeitungsschleife zurück. Befindet sich mindestens ein Event im Event-Puffer (num_events != 0), holen Sie dieses mit der Funktion XNextEvent() ab. Der erste Parameter ist wie immer der des Servers, der zweite ist eine Adresse vom Typ XEvent. XEvent ist wiederum eine Struktur, die das angekommene Event beinhaltet. Sie könnten dieses Event zwar jetzt schon abarbeiten, aber aus Übersichtlichkeitsgründen übergeben Sie XEvent xev an die Funktion process_event(), die das angekommene Event abarbeitet (dazu gleich mehr).
Sicherlich fällt Ihnen an der Funktion XNextEvent() auf, dass hier kein Parameter win für das Fenster verwendet wird. Aber in der Event-Struktur XEvent ist dazu natürlich eine Kennung enthalten, zu welchem Fenster welches Event gehört, für den Fall, wenn mehrere Fenster pro Anwendung geöffnet wurden.
14.5.3 Abarbeitung der Events
Zur Abarbeitung der einzelnen Events wurde hier die Funktion process_event() mit dem auftretenden Event (XEvent) aufgerufen:
static void process_event (XEvent report) {
...
switch (report.type) {
/* Events abfragen mit case-Statements */
}
}
14.5.4 Tastatur-Event
Ein Tastatur-Event wie KeyPress oder KeyRelease erhalten Sie gewöhnlich, wenn der Window-Manager den Tastaturfokus an ein Fenster übergeben hat. Meistens ist dies der Fall, wenn sich der Mauszeiger darin befindet. Wenn ein KeyPress-Event oder ein KeyRelease-Event aufgetreten ist, können Sie mit einer Behandlungsroutine das Zeichen lesen. Welches Zeichen das denn nun war, kann z. B. mit der Funktion XLookupString() ermittelt werden:
XLookupString (&report.xkey, buf, bufsz, &key, NULL);
Die Funktion übersetzt ein Tastatur-Event, das Sie zuvor mit der Funktion XLookupKeysym() ermittelt haben, in einen String. Der erste Parameter ist das Event, im zweiten wird anschließend der String mit maximal bufsz (dritter Parameter) Bytes beschrieben. Der vierte Parameter enthält den Tastaturcode, den Sie mit XLookupKeysym() ermittelt haben. Der letzte Parameter wird hier nicht benötigt.
Solange es sich bei der Taste um ein ASCII-Zeichen wie Buchstaben- und Zahlentasten oder die Zeichen: = – \ + (DEL) [ ] (ESC) / ' ` ; (˙_) . , (CR) (LF) (ć___) handelt, ist man mit XLookupString() gut bedient ((ş_) wird auch berücksichtigt). Wenn es sich allerdings um kein ASCII-Zeichen handelt, ist der String buf von XLookupString() leer. Dies ist z. B. beim Betätigen der Tasten (Ş), (Ctrl), (F1), (F2) usw. der Fall. In diesem Fall kann man den Tastaturcode, der von der Funktion XLookupKeysym() zurückgegeben wird, mit der Funktion XKeysymToString() in einen String konvertieren. Den Tastaturcode selbst können Sie sich mit der Funktion XKeysymToKeycode() ermitteln lassen. Hierzu der Vorgang beim Drücken bzw. Loslassen einer Taste:
...
KeySym key;
char buf[128] = { 0 };
int bufsz = 128;
...
switch (report.type) {
case KeyPress:
key = XLookupKeysym (&report.xkey, 0);
XLookupString (&report.xkey, buf, bufsz, &key, NULL);
if (strlen (buf) != 0 && buf[0] != '\n' && buf[0] != '\r')
printf ("Taste'%s' (Tasturcode: %d) wurde gedrückt\n",
buf, XKeysymToKeycode (display, key));
else
printf ("Taste '%s' (Tasturcode: %d) wurde gedrückt\n",
XKeysymToString (key), XKeysymToKeycode (display, key));
break;
case KeyRelease:
printf ("Taste wieder losgelassen\n");
break;
...
14.5.5 Mausbutton-Event
Wenn eine Maustaste innerhalb eines Fensters gedrückt bzw. losgelassen wurde, treffen die Events ButtonPress bzw. ButtonRelease ein. Wenn das Fenster bei gedrückter Maustaste verlassen und außerhalb losgelassen wird, wird dennoch das Event ButtonRelease gesendet. Wollen Sie wissen, welche Maustaste gedrückt bzw. losgelassen wurde, müssen Sie report.xbutton.button (wobei report vom Typ XEvent ist) auswerten. Darin befindet sich ein Integerwert mit folgenden Bedeutungen:
Tabelle 14.2
Integerwerte für das Drücken eines bestimmten Mausbuttons
Mausaktion
|
Integerwert
|
linke Maustaste
|
1
|
rechte Maustaste
|
3
|
mittlere Maustaste
|
2
|
Scrollrad nach oben
|
4
|
Scrollrad nach unten
|
5
|
Wollen Sie hingegen überprüfen, an welcher Stelle die Maustaste gedrückt bzw. losgelassen wurde, müssen Sie für die X-Position report.xbutton.x und für die Y-Position report.xbutton.y auswerten. Dabei wird jeweils ein Wert relativ von der linken oberen Seite des Fensters zurückgeliefert.
14.5.6 Expose-Event
Wenn das Fenster erzeugt, verkleinert, vergrößert, minimiert oder von einem anderen Fenster überdeckt wurde, wird das Expose-Event mitgeschickt. Dieses Event zeigt der Anwendung an, dass der Fensterinhalt neu gezeichnet werden muss. Beim Verschieben hingegen wird kein Expose-Event gesendet, da hierbei der Window-Manager dafür sorgt, dass ein Fenster wieder hergestellt wird.
Hinweis Das Expose-Event ist weitaus anspruchsvoller, als in diesem Buch dargestellt wird, denn häufig muss nur ein rechteckiger Detailausschnitt des Fensters neu gezeichnet werden. In diesem Fall wird eine Folge von Expose-Events mit einer Variablen myevent.xexpose.count > 0 erzeugt.
|
14.5.7 EnterWindowMask – LeaveWindowMask
Sobald der Cursor das Fenster betritt, wird das Event EnterNotify gesendet, und beim Verlassen des Cursors das Event LeaveNotify.
14.5.8 Listing zu den Events
Alle Events, die Ihnen im Beispiel hier erläutert wurden, sollen jetzt in der Praxis getestet werden. Das jeweilige Event, das eintrifft, wird auf der Konsole ausgegeben, von wo die X-Anwendung gestartet wurde.
/* events.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <X11/keysym.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "my_delay.h"
#define WINDOW_WIDTH 400
#define WINDOW_HEIGHT 300
#define MAUS_LINKS 1
#define MAUS_RECHTS 3
#define MAUS_MITTE 2
#define SCROLL_UP 4
#define SCROLL_DOWN 5
/* Funktionsprototypen */
static int create_window (void);
static void process_event (XEvent report);
static void eventloop (void);
static void close_window (void);
extern int delay (int i);
extern int time_diff (void);
/* Globale Variablen */
static unsigned int width, height;
static Display *display;
static int screen;
static int depth;
static Window win;
/* Funktion erzeugt ein Fenster */
static int create_window (void) {
char *window_name = "Event-Behandlung";
char *icon_name = "window";
static XSizeHints size_hints;
Window rootwin;
width = WINDOW_WIDTH;
height = WINDOW_HEIGHT;
/* X-Sitzung öffnen */
display = XOpenDisplay (NULL);
/* Fehlerüberprüfung */
if (display == NULL) {
printf ("Verbindung zum X-Server fehlgeschlagen?!?\n");
exit(EXIT_FAILURE);
}
/* Standardbildschirm eines Displays */
screen = XDefaultScreen (display);
/* Standardtiefe des Screens - Anzahl der Ebenen */
depth = XDefaultDepth (display, screen);
rootwin = RootWindow (display, screen);
win = XCreateSimpleWindow ( display, rootwin, 10, 10,
width, height, 5,
BlackPixel (display, screen),
WhitePixel (display, screen));
size_hints.flags = PSize | PMinSize | PMaxSize;
size_hints.min_width = width;
size_hints.max_width = width;
size_hints.min_height = height;
size_hints.max_height = height;
XSetStandardProperties ( display, win, window_name, icon_name,
None, 0, 0, &size_hints );
XSelectInput (display, win,
ButtonPressMask | ButtonReleaseMask | KeyPressMask |
KeyReleaseMask | EnterWindowMask | LeaveWindowMask |
ExposureMask );
XMapWindow (display, win);
return 1;
}
static void close_window (void) {
XDestroyWindow (display, win);
XCloseDisplay (display);
}
static void eventloop (void) {
XEvent xev;
int num_events;
XFlush (display);
num_events = XPending (display);
while ((num_events != 0)) {
num_events--;
XNextEvent (display, &xev);
process_event (xev);
}
}
static void process_event (XEvent report) {
KeySym key;
char buf[128] = { 0 };
int bufsz = 128;
char position[50];
switch (report.type) {
case KeyPress:
key = XLookupKeysym (&report.xkey, 0);
XLookupString (&report.xkey, buf, bufsz, &key, NULL);
if (strlen (buf) != 0 && buf[0] != '\n' && buf[0] != '\r')
printf ("Taste'%s' (Tasturcode: %d) wurde gedrückt\n",
buf, XKeysymToKeycode (display, key));
else
printf ("Taste '%s' (Tasturcode: %d) wurde gedrückt\n",
XKeysymToString (key), XKeysymToKeycode (display, key));
break;
case KeyRelease:
printf ("Taste wieder losgelassen\n");
break;
case ButtonPress:
switch (report.xbutton.button) {
case MAUS_LINKS:
sprintf (position, "(X,Y)=(%d,%d).", report.xmotion.x,
report.xmotion.y);
printf ("Linke Maustaste wurde gedrückt an Pos.: %s\n",
position);
break;
case MAUS_RECHTS:
sprintf (position, "(X,Y)=(%d,%d).",
report.xmotion.x, report.xmotion.y);
printf ("Rechte Maustaste wurde gedrückt an Pos.: %s\n",
position);
break;
case MAUS_MITTE:
sprintf (position, "(X,Y)=(%d,%d).",
report.xmotion.x, report.xmotion.y);
printf ("Mittlere Maustaste gedrückt an Pos.: %s\n",
position);
break;
case SCROLL_UP:
sprintf (position, "(X,Y)=(%d,%d).",
report.xmotion.x, report.xmotion.y);
printf ("Mausrad nach oben gescrollt an Pos.: %s\n",
position);
break;
case SCROLL_DOWN:
sprintf (position, "(X,Y)=(%d,%d).",
report.xmotion.x, report.xmotion.y);
printf ("Mausrad nach unten gescrollt an Pos.: %s\n",
position);
break;
}
break;
case ButtonRelease:
switch (report.xbutton.button) {
case MAUS_LINKS:
sprintf (position, "(X,Y)=(%d,%d).",
report.xmotion.x, report.xmotion.y);
printf ("Linke Maustaste losgelassen an Pos.: %s\n",
position);
break;
case MAUS_RECHTS:
sprintf (position, "(X,Y)=(%d,%d).",
report.xmotion.x, report.xmotion.y);
printf ("Rechte Maustaste losgelassen an Pos.: %s\n",
position);
break;
case MAUS_MITTE:
sprintf (position, "(X,Y)=(%d,%d).",
report.xmotion.x, report.xmotion.y);
printf ("Mittlere Maustaste losgelassen an Pos.: %s\n",
position);
break;
}
break;
case EnterNotify:
XSetInputFocus (display, win, RevertToParent, CurrentTime);
printf ("Cursor 'betritt' das Fenster\n");
break;
case LeaveNotify:
XSetInputFocus (display, win, RevertToParent, CurrentTime);
printf ("Cursor 'verlässt' das Fenster\n");
break;
case Expose:
printf("ExposeEvent: Bild sollte neu gezeichnet werden\n");
break;
default:
break;
}
}
int main (int argc, char **argv) {
int quit = 0;
int pause = 0;
int td;
create_window ();
gettimeofday (&st, NULL);
while (!quit) {
/* +++ Timer Anfang +++ */
gettimeofday (&rt, NULL);
td = time_diff ();
pause = delay (FRAME_LEN - td + pause);
st = rt;
/* +++ Timer Ende */
eventloop ();
}
close_window ();
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o events events.c -L/usr/X11R6/lib -lX11
$ ./events
---[nach Aktionen auf dem Fenster die Ausgabe der Konsole]---
Mausrad nach oben gescrollt an Pos.: (X,Y)=(182,169).
Mausrad nach unten gescrollt an Pos.: (X,Y)=(182,169).
Cursor 'verlässt' das Fenster
Cursor 'betritt' das Fenster
Cursor 'verlässt' das Fenster
ExposeEvent: Bild sollte neu gezeichnet werden
Cursor 'betritt' das Fenster
Rechte Maustaste wurde gedrückt an Pos.: (X,Y)=(368,276).
Linke Maustaste wurde gedrückt an Pos.: (X,Y)=(368,276).
Linke Maustaste losgelassen an Pos.: (X,Y)=(368,276).
Mausrad nach unten gescrollt an Pos.: (X,Y)=(368,276).
Cursor 'verlässt' das Fenster
Taste'e' (Tasturcode: 26) wurde gedrückt
Taste wieder losgelassen
Taste 'F2' (Tasturcode: 68) wurde gedrückt
Taste wieder losgelassen
Taste 'F4' (Tasturcode: 70) wurde gedrückt
Taste wieder losgelassen
Taste 'Home' (Tasturcode: 97) wurde gedrückt
...
|