Redrawing the window: redraw.c

Some of the screensavers we have seen suffer from a drawback: when the ``ask-password'' window of xscreensaver pops up, the region of the screeen below is deleted (usually, it simply becomes black). This can be seen for example for the module coloredsquares.c. The same erasure takes place when the screensaver is made running stand alone (in the root window): covering the root window with some other window and then uncovering the region, what the screensaver drawn is removed.

This happens because, each time part of a window (any window) is covered or made not visible for some other reason (for example, it is reduced to icon), its content is deleted. This is true for all windows, including the root window, which is the one we use for drawing. However, this effect is not visible for the windows of many applications people use. The reason is that, each time these windows are made visible, they are redraw. What happens undercover is:

  1. when the window is covered/iconized, the drawing it contains are removed
  2. when the window, or any of its parts, is made visible again, the X server sends a sort of message to the application to inform it that a part of the window has to be redraw again
  3. the application must be able to receive such message, understand it, and redraw all or part of the window, as necessary.

In X window, such messages are called events, perhaps because they are mainly used to report that some events happened. There are other kinds of events the X server deal with: request for selection, mouse movement, button and key press, etc. We are only interested in the event of the root window to become visible. First of all, we have to tell the X server that we want to receive such events, that is, the X server has to tell us that the root window is visible. This is done by using the command XSelectInput that application use to tell which event they are interested to. In our case, we want the event of the root window to become visible, and the corresponding instruction is:

  /* we want expose events */
  XSelectInput(dpy, root, ExposureMask);

Applications do not receive events immediately after they are generated. On the contrary, they have to perform a specific command to check whether there is some pending event. This is like to say that the X server sends the event like a snail mail, and the application has to check its mailbox to see whether there is any message. The command that can be used to check such mailbox is XCheckWindowEvent, which takes a display, a window, an event mask (in our case, ExposureMask), and the address of a variable of type XEvent. If there is any event that are relevant for this window, the function returns True, and the details of the event are returned in the variabile of type XEvent. Otherwise the function returns False.

We use these function to build the module redraw.c, which draws up to 100 random-colored squares, and is able to redraw them from scratch when the window is covered.

First of all, we need some variabile: three arrays for the position of the squares and their color, plus an integer variable that contains the number of squares drawn so far. These variables are needed (unlike what happens in coloredsquares.c) because we need a way for drawing the squares again whenever the window is erased. Moreover, we need to receive events, so we need a variable of type XEvent to pass to the function XCheckWindowEvent. The variable declaration of coloredsquares.c is modified by these new entries:

  int x[NRECTS], y[NRECTS];             /* the positions */
  int col[NRECTS];                      /* the colors */
  int lp=0;                             /* last point */

  XEvent e;                             /* the event */

When we draw a square, we have to remember its position and color. This can be done by replacing the commands of setting the color and drawing with:

      /* random position and color for the next point */

      /* set color and draw */
      XSetForeground(dpy, g, col[lp]);
      XFillRectangle (dpy, root, g, x[lp], y[lp], 50, 40);

      /* increase point number */
      if( lp==NRECTS ) {
        XClearWindow(dpy, root);

This is the beginning of the while cycle. This new module can be compiled and run: it draws 100 squares, then delete the whole window, and start again with a new set of 100 squares.

In order for the window to be drawn again when it is erased, we need two things: first, tell the X server that we want to know when the window is made visible; second, we have to check whether the X server has actually sent some ``Expose'' event to tell us that the window is now visible. The first is done with:

  /* we want expose events */
  XSelectInput(dpy, root, ExposureMask);

The poll for Expose events must be done inside the cycle, as the window may be covered at any time. In particular, if the Expose event is received, we have to redraw the squares. This can be done by calling the XCheckWindowEvent, and draw the squares if it returns True.

      /* check events */
      if( XCheckWindowEvent(dpy, root, ExposureMask, &e) ) {
        for(i=0; i<lp; i++) {
          XSetForeground(dpy, g, col[i]);
          XFillRectangle (dpy, root, g, x[i], y[i], 50, 40);

The for cycle simply draws all squares again (an integer variabile i is needed). This completes the module redraw.c. Note that XFlush is not needed any more, as the function XCheckWindowEvent flushes the graphics output.

Final note: since Expose event are dealt with a FIFO policy, it may be that the application receives a sequence of events before it has even started dealing with the first one. In this case, it is better to remove all expose events before drawing again (the function XCheckWindowEvent removes events in the queue if it finds any). Moreover, some events are nonmaskable, which means that they are received ny an application anyway, event if it have not requested them. It is always better to check the type of event anyway. The modified module is redraw-2.c.

Next: redrawing using the double buffer.