URI:
       tgraphics_xlib.c - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tgraphics_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 }