Programmieren mit der Xlib

Dieser Text befindet sich in der neusten Version auf http://www.eggdrop.ch/texts/xlib/index.htm.

04.01.04: Version 0.1

Inhalt

1. Einleitung
1.1. Was ist die Xlib?
1.2. Voraussetzungen
2. Das erste Fenster
2.1. Allgemeines zu Xlib-Programmen
2.2. Verbindung mit dem X Server herstellen
2.3. Ein Fenster erstellen
2.4. Das Fenster benennen
2.5. Das Fenster anzeigen
2.6. Der Code
3. Events
3.1. Die Event-Schleife
3.2. Events verwenden
3.3. Das Fenster mit dem Windowmanager schliessen
A. Links

1. Einleitung

1.1. Was ist die Xlib?

Die Xlib (X Library) ist eine Library (Bibliothek), welche Funktionen zum Zeichnen von Fenstern bereitstellt. Mit ihr lässt sich beispielsweise ein Windowmanager, also ein Programm, welches mehrere Fenster verwaltet, aber auch ein Bildbetrachter programmieren. In diesem Text werde ich einige Funktionen der Xlib möglichst verständlich und mit Beispielen beschreiben.

1.2. Voraussetzungen

Um mit der Xlib programmieren zu können brauchst du folgendes:

Ausserdem benötigst du gute C Kentnisse, ohne die es unmöglich ist, diesen Text zu verstehen. Eine kleine Einführung gibt es auf [4] und einen vollständigen Kurs auf [5].

2. Das erste Fenster

2.1. Allgemeines zu Xlib-Programmen

Ein Xlib-Programm braucht folgendes Headerfile:

#include <X11/Xlib.h>

In diesem Abschnitt beziehe ich mich auf den Compiler gcc. Wir müssen die Library mit -lX11 dazulinken. Zusätzlich müssen wir den Path (Pfad) den Library angeben, weil sie gcc in der meisten Fällen nicht finden wird, z.B.: -L/usr/X11R6/lib und den Path zu den Include-Dateien mit -I/usr/X11R6/include. Will ich nun z.B. die Datei test.c kompilieren, benutze ich folgenden Compileraufruf:

gcc test.c -o test -L/usr/X11R6/lib -lX11 -I/usr/X11R6/include

2.2. Verbindung mit dem X Server herstellen

Bevor wir etwas auf den Bildschirm zeichnen können, müssen wir die Verbindung zu einem Display (Bildschirm) herstellen. Das geschieht mit der Funktion XOpenDisplay(). Die Funktion erwartet als Argument ein char * mit dem Displaynamen. In den meisten Fällen wollen wir aber das Fenster auf dem aktuellen (aktiven) Display erzeugen. Dazu übergeben wir einfach einen NULL-Zeiger. Die Funktion liefert einen Zeiger auf den Display (Display *), oder NULL bei einem Fehler. Wir verwenden folgenden Code:

Display *dpy;

if ((dpy = XOpenDisplay(NULL)) == NULL)
{
        printf("Unable to open display\n");
        return 1;
}

2.3. Ein Fenster erstellen

Nun ist es Zeit, ein Fenster zu erstellen, was man mit der Funktion XCreateSimpleWindow() machen kann. Die Funktion gibt ein Window zurück und hat 9 Parameter:

Verwenden wir also folgenden Funktionsaufruf, um das Fenster zu erstellen:

Window win;

win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, \
                400, 300, BlackPixel(dpy, DefaultScreen(dpy)), 0, \
                WhitePixel(dpy, DefaultScreen(dpy)));

Wir sehen hier die zwei Makros BlackPixel() und WhitePixel(), welche uns die Farbe schwarz bzw. weiss zurückliefern. Diese zwei Farben sind die einzigen, die uns X garantiert. Schliesslich könnte es ja sein, dass der Benutzer vor einem uralten monochromen Bildschirm sitzt...

2.4. Das Fenster benennen

Jetzt sollten wir unserem Fenster noch einen Namen geben. Weil wir in diesem Text nur Testprogramme verwenden werden, empfiehlt sich der Name "Test-Programm". Und zwar setzen wir zuerst den Text der Titelzeile, was ich am liebsten mit einem #define mache:

#define WIN_TITLE "Test-Programm"

XStoreName(dpy, win, WIN_TITLE);

Und dann noch den Namen, mit welchem der Windowmanager das Fenster unterscheidet. Dabei gibt es einen Namen für das gesamte Programm (wir verwenden "TEST") und einen Namen für das Fenster (wir verwenden "test"). Wie schon erwähnt, werden diese Namen nicht von X benötigt, sondern dienen nur für den Windowmanager. Nicht zu Vergessen ist die Header-Datei X11/Xutil.h, die für diese Funktion eingebunden werden muss. Um nun diesen Namen zu setzen, braucht es folgenden Code:

#include <X11/Xutil.h>

#define WIN_NAME "TEST"
#define WIN_CLASS "test"

XClassHint class_hint;

class_hint.res_name = WIN_NAME;
class_hint.res_class = WIN_CLASS;

XSetClassHint(dpy, win, &class_hint);

Wir können diese Namen dann später z.B. mit xprop abrufen.

2.5. Das Fenster anzeigen

Nun müssen wir das Fenster nur noch anzeigen, was nicht sonderlich schwierig ist:

XMapWindow(dpy, win);

Jedoch wird das Fenster nicht auf Anhieb gezeichnet, wir müssen X mitteilen, dass der Bildschirm aktualisiert ("geflusht") werden muss:

XFlush(dpy);

2.6. Der Code

Nun ist es Zeit, den gesamten Code zusammenzustellen. Dazu verwende ich eine etwas unsaubere Methode, damit sich das Fenster nicht sofort schliesst: Ich lasse es mit sleep(10); zehn Sekunden lang anzeigen. Später werden wir eine Schleife verwenden, die ausgeführt wird, bis der Benutzer das Fenster schliesst. Momentan lässt sich das Fenster nicht durch den Windowmanager schliessen. Hier ist also der Code:

#include <stdio.h>
#include <unistd.h> /* for sleep() */
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#define WIN_TITLE "Test-Programm"
#define WIN_NAME "TEST"
#define WIN_CLASS "test"

int main(int argc, char *argv[])
{
        Display *dpy;
        Window win;
        XClassHint class_hint;
        
        if ((dpy = XOpenDisplay(NULL)) == NULL)
        {
                printf("Unable to open display\n");
                return 1;
        }

        win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, \
                        400, 300, BlackPixel(dpy, DefaultScreen(dpy)), 0, \
                        WhitePixel(dpy, DefaultScreen(dpy)));
        
        XStoreName(dpy, win, WIN_TITLE);
        
        class_hint.res_name = WIN_NAME;
        class_hint.res_class = WIN_CLASS;
        XSetClassHint(dpy, win, &class_hint);

        XMapWindow(dpy, win);

        XFlush(dpy);
        
        sleep(10);
        
        return 0;
}

Nachdem wir das Programm kompiliert haben, können wir es ausführen und wir bekommen ein weisses X-Fenster, mit der in WIN_TITLE definierten Titelzeile. Wir können nun in einer Konsole xprop eingeben und dann dieses Fenster selektieren und erhalten so alle eingestellten Informationen:

[tom@ws tom]$ xprop
_WIN_STATE(CARDINAL) = 0
_WIN_WORKSPACE(CARDINAL) = 0
WM_STATE(WM_STATE):
                window state: Normal
                icon window: 0x0
WM_CLASS(STRING) = "TEST", "test"
WM_NAME(STRING) = "Test-Programm"
[tom@ws tom]$ 

3. Events

3.1. Die Event-Schleife

Wenn wir nun abfragen wollen, ob eine Taste gedrückt wurde, die Maus bewegt wurde oder sonst irgendetwas passiert ist, benötigen wir eine Schleife, welche folgendermassen aussieht:

XEvent event;
int done=0;

while (!done)
{
        while(XPending(dpy) > 0)
        {
                XNextEvent(dpy, &event);
                switch(event.type)
                {
                        /* Event1: */
                                /* befehle */
                        /* Event2: */
                                /* befehle */
                        /* ... */
                        default:
                                break;
                }
        }
        usleep(10000); /* damit der Prozessor nicht ausgelastet ist */
}

Alle möglichen Events kann man in der Datei /usr/X11R6/include/X11/X.h beim Abschnitt Event names nachlesen. Bevor wir aber Events verwenden können, müssen wir dem X-Server angeben, über welche Events er uns überhaupt informieren soll. Das geschieht mit der Funktion XSelectInput(), die folgendermassen aufgerufen wird:

XSelectInput(dpy, win, EventMask1 | EventMask2 | ...);

Die EventMasks finden sich ebenfalls in der Datei /usr/X11R6/include/X11/X.h. Hier eine Tabelle einiger dieser EventMasks mit den dazugehörigen Events (was die Struktur bedeutet, erfährst du im nächsten Abschnitt):

Event EventMask Struktur Beschreibung
Expose ExposureMask XExposeEvent Teilt uns mit, dass wir das Fenster (oder einen Teil davon) neu zeichnen müssen, zum Beispiel, weil vorher ein anderes Fenster dieses überdeckte.
KeyPress KeyPressMask XKeyEvent Eine Taste wurde gedrückt.
KeyRelease KeyReleaseMask XKeyEvent Eine Taste wurde losgelassen.
ButtonPress ButtonPressMask XButtonEvent Eine Maustaste wurde gedrückt.
ButtonRelease ButtonReleaseMask XButtonEvent Eine Maustaste wurde losgelassen.
MotionNotify PointerMotionMask XMotionEvent Die Maus wurde bewegt.
ConfigureNotify StructureNotifyMask XConfigureEvent Die Grösse des Fensters wurde verändert.

3.2. Events verwenden

Um mit diesen Events etwas anfangen zu können, müssen wir meistens genauere Informationen haben, zum Beispiel welche Taste genau gedrückt wurde oder wo sich die Maus jetzt befindet. Diese Informationen sind in der XEvent-Struktur gespeichert. Am besten ist es, die Headerdateien zu Hilfe ziehen. Für die Events empfiehlt sich die Datei /usr/X11R6/include/X11/Xlib.h. Suchen wir dort nach _Xevent (mit Unterstrich am Anfang), kommen wir an den Anfang der XEvent-Struktur. Als Beispiel möchten wir schauen, welche Taste gedrückt wurde. Wir setzen also anstelle der sleep()-Anweisung XSelectInput() ein:

XSelectInput(dpy, win, KeyPressMask);

Anschliessend setzen wir die oben gezeigte Event-Schleife ein und können schon mal bei dem switch-Konstrukt folgendes hinschreiben:

case KeyPress:
        printf("Eine Taste wurde gedrückt!\n");
        break;

Führen wir nun das Programm aus und drücken eine Taste, erscheint die obere Meldung Nun schauen wir uns die XEvent-Struktur in der Headerdatei an. Wir sehen hier folgende interessante Zeile:

XKeyEvent xkey;

Daraus wissen wir, dass die nötigen Informationen irgendwo unter event.xkey angesprochen werden können. Wie sie heissen, können wir jetzt aus der XKeyEvent-Struktur ermitteln, die sich ebenfalls in dieser Headerdatei befindet. Suchen wir nach XKeyEvent, haben wir schon die Struktur gefunden:

typedef struct {
        int type;               /* of event */
        unsigned long serial;   /* # of last request processed by server */
        Bool send_event;        /* true if this came from a SendEvent request */
        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;

Wir können hier erkennen, dass die Taste ziemlich sicher unter keycode gespeichert sein muss. Wir müssen also event.xkey.keycode auf die Taste überprüfen. Um den Keycode einer Taste zu bekommen, verwenden wir die Funktion XKeysymToKeycode(), was etwa so geht:

#include <X11/keysym.h>

case KeyPress:
        if (event.xkey.keycode == XKeysymToKeycode(dpy, Taste))
        {
                /* die Taste wurde gedrückt */
        }
        break;

Die Tasten finden wir natürlich in einer Headerdatei. Es ist die Datei /usr/X11R6/include/X11/keysymdef.h, die von der von uns eingebunden Datei /usr/X11R6/include/X11/keysym.h eingebunden wird. Ersetzten wir nun die Taste durch XK_Return, können wir beispielsweise prüfen, ob die Enter-Taste gedrückt wurde.

Hier noch ein Beispiel zum prüfen, welche Maustaste gedrückt wurde (nicht zu vergessen ist die ButtonPressMask bei XSelectInput()):

case ButtonPress:
        switch(event.xbutton.button)
        {
                case 1:
                        /* linke Maustaste */
                        break;
                case 2:
                        /* mittlere Maustaste bzw. Scrollrad-Knopf */
                        break;
                case 3:
                        /* rechte Maustate */
                        break;
                case 4:
                        /* Scrollrad: nach oben */
                        break;
                case 5:
                        /* Scrollrad: nach unten */
                        break;
 
        }
        break;

3.3. Das Fenster mit dem Windowmanager schliessen

Wollen wir das Fenster mit dem "X"-Knopf eines Windowmanagers schliessen, dann setzen wir folgenden Code vor XMapWindow() (die Deklaration kommt natürlich wie immer an den Anfang):

Atom delete_atom;

delete_atom = XInternAtom(dpy, "WM_DELETE_WINDOW", True);

if (delete_atom)
        XSetWMProtocols(dpy, win, &delete_atom, 1);

Mit dem if-Konstrukt überprüfen wir, ob diese Funktion überhaupt unterstützt wird, also ob ein Windowmanager läuft. Nun müssen wir noch in der Event-Schleife abfangen können, wann der Benutzer den Knopf betätigt hat:

case ClientMessage:
        if (event.xclient.data.l[0] == delete_atom)
                done = 1;
        break;

A. Links


Zurück zu www.eggdrop.ch | Back to www.eggdrop.ch

Letzte Änderung: 04.01.04

Copyright (C) 2003, 2004 by Thomas "tom" S. <tom at eggdrop.ch>
Valid HTML 4.0!

Dieser Text gehört zu www.eggdrop.ch und ist urheberrechtlich geschützt. Das Weitergeben und Kopieren dieses Textes ist erwünscht unter folgenden Bedingungen: 1. Der Text muss so sein wie er ist und darf in keiner Weise verändert oder in die eigene Homepage integriert werden. 2. Es muss ein deutlicher Hinweis auf die Herkunft (www.eggdrop.ch) vorhanden sein. 3. Der Text sollte wenigstens alle 3 Monate auf Updates überprüft werden (siehe die Versionsnummer ganz am Anfang des Textes!)