ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

graphics_xlib.c (19307B)


      1 /*
      2  * Copyright (c) 2022 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 <stdint.h>
     19 
     20 #include <X11/Xlib.h>
     21 #include <X11/Xutil.h>
     22 #include <X11/extensions/Xdbe.h>
     23 #include <X11/XKBlib.h>
     24 #include <X11/extensions/XKBrules.h>
     25 
     26 #include "color.h"
     27 #include "rect.h"
     28 #include "util.h"
     29 #include "memory.h"
     30 #include "compat.h"
     31 #include "xlib_shared.h"
     32 
     33 struct ltk_surface {
     34 	int w, h;
     35 	ltk_renderdata *renderdata;
     36 	Drawable d;
     37 	#if USE_XFT == 1
     38 	XftDraw *xftdraw;
     39 	#endif
     40 	char resizable;
     41 };
     42 
     43 ltk_surface *
     44 ltk_surface_create(ltk_renderdata *renderdata, int w, int h) {
     45 	ltk_surface *s = ltk_malloc(sizeof(ltk_surface));
     46 	if (w <= 0)
     47 		w = 1;
     48 	if (h <= 0)
     49 		h = 1;
     50 	s->w = w;
     51 	s->h = h;
     52 	s->renderdata = renderdata;
     53 	s->d = XCreatePixmap(renderdata->dpy, renderdata->xwindow, w, h, renderdata->depth);
     54 	#if USE_XFT == 1
     55 	s->xftdraw = XftDrawCreate(renderdata->dpy, s->d, renderdata->vis, renderdata->cm);
     56 	#endif
     57 	s->resizable = 1;
     58 	return s;
     59 }
     60 
     61 ltk_surface *
     62 ltk_surface_from_window(ltk_renderdata *renderdata, int w, int h) {
     63 	ltk_surface *s = ltk_malloc(sizeof(ltk_surface));
     64 	s->w = w;
     65 	s->h = h;
     66 	s->renderdata = renderdata;
     67 	s->d = renderdata->drawable;
     68 	#if USE_XFT == 1
     69 	s->xftdraw = XftDrawCreate(renderdata->dpy, s->d, renderdata->vis, renderdata->cm);
     70 	#endif
     71 	s->resizable = 0;
     72 	return s;
     73 }
     74 
     75 void
     76 ltk_surface_destroy(ltk_surface *s) {
     77 	#if USE_XFT == 1
     78 	XftDrawDestroy(s->xftdraw);
     79 	#endif
     80 	if (s->resizable)
     81 		XFreePixmap(s->renderdata->dpy, (Pixmap)s->d);
     82 	ltk_free(s);
     83 }
     84 
     85 void
     86 ltk_surface_update_size(ltk_surface *s, int w, int h) {
     87 	/* FIXME: maybe return directly if surface is resizable? */
     88 	s->w = w;
     89 	s->h = h;
     90 }
     91 
     92 int
     93 ltk_surface_resize(ltk_surface *s, int w, int h) {
     94 	if (!s->resizable)
     95 		return 1;
     96 	s->w = w;
     97 	s->h = h;
     98 	XFreePixmap(s->renderdata->dpy, (Pixmap)s->d);
     99 	s->d = XCreatePixmap(s->renderdata->dpy, s->renderdata->xwindow, w, h, s->renderdata->depth);
    100 	#if USE_XFT == 1
    101 	XftDrawChange(s->xftdraw, s->d);
    102 	#endif
    103 	return 0;
    104 }
    105 
    106 void
    107 ltk_surface_get_size(ltk_surface *s, int *w, int *h) {
    108 	*w = s->w;
    109 	*h = s->h;
    110 }
    111 
    112 void
    113 ltk_surface_draw_rect(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width) {
    114 	XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
    115 	XSetLineAttributes(s->renderdata->dpy, s->renderdata->gc, line_width, LineSolid, CapButt, JoinMiter);
    116 	XDrawRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, rect.w, rect.h);
    117 }
    118 
    119 void
    120 ltk_surface_draw_border(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width, ltk_border_sides border_sides) {
    121 	/* drawn as rectangles to have proper control over line width - I'm not sure how exactly
    122 	   XDrawLine handles even line widths (i.e. on which side the extra pixel will be) */
    123 	XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
    124 	if (border_sides & LTK_BORDER_TOP)
    125 		XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, rect.w, line_width);
    126 	if (border_sides & LTK_BORDER_BOTTOM)
    127 		XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y + rect.h - line_width, rect.w, line_width);
    128 	if (border_sides & LTK_BORDER_LEFT)
    129 		XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, line_width, rect.h);
    130 	if (border_sides & LTK_BORDER_RIGHT)
    131 		XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x + rect.w - line_width, rect.y, line_width, rect.h);
    132 }
    133 
    134 void
    135 ltk_surface_draw_border_clipped(ltk_surface *s, ltk_color *c, ltk_rect rect, ltk_rect clip_rect, int line_width, ltk_border_sides border_sides) {
    136 	if (line_width <= 0)
    137 		return;
    138 	XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
    139 	int width;
    140 	ltk_rect final_rect = ltk_rect_intersect(rect, clip_rect);
    141 	if (border_sides & LTK_BORDER_TOP) {
    142 		width = rect.y - final_rect.y;
    143 		if (width > -line_width) {
    144 			width = line_width + width;
    145 			XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y, final_rect.w, width);
    146 		}
    147 	}
    148 	if (border_sides & LTK_BORDER_BOTTOM) {
    149 		width = (final_rect.y + final_rect.h) - (rect.y + rect.h);
    150 		if (width > -line_width) {
    151 			width = line_width + width;
    152 			XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y + final_rect.h - width, final_rect.w, width);
    153 		}
    154 	}
    155 	if (border_sides & LTK_BORDER_LEFT) {
    156 		width = rect.x - final_rect.x;
    157 		if (width > -line_width) {
    158 			width = line_width + width;
    159 			XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y, width, final_rect.h);
    160 		}
    161 	}
    162 	if (border_sides & LTK_BORDER_RIGHT) {
    163 		width = (final_rect.x + final_rect.w) - (rect.x + rect.w);
    164 		if (width > -line_width) {
    165 			width = line_width + width;
    166 			XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x + final_rect.w - width, final_rect.y, width, final_rect.h);
    167 		}
    168 	}
    169 }
    170 
    171 void
    172 ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect) {
    173 	XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
    174 	XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, rect.w, rect.h);
    175 }
    176 
    177 void
    178 ltk_surface_fill_polygon(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints) {
    179 	/* FIXME: maybe make this static since this won't be threaded anyways? */
    180 	XPoint tmp_points[6]; /* to avoid extra allocations when not necessary */
    181 	/* FIXME: this is ugly and inefficient */
    182 	XPoint *final_points;
    183 	if (npoints <= 6) {
    184 		final_points = tmp_points;
    185 	} else {
    186 		final_points = ltk_reallocarray(NULL, npoints, sizeof(XPoint));
    187 	}
    188 	/* FIXME: how to deal with ints that don't fit in short? */
    189 	for (size_t i = 0; i < npoints; i++) {
    190 		final_points[i].x = (short)points[i].x;
    191 		final_points[i].y = (short)points[i].y;
    192 	}
    193 	XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
    194 	XFillPolygon(s->renderdata->dpy, s->d, s->renderdata->gc, final_points, (int)npoints, Complex, CoordModeOrigin);
    195 	if (npoints > 6)
    196 		ltk_free(final_points);
    197 }
    198 
    199 static inline void
    200 swap_ptr(void **ptr1, void **ptr2) {
    201 	void *tmp = *ptr1;
    202 	*ptr1 = *ptr2;
    203 	*ptr2 = tmp;
    204 }
    205 
    206 #define check_size(cond) if (!(cond)) ltk_fatal("Unable to perform polygon clipping. This is a bug, tell lumidify about it.\n")
    207 
    208 /* FIXME: this can probably be optimized */
    209 /* This is basically Sutherland-Hodgman, but only the special case for clipping rectangles. */
    210 void
    211 ltk_surface_fill_polygon_clipped(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints, ltk_rect clip) {
    212 	/* FIXME: is this even more efficient? */
    213 	XPoint tmp_points1[12]; /* to avoid extra allocations when not necessary */
    214 	XPoint tmp_points2[12];
    215 	XPoint *points1;
    216 	XPoint *points2;
    217 	/* FIXME: be a bit smarter about this */
    218 	if (npoints <= 6) {
    219 		points1 = tmp_points1;
    220 		points2 = tmp_points2;
    221 	} else {
    222 		/* FIXME: I'm pretty sure there can never be more points than this
    223 		   since we're only clipping against a rectangle, right?
    224 		   If I can be sure about that, I can remove all the check_size's below. */
    225 		points1 = ltk_reallocarray(NULL, npoints, sizeof(XPoint) * 2);
    226 		points2 = ltk_reallocarray(NULL, npoints, sizeof(XPoint) * 2);
    227 	}
    228 
    229 	size_t num1 = npoints;
    230 	size_t num2 = 0;
    231 	for (size_t i = 0; i < npoints; i++) {
    232 		points1[i].x = (short)points[i].x;
    233 		points1[i].y = (short)points[i].y;
    234 	}
    235 
    236 	for (size_t i = 0; i < num1; i++) {
    237 		XPoint p1 = points1[i];
    238 		XPoint p2 = points1[(i + 1) % num1];
    239 		if (p1.x >= clip.x) {
    240 			check_size(num2 < npoints * 2);
    241 			points2[num2++] = p1;
    242 			if (p2.x < clip.x) {
    243 				check_size(num2 < npoints * 2);
    244 				points2[num2++] = (XPoint){.x = (short)clip.x, .y = (short)(p1.y + (p2.y - p1.y) * (float)(clip.x - p1.x) / (p2.x - p1.x))};
    245 			}
    246 		} else if (p2.x >= clip.x) {
    247 			check_size(num2 < npoints * 2);
    248 			points2[num2++] = (XPoint){.x = (short)clip.x, .y = (short)(p1.y + (p2.y - p1.y) * (float)(clip.x - p1.x) / (p2.x - p1.x))};
    249 		}
    250 	}
    251 	num1 = num2;
    252 	num2 = 0;
    253 	swap_ptr((void**)&points1, (void**)&points2);
    254 
    255 	for (size_t i = 0; i < num1; i++) {
    256 		XPoint p1 = points1[i];
    257 		XPoint p2 = points1[(i + 1) % num1];
    258 		if (p1.x <= clip.x + clip.w) {
    259 			check_size(num2 < npoints * 2);
    260 			points2[num2++] = p1;
    261 			if (p2.x > clip.x + clip.w) {
    262 				check_size(num2 < npoints * 2);
    263 				points2[num2++] = (XPoint){.x = (short)(clip.x + clip.w), .y = (short)(p1.y + (p2.y - p1.y) * (float)(clip.x + clip.w - p1.x) / (p2.x - p1.x))};
    264 			}
    265 		} else if (p2.x <= clip.x + clip.w) {
    266 			check_size(num2 < npoints * 2);
    267 			points2[num2++] = (XPoint){.x = (short)(clip.x + clip.w), .y = (short)(p1.y + (p2.y - p1.y) * (float)(clip.x + clip.w - p1.x) / (p2.x - p1.x))};
    268 		}
    269 	}
    270 	num1 = num2;
    271 	num2 = 0;
    272 	swap_ptr((void**)&points1, (void**)&points2);
    273 
    274 	for (size_t i = 0; i < num1; i++) {
    275 		XPoint p1 = points1[i];
    276 		XPoint p2 = points1[(i + 1) % num1];
    277 		if (p1.y >= clip.y) {
    278 			check_size(num2 < npoints * 2);
    279 			points2[num2++] = p1;
    280 			if (p2.y < clip.y) {
    281 				check_size(num2 < npoints * 2);
    282 				points2[num2++] = (XPoint){.y = (short)clip.y, .x = (short)(p1.x + (p2.x - p1.x) * (float)(clip.y - p1.y) / (p2.y - p1.y))};
    283 			}
    284 		} else if (p2.y >= clip.y) {
    285 			check_size(num2 < npoints * 2);
    286 			points2[num2++] = (XPoint){.y = (short)clip.y, .x = (short)(p1.x + (p2.x - p1.x) * (float)(clip.y - p1.y) / (p2.y - p1.y))};
    287 		}
    288 	}
    289 	num1 = num2;
    290 	num2 = 0;
    291 	swap_ptr((void**)&points1, (void**)&points2);
    292 
    293 	for (size_t i = 0; i < num1; i++) {
    294 		XPoint p1 = points1[i];
    295 		XPoint p2 = points1[(i + 1) % num1];
    296 		if (p1.y <= clip.y + clip.h) {
    297 			check_size(num2 < npoints * 2);
    298 			points2[num2++] = p1;
    299 			if (p2.y > clip.y + clip.h) {
    300 				check_size(num2 < npoints * 2);
    301 				points2[num2++] = (XPoint){.y = (short)clip.y + clip.h, .x = (short)(p1.x + (p2.x - p1.x) * (float)(clip.y + clip.h - p1.y) / (p2.y - p1.y))};
    302 			}
    303 		} else if (p2.y <= clip.y + clip.h) {
    304 			check_size(num2 < npoints * 2);
    305 			points2[num2++] = (XPoint){.y = (short)clip.y + clip.h, .x = (short)(p1.x + (p2.x - p1.x) * (float)(clip.y + clip.h - p1.y) / (p2.y - p1.y))};
    306 		}
    307 	}
    308 
    309 	if (num2 > 0) {
    310 		XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
    311 		XFillPolygon(s->renderdata->dpy, s->d, s->renderdata->gc, points2, (int)num2, Complex, CoordModeOrigin);
    312 	}
    313 	if (npoints > 6) {
    314 		ltk_free(points1);
    315 		ltk_free(points2);
    316 	}
    317 }
    318 
    319 void
    320 ltk_surface_copy(ltk_surface *src, ltk_surface *dst, ltk_rect src_rect, int dst_x, int dst_y) {
    321 	XCopyArea(
    322 	    src->renderdata->dpy, src->d, dst->d, src->renderdata->gc,
    323 	    src_rect.x, src_rect.y, src_rect.w, src_rect.h, dst_x, dst_y
    324 	);
    325 }
    326 
    327 /* TODO */
    328 /*
    329 void
    330 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) {
    331 }
    332 
    333 void
    334 ltk_surface_fill_arc(ltk_surface *s, ltk_color *c, int x, int y, int w, int h, int angle1, int angle2) {
    335 }
    336 
    337 void
    338 ltk_surface_draw_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r, int line_width) {
    339 }
    340 
    341 void
    342 ltk_surface_fill_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r) {
    343 }
    344 */
    345 
    346 #if USE_XFT == 1
    347 XftDraw *
    348 ltk_surface_get_xft_draw(ltk_surface *s) {
    349 	return s->xftdraw;
    350 }
    351 #endif
    352 
    353 Drawable
    354 ltk_surface_get_drawable(ltk_surface *s) {
    355 	return s->d;
    356 }
    357 
    358 /* FIXME: move this to a file where it makes more sense */
    359 /* blatantly stolen from st */
    360 static void ximinstantiate(Display *dpy, XPointer client, XPointer call);
    361 static void ximdestroy(XIM xim, XPointer client, XPointer call);
    362 static int xicdestroy(XIC xim, XPointer client, XPointer call);
    363 static int ximopen(ltk_renderdata *renderdata, Display *dpy);
    364 
    365 static void
    366 ximdestroy(XIM xim, XPointer client, XPointer call) {
    367 	(void)xim;
    368 	(void)call;
    369 	ltk_renderdata *renderdata = (ltk_renderdata *)client;
    370 	renderdata->xim = NULL;
    371 	XRegisterIMInstantiateCallback(
    372 	    renderdata->dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)renderdata
    373 	);
    374 	XFree(renderdata->spotlist);
    375 }
    376 
    377 static int
    378 xicdestroy(XIC xim, XPointer client, XPointer call) {
    379 	(void)xim;
    380 	(void)call;
    381 	ltk_renderdata *renderdata = (ltk_renderdata *)client;
    382 	renderdata->xic = NULL;
    383 	return 1;
    384 }
    385 
    386 static int
    387 ximopen(ltk_renderdata *renderdata, Display *dpy) {
    388 	XIMCallback imdestroy = { .client_data = (XPointer)renderdata, .callback = ximdestroy };
    389 	XICCallback icdestroy = { .client_data = (XPointer)renderdata, .callback = xicdestroy };
    390 
    391 	renderdata->xim = XOpenIM(dpy, NULL, NULL, NULL);
    392 	if (renderdata->xim == NULL)
    393 		return 0;
    394 
    395 	if (XSetIMValues(renderdata->xim, XNDestroyCallback, &imdestroy, NULL))
    396 		ltk_warn("XSetIMValues: Could not set XNDestroyCallback.\n");
    397 
    398 	renderdata->spotlist = XVaCreateNestedList(0, XNSpotLocation, &renderdata->spot, NULL);
    399 
    400 	if (renderdata->xic == NULL) {
    401 		renderdata->xic = XCreateIC(
    402 		    renderdata->xim, XNInputStyle,
    403 		    XIMPreeditNothing | XIMStatusNothing,
    404 		    XNClientWindow, renderdata->xwindow,
    405 		    XNDestroyCallback, &icdestroy, NULL
    406 		);
    407 	}
    408 	if (renderdata->xic == NULL)
    409 		ltk_warn("XCreateIC: Could not create input context.\n");
    410 
    411 	return 1;
    412 }
    413 
    414 static void
    415 ximinstantiate(Display *dpy, XPointer client, XPointer call) {
    416 	(void)call;
    417 	ltk_renderdata *renderdata = (ltk_renderdata *)client;
    418 	if (ximopen(renderdata, dpy)) {
    419 		XUnregisterIMInstantiateCallback(
    420 		    dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)renderdata
    421 		);
    422 	}
    423 }
    424 
    425 void
    426 renderer_set_imspot(ltk_renderdata *renderdata, int x, int y) {
    427 	if (renderdata->xic == NULL)
    428 		return;
    429 	/* FIXME! */
    430 	renderdata->spot.x = x;
    431 	renderdata->spot.y = y;
    432 	XSetICValues(renderdata->xic, XNPreeditAttributes, renderdata->spotlist, NULL);
    433 }
    434 
    435 ltk_renderdata *
    436 renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) {
    437 	XSetWindowAttributes attrs;
    438 	ltk_renderdata *renderdata = ltk_malloc(sizeof(ltk_renderdata));
    439 
    440 	renderdata->dpy = XOpenDisplay(NULL);
    441 	renderdata->screen = DefaultScreen(renderdata->dpy);
    442 	/* based on http://wili.cc/blog/xdbe.html */
    443 	int major, minor, found = 0;
    444 	if (XdbeQueryExtension(renderdata->dpy, &major, &minor)) {
    445 		int num_screens = 1;
    446 		Drawable screens[] = {DefaultRootWindow(renderdata->dpy)};
    447 		XdbeScreenVisualInfo *info = XdbeGetVisualInfo(
    448 		    renderdata->dpy, screens, &num_screens
    449 		);
    450 		if (!info || num_screens < 1 || info->count < 1) {
    451 			ltk_fatal("No visuals support Xdbe.");
    452 		}
    453 		XVisualInfo xvisinfo_templ;
    454 		/* we know there's at least one */
    455 		xvisinfo_templ.visualid = info->visinfo[0].visual;
    456 		/* FIXME: proper screen number? */
    457 		xvisinfo_templ.screen = 0;
    458 		xvisinfo_templ.depth = info->visinfo[0].depth;
    459 		int matches;
    460 		XVisualInfo *xvisinfo_match = XGetVisualInfo(
    461 		    renderdata->dpy,
    462 		    VisualIDMask | VisualScreenMask | VisualDepthMask,
    463 		    &xvisinfo_templ, &matches
    464 		);
    465 		if (!xvisinfo_match || matches < 1) {
    466 			ltk_fatal("Couldn't match a Visual with double buffering.\n");
    467 		}
    468 		renderdata->vis = xvisinfo_match->visual;
    469 		/* FIXME: is it legal to free this while keeping the visual? */
    470 		XFree(xvisinfo_match);
    471 		XdbeFreeVisualInfo(info);
    472 		found = 1;
    473 	} else {
    474 		renderdata->vis = DefaultVisual(renderdata->dpy, renderdata->screen);
    475 		ltk_warn("No Xdbe support.\n");
    476 	}
    477 	renderdata->cm = DefaultColormap(renderdata->dpy, renderdata->screen);
    478 	renderdata->wm_delete_msg = XInternAtom(renderdata->dpy, "WM_DELETE_WINDOW", False);
    479 
    480 	memset(&attrs, 0, sizeof(attrs));
    481 	attrs.background_pixel = BlackPixel(renderdata->dpy, renderdata->screen);
    482 	attrs.colormap = renderdata->cm;
    483 	attrs.border_pixel = WhitePixel(renderdata->dpy, renderdata->screen);
    484 	/* this causes the window contents to be kept
    485 	 * when it is resized, leading to less flicker */
    486 	attrs.bit_gravity = NorthWestGravity;
    487 	attrs.event_mask =
    488 		ExposureMask | KeyPressMask | KeyReleaseMask |
    489 		ButtonPressMask | ButtonReleaseMask |
    490 		StructureNotifyMask | PointerMotionMask;
    491 	renderdata->depth = DefaultDepth(renderdata->dpy, renderdata->screen);
    492 	/* FIXME: set border width */
    493 	renderdata->xwindow = XCreateWindow(
    494 	    renderdata->dpy, DefaultRootWindow(renderdata->dpy), x, y,
    495 	    w, h, 0, renderdata->depth,
    496 	    InputOutput, renderdata->vis,
    497 	    CWBackPixel | CWColormap | CWBitGravity | CWEventMask | CWBorderPixel, &attrs
    498 	);
    499 
    500 	if (found) {
    501 		renderdata->back_buf = XdbeAllocateBackBufferName(
    502 			renderdata->dpy, renderdata->xwindow, XdbeBackground
    503 		);
    504 	} else {
    505 		renderdata->back_buf = renderdata->xwindow;
    506 	}
    507 	renderdata->drawable = renderdata->back_buf;
    508 	renderdata->gc = XCreateGC(renderdata->dpy, renderdata->xwindow, 0, 0);
    509 	XSetStandardProperties(
    510 		renderdata->dpy, renderdata->xwindow,
    511 		title, NULL, None, NULL, 0, NULL
    512 	);
    513 	XSetWMProtocols(renderdata->dpy, renderdata->xwindow, &renderdata->wm_delete_msg, 1);
    514 
    515 	renderdata->xim = NULL;
    516 	renderdata->xic = NULL;
    517 	if (!ximopen(renderdata, renderdata->dpy)) {
    518 		XRegisterIMInstantiateCallback(
    519 		    renderdata->dpy, NULL, NULL, NULL,
    520 		    ximinstantiate, (XPointer)renderdata
    521 		);
    522 	}
    523 
    524 	XClearWindow(renderdata->dpy, renderdata->xwindow);
    525 	XMapRaised(renderdata->dpy, renderdata->xwindow);
    526 
    527 	renderdata->xkb_supported = 1;
    528 	renderdata->xkb_event_type = 0;
    529 	if (!XkbQueryExtension(renderdata->dpy, 0, &renderdata->xkb_event_type, NULL, &major, &minor)) {
    530 		ltk_warn("XKB not supported.\n");
    531 		renderdata->xkb_supported = 0;
    532 	} else {
    533 		/* This should select the events when the keyboard mapping changes.
    534 		 * When e.g. 'setxkbmap us' is executed, two events are sent, but I
    535 		 * haven't figured out how to change that. When the xkb layout
    536 		 * switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'),
    537 		 * this issue does not occur because only a state event is sent. */
    538 		XkbSelectEvents(
    539 		    renderdata->dpy, XkbUseCoreKbd,
    540 		    XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask
    541 		);
    542 		XkbSelectEventDetails(
    543 		    renderdata->dpy, XkbUseCoreKbd, XkbStateNotify,
    544 		    XkbAllStateComponentsMask, XkbGroupStateMask
    545 		);
    546 	}
    547 
    548 	return renderdata;
    549 }
    550 
    551 void
    552 renderer_destroy_window(ltk_renderdata *renderdata) {
    553 	XFreeGC(renderdata->dpy, renderdata->gc);
    554 	if (renderdata->spotlist)
    555 		XFree(renderdata->spotlist);
    556 	XDestroyWindow(renderdata->dpy, renderdata->xwindow);
    557 	XCloseDisplay(renderdata->dpy);
    558 	ltk_free(renderdata);
    559 }
    560 
    561 /* FIXME: this is a completely random collection of properties and should be
    562    changed to a more sensible list */
    563 void
    564 renderer_set_window_properties(ltk_renderdata *renderdata, ltk_color *bg) {
    565 	XSetWindowBackground(renderdata->dpy, renderdata->xwindow, bg->xcolor.pixel);
    566 }
    567 
    568 void
    569 renderer_swap_buffers(ltk_renderdata *renderdata) {
    570 	XdbeSwapInfo swap_info;
    571 	swap_info.swap_window = renderdata->xwindow;
    572 	swap_info.swap_action = XdbeBackground;
    573 	if (!XdbeSwapBuffers(renderdata->dpy, &swap_info, 1))
    574 		ltk_fatal("Unable to swap buffers.\n");
    575 	XFlush(renderdata->dpy);
    576 }
    577 
    578 unsigned long
    579 renderer_get_window_id(ltk_renderdata *renderdata) {
    580 	return (unsigned long)renderdata->xwindow;
    581 }