diff options
| -rw-r--r-- | config.def.h | 26 | ||||
| -rw-r--r-- | config.mk | 3 | ||||
| -rw-r--r-- | dmenu.1 | 5 | ||||
| -rw-r--r-- | dmenu.c | 223 | ||||
| -rw-r--r-- | drw.c | 88 | ||||
| -rw-r--r-- | drw.h | 1 | ||||
| -rw-r--r-- | stest.c | 2 |
7 files changed, 154 insertions, 194 deletions
diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..d7df283 --- /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 */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#ebdbb2", "#282828" }, + [SchemeSel] = { "#ebdbb2", "#928374" }, + [SchemeOut] = { "#ebdbb2", "#8ec07c" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 22; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* Size of the window border */ +static const unsigned int border_width = 3; @@ -1,5 +1,5 @@ # dmenu version -VERSION = 5.0 +VERSION = 5.1 # paths PREFIX = /usr/local @@ -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) @@ -6,8 +6,6 @@ dmenu \- dynamic menu .RB [ \-bfiv ] .RB [ \-l .IR lines ] -.RB [ \-h -.IR height ] .RB [ \-m .IR monitor ] .RB [ \-p @@ -52,9 +50,6 @@ dmenu matches menu items case insensitively. .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. @@ -58,6 +58,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) { @@ -82,13 +89,22 @@ 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) { @@ -97,19 +113,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; } @@ -131,7 +157,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; drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, 0, 0, mw, mh, 1, 1); @@ -148,13 +174,13 @@ 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) { /* draw vertical list */ for (item = curr; item != next; item = item->right) - drawitem(item, x - promptw, y += bh, mw - x); + drawitem(item, x - promptw, y += bh, mw); } else if (matches) { /* draw horizontal list */ x += inputw; @@ -165,7 +191,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]); @@ -225,7 +251,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; @@ -360,9 +386,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: @@ -396,10 +424,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); @@ -410,6 +439,7 @@ insert: insert(NULL, nextrune(-1) - cursor); break; case XK_End: + case XK_KP_End: if (text[cursor] != '\0') { cursor = strlen(text); break; @@ -429,6 +459,7 @@ insert: cleanup(); exit(1); case XK_Home: + case XK_KP_Home: if (sel == matches) { cursor = 0; break; @@ -437,6 +468,7 @@ insert: calcoffsets(); break; case XK_Left: + case XK_KP_Left: if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; @@ -445,18 +477,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; @@ -473,6 +508,7 @@ insert: sel->out = 1; break; case XK_Right: + case XK_KP_Right: if (text[cursor] != '\0') { cursor = nextrune(+1); break; @@ -481,6 +517,7 @@ insert: return; /* fallthrough */ case XK_Down: + case XK_KP_Down: if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); @@ -501,119 +538,6 @@ draw: } static void -buttonpress(XEvent *e) -{ - struct item *item; - XButtonPressedEvent *ev = &e->xbutton; - int x = 0, y = 0, h = bh, w; - - if (ev->window != win) - return; - - /* right-click: exit */ - if (ev->button == Button3) - exit(1); - - if (prompt && *prompt) - x += promptw; - - /* input field */ - w = (lines > 0 || !matches) ? mw - x : inputw; - - /* left-click on input: clear input, - * NOTE: if there is no left-arrow the space for < is reserved so - * add that to the input width */ - if (ev->button == Button1 && - ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + - ((!prev || !curr->left) ? TEXTW("<") : 0)) || - (lines > 0 && ev->y >= y && ev->y <= y + h))) { - insert(NULL, -cursor); - drawmenu(); - return; - } - /* middle-mouse click: paste selection */ - if (ev->button == Button2) { - XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, - utf8, utf8, win, CurrentTime); - drawmenu(); - return; - } - /* scroll up */ - if (ev->button == Button4 && prev) { - sel = curr = prev; - calcoffsets(); - drawmenu(); - return; - } - /* scroll down */ - if (ev->button == Button5 && next) { - sel = curr = next; - calcoffsets(); - drawmenu(); - return; - } - if (ev->button != Button1) - return; - if (ev->state & ~ControlMask) - return; - if (lines > 0) { - /* vertical list: (ctrl)left-click on item */ - w = mw - x; - for (item = curr; item != next; item = item->right) { - y += h; - if (ev->y >= y && ev->y <= (y + h)) { - puts(item->text); - if (!(ev->state & ControlMask)) - exit(0); - sel = item; - if (sel) { - sel->out = 1; - drawmenu(); - } - return; - } - } - } else if (matches) { - /* left-click on left arrow */ - x += inputw; - w = TEXTW("<"); - if (prev && curr->left) { - if (ev->x >= x && ev->x <= x + w) { - sel = curr = prev; - calcoffsets(); - drawmenu(); - return; - } - } - /* horizontal list: (ctrl)left-click on item */ - for (item = curr; item != next; item = item->right) { - x += w; - w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); - if (ev->x >= x && ev->x <= x + w) { - puts(item->text); - if (!(ev->state & ControlMask)) - exit(0); - sel = item; - if (sel) { - sel->out = 1; - drawmenu(); - } - return; - } - } - /* left-click on right arrow */ - w = TEXTW(">"); - x = mw - w; - if (next && ev->x >= x && ev->x <= x + w) { - sel = curr = next; - calcoffsets(); - drawmenu(); - return; - } - } -} - -static void paste(void) { char *p, *q; @@ -635,28 +559,21 @@ static void readstdin(void) { char buf[sizeof text], *p; - size_t i, imax = 0, size = 0; - unsigned int tmpmax = 0; + size_t i, 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)))) - die("cannot realloc %u bytes:", size); + die("cannot realloc %zu bytes:", size); if ((p = strchr(buf, '\n'))) *p = '\0'; if (!(items[i].text = strdup(buf))) - die("cannot strdup %u bytes:", strlen(buf) + 1); + die("cannot strdup %zu 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; - } } if (items) items[i].text = NULL; - inputw = items ? TEXTW(items[imax].text) : 0; lines = MIN(lines, i); } @@ -674,9 +591,6 @@ run(void) break; cleanup(); exit(1); - case ButtonPress: - buttonpress(&ev); - break; case Expose: if (ev.xexpose.count == 0) drw_map(drw, win, 0, 0, mw, mh); @@ -725,9 +639,9 @@ 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))) { @@ -751,12 +665,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; - x = info[i].x_org; - y = info[i].y_org + (topbar ? 0 : info[i].height - mh); - mw = info[i].width; + mw = MIN(MAX(max_textw() + promptw, 100), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); XFree(info); } else #endif @@ -764,22 +678,21 @@ setup(void) if (!XGetWindowAttributes(dpy, parentwin, &wa)) die("could not get embedding window attributes: 0x%lx", parentwin); - x = 0; - y = topbar ? 0 : wa.height - mh; - mw = wa.width; + mw = MIN(MAX(max_textw() + promptw, 100), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; } - 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 | - ButtonPressMask; - win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); XSetClassHint(dpy, win, &ch); @@ -807,7 +720,7 @@ setup(void) static void usage(void) { - fputs("usage: dmenu [-bfiv] [-l lines] [-h height] [-p prompt] [-fn font] [-m monitor]\n" + fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); exit(1); } @@ -835,10 +748,6 @@ main(int argc, char *argv[]) /* 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], "-h")) { /* minimum height of one menu line */ - lineheight = atoi(argv[++i]); - lineheight = MAX(lineheight, min_lineheight); - } else if (!strcmp(argv[i], "-m")) mon = atoi(argv[++i]); else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ @@ -251,12 +251,10 @@ 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 i, ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; - size_t i, len; int utf8strlen, utf8charlen, render = x || y || w || h; long utf8codepoint = 0; const char *utf8str; @@ -264,13 +262,17 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp 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. */ + enum { nomatches_len = 64 }; + static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; + static unsigned int ellipsis_width = 0; - 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); @@ -282,8 +284,10 @@ 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, "..."); while (1) { - utf8strlen = 0; + ew = ellipsis_len = utf8strlen = 0; utf8str = text; nextfont = NULL; while (*text) { @@ -291,9 +295,27 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp for (curfont = drw->fonts; curfont; curfont = curfont->next) { charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); if (charexists) { - if (curfont == usedfont) { + 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) { utf8strlen += utf8charlen; text += utf8charlen; + ew += tmpw; } else { nextfont = curfont; } @@ -301,36 +323,25 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } } - if (!charexists || nextfont) + if (overflow || !charexists || nextfont) 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 (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 +351,12 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp * character must be drawn. */ charexists = 1; + for (i = 0; i < nomatches_len; ++i) { + /* avoid calling XftFontMatch if we know we won't find a match */ + if (utf8codepoint == nomatches.codepoint[i]) + goto no_match; + } + fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, utf8codepoint); @@ -368,6 +385,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.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint; +no_match: usedfont = drw->fonts; } } @@ -397,6 +416,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); } |
