diff --git a/README.md b/README.md new file mode 100644 index 0000000..3929935 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +dmenu-db +======== + +This version of dmenu is a fork of the +[official version 4.2.1](http://tools.suckless.org/dmenu/). It adds ... + +* support for xft fonts (eg. [Inconsolata](http://www.levien.com/type/myfonts/inconsolata.html) ) +* multiple token search, ie typing "ong nam" will match items with names + like "xyz123abc456ThisIsAVeryLongFileNameThatIsDifficultToType" +* a few miscellaneous changes under the hood to make the code tidier + +To use an xft font, use the normal "-fn FontName" option, and supply a valid +xft font name, eg. "Inconsolata-15". This command also recognizes the names of +regular x fonts and font sets, eg. "fixed". + +These enhancements make dmenu suitable for use as an instant-search +navigator in the command line music player interface called [muss](http://github.com/dbro/muss) + +Dan Brown, 2010 + diff --git a/config.mk b/config.mk index ebaab81..1e09c70 100644 --- a/config.mk +++ b/config.mk @@ -14,9 +14,13 @@ X11LIB = /usr/X11R6/lib XINERAMALIBS = -lXinerama XINERAMAFLAGS = -DXINERAMA +# Xft, comment if you don't want it +XFTINC = /usr/include/freetype2 +XFTLIBS = -lXft -lXrender -lfreetype -lz -lfontconfig + # includes and libs -INCS = -I${X11INC} -LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} +INCS = -I${X11INC} -I${XFTINC} +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${XFTLIBS} # flags CPPFLAGS = -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} diff --git a/dmenu.1 b/dmenu.1 index d2a93d1..bae6cc0 100644 --- a/dmenu.1 +++ b/dmenu.1 @@ -4,9 +4,13 @@ dmenu \- dynamic menu .SH SYNOPSIS .B dmenu .RB [ \-b ] +.RB [ \-f +.IR filter ] .RB [ \-i ] .RB [ \-l .IR lines ] +.RB [ \-t +.IR token matching ] .RB [ \-m .IR monitor ] .RB [ \-p @@ -47,9 +51,15 @@ is a program used by dmenu_run to find and cache a list of executables. .B \-b dmenu appears at the bottom of the screen. .TP +.BI \-f " filter" +return all matching items, not only the one at the cursor. +.TP .B \-i dmenu matches menu items case insensitively. .TP +.BI \-t " token matching" +match fragments of the full items. +.TP .BI \-l " lines" dmenu lists items vertically, with the given number of lines. .TP @@ -60,7 +70,7 @@ dmenu appears on the given Xinerama screen. defines the prompt to be displayed to the left of the input field. .TP .BI \-fn " font" -defines the font or font set used. +defines the font or font set used. eg. "fixed" or "Monospace-12:normal" (an xft font) .TP .BI \-nb " color" defines the normal background color. diff --git a/dmenu.c b/dmenu.c index a24dfe3..60fd752 100644 --- a/dmenu.c +++ b/dmenu.c @@ -15,6 +15,7 @@ #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define DEFFONT "fixed" /* xft example: "Monospace-11" */ typedef struct Item Item; struct Item { @@ -25,12 +26,14 @@ struct Item { static void appenditem(Item *item, Item **list, Item **last); static void calcoffsets(void); +static void cleanup(void); static void drawmenu(void); static char *fstrstr(const char *s, const char *sub); static void grabkeyboard(void); static void insert(const char *s, ssize_t n); static void keypress(XKeyEvent *ev); -static void match(void); +static void matchstr(void); +static void matchtok(void); static size_t nextrune(int incr); static void paste(void); static void readstdin(void); @@ -51,10 +54,13 @@ static const char *normbgcolor = "#cccccc"; static const char *normfgcolor = "#000000"; static const char *selbgcolor = "#0066ff"; static const char *selfgcolor = "#ffffff"; -static unsigned long normcol[ColLast]; -static unsigned long selcol[ColLast]; +static ColorSet *normcol; +static ColorSet *selcol; static Atom utf8; +static Bool filter = False; static Bool topbar = True; +static Bool running = True; +static int ret = 0; static DC *dc; static Item *items = NULL; static Item *matches, *sel; @@ -62,6 +68,7 @@ static Item *prev, *curr, *next; static Window root, win; static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static void (*match)(void) = matchstr; int main(int argc, char *argv[]) { @@ -76,8 +83,12 @@ main(int argc, char *argv[]) { } else if(!strcmp(argv[i], "-b")) topbar = False; + else if(!strcmp(argv[i], "-f")) + filter = True; else if(!strcmp(argv[i], "-i")) fstrncmp = strncasecmp; + else if(!strcmp(argv[i], "-t")) + match = matchtok; else if(i == argc-1) usage(); /* double flags */ @@ -101,12 +112,14 @@ main(int argc, char *argv[]) { usage(); dc = initdc(); - initfont(dc, font); + initfont(dc, font ? font : DEFFONT); + normcol = initcolor(dc, normfgcolor, normbgcolor); + selcol = initcolor(dc, selfgcolor, selbgcolor); readstdin(); setup(); run(); - - return EXIT_FAILURE; /* should not reach */ + cleanup(); + return ret; } void @@ -138,6 +151,22 @@ calcoffsets(void) { } void +cleanup(void) { + Item *itm; + while(items) { + itm = items->next; + free(items->text); + free(items); + items = itm; + } + freecol(dc, normcol); + freecol(dc, selcol); + XDestroyWindow(dc->dpy, win); + XUngrabKeyboard(dc->dpy, CurrentTime); + freedc(dc); +} + +void drawmenu(void) { int curpos; Item *item; @@ -145,7 +174,7 @@ drawmenu(void) { dc->x = 0; dc->y = 0; dc->h = bh; - drawrect(dc, 0, 0, mw, mh, True, BG(dc, normcol)); + drawrect(dc, 0, 0, mw, mh, True, normcol->BG); if(prompt) { dc->w = promptw; @@ -155,7 +184,7 @@ drawmenu(void) { 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)); + drawrect(dc, curpos, 2, 1, dc->h - 4, True, normcol->FG); if(lines > 0) { dc->w = mw - dc->x; @@ -304,7 +333,8 @@ keypress(XKeyEvent *ev) { sel = sel->right; break; case XK_Escape: - exit(EXIT_FAILURE); + ret = EXIT_FAILURE; + running = False; case XK_Home: if(sel == matches) { cursor = 0; @@ -340,9 +370,18 @@ keypress(XKeyEvent *ev) { break; case XK_Return: case XK_KP_Enter: - fputs((sel && !(ev->state & ShiftMask)) ? sel->text : text, stdout); + if((ev->state & ShiftMask) || !sel) + puts(text); + else if(filter) { + for(Item *item = sel; item; item = item->right) + puts(item->text); + for(Item *item = matches; item != sel; item = item->right) + puts(item->text); + } else + puts(sel->text); fflush(stdout); - exit(EXIT_SUCCESS); + ret = EXIT_SUCCESS; + running = False; case XK_Right: if(cursor < len) { cursor = nextrune(+1); @@ -368,7 +407,7 @@ keypress(XKeyEvent *ev) { } void -match(void) { +matchstr(void) { size_t len; Item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend; @@ -407,6 +446,33 @@ match(void) { calcoffsets(); } +void +matchtok(void) { + char buf[sizeof text]; + char **tokv, *s; + int tokc, i; + Item *item, *end; + + tokc = 0; + tokv = NULL; + strcpy(buf, text); + for(s = strtok(buf, " "); s; tokv[tokc-1] = s, s = strtok(NULL, " ")) + if(!(tokv = realloc(tokv, ++tokc * sizeof *tokv))) + eprintf("cannot realloc %u bytes\n", tokc * sizeof *tokv); + + matches = end = NULL; + for(item = items; item; item = item->next) { + for(i = 0; i < tokc; i++) + if(!fstrstr(item->text, tokv[i])) + break; + if(i == tokc) + appenditem(item, &matches, &end); + } + free(tokv); + curr = prev = next = sel = matches; + calcoffsets(); +} + size_t nextrune(int incr) { size_t n, len; @@ -451,7 +517,7 @@ void run(void) { XEvent ev; - while(!XNextEvent(dc->dpy, &ev)) + while(running && !XNextEvent(dc->dpy, &ev)) switch(ev.type) { case Expose: if(ev.xexpose.count == 0) @@ -483,13 +549,8 @@ setup(void) { screen = DefaultScreen(dc->dpy); root = RootWindow(dc->dpy, screen); utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False); - - normcol[ColBG] = getcolor(dc, normbgcolor); - normcol[ColFG] = getcolor(dc, normfgcolor); - selcol[ColBG] = getcolor(dc, selbgcolor); - selcol[ColFG] = getcolor(dc, selfgcolor); - - /* menu geometry */ + + /* menu geometry */ bh = dc->font.height + 2; lines = MAX(lines, 0); mh = (lines + 1) * bh; @@ -536,7 +597,7 @@ setup(void) { void usage(void) { - fputs("usage: dmenu [-b] [-i] [-l lines] [-m monitor] [-p prompt] [-fn font]\n" - " [-nb color] [-nf color] [-sb color] [-sf color] [-v]\n", stderr); + fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-m monitor] [-p prompt] [-fn font]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-t] [-v]\n", stderr); exit(EXIT_FAILURE); } diff --git a/draw.c b/draw.c index 28c658c..cec5650 100644 --- a/draw.c +++ b/draw.c @@ -5,13 +5,11 @@ #include #include #include +#include #include "draw.h" #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define DEFFONT "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) { @@ -27,7 +25,7 @@ drawrect(DC *dc, int x, int y, unsigned int w, unsigned int h, Bool fill, unsign void -drawtext(DC *dc, const char *text, unsigned long col[ColLast]) { +drawtext(DC *dc, const char *text, ColorSet *col) { char buf[256]; size_t n, mn; @@ -40,21 +38,26 @@ drawtext(DC *dc, const char *text, unsigned long col[ColLast]) { 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)); + drawrect(dc, 0, 0, dc->w, dc->h, True, col->BG); drawtextn(dc, buf, mn, col); } void -drawtextn(DC *dc, const char *text, size_t n, unsigned long col[ColLast]) { +drawtextn(DC *dc, const char *text, size_t n, ColorSet *col) { int x, y; x = dc->x + dc->font.height/2; y = dc->y + dc->font.ascent+1; - XSetForeground(dc->dpy, dc->gc, FG(dc, col)); - if(dc->font.set) + XSetForeground(dc->dpy, dc->gc, col->FG); + if(dc->font.xft_font) { + if (!dc->xftdraw) + eprintf("error, xft drawable does not exist"); + XftDrawStringUtf8(dc->xftdraw, &col->FG_xft, + dc->font.xft_font, x, y, (unsigned char*)text, n); + } else if(dc->font.set) { XmbDrawString(dc->dpy, dc->canvas, dc->font.set, dc->gc, x, y, text, n); - else { + } else { XSetFont(dc->dpy, dc->gc, dc->font.xfont->fid); XDrawString(dc->dpy, dc->canvas, dc->gc, x, y, text, n); } @@ -72,16 +75,33 @@ eprintf(const char *fmt, ...) { } void +freecol(DC *dc, ColorSet *col) { + if(col) { + if(&col->FG_xft) + XftColorFree(dc->dpy, DefaultVisual(dc->dpy, DefaultScreen(dc->dpy)), + DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)), &col->FG_xft); + free(col); + } +} + +void freedc(DC *dc) { + if(dc->font.xft_font) { + XftFontClose(dc->dpy, dc->font.xft_font); + XftDrawDestroy(dc->xftdraw); + } if(dc->font.set) XFreeFontSet(dc->dpy, dc->font.set); - if(dc->font.xfont) + if(dc->font.xfont) XFreeFont(dc->dpy, dc->font.xfont); - if(dc->canvas) + if(dc->canvas) XFreePixmap(dc->dpy, dc->canvas); - XFreeGC(dc->dpy, dc->gc); - XCloseDisplay(dc->dpy); - free(dc); + if(dc->gc) + XFreeGC(dc->dpy, dc->gc); + if(dc->dpy) + XCloseDisplay(dc->dpy); + if(dc) + free(dc); } unsigned long @@ -94,6 +114,20 @@ getcolor(DC *dc, const char *colstr) { return color.pixel; } +ColorSet * +initcolor(DC *dc, const char * foreground, const char * background) { + ColorSet * col = (ColorSet *)malloc(sizeof(ColorSet)); + if(!col) + eprintf("error, cannot allocate memory for color set"); + col->BG = getcolor(dc, background); + col->FG = getcolor(dc, foreground); + if(dc->font.xft_font) + if(!XftColorAllocName(dc->dpy, DefaultVisual(dc->dpy, DefaultScreen(dc->dpy)), + DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)), foreground, &col->FG_xft)) + eprintf("error, cannot allocate xft font color '%s'\n", foreground); + return col; +} + DC * initdc(void) { DC *dc; @@ -109,29 +143,21 @@ initdc(void) { XSetLineAttributes(dc->dpy, dc->gc, 1, LineSolid, CapButt, JoinMiter); dc->font.xfont = NULL; dc->font.set = NULL; + dc->font.xft_font = NULL; dc->canvas = None; + dc->xftdraw = NULL; return dc; } void initfont(DC *dc, const char *fontstr) { - if(!loadfont(dc, fontstr ? fontstr : DEFFONT)) { - if(fontstr != NULL) - weprintf("cannot load font '%s'\n", fontstr); - if(fontstr == NULL || !loadfont(dc, DEFFONT)) - eprintf("cannot load font '%s'\n", DEFFONT); - } - dc->font.height = dc->font.ascent + dc->font.descent; -} - -Bool -loadfont(DC *dc, const char *fontstr) { - char *def, **missing; + char *def, **missing=NULL; int i, n; - if(!*fontstr) - return False; - if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def))) { + if((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr))) { + dc->font.ascent = dc->font.xfont->ascent; + dc->font.descent = dc->font.xfont->descent; + } else if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def))) { char **names; XFontStruct **xfonts; @@ -140,14 +166,16 @@ loadfont(DC *dc, const char *fontstr) { dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent); dc->font.descent = MAX(dc->font.descent, xfonts[i]->descent); } - } - else if((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr))) { - dc->font.ascent = dc->font.xfont->ascent; - dc->font.descent = dc->font.xfont->descent; - } + } else if((dc->font.xft_font = XftFontOpenName(dc->dpy, + DefaultScreen(dc->dpy), fontstr))) { + dc->font.ascent = dc->font.xft_font->ascent; + dc->font.descent = dc->font.xft_font->descent; + } else { + eprintf("cannot load font '%s'\n", fontstr); + } if(missing) XFreeStringList(missing); - return (dc->font.set || dc->font.xfont); + dc->font.height = dc->font.ascent + dc->font.descent; } void @@ -157,25 +185,34 @@ mapdc(DC *dc, Window win, unsigned int w, unsigned int h) { void resizedc(DC *dc, unsigned int w, unsigned int h) { + int screen = DefaultScreen(dc->dpy); if(dc->canvas) XFreePixmap(dc->dpy, dc->canvas); dc->canvas = XCreatePixmap(dc->dpy, DefaultRootWindow(dc->dpy), w, h, - DefaultDepth(dc->dpy, DefaultScreen(dc->dpy))); + DefaultDepth(dc->dpy, screen)); dc->x = dc->y = 0; dc->w = w; dc->h = h; - dc->invert = False; + if(dc->font.xft_font && !(dc->xftdraw)) { + dc->xftdraw = XftDrawCreate(dc->dpy, dc->canvas, DefaultVisual(dc->dpy,screen), DefaultColormap(dc->dpy,screen)); + if(!(dc->xftdraw)) + eprintf("error, cannot create xft drawable\n"); + } } int textnw(DC *dc, const char *text, size_t len) { - if(dc->font.set) { + if(dc->font.xft_font) { + XGlyphInfo gi; + XftTextExtentsUtf8(dc->dpy, dc->font.xft_font, (const FcChar8*)text, len, &gi); + return gi.width; + } else if(dc->font.set) { XRectangle r; - XmbTextExtents(dc->font.set, text, len, NULL, &r); return r.width; - } - return XTextWidth(dc->font.xfont, text, len); + } else { + return XTextWidth(dc->font.xfont, text, len); + } } int diff --git a/draw.h b/draw.h index ac3943f..d408c20 100644 --- a/draw.h +++ b/draw.h @@ -1,32 +1,37 @@ /* 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 }; +#include typedef struct { int x, y, w, h; - Bool invert; Display *dpy; GC gc; Pixmap canvas; + XftDraw *xftdraw; struct { int ascent; int descent; int height; XFontSet set; XFontStruct *xfont; + XftFont *xft_font; } font; } DC; /* draw context */ +typedef struct { + unsigned long FG; + XftColor FG_xft; + unsigned long BG; +} ColorSet; + unsigned long getcolor(DC *dc, const char *colstr); 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 initfont(DC *dc, const char *fontstr); +void drawtext(DC *dc, const char *text, ColorSet *col); +void drawtextn(DC *dc, const char *text, size_t n, ColorSet *col); +void freecol(DC *dc, ColorSet *col); void freedc(DC *dc); +ColorSet *initcolor(DC *dc, const char *foreground, const char *background); 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);