├── patch ├── multiselect.h ├── nonblockingstdin.h ├── fzfexpect.h ├── highpriority.h ├── dynamicoptions.h ├── navhistory.h ├── vi_mode.h ├── numbers.h ├── scroll.h ├── center.c ├── mousesupport.h ├── numbers.c ├── inputmethod.h ├── include.h ├── include.c ├── highpriority.c ├── fzfexpect.c ├── multiselect.c ├── nonblockingstdin.c ├── dynamicoptions.c ├── navhistory.c ├── highlight.c ├── fuzzymatch.c ├── vi_mode.c ├── xresources.c ├── scroll.c ├── inputmethod.c └── mousesupport.c ├── .gitignore ├── dmenu_run ├── dmenu_path ├── util.h ├── README ├── util.c ├── arg.h ├── config.mk ├── LICENSE ├── Makefile ├── stest.1 ├── stest.c ├── drw.h ├── dmenu.1 ├── config.def.h ├── README.md ├── patches.def.h ├── drw.c └── dmenu.c /patch/multiselect.h: -------------------------------------------------------------------------------- 1 | static int issel(size_t id); 2 | -------------------------------------------------------------------------------- /patch/nonblockingstdin.h: -------------------------------------------------------------------------------- 1 | static void readevent(); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | config.h 3 | patches.h 4 | dmenu 5 | stest 6 | -------------------------------------------------------------------------------- /patch/fzfexpect.h: -------------------------------------------------------------------------------- 1 | static void expect(char *expect, XKeyEvent *ev); 2 | -------------------------------------------------------------------------------- /patch/highpriority.h: -------------------------------------------------------------------------------- 1 | static int arrayhas(char **list, int length, char *item); 2 | -------------------------------------------------------------------------------- /patch/dynamicoptions.h: -------------------------------------------------------------------------------- 1 | static void refreshoptions(void); 2 | static void readstream(FILE* stream); 3 | -------------------------------------------------------------------------------- /patch/navhistory.h: -------------------------------------------------------------------------------- 1 | static void loadhistory(void); 2 | static void navhistory(int dir); 3 | static void savehistory(char *input); 4 | -------------------------------------------------------------------------------- /patch/vi_mode.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | KeySym ksym; 3 | unsigned int state; 4 | } Key; 5 | 6 | static void vi_keypress(KeySym ksym, const XKeyEvent *ev); 7 | -------------------------------------------------------------------------------- /patch/numbers.h: -------------------------------------------------------------------------------- 1 | #define NUMBERSMAXDIGITS 100 2 | #define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) + 1 3 | 4 | static void recalculatenumbers(void); 5 | -------------------------------------------------------------------------------- /patch/scroll.h: -------------------------------------------------------------------------------- 1 | enum { AlignL, AlignR }; 2 | 3 | int drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align); 4 | -------------------------------------------------------------------------------- /patch/center.c: -------------------------------------------------------------------------------- 1 | static int 2 | max_textw(void) 3 | { 4 | int len = 0; 5 | for (struct item *item = items; item && item->text; item++) 6 | len = MAX(TEXTW(item->text), len); 7 | return len; 8 | } 9 | -------------------------------------------------------------------------------- /patch/mousesupport.h: -------------------------------------------------------------------------------- 1 | static void buttonpress(XEvent *e); 2 | static void clickitem(struct item *item, XButtonEvent *ev); 3 | #if MOTION_SUPPORT_PATCH 4 | static void motionevent(XButtonEvent *ev); 5 | #endif // MOTION_SUPPORT_PATCH 6 | -------------------------------------------------------------------------------- /dmenu_run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export _JAVA_AWT_WM_NONREPARENTING=1 3 | dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & 4 | 5 | # Uncomment for the NAVHISTORY patch (and remove the exec above) 6 | #dmenu_path | dmenu -H "${XDG_CACHE_HOME:-$HOME/.cache/}/dmenu_run.hist" "$@" | ${SHELL:-"/bin/sh"} & -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /patch/numbers.c: -------------------------------------------------------------------------------- 1 | static char numbers[NUMBERSBUFSIZE] = ""; 2 | 3 | static void 4 | recalculatenumbers(void) 5 | { 6 | unsigned int numer = 0, denom = 0; 7 | struct item *item; 8 | if (matchend) { 9 | numer++; 10 | for (item = matchend; item && item->left; item = item->left) 11 | numer++; 12 | } 13 | for (item = items; item && item->text; item++) 14 | denom++; 15 | snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); 16 | } 17 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | #ifndef MAX 4 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 5 | #endif 6 | #ifndef MIN 7 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 8 | #endif 9 | #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) 10 | #define LENGTH(X) (sizeof (X) / sizeof (X)[0]) 11 | 12 | void die(const char *fmt, ...); 13 | void *ecalloc(size_t nmemb, size_t size); 14 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | dmenu - dynamic menu 2 | ==================== 3 | dmenu is an efficient dynamic menu for X. 4 | 5 | 6 | Requirements 7 | ------------ 8 | In order to build dmenu you need the Xlib header files. 9 | 10 | 11 | Installation 12 | ------------ 13 | Edit config.mk to match your local setup (dmenu is installed into 14 | the /usr/local namespace by default). 15 | 16 | Afterwards enter the following command to build and install dmenu 17 | (if necessary as root): 18 | 19 | make clean install 20 | 21 | 22 | Running dmenu 23 | ------------- 24 | See the man page for details. 25 | -------------------------------------------------------------------------------- /patch/inputmethod.h: -------------------------------------------------------------------------------- 1 | static int composing; 2 | static char preview[512] = ""; 3 | 4 | static size_t nextrunetext(const char *text, size_t position, int inc); 5 | static size_t runebytes(const char *text, size_t n); 6 | static size_t runechars(const char *text, size_t n); 7 | static void preeditcaret(XIC xic, XPointer clientdata, XPointer calldata); 8 | static int preeditstart(XIC xic, XPointer clientdata, XPointer calldata); 9 | static void preeditdone(XIC xic, XPointer clientdata, XPointer calldata); 10 | static void preeditdraw(XIC xic, XPointer clientdata, XPointer calldata); 11 | static void init_input_method(XIM xim); 12 | -------------------------------------------------------------------------------- /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 | die(const char *fmt, ...) 11 | { 12 | va_list ap; 13 | 14 | va_start(ap, fmt); 15 | vfprintf(stderr, fmt, ap); 16 | va_end(ap); 17 | 18 | if (fmt[0] && fmt[strlen(fmt)-1] == ':') { 19 | fputc(' ', stderr); 20 | perror(NULL); 21 | } else { 22 | fputc('\n', stderr); 23 | } 24 | 25 | exit(1); 26 | } 27 | 28 | void * 29 | ecalloc(size_t nmemb, size_t size) 30 | { 31 | void *p; 32 | 33 | if (!(p = calloc(nmemb, size))) 34 | die("calloc:"); 35 | return p; 36 | } 37 | -------------------------------------------------------------------------------- /patch/include.h: -------------------------------------------------------------------------------- 1 | #if DYNAMIC_OPTIONS_PATCH 2 | #include "dynamicoptions.h" 3 | #endif 4 | #if FZFEXPECT_PATCH 5 | #include "fzfexpect.h" 6 | #endif 7 | #if INPUTMETHOD_PATCH 8 | #include "inputmethod.h" 9 | #endif 10 | #if MOUSE_SUPPORT_PATCH 11 | #include "mousesupport.h" 12 | #endif 13 | #if MULTI_SELECTION_PATCH 14 | #include "multiselect.h" 15 | #endif 16 | #if NAVHISTORY_PATCH 17 | #include "navhistory.h" 18 | #endif 19 | #if VI_MODE_PATCH 20 | #include "vi_mode.h" 21 | #endif 22 | #if HIGHPRIORITY_PATCH 23 | #include "highpriority.h" 24 | #endif 25 | #if NON_BLOCKING_STDIN_PATCH 26 | #include "nonblockingstdin.h" 27 | #endif 28 | #if NUMBERS_PATCH 29 | #include "numbers.h" 30 | #endif 31 | -------------------------------------------------------------------------------- /patch/include.c: -------------------------------------------------------------------------------- 1 | #if CENTER_PATCH 2 | #include "center.c" 3 | #endif 4 | #if HIGHLIGHT_PATCH 5 | #include "highlight.c" 6 | #endif 7 | #if FUZZYMATCH_PATCH 8 | #include "fuzzymatch.c" 9 | #endif 10 | #if FZFEXPECT_PATCH 11 | #include "fzfexpect.c" 12 | #endif 13 | #if HIGHPRIORITY_PATCH 14 | #include "highpriority.c" 15 | #endif 16 | #if INPUTMETHOD_PATCH 17 | #include "inputmethod.c" 18 | #endif 19 | #if DYNAMIC_OPTIONS_PATCH 20 | #include "dynamicoptions.c" 21 | #endif 22 | #if MULTI_SELECTION_PATCH 23 | #include "multiselect.c" 24 | #endif 25 | #if MOUSE_SUPPORT_PATCH 26 | #include "mousesupport.c" 27 | #endif 28 | #if NAVHISTORY_PATCH 29 | #include "navhistory.c" 30 | #endif 31 | #if VI_MODE_PATCH 32 | #include "vi_mode.c" 33 | #endif 34 | #if NON_BLOCKING_STDIN_PATCH 35 | #include "nonblockingstdin.c" 36 | #endif 37 | #if NUMBERS_PATCH 38 | #include "numbers.c" 39 | #endif 40 | #if XRESOURCES_PATCH 41 | #include "xresources.c" 42 | #endif 43 | -------------------------------------------------------------------------------- /patch/highpriority.c: -------------------------------------------------------------------------------- 1 | static char **hpitems = NULL; 2 | static int hplength = 0; 3 | 4 | static char ** 5 | tokenize(char *source, const char *delim, int *llen) 6 | { 7 | int listlength = 0, list_size = 0; 8 | char **list = NULL, *token; 9 | 10 | token = strtok(source, delim); 11 | while (token) { 12 | if (listlength + 1 >= list_size) { 13 | if (!(list = realloc(list, (list_size += 8) * sizeof(*list)))) 14 | die("Unable to realloc %zu bytes\n", list_size * sizeof(*list)); 15 | } 16 | if (!(list[listlength] = strdup(token))) 17 | die("Unable to strdup %zu bytes\n", strlen(token) + 1); 18 | token = strtok(NULL, delim); 19 | listlength++; 20 | } 21 | 22 | *llen = listlength; 23 | return list; 24 | } 25 | 26 | static int 27 | arrayhas(char **list, int length, char *item) { 28 | for (int i = 0; i < length; i++) { 29 | int len1 = strlen(list[i]); 30 | int len2 = strlen(item); 31 | if (fstrncmp(list[i], item, len1 > len2 ? len2 : len1) == 0) 32 | return 1; 33 | } 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /patch/fzfexpect.c: -------------------------------------------------------------------------------- 1 | static char *expected; 2 | #if MULTI_SELECTION_PATCH 3 | void 4 | expect(char *expect, XKeyEvent *ev) 5 | { 6 | if (sel && expected && strstr(expected, expect)) { 7 | if (expected && sel && !(ev->state & ShiftMask)) 8 | puts(expect); 9 | for (int i = 0; i < selidsize; i++) 10 | if (selid[i] != -1 && (!sel || sel->id != selid[i])) 11 | puts(items[selid[i]].text); 12 | if (sel && !(ev->state & ShiftMask)) { 13 | puts(sel->text); 14 | } else 15 | puts(text); 16 | cleanup(); 17 | exit(1); 18 | } else if (!sel && expected && strstr(expected, expect)) { 19 | puts(expect); 20 | cleanup(); 21 | exit(1); 22 | } 23 | } 24 | #else 25 | void 26 | expect(char *expect, XKeyEvent *ignored) 27 | { 28 | if (sel && expected && strstr(expected, expect)) { 29 | puts(expect); 30 | puts(sel->text); 31 | cleanup(); 32 | exit(1); 33 | } else if (!sel && expected && strstr(expected, expect)){ 34 | puts(expect); 35 | cleanup(); 36 | exit(1); 37 | } 38 | } 39 | #endif // MULTI_SELECTION_PATCH 40 | -------------------------------------------------------------------------------- /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.mk: -------------------------------------------------------------------------------- 1 | # dmenu version 2 | VERSION = 5.4 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 | #MANPREFIX = ${PREFIX}/man 21 | 22 | # Uncomment on RHEL for strcasecmp 23 | #EXTRAFLAGS=-D_GNU_SOURCE 24 | 25 | # Uncomment for the alpha patch / ALPHA_PATCH 26 | #XRENDER = -lXrender 27 | 28 | # Uncomment for the bidi patch / BIDI_PATCH 29 | #BIDILIBS = `pkg-config --libs fribidi` 30 | #BIDIINC = `pkg-config --cflags fribidi` 31 | 32 | # Uncomment for the pango patch / PANGO_PATCH 33 | #PANGOINC = `pkg-config --cflags xft pango pangoxft` 34 | #PANGOLIB = `pkg-config --libs xft pango pangoxft` 35 | 36 | # includes and libs 37 | INCS = -I$(X11INC) -I$(FREETYPEINC) $(PANGOINC) $(BIDIINC) 38 | LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm $(XRENDER) $(PANGOLIB) $(BIDILIBS) 39 | 40 | # flags 41 | CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) $(EXTRAFLAGS) 42 | CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) 43 | LDFLAGS = $(LIBS) 44 | 45 | # compiler and linker 46 | CC = cc 47 | -------------------------------------------------------------------------------- /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-2022 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 | -------------------------------------------------------------------------------- /patch/multiselect.c: -------------------------------------------------------------------------------- 1 | static int 2 | issel(size_t id) 3 | { 4 | for (int i = 0;i < selidsize;i++) 5 | if (selid[i] == id) 6 | return 1; 7 | return 0; 8 | } 9 | 10 | static void 11 | printsel(unsigned int state) 12 | { 13 | for (int i = 0;i < selidsize;i++) 14 | if (selid[i] != -1 && (!sel || sel->id != selid[i])) { 15 | #if PRINTINDEX_PATCH 16 | if (print_index) 17 | printf("%d\n", selid[i]); 18 | else 19 | #if SEPARATOR_PATCH 20 | puts(items[selid[i]].text_output); 21 | #else 22 | puts(items[selid[i]].text); 23 | #endif // SEPARATOR_PATCH 24 | #elif SEPARATOR_PATCH 25 | puts(items[selid[i]].text_output); 26 | #else 27 | puts(items[selid[i]].text); 28 | #endif // PRINTINDEX_PATCH | SEPARATOR_PATCH 29 | } 30 | if (sel && !(state & ShiftMask)) { 31 | #if PRINTINDEX_PATCH 32 | if (print_index) 33 | printf("%d\n", sel->index); 34 | else 35 | #if SEPARATOR_PATCH 36 | puts(sel->text_output); 37 | #else 38 | puts(sel->text); 39 | #endif // SEPARATOR_PATCH 40 | #elif SEPARATOR_PATCH 41 | puts(sel->text_output); 42 | #else 43 | puts(sel->text); 44 | #endif // PRINTINDEX_PATCH | SEPARATOR_PATCH 45 | } else 46 | puts(text); 47 | 48 | } 49 | 50 | static void 51 | selsel(void) 52 | { 53 | if (!sel) 54 | return; 55 | if (issel(sel->id)) { 56 | for (int i = 0; i < selidsize; i++) 57 | if (selid[i] == sel->id) 58 | selid[i] = -1; 59 | } else { 60 | for (int i = 0; i < selidsize; i++) 61 | if (selid[i] == -1) { 62 | selid[i] = sel->id; 63 | return; 64 | } 65 | selidsize++; 66 | selid = realloc(selid, (selidsize + 1) * sizeof(int)); 67 | selid[selidsize - 1] = sel->id; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /patch/nonblockingstdin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static void 6 | readstdin(void) 7 | { 8 | static size_t max = 0; 9 | static struct item **end = &items; 10 | 11 | char buf[sizeof text], *p, *maxstr; 12 | struct item *item; 13 | 14 | #if PASSWORD_PATCH 15 | if (passwd) { 16 | inputw = lines = 0; 17 | return; 18 | } 19 | #endif // PASSWORD_PATCH 20 | 21 | /* read each line from stdin and add it to the item list */ 22 | while (fgets(buf, sizeof buf, stdin)) { 23 | if (!(item = malloc(sizeof *item))) 24 | die("cannot malloc %u bytes:", sizeof *item); 25 | if ((p = strchr(buf, '\n'))) 26 | *p = '\0'; 27 | if (!(item->text = strdup(buf))) 28 | die("cannot strdup %u bytes:", strlen(buf)+1); 29 | if (strlen(item->text) > max) { 30 | max = strlen(maxstr = item->text); 31 | #if PANGO_PATCH 32 | inputw = maxstr ? TEXTWM(maxstr) : 0; 33 | #else 34 | inputw = maxstr ? TEXTW(maxstr) : 0; 35 | #endif // PANGO_PATCH 36 | } 37 | *end = item; 38 | end = &item->next; 39 | item->next = NULL; 40 | item->out = 0; 41 | } 42 | match(); 43 | drawmenu(); 44 | } 45 | 46 | static void 47 | run(void) 48 | { 49 | fd_set fds; 50 | int flags, xfd = XConnectionNumber(dpy); 51 | 52 | if ((flags = fcntl(0, F_GETFL)) == -1) 53 | die("cannot get stdin control flags:"); 54 | if (fcntl(0, F_SETFL, flags | O_NONBLOCK) == -1) 55 | die("cannot set stdin control flags:"); 56 | for (;;) { 57 | FD_ZERO(&fds); 58 | FD_SET(xfd, &fds); 59 | if (!feof(stdin)) 60 | FD_SET(0, &fds); 61 | if (select(xfd + 1, &fds, NULL, NULL, NULL) == -1) 62 | die("cannot multiplex input:"); 63 | if (FD_ISSET(xfd, &fds)) 64 | readevent(); 65 | if (FD_ISSET(0, &fds)) 66 | readstdin(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /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: dmenu stest 10 | 11 | .c.o: 12 | $(CC) -c $(CFLAGS) $< 13 | 14 | config.h: 15 | cp config.def.h $@ 16 | 17 | patches.h: 18 | cp patches.def.h $@ 19 | 20 | $(OBJ): arg.h config.h config.mk drw.h patches.h 21 | 22 | dmenu: dmenu.o drw.o util.o 23 | $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) 24 | 25 | stest: stest.o 26 | $(CC) -o $@ stest.o $(LDFLAGS) 27 | 28 | clean: 29 | rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz 30 | 31 | dist: clean 32 | mkdir -p dmenu-$(VERSION) 33 | cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ 34 | drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ 35 | dmenu-$(VERSION) 36 | tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) 37 | gzip dmenu-$(VERSION).tar 38 | rm -rf dmenu-$(VERSION) 39 | 40 | install: all 41 | mkdir -p $(DESTDIR)$(PREFIX)/bin 42 | cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin 43 | chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu 44 | chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path 45 | chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run 46 | chmod 755 $(DESTDIR)$(PREFIX)/bin/stest 47 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1 48 | sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 49 | sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 50 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 51 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 52 | 53 | uninstall: 54 | rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ 55 | $(DESTDIR)$(PREFIX)/bin/dmenu_path\ 56 | $(DESTDIR)$(PREFIX)/bin/dmenu_run\ 57 | $(DESTDIR)$(PREFIX)/bin/stest\ 58 | $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ 59 | $(DESTDIR)$(MANPREFIX)/man1/stest.1 60 | 61 | .PHONY: all clean dist install uninstall 62 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /patch/dynamicoptions.c: -------------------------------------------------------------------------------- 1 | static void 2 | refreshoptions(void) 3 | { 4 | int dynlen = strlen(dynamic); 5 | char* cmd= malloc(dynlen + strlen(text) + 2); 6 | if (cmd == NULL) 7 | die("malloc:"); 8 | sprintf(cmd, "%s %s", dynamic, text); 9 | FILE *stream = popen(cmd, "r"); 10 | if (!stream) 11 | die("popen(%s):", cmd); 12 | readstream(stream); 13 | int pc = pclose(stream); 14 | if (pc == -1) 15 | die("pclose:"); 16 | free(cmd); 17 | curr = sel = items; 18 | } 19 | 20 | static void 21 | readstream(FILE* stream) 22 | { 23 | char buf[sizeof text], *p; 24 | size_t i, imax = 0, size = 0; 25 | unsigned int tmpmax = 0; 26 | 27 | /* read each line from stdin and add it to the item list */ 28 | for (i = 0; fgets(buf, sizeof buf, stream); i++) { 29 | if (i + 1 >= size / sizeof *items) 30 | if (!(items = realloc(items, (size += BUFSIZ)))) 31 | die("cannot realloc %u bytes:", size); 32 | if ((p = strchr(buf, '\n'))) 33 | *p = '\0'; 34 | if (!(items[i].text = strdup(buf))) 35 | die("cannot strdup %u bytes:", strlen(buf) + 1); 36 | #if SEPARATOR_PATCH 37 | if (separator && (p = separator_greedy ? 38 | strrchr(items[i].text, separator) : strchr(items[i].text, separator))) { 39 | *p = '\0'; 40 | items[i].text_output = ++p; 41 | } else { 42 | items[i].text_output = items[i].text; 43 | } 44 | if (separator_reverse) { 45 | p = items[i].text; 46 | items[i].text = items[i].text_output; 47 | items[i].text_output = p; 48 | } 49 | #elif TSV_PATCH 50 | if ((p = strchr(buf, '\t'))) 51 | *p = '\0'; 52 | if (!(items[i].stext = strdup(buf))) 53 | die("cannot strdup %u bytes:", strlen(buf) + 1); 54 | #endif // TSV_PATCH 55 | #if MULTI_SELECTION_PATCH 56 | items[i].id = i; 57 | #else 58 | items[i].out = 0; 59 | #endif // MULTI_SELECTION_PATCH 60 | #if HIGHPRIORITY_PATCH 61 | items[i].hp = arrayhas(hpitems, hplength, items[i].text); 62 | #endif // HIGHPRIORITY_PATCH 63 | #if PANGO_PATCH 64 | drw_font_getexts(drw->font, buf, strlen(buf), &tmpmax, NULL, True); 65 | #else 66 | drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); 67 | #endif // PANGO_PATCH 68 | if (tmpmax > inputw) { 69 | inputw = tmpmax; 70 | imax = i; 71 | } 72 | } 73 | 74 | /* If the command did not give any output at all, then do not clear the existing items */ 75 | if (!i) 76 | return; 77 | 78 | if (items) 79 | items[i].text = NULL; 80 | #if PANGO_PATCH 81 | inputw = items ? TEXTWM(items[imax].text) : 0; 82 | #else 83 | inputw = items ? TEXTW(items[imax].text) : 0; 84 | #endif // PANGO_PATCH 85 | if (!dynamic || !*dynamic) 86 | lines = MIN(lines, i); 87 | else { 88 | text[0] = '\0'; 89 | cursor = 0; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /patch/navhistory.c: -------------------------------------------------------------------------------- 1 | static char *histfile; 2 | static char **history; 3 | static size_t histsz, histpos; 4 | 5 | void 6 | loadhistory(void) 7 | { 8 | FILE *fp = NULL; 9 | static size_t cap = 0; 10 | size_t llen; 11 | char *line; 12 | 13 | if (!histfile) { 14 | return; 15 | } 16 | 17 | fp = fopen(histfile, "r"); 18 | if (!fp) { 19 | return; 20 | } 21 | 22 | for (;;) { 23 | line = NULL; 24 | llen = 0; 25 | if (-1 == getline(&line, &llen, fp)) { 26 | if (ferror(fp)) { 27 | die("failed to read history"); 28 | } 29 | free(line); 30 | break; 31 | } 32 | 33 | if (cap == histsz) { 34 | cap += 64 * sizeof(char*); 35 | history = realloc(history, cap); 36 | if (!history) { 37 | die("failed to realloc memory"); 38 | } 39 | } 40 | strtok(line, "\n"); 41 | history[histsz] = line; 42 | histsz++; 43 | } 44 | histpos = histsz; 45 | 46 | if (fclose(fp)) { 47 | die("failed to close file %s", histfile); 48 | } 49 | } 50 | 51 | void 52 | navhistory(int dir) 53 | { 54 | static char def[BUFSIZ]; 55 | char *p = NULL; 56 | size_t len = 0; 57 | 58 | if (!history || histpos + 1 == 0) 59 | return; 60 | 61 | if (histsz == histpos) { 62 | strncpy(def, text, sizeof(def)); 63 | } 64 | 65 | switch(dir) { 66 | case 1: 67 | if (histpos < histsz - 1) { 68 | p = history[++histpos]; 69 | } else if (histpos == histsz - 1) { 70 | p = def; 71 | histpos++; 72 | } 73 | break; 74 | case -1: 75 | if (histpos > 0) { 76 | p = history[--histpos]; 77 | } 78 | break; 79 | } 80 | if (p == NULL) { 81 | return; 82 | } 83 | 84 | len = MIN(strlen(p), BUFSIZ - 1); 85 | strncpy(text, p, len); 86 | text[len] = '\0'; 87 | cursor = len; 88 | match(); 89 | } 90 | 91 | void 92 | savehistory(char *input) 93 | { 94 | unsigned int i; 95 | FILE *fp; 96 | 97 | if (!histfile || 98 | 0 == maxhist || 99 | 0 == strlen(input)) { 100 | goto out; 101 | } 102 | 103 | fp = fopen(histfile, "w"); 104 | if (!fp) { 105 | die("failed to open %s", histfile); 106 | } 107 | for (i = histsz < maxhist ? 0 : histsz - maxhist; i < histsz; i++) { 108 | if (0 >= fprintf(fp, "%s\n", history[i])) { 109 | die("failed to write to %s", histfile); 110 | } 111 | } 112 | if (histsz == 0 || !histnodup || (histsz > 0 && strcmp(input, history[histsz-1]) != 0)) { /* TODO */ 113 | if (0 >= fputs(input, fp)) { 114 | die("failed to write to %s", histfile); 115 | } 116 | } 117 | if (fclose(fp)) { 118 | die("failed to close file %s", histfile); 119 | } 120 | 121 | out: 122 | for (i = 0; i < histsz; i++) { 123 | free(history[i]); 124 | } 125 | free(history); 126 | } 127 | -------------------------------------------------------------------------------- /patch/highlight.c: -------------------------------------------------------------------------------- 1 | static void 2 | #if EMOJI_HIGHLIGHT_PATCH 3 | drawhighlights(struct item *item, char *output, int x, int y, int maxw) 4 | #else 5 | drawhighlights(struct item *item, int x, int y, int maxw) 6 | #endif // EMOJI_HIGHLIGHT_PATCH 7 | { 8 | char restorechar, tokens[sizeof text], *highlight, *token; 9 | int indent, highlightlen; 10 | 11 | #if FUZZYMATCH_PATCH 12 | int i; 13 | #endif // FUZZYMATCH_PATCH 14 | 15 | #if EMOJI_HIGHLIGHT_PATCH 16 | char *itemtext = output; 17 | #elif TSV_PATCH && !SEPARATOR_PATCH 18 | char *itemtext = item->stext; 19 | #else 20 | char *itemtext = item->text; 21 | #endif // EMOJI_HIGHLIGHT_PATCH | TSV_PATCH 22 | 23 | if (!(strlen(itemtext) && strlen(text))) 24 | return; 25 | 26 | /* Do not highlight items scheduled for output */ 27 | #if MULTI_SELECTION_PATCH 28 | if (issel(item->id)) 29 | return; 30 | #else 31 | if (item->out) 32 | return; 33 | #endif // MULTI_SELECTION_PATCH 34 | 35 | drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); 36 | 37 | #if FUZZYMATCH_PATCH 38 | if (fuzzy) { 39 | for (i = 0, highlight = itemtext; *highlight && text[i];) { 40 | highlightlen = utf8len(highlight); 41 | #if FUZZYMATCH_PATCH 42 | if (!fstrncmp(&(*highlight), &text[i], highlightlen)) 43 | #else 44 | if (*highlight == text[i]) 45 | #endif // FUZZYMATCH_PATCH 46 | { 47 | /* get indentation */ 48 | restorechar = *highlight; 49 | *highlight = '\0'; 50 | indent = TEXTW(itemtext) - lrpad; 51 | *highlight = restorechar; 52 | 53 | /* highlight character */ 54 | restorechar = highlight[highlightlen]; 55 | highlight[highlightlen] = '\0'; 56 | drw_text( 57 | drw, 58 | x + indent + (lrpad / 2), 59 | y, 60 | MIN(maxw - indent - lrpad, TEXTW(highlight) - lrpad), 61 | bh, 0, highlight, 0 62 | #if PANGO_PATCH 63 | , True 64 | #endif // PANGO_PATCH 65 | ); 66 | highlight[highlightlen] = restorechar; 67 | i += highlightlen; 68 | } 69 | highlight++; 70 | } 71 | return; 72 | } 73 | #endif // FUZZYMATCH_PATCH 74 | 75 | strcpy(tokens, text); 76 | for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) { 77 | highlight = fstrstr(itemtext, token); 78 | while (highlight) { 79 | // Move item str end, calc width for highlight indent, & restore 80 | highlightlen = highlight - itemtext; 81 | restorechar = *highlight; 82 | itemtext[highlightlen] = '\0'; 83 | indent = TEXTW(itemtext); 84 | itemtext[highlightlen] = restorechar; 85 | 86 | // Move highlight str end, draw highlight, & restore 87 | restorechar = highlight[strlen(token)]; 88 | highlight[strlen(token)] = '\0'; 89 | if (indent - (lrpad / 2) - 1 < maxw) 90 | drw_text( 91 | drw, 92 | x + indent - (lrpad / 2) - 1, 93 | y, 94 | MIN(maxw - indent, TEXTW(highlight) - lrpad), 95 | bh, 0, highlight, 0 96 | #if PANGO_PATCH 97 | , True 98 | #endif // PANGO_PATCH 99 | ); 100 | highlight[strlen(token)] = restorechar; 101 | 102 | if (strlen(highlight) - strlen(token) < strlen(token)) 103 | break; 104 | highlight = fstrstr(highlight + strlen(token), token); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /patch/fuzzymatch.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int 4 | compare_distance(const void *a, const void *b) 5 | { 6 | struct item *da = *(struct item **) a; 7 | struct item *db = *(struct item **) b; 8 | 9 | if (!db) 10 | return 1; 11 | if (!da) 12 | return -1; 13 | 14 | return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1; 15 | } 16 | 17 | void 18 | fuzzymatch(void) 19 | { 20 | /* bang - we have so much memory */ 21 | struct item *it; 22 | struct item **fuzzymatches = NULL; 23 | char c; 24 | int number_of_matches = 0, i, pidx, sidx, eidx; 25 | int text_len = strlen(text), itext_len; 26 | #if HIGHPRIORITY_PATCH 27 | struct item *lhpprefix, *hpprefixend; 28 | lhpprefix = hpprefixend = NULL; 29 | #endif // HIGHPRIORITY_PATCH 30 | matches = matchend = NULL; 31 | 32 | /* walk through all items */ 33 | for (it = items; it && it->text; it++) { 34 | if (text_len) { 35 | itext_len = strlen(it->text); 36 | pidx = 0; /* pointer */ 37 | sidx = eidx = -1; /* start of match, end of match */ 38 | /* walk through item text */ 39 | for (i = 0; i < itext_len && (c = it->text[i]); i++) { 40 | /* fuzzy match pattern */ 41 | if (!fstrncmp(&text[pidx], &c, 1)) { 42 | if (sidx == -1) 43 | sidx = i; 44 | pidx++; 45 | if (pidx == text_len) { 46 | eidx = i; 47 | break; 48 | } 49 | } 50 | } 51 | /* build list of matches */ 52 | if (eidx != -1) { 53 | /* compute distance */ 54 | /* add penalty if match starts late (log(sidx+2)) 55 | * add penalty for long a match without many matching characters */ 56 | it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len); 57 | /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */ 58 | appenditem(it, &matches, &matchend); 59 | number_of_matches++; 60 | } 61 | } else { 62 | appenditem(it, &matches, &matchend); 63 | } 64 | } 65 | 66 | if (number_of_matches) { 67 | /* initialize array with matches */ 68 | if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*)))) 69 | die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*)); 70 | for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) { 71 | fuzzymatches[i] = it; 72 | } 73 | 74 | #if NO_SORT_PATCH 75 | if (sortmatches) 76 | #endif // NO_SORT_PATCH 77 | /* sort matches according to distance */ 78 | qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance); 79 | /* rebuild list of matches */ 80 | matches = matchend = NULL; 81 | for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \ 82 | it->text; i++, it = fuzzymatches[i]) { 83 | #if HIGHPRIORITY_PATCH 84 | #if NO_SORT_PATCH 85 | if (sortmatches && it->hp) 86 | #else 87 | if (it->hp) 88 | #endif // NO_SORT_PATCH 89 | appenditem(it, &lhpprefix, &hpprefixend); 90 | else 91 | appenditem(it, &matches, &matchend); 92 | #else 93 | appenditem(it, &matches, &matchend); 94 | #endif // HIGHPRIORITY_PATCH 95 | } 96 | free(fuzzymatches); 97 | } 98 | #if HIGHPRIORITY_PATCH 99 | if (lhpprefix) { 100 | hpprefixend->right = matches; 101 | matches = lhpprefix; 102 | } 103 | #endif // HIGHPRIORITY_PATCH 104 | curr = sel = matches; 105 | 106 | #if INSTANT_PATCH 107 | if (instant && matches && matches==matchend) { 108 | puts(matches->text); 109 | cleanup(); 110 | exit(0); 111 | } 112 | #endif // INSTANT_PATCH 113 | 114 | calcoffsets(); 115 | } 116 | -------------------------------------------------------------------------------- /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 (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 | -------------------------------------------------------------------------------- /drw.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | #if PANGO_PATCH 4 | #include 5 | #include 6 | #endif // PANGO_PATCH 7 | 8 | typedef struct { 9 | Cursor cursor; 10 | } Cur; 11 | 12 | typedef struct Fnt { 13 | Display *dpy; 14 | unsigned int h; 15 | #if PANGO_PATCH 16 | PangoLayout *layout; 17 | #else 18 | XftFont *xfont; 19 | FcPattern *pattern; 20 | struct Fnt *next; 21 | #endif // PANGO_PATCH 22 | } Fnt; 23 | 24 | enum { ColFg, ColBg }; /* Clr scheme index */ 25 | typedef XftColor Clr; 26 | 27 | typedef struct { 28 | unsigned int w, h; 29 | Display *dpy; 30 | int screen; 31 | Window root; 32 | #if ALPHA_PATCH 33 | Visual *visual; 34 | unsigned int depth; 35 | Colormap cmap; 36 | #endif // ALPHA_PATCH 37 | Drawable drawable; 38 | GC gc; 39 | Clr *scheme; 40 | #if PANGO_PATCH 41 | Fnt *font; 42 | #else 43 | Fnt *fonts; 44 | #endif // PANGO_PATCH 45 | } Drw; 46 | 47 | /* Drawable abstraction */ 48 | #if ALPHA_PATCH 49 | Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap); 50 | #else 51 | Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); 52 | #endif // ALPHA_PATCH 53 | void drw_resize(Drw *drw, unsigned int w, unsigned int h); 54 | void drw_free(Drw *drw); 55 | 56 | /* Fnt abstraction */ 57 | #if PANGO_PATCH 58 | Fnt *drw_font_create(Drw* drw, const char *font); 59 | void drw_font_free(Fnt* set); 60 | unsigned int drw_font_getwidth(Drw *drw, const char *text, Bool markup); 61 | unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); 62 | void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h, Bool markup); 63 | #else 64 | Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); 65 | void drw_fontset_free(Fnt* set); 66 | unsigned int drw_fontset_getwidth(Drw *drw, const char *text); 67 | unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); 68 | void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); 69 | #endif // PANGO_PATCH 70 | 71 | #if HIGHLIGHT_PATCH 72 | int utf8len(const char *c); 73 | #endif // HIGHLIGHT_PATCH 74 | 75 | /* Colorscheme abstraction */ 76 | #if ALPHA_PATCH 77 | void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha); 78 | Clr *drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount); 79 | #else 80 | void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); 81 | Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); 82 | #endif // ALPHA_PATCH 83 | void drw_clr_free(Drw *drw, Clr *c); 84 | void drw_scm_free(Drw *drw, Clr *scm, size_t clrcount); 85 | 86 | /* Cursor abstraction */ 87 | Cur *drw_cur_create(Drw *drw, int shape); 88 | void drw_cur_free(Drw *drw, Cur *cursor); 89 | 90 | /* Drawing context manipulation */ 91 | #if !PANGO_PATCH 92 | void drw_setfontset(Drw *drw, Fnt *set); 93 | #endif // PANGO_PATCH 94 | void drw_setscheme(Drw *drw, Clr *scm); 95 | 96 | /* Drawing functions */ 97 | void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); 98 | #if PANGO_PATCH 99 | int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert, Bool markup); 100 | #else 101 | int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); 102 | #endif // PANGO_PATCH 103 | 104 | /* Map functions */ 105 | void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); 106 | 107 | #if SCROLL_PATCH 108 | #include "patch/scroll.h" 109 | #endif 110 | -------------------------------------------------------------------------------- /dmenu.1: -------------------------------------------------------------------------------- 1 | .TH DMENU 1 dmenu\-VERSION 2 | .SH NAME 3 | dmenu \- dynamic menu 4 | .SH SYNOPSIS 5 | .B dmenu 6 | .RB [ \-bfiv ] 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 | .BI \-l " lines" 51 | dmenu lists items vertically, with the given number of lines. 52 | .TP 53 | .BI \-m " monitor" 54 | dmenu is displayed on the monitor number supplied. Monitor numbers are starting 55 | from 0. 56 | .TP 57 | .BI \-p " prompt" 58 | defines the prompt to be displayed to the left of the input field. 59 | .TP 60 | .BI \-fn " font" 61 | defines the font or font set used. 62 | .TP 63 | .BI \-nb " color" 64 | defines the normal background color. 65 | .IR #RGB , 66 | .IR #RRGGBB , 67 | and X color names are supported. 68 | .TP 69 | .BI \-nf " color" 70 | defines the normal foreground color. 71 | .TP 72 | .BI \-sb " color" 73 | defines the selected background color. 74 | .TP 75 | .BI \-sf " color" 76 | defines the selected foreground color. 77 | .TP 78 | .B \-v 79 | prints version information to stdout, then exits. 80 | .TP 81 | .BI \-w " windowid" 82 | embed into windowid. 83 | .SH USAGE 84 | dmenu is completely controlled by the keyboard. Items are selected using the 85 | arrow keys, page up, page down, home, and end. 86 | .TP 87 | .B Tab 88 | Copy the selected item to the input field. 89 | .TP 90 | .B Return 91 | Confirm selection. Prints the selected item to stdout and exits, returning 92 | success. 93 | .TP 94 | .B Ctrl-Return 95 | Confirm selection. Prints the selected item to stdout and continues. 96 | .TP 97 | .B Shift\-Return 98 | Confirm input. Prints the input text to stdout and exits, returning success. 99 | .TP 100 | .B Escape 101 | Exit without selecting an item, returning failure. 102 | .TP 103 | .B Ctrl-Left 104 | Move cursor to the start of the current word 105 | .TP 106 | .B Ctrl-Right 107 | Move cursor to the end of the current word 108 | .TP 109 | .B C\-a 110 | Home 111 | .TP 112 | .B C\-b 113 | Left 114 | .TP 115 | .B C\-c 116 | Escape 117 | .TP 118 | .B C\-d 119 | Delete 120 | .TP 121 | .B C\-e 122 | End 123 | .TP 124 | .B C\-f 125 | Right 126 | .TP 127 | .B C\-g 128 | Escape 129 | .TP 130 | .B C\-h 131 | Backspace 132 | .TP 133 | .B C\-i 134 | Tab 135 | .TP 136 | .B C\-j 137 | Return 138 | .TP 139 | .B C\-J 140 | Shift-Return 141 | .TP 142 | .B C\-k 143 | Delete line right 144 | .TP 145 | .B C\-m 146 | Return 147 | .TP 148 | .B C\-M 149 | Shift-Return 150 | .TP 151 | .B C\-n 152 | Down 153 | .TP 154 | .B C\-p 155 | Up 156 | .TP 157 | .B C\-u 158 | Delete line left 159 | .TP 160 | .B C\-w 161 | Delete word left 162 | .TP 163 | .B C\-y 164 | Paste from primary X selection 165 | .TP 166 | .B C\-Y 167 | Paste from X clipboard 168 | .TP 169 | .B M\-b 170 | Move cursor to the start of the current word 171 | .TP 172 | .B M\-f 173 | Move cursor to the end of the current word 174 | .TP 175 | .B M\-g 176 | Home 177 | .TP 178 | .B M\-G 179 | End 180 | .TP 181 | .B M\-h 182 | Up 183 | .TP 184 | .B M\-j 185 | Page down 186 | .TP 187 | .B M\-k 188 | Page up 189 | .TP 190 | .B M\-l 191 | Down 192 | .SH SEE ALSO 193 | .IR dwm (1), 194 | .IR stest (1) 195 | -------------------------------------------------------------------------------- /patch/vi_mode.c: -------------------------------------------------------------------------------- 1 | static unsigned int using_vi_mode = 0; 2 | 3 | void 4 | vi_keypress(KeySym ksym, const XKeyEvent *ev) 5 | { 6 | static const size_t quit_len = LENGTH(quit_keys); 7 | if (ev->state & ControlMask) { 8 | switch(ksym) { 9 | /* movement */ 10 | case XK_d: /* fallthrough */ 11 | if (next) { 12 | sel = curr = next; 13 | calcoffsets(); 14 | goto draw; 15 | } else 16 | ksym = XK_G; 17 | break; 18 | case XK_u: 19 | if (prev) { 20 | sel = curr = prev; 21 | calcoffsets(); 22 | goto draw; 23 | } else 24 | ksym = XK_g; 25 | break; 26 | case XK_p: /* fallthrough */ 27 | case XK_P: break; 28 | case XK_c: 29 | cleanup(); 30 | exit(1); 31 | case XK_Return: /* fallthrough */ 32 | case XK_KP_Enter: break; 33 | default: return; 34 | } 35 | } 36 | 37 | switch(ksym) { 38 | /* movement */ 39 | case XK_0: 40 | cursor = 0; 41 | break; 42 | case XK_dollar: 43 | if (text[cursor + 1] != '\0') { 44 | cursor = strlen(text) - 1; 45 | break; 46 | } 47 | break; 48 | case XK_b: 49 | movewordedge(-1); 50 | break; 51 | case XK_e: 52 | cursor = nextrune(+1); 53 | movewordedge(+1); 54 | if (text[cursor] == '\0') 55 | --cursor; 56 | else 57 | cursor = nextrune(-1); 58 | break; 59 | case XK_g: 60 | if (sel == matches) { 61 | break; 62 | } 63 | sel = curr = matches; 64 | calcoffsets(); 65 | break; 66 | case XK_G: 67 | if (next) { 68 | /* jump to end of list and position items in reverse */ 69 | curr = matchend; 70 | calcoffsets(); 71 | curr = prev; 72 | calcoffsets(); 73 | while (next && (curr = curr->right)) 74 | calcoffsets(); 75 | } 76 | sel = matchend; 77 | break; 78 | case XK_h: 79 | if (cursor) 80 | cursor = nextrune(-1); 81 | break; 82 | case XK_j: 83 | if (sel && sel->right && (sel = sel->right) == next) { 84 | curr = next; 85 | calcoffsets(); 86 | } 87 | break; 88 | case XK_k: 89 | if (sel && sel->left && (sel = sel->left)->right == curr) { 90 | curr = prev; 91 | calcoffsets(); 92 | } 93 | break; 94 | case XK_l: 95 | if (text[cursor] != '\0' && text[cursor + 1] != '\0') 96 | cursor = nextrune(+1); 97 | else if (text[cursor] == '\0' && cursor) 98 | --cursor; 99 | break; 100 | case XK_w: 101 | movewordedge(+1); 102 | if (text[cursor] != '\0' && text[cursor + 1] != '\0') 103 | cursor = nextrune(+1); 104 | else if (cursor) 105 | --cursor; 106 | break; 107 | /* insertion */ 108 | case XK_a: 109 | cursor = nextrune(+1); 110 | /* fallthrough */ 111 | case XK_i: 112 | using_vi_mode = 0; 113 | break; 114 | case XK_A: 115 | if (text[cursor] != '\0') 116 | cursor = strlen(text); 117 | using_vi_mode = 0; 118 | break; 119 | case XK_I: 120 | cursor = using_vi_mode = 0; 121 | break; 122 | case XK_p: 123 | if (text[cursor] != '\0') 124 | cursor = nextrune(+1); 125 | XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, 126 | utf8, utf8, win, CurrentTime); 127 | return; 128 | case XK_P: 129 | XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, 130 | utf8, utf8, win, CurrentTime); 131 | return; 132 | /* deletion */ 133 | case XK_D: 134 | text[cursor] = '\0'; 135 | if (cursor) 136 | cursor = nextrune(-1); 137 | match(); 138 | break; 139 | case XK_x: 140 | cursor = nextrune(+1); 141 | insert(NULL, nextrune(-1) - cursor); 142 | if (text[cursor] == '\0' && text[0] != '\0') 143 | --cursor; 144 | match(); 145 | break; 146 | /* misc. */ 147 | case XK_Return: 148 | case XK_KP_Enter: 149 | puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 150 | if (!(ev->state & ControlMask)) { 151 | cleanup(); 152 | exit(0); 153 | } 154 | if (sel) 155 | sel->out = 1; 156 | break; 157 | case XK_Tab: 158 | if (!sel) 159 | return; 160 | strncpy(text, sel->text, sizeof text - 1); 161 | text[sizeof text - 1] = '\0'; 162 | cursor = strlen(text) - 1; 163 | match(); 164 | break; 165 | default: 166 | for (size_t i = 0; i < quit_len; ++i) 167 | if (quit_keys[i].ksym == ksym && 168 | (quit_keys[i].state & ev->state) == quit_keys[i].state) { 169 | cleanup(); 170 | exit(1); 171 | } 172 | } 173 | 174 | draw: 175 | drawmenu(); 176 | } 177 | -------------------------------------------------------------------------------- /patch/xresources.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void 4 | readxresources(void) 5 | { 6 | XrmInitialize(); 7 | 8 | char* xrm; 9 | if ((xrm = XResourceManagerString(drw->dpy))) { 10 | char *type; 11 | XrmDatabase xdb = XrmGetStringDatabase(xrm); 12 | XrmValue xval; 13 | 14 | if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval)) 15 | #if PANGO_PATCH 16 | strcpy(font, xval.addr); 17 | #else 18 | fonts[0] = strdup(xval.addr); 19 | #endif // PANGO_PATCH 20 | #if !PANGO_PATCH 21 | else 22 | fonts[0] = strdup(fonts[0]); 23 | #endif // PANGO_PATCH 24 | if (XrmGetResource(xdb, "dmenu.background", "*", &type, &xval)) 25 | colors[SchemeNorm][ColBg] = strdup(xval.addr); 26 | if (XrmGetResource(xdb, "dmenu.foreground", "*", &type, &xval)) 27 | colors[SchemeNorm][ColFg] = strdup(xval.addr); 28 | if (XrmGetResource(xdb, "dmenu.selbackground", "*", &type, &xval)) 29 | colors[SchemeSel][ColBg] = strdup(xval.addr); 30 | if (XrmGetResource(xdb, "dmenu.selforeground", "*", &type, &xval)) 31 | colors[SchemeSel][ColFg] = strdup(xval.addr); 32 | if (XrmGetResource(xdb, "dmenu.outbackground", "*", &type, &xval)) 33 | colors[SchemeOut][ColBg] = strdup(xval.addr); 34 | if (XrmGetResource(xdb, "dmenu.outforeground", "*", &type, &xval)) 35 | colors[SchemeOut][ColFg] = strdup(xval.addr); 36 | #if MORECOLOR_PATCH 37 | if (XrmGetResource(xdb, "dmenu.midbackground", "*", &type, &xval)) 38 | colors[SchemeMid][ColBg] = strdup(xval.addr); 39 | if (XrmGetResource(xdb, "dmenu.midforeground", "*", &type, &xval)) 40 | colors[SchemeMid][ColFg] = strdup(xval.addr); 41 | #endif // MORECOLOR_PATCH 42 | #if BORDER_PATCH 43 | if (XrmGetResource(xdb, "dmenu.bordercolor", "*", &type, &xval)) 44 | colors[SchemeBorder][ColBg] = strdup(xval.addr); 45 | #endif // BORDER_PATCH 46 | #if HIGHLIGHT_PATCH 47 | if (XrmGetResource(xdb, "dmenu.selhlbackground", "*", &type, &xval)) 48 | colors[SchemeSelHighlight][ColBg] = strdup(xval.addr); 49 | if (XrmGetResource(xdb, "dmenu.selhlforeground", "*", &type, &xval)) 50 | colors[SchemeSelHighlight][ColFg] = strdup(xval.addr); 51 | if (XrmGetResource(xdb, "dmenu.hlbackground", "*", &type, &xval)) 52 | colors[SchemeNormHighlight][ColBg] = strdup(xval.addr); 53 | if (XrmGetResource(xdb, "dmenu.hlforeground", "*", &type, &xval)) 54 | colors[SchemeNormHighlight][ColFg] = strdup(xval.addr); 55 | #endif // HIGHLIGHT_PATCH 56 | #if HIGHPRIORITY_PATCH 57 | if (XrmGetResource(xdb, "dmenu.hpbackground", "*", &type, &xval)) 58 | colors[SchemeHp][ColBg] = strdup(xval.addr); 59 | if (XrmGetResource(xdb, "dmenu.hpforeground", "*", &type, &xval)) 60 | colors[SchemeHp][ColFg] = strdup(xval.addr); 61 | #endif // HIGHPRIORITY_PATCH 62 | #if EMOJI_HIGHLIGHT_PATCH 63 | if (XrmGetResource(xdb, "dmenu.hoverbackground", "*", &type, &xval)) 64 | colors[SchemeHover][ColBg] = strdup(xval.addr); 65 | if (XrmGetResource(xdb, "dmenu.hoverforeground", "*", &type, &xval)) 66 | colors[SchemeHover][ColFg] = strdup(xval.addr); 67 | if (XrmGetResource(xdb, "dmenu.greenbackground", "*", &type, &xval)) 68 | colors[SchemeGreen][ColBg] = strdup(xval.addr); 69 | if (XrmGetResource(xdb, "dmenu.greenforeground", "*", &type, &xval)) 70 | colors[SchemeGreen][ColFg] = strdup(xval.addr); 71 | if (XrmGetResource(xdb, "dmenu.yellowbackground", "*", &type, &xval)) 72 | colors[SchemeYellow][ColBg] = strdup(xval.addr); 73 | if (XrmGetResource(xdb, "dmenu.yellowforeground", "*", &type, &xval)) 74 | colors[SchemeYellow][ColFg] = strdup(xval.addr); 75 | if (XrmGetResource(xdb, "dmenu.bluebackground", "*", &type, &xval)) 76 | colors[SchemeBlue][ColBg] = strdup(xval.addr); 77 | if (XrmGetResource(xdb, "dmenu.blueforeground", "*", &type, &xval)) 78 | colors[SchemeBlue][ColFg] = strdup(xval.addr); 79 | if (XrmGetResource(xdb, "dmenu.purplebackground", "*", &type, &xval)) 80 | colors[SchemePurple][ColBg] = strdup(xval.addr); 81 | if (XrmGetResource(xdb, "dmenu.purpleforeground", "*", &type, &xval)) 82 | colors[SchemePurple][ColFg] = strdup(xval.addr); 83 | if (XrmGetResource(xdb, "dmenu.redbackground", "*", &type, &xval)) 84 | colors[SchemeRed][ColBg] = strdup(xval.addr); 85 | if (XrmGetResource(xdb, "dmenu.redforeground", "*", &type, &xval)) 86 | colors[SchemeRed][ColFg] = strdup(xval.addr); 87 | #endif // EMOJI_HIGHLIGHT_PATCH 88 | #if VI_MODE_PATCH 89 | if (XrmGetResource(xdb, "dmenu.cursorbackground", "*", &type, &xval)) 90 | colors[SchemeCursor][ColBg] = strdup(xval.addr); 91 | if (XrmGetResource(xdb, "dmenu.cursorforeground", "*", &type, &xval)) 92 | colors[SchemeCursor][ColFg] = strdup(xval.addr); 93 | #endif // VI_MODE_PATCH 94 | #if CARET_SCHEME_PATCH 95 | if (XrmGetResource(xdb, "dmenu.caretbackground", "*", &type, &xval)) 96 | colors[SchemeCaret][ColBg] = strdup(xval.addr); 97 | if (XrmGetResource(xdb, "dmenu.caretforeground", "*", &type, &xval)) 98 | colors[SchemeCaret][ColFg] = strdup(xval.addr); 99 | #endif // CARET_SCHEME_PATCH 100 | XrmDestroyDatabase(xdb); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /patch/scroll.c: -------------------------------------------------------------------------------- 1 | int 2 | utf8nextchar(const char *str, int len, int i, int inc) 3 | { 4 | int n; 5 | 6 | for (n = i + inc; n + inc >= 0 && n + inc <= len 7 | && (str[n] & 0xc0) == 0x80; n += inc) 8 | ; 9 | return n; 10 | } 11 | 12 | int 13 | drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align) 14 | { 15 | int ty; 16 | unsigned int ew; 17 | XftDraw *d = NULL; 18 | Fnt *usedfont, *curfont, *nextfont; 19 | size_t len; 20 | int utf8strlen, utf8charlen, render = x || y || w || h; 21 | long utf8codepoint = 0; 22 | const char *utf8str; 23 | FcCharSet *fccharset; 24 | FcPattern *fcpattern; 25 | FcPattern *match; 26 | XftResult result; 27 | int charexists = 0; 28 | int utf8err = 0; 29 | int i, n; 30 | 31 | if (!drw || (render && !drw->scheme) || !text || !drw->fonts || textlen <= 0 32 | || (align != AlignL && align != AlignR)) 33 | return 0; 34 | 35 | if (!render) { 36 | w = ~w; 37 | } else { 38 | XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel); 39 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 40 | #if ALPHA_PATCH 41 | d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); 42 | #else 43 | d = XftDrawCreate(drw->dpy, drw->drawable, 44 | DefaultVisual(drw->dpy, drw->screen), 45 | DefaultColormap(drw->dpy, drw->screen)); 46 | #endif // ALPHA_PATCH 47 | } 48 | 49 | #if BIDI_PATCH 50 | apply_fribidi(text); 51 | text = fribidi_text; 52 | #endif // BIDI_PATCH 53 | 54 | usedfont = drw->fonts; 55 | i = align == AlignL ? 0 : textlen; 56 | x = align == AlignL ? x : x + w; 57 | while (1) { 58 | utf8strlen = 0; 59 | nextfont = NULL; 60 | /* if (align == AlignL) */ 61 | utf8str = text + i; 62 | 63 | while ((align == AlignL && i < textlen) || (align == AlignR && i > 0)) { 64 | if (align == AlignL) { 65 | utf8charlen = utf8decode(text + i, &utf8codepoint, &utf8err); 66 | if (!utf8charlen) { 67 | textlen = i; 68 | break; 69 | } 70 | } else { 71 | n = utf8nextchar(text, textlen, i, -1); 72 | utf8charlen = utf8decode(text + n, &utf8codepoint, &utf8err); 73 | if (!utf8charlen) { 74 | textlen -= i; 75 | text += i; 76 | i = 0; 77 | break; 78 | } 79 | } 80 | for (curfont = drw->fonts; curfont; curfont = curfont->next) { 81 | charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); 82 | if (charexists) { 83 | if (curfont == usedfont) { 84 | utf8strlen += utf8charlen; 85 | i += align == AlignL ? utf8charlen : -utf8charlen; 86 | } else { 87 | nextfont = curfont; 88 | } 89 | break; 90 | } 91 | } 92 | 93 | if (!charexists || nextfont) 94 | break; 95 | else 96 | charexists = 0; 97 | } 98 | 99 | if (align == AlignR) 100 | utf8str = text + i; 101 | 102 | if (utf8strlen) { 103 | drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); 104 | /* shorten text if necessary */ 105 | if (align == AlignL) { 106 | for (len = utf8strlen; len && ew > w; ) { 107 | len = utf8nextchar(utf8str, len, len, -1); 108 | drw_font_getexts(usedfont, utf8str, len, &ew, NULL); 109 | } 110 | } else { 111 | for (len = utf8strlen; len && ew > w; ) { 112 | n = utf8nextchar(utf8str, len, 0, +1); 113 | utf8str += n; 114 | len -= n; 115 | drw_font_getexts(usedfont, utf8str, len, &ew, NULL); 116 | } 117 | } 118 | 119 | if (len) { 120 | if (render) { 121 | ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; 122 | XftDrawStringUtf8(d, &drw->scheme[ColFg], 123 | usedfont->xfont, align == AlignL ? x : x - ew, ty, (XftChar8 *)utf8str, len); 124 | } 125 | x += align == AlignL ? ew : -ew; 126 | w -= ew; 127 | } 128 | if (len < utf8strlen) 129 | break; 130 | } 131 | 132 | if ((align == AlignR && i <= 0) || (align == AlignL && i >= textlen)) { 133 | break; 134 | } else if (nextfont) { 135 | charexists = 0; 136 | usedfont = nextfont; 137 | } else { 138 | /* Regardless of whether or not a fallback font is found, the 139 | * character must be drawn. */ 140 | charexists = 1; 141 | 142 | fccharset = FcCharSetCreate(); 143 | FcCharSetAddChar(fccharset, utf8codepoint); 144 | 145 | if (!drw->fonts->pattern) { 146 | /* Refer to the comment in xfont_create for more information. */ 147 | die("the first font in the cache must be loaded from a font string."); 148 | } 149 | 150 | fcpattern = FcPatternDuplicate(drw->fonts->pattern); 151 | FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 152 | FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); 153 | 154 | FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); 155 | FcDefaultSubstitute(fcpattern); 156 | match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); 157 | 158 | FcCharSetDestroy(fccharset); 159 | FcPatternDestroy(fcpattern); 160 | 161 | if (match) { 162 | usedfont = xfont_create(drw, NULL, match); 163 | if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { 164 | for (curfont = drw->fonts; curfont->next; curfont = curfont->next) 165 | ; /* NOP */ 166 | curfont->next = usedfont; 167 | } else { 168 | xfont_free(usedfont); 169 | usedfont = drw->fonts; 170 | } 171 | } 172 | } 173 | } 174 | if (d) 175 | XftDrawDestroy(d); 176 | 177 | return x; 178 | } 179 | -------------------------------------------------------------------------------- /patch/inputmethod.c: -------------------------------------------------------------------------------- 1 | static size_t nextrunetext(const char *text, size_t position, int inc) 2 | { 3 | ssize_t n; 4 | 5 | /* return location of next utf8 rune in the given direction (+1 or -1) */ 6 | for (n = position + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) 7 | ; 8 | return n; 9 | } 10 | 11 | /* return bytes from beginning of text to nth utf8 rune to the right */ 12 | static size_t runebytes(const char *text, size_t n) 13 | { 14 | size_t ret; 15 | 16 | ret = 0; 17 | while (n-- > 0) 18 | ret += nextrunetext(text + ret, 0, 1); 19 | return ret; 20 | } 21 | 22 | /* return number of characters from beginning of text to nth byte to the right 23 | */ 24 | static size_t runechars(const char *text, size_t n) 25 | { 26 | size_t ret, i; 27 | 28 | ret = i = 0; 29 | while (i < n) { 30 | i += nextrunetext(text + i, 0, 1); 31 | ret++; 32 | } 33 | return ret; 34 | } 35 | 36 | /* move caret on pre-edit text */ 37 | static void preeditcaret(XIC xic, XPointer clientdata, XPointer calldata) 38 | { 39 | XIMPreeditCaretCallbackStruct *pcaret; 40 | 41 | (void)xic; 42 | pcaret = (XIMPreeditCaretCallbackStruct *)calldata; 43 | if (!pcaret) 44 | return; 45 | switch (pcaret->direction) { 46 | case XIMForwardChar: 47 | cursor = nextrune(+1); 48 | break; 49 | case XIMBackwardChar: 50 | cursor = nextrune(-1); 51 | break; 52 | case XIMForwardWord: 53 | movewordedge(+1); 54 | break; 55 | case XIMBackwardWord: 56 | movewordedge(-1); 57 | break; 58 | case XIMLineStart: 59 | cursor = 0; 60 | break; 61 | case XIMLineEnd: 62 | if (preview[cursor] != '\0') 63 | cursor = strlen(preview); 64 | break; 65 | case XIMAbsolutePosition: 66 | cursor = runebytes(text, pcaret->position); 67 | break; 68 | case XIMDontChange: 69 | /* do nothing */ 70 | break; 71 | case XIMCaretUp: 72 | case XIMCaretDown: 73 | case XIMNextLine: 74 | case XIMPreviousLine: 75 | /* not implemented */ 76 | break; 77 | } 78 | pcaret->position = runechars(preview, cursor); 79 | } 80 | 81 | /* start input method pre-editing */ 82 | static int preeditstart(XIC xic, XPointer clientdata, XPointer calldata) 83 | { 84 | (void)xic; 85 | (void)calldata; 86 | (void)clientdata; 87 | composing = 1; 88 | printf("PREEDIT\n"); 89 | return BUFSIZ; 90 | } 91 | 92 | /* end input method pre-editing */ 93 | static void preeditdone(XIC xic, XPointer clientdata, XPointer calldata) 94 | { 95 | (void)xic; 96 | (void)clientdata; 97 | (void)calldata; 98 | printf("DONE\n"); 99 | 100 | composing = 0; 101 | } 102 | 103 | /* draw input method pre-edit text */ 104 | static void preeditdraw(XIC xic, XPointer clientdata, XPointer calldata) 105 | { 106 | XIMPreeditDrawCallbackStruct *pdraw; 107 | size_t beg, dellen, inslen, endlen; 108 | 109 | printf("DRAW\n"); 110 | 111 | (void)xic; 112 | pdraw = (XIMPreeditDrawCallbackStruct *)calldata; 113 | if (!pdraw) 114 | return; 115 | 116 | /* we do not support wide characters */ 117 | if (pdraw->text && pdraw->text->encoding_is_wchar == True) { 118 | fputs("warning: wchar is not supportecd; use utf8", stderr); 119 | return; 120 | } 121 | 122 | beg = runebytes(text, pdraw->chg_first); 123 | dellen = runebytes(preview + beg, pdraw->chg_length); 124 | inslen = pdraw->text ? runebytes(pdraw->text->string.multi_byte, pdraw->text->length) : 0; 125 | endlen = 0; 126 | if (beg + dellen < strlen(preview)) 127 | endlen = strlen(preview + beg + dellen); 128 | 129 | /* we cannot change text past the end of our pre-edit string */ 130 | 131 | if (beg + dellen >= BUFSIZ || beg + inslen >= BUFSIZ) 132 | return; 133 | 134 | /* get space for text to be copied, and copy it */ 135 | memmove(preview + beg + inslen, preview + beg + dellen, endlen + 1); 136 | if (pdraw->text && pdraw->text->length) 137 | memcpy(preview + beg, pdraw->text->string.multi_byte, inslen); 138 | (preview + beg + inslen + endlen)[0] = '\0'; 139 | 140 | /* get caret position */ 141 | cursor = runebytes(text, pdraw->caret); 142 | } 143 | 144 | static void init_input_method(XIM xim) 145 | { 146 | XVaNestedList preedit = NULL; 147 | XICCallback start, done, draw, caret; 148 | XIMStyle preeditstyle; 149 | XIMStyle statusstyle; 150 | XIMStyles *imstyles; 151 | int i; 152 | 153 | /* get styles supported by input method */ 154 | if (XGetIMValues(xim, XNQueryInputStyle, &imstyles, NULL) != NULL) 155 | fputs("XGetIMValues: could not obtain input method values", stderr); 156 | 157 | /* check whether input method support on-the-spot pre-editing */ 158 | preeditstyle = XIMPreeditNothing; 159 | statusstyle = XIMStatusNothing; 160 | for (i = 0; i < imstyles->count_styles; i++) { 161 | if (imstyles->supported_styles[i] & XIMPreeditCallbacks) { 162 | preeditstyle = XIMPreeditCallbacks; 163 | break; 164 | } 165 | } 166 | 167 | /* create callbacks for the input context */ 168 | start.client_data = NULL; 169 | done.client_data = NULL; 170 | draw.client_data = (XPointer)text; 171 | caret.client_data = (XPointer)text; 172 | start.callback = (XICProc)preeditstart; 173 | done.callback = (XICProc)preeditdone; 174 | draw.callback = (XICProc)preeditdraw; 175 | caret.callback = (XICProc)preeditcaret; 176 | 177 | /* create list of values for input context */ 178 | preedit = XVaCreateNestedList(0, XNPreeditStartCallback, &start, XNPreeditDoneCallback, 179 | &done, XNPreeditDrawCallback, &draw, XNPreeditCaretCallback, 180 | &caret, NULL); 181 | if (preedit == NULL) 182 | fputs("XVaCreateNestedList: could not create nested list", stderr); 183 | 184 | xic = XCreateIC(xim, XNInputStyle, preeditstyle | statusstyle, XNPreeditAttributes, preedit, 185 | XNClientWindow, win, XNFocusWindow, win, NULL); 186 | XFree(preedit); 187 | 188 | long eventmask; 189 | /* get events the input method is interested in */ 190 | if (XGetICValues(xic, XNFilterEvents, &eventmask, NULL)) 191 | fputs("XGetICValues: could not obtain input context values", stderr); 192 | 193 | XSelectInput(dpy, win, 194 | ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask | 195 | PointerMotionMask | eventmask); 196 | } 197 | -------------------------------------------------------------------------------- /config.def.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 | #if ALPHA_PATCH 6 | static int opacity = 1; /* -o option; if 0, then alpha is disabled */ 7 | #endif // ALPHA_PATCH 8 | #if CARET_WIDTH_PATCH 9 | static int caret_width = 2; /* -cw option; set default caret width */ 10 | #endif // CARET_WIDTH_PATCH 11 | #if FUZZYMATCH_PATCH 12 | static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ 13 | #endif // FUZZYMATCH_PATCH 14 | #if INCREMENTAL_PATCH 15 | static int incremental = 0; /* -r option; if 1, outputs text each time a key is pressed */ 16 | #endif // INCREMENTAL_PATCH 17 | #if INSTANT_PATCH 18 | static int instant = 0; /* -n option; if 1, selects matching item without the need to press enter */ 19 | #endif // INSTANT_PATCH 20 | #if CENTER_PATCH 21 | static int center = 0; /* -c option; if 0, dmenu won't be centered on the screen */ 22 | static int min_width = 500; /* minimum width when centered */ 23 | #endif // CENTER_PATCH 24 | #if BARPADDING_PATCH 25 | static const int vertpad = 10; /* vertical padding of bar */ 26 | static const int sidepad = 10; /* horizontal padding of bar */ 27 | #endif // BARPADDING_PATCH 28 | #if QUIET_PATCH 29 | static int quiet = 0; /* -q option; if 1, dmenu will not show any items if the search string is empty */ 30 | #endif // QUIET_PATCH 31 | #if RESTRICT_RETURN_PATCH 32 | static int restrict_return = 0; /* -1 option; if 1, disables shift-return and ctrl-return */ 33 | #endif // RESTRICT_RETURN_PATCH 34 | /* -fn option overrides fonts[0]; default X11 font or font set */ 35 | #if PANGO_PATCH 36 | static char *font = "monospace 10"; 37 | #else 38 | #if XRESOURCES_PATCH 39 | static char *fonts[] = 40 | #else 41 | static const char *fonts[] = 42 | #endif // XRESOURCES_PATCH 43 | { 44 | "monospace:size=10" 45 | }; 46 | #endif // PANGO_PATCH 47 | #if MANAGED_PATCH 48 | static char *prompt = NULL; /* -p option; prompt to the left of input field */ 49 | #else 50 | static const char *prompt = NULL; /* -p option; prompt to the left of input field */ 51 | #endif // MANAGED_PATCH 52 | #if DYNAMIC_OPTIONS_PATCH 53 | static const char *dynamic = NULL; /* -dy option; dynamic command to run on input change */ 54 | #endif // DYNAMIC_OPTIONS_PATCH 55 | #if SYMBOLS_PATCH 56 | static const char *symbol_1 = "<"; 57 | static const char *symbol_2 = ">"; 58 | #endif // SYMBOLS_PATCH 59 | 60 | #if ALPHA_PATCH 61 | static const unsigned int baralpha = 0xd0; 62 | static const unsigned int borderalpha = OPAQUE; 63 | static const unsigned int alphas[][3] = { 64 | /* fg bg border */ 65 | [SchemeNorm] = { OPAQUE, baralpha, borderalpha }, 66 | [SchemeSel] = { OPAQUE, baralpha, borderalpha }, 67 | #if BORDER_PATCH 68 | [SchemeBorder] = { OPAQUE, OPAQUE, OPAQUE }, 69 | #endif // BORDER_PATCH 70 | #if MORECOLOR_PATCH 71 | [SchemeMid] = { OPAQUE, baralpha, borderalpha }, 72 | #endif // MORECOLOR_PATCH 73 | #if HIGHLIGHT_PATCH 74 | [SchemeSelHighlight] = { OPAQUE, baralpha, borderalpha }, 75 | [SchemeNormHighlight] = { OPAQUE, baralpha, borderalpha }, 76 | #endif // HIGHLIGHT_PATCH 77 | #if HIGHPRIORITY_PATCH 78 | [SchemeHp] = { OPAQUE, baralpha, borderalpha }, 79 | #endif // HIGHPRIORITY_PATCH 80 | #if EMOJI_HIGHLIGHT_PATCH 81 | [SchemeHover] = { OPAQUE, baralpha, borderalpha }, 82 | [SchemeGreen] = { OPAQUE, baralpha, borderalpha }, 83 | [SchemeRed] = { OPAQUE, baralpha, borderalpha }, 84 | [SchemeYellow] = { OPAQUE, baralpha, borderalpha }, 85 | [SchemeBlue] = { OPAQUE, baralpha, borderalpha }, 86 | [SchemePurple] = { OPAQUE, baralpha, borderalpha }, 87 | #endif // EMOJI_HIGHLIGHT_PATCH 88 | #if VI_MODE_PATCH 89 | [SchemeCursor] = { OPAQUE, baralpha, borderalpha }, 90 | #endif // VI_MODE_PATCH 91 | #if CARET_SCHEME_PATCH 92 | [SchemeCaret] = { OPAQUE, baralpha, borderalpha }, 93 | #endif // CARET_SCHEME_PATCH 94 | }; 95 | #endif // ALPHA_PATCH 96 | 97 | static 98 | #if !XRESOURCES_PATCH 99 | const 100 | #endif // XRESOURCES_PATCH 101 | char *colors[][2] = { 102 | /* fg bg */ 103 | [SchemeNorm] = { "#bbbbbb", "#222222" }, 104 | [SchemeSel] = { "#eeeeee", "#005577" }, 105 | [SchemeOut] = { "#000000", "#00ffff" }, 106 | #if BORDER_PATCH 107 | [SchemeBorder] = { "#000000", "#005577" }, 108 | #endif // BORDER_PATCH 109 | #if MORECOLOR_PATCH 110 | [SchemeMid] = { "#eeeeee", "#770000" }, 111 | #endif // MORECOLOR_PATCH 112 | #if HIGHLIGHT_PATCH 113 | [SchemeSelHighlight] = { "#ffc978", "#005577" }, 114 | [SchemeNormHighlight] = { "#ffc978", "#222222" }, 115 | #endif // HIGHLIGHT_PATCH 116 | #if HIGHPRIORITY_PATCH 117 | [SchemeHp] = { "#bbbbbb", "#333333" }, 118 | #endif // HIGHPRIORITY_PATCH 119 | #if EMOJI_HIGHLIGHT_PATCH 120 | [SchemeHover] = { "#ffffff", "#353D4B" }, 121 | [SchemeGreen] = { "#ffffff", "#52E067" }, 122 | [SchemeRed] = { "#ffffff", "#e05252" }, 123 | [SchemeYellow] = { "#ffffff", "#e0c452" }, 124 | [SchemeBlue] = { "#ffffff", "#5280e0" }, 125 | [SchemePurple] = { "#ffffff", "#9952e0" }, 126 | #endif // EMOJI_HIGHLIGHT_PATCH 127 | #if VI_MODE_PATCH 128 | [SchemeCursor] = { "#222222", "#bbbbbb" }, 129 | #endif // VI_MODE_PATCH 130 | #if CARET_SCHEME_PATCH 131 | [SchemeCaret] = { "#eeeeee", "#222222" }, 132 | #endif // CARET_SCHEME_PATCH 133 | }; 134 | /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ 135 | static unsigned int lines = 0; 136 | #if GRID_PATCH 137 | /* -g option; if nonzero, dmenu uses a grid comprised of columns and lines */ 138 | static unsigned int columns = 0; 139 | #endif // GRID_PATCH 140 | #if LINE_HEIGHT_PATCH 141 | static unsigned int lineheight = 0; /* -h option; minimum height of a menu line */ 142 | static unsigned int min_lineheight = 8; 143 | #endif // LINE_HEIGHT_PATCH 144 | #if NAVHISTORY_PATCH 145 | static unsigned int maxhist = 15; 146 | static int histnodup = 1; /* if 0, record repeated histories */ 147 | #endif // NAVHISTORY_PATCH 148 | 149 | /* 150 | * Characters not considered part of a word while deleting words 151 | * for example: " /?\"&[]" 152 | */ 153 | #if PIPEOUT_PATCH 154 | static const char startpipe[] = "#"; 155 | #endif // PIPEOUT_PATCH 156 | static const char worddelimiters[] = " "; 157 | 158 | #if VI_MODE_PATCH 159 | /* 160 | * -vi option; if nonzero, vi mode is always enabled and can be 161 | * accessed with the global_esc keysym + mod mask 162 | */ 163 | static unsigned int vi_mode = 1; 164 | static unsigned int start_mode = 0; /* mode to use when -vi is passed. 0 = insert mode, 1 = normal mode */ 165 | static Key global_esc = { XK_n, Mod1Mask }; /* escape key when vi mode is not enabled explicitly */ 166 | static Key quit_keys[] = { 167 | /* keysym modifier */ 168 | { XK_q, 0 } 169 | }; 170 | #endif // VI_MODE_PATCH 171 | 172 | #if BORDER_PATCH 173 | /* Size of the window border */ 174 | static unsigned int border_width = 0; 175 | #endif // BORDER_PATCH 176 | 177 | #if PREFIXCOMPLETION_PATCH 178 | /* 179 | * Use prefix matching by default; can be inverted with the -x flag. 180 | */ 181 | static int use_prefix = 1; 182 | #endif // PREFIXCOMPLETION_PATCH 183 | -------------------------------------------------------------------------------- /patch/mousesupport.c: -------------------------------------------------------------------------------- 1 | void 2 | buttonpress(XEvent *e) 3 | { 4 | struct item *item; 5 | XButtonPressedEvent *ev = &e->xbutton; 6 | int x = 0, y = 0, h = bh, w; 7 | #if GRID_PATCH 8 | int i, cols; 9 | #endif // GRID_PATCH 10 | 11 | if (ev->window != win) { 12 | /* automatically close dmenu if the user clicks outside of dmenu, but 13 | * ignore the scroll wheel and buttons above that */ 14 | if (ev->button <= Button3) { 15 | exit(1); 16 | } 17 | return; 18 | } 19 | 20 | /* right-click: exit */ 21 | if (ev->button == Button3) 22 | exit(1); 23 | 24 | if (prompt && *prompt) 25 | x += promptw; 26 | 27 | /* input field */ 28 | w = (lines > 0 || !matches) ? mw - x : inputw; 29 | 30 | /* left-click on input: clear input, 31 | * NOTE: if there is no left-arrow the space for < is reserved so 32 | * add that to the input width */ 33 | #if SYMBOLS_PATCH 34 | if (ev->button == Button1 && 35 | ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + 36 | ((!prev || !curr->left) ? TEXTW(symbol_1) : 0)) || 37 | (lines > 0 && ev->y >= y && ev->y <= y + h))) { 38 | insert(NULL, -cursor); 39 | drawmenu(); 40 | return; 41 | } 42 | #else 43 | if (ev->button == Button1 && 44 | ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + 45 | ((!prev || !curr->left) ? TEXTW("<") : 0)) || 46 | (lines > 0 && ev->y >= y && ev->y <= y + h))) { 47 | insert(NULL, -cursor); 48 | drawmenu(); 49 | return; 50 | } 51 | #endif // SYMBOLS_PATCH 52 | /* middle-mouse click: paste selection */ 53 | if (ev->button == Button2) { 54 | XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 55 | utf8, utf8, win, CurrentTime); 56 | drawmenu(); 57 | return; 58 | } 59 | /* scroll up */ 60 | if (ev->button == Button4 && prev) { 61 | sel = curr = prev; 62 | calcoffsets(); 63 | drawmenu(); 64 | return; 65 | } 66 | /* scroll down */ 67 | if (ev->button == Button5 && next) { 68 | sel = curr = next; 69 | calcoffsets(); 70 | drawmenu(); 71 | return; 72 | } 73 | if (ev->button != Button1) 74 | return; 75 | if (ev->state & ShiftMask) 76 | return; 77 | if (lines > 0) { 78 | #if GRID_PATCH 79 | cols = columns ? columns : 1; 80 | for (i = 0, item = curr; item != next; item = item->right, i++) { 81 | if ( 82 | (ev->y >= y + ((i % lines) + 1) * bh) && // line y start 83 | (ev->y <= y + ((i % lines) + 2) * bh) && // line y end 84 | (ev->x >= x + ((i / lines) * (w / cols))) && // column x start 85 | (ev->x <= x + ((i / lines + 1) * (w / cols))) // column x end 86 | ) { 87 | clickitem(item, ev); 88 | return; 89 | } 90 | } 91 | #else 92 | /* vertical list: (ctrl)left-click on item */ 93 | for (item = curr; item != next; item = item->right) { 94 | y += h; 95 | if (ev->y >= y && ev->y <= (y + h)) { 96 | clickitem(item, ev); 97 | return; 98 | } 99 | } 100 | #endif // GRID_PATCH 101 | } else if (matches) { 102 | /* left-click on left arrow */ 103 | x += inputw; 104 | #if SYMBOLS_PATCH 105 | w = TEXTW(symbol_1); 106 | #else 107 | w = TEXTW("<"); 108 | #endif // SYMBOLS_PATCH 109 | if (prev && curr->left) { 110 | if (ev->x >= x && ev->x <= x + w) { 111 | sel = curr = prev; 112 | calcoffsets(); 113 | drawmenu(); 114 | return; 115 | } 116 | } 117 | /* horizontal list: (ctrl)left-click on item */ 118 | for (item = curr; item != next; item = item->right) { 119 | x += w; 120 | #if SYMBOLS_PATCH 121 | w = MIN(TEXTW(item->text), mw - x - TEXTW(symbol_2)); 122 | #else 123 | w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); 124 | #endif // SYMBOLS_PATCH 125 | if (ev->x >= x && ev->x <= x + w) { 126 | clickitem(item, ev); 127 | return; 128 | } 129 | } 130 | /* left-click on right arrow */ 131 | #if SYMBOLS_PATCH 132 | w = TEXTW(symbol_2); 133 | #else 134 | w = TEXTW(">"); 135 | #endif // SYMBOLS_PATCH 136 | x = mw - w; 137 | if (next && ev->x >= x && ev->x <= x + w) { 138 | sel = curr = next; 139 | calcoffsets(); 140 | drawmenu(); 141 | return; 142 | } 143 | } 144 | } 145 | 146 | static void 147 | clickitem(struct item *item, XButtonEvent *ev) 148 | { 149 | #if RESTRICT_RETURN_PATCH 150 | if (restrict_return && (ev->state & (ShiftMask | ControlMask))) 151 | return; 152 | #endif // RESTRICT_RETURN_PATCH 153 | 154 | #if !MULTI_SELECTION_PATCH 155 | #if PIPEOUT_PATCH 156 | if (item && !(ev->state & ShiftMask)) 157 | { 158 | if (item->text[0] == startpipe[0]) { 159 | strncpy(item->text + strlen(item->text),pipeout,8); 160 | puts(item->text+1); 161 | } 162 | #if PRINTINDEX_PATCH 163 | if (print_index) { 164 | printf("%d\n", item->index); 165 | } else { 166 | #if SEPARATOR_PATCH 167 | puts(item->text_output); 168 | #else 169 | puts(item->text); 170 | #endif // SEPARATOR_PATCH 171 | } 172 | #else 173 | puts(item->text); 174 | #endif // PRINTINDEX_PATCH 175 | } else { 176 | if (text[0] == startpipe[0]) { 177 | strncpy(text + strlen(text),pipeout,8); 178 | puts(text+1); 179 | } 180 | puts(text); 181 | } 182 | #elif PRINTINDEX_PATCH 183 | if (print_index) { 184 | printf("%d\n", item->index); 185 | } else { 186 | #if SEPARATOR_PATCH 187 | puts(item->text_output); 188 | #else 189 | puts(item->text); 190 | #endif // SEPARATOR_PATCH 191 | } 192 | #elif SEPARATOR_PATCH 193 | puts(item->text_output); 194 | #else 195 | puts(item->text); 196 | #endif // PIPEOUT_PATCH | PRINTINDEX_PATCH 197 | #endif // MULTI_SELECTION_PATCH 198 | 199 | sel = item; 200 | if (!(ev->state & ControlMask)) { 201 | #if NAVHISTORY_PATCH 202 | savehistory(item->text); 203 | #endif // NAVHISTORY_PATCH 204 | #if MULTI_SELECTION_PATCH 205 | selsel(); 206 | printsel(ev->state); 207 | #endif // MULTI_SELECTION_PATCH 208 | cleanup(); 209 | exit(0); 210 | } 211 | 212 | #if MULTI_SELECTION_PATCH 213 | selsel(); 214 | #else 215 | sel->out = 1; 216 | #endif // MULTI_SELECTION_PATCH 217 | drawmenu(); 218 | } 219 | 220 | #if MOTION_SUPPORT_PATCH 221 | void 222 | motionevent(XButtonEvent *ev) 223 | { 224 | struct item *item; 225 | int x = 0, y = 0, w; 226 | #if GRID_PATCH 227 | int i, cols; 228 | #endif // GRID_PATCH 229 | 230 | if (ev->window != win || matches == 0) 231 | return; 232 | 233 | if (prompt && *prompt) 234 | x += promptw; 235 | 236 | if (lines > 0) { 237 | /* input field */ 238 | w = mw - x; 239 | #if GRID_PATCH 240 | cols = columns ? columns : 1; 241 | /* grid view or vertical list */ 242 | for (i = 0, item = curr; item != next; item = item->right, i++) { 243 | if ( 244 | (ev->y >= y + ((i % lines) + 1) * bh) && // line y start 245 | (ev->y <= y + ((i % lines) + 2) * bh) && // line y end 246 | (ev->x >= x + ((i / lines) * (w / cols))) && // column x start 247 | (ev->x <= x + ((i / lines + 1) * (w / cols))) // column x end 248 | ) { 249 | sel = item; 250 | calcoffsets(); 251 | drawmenu(); 252 | break; 253 | } 254 | } 255 | #else 256 | /* vertical list */ 257 | w = mw - x; 258 | for (item = curr; item != next; item = item->right) { 259 | y += bh; 260 | if (ev->y >= y && ev->y <= (y + bh)) { 261 | sel = item; 262 | calcoffsets(); 263 | drawmenu(); 264 | break; 265 | } 266 | } 267 | #endif // GRID_PATCH 268 | return; 269 | } 270 | 271 | /* left-click on left arrow */ 272 | x += inputw; 273 | #if SYMBOLS_PATCH 274 | w = TEXTW(symbol_1); 275 | #else 276 | w = TEXTW("<"); 277 | #endif // SYMBOLS_PATCH 278 | /* horizontal list */ 279 | for (item = curr; item != next; item = item->right) { 280 | x += w; 281 | #if SYMBOLS_PATCH 282 | w = MIN(TEXTW(item->text), mw - x - TEXTW(symbol_2)); 283 | #else 284 | w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); 285 | #endif // SYMBOLS_PATCH 286 | if (ev->x >= x && ev->x <= x + w) { 287 | sel = item; 288 | calcoffsets(); 289 | drawmenu(); 290 | break; 291 | } 292 | } 293 | } 294 | #endif 295 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Similar to [dwm-flexipatch](https://github.com/bakkeby/dwm-flexipatch) this dmenu 5.4 (8b48986, 2 | 2025-09-29) project has a different take on patching. It uses preprocessor directives to decide 3 | whether or not to include a patch during build time. Essentially this means that this build, for 4 | better or worse, contains both the patched _and_ the original code. The aim being that you can 5 | select which patches to include and the build will contain that code and nothing more. 6 | 7 | For example to include the `alpha` patch then you would only need to flip this setting from 0 8 | to 1 in [patches.h](https://github.com/bakkeby/dmenu-flexipatch/blob/master/patches.def.h): 9 | ```c 10 | #define ALPHA_PATCH 1 11 | ``` 12 | 13 | Once you have found out what works for you and what doesn't then you should be in a better position 14 | to choose patches should you want to start patching from scratch. 15 | 16 | Alternatively if you have found the patches you want, but don't want the rest of the flexipatch 17 | entanglement on your plate then you may want to have a look at 18 | [flexipatch-finalizer](https://github.com/bakkeby/flexipatch-finalizer); a custom pre-processor 19 | tool that removes all the unused flexipatch code leaving you with a build that contains the patches 20 | you selected. 21 | 22 | Refer to [https://tools.suckless.org/dmenu/](https://tools.suckless.org/dmenu/) for details on 23 | dmenu, how to install it and how it works. 24 | 25 | Browsing patches? There is a [map of patches](https://coggle.it/diagram/YjT2DD6jBM9dayf3) diagram which tries to organise patches into categories. 26 | 27 | --- 28 | 29 | ### Changelog: 30 | 31 | 2025-11-29 - Added the bidi patch 32 | 33 | 2025-10-16 - Added the dynamic height and quiet patches 34 | 35 | 2025-05-28 - Added the colored caret and vi mode patches 36 | 37 | 2024-07-17 - Added the input method patch 38 | 39 | 2024-07-16 - Added the mouse motion support patch 40 | 41 | 2023-06-15 - Added the caret width patch 42 | 43 | 2022-09-05 - Removed the json patch due to maintenance and compatibility reasons, added the 44 | separator patch 45 | 46 | 2022-09-04 - Added the fzfexpect patch 47 | 48 | 2022-06-21 - Adding barpadding patch and relative input width patch 49 | 50 | 2022-03-02 - Bump to 5.1 51 | 52 | 2021-05-23 - Adding support for `ctrl+v` to paste and adding emoji-highlight patch 53 | 54 | 2021-05-17 - Added the restrict return, no sort, gridnav and plain-prompt (listfullwidth) patches 55 | 56 | 2021-05-15 - Added the tsv and printindex patches 57 | 58 | 2020-08-08 - Added the json, symbols, managed, morecolor, multi-selection and preselect patches 59 | 60 | 2020-08-05 - Added the grid, highlight, highpriority, dynamic options and numbers patches 61 | 62 | 2020-06-13 - Added the pango patch 63 | 64 | 2020-06-10 - Added the case-insensitive patch 65 | 66 | 2020-05-29 - Added the alpha patch (derived from Baitinq's [build](https://github.com/Baitinq/dmenu)) 67 | and the color emoji patch 68 | 69 | 2020-04-05 - Added fuzzyhighlight patch 70 | 71 | 2020-02-09 - Added revised border patch (adding command line parameter for setting border width) 72 | 73 | 2019-12-29 - Added xresources patch 74 | 75 | 2019-10-16 - Introduced [flexipatch-finalizer](https://github.com/bakkeby/flexipatch-finalizer) 76 | 77 | 2019-09-18 - Added border, center, fuzzymatch, incremental, initialtext, instant, line-height, 78 | mouse-support, navhistory, non-blocking-stdin, password, pipeout, printinputtext, 79 | rejectnomatch, scroll, vertfull, wmtype and xyw patches 80 | 81 | ### Patches included: 82 | 83 | - [alpha](https://github.com/bakkeby/patches/blob/master/dmenu/dmenu-alpha-5.0_20210725_523aa08.diff) 84 | - adds transparency for the dmenu window 85 | 86 | - [barpadding](https://github.com/bakkeby/patches/wiki/barpadding) 87 | - adds padding for dmenu in similar fashion to the [barpadding](https://dwm.suckless.org/patches/barpadding/) 88 | patch for dwm 89 | 90 | - [bidi](https://tools.suckless.org/dmenu/patches/bidi/) 91 | - adds support for Right-To-Left (RTL) languages, such as Hebrew, Arabic, and Farsi 92 | 93 | - [border](http://tools.suckless.org/dmenu/patches/border/) 94 | - adds a border around the dmenu window 95 | 96 | - [caret-width](https://github.com/DarkSamus669/dmenu-patches/blob/main/dmenu-caretwidth-5.2.diff) 97 | - makes the caret width configurable and overridable via a command line option 98 | 99 | - [case-insensitive](http://tools.suckless.org/dmenu/patches/case-insensitive/) 100 | - makes dmenu case-insensitive by default, replacing the case-insensitive `-i` option with a 101 | case sensitive `-s` option 102 | 103 | - [center](https://tools.suckless.org/dmenu/patches/center/) 104 | - this patch centers dmenu in the middle of the screen 105 | 106 | - color_emoji 107 | - enables color emoji in dmenu by removing a workaround for a BadLength error in the Xft 108 | library when color glyphs are used 109 | - enabling this will crash dmenu on encountering such glyphs unless you also have an updated 110 | Xft library that can handle them 111 | 112 | - [colored caret](https://tools.suckless.org/dmenu/patches/colored-caret/) 113 | - adds the `SchemeCaret` colour scheme allowing customised styling of the caret 114 | 115 | - [dynamic height](https://gist.github.com/mircodz/1d9b88db958089bb08adbf45eb53b66f) 116 | - adjusts the height of the bar depending on how many items are drawn in the list presentation 117 | 118 | - [dynamic_options](https://tools.suckless.org/dmenu/patches/dynamicoptions/) 119 | - adds a flag (`-dy`) which makes dmenu run the command given to it whenever input is changed 120 | with the current input as the last argument and update the option list according to the 121 | output of that command 122 | 123 | - [emoji-highlight](https://tools.suckless.org/dmenu/patches/emoji-highlight/) 124 | - this patch will allow for emojis on the left side with a colored background when selected 125 | 126 | - [fuzzyhighlight](https://tools.suckless.org/dmenu/patches/fuzzyhighlight/) 127 | - intended to be combined with the fuzzymatch patch, this makes it so that fuzzy matches are 128 | highlighted 129 | 130 | - [fuzzymatch](https://tools.suckless.org/dmenu/patches/fuzzymatch/) 131 | - adds support for fuzzy-matching to dmenu, allowing users to type non-consecutive portions 132 | of the string to be matched 133 | 134 | - [fzfexpect](https://github.com/DAFF0D11/dafmenu/blob/master/patches/dmenu-fzfexpect-5.1.diff) 135 | - adds fzf expect functionality in dmenu 136 | 137 | - [grid](https://tools.suckless.org/dmenu/patches/grid/) 138 | - allows dmenu's entries to be rendered in a grid by adding a new `-g` flag to specify the 139 | number of grid columns 140 | - the `-g` and `-l` options can be used together to create a G columns * L lines grid 141 | 142 | - [gridnav](https://tools.suckless.org/dmenu/patches/gridnav/) 143 | - adds the ability to move left and right through a grid (when using the grid patch) 144 | 145 | - [highlight](https://tools.suckless.org/dmenu/patches/highlight/) 146 | - this patch highlights the individual characters of matched text for each dmenu list entry 147 | 148 | - [highpriority](https://tools.suckless.org/dmenu/patches/highpriority/) 149 | - this patch will automatically sort the search result so that high priority items are shown 150 | first 151 | 152 | - [incremental](https://tools.suckless.org/dmenu/patches/incremental/) 153 | - this patch causes dmenu to print out the current text each time a key is pressed 154 | 155 | - [initialtext](https://tools.suckless.org/dmenu/patches/initialtext/) 156 | - adds an option to provide preselected text 157 | 158 | - input-method 159 | - adds support for input methods (fctix, ibus, etc.) 160 | 161 | - [instant](https://tools.suckless.org/dmenu/patches/instant/) 162 | - adds a flag that will cause dmenu to select an item immediately if there is only one 163 | matching option left 164 | 165 | - [~json~](https://tools.suckless.org/dmenu/patches/json/) 166 | - ~adds basic support for json files~ 167 | 168 | - [line-height](http://tools.suckless.org/dmenu/patches/line-height/) 169 | - adds a `-h` option which sets the minimum height of a dmenu line 170 | - this helps integrate dmenu with other UI elements that require a particular vertical size 171 | 172 | - [managed](https://tools.suckless.org/dmenu/patches/managed/) 173 | - adds a `-wm` flag which sets override_redirect to false; thus letting your window manager 174 | manage the dmenu window 175 | - this may be helpful in contexts where you don't want to exclusively bind dmenu or want to 176 | treat dmenu more as a "window" rather than as an overlay 177 | 178 | - [morecolor](https://tools.suckless.org/dmenu/patches/morecolor/) 179 | - adds an additional color scheme for highlighting entries adjacent to the current selection 180 | 181 | - [mouse-support](https://tools.suckless.org/dmenu/patches/mouse-support/) 182 | - adds basic mouse support for dmenu 183 | 184 | - [multi-selection](https://tools.suckless.org/dmenu/patches/multi-selection/) 185 | - without this patch when you press `Ctrl+Enter` dmenu just outputs current item and it is 186 | not possible to undo that 187 | - with this patch dmenu will output all selected items only on exit 188 | - it is also possible to deselect any selected item 189 | 190 | - [navhistory](https://tools.suckless.org/dmenu/patches/navhistory/) 191 | - provides dmenu the ability for history navigation similar to that of bash 192 | 193 | - [no-sort](https://tools.suckless.org/dmenu/patches/no-sort/) 194 | - adds the `-S` option to disable sorting menu items after matching 195 | - useful, for example, when menu items are sorted by their frequency of use (using an 196 | external cache) and the most frequently selected items should always appear first regardless 197 | of how they were exact, prefix, or substring matches 198 | 199 | - [non-blocking-stdin](https://tools.suckless.org/dmenu/patches/non_blocking_stdin/) 200 | - this is a patch to have dmenu read stdin in a non blocking way, making it wait for input 201 | both from stdin and from X 202 | - this means that you can continue feeding dmenu while you type 203 | - the patch is meant to be used along with the incremental patch in order to use stdout to 204 | feed stdin 205 | 206 | - [numbers](https://tools.suckless.org/dmenu/patches/numbers/) 207 | - adds text which displays the number of matched and total items in the top right corner of 208 | dmenu 209 | 210 | - [pango](https://github.com/StillANixRookie/dmenu-pango/) 211 | - adds simple markup for dmenu using pango markup 212 | 213 | - [password](https://tools.suckless.org/dmenu/patches/password/) 214 | - with this patch dmenu will not directly display the keyboard input, but instead replace it 215 | with dots 216 | - all data from stdin will be ignored 217 | 218 | - [pipeout](https://tools.suckless.org/dmenu/patches/pipeout/) 219 | - this patch allows the selected text to be piped back out with dmenu 220 | - this can be useful if you want to display the output of a command on the screen 221 | 222 | - [plain-prompt](https://tools.suckless.org/dmenu/patches/listfullwidth/) 223 | - simple change that avoids colors for the prompt by making it use the same style as the 224 | rest of the input field 225 | 226 | - [prefix-completion](https://tools.suckless.org/dmenu/patches/prefix-completion/) 227 | - changes the behaviour of matched items and the Tab key to allow tab completion 228 | 229 | - [preselect](https://tools.suckless.org/dmenu/patches/preselect/) 230 | - adds an option `-ps` to preselect an item by providing the index that should be pre-selected 231 | 232 | - [printindex](https://tools.suckless.org/dmenu/patches/printindex/) 233 | - allows dmenu to print out the 0-based index of matched text instead of the matched text 234 | itself 235 | - this can be useful in cases where you would like to select entries from one array of text 236 | but index into another, or when you are selecting from an ordered list of non-unique items 237 | 238 | - [printinputtext](https://tools.suckless.org/dmenu/patches/printinputtext/) 239 | - this patch adds a flag `-t` which makes Return key ignore selection and print the input 240 | text to stdout 241 | - the flag basically swaps the functions of Return and Shift+Return hotkeys 242 | 243 | - [quiet](https://github.com/baskerville/dmenu_qxyw/blob/master/dmenu_qxyw-hg.diff) 244 | - adds a flag (-q) which makes dmenu not show any items if the search string is empty 245 | 246 | - [rejectnomatch](https://tools.suckless.org/dmenu/patches/reject-no-match/) 247 | - adds a new flag to dmenu with which text input will be rejected if it would result in no 248 | matching item 249 | 250 | - relative_input_width 251 | - prior to commit [e1e1de7](https://git.suckless.org/dmenu/commit/e1e1de7b3b8399cba90ddca9613f837b2dbef7b9.html) 252 | the input width was calculated based on the input options 253 | - this feature was removed in favour of hardcoding the input width to always take up 1/3rd of 254 | the available space 255 | - this patch adds that feature back in with some bespoke performance optimisations at the cost 256 | of accuracy and correctness 257 | 258 | - [restrict-return](https://tools.suckless.org/dmenu/patches/restrict-return/) 259 | - adds a `-1` option which disables Shift-Return and Ctrl-Return 260 | - this guarantees that dmenu will only output one item, and that item was read from stdin 261 | 262 | - [scroll](https://tools.suckless.org/dmenu/patches/scroll/) 263 | - this patch adds support for text scrolling 264 | - it doesn't append `...` for long input anymore as it can handle long text 265 | 266 | - [separator](https://tools.suckless.org/dmenu/patches/separator/) 267 | - adds `-d` and `-D` flags which separates the input into two halves; one half to be 268 | displayed in dmenu and the other to be printed to stdout 269 | 270 | - [symbols](https://tools.suckless.org/dmenu/patches/symbols/) 271 | - allows the symbols, which are printed in dmenu to indicate that either the input is too 272 | long or there are too many options to be shown in dmenu in one line, to be defined 273 | 274 | - [tsv](https://tools.suckless.org/dmenu/patches/tsv/) 275 | - makes dmenu split input lines at first tab character and only display first part, but it 276 | will perform matching on and output full lines as usual 277 | - can be useful if you want to separate data and representation 278 | 279 | - [vertfull](https://tools.suckless.org/dmenu/patches/vertfull/) 280 | - prevents dmenu from indenting items at the same level as the prompt length 281 | 282 | - [vi mode](https://tools.suckless.org/dmenu/patches/vi-mode/) 283 | - adds basic vi mode capabilities to dmenu 284 | 285 | - [wmtype](https://github.com/Baitinq/dmenu/blob/master/patches/dmenu-wm_type.diff) 286 | - adds extended window manager hints such as \_NET_WM_WINDOW_TYPE and \_NET_WM_WINDOW_TYPE_DOCK 287 | 288 | - [xresources](https://tools.suckless.org/dmenu/patches/xresources/) 289 | - allows dmenu to read font and colors from Xresources 290 | - note that with this patch the Xresources settings takes precedence over command line arguments 291 | 292 | - [xyw](https://tools.suckless.org/dmenu/patches/xyw/) 293 | - adds options for specifying dmenu window position and width 294 | -------------------------------------------------------------------------------- /patches.def.h: -------------------------------------------------------------------------------- 1 | /* Patches */ 2 | 3 | /* The alpha patch adds transparency for the dmenu window. 4 | * You need to uncomment the corresponding line in config.mk to use the -lXrender library 5 | * when including this patch. 6 | * https://github.com/bakkeby/patches/blob/master/dmenu/dmenu-alpha-5.0_20210725_523aa08.diff 7 | */ 8 | #define ALPHA_PATCH 0 9 | 10 | /* This adds padding for dmenu in similar fashion to the similarly named patch for dwm. The idea 11 | * is to have dmenu appear on top of the bar when using said patch in dwm. 12 | * https://github.com/bakkeby/patches/wiki/barpadding 13 | */ 14 | #define BARPADDING_PATCH 0 15 | 16 | /* This patch adds proper support for Right-To-Left (RTL) languages, such as Hebrew, 17 | * Arabic, and Farsi. 18 | * 19 | * Texts combining both RTL and LTR languages are displayed correctly. This is 20 | * achieved using the GNU FriBiDi library, which is an additional dependency for 21 | * this patch. 22 | * 23 | * You need to uncomment the corresponding line in config.mk to use the fribidi library. 24 | * https://tools.suckless.org/dmenu/patches/bidi/ 25 | */ 26 | #define BIDI_PATCH 0 27 | 28 | /* This patch adds a border around the dmenu window. It is intended to be used with the center 29 | * or xyw patches, to make the menu stand out from similarly coloured windows. 30 | * http://tools.suckless.org/dmenu/patches/border/ 31 | */ 32 | #define BORDER_PATCH 0 33 | 34 | /* The caret scheme patch, a.k.a. colored caret patch, adds the option to change the color 35 | * of the caret via the SchemeCaret color scheme. 36 | * 37 | * https://tools.suckless.org/dmenu/patches/colored-caret/ 38 | */ 39 | #define CARET_SCHEME_PATCH 0 40 | 41 | /* By default the caret in dmenu has a width of 2 pixels. This patch makes that configurable 42 | * as well as overridable via a command line option. 43 | * https://github.com/DarkSamus669/dmenu-patches/blob/main/dmenu-caretwidth-5.2.diff 44 | */ 45 | #define CARET_WIDTH_PATCH 0 46 | 47 | /* This patch makes dmenu case-insensitive by default, replacing the 48 | * case-insensitive -i option with a case sensitive -s option. 49 | * http://tools.suckless.org/dmenu/patches/case-insensitive/ 50 | */ 51 | #define CASEINSENSITIVE_PATCH 0 52 | 53 | /* This patch centers dmenu in the middle of the screen. 54 | * https://tools.suckless.org/dmenu/patches/center/ 55 | */ 56 | #define CENTER_PATCH 0 57 | 58 | /* Minor patch to enable the use of Ctrl+v (XA_PRIMARY) and Ctrl+Shift+v (CLIPBOARD) to paste. 59 | * By default dmenu only supports Ctrl+y and Ctrl+Shift+y to paste. 60 | */ 61 | #define CTRL_V_TO_PASTE_PATCH 0 62 | 63 | /* This patch dyamically changes the size of the dmenu window based on how many items are 64 | * drawn in a vertical view. For this to work set a higher maximum of lines, e.g. -l 500. 65 | * https://gist.github.com/mircodz/1d9b88db958089bb08adbf45eb53b66f 66 | */ 67 | #define DYNAMIC_HEIGHT_PATCH 0 68 | 69 | /* This patch adds a flag (-dy) which makes dmenu run the command given to it whenever input 70 | * is changed with the current input as the last argument and update the option list according 71 | * to the output of that command. 72 | * https://tools.suckless.org/dmenu/patches/dynamicoptions/ 73 | */ 74 | #define DYNAMIC_OPTIONS_PATCH 0 75 | 76 | /* This patch will allow for emojis on the left side with a colored background when selected. 77 | * To test this try running: 78 | * $ echo -e ":b here\n:p there\n:r and here" | ./dmenu -p "Search..." -W 400 -l 20 -i -h -1 79 | * NB: the original patch came embedded with the the xyw patch, the morecolors patch and the 80 | * line height patch and as such is intended to be combined with these. 81 | * https://tools.suckless.org/dmenu/patches/emoji-highlight/ 82 | */ 83 | #define EMOJI_HIGHLIGHT_PATCH 0 84 | 85 | /* This patch adds support for fuzzy-matching to dmenu, allowing users to type non-consecutive 86 | * portions of the string to be matched. 87 | * https://tools.suckless.org/dmenu/patches/fuzzymatch/ 88 | */ 89 | #define FUZZYMATCH_PATCH 0 90 | 91 | /* Adds fzf-like functionality for dmenu. 92 | * Refer to https://github.com/DAFF0D11/dafmenu/ for documentation and example use cases. 93 | * https://github.com/DAFF0D11/dafmenu/blob/master/patches/dmenu-fzfexpect-5.1.diff 94 | */ 95 | #define FZFEXPECT_PATCH 0 96 | 97 | /* Allows dmenu's entries to be rendered in a grid by adding a new -g flag to specify 98 | * the number of grid columns. The -g and -l options can be used together to create a 99 | * G columns * L lines grid. 100 | * https://tools.suckless.org/dmenu/patches/grid/ 101 | */ 102 | #define GRID_PATCH 0 103 | 104 | /* This patch adds the ability to move left and right through a grid. 105 | * This patch depends on the grid patch. 106 | * https://tools.suckless.org/dmenu/patches/gridnav/ 107 | */ 108 | #define GRIDNAV_PATCH 0 109 | 110 | /* This patch highlights the individual characters of matched text for each dmenu list entry. 111 | * If combined with the fuzzymatch patch then fuzzy highlight will be used for highlighting 112 | * depending on whether fuzzy matching is enabled. 113 | * 114 | * Known issue: highlighting does not work properly when pango markup is used 115 | * 116 | * https://tools.suckless.org/dmenu/patches/highlight/ 117 | * https://tools.suckless.org/dmenu/patches/fuzzyhighlight/ 118 | */ 119 | #define HIGHLIGHT_PATCH 0 120 | 121 | /* This will automatically sort the search result so that high priority items are shown first. 122 | * https://tools.suckless.org/dmenu/patches/highpriority/ 123 | */ 124 | #define HIGHPRIORITY_PATCH 0 125 | 126 | /* This patch causes dmenu to print out the current text each time a key is pressed. 127 | * https://tools.suckless.org/dmenu/patches/incremental/ 128 | */ 129 | #define INCREMENTAL_PATCH 0 130 | 131 | /* This patch adds an option to provide preselected text. 132 | * https://tools.suckless.org/dmenu/patches/initialtext/ 133 | */ 134 | #define INITIALTEXT_PATCH 0 135 | 136 | /* Adds support for input methods (fctix, ibus, etc.) allowing the user to change the 137 | * keyboard layout while dmenu is open. 138 | * https://github.com/bakkeby/dmenu-flexipatch/pull/22 139 | */ 140 | #define INPUTMETHOD_PATCH 0 141 | 142 | /* This patch adds a flag which will cause dmenu to select an item immediately if there 143 | * is only one matching option left. 144 | * https://tools.suckless.org/dmenu/patches/instant/ 145 | */ 146 | #define INSTANT_PATCH 0 147 | 148 | /* This patch adds a '-h' option which sets the minimum height of a dmenu line. This helps 149 | * integrate dmenu with other UI elements that require a particular vertical size. 150 | * http://tools.suckless.org/dmenu/patches/line-height/ 151 | */ 152 | #define LINE_HEIGHT_PATCH 0 153 | 154 | /* This patch adds a -wm flag which sets override_redirect to false; thus letting your window 155 | * manager manage the dmenu window. 156 | * 157 | * This may be helpful in contexts where you don't want to exclusively bind dmenu or want to 158 | * treat dmenu more as a "window" rather than as an overlay. 159 | * https://tools.suckless.org/dmenu/patches/managed/ 160 | */ 161 | #define MANAGED_PATCH 0 162 | 163 | /* This patch adds an additional color scheme for highlighting entries adjacent to the current 164 | * selection. 165 | * https://tools.suckless.org/dmenu/patches/morecolor/ 166 | */ 167 | #define MORECOLOR_PATCH 0 168 | 169 | /* This patch adds basic mouse support for dmenu. 170 | * https://tools.suckless.org/dmenu/patches/mouse-support/ 171 | */ 172 | #define MOUSE_SUPPORT_PATCH 0 173 | 174 | /* Expands the above to support mouse hovering. 175 | * https://tools.suckless.org/dmenu/patches/mouse-support/ 176 | */ 177 | #define MOTION_SUPPORT_PATCH 0 178 | 179 | /* Without this patch when you press Ctrl+Enter dmenu just outputs current item and it is not 180 | * possible to undo that. 181 | * With this patch dmenu will output all selected items only on exit. It is also possible to 182 | * deselect any selected item. 183 | * Also refer to the dmenu_run replacement on the below URL that supports multiple selections. 184 | * 185 | * This patch is not compatible with, and takes precedence over, the json, printinputtext, 186 | * pipeout and non-blocking stdin patches. 187 | * 188 | * https://tools.suckless.org/dmenu/patches/multi-selection/ 189 | */ 190 | #define MULTI_SELECTION_PATCH 0 191 | 192 | /* This patch provides dmenu the ability for history navigation similar to that of bash. 193 | * 194 | * If you take this patch then it is recommended that you also uncomment the line in the 195 | * dmenu_run script which replaces the exec command. 196 | * 197 | * https://tools.suckless.org/dmenu/patches/navhistory/ 198 | */ 199 | #define NAVHISTORY_PATCH 0 200 | 201 | /* This patch adds back in the workaround for a BadLength error in the Xft library when color 202 | * glyphs are used. This is for systems that do not have an updated version of the Xft library 203 | * (or generally prefer monochrome fonts). 204 | */ 205 | #define NO_COLOR_EMOJI_PATCH 0 206 | 207 | /* Adds the -S option to disable sorting menu items after matching. Useful, for example, when menu 208 | * items are sorted by their frequency of use (using an external cache) and the most frequently 209 | * selected items should always appear first regardless of how they were exact, prefix, or 210 | * substring matches. 211 | * https://tools.suckless.org/dmenu/patches/no-sort/ 212 | */ 213 | #define NO_SORT_PATCH 0 214 | 215 | /* This is a patch to have dmenu read stdin in a non blocking way, making it wait for input both 216 | * from stdin and from X. This means that you can continue feeding dmenu while you type. 217 | * This patch is meant to be used along with the incremental patch, so that you can use stdout 218 | * to feed stdin. 219 | * 220 | * This patch is not compatible with the json and multi-selection patches, both of which takes 221 | * precedence over this patch. 222 | * 223 | * https://tools.suckless.org/dmenu/patches/non_blocking_stdin/ 224 | */ 225 | #define NON_BLOCKING_STDIN_PATCH 0 226 | 227 | /* Adds text which displays the number of matched and total items in the top right corner of dmenu. 228 | * https://tools.suckless.org/dmenu/patches/numbers/ 229 | */ 230 | #define NUMBERS_PATCH 0 231 | 232 | /* This patch adds simple markup for dmenu using pango markup. 233 | * This depends on the pango library v1.44 or greater. 234 | * You need to uncomment the corresponding lines in config.mk to use the pango libraries 235 | * when including this patch. 236 | * 237 | * Note that the pango patch is incompatible with the scroll patch and will result in 238 | * compilation errors if both are enabled. 239 | * 240 | * Known issue: not compatible with the scroll patch 241 | * 242 | * Also see: 243 | * https://developer.gnome.org/pygtk/stable/pango-markup-language.html 244 | * https://github.com/StillANixRookie/dmenu-pango 245 | */ 246 | #define PANGO_PATCH 0 247 | 248 | /* With this patch dmenu will not directly display the keyboard input, but instead replace 249 | * it with dots. All data from stdin will be ignored. 250 | * https://tools.suckless.org/dmenu/patches/password/ 251 | */ 252 | #define PASSWORD_PATCH 0 253 | 254 | /* This patch allows the selected text to be piped back out with dmenu. This can be useful if you 255 | * want to display the output of a command on the screen. 256 | * Only text starting with the character '#' is piped out by default. 257 | * 258 | * This patch is not compatible with the json and multi-select patches, both of which takes 259 | * precedence over this one. 260 | * 261 | * https://tools.suckless.org/dmenu/patches/pipeout/ 262 | */ 263 | #define PIPEOUT_PATCH 0 264 | 265 | /* Lifted from the listfullwidth patch this simple change just avoids colors for the prompt (with 266 | * the -p option or in config.h) by making it use the same style as the rest of the input field. 267 | * The rest of the listfullwidth patch is covered by the vertfull patch. 268 | * https://tools.suckless.org/dmenu/patches/listfullwidth/ 269 | */ 270 | #define PLAIN_PROMPT_PATCH 0 271 | 272 | /* This patch changes the behaviour of matched items and the Tab key to allow tab completion. 273 | * https://tools.suckless.org/dmenu/patches/prefix-completion/ 274 | */ 275 | #define PREFIXCOMPLETION_PATCH 0 276 | 277 | /* This patch adds an option -ps to specify an item by providing the index that should be 278 | * pre-selected. 279 | * https://tools.suckless.org/dmenu/patches/preselect/ 280 | */ 281 | #define PRESELECT_PATCH 0 282 | 283 | /* This patch allows dmenu to print out the 0-based index of matched text instead of the matched 284 | * text itself. This can be useful in cases where you would like to select entries from one array 285 | * of text but index into another, or when you are selecting from an ordered list of non-unique 286 | * items. 287 | * https://tools.suckless.org/dmenu/patches/printindex/ 288 | */ 289 | #define PRINTINDEX_PATCH 0 290 | 291 | /* This patch adds a flag (-t) which makes Return key to ignore selection and print the input 292 | * text to stdout. The flag basically swaps the functions of Return and Shift+Return hotkeys. 293 | * 294 | * This patch is not compatible with the multi-select and json patches, both of which takes 295 | * precedence over this one. 296 | * 297 | * https://tools.suckless.org/dmenu/patches/printinputtext/ 298 | */ 299 | #define PRINTINPUTTEXT_PATCH 0 300 | 301 | /* This patch adds a flag (-q) which makes dmenu not show any items if the search string is 302 | * empty. 303 | * https://github.com/baskerville/dmenu_qxyw/blob/master/dmenu_qxyw-hg.diff 304 | */ 305 | #define QUIET_PATCH 0 306 | 307 | /* This patch adds a new flag to dmenu with which text input will be rejected if it would 308 | * result in no matching item. 309 | * https://tools.suckless.org/dmenu/patches/reject-no-match/ 310 | */ 311 | #define REJECTNOMATCH_PATCH 0 312 | 313 | /* The input width used to be relative to the input options prior to commit e1e1de7: 314 | * https://git.suckless.org/dmenu/commit/e1e1de7b3b8399cba90ddca9613f837b2dbef7b9.html 315 | * 316 | * This had a performance hit when using large data sets and was removed in favour of having the 317 | * input width take up 1/3rd of the available space. 318 | * 319 | * This option adds that feature back in with some performance optimisations at the cost of 320 | * accuracy and correctness. 321 | */ 322 | #define RELATIVE_INPUT_WIDTH_PATCH 0 323 | 324 | /* This patch adds a '-1' option which disables Shift-Return and Ctrl-Return. 325 | * This guarantees that dmenu will only output one item, and that item was read from stdin. 326 | * The original patch used '-r'. This was changed to '-1' to avoid conflict with the incremental 327 | * patch. 328 | * https://tools.suckless.org/dmenu/patches/restrict-return/ 329 | */ 330 | #define RESTRICT_RETURN_PATCH 0 331 | 332 | /* This patch adds support for text scrolling and no longer appends '...' for long input as 333 | * it can handle long text. 334 | * 335 | * Known issue: not compatible with the pango patch 336 | * 337 | * https://tools.suckless.org/dmenu/patches/scroll/ 338 | */ 339 | #define SCROLL_PATCH 0 340 | 341 | /* This patch adds -d and -D flags which separates the input into two halves; one half to be 342 | * displayed in dmenu and the other to be printed to stdout. This patch takes precedence over 343 | * the TSV patch. 344 | * https://tools.suckless.org/dmenu/patches/separator/ 345 | */ 346 | #define SEPARATOR_PATCH 0 347 | 348 | /* This patch allows the symbols, which are printed in dmenu to indicate that either the input 349 | * is too long or there are too many options to be shown in dmenu in one line, to be defined. 350 | * https://tools.suckless.org/dmenu/patches/symbols/ 351 | */ 352 | #define SYMBOLS_PATCH 0 353 | 354 | /* With this patch dmenu will split input lines at first tab character and only display first 355 | * part, but it will perform matching on and output full lines as usual. 356 | * 357 | * This can be useful if you want to separate data and representation, for example, a music 358 | * player wrapper can display only a track title to user, but still supply full filename to 359 | * the underlying script. 360 | * https://tools.suckless.org/dmenu/patches/tsv/ 361 | */ 362 | #define TSV_PATCH 0 363 | 364 | /* This patch prevents dmenu from indenting items at the same level as the prompt length. 365 | * https://tools.suckless.org/dmenu/patches/vertfull/ 366 | */ 367 | #define VERTFULL_PATCH 0 368 | 369 | /* This patch adds basic vi mode capabilities to dmenu. 370 | * - movements inside typed text with [h|l|w|b|e|0|$] 371 | * - movements through list with [j|k|g|G|C-d|C-u] 372 | * - standard insertions with [a|A|i|I] 373 | * - paste after|before cursor with [p|P], use ctrl to use clipboard 374 | * - delete from cursor to eol with D 375 | * - delete the character under cursor with x 376 | * - Enter and Tab will work like normal 377 | * 378 | * https://tools.suckless.org/dmenu/patches/vi-mode/ 379 | */ 380 | #define VI_MODE_PATCH 0 381 | 382 | /* Adds extended window manager hints such as _NET_WM_WINDOW_TYPE and _NET_WM_WINDOW_TYPE_DOCK. 383 | * https://github.com/Baitinq/dmenu/blob/master/patches/dmenu-wm_type.diff 384 | */ 385 | #define WMTYPE_PATCH 0 386 | 387 | /* This patch adds the ability to configure dmenu via Xresources. At startup, dmenu will read and 388 | * apply the resources named below: 389 | * dmenu.font : font or font set 390 | * dmenu.background : normal background color 391 | * dmenu.foreground : normal foreground color 392 | * dmenu.selbackground : selected background color 393 | * dmenu.selforeground : selected foreground color 394 | * 395 | * See patch/xresources.c for more color settings. 396 | * 397 | * https://tools.suckless.org/dmenu/patches/xresources/ 398 | */ 399 | #define XRESOURCES_PATCH 0 400 | 401 | /* This patch adds options for specifying dmenu window position and width. 402 | * The center patch takes precedence over the XYW patch if enabled. 403 | * https://tools.suckless.org/dmenu/patches/xyw/ 404 | */ 405 | #define XYW_PATCH 0 406 | -------------------------------------------------------------------------------- /drw.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "patches.h" 9 | #include "drw.h" 10 | #include "util.h" 11 | 12 | #if BIDI_PATCH && !PANGO_PATCH 13 | #include 14 | #endif //BIDI_PATCH 15 | 16 | #if !PANGO_PATCH || HIGHLIGHT_PATCH 17 | #define UTF_INVALID 0xFFFD 18 | 19 | static int 20 | utf8decode(const char *s_in, long *u, int *err) 21 | { 22 | static const unsigned char lens[] = { 23 | /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24 | /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ 25 | /* 110XX */ 2, 2, 2, 2, 26 | /* 1110X */ 3, 3, 27 | /* 11110 */ 4, 28 | /* 11111 */ 0, /* invalid */ 29 | }; 30 | static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 }; 31 | static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 }; 32 | 33 | const unsigned char *s = (const unsigned char *)s_in; 34 | int len = lens[*s >> 3]; 35 | *u = UTF_INVALID; 36 | *err = 1; 37 | if (len == 0) 38 | return 1; 39 | 40 | long cp = s[0] & leading_mask[len - 1]; 41 | for (int i = 1; i < len; ++i) { 42 | if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) 43 | return i; 44 | cp = (cp << 6) | (s[i] & 0x3F); 45 | } 46 | /* out of range, surrogate, overlong encoding */ 47 | if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1]) 48 | return len; 49 | 50 | *err = 0; 51 | *u = cp; 52 | return len; 53 | } 54 | 55 | #if HIGHLIGHT_PATCH 56 | int 57 | utf8len(const char *c) 58 | { 59 | long utf8codepoint = 0; 60 | int utf8err = 0; 61 | return utf8decode(c, &utf8codepoint, &utf8err); 62 | } 63 | #endif // HIGHLIGHT_PATCH 64 | #endif // PANGO_PATCH 65 | 66 | #if BIDI_PATCH && !PANGO_PATCH 67 | static char fribidi_text[BUFSIZ] = ""; 68 | 69 | static void 70 | apply_fribidi(const char *str) 71 | { 72 | FriBidiStrIndex len = strlen(str); 73 | FriBidiChar logical[BUFSIZ]; 74 | FriBidiChar visual[BUFSIZ]; 75 | FriBidiParType base = FRIBIDI_PAR_ON; 76 | FriBidiCharSet charset; 77 | 78 | charset = fribidi_parse_charset("UTF-8"); 79 | len = fribidi_charset_to_unicode(charset, str, len, logical); 80 | fribidi_log2vis(logical, len, &base, visual, NULL, NULL, NULL); 81 | fribidi_unicode_to_charset(charset, visual, len, fribidi_text); 82 | } 83 | #endif //BIDI_PATCH 84 | 85 | Drw * 86 | #if ALPHA_PATCH 87 | drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap) 88 | #else 89 | drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) 90 | #endif // ALPHA_PATCH 91 | { 92 | Drw *drw = ecalloc(1, sizeof(Drw)); 93 | 94 | drw->dpy = dpy; 95 | drw->screen = screen; 96 | drw->root = root; 97 | drw->w = w; 98 | drw->h = h; 99 | #if ALPHA_PATCH 100 | drw->visual = visual; 101 | drw->depth = depth; 102 | drw->cmap = cmap; 103 | drw->drawable = XCreatePixmap(dpy, root, w, h, depth); 104 | drw->gc = XCreateGC(dpy, drw->drawable, 0, NULL); 105 | #else 106 | drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); 107 | drw->gc = XCreateGC(dpy, root, 0, NULL); 108 | #endif // ALPHA_PATCH 109 | XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); 110 | 111 | return drw; 112 | } 113 | 114 | void 115 | drw_resize(Drw *drw, unsigned int w, unsigned int h) 116 | { 117 | if (!drw) 118 | return; 119 | 120 | drw->w = w; 121 | drw->h = h; 122 | if (drw->drawable) 123 | XFreePixmap(drw->dpy, drw->drawable); 124 | #if ALPHA_PATCH 125 | drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, drw->depth); 126 | #else 127 | drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); 128 | #endif // ALPHA_PATCH 129 | } 130 | 131 | void 132 | drw_free(Drw *drw) 133 | { 134 | XFreePixmap(drw->dpy, drw->drawable); 135 | XFreeGC(drw->dpy, drw->gc); 136 | #if PANGO_PATCH 137 | drw_font_free(drw->font); 138 | #else 139 | drw_fontset_free(drw->fonts); 140 | #endif // PANGO_PATCH 141 | free(drw); 142 | } 143 | 144 | #if PANGO_PATCH 145 | /* This function is an implementation detail. Library users should use 146 | * drw_font_create instead. 147 | */ 148 | static Fnt * 149 | xfont_create(Drw *drw, const char *fontname) 150 | { 151 | Fnt *font; 152 | PangoFontMap *fontmap; 153 | PangoContext *context; 154 | PangoFontDescription *desc; 155 | PangoFontMetrics *metrics; 156 | 157 | if (!fontname) { 158 | die("no font specified."); 159 | } 160 | 161 | font = ecalloc(1, sizeof(Fnt)); 162 | font->dpy = drw->dpy; 163 | 164 | fontmap = pango_xft_get_font_map(drw->dpy, drw->screen); 165 | context = pango_font_map_create_context(fontmap); 166 | desc = pango_font_description_from_string(fontname); 167 | font->layout = pango_layout_new(context); 168 | pango_layout_set_font_description(font->layout, desc); 169 | 170 | metrics = pango_context_get_metrics(context, desc, pango_language_from_string ("en-us")); 171 | font->h = pango_font_metrics_get_height(metrics) / PANGO_SCALE; 172 | 173 | pango_font_metrics_unref(metrics); 174 | g_object_unref(context); 175 | 176 | return font; 177 | } 178 | #else 179 | /* This function is an implementation detail. Library users should use 180 | * drw_fontset_create instead. 181 | */ 182 | static Fnt * 183 | xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) 184 | { 185 | Fnt *font; 186 | XftFont *xfont = NULL; 187 | FcPattern *pattern = NULL; 188 | 189 | if (fontname) { 190 | /* Using the pattern found at font->xfont->pattern does not yield the 191 | * same substitution results as using the pattern returned by 192 | * FcNameParse; using the latter results in the desired fallback 193 | * behaviour whereas the former just results in missing-character 194 | * rectangles being drawn, at least with some fonts. */ 195 | if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { 196 | fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); 197 | return NULL; 198 | } 199 | if (!(pattern = FcNameParse((FcChar8 *) fontname))) { 200 | fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); 201 | XftFontClose(drw->dpy, xfont); 202 | return NULL; 203 | } 204 | } else if (fontpattern) { 205 | if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { 206 | fprintf(stderr, "error, cannot load font from pattern.\n"); 207 | return NULL; 208 | } 209 | } else { 210 | die("no font specified."); 211 | } 212 | 213 | #if NO_COLOR_EMOJI_PATCH 214 | /* Do not allow using color fonts. This is a workaround for a BadLength 215 | * error from Xft with color glyphs. Modelled on the Xterm workaround. See 216 | * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 217 | * https://lists.suckless.org/dev/1701/30932.html 218 | * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 219 | * and lots more all over the internet. 220 | */ 221 | FcBool iscol; 222 | if (FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { 223 | XftFontClose(drw->dpy, xfont); 224 | return NULL; 225 | } 226 | #endif // NO_COLOR_EMOJI_PATCH 227 | 228 | font = ecalloc(1, sizeof(Fnt)); 229 | font->xfont = xfont; 230 | font->pattern = pattern; 231 | font->h = xfont->ascent + xfont->descent; 232 | font->dpy = drw->dpy; 233 | 234 | return font; 235 | } 236 | #endif // PANGO_PATCH 237 | 238 | static void 239 | xfont_free(Fnt *font) 240 | { 241 | if (!font) 242 | return; 243 | #if PANGO_PATCH 244 | if (font->layout) 245 | g_object_unref(font->layout); 246 | #else 247 | if (font->pattern) 248 | FcPatternDestroy(font->pattern); 249 | XftFontClose(font->dpy, font->xfont); 250 | #endif // PANGO_PATCH 251 | free(font); 252 | } 253 | 254 | #if PANGO_PATCH 255 | Fnt* 256 | drw_font_create(Drw* drw, const char *font) 257 | { 258 | Fnt *fnt = NULL; 259 | 260 | if (!drw || !font) 261 | return NULL; 262 | 263 | fnt = xfont_create(drw, font); 264 | 265 | return (drw->font = fnt); 266 | } 267 | #else 268 | Fnt* 269 | drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) 270 | { 271 | Fnt *cur, *ret = NULL; 272 | size_t i; 273 | 274 | if (!drw || !fonts) 275 | return NULL; 276 | 277 | for (i = 1; i <= fontcount; i++) { 278 | if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { 279 | cur->next = ret; 280 | ret = cur; 281 | } 282 | } 283 | return (drw->fonts = ret); 284 | } 285 | #endif // PANGO_PATCH 286 | 287 | #if PANGO_PATCH 288 | void 289 | drw_font_free(Fnt *font) 290 | { 291 | if (font) 292 | xfont_free(font); 293 | } 294 | #else 295 | void 296 | drw_fontset_free(Fnt *font) 297 | { 298 | if (font) { 299 | drw_fontset_free(font->next); 300 | xfont_free(font); 301 | } 302 | } 303 | #endif // PANGO_PATCH 304 | 305 | void 306 | #if ALPHA_PATCH 307 | drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha) 308 | #else 309 | drw_clr_create(Drw *drw, Clr *dest, const char *clrname) 310 | #endif // ALPHA_PATCH 311 | { 312 | if (!drw || !dest || !clrname) 313 | return; 314 | 315 | #if ALPHA_PATCH 316 | if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap, 317 | clrname, dest)) 318 | die("error, cannot allocate color '%s'", clrname); 319 | 320 | dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24); 321 | #else 322 | if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), 323 | DefaultColormap(drw->dpy, drw->screen), 324 | clrname, dest)) 325 | die("error, cannot allocate color '%s'", clrname); 326 | #endif // ALPHA_PATCH 327 | } 328 | 329 | /* Create color schemes. */ 330 | Clr * 331 | #if ALPHA_PATCH 332 | drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount) 333 | #else 334 | drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) 335 | #endif // ALPHA_PATCH 336 | { 337 | size_t i; 338 | Clr *ret; 339 | 340 | /* need at least two colors for a scheme */ 341 | if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(Clr)))) 342 | return NULL; 343 | 344 | for (i = 0; i < clrcount; i++) 345 | #if ALPHA_PATCH 346 | drw_clr_create(drw, &ret[i], clrnames[i], alphas[i]); 347 | #else 348 | drw_clr_create(drw, &ret[i], clrnames[i]); 349 | #endif // ALPHA_PATCH 350 | return ret; 351 | } 352 | 353 | void 354 | drw_clr_free(Drw *drw, Clr *c) 355 | { 356 | if (!drw || !c) 357 | return; 358 | 359 | /* c is typedef XftColor Clr */ 360 | XftColorFree(drw->dpy, DefaultVisual(drw->dpy, drw->screen), 361 | DefaultColormap(drw->dpy, drw->screen), c); 362 | } 363 | 364 | void 365 | drw_scm_free(Drw *drw, Clr *scm, size_t clrcount) 366 | { 367 | size_t i; 368 | 369 | if (!drw || !scm) 370 | return; 371 | 372 | for (i = 0; i < clrcount; i++) 373 | drw_clr_free(drw, &scm[i]); 374 | free(scm); 375 | } 376 | 377 | #if !PANGO_PATCH 378 | void 379 | drw_setfontset(Drw *drw, Fnt *set) 380 | { 381 | if (drw) 382 | drw->fonts = set; 383 | } 384 | #endif // PANGO_PATCH 385 | 386 | void 387 | drw_setscheme(Drw *drw, Clr *scm) 388 | { 389 | if (drw) 390 | drw->scheme = scm; 391 | } 392 | 393 | void 394 | drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) 395 | { 396 | if (!drw || !drw->scheme) 397 | return; 398 | XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); 399 | if (filled) 400 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 401 | else 402 | XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); 403 | } 404 | 405 | #if PANGO_PATCH 406 | int 407 | drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert, Bool markup) 408 | { 409 | char buf[1024]; 410 | int i, ty, th; 411 | unsigned int ew, eh; 412 | XftDraw *d = NULL; 413 | size_t len; 414 | int render = x || y || w || h; 415 | 416 | if (!drw || (render && !drw->scheme) || !text || !drw->font) 417 | return 0; 418 | 419 | if (!render) { 420 | w = invert ? invert : ~invert; 421 | } else { 422 | XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); 423 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 424 | #if ALPHA_PATCH 425 | d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); 426 | #else 427 | d = XftDrawCreate(drw->dpy, drw->drawable, 428 | DefaultVisual(drw->dpy, drw->screen), 429 | DefaultColormap(drw->dpy, drw->screen)); 430 | #endif // ALPHA_PATCH 431 | x += lpad; 432 | w -= lpad; 433 | } 434 | 435 | len = strlen(text); 436 | 437 | if (len) { 438 | drw_font_getexts(drw->font, text, len, &ew, &eh, markup); 439 | th = eh; 440 | /* shorten text if necessary */ 441 | for (len = MIN(len, sizeof(buf) - 1); len && ew > w; len--) { 442 | drw_font_getexts(drw->font, text, len, &ew, &eh, markup); 443 | if (eh > th) 444 | th = eh; 445 | } 446 | 447 | if (len) { 448 | memcpy(buf, text, len); 449 | buf[len] = '\0'; 450 | if (len < strlen(text)) 451 | for (i = len; i && i > len - 3; buf[--i] = '.') 452 | ; /* NOP */ 453 | 454 | if (render) { 455 | ty = y + (h - th) / 2; 456 | if (markup) 457 | pango_layout_set_markup(drw->font->layout, buf, len); 458 | else 459 | pango_layout_set_text(drw->font->layout, buf, len); 460 | pango_xft_render_layout(d, &drw->scheme[invert ? ColBg : ColFg], 461 | drw->font->layout, x * PANGO_SCALE, ty * PANGO_SCALE); 462 | if (markup) /* clear markup attributes */ 463 | pango_layout_set_attributes(drw->font->layout, NULL); 464 | } 465 | x += ew; 466 | w -= ew; 467 | } 468 | } 469 | 470 | if (d) 471 | XftDrawDestroy(d); 472 | 473 | return x + (render ? w : 0); 474 | } 475 | #else 476 | int 477 | drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) 478 | { 479 | int ty, ellipsis_x = 0; 480 | unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; 481 | XftDraw *d = NULL; 482 | Fnt *usedfont, *curfont, *nextfont; 483 | int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; 484 | long utf8codepoint = 0; 485 | const char *utf8str; 486 | FcCharSet *fccharset; 487 | FcPattern *fcpattern; 488 | FcPattern *match; 489 | XftResult result; 490 | int charexists = 0, overflow = 0; 491 | /* keep track of a couple codepoints for which we have no match. */ 492 | static unsigned int nomatches[128], ellipsis_width, invalid_width; 493 | static const char invalid[] = "�"; 494 | const char *ellipsis = "..."; 495 | 496 | if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) 497 | return 0; 498 | 499 | if (!render) { 500 | w = invert ? invert : ~invert; 501 | } else { 502 | XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); 503 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 504 | #if ALPHA_PATCH 505 | d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); 506 | #else 507 | d = XftDrawCreate(drw->dpy, drw->drawable, 508 | DefaultVisual(drw->dpy, drw->screen), 509 | DefaultColormap(drw->dpy, drw->screen)); 510 | #endif // ALPHA_PATCH 511 | x += lpad; 512 | w -= lpad; 513 | } 514 | 515 | if (!ellipsis_width && render) 516 | ellipsis_width = drw_fontset_getwidth(drw, ellipsis); 517 | if (!invalid_width && render) 518 | invalid_width = drw_fontset_getwidth(drw, invalid); 519 | 520 | #if BIDI_PATCH 521 | apply_fribidi(text); 522 | text = fribidi_text; 523 | #endif // BIDI_PATCH 524 | 525 | usedfont = drw->fonts; 526 | while (1) { 527 | ew = ellipsis_len = utf8err = utf8strlen = 0; 528 | utf8str = text; 529 | nextfont = NULL; 530 | while (*text) { 531 | utf8charlen = utf8decode(text, &utf8codepoint, &utf8err); 532 | for (curfont = drw->fonts; curfont; curfont = curfont->next) { 533 | charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); 534 | if (charexists) { 535 | drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); 536 | if (ew + ellipsis_width <= w) { 537 | /* keep track where the ellipsis still fits */ 538 | ellipsis_x = x + ew; 539 | ellipsis_w = w - ew; 540 | ellipsis_len = utf8strlen; 541 | } 542 | 543 | if (ew + tmpw > w) { 544 | overflow = 1; 545 | /* called from drw_fontset_getwidth_clamp(): 546 | * it wants the width AFTER the overflow 547 | */ 548 | if (!render) 549 | x += tmpw; 550 | else 551 | utf8strlen = ellipsis_len; 552 | } else if (curfont == usedfont) { 553 | text += utf8charlen; 554 | utf8strlen += utf8err ? 0 : utf8charlen; 555 | ew += utf8err ? 0 : tmpw; 556 | } else { 557 | nextfont = curfont; 558 | } 559 | break; 560 | } 561 | } 562 | 563 | if (overflow || !charexists || nextfont || utf8err) 564 | break; 565 | else 566 | charexists = 0; 567 | } 568 | 569 | if (utf8strlen) { 570 | if (render) { 571 | ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; 572 | XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], 573 | usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); 574 | } 575 | x += ew; 576 | w -= ew; 577 | } 578 | if (utf8err && (!render || invalid_width < w)) { 579 | if (render) 580 | drw_text(drw, x, y, w, h, 0, invalid, invert); 581 | x += invalid_width; 582 | w -= invalid_width; 583 | } 584 | if (render && overflow && ellipsis_w) 585 | drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, ellipsis, invert); 586 | 587 | if (!*text || overflow) { 588 | break; 589 | } else if (nextfont) { 590 | charexists = 0; 591 | usedfont = nextfont; 592 | } else { 593 | /* Regardless of whether or not a fallback font is found, the 594 | * character must be drawn. */ 595 | charexists = 1; 596 | 597 | hash = (unsigned int)utf8codepoint; 598 | hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; 599 | hash = ((hash >> 15) ^ hash) * 0xD35A2D97; 600 | h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); 601 | h1 = (hash >> 17) % LENGTH(nomatches); 602 | /* avoid expensive XftFontMatch call when we know we won't find a match */ 603 | if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint) 604 | goto no_match; 605 | 606 | fccharset = FcCharSetCreate(); 607 | FcCharSetAddChar(fccharset, utf8codepoint); 608 | 609 | if (!drw->fonts->pattern) { 610 | /* Refer to the comment in xfont_create for more information. */ 611 | die("the first font in the cache must be loaded from a font string."); 612 | } 613 | 614 | fcpattern = FcPatternDuplicate(drw->fonts->pattern); 615 | FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 616 | FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); 617 | #if NO_COLOR_EMOJI_PATCH 618 | FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); 619 | #endif // NO_COLOR_EMOJI_PATCH 620 | 621 | FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); 622 | FcDefaultSubstitute(fcpattern); 623 | match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); 624 | 625 | FcCharSetDestroy(fccharset); 626 | FcPatternDestroy(fcpattern); 627 | 628 | if (match) { 629 | usedfont = xfont_create(drw, NULL, match); 630 | if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { 631 | for (curfont = drw->fonts; curfont->next; curfont = curfont->next) 632 | ; /* NOP */ 633 | curfont->next = usedfont; 634 | } else { 635 | xfont_free(usedfont); 636 | nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; 637 | no_match: 638 | usedfont = drw->fonts; 639 | } 640 | } 641 | } 642 | } 643 | if (d) 644 | XftDrawDestroy(d); 645 | 646 | return x + (render ? w : 0); 647 | } 648 | #endif // PANGO_PATCH 649 | 650 | void 651 | drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) 652 | { 653 | if (!drw) 654 | return; 655 | 656 | XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); 657 | XSync(drw->dpy, False); 658 | } 659 | 660 | #if PANGO_PATCH 661 | unsigned int 662 | drw_font_getwidth(Drw *drw, const char *text, Bool markup) 663 | { 664 | if (!drw || !drw->font || !text) 665 | return 0; 666 | return drw_text(drw, 0, 0, 0, 0, 0, text, 0, markup); 667 | } 668 | 669 | unsigned int 670 | drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) 671 | { 672 | unsigned int tmp = 0; 673 | if (drw && drw->font && text && n) 674 | tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n, True); 675 | return MIN(n, tmp); 676 | } 677 | #else 678 | unsigned int 679 | drw_fontset_getwidth(Drw *drw, const char *text) 680 | { 681 | if (!drw || !drw->fonts || !text) 682 | return 0; 683 | return drw_text(drw, 0, 0, 0, 0, 0, text, 0); 684 | } 685 | 686 | unsigned int 687 | drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) 688 | { 689 | unsigned int tmp = 0; 690 | if (drw && drw->fonts && text && n) 691 | tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); 692 | return MIN(n, tmp); 693 | } 694 | #endif // PANGO_PATCH 695 | 696 | #if PANGO_PATCH 697 | void 698 | drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h, Bool markup) 699 | { 700 | if (!font || !text) 701 | return; 702 | 703 | PangoRectangle r; 704 | if (markup) 705 | pango_layout_set_markup(font->layout, text, len); 706 | else 707 | pango_layout_set_text(font->layout, text, len); 708 | pango_layout_get_extents(font->layout, 0, &r); 709 | if (markup) /* clear markup attributes */ 710 | pango_layout_set_attributes(font->layout, NULL); 711 | if (w) 712 | *w = r.width / PANGO_SCALE; 713 | if (h) 714 | *h = r.height / PANGO_SCALE; 715 | } 716 | #else 717 | void 718 | drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) 719 | { 720 | XGlyphInfo ext; 721 | 722 | if (!font || !text) 723 | return; 724 | 725 | XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); 726 | if (w) 727 | *w = ext.xOff; 728 | if (h) 729 | *h = font->h; 730 | } 731 | #endif // PANGO_PATCH 732 | 733 | Cur * 734 | drw_cur_create(Drw *drw, int shape) 735 | { 736 | Cur *cur; 737 | 738 | if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) 739 | return NULL; 740 | 741 | cur->cursor = XCreateFontCursor(drw->dpy, shape); 742 | 743 | return cur; 744 | } 745 | 746 | void 747 | drw_cur_free(Drw *drw, Cur *cursor) 748 | { 749 | if (!cursor) 750 | return; 751 | 752 | XFreeCursor(drw->dpy, cursor->cursor); 753 | free(cursor); 754 | } 755 | 756 | #if SCROLL_PATCH 757 | #include "patch/scroll.c" 758 | #endif 759 | -------------------------------------------------------------------------------- /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 | 19 | #include "patches.h" 20 | /* Patch incompatibility overrides */ 21 | #if MULTI_SELECTION_PATCH 22 | #undef NON_BLOCKING_STDIN_PATCH 23 | #undef PIPEOUT_PATCH 24 | #undef PRINTINPUTTEXT_PATCH 25 | #endif // MULTI_SELECTION_PATCH 26 | 27 | #include "drw.h" 28 | #include "util.h" 29 | #if GRIDNAV_PATCH 30 | #include 31 | #endif // GRIDNAV_PATCH 32 | 33 | /* macros */ 34 | #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ 35 | * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) 36 | #if PANGO_PATCH 37 | #define TEXTW(X) (drw_font_getwidth(drw, (X), False) + lrpad) 38 | #define TEXTWM(X) (drw_font_getwidth(drw, (X), True) + lrpad) 39 | #else 40 | #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) 41 | #endif // PANGO_PATCH 42 | #if ALPHA_PATCH 43 | #define OPAQUE 0xffU 44 | #define OPACITY "_NET_WM_WINDOW_OPACITY" 45 | #endif // ALPHA_PATCH 46 | 47 | /* enums */ 48 | enum { 49 | SchemeNorm, 50 | SchemeSel, 51 | SchemeOut, 52 | #if BORDER_PATCH 53 | SchemeBorder, 54 | #endif // BORDER_PATCH 55 | #if MORECOLOR_PATCH 56 | SchemeMid, 57 | #endif // MORECOLOR_PATCH 58 | #if HIGHLIGHT_PATCH 59 | SchemeNormHighlight, 60 | SchemeSelHighlight, 61 | #endif // HIGHLIGHT_PATCH 62 | #if HIGHPRIORITY_PATCH 63 | SchemeHp, 64 | #endif // HIGHPRIORITY_PATCH 65 | #if EMOJI_HIGHLIGHT_PATCH 66 | SchemeHover, 67 | SchemeGreen, 68 | SchemeYellow, 69 | SchemeBlue, 70 | SchemePurple, 71 | SchemeRed, 72 | #endif // EMOJI_HIGHLIGHT_PATCH 73 | #if VI_MODE_PATCH 74 | SchemeCursor, 75 | #endif // VI_MODE_PATCH 76 | #if CARET_SCHEME_PATCH 77 | SchemeCaret, 78 | #endif // CARET_SCHEME_PATCH 79 | SchemeLast, 80 | }; /* color schemes */ 81 | 82 | struct item { 83 | char *text; 84 | #if SEPARATOR_PATCH 85 | char *text_output; 86 | #elif TSV_PATCH 87 | char *stext; 88 | #endif // SEPARATOR_PATCH | TSV_PATCH 89 | struct item *left, *right; 90 | #if NON_BLOCKING_STDIN_PATCH 91 | struct item *next; 92 | #endif // NON_BLOCKING_STDIN_PATCH 93 | #if MULTI_SELECTION_PATCH 94 | int id; /* for multiselect */ 95 | #else 96 | int out; 97 | #endif // MULTI_SELECTION_PATCH 98 | #if HIGHPRIORITY_PATCH 99 | int hp; 100 | #endif // HIGHPRIORITY_PATCH 101 | #if FUZZYMATCH_PATCH 102 | double distance; 103 | #endif // FUZZYMATCH_PATCH 104 | #if PRINTINDEX_PATCH 105 | int index; 106 | #endif // PRINTINDEX_PATCH 107 | }; 108 | 109 | static char text[BUFSIZ] = ""; 110 | #if PIPEOUT_PATCH 111 | static char pipeout[8] = " | dmenu"; 112 | #endif // PIPEOUT_PATCH 113 | static char *embed; 114 | #if SEPARATOR_PATCH 115 | static char separator; 116 | static int separator_greedy; 117 | static int separator_reverse; 118 | #endif // SEPARATOR_PATCH 119 | static int bh, mw, mh; 120 | #if XYW_PATCH 121 | static int dmx = 0, dmy = 0; /* put dmenu at these x and y offsets */ 122 | static unsigned int dmw = 0; /* make dmenu this wide */ 123 | #endif // XYW_PATCH 124 | static int inputw = 0, promptw; 125 | #if PASSWORD_PATCH 126 | static int passwd = 0; 127 | #endif // PASSWORD_PATCH 128 | static int lrpad; /* sum of left and right padding */ 129 | #if BARPADDING_PATCH 130 | static int vp; /* vertical padding for bar */ 131 | static int sp; /* side padding for bar */ 132 | #endif // BARPADDING_PATCH 133 | #if REJECTNOMATCH_PATCH 134 | static int reject_no_match = 0; 135 | #endif // REJECTNOMATCH_PATCH 136 | static size_t cursor; 137 | static struct item *items = NULL; 138 | static struct item *matches, *matchend; 139 | static struct item *prev, *curr, *next, *sel; 140 | static int mon = -1, screen; 141 | #if PRINTINDEX_PATCH 142 | static int print_index = 0; 143 | #endif // PRINTINDEX_PATCH 144 | #if MANAGED_PATCH 145 | static int managed = 0; 146 | #endif // MANAGED_PATCH 147 | #if MULTI_SELECTION_PATCH 148 | static int *selid = NULL; 149 | static unsigned int selidsize = 0; 150 | #endif // MULTI_SELECTION_PATCH 151 | #if NO_SORT_PATCH 152 | static unsigned int sortmatches = 1; 153 | #endif // NO_SORT_PATCH 154 | #if PRINTINPUTTEXT_PATCH 155 | static int use_text_input = 0; 156 | #endif // PRINTINPUTTEXT_PATCH 157 | #if PRESELECT_PATCH 158 | static unsigned int preselected = 0; 159 | #endif // PRESELECT_PATCH 160 | #if EMOJI_HIGHLIGHT_PATCH 161 | static int commented = 0; 162 | static int animated = 0; 163 | #endif // EMOJI_HIGHLIGHT_PATCH 164 | 165 | static Atom clip, utf8; 166 | #if WMTYPE_PATCH 167 | static Atom type, dock; 168 | #endif // WMTYPE_PATCH 169 | static Display *dpy; 170 | static Window root, parentwin, win; 171 | static XIC xic; 172 | 173 | #if ALPHA_PATCH 174 | static int useargb = 0; 175 | static Visual *visual; 176 | static int depth; 177 | static Colormap cmap; 178 | #endif // ALPHA_PATCH 179 | 180 | static Drw *drw; 181 | static Clr *scheme[SchemeLast]; 182 | 183 | #include "patch/include.h" 184 | 185 | #include "config.h" 186 | 187 | static unsigned int 188 | textw_clamp(const char *str, unsigned int n) 189 | { 190 | unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; 191 | return MIN(w, n); 192 | } 193 | 194 | static void appenditem(struct item *item, struct item **list, struct item **last); 195 | static void calcoffsets(void); 196 | static void cleanup(void); 197 | static char * cistrstr(const char *s, const char *sub); 198 | static int drawitem(struct item *item, int x, int y, int w); 199 | static void drawmenu(void); 200 | static void grabfocus(void); 201 | static void grabkeyboard(void); 202 | static void match(void); 203 | static void insert(const char *str, ssize_t n); 204 | static size_t nextrune(int inc); 205 | static void movewordedge(int dir); 206 | static void keypress(XKeyEvent *ev); 207 | static void paste(void); 208 | #if ALPHA_PATCH 209 | static void xinitvisual(void); 210 | #endif // ALPHA_PATCH 211 | static void readstdin(void); 212 | static void run(void); 213 | static void setup(void); 214 | static void usage(void); 215 | 216 | #if CASEINSENSITIVE_PATCH 217 | static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp; 218 | static char *(*fstrstr)(const char *, const char *) = cistrstr; 219 | #else 220 | static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; 221 | static char *(*fstrstr)(const char *, const char *) = strstr; 222 | #endif // CASEINSENSITIVE_PATCH 223 | 224 | #include "patch/include.c" 225 | 226 | static void 227 | appenditem(struct item *item, struct item **list, struct item **last) 228 | { 229 | if (*last) 230 | (*last)->right = item; 231 | else 232 | *list = item; 233 | 234 | item->left = *last; 235 | item->right = NULL; 236 | *last = item; 237 | } 238 | 239 | static void 240 | calcoffsets(void) 241 | { 242 | int i, n, rpad = 0; 243 | 244 | if (lines > 0) { 245 | #if GRID_PATCH 246 | if (columns) 247 | n = lines * columns * bh; 248 | else 249 | n = lines * bh; 250 | #else 251 | n = lines * bh; 252 | #endif // GRID_PATCH 253 | } else { 254 | #if NUMBERS_PATCH 255 | rpad = TEXTW(numbers); 256 | #endif // NUMBERS_PATCH 257 | #if SYMBOLS_PATCH 258 | n = mw - (promptw + inputw + TEXTW(symbol_1) + TEXTW(symbol_2) + rpad); 259 | #else 260 | n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">") + rpad); 261 | #endif // SYMBOLS_PATCH 262 | } 263 | /* calculate which items will begin the next page and previous page */ 264 | for (i = 0, next = curr; next; next = next->right) 265 | if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n) 266 | break; 267 | for (i = 0, prev = curr; prev && prev->left; prev = prev->left) 268 | if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) 269 | break; 270 | } 271 | 272 | static void 273 | cleanup(void) 274 | { 275 | size_t i; 276 | 277 | XUngrabKeyboard(dpy, CurrentTime); 278 | #if INPUTMETHOD_PATCH 279 | XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); 280 | #endif // INPUTMETHOD_PATCH 281 | for (i = 0; i < SchemeLast; i++) 282 | drw_scm_free(drw, scheme[i], 2); 283 | for (i = 0; items && items[i].text; ++i) { 284 | #if SEPARATOR_PATCH 285 | free(separator_reverse ? items[i].text_output : items[i].text); 286 | #else 287 | free(items[i].text); 288 | #endif // SEPARATOR_PATCH 289 | } 290 | free(items); 291 | #if HIGHPRIORITY_PATCH 292 | for (i = 0; i < hplength; ++i) 293 | free(hpitems[i]); 294 | free(hpitems); 295 | #endif // HIGHPRIORITY_PATCH 296 | drw_free(drw); 297 | XSync(dpy, False); 298 | XCloseDisplay(dpy); 299 | #if MULTI_SELECTION_PATCH 300 | free(selid); 301 | #endif // MULTI_SELECTION_PATCH 302 | } 303 | 304 | static char * 305 | cistrstr(const char *s, const char *sub) 306 | { 307 | size_t len; 308 | 309 | for (len = strlen(sub); *s; s++) 310 | if (!strncasecmp(s, sub, len)) 311 | return (char *)s; 312 | return NULL; 313 | } 314 | 315 | static int 316 | drawitem(struct item *item, int x, int y, int w) 317 | { 318 | int r; 319 | #if TSV_PATCH && !SEPARATOR_PATCH 320 | char *text = item->stext; 321 | #else 322 | char *text = item->text; 323 | #endif // TSV_PATCH 324 | 325 | #if EMOJI_HIGHLIGHT_PATCH 326 | int iscomment = 0; 327 | if (text[0] == '>') { 328 | if (text[1] == '>') { 329 | iscomment = 3; 330 | switch (text[2]) { 331 | case 'r': 332 | drw_setscheme(drw, scheme[SchemeRed]); 333 | break; 334 | case 'g': 335 | drw_setscheme(drw, scheme[SchemeGreen]); 336 | break; 337 | case 'y': 338 | drw_setscheme(drw, scheme[SchemeYellow]); 339 | break; 340 | case 'b': 341 | drw_setscheme(drw, scheme[SchemeBlue]); 342 | break; 343 | case 'p': 344 | drw_setscheme(drw, scheme[SchemePurple]); 345 | break; 346 | #if HIGHLIGHT_PATCH 347 | case 'h': 348 | drw_setscheme(drw, scheme[SchemeNormHighlight]); 349 | break; 350 | #endif // HIGHLIGHT_PATCH 351 | case 's': 352 | drw_setscheme(drw, scheme[SchemeSel]); 353 | break; 354 | default: 355 | iscomment = 1; 356 | drw_setscheme(drw, scheme[SchemeNorm]); 357 | break; 358 | } 359 | } else { 360 | drw_setscheme(drw, scheme[SchemeNorm]); 361 | iscomment = 1; 362 | } 363 | } else if (text[0] == ':') { 364 | iscomment = 2; 365 | if (item == sel) { 366 | switch (text[1]) { 367 | case 'r': 368 | drw_setscheme(drw, scheme[SchemeRed]); 369 | break; 370 | case 'g': 371 | drw_setscheme(drw, scheme[SchemeGreen]); 372 | break; 373 | case 'y': 374 | drw_setscheme(drw, scheme[SchemeYellow]); 375 | break; 376 | case 'b': 377 | drw_setscheme(drw, scheme[SchemeBlue]); 378 | break; 379 | case 'p': 380 | drw_setscheme(drw, scheme[SchemePurple]); 381 | break; 382 | #if HIGHLIGHT_PATCH 383 | case 'h': 384 | drw_setscheme(drw, scheme[SchemeNormHighlight]); 385 | break; 386 | #endif // HIGHLIGHT_PATCH 387 | case 's': 388 | drw_setscheme(drw, scheme[SchemeSel]); 389 | break; 390 | default: 391 | drw_setscheme(drw, scheme[SchemeSel]); 392 | iscomment = 0; 393 | break; 394 | } 395 | } else { 396 | drw_setscheme(drw, scheme[SchemeNorm]); 397 | } 398 | } 399 | #endif // EMOJI_HIGHLIGHT_PATCH 400 | 401 | #if EMOJI_HIGHLIGHT_PATCH 402 | int temppadding = 0; 403 | if (iscomment == 2) { 404 | if (text[2] == ' ') { 405 | #if PANGO_PATCH 406 | temppadding = drw->font->h * 3; 407 | #else 408 | temppadding = drw->fonts->h * 3; 409 | #endif // PANGO_PATCH 410 | animated = 1; 411 | char dest[1000]; 412 | strcpy(dest, text); 413 | dest[6] = '\0'; 414 | drw_text(drw, x, y 415 | , temppadding 416 | #if LINE_HEIGHT_PATCH 417 | , MAX(lineheight, bh) 418 | #else 419 | , bh 420 | #endif // LINE_HEIGHT_PATCH 421 | , temppadding / 2.6 422 | , dest + 3 423 | , 0 424 | #if PANGO_PATCH 425 | , True 426 | #endif // PANGO_PATCH 427 | ); 428 | iscomment = 6; 429 | drw_setscheme(drw, sel == item ? scheme[SchemeHover] : scheme[SchemeNorm]); 430 | } 431 | } 432 | 433 | char *output; 434 | if (commented) { 435 | static char onestr[2]; 436 | onestr[0] = text[0]; 437 | onestr[1] = '\0'; 438 | output = onestr; 439 | } else { 440 | output = text; 441 | } 442 | #endif // EMOJI_HIGHLIGHT_PATCH 443 | 444 | if (item == sel) 445 | drw_setscheme(drw, scheme[SchemeSel]); 446 | #if HIGHPRIORITY_PATCH 447 | else if (item->hp) 448 | drw_setscheme(drw, scheme[SchemeHp]); 449 | #endif // HIGHPRIORITY_PATCH 450 | #if MORECOLOR_PATCH 451 | else if (item->left == sel || item->right == sel) 452 | drw_setscheme(drw, scheme[SchemeMid]); 453 | #endif // MORECOLOR_PATCH 454 | #if MULTI_SELECTION_PATCH 455 | else if (issel(item->id)) 456 | #else 457 | else if (item->out) 458 | #endif // MULTI_SELECTION_PATCH 459 | drw_setscheme(drw, scheme[SchemeOut]); 460 | else 461 | drw_setscheme(drw, scheme[SchemeNorm]); 462 | 463 | r = drw_text(drw 464 | #if EMOJI_HIGHLIGHT_PATCH 465 | , x + ((iscomment == 6) ? temppadding : 0) 466 | #else 467 | , x 468 | #endif // EMOJI_HIGHLIGHT_PATCH 469 | , y 470 | , w 471 | , bh 472 | #if EMOJI_HIGHLIGHT_PATCH 473 | , commented ? (bh - TEXTW(output) - lrpad) / 2 : lrpad / 2 474 | #else 475 | , lrpad / 2 476 | #endif // EMOJI_HIGHLIGHT_PATCH 477 | #if EMOJI_HIGHLIGHT_PATCH 478 | , output + iscomment 479 | #else 480 | , text 481 | #endif // EMOJI_HIGHLIGHT_PATCH 482 | , 0 483 | #if PANGO_PATCH 484 | , True 485 | #endif // PANGO_PATCH 486 | ); 487 | #if HIGHLIGHT_PATCH 488 | #if EMOJI_HIGHLIGHT_PATCH 489 | drawhighlights(item, output + iscomment, x + ((iscomment == 6) ? temppadding : 0), y, w); 490 | #else 491 | drawhighlights(item, x, y, w); 492 | #endif // EMOJI_HIGHLIGHT_PATCH 493 | #endif // HIGHLIGHT_PATCH 494 | return r; 495 | } 496 | 497 | static void 498 | drawmenu(void) 499 | { 500 | #if SCROLL_PATCH 501 | static int curpos, oldcurlen; 502 | int curlen, rcurlen; 503 | #else 504 | unsigned int curpos; 505 | #endif // SCROLL_PATCH 506 | struct item *item; 507 | int x = 0, y = 0, w, rpad = 0, itw = 0, stw = 0; 508 | #if LINE_HEIGHT_PATCH && PANGO_PATCH 509 | int fh = drw->font->h; 510 | #elif LINE_HEIGHT_PATCH 511 | int fh = drw->fonts->h; 512 | #endif // LINE_HEIGHT_PATCH 513 | #if PASSWORD_PATCH 514 | char *censort; 515 | #endif // PASSWORD_PATCH 516 | 517 | drw_setscheme(drw, scheme[SchemeNorm]); 518 | drw_rect(drw, 0, 0, mw, mh, 1, 1); 519 | 520 | if (prompt && *prompt) { 521 | #if !PLAIN_PROMPT_PATCH 522 | drw_setscheme(drw, scheme[SchemeSel]); 523 | #endif // PLAIN_PROMPT_PATCH 524 | x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0 525 | #if PANGO_PATCH 526 | , True 527 | #endif // PANGO_PATCH 528 | ); 529 | } 530 | /* draw input field */ 531 | w = (lines > 0 || !matches) ? mw - x : inputw; 532 | 533 | #if SCROLL_PATCH 534 | w -= lrpad / 2; 535 | x += lrpad / 2; 536 | rcurlen = TEXTW(text + cursor) - lrpad; 537 | curlen = TEXTW(text) - lrpad - rcurlen; 538 | curpos += curlen - oldcurlen; 539 | curpos = MIN(w, MAX(0, curpos)); 540 | curpos = MAX(curpos, w - rcurlen); 541 | curpos = MIN(curpos, curlen); 542 | oldcurlen = curlen; 543 | 544 | drw_setscheme(drw, scheme[SchemeNorm]); 545 | #if PASSWORD_PATCH 546 | if (passwd) { 547 | censort = ecalloc(1, sizeof(text)); 548 | memset(censort, '.', strlen(text)); 549 | drw_text_align(drw, x, 0, curpos, bh, censort, cursor, AlignR); 550 | drw_text_align(drw, x + curpos, 0, w - curpos, bh, censort + cursor, strlen(censort) - cursor, AlignL); 551 | free(censort); 552 | } else { 553 | drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR); 554 | drw_text_align(drw, x + curpos, 0, w - curpos, bh, text + cursor, strlen(text) - cursor, AlignL); 555 | } 556 | #else 557 | drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR); 558 | drw_text_align(drw, x + curpos, 0, w - curpos, bh, text + cursor, strlen(text) - cursor, AlignL); 559 | #endif // PASSWORD_PATCH 560 | 561 | #if VI_MODE_PATCH 562 | if (using_vi_mode && text[0] != '\0') { 563 | drw_setscheme(drw, scheme[SchemeCursor]); 564 | char vi_char[] = {text[cursor], '\0'}; 565 | drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0 566 | #if PANGO_PATCH 567 | , False 568 | #endif // PANGO_PATCH 569 | ); 570 | } else if (using_vi_mode) { 571 | drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); 572 | } else 573 | #endif // VI_MODE_PATCH 574 | #if LINE_HEIGHT_PATCH 575 | drw_rect(drw, x + curpos - 1, 2 + (bh-fh)/2, 2, fh - 4, 1, 0); 576 | #else 577 | drw_rect(drw, x + curpos - 1, 2, 2, bh - 4, 1, 0); 578 | #endif // LINE_HEIGHT_PATCH 579 | #else // !SCROLL_PATCH 580 | drw_setscheme(drw, scheme[SchemeNorm]); 581 | #if PASSWORD_PATCH 582 | if (passwd) { 583 | censort = ecalloc(1, sizeof(text)); 584 | memset(censort, '.', strlen(text)); 585 | drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0 586 | #if PANGO_PATCH 587 | , False 588 | #endif // PANGO_PATCH 589 | ); 590 | drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0 591 | #if PANGO_PATCH 592 | , False 593 | #endif // PANGO_PATCH 594 | ); 595 | free(censort); 596 | } else { 597 | drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0 598 | #if PANGO_PATCH 599 | , False 600 | #endif // PANGO_PATCH 601 | ); 602 | } 603 | #else 604 | drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0 605 | #if PANGO_PATCH 606 | , False 607 | #endif // PANGO_PATCH 608 | ); 609 | #endif // PASSWORD_PATCH 610 | 611 | curpos = TEXTW(text) - TEXTW(&text[cursor]); 612 | curpos += lrpad / 2 - 1; 613 | 614 | #if VI_MODE_PATCH 615 | if (using_vi_mode && text[0] != '\0') { 616 | drw_setscheme(drw, scheme[SchemeCursor]); 617 | char vi_char[] = {text[cursor], '\0'}; 618 | drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0 619 | #if PANGO_PATCH 620 | , False 621 | #endif // PANGO_PATCH 622 | ); 623 | } else if (using_vi_mode) { 624 | drw_setscheme(drw, scheme[SchemeNorm]); 625 | drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); 626 | } else 627 | #endif // VI_MODE_PATCH 628 | if (curpos < w) { 629 | #if CARET_SCHEME_PATCH 630 | drw_setscheme(drw, scheme[SchemeCaret]); 631 | #else 632 | drw_setscheme(drw, scheme[SchemeNorm]); 633 | #endif // CARET_SCHEME_PATCH 634 | #if CARET_WIDTH_PATCH && LINE_HEIGHT_PATCH 635 | drw_rect(drw, x + curpos, 2 + (bh-fh)/2, caret_width, fh - 4, 1, 0); 636 | #elif CARET_WIDTH_PATCH 637 | drw_rect(drw, x + curpos, 2, caret_width, bh - 4, 1, 0); 638 | #elif LINE_HEIGHT_PATCH 639 | drw_rect(drw, x + curpos, 2 + (bh-fh)/2, 2, fh - 4, 1, 0); 640 | #else 641 | drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); 642 | #endif // LINE_HEIGHT_PATCH 643 | } 644 | #endif // SCROLL_PATCH 645 | 646 | #if NUMBERS_PATCH 647 | recalculatenumbers(); 648 | rpad = TEXTW(numbers); 649 | #if BARPADDING_PATCH 650 | rpad += 2 * sp; 651 | #endif // BARPADDING_PATCH 652 | #if BORDER_PATCH 653 | rpad += border_width; 654 | #endif // BORDER_PATCH 655 | #endif // NUMBERS_PATCH 656 | 657 | #if QUIET_PATCH 658 | if (quiet && strlen(text) == 0) { 659 | #if DYNAMIC_HEIGHT_PATCH 660 | if (lines > 0) 661 | XResizeWindow(dpy, win, mw, bh); 662 | #endif // DYNAMIC_HEIGHT_PATCH 663 | goto skip_item_listing; 664 | } 665 | #endif // QUIET_PATCH 666 | 667 | if (lines > 0) { 668 | #if DYNAMIC_HEIGHT_PATCH || GRID_PATCH 669 | int i = 0; 670 | #endif // DYNAMIC_HEIGHT_PATCH | GRID_PATCH 671 | 672 | #if GRID_PATCH 673 | /* draw grid */ 674 | for (item = curr; item != next; item = item->right, i++) { 675 | if (columns) { 676 | #if VERTFULL_PATCH 677 | drawitem( 678 | item, 679 | 0 + ((i / lines) * (mw / columns)), 680 | y + (((i % lines) + 1) * bh), 681 | mw / columns 682 | ); 683 | #else 684 | drawitem( 685 | item, 686 | x + ((i / lines) * ((mw - x) / columns)), 687 | y + (((i % lines) + 1) * bh), 688 | (mw - x) / columns 689 | ); 690 | #endif // VERTFULL_PATCH 691 | } else { 692 | #if VERTFULL_PATCH 693 | drawitem(item, 0, y += bh, mw); 694 | #else 695 | drawitem(item, x, y += bh, mw - x); 696 | #endif // VERTFULL_PATCH 697 | } 698 | } 699 | #if DYNAMIC_HEIGHT_PATCH 700 | if (columns) { 701 | XResizeWindow(dpy, win, mw, (MIN(i, lines) + 1) * bh); 702 | } else { 703 | XResizeWindow(dpy, win, mw, (i + 1) * bh); 704 | } 705 | #endif // DYNAMIC_HEIGHT_PATCH 706 | 707 | #else 708 | /* draw vertical list */ 709 | for (item = curr; item != next; item = item->right) { 710 | #if DYNAMIC_HEIGHT_PATCH 711 | i++; 712 | #endif // DYNAMIC_HEIGHT_PATCH 713 | #if VERTFULL_PATCH 714 | drawitem(item, 0, y += bh, mw); 715 | #else 716 | drawitem(item, x, y += bh, mw - x); 717 | #endif // VERTFULL_PATCH 718 | } 719 | #if DYNAMIC_HEIGHT_PATCH 720 | XResizeWindow(dpy, win, mw, (i + 1) * bh); 721 | #endif // DYNAMIC_HEIGHT_PATCH 722 | #endif // GRID_PATCH 723 | } else if (matches) { 724 | /* draw horizontal list */ 725 | x += inputw; 726 | #if SYMBOLS_PATCH 727 | w = TEXTW(symbol_1); 728 | #else 729 | w = TEXTW("<"); 730 | #endif // SYMBOLS_PATCH 731 | if (curr->left) { 732 | drw_setscheme(drw, scheme[SchemeNorm]); 733 | #if SYMBOLS_PATCH 734 | drw_text(drw, x, 0, w, bh, lrpad / 2, symbol_1, 0 735 | #else 736 | drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0 737 | #endif // SYMBOLS_PATCH 738 | #if PANGO_PATCH 739 | , True 740 | #endif // PANGO_PATCH 741 | ); 742 | } 743 | x += w; 744 | for (item = curr; item != next; item = item->right) { 745 | #if SYMBOLS_PATCH 746 | stw = TEXTW(symbol_2); 747 | #else 748 | stw = TEXTW(">"); 749 | #endif // SYMBOLS_PATCH 750 | #if TSV_PATCH && !SEPARATOR_PATCH 751 | itw = textw_clamp(item->stext, mw - x - stw - rpad); 752 | #else 753 | itw = textw_clamp(item->text, mw - x - stw - rpad); 754 | #endif // TSV_PATCH 755 | x = drawitem(item, x, 0, itw); 756 | } 757 | if (next) { 758 | #if SYMBOLS_PATCH 759 | w = TEXTW(symbol_2); 760 | #else 761 | w = TEXTW(">"); 762 | #endif // SYMBOLS_PATCH 763 | drw_setscheme(drw, scheme[SchemeNorm]); 764 | drw_text(drw, mw - w - rpad, 0, w, bh, lrpad / 2 765 | #if SYMBOLS_PATCH 766 | , symbol_2 767 | #else 768 | , ">" 769 | #endif // SYMBOLS_PATCH 770 | , 0 771 | #if PANGO_PATCH 772 | , True 773 | #endif // PANGO_PATCH 774 | ); 775 | } 776 | } 777 | 778 | #if QUIET_PATCH 779 | skip_item_listing: 780 | #endif // QUIET_PATCH 781 | 782 | #if NUMBERS_PATCH 783 | drw_setscheme(drw, scheme[SchemeNorm]); 784 | #if PANGO_PATCH 785 | drw_text(drw, mw - rpad, 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0, False); 786 | #else 787 | drw_text(drw, mw - rpad, 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0); 788 | #endif // PANGO_PATCH 789 | #endif // NUMBERS_PATCH 790 | drw_map(drw, win, 0, 0, mw, mh); 791 | #if NON_BLOCKING_STDIN_PATCH 792 | XFlush(dpy); 793 | #endif // NON_BLOCKING_STDIN_PATCH 794 | } 795 | 796 | static void 797 | grabfocus(void) 798 | { 799 | struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; 800 | Window focuswin; 801 | int i, revertwin; 802 | 803 | for (i = 0; i < 100; ++i) { 804 | XGetInputFocus(dpy, &focuswin, &revertwin); 805 | if (focuswin == win) 806 | return; 807 | #if !MANAGED_PATCH 808 | XSetInputFocus(dpy, win, RevertToParent, CurrentTime); 809 | #endif // MANAGED_PATCH 810 | nanosleep(&ts, NULL); 811 | } 812 | die("cannot grab focus"); 813 | } 814 | 815 | static void 816 | grabkeyboard(void) 817 | { 818 | struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; 819 | int i; 820 | 821 | #if MANAGED_PATCH 822 | if (embed || managed) 823 | #else 824 | if (embed) 825 | #endif // MANAGED_PATCH 826 | return; 827 | /* try to grab keyboard, we may have to wait for another process to ungrab */ 828 | for (i = 0; i < 1000; i++) { 829 | if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, 830 | GrabModeAsync, CurrentTime) == GrabSuccess) { 831 | #if MOUSE_SUPPORT_PATCH 832 | /* one off attempt at grabbing the mouse pointer to avoid interactions 833 | * with other windows while dmenu is active */ 834 | XGrabPointer(dpy, DefaultRootWindow(dpy), True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); 835 | #endif // MOUSE_SUPPORT_PATCH 836 | return; 837 | } 838 | nanosleep(&ts, NULL); 839 | } 840 | die("cannot grab keyboard"); 841 | } 842 | 843 | static void 844 | match(void) 845 | { 846 | #if DYNAMIC_OPTIONS_PATCH 847 | if (dynamic && *dynamic) 848 | refreshoptions(); 849 | #endif // DYNAMIC_OPTIONS_PATCH 850 | 851 | #if FUZZYMATCH_PATCH 852 | if (fuzzy) { 853 | fuzzymatch(); 854 | return; 855 | } 856 | #endif 857 | static char **tokv = NULL; 858 | static int tokn = 0; 859 | 860 | char buf[sizeof text], *s; 861 | int i, tokc = 0; 862 | size_t len, textsize; 863 | struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; 864 | #if HIGHPRIORITY_PATCH 865 | struct item *lhpprefix, *hpprefixend; 866 | #endif // HIGHPRIORITY_PATCH 867 | #if NON_BLOCKING_STDIN_PATCH 868 | int preserve = 0; 869 | #endif // NON_BLOCKING_STDIN_PATCH 870 | 871 | strcpy(buf, text); 872 | /* separate input text into tokens to be matched individually */ 873 | for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) 874 | if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) 875 | die("cannot realloc %zu bytes:", tokn * sizeof *tokv); 876 | len = tokc ? strlen(tokv[0]) : 0; 877 | 878 | #if PREFIXCOMPLETION_PATCH 879 | if (use_prefix) { 880 | matches = lprefix = matchend = prefixend = NULL; 881 | textsize = strlen(text); 882 | } else { 883 | matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; 884 | textsize = strlen(text) + 1; 885 | } 886 | #else 887 | matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; 888 | textsize = strlen(text) + 1; 889 | #endif // PREFIXCOMPLETION_PATCH 890 | #if HIGHPRIORITY_PATCH 891 | lhpprefix = hpprefixend = NULL; 892 | #endif // HIGHPRIORITY_PATCH 893 | #if NON_BLOCKING_STDIN_PATCH && DYNAMIC_OPTIONS_PATCH 894 | for (item = items; item && (!(dynamic && *dynamic) || item->text); item = (dynamic && *dynamic) ? item + 1 : item->next) 895 | #elif NON_BLOCKING_STDIN_PATCH 896 | for (item = items; item; item = item->next) 897 | #else 898 | for (item = items; item && item->text; item++) 899 | #endif 900 | { 901 | for (i = 0; i < tokc; i++) 902 | if (!fstrstr(item->text, tokv[i])) 903 | break; 904 | #if DYNAMIC_OPTIONS_PATCH 905 | if (i != tokc && !(dynamic && *dynamic)) /* not all tokens match */ 906 | continue; 907 | #else 908 | if (i != tokc) /* not all tokens match */ 909 | continue; 910 | #endif // DYNAMIC_OPTIONS_PATCH 911 | #if HIGHPRIORITY_PATCH 912 | /* exact matches go first, then prefixes with high priority, then prefixes, then substrings */ 913 | #else 914 | /* exact matches go first, then prefixes, then substrings */ 915 | #endif // HIGHPRIORITY_PATCH 916 | #if NO_SORT_PATCH 917 | if (!sortmatches) 918 | appenditem(item, &matches, &matchend); 919 | else 920 | #endif // NO_SORT_PATCH 921 | if (!tokc || !fstrncmp(text, item->text, textsize)) 922 | appenditem(item, &matches, &matchend); 923 | #if HIGHPRIORITY_PATCH 924 | else if (item->hp && !fstrncmp(tokv[0], item->text, len)) 925 | appenditem(item, &lhpprefix, &hpprefixend); 926 | #endif // HIGHPRIORITY_PATCH 927 | else if (!fstrncmp(tokv[0], item->text, len)) 928 | appenditem(item, &lprefix, &prefixend); 929 | #if PREFIXCOMPLETION_PATCH 930 | else if (!use_prefix) 931 | #else 932 | else 933 | #endif // PREFIXCOMPLETION_PATCH 934 | appenditem(item, &lsubstr, &substrend); 935 | #if NON_BLOCKING_STDIN_PATCH 936 | if (sel == item) 937 | preserve = 1; 938 | #endif // NON_BLOCKING_STDIN_PATCH 939 | } 940 | #if HIGHPRIORITY_PATCH 941 | if (lhpprefix) { 942 | if (matches) { 943 | matchend->right = lhpprefix; 944 | lhpprefix->left = matchend; 945 | } else 946 | matches = lhpprefix; 947 | matchend = hpprefixend; 948 | } 949 | #endif // HIGHPRIORITY_PATCH 950 | if (lprefix) { 951 | if (matches) { 952 | matchend->right = lprefix; 953 | lprefix->left = matchend; 954 | } else 955 | matches = lprefix; 956 | matchend = prefixend; 957 | } 958 | #if PREFIXCOMPLETION_PATCH 959 | if (!use_prefix && lsubstr) 960 | #else 961 | if (lsubstr) 962 | #endif // PREFIXCOMPLETION_PATCH 963 | { 964 | if (matches) { 965 | matchend->right = lsubstr; 966 | lsubstr->left = matchend; 967 | } else 968 | matches = lsubstr; 969 | matchend = substrend; 970 | } 971 | #if NON_BLOCKING_STDIN_PATCH 972 | if (!preserve) 973 | #endif // NON_BLOCKING_STDIN_PATCH 974 | curr = sel = matches; 975 | 976 | #if INSTANT_PATCH 977 | if (instant && matches && matches==matchend && !lsubstr) { 978 | puts(matches->text); 979 | cleanup(); 980 | exit(0); 981 | } 982 | #endif // INSTANT_PATCH 983 | 984 | calcoffsets(); 985 | } 986 | 987 | static void 988 | insert(const char *str, ssize_t n) 989 | { 990 | if (strlen(text) + n > sizeof text - 1) 991 | return; 992 | 993 | #if REJECTNOMATCH_PATCH 994 | static char last[BUFSIZ] = ""; 995 | if (reject_no_match) { 996 | /* store last text value in case we need to revert it */ 997 | memcpy(last, text, BUFSIZ); 998 | } 999 | #endif // REJECTNOMATCH_PATCH 1000 | 1001 | /* move existing text out of the way, insert new text, and update cursor */ 1002 | memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); 1003 | if (n > 0) 1004 | memcpy(&text[cursor], str, n); 1005 | cursor += n; 1006 | match(); 1007 | 1008 | #if REJECTNOMATCH_PATCH 1009 | if (!matches && reject_no_match) { 1010 | /* revert to last text value if theres no match */ 1011 | memcpy(text, last, BUFSIZ); 1012 | cursor -= n; 1013 | match(); 1014 | } 1015 | #endif // REJECTNOMATCH_PATCH 1016 | } 1017 | 1018 | static size_t 1019 | nextrune(int inc) 1020 | { 1021 | ssize_t n; 1022 | 1023 | /* return location of next utf8 rune in the given direction (+1 or -1) */ 1024 | for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) 1025 | ; 1026 | return n; 1027 | } 1028 | 1029 | static void 1030 | movewordedge(int dir) 1031 | { 1032 | if (dir < 0) { /* move cursor to the start of the word*/ 1033 | while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) 1034 | cursor = nextrune(-1); 1035 | while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) 1036 | cursor = nextrune(-1); 1037 | } else { /* move cursor to the end of the word */ 1038 | while (text[cursor] && strchr(worddelimiters, text[cursor])) 1039 | cursor = nextrune(+1); 1040 | while (text[cursor] && !strchr(worddelimiters, text[cursor])) 1041 | cursor = nextrune(+1); 1042 | } 1043 | } 1044 | 1045 | static void 1046 | keypress(XKeyEvent *ev) 1047 | { 1048 | char buf[64]; 1049 | int len; 1050 | #if PREFIXCOMPLETION_PATCH 1051 | struct item * item; 1052 | #endif // PREFIXCOMPLETION_PATCH 1053 | KeySym ksym = NoSymbol; 1054 | Status status; 1055 | #if GRID_PATCH && GRIDNAV_PATCH 1056 | int i; 1057 | struct item *tmpsel; 1058 | bool offscreen = false; 1059 | #endif // GRIDNAV_PATCH 1060 | 1061 | len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); 1062 | switch (status) { 1063 | default: /* XLookupNone, XBufferOverflow */ 1064 | return; 1065 | case XLookupChars: /* composed string from input method */ 1066 | goto insert; 1067 | case XLookupKeySym: 1068 | case XLookupBoth: /* a KeySym and a string are returned: use keysym */ 1069 | break; 1070 | } 1071 | 1072 | #if VI_MODE_PATCH 1073 | if (using_vi_mode) { 1074 | vi_keypress(ksym, ev); 1075 | return; 1076 | } 1077 | 1078 | if (vi_mode && 1079 | (ksym == global_esc.ksym && 1080 | (ev->state & global_esc.state) == global_esc.state)) { 1081 | using_vi_mode = 1; 1082 | if (cursor) 1083 | cursor = nextrune(-1); 1084 | goto draw; 1085 | } 1086 | #endif // VI_MODE_PATCH 1087 | 1088 | if (ev->state & ControlMask) { 1089 | switch(ksym) { 1090 | #if FZFEXPECT_PATCH 1091 | case XK_a: expect("ctrl-a", ev); ksym = XK_Home; break; 1092 | case XK_b: expect("ctrl-b", ev); ksym = XK_Left; break; 1093 | case XK_c: expect("ctrl-c", ev); ksym = XK_Escape; break; 1094 | case XK_d: expect("ctrl-d", ev); ksym = XK_Delete; break; 1095 | case XK_e: expect("ctrl-e", ev); ksym = XK_End; break; 1096 | case XK_f: expect("ctrl-f", ev); ksym = XK_Right; break; 1097 | case XK_g: expect("ctrl-g", ev); ksym = XK_Escape; break; 1098 | case XK_h: expect("ctrl-h", ev); ksym = XK_BackSpace; break; 1099 | case XK_i: expect("ctrl-i", ev); ksym = XK_Tab; break; 1100 | case XK_j: expect("ctrl-j", ev); ksym = XK_Down; break; 1101 | case XK_J:/* fallthrough */ 1102 | case XK_l: expect("ctrl-l", ev); break; 1103 | case XK_m: expect("ctrl-m", ev); /* fallthrough */ 1104 | case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; 1105 | case XK_n: expect("ctrl-n", ev); ksym = XK_Down; break; 1106 | case XK_p: expect("ctrl-p", ev); ksym = XK_Up; break; 1107 | case XK_o: expect("ctrl-o", ev); break; 1108 | case XK_q: expect("ctrl-q", ev); break; 1109 | case XK_r: expect("ctrl-r", ev); break; 1110 | case XK_s: expect("ctrl-s", ev); break; 1111 | case XK_t: expect("ctrl-t", ev); break; 1112 | case XK_k: expect("ctrl-k", ev); ksym = XK_Up; break; 1113 | #else 1114 | case XK_a: ksym = XK_Home; break; 1115 | case XK_b: ksym = XK_Left; break; 1116 | case XK_c: ksym = XK_Escape; break; 1117 | case XK_d: ksym = XK_Delete; break; 1118 | case XK_e: ksym = XK_End; break; 1119 | case XK_f: ksym = XK_Right; break; 1120 | case XK_g: ksym = XK_Escape; break; 1121 | case XK_h: ksym = XK_BackSpace; break; 1122 | case XK_i: ksym = XK_Tab; break; 1123 | case XK_j: /* fallthrough */ 1124 | case XK_J: /* fallthrough */ 1125 | case XK_m: /* fallthrough */ 1126 | case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; 1127 | case XK_n: ksym = XK_Down; break; 1128 | case XK_p: ksym = XK_Up; break; 1129 | 1130 | case XK_k: /* delete right */ 1131 | text[cursor] = '\0'; 1132 | match(); 1133 | break; 1134 | #endif // FZFEXPECT_PATCH 1135 | #if FZFEXPECT_PATCH 1136 | case XK_u: expect("ctrl-u", ev); /* delete left */ 1137 | #else 1138 | case XK_u: /* delete left */ 1139 | #endif // FZFEXPECT_PATCH 1140 | insert(NULL, 0 - cursor); 1141 | break; 1142 | #if FZFEXPECT_PATCH 1143 | case XK_w: expect("ctrl-w", ev); /* delete word */ 1144 | #else 1145 | case XK_w: /* delete word */ 1146 | #endif // FZFEXPECT_PATCH 1147 | while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) 1148 | insert(NULL, nextrune(-1) - cursor); 1149 | while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) 1150 | insert(NULL, nextrune(-1) - cursor); 1151 | break; 1152 | #if FZFEXPECT_PATCH || CTRL_V_TO_PASTE_PATCH 1153 | case XK_v: 1154 | #if FZFEXPECT_PATCH 1155 | expect("ctrl-v", ev); 1156 | #endif // FZFEXPECT_PATCH 1157 | case XK_V: 1158 | XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 1159 | utf8, utf8, win, CurrentTime); 1160 | return; 1161 | #endif // FZFEXPECT_PATCH | CTRL_V_TO_PASTE_PATCH 1162 | #if FZFEXPECT_PATCH 1163 | case XK_y: expect("ctrl-y", ev); /* paste selection */ 1164 | #else 1165 | case XK_y: /* paste selection */ 1166 | #endif // FZFEXPECT_PATCH 1167 | case XK_Y: 1168 | XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 1169 | utf8, utf8, win, CurrentTime); 1170 | return; 1171 | #if FZFEXPECT_PATCH 1172 | case XK_x: expect("ctrl-x", ev); break; 1173 | case XK_z: expect("ctrl-z", ev); break; 1174 | #endif // FZFEXPECT_PATCH 1175 | case XK_Left: 1176 | case XK_KP_Left: 1177 | movewordedge(-1); 1178 | goto draw; 1179 | case XK_Right: 1180 | case XK_KP_Right: 1181 | movewordedge(+1); 1182 | goto draw; 1183 | case XK_Return: 1184 | case XK_KP_Enter: 1185 | #if RESTRICT_RETURN_PATCH 1186 | if (restrict_return) 1187 | break; 1188 | #endif // RESTRICT_RETURN_PATCH 1189 | #if MULTI_SELECTION_PATCH 1190 | selsel(); 1191 | #endif // MULTI_SELECTION_PATCH 1192 | break; 1193 | case XK_bracketleft: 1194 | cleanup(); 1195 | exit(1); 1196 | default: 1197 | return; 1198 | } 1199 | } else if (ev->state & Mod1Mask) { 1200 | switch(ksym) { 1201 | case XK_b: 1202 | movewordedge(-1); 1203 | goto draw; 1204 | case XK_f: 1205 | movewordedge(+1); 1206 | goto draw; 1207 | case XK_g: ksym = XK_Home; break; 1208 | case XK_G: ksym = XK_End; break; 1209 | case XK_h: ksym = XK_Up; break; 1210 | case XK_j: ksym = XK_Next; break; 1211 | case XK_k: ksym = XK_Prior; break; 1212 | case XK_l: ksym = XK_Down; break; 1213 | #if NAVHISTORY_PATCH 1214 | case XK_p: 1215 | navhistory(-1); 1216 | buf[0]=0; 1217 | break; 1218 | case XK_n: 1219 | navhistory(1); 1220 | buf[0]=0; 1221 | break; 1222 | #endif // NAVHISTORY_PATCH 1223 | default: 1224 | return; 1225 | } 1226 | } 1227 | 1228 | switch(ksym) { 1229 | default: 1230 | insert: 1231 | if (!iscntrl((unsigned char)*buf)) 1232 | insert(buf, len); 1233 | break; 1234 | case XK_Delete: 1235 | case XK_KP_Delete: 1236 | if (text[cursor] == '\0') 1237 | return; 1238 | cursor = nextrune(+1); 1239 | /* fallthrough */ 1240 | case XK_BackSpace: 1241 | if (cursor == 0) 1242 | return; 1243 | insert(NULL, nextrune(-1) - cursor); 1244 | break; 1245 | case XK_End: 1246 | case XK_KP_End: 1247 | if (text[cursor] != '\0') { 1248 | cursor = strlen(text); 1249 | break; 1250 | } 1251 | if (next) { 1252 | /* jump to end of list and position items in reverse */ 1253 | curr = matchend; 1254 | calcoffsets(); 1255 | curr = prev; 1256 | calcoffsets(); 1257 | while (next && (curr = curr->right)) 1258 | calcoffsets(); 1259 | } 1260 | sel = matchend; 1261 | break; 1262 | case XK_Escape: 1263 | cleanup(); 1264 | exit(1); 1265 | case XK_Home: 1266 | case XK_KP_Home: 1267 | if (sel == matches) { 1268 | cursor = 0; 1269 | break; 1270 | } 1271 | sel = curr = matches; 1272 | calcoffsets(); 1273 | break; 1274 | case XK_Left: 1275 | case XK_KP_Left: 1276 | #if GRID_PATCH && GRIDNAV_PATCH 1277 | if (columns > 1) { 1278 | if (!sel) 1279 | return; 1280 | tmpsel = sel; 1281 | for (i = 0; i < lines; i++) { 1282 | if (!tmpsel->left || tmpsel->left->right != tmpsel) 1283 | return; 1284 | if (tmpsel == curr) 1285 | offscreen = true; 1286 | tmpsel = tmpsel->left; 1287 | } 1288 | sel = tmpsel; 1289 | if (offscreen) { 1290 | curr = prev; 1291 | calcoffsets(); 1292 | } 1293 | break; 1294 | } 1295 | #endif // GRIDNAV_PATCH 1296 | if (cursor > 0 && (!sel || !sel->left || lines > 0)) { 1297 | cursor = nextrune(-1); 1298 | break; 1299 | } 1300 | if (lines > 0) 1301 | return; 1302 | /* fallthrough */ 1303 | case XK_Up: 1304 | case XK_KP_Up: 1305 | if (sel && sel->left && (sel = sel->left)->right == curr) { 1306 | curr = prev; 1307 | calcoffsets(); 1308 | } 1309 | break; 1310 | case XK_Next: 1311 | case XK_KP_Next: 1312 | if (!next) 1313 | return; 1314 | sel = curr = next; 1315 | calcoffsets(); 1316 | break; 1317 | case XK_Prior: 1318 | case XK_KP_Prior: 1319 | if (!prev) 1320 | return; 1321 | sel = curr = prev; 1322 | calcoffsets(); 1323 | break; 1324 | case XK_Return: 1325 | case XK_KP_Enter: 1326 | #if RESTRICT_RETURN_PATCH 1327 | if (restrict_return && (!sel || ev->state & (ShiftMask | ControlMask))) 1328 | break; 1329 | #endif // RESTRICT_RETURN_PATCH 1330 | #if !MULTI_SELECTION_PATCH 1331 | #if PIPEOUT_PATCH 1332 | #if PRINTINPUTTEXT_PATCH 1333 | if (sel && ( 1334 | (use_text_input && (ev->state & ShiftMask)) || 1335 | (!use_text_input && !(ev->state & ShiftMask)) 1336 | )) 1337 | #else 1338 | if (sel && !(ev->state & ShiftMask)) 1339 | #endif // PRINTINPUTTEXT_PATCH 1340 | { 1341 | if (sel->text[0] == startpipe[0]) { 1342 | strncpy(sel->text + strlen(sel->text),pipeout,8); 1343 | puts(sel->text+1); 1344 | } 1345 | #if PRINTINDEX_PATCH 1346 | if (print_index) 1347 | printf("%d\n", sel->index); 1348 | else 1349 | #if SEPARATOR_PATCH 1350 | puts(sel->text_output); 1351 | #else 1352 | puts(sel->text); 1353 | #endif // SEPARATOR_PATCH 1354 | #elif SEPARATOR_PATCH 1355 | puts(sel->text_output); 1356 | #else 1357 | puts(sel->text); 1358 | #endif // PRINTINDEX_PATCH | SEPARATOR_PATCH 1359 | } else { 1360 | if (text[0] == startpipe[0]) { 1361 | strncpy(text + strlen(text),pipeout,8); 1362 | puts(text+1); 1363 | } 1364 | puts(text); 1365 | } 1366 | #elif PRINTINPUTTEXT_PATCH 1367 | if (use_text_input) { 1368 | #if SEPARATOR_PATCH 1369 | puts((sel && (ev->state & ShiftMask)) ? sel->text_output : text); 1370 | #else 1371 | puts((sel && (ev->state & ShiftMask)) ? sel->text : text); 1372 | #endif // SEPARATOR_PATCH 1373 | #if PRINTINDEX_PATCH 1374 | } else if (print_index) { 1375 | printf("%d\n", (sel && !(ev->state & ShiftMask)) ? sel->index : -1); 1376 | #endif // PRINTINDEX_PATCH 1377 | } else { 1378 | #if SEPARATOR_PATCH 1379 | puts((sel && !(ev->state & ShiftMask)) ? sel->text_output : text); 1380 | #else 1381 | puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 1382 | #endif // SEPARATOR_PATCH 1383 | } 1384 | #elif PRINTINDEX_PATCH 1385 | if (print_index) { 1386 | printf("%d\n", (sel && !(ev->state & ShiftMask)) ? sel->index : -1); 1387 | } else { 1388 | #if SEPARATOR_PATCH 1389 | puts((sel && !(ev->state & ShiftMask)) ? sel->text_output : text); 1390 | #else 1391 | puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 1392 | #endif // SEPARATOR_PATCH 1393 | } 1394 | #elif SEPARATOR_PATCH 1395 | puts((sel && !(ev->state & ShiftMask)) ? sel->text_output : text); 1396 | #else 1397 | puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 1398 | #endif // PIPEOUT_PATCH | PRINTINPUTTEXT_PATCH | PRINTINDEX_PATCH 1399 | #endif // MULTI_SELECTION_PATCH 1400 | if (!(ev->state & ControlMask)) { 1401 | #if NAVHISTORY_PATCH 1402 | savehistory((sel && !(ev->state & ShiftMask)) 1403 | ? sel->text : text); 1404 | #endif // NAVHISTORY_PATCH 1405 | #if MULTI_SELECTION_PATCH 1406 | printsel(ev->state); 1407 | #endif // MULTI_SELECTION_PATCH 1408 | cleanup(); 1409 | exit(0); 1410 | } 1411 | #if !MULTI_SELECTION_PATCH 1412 | if (sel) 1413 | sel->out = 1; 1414 | #endif // MULTI_SELECTION_PATCH 1415 | break; 1416 | case XK_Right: 1417 | case XK_KP_Right: 1418 | #if GRID_PATCH && GRIDNAV_PATCH 1419 | if (columns > 1) { 1420 | if (!sel) 1421 | return; 1422 | tmpsel = sel; 1423 | for (i = 0; i < lines; i++) { 1424 | if (!tmpsel->right || tmpsel->right->left != tmpsel) 1425 | return; 1426 | tmpsel = tmpsel->right; 1427 | if (tmpsel == next) 1428 | offscreen = true; 1429 | } 1430 | sel = tmpsel; 1431 | if (offscreen) { 1432 | curr = next; 1433 | calcoffsets(); 1434 | } 1435 | break; 1436 | } 1437 | #endif // GRIDNAV_PATCH 1438 | if (text[cursor] != '\0') { 1439 | cursor = nextrune(+1); 1440 | break; 1441 | } 1442 | if (lines > 0) 1443 | return; 1444 | /* fallthrough */ 1445 | case XK_Down: 1446 | case XK_KP_Down: 1447 | if (sel && sel->right && (sel = sel->right) == next) { 1448 | curr = next; 1449 | calcoffsets(); 1450 | } 1451 | break; 1452 | case XK_Tab: 1453 | #if PREFIXCOMPLETION_PATCH 1454 | if (!matches) 1455 | break; /* cannot complete no matches */ 1456 | #if FUZZYMATCH_PATCH 1457 | /* only do tab completion if all matches start with prefix */ 1458 | for (item = matches; item && item->text; item = item->right) 1459 | if (item->text[0] != text[0]) 1460 | goto draw; 1461 | #endif // FUZZYMATCH_PATCH 1462 | strncpy(text, matches->text, sizeof text - 1); 1463 | text[sizeof text - 1] = '\0'; 1464 | len = cursor = strlen(text); /* length of longest common prefix */ 1465 | for (item = matches; item && item->text; item = item->right) { 1466 | cursor = 0; 1467 | while (cursor < len && text[cursor] == item->text[cursor]) 1468 | cursor++; 1469 | len = cursor; 1470 | } 1471 | memset(text + len, '\0', strlen(text) - len); 1472 | #else 1473 | if (!sel) 1474 | return; 1475 | cursor = strnlen(sel->text, sizeof text - 1); 1476 | memcpy(text, sel->text, cursor); 1477 | text[cursor] = '\0'; 1478 | match(); 1479 | #endif // PREFIXCOMPLETION_PATCH 1480 | break; 1481 | } 1482 | 1483 | draw: 1484 | #if INCREMENTAL_PATCH 1485 | if (incremental) { 1486 | puts(text); 1487 | fflush(stdout); 1488 | } 1489 | #endif // INCREMENTAL_PATCH 1490 | #if VI_MODE_PATCH 1491 | if (using_vi_mode && text[cursor] == '\0') 1492 | --cursor; 1493 | #endif // VI_MODE_PATCH 1494 | 1495 | drawmenu(); 1496 | } 1497 | 1498 | static void 1499 | paste(void) 1500 | { 1501 | char *p, *q; 1502 | int di; 1503 | unsigned long dl; 1504 | Atom da; 1505 | 1506 | /* we have been given the current selection, now insert it into input */ 1507 | if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, 1508 | utf8, &da, &di, &dl, &dl, (unsigned char **)&p) 1509 | == Success && p) { 1510 | insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); 1511 | XFree(p); 1512 | } 1513 | drawmenu(); 1514 | } 1515 | 1516 | #if ALPHA_PATCH 1517 | static void 1518 | xinitvisual(void) 1519 | { 1520 | XVisualInfo *infos; 1521 | XRenderPictFormat *fmt; 1522 | int nitems; 1523 | int i; 1524 | 1525 | XVisualInfo tpl = { 1526 | .screen = screen, 1527 | .depth = 32, 1528 | .class = TrueColor 1529 | }; 1530 | long masks = VisualScreenMask | VisualDepthMask | VisualClassMask; 1531 | 1532 | infos = XGetVisualInfo(dpy, masks, &tpl, &nitems); 1533 | visual = NULL; 1534 | for(i = 0; i < nitems; i ++) { 1535 | fmt = XRenderFindVisualFormat(dpy, infos[i].visual); 1536 | if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { 1537 | visual = infos[i].visual; 1538 | depth = infos[i].depth; 1539 | cmap = XCreateColormap(dpy, root, visual, AllocNone); 1540 | useargb = 1; 1541 | break; 1542 | } 1543 | } 1544 | 1545 | XFree(infos); 1546 | 1547 | if (!visual || !opacity) { 1548 | visual = DefaultVisual(dpy, screen); 1549 | depth = DefaultDepth(dpy, screen); 1550 | cmap = DefaultColormap(dpy, screen); 1551 | } 1552 | } 1553 | #endif // ALPHA_PATCH 1554 | 1555 | #if !NON_BLOCKING_STDIN_PATCH 1556 | static void 1557 | readstdin(void) 1558 | { 1559 | char *line = NULL; 1560 | #if SEPARATOR_PATCH 1561 | char *p; 1562 | #elif TSV_PATCH 1563 | char *buf, *p; 1564 | #endif // SEPARATOR_PATCH | TSV_PATCH 1565 | 1566 | size_t i, linesiz, itemsiz = 0; 1567 | ssize_t len; 1568 | 1569 | #if PASSWORD_PATCH 1570 | if (passwd) { 1571 | inputw = lines = 0; 1572 | return; 1573 | } 1574 | #endif // PASSWORD_PATCH 1575 | 1576 | /* read each line from stdin and add it to the item list */ 1577 | for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { 1578 | if (i + 1 >= itemsiz) { 1579 | itemsiz += 256; 1580 | if (!(items = realloc(items, itemsiz * sizeof(*items)))) 1581 | die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); 1582 | } 1583 | if (line[len - 1] == '\n') 1584 | line[len - 1] = '\0'; 1585 | 1586 | if (!(items[i].text = strdup(line))) 1587 | die("strdup:"); 1588 | #if SEPARATOR_PATCH 1589 | if (separator && (p = separator_greedy ? 1590 | strrchr(items[i].text, separator) : strchr(items[i].text, separator))) { 1591 | *p = '\0'; 1592 | items[i].text_output = ++p; 1593 | } else { 1594 | items[i].text_output = items[i].text; 1595 | } 1596 | if (separator_reverse) { 1597 | p = items[i].text; 1598 | items[i].text = items[i].text_output; 1599 | items[i].text_output = p; 1600 | } 1601 | #elif TSV_PATCH 1602 | if (!(buf = strdup(line))) 1603 | die("cannot strdup %u bytes:", strlen(line) + 1); 1604 | if ((p = strchr(buf, '\t'))) 1605 | *p = '\0'; 1606 | items[i].stext = buf; 1607 | #endif // SEPARATOR_PATCH | TSV_PATCH 1608 | #if MULTI_SELECTION_PATCH 1609 | items[i].id = i; /* for multiselect */ 1610 | #if PRINTINDEX_PATCH 1611 | items[i].index = i; 1612 | #endif // PRINTINDEX_PATCH 1613 | #elif PRINTINDEX_PATCH 1614 | items[i].index = i; 1615 | #else 1616 | items[i].out = 0; 1617 | #endif // MULTI_SELECTION_PATCH | PRINTINDEX_PATCH 1618 | 1619 | #if HIGHPRIORITY_PATCH 1620 | items[i].hp = arrayhas(hpitems, hplength, items[i].text); 1621 | #endif // HIGHPRIORITY_PATCH 1622 | } 1623 | free(line); 1624 | if (items) 1625 | items[i].text = NULL; 1626 | lines = MIN(lines, i); 1627 | } 1628 | #endif // NON_BLOCKING_STDIN_PATCH 1629 | 1630 | static void 1631 | #if NON_BLOCKING_STDIN_PATCH 1632 | readevent(void) 1633 | #else 1634 | run(void) 1635 | #endif // NON_BLOCKING_STDIN_PATCH 1636 | { 1637 | XEvent ev; 1638 | #if PRESELECT_PATCH 1639 | int i; 1640 | #endif // PRESELECT_PATCH 1641 | while (!XNextEvent(dpy, &ev)) { 1642 | #if PRESELECT_PATCH 1643 | if (preselected) { 1644 | for (i = 0; i < preselected; i++) { 1645 | if (sel && sel->right && (sel = sel->right) == next) { 1646 | curr = next; 1647 | calcoffsets(); 1648 | } 1649 | } 1650 | drawmenu(); 1651 | preselected = 0; 1652 | } 1653 | #endif // PRESELECT_PATCH 1654 | #if INPUTMETHOD_PATCH 1655 | if (XFilterEvent(&ev, None)) 1656 | continue; 1657 | if (composing) 1658 | continue; 1659 | #else 1660 | if (XFilterEvent(&ev, win)) 1661 | continue; 1662 | #endif // INPUTMETHOD_PATCH 1663 | switch(ev.type) { 1664 | #if MOUSE_SUPPORT_PATCH 1665 | case ButtonPress: 1666 | buttonpress(&ev); 1667 | break; 1668 | #if MOTION_SUPPORT_PATCH 1669 | case MotionNotify: 1670 | motionevent(&ev.xbutton); 1671 | break; 1672 | #endif // MOTION_SUPPORT_PATCH 1673 | #endif // MOUSE_SUPPORT_PATCH 1674 | case DestroyNotify: 1675 | if (ev.xdestroywindow.window != win) 1676 | break; 1677 | cleanup(); 1678 | exit(1); 1679 | case Expose: 1680 | if (ev.xexpose.count == 0) 1681 | drw_map(drw, win, 0, 0, mw, mh); 1682 | break; 1683 | case FocusIn: 1684 | /* regrab focus from parent window */ 1685 | if (ev.xfocus.window != win) 1686 | grabfocus(); 1687 | break; 1688 | case KeyPress: 1689 | keypress(&ev.xkey); 1690 | break; 1691 | case SelectionNotify: 1692 | if (ev.xselection.property == utf8) 1693 | paste(); 1694 | break; 1695 | case VisibilityNotify: 1696 | if (ev.xvisibility.state != VisibilityUnobscured) 1697 | XRaiseWindow(dpy, win); 1698 | break; 1699 | } 1700 | } 1701 | } 1702 | 1703 | static void 1704 | setup(void) 1705 | { 1706 | int x, y, i, j; 1707 | unsigned int du; 1708 | #if RELATIVE_INPUT_WIDTH_PATCH 1709 | unsigned int tmp, minstrlen = 0, curstrlen = 0; 1710 | int numwidthchecks = 100; 1711 | struct item *item; 1712 | #endif // RELATIVE_INPUT_WIDTH_PATCH 1713 | XSetWindowAttributes swa; 1714 | XIM xim; 1715 | Window w, dw, *dws; 1716 | XWindowAttributes wa; 1717 | XClassHint ch = {"dmenu", "dmenu"}; 1718 | #ifdef XINERAMA 1719 | XineramaScreenInfo *info; 1720 | Window pw; 1721 | int a, di, n, area = 0; 1722 | #endif 1723 | /* init appearance */ 1724 | #if XRESOURCES_PATCH 1725 | for (j = 0; j < SchemeLast; j++) 1726 | #if ALPHA_PATCH 1727 | scheme[j] = drw_scm_create(drw, (const char**)colors[j], alphas[j], 2); 1728 | #else 1729 | scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2); 1730 | #endif // ALPHA_PATCH 1731 | #else 1732 | for (j = 0; j < SchemeLast; j++) 1733 | #if ALPHA_PATCH 1734 | scheme[j] = drw_scm_create(drw, colors[j], alphas[j], 2); 1735 | #else 1736 | scheme[j] = drw_scm_create(drw, colors[j], 2); 1737 | #endif // ALPHA_PATCH 1738 | #endif // XRESOURCES_PATCH 1739 | 1740 | clip = XInternAtom(dpy, "CLIPBOARD", False); 1741 | utf8 = XInternAtom(dpy, "UTF8_STRING", False); 1742 | #if WMTYPE_PATCH 1743 | type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); 1744 | dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False); 1745 | #endif // WMTYPE_PATCH 1746 | 1747 | /* calculate menu geometry */ 1748 | #if PANGO_PATCH 1749 | bh = drw->font->h + 2; 1750 | #else 1751 | bh = drw->fonts->h + 2; 1752 | #endif // PANGO_PATCH 1753 | #if LINE_HEIGHT_PATCH 1754 | bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */ 1755 | #endif // LINE_HEIGHT_PATCH 1756 | lines = MAX(lines, 0); 1757 | mh = (lines + 1) * bh; 1758 | #if CENTER_PATCH && PANGO_PATCH 1759 | promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0; 1760 | #elif CENTER_PATCH 1761 | promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; 1762 | #endif // CENTER_PATCH 1763 | #ifdef XINERAMA 1764 | i = 0; 1765 | if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { 1766 | XGetInputFocus(dpy, &w, &di); 1767 | if (mon >= 0 && mon < n) 1768 | i = mon; 1769 | else if (w != root && w != PointerRoot && w != None) { 1770 | /* find top-level window containing current input focus */ 1771 | do { 1772 | if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) 1773 | XFree(dws); 1774 | } while (w != root && w != pw); 1775 | /* find xinerama screen with which the window intersects most */ 1776 | if (XGetWindowAttributes(dpy, pw, &wa)) 1777 | for (j = 0; j < n; j++) 1778 | if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { 1779 | area = a; 1780 | i = j; 1781 | } 1782 | } 1783 | /* no focused window is on screen, so use pointer location instead */ 1784 | if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) 1785 | for (i = 0; i < n; i++) 1786 | if (INTERSECT(x, y, 1, 1, info[i]) != 0) 1787 | break; 1788 | 1789 | #if CENTER_PATCH 1790 | if (center) { 1791 | #if XYW_PATCH 1792 | mw = (dmw>0 ? dmw : MIN(MAX(max_textw() + promptw, min_width), info[i].width)); 1793 | #else 1794 | mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); 1795 | #endif // XYW_PATCH 1796 | x = info[i].x_org + ((info[i].width - mw) / 2); 1797 | y = info[i].y_org + ((info[i].height - mh) / 2); 1798 | } else { 1799 | #if XYW_PATCH 1800 | x = info[i].x_org + dmx; 1801 | y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy); 1802 | mw = (dmw>0 ? dmw : info[i].width); 1803 | #else 1804 | x = info[i].x_org; 1805 | y = info[i].y_org + (topbar ? 0 : info[i].height - mh); 1806 | mw = info[i].width; 1807 | #endif // XYW_PATCH 1808 | } 1809 | #elif XYW_PATCH 1810 | x = info[i].x_org + dmx; 1811 | y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy); 1812 | mw = (dmw>0 ? dmw : info[i].width); 1813 | #else 1814 | x = info[i].x_org; 1815 | y = info[i].y_org + (topbar ? 0 : info[i].height - mh); 1816 | mw = info[i].width; 1817 | #endif // CENTER_PATCH / XYW_PATCH 1818 | XFree(info); 1819 | } else 1820 | #endif 1821 | { 1822 | if (!XGetWindowAttributes(dpy, parentwin, &wa)) 1823 | die("could not get embedding window attributes: 0x%lx", 1824 | parentwin); 1825 | #if CENTER_PATCH 1826 | if (center) { 1827 | #if XYW_PATCH 1828 | mw = (dmw>0 ? dmw : MIN(MAX(max_textw() + promptw, min_width), wa.width)); 1829 | #else 1830 | mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); 1831 | #endif // XYW_PATCH 1832 | x = (wa.width - mw) / 2; 1833 | y = (wa.height - mh) / 2; 1834 | } else { 1835 | #if XYW_PATCH 1836 | x = dmx; 1837 | y = topbar ? dmy : wa.height - mh - dmy; 1838 | mw = (dmw>0 ? dmw : wa.width); 1839 | #else 1840 | x = 0; 1841 | y = topbar ? 0 : wa.height - mh; 1842 | mw = wa.width; 1843 | #endif // XYW_PATCH 1844 | } 1845 | #elif XYW_PATCH 1846 | x = dmx; 1847 | y = topbar ? dmy : wa.height - mh - dmy; 1848 | mw = (dmw>0 ? dmw : wa.width); 1849 | #else 1850 | x = 0; 1851 | y = topbar ? 0 : wa.height - mh; 1852 | mw = wa.width; 1853 | #endif // CENTER_PATCH / XYW_PATCH 1854 | } 1855 | #if !CENTER_PATCH 1856 | #if PANGO_PATCH 1857 | promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0; 1858 | #else 1859 | promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; 1860 | #endif // PANGO_PATCH 1861 | #endif // CENTER_PATCH 1862 | #if RELATIVE_INPUT_WIDTH_PATCH 1863 | for (item = items; !lines && item && item->text; ++item) { 1864 | curstrlen = strlen(item->text); 1865 | if (numwidthchecks || minstrlen < curstrlen) { 1866 | numwidthchecks = MAX(numwidthchecks - 1, 0); 1867 | minstrlen = MAX(minstrlen, curstrlen); 1868 | if ((tmp = textw_clamp(item->text, mw/3)) > inputw) { 1869 | inputw = tmp; 1870 | if (tmp == mw/3) 1871 | break; 1872 | } 1873 | } 1874 | } 1875 | #else 1876 | inputw = mw / 3; /* input width: ~33.33% of monitor width */ 1877 | #endif // RELATIVE_INPUT_WIDTH_PATCH 1878 | match(); 1879 | 1880 | /* create menu window */ 1881 | #if MANAGED_PATCH 1882 | swa.override_redirect = managed ? False : True; 1883 | #else 1884 | swa.override_redirect = True; 1885 | #endif // MANAGED_PATCH 1886 | #if ALPHA_PATCH 1887 | swa.background_pixel = 0; 1888 | swa.colormap = cmap; 1889 | #else 1890 | swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; 1891 | #endif // ALPHA_PATCH 1892 | swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask 1893 | #if MOUSE_SUPPORT_PATCH 1894 | | ButtonPressMask 1895 | #if MOTION_SUPPORT_PATCH 1896 | | PointerMotionMask 1897 | #endif // MOTION_SUPPORT_PATCH 1898 | #endif // MOUSE_SUPPORT_PATCH 1899 | ; 1900 | win = XCreateWindow( 1901 | dpy, root, 1902 | #if BARPADDING_PATCH && BORDER_PATCH 1903 | x + sp, y + vp - (topbar ? 0 : border_width * 2), mw - 2 * sp - border_width * 2, mh, border_width, 1904 | #elif BARPADDING_PATCH 1905 | x + sp, y + vp, mw - 2 * sp, mh, 0, 1906 | #elif BORDER_PATCH 1907 | x, y - (topbar ? 0 : border_width * 2), mw - border_width * 2, mh, border_width, 1908 | #else 1909 | x, y, mw, mh, 0, 1910 | #endif // BORDER_PATCH | BARPADDING_PATCH 1911 | #if ALPHA_PATCH 1912 | depth, InputOutput, visual, 1913 | CWOverrideRedirect|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &swa 1914 | #else 1915 | CopyFromParent, CopyFromParent, CopyFromParent, 1916 | CWOverrideRedirect | CWBackPixel | CWEventMask, &swa 1917 | #endif // ALPHA_PATCH 1918 | ); 1919 | #if BORDER_PATCH 1920 | if (border_width) 1921 | XSetWindowBorder(dpy, win, scheme[SchemeBorder][ColBg].pixel); 1922 | #endif // BORDER_PATCH 1923 | XSetClassHint(dpy, win, &ch); 1924 | #if WMTYPE_PATCH 1925 | XChangeProperty(dpy, win, type, XA_ATOM, 32, PropModeReplace, 1926 | (unsigned char *) &dock, 1); 1927 | #endif // WMTYPE_PATCH 1928 | 1929 | /* input methods */ 1930 | if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) 1931 | die("XOpenIM failed: could not open input device"); 1932 | 1933 | #if INPUTMETHOD_PATCH 1934 | init_input_method(xim); 1935 | #else 1936 | xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 1937 | XNClientWindow, win, XNFocusWindow, win, NULL); 1938 | #endif // INPUTMETHOD_PATCH 1939 | 1940 | #if MANAGED_PATCH 1941 | if (managed) { 1942 | XTextProperty prop; 1943 | char *windowtitle = prompt != NULL ? prompt : "dmenu"; 1944 | Xutf8TextListToTextProperty(dpy, &windowtitle, 1, XUTF8StringStyle, &prop); 1945 | XSetWMName(dpy, win, &prop); 1946 | XSetTextProperty(dpy, win, &prop, XInternAtom(dpy, "_NET_WM_NAME", False)); 1947 | XFree(prop.value); 1948 | } 1949 | #endif // MANAGED_PATCH 1950 | 1951 | XMapRaised(dpy, win); 1952 | if (embed) { 1953 | XReparentWindow(dpy, win, parentwin, x, y); 1954 | XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); 1955 | if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { 1956 | for (i = 0; i < du && dws[i] != win; ++i) 1957 | XSelectInput(dpy, dws[i], FocusChangeMask); 1958 | XFree(dws); 1959 | } 1960 | #if !INPUTMETHOD_PATCH 1961 | grabfocus(); 1962 | #endif // INPUTMETHOD_PATCH 1963 | } 1964 | #if INPUTMETHOD_PATCH 1965 | grabfocus(); 1966 | #endif // INPUTMETHOD_PATCH 1967 | drw_resize(drw, mw, mh); 1968 | drawmenu(); 1969 | } 1970 | 1971 | static void 1972 | usage(void) 1973 | { 1974 | die("usage: dmenu [-bv" 1975 | #if CENTER_PATCH 1976 | "c" 1977 | #endif 1978 | #if !NON_BLOCKING_STDIN_PATCH 1979 | "f" 1980 | #endif // NON_BLOCKING_STDIN_PATCH 1981 | #if QUIET_PATCH 1982 | "q" 1983 | #endif // QUIET_PATCH 1984 | #if INCREMENTAL_PATCH 1985 | "r" 1986 | #endif // INCREMENTAL_PATCH 1987 | #if CASEINSENSITIVE_PATCH 1988 | "s" 1989 | #else 1990 | "i" 1991 | #endif // CASEINSENSITIVE_PATCH 1992 | #if INSTANT_PATCH 1993 | "n" 1994 | #endif // INSTANT_PATCH 1995 | #if PRINTINPUTTEXT_PATCH 1996 | "t" 1997 | #endif // PRINTINPUTTEXT_PATCH 1998 | #if PREFIXCOMPLETION_PATCH 1999 | "x" 2000 | #endif // PREFIXCOMPLETION_PATCH 2001 | #if FUZZYMATCH_PATCH 2002 | "F" 2003 | #endif // FUZZYMATCH_PATCH 2004 | #if PASSWORD_PATCH 2005 | "P" 2006 | #endif // PASSWORD_PATCH 2007 | #if NO_SORT_PATCH 2008 | "S" 2009 | #endif // NO_SORT_PATCH 2010 | #if REJECTNOMATCH_PATCH 2011 | "R" // (changed from r to R due to conflict with INCREMENTAL_PATCH) 2012 | #endif // REJECTNOMATCH_PATCH 2013 | #if RESTRICT_RETURN_PATCH 2014 | "1" 2015 | #endif // RESTRICT_RETURN_PATCH 2016 | "] " 2017 | #if CARET_WIDTH_PATCH 2018 | "[-cw caret_width] " 2019 | #endif // CARET_WIDTH_PATCH 2020 | #if VI_MODE_PATCH 2021 | "[-vi] " 2022 | #endif // VI_MODE_PATCH 2023 | #if MANAGED_PATCH 2024 | "[-wm] " 2025 | #endif // MANAGED_PATCH 2026 | #if GRID_PATCH 2027 | "[-g columns] " 2028 | #endif // GRID_PATCH 2029 | "[-l lines] [-p prompt] [-fn font] [-m monitor]" 2030 | "\n [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]" 2031 | #if DYNAMIC_OPTIONS_PATCH || FZFEXPECT_PATCH || ALPHA_PATCH || BORDER_PATCH || HIGHPRIORITY_PATCH 2032 | "\n " 2033 | #endif 2034 | #if DYNAMIC_OPTIONS_PATCH 2035 | " [-dy command]" 2036 | #endif // DYNAMIC_OPTIONS_PATCH 2037 | #if FZFEXPECT_PATCH 2038 | " [-ex expectkey]" 2039 | #endif // FZFEXPECT_PATCH 2040 | #if ALPHA_PATCH 2041 | " [-o opacity]" 2042 | #endif // ALPHA_PATCH 2043 | #if BORDER_PATCH 2044 | " [-bw width]" 2045 | #endif // BORDER_PATCH 2046 | #if HIGHPRIORITY_PATCH 2047 | " [-hb color] [-hf color] [-hp items]" 2048 | #endif // HIGHPRIORITY_PATCH 2049 | #if INITIALTEXT_PATCH || LINE_HEIGHT_PATCH || PRESELECT_PATCH || NAVHISTORY_PATCH || XYW_PATCH 2050 | "\n " 2051 | #endif 2052 | #if INITIALTEXT_PATCH 2053 | " [-it text]" 2054 | #endif // INITIALTEXT_PATCH 2055 | #if LINE_HEIGHT_PATCH 2056 | " [-h height]" 2057 | #endif // LINE_HEIGHT_PATCH 2058 | #if PRESELECT_PATCH 2059 | " [-ps index]" 2060 | #endif // PRESELECT_PATCH 2061 | #if NAVHISTORY_PATCH 2062 | " [-H histfile]" 2063 | #endif // NAVHISTORY_PATCH 2064 | #if XYW_PATCH 2065 | " [-X xoffset] [-Y yoffset] [-W width]" // (arguments made upper case due to conflicts) 2066 | #endif // XYW_PATCH 2067 | #if HIGHLIGHT_PATCH 2068 | "\n [-nhb color] [-nhf color] [-shb color] [-shf color]" // highlight colors 2069 | #endif // HIGHLIGHT_PATCH 2070 | #if SEPARATOR_PATCH 2071 | "\n [-d separator] [-D separator]" 2072 | #endif // SEPARATOR_PATCH 2073 | "\n"); 2074 | } 2075 | 2076 | int 2077 | main(int argc, char *argv[]) 2078 | { 2079 | XWindowAttributes wa; 2080 | int i; 2081 | #if !NON_BLOCKING_STDIN_PATCH 2082 | int fast = 0; 2083 | #endif // NON_BLOCKING_STDIN_PATCH 2084 | 2085 | #if XRESOURCES_PATCH 2086 | if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 2087 | fputs("warning: no locale support\n", stderr); 2088 | #if INPUTMETHOD_PATCH 2089 | if (!XSetLocaleModifiers("")) 2090 | fputs("warning: could not set locale modifiers", stderr); 2091 | #endif // INPUTMETHOD_PATCH 2092 | if (!(dpy = XOpenDisplay(NULL))) 2093 | die("cannot open display"); 2094 | 2095 | /* These need to be checked before we init the visuals and read X resources. */ 2096 | for (i = 1; i < argc; i++) { 2097 | if (!strcmp(argv[i], "-v")) { /* prints version information */ 2098 | puts("dmenu-"VERSION); 2099 | exit(0); 2100 | } else if (!strcmp(argv[i], "-w")) { 2101 | argv[i][0] = '\0'; 2102 | embed = strdup(argv[++i]); 2103 | #if ALPHA_PATCH 2104 | } else if (!strcmp(argv[i], "-o")) { /* opacity, pass -o 0 to disable alpha */ 2105 | opacity = atoi(argv[++i]); 2106 | #endif // ALPHA_PATCH 2107 | } else { 2108 | continue; 2109 | } 2110 | argv[i][0] = '\0'; // mark as used 2111 | } 2112 | 2113 | screen = DefaultScreen(dpy); 2114 | root = RootWindow(dpy, screen); 2115 | if (!embed || !(parentwin = strtol(embed, NULL, 0))) 2116 | parentwin = root; 2117 | if (!XGetWindowAttributes(dpy, parentwin, &wa)) 2118 | die("could not get embedding window attributes: 0x%lx", 2119 | parentwin); 2120 | 2121 | #if ALPHA_PATCH 2122 | xinitvisual(); 2123 | drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); 2124 | #else 2125 | drw = drw_create(dpy, screen, root, wa.width, wa.height); 2126 | #endif // ALPHA_PATCH 2127 | readxresources(); 2128 | #endif // XRESOURCES_PATCH 2129 | 2130 | for (i = 1; i < argc; i++) { 2131 | if (argv[i][0] == '\0') 2132 | continue; 2133 | 2134 | /* these options take no arguments */ 2135 | if (!strcmp(argv[i], "-v")) { /* prints version information */ 2136 | puts("dmenu-"VERSION); 2137 | exit(0); 2138 | } else if (!strcmp(argv[i], "-b")) { /* appears at the bottom of the screen */ 2139 | topbar = 0; 2140 | #if CENTER_PATCH 2141 | } else if (!strcmp(argv[i], "-c")) { /* toggles centering of dmenu window on screen */ 2142 | center = !center; 2143 | #endif // CENTER_PATCH 2144 | #if !NON_BLOCKING_STDIN_PATCH 2145 | } else if (!strcmp(argv[i], "-f")) { /* grabs keyboard before reading stdin */ 2146 | fast = 1; 2147 | #endif // NON_BLOCKING_STDIN_PATCH 2148 | #if INCREMENTAL_PATCH 2149 | } else if (!strcmp(argv[i], "-r")) { /* incremental */ 2150 | incremental = !incremental; 2151 | #endif // INCREMENTAL_PATCH 2152 | #if QUIET_PATCH 2153 | } else if (!strcmp(argv[i], "-q")) { /* quiet, don't list items if search is empty */ 2154 | quiet = !quiet; 2155 | #endif // QUIET_PATCH 2156 | #if CASEINSENSITIVE_PATCH 2157 | } else if (!strcmp(argv[i], "-s")) { /* case-sensitive item matching */ 2158 | fstrncmp = strncmp; 2159 | fstrstr = strstr; 2160 | #else 2161 | } else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ 2162 | fstrncmp = strncasecmp; 2163 | fstrstr = cistrstr; 2164 | #endif // CASEINSENSITIVE_PATCH 2165 | #if VI_MODE_PATCH 2166 | } else if (!strcmp(argv[i], "-vi")) { 2167 | if (i + 1 < argc) { 2168 | if (!strcmp(argv[i+1], "0")) { 2169 | start_mode = 0; 2170 | i++; 2171 | } else if (!strcmp(argv[i+1], "1")) { 2172 | start_mode = 1; 2173 | i++; 2174 | } 2175 | } 2176 | vi_mode = 1; 2177 | using_vi_mode = start_mode; 2178 | global_esc.ksym = XK_Escape; 2179 | global_esc.state = 0; 2180 | #endif // VI_MODE_PATCH 2181 | #if MANAGED_PATCH 2182 | } else if (!strcmp(argv[i], "-wm")) { /* display as managed wm window */ 2183 | managed = 1; 2184 | #endif // MANAGED_PATCH 2185 | #if INSTANT_PATCH 2186 | } else if (!strcmp(argv[i], "-n")) { /* instant select only match */ 2187 | instant = !instant; 2188 | #endif // INSTANT_PATCH 2189 | #if PRINTINPUTTEXT_PATCH 2190 | } else if (!strcmp(argv[i], "-t")) { /* favors text input over selection */ 2191 | use_text_input = 1; 2192 | #endif // PRINTINPUTTEXT_PATCH 2193 | #if PREFIXCOMPLETION_PATCH 2194 | } else if (!strcmp(argv[i], "-x")) { /* invert use_prefix */ 2195 | use_prefix = !use_prefix; 2196 | #endif // PREFIXCOMPLETION_PATCH 2197 | #if FUZZYMATCH_PATCH 2198 | } else if (!strcmp(argv[i], "-F")) { /* disable/enable fuzzy matching, depends on default */ 2199 | fuzzy = !fuzzy; 2200 | #endif // FUZZYMATCH_PATCH 2201 | #if PASSWORD_PATCH 2202 | } else if (!strcmp(argv[i], "-P")) { /* is the input a password */ 2203 | passwd = 1; 2204 | #endif // PASSWORD_PATCH 2205 | #if FZFEXPECT_PATCH 2206 | } else if (!strcmp(argv[i], "-ex")) { /* expect key */ 2207 | expected = argv[++i]; 2208 | #endif // FZFEXPECT_PATCH 2209 | #if REJECTNOMATCH_PATCH 2210 | } else if (!strcmp(argv[i], "-R")) { /* reject input which results in no match */ 2211 | reject_no_match = 1; 2212 | #endif // REJECTNOMATCH_PATCH 2213 | #if NO_SORT_PATCH 2214 | } else if (!strcmp(argv[i], "-S")) { /* do not sort matches */ 2215 | sortmatches = 0; 2216 | #endif // NO_SORT_PATCH 2217 | #if PRINTINDEX_PATCH 2218 | } else if (!strcmp(argv[i], "-ix")) { /* adds ability to return index in list */ 2219 | print_index = 1; 2220 | #endif // PRINTINDEX_PATCH 2221 | #if RESTRICT_RETURN_PATCH 2222 | } else if (!strcmp(argv[i], "-1")) { 2223 | restrict_return = 1; 2224 | #endif // RESTRICT_RETURN_PATCH 2225 | } else if (i + 1 == argc) 2226 | usage(); 2227 | /* these options take one argument */ 2228 | #if NAVHISTORY_PATCH 2229 | else if (!strcmp(argv[i], "-H")) 2230 | histfile = argv[++i]; 2231 | #endif // NAVHISTORY_PATCH 2232 | #if GRID_PATCH 2233 | else if (!strcmp(argv[i], "-g")) { /* number of columns in grid */ 2234 | columns = atoi(argv[++i]); 2235 | if (columns && lines == 0) 2236 | lines = 1; 2237 | } 2238 | #endif // GRID_PATCH 2239 | else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ 2240 | lines = atoi(argv[++i]); 2241 | #if XYW_PATCH 2242 | else if (!strcmp(argv[i], "-X")) /* window x offset */ 2243 | dmx = atoi(argv[++i]); 2244 | else if (!strcmp(argv[i], "-Y")) /* window y offset (from bottom up if -b) */ 2245 | dmy = atoi(argv[++i]); 2246 | else if (!strcmp(argv[i], "-W")) /* make dmenu this wide */ 2247 | dmw = atoi(argv[++i]); 2248 | #endif // XYW_PATCH 2249 | else if (!strcmp(argv[i], "-m")) 2250 | mon = atoi(argv[++i]); 2251 | #if ALPHA_PATCH && !XRESOURCES_PATCH 2252 | else if (!strcmp(argv[i], "-o")) /* opacity, pass -o 0 to disable alpha */ 2253 | opacity = atoi(argv[++i]); 2254 | #endif // ALPHA_PATCH 2255 | else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ 2256 | prompt = argv[++i]; 2257 | else if (!strcmp(argv[i], "-fn")) /* font or font set */ 2258 | #if PANGO_PATCH 2259 | font = argv[++i]; 2260 | #else 2261 | fonts[0] = argv[++i]; 2262 | #endif // PANGO_PATCH 2263 | #if LINE_HEIGHT_PATCH 2264 | else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ 2265 | lineheight = atoi(argv[++i]); 2266 | lineheight = MAX(lineheight, min_lineheight); /* reasonable default in case of value too small/negative */ 2267 | } 2268 | #endif // LINE_HEIGHT_PATCH 2269 | else if (!strcmp(argv[i], "-nb")) /* normal background color */ 2270 | colors[SchemeNorm][ColBg] = argv[++i]; 2271 | else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ 2272 | colors[SchemeNorm][ColFg] = argv[++i]; 2273 | else if (!strcmp(argv[i], "-sb")) /* selected background color */ 2274 | colors[SchemeSel][ColBg] = argv[++i]; 2275 | else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ 2276 | colors[SchemeSel][ColFg] = argv[++i]; 2277 | #if HIGHPRIORITY_PATCH 2278 | else if (!strcmp(argv[i], "-hb")) /* high priority background color */ 2279 | colors[SchemeHp][ColBg] = argv[++i]; 2280 | else if (!strcmp(argv[i], "-hf")) /* low priority background color */ 2281 | colors[SchemeHp][ColFg] = argv[++i]; 2282 | else if (!strcmp(argv[i], "-hp")) 2283 | hpitems = tokenize(argv[++i], ",", &hplength); 2284 | #endif // HIGHPRIORITY_PATCH 2285 | #if HIGHLIGHT_PATCH 2286 | else if (!strcmp(argv[i], "-nhb")) /* normal hi background color */ 2287 | colors[SchemeNormHighlight][ColBg] = argv[++i]; 2288 | else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */ 2289 | colors[SchemeNormHighlight][ColFg] = argv[++i]; 2290 | else if (!strcmp(argv[i], "-shb")) /* selected hi background color */ 2291 | colors[SchemeSelHighlight][ColBg] = argv[++i]; 2292 | else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */ 2293 | colors[SchemeSelHighlight][ColFg] = argv[++i]; 2294 | #endif // HIGHLIGHT_PATCH 2295 | #if CARET_WIDTH_PATCH 2296 | else if (!strcmp(argv[i], "-cw")) /* sets caret witdth */ 2297 | caret_width = atoi(argv[++i]); 2298 | #endif // CARET_WIDTH_PATCH 2299 | #if !XRESOURCES_PATCH 2300 | else if (!strcmp(argv[i], "-w")) /* embedding window id */ 2301 | embed = argv[++i]; 2302 | #endif // XRESOURCES_PATCH 2303 | #if SEPARATOR_PATCH 2304 | else if (!strcmp(argv[i], "-d") || /* field separator */ 2305 | (separator_greedy = !strcmp(argv[i], "-D"))) { 2306 | separator = argv[++i][0]; 2307 | separator_reverse = argv[i][1] == '|'; 2308 | } 2309 | #endif // SEPARATOR_PATCH 2310 | #if PRESELECT_PATCH 2311 | else if (!strcmp(argv[i], "-ps")) /* preselected item */ 2312 | preselected = atoi(argv[++i]); 2313 | #endif // PRESELECT_PATCH 2314 | #if DYNAMIC_OPTIONS_PATCH 2315 | else if (!strcmp(argv[i], "-dy")) /* dynamic command to run */ 2316 | dynamic = argv[++i]; 2317 | #endif // DYNAMIC_OPTIONS_PATCH 2318 | #if BORDER_PATCH 2319 | else if (!strcmp(argv[i], "-bw")) /* border width around dmenu */ 2320 | border_width = atoi(argv[++i]); 2321 | #endif // BORDER_PATCH 2322 | #if INITIALTEXT_PATCH 2323 | else if (!strcmp(argv[i], "-it")) { /* adds initial text */ 2324 | const char * text = argv[++i]; 2325 | insert(text, strlen(text)); 2326 | } 2327 | #endif // INITIALTEXT_PATCH 2328 | else { 2329 | usage(); 2330 | } 2331 | } 2332 | 2333 | #if XRESOURCES_PATCH 2334 | #if PANGO_PATCH 2335 | if (!drw_font_create(drw, font)) 2336 | die("no fonts could be loaded."); 2337 | #else 2338 | if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts))) 2339 | die("no fonts could be loaded."); 2340 | #endif // PANGO_PATCH 2341 | #else // !XRESOURCES_PATCH 2342 | if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 2343 | fputs("warning: no locale support\n", stderr); 2344 | #if INPUTMETHOD_PATCH 2345 | if (!XSetLocaleModifiers("")) 2346 | fputs("warning: could not set locale modifiers", stderr); 2347 | #endif // INPUTMETHOD_PATCH 2348 | if (!(dpy = XOpenDisplay(NULL))) 2349 | die("cannot open display"); 2350 | screen = DefaultScreen(dpy); 2351 | root = RootWindow(dpy, screen); 2352 | if (!embed || !(parentwin = strtol(embed, NULL, 0))) 2353 | parentwin = root; 2354 | if (!XGetWindowAttributes(dpy, parentwin, &wa)) 2355 | die("could not get embedding window attributes: 0x%lx", 2356 | parentwin); 2357 | 2358 | #if ALPHA_PATCH 2359 | xinitvisual(); 2360 | drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); 2361 | #else 2362 | drw = drw_create(dpy, screen, root, wa.width, wa.height); 2363 | #endif // ALPHA_PATCH 2364 | 2365 | #if PANGO_PATCH 2366 | if (!drw_font_create(drw, font)) 2367 | die("no fonts could be loaded."); 2368 | #else 2369 | if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) 2370 | die("no fonts could be loaded."); 2371 | #endif // PANGO_PATCH 2372 | #endif // XRESOURCES_PATCH 2373 | 2374 | #if PANGO_PATCH 2375 | lrpad = drw->font->h; 2376 | #else 2377 | lrpad = drw->fonts->h; 2378 | #endif // PANGO_PATCH 2379 | 2380 | #if BARPADDING_PATCH 2381 | sp = sidepad; 2382 | vp = (topbar ? vertpad : - vertpad); 2383 | #endif // BARPADDING_PATCH 2384 | 2385 | #if LINE_HEIGHT_PATCH 2386 | if (lineheight == -1) 2387 | #if PANGO_PATCH 2388 | lineheight = drw->font->h * 2.5; 2389 | #else 2390 | lineheight = drw->fonts->h * 2.5; 2391 | #endif // PANGO_PATCH 2392 | #endif // LINE_HEIGHT_PATCH 2393 | 2394 | #ifdef __OpenBSD__ 2395 | if (pledge("stdio rpath", NULL) == -1) 2396 | die("pledge"); 2397 | #endif 2398 | #if NAVHISTORY_PATCH 2399 | loadhistory(); 2400 | #endif // NAVHISTORY_PATCH 2401 | 2402 | #if NON_BLOCKING_STDIN_PATCH 2403 | grabkeyboard(); 2404 | #else 2405 | if (fast && !isatty(0)) { 2406 | grabkeyboard(); 2407 | #if DYNAMIC_OPTIONS_PATCH 2408 | if (!(dynamic && *dynamic)) 2409 | readstdin(); 2410 | #else 2411 | readstdin(); 2412 | #endif // DYNAMIC_OPTIONS_PATCH 2413 | } else { 2414 | #if DYNAMIC_OPTIONS_PATCH 2415 | if (!(dynamic && *dynamic)) 2416 | readstdin(); 2417 | #else 2418 | readstdin(); 2419 | #endif // DYNAMIC_OPTIONS_PATCH 2420 | grabkeyboard(); 2421 | } 2422 | #endif // NON_BLOCKING_STDIN_PATCH 2423 | setup(); 2424 | run(); 2425 | 2426 | return 1; /* unreachable */ 2427 | } 2428 | --------------------------------------------------------------------------------