From 2f8a3053764e8ef3689ec4d5106de44fbd207f58 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 00:37:55 +0600 Subject: [PATCH 01/21] drw_text: improve both performance and correctness this patch makes some non-trivial changes, which significantly improves the performance of drawing large strings as well as fixes any issues regarding the printing of the ellipsis when string gets truncated. * performance: before there were two O(n) loops, one which finds how long we can go without changing font, and the second loop would (incorrectly) truncate the string if it's too big. this patch merges the overflow calculation into the first loop and exits out when overflow is detected. when dumping lots of emojies into dmenu, i see some noticeable startup time improvement: before -> after 460ms -> 360ms input latency when scrolling up/down is also noticeably better and can be tested with the following: for _ in $(seq 20); do cat /dev/urandom | base64 | tr -d '\n' | head -c 1000000 echo done | ./dmenu -l 10 * correctness: the previous version would incorrectly assumed single byte chars and would overwrite them with '.' , this caused a whole bunch of obvious problems, including the ellipsis not getting rendered if then font changed. in addition to exiting out when we detect overflow, this patch also keeps track of the last x-position where the ellipsis would fit. if we detect overflow, we simply make a recursing call to drw_text() at the ellipsis_x position and overwrite what was there. so now the ellipsis will always be printed properly, regardless of weather the font changes or if the string is single byte char or not. the idea of rendering the ellipsis on top incase of overflow was from Bakkeby , thanks! however the original patch had some issues incorrectly truncating the prompt (-p flag) and cutting off emojies. those have been fixed in here. --- drw.c | 56 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/drw.c b/drw.c index 4cdbcbe..e65d069 100644 --- a/drw.c +++ b/drw.c @@ -251,12 +251,10 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) { - char buf[1024]; - int ty; - unsigned int ew; + int ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, ellipsis_width; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; - size_t i, len; int utf8strlen, utf8charlen, render = x || y || w || h; long utf8codepoint = 0; const char *utf8str; @@ -264,7 +262,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp FcPattern *fcpattern; FcPattern *match; XftResult result; - int charexists = 0; + int charexists = 0, overflow = 0; if (!drw || (render && !drw->scheme) || !text || !drw->fonts) return 0; @@ -282,8 +280,9 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } usedfont = drw->fonts; + drw_font_getexts(usedfont, "...", 3, &ellipsis_width, NULL); while (1) { - utf8strlen = 0; + ew = ellipsis_len = utf8strlen = 0; utf8str = text; nextfont = NULL; while (*text) { @@ -291,9 +290,21 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp for (curfont = drw->fonts; curfont; curfont = curfont->next) { charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); if (charexists) { - if (curfont == usedfont) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { utf8strlen += utf8charlen; text += utf8charlen; + ew += tmpw; } else { nextfont = curfont; } @@ -301,36 +312,25 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } } - if (!charexists || nextfont) + if (overflow || !charexists || nextfont) break; else charexists = 0; } if (utf8strlen) { - drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); - /* shorten text if necessary */ - for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) - drw_font_getexts(usedfont, utf8str, len, &ew, NULL); - - if (len) { - memcpy(buf, utf8str, len); - buf[len] = '\0'; - if (len < utf8strlen) - for (i = len; i && i > len - 3; buf[--i] = '.') - ; /* NOP */ - - if (render) { - ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; - XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], - usedfont->xfont, x, ty, (XftChar8 *)buf, len); - } - x += ew; - w -= ew; + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); } + x += ew; + w -= ew; } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); - if (!*text) { + if (!*text || overflow) { break; } else if (nextfont) { charexists = 0; From 576d46bf00fb3f52e577de62713847a753791c45 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 02:00:00 +0600 Subject: [PATCH 02/21] introduce drw_fontset_getwidth_clamp() getting the width of a string is an O(n) operation, and in many cases users only care about getting the width upto a certain number. instead of calling drw_fontset_getwidth() and *then* clamping the result, this patch introduces drw_fontset_getwidth_clamp() function, similar to strnlen(), which will stop once we reach n. the `invert` parameter was overloaded internally to preserve the API, however library users should be calling drw_fontset_getwidth_clamp() and not depend upon internal behavior of drw_text(). --- drw.c | 19 +++++++++++++++++-- drw.h | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/drw.c b/drw.c index e65d069..7d985b1 100644 --- a/drw.c +++ b/drw.c @@ -268,7 +268,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp return 0; if (!render) { - w = ~w; + w = invert ? invert : ~invert; } else { XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); @@ -300,7 +300,13 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp if (ew + tmpw > w) { overflow = 1; - utf8strlen = ellipsis_len; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; } else if (curfont == usedfont) { utf8strlen += utf8charlen; text += utf8charlen; @@ -397,6 +403,15 @@ drw_fontset_getwidth(Drw *drw, const char *text) return drw_text(drw, 0, 0, 0, 0, 0, text, 0); } +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) { diff --git a/drw.h b/drw.h index 4c67419..fd7631b 100644 --- a/drw.h +++ b/drw.h @@ -35,6 +35,7 @@ void drw_free(Drw *drw); Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); void drw_fontset_free(Fnt* set); unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); /* Colorscheme abstraction */ From 49c6f40235c21943aece610dc8c1b9c2db022765 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 02:04:04 +0600 Subject: [PATCH 03/21] significantly improve performance on large strings this replaces inefficient pattern of `MIN(TEXTW(..), n)` with drw_fontset_getwidth_clamp() instead, which is far more efficient when we only want up to a certain width. dumping a decently sized (unicode) emoji file into dmenu, I see the startup time drop significantly with this patch. before -> after 360ms -> 160ms this should also noticeably improve input latency (responsiveness) given that calcoffsets() and drawmenu() are pretty hot functions. --- dmenu.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dmenu.c b/dmenu.c index b264d53..79d7a02 100644 --- a/dmenu.c +++ b/dmenu.c @@ -61,6 +61,13 @@ static char * cistrstr(const char *s, const char *sub); static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp; static char *(*fstrstr)(const char *, const char *) = cistrstr; +static unsigned int +textw_clamp(const char *str, unsigned int n) +{ + unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; + return MIN(w, n); +} + static void appenditem(struct item *item, struct item **list, struct item **last) { @@ -85,10 +92,10 @@ calcoffsets(void) n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); /* calculate which items will begin the next page and previous page */ for (i = 0, next = curr; next; next = next->right) - if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) + if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n) break; for (i = 0, prev = curr; prev && prev->left; prev = prev->left) - if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) + if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) break; } @@ -175,7 +182,7 @@ drawmenu(void) } x += w; for (item = curr; item != next; item = item->right) - x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">"))); + x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); if (next) { w = TEXTW(">"); drw_setscheme(drw, scheme[SchemeNorm]); From 3450386f770f17a2b921d83a0aac397735201a61 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 00:37:55 +0600 Subject: [PATCH 04/21] inputw: improve correctness and startup performance a massive amount of time inside readstdin() is spent trying to get the max input width and then put it into inputw, only for it to get clamped down to mw/3 inside setup(). it makes more sense to calculate inputw inside setup() once we have mw available. similar to the last patch, i see noticeable startup performance improvement: before -> after 160ms -> 60ms additionally this will take fallback fonts into account compared to the previous version, so it's not only more performant but also more correct. --- dmenu.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/dmenu.c b/dmenu.c index 79d7a02..773f49a 100644 --- a/dmenu.c +++ b/dmenu.c @@ -635,8 +635,7 @@ static void readstdin(void) { char buf[sizeof text], *p; - size_t i, imax = 0, size = 0; - unsigned int tmpmax = 0; + size_t i, size = 0; /* read each line from stdin and add it to the item list */ for (i = 0; fgets(buf, sizeof buf, stdin); i++) { @@ -648,15 +647,9 @@ readstdin(void) if (!(items[i].text = strdup(buf))) die("cannot strdup %u bytes:", strlen(buf) + 1); items[i].out = 0; - drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); - if (tmpmax > inputw) { - inputw = tmpmax; - imax = i; - } } if (items) items[i].text = NULL; - inputw = items ? TEXTW(items[imax].text) : 0; lines = MIN(lines, i); } @@ -702,12 +695,13 @@ static void setup(void) { int x, y, i, j; - unsigned int du; + unsigned int du, tmp; XSetWindowAttributes swa; XIM xim; Window w, dw, *dws; XWindowAttributes wa; XClassHint ch = {"dmenu", "dmenu"}; + struct item *item; #ifdef XINERAMA XineramaScreenInfo *info; Window pw; @@ -765,7 +759,12 @@ setup(void) mw = wa.width; } promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; - inputw = MIN(inputw, mw/3); + for (item = items; item && item->text; ++item) { + if ((tmp = textw_clamp(item->text, mw/3)) > inputw) { + if ((inputw = tmp) == mw/3) + break; + } + } match(); /* create menu window */ From 3a060d98f52465a5c1ef643cd6705aedfb4e6c6e Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 00:37:55 +0600 Subject: [PATCH 05/21] drw_text: improve performance when there's no match this was the last piece of the puzzle, the case where we can't find any font to draw the codepoint. in such cases, we use XftFontMatch() which is INSANELY slow. but that's not the real problem. the real problem was we were continuously trying to match the same thing over and over again. this patch introduces a small cache, which keeps track a couple codepoints for which we know we won't find any matches. with this, i can dump lots of emojies into dmenu where some of them don't have any matching font, and still not have dmenu lag insanely or FREEZE completely when scrolling up and down. this also improves startup time, which will of course depend on the system and all installed fonts; but on my system and test case i see the following startup time drop: before -> after 60ms -> 34ms --- drw.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drw.c b/drw.c index 7d985b1..a50c9ee 100644 --- a/drw.c +++ b/drw.c @@ -251,7 +251,7 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) { - int ty, ellipsis_x = 0; + int i, ty, ellipsis_x = 0; unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, ellipsis_width; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; @@ -263,6 +263,9 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp FcPattern *match; XftResult result; int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + enum { nomatches_len = 64 }; + static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; if (!drw || (render && !drw->scheme) || !text || !drw->fonts) return 0; @@ -346,6 +349,12 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp * character must be drawn. */ charexists = 1; + for (i = 0; i < nomatches_len; ++i) { + /* avoid calling XftFontMatch if we know we won't find a match */ + if (utf8codepoint == nomatches.codepoint[i]) + goto no_match; + } + fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, utf8codepoint); @@ -374,6 +383,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp curfont->next = usedfont; } else { xfont_free(usedfont); + nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint; +no_match: usedfont = drw->fonts; } } From cced315e68e34a1dba8c68d7eaa86884a03c8a38 Mon Sep 17 00:00:00 2001 From: NRK Date: Fri, 25 Mar 2022 22:51:09 +0100 Subject: [PATCH 06/21] free all allocated items, use %zu for size_t `items` itself is not checked for NULL as calling free on NULL is defined to be a no-op. --- dmenu.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dmenu.c b/dmenu.c index 773f49a..0293dac 100644 --- a/dmenu.c +++ b/dmenu.c @@ -107,6 +107,9 @@ cleanup(void) XUngrabKey(dpy, AnyKey, AnyModifier, root); for (i = 0; i < SchemeLast; i++) free(scheme[i]); + for (i = 0; items && items[i].text; ++i) + free(items[i].text); + free(items); drw_free(drw); XSync(dpy, False); XCloseDisplay(dpy); @@ -327,7 +330,7 @@ match(void) /* separate input text into tokens to be matched individually */ for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) - die("cannot realloc %u bytes:", tokn * sizeof *tokv); + die("cannot realloc %zu bytes:", tokn * sizeof *tokv); len = tokc ? strlen(tokv[0]) : 0; matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; @@ -641,11 +644,11 @@ readstdin(void) for (i = 0; fgets(buf, sizeof buf, stdin); i++) { if (i + 1 >= size / sizeof *items) if (!(items = realloc(items, (size += BUFSIZ)))) - die("cannot realloc %u bytes:", size); + die("cannot realloc %zu bytes:", size); if ((p = strchr(buf, '\n'))) *p = '\0'; if (!(items[i].text = strdup(buf))) - die("cannot strdup %u bytes:", strlen(buf) + 1); + die("cannot strdup %zu bytes:", strlen(buf) + 1); items[i].out = 0; } if (items) From ad50c7b7417788c81e08f3711e737da7b4aed004 Mon Sep 17 00:00:00 2001 From: NRK Date: Fri, 25 Mar 2022 22:51:45 +0100 Subject: [PATCH 07/21] avoid redraw when there's no change while i was timing the performance issue, i noticed that there was lots of random redrawing going on. turns out there were coming from here; if someone presses CTRL/ALT etc without pressing anything else, nothing will be inserted, so nothing will change. but the code will `break`, go down and do a needless redraw. this patch changes it to simply return if the keypress iscntrl() also avoid potential UB by casting *buf into an unsigned char. --- dmenu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dmenu.c b/dmenu.c index 0293dac..4f485ce 100644 --- a/dmenu.c +++ b/dmenu.c @@ -503,8 +503,9 @@ keypress(XKeyEvent *ev) switch(ksym) { default: insert: - if (!iscntrl(*buf)) - insert(buf, len); + if (iscntrl((unsigned char)*buf)) + return; + insert(buf, len); break; case XK_Delete: case XK_KP_Delete: From 273d967324335f39d26afcf28f6b30c9e9fc782d Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 26 Mar 2022 17:57:50 +0100 Subject: [PATCH 08/21] Revert "avoid redraw when there's no change" This reverts commit 6818e07291f3b2913e687c8ec3d3fe4711724050. This broke keys such as ^W to delete-backward-word --- dmenu.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dmenu.c b/dmenu.c index 4f485ce..0293dac 100644 --- a/dmenu.c +++ b/dmenu.c @@ -503,9 +503,8 @@ keypress(XKeyEvent *ev) switch(ksym) { default: insert: - if (iscntrl((unsigned char)*buf)) - return; - insert(buf, len); + if (!iscntrl(*buf)) + insert(buf, len); break; case XK_Delete: case XK_KP_Delete: From d4b980b76342ec07652eb0b0a8d93423692b327b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 26 Mar 2022 17:58:47 +0100 Subject: [PATCH 09/21] fix UB with the function iscntrl() From commit 6818e07291f3b2913e687c8ec3d3fe4711724050 by NRK, thanks --- dmenu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dmenu.c b/dmenu.c index 0293dac..6975e64 100644 --- a/dmenu.c +++ b/dmenu.c @@ -503,7 +503,7 @@ keypress(XKeyEvent *ev) switch(ksym) { default: insert: - if (!iscntrl(*buf)) + if (!iscntrl((unsigned char)*buf)) insert(buf, len); break; case XK_Delete: From e29eca6fdb19ae7c41843382cab6a0196eb11387 Mon Sep 17 00:00:00 2001 From: NRK Date: Mon, 28 Mar 2022 01:02:52 +0600 Subject: [PATCH 10/21] drw_text: don't segfault when called with 0 width this patch just rejects *any* 0 width draws, which is surely an error by the caller. this also guards against cases where the width is too small for the ellipsis to fit, so ellipsis_w will remain 0. reported by Bakkeby --- drw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drw.c b/drw.c index a50c9ee..2f3a5df 100644 --- a/drw.c +++ b/drw.c @@ -267,7 +267,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp enum { nomatches_len = 64 }; static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; - if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) return 0; if (!render) { From af8fbc7a90f51e95b1171cec50738b517249f352 Mon Sep 17 00:00:00 2001 From: NRK Date: Mon, 28 Mar 2022 21:38:49 +0600 Subject: [PATCH 11/21] drw_text: account for fallback fonts in ellipsis_width additionally, ellipsis_width (which shouldn't change) is made static to avoid re-calculating it on each drw_text() call. --- drw.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drw.c b/drw.c index 2f3a5df..ced7d37 100644 --- a/drw.c +++ b/drw.c @@ -252,7 +252,7 @@ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) { int i, ty, ellipsis_x = 0; - unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, ellipsis_width; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; int utf8strlen, utf8charlen, render = x || y || w || h; @@ -266,6 +266,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp /* keep track of a couple codepoints for which we have no match. */ enum { nomatches_len = 64 }; static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; + static unsigned int ellipsis_width = 0; if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) return 0; @@ -283,7 +284,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } usedfont = drw->fonts; - drw_font_getexts(usedfont, "...", 3, &ellipsis_width, NULL); + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); while (1) { ew = ellipsis_len = utf8strlen = 0; utf8str = text; From a5a24f475f4b9b87b1cd2c6dc29f215b0371f17e Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 29 Apr 2022 20:15:48 +0200 Subject: [PATCH 12/21] inputw: improve correctness and startup performance, by NRK Always use ~30% of the monitor width for the input in horizontal mode. Patch adapted from NRK patches. This also does not calculate inputw when using vertical mode anymore (because the code is removed). --- dmenu.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/dmenu.c b/dmenu.c index 6975e64..1cee70f 100644 --- a/dmenu.c +++ b/dmenu.c @@ -698,13 +698,12 @@ static void setup(void) { int x, y, i, j; - unsigned int du, tmp; + unsigned int du; XSetWindowAttributes swa; XIM xim; Window w, dw, *dws; XWindowAttributes wa; XClassHint ch = {"dmenu", "dmenu"}; - struct item *item; #ifdef XINERAMA XineramaScreenInfo *info; Window pw; @@ -762,12 +761,7 @@ setup(void) mw = wa.width; } promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; - for (item = items; item && item->text; ++item) { - if ((tmp = textw_clamp(item->text, mw/3)) > inputw) { - if ((inputw = tmp) == mw/3) - break; - } - } + inputw = mw / 3; /* input width: ~30% of monitor width */ match(); /* create menu window */ From 801a3e50dd89821b7c56bb2fea91773d44cdc808 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 30 Apr 2022 13:19:33 +0200 Subject: [PATCH 13/21] fix incorrect comment, math is hard --- dmenu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dmenu.c b/dmenu.c index 1cee70f..2639530 100644 --- a/dmenu.c +++ b/dmenu.c @@ -761,7 +761,7 @@ setup(void) mw = wa.width; } promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; - inputw = mw / 3; /* input width: ~30% of monitor width */ + inputw = mw / 3; /* input width: ~33% of monitor width */ match(); /* create menu window */ From 78e64ffb9c997cdf070910b7d3af3c12fe1e991a Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 1 May 2022 18:38:25 +0200 Subject: [PATCH 14/21] Makefile: add manual path for OpenBSD --- config.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/config.mk b/config.mk index ddb33fa..cc68342 100644 --- a/config.mk +++ b/config.mk @@ -17,6 +17,7 @@ FREETYPELIBS = -lfontconfig -lXft FREETYPEINC = /usr/include/freetype2 # OpenBSD (uncomment) #FREETYPEINC = $(X11INC)/freetype2 +#MANPREFIX = ${PREFIX}/man # includes and libs INCS = -I$(X11INC) -I$(FREETYPEINC) From 16524400dca94f0232a826094f67f253449e2161 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 8 Aug 2022 10:42:54 +0200 Subject: [PATCH 15/21] sync code-style patch from libsl --- util.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/util.c b/util.c index fe044fc..96b82c9 100644 --- a/util.c +++ b/util.c @@ -6,18 +6,9 @@ #include "util.h" -void * -ecalloc(size_t nmemb, size_t size) -{ - void *p; - - if (!(p = calloc(nmemb, size))) - die("calloc:"); - return p; -} - void -die(const char *fmt, ...) { +die(const char *fmt, ...) +{ va_list ap; va_start(ap, fmt); @@ -33,3 +24,13 @@ die(const char *fmt, ...) { exit(1); } + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} From 61d32c8d358ba8c65aca63b70ab62de4b070a8da Mon Sep 17 00:00:00 2001 From: NRK Date: Fri, 2 Sep 2022 00:35:18 +0600 Subject: [PATCH 16/21] readstdin: use getline(3) currently readstdin(): - fgets() into a local buffer, - strchr() the buffer to eleminate the newline - stdups() the buffer into items a simpler way is to just use getline(3), which will do the allocation for us; eliminating the need for stdup()-ing. additionally getline returns back the amount of bytes read, which eliminates the need for strchr()-ing to find the newline. --- dmenu.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dmenu.c b/dmenu.c index 2639530..ae5f694 100644 --- a/dmenu.c +++ b/dmenu.c @@ -637,18 +637,18 @@ paste(void) static void readstdin(void) { - char buf[sizeof text], *p; - size_t i, size = 0; + char *line = NULL; + size_t i, junk, size = 0; + ssize_t len; /* read each line from stdin and add it to the item list */ - for (i = 0; fgets(buf, sizeof buf, stdin); i++) { + for (i = 0; (len = getline(&line, &junk, stdin)) != -1; i++, line = NULL) { if (i + 1 >= size / sizeof *items) if (!(items = realloc(items, (size += BUFSIZ)))) die("cannot realloc %zu bytes:", size); - if ((p = strchr(buf, '\n'))) - *p = '\0'; - if (!(items[i].text = strdup(buf))) - die("cannot strdup %zu bytes:", strlen(buf) + 1); + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + items[i].text = line; items[i].out = 0; } if (items) From 2960a6644f01b241193d898fd8ffa5e044271d3a Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 1 Sep 2022 23:51:43 +0600 Subject: [PATCH 17/21] tab-complete: figure out the size before copying we already need to know the string length since `cursor` needs to be adjusted. so just calculate the length beforehand and use `memcpy` to copy exactly as much as needed (as opposed to `strncpy` which always writes `n` bytes). --- dmenu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dmenu.c b/dmenu.c index ae5f694..da66121 100644 --- a/dmenu.c +++ b/dmenu.c @@ -605,9 +605,9 @@ insert: case XK_Tab: if (!sel) return; - strncpy(text, sel->text, sizeof text - 1); + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); text[sizeof text - 1] = '\0'; - cursor = strlen(text); match(); break; } From 4717a35cfb0c30f26dd729da3f810c833fe4f6df Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 2 Sep 2022 19:09:50 +0200 Subject: [PATCH 18/21] fix a regression in the previous commit for tab complete Reported by Santtu Lakkala , thanks! --- dmenu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dmenu.c b/dmenu.c index da66121..07b466d 100644 --- a/dmenu.c +++ b/dmenu.c @@ -607,7 +607,7 @@ insert: return; cursor = strnlen(sel->text, sizeof text - 1); memcpy(text, sel->text, cursor); - text[sizeof text - 1] = '\0'; + text[cursor] = '\0'; match(); break; } From c0783a60ad5033664727e46173ec50a4200d5ca8 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 16 Sep 2022 23:05:07 +0200 Subject: [PATCH 19/21] remove workaround for a crash with color emojis on some systems, now fixed in libXft 2.3.5 https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS --- drw.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drw.c b/drw.c index ced7d37..a58a2b4 100644 --- a/drw.c +++ b/drw.c @@ -133,19 +133,6 @@ xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) die("no font specified."); } - /* Do not allow using color fonts. This is a workaround for a BadLength - * error from Xft with color glyphs. Modelled on the Xterm workaround. See - * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 - * https://lists.suckless.org/dev/1701/30932.html - * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 - * and lots more all over the internet. - */ - FcBool iscol; - if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { - XftFontClose(drw->dpy, xfont); - return NULL; - } - font = ecalloc(1, sizeof(Fnt)); font->xfont = xfont; font->pattern = pattern; @@ -368,7 +355,6 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp fcpattern = FcPatternDuplicate(drw->fonts->pattern); FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); - FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); FcDefaultSubstitute(fcpattern); From e68533eb4bde738fd71a4f975aeffaa95bfecf53 Mon Sep 17 00:00:00 2001 From: Tom Schwindl Date: Mon, 26 Sep 2022 09:24:15 +0000 Subject: [PATCH 20/21] dmenu: use die() to print the usage message --- dmenu.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dmenu.c b/dmenu.c index 07b466d..0363aa0 100644 --- a/dmenu.c +++ b/dmenu.c @@ -798,9 +798,8 @@ setup(void) static void usage(void) { - fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" - " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); - exit(1); + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); } int From 8dfee8f81b57d5802e4ab1067b09ce036c415728 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 19 Oct 2022 17:40:07 +0200 Subject: [PATCH 21/21] Updated PKGBUILD --- PKGBUILD | 23 +++++++++++++---------- config.mk | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/PKGBUILD b/PKGBUILD index 44a319f..5476feb 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,9 +1,11 @@ # Maintainer: Jef Roosens -pkgname="jjr-dmenu" -pkgver="5.2" +pkgname=jjr-dmenu +pkgver=5.3 pkgrel=1 pkgdesc="Chewing_Bever's custom build of Suckless's dmenu." +url="https://git.rustybever.be/Chewing_Bever/dmenu" + arch=( "x86_64" "x86_64_tigerlake" @@ -11,11 +13,10 @@ arch=( "aarch64" ) -url="https://git.rustybever.be/Chewing_Bever/dmenu" license=("MIT") -depends=("libxinerama" "libxft" "fontconfig" "freetype2") -makedepends=("glibc" "gcc") +depends=("glibc" "libxinerama" "libxft" "fontconfig" "freetype2") +makedepends=("gcc") provides=("dmenu") conflicts=("dmenu-git") @@ -24,19 +25,19 @@ source=("arg.h" "config.h" "config.mk" "dmenu.1" "dmenu.c" "dmenu_path.sh" "stest.c" "util.c" "util.h") sha256sums=('9ed2b2e3396fdebc8ecab2cdbb09db115cca015f63ec0443b8ccc56340b6b03c' '9ae33f74aed02823abb8381e553d38455541ce3b6a0d642282b693992e71caef' - 'f6de4c72dab3b9bef214bd52e6a7dff22e87a3118abaffd26ec1ae589cf68b3f' + '5a6066f1cf97aaee4792708893faa1f16fad39d4dce22c79ccfb7590349fe0cc' '9f749419697381a1db77e9b23cb41629808fd7e451d475c9b5ee0e88bee803f6' - '5b2a53db7ba608d392efaa96be5530665b46107dc4e0ee1a864745a6e8a8587d' + 'dc93fd213531f01c968ec073382d4c91c14a3fb2464e1a62e23e94653a74fb81' 'a3f19e648e20eb80532dfe87e9bd9feeaa92df838e6ecc061f274c9da579d617' '564035fd8a8ade6504521d98fa10e8732e135403d546d04ca77316fe4a67b21c' - 'a7c7358ce13c8571044b79d5d91d01314d9174793474a27faa37d23c75ab603f' - 'c35796210ea8410ae096025cc0d7996dccb3d9936306dff7531a0bb253e079f5' + '8c9f81b98f447440cf59dd3eefda99eec4e7733266ad0c072612163ccff4e1df' + '7ed1cf72bfcb0f5444f338293aef74c455ce4ed5463f171a48358f64d7835d49' '9f8e922fcfc18ffcc4975dd0b1bee38302bd4e0c538251dfef6d77cd7ed59c89' 'c32b5173eddf376296af085725cb878e4ca2603d4fda8bba3faac6a2bb1b4390' 'ec98e8759049af796598f0790752a21a69eaea1d438b09c39922d4a9f4a7cfe2' '00d3389ef8858a565cf3fb3445620b90eb67d1a9b31434f4d54b05ed6c5abefb' 'ba76404e61abbd734e315999ffe4e883419c4c69ff17a5d4d88a5cd11dd4b055' - 'dec870d0b5834c9008ff62f495710daf524cffdf12d0cf8ba4fadf5643623987' + '1c093b9969daaf3e1a001b924c6c2da3770993916b444dfe65fe39aa0090aa1a' '1196a7b6efbf4cb3f5c435fffd72e7647f977483845d5c78c1c48d9ab8b96819') build() { @@ -56,3 +57,5 @@ package() { # install $installopts "$docdir" README.terminfo.rst # install $installopts "$shrdir/$pkgname" "$_sourcedir/st.info" } + +# vim: ft=bash diff --git a/config.mk b/config.mk index cc68342..0ea1efd 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # dmenu version -VERSION = 5.2 +VERSION = 5.3 # paths PREFIX = /usr/local