├── .gitignore ├── LICENSE ├── Makefile ├── README ├── config.mk ├── dmenu.1 ├── dmenu.c ├── dmenu_run ├── draw.c ├── draw.h ├── stest.1 └── stest.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | dmenu2: 4 | @ 2013 Michał Lemke 5 | 6 | dmenu 7 | © 2010-2012 Connor Lane Smith 8 | © 2006-2012 Anselm R Garbe 9 | © 2009 Gottox 10 | © 2009 Markus Schnalke 11 | © 2009 Evan Gates 12 | © 2006-2008 Sander van Dijk 13 | © 2006-2007 Michał Janeczek 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a 16 | copy of this software and associated documentation files (the "Software"), 17 | to deal in the Software without restriction, including without limitation 18 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 19 | and/or sell copies of the Software, and to permit persons to whom the 20 | Software is furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 28 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 30 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 31 | DEALINGS IN THE SOFTWARE. 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # dmenu - dynamic menu 2 | # See LICENSE file for copyright and license details. 3 | 4 | include config.mk 5 | 6 | SRC = dmenu.c draw.c stest.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 | @echo CC -c $< 19 | @${CC} -c $< ${CFLAGS} 20 | 21 | ${OBJ}: config.mk draw.h 22 | 23 | dmenu: dmenu.o draw.o 24 | @echo CC -o $@ 25 | @${CC} -o $@ dmenu.o draw.o ${LDFLAGS} 26 | 27 | stest: stest.o 28 | @echo CC -o $@ 29 | @${CC} -o $@ stest.o ${LDFLAGS} 30 | 31 | clean: 32 | @echo cleaning 33 | @rm -f dmenu stest ${OBJ} dmenu-${VERSION}.tar.gz 34 | 35 | dist: clean 36 | @echo creating dist tarball 37 | @mkdir -p dmenu-${VERSION} 38 | @cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_run stest.1 ${SRC} 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 | @echo installing executables to ${DESTDIR}${PREFIX}/bin 45 | @mkdir -p ${DESTDIR}${PREFIX}/bin 46 | @cp -f dmenu dmenu_run stest ${DESTDIR}${PREFIX}/bin 47 | @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu 48 | @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run 49 | @chmod 755 ${DESTDIR}${PREFIX}/bin/stest 50 | @echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1 51 | @mkdir -p ${DESTDIR}${MANPREFIX}/man1 52 | @sed "s/VERSION/${VERSION}/g" < dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/dmenu.1 53 | @sed "s/VERSION/${VERSION}/g" < stest.1 > ${DESTDIR}${MANPREFIX}/man1/stest.1 54 | @chmod 644 ${DESTDIR}${MANPREFIX}/man1/dmenu.1 55 | @chmod 644 ${DESTDIR}${MANPREFIX}/man1/stest.1 56 | 57 | uninstall: 58 | @echo removing executables from ${DESTDIR}${PREFIX}/bin 59 | @rm -f ${DESTDIR}${PREFIX}/bin/dmenu 60 | @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run 61 | @rm -f ${DESTDIR}${PREFIX}/bin/stest 62 | @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 63 | @rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1 64 | @rm -f ${DESTDIR}${MANPREFIX}/man1/stest.1 65 | 66 | .PHONY: all options clean dist install uninstall 67 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | dmenu-ee // dynamic menu extended edition 2 | ============================== 3 | dmenu-ee is a fork of dmenu - an efficient dynamic menu for X, patched with XFT, quiet, x & y, token, fuzzy matching, borders, follow focus, tab nav, filter and full mouse support. 4 | Added an option to set the screen on which dmenu appears. 5 | Also allows to dim screen with selected color and opacity while dmenu-ee is running. 6 | 7 | 8 | Requirements 9 | ------------ 10 | In order to build dmenu-ee you need the Xlib header files. 11 | 12 | 13 | Installation 14 | ------------ 15 | Edit config.mk to match your local setup (dmenu-ee is installed into 16 | the /usr/local namespace by default). 17 | 18 | Afterwards enter the following command to build and install dmenu-ee 19 | (if necessary as root): 20 | 21 | make clean install 22 | 23 | 24 | Running dmenu-ee 25 | ------------- 26 | See the man page for details. 27 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # dmenu2 version 2 | VERSION = 0.1 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 | # Xft, comment if you don't want it 16 | XFTINC = -I/usr/include/freetype2 17 | XFTLIBS = -lXft -lXrender -lfreetype -lz -lfontconfig 18 | 19 | # includes and libs 20 | INCS = -I${X11INC} ${XFTINC} 21 | LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${XFTLIBS} 22 | 23 | # flags 24 | CPPFLAGS = -D_BSD_SOURCE -D_POSIX_C_SOURCE=2 -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} 25 | #CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} 26 | CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} 27 | LDFLAGS = -s ${LIBS} 28 | 29 | # compiler and linker 30 | CC = cc 31 | -------------------------------------------------------------------------------- /dmenu.1: -------------------------------------------------------------------------------- 1 | .TH DMENU 1 dmenu\-VERSION 2 | .SH NAME 3 | dmenu \- dynamic menu 4 | .SH SYNOPSIS 5 | .B dmenu 6 | .RB [ \-b ] 7 | .RB [ \-f ] 8 | .RB [ \-r ] 9 | .RB [ \-i ] 10 | .RB [ \-z ] 11 | .RB [ \-t ] 12 | .RB [ \-mask ] 13 | .RB [ \-noinput ] 14 | .RB [ \-s 15 | .IR screen ] 16 | .RB [ \-name 17 | .IR name ] 18 | .RB [ \-class 19 | .IR class ] 20 | .RB [ \-o 21 | .IR opacity ] 22 | .RB [ \-dim 23 | .IR opacity ] 24 | .RB [ \-dc 25 | .IR color ] 26 | .RB [ \-l 27 | .IR lines ] 28 | .RB [ \-h 29 | .IR height ] 30 | .RB [ \-w 31 | .IR width ] 32 | .RB [ \-p 33 | .IR prompt ] 34 | .RB [ \-fn 35 | .IR font ] 36 | .RB [ \-nb 37 | .IR color ] 38 | .RB [ \-nf 39 | .IR color ] 40 | .RB [ \-sb 41 | .IR color ] 42 | .RB [ \-sf 43 | .IR color ] 44 | .RB [ \-v ] 45 | .P 46 | .BR dmenu_run " ..." 47 | .SH DESCRIPTION 48 | .B dmenu 49 | is a dynamic menu for X, which reads a list of newline\-separated items from 50 | stdin. When the user selects an item and presses Return, their choice is printed 51 | to stdout and dmenu terminates. Entering text will narrow the items to those 52 | matching the tokens in the input. 53 | .P 54 | .B dmenu_run 55 | is a script used by 56 | .IR dwm (1) 57 | which lists programs in the user's $PATH and runs the result in their $SHELL. 58 | .SH OPTIONS 59 | .TP 60 | .B \-b 61 | dmenu appears at the bottom of the screen. 62 | .TP 63 | .B \-q 64 | dmenu will not show any items if the search string is empty. 65 | .TP 66 | .B \-f 67 | dmenu grabs the keyboard before reading stdin. This is faster, but will lock up 68 | X until stdin reaches end\-of\-file. 69 | .TP 70 | .B \-r 71 | activates filter mode. All matching items currently shown in the list will be 72 | selected, starting with the item that is highlighted and wrapping around to the 73 | beginning of the list. 74 | .TP 75 | .B \-i 76 | dmenu matches menu items case insensitively. 77 | .TP 78 | .B \-z 79 | dmenu uses fuzzy matching. It matches items that have all characters entered, in sequence they are 80 | entered, but there may be any number of characters between matched characters. 81 | For example it takes "txt" makes it to "*t*x*t" glob pattern and checks if it 82 | matches. 83 | .TP 84 | .B \-t 85 | dmenu uses space\-separated tokens to match menu items. Using this overrides -z option. 86 | .TP 87 | .B \-mask 88 | dmenu masks input with asterisk characters (*). 89 | .TP 90 | .B \-noinput 91 | dmenu ignores input from stdin (equivalent to: echo | dmenu). 92 | .TP 93 | .BI \-s " screen" 94 | dmenu apears on the specified screen number. Number given corespondes to screen number in X configuration. 95 | .TP 96 | .BI \-name " name" 97 | defines window name for dmenu. Defaults to "dmenu". 98 | .TP 99 | .BI \-class " class" 100 | defines window class for dmenu. Defaults to "Dmenu". 101 | .TP 102 | .BI \-o " opacity" 103 | defines window opacity for dmenu. Defaults to 1.0. 104 | .TP 105 | .BI \-dim " opacity" 106 | enables screen dimming when dmenu appers. Takes dim opacity as argument. 107 | .TP 108 | .BI \-dc " color" 109 | defines color of screen dimming. Active only when -dim in effect. Defautls to black (#000000) 110 | .TP 111 | .BI \-l " lines" 112 | dmenu lists items vertically, with the given number of lines. 113 | .TP 114 | .BI \-h " height" 115 | defines the height of the bar in pixels. 116 | .TP 117 | .BI \-p " prompt" 118 | defines the prompt to be displayed to the left of the input field. 119 | .TP 120 | .BI \-fn " font" 121 | defines the font or font set used. eg. "fixed" or "Monospace-12:normal" (an xft font) 122 | .TP 123 | .BI \-x " xoffset" 124 | defines the offset from the left border of the screen. 125 | .TP 126 | .BI \-y " yoffset" 127 | defines the offset from the top border of the screen. 128 | .TP 129 | .BI \-w " width" 130 | defines the desired menu window width. 131 | .TP 132 | .BI \-nb " color" 133 | defines the normal background color. 134 | .IR #RGB , 135 | .IR #RRGGBB , 136 | and X color names are supported. 137 | .TP 138 | .BI \-nf " color" 139 | defines the normal foreground color. 140 | .TP 141 | .BI \-sb " color" 142 | defines the selected background color. 143 | .TP 144 | .BI \-sf " color" 145 | defines the selected foreground color. 146 | .TP 147 | .B \-v 148 | prints version information to stdout, then exits. 149 | .SH USAGE 150 | dmenu is completely controlled by the keyboard. Items are selected using the 151 | arrow keys, page up, page down, home, and end. 152 | .TP 153 | .B Tab 154 | Copy the selected item to the input field. 155 | .TP 156 | .B Return 157 | Confirm selection. Prints the selected item to stdout and exits, returning 158 | success. 159 | .TP 160 | .B Shift\-Return 161 | Confirm input. Prints the input text to stdout and exits, returning success. 162 | .TP 163 | .B Escape 164 | Exit without selecting an item, returning failure. 165 | .TP 166 | C\-a 167 | Home 168 | .TP 169 | C\-b 170 | Left 171 | .TP 172 | C\-c 173 | Escape 174 | .TP 175 | C\-d 176 | Delete 177 | .TP 178 | C\-e 179 | End 180 | .TP 181 | C\-f 182 | Right 183 | .TP 184 | C\-g 185 | Escape 186 | .TP 187 | C\-h 188 | Backspace 189 | .TP 190 | C\-i 191 | Tab 192 | .TP 193 | C\-j 194 | Return 195 | .TP 196 | C\-J 197 | Shift-Return 198 | .TP 199 | C\-k 200 | Delete line right 201 | .TP 202 | C\-m 203 | Return 204 | .TP 205 | C\-n 206 | Down 207 | .TP 208 | C\-p 209 | Up 210 | .TP 211 | C\-u 212 | Delete line left 213 | .TP 214 | C\-w 215 | Delete word left 216 | .TP 217 | C\-y 218 | Paste from primary X selection 219 | .TP 220 | C\-Y 221 | Paste from X clipboard 222 | .TP 223 | M\-g 224 | Home 225 | .TP 226 | M\-G 227 | End 228 | .TP 229 | M\-h 230 | Up 231 | .TP 232 | M\-j 233 | Page down 234 | .TP 235 | M\-k 236 | Page up 237 | .TP 238 | M\-l 239 | Down 240 | .SH SEE ALSO 241 | .IR dwm (1), 242 | .IR stest (1) 243 | -------------------------------------------------------------------------------- /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 | #include 11 | #include 12 | #ifdef XINERAMA 13 | #include 14 | #endif 15 | #include "draw.h" 16 | 17 | #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ 18 | * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) 19 | #define MIN(a,b) ((a) < (b) ? (a) : (b)) 20 | #define MAX(a,b) ((a) > (b) ? (a) : (b)) 21 | #define DEFFONT "fixed" /* xft example: "Monospace-11" */ 22 | 23 | typedef struct Item Item; 24 | struct Item { 25 | char *text; 26 | Item *left, *right; 27 | }; 28 | 29 | static void appenditem(Item *item, Item **list, Item **last); 30 | static void buttonpress(XEvent *e); 31 | static void pointermove(XEvent *e); 32 | static void calcoffsets(void); 33 | static void cleanup(void); 34 | static char *cistrstr(const char *s, const char *sub); 35 | static void drawmenu(void); 36 | static void grabkeyboard(void); 37 | static void grabpointer(void); 38 | static void insert(const char *str, ssize_t n); 39 | static void keypress(XKeyEvent *ev); 40 | static void matchstr(void); 41 | static void matchtok(void); 42 | static void matchfuzzy(void); 43 | static char *strchri(const char *s, int c); 44 | static size_t nextrune(int inc); 45 | static size_t utf8length(); 46 | static void paste(void); 47 | static void readstdin(void); 48 | static void run(void); 49 | static void setup(void); 50 | static void usage(void); 51 | static void read_resourses(void); 52 | static char text[BUFSIZ] = ""; 53 | static char originaltext[BUFSIZ] = ""; 54 | static int bh, mw, mh; 55 | static int inputw, promptw; 56 | static size_t cursor = 0; 57 | static const char *font = NULL; 58 | static const char *prompt = NULL; 59 | static const char *normbgcolor = NULL; 60 | static const char *normfgcolor = NULL; 61 | static const char *selbgcolor = NULL; 62 | static const char *selfgcolor = NULL; 63 | static const char *dimcolor = NULL; 64 | static const char *bordercolor = NULL; 65 | static char *name = "dmenu"; 66 | static char *class = "Dmenu"; 67 | static char *dimname = "dimenu"; 68 | static unsigned int border_width = 0; 69 | static unsigned int lines = 0, line_height = 0; 70 | static int xoffset = 0; 71 | static int yoffset = 0; 72 | static int width = 0; 73 | #ifdef XINERAMA 74 | static int snum = -1; 75 | #endif 76 | static ColorSet *normcol; 77 | static ColorSet *selcol; 78 | static ColorSet *dimcol; 79 | static ColorSet *bordercol; 80 | static Atom clip, utf8; 81 | static Bool topbar = True; 82 | static Bool running = True; 83 | static Bool filter = False; 84 | static Bool maskin = False; 85 | static Bool noinput = False; 86 | static int ret = 0; 87 | static Bool quiet = False; 88 | static DC *dc; 89 | static Item *items = NULL; 90 | static Item *matches, *matchend; 91 | static Item *prev, *curr, *next, *sel; 92 | static Window win, dim; 93 | static XIC xic; 94 | static double opacity = 1.0, dimopacity = 0.0; 95 | 96 | #define OPAQUE 0xffffffff 97 | #define OPACITY "_NET_WM_WINDOW_OPACITY" 98 | 99 | static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; 100 | static char *(*fstrstr)(const char *, const char *) = strstr; 101 | static void (*match)(void) = matchstr; 102 | static char *(*fstrchr)(const char *, const int) = strchr; 103 | 104 | int 105 | main(int argc, char *argv[]) { 106 | Bool fast = False; 107 | int i; 108 | 109 | for(i = 1; i < argc; i++) 110 | /* these options take no arguments */ 111 | if(!strcmp(argv[i], "-v")) { /* prints version information */ 112 | puts("dmenu-"VERSION", © 2006-2012 dmenu engineers, see LICENSE for details"); 113 | exit(EXIT_SUCCESS); 114 | } 115 | else if(!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ 116 | topbar = False; 117 | else if(!strcmp(argv[i], "-q")) 118 | quiet = True; 119 | else if(!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ 120 | fast = True; 121 | else if(!strcmp(argv[i], "-z")) /* enable fuzzy matching */ 122 | match = matchfuzzy; 123 | else if(!strcmp(argv[i], "-r")) 124 | filter = True; 125 | else if(!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ 126 | fstrncmp = strncasecmp; 127 | fstrstr = cistrstr; 128 | fstrchr = strchri; 129 | } 130 | else if(!strcmp(argv[i], "-mask")) /* password-style input */ 131 | maskin = True; 132 | else if(!strcmp(argv[i], "-noinput")) 133 | noinput = True; 134 | 135 | else if(!strcmp(argv[i], "-t")) 136 | match = matchtok; 137 | else if(i+1 == argc) 138 | usage(); 139 | /* these options take one argument */ 140 | else if(!strcmp(argv[i], "-x")) 141 | xoffset = atoi(argv[++i]); 142 | else if(!strcmp(argv[i], "-y")) 143 | yoffset = atoi(argv[++i]); 144 | else if(!strcmp(argv[i], "-w")) 145 | width = atoi(argv[++i]); 146 | else if(!strcmp(argv[i], "-l")) /* number of lines in vertical list */ 147 | lines = atoi(argv[++i]); 148 | else if(!strcmp(argv[i], "-h")) /* minimum height of single line */ 149 | line_height = atoi(argv[++i]); 150 | #ifdef XINERAMA 151 | else if(!strcmp(argv[i], "-s")) /* screen number for dmenu to appear in */ 152 | snum = atoi(argv[++i]); 153 | #endif 154 | else if (!strcmp(argv[i], "-name")) /* dmenu window name */ 155 | name = argv[++i]; 156 | else if (!strcmp(argv[i], "-class")) /* dmenu window class */ 157 | class = argv[++i]; 158 | else if (!strcmp(argv[i], "-o")) /* opacity */ 159 | opacity = atof(argv[++i]); 160 | else if (!strcmp(argv[i], "-dim")) /* dim opacity */ 161 | dimopacity = atof(argv[++i]); 162 | else if (!strcmp(argv[i], "-dc")) /* dim color */ 163 | dimcolor = argv[++i]; 164 | else if(!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ 165 | prompt = argv[++i]; 166 | else if(!strcmp(argv[i], "-fn")) /* font or font set */ 167 | font = argv[++i]; 168 | else if(!strcmp(argv[i], "-nb")) /* normal background color */ 169 | normbgcolor = argv[++i]; 170 | else if(!strcmp(argv[i], "-nf")) /* normal foreground color */ 171 | normfgcolor = argv[++i]; 172 | else if(!strcmp(argv[i], "-sb")) /* selected background color */ 173 | selbgcolor = argv[++i]; 174 | else if(!strcmp(argv[i], "-sf")) /* selected foreground color */ 175 | selfgcolor = argv[++i]; 176 | else if(!strcmp(argv[i], "-bw")) /* border width */ 177 | border_width = atoi(argv[++i]); 178 | else if(!strcmp(argv[i], "-bc")) /* selected border color */ 179 | bordercolor = argv[++i]; 180 | else 181 | usage(); 182 | 183 | dc = initdc(); 184 | read_resourses(); 185 | initfont(dc, font ? font : DEFFONT); 186 | normcol = initcolor(dc, normfgcolor, normbgcolor); 187 | selcol = initcolor(dc, selfgcolor, selbgcolor); 188 | dimcol = initcolor(dc, dimcolor, dimcolor); 189 | bordercol = initcolor(dc, bordercolor, bordercolor); 190 | 191 | if(noinput) { 192 | grabkeyboard(); 193 | grabpointer(); 194 | } 195 | else if(fast) { 196 | grabkeyboard(); 197 | grabpointer(); 198 | readstdin(); 199 | } 200 | else { 201 | readstdin(); 202 | grabkeyboard(); 203 | grabpointer(); 204 | } 205 | setup(); 206 | run(); 207 | 208 | cleanup(); 209 | return ret; 210 | } 211 | 212 | /* Set font and colors from X resources database if they are not set 213 | * from command line */ 214 | void 215 | read_resourses(void) { 216 | XrmDatabase xdb; 217 | char* xrm; 218 | char* datatype[20]; 219 | XrmValue xvalue; 220 | 221 | XrmInitialize(); 222 | xrm = XResourceManagerString(dc->dpy); 223 | if( xrm != NULL ) { 224 | xdb = XrmGetStringDatabase(xrm); 225 | if( font == NULL && XrmGetResource(xdb, "dmenu.font", "*", datatype, &xvalue) == True ) 226 | font = strdup(xvalue.addr); 227 | if( normfgcolor == NULL && XrmGetResource(xdb, "dmenu.foreground", "*", datatype, &xvalue) == True ) 228 | normfgcolor = strdup(xvalue.addr); 229 | if( normbgcolor == NULL && XrmGetResource(xdb, "dmenu.background", "*", datatype, &xvalue) == True ) 230 | normbgcolor = strdup(xvalue.addr); 231 | if( selfgcolor == NULL && XrmGetResource(xdb, "dmenu.selforeground", "*", datatype, &xvalue) == True ) 232 | selfgcolor = strdup(xvalue.addr); 233 | if( selbgcolor == NULL && XrmGetResource(xdb, "dmenu.selbackground", "*", datatype, &xvalue) == True ) 234 | selbgcolor = strdup(xvalue.addr); 235 | if( bordercolor == NULL && XrmGetResource(xdb, "dmenu.bordercolor", "*", datatype, &xvalue) == True ) 236 | bordercolor = strdup(xvalue.addr); 237 | if( dimcolor == NULL && XrmGetResource(xdb, "dmenu.dimcolor", "*", datatype, &xvalue) == True ) 238 | dimcolor = strdup(xvalue.addr); 239 | if( XrmGetResource(xdb, "dmenu.opacity", "*", datatype, &xvalue) == True ) 240 | opacity = atof(strdup(xvalue.addr)); 241 | XrmDestroyDatabase(xdb); 242 | } 243 | /* Set default colors if they are not set */ 244 | if( normbgcolor == NULL ) 245 | normbgcolor = "#222222"; 246 | if( normfgcolor == NULL ) 247 | normfgcolor = "#bbbbbb"; 248 | if( selbgcolor == NULL ) 249 | selbgcolor = "#005577"; 250 | if( selfgcolor == NULL ) 251 | selfgcolor = "#eeeeee"; 252 | if( bordercolor == NULL ) 253 | bordercolor = "#222222"; 254 | if( dimcolor == NULL ) 255 | dimcolor = "#000000"; 256 | if( !opacity ) 257 | opacity = 1.0; 258 | } 259 | 260 | void 261 | appenditem(Item *item, Item **list, Item **last) { 262 | if(*last) 263 | (*last)->right = item; 264 | else 265 | *list = item; 266 | 267 | item->left = *last; 268 | item->right = NULL; 269 | *last = item; 270 | } 271 | 272 | void 273 | calcoffsets(void) { 274 | int i, n; 275 | 276 | if(lines > 0) 277 | n = lines * bh; 278 | else 279 | n = mw - (promptw + inputw + textw(dc, "<") + textw(dc, ">")); 280 | /* calculate which items will begin the next page and previous page */ 281 | for(i = 0, next = curr; next; next = next->right) 282 | if((i += (lines > 0) ? bh : MIN(textw(dc, next->text), n)) > n) 283 | break; 284 | for(i = 0, prev = curr; prev && prev->left; prev = prev->left) 285 | if((i += (lines > 0) ? bh : MIN(textw(dc, prev->left->text), n)) > n) 286 | break; 287 | } 288 | 289 | char * 290 | cistrstr(const char *s, const char *sub) { 291 | size_t len; 292 | 293 | for(len = strlen(sub); *s; s++) 294 | if(!strncasecmp(s, sub, len)) 295 | return (char *)s; 296 | return NULL; 297 | } 298 | 299 | void 300 | cleanup(void) { 301 | freecol(dc, normcol); 302 | freecol(dc, selcol); 303 | freecol(dc, dimcol); 304 | XDestroyWindow(dc->dpy, win); 305 | if(dimopacity > 0) 306 | XDestroyWindow(dc->dpy, dim); 307 | XUngrabKeyboard(dc->dpy, CurrentTime); 308 | XUngrabPointer(dc->dpy, CurrentTime); 309 | freedc(dc); 310 | } 311 | 312 | const char * 313 | createmaskinput(char *maskinput, int length) 314 | { 315 | if (length <= 0) { 316 | *maskinput = '\0'; 317 | } else { 318 | memset(maskinput, '*', length); 319 | maskinput[length] = '\0'; 320 | } 321 | 322 | return (maskinput); 323 | } 324 | 325 | void 326 | drawmenu(void) { 327 | int curpos; 328 | char maskinput[sizeof text]; 329 | int length = maskin ? utf8length() : cursor; 330 | Item *item; 331 | 332 | dc->x = 0; 333 | dc->y = 0; 334 | dc->h = bh; 335 | drawrect(dc, 0, 0, mw, mh, True, normcol->BG); 336 | 337 | if(prompt && *prompt) { 338 | dc->w = promptw; 339 | drawtext(dc, prompt, selcol); 340 | dc->x = dc->w; 341 | } 342 | 343 | 344 | /* draw input field */ 345 | dc->w = (lines > 0 || !matches) ? mw - dc->x : inputw; 346 | drawtext(dc, maskin ? createmaskinput(maskinput, length) : text, normcol); 347 | if((curpos = textnw(dc, maskin ? maskinput : text, length) + dc->font.height/2) < dc->w) 348 | drawrect(dc, curpos, (dc->h - dc->font.height)/2 + 1, 1, dc->font.height -1, True, normcol->FG); 349 | 350 | if(!quiet || strlen(text) > 0) { 351 | if(lines > 0) { 352 | /* draw vertical list */ 353 | dc->w = mw - dc->x; 354 | for(item = curr; item != next; item = item->right) { 355 | dc->y += dc->h; 356 | drawtext(dc, item->text, (item == sel) ? selcol : normcol); 357 | } 358 | } 359 | else if(matches) { 360 | /* draw horizontal list */ 361 | dc->x += inputw; 362 | dc->w = textw(dc, "<"); 363 | if(curr->left) 364 | drawtext(dc, "<", normcol); 365 | for(item = curr; item != next; item = item->right) { 366 | dc->x += dc->w; 367 | dc->w = MIN(textw(dc, item->text), mw - dc->x - textw(dc, ">")); 368 | drawtext(dc, item->text, (item == sel) ? selcol : normcol); 369 | } 370 | dc->w = textw(dc, ">"); 371 | dc->x = mw - dc->w; 372 | if(next) 373 | drawtext(dc, ">", normcol); 374 | } 375 | } 376 | mapdc(dc, win, mw, mh, border_width); 377 | } 378 | 379 | void 380 | grabkeyboard(void) { 381 | int i; 382 | 383 | /* try to grab keyboard, we may have to wait for another process to ungrab */ 384 | for(i = 0; i < 1000; i++) { 385 | if(XGrabKeyboard(dc->dpy, DefaultRootWindow(dc->dpy), True, 386 | GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) 387 | return; 388 | usleep(1000); 389 | } 390 | eprintf("cannot grab keyboard\n"); 391 | } 392 | 393 | void grabpointer(void) { 394 | int i; 395 | 396 | /* try to grab pointer, we may have to wait for another process to ungrab */ 397 | for (i = 0; i < 1000; i++) { 398 | if (XGrabPointer(dc->dpy, DefaultRootWindow(dc->dpy), True, ButtonPress, 399 | GrabModeAsync, GrabModeAsync, None, None, CurrentTime) == GrabSuccess) 400 | return; 401 | usleep(1000); 402 | } 403 | eprintf("cannot grab pointer\n"); 404 | } 405 | 406 | void 407 | insert(const char *str, ssize_t n) { 408 | if(strlen(text) + n > sizeof text - 1) 409 | return; 410 | /* move existing text out of the way, insert new text, and update cursor */ 411 | memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); 412 | if(n > 0) 413 | memcpy(&text[cursor], str, n); 414 | cursor += n; 415 | match(); 416 | } 417 | 418 | void 419 | keypress(XKeyEvent *ev) { 420 | char buf[32]; 421 | int len; 422 | KeySym ksym = NoSymbol; 423 | Status status; 424 | 425 | len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); 426 | if(status == XBufferOverflow) 427 | return; 428 | if(ev->state & ControlMask) 429 | switch(ksym) { 430 | case XK_a: ksym = XK_Home; break; 431 | case XK_b: ksym = XK_Left; break; 432 | case XK_c: ksym = XK_Escape; break; 433 | case XK_d: ksym = XK_Delete; break; 434 | case XK_e: ksym = XK_End; break; 435 | case XK_f: ksym = XK_Right; break; 436 | case XK_g: ksym = XK_Escape; break; 437 | case XK_h: ksym = XK_BackSpace; break; 438 | case XK_i: ksym = XK_Tab; break; 439 | case XK_j: /* fallthrough */ 440 | case XK_J: ksym = XK_Return; break; 441 | case XK_m: /* fallthrough */ 442 | case XK_M: ksym = XK_Return; break; 443 | case XK_n: ksym = XK_Down; break; 444 | case XK_p: ksym = XK_Up; break; 445 | 446 | case XK_k: /* delete right */ 447 | text[cursor] = '\0'; 448 | match(); 449 | break; 450 | case XK_u: /* delete left */ 451 | insert(NULL, 0 - cursor); 452 | break; 453 | case XK_w: /* delete word */ 454 | while(cursor > 0 && text[nextrune(-1)] == ' ') 455 | insert(NULL, nextrune(-1) - cursor); 456 | while(cursor > 0 && text[nextrune(-1)] != ' ' && text[nextrune(-1)] != '/') 457 | insert(NULL, nextrune(-1) - cursor); 458 | break; 459 | case XK_y: /* fallthrough */ 460 | case XK_Y: /* paste selection */ 461 | XConvertSelection(dc->dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 462 | utf8, utf8, win, CurrentTime); 463 | return; 464 | default: 465 | return; 466 | } 467 | else if(ev->state & Mod1Mask) 468 | switch(ksym) { 469 | case XK_g: ksym = XK_Home; break; 470 | case XK_G: ksym = XK_End; break; 471 | case XK_h: ksym = XK_Up; break; 472 | case XK_j: ksym = XK_Next; break; 473 | case XK_k: ksym = XK_Prior; break; 474 | case XK_l: ksym = XK_Down; break; 475 | default: 476 | return; 477 | } 478 | switch(ksym) { 479 | default: 480 | if(!iscntrl(*buf)) 481 | insert(buf, len); 482 | break; 483 | case XK_Delete: 484 | if(text[cursor] == '\0') 485 | return; 486 | cursor = nextrune(+1); 487 | /* fallthrough */ 488 | case XK_BackSpace: 489 | if(cursor == 0) 490 | return; 491 | insert(NULL, nextrune(-1) - cursor); 492 | break; 493 | case XK_End: 494 | if(text[cursor] != '\0') { 495 | cursor = strlen(text); 496 | break; 497 | } 498 | if(next) { 499 | /* jump to end of list and position items in reverse */ 500 | curr = matchend; 501 | calcoffsets(); 502 | curr = prev; 503 | calcoffsets(); 504 | while(next && (curr = curr->right)) 505 | calcoffsets(); 506 | } 507 | sel = matchend; 508 | break; 509 | case XK_Escape: 510 | ret = EXIT_FAILURE; 511 | running = False; 512 | case XK_Home: 513 | if(sel == matches) { 514 | cursor = 0; 515 | break; 516 | } 517 | sel = curr = matches; 518 | calcoffsets(); 519 | break; 520 | case XK_Left: 521 | if(cursor > 0 && (!sel || !sel->left || lines > 0)) { 522 | cursor = nextrune(-1); 523 | break; 524 | } 525 | if(lines > 0) 526 | return; 527 | /* fallthrough */ 528 | case XK_Up: 529 | if(sel && sel->left && (sel = sel->left)->right == curr) { 530 | curr = prev; 531 | calcoffsets(); 532 | } 533 | break; 534 | case XK_Next: 535 | if(!next) 536 | return; 537 | sel = curr = next; 538 | calcoffsets(); 539 | break; 540 | case XK_Prior: 541 | if(!prev) 542 | return; 543 | sel = curr = prev; 544 | calcoffsets(); 545 | break; 546 | case XK_Return: 547 | case XK_KP_Enter: 548 | if((ev->state & ShiftMask) || !sel) 549 | puts(text); 550 | else if(!filter) 551 | puts(sel->text); 552 | else { 553 | for(Item *item = sel; item; item = item->right) 554 | puts(item->text); 555 | for(Item *item = matches; item != sel; item = item->right) 556 | puts(item->text); 557 | } 558 | ret = EXIT_SUCCESS; 559 | running = False; 560 | case XK_Right: 561 | if(text[cursor] != '\0') { 562 | cursor = nextrune(+1); 563 | break; 564 | } 565 | if(lines > 0) 566 | return; 567 | /* fallthrough */ 568 | case XK_Down: 569 | if(sel && sel->right && (sel = sel->right) == next) { 570 | curr = next; 571 | calcoffsets(); 572 | } 573 | break; 574 | case XK_Tab: 575 | if(!sel) 576 | return; 577 | if(strcmp(text, sel->text)) { 578 | strncpy(originaltext, text, sizeof originaltext); 579 | strncpy(text, sel->text, sizeof text); 580 | cursor = strlen(text); 581 | } else { 582 | if(sel && sel->right) { 583 | sel = sel->right; 584 | strncpy(text, sel->text, sizeof text); 585 | cursor = strlen(text); 586 | } 587 | else { 588 | strncpy(text, originaltext, sizeof text); 589 | cursor = strlen(text); 590 | match(); 591 | } 592 | } 593 | break; 594 | case XK_ISO_Left_Tab: 595 | if(!sel) 596 | return; 597 | if(strcmp(text, sel->text)) { 598 | sel = matchend; 599 | strncpy(originaltext, text, sizeof originaltext); 600 | strncpy(text, sel->text, sizeof text); 601 | cursor = strlen(text); 602 | } else { 603 | if(sel && sel->left) { 604 | sel = sel->left; 605 | strncpy(text, sel->text, sizeof text); 606 | cursor = strlen(text); 607 | } 608 | else { 609 | strncpy(text, originaltext, sizeof text); 610 | cursor = strlen(text); 611 | match(); 612 | } 613 | } 614 | break; 615 | } 616 | drawmenu(); 617 | } 618 | 619 | char * 620 | strchri(const char *s, int c) { 621 | char *u, *l; 622 | if(!isalpha(c)) return strchr(s, c); 623 | if(isupper(c)) { 624 | u = strchr(s, c); 625 | l = strchr(s, tolower(c)); 626 | } 627 | else { 628 | l = strchr(s, c); 629 | u = strchr(s, toupper(c)); 630 | } 631 | 632 | return u == NULL? l : u; 633 | } 634 | 635 | void 636 | pointermove(XEvent *e) { 637 | int curpos; 638 | Item *item; 639 | XPointerMovedEvent *ev = &e->xmotion; 640 | 641 | dc->x = 0; 642 | dc->y = 0; 643 | dc->h = bh; 644 | 645 | if (prompt && *prompt) { 646 | dc->w = promptw; 647 | dc->x = dc->w; 648 | } 649 | 650 | dc->w = (lines > 0 || !matches) ? mw - dc->x : inputw; 651 | if((curpos = textnw(dc, text, cursor) +dc->h/2 - 2) < dc->w); 652 | 653 | if(lines > 0) { 654 | /* vertical list: highlight */ 655 | dc->w = mw - dc->x; 656 | for(item = curr; item != next; item = item->right) { 657 | dc->y += dc->h; 658 | if(ev->y >= dc->y && ev->y <= (dc->y + dc->h)) { 659 | sel = item; 660 | drawmenu(); 661 | } 662 | } 663 | } 664 | else if(matches) { 665 | /* reached left end, page back */ 666 | dc->x += inputw; 667 | dc->w = textw(dc, "<"); 668 | if(prev && curr->left) { 669 | if(ev->x >= dc->x && ev->x <= dc->x + dc->w) { 670 | sel = curr = prev; 671 | calcoffsets(); 672 | drawmenu(); 673 | return; 674 | } 675 | } 676 | /* horizontal list: highlight */ 677 | for(item = curr; item != next; item = item->right) { 678 | dc->x += dc->w; 679 | dc->w = MIN(textw(dc, item->text), mw - dc->x - textw(dc, ">")); 680 | if(ev->x >= dc->x && ev->x <= (dc->x + dc->w)) { 681 | sel = item; 682 | drawmenu(); 683 | } 684 | } 685 | /* reached right end, page forward */ 686 | dc->w = textw(dc, ">"); 687 | dc->x = mw - dc->w; 688 | if(next && ev->x >= dc->x && ev->x <= dc->x + dc->w) { 689 | sel = curr = next; 690 | calcoffsets(); 691 | drawmenu(); 692 | return; 693 | } 694 | } 695 | } 696 | 697 | void 698 | buttonpress(XEvent *e) { 699 | int curpos; 700 | Item *item; 701 | XButtonPressedEvent *ev = &e->xbutton; 702 | 703 | if(ev->window != win) 704 | exit(EXIT_FAILURE); 705 | 706 | /* right-click: exit */ 707 | if(ev->button == Button3) 708 | exit(EXIT_FAILURE); 709 | 710 | dc->x = 0; 711 | dc->y = 0; 712 | dc->h = bh; 713 | 714 | if(prompt && *prompt) { 715 | dc->w = promptw; 716 | dc->x = dc->w; 717 | } 718 | /* input field */ 719 | dc->w = (lines > 0 || !matches) ? mw - dc->x : inputw; 720 | if((curpos = textnw(dc, text, cursor) + dc->h/2 - 2) < dc->w); 721 | 722 | /* left-click on input: clear input, 723 | * NOTE: if there is no left-arrow the space for < is reserved so 724 | * add that to the input width */ 725 | if(ev->button == Button1 && 726 | ((lines <= 0 && ev->x >= 0 && ev->x <= dc->x + dc->w + 727 | ((!prev || !curr->left) ? textw(dc, "<") : 0)) || 728 | (lines > 0 && ev->y >= dc->y && ev->y <= dc->y + dc->h))) { 729 | insert(NULL, 0 - cursor); 730 | drawmenu(); 731 | return; 732 | } 733 | /* middle-mouse click: paste selection */ 734 | if(ev->button == Button2) { 735 | XConvertSelection(dc->dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 736 | utf8, utf8, win, CurrentTime); 737 | drawmenu(); 738 | return; 739 | } 740 | /* scroll up */ 741 | if(ev->button == Button4 && prev) { 742 | sel = curr = prev; 743 | calcoffsets(); 744 | drawmenu(); 745 | return; 746 | } 747 | /* scroll down */ 748 | if(ev->button == Button5 && next) { 749 | sel = curr = next; 750 | calcoffsets(); 751 | drawmenu(); 752 | return; 753 | } 754 | if(ev->button != Button1) 755 | return; 756 | if(lines > 0) { 757 | /* vertical list: left-click on item */ 758 | dc->w = mw - dc->x; 759 | for(item = curr; item != next; item = item->right) { 760 | dc->y += dc->h; 761 | if(ev->y >= dc->y && ev->y <= (dc->y + dc->h)) { 762 | puts(item->text); 763 | exit(EXIT_SUCCESS); 764 | } 765 | } 766 | } 767 | else if(matches) { 768 | /* left-click on left arrow */ 769 | dc->x += inputw; 770 | dc->w = textw(dc, "<"); 771 | if(prev && curr->left) { 772 | if(ev->x >= dc->x && ev->x <= dc->x + dc->w) { 773 | sel = curr = prev; 774 | calcoffsets(); 775 | drawmenu(); 776 | return; 777 | } 778 | } 779 | /* horizontal list: left-click on item */ 780 | for(item = curr; item != next; item = item->right) { 781 | dc->x += dc->w; 782 | dc->w = MIN(textw(dc, item->text), mw - dc->x - textw(dc, ">")); 783 | if(ev->x >= dc->x && ev->x <= (dc->x + dc->w)) { 784 | puts(item->text); 785 | exit(EXIT_SUCCESS); 786 | } 787 | } 788 | /* left-click on right arrow */ 789 | dc->w = textw(dc, ">"); 790 | dc->x = mw - dc->w; 791 | if(next && ev->x >= dc->x && ev->x <= dc->x + dc->w) { 792 | sel = curr = next; 793 | calcoffsets(); 794 | drawmenu(); 795 | return; 796 | } 797 | } 798 | } 799 | 800 | void 801 | matchstr(void) { 802 | static char **tokv = NULL; 803 | static int tokn = 0; 804 | 805 | char buf[sizeof text], *s; 806 | int i, tokc = 0; 807 | size_t len; 808 | Item *item, *lprefix, *lsubstr, *prefixend, *substrend; 809 | 810 | strcpy(buf, text); 811 | /* separate input text into tokens to be matched individually */ 812 | for(s = strtok(buf, " "); s; tokv[tokc-1] = s, s = strtok(NULL, " ")) 813 | if(++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) 814 | eprintf("cannot realloc %u bytes\n", tokn * sizeof *tokv); 815 | len = tokc ? strlen(tokv[0]) : 0; 816 | 817 | matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; 818 | for(item = items; item && item->text; item++) { 819 | for(i = 0; i < tokc; i++) 820 | if(!fstrstr(item->text, tokv[i])) 821 | break; 822 | if(i != tokc) /* not all tokens match */ 823 | continue; 824 | /* exact matches go first, then prefixes, then substrings */ 825 | if(!tokc || !fstrncmp(tokv[0], item->text, len+1)) 826 | appenditem(item, &matches, &matchend); 827 | else if(!fstrncmp(tokv[0], item->text, len)) 828 | appenditem(item, &lprefix, &prefixend); 829 | else 830 | appenditem(item, &lsubstr, &substrend); 831 | } 832 | if(lprefix) { 833 | if(matches) { 834 | matchend->right = lprefix; 835 | lprefix->left = matchend; 836 | } 837 | else 838 | matches = lprefix; 839 | matchend = prefixend; 840 | } 841 | if(lsubstr) { 842 | if(matches) { 843 | matchend->right = lsubstr; 844 | lsubstr->left = matchend; 845 | } 846 | else 847 | matches = lsubstr; 848 | matchend = substrend; 849 | } 850 | curr = sel = matches; 851 | calcoffsets(); 852 | } 853 | 854 | void 855 | matchtok(void) { 856 | char buf[sizeof text]; 857 | char **tokv, *s; 858 | int tokc, i; 859 | Item *item, *end; 860 | 861 | tokc = 0; 862 | tokv = NULL; 863 | strcpy(buf, text); 864 | for(s = strtok(buf, " "); s; tokv[tokc-1] = s, s = strtok(NULL, " ")) 865 | if(!(tokv = realloc(tokv, ++tokc * sizeof *tokv))) 866 | eprintf("cannot realloc %u bytes\n", tokc * sizeof *tokv); 867 | 868 | matches = end = NULL; 869 | for(item = items; item->text; item++) { 870 | for(i = 0; i < tokc; i++) 871 | if(!fstrstr(item->text, tokv[i])) 872 | break; 873 | if(i == tokc) 874 | appenditem(item, &matches, &end); 875 | } 876 | free(tokv); 877 | curr = prev = next = sel = matches; 878 | calcoffsets(); 879 | } 880 | 881 | void 882 | matchfuzzy(void) { 883 | int i; 884 | size_t len; 885 | Item *item; 886 | char *pos; 887 | 888 | len = strlen(text); 889 | matches = matchend = NULL; 890 | for(item = items; item && item->text; item++) { 891 | i = 0; 892 | for(pos = fstrchr(item->text, text[i]); pos && text[i]; i++, pos = fstrchr(pos+1, text[i])); 893 | if(i == len) appenditem(item, &matches, &matchend); 894 | } 895 | 896 | curr = sel = matches; 897 | calcoffsets(); 898 | } 899 | 900 | size_t 901 | nextrune(int inc) { 902 | ssize_t n; 903 | 904 | /* return location of next utf8 rune in the given direction (+1 or -1) */ 905 | for(n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc); 906 | return n; 907 | } 908 | 909 | /* UTF-8 length for password */ 910 | size_t 911 | utf8length() 912 | { 913 | ssize_t n = cursor - 1, length = 0; 914 | 915 | while (n >= 0) { 916 | for (; n - 1 >= 0 && (text[n] & 0xc0) == 0x80; n--) 917 | ; 918 | n--; 919 | length++; 920 | } 921 | 922 | return (length); 923 | } 924 | 925 | void 926 | paste(void) { 927 | char *p, *q; 928 | int di; 929 | unsigned long dl; 930 | Atom da; 931 | 932 | /* we have been given the current selection, now insert it into input */ 933 | XGetWindowProperty(dc->dpy, win, utf8, 0, (sizeof text / 4) + 1, False, 934 | utf8, &da, &di, &dl, &dl, (unsigned char **)&p); 935 | insert(p, (q = strchr(p, '\n')) ? q-p : (ssize_t)strlen(p)); 936 | XFree(p); 937 | drawmenu(); 938 | } 939 | 940 | void 941 | readstdin(void) { 942 | char buf[sizeof text], *p, *maxstr = NULL; 943 | size_t i, max = 0, size = 0; 944 | 945 | /* read each line from stdin and add it to the item list */ 946 | for(i = 0; fgets(buf, sizeof buf, stdin); i++) { 947 | if(i+1 >= size / sizeof *items) 948 | if(!(items = realloc(items, (size += BUFSIZ)))) 949 | eprintf("cannot realloc %u bytes:", size); 950 | if((p = strchr(buf, '\n'))) 951 | *p = '\0'; 952 | if(!(items[i].text = strdup(buf))) 953 | eprintf("cannot strdup %u bytes:", strlen(buf)+1); 954 | if(strlen(items[i].text) > max) 955 | max = strlen(maxstr = items[i].text); 956 | } 957 | if(items) 958 | items[i].text = NULL; 959 | inputw = maxstr ? textw(dc, maxstr) : 0; 960 | lines = MIN(lines, i); 961 | } 962 | 963 | void 964 | run(void) { 965 | XEvent ev; 966 | 967 | while(running && !XNextEvent(dc->dpy, &ev)) { 968 | if(XFilterEvent(&ev, win)) 969 | continue; 970 | switch(ev.type) { 971 | case MotionNotify: 972 | while(XCheckTypedEvent(dc->dpy, MotionNotify, &ev)) 973 | (void)0; 974 | pointermove(&ev); 975 | break; 976 | case ButtonPress: 977 | buttonpress(&ev); 978 | break; 979 | case Expose: 980 | if(ev.xexpose.count == 0) 981 | mapdc(dc, win, mw, mh, border_width); 982 | break; 983 | case KeyPress: 984 | keypress(&ev.xkey); 985 | break; 986 | case SelectionNotify: 987 | if(ev.xselection.property == utf8) 988 | paste(); 989 | break; 990 | case VisibilityNotify: 991 | if(ev.xvisibility.state != VisibilityUnobscured) 992 | XRaiseWindow(dc->dpy, win); 993 | break; 994 | } 995 | } 996 | } 997 | 998 | void 999 | setup(void) { 1000 | int x, y, screen = DefaultScreen(dc->dpy); 1001 | Screen *defScreen = DefaultScreenOfDisplay(dc->dpy); 1002 | int dimx, dimy, dimw, dimh; 1003 | Window root = RootWindow(dc->dpy, screen); 1004 | XSetWindowAttributes swa; 1005 | XIM xim; 1006 | 1007 | #ifdef XINERAMA 1008 | int n; 1009 | XineramaScreenInfo *info; 1010 | #endif 1011 | 1012 | clip = XInternAtom(dc->dpy, "CLIPBOARD", False); 1013 | utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False); 1014 | 1015 | /* calculate menu geometry */ 1016 | bh = (line_height > dc->font.height + 2) ? line_height : dc->font.height + 2; 1017 | lines = MAX(lines, 0); 1018 | mh = (lines + 1) * bh; 1019 | #ifdef XINERAMA 1020 | if((info = XineramaQueryScreens(dc->dpy, &n))) { 1021 | int a, j, di, i = 0, area = 0; 1022 | unsigned int du; 1023 | Window w, pw, dw, *dws; 1024 | XWindowAttributes wa; 1025 | 1026 | if(snum > -1 && snum < n) { 1027 | x = info[snum].x_org; 1028 | y = info[snum].y_org + (topbar ? yoffset : info[i].height - mh - yoffset); 1029 | mw = info[snum].width; 1030 | 1031 | dimx = info[snum].x_org; 1032 | dimy = info[snum].y_org; 1033 | dimw = info[snum].width; 1034 | dimh = info[snum].height; 1035 | } 1036 | else { 1037 | XGetInputFocus(dc->dpy, &w, &di); 1038 | if(w != root && w != PointerRoot && w != None) { 1039 | /* find top-level window containing current input focus */ 1040 | do { 1041 | if(XQueryTree(dc->dpy, (pw = w), &dw, &w, &dws, &du) && dws) 1042 | XFree(dws); 1043 | } while(w != root && w != pw); 1044 | /* find xinerama screen with which the window intersects most */ 1045 | if(XGetWindowAttributes(dc->dpy, pw, &wa)) 1046 | for(j = 0; j < n; j++) 1047 | if((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { 1048 | area = a; 1049 | i = j; 1050 | } 1051 | } 1052 | /* no focused window is on screen, so use pointer location instead */ 1053 | if(!area && XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) 1054 | for(i = 0; i < n; i++) 1055 | if(INTERSECT(x, y, 1, 1, info[i])) 1056 | break; 1057 | 1058 | x = info[i].x_org; 1059 | y = info[i].y_org + (topbar ? yoffset : info[i].height - mh - yoffset); 1060 | mw = info[i].width; 1061 | 1062 | dimx = info[i].x_org; 1063 | dimy = info[i].y_org; 1064 | dimw = info[i].width; 1065 | dimh = info[i].height; 1066 | } 1067 | XFree(info); 1068 | } 1069 | else 1070 | #endif 1071 | { 1072 | x = 0; 1073 | y = topbar ? 0 : DisplayHeight(dc->dpy, screen) - mh - yoffset; 1074 | mw = DisplayWidth(dc->dpy, screen); 1075 | 1076 | dimx = 0; 1077 | dimy = 0; 1078 | dimw = WidthOfScreen(defScreen); 1079 | dimh = HeightOfScreen(defScreen); 1080 | } 1081 | 1082 | x += xoffset; 1083 | mw = width ? width : mw; 1084 | promptw = (prompt && *prompt) ? textw(dc, prompt) : 0; 1085 | inputw = MIN(inputw, mw/3); 1086 | match(); 1087 | 1088 | swa.override_redirect = True; 1089 | 1090 | /* create dim window */ 1091 | if(dimopacity > 0) { 1092 | swa.background_pixel = dimcol->BG; 1093 | swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; 1094 | dim = XCreateWindow(dc->dpy, root, dimx, dimy, dimw, dimh, 0, 1095 | DefaultDepth(dc->dpy, screen), CopyFromParent, 1096 | DefaultVisual(dc->dpy, screen), 1097 | CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); 1098 | XClassHint dimhint = { .res_name = dimname, .res_class = class }; 1099 | XSetClassHint(dc->dpy, dim, &dimhint); 1100 | 1101 | dimopacity = MIN(MAX(dimopacity, 0), 1); 1102 | unsigned int dimopacity_set = (unsigned int)(dimopacity * OPAQUE); 1103 | XChangeProperty(dc->dpy, dim, XInternAtom(dc->dpy, OPACITY, False), 1104 | XA_CARDINAL, 32, PropModeReplace, 1105 | (unsigned char *) &dimopacity_set, 1L); 1106 | 1107 | XMapRaised(dc->dpy, dim); 1108 | } 1109 | 1110 | /* create menu window */ 1111 | swa.background_pixel = border_width ? bordercol->BG : normcol->BG; 1112 | swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask | PointerMotionMask; 1113 | win = XCreateWindow(dc->dpy, root, x, y, mw + border_width * 2, mh + border_width * 2, 0, 1114 | DefaultDepth(dc->dpy, screen), CopyFromParent, 1115 | DefaultVisual(dc->dpy, screen), 1116 | CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); 1117 | XClassHint hint = { .res_name = name, .res_class = class }; 1118 | XSetClassHint(dc->dpy, win, &hint); 1119 | 1120 | opacity = MIN(MAX(opacity, 0), 1); 1121 | unsigned int opacity_set = (unsigned int)(opacity * OPAQUE); 1122 | XChangeProperty(dc->dpy, win, XInternAtom(dc->dpy, OPACITY, False), 1123 | XA_CARDINAL, 32, PropModeReplace, 1124 | (unsigned char *) &opacity_set, 1L); 1125 | 1126 | /* open input methods */ 1127 | xim = XOpenIM(dc->dpy, NULL, NULL, NULL); 1128 | xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 1129 | XNClientWindow, win, XNFocusWindow, win, NULL); 1130 | 1131 | XMapRaised(dc->dpy, win); 1132 | resizedc(dc, mw, mh); 1133 | drawmenu(); 1134 | } 1135 | 1136 | void 1137 | usage(void) { 1138 | fputs("usage: dmenu [-b] [-q] [-f] [-r] [-i] [-z] [-t] [-mask] [-noinput]\n" 1139 | " [-s screen] [-name name] [-class class] [ -o opacity]\n" 1140 | " [-dim opcity] [-dc color] [-l lines] [-p prompt] [-fn font]\n" 1141 | " [-x xoffset] [-y yoffset] [-h height] [-w width] [-bw pixels] \n" 1142 | " [-nb color] [-nf color] [-sb color] [-sf color] [-bc color] [-v]\n", stderr); 1143 | exit(EXIT_FAILURE); 1144 | } 1145 | -------------------------------------------------------------------------------- /dmenu_run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"} 3 | if [ -d "$cachedir" ]; then 4 | cache=$cachedir/dmenu_run 5 | else 6 | cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~ 7 | fi 8 | ( 9 | IFS=: 10 | if stest -dqr -n "$cache" $PATH; then 11 | stest -flx $PATH | sort -u | tee "$cache" | dmenu "$@" 12 | else 13 | dmenu "$@" < "$cache" 14 | fi 15 | ) | ${SHELL:-"/bin/sh"} & 16 | -------------------------------------------------------------------------------- /draw.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "draw.h" 9 | 10 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 11 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 12 | 13 | void 14 | drawrect(DC *dc, int x, int y, unsigned int w, unsigned int h, Bool fill, unsigned long color) { 15 | XSetForeground(dc->dpy, dc->gc, color); 16 | if(fill) 17 | XFillRectangle(dc->dpy, dc->canvas, dc->gc, dc->x + x, dc->y + y, w, h); 18 | else 19 | XDrawRectangle(dc->dpy, dc->canvas, dc->gc, dc->x + x, dc->y + y, w-1, h-1); 20 | } 21 | 22 | void 23 | drawtext(DC *dc, const char *text, ColorSet *col) { 24 | char buf[BUFSIZ]; 25 | size_t mn, n = strlen(text); 26 | 27 | /* shorten text if necessary */ 28 | for(mn = MIN(n, sizeof buf); textnw(dc, text, mn) + dc->font.height/2 > dc->w; mn--) 29 | if(mn == 0) 30 | return; 31 | memcpy(buf, text, mn); 32 | if(mn < n) 33 | for(n = MAX(mn-3, 0); n < mn; buf[n++] = '.'); 34 | 35 | drawrect(dc, 0, 0, dc->w, dc->h, True, col->BG); 36 | drawtextn(dc, buf, mn, col); 37 | } 38 | 39 | void 40 | drawtextn(DC *dc, const char *text, size_t n, ColorSet *col) { 41 | int x = dc->x + dc->font.height/2; 42 | int y = dc->y + dc->font.ascent + (dc->h - dc->font.height)/2; 43 | 44 | XSetForeground(dc->dpy, dc->gc, col->FG); 45 | if(dc->font.xft_font) { 46 | if (!dc->xftdraw) 47 | eprintf("error, xft drawable does not exist"); 48 | XftDrawStringUtf8(dc->xftdraw, &col->FG_xft, 49 | dc->font.xft_font, x, y, (unsigned char*)text, n); 50 | } else if(dc->font.set) { 51 | XmbDrawString(dc->dpy, dc->canvas, dc->font.set, dc->gc, x, y, text, n); 52 | } else { 53 | XSetFont(dc->dpy, dc->gc, dc->font.xfont->fid); 54 | XDrawString(dc->dpy, dc->canvas, dc->gc, x, y, text, n); 55 | } 56 | } 57 | 58 | void 59 | eprintf(const char *fmt, ...) { 60 | va_list ap; 61 | 62 | va_start(ap, fmt); 63 | vfprintf(stderr, fmt, ap); 64 | va_end(ap); 65 | 66 | if(fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') { 67 | fputc(' ', stderr); 68 | perror(NULL); 69 | } 70 | exit(EXIT_FAILURE); 71 | } 72 | 73 | void 74 | freecol(DC *dc, ColorSet *col) { 75 | if(col) { 76 | if(&col->FG_xft) 77 | XftColorFree(dc->dpy, DefaultVisual(dc->dpy, DefaultScreen(dc->dpy)), 78 | DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)), &col->FG_xft); 79 | free(col); 80 | } 81 | } 82 | 83 | void 84 | freedc(DC *dc) { 85 | if(dc->font.xft_font) { 86 | XftFontClose(dc->dpy, dc->font.xft_font); 87 | XftDrawDestroy(dc->xftdraw); 88 | } 89 | if(dc->font.set) 90 | XFreeFontSet(dc->dpy, dc->font.set); 91 | if(dc->font.xfont) 92 | XFreeFont(dc->dpy, dc->font.xfont); 93 | if(dc->canvas) 94 | XFreePixmap(dc->dpy, dc->canvas); 95 | if(dc->gc) 96 | XFreeGC(dc->dpy, dc->gc); 97 | if(dc->dpy) 98 | XCloseDisplay(dc->dpy); 99 | if(dc) 100 | free(dc); 101 | } 102 | 103 | unsigned long 104 | getcolor(DC *dc, const char *colstr) { 105 | Colormap cmap = DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)); 106 | XColor color; 107 | 108 | if(!XAllocNamedColor(dc->dpy, cmap, colstr, &color, &color)) 109 | eprintf("cannot allocate color '%s'\n", colstr); 110 | return color.pixel; 111 | } 112 | 113 | ColorSet * 114 | initcolor(DC *dc, const char * foreground, const char * background) { 115 | ColorSet * col = (ColorSet *)malloc(sizeof(ColorSet)); 116 | if(!col) 117 | eprintf("error, cannot allocate memory for color set"); 118 | col->BG = getcolor(dc, background); 119 | col->FG = getcolor(dc, foreground); 120 | if(dc->font.xft_font) 121 | if(!XftColorAllocName(dc->dpy, DefaultVisual(dc->dpy, DefaultScreen(dc->dpy)), 122 | DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)), foreground, &col->FG_xft)) 123 | eprintf("error, cannot allocate xft font color '%s'\n", foreground); 124 | return col; 125 | } 126 | 127 | DC * 128 | initdc(void) { 129 | DC *dc; 130 | 131 | if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 132 | fputs("no locale support\n", stderr); 133 | if(!(dc = calloc(1, sizeof *dc))) 134 | eprintf("cannot malloc %u bytes:", sizeof *dc); 135 | if(!(dc->dpy = XOpenDisplay(NULL))) 136 | eprintf("cannot open display\n"); 137 | 138 | dc->gc = XCreateGC(dc->dpy, DefaultRootWindow(dc->dpy), 0, NULL); 139 | XSetLineAttributes(dc->dpy, dc->gc, 1, LineSolid, CapButt, JoinMiter); 140 | return dc; 141 | } 142 | 143 | void 144 | initfont(DC *dc, const char *fontstr) { 145 | char *def, **missing, **names; 146 | int i, n; 147 | XFontStruct **xfonts; 148 | 149 | missing = NULL; 150 | if((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr))) { 151 | dc->font.ascent = dc->font.xfont->ascent; 152 | dc->font.descent = dc->font.xfont->descent; 153 | dc->font.width = dc->font.xfont->max_bounds.width; 154 | } else if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def))) { 155 | n = XFontsOfFontSet(dc->font.set, &xfonts, &names); 156 | for(i = 0; i < n; i++) { 157 | dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent); 158 | dc->font.descent = MAX(dc->font.descent, xfonts[i]->descent); 159 | dc->font.width = MAX(dc->font.width, xfonts[i]->max_bounds.width); 160 | } 161 | } else if((dc->font.xft_font = XftFontOpenName(dc->dpy, DefaultScreen(dc->dpy), fontstr))) { 162 | dc->font.ascent = dc->font.xft_font->ascent; 163 | dc->font.descent = dc->font.xft_font->descent; 164 | dc->font.width = dc->font.xft_font->max_advance_width; 165 | } else { 166 | eprintf("cannot load font '%s'\n", fontstr); 167 | } 168 | if(missing) 169 | XFreeStringList(missing); 170 | dc->font.height = dc->font.ascent + dc->font.descent; 171 | return; 172 | } 173 | 174 | void 175 | mapdc(DC *dc, Window win, unsigned int w, unsigned int h, int offset) { 176 | XCopyArea(dc->dpy, dc->canvas, win, dc->gc, 0, 0, w, h, offset, offset); 177 | } 178 | 179 | void 180 | resizedc(DC *dc, unsigned int w, unsigned int h) { 181 | int screen = DefaultScreen(dc->dpy); 182 | if(dc->canvas) 183 | XFreePixmap(dc->dpy, dc->canvas); 184 | 185 | dc->w = w; 186 | dc->h = h; 187 | dc->canvas = XCreatePixmap(dc->dpy, DefaultRootWindow(dc->dpy), w, h, 188 | DefaultDepth(dc->dpy, screen)); 189 | if(dc->font.xft_font && !(dc->xftdraw)) { 190 | dc->xftdraw = XftDrawCreate(dc->dpy, dc->canvas, DefaultVisual(dc->dpy,screen), DefaultColormap(dc->dpy,screen)); 191 | if(!(dc->xftdraw)) 192 | eprintf("error, cannot create xft drawable\n"); 193 | } 194 | } 195 | 196 | int 197 | textnw(DC *dc, const char *text, size_t len) { 198 | if(dc->font.xft_font) { 199 | XGlyphInfo gi; 200 | XftTextExtentsUtf8(dc->dpy, dc->font.xft_font, (const FcChar8*)text, len, &gi); 201 | return gi.width; 202 | } else if(dc->font.set) { 203 | XRectangle r; 204 | XmbTextExtents(dc->font.set, text, len, NULL, &r); 205 | return r.width; 206 | } 207 | return XTextWidth(dc->font.xfont, text, len); 208 | } 209 | 210 | int 211 | textw(DC *dc, const char *text) { 212 | return textnw(dc, text, strlen(text)) + dc->font.height; 213 | } 214 | -------------------------------------------------------------------------------- /draw.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | #include 4 | 5 | typedef struct { 6 | int x, y, w, h; 7 | Bool invert; 8 | Display *dpy; 9 | GC gc; 10 | Pixmap canvas; 11 | XftDraw *xftdraw; 12 | struct { 13 | int ascent; 14 | int descent; 15 | int height; 16 | int width; 17 | XFontSet set; 18 | XFontStruct *xfont; 19 | XftFont *xft_font; 20 | } font; 21 | } DC; /* draw context */ 22 | 23 | typedef struct { 24 | unsigned long FG; 25 | XftColor FG_xft; 26 | unsigned long BG; 27 | } ColorSet; 28 | 29 | void drawrect(DC *dc, int x, int y, unsigned int w, unsigned int h, Bool fill, unsigned long color); 30 | void drawtext(DC *dc, const char *text, ColorSet *col); 31 | void drawtextn(DC *dc, const char *text, size_t n, ColorSet *col); 32 | void freecol(DC *dc, ColorSet *col); 33 | void eprintf(const char *fmt, ...); 34 | void freedc(DC *dc); 35 | unsigned long getcolor(DC *dc, const char *colstr); 36 | ColorSet *initcolor(DC *dc, const char *foreground, const char *background); 37 | DC *initdc(void); 38 | void initfont(DC *dc, const char *fontstr); 39 | void mapdc(DC *dc, Window win, unsigned int w, unsigned int h, int offset); 40 | void resizedc(DC *dc, unsigned int w, unsigned int h); 41 | int textnw(DC *dc, const char *text, size_t len); 42 | int textw(DC *dc, const char *text); 43 | -------------------------------------------------------------------------------- /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 \-w 71 | Test that files are writable. 72 | .TP 73 | .B \-x 74 | Test that files are executable. 75 | .SH EXIT STATUS 76 | .TP 77 | .B 0 78 | At least one file passed all tests. 79 | .TP 80 | .B 1 81 | No files passed all tests. 82 | .TP 83 | .B 2 84 | An error occurred. 85 | .SH SEE ALSO 86 | .IR dmenu (1), 87 | .IR test (1) 88 | -------------------------------------------------------------------------------- /stest.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 | 10 | #define FLAG(x) (flag[(x)-'a']) 11 | 12 | static void test(const char *, const char *); 13 | 14 | static bool match = false; 15 | static bool flag[26]; 16 | static struct stat old, new; 17 | 18 | int 19 | main(int argc, char *argv[]) { 20 | struct dirent *d; 21 | char buf[BUFSIZ], *p; 22 | DIR *dir; 23 | int opt; 24 | 25 | while((opt = getopt(argc, argv, "abcdefghln:o:pqrsuwx")) != -1) 26 | switch(opt) { 27 | case 'n': /* newer than file */ 28 | case 'o': /* older than file */ 29 | if(!(FLAG(opt) = !stat(optarg, (opt == 'n' ? &new : &old)))) 30 | perror(optarg); 31 | break; 32 | default: /* miscellaneous operators */ 33 | FLAG(opt) = true; 34 | break; 35 | case '?': /* error: unknown flag */ 36 | fprintf(stderr, "usage: %s [-abcdefghlpqrsuwx] [-n file] [-o file] [file...]\n", argv[0]); 37 | exit(2); 38 | } 39 | if(optind == argc) 40 | while(fgets(buf, sizeof buf, stdin)) { 41 | if((p = strchr(buf, '\n'))) 42 | *p = '\0'; 43 | test(buf, buf); 44 | } 45 | for(; optind < argc; optind++) 46 | if(FLAG('l') && (dir = opendir(argv[optind]))) { 47 | /* test directory contents */ 48 | while((d = readdir(dir))) 49 | if(snprintf(buf, sizeof buf, "%s/%s", argv[optind], d->d_name) < sizeof buf) 50 | test(buf, d->d_name); 51 | closedir(dir); 52 | } 53 | else 54 | test(argv[optind], argv[optind]); 55 | 56 | return match ? 0 : 1; 57 | } 58 | 59 | void 60 | test(const char *path, const char *name) { 61 | struct stat st, ln; 62 | 63 | if(!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ 64 | && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ 65 | && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ 66 | && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ 67 | && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ 68 | && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ 69 | && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ 70 | && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ 71 | && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ 72 | && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ 73 | && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ 74 | && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ 75 | && (!FLAG('s') || st.st_size > 0) /* not empty */ 76 | && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ 77 | && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ 78 | && (!FLAG('x') || access(path, X_OK) == 0)) { /* executable */ 79 | if(FLAG('q')) 80 | exit(0); 81 | match = true; 82 | puts(name); 83 | } 84 | } 85 | --------------------------------------------------------------------------------