ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

graphics_xlib.c (16992B)


      1 /*
      2  * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org>
      3  *
      4  * Permission to use, copy, modify, and/or distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <stdio.h>
     18 #include <string.h>
     19 
     20 #include <X11/XKBlib.h>
     21 #include <X11/Xlib.h>
     22 #include <X11/Xutil.h>
     23 #include <X11/extensions/XKB.h>
     24 #include <X11/extensions/Xdbe.h>
     25 #include <X11/extensions/dbe.h>
     26 
     27 #if USE_XRANDR
     28 #include <X11/extensions/Xrandr.h>
     29 #endif
     30 
     31 #include "graphics_xlib.h"
     32 #include "color.h"
     33 #include "memory.h"
     34 #include "rect.h"
     35 #include "util.h"
     36 
     37 /* FIXME: kind of ugly to have this duplicated here and in event_xlib.c,
     38    but I don't really want to give these functions linkage */
     39 LTK_ARRAY_INIT_FUNC_DECL_STATIC(moninfo, struct ltk_moninfo)
     40 LTK_ARRAY_INIT_IMPL_STATIC(moninfo, struct ltk_moninfo)
     41 
     42 ltk_surface *
     43 ltk_surface_create(ltk_renderwindow *window, int w, int h) {
     44 	ltk_surface *s = ltk_malloc(sizeof(ltk_surface));
     45 	if (w <= 0)
     46 		w = 1;
     47 	if (h <= 0)
     48 		h = 1;
     49 	s->w = w;
     50 	s->h = h;
     51 	s->window = window;
     52 	s->d = XCreatePixmap(window->renderdata->dpy, window->xwindow, w, h, window->renderdata->depth);
     53 	#if USE_XFT == 1
     54 	s->xftdraw = XftDrawCreate(window->renderdata->dpy, s->d, window->renderdata->vis, window->renderdata->cm);
     55 	#endif
     56 	s->resizable = 1;
     57 	return s;
     58 }
     59 
     60 ltk_surface *
     61 ltk_surface_from_window(ltk_renderwindow *window, int w, int h) {
     62 	ltk_surface *s = ltk_malloc(sizeof(ltk_surface));
     63 	s->w = w;
     64 	s->h = h;
     65 	s->window = window;
     66 	s->d = window->drawable;
     67 	#if USE_XFT == 1
     68 	s->xftdraw = XftDrawCreate(window->renderdata->dpy, s->d, window->renderdata->vis, window->renderdata->cm);
     69 	#endif
     70 	s->resizable = 0;
     71 	return s;
     72 }
     73 
     74 void
     75 ltk_surface_destroy(ltk_surface *s) {
     76 	#if USE_XFT == 1
     77 	XftDrawDestroy(s->xftdraw);
     78 	#endif
     79 	if (s->resizable)
     80 		XFreePixmap(s->window->renderdata->dpy, (Pixmap)s->d);
     81 	ltk_free(s);
     82 }
     83 
     84 void
     85 ltk_surface_update_size(ltk_surface *s, int w, int h) {
     86 	/* FIXME: maybe return directly if surface is resizable? */
     87 	s->w = w;
     88 	s->h = h;
     89 	/* FIXME: sort of hacky (this is only used by window surface) */
     90 	#if USE_XFT == 1
     91 	XftDrawChange(s->xftdraw, s->d);
     92 	#endif
     93 }
     94 
     95 int
     96 ltk_surface_resize(ltk_surface *s, int w, int h) {
     97 	if (!s->resizable)
     98 		return 1;
     99 	s->w = w;
    100 	s->h = h;
    101 	XFreePixmap(s->window->renderdata->dpy, (Pixmap)s->d);
    102 	s->d = XCreatePixmap(s->window->renderdata->dpy, s->window->xwindow, w, h, s->window->renderdata->depth);
    103 	#if USE_XFT == 1
    104 	XftDrawChange(s->xftdraw, s->d);
    105 	#endif
    106 	return 0;
    107 }
    108 
    109 void
    110 ltk_surface_get_size(ltk_surface *s, int *w, int *h) {
    111 	*w = s->w;
    112 	*h = s->h;
    113 }
    114 
    115 void
    116 ltk_surface_draw_rect(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width) {
    117 	XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
    118 	XSetLineAttributes(s->window->renderdata->dpy, s->window->gc, line_width, LineSolid, CapButt, JoinMiter);
    119 	XDrawRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, rect.h);
    120 }
    121 
    122 void
    123 ltk_surface_draw_border(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width, ltk_border_sides border_sides) {
    124 	/* drawn as rectangles to have proper control over line width - I'm not sure how exactly
    125 	   XDrawLine handles even line widths (i.e. on which side the extra pixel will be) */
    126 	XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
    127 	if (border_sides & LTK_BORDER_TOP)
    128 		XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, line_width);
    129 	if (border_sides & LTK_BORDER_BOTTOM)
    130 		XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y + rect.h - line_width, rect.w, line_width);
    131 	if (border_sides & LTK_BORDER_LEFT)
    132 		XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, line_width, rect.h);
    133 	if (border_sides & LTK_BORDER_RIGHT)
    134 		XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x + rect.w - line_width, rect.y, line_width, rect.h);
    135 }
    136 
    137 void
    138 ltk_surface_draw_border_clipped(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width, ltk_border_sides border_sides, ltk_rect clip) {
    139 	if (line_width <= 0)
    140 		return;
    141 	/* NOTE: XRectangle only uses short, so this could cause issues */
    142 	XRectangle xclip = {clip.x, clip.y, clip.w, clip.h};
    143 	XSetClipRectangles(s->window->renderdata->dpy, s->window->gc, 0, 0, &xclip, 1, Unsorted);
    144 	ltk_surface_draw_border(s, c, rect, line_width, border_sides);
    145 	XSetClipMask(s->window->renderdata->dpy, s->window->gc, None);
    146 }
    147 
    148 void
    149 ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect) {
    150 	XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
    151 	XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, rect.h);
    152 }
    153 
    154 void
    155 ltk_surface_fill_polygon(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints) {
    156 	/* FIXME: maybe make this static since this won't be threaded anyways? */
    157 	XPoint tmp_points[6]; /* to avoid extra allocations when not necessary */
    158 	/* FIXME: this is ugly and inefficient */
    159 	XPoint *final_points;
    160 	if (npoints <= 6) {
    161 		final_points = tmp_points;
    162 	} else {
    163 		final_points = ltk_reallocarray(NULL, npoints, sizeof(XPoint));
    164 	}
    165 	/* FIXME: how to deal with ints that don't fit in short? */
    166 	for (size_t i = 0; i < npoints; i++) {
    167 		final_points[i].x = (short)points[i].x;
    168 		final_points[i].y = (short)points[i].y;
    169 	}
    170 	XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
    171 	XFillPolygon(s->window->renderdata->dpy, s->d, s->window->gc, final_points, (int)npoints, Complex, CoordModeOrigin);
    172 	if (npoints > 6)
    173 		ltk_free(final_points);
    174 }
    175 
    176 void
    177 ltk_surface_fill_polygon_clipped(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints, ltk_rect clip) {
    178 	/* NOTE: XRectangle only uses short, so this could cause issues */
    179 	XRectangle xclip = {clip.x, clip.y, clip.w, clip.h};
    180 	XSetClipRectangles(s->window->renderdata->dpy, s->window->gc, 0, 0, &xclip, 1, Unsorted);
    181 	ltk_surface_fill_polygon(s, c, points, npoints);
    182 	XSetClipMask(s->window->renderdata->dpy, s->window->gc, None);
    183 }
    184 
    185 void
    186 ltk_surface_fill_ellipse(ltk_surface *s, ltk_color *c, ltk_rect rect) {
    187 	XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
    188 	XFillArc(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, rect.h, 0, 360 * 64);
    189 }
    190 
    191 void
    192 ltk_surface_fill_ellipse_clipped(ltk_surface *s, ltk_color *c, ltk_rect rect, ltk_rect clip) {
    193 	/* NOTE: XRectangle only uses short, so this could cause issues */
    194 	XRectangle xclip = {clip.x, clip.y, clip.w, clip.h};
    195 	XSetClipRectangles(s->window->renderdata->dpy, s->window->gc, 0, 0, &xclip, 1, Unsorted);
    196 	ltk_surface_fill_ellipse(s, c, rect);
    197 	XSetClipMask(s->window->renderdata->dpy, s->window->gc, None);
    198 }
    199 
    200 void
    201 ltk_surface_copy(ltk_surface *src, ltk_surface *dst, ltk_rect src_rect, int dst_x, int dst_y) {
    202 	XCopyArea(
    203 	    src->window->renderdata->dpy, src->d, dst->d, src->window->gc,
    204 	    src_rect.x, src_rect.y, src_rect.w, src_rect.h, dst_x, dst_y
    205 	);
    206 }
    207 
    208 /* TODO */
    209 /*
    210 void
    211 ltk_surface_draw_arc(ltk_surface *s, ltk_color *c, int x, int y, int w, int h, int angle1, int angle2, int line_width) {
    212 }
    213 
    214 void
    215 ltk_surface_fill_arc(ltk_surface *s, ltk_color *c, int x, int y, int w, int h, int angle1, int angle2) {
    216 }
    217 
    218 void
    219 ltk_surface_draw_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r, int line_width) {
    220 }
    221 
    222 void
    223 ltk_surface_fill_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r) {
    224 }
    225 */
    226 
    227 /* FIXME: move this to a file where it makes more sense */
    228 /* blatantly stolen from st */
    229 static void ximinstantiate(Display *dpy, XPointer client, XPointer call);
    230 static void ximdestroy(XIM xim, XPointer client, XPointer call);
    231 static int xicdestroy(XIC xim, XPointer client, XPointer call);
    232 static int ximopen(ltk_renderwindow *window);
    233 
    234 static void
    235 ximdestroy(XIM xim, XPointer client, XPointer call) {
    236 	(void)xim;
    237 	(void)call;
    238 	ltk_renderwindow *window = (ltk_renderwindow *)client;
    239 	window->xim = NULL;
    240 	XRegisterIMInstantiateCallback(
    241 	    window->renderdata->dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)window
    242 	);
    243 	XFree(window->spotlist);
    244 }
    245 
    246 static int
    247 xicdestroy(XIC xim, XPointer client, XPointer call) {
    248 	(void)xim;
    249 	(void)call;
    250 	ltk_renderwindow *window = (ltk_renderwindow *)client;
    251 	window->xic = NULL;
    252 	return 1;
    253 }
    254 
    255 static int
    256 ximopen(ltk_renderwindow *window) {
    257 	XIMCallback imdestroy = { .client_data = (XPointer)window, .callback = ximdestroy };
    258 	XICCallback icdestroy = { .client_data = (XPointer)window, .callback = xicdestroy };
    259 
    260 	window->xim = XOpenIM(window->renderdata->dpy, NULL, NULL, NULL);
    261 	if (window->xim == NULL)
    262 		return 0;
    263 
    264 	if (XSetIMValues(window->xim, XNDestroyCallback, &imdestroy, NULL))
    265 		ltk_warn("XSetIMValues: Could not set XNDestroyCallback.\n");
    266 
    267 	window->spotlist = XVaCreateNestedList(0, XNSpotLocation, &window->spot, NULL);
    268 
    269 	if (window->xic == NULL) {
    270 		window->xic = XCreateIC(
    271 		    window->xim, XNInputStyle,
    272 		    XIMPreeditNothing | XIMStatusNothing,
    273 		    XNClientWindow, window->xwindow,
    274 		    XNDestroyCallback, &icdestroy, NULL
    275 		);
    276 	}
    277 	if (window->xic == NULL)
    278 		ltk_warn("XCreateIC: Could not create input context.\n");
    279 
    280 	return 1;
    281 }
    282 
    283 static void
    284 ximinstantiate(Display *dpy, XPointer client, XPointer call) {
    285 	(void)call;
    286 	ltk_renderwindow *window = (ltk_renderwindow *)client;
    287 	if (ximopen(window)) {
    288 		XUnregisterIMInstantiateCallback(
    289 		    dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)window
    290 		);
    291 	}
    292 }
    293 
    294 void
    295 ltk_renderer_set_imspot(ltk_renderwindow *window, int x, int y) {
    296 	if (window->xic == NULL)
    297 		return;
    298 	window->spot.x = x;
    299 	window->spot.y = y;
    300 	XSetICValues(window->xic, XNPreeditAttributes, window->spotlist, NULL);
    301 }
    302 
    303 ltk_renderdata *
    304 ltk_renderer_create(void) {
    305 	/* FIXME: this might not be the best place for this */
    306 	XSetLocaleModifiers("");
    307 	ltk_renderdata *renderdata = ltk_malloc(sizeof(ltk_renderdata));
    308 	renderdata->dpy = XOpenDisplay(NULL);
    309 	renderdata->screen = DefaultScreen(renderdata->dpy);
    310 	renderdata->db_enabled = 0;
    311 	/* based on http://wili.cc/blog/xdbe.html */
    312 	int major, minor;
    313 	if (XdbeQueryExtension(renderdata->dpy, &major, &minor)) {
    314 		int num_screens = 1;
    315 		Drawable screens[] = {DefaultRootWindow(renderdata->dpy)};
    316 		XdbeScreenVisualInfo *info = XdbeGetVisualInfo(
    317 		    renderdata->dpy, screens, &num_screens
    318 		);
    319 		if (!info || num_screens < 1 || info->count < 1) {
    320 			ltk_fatal("No visuals support Xdbe.");
    321 		}
    322 		XVisualInfo xvisinfo_templ;
    323 		/* we know there's at least one */
    324 		xvisinfo_templ.visualid = info->visinfo[0].visual;
    325 		/* FIXME: proper screen number? */
    326 		xvisinfo_templ.screen = 0;
    327 		xvisinfo_templ.depth = info->visinfo[0].depth;
    328 		int matches;
    329 		XVisualInfo *xvisinfo_match = XGetVisualInfo(
    330 		    renderdata->dpy,
    331 		    VisualIDMask | VisualScreenMask | VisualDepthMask,
    332 		    &xvisinfo_templ, &matches
    333 		);
    334 		if (!xvisinfo_match || matches < 1) {
    335 			ltk_fatal("Couldn't match a Visual with double buffering.\n");
    336 		}
    337 		renderdata->vis = xvisinfo_match->visual;
    338 		/* FIXME: is it legal to free this while keeping the visual? */
    339 		XFree(xvisinfo_match);
    340 		XdbeFreeVisualInfo(info);
    341 		renderdata->db_enabled = 1;
    342 	} else {
    343 		renderdata->vis = DefaultVisual(renderdata->dpy, renderdata->screen);
    344 		ltk_warn("No Xdbe support.\n");
    345 	}
    346 	renderdata->cm = DefaultColormap(renderdata->dpy, renderdata->screen);
    347 	renderdata->wm_delete_msg = XInternAtom(renderdata->dpy, "WM_DELETE_WINDOW", False);
    348 	renderdata->depth = DefaultDepth(renderdata->dpy, renderdata->screen);
    349 	renderdata->xkb_supported = 1;
    350 	renderdata->xkb_event_type = 0;
    351 	/* FIXME: check version */
    352 	if (!XkbQueryExtension(renderdata->dpy, 0, &renderdata->xkb_event_type, NULL, &major, &minor)) {
    353 		ltk_warn("XKB not supported.\n");
    354 		renderdata->xkb_supported = 0;
    355 	} else {
    356 		/* This should select the events when the keyboard mapping changes.
    357 		 * When e.g. 'setxkbmap us' is executed, two events are sent, but I
    358 		 * haven't figured out how to change that. When the xkb layout
    359 		 * switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'),
    360 		 * this issue does not occur because only a state event is sent. */
    361 		XkbSelectEvents(
    362 		    renderdata->dpy, XkbUseCoreKbd,
    363 		    XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask
    364 		);
    365 		XkbSelectEventDetails(
    366 		    renderdata->dpy, XkbUseCoreKbd, XkbStateNotify,
    367 		    XkbAllStateComponentsMask, XkbGroupStateMask
    368 		);
    369 	}
    370 	renderdata->monitors = NULL;
    371 	renderdata->xrandr_supported = 0;
    372 	renderdata->root_window = XRootWindow(renderdata->dpy, renderdata->screen);
    373 #if USE_XRANDR
    374 	if (!XRRQueryVersion(renderdata->dpy, &major, &minor)) {
    375 		ltk_warn("XRandR not supported.\n");
    376 	} else if (major < 1 || (major == 1 && minor < 5)) {
    377 		ltk_warn("X server only supports XRandR version %d.%d. Need at least 1.5.\n", major, minor);
    378 	} else {
    379 		/* FIXME: check if XRRQueryExtension allows passing NULL */
    380 		int tmp;
    381 		if (!XRRQueryExtension(renderdata->dpy, &renderdata->xrandr_event_type, &tmp)) {
    382 			ltk_warn("Unable to get XRandR event type.\n");
    383 		} else {
    384 			renderdata->xrandr_supported = 1;
    385 			/* FIXME: is this mask correct? */
    386 			XRRSelectInput(renderdata->dpy, renderdata->root_window, RRScreenChangeNotifyMask);
    387 		}
    388 	}
    389 
    390 #endif
    391 
    392 	return renderdata;
    393 }
    394 
    395 ltk_renderwindow *
    396 ltk_renderer_create_window(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h, unsigned int initial_dpi) {
    397 	XSetWindowAttributes attrs;
    398 	ltk_renderwindow *window = ltk_malloc(sizeof(ltk_renderwindow));
    399 	window->renderdata = data;
    400 	memset(&attrs, 0, sizeof(attrs));
    401 	attrs.background_pixel = BlackPixel(data->dpy, data->screen);
    402 	attrs.colormap = data->cm;
    403 	attrs.border_pixel = WhitePixel(data->dpy, data->screen);
    404 	/* this causes the window contents to be kept
    405 	 * when it is resized, leading to less flicker */
    406 	attrs.bit_gravity = NorthWestGravity;
    407 	attrs.event_mask =
    408 		ExposureMask | KeyPressMask | KeyReleaseMask |
    409 		ButtonPressMask | ButtonReleaseMask |
    410 		StructureNotifyMask | PointerMotionMask;
    411 	/* FIXME: conversion between signed and unsigned */
    412 	window->rect = (ltk_rect){x, y, w, h};
    413 	/* FIXME: set border width */
    414 	window->xwindow = XCreateWindow(
    415 	    data->dpy, DefaultRootWindow(data->dpy), x, y,
    416 	    w, h, 0, data->depth,
    417 	    InputOutput, data->vis,
    418 	    CWBackPixel | CWColormap | CWBitGravity | CWEventMask | CWBorderPixel, &attrs
    419 	);
    420 
    421 	if (data->db_enabled) {
    422 		window->back_buf = XdbeAllocateBackBufferName(
    423 			data->dpy, window->xwindow, XdbeBackground
    424 		);
    425 	} else {
    426 		window->back_buf = window->xwindow;
    427 	}
    428 	window->drawable = window->back_buf;
    429 	window->gc = XCreateGC(data->dpy, window->xwindow, 0, 0);
    430 	XSetStandardProperties(
    431 		data->dpy, window->xwindow,
    432 		title, NULL, None, NULL, 0, NULL
    433 	);
    434 	/* FIXME: check return value */
    435 	XSetWMProtocols(data->dpy, window->xwindow, &data->wm_delete_msg, 1);
    436 
    437 	window->xic = NULL;
    438 	window->xim = NULL;
    439 	if (!ximopen(window)) {
    440 		XRegisterIMInstantiateCallback(
    441 		    window->renderdata->dpy, NULL, NULL, NULL,
    442 		    ximinstantiate, (XPointer)window
    443 		);
    444 	}
    445 
    446 	XClearWindow(window->renderdata->dpy, window->xwindow);
    447 	XMapRaised(window->renderdata->dpy, window->xwindow);
    448 
    449 	window->dpi = initial_dpi;
    450 	ltk_recalc_renderwindow_dpi(window);
    451 
    452 	return window;
    453 }
    454 
    455 unsigned int
    456 ltk_renderer_get_window_dpi(ltk_renderwindow *window) {
    457 	return window->dpi;
    458 }
    459 
    460 void
    461 ltk_renderer_destroy_window(ltk_renderwindow *window) {
    462 	XFreeGC(window->renderdata->dpy, window->gc);
    463 	if (window->spotlist)
    464 		XFree(window->spotlist);
    465 	/* FIXME: destroy xim/xic? */
    466 	XDestroyWindow(window->renderdata->dpy, window->xwindow);
    467 	ltk_free(window);
    468 }
    469 
    470 void
    471 ltk_renderer_destroy(ltk_renderdata *renderdata) {
    472 	if (renderdata->monitors)
    473 		ltk_array_destroy(moninfo, renderdata->monitors);
    474 	XCloseDisplay(renderdata->dpy);
    475 	/* FIXME: destroy visual, wm_delete_msg, etc.? */
    476 	ltk_free(renderdata);
    477 }
    478 
    479 /* FIXME: this is a completely random collection of properties and should be
    480    changed to a more sensible list */
    481 void
    482 ltk_renderer_set_window_properties(ltk_renderwindow *window, ltk_color *bg) {
    483 	XSetWindowBackground(window->renderdata->dpy, window->xwindow, bg->xcolor.pixel);
    484 }
    485 
    486 void
    487 ltk_renderer_swap_buffers(ltk_renderwindow *window) {
    488 	XdbeSwapInfo swap_info;
    489 	swap_info.swap_window = window->xwindow;
    490 	swap_info.swap_action = XdbeBackground;
    491 	if (!XdbeSwapBuffers(window->renderdata->dpy, &swap_info, 1))
    492 		ltk_fatal("Unable to swap buffers.\n");
    493 	XFlush(window->renderdata->dpy);
    494 }
    495 
    496 unsigned long
    497 ltk_renderer_get_window_id(ltk_renderwindow *window) {
    498 	return (unsigned long)window->xwindow;
    499 }