1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
/* 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 <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}
}
|