├── FUNDING.yml ├── LICENSE ├── Makefile ├── README.md ├── arg.h ├── config.h ├── config.mk ├── dmenu.1 ├── dmenu.c ├── dmenu_path ├── dmenu_run ├── drw.c ├── drw.h ├── stest.1 ├── stest.c ├── util.c └── util.h /FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://lukesmith.xyz/donate.html"] 2 | github: lukesmithxyz 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2006-2019 Anselm R Garbe 4 | © 2006-2008 Sander van Dijk 5 | © 2006-2007 Michał Janeczek 6 | © 2007 Kris Maglione 7 | © 2009 Gottox 8 | © 2009 Markus Schnalke 9 | © 2009 Evan Gates 10 | © 2010-2012 Connor Lane Smith 11 | © 2014-2020 Hiltjo Posthuma 12 | © 2015-2019 Quentin Rameau 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a 15 | copy of this software and associated documentation files (the "Software"), 16 | to deal in the Software without restriction, including without limitation 17 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 18 | and/or sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 27 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 30 | DEALINGS IN THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # dmenu - dynamic menu 2 | # See LICENSE file for copyright and license details. 3 | 4 | include config.mk 5 | 6 | SRC = drw.c dmenu.c stest.c util.c 7 | OBJ = $(SRC:.c=.o) 8 | 9 | all: options dmenu stest 10 | 11 | options: 12 | @echo dmenu build options: 13 | @echo "CFLAGS = $(CFLAGS)" 14 | @echo "LDFLAGS = $(LDFLAGS)" 15 | @echo "CC = $(CC)" 16 | 17 | .c.o: 18 | $(CC) -c $(CFLAGS) $< 19 | 20 | config.h: 21 | cp config.def.h $@ 22 | 23 | $(OBJ): arg.h config.h config.mk drw.h 24 | 25 | dmenu: dmenu.o drw.o util.o 26 | $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) 27 | 28 | stest: stest.o 29 | $(CC) -o $@ stest.o $(LDFLAGS) 30 | 31 | clean: 32 | rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz *.rej *.orig 33 | 34 | dist: clean 35 | mkdir -p dmenu-$(VERSION) 36 | cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ 37 | drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ 38 | dmenu-$(VERSION) 39 | tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) 40 | gzip dmenu-$(VERSION).tar 41 | rm -rf dmenu-$(VERSION) 42 | 43 | install: all 44 | mkdir -p $(DESTDIR)$(PREFIX)/bin 45 | cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin 46 | chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu 47 | chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path 48 | chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run 49 | chmod 755 $(DESTDIR)$(PREFIX)/bin/stest 50 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1 51 | sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 52 | sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 53 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 54 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 55 | 56 | uninstall: 57 | rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ 58 | $(DESTDIR)$(PREFIX)/bin/dmenu_path\ 59 | $(DESTDIR)$(PREFIX)/bin/dmenu_run\ 60 | $(DESTDIR)$(PREFIX)/bin/stest\ 61 | $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ 62 | $(DESTDIR)$(MANPREFIX)/man1/stest.1 63 | 64 | .PHONY: all options clean dist install uninstall 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Luke's dmenu 2 | 3 | Extra stuff added to vanilla dmenu: 4 | 5 | - reads Xresources (ergo pywal compatible) 6 | - alpha patch, which importantly allows this build to be embedded in transparent st 7 | - can view color characters like emoji 8 | - `-P` for password mode: hide user input 9 | - `-r` to reject non-matching input 10 | - dmenu options are mouse clickable 11 | 12 | ## Installation 13 | 14 | After making any config changes that you want, but `make`, `sudo make install` it. 15 | -------------------------------------------------------------------------------- /arg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copy me if you can. 3 | * by 20h 4 | */ 5 | 6 | #ifndef ARG_H__ 7 | #define ARG_H__ 8 | 9 | extern char *argv0; 10 | 11 | /* use main(int argc, char *argv[]) */ 12 | #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ 13 | argv[0] && argv[0][0] == '-'\ 14 | && argv[0][1];\ 15 | argc--, argv++) {\ 16 | char argc_;\ 17 | char **argv_;\ 18 | int brk_;\ 19 | if (argv[0][1] == '-' && argv[0][2] == '\0') {\ 20 | argv++;\ 21 | argc--;\ 22 | break;\ 23 | }\ 24 | for (brk_ = 0, argv[0]++, argv_ = argv;\ 25 | argv[0][0] && !brk_;\ 26 | argv[0]++) {\ 27 | if (argv_ != argv)\ 28 | break;\ 29 | argc_ = argv[0][0];\ 30 | switch (argc_) 31 | 32 | #define ARGEND }\ 33 | } 34 | 35 | #define ARGC() argc_ 36 | 37 | #define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ 38 | ((x), abort(), (char *)0) :\ 39 | (brk_ = 1, (argv[0][1] != '\0')?\ 40 | (&argv[0][1]) :\ 41 | (argc--, argv++, argv[0]))) 42 | 43 | #define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ 44 | (char *)0 :\ 45 | (brk_ = 1, (argv[0][1] != '\0')?\ 46 | (&argv[0][1]) :\ 47 | (argc--, argv++, argv[0]))) 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | /* Default settings; can be overriden by command line. */ 3 | 4 | static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ 5 | /* -fn option overrides fonts[0]; default X11 font or font set */ 6 | static const char *fonts[] = { 7 | "monospace:size=10", 8 | "NotoColorEmoji:pixelsize=8:antialias=true:autohint=true" 9 | }; 10 | static const unsigned int bgalpha = 0xe0; 11 | static const unsigned int fgalpha = OPAQUE; 12 | static const char *prompt = NULL; /* -p option; prompt to the left of input field */ 13 | static const char *colors[SchemeLast][2] = { 14 | /* fg bg */ 15 | [SchemeNorm] = { "#bbbbbb", "#222222" }, 16 | [SchemeSel] = { "#eeeeee", "#005577" }, 17 | [SchemeOut] = { "#000000", "#00ffff" }, 18 | }; 19 | static const unsigned int alphas[SchemeLast][2] = { 20 | /* fgalpha bgalphga */ 21 | [SchemeNorm] = { fgalpha, bgalpha }, 22 | [SchemeSel] = { fgalpha, bgalpha }, 23 | [SchemeOut] = { fgalpha, bgalpha }, 24 | }; 25 | 26 | /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ 27 | static unsigned int lines = 0; 28 | 29 | /* 30 | * Characters not considered part of a word while deleting words 31 | * for example: " /?\"&[]" 32 | */ 33 | static const char worddelimiters[] = " "; 34 | 35 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # dmenu version 2 | VERSION = 5.0 3 | 4 | # paths 5 | PREFIX = /usr/local 6 | MANPREFIX = $(PREFIX)/share/man 7 | 8 | X11INC = /usr/X11R6/include 9 | X11LIB = /usr/X11R6/lib 10 | 11 | # Xinerama, comment if you don't want it 12 | XINERAMALIBS = -lXinerama 13 | XINERAMAFLAGS = -DXINERAMA 14 | 15 | # freetype 16 | FREETYPELIBS = -lfontconfig -lXft 17 | FREETYPEINC = /usr/include/freetype2 18 | # OpenBSD (uncomment) 19 | #FREETYPEINC = $(X11INC)/freetype2 20 | 21 | # includes and libs 22 | INCS = -I$(X11INC) -I$(FREETYPEINC) 23 | LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender 24 | 25 | # flags 26 | CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) 27 | CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) 28 | LDFLAGS = $(LIBS) 29 | 30 | # compiler and linker 31 | CC = cc 32 | -------------------------------------------------------------------------------- /dmenu.1: -------------------------------------------------------------------------------- 1 | .TH DMENU 1 dmenu\-VERSION 2 | .SH NAME 3 | dmenu \- dynamic menu 4 | .SH SYNOPSIS 5 | .B dmenu 6 | .RB [ \-bfirvP ] 7 | .RB [ \-l 8 | .IR lines ] 9 | .RB [ \-m 10 | .IR monitor ] 11 | .RB [ \-p 12 | .IR prompt ] 13 | .RB [ \-fn 14 | .IR font ] 15 | .RB [ \-nb 16 | .IR color ] 17 | .RB [ \-nf 18 | .IR color ] 19 | .RB [ \-sb 20 | .IR color ] 21 | .RB [ \-sf 22 | .IR color ] 23 | .RB [ \-w 24 | .IR windowid ] 25 | .P 26 | .BR dmenu_run " ..." 27 | .SH DESCRIPTION 28 | .B dmenu 29 | is a dynamic menu for X, which reads a list of newline\-separated items from 30 | stdin. When the user selects an item and presses Return, their choice is printed 31 | to stdout and dmenu terminates. Entering text will narrow the items to those 32 | matching the tokens in the input. 33 | .P 34 | .B dmenu_run 35 | is a script used by 36 | .IR dwm (1) 37 | which lists programs in the user's $PATH and runs the result in their $SHELL. 38 | .SH OPTIONS 39 | .TP 40 | .B \-b 41 | dmenu appears at the bottom of the screen. 42 | .TP 43 | .B \-f 44 | dmenu grabs the keyboard before reading stdin if not reading from a tty. This 45 | is faster, but will lock up X until stdin reaches end\-of\-file. 46 | .TP 47 | .B \-i 48 | dmenu matches menu items case insensitively. 49 | .TP 50 | .B \-P 51 | dmenu will not directly display the keyboard input, but instead replace it with dots. All data from stdin will be ignored. 52 | .TP 53 | .B \-r 54 | dmenu will reject any input which would result in no matching option left. 55 | .TP 56 | .BI \-l " lines" 57 | dmenu lists items vertically, with the given number of lines. 58 | .TP 59 | .BI \-m " monitor" 60 | dmenu is displayed on the monitor number supplied. Monitor numbers are starting 61 | from 0. 62 | .TP 63 | .BI \-p " prompt" 64 | defines the prompt to be displayed to the left of the input field. 65 | .TP 66 | .BI \-fn " font" 67 | defines the font or font set used. 68 | .TP 69 | .BI \-nb " color" 70 | defines the normal background color. 71 | .IR #RGB , 72 | .IR #RRGGBB , 73 | and X color names are supported. 74 | .TP 75 | .BI \-nf " color" 76 | defines the normal foreground color. 77 | .TP 78 | .BI \-sb " color" 79 | defines the selected background color. 80 | .TP 81 | .BI \-sf " color" 82 | defines the selected foreground color. 83 | .TP 84 | .B \-v 85 | prints version information to stdout, then exits. 86 | .TP 87 | .BI \-w " windowid" 88 | embed into windowid. 89 | .SH USAGE 90 | dmenu is completely controlled by the keyboard. Items are selected using the 91 | arrow keys, page up, page down, home, and end. 92 | .TP 93 | .B Tab 94 | Copy the selected item to the input field. 95 | .TP 96 | .B Return 97 | Confirm selection. Prints the selected item to stdout and exits, returning 98 | success. 99 | .TP 100 | .B Ctrl-Return 101 | Confirm selection. Prints the selected item to stdout and continues. 102 | .TP 103 | .B Shift\-Return 104 | Confirm input. Prints the input text to stdout and exits, returning success. 105 | .TP 106 | .B Escape 107 | Exit without selecting an item, returning failure. 108 | .TP 109 | .B Ctrl-Left 110 | Move cursor to the start of the current word 111 | .TP 112 | .B Ctrl-Right 113 | Move cursor to the end of the current word 114 | .TP 115 | .B C\-a 116 | Home 117 | .TP 118 | .B C\-b 119 | Left 120 | .TP 121 | .B C\-c 122 | Escape 123 | .TP 124 | .B C\-d 125 | Delete 126 | .TP 127 | .B C\-e 128 | End 129 | .TP 130 | .B C\-f 131 | Right 132 | .TP 133 | .B C\-g 134 | Escape 135 | .TP 136 | .B C\-h 137 | Backspace 138 | .TP 139 | .B C\-i 140 | Tab 141 | .TP 142 | .B C\-j 143 | Return 144 | .TP 145 | .B C\-J 146 | Shift-Return 147 | .TP 148 | .B C\-k 149 | Delete line right 150 | .TP 151 | .B C\-m 152 | Return 153 | .TP 154 | .B C\-M 155 | Shift-Return 156 | .TP 157 | .B C\-n 158 | Down 159 | .TP 160 | .B C\-p 161 | Up 162 | .TP 163 | .B C\-u 164 | Delete line left 165 | .TP 166 | .B C\-w 167 | Delete word left 168 | .TP 169 | .B C\-y 170 | Paste from primary X selection 171 | .TP 172 | .B C\-Y 173 | Paste from X clipboard 174 | .TP 175 | .B M\-b 176 | Move cursor to the start of the current word 177 | .TP 178 | .B M\-f 179 | Move cursor to the end of the current word 180 | .TP 181 | .B M\-g 182 | Home 183 | .TP 184 | .B M\-G 185 | End 186 | .TP 187 | .B M\-h 188 | Up 189 | .TP 190 | .B M\-j 191 | Page down 192 | .TP 193 | .B M\-k 194 | Page up 195 | .TP 196 | .B M\-l 197 | Down 198 | .SH SEE ALSO 199 | .IR dwm (1), 200 | .IR stest (1) 201 | -------------------------------------------------------------------------------- /dmenu.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #ifdef XINERAMA 15 | #include 16 | #endif 17 | #include 18 | #include 19 | 20 | #include "drw.h" 21 | #include "util.h" 22 | 23 | /* macros */ 24 | #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ 25 | && MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) 26 | #define LENGTH(X) (sizeof X / sizeof X[0]) 27 | #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) 28 | 29 | /* define opaqueness */ 30 | #define OPAQUE 0xFFU 31 | 32 | /* enums */ 33 | enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ 34 | 35 | struct item { 36 | char *text; 37 | struct item *left, *right; 38 | int out; 39 | }; 40 | 41 | static char text[BUFSIZ] = ""; 42 | static char *embed; 43 | static int bh, mw, mh; 44 | static int inputw = 0, promptw, passwd = 0; 45 | static int lrpad; /* sum of left and right padding */ 46 | static int reject_no_match = 0; 47 | static size_t cursor; 48 | static struct item *items = NULL; 49 | static struct item *matches, *matchend; 50 | static struct item *prev, *curr, *next, *sel; 51 | static int mon = -1, screen; 52 | 53 | static Atom clip, utf8; 54 | static Display *dpy; 55 | static Window root, parentwin, win; 56 | static XIC xic; 57 | 58 | static Drw *drw; 59 | static int usergb = 0; 60 | static Visual *visual; 61 | static int depth; 62 | static Colormap cmap; 63 | static Clr *scheme[SchemeLast]; 64 | 65 | #include "config.h" 66 | 67 | static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; 68 | static char *(*fstrstr)(const char *, const char *) = strstr; 69 | 70 | 71 | static void 72 | xinitvisual() 73 | { 74 | XVisualInfo *infos; 75 | XRenderPictFormat *fmt; 76 | int nitems; 77 | int i; 78 | 79 | XVisualInfo tpl = { 80 | .screen = screen, 81 | .depth = 32, 82 | .class = TrueColor 83 | }; 84 | 85 | long masks = VisualScreenMask | VisualDepthMask | VisualClassMask; 86 | 87 | infos = XGetVisualInfo(dpy, masks, &tpl, &nitems); 88 | visual = NULL; 89 | 90 | for (i = 0; i < nitems; i++){ 91 | fmt = XRenderFindVisualFormat(dpy, infos[i].visual); 92 | if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { 93 | visual = infos[i].visual; 94 | depth = infos[i].depth; 95 | cmap = XCreateColormap(dpy, root, visual, AllocNone); 96 | usergb = 1; 97 | break; 98 | } 99 | } 100 | 101 | XFree(infos); 102 | 103 | if (! visual) { 104 | visual = DefaultVisual(dpy, screen); 105 | depth = DefaultDepth(dpy, screen); 106 | cmap = DefaultColormap(dpy, screen); 107 | } 108 | } 109 | 110 | static void 111 | appenditem(struct item *item, struct item **list, struct item **last) 112 | { 113 | if (*last) 114 | (*last)->right = item; 115 | else 116 | *list = item; 117 | 118 | item->left = *last; 119 | item->right = NULL; 120 | *last = item; 121 | } 122 | 123 | static void 124 | calcoffsets(void) 125 | { 126 | int i, n; 127 | 128 | if (lines > 0) 129 | n = lines * bh; 130 | else 131 | n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); 132 | /* calculate which items will begin the next page and previous page */ 133 | for (i = 0, next = curr; next; next = next->right) 134 | if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) 135 | break; 136 | for (i = 0, prev = curr; prev && prev->left; prev = prev->left) 137 | if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) 138 | break; 139 | } 140 | 141 | static void 142 | cleanup(void) 143 | { 144 | size_t i; 145 | 146 | XUngrabKey(dpy, AnyKey, AnyModifier, root); 147 | for (i = 0; i < SchemeLast; i++) 148 | free(scheme[i]); 149 | drw_free(drw); 150 | XSync(dpy, False); 151 | XCloseDisplay(dpy); 152 | } 153 | 154 | static char * 155 | cistrstr(const char *s, const char *sub) 156 | { 157 | size_t len; 158 | 159 | for (len = strlen(sub); *s; s++) 160 | if (!strncasecmp(s, sub, len)) 161 | return (char *)s; 162 | return NULL; 163 | } 164 | 165 | static int 166 | drawitem(struct item *item, int x, int y, int w) 167 | { 168 | if (item == sel) 169 | drw_setscheme(drw, scheme[SchemeSel]); 170 | else if (item->out) 171 | drw_setscheme(drw, scheme[SchemeOut]); 172 | else 173 | drw_setscheme(drw, scheme[SchemeNorm]); 174 | 175 | return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); 176 | } 177 | 178 | static void 179 | drawmenu(void) 180 | { 181 | unsigned int curpos; 182 | struct item *item; 183 | int x = 0, y = 0, w; 184 | char *censort; 185 | 186 | drw_setscheme(drw, scheme[SchemeNorm]); 187 | drw_rect(drw, 0, 0, mw, mh, 1, 1); 188 | 189 | if (prompt && *prompt) { 190 | drw_setscheme(drw, scheme[SchemeSel]); 191 | x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); 192 | } 193 | /* draw input field */ 194 | w = (lines > 0 || !matches) ? mw - x : inputw; 195 | drw_setscheme(drw, scheme[SchemeNorm]); 196 | if (passwd) { 197 | censort = ecalloc(1, sizeof(text)); 198 | memset(censort, '.', strlen(text)); 199 | drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0); 200 | free(censort); 201 | } else drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); 202 | 203 | curpos = TEXTW(text) - TEXTW(&text[cursor]); 204 | if ((curpos += lrpad / 2 - 1) < w) { 205 | drw_setscheme(drw, scheme[SchemeNorm]); 206 | drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); 207 | } 208 | 209 | if (lines > 0) { 210 | /* draw vertical list */ 211 | for (item = curr; item != next; item = item->right) 212 | drawitem(item, x, y += bh, mw - x); 213 | } else if (matches) { 214 | /* draw horizontal list */ 215 | x += inputw; 216 | w = TEXTW("<"); 217 | if (curr->left) { 218 | drw_setscheme(drw, scheme[SchemeNorm]); 219 | drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); 220 | } 221 | x += w; 222 | for (item = curr; item != next; item = item->right) 223 | x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">"))); 224 | if (next) { 225 | w = TEXTW(">"); 226 | drw_setscheme(drw, scheme[SchemeNorm]); 227 | drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); 228 | } 229 | } 230 | drw_map(drw, win, 0, 0, mw, mh); 231 | } 232 | 233 | static void 234 | grabfocus(void) 235 | { 236 | struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; 237 | Window focuswin; 238 | int i, revertwin; 239 | 240 | for (i = 0; i < 100; ++i) { 241 | XGetInputFocus(dpy, &focuswin, &revertwin); 242 | if (focuswin == win) 243 | return; 244 | XSetInputFocus(dpy, win, RevertToParent, CurrentTime); 245 | nanosleep(&ts, NULL); 246 | } 247 | die("cannot grab focus"); 248 | } 249 | 250 | static void 251 | grabkeyboard(void) 252 | { 253 | struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; 254 | int i; 255 | 256 | if (embed) 257 | return; 258 | /* try to grab keyboard, we may have to wait for another process to ungrab */ 259 | for (i = 0; i < 1000; i++) { 260 | if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, 261 | GrabModeAsync, CurrentTime) == GrabSuccess) 262 | return; 263 | nanosleep(&ts, NULL); 264 | } 265 | die("cannot grab keyboard"); 266 | } 267 | 268 | static void 269 | match(void) 270 | { 271 | static char **tokv = NULL; 272 | static int tokn = 0; 273 | 274 | char buf[sizeof text], *s; 275 | int i, tokc = 0; 276 | size_t len, textsize; 277 | struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; 278 | 279 | strcpy(buf, text); 280 | /* separate input text into tokens to be matched individually */ 281 | for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) 282 | if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) 283 | die("cannot realloc %u bytes:", tokn * sizeof *tokv); 284 | len = tokc ? strlen(tokv[0]) : 0; 285 | 286 | matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; 287 | textsize = strlen(text) + 1; 288 | for (item = items; item && item->text; item++) { 289 | for (i = 0; i < tokc; i++) 290 | if (!fstrstr(item->text, tokv[i])) 291 | break; 292 | if (i != tokc) /* not all tokens match */ 293 | continue; 294 | /* exact matches go first, then prefixes, then substrings */ 295 | if (!tokc || !fstrncmp(text, item->text, textsize)) 296 | appenditem(item, &matches, &matchend); 297 | else if (!fstrncmp(tokv[0], item->text, len)) 298 | appenditem(item, &lprefix, &prefixend); 299 | else 300 | appenditem(item, &lsubstr, &substrend); 301 | } 302 | if (lprefix) { 303 | if (matches) { 304 | matchend->right = lprefix; 305 | lprefix->left = matchend; 306 | } else 307 | matches = lprefix; 308 | matchend = prefixend; 309 | } 310 | if (lsubstr) { 311 | if (matches) { 312 | matchend->right = lsubstr; 313 | lsubstr->left = matchend; 314 | } else 315 | matches = lsubstr; 316 | matchend = substrend; 317 | } 318 | curr = sel = matches; 319 | calcoffsets(); 320 | } 321 | 322 | static void 323 | insert(const char *str, ssize_t n) 324 | { 325 | if (strlen(text) + n > sizeof text - 1) 326 | return; 327 | 328 | static char last[BUFSIZ] = ""; 329 | if(reject_no_match) { 330 | /* store last text value in case we need to revert it */ 331 | memcpy(last, text, BUFSIZ); 332 | } 333 | 334 | /* move existing text out of the way, insert new text, and update cursor */ 335 | memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); 336 | if (n > 0) 337 | memcpy(&text[cursor], str, n); 338 | cursor += n; 339 | match(); 340 | 341 | if(!matches && reject_no_match) { 342 | /* revert to last text value if theres no match */ 343 | memcpy(text, last, BUFSIZ); 344 | cursor -= n; 345 | match(); 346 | } 347 | } 348 | 349 | static size_t 350 | nextrune(int inc) 351 | { 352 | ssize_t n; 353 | 354 | /* return location of next utf8 rune in the given direction (+1 or -1) */ 355 | for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) 356 | ; 357 | return n; 358 | } 359 | 360 | static void 361 | movewordedge(int dir) 362 | { 363 | if (dir < 0) { /* move cursor to the start of the word*/ 364 | while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) 365 | cursor = nextrune(-1); 366 | while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) 367 | cursor = nextrune(-1); 368 | } else { /* move cursor to the end of the word */ 369 | while (text[cursor] && strchr(worddelimiters, text[cursor])) 370 | cursor = nextrune(+1); 371 | while (text[cursor] && !strchr(worddelimiters, text[cursor])) 372 | cursor = nextrune(+1); 373 | } 374 | } 375 | 376 | static void 377 | keypress(XKeyEvent *ev) 378 | { 379 | char buf[32]; 380 | int len; 381 | KeySym ksym; 382 | Status status; 383 | 384 | len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); 385 | switch (status) { 386 | default: /* XLookupNone, XBufferOverflow */ 387 | return; 388 | case XLookupChars: 389 | goto insert; 390 | case XLookupKeySym: 391 | case XLookupBoth: 392 | break; 393 | } 394 | 395 | if (ev->state & ControlMask) { 396 | switch(ksym) { 397 | case XK_a: ksym = XK_Home; break; 398 | case XK_b: ksym = XK_Left; break; 399 | case XK_c: ksym = XK_Escape; break; 400 | case XK_d: ksym = XK_Delete; break; 401 | case XK_e: ksym = XK_End; break; 402 | case XK_f: ksym = XK_Right; break; 403 | case XK_g: ksym = XK_Escape; break; 404 | case XK_h: ksym = XK_BackSpace; break; 405 | case XK_i: ksym = XK_Tab; break; 406 | case XK_j: /* fallthrough */ 407 | case XK_J: /* fallthrough */ 408 | case XK_m: /* fallthrough */ 409 | case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; 410 | case XK_n: ksym = XK_Down; break; 411 | case XK_p: ksym = XK_Up; break; 412 | 413 | case XK_k: /* delete right */ 414 | text[cursor] = '\0'; 415 | match(); 416 | break; 417 | case XK_u: /* delete left */ 418 | insert(NULL, 0 - cursor); 419 | break; 420 | case XK_w: /* delete word */ 421 | while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) 422 | insert(NULL, nextrune(-1) - cursor); 423 | while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) 424 | insert(NULL, nextrune(-1) - cursor); 425 | break; 426 | case XK_y: /* paste selection */ 427 | case XK_Y: 428 | XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 429 | utf8, utf8, win, CurrentTime); 430 | return; 431 | case XK_Left: 432 | movewordedge(-1); 433 | goto draw; 434 | case XK_Right: 435 | movewordedge(+1); 436 | goto draw; 437 | case XK_Return: 438 | case XK_KP_Enter: 439 | break; 440 | case XK_bracketleft: 441 | cleanup(); 442 | exit(1); 443 | default: 444 | return; 445 | } 446 | } else if (ev->state & Mod1Mask) { 447 | switch(ksym) { 448 | case XK_b: 449 | movewordedge(-1); 450 | goto draw; 451 | case XK_f: 452 | movewordedge(+1); 453 | goto draw; 454 | case XK_g: ksym = XK_Home; break; 455 | case XK_G: ksym = XK_End; break; 456 | case XK_h: ksym = XK_Up; break; 457 | case XK_j: ksym = XK_Next; break; 458 | case XK_k: ksym = XK_Prior; break; 459 | case XK_l: ksym = XK_Down; break; 460 | default: 461 | return; 462 | } 463 | } 464 | 465 | switch(ksym) { 466 | default: 467 | insert: 468 | if (!iscntrl(*buf)) 469 | insert(buf, len); 470 | break; 471 | case XK_Delete: 472 | if (text[cursor] == '\0') 473 | return; 474 | cursor = nextrune(+1); 475 | /* fallthrough */ 476 | case XK_BackSpace: 477 | if (cursor == 0) 478 | return; 479 | insert(NULL, nextrune(-1) - cursor); 480 | break; 481 | case XK_End: 482 | if (text[cursor] != '\0') { 483 | cursor = strlen(text); 484 | break; 485 | } 486 | if (next) { 487 | /* jump to end of list and position items in reverse */ 488 | curr = matchend; 489 | calcoffsets(); 490 | curr = prev; 491 | calcoffsets(); 492 | while (next && (curr = curr->right)) 493 | calcoffsets(); 494 | } 495 | sel = matchend; 496 | break; 497 | case XK_Escape: 498 | cleanup(); 499 | exit(1); 500 | case XK_Home: 501 | if (sel == matches) { 502 | cursor = 0; 503 | break; 504 | } 505 | sel = curr = matches; 506 | calcoffsets(); 507 | break; 508 | case XK_Left: 509 | if (cursor > 0 && (!sel || !sel->left || lines > 0)) { 510 | cursor = nextrune(-1); 511 | break; 512 | } 513 | if (lines > 0) 514 | return; 515 | /* fallthrough */ 516 | case XK_Up: 517 | if (sel && sel->left && (sel = sel->left)->right == curr) { 518 | curr = prev; 519 | calcoffsets(); 520 | } 521 | break; 522 | case XK_Next: 523 | if (!next) 524 | return; 525 | sel = curr = next; 526 | calcoffsets(); 527 | break; 528 | case XK_Prior: 529 | if (!prev) 530 | return; 531 | sel = curr = prev; 532 | calcoffsets(); 533 | break; 534 | case XK_Return: 535 | case XK_KP_Enter: 536 | puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 537 | if (!(ev->state & ControlMask)) { 538 | cleanup(); 539 | exit(0); 540 | } 541 | if (sel) 542 | sel->out = 1; 543 | break; 544 | case XK_Right: 545 | if (text[cursor] != '\0') { 546 | cursor = nextrune(+1); 547 | break; 548 | } 549 | if (lines > 0) 550 | return; 551 | /* fallthrough */ 552 | case XK_Down: 553 | if (sel && sel->right && (sel = sel->right) == next) { 554 | curr = next; 555 | calcoffsets(); 556 | } 557 | break; 558 | case XK_Tab: 559 | if (!sel) 560 | return; 561 | strncpy(text, sel->text, sizeof text - 1); 562 | text[sizeof text - 1] = '\0'; 563 | cursor = strlen(text); 564 | match(); 565 | break; 566 | } 567 | 568 | draw: 569 | drawmenu(); 570 | } 571 | 572 | static void 573 | buttonpress(XEvent *e) 574 | { 575 | struct item *item; 576 | XButtonPressedEvent *ev = &e->xbutton; 577 | int x = 0, y = 0, h = bh, w; 578 | 579 | if (ev->window != win) 580 | return; 581 | 582 | /* right-click: exit */ 583 | if (ev->button == Button3) 584 | exit(1); 585 | 586 | if (prompt && *prompt) 587 | x += promptw; 588 | 589 | /* input field */ 590 | w = (lines > 0 || !matches) ? mw - x : inputw; 591 | 592 | /* left-click on input: clear input, 593 | * NOTE: if there is no left-arrow the space for < is reserved so 594 | * add that to the input width */ 595 | if (ev->button == Button1 && 596 | ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + 597 | ((!prev || !curr->left) ? TEXTW("<") : 0)) || 598 | (lines > 0 && ev->y >= y && ev->y <= y + h))) { 599 | insert(NULL, -cursor); 600 | drawmenu(); 601 | return; 602 | } 603 | /* middle-mouse click: paste selection */ 604 | if (ev->button == Button2) { 605 | XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 606 | utf8, utf8, win, CurrentTime); 607 | drawmenu(); 608 | return; 609 | } 610 | /* scroll up */ 611 | if (ev->button == Button4 && prev) { 612 | sel = curr = prev; 613 | calcoffsets(); 614 | drawmenu(); 615 | return; 616 | } 617 | /* scroll down */ 618 | if (ev->button == Button5 && next) { 619 | sel = curr = next; 620 | calcoffsets(); 621 | drawmenu(); 622 | return; 623 | } 624 | if (ev->button != Button1) 625 | return; 626 | if (ev->state & ~ControlMask) 627 | return; 628 | if (lines > 0) { 629 | /* vertical list: (ctrl)left-click on item */ 630 | w = mw - x; 631 | for (item = curr; item != next; item = item->right) { 632 | y += h; 633 | if (ev->y >= y && ev->y <= (y + h)) { 634 | puts(item->text); 635 | if (!(ev->state & ControlMask)) 636 | exit(0); 637 | sel = item; 638 | if (sel) { 639 | sel->out = 1; 640 | drawmenu(); 641 | } 642 | return; 643 | } 644 | } 645 | } else if (matches) { 646 | /* left-click on left arrow */ 647 | x += inputw; 648 | w = TEXTW("<"); 649 | if (prev && curr->left) { 650 | if (ev->x >= x && ev->x <= x + w) { 651 | sel = curr = prev; 652 | calcoffsets(); 653 | drawmenu(); 654 | return; 655 | } 656 | } 657 | /* horizontal list: (ctrl)left-click on item */ 658 | for (item = curr; item != next; item = item->right) { 659 | x += w; 660 | w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); 661 | if (ev->x >= x && ev->x <= x + w) { 662 | puts(item->text); 663 | if (!(ev->state & ControlMask)) 664 | exit(0); 665 | sel = item; 666 | if (sel) { 667 | sel->out = 1; 668 | drawmenu(); 669 | } 670 | return; 671 | } 672 | } 673 | /* left-click on right arrow */ 674 | w = TEXTW(">"); 675 | x = mw - w; 676 | if (next && ev->x >= x && ev->x <= x + w) { 677 | sel = curr = next; 678 | calcoffsets(); 679 | drawmenu(); 680 | return; 681 | } 682 | } 683 | } 684 | 685 | static void 686 | paste(void) 687 | { 688 | char *p, *q; 689 | int di; 690 | unsigned long dl; 691 | Atom da; 692 | 693 | /* we have been given the current selection, now insert it into input */ 694 | if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, 695 | utf8, &da, &di, &dl, &dl, (unsigned char **)&p) 696 | == Success && p) { 697 | insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); 698 | XFree(p); 699 | } 700 | drawmenu(); 701 | } 702 | 703 | static void 704 | readstdin(void) 705 | { 706 | char buf[sizeof text], *p; 707 | size_t i, imax = 0, size = 0; 708 | unsigned int tmpmax = 0; 709 | 710 | if(passwd){ 711 | inputw = lines = 0; 712 | return; 713 | } 714 | 715 | /* read each line from stdin and add it to the item list */ 716 | for (i = 0; fgets(buf, sizeof buf, stdin); i++) { 717 | if (i + 1 >= size / sizeof *items) 718 | if (!(items = realloc(items, (size += BUFSIZ)))) 719 | die("cannot realloc %u bytes:", size); 720 | if ((p = strchr(buf, '\n'))) 721 | *p = '\0'; 722 | if (!(items[i].text = strdup(buf))) 723 | die("cannot strdup %u bytes:", strlen(buf) + 1); 724 | items[i].out = 0; 725 | drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); 726 | if (tmpmax > inputw) { 727 | inputw = tmpmax; 728 | imax = i; 729 | } 730 | } 731 | if (items) 732 | items[i].text = NULL; 733 | inputw = items ? TEXTW(items[imax].text) : 0; 734 | lines = MIN(lines, i); 735 | } 736 | 737 | static void 738 | run(void) 739 | { 740 | XEvent ev; 741 | 742 | while (!XNextEvent(dpy, &ev)) { 743 | if (XFilterEvent(&ev, win)) 744 | continue; 745 | switch(ev.type) { 746 | case ButtonPress: 747 | buttonpress(&ev); 748 | break; 749 | case DestroyNotify: 750 | if (ev.xdestroywindow.window != win) 751 | break; 752 | cleanup(); 753 | exit(1); 754 | case Expose: 755 | if (ev.xexpose.count == 0) 756 | drw_map(drw, win, 0, 0, mw, mh); 757 | break; 758 | case FocusIn: 759 | /* regrab focus from parent window */ 760 | if (ev.xfocus.window != win) 761 | grabfocus(); 762 | break; 763 | case KeyPress: 764 | keypress(&ev.xkey); 765 | break; 766 | case SelectionNotify: 767 | if (ev.xselection.property == utf8) 768 | paste(); 769 | break; 770 | case VisibilityNotify: 771 | if (ev.xvisibility.state != VisibilityUnobscured) 772 | XRaiseWindow(dpy, win); 773 | break; 774 | } 775 | } 776 | } 777 | 778 | static void 779 | setup(void) 780 | { 781 | int x, y, i, j; 782 | unsigned int du; 783 | XSetWindowAttributes swa; 784 | XIM xim; 785 | Window w, dw, *dws; 786 | XWindowAttributes wa; 787 | XClassHint ch = {"dmenu", "dmenu"}; 788 | #ifdef XINERAMA 789 | XineramaScreenInfo *info; 790 | Window pw; 791 | int a, di, n, area = 0; 792 | #endif 793 | /* init appearance */ 794 | for (j = 0; j < SchemeLast; j++) 795 | scheme[j] = drw_scm_create(drw, colors[j], alphas[j], 2); 796 | 797 | clip = XInternAtom(dpy, "CLIPBOARD", False); 798 | utf8 = XInternAtom(dpy, "UTF8_STRING", False); 799 | 800 | /* calculate menu geometry */ 801 | bh = drw->fonts->h + 2; 802 | lines = MAX(lines, 0); 803 | mh = (lines + 1) * bh; 804 | #ifdef XINERAMA 805 | i = 0; 806 | if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { 807 | XGetInputFocus(dpy, &w, &di); 808 | if (mon >= 0 && mon < n) 809 | i = mon; 810 | else if (w != root && w != PointerRoot && w != None) { 811 | /* find top-level window containing current input focus */ 812 | do { 813 | if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) 814 | XFree(dws); 815 | } while (w != root && w != pw); 816 | /* find xinerama screen with which the window intersects most */ 817 | if (XGetWindowAttributes(dpy, pw, &wa)) 818 | for (j = 0; j < n; j++) 819 | if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { 820 | area = a; 821 | i = j; 822 | } 823 | } 824 | /* no focused window is on screen, so use pointer location instead */ 825 | if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) 826 | for (i = 0; i < n; i++) 827 | if (INTERSECT(x, y, 1, 1, info[i])) 828 | break; 829 | 830 | x = info[i].x_org; 831 | y = info[i].y_org + (topbar ? 0 : info[i].height - mh); 832 | mw = info[i].width; 833 | XFree(info); 834 | } else 835 | #endif 836 | { 837 | if (!XGetWindowAttributes(dpy, parentwin, &wa)) 838 | die("could not get embedding window attributes: 0x%lx", 839 | parentwin); 840 | x = 0; 841 | y = topbar ? 0 : wa.height - mh; 842 | mw = wa.width; 843 | } 844 | promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; 845 | inputw = MIN(inputw, mw/3); 846 | match(); 847 | 848 | /* create menu window */ 849 | swa.override_redirect = True; 850 | swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; 851 | swa.border_pixel = 0; 852 | swa.colormap = cmap; 853 | swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | 854 | ButtonPressMask; 855 | win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, 856 | depth, InputOutput, visual, 857 | CWOverrideRedirect | CWBackPixel | CWColormap | CWEventMask | CWBorderPixel, &swa); 858 | XSetClassHint(dpy, win, &ch); 859 | 860 | 861 | /* input methods */ 862 | if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) 863 | die("XOpenIM failed: could not open input device"); 864 | 865 | xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 866 | XNClientWindow, win, XNFocusWindow, win, NULL); 867 | 868 | XMapRaised(dpy, win); 869 | if (embed) { 870 | XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); 871 | if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { 872 | for (i = 0; i < du && dws[i] != win; ++i) 873 | XSelectInput(dpy, dws[i], FocusChangeMask); 874 | XFree(dws); 875 | } 876 | grabfocus(); 877 | } 878 | drw_resize(drw, mw, mh); 879 | drawmenu(); 880 | } 881 | 882 | static void 883 | usage(void) 884 | { 885 | fputs("usage: dmenu [-bfiPrv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" 886 | " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); 887 | exit(1); 888 | } 889 | 890 | void 891 | read_Xresources(void) { 892 | XrmInitialize(); 893 | 894 | char* xrm; 895 | if ((xrm = XResourceManagerString(drw->dpy))) { 896 | char *type; 897 | XrmDatabase xdb = XrmGetStringDatabase(xrm); 898 | XrmValue xval; 899 | 900 | if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval) == True) /* font or font set */ 901 | fonts[0] = strdup(xval.addr); 902 | if (XrmGetResource(xdb, "dmenu.color0", "*", &type, &xval) == True) /* normal background color */ 903 | colors[SchemeNorm][ColBg] = strdup(xval.addr); 904 | if (XrmGetResource(xdb, "dmenu.color4", "*", &type, &xval) == True) /* normal foreground color */ 905 | colors[SchemeNorm][ColFg] = strdup(xval.addr); 906 | if (XrmGetResource(xdb, "dmenu.color4", "*", &type, &xval) == True) /* selected background color */ 907 | colors[SchemeSel][ColBg] = strdup(xval.addr); 908 | if (XrmGetResource(xdb, "dmenu.color0", "*", &type, &xval) == True) /* selected foreground color */ 909 | colors[SchemeSel][ColFg] = strdup(xval.addr); 910 | 911 | XrmDestroyDatabase(xdb); 912 | } 913 | } 914 | 915 | int 916 | main(int argc, char *argv[]) 917 | { 918 | XWindowAttributes wa; 919 | int i, fast = 0; 920 | 921 | if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 922 | fputs("warning: no locale support\n", stderr); 923 | if (!(dpy = XOpenDisplay(NULL))) 924 | die("cannot open display"); 925 | screen = DefaultScreen(dpy); 926 | root = RootWindow(dpy, screen); 927 | if (!embed || !(parentwin = strtol(embed, NULL, 0))) 928 | parentwin = root; 929 | if (!XGetWindowAttributes(dpy, parentwin, &wa)) 930 | die("could not get embedding window attributes: 0x%lx", 931 | parentwin); 932 | xinitvisual(); 933 | drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); 934 | read_Xresources(); 935 | 936 | for (i = 1; i < argc; i++) 937 | /* these options take no arguments */ 938 | if (!strcmp(argv[i], "-v")) { /* prints version information */ 939 | puts("dmenu-"VERSION); 940 | exit(0); 941 | } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ 942 | topbar = 0; 943 | else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ 944 | fast = 1; 945 | else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ 946 | fstrncmp = strncasecmp; 947 | fstrstr = cistrstr; 948 | } else if (!strcmp(argv[i], "-P")) /* is the input a password */ 949 | passwd = 1; 950 | else if (!strcmp(argv[i], "-r")) /* reject input which results in no match */ 951 | reject_no_match = 1; 952 | else if (i + 1 == argc) 953 | usage(); 954 | /* these options take one argument */ 955 | else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ 956 | lines = atoi(argv[++i]); 957 | else if (!strcmp(argv[i], "-m")) 958 | mon = atoi(argv[++i]); 959 | else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ 960 | prompt = argv[++i]; 961 | else if (!strcmp(argv[i], "-fn")) /* font or font set */ 962 | fonts[0] = argv[++i]; 963 | else if (!strcmp(argv[i], "-nb")) /* normal background color */ 964 | colors[SchemeNorm][ColBg] = argv[++i]; 965 | else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ 966 | colors[SchemeNorm][ColFg] = argv[++i]; 967 | else if (!strcmp(argv[i], "-sb")) /* selected background color */ 968 | colors[SchemeSel][ColBg] = argv[++i]; 969 | else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ 970 | colors[SchemeSel][ColFg] = argv[++i]; 971 | else if (!strcmp(argv[i], "-w")) /* embedding window id */ 972 | embed = argv[++i]; 973 | else 974 | usage(); 975 | 976 | if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) 977 | die("no fonts could be loaded."); 978 | lrpad = drw->fonts->h; 979 | 980 | #ifdef __OpenBSD__ 981 | if (pledge("stdio rpath", NULL) == -1) 982 | die("pledge"); 983 | #endif 984 | 985 | if (fast && !isatty(0)) { 986 | grabkeyboard(); 987 | readstdin(); 988 | } else { 989 | readstdin(); 990 | grabkeyboard(); 991 | } 992 | setup(); 993 | run(); 994 | 995 | return 1; /* unreachable */ 996 | } 997 | 998 | -------------------------------------------------------------------------------- /dmenu_path: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" 4 | cache="$cachedir/dmenu_run" 5 | 6 | [ ! -e "$cachedir" ] && mkdir -p "$cachedir" 7 | 8 | IFS=: 9 | if stest -dqr -n "$cache" $PATH; then 10 | stest -flx $PATH | sort -u | tee "$cache" 11 | else 12 | cat "$cache" 13 | fi 14 | -------------------------------------------------------------------------------- /dmenu_run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & 3 | -------------------------------------------------------------------------------- /drw.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "drw.h" 9 | #include "util.h" 10 | 11 | #define UTF_INVALID 0xFFFD 12 | #define UTF_SIZ 4 13 | 14 | static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 15 | static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 16 | static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 17 | static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 18 | 19 | static long 20 | utf8decodebyte(const char c, size_t *i) 21 | { 22 | for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) 23 | if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) 24 | return (unsigned char)c & ~utfmask[*i]; 25 | return 0; 26 | } 27 | 28 | static size_t 29 | utf8validate(long *u, size_t i) 30 | { 31 | if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 32 | *u = UTF_INVALID; 33 | for (i = 1; *u > utfmax[i]; ++i) 34 | ; 35 | return i; 36 | } 37 | 38 | static size_t 39 | utf8decode(const char *c, long *u, size_t clen) 40 | { 41 | size_t i, j, len, type; 42 | long udecoded; 43 | 44 | *u = UTF_INVALID; 45 | if (!clen) 46 | return 0; 47 | udecoded = utf8decodebyte(c[0], &len); 48 | if (!BETWEEN(len, 1, UTF_SIZ)) 49 | return 1; 50 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 51 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 52 | if (type) 53 | return j; 54 | } 55 | if (j < len) 56 | return 0; 57 | *u = udecoded; 58 | utf8validate(u, len); 59 | 60 | return len; 61 | } 62 | 63 | Drw * 64 | drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap) 65 | { 66 | Drw *drw = ecalloc(1, sizeof(Drw)); 67 | 68 | drw->dpy = dpy; 69 | drw->screen = screen; 70 | drw->root = root; 71 | drw->w = w; 72 | drw->h = h; 73 | drw->visual = visual; 74 | drw->depth = depth; 75 | drw->cmap = cmap; 76 | drw->drawable = XCreatePixmap(dpy, root, w, h, depth); 77 | drw->gc = XCreateGC(dpy, drw->drawable, 0, NULL); 78 | XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); 79 | 80 | return drw; 81 | } 82 | 83 | void 84 | drw_resize(Drw *drw, unsigned int w, unsigned int h) 85 | { 86 | if (!drw) 87 | return; 88 | 89 | drw->w = w; 90 | drw->h = h; 91 | if (drw->drawable) 92 | XFreePixmap(drw->dpy, drw->drawable); 93 | drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, drw->depth); 94 | } 95 | 96 | void 97 | drw_free(Drw *drw) 98 | { 99 | XFreePixmap(drw->dpy, drw->drawable); 100 | XFreeGC(drw->dpy, drw->gc); 101 | drw_fontset_free(drw->fonts); 102 | free(drw); 103 | } 104 | 105 | /* This function is an implementation detail. Library users should use 106 | * drw_fontset_create instead. 107 | */ 108 | static Fnt * 109 | xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) 110 | { 111 | Fnt *font; 112 | XftFont *xfont = NULL; 113 | FcPattern *pattern = NULL; 114 | 115 | if (fontname) { 116 | /* Using the pattern found at font->xfont->pattern does not yield the 117 | * same substitution results as using the pattern returned by 118 | * FcNameParse; using the latter results in the desired fallback 119 | * behaviour whereas the former just results in missing-character 120 | * rectangles being drawn, at least with some fonts. */ 121 | if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { 122 | fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); 123 | return NULL; 124 | } 125 | if (!(pattern = FcNameParse((FcChar8 *) fontname))) { 126 | fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); 127 | XftFontClose(drw->dpy, xfont); 128 | return NULL; 129 | } 130 | } else if (fontpattern) { 131 | if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { 132 | fprintf(stderr, "error, cannot load font from pattern.\n"); 133 | return NULL; 134 | } 135 | } else { 136 | die("no font specified."); 137 | } 138 | 139 | font = ecalloc(1, sizeof(Fnt)); 140 | font->xfont = xfont; 141 | font->pattern = pattern; 142 | font->h = xfont->ascent + xfont->descent; 143 | font->dpy = drw->dpy; 144 | 145 | return font; 146 | } 147 | 148 | static void 149 | xfont_free(Fnt *font) 150 | { 151 | if (!font) 152 | return; 153 | if (font->pattern) 154 | FcPatternDestroy(font->pattern); 155 | XftFontClose(font->dpy, font->xfont); 156 | free(font); 157 | } 158 | 159 | Fnt* 160 | drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) 161 | { 162 | Fnt *cur, *ret = NULL; 163 | size_t i; 164 | 165 | if (!drw || !fonts) 166 | return NULL; 167 | 168 | for (i = 1; i <= fontcount; i++) { 169 | if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { 170 | cur->next = ret; 171 | ret = cur; 172 | } 173 | } 174 | return (drw->fonts = ret); 175 | } 176 | 177 | void 178 | drw_fontset_free(Fnt *font) 179 | { 180 | if (font) { 181 | drw_fontset_free(font->next); 182 | xfont_free(font); 183 | } 184 | } 185 | 186 | void 187 | drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha) 188 | { 189 | if (!drw || !dest || !clrname) 190 | return; 191 | 192 | if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap, 193 | clrname, dest)) 194 | die("error, cannot allocate color '%s'", clrname); 195 | dest->pixel = (dest->pixel & 0x00FFFFFFFU) | alpha << 24; 196 | } 197 | 198 | /* Wrapper to create color schemes. The caller has to call free(3) on the 199 | * returned color scheme when done using it. */ 200 | Clr * 201 | drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount) 202 | { 203 | size_t i; 204 | Clr *ret; 205 | 206 | /* need at least two colors for a scheme */ 207 | if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) 208 | return NULL; 209 | 210 | for (i = 0; i < clrcount; i++) 211 | drw_clr_create(drw, &ret[i], clrnames[i], alphas[i]); 212 | return ret; 213 | } 214 | 215 | void 216 | drw_setfontset(Drw *drw, Fnt *set) 217 | { 218 | if (drw) 219 | drw->fonts = set; 220 | } 221 | 222 | void 223 | drw_setscheme(Drw *drw, Clr *scm) 224 | { 225 | if (drw) 226 | drw->scheme = scm; 227 | } 228 | 229 | void 230 | drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) 231 | { 232 | if (!drw || !drw->scheme) 233 | return; 234 | XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); 235 | if (filled) 236 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 237 | else 238 | XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); 239 | } 240 | 241 | int 242 | drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) 243 | { 244 | char buf[1024]; 245 | int ty; 246 | unsigned int ew; 247 | XftDraw *d = NULL; 248 | Fnt *usedfont, *curfont, *nextfont; 249 | size_t i, len; 250 | int utf8strlen, utf8charlen, render = x || y || w || h; 251 | long utf8codepoint = 0; 252 | const char *utf8str; 253 | FcCharSet *fccharset; 254 | FcPattern *fcpattern; 255 | FcPattern *match; 256 | XftResult result; 257 | int charexists = 0; 258 | 259 | if (!drw || (render && !drw->scheme) || !text || !drw->fonts) 260 | return 0; 261 | 262 | if (!render) { 263 | w = ~w; 264 | } else { 265 | XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); 266 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 267 | d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); 268 | x += lpad; 269 | w -= lpad; 270 | } 271 | 272 | usedfont = drw->fonts; 273 | while (1) { 274 | utf8strlen = 0; 275 | utf8str = text; 276 | nextfont = NULL; 277 | while (*text) { 278 | utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); 279 | for (curfont = drw->fonts; curfont; curfont = curfont->next) { 280 | charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); 281 | if (charexists) { 282 | if (curfont == usedfont) { 283 | utf8strlen += utf8charlen; 284 | text += utf8charlen; 285 | } else { 286 | nextfont = curfont; 287 | } 288 | break; 289 | } 290 | } 291 | 292 | if (!charexists || nextfont) 293 | break; 294 | else 295 | charexists = 0; 296 | } 297 | 298 | if (utf8strlen) { 299 | drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); 300 | /* shorten text if necessary */ 301 | for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) 302 | drw_font_getexts(usedfont, utf8str, len, &ew, NULL); 303 | 304 | if (len) { 305 | memcpy(buf, utf8str, len); 306 | buf[len] = '\0'; 307 | if (len < utf8strlen) 308 | for (i = len; i && i > len - 3; buf[--i] = '.') 309 | ; /* NOP */ 310 | 311 | if (render) { 312 | ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; 313 | XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], 314 | usedfont->xfont, x, ty, (XftChar8 *)buf, len); 315 | } 316 | x += ew; 317 | w -= ew; 318 | } 319 | } 320 | 321 | if (!*text) { 322 | break; 323 | } else if (nextfont) { 324 | charexists = 0; 325 | usedfont = nextfont; 326 | } else { 327 | /* Regardless of whether or not a fallback font is found, the 328 | * character must be drawn. */ 329 | charexists = 1; 330 | 331 | fccharset = FcCharSetCreate(); 332 | FcCharSetAddChar(fccharset, utf8codepoint); 333 | 334 | if (!drw->fonts->pattern) { 335 | /* Refer to the comment in xfont_create for more information. */ 336 | die("the first font in the cache must be loaded from a font string."); 337 | } 338 | 339 | fcpattern = FcPatternDuplicate(drw->fonts->pattern); 340 | FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 341 | FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); 342 | 343 | FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); 344 | FcDefaultSubstitute(fcpattern); 345 | match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); 346 | 347 | FcCharSetDestroy(fccharset); 348 | FcPatternDestroy(fcpattern); 349 | 350 | if (match) { 351 | usedfont = xfont_create(drw, NULL, match); 352 | if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { 353 | for (curfont = drw->fonts; curfont->next; curfont = curfont->next) 354 | ; /* NOP */ 355 | curfont->next = usedfont; 356 | } else { 357 | xfont_free(usedfont); 358 | usedfont = drw->fonts; 359 | } 360 | } 361 | } 362 | } 363 | if (d) 364 | XftDrawDestroy(d); 365 | 366 | return x + (render ? w : 0); 367 | } 368 | 369 | void 370 | drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) 371 | { 372 | if (!drw) 373 | return; 374 | 375 | XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); 376 | XSync(drw->dpy, False); 377 | } 378 | 379 | unsigned int 380 | drw_fontset_getwidth(Drw *drw, const char *text) 381 | { 382 | if (!drw || !drw->fonts || !text) 383 | return 0; 384 | return drw_text(drw, 0, 0, 0, 0, 0, text, 0); 385 | } 386 | 387 | void 388 | drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) 389 | { 390 | XGlyphInfo ext; 391 | 392 | if (!font || !text) 393 | return; 394 | 395 | XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); 396 | if (w) 397 | *w = ext.xOff; 398 | if (h) 399 | *h = font->h; 400 | } 401 | 402 | Cur * 403 | drw_cur_create(Drw *drw, int shape) 404 | { 405 | Cur *cur; 406 | 407 | if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) 408 | return NULL; 409 | 410 | cur->cursor = XCreateFontCursor(drw->dpy, shape); 411 | 412 | return cur; 413 | } 414 | 415 | void 416 | drw_cur_free(Drw *drw, Cur *cursor) 417 | { 418 | if (!cursor) 419 | return; 420 | 421 | XFreeCursor(drw->dpy, cursor->cursor); 422 | free(cursor); 423 | } 424 | -------------------------------------------------------------------------------- /drw.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | typedef struct { 4 | Cursor cursor; 5 | } Cur; 6 | 7 | typedef struct Fnt { 8 | Display *dpy; 9 | unsigned int h; 10 | XftFont *xfont; 11 | FcPattern *pattern; 12 | struct Fnt *next; 13 | } Fnt; 14 | 15 | enum { ColFg, ColBg }; /* Clr scheme index */ 16 | typedef XftColor Clr; 17 | 18 | typedef struct { 19 | unsigned int w, h; 20 | Display *dpy; 21 | int screen; 22 | Window root; 23 | Visual *visual; 24 | unsigned int depth; 25 | Colormap cmap; 26 | Drawable drawable; 27 | GC gc; 28 | Clr *scheme; 29 | Fnt *fonts; 30 | } Drw; 31 | 32 | /* Drawable abstraction */ 33 | Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap); 34 | void drw_resize(Drw *drw, unsigned int w, unsigned int h); 35 | void drw_free(Drw *drw); 36 | 37 | /* Fnt abstraction */ 38 | Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); 39 | void drw_fontset_free(Fnt* set); 40 | unsigned int drw_fontset_getwidth(Drw *drw, const char *text); 41 | void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); 42 | 43 | /* Colorscheme abstraction */ 44 | void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha); 45 | Clr *drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount); 46 | 47 | /* Cursor abstraction */ 48 | Cur *drw_cur_create(Drw *drw, int shape); 49 | void drw_cur_free(Drw *drw, Cur *cursor); 50 | 51 | /* Drawing context manipulation */ 52 | void drw_setfontset(Drw *drw, Fnt *set); 53 | void drw_setscheme(Drw *drw, Clr *scm); 54 | 55 | /* Drawing functions */ 56 | void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); 57 | int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); 58 | 59 | /* Map functions */ 60 | void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); 61 | -------------------------------------------------------------------------------- /stest.1: -------------------------------------------------------------------------------- 1 | .TH STEST 1 dmenu\-VERSION 2 | .SH NAME 3 | stest \- filter a list of files by properties 4 | .SH SYNOPSIS 5 | .B stest 6 | .RB [ -abcdefghlpqrsuwx ] 7 | .RB [ -n 8 | .IR file ] 9 | .RB [ -o 10 | .IR file ] 11 | .RI [ file ...] 12 | .SH DESCRIPTION 13 | .B stest 14 | takes a list of files and filters by the files' properties, analogous to 15 | .IR test (1). 16 | Files which pass all tests are printed to stdout. If no files are given, stest 17 | reads files from stdin. 18 | .SH OPTIONS 19 | .TP 20 | .B \-a 21 | Test hidden files. 22 | .TP 23 | .B \-b 24 | Test that files are block specials. 25 | .TP 26 | .B \-c 27 | Test that files are character specials. 28 | .TP 29 | .B \-d 30 | Test that files are directories. 31 | .TP 32 | .B \-e 33 | Test that files exist. 34 | .TP 35 | .B \-f 36 | Test that files are regular files. 37 | .TP 38 | .B \-g 39 | Test that files have their set-group-ID flag set. 40 | .TP 41 | .B \-h 42 | Test that files are symbolic links. 43 | .TP 44 | .B \-l 45 | Test the contents of a directory given as an argument. 46 | .TP 47 | .BI \-n " file" 48 | Test that files are newer than 49 | .IR file . 50 | .TP 51 | .BI \-o " file" 52 | Test that files are older than 53 | .IR file . 54 | .TP 55 | .B \-p 56 | Test that files are named pipes. 57 | .TP 58 | .B \-q 59 | No files are printed, only the exit status is returned. 60 | .TP 61 | .B \-r 62 | Test that files are readable. 63 | .TP 64 | .B \-s 65 | Test that files are not empty. 66 | .TP 67 | .B \-u 68 | Test that files have their set-user-ID flag set. 69 | .TP 70 | .B \-v 71 | Invert the sense of tests, only failing files pass. 72 | .TP 73 | .B \-w 74 | Test that files are writable. 75 | .TP 76 | .B \-x 77 | Test that files are executable. 78 | .SH EXIT STATUS 79 | .TP 80 | .B 0 81 | At least one file passed all tests. 82 | .TP 83 | .B 1 84 | No files passed all tests. 85 | .TP 86 | .B 2 87 | An error occurred. 88 | .SH SEE ALSO 89 | .IR dmenu (1), 90 | .IR test (1) 91 | -------------------------------------------------------------------------------- /stest.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "arg.h" 12 | char *argv0; 13 | 14 | #define FLAG(x) (flag[(x)-'a']) 15 | 16 | static void test(const char *, const char *); 17 | static void usage(void); 18 | 19 | static int match = 0; 20 | static int flag[26]; 21 | static struct stat old, new; 22 | 23 | static void 24 | test(const char *path, const char *name) 25 | { 26 | struct stat st, ln; 27 | 28 | if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ 29 | && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ 30 | && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ 31 | && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ 32 | && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ 33 | && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ 34 | && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ 35 | && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ 36 | && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ 37 | && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ 38 | && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ 39 | && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ 40 | && (!FLAG('s') || st.st_size > 0) /* not empty */ 41 | && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ 42 | && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ 43 | && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ 44 | if (FLAG('q')) 45 | exit(0); 46 | match = 1; 47 | puts(name); 48 | } 49 | } 50 | 51 | static void 52 | usage(void) 53 | { 54 | fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " 55 | "[-n file] [-o file] [file...]\n", argv0); 56 | exit(2); /* like test(1) return > 1 on error */ 57 | } 58 | 59 | int 60 | main(int argc, char *argv[]) 61 | { 62 | struct dirent *d; 63 | char path[PATH_MAX], *line = NULL, *file; 64 | size_t linesiz = 0; 65 | ssize_t n; 66 | DIR *dir; 67 | int r; 68 | 69 | ARGBEGIN { 70 | case 'n': /* newer than file */ 71 | case 'o': /* older than file */ 72 | file = EARGF(usage()); 73 | if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) 74 | perror(file); 75 | break; 76 | default: 77 | /* miscellaneous operators */ 78 | if (strchr("abcdefghlpqrsuvwx", ARGC())) 79 | FLAG(ARGC()) = 1; 80 | else 81 | usage(); /* unknown flag */ 82 | } ARGEND; 83 | 84 | if (!argc) { 85 | /* read list from stdin */ 86 | while ((n = getline(&line, &linesiz, stdin)) > 0) { 87 | if (n && line[n - 1] == '\n') 88 | line[n - 1] = '\0'; 89 | test(line, line); 90 | } 91 | free(line); 92 | } else { 93 | for (; argc; argc--, argv++) { 94 | if (FLAG('l') && (dir = opendir(*argv))) { 95 | /* test directory contents */ 96 | while ((d = readdir(dir))) { 97 | r = snprintf(path, sizeof path, "%s/%s", 98 | *argv, d->d_name); 99 | if (r >= 0 && (size_t)r < sizeof path) 100 | test(path, d->d_name); 101 | } 102 | closedir(dir); 103 | } else { 104 | test(*argv, *argv); 105 | } 106 | } 107 | } 108 | return match ? 0 : 1; 109 | } 110 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util.h" 8 | 9 | void * 10 | ecalloc(size_t nmemb, size_t size) 11 | { 12 | void *p; 13 | 14 | if (!(p = calloc(nmemb, size))) 15 | die("calloc:"); 16 | return p; 17 | } 18 | 19 | void 20 | die(const char *fmt, ...) { 21 | va_list ap; 22 | 23 | va_start(ap, fmt); 24 | vfprintf(stderr, fmt, ap); 25 | va_end(ap); 26 | 27 | if (fmt[0] && fmt[strlen(fmt)-1] == ':') { 28 | fputc(' ', stderr); 29 | perror(NULL); 30 | } else { 31 | fputc('\n', stderr); 32 | } 33 | 34 | exit(1); 35 | } 36 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 4 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 5 | #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) 6 | 7 | void die(const char *fmt, ...); 8 | void *ecalloc(size_t nmemb, size_t size); 9 | --------------------------------------------------------------------------------