dmenu-4.5/0000755000175000017500000000000011702304643010616 5ustar clsclsdmenu-4.5/config.mk0000644000175000017500000000112211702304643012410 0ustar clscls# dmenu version VERSION = 4.5 # paths PREFIX = /usr/local MANPREFIX = ${PREFIX}/share/man X11INC = /usr/X11R6/include X11LIB = /usr/X11R6/lib # Xinerama, comment if you don't want it XINERAMALIBS = -lXinerama XINERAMAFLAGS = -DXINERAMA # includes and libs INCS = -I${X11INC} LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} # flags CPPFLAGS = -D_BSD_SOURCE -D_POSIX_C_SOURCE=2 -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} #CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} LDFLAGS = -s ${LIBS} # compiler and linker CC = cc dmenu-4.5/LICENSE0000644000175000017500000000255411702304643011631 0ustar clsclsMIT/X Consortium License © 2010-2012 Connor Lane Smith © 2006-2012 Anselm R Garbe © 2009 Gottox © 2009 Markus Schnalke © 2009 Evan Gates © 2006-2008 Sander van Dijk © 2006-2007 Michał Janeczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dmenu-4.5/stest.c0000644000175000017500000000555511702304643012136 0ustar clscls/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #define FLAG(x) (flag[(x)-'a']) static void test(const char *, const char *); static bool match = false; static bool flag[26]; static struct stat old, new; int main(int argc, char *argv[]) { struct dirent *d; char buf[BUFSIZ], *p; DIR *dir; int opt; while((opt = getopt(argc, argv, "abcdefghln:o:pqrsuwx")) != -1) switch(opt) { case 'n': /* newer than file */ case 'o': /* older than file */ if(!(FLAG(opt) = !stat(optarg, (opt == 'n' ? &new : &old)))) perror(optarg); break; default: /* miscellaneous operators */ FLAG(opt) = true; break; case '?': /* error: unknown flag */ fprintf(stderr, "usage: %s [-abcdefghlpqrsuwx] [-n file] [-o file] [file...]\n", argv[0]); exit(2); } if(optind == argc) while(fgets(buf, sizeof buf, stdin)) { if((p = strchr(buf, '\n'))) *p = '\0'; test(buf, buf); } for(; optind < argc; optind++) if(FLAG('l') && (dir = opendir(argv[optind]))) { /* test directory contents */ while((d = readdir(dir))) if(snprintf(buf, sizeof buf, "%s/%s", argv[optind], d->d_name) < sizeof buf) test(buf, d->d_name); closedir(dir); } else test(argv[optind], argv[optind]); return match ? 0 : 1; } void test(const char *path, const char *name) { struct stat st, ln; if(!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ && (!FLAG('s') || st.st_size > 0) /* not empty */ && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ && (!FLAG('x') || access(path, X_OK) == 0)) { /* executable */ if(FLAG('q')) exit(0); match = true; puts(name); } } dmenu-4.5/README0000644000175000017500000000073311702304643011501 0ustar clsclsdmenu - dynamic menu ==================== dmenu is an efficient dynamic menu for X. Requirements ------------ In order to build dmenu you need the Xlib header files. Installation ------------ 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. dmenu-4.5/dmenu.10000644000175000017500000000470711702304643012020 0ustar clscls.TH DMENU 1 dmenu\-VERSION .SH NAME dmenu \- dynamic menu .SH SYNOPSIS .B dmenu .RB [ \-b ] .RB [ \-f ] .RB [ \-i ] .RB [ \-l .IR lines ] .RB [ \-p .IR prompt ] .RB [ \-fn .IR font ] .RB [ \-nb .IR color ] .RB [ \-nf .IR color ] .RB [ \-sb .IR color ] .RB [ \-sf .IR color ] .RB [ \-v ] .P .BR dmenu_run " ..." .SH DESCRIPTION .B dmenu 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 script used by .IR dwm (1) which lists programs in the user's $PATH and runs the result in their $SHELL. .SH OPTIONS .TP .B \-b dmenu appears at the bottom of the screen. .TP .B \-f 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. .TP .BI \-l " lines" dmenu lists items vertically, with the given number of lines. .TP .BI \-p " prompt" defines the prompt to be displayed to the left of the input field. .TP .BI \-fn " font" defines the font or font set used. .TP .BI \-nb " color" defines the normal background color. .IR #RGB , .IR #RRGGBB , and X color names are supported. .TP .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 .B \-v prints version information to stdout, then exits. .SH USAGE dmenu is completely controlled by the keyboard. Items are selected using the arrow keys, page up, page down, home, and end. .TP .B Tab Copy the selected item to the input field. .TP .B Return Confirm selection. Prints the selected item to stdout and exits, returning success. .TP .B Shift\-Return Confirm input. Prints the input text to stdout and exits, returning success. .TP .B Escape Exit without selecting an item, returning failure. .TP C\-a Home .TP C\-b Left .TP C\-c Escape .TP C\-d Delete .TP C\-e End .TP C\-f Right .TP C\-h Backspace .TP C\-i Tab .TP C\-j Return .TP C\-J Shift-Return .TP C\-k Delete line right .TP C\-m Return .TP C\-n Down .TP C\-p Up .TP C\-u Delete line left .TP C\-w Delete word left .TP C\-y Paste from primary X selection .TP C\-Y Paste from X clipboard .TP M\-g Home .TP M\-G End .TP M\-h Up .TP M\-j Page down .TP M\-k Page up .TP M\-l Down .SH SEE ALSO .IR dwm (1), .IR stest (1) dmenu-4.5/draw.c0000644000175000017500000001076311702304643011726 0ustar clscls/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include "draw.h" #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define DEFAULTFN "fixed" static Bool loadfont(DC *dc, const char *fontstr); void drawrect(DC *dc, int x, int y, unsigned int w, unsigned int h, Bool fill, unsigned long color) { XSetForeground(dc->dpy, dc->gc, color); if(fill) XFillRectangle(dc->dpy, dc->canvas, dc->gc, dc->x + x, dc->y + y, w, h); else XDrawRectangle(dc->dpy, dc->canvas, dc->gc, dc->x + x, dc->y + y, w-1, h-1); } void drawtext(DC *dc, const char *text, unsigned long col[ColLast]) { char buf[BUFSIZ]; size_t mn, n = strlen(text); /* shorten text if necessary */ for(mn = MIN(n, sizeof buf); textnw(dc, text, mn) + dc->font.height/2 > dc->w; mn--) if(mn == 0) return; memcpy(buf, text, mn); if(mn < n) for(n = MAX(mn-3, 0); n < mn; buf[n++] = '.'); drawrect(dc, 0, 0, dc->w, dc->h, True, BG(dc, col)); drawtextn(dc, buf, mn, col); } void drawtextn(DC *dc, const char *text, size_t n, unsigned long col[ColLast]) { int x = dc->x + dc->font.height/2; int y = dc->y + dc->font.ascent+1; XSetForeground(dc->dpy, dc->gc, FG(dc, col)); if(dc->font.set) XmbDrawString(dc->dpy, dc->canvas, dc->font.set, dc->gc, x, y, text, n); else { XSetFont(dc->dpy, dc->gc, dc->font.xfont->fid); XDrawString(dc->dpy, dc->canvas, dc->gc, x, y, text, n); } } void eprintf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if(fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') { fputc(' ', stderr); perror(NULL); } exit(EXIT_FAILURE); } void freedc(DC *dc) { if(dc->font.set) XFreeFontSet(dc->dpy, dc->font.set); if(dc->font.xfont) XFreeFont(dc->dpy, dc->font.xfont); if(dc->canvas) XFreePixmap(dc->dpy, dc->canvas); XFreeGC(dc->dpy, dc->gc); XCloseDisplay(dc->dpy); free(dc); } unsigned long getcolor(DC *dc, const char *colstr) { Colormap cmap = DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)); XColor color; if(!XAllocNamedColor(dc->dpy, cmap, colstr, &color, &color)) eprintf("cannot allocate color '%s'\n", colstr); return color.pixel; } DC * initdc(void) { DC *dc; if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("no locale support\n", stderr); if(!(dc = calloc(1, sizeof *dc))) eprintf("cannot malloc %u bytes:", sizeof *dc); if(!(dc->dpy = XOpenDisplay(NULL))) eprintf("cannot open display\n"); dc->gc = XCreateGC(dc->dpy, DefaultRootWindow(dc->dpy), 0, NULL); XSetLineAttributes(dc->dpy, dc->gc, 1, LineSolid, CapButt, JoinMiter); return dc; } void initfont(DC *dc, const char *fontstr) { if(!loadfont(dc, fontstr ? fontstr : DEFAULTFN)) { if(fontstr != NULL) fprintf(stderr, "cannot load font '%s'\n", fontstr); if(fontstr == NULL || !loadfont(dc, DEFAULTFN)) eprintf("cannot load font '%s'\n", DEFAULTFN); } dc->font.height = dc->font.ascent + dc->font.descent; } Bool loadfont(DC *dc, const char *fontstr) { char *def, **missing, **names; int i, n; XFontStruct **xfonts; if(!*fontstr) return False; if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def))) { n = XFontsOfFontSet(dc->font.set, &xfonts, &names); 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.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; } void mapdc(DC *dc, Window win, unsigned int w, unsigned int h) { XCopyArea(dc->dpy, dc->canvas, win, dc->gc, 0, 0, w, h, 0, 0); } void resizedc(DC *dc, unsigned int w, unsigned int h) { if(dc->canvas) XFreePixmap(dc->dpy, dc->canvas); dc->w = w; dc->h = h; dc->canvas = XCreatePixmap(dc->dpy, DefaultRootWindow(dc->dpy), w, h, DefaultDepth(dc->dpy, DefaultScreen(dc->dpy))); } int textnw(DC *dc, const char *text, size_t len) { if(dc->font.set) { XRectangle r; XmbTextExtents(dc->font.set, text, len, NULL, &r); return r.width; } return XTextWidth(dc->font.xfont, text, len); } int textw(DC *dc, const char *text) { return textnw(dc, text, strlen(text)) + dc->font.height; } dmenu-4.5/dmenu.c0000644000175000017500000003600011702304643012071 0ustar clscls/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #ifdef XINERAMA #include #endif #include "draw.h" #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 MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) typedef struct Item Item; struct Item { char *text; Item *left, *right; }; 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 void grabkeyboard(void); static void insert(const char *str, ssize_t n); static void keypress(XKeyEvent *ev); static void match(void); static size_t nextrune(int inc); static void paste(void); static void readstdin(void); static void run(void); static void setup(void); static void usage(void); static char text[BUFSIZ] = ""; static int bh, mw, mh; static int inputw, promptw; static size_t cursor = 0; static const char *font = NULL; static const char *prompt = NULL; static const char *normbgcolor = "#222222"; static const char *normfgcolor = "#bbbbbb"; static const char *selbgcolor = "#005577"; static const char *selfgcolor = "#eeeeee"; static unsigned int lines = 0; static unsigned long normcol[ColLast]; static unsigned long selcol[ColLast]; static Atom clip, utf8; static Bool topbar = True; static DC *dc; static Item *items = NULL; static Item *matches, *matchend; static Item *prev, *curr, *next, *sel; static Window win; static XIC xic; static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static char *(*fstrstr)(const char *, const char *) = strstr; int main(int argc, char *argv[]) { Bool fast = False; int i; for(i = 1; i < argc; i++) /* these options take no arguments */ if(!strcmp(argv[i], "-v")) { /* prints version information */ puts("dmenu-"VERSION", © 2006-2012 dmenu engineers, see LICENSE for details"); exit(EXIT_SUCCESS); } else if(!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ topbar = False; else if(!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ fast = True; else if(!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; fstrstr = cistrstr; } else if(i+1 == argc) usage(); /* these options take one argument */ else if(!strcmp(argv[i], "-l")) /* number of lines in vertical list */ lines = atoi(argv[++i]); else if(!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ prompt = argv[++i]; else if(!strcmp(argv[i], "-fn")) /* font or font set */ font = argv[++i]; else if(!strcmp(argv[i], "-nb")) /* normal background color */ normbgcolor = argv[++i]; else if(!strcmp(argv[i], "-nf")) /* normal foreground color */ normfgcolor = argv[++i]; else if(!strcmp(argv[i], "-sb")) /* selected background color */ selbgcolor = argv[++i]; else if(!strcmp(argv[i], "-sf")) /* selected foreground color */ selfgcolor = argv[++i]; else usage(); dc = initdc(); initfont(dc, font); if(fast) { grabkeyboard(); readstdin(); } else { readstdin(); grabkeyboard(); } setup(); run(); return 1; /* unreachable */ } void appenditem(Item *item, Item **list, Item **last) { if(*last) (*last)->right = item; else *list = item; item->left = *last; item->right = NULL; *last = item; } void calcoffsets(void) { int i, n; if(lines > 0) n = lines * bh; else n = mw - (promptw + inputw + textw(dc, "<") + textw(dc, ">")); /* 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(dc, next->text), n)) > n) break; for(i = 0, prev = curr; prev && prev->left; prev = prev->left) if((i += (lines > 0) ? bh : MIN(textw(dc, prev->left->text), n)) > n) 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; Item *item; dc->x = 0; dc->y = 0; dc->h = bh; drawrect(dc, 0, 0, mw, mh, True, BG(dc, normcol)); if(prompt) { dc->w = promptw; drawtext(dc, prompt, selcol); dc->x = dc->w; } /* draw input field */ dc->w = (lines > 0 || !matches) ? mw - dc->x : inputw; drawtext(dc, text, normcol); if((curpos = textnw(dc, text, cursor) + dc->h/2 - 2) < dc->w) drawrect(dc, curpos, 2, 1, dc->h - 4, True, FG(dc, normcol)); if(lines > 0) { /* draw vertical list */ dc->w = mw - dc->x; for(item = curr; item != next; item = item->right) { dc->y += dc->h; drawtext(dc, item->text, (item == sel) ? selcol : normcol); } } else if(matches) { /* draw horizontal list */ dc->x += inputw; dc->w = textw(dc, "<"); if(curr->left) drawtext(dc, "<", normcol); for(item = curr; item != next; item = item->right) { dc->x += dc->w; dc->w = MIN(textw(dc, item->text), mw - dc->x - textw(dc, ">")); drawtext(dc, item->text, (item == sel) ? selcol : normcol); } dc->w = textw(dc, ">"); dc->x = mw - dc->w; if(next) drawtext(dc, ">", normcol); } mapdc(dc, win, mw, mh); } void grabkeyboard(void) { int i; /* try to grab keyboard, we may have to wait for another process to ungrab */ for(i = 0; i < 1000; i++) { if(XGrabKeyboard(dc->dpy, DefaultRootWindow(dc->dpy), True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) return; usleep(1000); } eprintf("cannot grab keyboard\n"); } void insert(const char *str, ssize_t n) { if(strlen(text) + n > sizeof text - 1) return; /* move existing text out of the way, insert new text, and update cursor */ memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); if(n > 0) memcpy(&text[cursor], str, n); cursor += n; match(); } void keypress(XKeyEvent *ev) { char buf[32]; int len; KeySym ksym = NoSymbol; Status status; len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); if(status == XBufferOverflow) return; if(ev->state & ControlMask) switch(ksym) { 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_Down; break; case XK_p: ksym = XK_Up; break; case XK_k: /* delete right */ text[cursor] = '\0'; match(); break; case XK_u: /* delete left */ insert(NULL, 0 - cursor); break; 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 */ XConvertSelection(dc->dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, utf8, utf8, win, CurrentTime); return; default: return; } else if(ev->state & Mod1Mask) switch(ksym) { case XK_g: ksym = XK_Home; break; case XK_G: ksym = XK_End; break; case XK_h: ksym = XK_Up; break; case XK_j: ksym = XK_Next; break; case XK_k: ksym = XK_Prior; break; case XK_l: ksym = XK_Down; break; default: return; } switch(ksym) { default: if(!iscntrl(*buf)) insert(buf, len); break; case XK_Delete: if(text[cursor] == '\0') return; cursor = nextrune(+1); /* fallthrough */ case XK_BackSpace: if(cursor == 0) return; insert(NULL, nextrune(-1) - cursor); break; case XK_End: if(text[cursor] != '\0') { cursor = strlen(text); break; } if(next) { /* jump to end of list and position items in reverse */ curr = matchend; calcoffsets(); curr = prev; calcoffsets(); while(next && (curr = curr->right)) calcoffsets(); } sel = matchend; break; case XK_Escape: exit(EXIT_FAILURE); case XK_Home: if(sel == matches) { cursor = 0; break; } sel = curr = matches; calcoffsets(); break; case XK_Left: if(cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; } if(lines > 0) return; /* fallthrough */ case XK_Up: if(sel && sel->left && (sel = sel->left)->right == curr) { curr = prev; calcoffsets(); } break; case XK_Next: if(!next) return; sel = curr = next; calcoffsets(); break; case XK_Prior: if(!prev) return; sel = curr = prev; calcoffsets(); break; case XK_Return: case XK_KP_Enter: puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); exit(EXIT_SUCCESS); case XK_Right: if(text[cursor] != '\0') { cursor = nextrune(+1); break; } if(lines > 0) return; /* fallthrough */ case XK_Down: if(sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); } break; case XK_Tab: if(!sel) return; strncpy(text, sel->text, sizeof text); cursor = strlen(text); match(); break; } drawmenu(); } void 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); /* 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))) 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) /* not all tokens match */ continue; /* exact matches go first, then prefixes, then substrings */ 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 appenditem(item, &lsubstr, &substrend); } if(lprefix) { if(matches) { matchend->right = lprefix; lprefix->left = matchend; } else matches = lprefix; matchend = prefixend; } if(lsubstr) { if(matches) { matchend->right = lsubstr; lsubstr->left = matchend; } else matches = lsubstr; matchend = substrend; } curr = sel = matches; calcoffsets(); } size_t nextrune(int inc) { ssize_t n; /* return location of next utf8 rune in the given direction (+1 or -1) */ for(n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc); return n; } void paste(void) { char *p, *q; int di; unsigned long dl; Atom da; /* we have been given the current selection, now insert it into input */ XGetWindowProperty(dc->dpy, win, utf8, 0, (sizeof text / 4) + 1, False, utf8, &da, &di, &dl, &dl, (unsigned char **)&p); insert(p, (q = strchr(p, '\n')) ? q-p : (ssize_t)strlen(p)); XFree(p); drawmenu(); } void readstdin(void) { char buf[sizeof text], *p, *maxstr = NULL; size_t i, max = 0, size = 0; /* 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)))) eprintf("cannot realloc %u bytes:", size); if((p = strchr(buf, '\n'))) *p = '\0'; if(!(items[i].text = strdup(buf))) eprintf("cannot strdup %u bytes:", strlen(buf)+1); if(strlen(items[i].text) > max) max = strlen(maxstr = items[i].text); } if(items) items[i].text = NULL; inputw = maxstr ? textw(dc, maxstr) : 0; lines = MIN(lines, i); } void run(void) { XEvent ev; while(!XNextEvent(dc->dpy, &ev)) { if(XFilterEvent(&ev, win)) continue; switch(ev.type) { case Expose: if(ev.xexpose.count == 0) mapdc(dc, win, mw, mh); break; case KeyPress: keypress(&ev.xkey); break; case SelectionNotify: if(ev.xselection.property == utf8) paste(); break; case VisibilityNotify: if(ev.xvisibility.state != VisibilityUnobscured) XRaiseWindow(dc->dpy, win); break; } } } void setup(void) { int x, y, screen = DefaultScreen(dc->dpy); Window root = RootWindow(dc->dpy, screen); XSetWindowAttributes swa; XIM xim; #ifdef XINERAMA int n; XineramaScreenInfo *info; #endif normcol[ColBG] = getcolor(dc, normbgcolor); normcol[ColFG] = getcolor(dc, normfgcolor); selcol[ColBG] = getcolor(dc, selbgcolor); selcol[ColFG] = getcolor(dc, selfgcolor); clip = XInternAtom(dc->dpy, "CLIPBOARD", False); utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False); /* calculate menu geometry */ bh = dc->font.height + 2; lines = MAX(lines, 0); mh = (lines + 1) * bh; #ifdef XINERAMA if((info = XineramaQueryScreens(dc->dpy, &n))) { int a, j, di, i = 0, area = 0; unsigned int du; Window w, pw, dw, *dws; XWindowAttributes wa; XGetInputFocus(dc->dpy, &w, &di); if(w != root && w != PointerRoot && w != None) { /* find top-level window containing current input focus */ do { if(XQueryTree(dc->dpy, (pw = w), &dw, &w, &dws, &du) && dws) XFree(dws); } while(w != root && w != pw); /* find xinerama screen with which the window intersects most */ if(XGetWindowAttributes(dc->dpy, pw, &wa)) for(j = 0; j < n; j++) if((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { area = a; i = j; } } /* no focused window is on screen, so use pointer location instead */ if(!area && XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) for(i = 0; i < n; i++) if(INTERSECT(x, y, 1, 1, info[i])) break; x = info[i].x_org; y = info[i].y_org + (topbar ? 0 : info[i].height - mh); mw = info[i].width; XFree(info); } else #endif { x = 0; y = topbar ? 0 : DisplayHeight(dc->dpy, screen) - mh; mw = DisplayWidth(dc->dpy, screen); } promptw = prompt ? textw(dc, prompt) : 0; inputw = MIN(inputw, mw/3); match(); /* create menu window */ swa.override_redirect = True; swa.background_pixel = normcol[ColBG]; 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 | CWBackPixel | CWEventMask, &swa); /* open input methods */ xim = XOpenIM(dc->dpy, NULL, NULL, NULL); xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, XNFocusWindow, win, NULL); XMapRaised(dc->dpy, win); resizedc(dc, mw, mh); drawmenu(); } void usage(void) { fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-p prompt] [-fn font]\n" " [-nb color] [-nf color] [-sb color] [-sf color] [-v]\n", stderr); exit(EXIT_FAILURE); } dmenu-4.5/draw.h0000644000175000017500000000206411702304643011726 0ustar clscls/* See LICENSE file for copyright and license details. */ #define FG(dc, col) ((col)[(dc)->invert ? ColBG : ColFG]) #define BG(dc, col) ((col)[(dc)->invert ? ColFG : ColBG]) enum { ColBG, ColFG, ColBorder, ColLast }; typedef struct { int x, y, w, h; Bool invert; Display *dpy; GC gc; Pixmap canvas; struct { int ascent; int descent; int height; int width; XFontSet set; XFontStruct *xfont; } font; } DC; /* draw context */ void drawrect(DC *dc, int x, int y, unsigned int w, unsigned int h, Bool fill, unsigned long color); void drawtext(DC *dc, const char *text, unsigned long col[ColLast]); void drawtextn(DC *dc, const char *text, size_t n, unsigned long col[ColLast]); void eprintf(const char *fmt, ...); void freedc(DC *dc); unsigned long getcolor(DC *dc, const char *colstr); DC *initdc(void); void initfont(DC *dc, const char *fontstr); void mapdc(DC *dc, Window win, unsigned int w, unsigned int h); void resizedc(DC *dc, unsigned int w, unsigned int h); int textnw(DC *dc, const char *text, size_t len); int textw(DC *dc, const char *text); dmenu-4.5/stest.10000644000175000017500000000267311702304643012052 0ustar clscls.TH STEST 1 dmenu\-VERSION .SH NAME stest \- filter a list of files by properties .SH SYNOPSIS .B stest .RB [ -abcdefghlpqrsuwx ] .RB [ -n .IR file ] .RB [ -o .IR file ] .RI [ file ...] .SH DESCRIPTION .B stest takes a list of files and filters by the files' properties, analogous to .IR test (1). Files which pass all tests are printed to stdout. If no files are given, stest reads files from stdin. .SH OPTIONS .TP .B \-a Test hidden files. .TP .B \-b Test that files are block specials. .TP .B \-c Test that files are character specials. .TP .B \-d Test that files are directories. .TP .B \-e Test that files exist. .TP .B \-f Test that files are regular files. .TP .B \-g Test that files have their set-group-ID flag set. .TP .B \-h Test that files are symbolic links. .TP .B \-l Test the contents of a directory given as an argument. .TP .BI \-n " file" Test that files are newer than .IR file . .TP .BI \-o " file" Test that files are older than .IR file . .TP .B \-p Test that files are named pipes. .TP .B \-q No files are printed, only the exit status is returned. .TP .B \-r Test that files are readable. .TP .B \-s Test that files are not empty. .TP .B \-u Test that files have their set-user-ID flag set. .TP .B \-w Test that files are writable. .TP .B \-x Test that files are executable. .SH EXIT STATUS .TP .B 0 At least one file passed all tests. .TP .B 1 No files passed all tests. .TP .B 2 An error occurred. .SH SEE ALSO .IR dmenu (1), .IR test (1) dmenu-4.5/Makefile0000644000175000017500000000353711702304643012266 0ustar clscls# dmenu - dynamic menu # See LICENSE file for copyright and license details. include config.mk SRC = dmenu.c draw.c stest.c OBJ = ${SRC:.c=.o} all: options dmenu stest options: @echo dmenu build options: @echo "CFLAGS = ${CFLAGS}" @echo "LDFLAGS = ${LDFLAGS}" @echo "CC = ${CC}" .c.o: @echo CC -c $< @${CC} -c $< ${CFLAGS} ${OBJ}: config.mk draw.h dmenu: dmenu.o draw.o @echo CC -o $@ @${CC} -o $@ dmenu.o draw.o ${LDFLAGS} stest: stest.o @echo CC -o $@ @${CC} -o $@ stest.o ${LDFLAGS} clean: @echo cleaning @rm -f dmenu stest ${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 stest.1 ${SRC} dmenu-${VERSION} @tar -cf dmenu-${VERSION}.tar dmenu-${VERSION} @gzip dmenu-${VERSION}.tar @rm -rf dmenu-${VERSION} install: all @echo installing executables to ${DESTDIR}${PREFIX}/bin @mkdir -p ${DESTDIR}${PREFIX}/bin @cp -f dmenu dmenu_run stest ${DESTDIR}${PREFIX}/bin @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run @chmod 755 ${DESTDIR}${PREFIX}/bin/stest @echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1 @sed "s/VERSION/${VERSION}/g" < dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/dmenu.1 @sed "s/VERSION/${VERSION}/g" < stest.1 > ${DESTDIR}${MANPREFIX}/man1/stest.1 @chmod 644 ${DESTDIR}${MANPREFIX}/man1/dmenu.1 @chmod 644 ${DESTDIR}${MANPREFIX}/man1/stest.1 uninstall: @echo removing executables from ${DESTDIR}${PREFIX}/bin @rm -f ${DESTDIR}${PREFIX}/bin/dmenu @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run @rm -f ${DESTDIR}${PREFIX}/bin/stest @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 @rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1 @rm -f ${DESTDIR}${MANPREFIX}/man1/stest.1 .PHONY: all options clean dist install uninstall dmenu-4.5/dmenu_run0000755000175000017500000000053611702304643012544 0ustar clscls#!/bin/sh cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"} if [ -d "$cachedir" ]; then cache=$cachedir/dmenu_run else cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~ fi ( IFS=: if stest -dqr -n "$cache" $PATH; then stest -flx $PATH | sort -u | tee "$cache" | dmenu "$@" else dmenu "$@" < "$cache" fi ) | ${SHELL:-"/bin/sh"} &