From 40c2cdce48bfb97742e8756190bd1404e24de6fd Mon Sep 17 00:00:00 2001 From: cptbbVM Date: Mon, 11 Dec 2023 20:30:20 +0100 Subject: [PATCH] add dmenu d --- dmenu | 1 - dmenud/LICENSE | 30 ++ dmenud/Makefile | 58 ++++ dmenud/README | 24 ++ dmenud/arg.h | 49 +++ dmenud/config.def.h | 23 ++ dmenud/config.h | 23 ++ dmenud/config.mk | 32 ++ dmenud/dmenu | Bin 0 -> 42856 bytes dmenud/dmenu.1 | 194 +++++++++++ dmenud/dmenu.c | 796 ++++++++++++++++++++++++++++++++++++++++++++ dmenud/dmenu.o | Bin 0 -> 32224 bytes dmenud/dmenu_path | 13 + dmenud/dmenu_run | 2 + dmenud/drw.c | 451 +++++++++++++++++++++++++ dmenud/drw.h | 58 ++++ dmenud/drw.o | Bin 0 -> 11088 bytes dmenud/stest | Bin 0 -> 16408 bytes dmenud/stest.1 | 90 +++++ dmenud/stest.c | 109 ++++++ dmenud/stest.o | Bin 0 -> 5248 bytes dmenud/util.c | 36 ++ dmenud/util.h | 9 + dmenud/util.o | Bin 0 -> 2224 bytes 24 files changed, 1997 insertions(+), 1 deletion(-) delete mode 160000 dmenu create mode 100644 dmenud/LICENSE create mode 100644 dmenud/Makefile create mode 100644 dmenud/README create mode 100644 dmenud/arg.h create mode 100644 dmenud/config.def.h create mode 100644 dmenud/config.h create mode 100644 dmenud/config.mk create mode 100755 dmenud/dmenu create mode 100644 dmenud/dmenu.1 create mode 100644 dmenud/dmenu.c create mode 100644 dmenud/dmenu.o create mode 100755 dmenud/dmenu_path create mode 100755 dmenud/dmenu_run create mode 100644 dmenud/drw.c create mode 100644 dmenud/drw.h create mode 100644 dmenud/drw.o create mode 100755 dmenud/stest create mode 100644 dmenud/stest.1 create mode 100644 dmenud/stest.c create mode 100644 dmenud/stest.o create mode 100644 dmenud/util.c create mode 100644 dmenud/util.h create mode 100644 dmenud/util.o diff --git a/dmenu b/dmenu deleted file mode 160000 index 8df553e..0000000 --- a/dmenu +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8df553e0048733bab4bc85a6b76bcfd44c046e71 diff --git a/dmenud/LICENSE b/dmenud/LICENSE new file mode 100644 index 0000000..2a64b28 --- /dev/null +++ b/dmenud/LICENSE @@ -0,0 +1,30 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2008 Sander van Dijk +© 2006-2007 Michał Janeczek +© 2007 Kris Maglione +© 2009 Gottox +© 2009 Markus Schnalke +© 2009 Evan Gates +© 2010-2012 Connor Lane Smith +© 2014-2022 Hiltjo Posthuma +© 2015-2019 Quentin Rameau + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dmenud/Makefile b/dmenud/Makefile new file mode 100644 index 0000000..458c524 --- /dev/null +++ b/dmenud/Makefile @@ -0,0 +1,58 @@ +# 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: dmenu stest + +.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 clean dist install uninstall diff --git a/dmenud/README b/dmenud/README new file mode 100644 index 0000000..a8fcdfe --- /dev/null +++ b/dmenud/README @@ -0,0 +1,24 @@ +dmenu - dynamic menu +==================== +dmenu is an efficient dynamic menu for X. + + +Requirements +------------ +In order to build dmenu you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (dmenu is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dmenu +(if necessary as root): + + make clean install + + +Running dmenu +------------- +See the man page for details. diff --git a/dmenud/arg.h b/dmenud/arg.h new file mode 100644 index 0000000..e94e02b --- /dev/null +++ b/dmenud/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/dmenud/config.def.h b/dmenud/config.def.h new file mode 100644 index 0000000..1edb647 --- /dev/null +++ b/dmenud/config.def.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/dmenud/config.h b/dmenud/config.h new file mode 100644 index 0000000..1edb647 --- /dev/null +++ b/dmenud/config.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/dmenud/config.mk b/dmenud/config.mk new file mode 100644 index 0000000..566348b --- /dev/null +++ b/dmenud/config.mk @@ -0,0 +1,32 @@ +# dmenu version +VERSION = 5.2 + +# 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 +#MANPREFIX = ${PREFIX}/man + +# includes and libs +INCS = -I$(X11INC) -I$(FREETYPEINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) + +# 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/dmenud/dmenu b/dmenud/dmenu new file mode 100755 index 0000000000000000000000000000000000000000..517a3e6d8fd66f3a33266a45564be81024f81db1 GIT binary patch literal 42856 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 KDFsqk;eP<+?jza& literal 0 HcmV?d00001 diff --git a/dmenud/dmenu.1 b/dmenud/dmenu.1 new file mode 100644 index 0000000..323f93c --- /dev/null +++ b/dmenud/dmenu.1 @@ -0,0 +1,194 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfiv ] +.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 [ \-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 \-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 \-l " lines" +dmenu lists items vertically, with the given number of lines. +.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 +.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/dmenud/dmenu.c b/dmenud/dmenu.c new file mode 100644 index 0000000..40f93e0 --- /dev/null +++ b/dmenud/dmenu.c @@ -0,0 +1,796 @@ +/* See LICENSE file for copyright and license details. */ +#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 TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw; +static int lrpad; /* sum of left and right padding */ +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + +static unsigned int +textw_clamp(const char *str, unsigned int n) +{ + unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; + return MIN(w, n); +} + +static void +appenditem(struct item *item, struct item **list, struct item **last) +{ + if (*last) + (*last)->right = item; + else + *list = item; + + item->left = *last; + item->right = NULL; + *last = item; +} + +static void +calcoffsets(void) +{ + int i, n; + + if (lines > 0) + n = lines * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ + for (i = 0, next = curr; next; next = next->right) + if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) + break; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + for (i = 0; items && items[i].text; ++i) + free(items[i].text); + free(items); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static char * +cistrstr(const char *h, const char *n) +{ + size_t i; + + if (!n[0]) + return (char *)h; + + for (; *h; ++h) { + for (i = 0; n[i] && tolower((unsigned char)n[i]) == + tolower((unsigned char)h[i]); ++i) + ; + if (n[i] == '\0') + return (char *)h; + } + return NULL; +} + +static int +drawitem(struct item *item, int x, int y, int w) +{ + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); + + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); +} + +static void +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, w; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } + + if (lines > 0) { + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_map(drw, win, 0, 0, mw, mh); +} + +static void +grabfocus(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + nanosleep(&ts, NULL); + } + die("cannot grab focus"); +} + +static void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); +} + +static void +match(void) +{ + static char **tokv = NULL; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) + if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + die("cannot realloc %zu bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) /* not all tokens match */ + continue; + /* exact matches go first, then prefixes, then substrings */ + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); +} + +static void +insert(const char *str, ssize_t n) +{ + if (strlen(text) + n > sizeof text - 1) + return; + /* move existing text out of the way, insert new text, and update cursor */ + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); +} + +static size_t +nextrune(int inc) +{ + ssize_t n; + + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +static void +movewordedge(int dir) +{ + if (dir < 0) { /* move cursor to the start of the word*/ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + } else { /* move cursor to the end of the word */ + while (text[cursor] && strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + while (text[cursor] && !strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + } +} + +static void +keypress(XKeyEvent *ev) +{ + char buf[64]; + int len; + KeySym ksym = NoSymbol; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: /* composed string from input method */ + goto insert; + case XLookupKeySym: + case XLookupBoth: /* a KeySym and a string are returned: use keysym */ + break; + } + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: /* delete right */ + text[cursor] = '\0'; + match(); + break; + case XK_u: /* delete left */ + insert(NULL, 0 - cursor); + break; + case XK_w: /* delete word */ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + break; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + case XK_KP_Left: + movewordedge(-1); + goto draw; + case XK_Right: + case XK_KP_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + case XK_g: ksym = XK_Home; break; + case XK_G: ksym = XK_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; + } + } + + switch(ksym) { + default: +insert: + if (!iscntrl((unsigned char)*buf)) + insert(buf, len); + break; + case XK_Delete: + case XK_KP_Delete: + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); + break; + case XK_End: + case XK_KP_End: + if (text[cursor] != '\0') { + cursor = strlen(text); + break; + } + if (next) { + /* jump to end of list and position items in reverse */ + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + break; + case XK_Escape: + cleanup(); + exit(1); + case XK_Home: + case XK_KP_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + case XK_KP_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Up: + case XK_KP_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_Next: + case XK_KP_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_Prior: + case XK_KP_Prior: + if (!prev) + return; + sel = curr = prev; + calcoffsets(); + break; + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + case XK_KP_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + case XK_KP_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; + match(); + break; + } + +draw: + drawmenu(); +} + +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static void +readstdin(void) +{ + char *line = NULL; + size_t i, itemsiz = 0, linesiz = 0; + ssize_t len; + + /* read each line from stdin and add it to the item list */ + for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { + if (i + 1 >= itemsiz) { + itemsiz += 256; + if (!(items = realloc(items, itemsiz * sizeof(*items)))) + die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); + } + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + if (!(items[i].text = strdup(line))) + die("strdup:"); + + items[i].out = 0; + } + free(line); + if (items) + items[i].text = NULL; + lines = MIN(lines, i); +} + +static void +run(void) +{ + XEvent ev; + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static void +setup(void) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; +#endif + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i]) != 0) + break; + + 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); + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = mw / 3; /* input width: ~33% of monitor width */ + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, root, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + XReparentWindow(dpy, win, parentwin, x, y); + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static void +usage(void) +{ + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); +} + +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else + usage(); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} diff --git a/dmenud/dmenu.o b/dmenud/dmenu.o new file mode 100644 index 0000000000000000000000000000000000000000..a14f86706a3b1457494d92d62eb8ac526a34b226 GIT binary patch literal 32224 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 Lw&_#yQ>OnvvvFob literal 0 HcmV?d00001 diff --git a/dmenud/dmenu_path b/dmenud/dmenu_path new file mode 100755 index 0000000..3a7cda7 --- /dev/null +++ b/dmenud/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/dmenud/dmenu_run b/dmenud/dmenu_run new file mode 100755 index 0000000..834ede5 --- /dev/null +++ b/dmenud/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/dmenud/drw.c b/dmenud/drw.c new file mode 100644 index 0000000..78a2b27 --- /dev/null +++ b/dmenud/drw.c @@ -0,0 +1,451 @@ +/* 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."); + } + + 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) +{ + int ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + 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, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + static unsigned int nomatches[128], ellipsis_width; + + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + while (1) { + ew = ellipsis_len = 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) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + ew += tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); + } + x += ew; + w -= ew; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + hash = (unsigned int)utf8codepoint; + hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; + hash = ((hash >> 15) ^ hash) * 0xD35A2D97; + h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); + h1 = (hash >> 17) % LENGTH(nomatches); + /* avoid expensive XftFontMatch call when we know we won't find a match */ + if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint) + goto no_match; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + 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); + nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; +no_match: + 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); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + 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/dmenud/drw.h b/dmenud/drw.h new file mode 100644 index 0000000..fd7631b --- /dev/null +++ b/dmenud/drw.h @@ -0,0 +1,58 @@ +/* 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); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +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/dmenud/drw.o b/dmenud/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..6a8145e766f7aeedbd52c3740a5525b524c223ef GIT binary patch literal 11088 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 literal 0 HcmV?d00001 diff --git a/dmenud/stest b/dmenud/stest new file mode 100755 index 0000000000000000000000000000000000000000..b48e0712c53b10417b1346683084b9a3c66e57d7 GIT binary patch literal 16408 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 Oc)h8y%4T3=#h(D-Y`hx) literal 0 HcmV?d00001 diff --git a/dmenud/stest.1 b/dmenud/stest.1 new file mode 100644 index 0000000..2667d8a --- /dev/null +++ b/dmenud/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/dmenud/stest.c b/dmenud/stest.c new file mode 100644 index 0000000..e27d3a5 --- /dev/null +++ b/dmenud/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 (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/dmenud/stest.o b/dmenud/stest.o new file mode 100644 index 0000000000000000000000000000000000000000..63f314a7efb3ffb9d8965926f94dcafed3005205 GIT binary patch literal 5248 zcmbW4Z){uD6~M3UG%*eJbN{T%U}I)=AZX+fr`vFuisHl_FW^G7u4Pga632e|vpRNW zzefUt)yQ_i7#B8*0PRDI_&}!#sbbSsZBu8|Qle>0Tcv$0L#SfQ%tSY-WrGRtocr#v zZ!YmZ>`2e|p5Hy^+nu@Q0EUi*L*N5+$xH)ZkfulXEB12b>s?8!U&U@nxqztPKu->>QTO!HKgFe~s9=_^f$H zpf?vd*dL!xB+Z%Gxzyr!vpM_0rR52L>A=i<%EVrVdI)q~pV-Jg<-vM$xAo>&UD8KBM}87s|H}lDwvTdjN!LP{8GA(2GBQqta=VoujOHj8aW2Hek*9 zaKBc%3Y{m%T*V7ov5xGcetUC%+0E>3qj1*Scx&}fK#fLiw0zMhdqTCn6`K_!MaV4xOlW=G{m&8vJ45U#_J@B>#OEvwh&?-CMe9Z{)cfZJe)n zHr^U7yy4A%ls2l)!kpVRR{qe?qL(9u_v3pLm$*4BmmlUxx#JbPiywFWHn8J^^$()u z_Y5aB9Ki0Z7ttM-nz%IbX2&adoTH6BY&8Nk4bxU9ysKBB2ugpqyNv31&|lvXb0)9P z8`Wr6BOl&qd(OkIE019QK5^nYPZ6Wa+1rAlQmP$Zan|xMZ$`^+;3B4CWl=(Dv2O^Va*-uZEyw89!%*Z}3 z#%{(o;`8@GGa%R~-l4#bxTC(Fg6D4EAKaeX-M$_`5uX=Q0K?Py`oLt^*8?uY80&ut?yH0S181BrM2uDP$5@M$oC8y93bbU^L+!uytyFGlyI2?9p!UyArGJe@{H54FF=dD zCUoLA3ce!Z;8~G)SSNLxt+Nh(?==~AZspnrcngJsW$vr8~$P&{!$zM z<2L-YHk{=wGiT`u#$R9z6*iNK&$6_+#4@QQTZmhUIi|-(2Jk0@KZD?uG_zS2&(1zL zz-AV+^J!~_nalGQizgChF2_j*v~d~p2-pY|Z# z630GqdS0PD-kZenzC*!ySYO0A6N2OUqLBTp;JV=^U?E;;y)|wzN6T06Z-cQ{=Cp%Q2247zohV&h5l2~s(JXmV*eAN zzpHTauPgi)LgxnpdsO3p7B{*Mze#Y)!zmF*SNM+v4@+_6I=oY{uL=9RC42lMMCURs z@$C|)Hv$f0-HwYmQRt%}m+zMqP92V86#t^cF%EqLTvGV2h5n&vF>msHMCXsZ2QG>q z5bMG@ua$VO;AoHcGTFx^`%MymRN@$G2V5vciC+gg#rc60|9Xj^kT~v@?D_X6=(rwy zcN3?2L4Je8&q;B0>itCoSY%x6rO(P)jtYt4UJ&{>hFw@|b&RC`nU$6cA%_H>Lw5=~@;kA;r9un^6+~iC) zzF^Ah`ak;t@Wa%pUtB}LP!{E6fiBX_$M6Sh3UfUKmn>nCGy@L@1Zx<5>AMQ=4V0VU zLN3e@aXF>TOI)2FzyC0%>R%K7zf&9n6iBrAororb3Mc;q3a4qE-FzJ9fSNoy3Peh~YH6{_uN>{HgwMKkD4* wG9?bQN1P*mqv56cn8{NX!sJ^#yM{YRUgPDl0sFT4C_g8%>k literal 0 HcmV?d00001 diff --git a/dmenud/util.c b/dmenud/util.c new file mode 100644 index 0000000..96b82c9 --- /dev/null +++ b/dmenud/util.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +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); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} diff --git a/dmenud/util.h b/dmenud/util.h new file mode 100644 index 0000000..c0a50d4 --- /dev/null +++ b/dmenud/util.h @@ -0,0 +1,9 @@ +/* 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)) +#define LENGTH(X) (sizeof (X) / sizeof (X)[0]) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/dmenud/util.o b/dmenud/util.o new file mode 100644 index 0000000000000000000000000000000000000000..8e55fa18d6ada0bfba70d02362b40ffaf2d35ebf GIT binary patch literal 2224 zcmbtUO=uHQ5T0$TjWtbT?V(Z+5mrf{E}Ik~q$<%yyGoE+D{3#ACR-cKpPNldRWM>b zgb?V#qX!Qj6g(94C^YE7t4B}Pqfig^ASe~$%?e!*H_wR z+#gQLxBTIhT=s{b%5|js!_VYZOTUnxTl%%UX6d(b+tTmkPpGq6v+rmK4Gjy1B5kdC z3LW&mXss1_iv>os)~p=RT1i>`n3Mw^gi#NxEFAoX2Gl1&Yn~v>9Mc*b)Ud}2Xic>0 z4|+TC>cRCO#w|3`vDD_k`T&R4>1-_YvYq&(UG-`$t0N1T`;bZJ^QBB2U}|zQ9t=%g zx*85f#+9fN2}afExEfO#KRRyF-kt7tqjojnaLGz| z^@i91Tmi-r6@7;rexwT?>Vhj>aGJVXeVmikC-2WXC&VVY@S$qv@=6Aj)T9d8oDLN; zs~ZL^EtHL1(Of{^$m>N|C|Au4lrdB?K(FOY(Cv$XbxEqW!nxab;7`YvR}81SuooA{ z-!RcV5AE=@9h6{5Yy=>Xttsx zm@~xAiN_o+jADS!FTn{+*XXcOJS<^9+GN`A)BaCT{20R-oghnQoEtIR36CL8HUBlA z{(V$4ic1u}Kkt{v`2JicFhDl_*hQIR1pY7NF`mcu1q^VTb4V|_fiBz|h|xO}?<%@b zWBJa$oZoX7xkPp5zhwE-R7?