4

I got this XWindows "hello, world" off the net. I have behavior I don't understand in a more complex program, but the simple program here also displays it:

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
   Display *d;
   Window w;
   XEvent e;
   const char *msg = "Hello, World!";
   int s;
   int x;

   d = XOpenDisplay(NULL);
   if (d == NULL) {
      fprintf(stderr, "Cannot open display\n");
      exit(1);
   }

   s = DefaultScreen(d);
   w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1,
                           BlackPixel(d, s), WhitePixel(d, s));
   XSelectInput(d, w, ExposureMask | KeyPressMask);
   XMapWindow(d, w);

   XDrawString(d, w, DefaultGC(d, s), 10, 50, msg, strlen(msg));

   //XFlush(d);

   while (1) {
      XNextEvent(d, &e);
      if (e.type == Expose) {
//         XDrawString(d, w, DefaultGC(d, s), 10, 50, msg, strlen(msg));
      }
      if (e.type == KeyPress) break;
   }

   XCloseDisplay(d);
   return 0;
}

So the first XDrawString() call does not actually write to the window, but uncommenting the one in the expose event does. I find this behavior confusing. At the time of the first XDrawString(), the display, screen and window are all set up. Yet that does not draw and the one that appears in the event handler does. I also tried XFlush()'ing the queue, it makes no difference.

This effect had bearing on the (much) more complex code I am working on, which is placing characters on screen. I place them at the desired location in a pixmap that backs the screen, and then I do the same draw to the real screen to keep it in sync. The expose handler will copy the pixmap to the screen, which updates it, but that expose event won't happen until later, and besides, copying the entire pixmap back to the screen is more expensive than placing a single character.

I suspect I need to follow the draw to buffer with an operation flagging the rectangle under the character as invalid (this would be the MS windows way) and letting the event handler take care of it, but I would like to understand what goes on inside X11 to make this happen.

melpomene
  • 84,125
  • 8
  • 85
  • 148
Scott Franco
  • 481
  • 2
  • 15

4 Answers4

6

X servers are not required to provide backing store by themselves, so Expose events are sent when the window needs to be drawn. This is the same functionality that happens in other environments also. In the beginning you can draw, but the X server may not have set up the window properly yet and will later report to you when it’s ready to have contents drawn on to screen. The drawing is kind of directly on screen, not to a backing buffer, so it has to be done every time the window is brought in front.

As for requesting a redraw yourself you can use XSendEvent to send an Expose event yourself and then X will relay it to your event loop. So it’s practically exactly the same as Windows does.

Sami Kuhmonen
  • 30,146
  • 9
  • 61
  • 74
  • Yes, I get what you are saying and thanks for that. The send yourself an expose event works, but would result in the entire buffer/screen being repainted. Since the routine is printing one character at a time, I expect that performance of that to suck. The traditional MS windows method is to invalidate just the bounding box for the character, so that the expose event only copies that part of the buffer, plus it optimizes by combining multiple events and even finding the greatest enclosing rectangle. This would apply, if for example I draw multiple characters before getting the next event. – Scott Franco Jun 05 '19 at 06:42
  • This question covers this a bit, but does not give an answer: https://stackoverflow.com/questions/17027993/equivalent-of-invalidate-rect-wm-paint-in-x11 – Scott Franco Jun 05 '19 at 06:47
  • @ScottFranco It doesn’t result in the whole window being painted, you can send the rectangle to paint and only paint that part in the event handler. So you’re in control. But it’s definitely not as easy as just drawing things on screen and letting some underlying system draw the necessary parts. – Sami Kuhmonen Jun 05 '19 at 06:52
  • There is a way in X11 to pass a rectangle to draw to expose, or were you talking about end-around with a global? – Scott Franco Jun 05 '19 at 06:57
  • 1
    @ScottFranco Yes, the Expose event carries information about what area needs to be painted. See here https://tronche.com/gui/x/xlib/events/exposure/expose.html so you can send that information yourself as well as use it in the event handler to only paint part of the window – Sami Kuhmonen Jun 05 '19 at 07:27
  • Sami I saw that later and incorporated it into the example below. Thank you. – Scott Franco Jun 05 '19 at 16:42
2

Just to add a bit to the good reply from Sami.

Thinking deeply, the way X11 works is the only correct one when multiple windows, which can overlap each other, are used (not counting a lot of other things). This is why this mechanism (invalidate and expose | draw | paint) is used by many frameworks (X11, windows, gtk, qt...).

It's all about optimizing the drawing. You should not draw blindly, because your drawing could be discarded; may be your window is invisible, may be the user is moving the window without repainting its content, or whatever.

Instead, X11 (or other toolkit/framework) knows very well when data has to be displayed, and can be displayed. In that situation, X11 tells you to redraw (via an expose event). When X11 asks to repaint, your drawing operations will not be discarded.

On the other hands, a good X11 program should be prepared to repaint when needed: it will happen often, if the user drags windows around, or changes virtual desktops, iconifies the application, and so on.

Combine the two aspects: the program must draw when asked to, and it will be asked for sure just after having created its first window. So, it is perfect to draw only in the expose event.

This basic mechanism can be improved by backing store: X11 can cache the drawing data, and use them later instead of asking the program again and again and again for the same data. Sometimes backing store is faster, but sometimes it only wastes memory.

  • I agree with you mostly. Windows (as pointed out elsewhere) does not follow the X11 model. If you have valid data structures, and the window is onscreen, you are going to be able to draw on it. The place where the X11 model runs into trouble is with continuous animator programs, where drawing operations are pushed either continuously to the display or via a periodic timer. I see from elsewhere that timers, so essential for animation, are also left out of the X11 specification, IE, you cannot treat timers as an event. – Scott Franco Jun 05 '19 at 16:38
  • @ScottFranco I agree partly. Windows timers are quite crappy, but they have the advantage of letting you know *when* the GUI is ready. X11 does not have them, sometimes they would be handy, but all that fits in client/server paradigm which is not very suitable for real time animation. Apart from that, X11 is far more powerful than windows, especially if you consider its age! – linuxfan says Reinstate Monica Jun 06 '19 at 07:06
  • Ok, then what interface *IS* suitable for animation on Linux? I use windows timers for buffer flip animation. The Xwindows equivalent is actually pretty painful. – Scott Franco Jun 09 '19 at 20:15
  • @ScottFranco Sadly I don't know, **but** I suggest to take a look at some prominent software, for example mplayer. Anyway I suppose you can use a simple timer to invalidate regularly your window, and reply to expose events as usual. – linuxfan says Reinstate Monica Jun 10 '19 at 08:11
2

Remember that you are in client/server environment. Means that there are delays in between your code requests and their realizations. Returning from call to XMapWindow does not means that your window is really ready to accept drawings only that the XServer has to do it, so you need to wait the first Expose event to draw, which means that the XServer did the job. It is mandatory, because Expose is the event representing the fact that the window (or a portion of it) is viewable on screen.

Always remember : UIs are event-driven.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • I considered that X11 might need to wait for the first expose. However, I saw the behavior in my full program (not the demo) that this occurred even after the window was already running. In fact I saw cases where the single characters got painted in the window many seconds after the draw operation. Its not sitting in the queue, I printed the queue numbers and they are 0, and (as above) XFlush does not help. – Scott Franco Jun 05 '19 at 15:04
1

Ok, I thought about this last night and realized that an old, classic program called "lines" illustrates the issue I am talking about:

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int randr(int upper)

{

   return rand() % (upper+1);

}   

int main(void) {

    Display *d;
    Window w;
    XEvent e;
    const char *msg = "Hello, World!";
    int s;
    XEvent se;

    d = XOpenDisplay(NULL);
    if (d == NULL) {
       fprintf(stderr, "Cannot open display\n");
       exit(1);
    }

    s = DefaultScreen(d);
    w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 400, 400, 1,
                            BlackPixel(d, s), WhitePixel(d, s));
    XSelectInput(d, w, ExposureMask | KeyPressMask);
    XMapWindow(d, w);

    se.type = Expose;
    se.xexpose.type = Expose;
    se.xexpose.serial = 0;
    se.xexpose.send_event = 1;
    se.xexpose.window = w;
    se.xexpose.x = 0;
    se.xexpose.y = 0;
    se.xexpose.width = 400;
    se.xexpose.height = 400;
    se.xexpose.count = 0;

    while (1) {
       XNextEvent(d, &e);
       if (e.type == Expose) {
          XSetForeground(d, DefaultGC(d, s), randr(255)<<16 | randr(255)<<8 | randr(255));
          XDrawLine(d, w, DefaultGC(d, s), randr(400), randr(400), randr(400), randr(400));
          XSendEvent(d, w, 0, ExposureMask, &se);
       }
       if (e.type == KeyPress) break;
    }

    XCloseDisplay(d);

    return 0;
}

Lines is an old program that goes back to the days of the Cromemco Dazzler of the late 1970's. It just draws random lines, it does it quite fast. It has no need of input, it just draws and draws, and it can serve as a performance test as well.

As you see here I have tricked X11 into continuously processing the expose event by posting another one in the expose event itself. In (for example) MS windows this would not be necessary. You can draw on the window at any time, and not reading your event queue is not nice, but the program still works.

This program works in X11, and (on my machine) works so rapidly that it basically fills the window almost immediately.

Now if I wanted to make that demo work as fast as possible I would need to account for the major time sinks in the program, which, as far as I can tell, is the need to continually post the new expose events.

Scott Franco
  • 481
  • 2
  • 15
  • So in case it was not apparent, I am pretty happy with the answer, which is to send yourself events via XSendEvent. I don't think I quite understand when and why X11 is ignoring or delaying draws, but I agree this detail is not required to achieve my ends. This information is going into a real program: https://sourceforge.net/projects/petitami/. This is a graphical toolkit I created for MS Windows back in 1997 that I am now porting to X11 (yea,a bit late I know). – Scott Franco Jun 05 '19 at 16:48