dmenu

Simple dynamic menu for X
git clone https://git.sinitax.com/suckless/dmenu
Log | Files | Refs | README | LICENSE | sfeed.txt

commit 4126b1e32379ee206c5c9bda2bf3699f171c9899
parent 07d452e0c8b158e571fb0adf528897829850ecf5
Author: Connor Lane Smith <cls@lubutu.com>
Date:   Sun, 16 Oct 2011 18:13:29 +0100

merge default -> lsx
Diffstat:
M.hgtags | 1+
MMakefile | 4++--
Mconfig.mk | 2+-
Mdmenu.1 | 15++++++---------
Mdmenu.c | 186++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mdmenu_run | 2+-
Mdraw.c | 22+++++++++++-----------
Mdraw.h | 1+
Mlsx.c | 4++--
9 files changed, 118 insertions(+), 119 deletions(-)

diff --git a/.hgtags b/.hgtags @@ -46,3 +46,4 @@ e4c81a78ffbad6ba4d1ad119cc654da6eca63a4c 3.2 abb6579a324fffdf6a23c2fa4c32911277da594a 4.2.1 14c79f054bdf43ff3213af8e60a783192e92a018 4.3 34a2d77049a95b02f3332a0b88f9370965ebcfad 4.3.1 +2b105eaae8315b076da93056da9ecd60de5a7ac9 4.4 diff --git a/Makefile b/Makefile @@ -30,12 +30,12 @@ lsx: lsx.o clean: @echo cleaning - @rm -f dmenu ${OBJ} dmenu-${VERSION}.tar.gz + @rm -f dmenu lsx ${OBJ} dmenu-${VERSION}.tar.gz dist: clean @echo creating dist tarball @mkdir -p dmenu-${VERSION} - @cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_run ${SRC} dmenu-${VERSION} + @cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_run lsx.1 ${SRC} dmenu-${VERSION} @tar -cf dmenu-${VERSION}.tar dmenu-${VERSION} @gzip dmenu-${VERSION}.tar @rm -rf dmenu-${VERSION} diff --git a/config.mk b/config.mk @@ -1,5 +1,5 @@ # dmenu version -VERSION = 4.3.1 +VERSION = hg # paths PREFIX = /usr/local diff --git a/dmenu.1 b/dmenu.1 @@ -25,13 +25,10 @@ dmenu \- dynamic menu .BR dmenu_run " ..." .SH DESCRIPTION .B dmenu -is a dynamic menu for X, originally designed for -.IR dwm (1). -It manages huge numbers of user\-defined menu items efficiently. -.P -dmenu reads a list of newline\-separated items from stdin and creates a menu. -When the user selects an item or enters any text and presses Return, their -choice is printed to stdout and dmenu terminates. +is a dynamic menu for X, which reads a list of newline\-separated items from +stdin. When the user selects an item and presses Return, their choice is printed +to stdout and dmenu terminates. Entering text will narrow the items to those +matching the tokens in the input. .P .B dmenu_run is a dmenu script used by dwm which lists programs in the user's $PATH and @@ -42,8 +39,8 @@ executes the selected item. dmenu appears at the bottom of the screen. .TP .B \-f -dmenu grabs the keyboard before reading stdin. This is faster, but may lock up -X if stdin is from a terminal. +dmenu grabs the keyboard before reading stdin. This is faster, but will lock up +X until stdin reaches end\-of\-file. .TP .B \-i dmenu matches menu items case insensitively. diff --git a/dmenu.c b/dmenu.c @@ -25,12 +25,12 @@ struct Item { static void appenditem(Item *item, Item **list, Item **last); static void calcoffsets(void); +static char *cistrstr(const char *s, const char *sub); static void drawmenu(void); -static char *fstrstr(const char *s, const char *sub); static void grabkeyboard(void); static void insert(const char *str, ssize_t n); static void keypress(XKeyEvent *ev); -static void match(Bool sub); +static void match(void); static size_t nextrune(int inc); static void paste(void); static void readstdin(void); @@ -60,6 +60,7 @@ static Item *prev, *curr, *next, *sel; static Window win; static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; int main(int argc, char *argv[]) { @@ -76,8 +77,10 @@ main(int argc, char *argv[]) { topbar = False; else if(!strcmp(argv[i], "-f")) fast = True; - else if(!strcmp(argv[i], "-i")) + else if(!strcmp(argv[i], "-i")) { fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if(i+1 == argc) usage(); /* double flags */ @@ -112,15 +115,16 @@ main(int argc, char *argv[]) { setup(); run(); - return EXIT_FAILURE; /* should not reach */ + return EXIT_FAILURE; /* unreachable */ } void appenditem(Item *item, Item **list, Item **last) { - if(!*last) - *list = item; - else + if(*last) (*last)->right = item; + else + *list = item; + item->left = *last; item->right = NULL; *last = item; @@ -143,6 +147,16 @@ calcoffsets(void) { break; } +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; +} + void drawmenu(void) { int curpos; @@ -188,16 +202,6 @@ drawmenu(void) { mapdc(dc, win, mw, mh); } -char * -fstrstr(const char *s, const char *sub) { - size_t len; - - for(len = strlen(sub); *s; s++) - if(!fstrncmp(s, sub, len)) - return (char *)s; - return NULL; -} - void grabkeyboard(void) { int i; @@ -219,7 +223,7 @@ insert(const char *str, ssize_t n) { if(n > 0) memcpy(&text[cursor], str, n); cursor += n; - match(n > 0 && text[cursor] == '\0'); + match(); } void @@ -233,58 +237,37 @@ keypress(XKeyEvent *ev) { XConvertCase(ksym, &lower, &upper); switch(lower) { - default: - return; - case XK_a: - ksym = XK_Home; - break; - case XK_b: - ksym = XK_Left; - break; - case XK_c: - ksym = XK_Escape; - break; - case XK_d: - ksym = XK_Delete; - break; - case XK_e: - ksym = XK_End; - break; - case XK_f: - ksym = XK_Right; - break; - case XK_h: - ksym = XK_BackSpace; - break; - case XK_i: - ksym = XK_Tab; - break; - case XK_j: - case XK_m: - ksym = XK_Return; - break; - case XK_k: /* delete right */ + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: ksym = XK_Return; break; + case XK_m: ksym = XK_Return; break; + case XK_n: ksym = XK_Up; break; + case XK_p: ksym = XK_Down; break; + + case XK_k: /* delete right */ text[cursor] = '\0'; - match(False); - break; - case XK_n: - ksym = XK_Down; - break; - case XK_p: - ksym = XK_Up; + match(); break; - case XK_u: /* delete left */ + case XK_u: /* delete left */ insert(NULL, 0 - cursor); break; - case XK_w: /* delete word */ + case XK_w: /* delete word */ while(cursor > 0 && text[nextrune(-1)] == ' ') insert(NULL, nextrune(-1) - cursor); while(cursor > 0 && text[nextrune(-1)] != ' ') insert(NULL, nextrune(-1) - cursor); break; - case XK_y: /* paste selection */ + case XK_y: /* paste selection */ XConvertSelection(dc->dpy, XA_PRIMARY, utf8, utf8, win, CurrentTime); return; + default: + return; } } switch(ksym) { @@ -296,9 +279,11 @@ keypress(XKeyEvent *ev) { if(text[cursor] == '\0') return; cursor = nextrune(+1); + /* fallthrough */ case XK_BackSpace: - if(cursor > 0) - insert(NULL, nextrune(-1) - cursor); + if(cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); break; case XK_End: if(text[cursor] != '\0') { @@ -330,8 +315,7 @@ keypress(XKeyEvent *ev) { cursor = nextrune(-1); break; } - else if(lines > 0) - return; + /* fallthrough */ case XK_Up: if(sel && sel->left && (sel = sel->left)->right == curr) { curr = prev; @@ -352,15 +336,14 @@ keypress(XKeyEvent *ev) { break; case XK_Return: case XK_KP_Enter: - fputs((sel && !(ev->state & ShiftMask)) ? sel->text : text, stdout); + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); exit(EXIT_SUCCESS); case XK_Right: if(text[cursor] != '\0') { cursor = nextrune(+1); break; } - else if(lines > 0) - return; + /* fallthrough */ case XK_Down: if(sel && sel->right && (sel = sel->right) == next) { curr = next; @@ -372,33 +355,44 @@ keypress(XKeyEvent *ev) { return; strncpy(text, sel->text, sizeof text); cursor = strlen(text); - match(True); + match(); break; } drawmenu(); } void -match(Bool sub) { - size_t len = strlen(text); - Item *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend; - Item *item, *lnext; - - lexact = lprefix = lsubstr = exactend = prefixend = substrend = NULL; - for(item = sub ? matches : items; item && item->text; item = lnext) { - lnext = sub ? item->right : item + 1; - if(!fstrncmp(text, item->text, len + 1)) - appenditem(item, &lexact, &exactend); - else if(!fstrncmp(text, item->text, len)) +match(void) { + static char **tokv = NULL; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len; + Item *item, *lprefix, *lsubstr, *prefixend, *substrend; + + strcpy(buf, text); + for(s = strtok(buf, " "); s; tokv[tokc-1] = s, s = strtok(NULL, " ")) + if(++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + eprintf("cannot realloc %u bytes\n", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + for(item = items; item && item->text; item++) { + for(i = 0; i < tokc; i++) + if(!fstrstr(item->text, tokv[i])) + break; + if(i != tokc) + continue; + if(!tokc || !fstrncmp(tokv[0], item->text, len+1)) + appenditem(item, &matches, &matchend); + else if(!fstrncmp(tokv[0], item->text, len)) appenditem(item, &lprefix, &prefixend); - else if(fstrstr(item->text, text)) + else appenditem(item, &lsubstr, &substrend); } - matches = lexact; - matchend = exactend; - if(lprefix) { - if(matchend) { + if(matches) { matchend->right = lprefix; lprefix->left = matchend; } @@ -407,7 +401,7 @@ match(Bool sub) { matchend = prefixend; } if(lsubstr) { - if(matchend) { + if(matches) { matchend->right = lsubstr; lsubstr->left = matchend; } @@ -460,6 +454,7 @@ readstdin(void) { if(items) items[i].text = NULL; inputw = maxstr ? textw(dc, maxstr) : 0; + lines = MIN(lines, i); } void @@ -470,7 +465,7 @@ run(void) { switch(ev.type) { case Expose: if(ev.xexpose.count == 0) - drawmenu(); + mapdc(dc, win, mw, mh); break; case KeyPress: keypress(&ev.xkey); @@ -490,7 +485,7 @@ void setup(void) { int x, y, screen = DefaultScreen(dc->dpy); Window root = RootWindow(dc->dpy, screen); - XSetWindowAttributes wa; + XSetWindowAttributes swa; #ifdef XINERAMA int n; XineramaScreenInfo *info; @@ -511,9 +506,14 @@ setup(void) { if((info = XineramaQueryScreens(dc->dpy, &n))) { int i, di; unsigned int du; - Window dw; + Window w, dw; + XWindowAttributes wa; - XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du); + XGetInputFocus(dc->dpy, &w, &di); + if(w != root && w != PointerRoot && w != None && XGetWindowAttributes(dc->dpy, w, &wa)) + XTranslateCoordinates(dc->dpy, w, root, wa.x, wa.y, &x, &y, &dw); + else + XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du); for(i = 0; i < n-1; i++) if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height)) break; @@ -531,16 +531,16 @@ setup(void) { } promptw = prompt ? textw(dc, prompt) : 0; inputw = MIN(inputw, mw/3); - match(False); + match(); /* menu window */ - wa.override_redirect = True; - wa.background_pixmap = ParentRelative; - wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + swa.override_redirect = True; + swa.background_pixmap = ParentRelative; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; win = XCreateWindow(dc->dpy, root, x, y, mw, mh, 0, DefaultDepth(dc->dpy, screen), CopyFromParent, DefaultVisual(dc->dpy, screen), - CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); + CWOverrideRedirect | CWBackPixmap | CWEventMask, &swa); XMapRaised(dc->dpy, win); resizedc(dc, mw, mh); diff --git a/dmenu_run b/dmenu_run @@ -6,4 +6,4 @@ CACHE=${XDG_CACHE_HOME:-"$HOME/.cache"}/dmenu_run mkdir -p "`dirname "$CACHE"`" && lsx $PATH | sort -u > "$CACHE" fi ) -cmd=`dmenu "$@" < "$CACHE"` && exec $cmd +cmd=`dmenu "$@" < "$CACHE"` && exec sh -c "$cmd" diff --git a/draw.c b/draw.c @@ -96,7 +96,7 @@ initdc(void) { DC *dc; if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) - fprintf(stderr, "no locale support\n"); + fputs("no locale support\n", stderr); if(!(dc = calloc(1, sizeof *dc))) eprintf("cannot malloc %u bytes:", sizeof *dc); if(!(dc->dpy = XOpenDisplay(NULL))) @@ -120,28 +120,28 @@ initfont(DC *dc, const char *fontstr) { Bool loadfont(DC *dc, const char *fontstr) { - char *def, **missing; + char *def, **missing, **names; int i, n; + XFontStruct **xfonts; if(!*fontstr) return False; if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def))) { - char **names; - XFontStruct **xfonts; - n = XFontsOfFontSet(dc->font.set, &xfonts, &names); - for(i = dc->font.ascent = dc->font.descent = 0; i < n; i++) { - dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent); + for(i = 0; i < n; i++) { + dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent); dc->font.descent = MAX(dc->font.descent, xfonts[i]->descent); + dc->font.width = MAX(dc->font.width, xfonts[i]->max_bounds.width); } } else if((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr))) { - dc->font.ascent = dc->font.xfont->ascent; + dc->font.ascent = dc->font.xfont->ascent; dc->font.descent = dc->font.xfont->descent; + dc->font.width = dc->font.xfont->max_bounds.width; } if(missing) XFreeStringList(missing); - return (dc->font.set || dc->font.xfont); + return dc->font.set || dc->font.xfont; } void @@ -154,10 +154,10 @@ resizedc(DC *dc, unsigned int w, unsigned int h) { if(dc->canvas) XFreePixmap(dc->dpy, dc->canvas); - dc->canvas = XCreatePixmap(dc->dpy, DefaultRootWindow(dc->dpy), w, h, - DefaultDepth(dc->dpy, DefaultScreen(dc->dpy))); dc->w = w; dc->h = h; + dc->canvas = XCreatePixmap(dc->dpy, DefaultRootWindow(dc->dpy), w, h, + DefaultDepth(dc->dpy, DefaultScreen(dc->dpy))); } int diff --git a/draw.h b/draw.h @@ -15,6 +15,7 @@ typedef struct { int ascent; int descent; int height; + int width; XFontSet set; XFontStruct *xfont; } font; diff --git a/lsx.c b/lsx.c @@ -1,8 +1,8 @@ /* See LICENSE file for copyright and license details. */ #include <dirent.h> +#include <limits.h> #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <unistd.h> #include <sys/stat.h> @@ -31,7 +31,7 @@ lsx(const char *dir) { return; } while((d = readdir(dp))) - if(snprintf(buf, sizeof buf, "%s/%s", dir, d->d_name) < sizeof buf + if(snprintf(buf, sizeof buf, "%s/%s", dir, d->d_name) < (int)sizeof buf && !stat(buf, &st) && S_ISREG(st.st_mode) && access(buf, X_OK) == 0) puts(d->d_name); closedir(dp);