From 3a505cebe8adab204e5619357e0bfe3f9f3a92ff Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 1 Mar 2022 22:45:39 +0100 Subject: [PATCH 01/15] remove false-positive warning for int comparison as bool Reported by Prathu Baronia , patch slightly changed. Thanks! --- dmenu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dmenu.c b/dmenu.c index d95e6c6..eca67ac 100644 --- a/dmenu.c +++ b/dmenu.c @@ -652,7 +652,7 @@ setup(void) /* no focused window is on screen, so use pointer location instead */ if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) for (i = 0; i < n; i++) - if (INTERSECT(x, y, 1, 1, info[i])) + if (INTERSECT(x, y, 1, 1, info[i]) != 0) break; x = info[i].x_org; From 41fdabbf7c517f8d524b70cbd78238cc319ccef3 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 00:37:55 +0600 Subject: [PATCH 02/15] 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 6be057f060543bb0f3ed9423904263617cdffffe Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 02:00:00 +0600 Subject: [PATCH 03/15] 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 7269c5355d257dd2ad2c53f15dc9c1cf6796aea5 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 02:04:04 +0600 Subject: [PATCH 04/15] 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 eca67ac..cde394b 100644 --- a/dmenu.c +++ b/dmenu.c @@ -58,6 +58,13 @@ static Clr *scheme[SchemeLast]; static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static char *(*fstrstr)(const char *, const char *) = strstr; +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) { @@ -82,10 +89,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; } @@ -172,7 +179,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 77526f756e23e362081ac807521f901f2e5cd5e6 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 00:37:55 +0600 Subject: [PATCH 05/15] 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 cde394b..d989d39 100644 --- a/dmenu.c +++ b/dmenu.c @@ -547,8 +547,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++) { @@ -560,15 +559,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); } @@ -614,12 +607,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; @@ -677,7 +671,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 22511c41d55a38a770541ae617a09383d5e6ad1c Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 24 Mar 2022 00:37:55 +0600 Subject: [PATCH 06/15] 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 b43ec0577f2ad8ad33a0b893fe5360d966036786 Mon Sep 17 00:00:00 2001 From: NRK Date: Fri, 25 Mar 2022 22:51:09 +0100 Subject: [PATCH 07/15] 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 d989d39..085dc29 100644 --- a/dmenu.c +++ b/dmenu.c @@ -104,6 +104,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); @@ -239,7 +242,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; @@ -553,11 +556,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 6818e07291f3b2913e687c8ec3d3fe4711724050 Mon Sep 17 00:00:00 2001 From: NRK Date: Fri, 25 Mar 2022 22:51:45 +0100 Subject: [PATCH 08/15] 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 085dc29..19f6385 100644 --- a/dmenu.c +++ b/dmenu.c @@ -415,8 +415,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 31fa07b9849b0ffbf4b7efb55943f466b3ff160f Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 26 Mar 2022 17:57:50 +0100 Subject: [PATCH 09/15] 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 19f6385..085dc29 100644 --- a/dmenu.c +++ b/dmenu.c @@ -415,9 +415,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 e73651f12a406629778f02d8e5acbe2caec0dfc2 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 26 Mar 2022 17:58:47 +0100 Subject: [PATCH 10/15] 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 085dc29..839f6cc 100644 --- a/dmenu.c +++ b/dmenu.c @@ -415,7 +415,7 @@ keypress(XKeyEvent *ev) switch(ksym) { default: insert: - if (!iscntrl(*buf)) + if (!iscntrl((unsigned char)*buf)) insert(buf, len); break; case XK_Delete: From e4827b0c4048718ab06670cf60ef68d028fe7fc4 Mon Sep 17 00:00:00 2001 From: NRK Date: Mon, 28 Mar 2022 01:02:52 +0600 Subject: [PATCH 11/15] 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 33685b06e9332638769e677e77b257e24e069fd1 Mon Sep 17 00:00:00 2001 From: NRK Date: Mon, 28 Mar 2022 21:38:49 +0600 Subject: [PATCH 12/15] 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 e1e1de7b3b8399cba90ddca9613f837b2dbef7b9 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 29 Apr 2022 20:15:48 +0200 Subject: [PATCH 13/15] 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 839f6cc..4e286cf 100644 --- a/dmenu.c +++ b/dmenu.c @@ -610,13 +610,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; @@ -674,12 +673,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 fe5d5c6709a77ac5d554e26dda76a67df68618ae Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sat, 30 Apr 2022 13:19:33 +0200 Subject: [PATCH 14/15] 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 4e286cf..571bc35 100644 --- a/dmenu.c +++ b/dmenu.c @@ -673,7 +673,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 28fb3e28120db29ea45d1951eee7047b4109ab5f Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 1 May 2022 18:38:25 +0200 Subject: [PATCH 15/15] Makefile: add manual path for OpenBSD --- config.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/config.mk b/config.mk index 0df3fc8..b0bd246 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)