From a8cb8e94547d7e31441d2444e8a196415e3e4c1f Mon Sep 17 00:00:00 2001 From: magras Date: Thu, 28 Feb 2019 04:56:01 +0300 Subject: [PATCH 01/30] fix use after free in font caching algorithm Current font caching algorithm contains a use after free error. A font removed from `frc` might be still listed in `wx.specbuf`. It will lead to a crash inside `XftDrawGlyphFontSpec()`. Steps to reproduce: $ st -f 'Misc Tamsyn:scalable=false' $ curl https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-demo.txt Of course, result depends on fonts installed on a system and fontconfig. In my case, I'm getting consistent segfaults with different fonts. I replaced a fixed array with a simple unbounded buffer with a constant growth rate. Cache starts with a capacity of 0, gets increments by 16, and never shrinks. On my machine after `cat UTF-8-demo.txt` buffer reaches a capacity of 192. During casual use capacity stays at 0. --- x.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/x.c b/x.c index 865dacc..2cd76d0 100644 --- a/x.c +++ b/x.c @@ -226,8 +226,9 @@ typedef struct { } Fontcache; /* Fontcache is an array now. A new font will be appended to the array. */ -static Fontcache frc[16]; +static Fontcache *frc = NULL; static int frclen = 0; +static int frccap = 0; static char *usedfont = NULL; static double usedfontsize = 0; static double defaultfontsize = 0; @@ -1244,12 +1245,14 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x fcpattern, &fcres); /* - * Overwrite or create the new cache entry. + * Allocate memory for the new cache entry. */ - if (frclen >= LEN(frc)) { - frclen = LEN(frc) - 1; - XftFontClose(xw.dpy, frc[frclen].font); - frc[frclen].unicodep = 0; + if (frclen >= frccap) { + frccap += 16; + if (!frc) + frc = xmalloc(frccap * sizeof(Fontcache)); + else + frc = xrealloc(frc, frccap * sizeof(Fontcache)); } frc[frclen].font = XftFontOpenPattern(xw.dpy, From 4e0135afeca43f5affe13d7269cb98e7ac526074 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 3 Mar 2019 11:23:54 +0100 Subject: [PATCH 02/30] style: remove double empty newlines --- st.c | 1 - x.c | 2 -- 2 files changed, 3 deletions(-) diff --git a/st.c b/st.c index cf8687e..379dd10 100644 --- a/st.c +++ b/st.c @@ -2335,7 +2335,6 @@ tputc(Rune u) goto check_control_code; } - if (IS_SET(MODE_SIXEL)) { /* TODO: implement sixel mode */ return; diff --git a/x.c b/x.c index 2cd76d0..aa86b31 100644 --- a/x.c +++ b/x.c @@ -763,7 +763,6 @@ xsetcolorname(int x, const char *name) if (!BETWEEN(x, 0, dc.collen)) return 1; - if (!xloadcolor(x, name, &ncolor)) return 1; @@ -1769,7 +1768,6 @@ kpress(XEvent *ev) ttywrite(buf, len, 1); } - void cmessage(XEvent *e) { From ed68fe7dce2b21b4e0e595b99d47790e76812cb7 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 3 Mar 2019 11:29:43 +0100 Subject: [PATCH 03/30] simplify (greedy) font caching allocating a bit POSIX says: "If ptr is a null pointer, realloc() shall be equivalent to malloc() for the specified size." --- x.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/x.c b/x.c index aa86b31..5828a3b 100644 --- a/x.c +++ b/x.c @@ -1243,15 +1243,10 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x fontpattern = FcFontSetMatch(0, fcsets, 1, fcpattern, &fcres); - /* - * Allocate memory for the new cache entry. - */ + /* Allocate memory for the new cache entry. */ if (frclen >= frccap) { frccap += 16; - if (!frc) - frc = xmalloc(frccap * sizeof(Fontcache)); - else - frc = xrealloc(frc, frccap * sizeof(Fontcache)); + frc = xrealloc(frc, frccap * sizeof(Fontcache)); } frc[frclen].font = XftFontOpenPattern(xw.dpy, From 75b4ba4b4be70a3ae429b1719d18b021839216d5 Mon Sep 17 00:00:00 2001 From: Lauri Tirkkonen Date: Wed, 13 Mar 2019 17:08:50 +0200 Subject: [PATCH 04/30] be silent about explicitly unhandled mouse modes --- st.c | 1 + 1 file changed, 1 insertion(+) diff --git a/st.c b/st.c index 379dd10..d35f89d 100644 --- a/st.c +++ b/st.c @@ -1575,6 +1575,7 @@ tsetmode(int priv, int set, int *args, int narg) case 1015: /* urxvt mangled mouse mode; incompatible and can be mistaken for other control codes. */ + break; default: fprintf(stderr, "erresc: unknown private set/reset mode %d\n", From d5efd256aa3840476579a27293ef1fb92a4b51e7 Mon Sep 17 00:00:00 2001 From: Lauri Tirkkonen Date: Wed, 13 Mar 2019 19:40:52 +0200 Subject: [PATCH 05/30] replace utf8strchr with wcschr --- config.def.h | 4 ++-- st.c | 20 +------------------- st.h | 2 +- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/config.def.h b/config.def.h index 0e01717..482901e 100644 --- a/config.def.h +++ b/config.def.h @@ -30,9 +30,9 @@ static float chscale = 1.0; /* * word delimiter string * - * More advanced example: " `'\"()[]{}" + * More advanced example: L" `'\"()[]{}" */ -char *worddelimiters = " "; +wchar_t *worddelimiters = L" "; /* selection timeouts (in milliseconds) */ static unsigned int doubleclicktimeout = 300; diff --git a/st.c b/st.c index d35f89d..812f30c 100644 --- a/st.c +++ b/st.c @@ -41,7 +41,7 @@ #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) -#define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL) +#define ISDELIM(u) (u != 0 && wcschr(worddelimiters, u) != NULL) enum term_mode { MODE_WRAP = 1 << 0, @@ -210,7 +210,6 @@ static void selsnap(int *, int *, int); static size_t utf8decode(const char *, Rune *, size_t); static Rune utf8decodebyte(char, size_t *); static char utf8encodebyte(Rune, size_t); -static char *utf8strchr(char *, Rune); static size_t utf8validate(Rune *, size_t); static char *base64dec(const char *); @@ -337,23 +336,6 @@ utf8encodebyte(Rune u, size_t i) return utfbyte[i] | (u & ~utfmask[i]); } -char * -utf8strchr(char *s, Rune u) -{ - Rune r; - size_t i, j, len; - - len = strlen(s); - for (i = 0, j = 0; i < len; i += j) { - if (!(j = utf8decode(&s[i], &r, len - i))) - break; - if (r == u) - return &(s[i]); - } - - return NULL; -} - size_t utf8validate(Rune *u, size_t i) { diff --git a/st.h b/st.h index 38c61c4..4da3051 100644 --- a/st.h +++ b/st.h @@ -114,7 +114,7 @@ char *xstrdup(char *); extern char *utmp; extern char *stty_args; extern char *vtiden; -extern char *worddelimiters; +extern wchar_t *worddelimiters; extern int allowaltscreen; extern char *termname; extern unsigned int tabspaces; From add0211522737b79dad990ccd65c8af63b5cc1dd Mon Sep 17 00:00:00 2001 From: Lauri Tirkkonen Date: Wed, 13 Mar 2019 17:15:04 +0200 Subject: [PATCH 06/30] use iswspace()/iswpunct() to find word delimiters this inverts the configuration logic: you no longer provide a list of delimiters -- all space and punctuation characters are considered delimiters, unless listed in extrawordchars. --- config.def.h | 7 ++++--- st.c | 3 ++- st.h | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/config.def.h b/config.def.h index 482901e..9ce45a7 100644 --- a/config.def.h +++ b/config.def.h @@ -28,11 +28,12 @@ static float cwscale = 1.0; static float chscale = 1.0; /* - * word delimiter string + * all space and punctuation characters are considered word delimiters, unless + * listed here. * - * More advanced example: L" `'\"()[]{}" + * More advanced example: L"#$%&+,-./:=?_~" */ -wchar_t *worddelimiters = L" "; +wchar_t *extrawordchars = L"./:"; /* selection timeouts (in milliseconds) */ static unsigned int doubleclicktimeout = 300; diff --git a/st.c b/st.c index 812f30c..c383b43 100644 --- a/st.c +++ b/st.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "st.h" #include "win.h" @@ -41,7 +42,7 @@ #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) -#define ISDELIM(u) (u != 0 && wcschr(worddelimiters, u) != NULL) +#define ISDELIM(u) ((iswspace(u) || iswpunct(u)) && wcschr(extrawordchars, u) == NULL) enum term_mode { MODE_WRAP = 1 << 0, diff --git a/st.h b/st.h index 4da3051..a3b19de 100644 --- a/st.h +++ b/st.h @@ -114,7 +114,7 @@ char *xstrdup(char *); extern char *utmp; extern char *stty_args; extern char *vtiden; -extern wchar_t *worddelimiters; +extern wchar_t *extrawordchars; extern int allowaltscreen; extern char *termname; extern unsigned int tabspaces; From 927621f6da015f51710c03279b00c6cc38057e32 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 15 Mar 2019 12:31:54 +0100 Subject: [PATCH 07/30] config.def.h: tweak extra worddelimiters This changes the selection more like xterm. To test try: "find /" and select a path. --- config.def.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.def.h b/config.def.h index 9ce45a7..ac5e8ce 100644 --- a/config.def.h +++ b/config.def.h @@ -33,7 +33,7 @@ static float chscale = 1.0; * * More advanced example: L"#$%&+,-./:=?_~" */ -wchar_t *extrawordchars = L"./:"; +wchar_t *extrawordchars = L""; /* selection timeouts (in milliseconds) */ static unsigned int doubleclicktimeout = 300; From 9acec468fbeaa9f90578352b610431ca9b2d4ee4 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 15 Mar 2019 14:42:50 +0100 Subject: [PATCH 08/30] minor code-style, initialize var at the top of function --- st.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/st.c b/st.c index c383b43..f342e5c 100644 --- a/st.c +++ b/st.c @@ -1830,7 +1830,7 @@ csireset(void) void strhandle(void) { - char *p = NULL; + char *p = NULL, *dec; int j, narg, par; term.esc &= ~(ESC_STR_END|ESC_STR); @@ -1848,8 +1848,6 @@ strhandle(void) return; case 52: if (narg > 2) { - char *dec; - dec = base64dec(strescseq.args[2]); if (dec) { xsetsel(dec); From b650256044f867851725f712fdac58d4ff294808 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 15 Mar 2019 14:44:28 +0100 Subject: [PATCH 09/30] dont print color warning on color reset OSC 104 without parameter also print explicitly "(null)" when printf "%s" p=NULL. noticed when exiting mutt: printf '\x1b]104\x07' --- st.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/st.c b/st.c index f342e5c..48d65ca 100644 --- a/st.c +++ b/st.c @@ -1865,7 +1865,10 @@ strhandle(void) case 104: /* color reset, here p = NULL */ j = (narg > 1) ? atoi(strescseq.args[1]) : -1; if (xsetcolorname(j, p)) { - fprintf(stderr, "erresc: invalid color %s\n", p); + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); } else { /* * TODO if defaultbg color is changed, borders From 21367a040f056f6a207fafa066bd1cb2d9cae586 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 15 Mar 2019 20:40:16 +0100 Subject: [PATCH 10/30] revert part of commit add0211522737b79dad990ccd65c8af63b5cc1dd "use iswspace()/iswpunct() to find word delimiters this inverts the configuration logic: you no longer provide a list of delimiters -- all space and punctuation characters are considered delimiters, unless listed in extrawordchars." Feedback from IRC and personal preference. --- config.def.h | 7 +++---- st.c | 3 +-- st.h | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/config.def.h b/config.def.h index ac5e8ce..482901e 100644 --- a/config.def.h +++ b/config.def.h @@ -28,12 +28,11 @@ static float cwscale = 1.0; static float chscale = 1.0; /* - * all space and punctuation characters are considered word delimiters, unless - * listed here. + * word delimiter string * - * More advanced example: L"#$%&+,-./:=?_~" + * More advanced example: L" `'\"()[]{}" */ -wchar_t *extrawordchars = L""; +wchar_t *worddelimiters = L" "; /* selection timeouts (in milliseconds) */ static unsigned int doubleclicktimeout = 300; diff --git a/st.c b/st.c index 48d65ca..8e6ccb5 100644 --- a/st.c +++ b/st.c @@ -16,7 +16,6 @@ #include #include #include -#include #include "st.h" #include "win.h" @@ -42,7 +41,7 @@ #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) -#define ISDELIM(u) ((iswspace(u) || iswpunct(u)) && wcschr(extrawordchars, u) == NULL) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) enum term_mode { MODE_WRAP = 1 << 0, diff --git a/st.h b/st.h index a3b19de..4da3051 100644 --- a/st.h +++ b/st.h @@ -114,7 +114,7 @@ char *xstrdup(char *); extern char *utmp; extern char *stty_args; extern char *vtiden; -extern wchar_t *extrawordchars; +extern wchar_t *worddelimiters; extern int allowaltscreen; extern char *termname; extern unsigned int tabspaces; From f1546cf9c1f9fc52d26dbbcf73210901e83c7ecf Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Wed, 10 Apr 2019 01:54:43 +0300 Subject: [PATCH 11/30] selection: fix view to match actual selection on first cell --- st.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/st.c b/st.c index 8e6ccb5..ede7ae6 100644 --- a/st.c +++ b/st.c @@ -458,7 +458,7 @@ selextend(int col, int row, int type, int done) selnormalize(); sel.type = type; - if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type) + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); sel.mode = done ? SEL_IDLE : SEL_READY; From caa1d8fbea2b92bca24652af0fee874bdbbbb3e5 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 17 May 2019 13:00:10 +0200 Subject: [PATCH 12/30] FAQ: add entry about color emoji Xft bug This has been asked many times on IRC and the mailinglist. Make it easier to find information about this particular Xft issue by adding it to the FAQ. --- FAQ | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/FAQ b/FAQ index 921c493..ecf7af8 100644 --- a/FAQ +++ b/FAQ @@ -165,3 +165,30 @@ Apply [1]. [1] http://st.suckless.org/patches/delkey +## BadLength X error in Xft when trying to render emoji + +Xft makes st crash when rendering color emojis with the following error: + +"X Error of failed request: BadLength (poly request too large or internal Xlib length error)" + Major opcode of failed request: 139 (RENDER) + Minor opcode of failed request: 20 (RenderAddGlyphs) + Serial number of failed request: 1595 + Current serial number in output stream: 1818" + +This is a known bug in Xft (not st) which happens on some platforms and +combination of particular fonts and fontconfig settings. + +See also: +https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6 +https://bugs.freedesktop.org/show_bug.cgi?id=107534 +https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + +The solution is to remove color emoji fonts or disable this in the fontconfig +XML configuration. As an ugly workaround (which may work only on newer +fontconfig versions (FC_COLOR)), the following code can be used to mask color +fonts: + + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + +Please don't bother reporting this bug to st, but notify the upstream Xft +developers about fixing this bug. From 2b8333f553c14c15398e810353e192eb05938580 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 26 Aug 2019 17:58:47 +0200 Subject: [PATCH 13/30] config.def.h: remove crlf value section this is not used anymore. patch sent as an ed script using RFC2549 by k0ga. --- config.def.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config.def.h b/config.def.h index 482901e..6ebea98 100644 --- a/config.def.h +++ b/config.def.h @@ -195,10 +195,6 @@ static Shortcut shortcuts[] = { * * 0: no value * * > 0: cursor application mode enabled * * < 0: cursor application mode disabled - * crlf value - * * 0: no value - * * > 0: crlf mode is enabled - * * < 0: crlf mode is disabled * * Be careful with the order of the definitions because st searches in * this table sequentially, so any XK_ANY_MOD must be in the last From ba7f4d69af62d20e13fea78a408095e017410651 Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Thu, 10 Oct 2019 23:02:26 +0300 Subject: [PATCH 14/30] 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 15/30] 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 16/30] 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 17/30] 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 18/30] 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 19/30] 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 20/30] 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 21/30] 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 22/30] 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 23/30] 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 24/30] 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 From 895e5b50a8cc835c19a45e1e328eb4dc78f5fd0c Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sat, 18 Jan 2020 15:52:25 +0800 Subject: [PATCH 25/30] Increase XmbLookupString buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current buffer is too short to input medium to long sentences from IME. Input with longer text will show the wrong input, taking 64 instead of 32 bytes should be enough for most of the cases. Broken cases before, Chinese (taken from song 也可以) 可不可以轻轻的松开自己 Japanese (taken from bootleggers rom quote) あなたは家のように感じる --- x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x.c b/x.c index bc3ad5a..e000894 100644 --- a/x.c +++ b/x.c @@ -1743,7 +1743,7 @@ kpress(XEvent *ev) { XKeyEvent *e = &ev->xkey; KeySym ksym; - char buf[32], *customkey; + char buf[64], *customkey; int len; Rune c; Status status; From 99de33395126fc9708f442d155e737b9182f6ef4 Mon Sep 17 00:00:00 2001 From: Quentin Rameau Date: Sun, 2 Feb 2020 15:37:29 +0100 Subject: [PATCH 26/30] x: move IME variables into XWindow ime embedded struct --- x.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/x.c b/x.c index e000894..60eeee1 100644 --- a/x.c +++ b/x.c @@ -94,8 +94,10 @@ typedef struct { Drawable buf; GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ Atom xembed, wmdeletewin, netwmname, netwmpid; - XIM xim; - XIC xic; + struct { + XIM xim; + XIC xic; + } ime; Draw draw; Visual *vis; XSetWindowAttributes attrs; @@ -1026,18 +1028,18 @@ ximopen(Display *dpy) { XIMCallback destroy = { .client_data = NULL, .callback = ximdestroy }; - if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { + if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { XSetLocaleModifiers("@im=local"); - if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { + if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { XSetLocaleModifiers("@im="); - if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) + if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM failed. Could not open input device.\n"); } } - if (XSetIMValues(xw.xim, XNDestroyCallback, &destroy, NULL) != NULL) + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &destroy, NULL) != NULL) die("XSetIMValues failed. Could not set input method value.\n"); - xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL); + xw.xic = XCreateIC(xw.ime.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL); if (xw.xic == NULL) die("XCreateIC failed. Could not obtain input method.\n"); } @@ -1053,7 +1055,7 @@ ximinstantiate(Display *dpy, XPointer client, XPointer call) void ximdestroy(XIM xim, XPointer client, XPointer call) { - xw.xim = NULL; + xw.ime.xim = NULL; XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, ximinstantiate, NULL); } @@ -1682,13 +1684,13 @@ focus(XEvent *ev) return; if (ev->type == FocusIn) { - XSetICFocus(xw.xic); + XSetICFocus(xw.ime.xic); win.mode |= MODE_FOCUSED; xseturgency(0); if (IS_SET(MODE_FOCUS)) ttywrite("\033[I", 3, 0); } else { - XUnsetICFocus(xw.xic); + XUnsetICFocus(xw.ime.xic); win.mode &= ~MODE_FOCUSED; if (IS_SET(MODE_FOCUS)) ttywrite("\033[O", 3, 0); @@ -1752,7 +1754,7 @@ kpress(XEvent *ev) if (IS_SET(MODE_KBDLOCK)) return; - len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); /* 1. shortcuts */ for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { if (ksym == bp->keysym && match(bp->mod, e->state)) { From 2cb539142b97bd2a5c1a322fd7c063c6afb67c9b Mon Sep 17 00:00:00 2001 From: Quentin Rameau Date: Sun, 2 Feb 2020 15:38:08 +0100 Subject: [PATCH 27/30] x: do not instantiate a new nested list on each cursor move --- x.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/x.c b/x.c index 60eeee1..5af6e4d 100644 --- a/x.c +++ b/x.c @@ -97,6 +97,8 @@ typedef struct { struct { XIM xim; XIC xic; + XPoint spot; + XVaNestedList spotlist; } ime; Draw draw; Visual *vis; @@ -1042,6 +1044,9 @@ ximopen(Display *dpy) XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL); if (xw.xic == NULL) die("XCreateIC failed. Could not obtain input method.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); } void @@ -1058,6 +1063,7 @@ ximdestroy(XIM xim, XPointer client, XPointer call) xw.ime.xim = NULL; XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, ximinstantiate, NULL); + XFree(xw.ime.spotlist); } void @@ -1603,11 +1609,13 @@ xfinishdraw(void) void xximspot(int x, int y) { - XPoint spot = { borderpx + x * win.cw, borderpx + (y + 1) * win.ch }; - XVaNestedList attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); + if (xw.ime.xic == NULL) + return; - XSetICValues(xw.xic, XNPreeditAttributes, attr, NULL); - XFree(attr); + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); } void From cd785755f2e3e3305c7d2556a04423a40bce060a Mon Sep 17 00:00:00 2001 From: Quentin Rameau Date: Sun, 2 Feb 2020 17:38:36 +0100 Subject: [PATCH 28/30] x: check we still have an XIC context before accessing it --- x.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x.c b/x.c index 5af6e4d..b488617 100644 --- a/x.c +++ b/x.c @@ -1061,6 +1061,7 @@ void ximdestroy(XIM xim, XPointer client, XPointer call) { xw.ime.xim = NULL; + xw.ime.xic = NULL; XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, ximinstantiate, NULL); XFree(xw.ime.spotlist); @@ -1692,13 +1693,15 @@ focus(XEvent *ev) return; if (ev->type == FocusIn) { - XSetICFocus(xw.ime.xic); + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); win.mode |= MODE_FOCUSED; xseturgency(0); if (IS_SET(MODE_FOCUS)) ttywrite("\033[I", 3, 0); } else { - XUnsetICFocus(xw.ime.xic); + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); win.mode &= ~MODE_FOCUSED; if (IS_SET(MODE_FOCUS)) ttywrite("\033[O", 3, 0); From 26cdfebf31f024e331429e482b1ee342708888e3 Mon Sep 17 00:00:00 2001 From: Quentin Rameau Date: Sun, 2 Feb 2020 21:47:19 +0100 Subject: [PATCH 29/30] x: fix XIM handling Do not try to set specific IM method, let the user specify it with XMODIFIERS. If the requested method is not available or opening fails, fallback to the default input handler and register a handler on the new IM server availability signal. Do the same when the input server is closed and (re)started. --- x.c | 68 +++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/x.c b/x.c index b488617..1f62129 100644 --- a/x.c +++ b/x.c @@ -146,9 +146,10 @@ static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); static void xdrawglyph(Glyph, int, int); static void xclear(int, int, int, int); static int xgeommasktogravity(int); -static void ximopen(Display *); +static int ximopen(Display *); static void ximinstantiate(Display *, XPointer, XPointer); static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); static void xinit(int, int); static void cresize(int, int); static void xresize(int, int); @@ -1025,48 +1026,61 @@ xunloadfonts(void) xunloadfont(&dc.ibfont); } -void +int ximopen(Display *dpy) { - XIMCallback destroy = { .client_data = NULL, .callback = ximdestroy }; + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; - if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { - XSetLocaleModifiers("@im=local"); - if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { - XSetLocaleModifiers("@im="); - if ((xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) - die("XOpenIM failed. Could not open input device.\n"); - } - } - if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &destroy, NULL) != NULL) - die("XSetIMValues failed. Could not set input method value.\n"); - xw.xic = XCreateIC(xw.ime.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL); - if (xw.xic == NULL) - die("XCreateIC failed. Could not obtain input method.\n"); + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNFocusWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; } void ximinstantiate(Display *dpy, XPointer client, XPointer call) { - ximopen(dpy); - XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, - ximinstantiate, NULL); + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); } void ximdestroy(XIM xim, XPointer client, XPointer call) { xw.ime.xim = NULL; - xw.ime.xic = NULL; XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, - ximinstantiate, NULL); + ximinstantiate, NULL); XFree(xw.ime.spotlist); } +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + void xinit(int cols, int rows) { @@ -1132,7 +1146,10 @@ xinit(int cols, int rows) xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); /* input methods */ - ximopen(xw.dpy); + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } /* white cursor, black outline */ cursor = XCreateFontCursor(xw.dpy, mouseshape); @@ -1765,7 +1782,10 @@ kpress(XEvent *ev) if (IS_SET(MODE_KBDLOCK)) return; - len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); /* 1. shortcuts */ for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { if (ksym == bp->keysym && match(bp->mod, e->state)) { From 51e19ea11dd42eefed1ca136ee3f6be975f618b1 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Tue, 18 Feb 2020 23:28:47 +0800 Subject: [PATCH 30/30] Remove explicit XNFocusWindow XCreateIC ICValues default XNFocusWindow to XNClientWindow if not specified, it can be omitted since it is the same. From the documentation https://www.x.org/releases/current/doc/libX11/libX11/libX11.html > Focus Window > > The XNFocusWindow argument specifies the focus window. The primary > purpose of the XNFocusWindow is to identify the window that will receive > the key event when input is composed. > > When this XIC value is left unspecified, the input method will use the > client window as the default focus window. --- x.c | 1 - 1 file changed, 1 deletion(-) diff --git a/x.c b/x.c index 1f62129..48a6676 100644 --- a/x.c +++ b/x.c @@ -1047,7 +1047,6 @@ ximopen(Display *dpy) xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, - XNFocusWindow, xw.win, XNDestroyCallback, &icdestroy, NULL); }