Cairo is a graphics library which offers common drawing primitives independently of the actual backend. It comes with a set of backends, such as PNG or PDF. One of these backends allows to use libcairo for drawing on X11 windows. Although there are very powerful libraries such as Qt or GTK, they are often far too complex for simple applications.
Following I explain how to open an X11 window using Xlib and show how to create graphics output with Cairo graphics.
Opening a Window
X11 is probably the most flexible graphical interface which makes it a little bit complicated, at least at a first sight. To open a window, you need to do the following steps:
Connect to the X sever:
XOpenDisplay(3)
.Select the output screen:
DefaultScreen(3)
.Create a window:
XCreateSimpleWindow(3)
.Choose input events:
XSelectInput(3)
. Please note that this is not mandatory for opening a window but typically you'd like to receive events such as mouse clicks and key board input.Display the window:
XMapWindow(3)
.
After the window is ready, it is mapped to a cairo Xlib surface. The following function shows how to do it.
cairo_surface_t *cairo_create_x11_surface0(int x, int y)
{
Display *dsp;
Drawable da;
int screen;
cairo_surface_t *sfc;
if ((dsp = XOpenDisplay(NULL)) == NULL)
exit(1);
screen = DefaultScreen(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);
return sfc;
}
Receiving Events
The next task is to receive an event. The function from above configured the
window to receive mouse button and keyboard events. The function
XNextEvent(3)
returns the next event of the X server's event queue and it
blocks if the queue is empty. If blocking is not an option for your tool
because you have permanent interaction -- such as in e.g. computer games -- you
have to check if there are events in the queue before retrieving it with
XNextEvent(3)
to avoid blocking. This is done with XPending(3)
. The
function immediately returns the number of events in the queue. Thus, it
returns 0 if there are no events.
XNextEvent(3)
returns an XEvent
which actually is a union of all different
kinds of X events. The type field distinguishes between them. The XKeyEvent
receives key codes which have to be translated with XLookupString(3)
to
symbols. All symbols are defined in X11/keysyndef.h
. The following function
shows how to do it.
int cairo_check_event(cairo_surface_t *sfc, int block)
{
char keybuf[8];
KeySym key;
XEvent e;
for (;;)
{
if (block || XPending(cairo_xlib_surface_get_display(sfc)))
XNextEvent(cairo_xlib_surface_get_display(sfc), &e);
else
return 0;
switch (e.type)
{
case ButtonPress:
return -e.xbutton.button;
case KeyPress:
XLookupString(&e.xkey, keybuf, sizeof(keybuf), &key, NULL);
return key;
default:
fprintf(stderr, "Dropping unhandled XEevent.type = %d.\n", e.type);
}
}
}
Putting all this together creates a first simple window example. Download the cairo_xlib_simple.c source file.
Animations and Full Screen
To create animations you simple repaint the image in a loop. The problem is
that you basically have no control about the timing when the X server actually
updates the screen. All graphic operations are queued and at some time the
queue is processed by the X server. This creates the problem that the
animation might flicker. The solution is to push all operations to a group
(cairo_push_group()
) instead of directly drawing to the surface. Finally the
group is popped to the Cairo drawing source (cairo_pop_group_to_source()
) and
painted (cairo_paint()
) all at once. Finally we force the X server to flush
its queue (cairo_surface_flush()
). Although this produces good results you
should be aware that this (Cairo + Xlib) is not the method of choice if you
intend to write a high speed graphics intensive computer game. If this is the
case you should start to learn OpenGL or
SDL.
Making the window fullscreen seems to be a well protected X11 secret.
Fullscreen is a specific property of a window, such as "maximized",
"minimized", and similar ones. The property _NET_WM_STATE_FULLSCREEN
is set
with the function XChangeProperty(3)
.
Putting all this together leads to this second final example cairo_xlib.c.
A last note: since the size of a window can change you have to react
accordingly. The window change event is sent to the event queue as an
XConfigureEvent
. It contains the new width and height which has to be passed
to the Cairo surface with cairo_xlib_surface_set_size()
.