feat: kitty images patch applied

This commit is contained in:
2026-04-15 13:46:38 +02:00
parent 688f70add0
commit 67c54ec28b
15 changed files with 10000 additions and 58 deletions

411
x.c
View File

@@ -4,6 +4,8 @@
#include <limits.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
@@ -19,6 +21,7 @@ char *argv0;
#include "arg.h"
#include "st.h"
#include "win.h"
#include "graphics.h"
/* types used in config.h */
typedef struct {
@@ -59,6 +62,12 @@ static void zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
static void ttysend(const Arg *);
static void previewimage(const Arg *);
static void showimageinfo(const Arg *);
static void togglegrdebug(const Arg *);
static void dumpgrstate(const Arg *);
static void unloadimages(const Arg *);
static void toggleimages(const Arg *);
/* config.h for applying patches and the configuration. */
#include "config.h"
@@ -81,6 +90,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
typedef struct {
int tw, th; /* tty width and height */
int w, h; /* window width and height */
int hborderpx, vborderpx;
int ch; /* char height */
int cw; /* char width */
int mode; /* window state/mode flags */
@@ -144,6 +154,8 @@ static inline ushort sixd_to_16bit(int);
static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
static void xdrawglyph(Glyph, int, int);
static void xdrawimages(Glyph, Line, int x1, int y1, int x2);
static void xdrawoneimagecell(Glyph, int x, int y);
static void xclear(int, int, int, int);
static int xgeommasktogravity(int);
static int ximopen(Display *);
@@ -220,6 +232,7 @@ static DC dc;
static XWindow xw;
static XSelection xsel;
static TermWindow win;
static unsigned int mouse_col = 0, mouse_row = 0;
/* Font Ring Cache */
enum {
@@ -328,10 +341,72 @@ ttysend(const Arg *arg)
ttywrite(arg->s, strlen(arg->s), 1);
}
void
previewimage(const Arg *arg)
{
Glyph g = getglyphat(mouse_col, mouse_row);
if (g.mode & ATTR_IMAGE) {
uint32_t image_id = tgetimgid(&g);
fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n",
image_id, tgetimgplacementid(&g), tgetimgcol(&g),
tgetimgrow(&g));
gr_preview_image(image_id, arg->s);
}
}
void
showimageinfo(const Arg *arg)
{
Glyph g = getglyphat(mouse_col, mouse_row);
if (g.mode & ATTR_IMAGE) {
uint32_t image_id = tgetimgid(&g);
fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n",
image_id, tgetimgplacementid(&g), tgetimgcol(&g),
tgetimgrow(&g));
char stcommand[256] = {0};
size_t len = snprintf(stcommand, sizeof(stcommand), "%s -e less", argv0);
if (len > sizeof(stcommand) - 1) {
fprintf(stderr, "Executable name too long: %s\n",
argv0);
return;
}
gr_show_image_info(image_id, tgetimgplacementid(&g),
tgetimgcol(&g), tgetimgrow(&g),
tgetisclassicplaceholder(&g),
tgetimgdiacriticcount(&g), argv0);
}
}
void
togglegrdebug(const Arg *arg)
{
graphics_debug_mode = (graphics_debug_mode + 1) % 3;
redraw();
}
void
dumpgrstate(const Arg *arg)
{
gr_dump_state();
}
void
unloadimages(const Arg *arg)
{
gr_unload_images_to_reduce_ram();
}
void
toggleimages(const Arg *arg)
{
graphics_display_images = !graphics_display_images;
redraw();
}
int
evcol(XEvent *e)
{
int x = e->xbutton.x - borderpx;
int x = e->xbutton.x - win.hborderpx;
LIMIT(x, 0, win.tw - 1);
return x / win.cw;
}
@@ -339,7 +414,7 @@ evcol(XEvent *e)
int
evrow(XEvent *e)
{
int y = e->xbutton.y - borderpx;
int y = e->xbutton.y - win.vborderpx;
LIMIT(y, 0, win.th - 1);
return y / win.ch;
}
@@ -452,6 +527,9 @@ mouseaction(XEvent *e, uint release)
/* ignore Button<N>mask for Button<N> - it's set on release */
uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
mouse_col = evcol(e);
mouse_row = evrow(e);
for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
if (ms->release == release &&
ms->button == e->xbutton.button &&
@@ -739,6 +817,9 @@ cresize(int width, int height)
col = MAX(1, col);
row = MAX(1, row);
win.hborderpx = (win.w - col * win.cw) * anysize_halign / 100;
win.vborderpx = (win.h - row * win.ch) * anysize_valign / 100;
tresize(col, row);
xresize(col, row);
ttyresize(win.tw, win.th);
@@ -869,8 +950,8 @@ xhints(void)
sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
sizeh->height = win.h;
sizeh->width = win.w;
sizeh->height_inc = win.ch;
sizeh->width_inc = win.cw;
sizeh->height_inc = 1;
sizeh->width_inc = 1;
sizeh->base_height = 2 * borderpx;
sizeh->base_width = 2 * borderpx;
sizeh->min_height = win.ch + 2 * borderpx;
@@ -1014,7 +1095,8 @@ xloadfonts(const char *fontstr, double fontsize)
FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
usedfontsize = 12;
}
defaultfontsize = usedfontsize;
if (defaultfontsize <= 0)
defaultfontsize = usedfontsize;
}
if (xloadfont(&dc.font, pattern))
@@ -1024,7 +1106,7 @@ xloadfonts(const char *fontstr, double fontsize)
FcPatternGetDouble(dc.font.match->pattern,
FC_PIXEL_SIZE, 0, &fontval);
usedfontsize = fontval;
if (fontsize == 0)
if (defaultfontsize <= 0 && fontsize == 0)
defaultfontsize = fontval;
}
@@ -1152,8 +1234,8 @@ xinit(int cols, int rows)
xloadcols();
/* adjust fixed window geometry */
win.w = 2 * borderpx + cols * win.cw;
win.h = 2 * borderpx + rows * win.ch;
win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw;
win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch;
if (xw.gm & XNegative)
xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
if (xw.gm & YNegative)
@@ -1240,12 +1322,15 @@ xinit(int cols, int rows)
xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None)
xsel.xtarget = XA_STRING;
// Initialize the graphics (image display) module.
gr_init(xw.dpy, xw.vis, xw.cmap);
}
int
xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
{
float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp;
ushort mode, prevmode = USHRT_MAX;
Font *font = &dc.font;
int frcflags = FRC_NORMAL;
@@ -1267,6 +1352,11 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
if (mode == ATTR_WDUMMY)
continue;
/* Draw spaces for image placeholders (images will be drawn
* separately). */
if (mode & ATTR_IMAGE)
rune = ' ';
/* Determine font for glyph if different from previous glyph. */
if (prevmode != mode) {
prevmode = mode;
@@ -1374,11 +1464,61 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
return numspecs;
}
/* Draws a horizontal dashed line of length `w` starting at `(x, y)`. `wavelen`
* is the length of the dash plus the length of the gap. `fraction` is the
* fraction of the dash length compared to `wavelen`. */
static void
xdrawunderdashed(Draw draw, Color *color, int x, int y, int w,
int wavelen, float fraction, int thick)
{
int dashw = MAX(1, fraction * wavelen);
for (int i = x - x % wavelen; i < x + w; i += wavelen) {
int startx = MAX(i, x);
int endx = MIN(i + dashw, x + w);
if (startx < endx)
XftDrawRect(xw.draw, color, startx, y, endx - startx,
thick);
}
}
/* Draws an undercurl. `h` is the total height, including line thickness. */
static void
xdrawundercurl(Draw draw, Color *color, int x, int y, int w, int h, int thick)
{
XGCValues gcvals = {.foreground = color->pixel,
.line_width = thick,
.line_style = LineSolid,
.cap_style = CapRound};
GC gc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw),
GCForeground | GCLineWidth | GCLineStyle | GCCapStyle,
&gcvals);
XRectangle clip = {.x = x, .y = y, .width = w, .height = h};
XSetClipRectangles(xw.dpy, gc, 0, 0, &clip, 1, Unsorted);
int yoffset = thick / 2;
int segh = MAX(1, h - thick);
/* Make sure every segment is at a 45 degree angle, otherwise it doesn't
* look good without antialiasing. */
int segw = segh;
int wavelen = MAX(1, segw * 2);
for (int i = x - (x % wavelen); i < x + w; i += wavelen) {
XPoint points[3] = {{.x = i, .y = y + yoffset},
{.x = i + segw, .y = y + yoffset + segh},
{.x = i + wavelen, .y = y + yoffset}};
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), gc, points, 3,
CoordModeOrigin);
}
XFreeGC(xw.dpy, gc);
}
void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
{
int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch,
width = charlen * win.cw;
Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
XRenderColor colfg, colbg;
@@ -1468,17 +1608,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
/* Intelligent cleaning up of the borders. */
if (x == 0) {
xclear(0, (y == 0)? 0 : winy, borderpx,
xclear(0, (y == 0)? 0 : winy, win.hborderpx,
winy + win.ch +
((winy + win.ch >= borderpx + win.th)? win.h : 0));
((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
}
if (winx + width >= borderpx + win.tw) {
if (winx + width >= win.hborderpx + win.tw) {
xclear(winx + width, (y == 0)? 0 : winy, win.w,
((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
}
if (y == 0)
xclear(winx, 0, winx + width, borderpx);
if (winy + win.ch >= borderpx + win.th)
xclear(winx, 0, winx + width, win.vborderpx);
if (winy + win.ch >= win.vborderpx + win.th)
xclear(winx, winy + win.ch, winx + width, win.h);
/* Clean up the region we want to draw to. */
@@ -1491,18 +1631,68 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
r.width = width;
XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
/* Decoration color. */
Color decor;
uint32_t decorcolor = tgetdecorcolor(&base);
if (decorcolor == DECOR_DEFAULT_COLOR) {
decor = *fg;
} else if (IS_TRUECOL(decorcolor)) {
colfg.alpha = 0xffff;
colfg.red = TRUERED(decorcolor);
colfg.green = TRUEGREEN(decorcolor);
colfg.blue = TRUEBLUE(decorcolor);
XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &decor);
} else {
decor = dc.col[decorcolor];
}
decor.color.alpha = 0xffff;
decor.pixel |= 0xff << 24;
/* Float thickness, used as a base to compute other values. */
float fthick = dc.font.height / 18.0;
/* Integer thickness in pixels. Must not be 0. */
int thick = MAX(1, roundf(fthick));
/* The default gap between the baseline and a single underline. */
int gap = roundf(fthick * 2);
/* The total thickness of a double underline. */
int doubleh = thick * 2 + ceilf(fthick * 0.5);
/* The total thickness of an undercurl. */
int curlh = thick * 2 + roundf(fthick * 0.75);
/* Render the underline before the glyphs. */
if (base.mode & ATTR_UNDERLINE) {
uint32_t style = tgetdecorstyle(&base);
int liney = winy + dc.font.ascent + gap;
/* Adjust liney to guarantee that a single underline fits. */
liney -= MAX(0, liney + thick - (winy + win.ch));
if (style == UNDERLINE_DOUBLE) {
liney -= MAX(0, liney + doubleh - (winy + win.ch));
XftDrawRect(xw.draw, &decor, winx, liney, width, thick);
XftDrawRect(xw.draw, &decor, winx,
liney + doubleh - thick, width, thick);
} else if (style == UNDERLINE_DOTTED) {
xdrawunderdashed(xw.draw, &decor, winx, liney, width,
thick * 2, 0.5, thick);
} else if (style == UNDERLINE_DASHED) {
int wavelen = MAX(2, win.cw * 0.9);
xdrawunderdashed(xw.draw, &decor, winx, liney, width,
wavelen, 0.65, thick);
} else if (style == UNDERLINE_CURLY) {
liney -= MAX(0, liney + curlh - (winy + win.ch));
xdrawundercurl(xw.draw, &decor, winx, liney, width,
curlh, thick);
} else {
XftDrawRect(xw.draw, &decor, winx, liney, width, thick);
}
}
/* Render the glyphs. */
XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
/* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE) {
XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
width, 1);
}
/* Render strikethrough. Alway use the fg color. */
if (base.mode & ATTR_STRUCK) {
XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
width, 1);
XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
width, thick);
}
/* Reset clip to none. */
@@ -1517,6 +1707,11 @@ xdrawglyph(Glyph g, int x, int y)
numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
xdrawglyphfontspecs(&spec, g, numspecs, x, y);
if (g.mode & ATTR_IMAGE) {
gr_start_drawing(xw.buf, win.cw, win.ch);
xdrawoneimagecell(g, x, y);
gr_finish_drawing(xw.buf);
}
}
void
@@ -1532,6 +1727,10 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
if (IS_SET(MODE_HIDE))
return;
// If it's an image, just draw a ballot box for simplicity.
if (g.mode & ATTR_IMAGE)
g.u = 0x2610;
/*
* Select the right color for the right mode.
*/
@@ -1572,39 +1771,167 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
case 3: /* Blinking Underline */
case 4: /* Steady Underline */
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + (cy + 1) * win.ch - \
win.hborderpx + cx * win.cw,
win.vborderpx + (cy + 1) * win.ch - \
cursorthickness,
win.cw, cursorthickness);
break;
case 5: /* Blinking bar */
case 6: /* Steady bar */
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + cy * win.ch,
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
cursorthickness, win.ch);
break;
}
} else {
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + cy * win.ch,
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
win.cw - 1, 1);
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + cy * win.ch,
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
1, win.ch - 1);
XftDrawRect(xw.draw, &drawcol,
borderpx + (cx + 1) * win.cw - 1,
borderpx + cy * win.ch,
win.hborderpx + (cx + 1) * win.cw - 1,
win.vborderpx + cy * win.ch,
1, win.ch - 1);
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + (cy + 1) * win.ch - 1,
win.hborderpx + cx * win.cw,
win.vborderpx + (cy + 1) * win.ch - 1,
win.cw, 1);
}
}
/* Draw (or queue for drawing) image cells between columns x1 and x2 assuming
* that they have the same attributes (and thus the same lower 24 bits of the
* image ID and the same placement ID). */
void
xdrawimages(Glyph base, Line line, int x1, int y1, int x2) {
int y_pix = win.vborderpx + y1 * win.ch;
uint32_t image_id_24bits = base.fg & 0xFFFFFF;
uint32_t placement_id = tgetimgplacementid(&base);
// Columns and rows are 1-based, 0 means unspecified.
int last_col = 0;
int last_row = 0;
int last_start_col = 0;
int last_start_x = x1;
// The most significant byte is also 1-base, subtract 1 before use.
uint32_t last_id_4thbyteplus1 = 0;
// We may need to inherit row/column/4th byte from the previous cell.
Glyph *prev = &line[x1 - 1];
if (x1 > 0 && (prev->mode & ATTR_IMAGE) &&
(prev->fg & 0xFFFFFF) == image_id_24bits &&
prev->decor == base.decor) {
last_row = tgetimgrow(prev);
last_col = tgetimgcol(prev);
last_id_4thbyteplus1 = tgetimgid4thbyteplus1(prev);
last_start_col = last_col + 1;
}
for (int x = x1; x < x2; ++x) {
Glyph *g = &line[x];
uint32_t cur_row = tgetimgrow(g);
uint32_t cur_col = tgetimgcol(g);
uint32_t cur_id_4thbyteplus1 = tgetimgid4thbyteplus1(g);
uint32_t num_diacritics = tgetimgdiacriticcount(g);
// If the row is not specified, assume it's the same as the row
// of the previous cell. Note that `cur_row` may contain a
// value imputed earlier, which will be preserved if `last_row`
// is zero (i.e. we don't know the row of the previous cell).
if (last_row && (num_diacritics == 0 || !cur_row))
cur_row = last_row;
// If the column is not specified and the row is the same as the
// row of the previous cell, then assume that the column is the
// next one.
if (last_col && (num_diacritics <= 1 || !cur_col) &&
cur_row == last_row)
cur_col = last_col + 1;
// If the additional id byte is not specified and the
// coordinates are consecutive, assume the byte is also the
// same.
if (last_id_4thbyteplus1 &&
(num_diacritics <= 2 || !cur_id_4thbyteplus1) &&
cur_row == last_row && cur_col == last_col + 1)
cur_id_4thbyteplus1 = last_id_4thbyteplus1;
// If we couldn't infer row and column, start from the top left
// corner.
if (cur_row == 0)
cur_row = 1;
if (cur_col == 0)
cur_col = 1;
// If this cell breaks a contiguous stripe of image cells, draw
// that line and start a new one.
if (cur_col != last_col + 1 || cur_row != last_row ||
cur_id_4thbyteplus1 != last_id_4thbyteplus1) {
uint32_t image_id = image_id_24bits;
if (last_id_4thbyteplus1)
image_id |= (last_id_4thbyteplus1 - 1) << 24;
if (last_row != 0) {
int x_pix =
win.hborderpx + last_start_x * win.cw;
gr_append_imagerect(
xw.buf, image_id, placement_id,
last_start_col - 1, last_col,
last_row - 1, last_row, last_start_x,
y1, x_pix, y_pix, win.cw, win.ch,
base.mode & ATTR_REVERSE);
}
last_start_col = cur_col;
last_start_x = x;
}
last_row = cur_row;
last_col = cur_col;
last_id_4thbyteplus1 = cur_id_4thbyteplus1;
// Populate the missing glyph data to enable inheritance between
// runs and support the naive implementation of tgetimgid.
if (!tgetimgrow(g))
tsetimgrow(g, cur_row);
// We cannot save this information if there are > 511 cols.
if (!tgetimgcol(g) && (cur_col & ~0x1ff) == 0)
tsetimgcol(g, cur_col);
if (!tgetimgid4thbyteplus1(g))
tsetimg4thbyteplus1(g, cur_id_4thbyteplus1);
}
uint32_t image_id = image_id_24bits;
if (last_id_4thbyteplus1)
image_id |= (last_id_4thbyteplus1 - 1) << 24;
// Draw the last contiguous stripe.
if (last_row != 0) {
int x_pix = win.hborderpx + last_start_x * win.cw;
gr_append_imagerect(xw.buf, image_id, placement_id,
last_start_col - 1, last_col, last_row - 1,
last_row, last_start_x, y1, x_pix, y_pix,
win.cw, win.ch, base.mode & ATTR_REVERSE);
}
}
/* Draw just one image cell without inheriting attributes from the left. */
void xdrawoneimagecell(Glyph g, int x, int y) {
if (!(g.mode & ATTR_IMAGE))
return;
int x_pix = win.hborderpx + x * win.cw;
int y_pix = win.vborderpx + y * win.ch;
uint32_t row = tgetimgrow(&g) - 1;
uint32_t col = tgetimgcol(&g) - 1;
uint32_t placement_id = tgetimgplacementid(&g);
uint32_t image_id = tgetimgid(&g);
gr_append_imagerect(xw.buf, image_id, placement_id, col, col + 1, row,
row + 1, x, y, x_pix, y_pix, win.cw, win.ch,
g.mode & ATTR_REVERSE);
}
/* Prepare for image drawing. */
void xstartimagedraw(int *dirty, int rows) {
gr_start_drawing(xw.buf, win.cw, win.ch);
gr_mark_dirty_animations(dirty, rows);
}
/* Draw all queued image cells. */
void xfinishimagedraw() {
gr_finish_drawing(xw.buf);
}
void
xsetenv(void)
{
@@ -1671,6 +1998,8 @@ xdrawline(Line line, int x1, int y1, int x2)
new.mode ^= ATTR_REVERSE;
if (i > 0 && ATTRCMP(base, new)) {
xdrawglyphfontspecs(specs, base, i, ox, y1);
if (base.mode & ATTR_IMAGE)
xdrawimages(base, line, ox, y1, x);
specs += i;
numspecs -= i;
i = 0;
@@ -1683,6 +2012,8 @@ xdrawline(Line line, int x1, int y1, int x2)
}
if (i > 0)
xdrawglyphfontspecs(specs, base, i, ox, y1);
if (i > 0 && base.mode & ATTR_IMAGE)
xdrawimages(base, line, ox, y1, x);
}
void
@@ -1907,6 +2238,7 @@ cmessage(XEvent *e)
}
} else if (e->xclient.data.l[0] == xw.wmdeletewin) {
ttyhangup();
gr_deinit();
exit(0);
}
}
@@ -1957,6 +2289,13 @@ run(void)
if (XPending(xw.dpy))
timeout = 0; /* existing events might not set xfd */
/* Decrease the timeout if there are active animations. */
if (graphics_next_redraw_delay != INT_MAX &&
IS_SET(MODE_VISIBLE))
timeout = timeout < 0 ? graphics_next_redraw_delay
: MIN(timeout,
graphics_next_redraw_delay);
seltv.tv_sec = timeout / 1E3;
seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
tv = timeout >= 0 ? &seltv : NULL;