diff options
author | Philip Wittamore <philip@wittamore.com> | 2025-05-28 14:33:10 +0200 |
---|---|---|
committer | Philip Wittamore <philip@wittamore.com> | 2025-05-28 14:33:10 +0200 |
commit | b9d58d440eb8a7fc7f124f5f268d3dc25336c233 (patch) | |
tree | f5de63fa4dbd9713a06d824088f9f6b72d184fbd /patch-st-280525 | |
parent | 15c5d30c7ed6ca9f4411ea238f559612e488b8fc (diff) | |
download | diffs-master.tar.gz diffs-master.tar.bz2 diffs-master.zip |
Diffstat (limited to 'patch-st-280525')
-rw-r--r-- | patch-st-280525 | 1548 |
1 files changed, 1548 insertions, 0 deletions
diff --git a/patch-st-280525 b/patch-st-280525 new file mode 100644 index 0000000..135b6fc --- /dev/null +++ b/patch-st-280525 @@ -0,0 +1,1548 @@ +diff -u /src/MASTER/st/config.def.h /src/WORKING/st/config.def.h +--- /src/MASTER/st/config.def.h 2025-05-01 17:34:28.799888436 +0200 ++++ /src/WORKING/st/config.def.h 2025-05-21 14:09:06.805450577 +0200 +@@ -8,6 +8,13 @@ + static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; + static int borderpx = 2; + ++/* How to align the content in the window when the size of the terminal ++ * doesn't perfectly match the size of the window. The values are percentages. ++ * 50 means center, 0 means flush left/top, 100 means flush right/bottom. ++ */ ++static int anysize_halign = 50; ++static int anysize_valign = 50; ++ + /* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e +@@ -23,7 +30,8 @@ + char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + + /* identification sequence returned in DA and DECID */ +-char *vtiden = "\033[?6c"; ++/* By default, use the same one as kitty. */ ++char *vtiden = "\033[?62c"; + + /* Kerning / character bounding-box multipliers */ + static float cwscale = 1.0; +@@ -164,18 +172,46 @@ + static unsigned int defaultattr = 11; + + /* ++ * Graphics configuration ++ */ ++ ++/// The template for the cache directory. ++const char graphics_cache_dir_template[] = "/tmp/st-images-XXXXXX"; ++/// The max size of a single image file, in bytes. ++unsigned graphics_max_single_image_file_size = 20 * 1024 * 1024; ++/// The max size of the cache, in bytes. ++unsigned graphics_total_file_cache_size = 300 * 1024 * 1024; ++/// The max ram size of an image or placement, in bytes. ++unsigned graphics_max_single_image_ram_size = 100 * 1024 * 1024; ++/// The max total size of all images loaded into RAM. ++unsigned graphics_max_total_ram_size = 300 * 1024 * 1024; ++/// The max total number of image placements and images. ++unsigned graphics_max_total_placements = 4096; ++/// The ratio by which limits can be exceeded. This is to reduce the frequency ++/// of image removal. ++double graphics_excess_tolerance_ratio = 0.05; ++/// The minimum delay between redraws caused by animations, in milliseconds. ++unsigned graphics_animation_min_delay = 20; ++ ++/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ + static uint forcemousemod = ShiftMask; + ++/* Internal keyboard shortcuts. */ ++#define MODKEY Mod1Mask ++#define TERMMOD (ControlMask|ShiftMask) ++ + /* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ + static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ ++ { TERMMOD, Button3, previewimage, {.s = "feh"} }, ++ { TERMMOD, Button2, showimageinfo, {}, 1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, +@@ -183,10 +219,6 @@ + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, + }; + +-/* Internal keyboard shortcuts. */ +-#define MODKEY Mod1Mask +-#define TERMMOD (ControlMask|ShiftMask) +- + static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, +@@ -201,6 +233,10 @@ + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, ++ { TERMMOD, XK_F1, togglegrdebug, {.i = 0} }, ++ { TERMMOD, XK_F6, dumpgrstate, {.i = 0} }, ++ { TERMMOD, XK_F7, unloadimages, {.i = 0} }, ++ { TERMMOD, XK_F8, toggleimages, {.i = 0} }, + }; + + /* +Only in /src/WORKING/st/: config.h +diff -u /src/MASTER/st/config.mk /src/WORKING/st/config.mk +--- /src/MASTER/st/config.mk 2025-05-01 17:34:28.799888436 +0200 ++++ /src/WORKING/st/config.mk 2025-05-21 14:09:06.805450577 +0200 +@@ -14,9 +14,12 @@ + + # includes and libs + INCS = -I$(X11INC) \ ++ `$(PKG_CONFIG) --cflags imlib2` \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +-LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ ++LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ ++ `$(PKG_CONFIG) --libs imlib2` \ ++ `$(PKG_CONFIG) --libs zlib` \ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +Only in /src/MASTER/st/: .git +Only in /src/WORKING/st/: graphics.c +Only in /src/WORKING/st/: graphics.h +Only in /src/WORKING/st/: icat-mini.sh +Only in /src/WORKING/st/: khash.h +Only in /src/WORKING/st/: kvec.h +diff -u /src/MASTER/st/Makefile /src/WORKING/st/Makefile +--- /src/MASTER/st/Makefile 2025-05-01 17:34:28.799888436 +0200 ++++ /src/WORKING/st/Makefile 2025-05-21 14:09:06.805450577 +0200 +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c rowcolumn_diacritics_helpers.c graphics.c + OBJ = $(SRC:.c=.o) + + all: st +@@ -16,7 +16,7 @@ + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h graphics.h + + $(OBJ): config.h config.mk + +Only in /src/WORKING/st/: rowcolumn_diacritics_helpers.c +diff -u /src/MASTER/st/st.c /src/WORKING/st/st.c +--- /src/MASTER/st/st.c 2025-05-01 17:34:28.803221774 +0200 ++++ /src/WORKING/st/st.c 2025-05-21 14:09:06.808783912 +0200 +@@ -19,6 +19,7 @@ + + #include "st.h" + #include "win.h" ++#include "graphics.h" + + #if defined(__linux) + #include <pty.h> +@@ -36,6 +37,10 @@ + #define STR_BUF_SIZ ESC_BUF_SIZ + #define STR_ARG_SIZ ESC_ARG_SIZ + ++/* PUA character used as an image placeholder */ ++#define IMAGE_PLACEHOLDER_CHAR 0x10EEEE ++#define IMAGE_PLACEHOLDER_CHAR_OLD 0xEEEE ++ + /* macros */ + #define IS_SET(flag) ((term.mode & (flag)) != 0) + #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +@@ -113,6 +118,8 @@ + typedef struct { + int row; /* nb row */ + int col; /* nb col */ ++ int pixw; /* width of the text area in pixels */ ++ int pixh; /* height of the text area in pixels */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ +@@ -213,7 +220,6 @@ + static char utf8encodebyte(Rune, size_t); + static size_t utf8validate(Rune *, size_t); + +-static char *base64dec(const char *); + static char base64dec_getc(const char **); + + static ssize_t xwrite(int, const char *, size_t); +@@ -232,6 +238,10 @@ + static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; + static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + ++/* Converts a diacritic to a row/column/etc number. The result is 1-base, 0 ++ * means "couldn't convert". Defined in rowcolumn_diacritics_helpers.c */ ++uint16_t diacritic_to_num(uint32_t code); ++ + ssize_t + xwrite(int fd, const char *s, size_t len) + { +@@ -616,6 +626,12 @@ + if (gp->mode & ATTR_WDUMMY) + continue; + ++ if (gp->mode & ATTR_IMAGE) { ++ // TODO: Copy diacritics as well ++ ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr); ++ continue; ++ } ++ + ptr += utf8encode(gp->u, ptr); + } + +@@ -819,7 +835,11 @@ + { + static char buf[BUFSIZ]; + static int buflen = 0; +- int ret, written; ++ static int already_processing = 0; ++ int ret, written = 0; ++ ++ if (buflen >= LEN(buf)) ++ return 0; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); +@@ -831,7 +851,24 @@ + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; +- written = twrite(buf, buflen, 0); ++ if (already_processing) { ++ /* Avoid recursive call to twrite() */ ++ return ret; ++ } ++ already_processing = 1; ++ while (1) { ++ int buflen_before_processing = buflen; ++ written += twrite(buf + written, buflen - written, 0); ++ // If buflen changed during the call to twrite, there is ++ // new data, and we need to keep processing, otherwise ++ // we can exit. This will not loop forever because the ++ // buffer is limited, and we don't clean it in this ++ // loop, so at some point ttywrite will have to drop ++ // some data. ++ if (buflen_before_processing == buflen) ++ break; ++ } ++ already_processing = 0; + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) +@@ -874,6 +911,7 @@ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; ++ int retries_left = 100; + + /* + * Remember that we are using a pty, which might be a modem line. +@@ -882,6 +920,9 @@ + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { ++ if (retries_left-- <= 0) ++ goto too_many_retries; ++ + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); +@@ -923,11 +964,16 @@ + + write_error: + die("write error on tty: %s\n", strerror(errno)); ++too_many_retries: ++ fprintf(stderr, "Could not write %zu bytes to tty\n", n); + } + + void + ttyresize(int tw, int th) + { ++ term.pixw = tw; ++ term.pixh = th; ++ + struct winsize w; + + w.ws_row = term.row; +@@ -1015,7 +1061,8 @@ + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, +- .bg = defaultbg ++ .bg = defaultbg, ++ .decor = DECOR_DEFAULT_COLOR + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); +@@ -1038,7 +1085,9 @@ + void + tnew(int col, int row) + { +- term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; ++ term = (Term){.c = {.attr = {.fg = defaultfg, ++ .bg = defaultbg, ++ .decor = DECOR_DEFAULT_COLOR}}}; + tresize(col, row); + treset(); + } +@@ -1215,9 +1264,24 @@ + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + ++ if (u == ' ' && term.line[y][x].mode & ATTR_IMAGE && ++ tgetisclassicplaceholder(&term.line[y][x])) { ++ // This is a workaround: don't overwrite classic placement ++ // placeholders with space symbols (unlike Unicode placeholders ++ // which must be overwritten by anything). ++ term.line[y][x].bg = attr->bg; ++ term.dirty[y] = 1; ++ return; ++ } ++ + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; ++ ++ if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) { ++ term.line[y][x].u = 0; ++ term.line[y][x].mode |= ATTR_IMAGE; ++ } + } + + void +@@ -1244,12 +1308,104 @@ + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; ++ gp->decor = term.c.attr.decor; + gp->mode = 0; + gp->u = ' '; + } + } + } + ++/// Fills a rectangle area with an image placeholder. The starting point is the ++/// cursor. Adds empty lines if needed. The placeholder will be marked as ++/// classic. ++void ++tcreateimgplaceholder(uint32_t image_id, uint32_t placement_id, ++ int cols, int rows, char do_not_move_cursor) ++{ ++ for (int row = 0; row < rows; ++row) { ++ int y = term.c.y; ++ term.dirty[y] = 1; ++ for (int col = 0; col < cols; ++col) { ++ int x = term.c.x + col; ++ if (x >= term.col) ++ break; ++ Glyph *gp = &term.line[y][x]; ++ if (selected(x, y)) ++ selclear(); ++ gp->mode = ATTR_IMAGE; ++ gp->u = 0; ++ tsetimgrow(gp, row + 1); ++ tsetimgcol(gp, col + 1); ++ tsetimgid(gp, image_id); ++ tsetimgplacementid(gp, placement_id); ++ tsetimgdiacriticcount(gp, 3); ++ tsetisclassicplaceholder(gp, 1); ++ } ++ // If moving the cursor is not allowed and this is the last line ++ // of the terminal, we are done. ++ if (do_not_move_cursor && y == term.row - 1) ++ break; ++ // Move the cursor down, maybe creating a new line. The x is ++ // preserved (we never change term.c.x in the loop above). ++ if (row != rows - 1) ++ tnewline(/*first_col=*/0); ++ } ++ if (do_not_move_cursor) { ++ // Return the cursor to the original position. ++ tmoveto(term.c.x, term.c.y - rows + 1); ++ } else { ++ // Move the cursor beyond the last column, as required by the ++ // protocol. If the cursor goes beyond the screen edge, insert a ++ // newline to match the behavior of kitty. ++ if (term.c.x + cols >= term.col) ++ tnewline(/*first_col=*/1); ++ else ++ tmoveto(term.c.x + cols, term.c.y); ++ } ++} ++ ++void gr_for_each_image_cell(int (*callback)(void *data, uint32_t image_id, ++ uint32_t placement_id, int col, ++ int row, char is_classic), ++ void *data) { ++ for (int row = 0; row < term.row; ++row) { ++ for (int col = 0; col < term.col; ++col) { ++ Glyph *gp = &term.line[row][col]; ++ if (gp->mode & ATTR_IMAGE) { ++ uint32_t image_id = tgetimgid(gp); ++ uint32_t placement_id = tgetimgplacementid(gp); ++ int ret = ++ callback(data, tgetimgid(gp), ++ tgetimgplacementid(gp), ++ tgetimgcol(gp), tgetimgrow(gp), ++ tgetisclassicplaceholder(gp)); ++ if (ret == 1) { ++ term.dirty[row] = 1; ++ gp->mode = 0; ++ gp->u = ' '; ++ } ++ } ++ } ++ } ++} ++ ++void gr_schedule_image_redraw_by_id(uint32_t image_id) { ++ for (int row = 0; row < term.row; ++row) { ++ if (term.dirty[row]) ++ continue; ++ for (int col = 0; col < term.col; ++col) { ++ Glyph *gp = &term.line[row][col]; ++ if (gp->mode & ATTR_IMAGE) { ++ uint32_t cell_image_id = tgetimgid(gp); ++ if (cell_image_id == image_id) { ++ term.dirty[row] = 1; ++ break; ++ } ++ } ++ } ++ } ++} ++ + void + tdeletechar(int n) + { +@@ -1368,6 +1524,7 @@ + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; ++ term.c.attr.decor = DECOR_DEFAULT_COLOR; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; +@@ -1380,6 +1537,20 @@ + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; ++ if (i + 1 < l) { ++ idx = attr[++i]; ++ if (BETWEEN(idx, 1, 5)) { ++ tsetdecorstyle(&term.c.attr, idx); ++ } else if (idx == 0) { ++ term.c.attr.mode &= ~ATTR_UNDERLINE; ++ tsetdecorstyle(&term.c.attr, 0); ++ } else { ++ fprintf(stderr, ++ "erresc: unknown underline " ++ "style %d\n", ++ idx); ++ } ++ } + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ +@@ -1403,6 +1574,7 @@ + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; ++ tsetdecorstyle(&term.c.attr, 0); + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; +@@ -1430,6 +1602,13 @@ + case 49: + term.c.attr.bg = defaultbg; + break; ++ case 58: ++ if ((idx = tdefcolor(attr, &i, l)) >= 0) ++ tsetdecorcolor(&term.c.attr, idx); ++ break; ++ case 59: ++ tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR); ++ break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; +@@ -1817,6 +1996,39 @@ + goto unknown; + } + break; ++ case '>': ++ switch (csiescseq.mode[1]) { ++ case 'q': /* XTVERSION -- Print terminal name and version */ ++ len = snprintf(buf, sizeof(buf), ++ "\033P>|st-graphics(%s)\033\\", VERSION); ++ ttywrite(buf, len, 0); ++ break; ++ default: ++ goto unknown; ++ } ++ break; ++ case 't': /* XTWINOPS -- Window manipulation */ ++ switch (csiescseq.arg[0]) { ++ case 14: /* Report text area size in pixels. */ ++ len = snprintf(buf, sizeof(buf), "\033[4;%i;%it", ++ term.pixh, term.pixw); ++ ttywrite(buf, len, 0); ++ break; ++ case 16: /* Report character cell size in pixels. */ ++ len = snprintf(buf, sizeof(buf), "\033[6;%i;%it", ++ term.pixh / term.row, ++ term.pixw / term.col); ++ ttywrite(buf, len, 0); ++ break; ++ case 18: /* Report the size of the text area in characters. */ ++ len = snprintf(buf, sizeof(buf), "\033[8;%i;%it", ++ term.row, term.col); ++ ttywrite(buf, len, 0); ++ break; ++ default: ++ goto unknown; ++ } ++ break; + } + } + +@@ -1966,8 +2178,26 @@ + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; +- case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ ++ if (gr_parse_command(strescseq.buf, strescseq.len)) { ++ GraphicsCommandResult *res = &graphics_command_result; ++ if (res->create_placeholder) { ++ tcreateimgplaceholder( ++ res->placeholder.image_id, ++ res->placeholder.placement_id, ++ res->placeholder.columns, ++ res->placeholder.rows, ++ res->placeholder.do_not_move_cursor); ++ } ++ if (res->response[0]) ++ ttywrite(res->response, strlen(res->response), ++ 0); ++ if (res->redraw) ++ tfulldirt(); ++ return; ++ } ++ return; ++ case 'P': /* DCS -- Device Control String */ + case '^': /* PM -- Privacy Message */ + return; + } +@@ -2473,6 +2703,33 @@ + if (selected(term.c.x, term.c.y)) + selclear(); + ++ if (width == 0) { ++ // It's probably a combining char. Combining characters are not ++ // supported, so we just ignore them, unless it denotes the row and ++ // column of an image character. ++ if (term.c.y <= 0 && term.c.x <= 0) ++ return; ++ else if (term.c.x == 0) ++ gp = &term.line[term.c.y-1][term.col-1]; ++ else if (term.c.state & CURSOR_WRAPNEXT) ++ gp = &term.line[term.c.y][term.c.x]; ++ else ++ gp = &term.line[term.c.y][term.c.x-1]; ++ uint16_t num = diacritic_to_num(u); ++ if (num && (gp->mode & ATTR_IMAGE)) { ++ unsigned diaccount = tgetimgdiacriticcount(gp); ++ if (diaccount == 0) ++ tsetimgrow(gp, num); ++ else if (diaccount == 1) ++ tsetimgcol(gp, num); ++ else if (diaccount == 2) ++ tsetimg4thbyteplus1(gp, num); ++ tsetimgdiacriticcount(gp, diaccount + 1); ++ } ++ term.lastc = u; ++ return; ++ } ++ + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; +@@ -2639,6 +2896,8 @@ + { + int y; + ++ xstartimagedraw(term.dirty, term.row); ++ + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; +@@ -2646,6 +2905,8 @@ + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } ++ ++ xfinishimagedraw(); + } + + void +@@ -2680,3 +2941,9 @@ + tfulldirt(); + draw(); + } ++ ++Glyph ++getglyphat(int col, int row) ++{ ++ return term.line[row][col]; ++} +diff -u /src/MASTER/st/st.h /src/WORKING/st/st.h +--- /src/MASTER/st/st.h 2025-05-01 17:34:28.803221774 +0200 ++++ /src/WORKING/st/st.h 2025-05-21 14:09:06.808783912 +0200 +@@ -12,7 +12,7 @@ + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) + #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ +- (a).bg != (b).bg) ++ (a).bg != (b).bg || (a).decor != (b).decor) + #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) + #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) +@@ -20,6 +20,10 @@ + #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) + #define IS_TRUECOL(x) (1 << 24 & (x)) + ++// This decor color indicates that the fg color should be used. Note that it's ++// not a 24-bit color because the 25-th bit is not set. ++#define DECOR_DEFAULT_COLOR 0x0ffffff ++ + enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, +@@ -34,6 +38,7 @@ + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, ++ ATTR_IMAGE = 1 << 14, + }; + + enum selection_mode { +@@ -52,6 +57,14 @@ + SNAP_LINE = 2 + }; + ++enum underline_style { ++ UNDERLINE_STRAIGHT = 1, ++ UNDERLINE_DOUBLE = 2, ++ UNDERLINE_CURLY = 3, ++ UNDERLINE_DOTTED = 4, ++ UNDERLINE_DASHED = 5, ++}; ++ + typedef unsigned char uchar; + typedef unsigned int uint; + typedef unsigned long ulong; +@@ -65,6 +78,7 @@ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ ++ uint32_t decor; /* decoration (like underline) */ + } Glyph; + + typedef Glyph *Line; +@@ -105,6 +119,8 @@ + int selected(int, int); + char *getsel(void); + ++Glyph getglyphat(int, int); ++ + size_t utf8encode(Rune, char *); + + void *xmalloc(size_t); +@@ -124,3 +140,69 @@ + extern unsigned int defaultfg; + extern unsigned int defaultbg; + extern unsigned int defaultcs; ++ ++// Accessors to decoration properties stored in `decor`. ++// The 25-th bit is used to indicate if it's a 24-bit color. ++static inline uint32_t tgetdecorcolor(Glyph *g) { return g->decor & 0x1ffffff; } ++static inline uint32_t tgetdecorstyle(Glyph *g) { return (g->decor >> 25) & 0x7; } ++static inline void tsetdecorcolor(Glyph *g, uint32_t color) { ++ g->decor = (g->decor & ~0x1ffffff) | (color & 0x1ffffff); ++} ++static inline void tsetdecorstyle(Glyph *g, uint32_t style) { ++ g->decor = (g->decor & ~(0x7 << 25)) | ((style & 0x7) << 25); ++} ++ ++ ++// Some accessors to image placeholder properties stored in `u`: ++// - row (1-base) - 9 bits ++// - column (1-base) - 9 bits ++// - most significant byte of the image id plus 1 - 9 bits (0 means unspecified, ++// don't forget to subtract 1). ++// - the original number of diacritics (0, 1, 2, or 3) - 2 bits ++// - whether this is a classic (1) or Unicode (0) placeholder - 1 bit ++static inline uint32_t tgetimgrow(Glyph *g) { return g->u & 0x1ff; } ++static inline uint32_t tgetimgcol(Glyph *g) { return (g->u >> 9) & 0x1ff; } ++static inline uint32_t tgetimgid4thbyteplus1(Glyph *g) { return (g->u >> 18) & 0x1ff; } ++static inline uint32_t tgetimgdiacriticcount(Glyph *g) { return (g->u >> 27) & 0x3; } ++static inline uint32_t tgetisclassicplaceholder(Glyph *g) { return (g->u >> 29) & 0x1; } ++static inline void tsetimgrow(Glyph *g, uint32_t row) { ++ g->u = (g->u & ~0x1ff) | (row & 0x1ff); ++} ++static inline void tsetimgcol(Glyph *g, uint32_t col) { ++ g->u = (g->u & ~(0x1ff << 9)) | ((col & 0x1ff) << 9); ++} ++static inline void tsetimg4thbyteplus1(Glyph *g, uint32_t byteplus1) { ++ g->u = (g->u & ~(0x1ff << 18)) | ((byteplus1 & 0x1ff) << 18); ++} ++static inline void tsetimgdiacriticcount(Glyph *g, uint32_t count) { ++ g->u = (g->u & ~(0x3 << 27)) | ((count & 0x3) << 27); ++} ++static inline void tsetisclassicplaceholder(Glyph *g, uint32_t isclassic) { ++ g->u = (g->u & ~(0x1 << 29)) | ((isclassic & 0x1) << 29); ++} ++ ++/// Returns the full image id. This is a naive implementation, if the most ++/// significant byte is not specified, it's assumed to be 0 instead of inferring ++/// it from the cells to the left. ++static inline uint32_t tgetimgid(Glyph *g) { ++ uint32_t msb = tgetimgid4thbyteplus1(g); ++ if (msb != 0) ++ --msb; ++ return (msb << 24) | (g->fg & 0xFFFFFF); ++} ++ ++/// Sets the full image id. ++static inline void tsetimgid(Glyph *g, uint32_t id) { ++ g->fg = (id & 0xFFFFFF) | (1 << 24); ++ tsetimg4thbyteplus1(g, ((id >> 24) & 0xFF) + 1); ++} ++ ++static inline uint32_t tgetimgplacementid(Glyph *g) { ++ if (tgetdecorcolor(g) == DECOR_DEFAULT_COLOR) ++ return 0; ++ return g->decor & 0xFFFFFF; ++} ++ ++static inline void tsetimgplacementid(Glyph *g, uint32_t id) { ++ g->decor = (id & 0xFFFFFF) | (1 << 24); ++} +diff -u /src/MASTER/st/st.info /src/WORKING/st/st.info +--- /src/MASTER/st/st.info 2025-05-01 17:34:28.803221774 +0200 ++++ /src/WORKING/st/st.info 2025-05-21 14:09:06.808783912 +0200 +@@ -195,6 +195,7 @@ + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, ++ Smulx=\E[4:%p1%dm, + + st| simpleterm, + use=st-mono, +@@ -215,6 +216,11 @@ + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, ++# Underline colors ++ Su, ++ Setulc=\E[58:2:%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%d%;m, ++ Setulc1=\E[58:5:%p1%dm, ++ ol=\E[59m, + + st-meta| simpleterm with meta key, + use=st, +diff -u /src/MASTER/st/win.h /src/WORKING/st/win.h +--- /src/MASTER/st/win.h 2025-05-01 17:34:28.803221774 +0200 ++++ /src/WORKING/st/win.h 2025-05-21 14:09:06.808783912 +0200 +@@ -39,3 +39,6 @@ + void xsetsel(char *); + int xstartdraw(void); + void xximspot(int, int); ++ ++void xstartimagedraw(int *dirty, int rows); ++void xfinishimagedraw(); +diff -u /src/MASTER/st/x.c /src/WORKING/st/x.c +--- /src/MASTER/st/x.c 2025-05-01 17:34:28.803221774 +0200 ++++ /src/WORKING/st/x.c 2025-05-21 14:09:12.325454989 +0200 +@@ -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> +@@ -14,11 +16,13 @@ + #include <X11/keysym.h> + #include <X11/Xft/Xft.h> + #include <X11/XKBlib.h> ++#include <X11/Xresource.h> + + char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "graphics.h" + + /* types used in config.h */ + typedef struct { +@@ -59,6 +63,12 @@ + 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 +91,7 @@ + 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 +155,8 @@ + 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 +233,7 @@ + static XWindow xw; + static XSelection xsel; + static TermWindow win; ++static unsigned int mouse_col = 0, mouse_row = 0; + + /* Font Ring Cache */ + enum { +@@ -328,10 +342,72 @@ + 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 +415,7 @@ + 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 +528,9 @@ + /* 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 +818,9 @@ + 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 +951,8 @@ + 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 +1096,8 @@ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } +- defaultfontsize = usedfontsize; ++ if (defaultfontsize <= 0) ++ defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) +@@ -1024,7 +1107,7 @@ + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; +- if (fontsize == 0) ++ if (defaultfontsize <= 0 && fontsize == 0) + defaultfontsize = fontval; + } + +@@ -1152,8 +1235,8 @@ + 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 +1323,15 @@ + 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 +1353,11 @@ + 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 +1465,61 @@ + 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 +1609,17 @@ + + /* 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 +1632,68 @@ + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + +- /* Render the glyphs. */ +- XftDrawGlyphFontSpec(xw.draw, fg, specs, len); ++ /* 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; + +- /* Render underline and strikethrough. */ ++ /* 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) { +- XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, +- width, 1); ++ 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 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 +1708,11 @@ + + 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 +1728,10 @@ + 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 +1772,167 @@ + 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 +1999,8 @@ + 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 +2013,8 @@ + } + 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 +2239,7 @@ + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); ++ gr_deinit(); + exit(0); + } + } +@@ -1957,6 +2290,13 @@ + 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; +@@ -2023,6 +2363,118 @@ + } + } + ++ ++#define XRESOURCE_LOAD_META(NAME) \ ++ if(!XrmGetResource(xrdb, "st." NAME, "st." NAME, &type, &ret)) \ ++ XrmGetResource(xrdb, "*." NAME, "*." NAME, &type, &ret); \ ++ if (ret.addr != NULL && !strncmp("String", type, 64)) ++ ++#define XRESOURCE_LOAD_STRING(NAME, DST) \ ++ XRESOURCE_LOAD_META(NAME) \ ++ DST = ret.addr; ++ ++#define XRESOURCE_LOAD_CHAR(NAME, DST) \ ++ XRESOURCE_LOAD_META(NAME) \ ++ DST = ret.addr[0]; ++ ++#define XRESOURCE_LOAD_INTEGER(NAME, DST) \ ++ XRESOURCE_LOAD_META(NAME) \ ++ DST = strtoul(ret.addr, NULL, 10); ++ ++#define XRESOURCE_LOAD_FLOAT(NAME, DST) \ ++ XRESOURCE_LOAD_META(NAME) \ ++ DST = strtof(ret.addr, NULL); ++ ++void ++xrdb_load(void) ++{ ++ /* XXX */ ++ char *xrm; ++ char *type; ++ XrmDatabase xrdb; ++ XrmValue ret; ++ Display *dpy; ++ ++ if(!(dpy = XOpenDisplay(NULL))) ++ die("Can't open display\n"); ++ ++ XrmInitialize(); ++ xrm = XResourceManagerString(dpy); ++ ++ if (xrm != NULL) { ++ xrdb = XrmGetStringDatabase(xrm); ++ ++ /* handling colors here without macros to do via loop. */ ++ int i = 0; ++ char loadValue[12] = ""; ++ for (i = 0; i < 256; i++) ++ { ++ sprintf(loadValue, "%s%d", "st.color", i); ++ ++ if(!XrmGetResource(xrdb, loadValue, loadValue, &type, &ret)) ++ { ++ sprintf(loadValue, "%s%d", "*.color", i); ++ if (!XrmGetResource(xrdb, loadValue, loadValue, &type, &ret)) ++ /* reset if not found (unless in range for defaults). */ ++ if (i > 15) ++ colorname[i] = NULL; ++ } ++ ++ if (ret.addr != NULL && !strncmp("String", type, 64)) ++ colorname[i] = ret.addr; ++ } ++ ++ XRESOURCE_LOAD_STRING("foreground", colorname[defaultfg]); ++ XRESOURCE_LOAD_STRING("background", colorname[defaultbg]); ++ XRESOURCE_LOAD_STRING("cursorColor", colorname[defaultcs]) ++ else { ++ // this looks confusing because we are chaining off of the if ++ // in the macro. probably we should be wrapping everything blocks ++ // so this isn't possible... ++ defaultcs = defaultfg; ++ } ++ XRESOURCE_LOAD_STRING("reverse-cursor", colorname[defaultrcs]) ++ else { ++ // see above. ++ defaultrcs = defaultbg; ++ } ++ ++ XRESOURCE_LOAD_STRING("font", font); ++ XRESOURCE_LOAD_STRING("termname", termname); ++ ++ XRESOURCE_LOAD_INTEGER("blinktimeout", blinktimeout); ++ XRESOURCE_LOAD_INTEGER("bellvolume", bellvolume); ++ XRESOURCE_LOAD_INTEGER("borderpx", borderpx); ++ XRESOURCE_LOAD_INTEGER("cursorshape", cursorshape); ++ ++ XRESOURCE_LOAD_FLOAT("cwscale", cwscale); ++ XRESOURCE_LOAD_FLOAT("chscale", chscale); ++ } ++ XFlush(dpy); ++} ++ ++void ++reload(int sig) ++{ ++ xrdb_load(); ++ ++ /* colors, fonts */ ++ xloadcols(); ++ xunloadfonts(); ++ xloadfonts(font, 0); ++ ++ /* pretend the window just got resized */ ++ cresize(win.w, win.h); ++ ++ redraw(); ++ ++ /* triggers re-render if we're visible. */ ++ ttywrite("\033[O", 3, 1); ++ ++ signal(SIGUSR1, reload); ++} ++ ++ + void + usage(void) + { +@@ -2096,6 +2548,8 @@ + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); ++ xrdb_load(); ++ signal(SIGUSR1, reload); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); |