diff options
author | Joe <rbo@gmx.us> | 2024-12-26 16:04:49 +0100 |
---|---|---|
committer | Joe <rbo@gmx.us> | 2024-12-26 16:04:49 +0100 |
commit | 9c8bc4e708647be16b6c69a28603c3542db9c100 (patch) | |
tree | 18ef2626c9db480d5122b99d34c2b40fa1afcd01 | |
parent | up (diff) | |
download | dmenu-9c8bc4e708647be16b6c69a28603c3542db9c100.tar.gz dmenu-9c8bc4e708647be16b6c69a28603c3542db9c100.tar.bz2 dmenu-9c8bc4e708647be16b6c69a28603c3542db9c100.tar.xz dmenu-9c8bc4e708647be16b6c69a28603c3542db9c100.tar.zst dmenu-9c8bc4e708647be16b6c69a28603c3542db9c100.zip |
5.3
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | Makefile | 17 | ||||
-rw-r--r-- | README | 68 | ||||
-rw-r--r-- | config.def.h | 26 | ||||
-rw-r--r-- | config.h | 2 | ||||
-rw-r--r-- | config.mk | 9 | ||||
-rw-r--r-- | dmenu.1 | 21 | ||||
-rw-r--r-- | dmenu.c | 302 | ||||
-rw-r--r-- | drw.c | 190 | ||||
-rw-r--r-- | drw.h | 1 | ||||
-rw-r--r-- | stest.c | 2 | ||||
-rw-r--r-- | util.c | 36 | ||||
-rw-r--r-- | util.h | 1 |
14 files changed, 320 insertions, 358 deletions
@@ -128,3 +128,4 @@ flycheck_*.el dmenu stest .ccls-cache +config.h @@ -8,7 +8,7 @@ MIT/X Consortium License © 2009 Markus Schnalke <meillo@marmaro.de> © 2009 Evan Gates <evan.gates@gmail.com> © 2010-2012 Connor Lane Smith <cls@lubutu.com> -© 2014-2020 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org> © 2015-2019 Quentin Rameau <quinq@fifth.space> Permission is hereby granted, free of charge, to any person obtaining a @@ -6,17 +6,14 @@ include config.mk SRC = drw.c dmenu.c stest.c util.c OBJ = $(SRC:.c=.o) -all: options dmenu stest - -options: - @echo dmenu build options: - @echo "CFLAGS = $(CFLAGS)" - @echo "LDFLAGS = $(LDFLAGS)" - @echo "CC = $(CC)" +all: dmenu stest .c.o: $(CC) -c $(CFLAGS) $< +config.h: + cp config.def.h $@ + $(OBJ): arg.h config.h config.mk drw.h dmenu: dmenu.o drw.o util.o @@ -26,11 +23,11 @@ stest: stest.o $(CC) -o $@ stest.o $(LDFLAGS) clean: - rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz config.h dist: clean mkdir -p dmenu-$(VERSION) - cp LICENSE Makefile README arg.h config.h config.mk dmenu.1\ + cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ dmenu-$(VERSION) tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) @@ -58,4 +55,4 @@ uninstall: $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ $(DESTDIR)$(MANPREFIX)/man1/stest.1 -.PHONY: all options clean dist install uninstall +.PHONY: all clean dist install uninstall @@ -1,56 +1,24 @@ -dmenu - dynamic menu for X -=========================== +dmenu - dynamic menu +==================== +dmenu is an efficient dynamic menu for X. -Introduction + +Requirements ------------ -This is my personnal build of dmenu, a simple dynamic menu for -X which sucks less. I've applied some patches. +In order to build dmenu you need the Xlib header files. -Depedencies ------------ -- A C compiler -- xorg -- make Installation ------------ -To install this build, run these commands: - - $ git clone git://jozanleclerc.xyz/jozan/dmenu.git - $ cd dmenu - $ make - # make install clean - -It is installed by default in the user /usr/local/bin directory but you can -change it to something that matches more your $PATH by editing the PREFIX -variable in config.mk. - -Applied patches ---------------- -You can find a list of patches and check what those I've applied are -doing on dmenu's patches page. - -Here is my list: - - border - - case insensitive - - center - - fuzzyhighlight - - fuzzymatch - - line height - -Settings --------- -Most settings are done by editing config.h and recompiling. All settings -can also be configured via command line options, see - $ man 1 dmenu -for more informations. - - Fonts - ----- - The default font is part of the Nerd Fonts collection but can - be changed at config.h:11. - - Colors - ------ - The colorscheme used in this build is gruvbox dark hard, you can - change it at config.h:16. +Edit config.mk to match your local setup (dmenu is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dmenu +(if necessary as root): + + make clean install + + +Running dmenu +------------- +See the man page for details. diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..f34ef7b --- /dev/null +++ b/config.def.h @@ -0,0 +1,26 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "BigBlueTermPlus Nerd Font:size=13" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#ebdbb2", "#1d2021" }, + [SchemeSel] = { "#ebdbb2", "#cc241d" }, + [SchemeSelHighlight] = { "#fabd2f", "#cc241d" }, + [SchemeNormHighlight] = { "#fabd2f", "#1d2021" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; @@ -8,7 +8,7 @@ static int centered = 1; /* -c option; centers dmenu on scree static int min_width = 500; /* minimum width when centered */ /* -fn option overrides fonts[0]; default X11 font or font set */ static const char *fonts[] = { - "Terminess Nerd Font:style=Bold:size=15" + "Terminess Nerd Font:style=Bold:size=16" // "UbuntuMono Nerd Font:size=15" }; static const char *prompt = NULL; /* -p option; prompt to the left of input field */ @@ -1,12 +1,12 @@ # dmenu version -VERSION = 5.0 +VERSION = 5.3 # paths PREFIX = /usr/local MANPREFIX = $(PREFIX)/share/man -X11INC = /usr/local/include -X11LIB = /usr/local/lib +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib # Xinerama, comment if you don't want it XINERAMALIBS = -lXinerama @@ -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) @@ -24,7 +25,7 @@ LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm # flags CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) -CFLAGS = -std=c99 -pedantic -Wall -march=haswell -O3 -pipe $(INCS) $(CPPFLAGS) +CFLAGS = -std=c99 -pedantic -Wall -Os -march=alderlake -pipe $(INCS) $(CPPFLAGS) LDFLAGS = $(LIBS) # compiler and linker @@ -3,7 +3,7 @@ dmenu \- dynamic menu .SH SYNOPSIS .B dmenu -.RB [ \-bfivP ] +.RB [ \-bFfivP ] .RB [ \-l .IR lines ] .RB [ \-m @@ -48,8 +48,8 @@ which lists programs in the user's $PATH and runs the result in their $SHELL. .B \-b dmenu appears at the bottom of the screen. .TP -.B \-c -dmenu appears centered on the screen. +.B \-F +disables fuzzy matching. .TP .B \-f dmenu grabs the keyboard before reading stdin if not reading from a tty. This @@ -64,9 +64,6 @@ dmenu will not directly display the keyboard input, but instead replace it with .BI \-l " lines" dmenu lists items vertically, with the given number of lines. .TP -.BI \-h " height" -dmenu uses a menu line of at least 'height' pixels tall, but no less than 8. -.TP .BI \-m " monitor" dmenu is displayed on the monitor number supplied. Monitor numbers are starting from 0. @@ -86,6 +83,12 @@ and X color names are supported. .BI \-nf " color" defines the normal foreground color. .TP +.BI \-sb " color" +defines the selected background color. +.TP +.BI \-sf " color" +defines the selected foreground color. +.TP .BI \-nhb " color" defines the normal highlight background color. .TP @@ -98,12 +101,6 @@ defines the selected highlight background color. .BI \-shf " color" defines the selected highlight foreground color. .TP -.BI \-sb " color" -defines the selected background color. -.TP -.BI \-sf " color" -defines the selected foreground color. -.TP .B \-v prints version information to stdout, then exits. .TP @@ -23,11 +23,11 @@ /* macros */ #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) -#define LENGTH(X) (sizeof X / sizeof X[0]) #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) /* enums */ -enum { SchemeNorm, SchemeSel, SchemeOut, SchemeNormHighlight, SchemeSelHighlight, SchemeLast }; /* color schemes */ +enum { SchemeNorm, SchemeSel, SchemeNormHighlight, SchemeSelHighlight, + SchemeOut, SchemeLast }; /* color schemes */ struct item { char *text; @@ -60,6 +60,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) { @@ -84,22 +91,13 @@ 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; } -static int -max_textw(void) -{ - int len = 0; - for (struct item *item = items; item && item->text; item++) - len = MAX(TEXTW(item->text), len); - return len; -} - static void cleanup(void) { @@ -108,19 +106,29 @@ 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); } static char * -cistrstr(const char *s, const char *sub) +cistrstr(const char *h, const char *n) { - size_t len; + size_t i; + + if (!n[0]) + return (char *)h; - for (len = strlen(sub); *s; s++) - if (!strncasecmp(s, sub, len)) - return (char *)s; + 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; } @@ -128,17 +136,16 @@ static void drawhighlights(struct item *item, int x, int y, int maxw) { int i, indent; - char *highlight; - char c; + char c, *highlight; if (!(strlen(item->text) && strlen(text))) return; drw_setscheme(drw, scheme[item == sel - ? SchemeSelHighlight - : SchemeNormHighlight]); + ? SchemeSelHighlight + : SchemeNormHighlight]); for (i = 0, highlight = item->text; *highlight && text[i];) { - if (!fstrncmp(&(*highlight), &text[i], 1)) { + if (!fstrncmp(highlight, &text[i], 1)) { /* get indentation */ c = *highlight; *highlight = '\0'; @@ -149,59 +156,24 @@ drawhighlights(struct item *item, int x, int y, int maxw) c = highlight[1]; highlight[1] = '\0'; drw_text( - drw, - x + indent - (lrpad / 2), - y, - MIN(maxw - indent, TEXTW(highlight) - lrpad), - bh, 0, highlight, 0 - ); + drw, + x + indent - (lrpad / 2.), + y, + MIN(maxw - indent, TEXTW(highlight) - lrpad), + bh, 0, highlight, 0 + ); highlight[1] = c; - i++; + ++i; } - highlight++; + ++highlight; } } -/* static void */ -/* drawhighlights(struct item *item, int x, int y, int maxw) */ -/* { */ -/* char restorechar, tokens[sizeof text], *highlight, *token; */ -/* int indentx, highlightlen; */ -/* */ -/* drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); */ -/* strcpy(tokens, text); */ -/* for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) { */ -/* highlight = fstrstr(item->text, token); */ -/* while (highlight) { */ -/* // Move item str end, calc width for highlight indent, & restore */ -/* highlightlen = highlight - item->text; */ -/* restorechar = *highlight; */ -/* item->text[highlightlen] = '\0'; */ -/* indentx = TEXTW(item->text); */ -/* item->text[highlightlen] = restorechar; */ -/* */ -/* // Move highlight str end, draw highlight, & restore */ -/* restorechar = highlight[strlen(token)]; */ -/* highlight[strlen(token)] = '\0'; */ -/* if (indentx - (lrpad / 2) - 1 < maxw) */ -/* drw_text( */ -/* drw, */ -/* x + indentx - (lrpad / 2) - 1, */ -/* y, */ -/* MIN(maxw - indentx, TEXTW(highlight) - lrpad), */ -/* bh, 0, highlight, 0 */ -/* ); */ -/* highlight[strlen(token)] = restorechar; */ - /* */ - /* if (strlen(highlight) - strlen(token) < strlen(token)) break; */ - /* highlight = fstrstr(highlight + strlen(token), token); */ - /* } */ - /* } */ -/* } */ - static int drawitem(struct item *item, int x, int y, int w) { + int r; + if (item == sel) drw_setscheme(drw, scheme[SchemeSel]); else if (item->out) @@ -209,7 +181,7 @@ drawitem(struct item *item, int x, int y, int w) else drw_setscheme(drw, scheme[SchemeNorm]); - int r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); + r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); drawhighlights(item, x, y, w); return r; } @@ -219,7 +191,7 @@ drawmenu(void) { unsigned int curpos; struct item *item; - int x = 0, y = 0, fh = drw->fonts->h, w; + int x = 0, y = 0, w; char *censort; drw_setscheme(drw, scheme[SchemeNorm]); @@ -234,7 +206,7 @@ drawmenu(void) drw_setscheme(drw, scheme[SchemeNorm]); if (passwd) { censort = ecalloc(1, sizeof(text)); - memset(censort, '*', strlen(text)); + memset(censort, '.', strlen(text)); drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0); free(censort); } else drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); @@ -242,7 +214,7 @@ drawmenu(void) curpos = TEXTW(text) - TEXTW(&text[cursor]); if ((curpos += lrpad / 2 - 1) < w) { drw_setscheme(drw, scheme[SchemeNorm]); - drw_rect(drw, x + curpos, 2 + (bh-fh)/2, 2, fh - 4, 1, 0); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); } if (lines > 0) { @@ -259,7 +231,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]); @@ -286,6 +258,24 @@ grabfocus(void) die("cannot grab focus"); } +static void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); +} + int compare_distance(const void *a, const void *b) { @@ -313,18 +303,18 @@ fuzzymatch(void) matches = matchend = NULL; /* walk through all items */ - for (it = items; it && it->text; it++) { + for (it = items; it && it->text; ++it) { if (text_len) { itext_len = strlen(it->text); pidx = 0; /* pointer */ sidx = eidx = -1; /* start of match, end of match */ /* walk through item text */ - for (i = 0; i < itext_len && (c = it->text[i]); i++) { + for (i = 0; i < itext_len && (c = it->text[i]); ++i) { /* fuzzy match pattern */ if (!fstrncmp(&text[pidx], &c, 1)) { if(sidx == -1) sidx = i; - pidx++; + ++pidx; if (pidx == text_len) { eidx = i; break; @@ -339,7 +329,7 @@ fuzzymatch(void) it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len); /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */ appenditem(it, &matches, &matchend); - number_of_matches++; + ++number_of_matches; } } else { appenditem(it, &matches, &matchend); @@ -348,19 +338,18 @@ fuzzymatch(void) if (number_of_matches) { /* initialize array with matches */ - if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*)))) - die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*)); - for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) { + if (!(fuzzymatches = realloc(fuzzymatches, + number_of_matches * sizeof(struct item *)))) + die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item *)); + for (i = 0, it = matches; it && i < number_of_matches; ++i, it = it->right) fuzzymatches[i] = it; - } /* sort matches according to distance */ qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance); /* rebuild list of matches */ matches = matchend = NULL; - for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \ - it->text; i++, it = fuzzymatches[i]) { + for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && + it->text; ++i, it = fuzzymatches[i]) appenditem(it, &matches, &matchend); - } free(fuzzymatches); } curr = sel = matches; @@ -368,24 +357,6 @@ fuzzymatch(void) } static void -grabkeyboard(void) -{ - struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; - int i; - - if (embed) - return; - /* try to grab keyboard, we may have to wait for another process to ungrab */ - for (i = 0; i < 1000; i++) { - if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, - GrabModeAsync, CurrentTime) == GrabSuccess) - return; - nanosleep(&ts, NULL); - } - die("cannot grab keyboard"); -} - -static void match(void) { if (fuzzy) { @@ -404,7 +375,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; @@ -486,19 +457,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; } @@ -511,10 +482,11 @@ keypress(XKeyEvent *ev) case XK_e: ksym = XK_End; break; case XK_f: ksym = XK_Right; break; case XK_g: ksym = XK_Escape; break; - case XK_h: ksym = XK_BackSpace; break; case XK_i: ksym = XK_Tab; break; - case XK_j: ksym = XK_Down; break; - case XK_k: ksym = XK_Up; break; + case XK_j: ksym = XK_Down; break; + case XK_k: ksym = XK_Up; break; + case XK_l: ksym = XK_Down; break; + case XK_h: ksym = XK_Up; break; case XK_J: /* fallthrough */ case XK_m: /* fallthrough */ case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; @@ -536,9 +508,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: @@ -572,10 +546,11 @@ keypress(XKeyEvent *ev) switch(ksym) { default: insert: - if (!iscntrl(*buf)) + if (!iscntrl((unsigned char)*buf)) insert(buf, len); break; case XK_Delete: + case XK_KP_Delete: if (text[cursor] == '\0') return; cursor = nextrune(+1); @@ -586,6 +561,7 @@ insert: insert(NULL, nextrune(-1) - cursor); break; case XK_End: + case XK_KP_End: if (text[cursor] != '\0') { cursor = strlen(text); break; @@ -605,6 +581,7 @@ insert: cleanup(); exit(1); case XK_Home: + case XK_KP_Home: if (sel == matches) { cursor = 0; break; @@ -613,6 +590,7 @@ insert: calcoffsets(); break; case XK_Left: + case XK_KP_Left: if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; @@ -621,18 +599,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; @@ -649,6 +630,7 @@ insert: sel->out = 1; break; case XK_Right: + case XK_KP_Right: if (text[cursor] != '\0') { cursor = nextrune(+1); break; @@ -657,6 +639,7 @@ insert: return; /* fallthrough */ case XK_Down: + case XK_KP_Down: if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); @@ -665,9 +648,9 @@ insert: case XK_Tab: if (!sel) return; - strncpy(text, sel->text, sizeof text - 1); - text[sizeof text - 1] = '\0'; - cursor = strlen(text); + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; match(); break; } @@ -697,34 +680,32 @@ paste(void) static void readstdin(void) { - char buf[sizeof text], *p; - size_t i, imax = 0, size = 0; - unsigned int tmpmax = 0; - if(passwd){ + char *line = NULL; + size_t i, itemsiz = 0, linesiz = 0; + ssize_t len; + + if (passwd){ inputw = lines = 0; return; } - /* read each line from stdin and add it to the item list */ - 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); - if ((p = strchr(buf, '\n'))) - *p = '\0'; - 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; + for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { + 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'; + if (!(items[i].text = strdup(line))) + die("strdup:"); + + items[i].out = 0; } + free(line); if (items) items[i].text = NULL; - inputw = items ? TEXTW(items[imax].text) : 0; lines = MIN(lines, i); } @@ -790,10 +771,8 @@ setup(void) /* calculate menu geometry */ bh = drw->fonts->h + 2; - bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */ lines = MAX(lines, 0); mh = (lines + 1) * bh; - promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; #ifdef XINERAMA i = 0; if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { @@ -817,19 +796,12 @@ 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; - if (centered) { - mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); - x = info[i].x_org + ((info[i].width - mw) / 2); - y = info[i].y_org + ((info[i].height - mh) / 2); - } else { - x = info[i].x_org; - y = info[i].y_org + (topbar ? 0 : info[i].height - mh); - mw = info[i].width; - } - + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; XFree(info); } else #endif @@ -837,32 +809,23 @@ setup(void) if (!XGetWindowAttributes(dpy, parentwin, &wa)) die("could not get embedding window attributes: 0x%lx", parentwin); - - if (centered) { - mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); - x = (wa.width - mw) / 2; - y = (wa.height - mh) / 2; - } else { - x = 0; - y = topbar ? 0 : wa.height - mh; - mw = wa.width; - } + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; } promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; - inputw = MIN(inputw, mw/3); + inputw = mw / 3; /* input width: ~33% of monitor width */ match(); /* create menu window */ swa.override_redirect = True; swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; - win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, + win = XCreateWindow(dpy, root, x, y, mw, mh, 0, CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); - XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); XSetClassHint(dpy, win, &ch); - /* input methods */ if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM failed: could not open input device"); @@ -872,6 +835,7 @@ setup(void) XMapRaised(dpy, win); if (embed) { + XReparentWindow(dpy, win, parentwin, x, y); XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { for (i = 0; i < du && dws[i] != win; ++i) @@ -887,11 +851,9 @@ setup(void) static void usage(void) { - fputs("usage: dmenu [-bfivP] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" - " [-h height]\n" - " [-nb color] [-nf color] [-sb color] [-sf color]\n" - " [-nhb color] [-nhf color] [-shb color] [-shf color] [-w windowid]\n", stderr); - exit(1); + die("usage: dmenu [-bFfivP] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color]\n" + " [-nhb color] [-nhf color] [-shb color] [-shf color] [-w windowid]"); } int @@ -907,12 +869,10 @@ main(int argc, char *argv[]) exit(0); } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ topbar = 0; + else if (!strcmp(argv[i], "-F")) /* disables fuzzy matching */ + fuzzy = 0; else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ fast = 1; - else if (!strcmp(argv[i], "-F")) /* grabs keyboard before reading stdin */ - fuzzy = 0; - else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ - centered = 1; else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; fstrstr = cistrstr; @@ -929,10 +889,6 @@ main(int argc, char *argv[]) prompt = argv[++i]; else if (!strcmp(argv[i], "-fn")) /* font or font set */ fonts[0] = argv[++i]; - else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ - lineheight = atoi(argv[++i]); - lineheight = MAX(lineheight,8); /* reasonable default in case of value too small/negative */ - } else if (!strcmp(argv[i], "-nb")) /* normal background color */ colors[SchemeNorm][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ @@ -9,54 +9,40 @@ #include "util.h" #define UTF_INVALID 0xFFFD -#define UTF_SIZ 4 -static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; -static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; -static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; -static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; - -static long -utf8decodebyte(const char c, size_t *i) -{ - for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) - if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) - return (unsigned char)c & ~utfmask[*i]; - return 0; -} - -static size_t -utf8validate(long *u, size_t i) -{ - if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) - *u = UTF_INVALID; - for (i = 1; *u > utfmax[i]; ++i) - ; - return i; -} - -static size_t -utf8decode(const char *c, long *u, size_t clen) +static int +utf8decode(const char *s_in, long *u, int *err) { - size_t i, j, len, type; - long udecoded; - + static const unsigned char lens[] = { + /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ + /* 110XX */ 2, 2, 2, 2, + /* 1110X */ 3, 3, + /* 11110 */ 4, + /* 11111 */ 0, /* invalid */ + }; + static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 }; + static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 }; + + const unsigned char *s = (const unsigned char *)s_in; + int len = lens[*s >> 3]; *u = UTF_INVALID; - if (!clen) - return 0; - udecoded = utf8decodebyte(c[0], &len); - if (!BETWEEN(len, 1, UTF_SIZ)) + *err = 1; + if (len == 0) return 1; - for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { - udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); - if (type) - return j; + + long cp = s[0] & leading_mask[len - 1]; + for (int i = 1; i < len; ++i) { + if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) + return i; + cp = (cp << 6) | (s[i] & 0x3F); } - if (j < len) - return 0; - *u = udecoded; - utf8validate(u, len); + /* out of range, surrogate, overlong encoding */ + if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1]) + return len; + *err = 0; + *u = cp; return len; } @@ -133,19 +119,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; @@ -251,29 +224,32 @@ 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, hash, h0, h1; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; - size_t i, len; - int utf8strlen, utf8charlen, render = x || y || w || h; + int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; long utf8codepoint = 0; const char *utf8str; FcCharSet *fccharset; FcPattern *fcpattern; FcPattern *match; XftResult result; - int charexists = 0; + int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + static unsigned int nomatches[128], ellipsis_width, invalid_width; + static const char invalid[] = "�"; - if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) 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); + if (w < lpad) + return x + w; d = XftDrawCreate(drw->dpy, drw->drawable, DefaultVisual(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen)); @@ -282,18 +258,40 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + if (!invalid_width && render) + invalid_width = drw_fontset_getwidth(drw, invalid); while (1) { - utf8strlen = 0; + ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0; utf8str = text; nextfont = NULL; while (*text) { - utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + utf8charlen = utf8decode(text, &utf8codepoint, &utf8err); for (curfont = drw->fonts; curfont; curfont = curfont->next) { charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); if (charexists) { - if (curfont == usedfont) { - utf8strlen += utf8charlen; + 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; + /* 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) { text += utf8charlen; + utf8strlen += utf8err ? 0 : utf8charlen; + ew += utf8err ? 0 : tmpw; } else { nextfont = curfont; } @@ -301,36 +299,31 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } } - if (!charexists || nextfont) + if (overflow || !charexists || nextfont || utf8err) 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 (utf8err && (!render || invalid_width < w)) { + if (render) + drw_text(drw, x, y, w, h, 0, invalid, invert); + x += invalid_width; + w -= invalid_width; + } + 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; @@ -340,6 +333,15 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp * character must be drawn. */ charexists = 1; + hash = (unsigned int)utf8codepoint; + hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; + hash = ((hash >> 15) ^ hash) * 0xD35A2D97; + h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); + h1 = (hash >> 17) % LENGTH(nomatches); + /* avoid expensive XftFontMatch call when we know we won't find a match */ + if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint) + goto no_match; + fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, utf8codepoint); @@ -351,7 +353,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); @@ -368,6 +369,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[nomatches[h0] ? h1 : h0] = utf8codepoint; +no_match: usedfont = drw->fonts; } } @@ -397,6 +400,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) { @@ -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 */ @@ -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); } @@ -1,4 +1,5 @@ /* See LICENSE file for copyright and license details. */ +#include <errno.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -6,30 +7,31 @@ #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; + int saved_errno; + + saved_errno = errno; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); - if (fmt[0] && fmt[strlen(fmt)-1] == ':') { - fputc(' ', stderr); - perror(NULL); - } else { - fputc('\n', stderr); - } + if (fmt[0] && fmt[strlen(fmt)-1] == ':') + fprintf(stderr, " %s", strerror(saved_errno)); + fputc('\n', stderr); exit(1); } + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} @@ -3,6 +3,7 @@ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) +#define LENGTH(X) (sizeof (X) / sizeof (X)[0]) void die(const char *fmt, ...); void *ecalloc(size_t nmemb, size_t size); |