diff options
| author | shipwreckt <me@shipwreckt.co.uk> | 2025-10-16 22:13:02 +0100 |
|---|---|---|
| committer | shipwreckt <me@shipwreckt.co.uk> | 2025-10-16 22:13:02 +0100 |
| commit | e63a16b509b05993fc7900b6296ba8601e343976 (patch) | |
| tree | b9e1d57e09a111981427bafe3b3223384e358830 /files/config/suckless/st/st.c | |
| parent | e8b5675eb77aa20027f369ca278457b6a7c2e142 (diff) | |
More small changes, added thunderbird items.
Diffstat (limited to 'files/config/suckless/st/st.c')
| -rw-r--r-- | files/config/suckless/st/st.c | 268 |
1 files changed, 262 insertions, 6 deletions
diff --git a/files/config/suckless/st/st.c b/files/config/suckless/st/st.c index 03e10d1..f634c9a 100644 --- a/files/config/suckless/st/st.c +++ b/files/config/suckless/st/st.c @@ -19,6 +19,7 @@ #include "st.h" #include "win.h" +#include "graphics.h" #if defined(__linux) #include <pty.h> @@ -37,6 +38,14 @@ #define STR_ARG_SIZ ESC_ARG_SIZ #define HISTSIZE 2000 +static inline void tsetimgrow(Glyph *g, uint32_t row) { + g->u.img.row = row; +} + +/* 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) @@ -117,6 +126,8 @@ typedef struct { 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 */ Line hist[HISTSIZE]; /* history buffer */ @@ -220,7 +231,6 @@ static Rune utf8decodebyte(char, size_t *); 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); @@ -239,6 +249,10 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 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) { @@ -623,6 +637,12 @@ getsel(void) 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); } @@ -826,7 +846,11 @@ ttyread(void) { 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); @@ -838,7 +862,24 @@ ttyread(void) 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) @@ -884,6 +925,7 @@ ttywriteraw(const char *s, size_t n) 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. @@ -892,6 +934,9 @@ ttywriteraw(const char *s, size_t n) * 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); @@ -933,11 +978,16 @@ ttywriteraw(const char *s, size_t n) 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; @@ -1025,7 +1075,8 @@ treset(void) 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)); @@ -1048,7 +1099,9 @@ treset(void) 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(); } @@ -1309,12 +1362,104 @@ tclearregion(int x1, int y1, int x2, int y2) 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) { @@ -1433,6 +1578,7 @@ tsetattr(const int *attr, int l) 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; @@ -1445,6 +1591,20 @@ tsetattr(const int *attr, int l) 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 */ @@ -1468,6 +1628,7 @@ tsetattr(const int *attr, int l) break; case 24: term.c.attr.mode &= ~ATTR_UNDERLINE; + tsetdecorstyle(&term.c.attr, 0); break; case 25: term.c.attr.mode &= ~ATTR_BLINK; @@ -1495,6 +1656,13 @@ tsetattr(const int *attr, int l) 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; @@ -1878,6 +2046,39 @@ csihandle(void) 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; } } @@ -2027,8 +2228,26 @@ strhandle(void) 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; } @@ -2534,6 +2753,33 @@ check_control_code: 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; @@ -2708,6 +2954,8 @@ drawregion(int x1, int y1, int x2, int y2) { int y; + xstartimagedraw(term.dirty, term.row); + for (y = y1; y < y2; y++) { if (!term.dirty[y]) continue; @@ -2715,6 +2963,8 @@ drawregion(int x1, int y1, int x2, int y2) term.dirty[y] = 0; xdrawline(TLINE(y), x1, y, x2); } + + xfinishimagedraw(); } void @@ -2750,3 +3000,9 @@ redraw(void) tfulldirt(); draw(); } + +Glyph +getglyphat(int col, int row) +{ + return term.line[row][col]; +} |
