From 523aa08f51a88c59ad4b1f600f8ce3d122e9e289 Mon Sep 17 00:00:00 2001 From: Guilherme Janczak Date: Sun, 25 Jul 2021 01:55:25 +0000 Subject: [PATCH 01/35] remove always true condition in if statement --- stest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stest.c b/stest.c index 7a7b0bc..e27d3a5 100644 --- a/stest.c +++ b/stest.c @@ -84,7 +84,7 @@ main(int argc, char *argv[]) if (!argc) { /* read list from stdin */ while ((n = getline(&line, &linesiz, stdin)) > 0) { - if (n && line[n - 1] == '\n') + if (line[n - 1] == '\n') line[n - 1] = '\0'; test(line, line); } From c585e8e498ec6f9c423ab8ea07cf853ee5b05fbe Mon Sep 17 00:00:00 2001 From: Miles Alan Date: Sat, 7 Aug 2021 21:34:35 -0400 Subject: [PATCH 02/35] Improve speed of drw_text when provided with large strings Calculates len & ew in drw_font_getexts loop by incrementing instead of decrementing; as such avoids proportional increase in time spent in loop based on provided strings size. --- drw.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drw.c b/drw.c index 4cdbcbe..9c39086 100644 --- a/drw.c +++ b/drw.c @@ -310,8 +310,11 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp 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 (ew > w) + for (ew = 0, len = 0; ew < w - lpad * 2 && len < MIN(utf8strlen, sizeof(buf) - 1); len++) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + else + len = MIN(utf8strlen, sizeof(buf) - 1); if (len) { memcpy(buf, utf8str, len); From cd2133a5f66b42f992a9a1b92bbbce11dc26b941 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 9 Aug 2021 18:39:25 +0200 Subject: [PATCH 03/35] add support for more keypad keys The keypad Enter key was already supported. On some keyboard layouts like my laptop the page-up and page-down key is more comfortable to use. This adds a few lines but no complexity. --- dmenu.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dmenu.c b/dmenu.c index 65f25ce..98507d9 100644 --- a/dmenu.c +++ b/dmenu.c @@ -360,9 +360,11 @@ keypress(XKeyEvent *ev) utf8, utf8, win, CurrentTime); return; case XK_Left: + case XK_KP_Left: movewordedge(-1); goto draw; case XK_Right: + case XK_KP_Right: movewordedge(+1); goto draw; case XK_Return: @@ -400,6 +402,7 @@ insert: insert(buf, len); break; case XK_Delete: + case XK_KP_Delete: if (text[cursor] == '\0') return; cursor = nextrune(+1); @@ -410,6 +413,7 @@ insert: insert(NULL, nextrune(-1) - cursor); break; case XK_End: + case XK_KP_End: if (text[cursor] != '\0') { cursor = strlen(text); break; @@ -429,6 +433,7 @@ insert: cleanup(); exit(1); case XK_Home: + case XK_KP_Home: if (sel == matches) { cursor = 0; break; @@ -437,6 +442,7 @@ insert: calcoffsets(); break; case XK_Left: + case XK_KP_Left: if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; @@ -445,18 +451,21 @@ insert: return; /* fallthrough */ case XK_Up: + case XK_KP_Up: if (sel && sel->left && (sel = sel->left)->right == curr) { curr = prev; calcoffsets(); } break; case XK_Next: + case XK_KP_Next: if (!next) return; sel = curr = next; calcoffsets(); break; case XK_Prior: + case XK_KP_Prior: if (!prev) return; sel = curr = prev; @@ -473,6 +482,7 @@ insert: sel->out = 1; break; case XK_Right: + case XK_KP_Right: if (text[cursor] != '\0') { cursor = nextrune(+1); break; @@ -481,6 +491,7 @@ insert: return; /* fallthrough */ case XK_Down: + case XK_KP_Down: if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); From d78ff08d99780a73447d5a95bf1e358e8c23aa3c Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 20 Aug 2021 23:05:53 +0200 Subject: [PATCH 04/35] Revert "Improve speed of drw_text when provided with large strings" This reverts commit c585e8e498ec6f9c423ab8ea07cf853ee5b05fbe. It causes issues with truncation of characters when the text does not fit and so on. The patch should be reworked and properly tested. --- drw.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drw.c b/drw.c index 9c39086..4cdbcbe 100644 --- a/drw.c +++ b/drw.c @@ -310,11 +310,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp if (utf8strlen) { drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); /* shorten text if necessary */ - if (ew > w) - for (ew = 0, len = 0; ew < w - lpad * 2 && len < MIN(utf8strlen, sizeof(buf) - 1); len++) - drw_font_getexts(usedfont, utf8str, len, &ew, NULL); - else - len = MIN(utf8strlen, sizeof(buf) - 1); + 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); From eb96af27f4059c93d7e000e910b71d74829a239b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 7 Feb 2022 00:21:12 +0100 Subject: [PATCH 05/35] improve performance of case-insensitive matching --- dmenu.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/dmenu.c b/dmenu.c index 98507d9..d06bea1 100644 --- a/dmenu.c +++ b/dmenu.c @@ -102,17 +102,6 @@ cleanup(void) XCloseDisplay(dpy); } -static char * -cistrstr(const char *s, const char *sub) -{ - size_t len; - - for (len = strlen(sub); *s; s++) - if (!strncasecmp(s, sub, len)) - return (char *)s; - return NULL; -} - static int drawitem(struct item *item, int x, int y, int w) { @@ -722,7 +711,7 @@ main(int argc, char *argv[]) fast = 1; else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; - fstrstr = cistrstr; + fstrstr = strcasestr; } else if (i + 1 == argc) usage(); /* these options take one argument */ From a9a3836861bd23387b5a51d6f6ac23377e98e26f Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 7 Feb 2022 10:36:13 +0100 Subject: [PATCH 06/35] follow-up fix: add -D_GNU_SOURCE for strcasestr for some systems --- config.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.mk b/config.mk index 05d5a3e..bea4e4b 100644 --- a/config.mk +++ b/config.mk @@ -23,7 +23,7 @@ INCS = -I$(X11INC) -I$(FREETYPEINC) LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) # flags -CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_GNU_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) LDFLAGS = $(LIBS) From 3e39c526d28582b0b5606d3e3bb36ee3d271e616 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 8 Feb 2022 19:32:25 +0100 Subject: [PATCH 07/35] revert using strcasestr and use a more optimized portable version ... compared to the old cistrstr(). Thanks for the feedback! --- config.mk | 2 +- dmenu.c | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/config.mk b/config.mk index bea4e4b..05d5a3e 100644 --- a/config.mk +++ b/config.mk @@ -23,7 +23,7 @@ INCS = -I$(X11INC) -I$(FREETYPEINC) LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) # flags -CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_GNU_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) LDFLAGS = $(LIBS) diff --git a/dmenu.c b/dmenu.c index d06bea1..88d2f12 100644 --- a/dmenu.c +++ b/dmenu.c @@ -102,6 +102,25 @@ cleanup(void) XCloseDisplay(dpy); } +static char * +cistrstr(const char *h, const char *n) + +{ + size_t i; + + if (!n[0]) + return (char *)h; + + for (; *h; ++h) { + for (i = 0; n[i] && tolower((unsigned char)n[i]) == + tolower((unsigned char)h[i]); ++i) + ; + if (n[i] == '\0') + return (char *)h; + } + return NULL; +} + static int drawitem(struct item *item, int x, int y, int w) { @@ -711,7 +730,7 @@ main(int argc, char *argv[]) fast = 1; else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; - fstrstr = strcasestr; + fstrstr = cistrstr; } else if (i + 1 == argc) usage(); /* these options take one argument */ From c4b656e0da36070a834b03ceb76269ffee8ac952 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 8 Feb 2022 21:45:28 +0100 Subject: [PATCH 08/35] code-style: rm newline (oops) --- dmenu.c | 1 - 1 file changed, 1 deletion(-) diff --git a/dmenu.c b/dmenu.c index 88d2f12..d95e6c6 100644 --- a/dmenu.c +++ b/dmenu.c @@ -104,7 +104,6 @@ cleanup(void) static char * cistrstr(const char *h, const char *n) - { size_t i; From 308fe78b83836371720c7d7eb2c3eac409f3cc16 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 11 Feb 2022 12:26:35 +0100 Subject: [PATCH 09/35] bump version to 5.1 --- LICENSE | 2 +- config.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 3afd28e..2a64b28 100644 --- a/LICENSE +++ b/LICENSE @@ -8,7 +8,7 @@ MIT/X Consortium License © 2009 Markus Schnalke © 2009 Evan Gates © 2010-2012 Connor Lane Smith -© 2014-2020 Hiltjo Posthuma +© 2014-2022 Hiltjo Posthuma © 2015-2019 Quentin Rameau Permission is hereby granted, free of charge, to any person obtaining a diff --git a/config.mk b/config.mk index 05d5a3e..0df3fc8 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # dmenu version -VERSION = 5.0 +VERSION = 5.1 # paths PREFIX = /usr/local From 3a505cebe8adab204e5619357e0bfe3f9f3a92ff Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 1 Mar 2022 22:45:39 +0100 Subject: [PATCH 10/35] 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 11/35] 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 12/35] 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 13/35] 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 14/35] 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 15/35] 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 16/35] 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 17/35] 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 18/35] 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 19/35] 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 20/35] 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 21/35] 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 22/35] 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 23/35] 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 24/35] 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) From e35976f4a50f884c2162f71e4128d7c273e3e042 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 8 Aug 2022 10:42:54 +0200 Subject: [PATCH 25/35] 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 32db2b125190d366be472ccb7cad833248696144 Mon Sep 17 00:00:00 2001 From: NRK Date: Fri, 2 Sep 2022 00:35:18 +0600 Subject: [PATCH 26/35] 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 571bc35..969f6d8 100644 --- a/dmenu.c +++ b/dmenu.c @@ -549,18 +549,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 528d39b011afb7ef6fd794ba6b74155d4e69bc68 Mon Sep 17 00:00:00 2001 From: NRK Date: Thu, 1 Sep 2022 23:51:43 +0600 Subject: [PATCH 27/35] 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 969f6d8..6b285df 100644 --- a/dmenu.c +++ b/dmenu.c @@ -517,9 +517,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 1e8c5b68f4881bd4ae257c780fd41f129c79f419 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 2 Sep 2022 19:09:50 +0200 Subject: [PATCH 28/35] 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 6b285df..818313a 100644 --- a/dmenu.c +++ b/dmenu.c @@ -519,7 +519,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 fce06f437dcec646ee0a2728fe695f3084cc6ccb Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Fri, 16 Sep 2022 23:05:07 +0200 Subject: [PATCH 29/35] 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 7ec32fe4944d4f7137cf2a23366324ffe0f10a70 Mon Sep 17 00:00:00 2001 From: Tom Schwindl Date: Mon, 26 Sep 2022 09:24:15 +0000 Subject: [PATCH 30/35] 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 818313a..7cf253b 100644 --- a/dmenu.c +++ b/dmenu.c @@ -710,9 +710,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 1d2b462acf1210b8f86966b8dd9bb6e36e369ee1 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Tue, 4 Oct 2022 19:36:02 +0200 Subject: [PATCH 31/35] bump version to 5.2 --- config.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.mk b/config.mk index b0bd246..566348b 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # dmenu version -VERSION = 5.1 +VERSION = 5.2 # paths PREFIX = /usr/local From e42c03663442f5fb2f66dd59cc5bfdc61c53192c Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Wed, 26 Oct 2022 09:43:17 +0200 Subject: [PATCH 32/35] dmenu: small XmbLookupString code improvements * Increase the length of composed strings to the same limit as st (32 to 64 bytes). * Initialize ksym to NoSymbol to be safe: currently this is not an issue though. * Add comments to clarify the return values of XmbLookupString a bit. --- dmenu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dmenu.c b/dmenu.c index 7cf253b..e7be8af 100644 --- a/dmenu.c +++ b/dmenu.c @@ -324,19 +324,19 @@ movewordedge(int dir) static void keypress(XKeyEvent *ev) { - char buf[32]; + char buf[64]; int len; - KeySym ksym; + KeySym ksym = NoSymbol; Status status; len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); switch (status) { default: /* XLookupNone, XBufferOverflow */ return; - case XLookupChars: + case XLookupChars: /* composed string from input method */ goto insert; case XLookupKeySym: - case XLookupBoth: + case XLookupBoth: /* a KeySym and a string are returned: use keysym */ break; } From 689d9bfcf6859e5ce85c296ff0f23b5c08b1fedc Mon Sep 17 00:00:00 2001 From: NRK Date: Mon, 31 Oct 2022 00:10:45 +0600 Subject: [PATCH 33/35] fix leak when getline fails according to the getline(3) documentation, the calling code needs to free the buffer even if getline fails. dmenu currently doesn't do that which results in a small leak in case of failure (e.g when piped /dev/null) $ ./dmenu < /dev/null ==8201==ERROR: LeakSanitizer: detected memory leaks Direct leak of 120 byte(s) in 1 object(s) allocated from: #0 0x7f6bf5785ef7 in malloc #1 0x7f6bf538ec84 in __getdelim #2 0x405d0c in readstdin dmenu.c:557 moving `line = NULL` inside the loop body wasn't strictly necessary, but IMO it makes it more apparent that `line` is getting cleared to NULL after each successful iteration. --- dmenu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dmenu.c b/dmenu.c index e7be8af..e786d7a 100644 --- a/dmenu.c +++ b/dmenu.c @@ -554,7 +554,7 @@ readstdin(void) ssize_t len; /* read each line from stdin and add it to the item list */ - for (i = 0; (len = getline(&line, &junk, stdin)) != -1; i++, line = NULL) { + for (i = 0; (len = getline(&line, &junk, stdin)) != -1; i++) { if (i + 1 >= size / sizeof *items) if (!(items = realloc(items, (size += BUFSIZ)))) die("cannot realloc %zu bytes:", size); @@ -562,7 +562,9 @@ readstdin(void) line[len - 1] = '\0'; items[i].text = line; items[i].out = 0; + line = NULL; } + free(line); if (items) items[i].text = NULL; lines = MIN(lines, i); From bcbc1ef5c4cf4875a4d66e7dc0919da88a6096a5 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 31 Oct 2022 11:43:34 +0100 Subject: [PATCH 34/35] readstdin: add a comment Maybe too obvious / redundant, but OK. --- dmenu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dmenu.c b/dmenu.c index e786d7a..8d96b6c 100644 --- a/dmenu.c +++ b/dmenu.c @@ -562,7 +562,7 @@ readstdin(void) line[len - 1] = '\0'; items[i].text = line; items[i].out = 0; - line = NULL; + line = NULL; /* next call of getline() allocates a new line */ } free(line); if (items) From ba1a347dcaba055f824161007dfee60db3ea785b Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Mon, 31 Oct 2022 11:52:30 +0100 Subject: [PATCH 35/35] readstdin: allocate amount of items Keep track of the amount of items (not a total buffer size), allocate an array of new items. For now change BUFSIZ bytes to 256 * sizeof(struct item)). --- dmenu.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dmenu.c b/dmenu.c index 8d96b6c..27b7a30 100644 --- a/dmenu.c +++ b/dmenu.c @@ -550,14 +550,16 @@ static void readstdin(void) { char *line = NULL; - size_t i, junk, size = 0; + size_t i, junk, itemsiz = 0; ssize_t len; /* read each line from stdin and add it to the item list */ for (i = 0; (len = getline(&line, &junk, stdin)) != -1; i++) { - if (i + 1 >= size / sizeof *items) - if (!(items = realloc(items, (size += BUFSIZ)))) - die("cannot realloc %zu bytes:", size); + if (i + 1 >= itemsiz) { + itemsiz += 256; + if (!(items = realloc(items, itemsiz * sizeof(*items)))) + die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); + } if (line[len - 1] == '\n') line[len - 1] = '\0'; items[i].text = line;