From f08126d982987a0578f596e85afa0dd85073869b Mon Sep 17 00:00:00 2001 From: George Parsehyan Date: Sun, 30 May 2021 15:29:28 +0200 Subject: [PATCH] init commit --- Makefile | 64 ++ PKGBUILD | 42 + README.md | 57 ++ arg.h | 49 ++ config.def.h | 34 + config.h | 34 + config.mk | 31 + dmenu | Bin 0 -> 44048 bytes dmenu-border-4.9.diff | 25 + dmenu-center-20200111-8cd37e1.diff | 120 +++ dmenu-fuzzyhighlight-4.9.diff | 152 ++++ dmenu-fuzzymatch-4.9.diff | 163 ++++ dmenu-grid-4.9.diff | 107 +++ dmenu-lineheight-4.9.diff | 94 +++ dmenu-morecolor-20190922-4bf895b.diff | 47 ++ dmenu-mousesupport-5.0.diff | 144 ++++ dmenu-numbers-4.9.diff | 81 ++ dmenu.1 | 225 +++++ dmenu.c | 1097 +++++++++++++++++++++++++ dmenu.o | Bin 0 -> 42984 bytes dmenu_path | 13 + dmenu_run | 2 + drw.c | 436 ++++++++++ drw.h | 57 ++ drw.o | Bin 0 -> 10616 bytes stest | Bin 0 -> 17080 bytes stest.1 | 90 ++ stest.c | 109 +++ stest.o | Bin 0 -> 5288 bytes util.c | 35 + util.h | 8 + util.o | Bin 0 -> 2256 bytes 32 files changed, 3316 insertions(+) create mode 100644 Makefile create mode 100644 PKGBUILD create mode 100644 README.md create mode 100644 arg.h create mode 100644 config.def.h create mode 100644 config.h create mode 100644 config.mk create mode 100755 dmenu create mode 100644 dmenu-border-4.9.diff create mode 100644 dmenu-center-20200111-8cd37e1.diff create mode 100644 dmenu-fuzzyhighlight-4.9.diff create mode 100644 dmenu-fuzzymatch-4.9.diff create mode 100644 dmenu-grid-4.9.diff create mode 100644 dmenu-lineheight-4.9.diff create mode 100644 dmenu-morecolor-20190922-4bf895b.diff create mode 100644 dmenu-mousesupport-5.0.diff create mode 100644 dmenu-numbers-4.9.diff create mode 100644 dmenu.1 create mode 100644 dmenu.c create mode 100644 dmenu.o create mode 100755 dmenu_path create mode 100755 dmenu_run create mode 100644 drw.c create mode 100644 drw.h create mode 100644 drw.o create mode 100755 stest create mode 100644 stest.1 create mode 100644 stest.c create mode 100644 stest.o create mode 100644 util.c create mode 100644 util.h create mode 100644 util.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a03a95c --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +# dmenu - dynamic menu +# See LICENSE file for copyright and license details. + +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)" + +.c.o: + $(CC) -c $(CFLAGS) $< + +config.h: + cp config.def.h $@ + +$(OBJ): arg.h config.h config.mk drw.h + +dmenu: dmenu.o drw.o util.o + $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) + +stest: stest.o + $(CC) -o $@ stest.o $(LDFLAGS) + +clean: + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz + +dist: clean + mkdir -p dmenu-$(VERSION) + cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ + drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ + dmenu-$(VERSION) + tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) + gzip dmenu-$(VERSION).tar + rm -rf dmenu-$(VERSION) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run + chmod 755 $(DESTDIR)$(PREFIX)/bin/stest + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run\ + $(DESTDIR)$(PREFIX)/bin/stest\ + $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ + $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +.PHONY: all options clean dist install uninstall diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..5ad0d60 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,42 @@ +pkgname=dmenu-feanor +pkgver=5.0.r. +pkgrel=1 +epoch= +pkgdesc="A build of dmenu patched for centering, borders, grids, numbers, line height, mouse support, fuzzy matching and highlighting." +arch=(x86_64 i686) +url="https://git.erunar.com/Feanor306/dmenu-feanor.git" +groups=() +depends=(ttf-hack ttf-joypixels) +makedepends=(git) +checkdepends=() +optdepends=() +provides=(dmenu) +conflicts=(dmenu) +replaces=() +backup=() +options=() +install= +changelog= +source=("git+$url") +noextract=() +md5sums=('SKIP') +validpgpkeys=() + +pkgver() { + cd "${_pkgname}" + printf "5.0.r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" +} + +build() { + cd dmenu-feanor + make X11INC=/usr/include/X11 X11LIB=/usr/lib/X11 +} + +package() { + cd dmenu-feanor + mkdir -p ${pkgdir}/opt/${pkgname} + cp -rf * ${pkgdir}/opt/${pkgname} + make PREFIX=/usr DESTDIR="${pkgdir}" install + install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" + install -Dm644 README.org "${pkgdir}/usr/share/doc/${pkgname}/README.org" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e7095ad --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# DMENU FEANOR CUSTOM BUILD +## dmenu is a dynamic menu for X, originally designed for dwm. It manages large numbers of user-defined menu items efficiently. It is a very powerful tool that allows you to pipe information into it. This makes dmenu a perfect utility to incorporate into your scripting. +## The patches used in this build include: +- dmenu-border -- adds border around dmenu window +- dmenu-center -- centers dmenu in the middle of screen (-c) +- dmenu-fuzzyhighlight -- fuzzy matches gets highlighted +- dmenu-fuzzymatch -- adds support for fuzzy-matching +- dmenu-grid -- adds a flag (-g) to specify the number of grid columns +- dmenu-lineheight -- adds a flag (-h) to set the minimum height of dmenu lines +- dmenu-morecolor -- creates a color option for use the entries adjacent to the selection +- dmenu-mousesupport -- basic mouse support +- dmenu-numbers -- Displays number of matched and total items in top right corner +- Mouse functionality +## Mouse actions supported: +- Left-mouse click: + - On prompt and input field: clear input text and selection. + - In horizontal and vertical mode on item: select and output item (same as pressing enter). + - In horizontal mode on arrows: change items to show left or right. +- Ctrl-left-mouse click: multisel modifier. +- Right-mouse click: close. +- Middle-mouse click: + - Paste current selection. + - While holding shift: paste primary selection. +- Scroll up: + - In horizontal mode: same as left-clicking on left arrow. + - In vertical mode: show items above. +- Scroll down: + - In horizontal mode: same as left-clicking on right arrow. + - In vertical mode: show items below. +## Available flags +- [-l lines] +- [-g columns] +- [-p prompt] +- [-fn font] +- [-m monitor] +- [-h height] +- [-nb color] +- [-nf color] +- [-sb color] +- [-sf color] +- [-nhb color] +- [-nhf color] +- [-shb color] +- [-shf color] +- [-w windowid] +- [-c] for centering + +An example: dmenu_run -c -bw 2 -l 20 -g 4 -p 'Run: ' + +This launches dmenu_run with -c (centered), -bw (border width), -l (number of vertical lines) and -g (number of grid columns). +## Installing dmenu on Arch Linux +All you need to do is to download this repository. Then run the following command: + +``` +sudo make +sudo make clean install +``` \ No newline at end of file diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..e94e02b --- /dev/null +++ b/arg.h @@ -0,0 +1,49 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..50c19ec --- /dev/null +++ b/config.def.h @@ -0,0 +1,34 @@ +/* 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 */ +static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "Hack:pixelsize=11:antialias=true:autohint=true", + "JoyPixels:pixelsize=8:antialias=true:autohint=true" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#cccccc", "#282a36" }, + [SchemeSel] = { "#ffffff", "#924441" }, + [SchemeSelHighlight] = { "#d7d7d7", "#000000" }, + [SchemeNormHighlight] = { "#e78481", "#000000" }, + [SchemeOut] = { "#000000", "#00ffff" }, + [SchemeMid] = { "#d7d7d7", "#1f2026" }, +}; +/* -l and -g options; controls number of lines and columns in grid if > 0 */ +static unsigned int lines = 0; +static unsigned int lineheight = 22; /* -h option; minimum height of a menu line */ +static unsigned int columns = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* Size of the window border */ +static unsigned int border_width = 0; /* -bw option; to add border width */ diff --git a/config.h b/config.h new file mode 100644 index 0000000..7e5a8a2 --- /dev/null +++ b/config.h @@ -0,0 +1,34 @@ +/* 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 */ +static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "Hack:pixelsize=38:antialias=true:autohint=true", + "JoyPixels:pixelsize=38:antialias=true:autohint=true" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#cccccc", "#282a36" }, + [SchemeSel] = { "#ffffff", "#005577" }, + [SchemeSelHighlight] = { "#d7d7d7", "#000000" }, + [SchemeNormHighlight] = { "#00ff7f", "#000000" }, + [SchemeOut] = { "#000000", "#00ffff" }, + [SchemeMid] = { "#d7d7d7", "#1f2026" }, +}; +/* -l and -g options; controls number of lines and columns in grid if > 0 */ +static unsigned int lines = 0; +static unsigned int lineheight = 40; /* -h option; minimum height of a menu line */ +static unsigned int columns = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* Size of the window border */ +static unsigned int border_width = 2; /* -bw option; to add border width */ diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..0c84121 --- /dev/null +++ b/config.mk @@ -0,0 +1,31 @@ +# dmenu version +VERSION = 5.0 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = $(X11INC)/freetype2 + +# includes and libs +INCS = -I$(X11INC) -I$(FREETYPEINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) +LDFLAGS = $(LIBS) + +# compiler and linker +CC = cc diff --git a/dmenu b/dmenu new file mode 100755 index 0000000000000000000000000000000000000000..72871b429881b9fbcf5f427e73fdb6b59bfe2648 GIT binary patch literal 44048 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 +Date: Sat, 11 Jan 2020 21:16:08 -0600 +Subject: [PATCH] center patch now has adjustable minimum width + +--- + config.def.h | 2 ++ + dmenu.1 | 3 +++ + dmenu.c | 39 ++++++++++++++++++++++++++++++++------- + 3 files changed, 37 insertions(+), 7 deletions(-) + +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 65f25ce..041c7f8 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -89,6 +89,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) + { +@@ -611,6 +620,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))) { +@@ -637,9 +647,16 @@ setup(void) + if (INTERSECT(x, y, 1, 1, info[i])) + 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 +@@ -647,11 +664,17 @@ 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); + match(); + +@@ -709,6 +732,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; +-- +2.24.1 + diff --git a/dmenu-fuzzyhighlight-4.9.diff b/dmenu-fuzzyhighlight-4.9.diff new file mode 100644 index 0000000..58d5c6f --- /dev/null +++ b/dmenu-fuzzyhighlight-4.9.diff @@ -0,0 +1,152 @@ +Author: Chris Noxz +note: This patch is meant to be used together with fuzzymatch + +diff -upN dmenu-4.9/config.def.h dmenu-4.9-fuzzyhighlight/config.def.h +--- dmenu-4.9/config.def.h 2019-02-02 13:55:02.000000000 +0100 ++++ dmenu-4.9-fuzzyhighlight/config.def.h 2020-04-04 10:26:36.990890854 +0200 +@@ -11,6 +11,8 @@ static const char *colors[SchemeLast][2] + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, ++ [SchemeSelHighlight] = { "#ffc978", "#005577" }, ++ [SchemeNormHighlight] = { "#ffc978", "#222222" }, + [SchemeOut] = { "#000000", "#00ffff" }, + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +diff -upN dmenu-4.9/dmenu.1 dmenu-4.9-fuzzyhighlight/dmenu.1 +--- dmenu-4.9/dmenu.1 2019-02-02 13:55:02.000000000 +0100 ++++ dmenu-4.9-fuzzyhighlight/dmenu.1 2020-04-04 10:30:16.430054933 +0200 +@@ -20,6 +20,14 @@ dmenu \- dynamic menu + .IR color ] + .RB [ \-sf + .IR color ] ++.RB [ \-nhb ++.IR color ] ++.RB [ \-nhf ++.IR color ] ++.RB [ \-shb ++.IR color ] ++.RB [ \-shf ++.IR color ] + .RB [ \-w + .IR windowid ] + .P +@@ -75,6 +83,18 @@ defines the selected background color. + .BI \-sf " color" + defines the selected foreground color. + .TP ++.BI \-nhb " color" ++defines the normal highlight background color. ++.TP ++.BI \-nhf " color" ++defines the normal highlight foreground color. ++.TP ++.BI \-shb " color" ++defines the selected highlight background color. ++.TP ++.BI \-shf " color" ++defines the selected highlight foreground color. ++.TP + .B \-v + prints version information to stdout, then exits. + .TP +diff -upN dmenu-4.9/dmenu.c dmenu-4.9-fuzzyhighlight/dmenu.c +--- dmenu-4.9/dmenu.c 2019-02-02 13:55:02.000000000 +0100 ++++ dmenu-4.9-fuzzyhighlight/dmenu.c 2020-04-04 10:27:43.888026309 +0200 +@@ -26,7 +26,9 @@ + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + + /* enums */ +-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeNormHighlight, SchemeSelHighlight, ++ SchemeOut, SchemeLast }; /* color schemes */ ++ + + struct item { + char *text; +@@ -113,9 +115,49 @@ cistrstr(const char *s, const char *sub) + return NULL; + } + ++static void ++drawhighlights(struct item *item, int x, int y, int maxw) ++{ ++ int i, indent; ++ char *highlight; ++ char c; ++ ++ if (!(strlen(item->text) && strlen(text))) ++ return; ++ ++ drw_setscheme(drw, scheme[item == sel ++ ? SchemeSelHighlight ++ : SchemeNormHighlight]); ++ for (i = 0, highlight = item->text; *highlight && text[i];) { ++ if (*highlight == text[i]) { ++ /* get indentation */ ++ c = *highlight; ++ *highlight = '\0'; ++ indent = TEXTW(item->text); ++ *highlight = c; ++ ++ /* highlight character */ ++ c = highlight[1]; ++ highlight[1] = '\0'; ++ drw_text( ++ drw, ++ x + indent - (lrpad / 2), ++ y, ++ MIN(maxw - indent, TEXTW(highlight) - lrpad), ++ bh, 0, highlight, 0 ++ ); ++ highlight[1] = c; ++ i++; ++ } ++ highlight++; ++ } ++} ++ ++ + static int + drawitem(struct item *item, int x, int y, int w) + { ++ int r; + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) +@@ -123,7 +165,9 @@ drawitem(struct item *item, int x, int y + else + drw_setscheme(drw, scheme[SchemeNorm]); + +- return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); ++ r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); ++ drawhighlights(item, x, y, w); ++ return r; + } + + static void +@@ -683,7 +727,8 @@ 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); ++ " [-nb color] [-nf color] [-sb color] [-sf color]\n" ++ " [-nhb color] [-nhf color] [-shb color] [-shf color] [-w windowid]\n", stderr); + exit(1); + } + +@@ -724,6 +769,14 @@ main(int argc, char *argv[]) + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; ++ else if (!strcmp(argv[i], "-nhb")) /* normal hi background color */ ++ colors[SchemeNormHighlight][ColBg] = argv[++i]; ++ else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */ ++ colors[SchemeNormHighlight][ColFg] = argv[++i]; ++ else if (!strcmp(argv[i], "-shb")) /* selected hi background color */ ++ colors[SchemeSelHighlight][ColBg] = argv[++i]; ++ else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */ ++ colors[SchemeSelHighlight][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else diff --git a/dmenu-fuzzymatch-4.9.diff b/dmenu-fuzzymatch-4.9.diff new file mode 100644 index 0000000..9fd206d --- /dev/null +++ b/dmenu-fuzzymatch-4.9.diff @@ -0,0 +1,163 @@ +From 94353eb52055927d9079f3d9e33da1c954abf386 Mon Sep 17 00:00:00 2001 +From: aleks +Date: Wed, 26 Jun 2019 13:25:10 +0200 +Subject: [PATCH] Add support for fuzzy-matching + +--- + config.def.h | 1 + + config.mk | 2 +- + dmenu.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 91 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..51612b9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -2,6 +2,7 @@ + /* Default settings; can be overriden by command line. */ + + static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ ++static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ + /* -fn option overrides fonts[0]; default X11 font or font set */ + static const char *fonts[] = { + "monospace:size=10" +diff --git a/config.mk b/config.mk +index 0929b4a..d14309a 100644 +--- a/config.mk ++++ b/config.mk +@@ -20,7 +20,7 @@ FREETYPEINC = /usr/include/freetype2 + + # includes and libs + INCS = -I$(X11INC) -I$(FREETYPEINC) +-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) ++LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +diff --git a/dmenu.c b/dmenu.c +index 6b8f51b..96ddc98 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -1,6 +1,7 @@ + /* See LICENSE file for copyright and license details. */ + #include + #include ++#include + #include + #include + #include +@@ -32,6 +33,7 @@ struct item { + char *text; + struct item *left, *right; + int out; ++ double distance; + }; + + static char text[BUFSIZ] = ""; +@@ -210,9 +212,94 @@ grabkeyboard(void) + die("cannot grab keyboard"); + } + ++int ++compare_distance(const void *a, const void *b) ++{ ++ struct item *da = *(struct item **) a; ++ struct item *db = *(struct item **) b; ++ ++ if (!db) ++ return 1; ++ if (!da) ++ return -1; ++ ++ return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1; ++} ++ ++void ++fuzzymatch(void) ++{ ++ /* bang - we have so much memory */ ++ struct item *it; ++ struct item **fuzzymatches = NULL; ++ char c; ++ int number_of_matches = 0, i, pidx, sidx, eidx; ++ int text_len = strlen(text), itext_len; ++ ++ matches = matchend = NULL; ++ ++ /* walk through all items */ ++ for (it = items; it && it->text; it++) { ++ if (text_len) { ++ itext_len = strlen(it->text); ++ pidx = 0; /* pointer */ ++ sidx = eidx = -1; /* start of match, end of match */ ++ /* walk through item text */ ++ for (i = 0; i < itext_len && (c = it->text[i]); i++) { ++ /* fuzzy match pattern */ ++ if (!fstrncmp(&text[pidx], &c, 1)) { ++ if(sidx == -1) ++ sidx = i; ++ pidx++; ++ if (pidx == text_len) { ++ eidx = i; ++ break; ++ } ++ } ++ } ++ /* build list of matches */ ++ if (eidx != -1) { ++ /* compute distance */ ++ /* add penalty if match starts late (log(sidx+2)) ++ * add penalty for long a match without many matching characters */ ++ it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len); ++ /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */ ++ appenditem(it, &matches, &matchend); ++ number_of_matches++; ++ } ++ } else { ++ appenditem(it, &matches, &matchend); ++ } ++ } ++ ++ if (number_of_matches) { ++ /* initialize array with matches */ ++ if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*)))) ++ die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*)); ++ for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) { ++ fuzzymatches[i] = it; ++ } ++ /* sort matches according to distance */ ++ qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance); ++ /* rebuild list of matches */ ++ matches = matchend = NULL; ++ for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \ ++ it->text; i++, it = fuzzymatches[i]) { ++ appenditem(it, &matches, &matchend); ++ } ++ free(fuzzymatches); ++ } ++ curr = sel = matches; ++ calcoffsets(); ++} ++ + static void + match(void) + { ++ if (fuzzy) { ++ fuzzymatch(); ++ return; ++ } + static char **tokv = NULL; + static int tokn = 0; + +@@ -702,6 +789,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], "-F")) /* grabs keyboard before reading stdin */ ++ fuzzy = 0; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; +-- +2.22.0 + diff --git a/dmenu-grid-4.9.diff b/dmenu-grid-4.9.diff new file mode 100644 index 0000000..c27689b --- /dev/null +++ b/dmenu-grid-4.9.diff @@ -0,0 +1,107 @@ +From 39ab9676914bd0d8105d0f96bbd7611a53077438 Mon Sep 17 00:00:00 2001 +From: Miles Alan +Date: Sat, 4 Jul 2020 11:19:04 -0500 +Subject: [PATCH] Add -g option to display entries in the given number of grid + columns + +This option can be used in conjunction with -l to format dmenu's options in +arbitrary size grids. For example, to create a 4 column by 6 line grid, you +could use: dmenu -g 4 -l 6 +--- + config.def.h | 3 ++- + dmenu.1 | 7 ++++++- + dmenu.c | 22 ++++++++++++++++------ + 3 files changed, 24 insertions(+), 8 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..96cf3c9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,8 +13,9 @@ static const char *colors[SchemeLast][2] = { + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, + }; +-/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ ++/* -l and -g options; controls number of lines and columns in grid if > 0 */ + static unsigned int lines = 0; ++static unsigned int columns = 0; + + /* + * Characters not considered part of a word while deleting words +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..d0a734a 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -4,6 +4,8 @@ dmenu \- dynamic menu + .SH SYNOPSIS + .B dmenu + .RB [ \-bfiv ] ++.RB [ \-g ++.IR columns ] + .RB [ \-l + .IR lines ] + .RB [ \-m +@@ -47,8 +49,11 @@ is faster, but will lock up X until stdin reaches end\-of\-file. + .B \-i + dmenu matches menu items case insensitively. + .TP ++.BI \-g " columns" ++dmenu lists items in a grid with the given number of columns. ++.TP + .BI \-l " lines" +-dmenu lists items vertically, with the given number of lines. ++dmenu lists items in a grid with the given number of lines. + .TP + .BI \-m " monitor" + dmenu is displayed on the monitor number supplied. Monitor numbers are starting +diff --git a/dmenu.c b/dmenu.c +index 6b8f51b..d79b6bb 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -77,7 +77,7 @@ calcoffsets(void) + int i, n; + + if (lines > 0) +- n = lines * bh; ++ n = lines * columns * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ +@@ -152,9 +152,15 @@ drawmenu(void) + } + + if (lines > 0) { +- /* draw vertical list */ +- for (item = curr; item != next; item = item->right) +- drawitem(item, x, y += bh, mw - x); ++ /* draw grid */ ++ int i = 0; ++ for (item = curr; item != next; item = item->right, i++) ++ drawitem( ++ item, ++ x + ((i / lines) * ((mw - x) / columns)), ++ y + (((i % lines) + 1) * bh), ++ (mw - x) / columns ++ ); + } else if (matches) { + /* draw horizontal list */ + x += inputw; +@@ -708,9 +714,13 @@ main(int argc, char *argv[]) + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ +- else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ ++ else if (!strcmp(argv[i], "-g")) { /* number of columns in grid */ ++ columns = atoi(argv[++i]); ++ if (lines == 0) lines = 1; ++ } else if (!strcmp(argv[i], "-l")) { /* number of lines in grid */ + lines = atoi(argv[++i]); +- else if (!strcmp(argv[i], "-m")) ++ if (columns == 0) columns = 1; ++ } 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]; +-- +2.23.1 + diff --git a/dmenu-lineheight-4.9.diff b/dmenu-lineheight-4.9.diff new file mode 100644 index 0000000..d12c77a --- /dev/null +++ b/dmenu-lineheight-4.9.diff @@ -0,0 +1,94 @@ +From 87f92a561c31246f6f9effc0e89ef92677c87746 Mon Sep 17 00:00:00 2001 +From: astier +Date: Wed, 27 Feb 2019 21:44:55 +0100 +Subject: [PATCH] Add an option which defines the lineheight + +Despite both the panel and dmenu using the same font (a Terminus 12), +dmenu is shorter and the panel is visible from under the dmenu bar. +The appearance can be even more distracting when using similar colors +for background and selections. With the option added by this patch, +dmenu can be launched with a '-h 24', thus completely covering the panel. +--- + config.def.h | 1 + + dmenu.1 | 3 +++ + dmenu.c | 10 ++++++++-- + 3 files changed, 12 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..317fa2f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -15,6 +15,7 @@ static const char *colors[SchemeLast][2] = { + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; ++static unsigned int lineheight = 0; /* -h option; minimum height of a menu line */ + + /* + * Characters not considered part of a word while deleting words +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..7ef34d2 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -50,6 +50,9 @@ 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. +diff --git a/dmenu.c b/dmenu.c +index 6b8f51b..45d1946 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -131,7 +131,7 @@ drawmenu(void) + { + unsigned int curpos; + struct item *item; +- int x = 0, y = 0, w; ++ int x = 0, y = 0, fh = drw->fonts->h, w; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); +@@ -148,7 +148,7 @@ 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, 2, bh - 4, 1, 0); ++ drw_rect(drw, x + curpos, 2 + (bh-fh)/2, 2, fh - 4, 1, 0); + } + + if (lines > 0) { +@@ -604,6 +604,7 @@ 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; + #ifdef XINERAMA +@@ -683,6 +684,7 @@ static void + usage(void) + { + fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" ++ " [-h height]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); + } +@@ -716,6 +718,10 @@ main(int argc, char *argv[]) + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; ++ else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ ++ lineheight = atoi(argv[++i]); ++ lineheight = MAX(lineheight,8); /* reasonable default in case of value too small/negative */ ++ } + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ +-- +2.21.0 + diff --git a/dmenu-morecolor-20190922-4bf895b.diff b/dmenu-morecolor-20190922-4bf895b.diff new file mode 100644 index 0000000..539e9c1 --- /dev/null +++ b/dmenu-morecolor-20190922-4bf895b.diff @@ -0,0 +1,47 @@ +From 4bf895be219ae00394a5cde901dc43ec6dcb3759 Mon Sep 17 00:00:00 2001 +From: Tanner Babcock +Date: Sun, 22 Sep 2019 03:07:26 -0500 +Subject: [PATCH] Additional color scheme, for adjacent entries + +--- + config.def.h | 1 + + dmenu.c | 4 +++- + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..767c88f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,6 +12,7 @@ static const char *colors[SchemeLast][2] = { + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, ++ [SchemeMid] = { "#eeeeee", "#770000" }, + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; +diff --git a/dmenu.c b/dmenu.c +index 65f25ce..0a5c08d 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -26,7 +26,7 @@ + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + + /* enums */ +-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeOut, SchemeMid, SchemeLast }; /* color schemes */ + + struct item { + char *text; +@@ -118,6 +118,8 @@ drawitem(struct item *item, int x, int y, int w) + { + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); ++ else if (item->left == sel || item->right == sel) ++ drw_setscheme(drw, scheme[SchemeMid]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else +-- +2.23.0 + diff --git a/dmenu-mousesupport-5.0.diff b/dmenu-mousesupport-5.0.diff new file mode 100644 index 0000000..6a8c4d4 --- /dev/null +++ b/dmenu-mousesupport-5.0.diff @@ -0,0 +1,144 @@ +diff --git a/dmenu.c b/dmenu.c +index 65f25ce..dfa59db 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -500,6 +500,119 @@ draw: + drawmenu(); + } + ++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) + { +@@ -561,6 +674,9 @@ 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); +@@ -658,7 +774,8 @@ setup(void) + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; +- swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; ++ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ++ ButtonPressMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); diff --git a/dmenu-numbers-4.9.diff b/dmenu-numbers-4.9.diff new file mode 100644 index 0000000..113be80 --- /dev/null +++ b/dmenu-numbers-4.9.diff @@ -0,0 +1,81 @@ +From 61abc60dbfaa8ec63fcd176307308aee88a19e32 Mon Sep 17 00:00:00 2001 +From: Miles Alan +Date: Sat, 10 Aug 2019 17:20:08 -0500 +Subject: [PATCH] Display number of matched and total items in top right corner + +--- + dmenu.c | 25 +++++++++++++++++++++++-- + 1 file changed, 23 insertions(+), 2 deletions(-) + +diff --git a/dmenu.c b/dmenu.c +index 6b8f51b..98c5810 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -24,6 +24,8 @@ + * 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) ++#define NUMBERSMAXDIGITS 100 ++#define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) + 1 + + /* enums */ + enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ +@@ -34,6 +36,7 @@ struct item { + int out; + }; + ++static char numbers[NUMBERSBUFSIZE] = ""; + static char text[BUFSIZ] = ""; + static char *embed; + static int bh, mw, mh; +@@ -126,6 +129,21 @@ drawitem(struct item *item, int x, int y, int w) + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); + } + ++static void ++recalculatenumbers() ++{ ++ unsigned int numer = 0, denom = 0; ++ struct item *item; ++ if (matchend) { ++ numer++; ++ for (item = matchend; item && item->left; item = item->left) ++ numer++; ++ } ++ for (item = items; item && item->text; item++) ++ denom++; ++ snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); ++} ++ + static void + drawmenu(void) + { +@@ -151,6 +169,7 @@ drawmenu(void) + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } + ++ recalculatenumbers(); + if (lines > 0) { + /* draw vertical list */ + for (item = curr; item != next; item = item->right) +@@ -165,13 +184,15 @@ 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, MIN(TEXTW(item->text), mw - x - TEXTW(">") - TEXTW(numbers))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); +- drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); ++ drw_text(drw, mw - w - TEXTW(numbers), 0, w, bh, lrpad / 2, ">", 0); + } + } ++ drw_setscheme(drw, scheme[SchemeNorm]); ++ drw_text(drw, mw - TEXTW(numbers), 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0); + drw_map(drw, win, 0, 0, mw, mh); + } + +-- +2.19.2 + diff --git a/dmenu.1 b/dmenu.1 new file mode 100644 index 0000000..addc2e9 --- /dev/null +++ b/dmenu.1 @@ -0,0 +1,225 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfiv ] +.RB [ \-g +.IR columns ] +.RB [ \-l +.IR lines ] +.RB [ \-m +.IR monitor ] +.RB [ \-p +.IR prompt ] +.RB [ \-fn +.IR font ] +.RB [ \-nb +.IR color ] +.RB [ \-nf +.IR color ] +.RB [ \-sb +.IR color ] +.RB [ \-sf +.IR color ] +.RB [ \-nhb +.IR color ] +.RB [ \-nhf +.IR color ] +.RB [ \-shb +.IR color ] +.RB [ \-shf +.IR color ] +.RB [ \-w +.IR windowid ] +.P +.BR dmenu_run " ..." +.SH DESCRIPTION +.B dmenu +is a dynamic menu for X, which reads a list of newline\-separated items from +stdin. When the user selects an item and presses Return, their choice is printed +to stdout and dmenu terminates. Entering text will narrow the items to those +matching the tokens in the input. +.P +.B dmenu_run +is a script used by +.IR dwm (1) +which lists programs in the user's $PATH and runs the result in their $SHELL. +.SH OPTIONS +.TP +.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. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.BI \-g " columns" +dmenu lists items in a grid with the given number of columns. +.TP +.BI \-l " lines" +dmenu lists items in a grid 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. +.TP +.BI \-p " prompt" +defines the prompt to be displayed to the left of the input field. +.TP +.BI \-fn " font" +defines the font or font set used. +.TP +.BI \-nb " color" +defines the normal background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-nf " color" +defines the normal foreground color. +.TP +.BI \-sb " color" +defines the selected background color. +.TP +.BI \-sf " color" +defines the selected foreground color. +.TP +.BI \-nhb " color" +defines the normal highlight background color. +.TP +.BI \-nhf " color" +defines the normal highlight foreground color. +.TP +.BI \-shb " color" +defines the selected highlight background color. +.TP +.BI \-shf " color" +defines the selected highlight foreground color. +.TP +.B \-v +prints version information to stdout, then exits. +.TP +.BI \-w " windowid" +embed into windowid. +.SH USAGE +dmenu is completely controlled by the keyboard. Items are selected using the +arrow keys, page up, page down, home, and end. +.TP +.B Tab +Copy the selected item to the input field. +.TP +.B Return +Confirm selection. Prints the selected item to stdout and exits, returning +success. +.TP +.B Ctrl-Return +Confirm selection. Prints the selected item to stdout and continues. +.TP +.B Shift\-Return +Confirm input. Prints the input text to stdout and exits, returning success. +.TP +.B Escape +Exit without selecting an item, returning failure. +.TP +.B Ctrl-Left +Move cursor to the start of the current word +.TP +.B Ctrl-Right +Move cursor to the end of the current word +.TP +.B C\-a +Home +.TP +.B C\-b +Left +.TP +.B C\-c +Escape +.TP +.B C\-d +Delete +.TP +.B C\-e +End +.TP +.B C\-f +Right +.TP +.B C\-g +Escape +.TP +.B C\-h +Backspace +.TP +.B C\-i +Tab +.TP +.B C\-j +Return +.TP +.B C\-J +Shift-Return +.TP +.B C\-k +Delete line right +.TP +.B C\-m +Return +.TP +.B C\-M +Shift-Return +.TP +.B C\-n +Down +.TP +.B C\-p +Up +.TP +.B C\-u +Delete line left +.TP +.B C\-w +Delete word left +.TP +.B C\-y +Paste from primary X selection +.TP +.B C\-Y +Paste from X clipboard +.TP +.B M\-b +Move cursor to the start of the current word +.TP +.B M\-f +Move cursor to the end of the current word +.TP +.B M\-g +Home +.TP +.B M\-G +End +.TP +.B M\-h +Up +.TP +.B M\-j +Page down +.TP +.B M\-k +Page up +.TP +.B M\-l +Down +.SH SEE ALSO +.IR dwm (1), +.IR stest (1) diff --git a/dmenu.c b/dmenu.c new file mode 100644 index 0000000..9448959 --- /dev/null +++ b/dmenu.c @@ -0,0 +1,1097 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#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 LENGTH(X) (sizeof X / sizeof X[0]) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) +#define NUMBERSMAXDIGITS 100 +#define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) + 1 + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeNormHighlight, SchemeSelHighlight, + SchemeOut, SchemeMid, SchemeLast }; /* color schemes */ + + +struct item { + char *text; + struct item *left, *right; + int out; + double distance; +}; + +static char numbers[NUMBERSBUFSIZE] = ""; +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 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 * columns * 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 : MIN(TEXTW(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) + 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]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static char * +cistrstr(const char *s, const char *sub) +{ + size_t len; + + for (len = strlen(sub); *s; s++) + if (!strncasecmp(s, sub, len)) + return (char *)s; + return NULL; +} + +static void +drawhighlights(struct item *item, int x, int y, int maxw) +{ + int i, indent; + char *highlight; + char c; + + if (!(strlen(item->text) && strlen(text))) + return; + + drw_setscheme(drw, scheme[item == sel + ? SchemeSelHighlight + : SchemeNormHighlight]); + for (i = 0, highlight = item->text; *highlight && text[i];) { + if (*highlight == text[i]) { + /* get indentation */ + c = *highlight; + *highlight = '\0'; + indent = TEXTW(item->text); + *highlight = c; + + /* highlight character */ + c = highlight[1]; + highlight[1] = '\0'; + drw_text( + drw, + x + indent - (lrpad / 2), + y, + MIN(maxw - indent, TEXTW(highlight) - lrpad), + bh, 0, highlight, 0 + ); + highlight[1] = c; + i++; + } + highlight++; + } +} + + +static int +drawitem(struct item *item, int x, int y, int w) +{ + int r; + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->left == sel || item->right == sel) + drw_setscheme(drw, scheme[SchemeMid]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); + + r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); + drawhighlights(item, x, y, w); + return r; +} + +static void +recalculatenumbers() +{ + unsigned int numer = 0, denom = 0; + struct item *item; + if (matchend) { + numer++; + for (item = matchend; item && item->left; item = item->left) + numer++; + } + for (item = items; item && item->text; item++) + denom++; + snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); +} + +static void +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, fh = drw->fonts->h, 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 + (bh-fh)/2, 2, fh - 4, 1, 0); + } + + recalculatenumbers(); + if (lines > 0) { + /* draw grid */ + int i = 0; + for (item = curr; item != next; item = item->right, i++) + drawitem( + item, + x + ((i / lines) * ((mw - x) / columns)), + y + (((i % lines) + 1) * bh), + (mw - x) / columns + ); + } 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, MIN(TEXTW(item->text), mw - x - TEXTW(">") - TEXTW(numbers))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w - TEXTW(numbers), 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - TEXTW(numbers), 0, TEXTW(numbers), bh, lrpad / 2, numbers, 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"); +} + +int +compare_distance(const void *a, const void *b) +{ + struct item *da = *(struct item **) a; + struct item *db = *(struct item **) b; + + if (!db) + return 1; + if (!da) + return -1; + + return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1; +} + +void +fuzzymatch(void) +{ + /* bang - we have so much memory */ + struct item *it; + struct item **fuzzymatches = NULL; + char c; + int number_of_matches = 0, i, pidx, sidx, eidx; + int text_len = strlen(text), itext_len; + + matches = matchend = NULL; + + /* walk through all items */ + for (it = items; it && it->text; it++) { + if (text_len) { + itext_len = strlen(it->text); + pidx = 0; /* pointer */ + sidx = eidx = -1; /* start of match, end of match */ + /* walk through item text */ + for (i = 0; i < itext_len && (c = it->text[i]); i++) { + /* fuzzy match pattern */ + if (!fstrncmp(&text[pidx], &c, 1)) { + if(sidx == -1) + sidx = i; + pidx++; + if (pidx == text_len) { + eidx = i; + break; + } + } + } + /* build list of matches */ + if (eidx != -1) { + /* compute distance */ + /* add penalty if match starts late (log(sidx+2)) + * add penalty for long a match without many matching characters */ + it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len); + /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */ + appenditem(it, &matches, &matchend); + number_of_matches++; + } + } else { + appenditem(it, &matches, &matchend); + } + } + + if (number_of_matches) { + /* initialize array with matches */ + if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*)))) + die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*)); + for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) { + fuzzymatches[i] = it; + } + /* sort matches according to distance */ + qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance); + /* rebuild list of matches */ + matches = matchend = NULL; + for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \ + it->text; i++, it = fuzzymatches[i]) { + appenditem(it, &matches, &matchend); + } + free(fuzzymatches); + } + curr = sel = matches; + calcoffsets(); +} + +static void +match(void) +{ + if (fuzzy) { + fuzzymatch(); + return; + } + 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 %u 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[32]; + int len; + KeySym ksym; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + 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: + movewordedge(-1); + goto draw; + case XK_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(*buf)) + insert(buf, len); + break; + case XK_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: + 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: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_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: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text); + match(); + break; + } + +draw: + drawmenu(); +} + +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; + 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 buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 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); + 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; + } + } + if (items) + items[i].text = NULL; + inputw = items ? TEXTW(items[imax].text) : 0; + 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 ButtonPress: + buttonpress(&ev); + break; + 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; + 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))) { + 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])) + 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; + } + } + inputw = MIN(inputw, mw/3); + 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, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + 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) { + 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) +{ + fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-h height]\n" + " [-nb color] [-nf color] [-sb color] [-sf color]\n" + " [-nhb color] [-nhf color] [-shb color] [-shf color] [-w windowid]\n", stderr); + exit(1); +} + +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], "-F")) /* grabs keyboard before reading stdin */ + fuzzy = 0; + 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], "-g")) { /* number of columns in grid */ + columns = atoi(argv[++i]); + if (lines == 0) lines = 1; + } else if (!strcmp(argv[i], "-l")) { /* number of lines in grid */ + lines = atoi(argv[++i]); + if (columns == 0) columns = 1; + } 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], "-h")) { /* minimum height of one menu line */ + lineheight = atoi(argv[++i]); + lineheight = MAX(lineheight,8); /* reasonable default in case of value too small/negative */ + } + 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], "-nhb")) /* normal hi background color */ + colors[SchemeNormHighlight][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */ + colors[SchemeNormHighlight][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-shb")) /* selected hi background color */ + colors[SchemeSelHighlight][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */ + colors[SchemeSelHighlight][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(); + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..653d17bdcdf3714cb71d5d0aeae68e749c631cb9 GIT binary patch literal 42984 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 literal 0 HcmV?d00001 diff --git a/dmenu_path b/dmenu_path new file mode 100755 index 0000000..3a7cda7 --- /dev/null +++ b/dmenu_path @@ -0,0 +1,13 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/dmenu_run" + +[ ! -e "$cachedir" ] && mkdir -p "$cachedir" + +IFS=: +if stest -dqr -n "$cache" $PATH; then + stest -flx $PATH | sort -u | tee "$cache" +else + cat "$cache" +fi diff --git a/dmenu_run b/dmenu_run new file mode 100755 index 0000000..834ede5 --- /dev/null +++ b/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/drw.c b/drw.c new file mode 100644 index 0000000..4cdbcbe --- /dev/null +++ b/drw.c @@ -0,0 +1,436 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + 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; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +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; + 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; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!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 (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + 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 string."); + } + + 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); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/drw.h b/drw.h new file mode 100644 index 0000000..4c67419 --- /dev/null +++ b/drw.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +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); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/drw.o b/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..b410ae3ca662d80d497f45cc8ffc33f383ac5697 GIT binary patch literal 10616 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 Kyv(4C75@PcI(`rU literal 0 HcmV?d00001 diff --git a/stest.1 b/stest.1 new file mode 100644 index 0000000..2667d8a --- /dev/null +++ b/stest.1 @@ -0,0 +1,90 @@ +.TH STEST 1 dmenu\-VERSION +.SH NAME +stest \- filter a list of files by properties +.SH SYNOPSIS +.B stest +.RB [ -abcdefghlpqrsuwx ] +.RB [ -n +.IR file ] +.RB [ -o +.IR file ] +.RI [ file ...] +.SH DESCRIPTION +.B stest +takes a list of files and filters by the files' properties, analogous to +.IR test (1). +Files which pass all tests are printed to stdout. If no files are given, stest +reads files from stdin. +.SH OPTIONS +.TP +.B \-a +Test hidden files. +.TP +.B \-b +Test that files are block specials. +.TP +.B \-c +Test that files are character specials. +.TP +.B \-d +Test that files are directories. +.TP +.B \-e +Test that files exist. +.TP +.B \-f +Test that files are regular files. +.TP +.B \-g +Test that files have their set-group-ID flag set. +.TP +.B \-h +Test that files are symbolic links. +.TP +.B \-l +Test the contents of a directory given as an argument. +.TP +.BI \-n " file" +Test that files are newer than +.IR file . +.TP +.BI \-o " file" +Test that files are older than +.IR file . +.TP +.B \-p +Test that files are named pipes. +.TP +.B \-q +No files are printed, only the exit status is returned. +.TP +.B \-r +Test that files are readable. +.TP +.B \-s +Test that files are not empty. +.TP +.B \-u +Test that files have their set-user-ID flag set. +.TP +.B \-v +Invert the sense of tests, only failing files pass. +.TP +.B \-w +Test that files are writable. +.TP +.B \-x +Test that files are executable. +.SH EXIT STATUS +.TP +.B 0 +At least one file passed all tests. +.TP +.B 1 +No files passed all tests. +.TP +.B 2 +An error occurred. +.SH SEE ALSO +.IR dmenu (1), +.IR test (1) diff --git a/stest.c b/stest.c new file mode 100644 index 0000000..7a7b0bc --- /dev/null +++ b/stest.c @@ -0,0 +1,109 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include + +#include "arg.h" +char *argv0; + +#define FLAG(x) (flag[(x)-'a']) + +static void test(const char *, const char *); +static void usage(void); + +static int match = 0; +static int flag[26]; +static struct stat old, new; + +static void +test(const char *path, const char *name) +{ + struct stat st, ln; + + if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ + && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ + && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ + && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ + && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ + && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ + && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ + && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ + && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ + && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ + && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ + && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ + && (!FLAG('s') || st.st_size > 0) /* not empty */ + && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ + && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ + && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ + if (FLAG('q')) + exit(0); + match = 1; + puts(name); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " + "[-n file] [-o file] [file...]\n", argv0); + exit(2); /* like test(1) return > 1 on error */ +} + +int +main(int argc, char *argv[]) +{ + struct dirent *d; + char path[PATH_MAX], *line = NULL, *file; + size_t linesiz = 0; + ssize_t n; + DIR *dir; + int r; + + ARGBEGIN { + case 'n': /* newer than file */ + case 'o': /* older than file */ + file = EARGF(usage()); + if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) + perror(file); + break; + default: + /* miscellaneous operators */ + if (strchr("abcdefghlpqrsuvwx", ARGC())) + FLAG(ARGC()) = 1; + else + usage(); /* unknown flag */ + } ARGEND; + + if (!argc) { + /* read list from stdin */ + while ((n = getline(&line, &linesiz, stdin)) > 0) { + if (n && line[n - 1] == '\n') + line[n - 1] = '\0'; + test(line, line); + } + free(line); + } else { + for (; argc; argc--, argv++) { + if (FLAG('l') && (dir = opendir(*argv))) { + /* test directory contents */ + while ((d = readdir(dir))) { + r = snprintf(path, sizeof path, "%s/%s", + *argv, d->d_name); + if (r >= 0 && (size_t)r < sizeof path) + test(path, d->d_name); + } + closedir(dir); + } else { + test(*argv, *argv); + } + } + } + return match ? 0 : 1; +} diff --git a/stest.o b/stest.o new file mode 100644 index 0000000000000000000000000000000000000000..c7926cec7aa2152edb0566d0edeafe9af99b6459 GIT binary patch literal 5288 zcmbW4Z){sv6~M3Uv@uQbOaD+Z%9zWXk!s7su3$>bit>`UeI5=i^v^&|=eYKB64kLY z`?*QcP&38pa9wv3Y=8h!*!WPvB#H)<4^wBY?OG6%%0TdCpnaIwEN^O&rWTED=A7$$ z?3;_<7moCN@A=(x&pr3vbKko@5svP1yIhRK#lFbe8d1j1^|tH7ygJN!na1{-<(;US zmCs`ls+#4GVdX9SdC^TZV}GRd?g6v(f%(|mL*PGYmb>}~pw%oD0|Nk{W5o`5dAH@N z-5sjdt^!`-&RL<`SbQL$+clxvz2&ihzn|S?Y$uy!tZ=vU_jCpg|n79Pi zZ7JmAQ`QcDf8Kv+Fn%PFv{F;k>A6R8`NFaJg~I?d{?ts`!djMU2y|UPyoUWx57wXm zY=52^4@E0H^NfSW0b~DxO181jtUT<6>>Yq5UXi_h05I)Wn^(sIUW^PY0C63f+gbLQ z%T=xT0iea9L2db6sGQpCD!!@}YslUkEUa5xaI->>xpc|fc%}Ad?A)kEO0R`6B|YQi z`vS(&EthdB%!*a*>6eV+U$x>-!78%-HEkJlOuKlqa`4+@m1Ad@-gC{zjm2-eSV0>v zrvu*c<#!5e_$o#kSL&-9uSAwEdmqS%BWqT8kkQf&Qw!hZW}#P&QZ3vt3Ma23U842X8CjglK@h7Eq|xAQx=ou5ag_J*m&58U1GaOM|b!Kc7OT6 zR(~j{Z_|SekE9%O%=L}0GS@<{YxCM}?~`DR`SwZC_8BY< zU+(}ELBXPUE=PR5r`=<|zLTB@eb?QdA-As&%tm}(m^n002-}B*?Gx^ZukVa!#OJSc zjraymuQGhWldH#kLrdLX^1bf&47zWFG+))~W^hoI!(qU@N(d!fz-0 zuRHL#1J5|{lmnl2;CTnW;J{13*Gucka}wAY2m7ZT_>Ua;GYHHwyAaTrd z6!h6&-aA>1BIUz`m+ir{WpTE z>u_DM|Ao-sQ26sge@Ef`J^~TCF+dR2{~Zo|KyaGZdEqyx@E;1kL-Hf%;jm&~754O9 z!gw~pL;I4H{Cr&E^aa6Y%-ad!=Rv5WAeYYzKX33uN5y!>YY20Td|M=}D{+z@= zCGi&|ewW05FLA%b|0X!C+v7riTjC#;?BAFCY>~JR9e`jV&v&x}@0U34`z7Jm5FFP* z&J#cADA+7axc)i*+vapXnw0n&QRju8KR#>6k1!oR0?l%oYB?(%*KKRTW_mJi$C-Wv zzA1Vxiwc^Dboe~AjDW;*c43a`iR|pGm4RNFtZnIV+a2WJ9-&WV3i@0QJ}f!=xae+8 z$5OfYtR=_wf7bob50_58p$!8=$;*ocx>#o3hCi5txYon)$TBGw&BVO`&UK(KPnykvtVr`XFYr;Q5F5De6!8hjUlQ y#x7qI8yXb*h<7x6)c*K?OU*q%>|59X@~39e`c>B&?`)m=;~!@fyw}U3_Wv*7_h}IT literal 0 HcmV?d00001 diff --git a/util.c b/util.c new file mode 100644 index 0000000..fe044fc --- /dev/null +++ b/util.c @@ -0,0 +1,35 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#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, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#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)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/util.o b/util.o new file mode 100644 index 0000000000000000000000000000000000000000..5dee2d71351881ac743ab3aad98bf048e9418476 GIT binary patch literal 2256 zcmbuAO-vI(6vtl+O2yJv;DEtH538XGZdwu}sY(hISal;sYD6xzv|DNHm)orYH4!5o znx+W{4<0;l@M7Y@M2{vkdhp88lXx`210FmWOzL~PGceh-7k$a(z4^WWo0*-N-F-Zz zPCFeA;O2laIP7r>5Ib@hN6at+9_WF2rMj;?`SA^rQZ=Mer5cwaN>!BpBBfT~Kd#Pdi57IXJv{LIlJ%oXR0}h26 zO6?MMu=iD|uSv?E)yZa}*iFN_B@8>@pKd z4WqS39nEm1Z_AH)lZKk6+wI=!=4gxd#-wvr;j>1?t<=pGnPBl#Dw)d_Q(=Ia$;q&P zXy)3DfIk$JM`T(HbYk;kqwT~LIKj~%#`oO8`p1xw^;-@c53Yk_#pf7wceyrDJb?TZ z%l#%l?(sc!Uh(X)+$hVPWVxFj7bC45&EE+>o2{}P2dg{GTB8YJ*ZOBew z^D&L+=s8+(xeY(xhL5%3(Kh^Q8y-VW>*wn>_Yc`z8$DEvY)(#rtWE|YozEzq;)(e9ggTXg`+BG?n{j$BYFzKfn2hw)xLsrc=lr}tF0bZ|)$C<c4h$Z5=ftq)->jX8o%G(JCHFVFG$xsPCiV)J9mMd%lqpif{F@;dG> z3rv*exj=+1w6~Dadnw*kDk-sg`?~DUa}Tv7wbyU5dip+zX0Oj-veo%-5=7(k)A`eT z%