diff --git a/LICENSE b/LICENSE index 83e4688..d729fc1 100644 --- a/LICENSE +++ b/LICENSE @@ -8,7 +8,7 @@ MIT/X Consortium License © 2009 Markus Schnalke © 2009 Evan Gates © 2010-2012 Connor Lane Smith -© 2014-2020 Hiltjo Posthuma +© 2014-2022 Hiltjo Posthuma © 2015-2019 Quentin Rameau © 2024 Jay Tauron diff --git a/Makefile b/Makefile index fe864c3..af36567 100644 --- a/Makefile +++ b/Makefile @@ -6,13 +6,7 @@ include config.mk SRC = drw.c dmenu.c stest.c util.c OBJ = $(SRC:.c=.o) -all: options dmenu stest - -options: - @echo dmenu build options: - @echo "CFLAGS = $(CFLAGS)" - @echo "LDFLAGS = $(LDFLAGS)" - @echo "CC = $(CC)" +all: dmenu stest .c.o: $(CC) -c $(CFLAGS) $< @@ -61,4 +55,4 @@ uninstall: $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ $(DESTDIR)$(MANPREFIX)/man1/stest.1 -.PHONY: all options clean dist install uninstall +.PHONY: all clean dist install uninstall diff --git a/config.mk b/config.mk index 0c84121..6a19175 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # dmenu version -VERSION = 5.0 +VERSION = 5.3 # 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) diff --git a/dmenu.c b/dmenu.c index 76287fa..ad077f2 100644 --- a/dmenu.c +++ b/dmenu.c @@ -72,6 +72,11 @@ 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) { if (*last) @@ -93,9 +98,9 @@ static void 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) break; + 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; } @@ -111,6 +116,8 @@ static void 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); @@ -440,11 +447,11 @@ static void movewordedge(int dir) { } static void keypress(XKeyEvent *ev) { - char buf[32]; + char buf[64]; int len; KeySym ksym; Status status; - int i; + struct item *tmpsel; bool offscreen = false; @@ -600,6 +607,7 @@ static void keypress(XKeyEvent *ev) { cleanup(); exit(1); case XK_Home: + case XK_KP_Home: if (sel == matches) { cursor = 0; break; @@ -835,6 +843,27 @@ static void mousemove(XEvent *e) { } } +static void readstdin(void) { + char *line = NULL; + size_t i, itemsiz = 0, linesiz = 0; + ssize_t len; + /* read each line from stdin and add it to the item list */ + for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { + if (i + 1 >= itemsiz) { + itemsiz += 256; + if (!(items = realloc(items, itemsiz * sizeof(*items)))) + die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); + } + if (line[len - 1] == '\n') line[len - 1] = '\0'; + if (!(items[i].text = strdup(line))) die("strdup:"); + + items[i].out = 0; + } + free(line); + if (items) items[i].text = NULL; + lines = MIN(lines, i); +} + static void paste(void) { char *p, *q; int di; @@ -966,7 +995,7 @@ static void setup(void) { 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])) break; + if (INTERSECT(x, y, 1, 1, info[i]) != 0) break; mw = (dmw > 0 ? dmw : MIN(MAX(max_textw(), 100), info[i].width)); x = info[i].x_org + ((info[i].width - mw) / 2); @@ -989,8 +1018,8 @@ static void setup(void) { swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask | PointerMotionMask; - win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, - CopyFromParent, CopyFromParent, CopyFromParent, + win = XCreateWindow(dpy, root, x, y, mw, mh, border_width, CopyFromParent, + CopyFromParent, CopyFromParent, CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); XSetClassHint(dpy, win, &ch); @@ -1004,6 +1033,7 @@ static void setup(void) { XMapRaised(dpy, win); if (embed) { + XReparentWindow(dpy, win, parentwin, x, y); XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { for (i = 0; i < du && dws[i] != win; ++i) @@ -1017,13 +1047,12 @@ static void setup(void) { } static void usage(void) { - fputs( - "usage: dmenu [-bfiv] [-l lines] [-g columns] [-p prompt] [-fn font] \n" + die("usage: dmenu [-bfiv] [-l lines] [-g columns] [-p prompt] [-fn " + "font] \n" " [-m monitor] [-h height] [-z width] [-w windowid]\n" " [-nb color] [-nf color] [-sb color] [-sf color]\n" - " [-nhb color] [-nhf color] [-shb color] [-shf color]\n", - stderr); - exit(1); + " [-nhb color] [-nhf color] [-shb color] [-shf " + "color]\n"); } int main(int argc, char *argv[]) { diff --git a/drw.c b/drw.c index dcb1d3e..b27bc37 100644 --- a/drw.c +++ b/drw.c @@ -203,12 +203,10 @@ void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, 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 ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; 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; @@ -216,12 +214,15 @@ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, 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. */ + static unsigned int nomatches[128], ellipsis_width; - if (!drw || (render && !drw->scheme) || !text || !drw->fonts) return 0; + 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); @@ -234,8 +235,10 @@ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, } 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) { @@ -245,9 +248,27 @@ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, 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; } @@ -255,52 +276,54 @@ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, } } - 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; usedfont = nextfont; } else { - /* Regardless of whether or not a fallback font is found, the - * character must be drawn. */ + /* Regardless of whether or not a fallback font is found, + * the character must be drawn. */ charexists = 1; + hash = (unsigned int)utf8codepoint; + hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; + hash = ((hash >> 15) ^ hash) * 0xD35A2D97; + h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); + h1 = (hash >> 17) % LENGTH(nomatches); + /* avoid expensive XftFontMatch call when we know we won't + * find a match */ + if (nomatches[h0] == utf8codepoint || + nomatches[h1] == utf8codepoint) + goto no_match; + fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, utf8codepoint); if (!drw->fonts->pattern) { - /* Refer to the comment in xfont_create for more information. */ - die("the first font in the cache must be loaded from a font " + /* Refer to the comment in xfont_create for more + * information. */ + die("the first font in the cache must be loaded from a " + "font " "string."); } @@ -326,6 +349,8 @@ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, curfont->next = usedfont; } else { xfont_free(usedfont); + nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; + no_match: usedfont = drw->fonts; } } @@ -349,6 +374,14 @@ unsigned int 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) { XGlyphInfo ext; diff --git a/drw.h b/drw.h index cbfb5d8..f6141fb 100644 --- a/drw.h +++ b/drw.h @@ -39,6 +39,8 @@ 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); diff --git a/util.c b/util.c index c34dbc4..38de953 100644 --- a/util.c +++ b/util.c @@ -29,3 +29,4 @@ void die(const char *fmt, ...) { exit(1); } + diff --git a/util.h b/util.h index c4df1df..e758f98 100644 --- a/util.h +++ b/util.h @@ -4,6 +4,7 @@ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) +#define LENGTH(X) (sizeof(X) / sizeof(X)[0]) void die(const char *fmt, ...); void *ecalloc(size_t nmemb, size_t size);