From cd5d6389ae36ce53f021914b19b82f4018e68171 Mon Sep 17 00:00:00 2001 From: Asdam9 <49947700+Asdam9@users.noreply.github.com> Date: Tue, 15 Dec 2020 22:15:49 +0100 Subject: [PATCH] Add files via upload --- LICENSE | 30 ++ Makefile | 64 +++++ README | 24 ++ arg.h | 49 ++++ config.def.h | 23 ++ config.mk | 31 ++ dmenu | Bin 0 -> 43928 bytes dmenu.1 | 212 ++++++++++++++ dmenu.c | 776 +++++++++++++++++++++++++++++++++++++++++++++++++++ dmenu.o | Bin 0 -> 32424 bytes dmenu_path | 13 + dmenu_run | 2 + drw.c | 435 +++++++++++++++++++++++++++++ drw.h | 57 ++++ drw.o | Bin 0 -> 10600 bytes stest | Bin 0 -> 17680 bytes stest.1 | 90 ++++++ stest.c | 109 ++++++++ stest.o | Bin 0 -> 5280 bytes util.c | 35 +++ util.h | 8 + util.o | Bin 0 -> 2264 bytes 22 files changed, 1958 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 arg.h create mode 100644 config.def.h create mode 100644 config.mk create mode 100644 dmenu create mode 100644 dmenu.1 create mode 100644 dmenu.c create mode 100644 dmenu.o create mode 100644 dmenu_path create mode 100644 dmenu_run create mode 100644 drw.c create mode 100644 drw.h create mode 100644 drw.o create mode 100644 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/LICENSE b/LICENSE new file mode 100644 index 0000000..6ed8ad3 --- /dev/null +++ b/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-2019 Hiltjo Posthuma +© 2015-2018 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/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/README b/README new file mode 100644 index 0000000..a8fcdfe --- /dev/null +++ b/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/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..23d15b6 --- /dev/null +++ b/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[] = { + "ttf:LiberationMono-Regular: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] = { "#aaaaaa", "#222222" }, + [SchemeSel] = { "#aaaaaa", "#224822" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 10; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..0929b4a --- /dev/null +++ b/config.mk @@ -0,0 +1,31 @@ +# dmenu version +VERSION = 4.9 + +# 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) + +# 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 100644 index 0000000000000000000000000000000000000000..f484b07ff37af5b9893dbad5b9400b7936c1ff95 GIT binary patch literal 43928 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 +#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) + +/* 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 dmx = 0; /* put dmenu at this x offset */ +static int dmy = 0; /* put dmenu at this y offset (measured from the bottom if topbar is 0) */ +static unsigned int dmw = 0; /* make dmenu this wide */ +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 * 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 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 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, MIN(TEXTW(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 %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 +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, None)) + continue; + switch(ev.type) { + 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])) + break; + + x = info[i].x_org + dmx; + y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy); + mw = (dmw>0 ? dmw : info[i].width); + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + x = dmx; + y = topbar ? dmy : wa.height - mh - dmy; + mw = (dmw>0 ? dmw : wa.width); + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + 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; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetClassHint(dpy, win, &ch); + + /* open input methods */ + xim = XOpenIM(dpy, NULL, NULL, NULL); + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + if (embed) { + XSelectInput(dpy, parentwin, FocusChangeMask); + 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] [-x xoffset] [-y yoffset] [-w width]\n" + " [-nb color] [-nf color] [-sb color] [-sf 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], "-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], "-x")) /* window x offset */ + dmx = atoi(argv[++i]); + else if (!strcmp(argv[i], "-y")) /* window y offset (from bottom up if -b) */ + dmy = atoi(argv[++i]); + else if (!strcmp(argv[i], "-w")) /* make dmenu this wide */ + dmw = 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 (!XSetLocaleModifiers("")) + fputs("warning: no locale modifiers 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..a4bde75023c97cd89caffd50c2a7d15b55ba3690 GIT binary patch literal 32424 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 T9uAcM>6JRu<03C^n)&|=VVx*S literal 0 HcmV?d00001 diff --git a/dmenu_path b/dmenu_path new file mode 100644 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 100644 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..8fd1ca4 --- /dev/null +++ b/drw.c @@ -0,0 +1,435 @@ +/* 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); + 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..3adc0540f73c532f91900cbeee169c40f77876fc GIT binary patch literal 10600 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 EAC$FZ=Kufz literal 0 HcmV?d00001 diff --git a/stest b/stest new file mode 100644 index 0000000000000000000000000000000000000000..a90d14419722f33434f8ad906e4c42be8eb6dfe5 GIT binary patch literal 17680 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 Ul+xUwxc#g5ni>w9fsGaa4@^_6PXGV_ 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..e69d354b1974021405a606d92f1aee6693b9a891 GIT binary patch literal 5280 zcmbW4Z){sv6~K?}v@uQbOV?5|%9y*o3|CuBT)~u<73C#y`#c<4=${ESo#WciNmR$q z?B^zfp=OHR$#vaLV*><;!p4URCNVUie3&|GZP$XBR0e`C1NFniW_eSKG_`1KGv{32 z8{gdeec?pU_nzN9=iGDeJ?~GBg`+#&E*A)K!B?QI2?}tvx7{CR-C@wc2YbxQ4pPnP zm#7KV%*v;z^A7#I;ubw)U!?s0ezW|M`S`m-7(Z!Ny7~h+YL<(700BByZQaYob5C)i~~Orp?ieImd5(f z4YPbrvtKc%p15jQTFl~u^W+%~39YN!75aeJ!URt#1DAq{PiQp8izO11W2I~~2T@mv?h zUe3jStNuN-G?yOJhd?`ZqU~j~g?Y8&(Kaje^Hu+z%EKNwM@>7J_DoItT@B};BX48- zkM*q>V;ftReV5p{?ogBM@+HP8QndUwBPQ+*)kD&}w^bnasuq9z0@ckSW8yqox22Ge zPg&dj{dxbvf%xG>(n?KDr{^BYJ8u)uc+QW1XT8`%}ZmtmolRcLfVJcaaKHLsiqY_ zLbNzEpq1Xo&WSy);u~7APV9q$!n(x;HxzozrSsnA>-E2obF&sHzZs^I^o&>T(~YGY zF5^TPiZ$)oSB>J|wBpauDzfcOtwc4Zox5H=@bp;q=;@^oT=Q{b@jEUkXycW%?j0|^ zS6IV#G19!)SlxU*vUI`wK!)G4W|buwEnhRW@O5Sudc!E!B8k@wcF1AO{h3#d@`fku zEoj9o8h4tl4PqLu&OSY!xG{RE>nt56CLL*Z>Gd!cHO^L@(7pAWSO%C!w~1PBV{;Vm z_P$2itd4az7l+mqJg?!3E05`3v;1)@X6rd(RvG&zXQY&B$1b;$c+{+Xne`&&iM`HYQukMVSN)y= zw{HlM;R~S2h_4UBvsgS#$A*r(3;x~gH0bI0CIRr^UnlWkq)y@iR-r3~K7pEBiqG~4 z8J-*CLTCGgjP}`u@h2%^l<8Ijw_{7(&-JHxdk5z=g>UBkGLIu??19zXdS&AQ%Jx?A zz?xfNEALy^dmpo0g>UDT65@Q%O3XA5U?C`SKk8Ils?gJJowWbLa};?e`)@k%xC75P z@RS3eb>MjizTm*i$ZwO*h20CtPC3{=>%f2Pz@KyAzi{Bca^Pnj_$v_ZRPD? z0@#ZYNP$#3J_Q+T93%1ncpLE&{BD9M#6OLf;Nrd_-Y0RYa}tVAV+Z;3 z;1Pl-g2y;N(Zb6-!TIwFpW^&wg&*PkqQbMB|CMvfh1MwYzp2>gxqT0BMV|9q-=Od( zxxR(B6#p|){I4taHlOdX!oSP)w8F)F__e}Bn_FaP+x+y^vHU8}mJixhF*NZ&vpu&H|`F1IeT!+JoeU00T_Z#K22`_Q( zNhuC}M+h$7EHq3$6Fkm?*e4+_-!G!Bh|?wFyn}rb5r^g=xz=iyxv1m+a!zx~_t71E ze&V0QOT@p2bBa&zAHnaH?C+HLG0C2M#Ql6+;(p0q)FaKiS>n%0@$ZuO3lgW;BA=Hf zu1ow667QGz-#Hii_5|18mH0Zz{zECwJreg(02FHI66@XM!22amYd+888k`f8>%@;b z2@P`-rhk$Bwz>Gunw0oj-e*lP|LCk8KMX;93AEa2(aTxsc+j>MYzQXfb{v9-@tqRP zWl16PP!L~F%SK4zIlC|i!9;d;*2S>G^kM9D+P~|c zY*~osEA1c6q3+*aWE67{FIi^!gOI)@WD)a=b^-@QU$$H1{xYM!YD?b(#MI*w{%6QZ z5|O_^Y3y`bKmEf=VzwHf^;1a+e^Ec3<|dZ_Kaey09B4afFI0c}&qb{rL~w=wOY>(^ WBvIE-&$3SblRW=#OTN@q{r?A>USEO$ 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..2a026a6be15ace8022555f1bfc7f2a310bd65fbc GIT binary patch literal 2264 zcmbVNJxo(k6h5UW6``%bfWe`|)6fK8S`#CwN(vNs>WhHXhz!=!zJ|vB<@ME|F%csU zP1A(I!NGyS#l*oxM-v(yTpgW+(F6w^Of;C(bKbj`-aKCy4!Ql#{myspJ@5YUcuJk_ zaJhh+3&x<;<1E07BP~B_x>4wb9#~MS`^u9aJ4h*2LmE}8aVe@)RY_5+F=iYhKV1fFNkTS)_Hmu9R&v*4mXtA zW$a+@n^Iqw_E=z0smCS1QjbZY&oRmGK^k!&#KM8^=)l@VP-^GNGe(r^HWlnK14<3O zwMXt|q|(0?z_`hVn}!N0A?m9BZ1+W z>o<48{C2H{W$0ylICy_V~S|7*6GcHGVDmGL2(+$FyxfMr(1sst!^hh2*V^!N$W#5iZgW48n{T6sijmF9DUj945Tvsj zl#R5e>yVgHXD7zh#O(C+!c;sFAD>XC5^z6L(z694gDO3z6(Ccp7%3=WLQw~8HEV!o z9Z#s>d}JNij$`}VwCpvJXtd+B)|`JwB${3S(V|Vg7k#e(D)2sm-xhe<7p~tGcv1gL z;AxM!eqZ4G1-^?8J{r|YXN&857*F3tx0&++^vOt1joT&WaGviM$mIulBe{%d=oYSU zPuFru+0a%Ekkd&c3G#Be46F(+Y02#L2y!&$Ve`Xm%yWpM@%eeZJjUne7{vhjAueFJ!~}HaMB{lJuV9e-qAu@5 zI%w}OPCSz|PKo8yJ0hC>dv0UUY5(|>&A*HGk8*iF-#?nhJ~vX(VDs-Vq_=~QL2;gc i41N3FQGAn;i_{T}4R^rMU3bb4v40gS2TIGa=l=tEz6a_6 literal 0 HcmV?d00001