dmenu

fork of dmenu
git clone git://popovic.xyz/dmenu.git
Log | Files | Refs | README | LICENSE

commit 728211419f75cb1cb719d684412f8a9b153a480b
parent 5a0d919bc47d50a8558a76d11314e157d1334d1a
Author: miksa234 <milutin@popovic.xyz>
Date:   Wed, 18 Dec 2024 22:31:23 +0100

some fixes

Diffstat:
MLICENSE | 2+-
MMakefile | 12+++---------
Mconfig.def.h | 15++++++++++-----
Aconfig.def.h.orig | 25+++++++++++++++++++++++++
Mconfig.h | 15++++++++++-----
Mconfig.mk | 3++-
Mdmenu | 0
Mdmenu.1 | 3+++
Mdmenu.c | 225+++++++++++++++++++++++++++++++++++++++----------------------------------------
Admenu.c.orig | 822+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdmenu.o | 0
Mdrw.c | 103+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mdrw.h | 1+
Mdrw.o | 0
Apatches/dmenu-border-20230512-0fe460d.diff | 36++++++++++++++++++++++++++++++++++++
Apatches/dmenu-center-5.2.diff | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpatches/dmenu-xresources-4.9.diff | 126-------------------------------------------------------------------------------
Mstest | 0
Mstest.c | 2+-
Mstest.o | 0
Mutil.c | 23++++++++++++-----------
Mutil.h | 1+
Mutil.o | 0
23 files changed, 1200 insertions(+), 318 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -8,7 +8,7 @@ MIT/X Consortium License © 2009 Markus Schnalke <meillo@marmaro.de> © 2009 Evan Gates <evan.gates@gmail.com> © 2010-2012 Connor Lane Smith <cls@lubutu.com> -© 2014-2020 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org> © 2015-2019 Quentin Rameau <quinq@fifth.space> Permission is hereby granted, free of charge, to any person obtaining a diff --git 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) $< @@ -29,7 +23,7 @@ stest: stest.o $(CC) -o $@ stest.o $(LDFLAGS) clean: - rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz config.h + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz dist: clean mkdir -p dmenu-$(VERSION) @@ -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.def.h b/config.def.h @@ -2,16 +2,18 @@ /* Default settings; can be overriden by command line. */ static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +static int centered = 0; /* -c option; centers dmenu on screen */ +static int min_width = 500; /* minimum width when centered */ /* -fn option overrides fonts[0]; default X11 font or font set */ static const char *fonts[] = { - "terminus:pixelsize=17:style=Bold" + "terminus:size=20:style=Bold" }; static const char *prompt = NULL; /* -p option; prompt to the left of input field */ static const char *colors[SchemeLast][2] = { - /* fg bg */ - [SchemeNorm] = { "#6c6c93", "#263238" }, - [SchemeSel] = { "#000000", "#606F88" }, -/* [SchemeOut] = { "#263238", "#6c6c93" } */ + /* fg bg */ + [SchemeNorm] = { "#bcbcbc", "#000000" }, + [SchemeSel] = { "#eeeeee", "#bcbcbc" }, + [SchemeOut] = { "#000000", "#00ffff" }, }; /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ static unsigned int lines = 0; @@ -21,3 +23,6 @@ static unsigned int lines = 0; * for example: " /?\"&[]" */ static const char worddelimiters[] = " "; + +/* Size of the window border */ +static unsigned int border_width = 0; diff --git a/config.def.h.orig b/config.def.h.orig @@ -0,0 +1,25 @@ +/* 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 */ +static int centered = 0; /* -c option; centers dmenu on screen */ +static int min_width = 500; /* minimum width when centered */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "terminus:size=20:style=Bold" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bcbcbc", "#000000" }, + [SchemeSel] = { "#eeeeee", "#bcbcbc" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/config.h b/config.h @@ -2,16 +2,18 @@ /* Default settings; can be overriden by command line. */ static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +static int centered = 1; /* -c option; centers dmenu on screen */ +static int min_width = 1200; /* minimum width when centered */ /* -fn option overrides fonts[0]; default X11 font or font set */ static const char *fonts[] = { - "terminus:pixelsize=17:style=Bold" + "terminus:size=20:style=Bold" }; static const char *prompt = NULL; /* -p option; prompt to the left of input field */ static const char *colors[SchemeLast][2] = { - /* fg bg */ - [SchemeNorm] = { "#6c6c93", "#263238" }, - [SchemeSel] = { "#000000", "#606F88" }, -/* [SchemeOut] = { "#263238", "#6c6c93" } */ + /* fg bg */ + [SchemeNorm] = { "#bcbcbc", "#000000" }, + [SchemeSel] = { "#000000", "#bcbcbc" }, + [SchemeOut] = { "#642b2b", "#00ffff" }, }; /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ static unsigned int lines = 0; @@ -21,3 +23,6 @@ static unsigned int lines = 0; * for example: " /?\"&[]" */ static const char worddelimiters[] = " "; + +/* Size of the window border */ +static unsigned int border_width = 4; diff --git 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 b/dmenu Binary files differ. diff --git a/dmenu.1 b/dmenu.1 @@ -40,6 +40,9 @@ which lists programs in the user's $PATH and runs the result in their $SHELL. .B \-b dmenu appears at the bottom of the screen. .TP +.B \-c +dmenu appears centered on the screen. +.TP .B \-f dmenu grabs the keyboard before reading stdin if not reading from a tty. This is faster, but will lock up X until stdin reaches end\-of\-file. diff --git a/dmenu.c b/dmenu.c @@ -15,7 +15,6 @@ #include <X11/extensions/Xinerama.h> #endif #include <X11/Xft/Xft.h> -#include <X11/Xresource.h> #include "drw.h" #include "util.h" @@ -23,7 +22,6 @@ /* macros */ #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 LENGTH(X) (sizeof X / sizeof X[0]) #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) /* enums */ @@ -54,15 +52,18 @@ static XIC xic; static Drw *drw; static Clr *scheme[SchemeLast]; -/* Temporary arrays to allow overriding xresources values */ -static char *colortemp[4]; -static char *tempfonts; - #include "config.h" 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) { @@ -87,13 +88,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) { @@ -102,19 +112,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; - for (len = strlen(sub); *s; s++) - if (!strncasecmp(s, sub, len)) - return (char *)s; + if (!n[0]) + return (char *)h; + + 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; } @@ -170,7 +190,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]); @@ -230,7 +250,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; @@ -312,19 +332,19 @@ movewordedge(int dir) static void keypress(XKeyEvent *ev) { - char buf[32]; + char buf[64]; int len; - KeySym ksym; + KeySym ksym = NoSymbol; Status status; len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); switch (status) { default: /* XLookupNone, XBufferOverflow */ return; - case XLookupChars: + case XLookupChars: /* composed string from input method */ goto insert; case XLookupKeySym: - case XLookupBoth: + case XLookupBoth: /* a KeySym and a string are returned: use keysym */ break; } @@ -365,9 +385,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: @@ -401,10 +423,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); @@ -415,6 +438,7 @@ insert: insert(NULL, nextrune(-1) - cursor); break; case XK_End: + case XK_KP_End: if (text[cursor] != '\0') { cursor = strlen(text); break; @@ -434,6 +458,7 @@ insert: cleanup(); exit(1); case XK_Home: + case XK_KP_Home: if (sel == matches) { cursor = 0; break; @@ -442,6 +467,7 @@ insert: calcoffsets(); break; case XK_Left: + case XK_KP_Left: if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; @@ -450,18 +476,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; @@ -478,6 +507,7 @@ insert: sel->out = 1; break; case XK_Right: + case XK_KP_Right: if (text[cursor] != '\0') { cursor = nextrune(+1); break; @@ -486,6 +516,7 @@ insert: return; /* fallthrough */ case XK_Down: + case XK_KP_Down: if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); @@ -494,9 +525,9 @@ insert: case XK_Tab: if (!sel) return; - strncpy(text, sel->text, sizeof text - 1); - text[sizeof text - 1] = '\0'; - cursor = strlen(text); + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; match(); break; } @@ -526,29 +557,27 @@ paste(void) static void readstdin(void) { - char buf[sizeof text], *p; - size_t i, imax = 0, size = 0; - unsigned int tmpmax = 0; + 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; fgets(buf, sizeof buf, stdin); i++) { - if (i + 1 >= size / sizeof *items) - if (!(items = realloc(items, (size += BUFSIZ)))) - die("cannot realloc %u bytes:", size); - if ((p = strchr(buf, '\n'))) - *p = '\0'; - if (!(items[i].text = strdup(buf))) - die("cannot strdup %u 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; + 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; - inputw = items ? TEXTW(items[imax].text) : 0; lines = MIN(lines, i); } @@ -606,13 +635,8 @@ setup(void) int a, di, n, area = 0; #endif /* init appearance */ - for (j = 0; j < SchemeLast; j++) { - scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2); - } - for (j = 0; j < SchemeOut; ++j) { - for (i = 0; i < 2; ++i) - free(colors[j][i]); - } + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], 2); clip = XInternAtom(dpy, "CLIPBOARD", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False); @@ -621,6 +645,7 @@ setup(void) bh = drw->fonts->h + 2; 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))) { @@ -644,12 +669,19 @@ 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; + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + } else { + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + } + XFree(info); } else #endif @@ -657,21 +689,30 @@ 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; + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } else { + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } } 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; - win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + win = XCreateWindow(dpy, root, x, y/2, mw, mh, border_width, CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + if (border_width) + XSetWindowBorder(dpy, win, scheme[SchemeOut][ColFg].pixel); XSetClassHint(dpy, win, &ch); @@ -684,6 +725,7 @@ 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) @@ -699,44 +741,8 @@ setup(void) static void usage(void) { - 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); -} - -void -readxresources(void) { - XrmInitialize(); - - char* xrm; - if ((xrm = XResourceManagerString(drw->dpy))) { - char *type; - XrmDatabase xdb = XrmGetStringDatabase(xrm); - XrmValue xval; - - if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval)) - fonts[0] = strdup(xval.addr); - else - fonts[0] = strdup(fonts[0]); - if (XrmGetResource(xdb, "dmenu.background", "*", &type, &xval)) - colors[SchemeNorm][ColBg] = strdup(xval.addr); - else - colors[SchemeNorm][ColBg] = strdup(colors[SchemeNorm][ColBg]); - if (XrmGetResource(xdb, "dmenu.foreground", "*", &type, &xval)) - colors[SchemeNorm][ColFg] = strdup(xval.addr); - else - colors[SchemeNorm][ColFg] = strdup(colors[SchemeNorm][ColFg]); - if (XrmGetResource(xdb, "dmenu.selbackground", "*", &type, &xval)) - colors[SchemeSel][ColBg] = strdup(xval.addr); - else - colors[SchemeSel][ColBg] = strdup(colors[SchemeSel][ColBg]); - if (XrmGetResource(xdb, "dmenu.selforeground", "*", &type, &xval)) - colors[SchemeSel][ColFg] = strdup(xval.addr); - else - colors[SchemeSel][ColFg] = strdup(colors[SchemeSel][ColFg]); - - XrmDestroyDatabase(xdb); - } + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); } int @@ -754,6 +760,8 @@ main(int argc, char *argv[]) topbar = 0; else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ fast = 1; + else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ + centered = 1; else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; fstrstr = cistrstr; @@ -767,17 +775,19 @@ main(int argc, char *argv[]) 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 */ - tempfonts = argv[++i]; + fonts[0] = argv[++i]; else if (!strcmp(argv[i], "-nb")) /* normal background color */ - colortemp[0] = argv[++i]; + colors[SchemeNorm][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ - colortemp[1] = argv[++i]; + colors[SchemeNorm][ColFg] = argv[++i]; else if (!strcmp(argv[i], "-sb")) /* selected background color */ - colortemp[2] = argv[++i]; + colors[SchemeSel][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ - colortemp[3] = argv[++i]; + colors[SchemeSel][ColFg] = argv[++i]; else if (!strcmp(argv[i], "-w")) /* embedding window id */ embed = argv[++i]; + else if (!strcmp(argv[i], "-bw")) + border_width = atoi(argv[++i]); /* border width */ else usage(); @@ -793,23 +803,8 @@ main(int argc, char *argv[]) die("could not get embedding window attributes: 0x%lx", parentwin); drw = drw_create(dpy, screen, root, wa.width, wa.height); - readxresources(); - /* Now we check whether to override xresources with commandline parameters */ - if ( tempfonts ) - fonts[0] = strdup(tempfonts); - if ( colortemp[0]) - colors[SchemeNorm][ColBg] = strdup(colortemp[0]); - if ( colortemp[1]) - colors[SchemeNorm][ColFg] = strdup(colortemp[1]); - if ( colortemp[2]) - colors[SchemeSel][ColBg] = strdup(colortemp[2]); - if ( colortemp[3]) - colors[SchemeSel][ColFg] = strdup(colortemp[3]); - - if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts))) + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) die("no fonts could be loaded."); - - free(fonts[0]); lrpad = drw->fonts->h; #ifdef __OpenBSD__ diff --git a/dmenu.c.orig b/dmenu.c.orig @@ -0,0 +1,822 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#ifdef XINERAMA +#include <X11/extensions/Xinerama.h> +#endif +#include <X11/Xft/Xft.h> + +#include "drw.h" +#include "util.h" + +/* macros */ +#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 TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw; +static int lrpad; /* sum of left and right padding */ +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +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) + (*last)->right = item; + else + *list = item; + + item->left = *last; + item->right = NULL; + *last = item; +} + +static void +calcoffsets(void) +{ + int i, n; + + if (lines > 0) + n = lines * bh; + else + 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 : textw_clamp(next->text, n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + 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) +{ + size_t i; + + 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 *h, const char *n) +{ + size_t i; + + if (!n[0]) + return (char *)h; + + 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; +} + +static int +drawitem(struct item *item, int x, int y, int w) +{ + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); + + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); +} + +static void +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, w; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + 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, y += bh, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_map(drw, win, 0, 0, mw, mh); +} + +static void +grabfocus(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + nanosleep(&ts, NULL); + } + die("cannot grab focus"); +} + +static void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); +} + +static void +match(void) +{ + static char **tokv = NULL; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct 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))) + die("cannot realloc %zu bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + 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(text, item->text, textsize)) + 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(); +} + +static 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(); +} + +static 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; +} + +static void +movewordedge(int dir) +{ + if (dir < 0) { /* move cursor to the start of the word*/ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + } else { /* move cursor to the end of the word */ + while (text[cursor] && strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + while (text[cursor] && !strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + } +} + +static void +keypress(XKeyEvent *ev) +{ + char buf[64]; + int len; + KeySym ksym = NoSymbol; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: /* composed string from input method */ + goto insert; + case XLookupKeySym: + case XLookupBoth: /* a KeySym and a string are returned: use keysym */ + break; + } + + 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_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; 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 && strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + break; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + 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: + case XK_KP_Enter: + break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + 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: +insert: + if (!iscntrl((unsigned char)*buf)) + insert(buf, len); + break; + case XK_Delete: + case XK_KP_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: + case XK_KP_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: + cleanup(); + exit(1); + case XK_Home: + case XK_KP_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + case XK_KP_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + 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; + calcoffsets(); + break; + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + case XK_KP_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + case XK_KP_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; + match(); + break; + } + +draw: + drawmenu(); +} + +static 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 */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +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 +run(void) +{ + XEvent ev; + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static void +setup(void) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; +#endif + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + 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))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(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(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 (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]) != 0) + break; + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + } else { + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + } + + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } else { + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + 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; + win = XCreateWindow(dpy, root, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + 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) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static void +usage(void) +{ + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); +} + +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ + centered = 1; + 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], "-m")) + mon = 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 */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else + usage(); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} diff --git a/dmenu.o b/dmenu.o Binary files differ. diff --git a/drw.c b/drw.c @@ -133,19 +133,6 @@ xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) die("no font specified."); } - /* Do not allow using color fonts. This is a workaround for a BadLength - * error from Xft with color glyphs. Modelled on the Xterm workaround. See - * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 - * https://lists.suckless.org/dev/1701/30932.html - * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 - * and lots more all over the internet. - */ - FcBool iscol; - if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { - XftFontClose(drw->dpy, xfont); - return NULL; - } - font = ecalloc(1, sizeof(Fnt)); font->xfont = xfont; font->pattern = pattern; @@ -251,12 +238,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 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; @@ -264,13 +249,15 @@ 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. */ + static unsigned int nomatches[128], ellipsis_width; - 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 +269,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 +280,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 +308,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 +336,15 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp * 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); @@ -351,7 +356,6 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp fcpattern = FcPatternDuplicate(drw->fonts->pattern); FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); - FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); FcDefaultSubstitute(fcpattern); @@ -368,6 +372,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[nomatches[h0] ? h1 : h0] = utf8codepoint; +no_match: usedfont = drw->fonts; } } @@ -397,6 +403,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) { diff --git a/drw.h b/drw.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 */ diff --git a/drw.o b/drw.o Binary files differ. diff --git a/patches/dmenu-border-20230512-0fe460d.diff b/patches/dmenu-border-20230512-0fe460d.diff @@ -0,0 +1,36 @@ +diff --git a/config.def.h b/config.def.h +index 1edb647..dd3eb31 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,3 +21,6 @@ static unsigned int lines = 0; + * for example: " /?\"&[]" + */ + static const char worddelimiters[] = " "; ++ ++/* Size of the window border */ ++static unsigned int border_width = 0; +diff --git a/dmenu.c b/dmenu.c +index 27b7a30..7c130fc 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -684,9 +684,11 @@ setup(void) + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; +- win = XCreateWindow(dpy, root, x, y, mw, mh, 0, ++ win = XCreateWindow(dpy, root, x, y, mw, mh, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); ++ if (border_width) ++ XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + XSetClassHint(dpy, win, &ch); + + +@@ -757,6 +759,8 @@ main(int argc, char *argv[]) + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; ++ else if (!strcmp(argv[i], "-bw")) ++ border_width = atoi(argv[++i]); /* border width */ + else + usage(); diff --git a/patches/dmenu-center-5.2.diff b/patches/dmenu-center-5.2.diff @@ -0,0 +1,104 @@ +diff --git a/config.def.h b/config.def.h +index 1edb647..88ef264 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -2,6 +2,8 @@ + /* Default settings; can be overriden by command line. */ + + static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ ++static int centered = 0; /* -c option; centers dmenu on screen */ ++static int min_width = 500; /* minimum width when centered */ + /* -fn option overrides fonts[0]; default X11 font or font set */ + static const char *fonts[] = { + "monospace:size=10" +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..c036baa 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -40,6 +40,9 @@ which lists programs in the user's $PATH and runs the result in their $SHELL. + .B \-b + dmenu appears at the bottom of the screen. + .TP ++.B \-c ++dmenu appears centered on the screen. ++.TP + .B \-f + dmenu grabs the keyboard before reading stdin if not reading from a tty. This + is faster, but will lock up X until stdin reaches end\-of\-file. +diff --git a/dmenu.c b/dmenu.c +index 27b7a30..427fb04 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -96,6 +96,15 @@ calcoffsets(void) + 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) + { +@@ -636,6 +645,7 @@ setup(void) + bh = drw->fonts->h + 2; + 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))) { +@@ -662,9 +672,16 @@ setup(void) + 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; ++ if (centered) { ++ mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); ++ x = info[i].x_org + ((info[i].width - mw) / 2); ++ y = info[i].y_org + ((info[i].height - mh) / 2); ++ } else { ++ x = info[i].x_org; ++ y = info[i].y_org + (topbar ? 0 : info[i].height - mh); ++ mw = info[i].width; ++ } ++ + XFree(info); + } else + #endif +@@ -672,9 +689,16 @@ 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; ++ ++ if (centered) { ++ mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); ++ x = (wa.width - mw) / 2; ++ y = (wa.height - mh) / 2; ++ } else { ++ x = 0; ++ y = topbar ? 0 : wa.height - mh; ++ mw = wa.width; ++ } + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = mw / 3; /* input width: ~33% of monitor width */ +@@ -733,6 +757,8 @@ main(int argc, char *argv[]) + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; ++ else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ ++ centered = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; diff --git a/patches/dmenu-xresources-4.9.diff b/patches/dmenu-xresources-4.9.diff @@ -1,126 +0,0 @@ -diff '--color=auto' -up ../dmenu-4.9/dmenu.c ./dmenu.c ---- ../dmenu-4.9/dmenu.c 2019-02-02 13:55:02.000000000 +0100 -+++ ./dmenu.c 2020-05-24 00:27:58.038586112 +0200 -@@ -15,6 +15,7 @@ - #include <X11/extensions/Xinerama.h> - #endif - #include <X11/Xft/Xft.h> -+#include <X11/Xresource.h> - - #include "drw.h" - #include "util.h" -@@ -53,6 +54,10 @@ static XIC xic; - static Drw *drw; - static Clr *scheme[SchemeLast]; - -+/* Temporary arrays to allow overriding xresources values */ -+static char *colortemp[4]; -+static char *tempfonts; -+ - #include "config.h" - - static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; -@@ -596,8 +601,13 @@ setup(void) - int a, di, n, area = 0; - #endif - /* init appearance */ -- for (j = 0; j < SchemeLast; j++) -- scheme[j] = drw_scm_create(drw, colors[j], 2); -+ for (j = 0; j < SchemeLast; j++) { -+ scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2); -+ } -+ for (j = 0; j < SchemeOut; ++j) { -+ for (i = 0; i < 2; ++i) -+ free(colors[j][i]); -+ } - - clip = XInternAtom(dpy, "CLIPBOARD", False); - utf8 = XInternAtom(dpy, "UTF8_STRING", False); -@@ -687,6 +697,41 @@ usage(void) - exit(1); - } - -+void -+readxresources(void) { -+ XrmInitialize(); -+ -+ char* xrm; -+ if ((xrm = XResourceManagerString(drw->dpy))) { -+ char *type; -+ XrmDatabase xdb = XrmGetStringDatabase(xrm); -+ XrmValue xval; -+ -+ if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval)) -+ fonts[0] = strdup(xval.addr); -+ else -+ fonts[0] = strdup(fonts[0]); -+ if (XrmGetResource(xdb, "dmenu.background", "*", &type, &xval)) -+ colors[SchemeNorm][ColBg] = strdup(xval.addr); -+ else -+ colors[SchemeNorm][ColBg] = strdup(colors[SchemeNorm][ColBg]); -+ if (XrmGetResource(xdb, "dmenu.foreground", "*", &type, &xval)) -+ colors[SchemeNorm][ColFg] = strdup(xval.addr); -+ else -+ colors[SchemeNorm][ColFg] = strdup(colors[SchemeNorm][ColFg]); -+ if (XrmGetResource(xdb, "dmenu.selbackground", "*", &type, &xval)) -+ colors[SchemeSel][ColBg] = strdup(xval.addr); -+ else -+ colors[SchemeSel][ColBg] = strdup(colors[SchemeSel][ColBg]); -+ if (XrmGetResource(xdb, "dmenu.selforeground", "*", &type, &xval)) -+ colors[SchemeSel][ColFg] = strdup(xval.addr); -+ else -+ colors[SchemeSel][ColFg] = strdup(colors[SchemeSel][ColFg]); -+ -+ XrmDestroyDatabase(xdb); -+ } -+} -+ - int - main(int argc, char *argv[]) - { -@@ -715,15 +760,15 @@ main(int argc, char *argv[]) - 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 */ -- fonts[0] = argv[++i]; -+ tempfonts = argv[++i]; - else if (!strcmp(argv[i], "-nb")) /* normal background color */ -- colors[SchemeNorm][ColBg] = argv[++i]; -+ colortemp[0] = argv[++i]; - else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ -- colors[SchemeNorm][ColFg] = argv[++i]; -+ colortemp[1] = argv[++i]; - else if (!strcmp(argv[i], "-sb")) /* selected background color */ -- colors[SchemeSel][ColBg] = argv[++i]; -+ colortemp[2] = argv[++i]; - else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ -- colors[SchemeSel][ColFg] = argv[++i]; -+ colortemp[3] = argv[++i]; - else if (!strcmp(argv[i], "-w")) /* embedding window id */ - embed = argv[++i]; - else -@@ -743,8 +788,23 @@ main(int argc, char *argv[]) - die("could not get embedding window attributes: 0x%lx", - parentwin); - drw = drw_create(dpy, screen, root, wa.width, wa.height); -- if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) -+ readxresources(); -+ /* Now we check whether to override xresources with commandline parameters */ -+ if ( tempfonts ) -+ fonts[0] = strdup(tempfonts); -+ if ( colortemp[0]) -+ colors[SchemeNorm][ColBg] = strdup(colortemp[0]); -+ if ( colortemp[1]) -+ colors[SchemeNorm][ColFg] = strdup(colortemp[1]); -+ if ( colortemp[2]) -+ colors[SchemeSel][ColBg] = strdup(colortemp[2]); -+ if ( colortemp[3]) -+ colors[SchemeSel][ColFg] = strdup(colortemp[3]); -+ -+ if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts))) - die("no fonts could be loaded."); -+ -+ free(fonts[0]); - lrpad = drw->fonts->h; - - #ifdef __OpenBSD__ diff --git a/stest b/stest Binary files differ. diff --git a/stest.c b/stest.c @@ -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); } diff --git a/stest.o b/stest.o Binary files differ. diff --git a/util.c b/util.c @@ -6,18 +6,9 @@ #include "util.h" -void * -ecalloc(size_t nmemb, size_t size) -{ - void *p; - - if (!(p = calloc(nmemb, size))) - die("calloc:"); - return p; -} - void -die(const char *fmt, ...) { +die(const char *fmt, ...) +{ va_list ap; va_start(ap, fmt); @@ -33,3 +24,13 @@ die(const char *fmt, ...) { exit(1); } + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} diff --git a/util.h b/util.h @@ -3,6 +3,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); diff --git a/util.o b/util.o Binary files differ.