From 17d6edb1fb478a504b5aabed8f628f528bd28f00 Mon Sep 17 00:00:00 2001 From: tomit4 Date: Tue, 3 Mar 2026 11:48:12 -0800 Subject: [PATCH] :wrench: Patched custom dmenu --- dmenu/LICENSE | 30 ++ dmenu/Makefile | 58 +++ dmenu/README | 24 ++ dmenu/arg.h | 49 +++ dmenu/config.def.h | 24 ++ dmenu/config.h | 24 ++ dmenu/config.mk | 32 ++ dmenu/dmenu | Bin 0 -> 42304 bytes dmenu/dmenu-instant-5.3.diff | 88 ++++ dmenu/dmenu.1 | 197 +++++++++ dmenu/dmenu.c | 805 +++++++++++++++++++++++++++++++++++ dmenu/dmenu.o | Bin 0 -> 32512 bytes dmenu/dmenu_path | 13 + dmenu/dmenu_run | 2 + dmenu/drw.c | 451 ++++++++++++++++++++ dmenu/drw.h | 58 +++ dmenu/drw.o | Bin 0 -> 10952 bytes dmenu/stest | Bin 0 -> 16440 bytes dmenu/stest.1 | 90 ++++ dmenu/stest.c | 109 +++++ dmenu/stest.o | Bin 0 -> 5360 bytes dmenu/util.c | 36 ++ dmenu/util.h | 9 + dmenu/util.o | Bin 0 -> 2288 bytes 24 files changed, 2099 insertions(+) create mode 100644 dmenu/LICENSE create mode 100644 dmenu/Makefile create mode 100644 dmenu/README create mode 100644 dmenu/arg.h create mode 100644 dmenu/config.def.h create mode 100644 dmenu/config.h create mode 100644 dmenu/config.mk create mode 100755 dmenu/dmenu create mode 100644 dmenu/dmenu-instant-5.3.diff create mode 100644 dmenu/dmenu.1 create mode 100644 dmenu/dmenu.c create mode 100644 dmenu/dmenu.o create mode 100755 dmenu/dmenu_path create mode 100755 dmenu/dmenu_run create mode 100644 dmenu/drw.c create mode 100644 dmenu/drw.h create mode 100644 dmenu/drw.o create mode 100755 dmenu/stest create mode 100644 dmenu/stest.1 create mode 100644 dmenu/stest.c create mode 100644 dmenu/stest.o create mode 100644 dmenu/util.c create mode 100644 dmenu/util.h create mode 100644 dmenu/util.o diff --git a/dmenu/LICENSE b/dmenu/LICENSE new file mode 100644 index 00000000..2a64b28b --- /dev/null +++ b/dmenu/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/dmenu/Makefile b/dmenu/Makefile new file mode 100644 index 00000000..458c5246 --- /dev/null +++ b/dmenu/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/dmenu/README b/dmenu/README new file mode 100644 index 00000000..a8fcdfe1 --- /dev/null +++ b/dmenu/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/dmenu/arg.h b/dmenu/arg.h new file mode 100644 index 00000000..e94e02bb --- /dev/null +++ b/dmenu/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/dmenu/config.def.h b/dmenu/config.def.h new file mode 100644 index 00000000..7e6f1ed7 --- /dev/null +++ b/dmenu/config.def.h @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int instant = 0; /* -n option; if 1, select single entry automatically */ +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/dmenu/config.h b/dmenu/config.h new file mode 100644 index 00000000..7e6f1ed7 --- /dev/null +++ b/dmenu/config.h @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int instant = 0; /* -n option; if 1, select single entry automatically */ +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/dmenu/config.mk b/dmenu/config.mk new file mode 100644 index 00000000..137f7c86 --- /dev/null +++ b/dmenu/config.mk @@ -0,0 +1,32 @@ +# dmenu version +VERSION = 5.3 + +# 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/dmenu/dmenu b/dmenu/dmenu new file mode 100755 index 0000000000000000000000000000000000000000..9c74cc2b8eaad6382a5b7ec02ad0245fb57c4657 GIT binary patch literal 42304 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%0D8Ckm>mv*%z&8Y3@ZV(T(pzRD~T)y$A$ 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 Ns7Q?070|K5{{l`z;(q`D literal 0 HcmV?d00001 diff --git a/dmenu/dmenu-instant-5.3.diff b/dmenu/dmenu-instant-5.3.diff new file mode 100644 index 00000000..0aefd081 --- /dev/null +++ b/dmenu/dmenu-instant-5.3.diff @@ -0,0 +1,88 @@ +From 26b302783a16dc2aa19451d9ba4515c8484f4a05 Mon Sep 17 00:00:00 2001 +From: Max Schillinger +Date: Tue, 27 Aug 2024 16:19:41 +0200 +Subject: [PATCH] Instant mode + +Add '-n' flag to select the only (remaining) entry automatically. +--- + config.def.h | 1 + + dmenu.1 | 5 ++++- + dmenu.c | 13 +++++++++++-- + 3 files changed, 16 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..7e6f1ed 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -1,6 +1,7 @@ + /* See LICENSE file for copyright and license details. */ + /* Default settings; can be overriden by command line. */ + ++static int instant = 0; /* -n option; if 1, select single entry automatically */ + 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[] = { +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..29bdf7f 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -3,7 +3,7 @@ + dmenu \- dynamic menu + .SH SYNOPSIS + .B dmenu +-.RB [ \-bfiv ] ++.RB [ \-bfinv ] + .RB [ \-l + .IR lines ] + .RB [ \-m +@@ -47,6 +47,9 @@ is faster, but will lock up X until stdin reaches end\-of\-file. + .B \-i + dmenu matches menu items case insensitively. + .TP ++.B \-n ++dmenu instantly selects if only one match. ++.TP + .BI \-l " lines" + dmenu lists items vertically, with the given number of lines. + .TP +diff --git a/dmenu.c b/dmenu.c +index 40f93e0..92d5154 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -277,6 +277,13 @@ match(void) + matchend = substrend; + } + curr = sel = matches; ++ ++ if (instant && matches && matches==matchend && !lsubstr) { ++ puts(matches->text); ++ cleanup(); ++ exit(0); ++ } ++ + calcoffsets(); + } + +@@ -715,7 +722,7 @@ setup(void) + static void + usage(void) + { +- die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" ++ die("usage: dmenu [-bfinv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); + } + +@@ -737,7 +744,9 @@ main(int argc, char *argv[]) + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; +- } else if (i + 1 == argc) ++ } else if (!strcmp(argv[i], "-n")) /* instant select only match */ ++ instant = 1; ++ else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ +-- +2.46.0 + diff --git a/dmenu/dmenu.1 b/dmenu/dmenu.1 new file mode 100644 index 00000000..29bdf7f8 --- /dev/null +++ b/dmenu/dmenu.1 @@ -0,0 +1,197 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfinv ] +.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 +.B \-n +dmenu instantly selects if only one match. +.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/dmenu/dmenu.c b/dmenu/dmenu.c new file mode 100644 index 00000000..92d51545 --- /dev/null +++ b/dmenu/dmenu.c @@ -0,0 +1,805 @@ +/* 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; + + if (instant && matches && matches==matchend && !lsubstr) { + puts(matches->text); + cleanup(); + exit(0); + } + + 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 [-bfinv] [-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 (!strcmp(argv[i], "-n")) /* instant select only match */ + instant = 1; + else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else + usage(); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} diff --git a/dmenu/dmenu.o b/dmenu/dmenu.o new file mode 100644 index 0000000000000000000000000000000000000000..77692a23f60fe5b27bfee9f261ac4c9122671da4 GIT binary patch literal 32512 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 mkw_DLuRAJTtl({Sngmg*E+I3b;7`u_nWJ>^UQ literal 0 HcmV?d00001 diff --git a/dmenu/dmenu_path b/dmenu/dmenu_path new file mode 100755 index 00000000..3a7cda79 --- /dev/null +++ b/dmenu/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/dmenu_run b/dmenu/dmenu_run new file mode 100755 index 00000000..834ede54 --- /dev/null +++ b/dmenu/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/dmenu/drw.c b/dmenu/drw.c new file mode 100644 index 00000000..78a2b272 --- /dev/null +++ b/dmenu/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/dmenu/drw.h b/dmenu/drw.h new file mode 100644 index 00000000..fd7631b2 --- /dev/null +++ b/dmenu/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/dmenu/drw.o b/dmenu/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..b6385eda7938e87141ffcecc2ea26376d07965cc GIT binary patch literal 10952 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 literal 0 HcmV?d00001 diff --git a/dmenu/stest.1 b/dmenu/stest.1 new file mode 100644 index 00000000..2667d8aa --- /dev/null +++ b/dmenu/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/dmenu/stest.c b/dmenu/stest.c new file mode 100644 index 00000000..e27d3a5e --- /dev/null +++ b/dmenu/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/dmenu/stest.o b/dmenu/stest.o new file mode 100644 index 0000000000000000000000000000000000000000..285b13f5140291a1a21079cbe75b32adef91b13e GIT binary patch literal 5360 zcmb7|Z){sv6~M2Zq&7|WUe=Z^(6YTDXJoW_#3`N9YTe?*X~R08VDxASU{+A)vc+}KEMzHgf#Y{VhgU;Y>2`NsyXMr zd*Ykd_yb3JzW4m@Ip^MY?s@Os8ssf95;&F+uC8oDEv6k4!|h<(IwJJ?C&zPEz10#>->*2sXjcRVV#_W}F{gt3m`g!0vhBrq+ z)qDVB<^nGP7VG~WhP@4Njm6vtAlTOEtvXDS^Y&B z=D_W)Ve2u>qDNwJb9`blG4s`Q#y&PXHwrMRjZY;^?4_uOK-cxr_3Rc8)|UB9 zTZa8Zz3WEkOQA#OM@Rk#=h)TZv*p~SdzUq3r=~CENWd^5( zo7|+b$eX1S%ws@qxi(mM9lWgM5S3BBY;RproU~epsr&{imyFV*n}*fe1?;k4e zW52}R{uM5MsKoc}Jc6OZzj*wwe0fh*Wo-5wERg>9|vn%*nZD!|v0&2t< z!{->HFkI)r=MOS?=E}$#;6lN^23Kv|!^iRNP&{n4zS+Ud^R+KB&s>XV%LbqSX)taD zzEzCr*oKsrkmBE`z~t?qc8jqAeCT29ac@Xz%{F{a`K`C1+pDxX=8TQNa9r3PL)*Pd z%TwN7rS(ihucDP2dzJPln?g$G?o+zK zE~K=BANcnwExn3gM837b`)y?JS8pA`Tv0Ywk2dh^t!5tq)hpTaTmXZL;GA@x2Vj^_ z55yT0Q%r)6@-euOYp8%c?KJEY&>}Ajo%lV1Ulwt2Ur8F+I_j>ppW;1Uy(w@dk<%=ZCb%l->>_~APIkve>|4v*F0nD4dLa~$|aDQ~0235YwDWHS&Ym4MSVMuZ^96}X6RK?s5zpHbo(^il9R!ulZoX@nri zZ-sy&_s#&#chzbn&NzzFo8!AKz^h|A5P$?%#CrZwQ^%OY?3PeBNdM ztkA#f;^&0^yo-Nd==`(->vQMf_b&S%3H==x|C!L2UHlcHH{rt9h`+TC-zGTS?@J=i z4j2E9;CrMvavk=$?90ObVaXoPQnW7<62C*@0bD!?4rASph&cNZf*_Zlmp3_eIF3>L zzeyb9{95QAxHz31ZVHb3B0ooWi}{h`S%czl6CCH1kzPj!vm)5_#4i8Hl=e;QA9TObm%XOlGj)KF&gzFFR-x{ZL51nt&cD=CSjh;C^ zZAFhT9XddD%5DargXq4#b#l{{`Xy zhRZ>tK%&L-BARr$IQirGO%@7AZ9fcR-<>~2EQroy6odRo?}5%*`CG<>fwYVJKo#e3 z=MUd=7_$>DS#nK|wnK){r}?QJk(g}yitv{i_Q{WU0y;jp+}pJv{D;X35aV`HbvHmkFo=(T@|36mvgGm4Y literal 0 HcmV?d00001 diff --git a/dmenu/util.c b/dmenu/util.c new file mode 100644 index 00000000..96b82c98 --- /dev/null +++ b/dmenu/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/dmenu/util.h b/dmenu/util.h new file mode 100644 index 00000000..c0a50d44 --- /dev/null +++ b/dmenu/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/dmenu/util.o b/dmenu/util.o new file mode 100644 index 0000000000000000000000000000000000000000..3b3f2b7cce5bcc62950c033fd8306ddf246bae37 GIT binary patch literal 2288 zcmbuA&ui326u{p%yXtC^-L;eu(3bCE)GX+i-PPETXxWgBnU+Pg{-Qm&T{qt}A)DQ= z-<@rv6-;RqmJraJhaP(9v4tLT@E~TzL;eDxh#=ksi$uhORfzA+cg74~cA@Bl`Mx)w z_ukCQ%$v>C@xrk#%K|MHd(8|K(ovk?>a{3~Nl%`|_;Q}vYk3w;E0TPQ z2Joc=q!va%?BUJFk}X_`Yz#O0c=P$HjXudT8&W;%CAve?RoX>5=np?BEq<-l=;6z< zCJDZ~UoKTDwem2)#Ms!dJuu-`MXhFM2A!;vDSFv-_Ha6zIb?f-DFtoKaCs?F{3dbG zu}>U716=?uVi6o9WZO74{g(7oeIynH^XcB@j@EmfgQ`aZY2-#?dcxZ^3icf;`RcT)qQP@UB4IzFj5 z#naH-Mw^7u*TJWGpe{<~SvCKm+HgF-hBqcIVJa=?P8X;A(wwU+`#5c5IvoYw zttmM*=~L&VeW5ArLI<5+P6sG`Tm7)aR6;j$GWAQR_eHW72XXy7M2zHYc%uNzSY=AJ zAHTHbhvsK6m$u5{Fh%dL>(xG8mex6RkWK#-OgXOt4w?{cou~Cz1~Wr%>HrhAiTxHC zy)&~{Ddm>RKS2{i!hg?2^igOy|LOnCuV8Mw`)?6M(dR<