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 @@ -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 #include #include +#include +#include #include #include #include @@ -14,11 +16,13 @@ #include #include #include +#include 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 Buttonmask for Button - 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);