Re: cairo / xlib not updating window content
Cedric Roux <[email protected]> Sun, 29 Jul 2018 12:29:39 +0200
| Newsgroups | gmane.comp.lib.cairo |
|---|---|
| Message-ID | <[email protected]> |
Hi, On 07/28/2018 09:50 AM, Pavel Petrovic wrote: > I am trying to learn how to use Cairo 2D drawing library with xlib surfaces. attached a version that "works" for me. At https://cairographics.org/Xlib/ it is said you need to call cairo_push_group and cairo_pop_group_to_source (and also cairo_surface_flush, which I didn't add; it works for me without it). That said, keep in mind that the content of an X window may vanish at any time. So all you did between XMapWindow and the current time may be displayed, or not. That's why your white background is there for one window and not for the others. There is the notion of "backing store" for X windows. The X server may store content, but it's not guarantied (by default, when calling XCreateSimpleWindow at least). You should thus change your inner thinking of what an X window is. This is not a buffer that keeps all what you plot into. You should (must?) react to the Expose event and redraw dirty the dirty area indicated by this event. I don't know what your ultimate goal is, but maybe you want to use X pixmaps (that keep their contents), draw cairo into them and copy the pixmaps to the X windows whenever the content is ready, instead of directly draw to X windows. If you minimize your windows or put another window in front of them, and then put the window back in front, depending on the X server you use (and maybe also the window manager), the hidden content will have been preserved or not. Anyway, in short: X windows are very "volatile" creatures. Do not assume the content you send there will survive long. React to Expose event, and redraw the area that is invalid. Use X pixmaps if you want some kind of permanent content. Something like that. HTH. Cedric. -- cairo mailing list [email protected] https://lists.cairographics.org/mailman/listinfo/cairo
test_cairo2.c
(text/x-csrc, 14.3 KB)
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <cairo.h>
#include <cairo-xlib.h>
#define LEFT_MOUSE_BUTTON -1
#define RIGHT_MOUSE_BUTTON -2
#define GUI_ESC_KEY 0xff1b
#define GUI_TAB_KEY 65289
#define GUI_SHIFT_TAB_KEY 65056
#define GUI_RIGHT_ARROW_KEY 0xff53
#define GUI_LEFT_ARROW_KEY 0xff51
#define GUI_UP_ARROW_KEY 0xff52
#define GUI_DOWN_ARROW_KEY 0xff54
#define WIN1_WIDTH 600
#define WIN1_HEIGHT 400
#define WIN2_WIDTH 300
#define WIN2_HEIGHT 300
#define WIN3_WIDTH 400
#define WIN3_HEIGHT 200
#define MAX_GUI_CONTEXTS_COUNT 10
#define MAX_GUI_WINDOWS_COUNT 20
typedef void (*gui_key_callback)(int, int); // active window, key
typedef void (*gui_draw_callback)(cairo_t *); // window cairo handle
typedef void (*gui_mouse_callback)(int, int, int); // x, y, button
int win1, win2, win3;
int x1, yy1;
int x2, y2;
int x3, y3;
int program_runs;
static Display *dsp;
static Screen *scr;
static int screen;
static unsigned char x_opened;
static char *context_names[MAX_GUI_CONTEXTS_COUNT];
static gui_key_callback key_callbacks[MAX_GUI_CONTEXTS_COUNT];
static volatile int contexts_count;
static volatile int current_context;
static gui_draw_callback draw_callbacks[MAX_GUI_WINDOWS_COUNT];
static int window_in_use[MAX_GUI_WINDOWS_COUNT];
static gui_mouse_callback mouse_callbacks[MAX_GUI_WINDOWS_COUNT];
static cairo_t *windows[MAX_GUI_WINDOWS_COUNT];
static cairo_surface_t *surfaces[MAX_GUI_WINDOWS_COUNT];
static Window x11windows[MAX_GUI_WINDOWS_COUNT];
static char *window_names[MAX_GUI_WINDOWS_COUNT];
static int window_update_periods[MAX_GUI_WINDOWS_COUNT];
static long long next_window_update[MAX_GUI_WINDOWS_COUNT];
long long usec()
{
struct timeval tv;
gettimeofday(&tv, 0);
return (1000000L * (long long)tv.tv_sec) + tv.tv_usec;
}
void repaint_window(int window_handle)
{
draw_callbacks[window_handle](windows[window_handle]);
cairo_surface_flush(surfaces[window_handle]);
}
int gui_cairo_check_event(int *xclick, int *yclick, int *win)
{
char keybuf[8];
KeySym key;
XEvent e;
for (;;)
{
if (XPending(dsp)) XNextEvent(dsp, &e);
else return 0;
*win = -1;
switch (e.type)
{
case ButtonPress:
*xclick = e.xbutton.x;
*yclick = e.xbutton.y;
for (int i = 0; i < MAX_GUI_WINDOWS_COUNT; i++)
if (x11windows[i] == e.xbutton.window) *win = i;
return -e.xbutton.button;
case KeyPress:
for (int i = 0; i < MAX_GUI_WINDOWS_COUNT; i++)
if (x11windows[i] == e.xkey.window) *win = i;
XLookupString(&e.xkey, keybuf, sizeof(keybuf), &key, NULL);
return key;
default:
printf("Dropping unhandled XEevent.type = %d.\n", e.type);
}
}
}
cairo_surface_t *gui_cairo_create_x11_surface(int *x, int *y, int win)
{
Drawable da;
cairo_surface_t *sfc;
if (!x_opened)
{
if ((dsp = XOpenDisplay(0)) == 0)
{
printf("could not open X display, will not use graphics");
return 0;
}
screen = DefaultScreen(dsp);
scr = DefaultScreenOfDisplay(dsp);
}
da = XCreateSimpleWindow(dsp, DefaultRootWindow(dsp), 0, 0, *x, *y, 0, 0, 0);
XSelectInput(dsp, da, ButtonPressMask | KeyPressMask);
XMapWindow(dsp, da);
sfc = cairo_xlib_surface_create(dsp, da, DefaultVisual(dsp, screen), *x, *y);
cairo_xlib_surface_set_size(sfc, *x, *y);
x11windows[win] = da;
x_opened = 1;
return sfc;
}
void draw_windows_title(int window_handle)
{
char fullname[100];
if (window_names[window_handle] == 0)
sprintf(fullname, "Mikes - %d - [%s]", window_handle, context_names[current_context]);
else
sprintf(fullname, "Mikes - %s - [%s]", window_names[window_handle], context_names[current_context]);
cairo_surface_flush(surfaces[window_handle]);
XStoreName(dsp, x11windows[window_handle], fullname);
}
void gui_set_window_title(int window_handle, char *title)
{
if (!window_in_use[window_handle]) return;
if (title && strlen(title) > 40)
{
printf("windows title too long: %s", title);
title = "untitled";
}
window_names[window_handle] = title;
draw_windows_title(window_handle);
}
int gui_open_window(gui_draw_callback paint, int width, int height, int update_period_in_ms)
{
int window_handle = -1;
for (int i = 0; i < MAX_GUI_WINDOWS_COUNT; i++)
if (!window_in_use[i])
{
window_handle = i;
break;
}
if (window_handle < 0) return -1;
surfaces[window_handle] = gui_cairo_create_x11_surface(&width, &height, window_handle);
windows[window_handle] = cairo_create(surfaces[window_handle]);
mouse_callbacks[window_handle] = 0;
draw_callbacks[window_handle] = paint;
window_update_periods[window_handle] = update_period_in_ms;
window_names[window_handle] = 0;
cairo_surface_flush(surfaces[window_handle]);
cairo_set_source_rgb(windows[window_handle], 1, 1, 1);
cairo_paint(windows[window_handle]);
cairo_surface_flush(surfaces[window_handle]);
draw_callbacks[window_handle](windows[window_handle]);
if (update_period_in_ms > 0)
next_window_update[window_handle] = usec() + update_period_in_ms * 1000;
else next_window_update[window_handle] = 0;
draw_windows_title(window_handle);
cairo_surface_flush(surfaces[window_handle]);
window_in_use[window_handle] = 1;
return window_handle;
}
void gui_close_window(int window_handle)
{
if (!window_in_use[window_handle]) return;
cairo_destroy(windows[window_handle]);
cairo_surface_destroy(surfaces[window_handle]);
window_in_use[window_handle] = 0;
int no_more_windows = 1;
for (int i = 0; i < MAX_GUI_WINDOWS_COUNT; i++)
if (window_in_use[i])
{
no_more_windows = 0;
break;
}
if (no_more_windows)
{
XCloseDisplay(dsp);
x_opened = 0;
}
}
cairo_t *get_cairo_t(int window_handle)
{
if (!window_in_use[window_handle]) return 0;
return windows[window_handle];
}
void gui_add_key_listener(char *context, gui_key_callback callback)
{
if (strlen(context) > 40)
{
printf("context name too long: %s\n", context);
return;
}
if (contexts_count == MAX_GUI_CONTEXTS_COUNT)
{
printf("gui contexts full!");
return;
}
context_names[contexts_count] = context;
key_callbacks[contexts_count] = callback;
contexts_count++;
}
void remove_key_listener(char *context)
{
for (int i = 1; i < contexts_count; i++)
if (strcmp(context_names[i], context) == 0)
{
context_names[i] = context_names[contexts_count - 1];
key_callbacks[i] = key_callbacks[contexts_count - 1];
contexts_count--;
if (current_context == i) current_context = 0;
else if (current_context > i) current_context--;
break;
}
}
void gui_add_mouse_listener(int window_handle, gui_mouse_callback callback)
{
if (!window_in_use[window_handle]) return;
mouse_callbacks[window_handle] = callback;
}
void system_gui_context_callback(int win, int key)
{
switch(key) {
case GUI_ESC_KEY: program_runs = 0;
printf("quit by ESC");
break;
}
}
void shutdown_gui()
{
for (int i = 0; i < MAX_GUI_WINDOWS_COUNT; i++)
if (window_in_use[i])
gui_close_window(i);
}
void redraw_windows_titles()
{
for (int i = 0; i < MAX_GUI_WINDOWS_COUNT; i++)
if (window_in_use[i])
draw_windows_title(i);
}
void gui_switch_context(int win, int ch)
{
if (ch == GUI_TAB_KEY) current_context++; else current_context--;
if (current_context == contexts_count) current_context = 0;
if (current_context < 0) current_context = contexts_count - 1;
redraw_windows_titles();
for (int i = 0; i < contexts_count; i++)
key_callbacks[i](win, ch);
}
long long repaint_expired_windows()
{
long long tm = usec();
long long next_update = tm + 100000;
for (int i = 0; i < MAX_GUI_WINDOWS_COUNT; i++)
if (window_in_use[i] && next_window_update[i])
{
if (next_window_update[i] <= tm)
{
repaint_window(i);
next_window_update[i] = tm + window_update_periods[i] * 1000;
if (next_window_update[i] < next_update) next_update = next_window_update[i];
}
else if (next_window_update[i] < next_update) next_update = next_window_update[i];
}
return next_update;
}
void process_gui_events()
{
int xclick, yclick, win;
do {
int event = gui_cairo_check_event(&xclick, &yclick, &win);
if (event == 0) break;
if (win < 0) continue;
if (!window_in_use[win]) continue;
if (event < 0)
{
if (mouse_callbacks[win])
{
mouse_callbacks[win](xclick, yclick, event);
}
}
else if ((event == GUI_TAB_KEY) || (event == GUI_SHIFT_TAB_KEY))
gui_switch_context(win, event);
else
{
key_callbacks[current_context](win, event);
}
} while (1);
}
void init_gui()
{
x_opened = 0;
dsp = 0;
for (int i = 0; i < MAX_GUI_WINDOWS_COUNT; i++)
window_in_use[i] = 0;
contexts_count = 1;
current_context = 0;
context_names[0] = "system";
key_callbacks[0] = system_gui_context_callback;
}
char *get_current_gui_context()
{
return context_names[current_context];
}
void set_current_gui_context(char *context)
{
for (int i = 0; i < contexts_count; i++)
if (strcmp(context_names[i], context) == 0)
{
current_context = i;
redraw_windows_titles();
break;
}
}
void mouse1(int x, int y, int button)
{
if (strcmp(get_current_gui_context(), "drawing") != 0) return;
cairo_t *win = get_cairo_t(win1);
cairo_push_group(win);
cairo_set_source_rgba(win, 0, 0, 1, 0.4);
cairo_set_line_width(win, 1);
cairo_move_to(win, x1, yy1);
cairo_line_to(win, x, y);
cairo_stroke(win);
cairo_pop_group_to_source(win);
cairo_paint(win);
x1 = x;
yy1 = y;
}
void mouse2(int x, int y, int button)
{
if (strcmp(get_current_gui_context(), "drawing") != 0) return;
cairo_t *win = get_cairo_t(win2);
cairo_push_group(win);
cairo_set_source_rgba(win, 0, 1, 0, 0.4);
cairo_set_line_width(win, 3);
cairo_move_to(win, x2, y2);
x = rand() % WIN2_WIDTH;
y = rand() % WIN2_HEIGHT;
cairo_line_to(win, x, y);
cairo_stroke(win);
cairo_pop_group_to_source(win);
cairo_paint(win);
x2 = x;
y2 = y;
}
void line3_from(int x, int y)
{
cairo_t *win = get_cairo_t(win3);
cairo_push_group(win);
cairo_set_source_rgba(win, 1, 0, 0, 0.6);
cairo_set_line_width(win, 4);
cairo_move_to(win, x, y);
cairo_line_to(win, x3, y3);
cairo_stroke(win);
cairo_pop_group_to_source(win);
cairo_paint(win);
}
void paint1(cairo_t *win)
{
cairo_push_group(win);
cairo_set_source_rgba(win, 0.7, 0.7, 0, 0.4);
cairo_set_line_width(win, 1);
int x = rand() % (WIN1_WIDTH - 30);
int y = rand() % (WIN1_HEIGHT - 20);
int w = rand() % 30;
int h = rand() % 20;
cairo_rectangle(win, x, y, w, h);
cairo_fill(win);
cairo_pop_group_to_source(win);
cairo_paint(win);
}
void paint2(cairo_t *win)
{
cairo_push_group(win);
cairo_select_font_face(win, "Arial", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_source_rgba(win, 0.1, 0.5, 0.5, 0.5);
cairo_set_font_size(win, 12);
int x = rand() % (WIN2_WIDTH - 30);
int y = rand() % (WIN2_HEIGHT - 12);
cairo_move_to(win, x, y);
cairo_show_text(win, "hi!");
cairo_pop_group_to_source(win);
cairo_paint(win);
}
void paint3(cairo_t *win)
{
cairo_push_group(win);
cairo_set_source_rgba(win, 0.5, 0.5, 0.9, 0.4);
int x = rand() % (WIN3_WIDTH - 30);
int y = rand() % (WIN3_HEIGHT - 20);
int r = rand() % 30;
cairo_arc(win, x, y, r, 0, 2 * M_PI);
cairo_fill(win);
cairo_pop_group_to_source(win);
cairo_paint(win);
}
void key_listener(int win, int key)
{
int x = x3;
int y = y3;
if (key == GUI_ESC_KEY) program_runs = 0;
else if (key == GUI_RIGHT_ARROW_KEY)
{
if (x3 < WIN3_WIDTH - 1) x3++;
line3_from(x, y);
}
else if (key == GUI_LEFT_ARROW_KEY)
{
if (x3 > 0) x3--;
line3_from(x, y);
}
else if (key == GUI_UP_ARROW_KEY)
{
if (y3 > 0) y3--;
line3_from(x, y);
}
else if (key == GUI_DOWN_ARROW_KEY)
{
if (y3 < WIN3_HEIGHT - 1) y3++;
line3_from(x, y);
}
else if ((key != GUI_TAB_KEY) && (key != GUI_SHIFT_TAB_KEY)) printf("key %d\n", key);
}
void test_gui()
{
win2 = gui_open_window(paint2, WIN2_WIDTH, WIN2_HEIGHT, 100);
win1 = gui_open_window(paint1, WIN1_WIDTH, WIN1_HEIGHT, 300);
win3 = gui_open_window(paint3, WIN3_WIDTH, WIN3_HEIGHT, 109);
x1 = WIN1_WIDTH / 2;
yy1 = WIN1_HEIGHT / 2;
x2 = WIN2_WIDTH / 2;
y2 = WIN2_HEIGHT / 2;
x3 = WIN3_WIDTH / 2;
y3 = WIN3_HEIGHT / 2;
gui_set_window_title(win1, "click to draw");
gui_set_window_title(win3, "use arrows to draw");
gui_add_mouse_listener(win1, mouse1);
gui_add_mouse_listener(win2, mouse2);
gui_add_key_listener("drawing", key_listener);
printf("Welcome to test_gui!\n\n");
printf(" press TAB/SHIFT-TAB to switch between drawing/system contexts.\n");
printf(" press ESC to quit\n");
printf(" when in drawing context,\n click with mouse to draw lines to first window,\n random lines to second window,\n and using arrow keys in the third window\n");
while (program_runs)
{
if (dsp == 0)
{
usleep(1000);
continue;
}
long long next_update = repaint_expired_windows();
process_gui_events();
long long tm = usec();
if (next_update > tm)
usleep((long)(next_update - tm));
}
shutdown_gui();
}
int main(int argc, char **argv)
{
program_runs = 1;
init_gui();
test_gui();
return 0;
}