From 5d2d1d818c613ad436ea8d4b434f1c819caf0cc8 Mon Sep 17 00:00:00 2001 From: veltza <106755522+veltza@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:17:15 +0300 Subject: [PATCH 1/7] sixel: prevent images from piling up (#149) Old images are automatically deleted if a new image is spawned over them. This prevents them from piling up and choking the terminal when viewing animated gifs. Now if you use the latest version of Chafa to view the gifs, it will set the transparency attribute (P2=1) to all sixel images regardless of whether they are transparent or not. This prevents the auto-delete from working because if the image is transparent, we can't delete any images behind it. The solution is that since Chafa fills the animation frames with an opaque black background color, we treat the images as non-transparent if they don't have any transparent pixels. This keeps the auto-delete running with the new Chafa. Although the solution works now, it may not be a long-term solution. --- sixel.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sixel.c b/sixel.c index ad6f6bc..208fc4c 100644 --- a/sixel.c +++ b/sixel.c @@ -261,10 +261,10 @@ sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, sixel_image_t *image = &st->image; int x, y; sixel_color_no_t *src; - sixel_color_t *dst; - int color; + sixel_color_t *dst, color; int w, h; int i, j, cols, numimages; + char trans; ImageList *im, *next, *tail; if (!image->data) @@ -311,7 +311,6 @@ sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, im->clipmask = NULL; im->cw = cw; im->ch = ch; - im->transparent = st->transparent; } if (!im || !im->pixels) { for (im = *newimages; im; im = next) { @@ -324,11 +323,15 @@ sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, return -1; } dst = (sixel_color_t *)im->pixels; - for (j = 0; j < im->height && y < h; j++, y++) { + for (trans = 0, j = 0; j < im->height && y < h; j++, y++) { src = st->image.data + image->width * y; - for (x = 0; x < w; x++) - *dst++ = st->image.palette[*src++]; + for (x = 0; x < w; x++) { + color = st->image.palette[*src++]; + trans |= (color == 0); + *dst++ = color; + } } + im->transparent = (st->transparent && trans); } return numimages; From aaaa59eb77be7eaaa05d6474ed0730e342a24b51 Mon Sep 17 00:00:00 2001 From: veltza <106755522+veltza@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:21:30 +0300 Subject: [PATCH 2/7] sixel: fix image deletion issue (#150) This fixes the current implementation, which does not delete an image if an application first erases the image and then spawns a new transparent image in its place. The reason it didn't work before was because the two operations were handled at different stages in the rendering pipeline. --- st.c | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/st.c b/st.c index 4721ee2..7451f87 100644 --- a/st.c +++ b/st.c @@ -2603,8 +2603,8 @@ strhandle(void) { defaultcs, "cursor" } }; #if SIXEL_PATCH - ImageList *im, *newimages, *next, *tail; - int i, x1, y1, x2, y2, numimages; + ImageList *im, *newimages, *next, *tail = NULL; + int i, x1, y1, x2, y2, y, numimages; int cx, cy; Line line; #if SCROLLBACK_PATCH || REFLOW_PATCH @@ -2729,15 +2729,33 @@ strhandle(void) y1 = newimages->y; x2 = x1 + newimages->cols; y2 = y1 + numimages; - if (newimages->transparent) { - for (tail = term.images; tail && tail->next; tail = tail->next); - } else { - for (tail = NULL, im = term.images; im; im = next) { + /* Delete the old images that are covered by the new image(s). We also need + * to check if they have already been deleted before adding the new ones. */ + if (term.images) { + char transparent[numimages]; + for (i = 0, im = newimages; im; im = im->next, i++) { + transparent[i] = im->transparent; + } + for (im = term.images; im; im = next) { next = im->next; - if (im->x >= x1 && im->x + im->cols <= x2 && - im->y >= y1 && im->y <= y2) { - delete_image(im); - continue; + if (im->y >= y1 && im->y < y2) { + y = im->y - scr; + if (y >= 0 && y < term.row && term.dirty[y]) { + line = term.line[y]; + j = MIN(im->x + im->cols, term.col); + for (i = im->x; i < j; i++) { + if (line[i].mode & ATTR_SIXEL) + break; + } + if (i == j) { + delete_image(im); + continue; + } + } + if (im->x >= x1 && im->x + im->cols <= x2 && !transparent[im->y - y1]) { + delete_image(im); + continue; + } } tail = im; } From e7bdaa65d7234524145b0e914532965d2276e54c Mon Sep 17 00:00:00 2001 From: lmccartneystar <115428651+lmccartneystar@users.noreply.github.com> Date: Sun, 29 Sep 2024 04:08:38 -0400 Subject: [PATCH 3/7] mouse shortcuts: use XK_ANY_MOD with altscreen mouse scrollback patch (#151) --- config.def.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.def.h b/config.def.h index c5914c3..0c9ab2c 100644 --- a/config.def.h +++ b/config.def.h @@ -378,8 +378,8 @@ static MouseShortcut mshortcuts[] = { { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, #endif // SCROLLBACK_MOUSE_PATCH #if SCROLLBACK_MOUSE_ALTSCREEN_PATCH || REFLOW_PATCH - { XK_NO_MOD, Button4, kscrollup, {.i = 1}, 0, S_PRI }, - { XK_NO_MOD, Button5, kscrolldown, {.i = 1}, 0, S_PRI }, + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, S_PRI }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, S_PRI }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"}, 0, S_ALT }, { XK_ANY_MOD, Button5, ttysend, {.s = "\005"}, 0, S_ALT }, #else From fe065cc366d882cae765a8b664a7be8f212db0c1 Mon Sep 17 00:00:00 2001 From: Utkarsh Verma Date: Tue, 1 Oct 2024 21:35:30 +0200 Subject: [PATCH 4/7] osc133: initial patch implementation (#127) * osc133: initial patch implementation * Specify dependency on reflow or scrollback patch --- config.def.h | 4 ++++ patch/osc133.c | 27 +++++++++++++++++++++++++++ patch/osc133.h | 1 + patch/x_include.c | 5 ++++- patch/x_include.h | 3 +++ patches.def.h | 7 +++++++ st.c | 22 ++++++++++++++++++++++ st.h | 3 +++ 8 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 patch/osc133.c create mode 100644 patch/osc133.h diff --git a/config.def.h b/config.def.h index 0c9ab2c..3e9db11 100644 --- a/config.def.h +++ b/config.def.h @@ -467,6 +467,10 @@ static Shortcut shortcuts[] = { #if INVERT_PATCH { TERMMOD, XK_X, invert, { 0 } }, #endif // INVERT_PATCH + #if OSC133_PATCH + { TERMMOD, XK_Z, scrolltoprompt, {.i = -1}, S_PRI }, + { TERMMOD, XK_X, scrolltoprompt, {.i = 1}, S_PRI }, + #endif // OSC133_PATCH }; /* diff --git a/patch/osc133.c b/patch/osc133.c new file mode 100644 index 0000000..6d72453 --- /dev/null +++ b/patch/osc133.c @@ -0,0 +1,27 @@ +void scrolltoprompt(const Arg *arg) { + int x, y; + #if REFLOW_PATCH + int top = term.scr - term.histf; + #else + int top = term.scr - term.histn; + #endif // REFLOW_PATCH + int bot = term.scr + term.row-1; + int dy = arg->i; + Line line; + + if (!dy || tisaltscr()) + return; + + for (y = dy; y >= top && y <= bot; y += dy) { + for (line = TLINE(y), x = 0; x < term.col; x++) { + if (line[x].mode & ATTR_FTCS_PROMPT) + goto scroll; + } + } + +scroll: + if (dy < 0) + kscrollup(&((Arg){ .i = -y })); + else + kscrolldown(&((Arg){ .i = y })); +} diff --git a/patch/osc133.h b/patch/osc133.h new file mode 100644 index 0000000..f5a6c45 --- /dev/null +++ b/patch/osc133.h @@ -0,0 +1 @@ +static void scrolltoprompt(const Arg *); diff --git a/patch/x_include.c b/patch/x_include.c index 73a709a..30b77da 100644 --- a/patch/x_include.c +++ b/patch/x_include.c @@ -46,4 +46,7 @@ #endif #if XRESOURCES_PATCH #include "xresources.c" -#endif \ No newline at end of file +#endif +#if OSC133_PATCH +#include "osc133.c" +#endif diff --git a/patch/x_include.h b/patch/x_include.h index b94317c..78290e1 100644 --- a/patch/x_include.h +++ b/patch/x_include.h @@ -41,3 +41,6 @@ #if XRESOURCES_PATCH #include "xresources.h" #endif +#if OSC133_PATCH +#include "osc133.h" +#endif diff --git a/patches.def.h b/patches.def.h index cb1b958..65d883c 100644 --- a/patches.def.h +++ b/patches.def.h @@ -301,6 +301,13 @@ */ #define OPENURLONCLICK_PATCH 0 +/* This patch allows jumping between prompts by utilizing the OSC 133 escape sequence + * emitted by shells. Must be used with either reflow or scrollback patch. + * + * https://codeberg.org/dnkl/foot#jumping-between-prompts + */ +#define OSC133_PATCH 0 + /* Reflow. * Allows st to be resized without cutting off text when the terminal window is made larger again. * Text wraps when the terminal window is made smaller. diff --git a/st.c b/st.c index 7451f87..7b25227 100644 --- a/st.c +++ b/st.c @@ -2699,6 +2699,25 @@ strhandle(void) tfulldirt(); } return; + #if OSC133_PATCH + case 133: + if (narg < 2) + break; + switch (*strescseq.args[1]) { + case 'A': + term.c.attr.mode |= ATTR_FTCS_PROMPT; + break; + /* We don't handle these arguments yet */ + case 'B': + case 'C': + case 'D': + break; + default: + fprintf(stderr, "erresc: unknown OSC 133 argument: %c\n", *strescseq.args[1]); + break; + } + return; + #endif // OSC133_PATCH } break; case 'k': /* old title set compatibility */ @@ -3449,6 +3468,9 @@ check_control_code: } tsetchar(u, &term.c.attr, term.c.x, term.c.y); + #if OSC133_PATCH + term.c.attr.mode &= ~ATTR_FTCS_PROMPT; + #endif // OSC133_PATCH term.lastc = u; if (width == 2) { diff --git a/st.h b/st.h index 95d1be1..cd6982e 100644 --- a/st.h +++ b/st.h @@ -70,6 +70,9 @@ enum glyph_attribute { ATTR_HIGHLIGHT = 1 << 17, #endif // KEYBOARDSELECT_PATCH ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + #if OSC133_PATCH + ATTR_FTCS_PROMPT = 1 << 18, /* OSC 133 ; A ST */ + #endif // OSC133_PATCH }; #if SIXEL_PATCH From 398aeb1cd6c8d1d4c8c5461bc7cc421c2c442302 Mon Sep 17 00:00:00 2001 From: Bakkeby Date: Wed, 21 Aug 2024 09:33:57 +0200 Subject: [PATCH 5/7] undercurl: support semicolons in SGR character attributes ref. #148 Back in May 2024 support for colons in SGR character attributes was added to allow both colons and semicolons to be used to separate the subparameters in SGR escape codes. The undercurl patch only read colons to separate parameters. This commit allows for semicolons to be used as well when using escape codes for undercurl. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html https://git.suckless.org/st/commit/5dbcca49263be094fc38159c297458ae323ef647.html https://st.suckless.org/patches/undercurl/ --- st.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/st.c b/st.c index 7b25227..0271109 100644 --- a/st.c +++ b/st.c @@ -170,7 +170,7 @@ static void csihandle(void); static void dcshandle(void); #endif // SIXEL_PATCH #if UNDERCURL_PATCH -static void readcolonargs(char **, int, int[][CAR_PER_ARG]); +static void readcolonargs(char **, int, int[][CAR_PER_ARG], int sep); #endif // UNDERCURL_PATCH static void csiparse(void); static void csireset(void); @@ -1483,20 +1483,21 @@ tnewline(int first_col) #if UNDERCURL_PATCH void -readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG]) +readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG], int sep) { int i = 0; + for (; i < CAR_PER_ARG; i++) params[cursor][i] = -1; - if (**p != ':') + if (**p != sep) return; char *np = NULL; i = 0; - while (**p == ':' && i < CAR_PER_ARG) { - while (**p == ':') + while (**p == sep && i < CAR_PER_ARG) { + while (**p == sep) (*p)++; params[cursor][i] = strtol(*p, &np, 10); *p = np; @@ -1528,11 +1529,13 @@ csiparse(void) v = -1; csiescseq.arg[csiescseq.narg++] = v; p = np; - #if UNDERCURL_PATCH - readcolonargs(&p, csiescseq.narg-1, csiescseq.carg); - #endif // UNDERCURL_PATCH if (sep == ';' && *p == ':') sep = ':'; /* allow override to colon once */ + #if UNDERCURL_PATCH + if (v == 4 || v == 58) + readcolonargs(&p, csiescseq.narg-1, csiescseq.carg, sep); + #endif // UNDERCURL_PATCH + if (*p != sep || csiescseq.narg == ESC_ARG_SIZ) break; p++; From c9390f2ca7a9e4b8ec3d9c72e40b90ac09c03940 Mon Sep 17 00:00:00 2001 From: Bakkeby Date: Tue, 1 Oct 2024 21:39:50 +0200 Subject: [PATCH 6/7] osc133 - changing default configuration options to Ctrl + PgUp/PgDown --- config.def.h | 4 ++-- patch/osc133.c | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config.def.h b/config.def.h index 3e9db11..952efed 100644 --- a/config.def.h +++ b/config.def.h @@ -468,8 +468,8 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_X, invert, { 0 } }, #endif // INVERT_PATCH #if OSC133_PATCH - { TERMMOD, XK_Z, scrolltoprompt, {.i = -1}, S_PRI }, - { TERMMOD, XK_X, scrolltoprompt, {.i = 1}, S_PRI }, + { ControlMask, XK_Page_Up, scrolltoprompt, {.i = -1}, S_PRI }, + { ControlMask, XK_Page_Down, scrolltoprompt, {.i = 1}, S_PRI }, #endif // OSC133_PATCH }; diff --git a/patch/osc133.c b/patch/osc133.c index 6d72453..6baa82a 100644 --- a/patch/osc133.c +++ b/patch/osc133.c @@ -1,4 +1,6 @@ -void scrolltoprompt(const Arg *arg) { +void +scrolltoprompt(const Arg *arg) +{ int x, y; #if REFLOW_PATCH int top = term.scr - term.histf; From 3f1a5ed034b65e8d9542e506bb7e85ed1fd4303c Mon Sep 17 00:00:00 2001 From: Bakkeby Date: Tue, 1 Oct 2024 23:17:07 +0200 Subject: [PATCH 7/7] Revert "undercurl: support semicolons in SGR character attributes ref. #148" This reverts commit 398aeb1cd6c8d1d4c8c5461bc7cc421c2c442302. --- st.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/st.c b/st.c index 0271109..7b25227 100644 --- a/st.c +++ b/st.c @@ -170,7 +170,7 @@ static void csihandle(void); static void dcshandle(void); #endif // SIXEL_PATCH #if UNDERCURL_PATCH -static void readcolonargs(char **, int, int[][CAR_PER_ARG], int sep); +static void readcolonargs(char **, int, int[][CAR_PER_ARG]); #endif // UNDERCURL_PATCH static void csiparse(void); static void csireset(void); @@ -1483,21 +1483,20 @@ tnewline(int first_col) #if UNDERCURL_PATCH void -readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG], int sep) +readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG]) { int i = 0; - for (; i < CAR_PER_ARG; i++) params[cursor][i] = -1; - if (**p != sep) + if (**p != ':') return; char *np = NULL; i = 0; - while (**p == sep && i < CAR_PER_ARG) { - while (**p == sep) + while (**p == ':' && i < CAR_PER_ARG) { + while (**p == ':') (*p)++; params[cursor][i] = strtol(*p, &np, 10); *p = np; @@ -1529,13 +1528,11 @@ csiparse(void) v = -1; csiescseq.arg[csiescseq.narg++] = v; p = np; + #if UNDERCURL_PATCH + readcolonargs(&p, csiescseq.narg-1, csiescseq.carg); + #endif // UNDERCURL_PATCH if (sep == ';' && *p == ':') sep = ':'; /* allow override to colon once */ - #if UNDERCURL_PATCH - if (v == 4 || v == 58) - readcolonargs(&p, csiescseq.narg-1, csiescseq.carg, sep); - #endif // UNDERCURL_PATCH - if (*p != sep || csiescseq.narg == ESC_ARG_SIZ) break; p++;