Search code examples
cx11cairo

Why doesn't cairo draw on first iteration of loop?


I'm working on a library that creates transparent X windows and uses cairo to draw on them. There is an event loop implemented in the main thread, while the drawing operations take place in a separate thread within a loop. The latter looks like this

  while (self->_running) {
    PyGILState_Release(gstate);
    usleep(1000); // Sleep 1 ms
    gstate = PyGILState_Ensure();

    if (self->_expiry <= gettime()) {
      draw(self, args_tuple); // All the cairo code is in here
      self->_expiry += interval;
      interval = self->interval;
    }
  }

The event loop calls XNextEvent periodically to trap key/button presses only. The window is mapped before the new UI thread is started from the main thread.

When the interval between iterations on the UI thread (the self->inteval value above) is large (order of seconds), the window stays transparent on the first iteration of the loop, and it only gets painted on from the second iteration onward. Calling draw right before the while loop doesn't help, unless there is a pause of some milliseconds in between calls to draw. For example, if I put interval = 25 right before the while loop, then the second call to draw paints on the window in most of the executions of the application implementing this code.

Things that I have tried:

  • cairo_surface_flush and XFlush right after draw don't seem to work
  • Sending an Expose event doesn't seem to help either.

How can I make sure that my loop starts painting on the window from the first iteration?


Solution

  • What I'm missing is the ExposureMask flag in the call to XSelectInput. With this flag set, one then has to look for Expose events in the event loop with the following pattern:

      switch (e.type) {
      case Expose:
        if (e.xexpose.count == 0) {
          BaseCanvas__redraw(canvas);
        }
        return;
      }
    

    The redraw operation doesn't need to be the full set of draw operations. Having modified the cairo context, it is enough to repaint it on the destination with no more than this

    void
    BaseCanvas__redraw(BaseCanvas * self) {
      cairo_save(self->context);
      cairo_set_operator(self->context, CAIRO_OPERATOR_SOURCE);
      cairo_paint(self->context);
      cairo_restore(self->context);
    }