/* mu_x11.c -- an Xlib/Xft backend for microui. * * microui is renderer-agnostic: it emits a command list of clipped * rectangles, text runs and icons, and asks the host two questions about * font metrics. This answers those and draws the commands into an * off-screen pixmap, then blits it, so a frame never appears half-drawn. * * SPDX-License-Identifier: MIT (matching the vendored microui) */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "vendor/microui.h" #include "mu_x11.h" static Display *dpy; static Window win; static Visual *vis; static Colormap cmap; static XftFont *fnt; static XftDraw *xd; static Pixmap buf; static GC gc; static int bw, bh; int mux_text_width(mu_Font font, const char *s, int len) { (void)font; XGlyphInfo gi; if (len < 0) len = strlen(s); XftTextExtentsUtf8(dpy, fnt, (const FcChar8 *)s, len, &gi); return gi.xOff; } int mux_text_height(mu_Font font) { (void)font; return fnt->ascent + fnt->descent; } void mux_init(Display *d, Window w, const char *fontname) { dpy = d; win = w; int scr = DefaultScreen(dpy); vis = DefaultVisual(dpy, scr); cmap = DefaultColormap(dpy, scr); fnt = XftFontOpenName(dpy, scr, fontname); gc = XCreateGC(dpy, win, 0, NULL); buf = 0; xd = NULL; bw = bh = 0; } static void ensure_buffer(int w, int h) { if (buf && w == bw && h == bh) return; if (xd) { XftDrawDestroy(xd); xd = NULL; } if (buf) { XFreePixmap(dpy, buf); buf = 0; } buf = XCreatePixmap(dpy, win, w, h, DefaultDepth(dpy, DefaultScreen(dpy))); xd = XftDrawCreate(dpy, buf, vis, cmap); bw = w; bh = h; } static void xft_of(mu_Color c, XftColor *out) { XRenderColor rc = { c.r * 257, c.g * 257, c.b * 257, c.a * 257 }; XftColorAllocValue(dpy, vis, cmap, &rc, out); } static const char *icon_glyph(int id) { switch (id) { case MU_ICON_CLOSE: return "x"; case MU_ICON_CHECK: return "*"; case MU_ICON_COLLAPSED: return ">"; case MU_ICON_EXPANDED: return "v"; default: return "?"; } } /* CLAMP BEFORE CONVERTING. XRectangle's w/h are unsigned SHORT, and microui resets its scissor by emitting an "unclipped" rect of 0x1000000 -- which truncates to 0 in 16 bits, turning "clip nothing" into "clip everything". The symptom is that a panel draws its first screenful and the rest silently vanishes, which reads as a layout or buffer bug and is neither. */ static void set_clip(mu_Rect r) { int x = r.x, y = r.y, w = r.w, h = r.h; if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (w > bw - x) w = bw - x; if (h > bh - y) h = bh - y; if (w <= 0 || h <= 0) { w = 0; h = 0; } XRectangle xr = { (short)x, (short)y, (unsigned short)w, (unsigned short)h }; Region reg = XCreateRegion(); XUnionRectWithRegion(&xr, reg, reg); XftDrawSetClip(xd, reg); XDestroyRegion(reg); } void mux_present(mu_Context *ctx, int w, int h, mu_Color bg) { ensure_buffer(w, h); XftDrawSetClip(xd, NULL); XftColor col; xft_of(bg, &col); XftDrawRect(xd, &col, 0, 0, w, h); XftColorFree(dpy, vis, cmap, &col); mu_Command *cmd = NULL; while (mu_next_command(ctx, &cmd)) { switch (cmd->type) { case MU_COMMAND_RECT: xft_of(cmd->rect.color, &col); XftDrawRect(xd, &col, cmd->rect.rect.x, cmd->rect.rect.y, cmd->rect.rect.w, cmd->rect.rect.h); XftColorFree(dpy, vis, cmap, &col); break; case MU_COMMAND_TEXT: xft_of(cmd->text.color, &col); XftDrawStringUtf8(xd, &col, fnt, cmd->text.pos.x, cmd->text.pos.y + fnt->ascent, (const FcChar8 *)cmd->text.str, strlen(cmd->text.str)); XftColorFree(dpy, vis, cmap, &col); break; case MU_COMMAND_ICON: { const char *g = icon_glyph(cmd->icon.id); int tw = mux_text_width(NULL, g, 1); xft_of(cmd->icon.color, &col); XftDrawStringUtf8(xd, &col, fnt, cmd->icon.rect.x + (cmd->icon.rect.w - tw) / 2, cmd->icon.rect.y + fnt->ascent + (cmd->icon.rect.h - mux_text_height(NULL)) / 2, (const FcChar8 *)g, 1); XftColorFree(dpy, vis, cmap, &col); break; } case MU_COMMAND_CLIP: set_clip(cmd->clip.rect); break; } } XftDrawSetClip(xd, NULL); XCopyArea(dpy, buf, win, gc, 0, 0, w, h, 0, 0); XFlush(dpy); /* MUX_DUMP=path writes the frame out as a PPM and exits. Reading back the off-screen PIXMAP rather than the window is what makes this reliable: a window's contents are not retained when it is obscured or on an unfocused tag, so screenshotting one can return black even though the program is drawing perfectly. A pixmap is always there. */ { const char *path = getenv("MUX_DUMP"); if (!path) return; XImage *im = XGetImage(dpy, buf, 0, 0, w, h, AllPlanes, ZPixmap); if (!im) { fprintf(stderr, "MUX_DUMP: XGetImage failed\n"); exit(1); } FILE *f = fopen(path, "wb"); if (!f) { fprintf(stderr, "MUX_DUMP: cannot write %s\n", path); exit(1); } fprintf(f, "P6\n%d %d\n255\n", w, h); for (int yy = 0; yy < h; yy++) for (int xx = 0; xx < w; xx++) { unsigned long px = XGetPixel(im, xx, yy); unsigned char rgb[3] = { (unsigned char)((px >> 16) & 0xff), (unsigned char)((px >> 8) & 0xff), (unsigned char)(px & 0xff) }; fwrite(rgb, 1, 3, f); } fclose(f); XDestroyImage(im); fprintf(stderr, "MUX_DUMP: wrote %s (%dx%d)\n", path, w, h); exit(0); } }