1
votes

I'm writing an application in Linux using Xlib to manage a window and cairo to draw some text in it. The text content of the window changes during the execution, so I want to adapt the window size to match that of the text extent. If the size of the text extent does not change, the window is always correctly updated with the new text.

But when the text extent changes, and so the window is resized accordingly, the window is cleared but the new text is never shown. Only if there is no call to XResizeWindow the text is actually displayed. The code I'm using is

if (/* Text extent is changed */)
{
    XResizeWindow (display, window, new_width, new_height);
    cairo_xlib_surface_set_size (surface, new_width, new_height);
}

XClearWindow (display, window);

/* ... Cairo code to draw the text ... */

// cairo_surface_flush (surface);
// XFlush (display);

I have also tried to add after the Cairo code that draws the text the methods cairo_surface_flush and XFlush (commented in the example) but nothing changes.

EDIT: I solved the problem using two threads: the first thread with the usual loop for listening to the Expose events plus the code to redraw the content and the second thread that issues the resize of the window and sends an Expose event to wake up the first thread.

In this example the window is resized every 500 ms to random width and height and a progressive counter is displayed in it at every resize. I use C++11, compile with:

g++ -std=c++11 -o test test.cpp -lX11 -lcairo -lpthread

The code is:

#include <random>
#include <chrono>
#include <thread>
#include <string>
#include <X11/Xlib.h>
#include <cairo/cairo-xlib.h>

Display * d;
Window w;
cairo_surface_t * surface;
int width = 300, height = 300;
unsigned char counter = 0;
std::random_device rd;
std::knuth_b gen (rd ());
std::uniform_int_distribution < > dist (150, 300);

void logic ()
{
    XEvent send_event;
    send_event.type = Expose;
    send_event.xexpose.window = w;

    while (true)
    {
        std::this_thread::sleep_for (std::chrono::milliseconds (500));
        ++ counter;
        width = dist (gen);
        height = dist (gen);
        cairo_xlib_surface_set_size (surface, width, height);
        XResizeWindow (d, w, width, height);
        XSendEvent (d, w, False, ExposureMask, & send_event);
        XFlush (d);
    }
}

int main ( )
{
    XInitThreads ();

    d = XOpenDisplay (NULL);
    w = XCreateSimpleWindow (d, RootWindow (d, 0), 0, 0, width, height, 0, 0, 0x000000);
    XMapWindow (d, w);
    XSelectInput (d, w, ExposureMask | KeyPressMask);

    surface = cairo_xlib_surface_create (d, w, DefaultVisual (d, 0), width, height);
    cairo_t * cairo = cairo_create (surface);
    cairo_select_font_face (cairo, "FreeSans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size (cairo, 40 );
    cairo_set_source_rgb (cairo, 0.8, 0.8, 0.8);
    cairo_move_to (cairo, 40.0, 60.0);
    cairo_show_text (cairo, std::to_string (counter).c_str ());
    XFlush (d);

    std::thread T (logic);

    XEvent event;
    while (true)
    {
        XNextEvent (d, & event);
        if (event.type == Expose)
        {
            XClearWindow (d, w);
            cairo_move_to (cairo, 40.0, 60.0);
            cairo_show_text (cairo, std::to_string (counter).c_str ());
        }
        else if (event.type == KeyPress)
        {
            XCloseDisplay (d);
            return 0;
        }
    }
}

But a question remains: is it possible to obtain the same result using only one thread?

1
Do you have a self-contained example that one could test? How does your event-loop look like, i.e. what do you do on Expose events?Uli Schlachter
Ok I will provide a compact self-contained code example that reproduces the behaviour.disquisitiones

1 Answers

1
votes

Here is a single-threaded version of your code. It is not nice, but it seems to work. The hard part is waiting for events from the X11 server and the timeout simultaneously. I do this with select() in the following code. Note that I also handle ConfigureNotify events instead of assuming that XResizeWindow always does just what we want.

#include <random>
#include <chrono>
#include <thread>
#include <string>
#include <X11/Xlib.h>
#include <cairo/cairo-xlib.h>
#include <sys/time.h>

Display * d;
Window w;
cairo_surface_t * surface;
int width = 300, height = 300;
unsigned char counter = 0;
std::random_device rd;
std::knuth_b gen (rd ());
std::uniform_int_distribution < > dist (150, 300);

void do_update ()
{
        ++ counter;
        width = dist (gen);
        height = dist (gen);
        XResizeWindow (d, w, width, height);
        // Force a redraw
        XClearArea(d, w, 0, 0, 0, 0, True);
}

int main ( )
{
    XInitThreads ();

    d = XOpenDisplay (NULL);
    w = XCreateSimpleWindow (d, RootWindow (d, 0), 0, 0, width, height, 0, 0, 0x000000);
    XMapWindow (d, w);
    XSelectInput (d, w, ExposureMask | KeyPressMask);

    surface = cairo_xlib_surface_create (d, w, DefaultVisual (d, 0), width, height);
    cairo_t * cairo = cairo_create (surface);
    cairo_select_font_face (cairo, "FreeSans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size (cairo, 40 );
    cairo_set_source_rgb (cairo, 0.8, 0.8, 0.8);
    cairo_move_to (cairo, 40.0, 60.0);
    cairo_show_text (cairo, std::to_string (counter).c_str ());
    XFlush (d);

    struct timeval next_update;
    struct timeval now;
    struct timeval interval = { 0, 500000 };
    gettimeofday(&now, NULL);
    timeradd(&now, &interval, &next_update);

    while (true)
    {
        XEvent event;

        gettimeofday(&now, NULL);
        if (timercmp(&now, &next_update, >)) {
            // Store time of next update
            timeradd(&now, &interval, &next_update);
            puts("update");
            do_update();
        }

        if (!XPending(d)) {
            struct timeval remaining;
            fd_set fds;
            int fd = ConnectionNumber(d);
            FD_ZERO(&fds);
            FD_SET(fd, &fds);
            timersub(&next_update, &now, &remaining);
            select(fd + 1, &fds, NULL, NULL, &remaining);
        } else {
            XNextEvent (d, & event);
            if (event.type == Expose)
            {
                XClearWindow (d, w);
                cairo_move_to (cairo, 40.0, 60.0);
                cairo_show_text (cairo, std::to_string (counter).c_str ());
            }
            if (event.type == ConfigureNotify)
            {
                cairo_xlib_surface_set_size (surface, event.xconfigure.width, event.xconfigure.height);
            }
            else if (event.type == KeyPress)
            {
                XCloseDisplay (d);
                return 0;
            }
        }
    }
}