From ba7f4d69af62d20e13fea78a408095e017410651 Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Thu, 10 Oct 2019 23:02:26 +0300 Subject: [PATCH 01/11] mouse shortcuts: allow same functions as kb shortcuts Previously mouse shortcuts supported only ttywrite. This required adding an "Arg" function ttysend - which does what the original mouse shortcuts did. --- config.def.h | 6 +++--- st.h | 1 + x.c | 20 ++++++++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/config.def.h b/config.def.h index 6ebea98..36ff6ce 100644 --- a/config.def.h +++ b/config.def.h @@ -155,9 +155,9 @@ static unsigned int defaultattr = 11; * Beware that overloading Button1 will disable the selection. */ static MouseShortcut mshortcuts[] = { - /* button mask string */ - { Button4, XK_ANY_MOD, "\031" }, - { Button5, XK_ANY_MOD, "\005" }, + /* mask button function argument */ + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, }; /* Internal keyboard shortcuts. */ diff --git a/st.h b/st.h index 4da3051..a1928ca 100644 --- a/st.h +++ b/st.h @@ -74,6 +74,7 @@ typedef union { uint ui; float f; const void *v; + const char *s; } Arg; void die(const char *, ...); diff --git a/x.c b/x.c index 5828a3b..2a05a81 100644 --- a/x.c +++ b/x.c @@ -29,9 +29,10 @@ typedef struct { } Shortcut; typedef struct { - uint b; - uint mask; - char *s; + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; } MouseShortcut; typedef struct { @@ -56,6 +57,7 @@ static void selpaste(const Arg *); static void zoom(const Arg *); static void zoomabs(const Arg *); static void zoomreset(const Arg *); +static void ttysend(const Arg *); /* config.h for applying patches and the configuration. */ #include "config.h" @@ -312,6 +314,12 @@ zoomreset(const Arg *arg) } } +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + int evcol(XEvent *e) { @@ -421,9 +429,9 @@ bpress(XEvent *e) } for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { - if (e->xbutton.button == ms->b - && match(ms->mask, e->xbutton.state)) { - ttywrite(ms->s, strlen(ms->s), 1); + if (e->xbutton.button == ms->button + && match(ms->mod, e->xbutton.state)) { + ms->func(&(ms->arg)); return; } } From b6d280de6df30167ce9cf30fadefc362e77729e7 Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Thu, 10 Oct 2019 23:42:30 +0300 Subject: [PATCH 02/11] mouse shortcuts: allow override for all shortcuts Allow forceselmod to override all mouse shortcuts rather than only selection, and rename it to forcemousemod as it's now more appropriate. This will affect mouse shortcuts which use mask other than XK_ANY_MOD. This does not affect the default behavior because the default mouse shortcuts (wheel) use XK_ANY_MOD, where forceselmod already activated the override also before this change. Previously, if a mouse shortcut was configured with a specific mod and forceselmod was held, then the shortcut did not execute unless the configured mod included forceselmod. --- config.def.h | 14 +++++++------- x.c | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/config.def.h b/config.def.h index 36ff6ce..a0a0d2d 100644 --- a/config.def.h +++ b/config.def.h @@ -150,6 +150,13 @@ static unsigned int mousebg = 0; */ static unsigned int defaultattr = 11; +/* + * 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 mouse shortcuts. * Beware that overloading Button1 will disable the selection. @@ -213,13 +220,6 @@ static KeySym mappedkeys[] = { -1 }; */ static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; -/* - * Override mouse-select 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 forceselmod = ShiftMask; - /* * This is the huge key array which defines all compatibility to the Linux * world. Please decide about changes wisely. diff --git a/x.c b/x.c index 2a05a81..c967caf 100644 --- a/x.c +++ b/x.c @@ -340,7 +340,7 @@ void mousesel(XEvent *e, int done) { int type, seltype = SEL_REGULAR; - uint state = e->xbutton.state & ~(Button1Mask | forceselmod); + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); for (type = 1; type < LEN(selmasks); ++type) { if (match(selmasks[type], state)) { @@ -423,14 +423,14 @@ bpress(XEvent *e) MouseShortcut *ms; int snap; - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { mousereport(e); return; } for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { - if (e->xbutton.button == ms->button - && match(ms->mod, e->xbutton.state)) { + if (e->xbutton.button == ms->button && + match(ms->mod, e->xbutton.state & ~forcemousemod)) { ms->func(&(ms->arg)); return; } @@ -650,7 +650,7 @@ xsetsel(char *str) void brelease(XEvent *e) { - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { mousereport(e); return; } @@ -664,7 +664,7 @@ brelease(XEvent *e) void bmotion(XEvent *e) { - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { mousereport(e); return; } From d2b75db8d7519a20af8bf09e9c205507f9ff828c Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Fri, 11 Oct 2019 02:26:10 +0300 Subject: [PATCH 03/11] mouse shortcuts: don't hardcode selpaste Because selpaste is activated on release, a release flag was added to mouse shortcuts which controls whether activation is on press/release, and selpaste binding to button2 was moved to config.h . button1 remains the only hardcoded mouse button - for selection + copy. --- config.def.h | 3 ++- x.c | 35 ++++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/config.def.h b/config.def.h index a0a0d2d..546edda 100644 --- a/config.def.h +++ b/config.def.h @@ -162,7 +162,8 @@ static uint forcemousemod = ShiftMask; * Beware that overloading Button1 will disable the selection. */ static MouseShortcut mshortcuts[] = { - /* mask button function argument */ + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, }; diff --git a/x.c b/x.c index c967caf..8f570c2 100644 --- a/x.c +++ b/x.c @@ -33,6 +33,7 @@ typedef struct { uint button; void (*func)(const Arg *); const Arg arg; + uint release; } MouseShortcut; typedef struct { @@ -165,6 +166,7 @@ static void kpress(XEvent *); static void cmessage(XEvent *); static void resize(XEvent *); static void focus(XEvent *); +static int mouseaction(XEvent *, uint); static void brelease(XEvent *); static void bpress(XEvent *); static void bmotion(XEvent *); @@ -416,11 +418,27 @@ mousereport(XEvent *e) ttywrite(buf, len, 0); } +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + match(ms->mod, e->xbutton.state & ~forcemousemod)) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + void bpress(XEvent *e) { struct timespec now; - MouseShortcut *ms; int snap; if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { @@ -428,13 +446,8 @@ bpress(XEvent *e) return; } - for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { - if (e->xbutton.button == ms->button && - match(ms->mod, e->xbutton.state & ~forcemousemod)) { - ms->func(&(ms->arg)); - return; - } - } + if (mouseaction(e, 0)) + return; if (e->xbutton.button == Button1) { /* @@ -655,9 +668,9 @@ brelease(XEvent *e) return; } - if (e->xbutton.button == Button2) - selpaste(NULL); - else if (e->xbutton.button == Button1) + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == Button1) mousesel(e, 1); } From a2c479c4c8d035c11a91e4b954a9f161bf4c7150 Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Thu, 24 Oct 2019 15:42:07 +0300 Subject: [PATCH 04/11] mouse shortcuts: allow using forcemousemod (e.g. shift) The recent mouse shurtcuts commits allow customization, but ignore forcemousemod mask (default: shift) as a modifier, for no good reason other than following the behavior of the KB shortcuts. Allow using forcemousemod too, which now can be used to invoke different shortcuts, though the automatic effect of forcemousemod will be lost for buttons which use mask with forcemousemod. E.g. the default is: static uint forcemousemod = ShiftMask; ... { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, ... where ttysend will be invoked for button4 with any mod when not in mouse mode, and with shift when in mouse mode. Now it's possible to do this: { ShiftMask, Button4, ttysend, {.s = "foo"} }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, Which will invoke ttysend("foo") while shift is held and ttysend("\031") otherwise. Shift still overrides mouse mode, but will now send "foo". Previously with this setup the second binding was always invoked because the forceousemod mask was always removed from the event. Buttons which don't use forcemousemod behave the same as before. This is useful e.g. for the scrollback mouse patch, which wants to configure shift+wheel for scrollback, while keeping the normal behavior without shift. --- x.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x.c b/x.c index 8f570c2..6406925 100644 --- a/x.c +++ b/x.c @@ -426,7 +426,8 @@ mouseaction(XEvent *e, uint release) for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { if (ms->release == release && ms->button == e->xbutton.button && - match(ms->mod, e->xbutton.state & ~forcemousemod)) { + (match(ms->mod, e->xbutton.state) || /* exact or forced */ + match(ms->mod, e->xbutton.state & ~forcemousemod))) { ms->func(&(ms->arg)); return 1; } From 1f09f0b0bbba29ceed9b4e6d558b6ad5b0843cfe Mon Sep 17 00:00:00 2001 From: Ingo Lohmar Date: Fri, 31 May 2019 22:25:35 +0200 Subject: [PATCH 05/11] apply hints before initial mapping (ICCCM) For WM_CLASS this is mentioned in the ICCCM docs https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.5 (third sentence). When changing the WM_CLASS from the command line, this is necessary for window managers to pick it up before applying class-based rules. --- x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x.c b/x.c index 6406925..bc3ad5a 100644 --- a/x.c +++ b/x.c @@ -1154,8 +1154,8 @@ xinit(int cols, int rows) win.mode = MODE_NUMLOCK; resettitle(); - XMapWindow(xw.dpy, xw.win); xhints(); + XMapWindow(xw.dpy, xw.win); XSync(xw.dpy, False); clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); From 83866428de031300eab03fbb116bcf7d2b1d4f60 Mon Sep 17 00:00:00 2001 From: "Sebastian J. Bronner" Date: Tue, 5 Nov 2019 18:16:39 +0100 Subject: [PATCH 06/11] Fix tmux terminfo extensions Se and Ss The tmux terminfo extensions Ss and Se are currently specified as booleans in `st.info`. They should be strings. See https://github.com/tmux/tmux/blob/eeedb43ae847a0a692ceea965f7556e84bca4fd0/tty-term.c lines 254 and 265. I have used the values from https://invisible-island.net/ncurses/terminfo.src.html#toc-_S_I_M_P_L_E_T_E_R_M for this patch. --- st.info | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/st.info b/st.info index 52fc617..78ffd30 100644 --- a/st.info +++ b/st.info @@ -189,10 +189,10 @@ st| simpleterm, rmxx=\E[29m, smxx=\E[9m, # tmux extensions, see TERMINFO EXTENSIONS in tmux(1) - Se, - Ss, Tc, Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, st-256color| simpleterm with 256 colors, use=st, From ea4d933ed9d8ce16699c84892a29e070c70b2eb9 Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Wed, 16 Oct 2019 11:17:23 +0300 Subject: [PATCH 07/11] base64dec: don't read out of bounds Previously, base64dec checked terminating input '\0' every 4 calls to base64dec_getc, where the latter progressed one or more chars on each call, and could read past '\0' in the way it was used. The input to base64dec currently comes only from OSC 52 escape seq (copy to clipboard), and reading past '\0' or even past the buffer boundary was easy to trigger. Also, even if we could trust external input to be valid base64, there are different base64 standards, and not all of them require padding to 4 bytes blocks (using trailing '=' chars). It didn't affect short OSC 52 strings because the buffer is initialized to 0's, so typically it did stop within the buffer, but if the string was trimmed to fit (the buffer is 512 bytes) then it did also read past the end of the buffer, and the decoded suffix ended up arbitrary. This patch makes base64dec_getc not progress past '\0', and instead produce fake trailing padding of '='. Additionally, at base64dec, if padding is detected at the first or second byte of a quartet, then we identify it as invalid and abort (a valid quartet has at least two leading non-padding bytes). --- st.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/st.c b/st.c index ede7ae6..a8f8232 100644 --- a/st.c +++ b/st.c @@ -366,7 +366,7 @@ char base64dec_getc(const char **src) { while (**src && !isprint(**src)) (*src)++; - return *((*src)++); + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ } char * @@ -384,6 +384,10 @@ base64dec(const char *src) int c = base64_digits[(unsigned char) base64dec_getc(&src)]; int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + *dst++ = (a << 2) | ((b & 0x30) >> 4); if (c == -1) break; From 7ceb3d1f72eabfa678e5cfae176c57630ad98c43 Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Wed, 16 Oct 2019 12:19:49 +0300 Subject: [PATCH 08/11] STREscape: don't trim prematurely STRescape holds strings in escape sequences such as OSC and DCS, and its buffer is 512 bytes. If the input is too big then trailing chars are ignored, but the test was off-by-1 such that it took 510 chars instead of 511 (before a terminating NULL is added). Now the full size can be utilized. --- st.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/st.c b/st.c index a8f8232..50226d1 100644 --- a/st.c +++ b/st.c @@ -2330,7 +2330,7 @@ tputc(Rune u) if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') term.mode |= MODE_SIXEL; - if (strescseq.len+len >= sizeof(strescseq.buf)-1) { + if (strescseq.len+len >= sizeof(strescseq.buf)) { /* * Here is a bug in terminals. If the user never sends * some code to stop the str or esc command, then st From 289c52b7aa9b0e826bbea6f956755b3199b3ccac Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Wed, 16 Oct 2019 12:38:43 +0300 Subject: [PATCH 09/11] CSIEscape, STREscape: use size_t for buffer length --- st.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/st.c b/st.c index 50226d1..0c1acd4 100644 --- a/st.c +++ b/st.c @@ -135,7 +135,7 @@ typedef struct { /* ESC '[' [[ [] [;]] []] */ typedef struct { char buf[ESC_BUF_SIZ]; /* raw string */ - int len; /* raw string length */ + size_t len; /* raw string length */ char priv; int arg[ESC_ARG_SIZ]; int narg; /* nb of args */ @@ -147,7 +147,7 @@ typedef struct { typedef struct { char type; /* ESC type ... */ char buf[STR_BUF_SIZ]; /* raw string */ - int len; /* raw string length */ + size_t len; /* raw string length */ char *args[STR_ARG_SIZ]; int narg; /* nb of args */ } STREscape; @@ -1803,7 +1803,7 @@ csihandle(void) void csidump(void) { - int i; + size_t i; uint c; fprintf(stderr, "ESC["); @@ -1921,7 +1921,7 @@ strparse(void) void strdump(void) { - int i; + size_t i; uint c; fprintf(stderr, "ESC%c", strescseq.type); From 2e54a21b5ae249a6bcedab9db611ea86037a018b Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Wed, 16 Oct 2019 12:55:53 +0300 Subject: [PATCH 10/11] OSC 52 - copy to clipboard: don't limit to 382 bytes Strings which an application sends to the terminal in OSC, DCS, etc are typically small (title, colors, etc) but one exception is OSC 52 which copies text to the clipboard, and is used for instance by tmux. Previously st cropped these strings at 512 bytes, which for OSC 52 limited the copied text to 382 bytes (remaining buffer space before base64). This made it less useful than it can be. Now it's a dynamic growing buffer. It remains allocated after use, resets to 512 when a new string starts, or leaked on exit. Resetting/deallocating the buffer right after use (at strhandle) is possible with some more code, however, it doesn't always end up used, and to cover those cases too will require even more code, so resetting only on new string is good enough for now. --- st.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/st.c b/st.c index 0c1acd4..3e48410 100644 --- a/st.c +++ b/st.c @@ -146,7 +146,8 @@ typedef struct { /* ESC type [[ [] [;]] ] ESC '\' */ typedef struct { char type; /* ESC type ... */ - char buf[STR_BUF_SIZ]; /* raw string */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ size_t len; /* raw string length */ char *args[STR_ARG_SIZ]; int narg; /* nb of args */ @@ -1948,7 +1949,10 @@ strdump(void) void strreset(void) { - memset(&strescseq, 0, sizeof(strescseq)); + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; } void @@ -2330,7 +2334,7 @@ tputc(Rune u) if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') term.mode |= MODE_SIXEL; - if (strescseq.len+len >= sizeof(strescseq.buf)) { + if (strescseq.len+len >= strescseq.siz) { /* * Here is a bug in terminals. If the user never sends * some code to stop the str or esc command, then st @@ -2344,7 +2348,10 @@ tputc(Rune u) * term.esc = 0; * strhandle(); */ - return; + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); } memmove(&strescseq.buf[strescseq.len], c, len); From 384830110bddcebed00b6530a5336f07ad7c405f Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 17 Nov 2019 20:04:52 +0100 Subject: [PATCH 11/11] update FAQ - add common question about the w3m image drawing hack. - remove some bad advise about $TERM. - change some links to https. --- FAQ | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/FAQ b/FAQ index ecf7af8..78c769a 100644 --- a/FAQ +++ b/FAQ @@ -1,6 +1,6 @@ ## Why does st not handle utmp entries? -Use the excellent tool of [utmp](http://git.suckless.org/utmp/) for this task. +Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task. ## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! @@ -15,13 +15,6 @@ you can manually run `tic -sx st.info`. * Some programs don’t complain about the lacking st description and default to another terminal. In that case see the question about terminfo. -## I get some weird glitches/visual bug on _random program_! - -Try launching it with a different TERM: $ TERM=xterm myapp. toe(1) will give -you a list of available terminals, but you’ll most likely switch between xterm, -st or st-256color. The default value for TERM can be changed in config.h -(TNAME). - ## How do I scroll back up? Using a terminal multiplexer. @@ -104,7 +97,7 @@ St is emulating the Linux way of handling backspace being delete and delete bein backspace. This is an issue that was discussed in suckless mailing list -. Here is why some old grumpy +. Here is why some old grumpy terminal users wants its backspace to be how he feels it: Well, I am going to comment why I want to change the behaviour @@ -163,7 +156,15 @@ terminal users wants its backspace to be how he feels it: Apply [1]. -[1] http://st.suckless.org/patches/delkey +[1] https://st.suckless.org/patches/delkey + +## Why do images not work in st (in programs such as w3m)? + +This is a terrible hack that overdraws an image on top of the terminal emulator +window. It also relies on a very specific way the terminal draws it's contents. + +A more proper (but limited way) would be using sixels. Which st doesn't +support. ## BadLength X error in Xft when trying to render emoji