Found at: http://publish.ez.no/article/articleprint/95

Xlib tutorial 2 - Events and errors



Welcome back to the second Xlib tutorial. This time we will make our little program behave much more like a real application. We will learn how to handle events from the X server in order to process user input and resizing of our window. We will also set the title property of the window and how to handle errors.


The famous quit button
As a starting point for this tutorial I will use the object oriented version of the first tutorial. Only the new parts will be covered here, so if you have any questions about uncovered code take a look at tutorial 1.

The goal for today is to create a small application with a button that darkens on mouse over and quits the program when clicked. The application should also make the windowmanager show it's title in the titlebar.

The source code used in this tutorial can be downloaded with the link at the bottom of this page.

Setting an error handler


Whenever your program does something bad i.e calls drawing operations on a window that doesn't exist or you try to initialize a font with an attribute that is out of range the X server will generate a protocol error. Usually when this happens, and you haven't installed an error handler, the default error handler is called. The default error handler prints an explanatory message and then quits your program. This is quite a harsh reaction since such an error doesn't need to be fatal. In our program we won't extend the default behavior, only re implement it in order to get a feeling for how it works.

int errorHandler( Display *dpy, XErrorEvent *e )
{
    char errorText[1024];
    XGetErrorText( dpy, e->error_code, errorText, sizeof(errorText) );
    printf( "**********************************\n" );
    printf( "X Error: %s\n", errorText );
    printf( "**********************************\n" );

    exit( 1 );
}

All error handler functions must take pointers to a Display structure and a XErrorEvent as parameters and return an integer. If you choose to ignore the error you can return any integer value you like as it is ignored by the server. Now you may ask what the display structure is good for as your program already knows the connection to the server! Or does it? Since errors can occur at any time you never know what you program was doing prior to the error call, and if your program connects to two servers it is nice to know which one generated the error!

The XErrorEvent structure holds information about the error that just occurred and the values of it can be used to determine what to do with the error, can we ignore it or is it fatal? Since we want to mimic the default error handler we need some textual information about the error. This is done with the XGetErrorText function which takes the usual display structure, an error code, a string to hold the error and the size of this string as parameters. XGetErrorText queries the servers error database and finds the string assigned to the given error. It is always a good idea to use this function instead of writing your own error messages. This is because X11 extensions may define their own error codes and error messages which may be unknown to you when you write your program.
After fetching the error message, we print it and exit the program.

Painting a button

Our program is supposed to paint a button in the center of the program window. To ease this task we implement a function paintButton which in addition to the display structure, a pointer to the painter object we are using and the window to paint the button in takes a state parameter. The state parameter can be NORMAL, HOVER or PRESSED.
When the button is in PRESSED or HOVER state we fill it with a darker Grey than when it's normal. In addition to this we move the text 2 pixels down and to the right when the button is PRESSED. This creates a 3d effect.

enum buttonState { NORMAL, HOVER, PRESSED };
void paintButton( Display *dpy, Painter *p, Window button, buttonState state = NORMAL )
{
    XWindowAttributes attr;
    // fetch window size
    XGetWindowAttributes( dpy, button, &attr );
    if( state == NORMAL )
        p->setForeground( "gray" );
    else
        p->setForeground( "darkgray" );

    p->drawRectangle( button, 0, 0, attr.width, attr.height, true );

    p->setForeground( "red" );
    p->drawRectangle( button, 0, 0, attr.width -1, attr.height -1, false );

    // draw the button text
    const char *text = "Quit";

    // fetch the Font from the painter
    const FFont *font = p->font();
    // fetch the height of the font, and the width of the text.
    int fontHeight = font->fontHeight(); // max height above and below baseline
    int textWidth = font->textWidth( text );
    
    // center the text at the top of the window
    p->setForeground( "black" );
    if( state == PRESSED )
        p->drawText( button, 2 + (attr.width - textWidth)/2, 2 + (attr.height + fontHeight)/2, text );
    else
        p->drawText( button, (attr.width - textWidth)/2, (attr.height + fontHeight)/2, text );
        
}

I'm not going to explain exactly what we're doing here since it the function is quite straight forward, and this functionality was covered in the last tutorial. It is worth noting however that we use XGetWindowAttributes to fetch the size of the button before painting it. This is of course not optimal if you have many buttons to paint since asking the server for this information can be slow. The solution for a large scale application is to have a local cache of the button sizes which you update when there are changes in the layout.

Handling a resize

Allthough we do not yet know how to get a resize event, we do know what to do when it happens. This little piece of code does what we want: It fetches the size of the button and places it nicely in the middle of the available space.

void resize( Display *dpy, int mainW, int mainH, Window button )
{
    XWindowAttributes attr;
    // fetch button size
    XGetWindowAttributes( dpy, button, &attr );
    int buttonW = attr.width, buttonH = attr.height;
    
    // place button in middle
    XMoveWindow( dpy, button, (mainW - buttonW) /2, (mainH - buttonH)/2 );
}

The new function here is XMoveWindow which is one in a series of function to manipulate window attributes. XResizeWindow, XMoveResizeWindow and XConfigureWindow are the other functions in that series you will probably be using a lot. XMoveWindow takes two interesting parameters other than the usual display structure and the window you want to move; the x and the y coordinate you want to move your window to. This coordinate specifies where you want the top left corner of your window to be placed on the parent window. The coordinate is thus in the parent window coordinate space where 0,0 is the top left corner.

Telling X we want to receive events


Concept Event:An event is a way for the server to notify the client about changes in the program it is running. This could for example be that the mouse moves, the keyboard is pressed, that a part of your program has become visible and needs repainting or that your program window has been resized. Events will be queued up at the client until you decide to handle them or ignore them.
The relation between the event mask, the name of the event the event structure and the XEvent variable name for each event type can be a bit confusing in the beginning. This table might be helpfull if you get confused.

    XSelectInput( dpy, win, StructureNotifyMask | ExposureMask );
    XSelectInput( dpy, button, ButtonPressMask | ButtonReleaseMask 
                   | ExposureMask | EnterWindowMask | LeaveWindowMask );

Before we can start processing events we need to tell the server that we want to receive them. This is done with the XSelectInput function on a per window basis. If you want more than one event type to be propagated you can combine multiple masks with a logical or. For our application window we select the StructureNotifyMask and the ExposureMask. The StructureNotifyMask means that we will select ConfigureNotify events from this window. ConfigureNotify events are triggered for example by a window resize. The ExposureMask makes sure that we receive events when the main window or parts of it have become visible to the user.
For our button we also select ButtenPressMask, ButtonReleaseMask, EnterWindowMask and LeaveWindowMask. These are quite self explanatory as the button masks will allow mouse button (not keyboard) press and release events come through while the enter and leave window mask will generate events when the mouse moves into the button window or leaves the button window.

Running a real event loop


    XEvent event;
    bool running = true;
    bool buttonHover = false;
    while( running )
    {
        XNextEvent( dpy, &event );
        switch( event.type )
        {
	  // the event handling code comes here.
	}
    }

Next up we create an instance of the XEvent union. Whenever you get an event with it will be of this type, and you have to examine it's type variable in order to determine the correct event type. The running variable is used to keep our program in the event loop until the user pressed our button. We'll set the buttonHover boolean to true if the mouse pointer is over the button and should be drawn with the shaded style.
Next up we enter the eventloop and call XNextEvent. XNextEvent is the simplest of the functions that manipulate the event queue. It just takes the first event and puts it into the event structure, if there are none it will block until an event is received. This is perfect for the global event loop, why do something if there is nothing to do?


Events, events and more events


Now that we have got an eventloop it is a good idea to actually do something with the events we have received.

     case Expose:
     {
          paintButton( dpy, painter, button );
     }
     break;


An expose event happens when one of the windows we specified ExposureMask for is shown and needs repainting. Since we have only one window where this can happen (the button) we simply call paintButton when we receive an ExposeEvent.
If your program has several windows that can receive expose events you must only repaint the window that was actually exposed. This can be done by checking the window member of the xconfigure structure received in the event (event.xconfigure.window in our case).

    case ConfigureNotify:
    {
         resize( dpy, event.xconfigure.width, event.xconfigure.height, button );
    }


You'll receive a ConfigureNotify event when something has physically changed with one of your windows i.e. if it has been moved or resized. We know that any ConfigureNotify events we received are on the main window since this is the only window we have specified the StructureNotifyMask on. When we receive the event we do now know if it was triggered because of a move or a resize. In this example we don't care since the overhead of re-computing the position of the button is minimal.
It is not necessary to repaint after a move. The X server will trigger expose events if this is needed.

     case ButtonPress:
     {
         paintButton( dpy, painter, button, PRESSED );
     }
     break;

If the mouse button is pressed, repaint the button in the PRESSED state.

     case EnterNotify:
     case LeaveNotify:
     {
          buttonHover = (event.type == EnterNotify ) ? true : false;
	  paintButton( dpy, painter, button, buttonHover ? HOVER : NORMAL );
     }
     break;

We get EnterNotify and LeaveNotify evenents when the mouse enters or leaves the button.Since we want to know if the mouse is really over the button in case it is pressed we toggle the buttonHover boolean according to the event before repainting the button with the correct state.

     case ButtonRelease:
     {
         if( event.xbutton.window == button && buttonHover ) 
         {
             paintButton( dpy, painter, button, HOVER );
             XFlush( dpy );  
             running = false;
         }
     }

When the mouse button is released we check that the event really happened on the button and that we are int the buttonHover state. When this is done we paint repaint the button one final time before setting running to false which will quit the event loop. However, since we are quitting the program we want to make sure that the button is actually repainted before we quit. This is done by calling XFlush which sends all buffered calls to the server. How is it that we don't need to do this after the other painting calls? That is because XNextEvent implicitly flushes the buffer before fetching the next event.

Setting the WM_NAME property.

Until now our little program hasn't displayed anything in it's titlebar. The windowmanager will look for the WM_NAME property for our top level window and use this as the title. Setting properties is usually a pretty complicated business but luckily this is such a common action that a convenience function has been made. More information about properties and atoms will follow in the third tutorial.

    char *title = "Hello!";
    XTextProperty windowProp;
    if( XStringListToTextProperty( &title, 1, &windowProp ) == 0 )
    {
        printf( "XStringListToTextProperty ran out of memory" );
        exit( 1 );
    }
    XSetWMName( dpy, win, &windowProp );
    XFree( windowProp.value );

To set the WM_NAME property we choose to use the XSetWMName function on our main window. Unfortunately we can't just give XSetWMName a pointer to the string we want to use as title. It expects an XTextProperty structure.

We start out by defining the title we want and the XTextProperty structure we want to use. We then call the XStringListToTextProperty function which fills out the XTextProperty structure for us. Note that XStringListToTextProperty expects an array as the first argument and that we cheat by giving it the pointer the title pointer specifying that this is an array of length 1. In case we're out of memory we check the return value of XStringListToTextProperty and show the user and error before we quit.
The title has successfully been transferred to our XTextProperty structure and we call XSetWMName to set this property on the X server. Finally we clean up after ourselves by calling XFree on the value variable of our XTextProperty which XStringListToTextProperty allocated for us.

Final words

The latest version of this tutorial is part of the frekko project and can always be found here.

Hopefully this little tutorial has given you a feeling about how events work. Next time we will be back with a tutorial about atoms, the inner building blocks of X ;)

Attached files:

tutorial2.tar.gz

| Back to normal page view |