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 }