Hello. Continuing the discussion ... Connor Lane Smith <cls_AT_lubutu.com> wrote: > Anselm R Garbe <garbeam_AT_gmail.com> wrote: >> Well for clarity and consistency reasons I prefer if dmenu cleans up >> all resources acquired at the end. So that cleanup() was removes looks >> more like a bug to me and thanks to Dan for pointing this out. >It was intentional. It is not even possible for there to be a memory >leak, so all we get is a slower dmenu with more complex code, with >absolutely no benefit. The more items there are the slower it gets, >and most of the heap doesn't get freed until exit anyway. cleanup() is >like sweeping the floor for the apocalypse. That makes sense. Actually, both approaches make sense to me. I will leave it up to you to decide which way to go.
>On 21 November 2010 03:56, Dan Brown <danbrown_AT_gmail.com> wrote: >> 1) Attached is a patch that enables xft fonts in dmenu 4.2.1. >There has been a discussion about Xft fonts in dmenu before [1]. I >haven't come to a decision myself. Is there really not a way to add >Xft support without making huge changes to the code to compensate? ... >[1]: http://lists.suckless.org/dev/1009/5855.html This was the patch I used as an starting point for adding xft support. After studying it, I came up with a few ways to simplify it that I think you'll appreciate. In particular, I wanted to work within with the existing DC data structure, rather than bolting on whole new fields with different approaches. The patch I sent adds only 2 fields to DC (compared to the patch you reference, which adds 7 or 8 to DC, along with one global variable and a few function variables). The other data change in this patch deals with colors, to allow different data types to be grouped in one place (xft colors and regular x colors are different data types). By harmonizing the data structures, the code becomes much simpler. This made it easy to remove all references to xft from dmenu.c and even made the draw.c code simpler, too. These simplifying improvements may have been obscured by the presence of other code changes in the patch. (and yes, the token matching is taken verbatim from http://tools.suckless.org/dmenu/patches/dmenu-tip-tok.diff) >Also when I applied your patch and tried to run 'dmenu -fn fixed' I >instead got some sans-serif font..? This has been fixed in the attached patch. For some reason, the xft font name lookup is very aggressive, and seems to match even bogus strings to the default xft font. To deal with this behavior, the code will attempt to match xft fonts as a last resort after trying to find regular x fonts and fontsets first. Not ideal, but I couldn't see how to tell the xft font lookup function to be more picky. Dan
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 <stdlib.h> #include <string.h> #include <X11/Xlib.h> +#include <X11/Xft/Xft.h> #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 <X11/Xft/Xft.h> 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);