├── instantmenu_run ├── .clang-format ├── themes ├── mac.theme ├── manjaro.theme ├── dracula.theme └── arc.theme ├── .gitignore ├── instantmenu_path ├── util.h ├── .vscode ├── settings.json ├── c_cpp_properties.json └── launch.json ├── enums.h ├── util.c ├── config.mk ├── README.md ├── arg.h ├── LICENSE ├── itest.1 ├── drw.h ├── Makefile ├── instantmenu_smartrun ├── config.def.h ├── itest.c ├── instantmenu.1 ├── drw.c └── instantmenu.c /instantmenu_run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | instantmenu_path | instantmenu "$@" | ${SHELL:-"/bin/sh"} & 3 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | IndentWidth: 4 5 | ColumnLimit: 80 6 | --- -------------------------------------------------------------------------------- /themes/mac.theme: -------------------------------------------------------------------------------- 1 | "SF-Mono-Bold:size=12" 2 | [SchemeNorm] = { "#4c4c4c", "#fafafa" }, 3 | [SchemeSel] = { "#fafafa", "#007aff" }, 4 | [SchemeOut] = { "#000000", "#00ffff" }, -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.h 2 | dmenu 3 | dmenu.o 4 | drw.o 5 | stest 6 | stest.o 7 | util.o 8 | instantmenu 9 | instantmenu.o 10 | itest 11 | itest.o 12 | tags 13 | .nvimlog 14 | -------------------------------------------------------------------------------- /themes/manjaro.theme: -------------------------------------------------------------------------------- 1 | "Cantarell-Regular:size=12" 2 | [SchemeNorm] = { "#A4ABAA", "#1B2224" }, 3 | [SchemeSel] = { "#ffffff", "#2EB398" }, 4 | [SchemeOut] = { "#000000", "#00ffff" }, -------------------------------------------------------------------------------- /themes/dracula.theme: -------------------------------------------------------------------------------- 1 | "Monaco-Nerd-Font-Complete-Mono:size=12" 2 | [SchemeNorm] = { "#bd93f9", "#282a36" }, 3 | [SchemeSel] = { "#8be9fd", "#44475a" }, 4 | [SchemeOut] = { "#000000", "#00ffff" }, -------------------------------------------------------------------------------- /themes/arc.theme: -------------------------------------------------------------------------------- 1 | "Cantarell-Regular:size=12" 2 | [SchemeNorm] = { "#ffffff", "#292f3a", "#3E485B" }, 3 | [SchemeSel] = { "#ffffff", "#5294E2", "#3579CA" }, 4 | [SchemeOut] = { "#000000", "#3579CA", "#3579CA" }, -------------------------------------------------------------------------------- /instantmenu_path: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" 4 | cache="$cachedir/instantmenu_run" 5 | 6 | [ ! -e "$cachedir" ] && mkdir -p "$cachedir" 7 | 8 | IFS=: 9 | if itest -dqr -n "$cache" $PATH; then 10 | itest -flx $PATH | sort -u | tee "$cache" 11 | else 12 | cat "$cache" 13 | fi 14 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 4 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 5 | #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) 6 | #define LENGTH(X) (sizeof X / sizeof X[0]) 7 | 8 | void die(const char *fmt, ...); 9 | void *ecalloc(size_t nmemb, size_t size); 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "random": "c" 4 | }, 5 | "[c]": { 6 | "editor.wordBasedSuggestions": "off", 7 | "editor.semanticHighlighting.enabled": true, 8 | "editor.stickyScroll.defaultModel": "foldingProviderModel", 9 | "editor.suggest.insertMode": "replace", 10 | "editor.defaultFormatter": "ms-vscode.cpptools", 11 | "editor.formatOnSave": true, 12 | } 13 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/bin/clang", 10 | "cStandard": "c11", 11 | "cppStandard": "c++17", 12 | "intelliSenseMode": "clang-x64" 13 | } 14 | ], 15 | "version": 4 16 | } -------------------------------------------------------------------------------- /enums.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ENUMS_H 3 | #define ENUMS_H 4 | /* enums */ 5 | enum { 6 | SchemeNorm, 7 | SchemeFade, 8 | SchemeHighlight, 9 | SchemeHover, 10 | SchemeSel, 11 | SchemeOut, 12 | SchemeGreen, 13 | SchemeYellow, 14 | SchemeRed, 15 | SchemeLast 16 | }; /* color schemes */ 17 | 18 | // item categories 19 | enum { 20 | ItemNormal, 21 | ItemComment, 22 | ItemColoredComment, 23 | ItemColored, 24 | ItemIcon, 25 | ItemLast 26 | }; 27 | #endif -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "util.h" 9 | 10 | void *ecalloc(size_t nmemb, size_t size) { 11 | void *p; 12 | 13 | if (!(p = calloc(nmemb, size))) 14 | die("calloc:"); 15 | return p; 16 | } 17 | 18 | void die(const char *fmt, ...) { 19 | va_list ap; 20 | int saved_errno; 21 | 22 | saved_errno = errno; 23 | 24 | va_start(ap, fmt); 25 | vfprintf(stderr, fmt, ap); 26 | va_end(ap); 27 | 28 | if (fmt[0] && fmt[strlen(fmt) - 1] == ':') 29 | fprintf(stderr, " %s", strerror(saved_errno)); 30 | fputc('\n', stderr); 31 | 32 | exit(1); 33 | } 34 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # instantmenu version 2 | VERSION = 4.9 3 | 4 | # paths 5 | PREFIX = /usr 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 | # includes and libs 23 | INCS = -I$(X11INC) -I$(FREETYPEINC) 24 | LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm 25 | 26 | # flags 27 | CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) 28 | CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) 29 | LDFLAGS = $(LIBS) 30 | 31 | # compiler and linker 32 | CC = cc 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

instantMENU

3 |

General purpose menu for instantOS

4 | 5 |
6 | 7 | instantMENU is a simple menu for instantOS 8 | it is used for instantASSIST and in some other places 9 | 10 | ## Installation from source 11 | 12 | ```sh 13 | git clone https://github.com/instantOS/instantMENU 14 | cd instantMENU 15 | ./build.sh 16 | ``` 17 | 18 | ## Features 19 | 20 | - alt-tab functionality 21 | - mouse support 22 | - animations and hover over indicators 23 | - icon prefixes 24 | - comments 25 | 26 | ## is this dmenu? 27 | 28 | instantMENU is a fork of dmenu and can be used as a drop in replacement, maintaining all dmenu behavior and making all extra features optional 29 | 30 | -------- 31 | ### instantOS is still in early beta, contributions always welcome 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) launch emoji picker", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/instantmenu", 12 | "args": ["-b", "-l", "20", "-p", "emoji picker", "<", "~/.cache/emoji/list.txt"], 13 | "stopAtEntry": false, 14 | "sourceFileMap" : {}, 15 | "cwd": "${fileDirname}", 16 | "environment": [], 17 | "externalConsole": false, 18 | "MIMode": "gdb", 19 | "setupCommands": [ 20 | { 21 | "description": "Enable pretty-printing for gdb", 22 | "text": "-enable-pretty-printing", 23 | "ignoreFailures": true 24 | }, 25 | { 26 | "description": "Set Disassembly Flavor to Intel", 27 | "text": "-gdb-set disassembly-flavor intel", 28 | "ignoreFailures": true 29 | } 30 | ] 31 | } 32 | 33 | ] 34 | } -------------------------------------------------------------------------------- /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-2019 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 | -------------------------------------------------------------------------------- /itest.1: -------------------------------------------------------------------------------- 1 | .TH itest 1 instantmenu\-VERSION 2 | .SH NAME 3 | itest \- filter a list of files by properties 4 | .SH SYNOPSIS 5 | .B itest 6 | .RB [ -abcdefghlpqrsuwx ] 7 | .RB [ -n 8 | .IR file ] 9 | .RB [ -o 10 | .IR file ] 11 | .RI [ file ...] 12 | .SH DESCRIPTION 13 | .B itest 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, itest 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 instantmenu (1), 90 | .IR test (1) 91 | -------------------------------------------------------------------------------- /drw.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | Cursor cursor; 9 | } Cur; 10 | 11 | typedef struct Fnt { 12 | Display *dpy; 13 | unsigned int h; 14 | XftFont *xfont; 15 | FcPattern *pattern; 16 | struct Fnt *next; 17 | } Fnt; 18 | 19 | enum { ColFg, ColBg, ColDetail, ColLast }; /* Clr scheme index */ 20 | typedef XftColor Clr; 21 | 22 | typedef struct { 23 | unsigned int w, h; 24 | Display *dpy; 25 | int screen; 26 | Window root; 27 | Drawable drawable; 28 | GC gc; 29 | Clr *scheme; 30 | Fnt *fonts; 31 | } Drw; 32 | 33 | /* Drawable abstraction */ 34 | Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); 35 | void drw_resize(Drw *drw, unsigned int w, unsigned int h); 36 | void drw_free(Drw *drw); 37 | 38 | /* Fnt abstraction */ 39 | Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); 40 | void drw_fontset_free(Fnt* set); 41 | unsigned int drw_fontset_getwidth(Drw *drw, const char *text); 42 | unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); 43 | void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); 44 | 45 | /* Colorscheme abstraction */ 46 | void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); 47 | Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); 48 | 49 | /* Cursor abstraction */ 50 | Cur *drw_cur_create(Drw *drw, int shape); 51 | void drw_cur_free(Drw *drw, Cur *cursor); 52 | 53 | /* Drawing context manipulation */ 54 | void drw_setfontset(Drw *drw, Fnt *set); 55 | void drw_setscheme(Drw *drw, Clr *scm); 56 | 57 | /* Drawing functions */ 58 | void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert, int rounded); 59 | int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert, int rounded); 60 | 61 | /* Map functions */ 62 | void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); 63 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # instantmenu - menu for instantOS 2 | # See LICENSE file for copyright and license details. 3 | 4 | include config.mk 5 | 6 | SRC = drw.c instantmenu.c itest.c util.c 7 | OBJ = $(SRC:.c=.o) 8 | 9 | all: instantmenu itest 10 | 11 | .c.o: 12 | $(CC) -g -c $(CFLAGS) $< 13 | 14 | config.h: 15 | cp config.def.h $@ 16 | 17 | $(OBJ): arg.h config.h config.mk drw.h 18 | 19 | instantmenu: instantmenu.o drw.o util.o 20 | $(CC) -g -o $@ instantmenu.o drw.o util.o $(LDFLAGS) 21 | 22 | itest: itest.o 23 | $(CC) -g -o $@ itest.o $(LDFLAGS) 24 | 25 | clean: 26 | rm -f instantmenu *.o *.out itest $(OBJ) instantmenu-$(VERSION).tar.gz config.h instantmenu 27 | 28 | dist: clean 29 | mkdir -p instantmenu-$(VERSION) 30 | cp LICENSE Makefile README arg.h config.def.h config.mk instantmenu.1\ 31 | drw.h util.h instantmenu_path instantmenu_run instantmenu_smartrun itest.1 $(SRC)\ 32 | instantmenu-$(VERSION) 33 | tar -cf instantmenu-$(VERSION).tar instantmenu-$(VERSION) 34 | gzip instantmenu-$(VERSION).tar 35 | rm -rf instantmenu-$(VERSION) 36 | 37 | install: all 38 | mkdir -p $(DESTDIR)$(PREFIX)/bin 39 | cp -f instantmenu instantmenu_path instantmenu_run instantmenu_smartrun itest $(DESTDIR)$(PREFIX)/bin 40 | chmod 755 $(DESTDIR)$(PREFIX)/bin/instantmenu 41 | chmod 755 $(DESTDIR)$(PREFIX)/bin/instantmenu_path 42 | chmod 755 $(DESTDIR)$(PREFIX)/bin/instantmenu_run 43 | chmod 755 $(DESTDIR)$(PREFIX)/bin/instantmenu_smartrun 44 | chmod 755 $(DESTDIR)$(PREFIX)/bin/itest 45 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1 46 | sed "s/VERSION/$(VERSION)/g" < instantmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/instantmenu.1 47 | sed "s/VERSION/$(VERSION)/g" < itest.1 > $(DESTDIR)$(MANPREFIX)/man1/itest.1 48 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/instantmenu.1 49 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/itest.1 50 | 51 | uninstall: 52 | rm -f $(DESTDIR)$(PREFIX)/bin/instantmenu\ 53 | $(DESTDIR)$(PREFIX)/bin/instantmenu_path\ 54 | $(DESTDIR)$(PREFIX)/bin/instantmenu_run\ 55 | $(DESTDIR)$(PREFIX)/bin/instantmenu_smartrun\ 56 | $(DESTDIR)$(PREFIX)/bin/itest\ 57 | $(DESTDIR)$(MANPREFIX)/man1/instantmenu.1\ 58 | $(DESTDIR)$(MANPREFIX)/man1/itest.1 59 | 60 | .PHONY: all clean dist install uninstall 61 | -------------------------------------------------------------------------------- /instantmenu_smartrun: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # smart application launcher that remembers often used apps 4 | # define $1 to run in terminal 5 | if [ -n "$2" ] || [ -z "$1" ]; then 6 | LIST="$HOME/.cache/instantmenuhist" 7 | RARGS="terminal" 8 | LARGS="desktop" 9 | else 10 | if [ "$1" = "desktop" ]; then 11 | shift 1 12 | i3-dmenu-desktop --dmenu='instantmenu -p desktop -lc "instantmenu_smartrun terminal" -rc "instantmenu_smartrun" -q "search apps" -l 10 -c -i -w -1 -h -1 -bw 4' 13 | exit 14 | fi 15 | LIST="$HOME/.cache/instanttermmenuhist" 16 | RARGS="desktop" 17 | LARGS="" 18 | fi 19 | 20 | if [ -n "$2" ]; then 21 | # read history and path 22 | # put in initial text 23 | read -r SELECTION < <({ 24 | tac "$LIST" 25 | instantmenu_path | sort -u 26 | } | perl -nE '$seen{$_}++ or print' | 27 | instantmenu -rc "instantmenu_smartrun $RARGS" -lc "instantmenu_smartrun $LARGS" \ 28 | -p "${1}" -i -f -q "search apps" -l 10 -c -w -1 -h -1 -bw 4 -it "$2") 29 | else 30 | 31 | # read history and path 32 | read -r SELECTION < <({ 33 | tac "$LIST" 34 | instantmenu_path | sort -u 35 | } | perl -nE '$seen{$_}++ or print' | 36 | instantmenu -rc "instantmenu_smartrun $RARGS" -lc "instantmenu_smartrun $LARGS" \ 37 | -p "${1}" -i -f -q "search apps" -l 10 -c -w -1 -h -1 -bw 4) 38 | fi 39 | 40 | # check if the application doesn't exist (indicated by exit code 1 and short runtime) 41 | 42 | if [ -z "$SELECTION" ]; then 43 | echo "selection empty, exiting" 44 | exit 45 | fi 46 | 47 | a="$(TZ=UTC0 printf '%(%s)T\n' '-1')" ### `-1` is the current time 48 | if [ -z "$1" ]; then 49 | EXITSTATUS=$({ 50 | /bin/sh -c "$SELECTION" &>/dev/null && echo "$?" 51 | }) 52 | else 53 | EXITSTATUS=$({ 54 | ~/.config/instantos/default/terminal -e "/bin/sh" -c "$SELECTION" &>/dev/null && echo "$?" 55 | }) 56 | fi 57 | 58 | elapsedseconds=$(($(TZ=UTC0 printf '%(%s)T\n' '-1') - a)) 59 | 60 | if [ "$elapsedseconds" -gt 5 ] || [ "$EXITSTATUS" = 0 ]; then 61 | [ -e "$HOME/.cache" ] || mkdir "$HOME/.cache" 62 | echo "$SELECTION" | tee -a "$LIST" 63 | else 64 | echo "error in running $SELECTION: skipping history" 65 | fi 66 | 67 | # max history size 68 | if [ "$(wc -l "$LIST" | grep -o '^[^ ]*')" -gt 1300 ]; then 69 | head -1000 <"$LIST" | tee "${LIST}2" 70 | cat "${LIST}2" >"$LIST" 71 | fi 72 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | /* Default settings; can be overriden by command line. */ 3 | #include "enums.h" 4 | 5 | #ifndef INSTANTMENU_CONFIG_H 6 | #define INSTANTMENU_CONFIG_H 7 | 8 | static int topbar = 1; /* -b option; if 0, instantmenu appears at bottom */ 9 | static int centered = 0; /* -c option; centers dmenu on screen */ 10 | static int followcursor = 0; /* -c option; centers dmenu on screen */ 11 | static int min_width = 500; /* minimum width when centered */ 12 | 13 | static int instant = 0; 14 | static int spaceconfirm = 0; 15 | static int fuzzy = 1; 16 | static int prematch = 0; 17 | static int smartcase = 0; 18 | static int exact = 0; 19 | static int sely = 0; 20 | static int animated = 0; 21 | static int framecount = 7; 22 | static int fullheight = 0; 23 | static unsigned int lineheight = 0; /* -h option; minimum height of a menu line */ 24 | 25 | /* -fn option overrides fonts[0]; default X11 font or font set */ 26 | static const char *fonts[] = { 27 | "Inter-Regular:size=12", 28 | "Fira Code Nerd Font:size=14", 29 | "JoyPixels:pixelsize=20:antialias=true:autohint=true", 30 | }; 31 | 32 | static const char *prompt = NULL; /* -p option; prompt to the left of input field */ 33 | static const char *searchtext = NULL; /* -p option; prompt to the left of input field */ 34 | static const char *leftcmd = NULL; /* -p option; prompt to the left of input field */ 35 | static const char *rightcmd = NULL; /* -p option; prompt to the left of input field */ 36 | static const char *colors[SchemeLast][9] = { 37 | /* fg bg darker */ 38 | [SchemeNorm] = { "#DFDFDF", "#121212", "#3E485B" }, 39 | [SchemeFade] = { "#575E70", "#121212", "#3E485B" }, 40 | [SchemeHighlight] = { "#DFDFDF", "#384252", "#272727" }, 41 | [SchemeHover] = { "#DFDFDF", "#272727", "#2E2E2E" }, 42 | [SchemeSel] = { "#000000", "#8AB4F8", "#536DFE" }, 43 | [SchemeOut] = { "#000000", "#3579CA", "#3579CA" }, 44 | [SchemeGreen] = { "#000000", "#81c995", "#1e8e3e" }, 45 | [SchemeRed] = { "#000000", "#f28b82", "#d93025" }, 46 | [SchemeYellow] = { "#000000", "#fdd663", "#f9ab00" }, 47 | }; 48 | 49 | /* -l option; if nonzero, instantmenu uses vertical list with given number of lines */ 50 | /* -g option; controls columns in grid if nonzero and lines is nonzero */ 51 | static unsigned int lines = 0; 52 | static unsigned int columns = 1; 53 | 54 | /* 55 | * Characters not considered part of a word while deleting words 56 | * for example: " /?\"&[]" 57 | */ 58 | static const char worddelimiters[] = " "; 59 | 60 | /* -ps option; preselected item starting from 0 */ 61 | static unsigned int preselected = 0; 62 | 63 | /* Size of the window border */ 64 | static unsigned int border_width = 0; 65 | 66 | #endif /* INSTANTMENU_CONFIG_H */ -------------------------------------------------------------------------------- /itest.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 test(const char *path, const char *name) { 24 | struct stat st, ln; 25 | 26 | if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ 27 | && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ 28 | && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ 29 | && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ 30 | && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ 31 | && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ 32 | && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ 33 | && (!FLAG('h') || 34 | (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ 35 | && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ 36 | && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ 37 | && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ 38 | && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ 39 | && (!FLAG('s') || st.st_size > 0) /* not empty */ 40 | && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ 41 | && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ 42 | && (!FLAG('x') || access(path, X_OK) == 0)) != 43 | FLAG('v')) { /* executable */ 44 | if (FLAG('q')) 45 | exit(0); 46 | match = 1; 47 | puts(name); 48 | } 49 | } 50 | 51 | static void usage(void) { 52 | fprintf(stderr, 53 | "usage: %s [-abcdefghlpqrsuvwx] " 54 | "[-n file] [-o file] [file...]\n", 55 | argv0); 56 | exit(2); /* like test(1) return > 1 on error */ 57 | } 58 | 59 | int main(int argc, char *argv[]) { 60 | struct dirent *d; 61 | char path[PATH_MAX], *line = NULL, *file; 62 | size_t linesiz = 0; 63 | ssize_t n; 64 | DIR *dir; 65 | int r; 66 | 67 | ARGBEGIN { 68 | case 'n': /* newer than file */ 69 | case 'o': /* older than file */ 70 | file = EARGF(usage()); 71 | if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) 72 | perror(file); 73 | break; 74 | default: 75 | /* miscellaneous operators */ 76 | if (strchr("abcdefghlpqrsuvwx", ARGC())) 77 | FLAG(ARGC()) = 1; 78 | else 79 | usage(); /* unknown flag */ 80 | } 81 | ARGEND; 82 | 83 | if (!argc) { 84 | /* read list from stdin */ 85 | while ((n = getline(&line, &linesiz, stdin)) > 0) { 86 | if (line[n - 1] == '\n') 87 | line[n - 1] = '\0'; 88 | test(line, line); 89 | } 90 | free(line); 91 | } else { 92 | for (; argc; argc--, argv++) { 93 | if (FLAG('l') && (dir = opendir(*argv))) { 94 | /* test directory contents */ 95 | while ((d = readdir(dir))) { 96 | r = snprintf(path, sizeof path, "%s/%s", *argv, d->d_name); 97 | if (r >= 0 && (size_t)r < sizeof path) 98 | test(path, d->d_name); 99 | } 100 | closedir(dir); 101 | } else { 102 | test(*argv, *argv); 103 | } 104 | } 105 | } 106 | return match ? 0 : 1; 107 | } 108 | -------------------------------------------------------------------------------- /instantmenu.1: -------------------------------------------------------------------------------- 1 | .TH INSTANTMENU 1 instantmenu\-VERSION 2 | .SH NAME 3 | instantMENU \- menu for instantOS 4 | .SH SYNOPSIS 5 | .B instantmenu 6 | .RB [ \-bfirvP ] 7 | .RB [ \-l 8 | .IR lines ] 9 | .RB [ \-g 10 | .IR columns ] 11 | .RB [ \-m 12 | .IR monitor ] 13 | .RB [ \-p 14 | .IR prompt ] 15 | .RB [ \-fn 16 | .IR font ] 17 | .RB [ \-nb 18 | .IR color ] 19 | .RB [ \-nf 20 | .IR color ] 21 | .RB [ \-sb 22 | .IR color ] 23 | .RB [ \-sf 24 | .IR color ] 25 | .RB [ \-w 26 | .IR windowid ] 27 | .P 28 | .BR instantmenu_run " ..." 29 | .SH DESCRIPTION 30 | .B instantmenu 31 | is a dynamic menu for X, which reads a list of newline\-separated items from 32 | stdin. When the user selects an item and presses Return, their choice is printed 33 | to stdout and instantmenu terminates. Entering text will narrow the items to those 34 | matching the tokens in the input. 35 | It is an extension of dmenu, meaning it can be used as a drop-in replacement but has a lot of extra features as well. 36 | instantmenu is used for most of the interfaces present in instantOS. 37 | .P 38 | .B instantmenu_run 39 | is a script used by 40 | .IR instantwm (1) 41 | which lists programs in the user's $PATH and runs the result in their $SHELL. 42 | .SH OPTIONS 43 | .TP 44 | .B \-b 45 | instantmenu appears at the bottom of the screen. 46 | .TP 47 | .B \-S 48 | confirm using space 49 | .TP 50 | .B \-c 51 | instanmenu appears centered on the screen. 52 | .TP 53 | .B \-wm 54 | let instantmenu be managed by the WM 55 | .TP 56 | .B \-f 57 | instantmenu grabs the keyboard before reading stdin if not reading from a tty. This 58 | is faster, but will lock up X until stdin reaches end\-of\-file. 59 | .TP 60 | .B \-i 61 | instantmenu matches menu items case insensitively. 62 | .TP 63 | .B \-P 64 | replace input indicator with dots 65 | .TP 66 | .B \-I 67 | only display input field 68 | .TP 69 | .B \-A 70 | activate alttab behaviour 71 | .TP 72 | .B \-F 73 | disable fuzzy matching 74 | .TP 75 | .BI \-E 76 | enable exact matching 77 | .TP 78 | .B \-A 79 | Enable alt-tab behaviour 80 | .TP 81 | .B \-r 82 | instantmenu rejects input that does not result in a match 83 | .TP 84 | .B \-it " initialtext" 85 | instantmenu starts with an initial text in the input field 86 | .TP 87 | .B \-ps " index" 88 | instantmenu starts with the item at index n preselected. it starts counting at 0 89 | .TP 90 | .BI \-l " lines" 91 | instantmenu lists items vertically, with the given number of lines. 92 | .TP 93 | .BI \-g " columns" 94 | instantmenu lists items in a grid with the given number of columns if the -l option is used. 95 | .TP 96 | .BI \-h " height" 97 | instantmenu uses a menu line of at least 'height' pixels tall, but no less than 8. 98 | .TP 99 | .BI \-bw " borderwidth" 100 | add a border around instantmenu with width n 101 | .TP 102 | .BI \-rc " command" 103 | run command on shift + right 104 | .TP 105 | .BI \-lc " command" 106 | run command on shift + left 107 | .TP 108 | .BI \-a " duration" 109 | set animation duration in frames 110 | .TP 111 | .B \-n 112 | instantmenu instantly selects if there is only one match. 113 | .TP 114 | .BI \-x " xoffset" 115 | instantmenu is placed at this offset measured from the left side of the monitor. 116 | Can be negative. 117 | If option 118 | .B \-C 119 | instantmenu will appear at the position of the mouse cursor 120 | .TP 121 | .B \-H 122 | Max out height to fit monitor 123 | .TP 124 | .B \-m 125 | is present, the measurement will use the given monitor. 126 | .TP 127 | .BI \-xr " right xoffset" 128 | instantmenu is placed at this offset measured from the right side of the monitor. 129 | Can be negative. 130 | If option 131 | .B \-m 132 | is present, the measurement will use the given monitor. 133 | .TP 134 | .BI \-y " yoffset" 135 | instantmenu is placed at this offset measured from the top of the monitor. If the 136 | .B \-b 137 | option is used, the offset is measured from the bottom. Can be negative. 138 | If option 139 | .B \-m 140 | is present, the measurement will use the given monitor. 141 | .TP 142 | .BI \-w " width" 143 | sets the width of the instantmenu window. 144 | .TP 145 | .B \-W 146 | The width will be automatically adjusted depending of the width of the longest line of text present in instdin 147 | .TP 148 | .BI \-m " monitor" 149 | instantmenu is displayed on the monitor number supplied. Monitor numbers are starting 150 | from 0. 151 | .TP 152 | .BI \-p " prompt" 153 | defines the prompt to be displayed to the left of the input field 154 | .TP 155 | .BI \-q " pretext" 156 | defines text to be displayed in empty input field 157 | .TP 158 | .BI \-G 159 | disables grabbing the keyboard 160 | .TP 161 | .BI \-fn " font" 162 | defines the font or font set used. 163 | .TP 164 | .BI \-nb " color" 165 | defines the normal background color. 166 | .IR #RGB , 167 | .IR #RRGGBB , 168 | and X color names are supported. 169 | .TP 170 | .BI \-nf " color" 171 | defines the normal foreground color. 172 | .TP 173 | .BI \-sb " color" 174 | defines the selected background color. 175 | .TP 176 | .BI \-sf " color" 177 | defines the selected foreground color. 178 | .TP 179 | .B \-v 180 | prints version information to stdout, then exits. 181 | .TP 182 | .BI \-w " windowid" 183 | embed into windowid. 184 | .SH USAGE 185 | instantmenu is completely controlled by the keyboard. Items are selected using the 186 | arrow keys, page up, page down, home, and end. 187 | .TP 188 | .B Tab 189 | Copy the selected item to the input field. 190 | .TP 191 | .B Return 192 | Confirm selection. Prints the selected item to stdout and exits, returning 193 | success. 194 | .TP 195 | .B Ctrl-Return 196 | Confirm selection. Prints the selected item to stdout and continues. 197 | .TP 198 | .B Shift\-Return 199 | Confirm input. Prints the input text to stdout and exits, returning success. 200 | .TP 201 | .B Escape 202 | Exit without selecting an item, returning failure. 203 | .TP 204 | .B Ctrl-Left 205 | Move cursor to the start of the current word 206 | .TP 207 | .B Ctrl-Right 208 | Move cursor to the end of the current word 209 | .TP 210 | .B C\-a 211 | Home 212 | .TP 213 | .B C\-b 214 | Left 215 | .TP 216 | .B C\-c 217 | Escape 218 | .TP 219 | .B C\-d 220 | Delete 221 | .TP 222 | .B C\-e 223 | End 224 | .TP 225 | .B C\-f 226 | Right 227 | .TP 228 | .B C\-g 229 | Escape 230 | .TP 231 | .B C\-h 232 | Backspace 233 | .TP 234 | .B C\-i 235 | Tab 236 | .TP 237 | .B C\-j 238 | Return 239 | .TP 240 | .B C\-J 241 | Shift-Return 242 | .TP 243 | .B C\-k 244 | Delete line right 245 | .TP 246 | .B C\-m 247 | Return 248 | .TP 249 | .B C\-M 250 | Shift-Return 251 | .TP 252 | .B C\-n 253 | Down 254 | .TP 255 | .B C\-p 256 | Up 257 | .TP 258 | .B C\-u 259 | Delete line left 260 | .TP 261 | .B C\-w 262 | Delete word left 263 | .TP 264 | .B C\-y 265 | Paste from primary X selection 266 | .TP 267 | .B C\-Y 268 | Paste from X clipboard 269 | .TP 270 | .B M\-b 271 | Move cursor to the start of the current word 272 | .TP 273 | .B M\-f 274 | Move cursor to the end of the current word 275 | .TP 276 | .B M\-g 277 | Home 278 | .TP 279 | .B M\-G 280 | End 281 | .TP 282 | .B M\-h 283 | Up 284 | .TP 285 | .B M\-j 286 | Page down 287 | .TP 288 | .B M\-k 289 | Page up 290 | .TP 291 | .B M\-l 292 | Down 293 | .SH SEE ALSO 294 | .IR instantwm (1), 295 | .IR itest (1) 296 | -------------------------------------------------------------------------------- /drw.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "config.h" 9 | #include "drw.h" 10 | #include "util.h" 11 | 12 | #define UTF_INVALID 0xFFFD 13 | 14 | static int utf8decode(const char *s_in, long *u, int *err) { 15 | static const unsigned char lens[] = { 16 | /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17 | /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ 18 | /* 110XX */ 2, 2, 2, 2, 19 | /* 1110X */ 3, 3, 20 | /* 11110 */ 4, 21 | /* 11111 */ 0, /* invalid */ 22 | }; 23 | static const unsigned char leading_mask[] = {0x7F, 0x1F, 0x0F, 0x07}; 24 | static const unsigned int overlong[] = {0x0, 0x80, 0x0800, 0x10000}; 25 | 26 | const unsigned char *s = (const unsigned char *)s_in; 27 | int len = lens[*s >> 3]; 28 | *u = UTF_INVALID; 29 | *err = 1; 30 | if (len == 0) 31 | return 1; 32 | 33 | long cp = s[0] & leading_mask[len - 1]; 34 | for (int i = 1; i < len; ++i) { 35 | if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) 36 | return i; 37 | cp = (cp << 6) | (s[i] & 0x3F); 38 | } 39 | /* out of range, surrogate, overlong encoding */ 40 | if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1]) 41 | return len; 42 | 43 | *err = 0; 44 | *u = cp; 45 | return len; 46 | } 47 | 48 | Drw *drw_create(Display *dpy, int screen, Window root, unsigned int w, 49 | unsigned int h) { 50 | Drw *drw = ecalloc(1, sizeof(Drw)); 51 | 52 | drw->dpy = dpy; 53 | drw->screen = screen; 54 | drw->root = root; 55 | drw->w = w; 56 | drw->h = h; 57 | drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); 58 | drw->gc = XCreateGC(dpy, root, 0, NULL); 59 | XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); 60 | 61 | return drw; 62 | } 63 | 64 | void drw_resize(Drw *drw, unsigned int w, unsigned int h) { 65 | if (!drw) 66 | return; 67 | 68 | drw->w = w; 69 | drw->h = h; 70 | if (drw->drawable) 71 | XFreePixmap(drw->dpy, drw->drawable); 72 | drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, 73 | DefaultDepth(drw->dpy, drw->screen)); 74 | } 75 | 76 | void drw_free(Drw *drw) { 77 | XFreePixmap(drw->dpy, drw->drawable); 78 | XFreeGC(drw->dpy, drw->gc); 79 | drw_fontset_free(drw->fonts); 80 | free(drw); 81 | } 82 | 83 | /* This function is an implementation detail. Library users should use 84 | * drw_fontset_create instead. 85 | */ 86 | static Fnt *xfont_create(Drw *drw, const char *fontname, 87 | FcPattern *fontpattern) { 88 | Fnt *font; 89 | XftFont *xfont = NULL; 90 | FcPattern *pattern = NULL; 91 | 92 | if (fontname) { 93 | /* Using the pattern found at font->xfont->pattern does not yield the 94 | * same substitution results as using the pattern returned by 95 | * FcNameParse; using the latter results in the desired fallback 96 | * behaviour whereas the former just results in missing-character 97 | * rectangles being drawn, at least with some fonts. */ 98 | if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { 99 | fprintf(stderr, "error, cannot load font from name: '%s'\n", 100 | fontname); 101 | return NULL; 102 | } 103 | if (!(pattern = FcNameParse((FcChar8 *)fontname))) { 104 | fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", 105 | fontname); 106 | XftFontClose(drw->dpy, xfont); 107 | return NULL; 108 | } 109 | } else if (fontpattern) { 110 | if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { 111 | fprintf(stderr, "error, cannot load font from pattern.\n"); 112 | return NULL; 113 | } 114 | } else { 115 | die("no font specified."); 116 | } 117 | 118 | font = ecalloc(1, sizeof(Fnt)); 119 | font->xfont = xfont; 120 | font->pattern = pattern; 121 | font->h = xfont->ascent + xfont->descent; 122 | font->dpy = drw->dpy; 123 | 124 | return font; 125 | } 126 | 127 | static void xfont_free(Fnt *font) { 128 | if (!font) 129 | return; 130 | if (font->pattern) 131 | FcPatternDestroy(font->pattern); 132 | XftFontClose(font->dpy, font->xfont); 133 | free(font); 134 | } 135 | 136 | Fnt *drw_fontset_create(Drw *drw, const char *fonts[], size_t fontcount) { 137 | Fnt *cur, *ret = NULL; 138 | size_t i; 139 | 140 | if (!drw || !fonts) 141 | return NULL; 142 | 143 | for (i = 1; i <= fontcount; i++) { 144 | if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { 145 | cur->next = ret; 146 | ret = cur; 147 | } 148 | } 149 | return (drw->fonts = ret); 150 | } 151 | 152 | void drw_fontset_free(Fnt *font) { 153 | if (font) { 154 | drw_fontset_free(font->next); 155 | xfont_free(font); 156 | } 157 | } 158 | 159 | void drw_clr_create(Drw *drw, Clr *dest, const char *clrname) { 160 | if (!drw || !dest || !clrname) 161 | return; 162 | 163 | if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), 164 | DefaultColormap(drw->dpy, drw->screen), clrname, 165 | dest)) 166 | die("error, cannot allocate color '%s'", clrname); 167 | } 168 | 169 | /* Wrapper to create color schemes. The caller has to call free(3) on the 170 | * returned color scheme when done using it. */ 171 | Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) { 172 | size_t i; 173 | Clr *ret; 174 | 175 | /* need at least two colors for a scheme */ 176 | if (!drw || !clrnames || clrcount < 2 || 177 | !(ret = ecalloc(clrcount, sizeof(XftColor)))) 178 | return NULL; 179 | 180 | for (i = 0; i < clrcount; i++) 181 | drw_clr_create(drw, &ret[i], clrnames[i]); 182 | return ret; 183 | } 184 | 185 | void drw_setfontset(Drw *drw, Fnt *set) { 186 | if (drw) 187 | drw->fonts = set; 188 | } 189 | 190 | void drw_setscheme(Drw *drw, Clr *scm) { 191 | if (drw) 192 | drw->scheme = scm; 193 | } 194 | 195 | void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, 196 | int filled, int invert, int rounded) { 197 | if (!drw || !drw->scheme) 198 | return; 199 | XSetForeground(drw->dpy, drw->gc, 200 | invert ? drw->scheme[ColBg].pixel 201 | : drw->scheme[ColFg].pixel); 202 | if (filled && h < 40) { 203 | if (rounded) { 204 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h - 4); 205 | XSetForeground(drw->dpy, drw->gc, drw->scheme[ColDetail].pixel); 206 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y + h - 4, w, 207 | 4); 208 | } else { 209 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 210 | } 211 | } else { 212 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 213 | } 214 | } 215 | 216 | int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, 217 | unsigned int lpad, const char *text, int invert, int rounded) { 218 | int ty, ellipsis_x = 0; 219 | unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; 220 | 221 | XftDraw *d = NULL; 222 | Fnt *usedfont, *curfont, *nextfont; 223 | int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; 224 | long utf8codepoint = 0; 225 | const char *utf8str; 226 | FcCharSet *fccharset; 227 | FcPattern *fcpattern; 228 | FcPattern *match; 229 | XftResult result; 230 | int charexists = 0, overflow = 0; 231 | /* keep track of a couple codepoints for which we have no match. */ 232 | static unsigned int nomatches[128], ellipsis_width, invalid_width; 233 | static const char invalid[] = "�"; 234 | 235 | if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) 236 | return 0; 237 | 238 | if (!render) { 239 | w = invert ? invert : ~invert; 240 | } else { 241 | XSetForeground(drw->dpy, drw->gc, 242 | drw->scheme[invert ? ColFg : ColBg].pixel); 243 | if (rounded) { 244 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h - 4); 245 | XSetForeground(drw->dpy, drw->gc, drw->scheme[ColDetail].pixel); 246 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y + h - 4, w, 247 | 4); 248 | } else { 249 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 250 | } 251 | if (w < lpad) 252 | return x + w; 253 | 254 | d = XftDrawCreate(drw->dpy, drw->drawable, 255 | DefaultVisual(drw->dpy, drw->screen), 256 | DefaultColormap(drw->dpy, drw->screen)); 257 | x += lpad; 258 | w -= lpad; 259 | } 260 | 261 | usedfont = drw->fonts; 262 | if (!ellipsis_width && render) 263 | ellipsis_width = drw_fontset_getwidth(drw, "..."); 264 | if (!invalid_width && render) 265 | invalid_width = drw_fontset_getwidth(drw, invalid); 266 | while (1) { 267 | ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0; 268 | utf8str = text; 269 | nextfont = NULL; 270 | while (*text) { 271 | utf8charlen = utf8decode(text, &utf8codepoint, &utf8err); 272 | while (!charexists) { 273 | for (curfont = drw->fonts; curfont; curfont = curfont->next) { 274 | charexists = 275 | charexists || 276 | XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); 277 | if (charexists) { 278 | drw_font_getexts(curfont, text, utf8charlen, &tmpw, 279 | NULL); 280 | if (ew + ellipsis_width <= w) { 281 | /* keep track where the ellipsis still fits */ 282 | ellipsis_x = x + ew; 283 | ellipsis_w = w - ew; 284 | ellipsis_len = utf8strlen; 285 | } 286 | 287 | if (ew + tmpw > w) { 288 | overflow = 1; 289 | /* called from drw_fontset_getwidth_clamp(): 290 | * it wants the width AFTER the overflow 291 | */ 292 | if (!render) 293 | x += tmpw; 294 | else 295 | utf8strlen = ellipsis_len; 296 | } else if (curfont == usedfont) { 297 | text += utf8charlen; 298 | utf8strlen += utf8err ? 0 : utf8charlen; 299 | ew += utf8err ? 0 : tmpw; 300 | } else { 301 | nextfont = curfont; 302 | } 303 | break; 304 | } 305 | } 306 | if (!charexists) 307 | utf8charlen = utf8decode("a", &utf8codepoint, &utf8err); 308 | } 309 | // the last part is a horrible hack 310 | // TODO: find out why the emoji picker has loads of utf8 errors 311 | // and some infinite loop 312 | if (overflow || !charexists || nextfont || (lines >= 1 && utf8err)) 313 | break; 314 | else 315 | charexists = 0; 316 | } 317 | 318 | if (utf8strlen) { 319 | if (render) { 320 | ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; 321 | XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], 322 | usedfont->xfont, x, ty - (rounded ? 2 : 0), 323 | (XftChar8 *)utf8str, utf8strlen); 324 | } 325 | x += ew; 326 | w -= ew; 327 | } 328 | if (utf8err && (!render || invalid_width < w)) { 329 | if (render) 330 | drw_text(drw, x, y, w, h, 0, invalid, invert, rounded); 331 | x += invalid_width; 332 | w -= invalid_width; 333 | } 334 | 335 | if (render && overflow) 336 | drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert, 337 | rounded); 338 | 339 | if (!*text || overflow) { 340 | break; 341 | } else if (nextfont) { 342 | charexists = 0; 343 | usedfont = nextfont; 344 | } else { 345 | /* Regardless of whether or not a fallback font is found, the 346 | * character must be drawn. */ 347 | charexists = 1; 348 | 349 | hash = (unsigned)utf8codepoint; 350 | hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; 351 | hash = ((hash >> 15) ^ hash) * 0xD35A2D97; 352 | h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); 353 | h1 = (hash >> 17) % LENGTH(nomatches); 354 | 355 | /* avoid expensive XftFontMatch call when we know we won't find a 356 | * match */ 357 | if (nomatches[h0] == utf8codepoint || 358 | nomatches[h1] == utf8codepoint) 359 | goto no_match; 360 | 361 | fccharset = FcCharSetCreate(); 362 | FcCharSetAddChar(fccharset, utf8codepoint); 363 | 364 | if (!drw->fonts->pattern) { 365 | /* Refer to the comment in xfont_create for more information. */ 366 | die("the first font in the cache must be loaded from a font " 367 | "string."); 368 | } 369 | 370 | fcpattern = FcPatternDuplicate(drw->fonts->pattern); 371 | FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 372 | FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); 373 | FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); 374 | 375 | FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); 376 | FcDefaultSubstitute(fcpattern); 377 | match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); 378 | 379 | FcCharSetDestroy(fccharset); 380 | FcPatternDestroy(fcpattern); 381 | 382 | if (match) { 383 | usedfont = xfont_create(drw, NULL, match); 384 | if (usedfont && 385 | XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { 386 | for (curfont = drw->fonts; curfont->next; 387 | curfont = curfont->next) 388 | ; /* NOP */ 389 | curfont->next = usedfont; 390 | } else { 391 | xfont_free(usedfont); 392 | nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; 393 | no_match: 394 | usedfont = drw->fonts; 395 | } 396 | } 397 | } 398 | } 399 | if (d) 400 | XftDrawDestroy(d); 401 | 402 | return x + (render ? w : 0); 403 | } 404 | 405 | void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, 406 | unsigned int h) { 407 | if (!drw) 408 | return; 409 | 410 | XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); 411 | XSync(drw->dpy, False); 412 | } 413 | 414 | unsigned int drw_fontset_getwidth(Drw *drw, const char *text) { 415 | if (!drw || !drw->fonts || !text) 416 | return 0; 417 | return drw_text(drw, 0, 0, 0, 0, 0, text, 0, 0); 418 | } 419 | 420 | unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, 421 | unsigned int n) { 422 | unsigned int tmp = 0; 423 | if (drw && drw->fonts && text && n) 424 | tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n, 0); 425 | return MIN(n, tmp); 426 | } 427 | 428 | void drw_font_getexts(Fnt *font, const char *text, unsigned int len, 429 | unsigned int *w, unsigned int *h) { 430 | XGlyphInfo ext; 431 | 432 | if (!font || !text) 433 | return; 434 | 435 | XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); 436 | if (w) 437 | *w = ext.xOff; 438 | if (h) 439 | *h = font->h; 440 | } 441 | 442 | Cur *drw_cur_create(Drw *drw, int shape) { 443 | Cur *cur; 444 | 445 | if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) 446 | return NULL; 447 | 448 | cur->cursor = XCreateFontCursor(drw->dpy, shape); 449 | 450 | return cur; 451 | } 452 | 453 | void drw_cur_free(Drw *drw, Cur *cursor) { 454 | if (!cursor) 455 | return; 456 | 457 | XFreeCursor(drw->dpy, cursor->cursor); 458 | free(cursor); 459 | } 460 | -------------------------------------------------------------------------------- /instantmenu.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #ifdef XINERAMA 17 | #include 18 | #endif 19 | #include 20 | #include 21 | #include 22 | 23 | #include "drw.h" 24 | #include "enums.h" 25 | #include "util.h" 26 | 27 | /* macros */ 28 | #define INTERSECT(x, y, w, h, r) \ 29 | (MAX(0, MIN((x) + (w), (r).x_org + (r).width) - MAX((x), (r).x_org)) * \ 30 | MAX(0, MIN((y) + (h), (r).y_org + (r).height) - MAX((y), (r).y_org))) 31 | #define LENGTH(X) (sizeof X / sizeof X[0]) 32 | #define TEXTW(X) (commented ? bh : drw_fontset_getwidth(drw, (X)) + lrpad) 33 | 34 | #define NUMBERSMAXDIGITS 100 35 | #define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) + 1 36 | #define BUTTONMASK (ButtonPressMask | ButtonReleaseMask) 37 | #define MOUSEMASK (BUTTONMASK | PointerMotionMask) 38 | 39 | struct item { 40 | char *text; 41 | char *stext; 42 | struct item *left, *right; 43 | int out; 44 | double distance; 45 | }; 46 | 47 | static char numbers[NUMBERSBUFSIZE] = ""; 48 | static int tempnumer; 49 | static char text[BUFSIZ] = ""; 50 | static char *embed; 51 | static int bh, mw, mh; 52 | static int dmx = 0, dmy = 0; /* put instantmenu at these x and y offsets */ 53 | static int dmw = 0; /* make instantmenu this wide */ 54 | static int rightxoffset = 0; /* make instantmenu x offset come from the right */ 55 | static int inputw = 0, promptw, toast = 0, inputonly = 0, passwd = 0, 56 | nograb = 0, alttab = 0, tabbed = 0; 57 | static int lrpad; /* sum of left and right padding */ 58 | static size_t cursor; 59 | static struct item *items = NULL; 60 | static struct item *matches, *matchend; 61 | static struct item *prev, *curr, *next, *sel; 62 | static int mon = -1, screen; 63 | static int managed = 0; 64 | // instantASSIST: items are a single letter with a description appearing on 65 | // selection. 66 | static int commented = 0; 67 | 68 | static int rejectnomatch = 0; 69 | 70 | static Atom clip, utf8; 71 | static Display *dpy; 72 | static Window root, parentwin, win; 73 | static XIC xic; 74 | 75 | static Drw *drw; 76 | static Clr *scheme[SchemeLast]; 77 | 78 | /* Temporary arrays to allow overriding xresources values */ 79 | static char *colortemp[SchemeLast][ColLast]; 80 | static char *tempfont; 81 | static const char *xresname = "instantmenu"; 82 | static const char *xresscheme[SchemeLast] = { 83 | [SchemeNorm] = "norm", 84 | [SchemeFade] = "fade", 85 | [SchemeHighlight] = "highlight", 86 | [SchemeHover] = "hover", 87 | [SchemeSel] = "sel", 88 | [SchemeOut] = "out", 89 | [SchemeGreen] = "green", 90 | [SchemeRed] = "red", 91 | [SchemeYellow] = "yellow", 92 | }; 93 | static const char *xrescolortype[ColLast] = { 94 | "fg", 95 | "bg", 96 | "detail", 97 | }; 98 | 99 | static const int outputoffset[ItemLast] = { 100 | [ItemNormal] = 0, [ItemComment] = 1, [ItemColoredComment] = 4, 101 | [ItemColored] = 2, [ItemIcon] = 6, 102 | }; 103 | 104 | #include "config.h" 105 | 106 | static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; 107 | static char *(*fstrstr)(const char *, const char *) = strstr; 108 | 109 | int getrootptr(int *x, int *y) { 110 | int di; 111 | unsigned int dui; 112 | Window dummy; 113 | 114 | return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); 115 | } 116 | 117 | static unsigned int textw_clamp(const char *str, unsigned int n, 118 | int commented) { 119 | if (commented) { 120 | return bh; 121 | } 122 | unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; 123 | return MIN(w, n); 124 | } 125 | 126 | static void appenditem(struct item *item, struct item **list, 127 | struct item **last) { 128 | if (*last) 129 | (*last)->right = item; 130 | else 131 | *list = item; 132 | 133 | item->left = *last; 134 | item->right = NULL; 135 | *last = item; 136 | } 137 | 138 | static void calcoffsets(void) { 139 | int i, n; 140 | 141 | if (lines > 0) 142 | n = lines * columns * bh; 143 | else 144 | n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); 145 | /* calculate which items will begin the next page and previous page */ 146 | for (i = 0, next = curr; next; next = next->right) { 147 | if ((i += (lines > 0) ? bh : textw_clamp(next->text, n, commented)) > n) 148 | break; 149 | } 150 | for (i = 0, prev = curr; prev && prev->left; prev = prev->left) 151 | if ((i += (lines > 0) 152 | ? bh 153 | : textw_clamp(prev->left->text, n, commented)) > n) 154 | break; 155 | } 156 | 157 | static int max_textw(void) { 158 | int len = 0; 159 | for (struct item *item = items; item && item->text; item++) 160 | len = MAX(TEXTW(item->text), len); 161 | return len; 162 | } 163 | 164 | static void cleanup(void) { 165 | size_t i; 166 | 167 | XUngrabKey(dpy, AnyKey, AnyModifier, root); 168 | for (i = 0; i < SchemeLast; i++) 169 | free(scheme[i]); 170 | for (i = 0; items && items[i].text; ++i) 171 | free(items[i].text); 172 | free(items); 173 | drw_free(drw); 174 | XSync(dpy, False); 175 | XCloseDisplay(dpy); 176 | } 177 | 178 | static char *cistrstr(const char *h, const char *n) { 179 | size_t i; 180 | 181 | if (!n[0]) 182 | return (char *)h; 183 | 184 | for (; *h; ++h) { 185 | for (i = 0; n[i] && tolower((unsigned char)n[i]) == 186 | tolower((unsigned char)h[i]); 187 | ++i) 188 | ; 189 | if (n[i] == '\0') 190 | return (char *)h; 191 | } 192 | return NULL; 193 | } 194 | 195 | static int drawitem(struct item *item, int x, int y, int w) { 196 | int itemcategory = ItemNormal; 197 | if (item->text[0] == '>') { 198 | if (item->text[1] == '>') { 199 | itemcategory = ItemColoredComment; 200 | switch (item->text[2]) { 201 | case 'r': 202 | drw_setscheme(drw, scheme[SchemeRed]); 203 | break; 204 | case 'g': 205 | drw_setscheme(drw, scheme[SchemeGreen]); 206 | break; 207 | case 'y': 208 | drw_setscheme(drw, scheme[SchemeYellow]); 209 | break; 210 | case 'h': 211 | drw_setscheme(drw, scheme[SchemeHighlight]); 212 | break; 213 | 214 | case 'b': 215 | drw_setscheme(drw, scheme[SchemeSel]); 216 | break; 217 | default: 218 | itemcategory = ItemComment; 219 | drw_setscheme(drw, scheme[SchemeNorm]); 220 | break; 221 | } 222 | } else { 223 | drw_setscheme(drw, scheme[SchemeNorm]); 224 | itemcategory = ItemComment; 225 | } 226 | 227 | } else if (item->text[0] == ':') { 228 | itemcategory = ItemColored; 229 | if (item == sel) { 230 | switch (item->text[1]) { 231 | case 'r': 232 | drw_setscheme(drw, scheme[SchemeRed]); 233 | break; 234 | case 'g': 235 | drw_setscheme(drw, scheme[SchemeGreen]); 236 | break; 237 | case 'y': 238 | drw_setscheme(drw, scheme[SchemeYellow]); 239 | break; 240 | case 'b': 241 | drw_setscheme(drw, scheme[SchemeSel]); 242 | break; 243 | default: 244 | drw_setscheme(drw, scheme[SchemeSel]); 245 | itemcategory = ItemNormal; 246 | break; 247 | } 248 | } else { 249 | drw_setscheme(drw, scheme[SchemeNorm]); 250 | } 251 | } else { 252 | if (item == sel) 253 | drw_setscheme(drw, scheme[SchemeSel]); 254 | else if (item->out) 255 | drw_setscheme(drw, scheme[SchemeOut]); 256 | else 257 | drw_setscheme(drw, scheme[SchemeNorm]); 258 | } 259 | 260 | int temppadding; 261 | temppadding = 0; 262 | if (itemcategory == ItemColored) { 263 | if (item->text[2] == ' ') { 264 | temppadding = drw->fonts->h * 3; 265 | animated = 1; 266 | char dest[1000]; 267 | strcpy(dest, item->text); 268 | dest[6] = '\0'; 269 | drw_text(drw, x, y, temppadding, lineheight, temppadding / 2.6, 270 | dest + 3, 0, item == sel); 271 | itemcategory = ItemIcon; 272 | drw_setscheme(drw, sel == item ? scheme[SchemeHover] 273 | : scheme[SchemeNorm]); 274 | } 275 | } 276 | 277 | char *output; 278 | if (commented) { 279 | static char onestr[2]; 280 | onestr[0] = item->stext[0]; 281 | onestr[1] = '\0'; 282 | output = onestr; 283 | } else { 284 | output = item->stext; 285 | } 286 | 287 | if (item == sel) 288 | sely = y; 289 | return drw_text( 290 | drw, x + ((itemcategory == ItemIcon) ? temppadding : 0), y, 291 | commented ? bh : (w - ((itemcategory == ItemIcon) ? temppadding : 0)), 292 | bh, 293 | commented ? (bh - drw_fontset_getwidth(drw, (output))) / 2 : lrpad / 2, 294 | output + outputoffset[itemcategory], 0, 295 | (itemcategory == ItemColoredComment || item == sel)); 296 | } 297 | 298 | static void recalculatenumbers() { 299 | unsigned int numer = 0, denom = 0; 300 | struct item *item; 301 | if (matchend) { 302 | numer++; 303 | for (item = matchend; item && item->left; item = item->left) 304 | numer++; 305 | } 306 | 307 | if (toast) { 308 | tempnumer = 0; 309 | return; 310 | } 311 | 312 | for (item = items; item && item->text; item++) 313 | denom++; 314 | if (numer > 1) { 315 | if (lines > 1) { 316 | if (numer > lines) 317 | tempnumer = 1; 318 | else 319 | tempnumer = 0; 320 | } else { 321 | tempnumer = 1; 322 | } 323 | } else { 324 | tempnumer = 0; 325 | } 326 | snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); 327 | } 328 | 329 | static void drawmenu(void) { 330 | unsigned int curpos; 331 | struct item *item; 332 | int x = 0, y = 0, fh = drw->fonts->h, w; 333 | int arrowwidth = TEXTW(""); 334 | 335 | char *censort; 336 | drw_setscheme(drw, scheme[SchemeNorm]); 337 | drw_rect(drw, 0, 0, mw, mh, 1, 1, 0); 338 | if (commented && matches) 339 | prompt = sel->text + 1; 340 | 341 | if (prompt && *prompt) { 342 | if (leftcmd) 343 | x += arrowwidth; 344 | drw_setscheme(drw, scheme[SchemeSel]); 345 | if (lines < 8) { 346 | x = drw_text(drw, x, 0, promptw, bh * (lines + 1), lrpad / 2, 347 | prompt, 0, 1); 348 | } else { 349 | x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0, 1); 350 | } 351 | } 352 | 353 | /* draw input field */ 354 | w = (lines > 0 || !matches) ? mw - x : inputw; 355 | drw_setscheme(drw, scheme[SchemeNorm]); 356 | 357 | if (passwd) { 358 | censort = ecalloc(1, sizeof(text)); 359 | memset(censort, '.', strlen(text)); 360 | drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0, 0); 361 | free(censort); 362 | } else { 363 | if (text[0] != '\0') { 364 | drw_text(drw, x + (leftcmd ? arrowwidth : 0), 0, w, bh, lrpad / 2, 365 | text, 0, 0); 366 | } else if (searchtext) { 367 | drw_setscheme(drw, scheme[SchemeFade]); 368 | drw_text(drw, x + (leftcmd ? arrowwidth : 0), 0, w, bh, lrpad / 2, 369 | searchtext, 0, 0); 370 | drw_setscheme(drw, scheme[SchemeNorm]); 371 | } 372 | } 373 | curpos = TEXTW(text) - TEXTW(&text[cursor]); 374 | if ((curpos += lrpad / 2 - 1) < w) { 375 | drw_setscheme(drw, scheme[SchemeNorm]); 376 | // disable cursor on password prompt 377 | if (!passwd && !toast) 378 | drw_rect(drw, x + (leftcmd ? arrowwidth : 0) + curpos, 379 | 2 + (bh - fh) / 2, 2, fh - 4, 1, 0, 0); 380 | } 381 | 382 | recalculatenumbers(); 383 | 384 | if (lines > 0) { 385 | /* draw grid */ 386 | int i = 0; 387 | for (item = curr; item != next; item = item->right, i++) 388 | drawitem(item, x + ((i / lines) * ((mw - x) / columns)), 389 | y + (((i % lines) + 1) * bh), (mw - x) / columns); 390 | } else if (matches) { 391 | /* draw horizontal list */ 392 | x += inputw; 393 | w = TEXTW("<"); 394 | if (curr->left) { 395 | drw_setscheme(drw, scheme[SchemeNorm]); 396 | drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0, 0); 397 | } 398 | x += w; 399 | for (item = curr; item != next; item = item->right) 400 | x = drawitem(item, x, 0, 401 | textw_clamp(item->stext, 402 | mw - x - TEXTW(">") - TEXTW(numbers), 403 | commented)); 404 | 405 | if (next) { 406 | w = TEXTW(">"); 407 | drw_setscheme(drw, scheme[SchemeNorm]); 408 | if (tempnumer) 409 | drw_text(drw, mw - w - TEXTW(numbers), 0, w, bh, lrpad / 2, ">", 410 | 0, 0); 411 | } 412 | } 413 | 414 | drw_setscheme(drw, scheme[SchemeNorm]); 415 | if (tempnumer) { 416 | drw_text(drw, mw - TEXTW(numbers) - (rightcmd ? arrowwidth : 0), 0, 417 | TEXTW(numbers), bh, lrpad / 2, numbers, 0, 0); 418 | } 419 | if (lines > 0) { 420 | if (leftcmd) { 421 | drw_setscheme(drw, scheme[SchemeHighlight]); 422 | drw_text(drw, 0, 0, arrowwidth, bh, lrpad / 2, "", 0, 0); 423 | } 424 | if (rightcmd) { 425 | drw_setscheme(drw, scheme[SchemeHighlight]); 426 | drw_text(drw, mw - arrowwidth, 0, arrowwidth, bh, lrpad / 2, "", 427 | 0, 0); 428 | } 429 | } 430 | drw_map(drw, win, 0, 0, mw, mh); 431 | } 432 | 433 | static void grabfocus(void) { 434 | if (toast) 435 | return; 436 | 437 | struct timespec ts = {.tv_sec = 0, .tv_nsec = 10000000}; 438 | Window focuswin; 439 | int i, revertwin; 440 | 441 | for (i = 0; i < 100; ++i) { 442 | XGetInputFocus(dpy, &focuswin, &revertwin); 443 | if (focuswin == win) 444 | return; 445 | if (managed) { 446 | XTextProperty prop; 447 | char *windowtitle = prompt != NULL ? prompt : "dmenu"; 448 | Xutf8TextListToTextProperty(dpy, &windowtitle, 1, XUTF8StringStyle, 449 | &prop); 450 | XSetWMName(dpy, win, &prop); 451 | XSetTextProperty(dpy, win, &prop, 452 | XInternAtom(dpy, "_NET_WM_NAME", False)); 453 | XFree(prop.value); 454 | } else { 455 | XSetInputFocus(dpy, win, RevertToParent, CurrentTime); 456 | } 457 | nanosleep(&ts, NULL); 458 | } 459 | die("cannot grab focus"); 460 | } 461 | 462 | static void grabkeyboard(void) { 463 | if (toast) 464 | return; 465 | struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000}; 466 | int i; 467 | 468 | if (nograb) 469 | return; 470 | 471 | if (embed || managed) 472 | return; 473 | /* try to grab keyboard, we may have to wait for another process to ungrab 474 | */ 475 | for (i = 0; i < 1000; i++) { 476 | if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, 477 | GrabModeAsync, CurrentTime) == GrabSuccess) 478 | return; 479 | nanosleep(&ts, NULL); 480 | } 481 | die("cannot grab keyboard"); 482 | } 483 | 484 | static void grabpointer(void) { 485 | XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, 486 | None, XCreateFontCursor(dpy, XC_fleur), CurrentTime); 487 | } 488 | 489 | int compare_distance(const void *a, const void *b) { 490 | struct item *da = *(struct item **)a; 491 | struct item *db = *(struct item **)b; 492 | 493 | if (!db) 494 | return 1; 495 | if (!da) 496 | return -1; 497 | 498 | return da->distance == db->distance ? 0 499 | : da->distance < db->distance ? -1 500 | : 1; 501 | } 502 | 503 | void fuzzymatch(void) { 504 | /* bang - we have so much memory */ 505 | struct item *it; 506 | struct item **fuzzymatches = NULL; 507 | char c; 508 | int number_of_matches = 0, i, pidx, sidx, eidx; 509 | int text_len = strlen(text), itext_len; 510 | 511 | matches = matchend = NULL; 512 | 513 | /* walk through all items */ 514 | for (it = items; it && it->text; it++) { 515 | if (text_len) { 516 | itext_len = strlen(it->text); 517 | pidx = 0; /* pointer */ 518 | sidx = eidx = -1; /* start of match, end of match */ 519 | /* walk through item text */ 520 | for (i = 0; i < itext_len && (c = it->text[i]); i++) { 521 | /* fuzzy match pattern */ 522 | if (!fstrncmp(&text[pidx], &c, 1)) { 523 | if (sidx == -1) 524 | sidx = i; 525 | pidx++; 526 | if (pidx == text_len) { 527 | eidx = i; 528 | break; 529 | } 530 | } 531 | } 532 | /* build list of matches */ 533 | if (eidx != -1) { 534 | /* compute distance */ 535 | /* add penalty if match starts late (log(sidx+2)) 536 | * add penalty for long a match without many matching characters 537 | */ 538 | it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len); 539 | /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); 540 | */ 541 | appenditem(it, &matches, &matchend); 542 | number_of_matches++; 543 | } 544 | } else { 545 | appenditem(it, &matches, &matchend); 546 | } 547 | } 548 | 549 | if (number_of_matches) { 550 | /* initialize array with matches */ 551 | if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * 552 | sizeof(struct item *)))) 553 | die("cannot realloc %u bytes:", 554 | number_of_matches * sizeof(struct item *)); 555 | for (i = 0, it = matches; it && i < number_of_matches; 556 | i++, it = it->right) { 557 | fuzzymatches[i] = it; 558 | } 559 | /* sort matches according to distance */ 560 | qsort(fuzzymatches, number_of_matches, sizeof(struct item *), 561 | compare_distance); 562 | /* rebuild list of matches */ 563 | matches = matchend = NULL; 564 | for (i = 0, it = fuzzymatches[i]; 565 | i < number_of_matches && it && it->text; 566 | i++, it = fuzzymatches[i]) { 567 | appenditem(it, &matches, &matchend); 568 | } 569 | free(fuzzymatches); 570 | } 571 | curr = sel = matches; 572 | 573 | if (instant && matches && matches == matchend) { 574 | puts(matches->text); 575 | cleanup(); 576 | exit(0); 577 | } 578 | 579 | calcoffsets(); 580 | } 581 | 582 | static void match(void) { 583 | 584 | if (commented) { 585 | struct item *it; 586 | for (it = items; it && it->text; it++) { 587 | if (text && it->text[0] == text[0]) { 588 | puts(it->text); 589 | cleanup(); 590 | exit(0); 591 | } 592 | } 593 | // exit if no match is found 594 | if (text[0] != '\0') { 595 | cleanup(); 596 | exit(0); 597 | } 598 | } 599 | 600 | if (fuzzy) { 601 | fuzzymatch(); 602 | return; 603 | } 604 | static char **tokv = NULL; 605 | static int tokn = 0; 606 | 607 | char buf[sizeof text], *s; 608 | int i, tokc = 0; 609 | size_t len, textsize; 610 | struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; 611 | 612 | strcpy(buf, text); 613 | /* separate input text into tokens to be matched individually */ 614 | for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) 615 | if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) 616 | die("cannot realloc %zu bytes:", tokn * sizeof *tokv); 617 | len = tokc ? strlen(tokv[0]) : 0; 618 | 619 | matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; 620 | textsize = strlen(text) + 1; 621 | for (item = items; item && item->text; item++) { 622 | for (i = 0; i < tokc; i++) 623 | if (!fstrstr(item->text, tokv[i])) 624 | break; 625 | if (i != tokc) /* not all tokens match */ 626 | continue; 627 | /* exact matches go first, then prefixes, then substrings */ 628 | if (!tokc || !fstrncmp(text, item->text, textsize)) 629 | appenditem(item, &matches, &matchend); 630 | else if (!fstrncmp(tokv[0], item->text, len)) 631 | appenditem(item, &lprefix, &prefixend); 632 | else if (!exact) 633 | appenditem(item, &lsubstr, &substrend); 634 | } 635 | if (lprefix) { 636 | if (matches) { 637 | matchend->right = lprefix; 638 | lprefix->left = matchend; 639 | } else 640 | matches = lprefix; 641 | matchend = prefixend; 642 | } 643 | if (lsubstr) { 644 | if (matches) { 645 | matchend->right = lsubstr; 646 | lsubstr->left = matchend; 647 | } else 648 | matches = lsubstr; 649 | matchend = substrend; 650 | } 651 | curr = sel = matches; 652 | 653 | if (instant && matches && matches == matchend && !lsubstr) { 654 | puts(matches->text); 655 | cleanup(); 656 | exit(0); 657 | } 658 | 659 | calcoffsets(); 660 | } 661 | 662 | static void insert(const char *str, ssize_t n) { 663 | if (strlen(text) + n > sizeof text - 1) 664 | return; 665 | 666 | static char last[BUFSIZ] = ""; 667 | if (rejectnomatch) { 668 | /* store last text value in case we need to revert it */ 669 | memcpy(last, text, BUFSIZ); 670 | } 671 | 672 | /* move existing text out of the way, insert new text, and update cursor */ 673 | memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); 674 | if (n > 0) { 675 | memcpy(&text[cursor], str, n); 676 | int i; 677 | if (smartcase) { 678 | for (i = 0; i < strlen(text); i++) { 679 | if (text[i] >= 65 && text[i] <= 90) { 680 | smartcase = 0; 681 | fstrncmp = strncmp; 682 | fstrstr = strstr; 683 | } 684 | } 685 | } 686 | } 687 | cursor += n; 688 | match(); 689 | 690 | if (!matches && rejectnomatch) { 691 | /* revert to last text value if theres no match */ 692 | memcpy(text, last, BUFSIZ); 693 | cursor -= n; 694 | match(); 695 | } 696 | } 697 | 698 | static size_t nextrune(int inc) { 699 | ssize_t n; 700 | 701 | /* return location of next utf8 rune in the given direction (+1 or -1) */ 702 | for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) 703 | ; 704 | return n; 705 | } 706 | 707 | static void movewordedge(int dir) { 708 | if (dir < 0) { /* move cursor to the start of the word*/ 709 | while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) 710 | cursor = nextrune(-1); 711 | while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) 712 | cursor = nextrune(-1); 713 | } else { /* move cursor to the end of the word */ 714 | while (text[cursor] && strchr(worddelimiters, text[cursor])) 715 | cursor = nextrune(+1); 716 | while (text[cursor] && !strchr(worddelimiters, text[cursor])) 717 | cursor = nextrune(+1); 718 | } 719 | } 720 | 721 | static void keyrelease(XKeyEvent *ev) { 722 | char buf[32]; 723 | int len; 724 | KeySym ksym; 725 | Status status; 726 | if (!alttab) 727 | return; 728 | if (tabbed) { 729 | tabbed = 0; 730 | return; 731 | } 732 | 733 | if (ev->state & Mod1Mask) { 734 | if (ev->state & ShiftMask) 735 | return; 736 | if (sel->text && sel->text[0] == '>') 737 | return; 738 | puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 739 | if (!(ev->state & ControlMask)) { 740 | cleanup(); 741 | exit(0); 742 | } 743 | if (sel) 744 | sel->out = 1; 745 | } 746 | } 747 | 748 | double easeOutQuint(double t) { return 1 + (--t) * t * t; } 749 | 750 | void animatesel() { 751 | if (!animated || !framecount) 752 | return; 753 | int time; 754 | time = 0; 755 | drw_setscheme(drw, scheme[SchemeSel]); 756 | while (time < framecount) { 757 | // bottom animation 758 | if (sely + lineheight < mh - 10) 759 | drw_rect(drw, 0, sely + (lineheight - 4), mw, 760 | (easeOutQuint(((double)time / framecount)) * 761 | (mh - (lineheight - 4) - sely)), 762 | 1, 1, 0); 763 | // top animation 764 | drw_rect( 765 | drw, 0, 766 | sely + 4 - (easeOutQuint(((double)time / framecount)) * (sely + 4)), 767 | mw, (easeOutQuint(((double)time / framecount)) * sely), 1, 1, 0); 768 | drw_map(drw, win, 0, 0, mw, mh); 769 | time++; 770 | usleep(19000); 771 | } 772 | } 773 | 774 | void spawn(char *cmd) { 775 | char command[1000]; 776 | strcpy(command, cmd); 777 | strcat(command, " &> /dev/null"); 778 | system(command); 779 | exit(0); 780 | } 781 | 782 | void animaterect(int x1, int y1, int w1, int h1, int x2, int y2, int w2, 783 | int h2) { 784 | if (!animated || !framecount) 785 | return; 786 | int time; 787 | time = 0; 788 | double timefactor = 0; 789 | drw_setscheme(drw, scheme[SchemeSel]); 790 | while (time < framecount) { 791 | timefactor = easeOutQuint((double)time / framecount); 792 | drw_rect(drw, x1 + (x2 - x1) * timefactor, y1 + (y2 - y1) * timefactor, 793 | w1 + (w2 - w1) * timefactor, h1 + (h2 - h1) * timefactor, 1, 1, 794 | 0); 795 | drw_map(drw, win, 0, 0, mw, mh); 796 | time++; 797 | usleep(19000); 798 | } 799 | } 800 | 801 | void cmdtrigger(int direction) { 802 | char *tmpcmd; 803 | 804 | animated = 1; 805 | if (direction) { 806 | if (rightcmd) 807 | tmpcmd = rightcmd; 808 | else 809 | tmpcmd = leftcmd; 810 | animaterect(mw + border_width, 0, 0, mh, 0, 0, mw, mh); 811 | } else { 812 | if (leftcmd) 813 | tmpcmd = leftcmd; 814 | else 815 | tmpcmd = rightcmd; 816 | animaterect(0, 0, 0, mh, 0, 0, mw, mh); 817 | } 818 | 819 | cleanup(); 820 | spawn(tmpcmd); 821 | } 822 | 823 | void selectnumber(int number, XKeyEvent *ev, KeySym *sym) { 824 | int i; 825 | sel = curr; 826 | for (i = 0; i < number; ++i) { 827 | if (sel && sel->right && (sel = sel->right) == next) { 828 | curr = next; 829 | calcoffsets(); 830 | } 831 | } 832 | ev->state = ev->state ^ ControlMask; 833 | *sym = XK_Return; 834 | } 835 | 836 | static void keypress(XKeyEvent *ev) { 837 | char buf[64]; 838 | int len; 839 | KeySym ksym = NoSymbol; 840 | Status status; 841 | int i; 842 | struct item *tmpsel; 843 | int offscreen = 0; 844 | 845 | len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); 846 | switch (status) { 847 | default: /* XLookupNone, XBufferOverflow */ 848 | return; 849 | case XLookupChars: /* composed string from input method */ 850 | goto insert; 851 | case XLookupKeySym: 852 | case XLookupBoth: /* a KeySym and a string are returned: use keysym */ 853 | break; 854 | } 855 | 856 | if (ev->state & ControlMask) { 857 | switch (ksym) { 858 | case XK_a: 859 | ksym = XK_Home; 860 | break; 861 | case XK_b: 862 | ksym = XK_Left; 863 | break; 864 | case XK_c: 865 | ksym = XK_Escape; 866 | break; 867 | case XK_d: 868 | ksym = XK_Delete; 869 | break; 870 | case XK_e: 871 | ksym = XK_End; 872 | break; 873 | case XK_f: 874 | ksym = XK_Right; 875 | break; 876 | case XK_g: 877 | ksym = XK_Escape; 878 | break; 879 | case XK_h: 880 | ksym = XK_BackSpace; 881 | break; 882 | case XK_i: 883 | ksym = XK_Tab; 884 | break; 885 | case XK_j: /* fallthrough */ 886 | case XK_J: /* fallthrough */ 887 | case XK_m: /* fallthrough */ 888 | case XK_M: 889 | ksym = XK_Return; 890 | ev->state &= ~ControlMask; 891 | break; 892 | case XK_n: 893 | ksym = XK_Down; 894 | break; 895 | case XK_p: 896 | ksym = XK_Up; 897 | break; 898 | case XK_s: 899 | insert(".*", 2); 900 | break; 901 | 902 | case XK_v: /* paste clipboard */ 903 | XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 904 | utf8, utf8, win, CurrentTime); 905 | drawmenu(); 906 | return; 907 | break; 908 | 909 | case XK_k: /* delete right */ 910 | text[cursor] = '\0'; 911 | match(); 912 | break; 913 | case XK_u: /* delete left */ 914 | insert(NULL, 0 - cursor); 915 | break; 916 | case XK_w: /* delete word */ 917 | while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) 918 | insert(NULL, nextrune(-1) - cursor); 919 | while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) 920 | insert(NULL, nextrune(-1) - cursor); 921 | break; 922 | case XK_y: /* paste selection */ 923 | case XK_Y: 924 | XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 925 | utf8, utf8, win, CurrentTime); 926 | return; 927 | case XK_Left: 928 | case XK_KP_Left: 929 | movewordedge(-1); 930 | goto draw; 931 | case XK_Right: 932 | case XK_KP_Right: 933 | movewordedge(+1); 934 | goto draw; 935 | case XK_Return: 936 | case XK_KP_Enter: 937 | break; 938 | case XK_1: 939 | selectnumber(0, ev, &ksym); 940 | break; 941 | case XK_2: 942 | selectnumber(1, ev, &ksym); 943 | break; 944 | case XK_3: 945 | selectnumber(2, ev, &ksym); 946 | break; 947 | case XK_4: 948 | selectnumber(3, ev, &ksym); 949 | break; 950 | case XK_5: 951 | selectnumber(4, ev, &ksym); 952 | break; 953 | case XK_6: 954 | selectnumber(5, ev, &ksym); 955 | break; 956 | case XK_7: 957 | selectnumber(6, ev, &ksym); 958 | break; 959 | case XK_8: 960 | selectnumber(7, ev, &ksym); 961 | break; 962 | case XK_9: 963 | selectnumber(8, ev, &ksym); 964 | break; 965 | 966 | case XK_bracketleft: 967 | cleanup(); 968 | exit(1); 969 | default: 970 | return; 971 | } 972 | } else if (ev->state & ShiftMask) { 973 | if (alttab) { 974 | if (sel) { 975 | if (sel == items) { 976 | struct item *lastitem; 977 | for (lastitem = items; lastitem && lastitem->right; 978 | lastitem = lastitem->right) 979 | ; 980 | sel = lastitem; 981 | // curr = lastitem; 982 | calcoffsets(); 983 | } else { 984 | if (sel->left && (sel = sel->left)->right == curr) { 985 | curr = prev; 986 | calcoffsets(); 987 | } 988 | } 989 | } 990 | } 991 | } else if (ev->state & Mod1Mask) { 992 | switch (ksym) { 993 | case XK_F4: 994 | cleanup(); 995 | exit(1); 996 | break; 997 | case XK_b: 998 | movewordedge(-1); 999 | goto draw; 1000 | case XK_f: 1001 | movewordedge(+1); 1002 | goto draw; 1003 | case XK_g: 1004 | ksym = XK_Home; 1005 | break; 1006 | case XK_G: 1007 | ksym = XK_End; 1008 | break; 1009 | case XK_h: 1010 | ksym = XK_Up; 1011 | break; 1012 | case XK_j: 1013 | ksym = XK_Next; 1014 | break; 1015 | case XK_k: 1016 | ksym = XK_Prior; 1017 | break; 1018 | case XK_l: 1019 | ksym = XK_Down; 1020 | break; 1021 | case XK_space: 1022 | 1023 | if (alttab) { 1024 | tabbed = 0; 1025 | alttab = 0; 1026 | } 1027 | break; 1028 | case XK_Tab: 1029 | tabbed = 1; 1030 | 1031 | if (sel) { 1032 | 1033 | struct item *lastitem; 1034 | for (lastitem = items; lastitem && lastitem->right; 1035 | lastitem = lastitem->right) 1036 | ; 1037 | 1038 | if (sel == lastitem) { 1039 | sel = items; 1040 | curr = items; 1041 | calcoffsets(); 1042 | } else { 1043 | if (sel->right && (sel = sel->right) == next) { 1044 | curr = next; 1045 | calcoffsets(); 1046 | } 1047 | } 1048 | } 1049 | 1050 | break; 1051 | default: 1052 | return; 1053 | } 1054 | } else if (ev->state & Mod4Mask) { 1055 | if (ksym == XK_q) { 1056 | cleanup(); 1057 | exit(1); 1058 | } 1059 | } 1060 | 1061 | switch (ksym) { 1062 | default: 1063 | insert: 1064 | if (!iscntrl((unsigned char)*buf)) 1065 | insert(buf, len); 1066 | break; 1067 | case XK_Delete: 1068 | case XK_KP_Delete: 1069 | if (text[cursor] == '\0') 1070 | return; 1071 | cursor = nextrune(+1); 1072 | /* fallthrough */ 1073 | case XK_BackSpace: 1074 | if (cursor == 0) 1075 | return; 1076 | insert(NULL, nextrune(-1) - cursor); 1077 | break; 1078 | case XK_End: 1079 | case XK_KP_End: 1080 | if (text[cursor] != '\0') { 1081 | cursor = strlen(text); 1082 | break; 1083 | } 1084 | if (next) { 1085 | /* jump to end of list and position items in reverse */ 1086 | curr = matchend; 1087 | calcoffsets(); 1088 | curr = prev; 1089 | calcoffsets(); 1090 | while (next && (curr = curr->right)) 1091 | calcoffsets(); 1092 | } 1093 | sel = matchend; 1094 | break; 1095 | case XK_Escape: 1096 | cleanup(); 1097 | exit(1); 1098 | case XK_Home: 1099 | case XK_KP_Home: 1100 | if (sel == matches) { 1101 | cursor = 0; 1102 | break; 1103 | } 1104 | sel = curr = matches; 1105 | calcoffsets(); 1106 | break; 1107 | case XK_Left: 1108 | case XK_KP_Left: 1109 | if (columns > 1) { 1110 | if (!sel) 1111 | return; 1112 | tmpsel = sel; 1113 | for (i = 0; i < lines; i++) { 1114 | if (!tmpsel->left || tmpsel->left->right != tmpsel) 1115 | return; 1116 | if (tmpsel == curr) 1117 | offscreen = 1; 1118 | tmpsel = tmpsel->left; 1119 | } 1120 | sel = tmpsel; 1121 | if (offscreen) { 1122 | curr = prev; 1123 | calcoffsets(); 1124 | } 1125 | break; 1126 | } 1127 | 1128 | if ((ev->state & ShiftMask || ev->state & Mod4Mask) && 1129 | (leftcmd || rightcmd)) { 1130 | cmdtrigger(0); 1131 | break; 1132 | } 1133 | 1134 | if (cursor > 0 && (!sel || !sel->left || lines > 0)) { 1135 | cursor = nextrune(-1); 1136 | break; 1137 | } 1138 | if (lines > 0) 1139 | return; 1140 | /* fallthrough */ 1141 | case XK_Up: 1142 | case XK_KP_Up: 1143 | if (sel && sel->left && (sel = sel->left)->right == curr) { 1144 | curr = prev; 1145 | calcoffsets(); 1146 | } 1147 | break; 1148 | case XK_Next: 1149 | case XK_KP_Next: 1150 | if (!next) 1151 | return; 1152 | sel = curr = next; 1153 | calcoffsets(); 1154 | break; 1155 | case XK_Prior: 1156 | case XK_KP_Prior: 1157 | if (!prev) 1158 | return; 1159 | sel = curr = prev; 1160 | calcoffsets(); 1161 | break; 1162 | case XK_Return: 1163 | case XK_KP_Enter: 1164 | // non-selectable comment 1165 | if (sel && sel->text[0] == '>') 1166 | break; 1167 | animatesel(); 1168 | 1169 | puts((sel && !(ev->state & ShiftMask & (!rejectnomatch))) ? sel->text 1170 | : text); 1171 | if (!(ev->state & ControlMask)) { 1172 | cleanup(); 1173 | exit(0); 1174 | } 1175 | if (sel) 1176 | sel->out = 1; 1177 | break; 1178 | case XK_Right: 1179 | case XK_KP_Right: 1180 | if (columns > 1) { 1181 | if (!sel) 1182 | return; 1183 | tmpsel = sel; 1184 | for (i = 0; i < lines; i++) { 1185 | if (!tmpsel->right || tmpsel->right->left != tmpsel) 1186 | return; 1187 | tmpsel = tmpsel->right; 1188 | if (tmpsel == next) 1189 | offscreen = 1; 1190 | } 1191 | sel = tmpsel; 1192 | if (offscreen) { 1193 | curr = next; 1194 | calcoffsets(); 1195 | } 1196 | break; 1197 | } 1198 | 1199 | if ((ev->state & ShiftMask || ev->state & Mod4Mask) && 1200 | (rightcmd || leftcmd)) { 1201 | cmdtrigger(1); 1202 | break; 1203 | } 1204 | if (text[cursor] != '\0') { 1205 | cursor = nextrune(+1); 1206 | break; 1207 | } 1208 | if (lines > 0) 1209 | return; 1210 | /* fallthrough */ 1211 | case XK_Down: 1212 | case XK_KP_Down: 1213 | if (sel && sel->right && (sel = sel->right) == next) { 1214 | curr = next; 1215 | calcoffsets(); 1216 | } 1217 | break; 1218 | case XK_Tab: 1219 | if (!alttab) { 1220 | if (!sel) 1221 | return; 1222 | cursor = strnlen(sel->text, sizeof text - 1); 1223 | memcpy(text, sel->text, cursor); 1224 | text[cursor] = '\0'; 1225 | match(); 1226 | } else { 1227 | tabbed = 1; 1228 | } 1229 | break; 1230 | } 1231 | 1232 | draw: 1233 | drawmenu(); 1234 | } 1235 | 1236 | static void setselection(XEvent *e) { 1237 | struct item *item; 1238 | XMotionEvent *ev = &e->xmotion; 1239 | int x = 0, y = 0, h = bh, w; 1240 | 1241 | if (ev->window != win) 1242 | return; 1243 | 1244 | /* right-click: exit */ 1245 | if (prompt && *prompt) 1246 | x += promptw; 1247 | 1248 | /* input field */ 1249 | w = (lines > 0 || !matches) ? mw - x : inputw; 1250 | 1251 | /* left-click on input: clear input, 1252 | * NOTE: if there is no left-arrow the space for < is reserved so 1253 | * add that to the input width */ 1254 | 1255 | if (lines > 0) { 1256 | w = mw - x; 1257 | // check mouse hover for columns 1258 | if (columns > 0) { 1259 | int i = 0; 1260 | int init = 0; 1261 | int checky = y; 1262 | int checkx = x; 1263 | int colwidth = mw / columns; 1264 | for (item = curr; item != next;) { 1265 | if (i >= lines) { 1266 | i = 0; 1267 | checkx += colwidth; 1268 | checky = y; 1269 | } else { 1270 | if (!init) 1271 | init = 1; 1272 | else 1273 | item = item->right; 1274 | i++; 1275 | checky += h; 1276 | } 1277 | // event in y range 1278 | if (ev->y >= checky && ev->y <= (checky + h) && 1279 | ev->x >= checkx && ev->x <= (checkx + colwidth)) { 1280 | if (sel == item) 1281 | return; 1282 | sel = item; 1283 | if (sel) { 1284 | // sel->out = 1; 1285 | drawmenu(); 1286 | } 1287 | return; 1288 | } 1289 | } 1290 | 1291 | } else { 1292 | /* vertical list: (ctrl)left-click on item */ 1293 | for (item = curr; item != next; item = item->right) { 1294 | y += h; 1295 | if (ev->y >= y && ev->y <= (y + h)) { 1296 | if (sel == item) 1297 | return; 1298 | sel = item; 1299 | if (sel) { 1300 | // sel->out = 1; 1301 | drawmenu(); 1302 | } 1303 | return; 1304 | } 1305 | } 1306 | } 1307 | } else if (matches) { 1308 | /* left-click on left arrow */ 1309 | x += inputw; 1310 | w = TEXTW("<"); 1311 | /* horizontal list: (ctrl)left-click on item */ 1312 | for (item = curr; item != next; item = item->right) { 1313 | x += w; 1314 | w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); 1315 | if (ev->x >= x && ev->x <= x + w) { 1316 | if (sel == item) 1317 | return; 1318 | sel = item; 1319 | if (sel) { 1320 | // sel->out = 1; 1321 | drawmenu(); 1322 | } 1323 | return; 1324 | } 1325 | } 1326 | /* left-click on right arrow */ 1327 | w = TEXTW(">"); 1328 | x = mw - w; 1329 | } 1330 | } 1331 | 1332 | static void buttonpress(XEvent *e) { 1333 | struct item *item; 1334 | XButtonPressedEvent *ev = &e->xbutton; 1335 | int x = 0, y = 0, h = bh, w; 1336 | 1337 | if (ev->window != win) 1338 | return; 1339 | 1340 | /* right-click: exit */ 1341 | if (ev->button == Button3) 1342 | exit(1); 1343 | 1344 | if (prompt && *prompt) 1345 | x += promptw; 1346 | 1347 | /* input field */ 1348 | w = (lines > 0 || !matches) ? mw - x : inputw; 1349 | 1350 | /* left-click on input: clear input, 1351 | * NOTE: if there is no left-arrow the space for < is reserved so 1352 | * add that to the input width */ 1353 | if (ev->button == Button1) { 1354 | if ((lines <= 0 && ev->x >= 0 && 1355 | ev->x <= x + w + ((!prev || !curr->left) ? TEXTW("<") : 0)) || 1356 | (lines > 0 && ev->y >= y && ev->y <= y + h)) { 1357 | if (leftcmd && ev->x < TEXTW("")) { 1358 | cmdtrigger(0); 1359 | } else if (ev->x > mw - TEXTW("")) { 1360 | cmdtrigger(1); 1361 | } else { 1362 | insert(NULL, -cursor); 1363 | drawmenu(); 1364 | } 1365 | return; 1366 | } else { 1367 | if (lines > 0) { 1368 | /* vertical list: (ctrl)left-click on item */ 1369 | w = mw - x; 1370 | item = sel; 1371 | if (sel && sel->text[0] == '>') 1372 | return; 1373 | animatesel(); 1374 | puts(item->text); 1375 | if (!(ev->state & ControlMask)) 1376 | exit(0); 1377 | sel = item; 1378 | if (sel) { 1379 | sel->out = 1; 1380 | drawmenu(); 1381 | } 1382 | return; 1383 | } else if (matches) { 1384 | /* left-click on left arrow */ 1385 | x += inputw; 1386 | w = TEXTW("<"); 1387 | if (prev && curr->left) { 1388 | if (ev->x >= x && ev->x <= x + w) { 1389 | sel = curr = prev; 1390 | calcoffsets(); 1391 | drawmenu(); 1392 | return; 1393 | } 1394 | } 1395 | /* horizontal list: (ctrl)left-click on item */ 1396 | for (item = curr; item != next; item = item->right) { 1397 | x += w; 1398 | w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); 1399 | if (ev->x >= x && ev->x <= x + w) { 1400 | if (sel && item->text[0] == '>') 1401 | break; 1402 | animatesel(); 1403 | puts(item->text); 1404 | if (!(ev->state & ControlMask)) 1405 | exit(0); 1406 | sel = item; 1407 | if (sel) { 1408 | sel->out = 1; 1409 | drawmenu(); 1410 | } 1411 | return; 1412 | } 1413 | } 1414 | /* left-click on right arrow */ 1415 | w = TEXTW(">"); 1416 | x = mw - w; 1417 | if (next && ev->x >= x && ev->x <= x + w) { 1418 | sel = curr = next; 1419 | calcoffsets(); 1420 | drawmenu(); 1421 | return; 1422 | } 1423 | } 1424 | } 1425 | } 1426 | 1427 | /* middle-mouse click: paste selection */ 1428 | if (ev->button == Button2) { 1429 | XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 1430 | utf8, utf8, win, CurrentTime); 1431 | drawmenu(); 1432 | return; 1433 | } 1434 | /* scroll up */ 1435 | if (ev->button == Button4 && prev) { 1436 | sel = curr = prev; 1437 | calcoffsets(); 1438 | drawmenu(); 1439 | return; 1440 | } 1441 | /* scroll down */ 1442 | if (ev->button == Button5 && next) { 1443 | sel = curr = next; 1444 | calcoffsets(); 1445 | drawmenu(); 1446 | return; 1447 | } 1448 | } 1449 | 1450 | static void paste(void) { 1451 | char *p, *q; 1452 | int di; 1453 | unsigned long dl; 1454 | Atom da; 1455 | 1456 | /* we have been given the current selection, now insert it into input */ 1457 | if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, 1458 | utf8, &da, &di, &dl, &dl, 1459 | (unsigned char **)&p) == Success && 1460 | p) { 1461 | insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); 1462 | XFree(p); 1463 | } 1464 | drawmenu(); 1465 | } 1466 | 1467 | static void readstdin(void) { 1468 | char *line = NULL; 1469 | size_t i, itemsiz = 0, linesiz = 0; 1470 | ssize_t len; 1471 | 1472 | if (passwd || inputonly) { 1473 | inputw = lines = 0; 1474 | return; 1475 | } 1476 | 1477 | /* read each line from stdin and add it to the item list */ 1478 | for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { 1479 | if (i + 1 >= itemsiz) { 1480 | itemsiz += 256; 1481 | if (!(items = realloc(items, itemsiz * sizeof(*items)))) 1482 | die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); 1483 | } 1484 | if (line[len - 1] == '\n' || line[len - 1] == '\t') 1485 | line[len - 1] = '\0'; 1486 | if (!(items[i].text = strdup(line)) || !(items[i].stext = strdup(line))) 1487 | die("strdup:"); 1488 | items[i].out = 0; 1489 | } 1490 | free(line); 1491 | if (items) 1492 | items[i].text = NULL; 1493 | lines = MIN(lines, i / columns + (i % columns != 0)); 1494 | if (columns != 1) 1495 | columns = MIN(i / lines + (i % lines != 0), columns); 1496 | } 1497 | 1498 | static void run(void) { 1499 | XEvent ev; 1500 | Time lasttime = 0; 1501 | int i; 1502 | 1503 | if (toast) { 1504 | drawmenu(); 1505 | usleep(toast * 100000); 1506 | exit(0); 1507 | } 1508 | 1509 | while (!XNextEvent(dpy, &ev)) { 1510 | if (preselected) { 1511 | for (i = 0; i < preselected; i++) { 1512 | if (sel && sel->right && (sel = sel->right) == next) { 1513 | curr = next; 1514 | calcoffsets(); 1515 | } 1516 | } 1517 | drawmenu(); 1518 | preselected = 0; 1519 | } 1520 | 1521 | if (XFilterEvent(&ev, win)) 1522 | continue; 1523 | switch (ev.type) { 1524 | case MotionNotify: 1525 | if ((ev.xmotion.time - lasttime) <= (1000 / 60)) 1526 | continue; 1527 | lasttime = ev.xmotion.time; 1528 | setselection(&ev); 1529 | break; 1530 | case DestroyNotify: 1531 | if (ev.xdestroywindow.window != win) 1532 | break; 1533 | cleanup(); 1534 | exit(1); 1535 | case ButtonPress: 1536 | buttonpress(&ev); 1537 | break; 1538 | case Expose: 1539 | if (ev.xexpose.count == 0) 1540 | drw_map(drw, win, 0, 0, mw, mh); 1541 | break; 1542 | case FocusIn: 1543 | /* regrab focus from parent window */ 1544 | if (ev.xfocus.window != win) 1545 | grabfocus(); 1546 | break; 1547 | case KeyPress: 1548 | keypress(&ev.xkey); 1549 | break; 1550 | case KeyRelease: 1551 | keyrelease(&ev.xkey); 1552 | break; 1553 | case SelectionNotify: 1554 | if (ev.xselection.property == utf8) 1555 | paste(); 1556 | break; 1557 | case VisibilityNotify: 1558 | if (ev.xvisibility.state != VisibilityUnobscured) 1559 | XRaiseWindow(dpy, win); 1560 | break; 1561 | } 1562 | } 1563 | } 1564 | 1565 | static void setup(void) { 1566 | int x, y, i, j; 1567 | unsigned int du; 1568 | XSetWindowAttributes swa; 1569 | XIM xim; 1570 | Window w, dw, *dws; 1571 | XWindowAttributes wa; 1572 | 1573 | char wmclass[20]; 1574 | 1575 | if (!managed) 1576 | strcpy(wmclass, "dmenu"); 1577 | else 1578 | strcpy(wmclass, "floatmenu"); 1579 | 1580 | XClassHint ch = {wmclass, wmclass}; 1581 | 1582 | #ifdef XINERAMA 1583 | XineramaScreenInfo *info; 1584 | Window pw; 1585 | int a, di, n, area = 0; 1586 | #endif 1587 | /* init appearance */ 1588 | for (j = 0; j < SchemeLast; j++) { 1589 | scheme[j] = drw_scm_create(drw, (const char **)colors[j], 3); 1590 | } 1591 | for (j = 0; j < SchemeOut; ++j) { 1592 | for (i = 0; i < ColLast; ++i) { 1593 | if (colortemp[j][i]) 1594 | free(colors[j] 1595 | [i]); // only free if overwritten with new colortemp 1596 | } 1597 | } 1598 | 1599 | clip = XInternAtom(dpy, "CLIPBOARD", False); 1600 | utf8 = XInternAtom(dpy, "UTF8_STRING", False); 1601 | 1602 | /* calculate menu geometry */ 1603 | bh = drw->fonts->h + 12; 1604 | bh = MAX(bh, lineheight); /* make a menu line AT LEAST 'lineheight' tall */ 1605 | 1606 | lines = MAX(lines, 0); 1607 | mh = (lines + 1) * bh; 1608 | promptw = commented ? bh * 15 1609 | : (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 1610 | : 0; 1611 | 1612 | #ifdef XINERAMA 1613 | i = 0; 1614 | if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { 1615 | XGetInputFocus(dpy, &w, &di); 1616 | if (mon >= 0 && mon < n) 1617 | i = mon; 1618 | else if (w != root && w != PointerRoot && w != None) { 1619 | /* find top-level window containing current input focus */ 1620 | do { 1621 | if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) 1622 | XFree(dws); 1623 | } while (w != root && w != pw); 1624 | /* find xinerama screen with which the window intersects most */ 1625 | if (XGetWindowAttributes(dpy, pw, &wa)) 1626 | for (j = 0; j < n; j++) 1627 | if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, 1628 | info[j])) > area) { 1629 | area = a; 1630 | i = j; 1631 | } 1632 | } 1633 | /* no focused window is on screen, so use pointer location instead */ 1634 | if (mon < 0 && !area && 1635 | XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) 1636 | for (i = 0; i < n; i++) 1637 | if (INTERSECT(x, y, 1, 1, info[i]) != 0) 1638 | break; 1639 | if (centered) { 1640 | if (dmw && dmw < info[i].width && info[i].width) 1641 | mw = dmw; 1642 | else 1643 | mw = info[i].width - 100; 1644 | 1645 | while ((lines + 1) * bh > info[i].height) { 1646 | lines--; 1647 | } 1648 | 1649 | mh = (lines + 1) * bh; 1650 | x = info[i].x_org + ((info[i].width - mw) / 2); 1651 | y = info[i].y_org + ((info[i].height - mh) / 2); 1652 | 1653 | if (y < 0) 1654 | y = 0; 1655 | 1656 | } else if (followcursor) { 1657 | if (dmw) 1658 | mw = dmw; 1659 | else 1660 | mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); 1661 | getrootptr(&x, &y); 1662 | if (x > info[i].x_org + (drw->w - info[i].x_org) / 2) { 1663 | x = x - mw + 20; 1664 | } else { 1665 | x = x - 20; 1666 | } 1667 | if (y > info[i].y_org + (drw->h - info[i].y_org) / 2) { 1668 | y = y - mh + 20; 1669 | } else { 1670 | y = y - 20; 1671 | } 1672 | 1673 | if (x < 0) 1674 | x = 0; 1675 | if (y < 0) 1676 | y = 0; 1677 | 1678 | } else { 1679 | if (dmy <= -1) { 1680 | if (dmy == -1) 1681 | dmy = (info[i].height - mh) / 2; 1682 | else 1683 | dmy = drw->fonts->h * 1.55; 1684 | } 1685 | mw = ((dmw > 0 && dmw < info[i].width) ? dmw : info[i].width); 1686 | if (dmx == -1) 1687 | dmx = (info[i].width - mw) / 2; 1688 | x = rightxoffset ? info[i].x_org + info[i].width - dmx - mw - 1689 | 2 * border_width 1690 | : info[i].x_org + dmx; 1691 | y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy); 1692 | } 1693 | 1694 | if (mh > drw->h - 10) { 1695 | mh = drw->h - border_width * 2 - 10; 1696 | lines = (drw->h / (lineheight ? lineheight : bh)) - 1; 1697 | } 1698 | 1699 | if (mw > drw->w - 10) { 1700 | mw = drw->w - border_width * 2; 1701 | } 1702 | 1703 | if (x < info[i].x_org) 1704 | x = info[i].x_org; 1705 | if (x + mw > info[i].x_org + info[i].width) 1706 | x = info[i].x_org + info[i].width - mw - border_width * 2; 1707 | if (fullheight) { 1708 | y = info[i].y_org + 32; 1709 | mh = drw->h - border_width * 2 - (drw->h - info[i].height + 32); 1710 | lines = (drw->h / lineheight) - 2; 1711 | } else { 1712 | if (y + mh > drw->h) 1713 | y = drw->h - mh; 1714 | } 1715 | XFree(info); 1716 | } else 1717 | #endif 1718 | { 1719 | if (!XGetWindowAttributes(dpy, parentwin, &wa)) 1720 | die("could not get embedding window attributes: 0x%lx", parentwin); 1721 | if (centered) { 1722 | mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); 1723 | x = (wa.width - mw) / 2; 1724 | y = (wa.height - mh) / 2; 1725 | } else if (followcursor) { 1726 | getrootptr(&x, &y); 1727 | if (x > drw->w / 2) { 1728 | x = x - mw; 1729 | } 1730 | if (y > drw->h / 2) { 1731 | y = y - mh; 1732 | } 1733 | mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); 1734 | } else { 1735 | x = dmx; 1736 | y = topbar ? dmy : wa.height - mh - dmy; 1737 | mw = ((dmw > 0 && dmw < wa.width) ? dmw : wa.width); 1738 | } 1739 | } 1740 | 1741 | inputw = mw / (commented ? 10 : 3); /* input width: ~33% of monitor width */ 1742 | match(); 1743 | if (prematch && matches && strlen(text) > 0) { 1744 | struct item *tmpmatch; 1745 | struct item *item; 1746 | tmpmatch = matches; 1747 | insert(NULL, 0 - cursor); 1748 | sel = tmpmatch; 1749 | if (next) { 1750 | for (item = next; item->right; item = item->right) { 1751 | if (item == sel) { 1752 | curr = sel; 1753 | break; 1754 | } 1755 | } 1756 | } 1757 | calcoffsets(); 1758 | prematch = 0; 1759 | } 1760 | 1761 | /* create menu window */ 1762 | swa.override_redirect = managed ? False : True; 1763 | swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; 1764 | swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | 1765 | VisibilityChangeMask | ButtonPressMask | PointerMotionMask; 1766 | ; 1767 | win = XCreateWindow(dpy, root, x, y, mw, mh, border_width, 1768 | DefaultDepth(dpy, screen), CopyFromParent, 1769 | DefaultVisual(dpy, screen), 1770 | CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); 1771 | XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); 1772 | 1773 | XSetClassHint(dpy, win, &ch); 1774 | 1775 | if (managed) 1776 | XStoreName(dpy, win, searchtext ? searchtext : "menu"); 1777 | 1778 | /* input methods */ 1779 | if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) 1780 | die("XOpenIM failed: could not open input device"); 1781 | 1782 | xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 1783 | XNClientWindow, win, XNFocusWindow, win, NULL); 1784 | 1785 | XMapRaised(dpy, win); 1786 | if (embed) { 1787 | XReparentWindow(dpy, win, parentwin, x, y); 1788 | XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); 1789 | if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { 1790 | for (i = 0; i < du && dws[i] != win; ++i) 1791 | XSelectInput(dpy, dws[i], FocusChangeMask); 1792 | XFree(dws); 1793 | } 1794 | grabfocus(); 1795 | } 1796 | drw_resize(drw, mw, mh); 1797 | drawmenu(); 1798 | } 1799 | 1800 | static void usage(void) { 1801 | die("usage: instantmenu [-bfirnPv] [-l lines] [-g columns] [-p prompt] [-m " 1802 | "monitor]\n" 1803 | " [-x xoffset] [-xr right xoffset] [-y yoffset] [-w " 1804 | "width]\n" 1805 | " [-h height] [-fn font]\n" 1806 | 1807 | " [-nb color] [-nf color] [-sb color] [-sf color] [-w " 1808 | "windowid]\n"); 1809 | } 1810 | 1811 | void readxresources(void) { 1812 | XrmInitialize(); 1813 | 1814 | char *xrm; 1815 | if ((xrm = XResourceManagerString(drw->dpy))) { 1816 | char *type; 1817 | XrmDatabase xdb = XrmGetStringDatabase(xrm); 1818 | XrmValue xval; 1819 | 1820 | char xresfont[20] = ""; 1821 | snprintf(xresfont, sizeof(xresfont), "%s.font", xresname); 1822 | if (XrmGetResource(xdb, xresfont, "*", &type, &xval)) 1823 | fonts[0] = strdup(xval.addr); // overwrite fonts[0] 1824 | 1825 | for (int i = 0; i < SchemeLast; ++i) { 1826 | for (int j = 0; j < ColLast; ++j) { 1827 | char xresprop[100] = ""; 1828 | snprintf(xresprop, sizeof(xresprop), "%s.%s.%s", xresname, 1829 | xresscheme[i], xrescolortype[j]); 1830 | 1831 | if (XrmGetResource(xdb, xresprop, "*", &type, &xval)) 1832 | colors[i][j] = strdup(xval.addr); 1833 | else 1834 | colors[i][j] = strdup(colors[i][j]); 1835 | } 1836 | } 1837 | 1838 | XrmDestroyDatabase(xdb); 1839 | } 1840 | } 1841 | 1842 | int main(int argc, char *argv[]) { 1843 | XWindowAttributes wa; 1844 | int i, fast = 0; 1845 | 1846 | for (i = 1; i < argc; i++) 1847 | /* these options take no arguments */ 1848 | if (!strcmp(argv[i], "-v")) { /* prints version information */ 1849 | puts("instantmenu-" VERSION); 1850 | exit(0); 1851 | } else if (!strcmp(argv[i], 1852 | "-b")) /* appears at the bottom of the screen */ 1853 | topbar = 0; 1854 | else if (!strcmp(argv[i], 1855 | "-r")) /* reject input if it results in no matches */ 1856 | rejectnomatch = 1; 1857 | else if (!strcmp(argv[i], 1858 | "-f")) /* grabs keyboard before reading stdin */ 1859 | fast = 1; 1860 | else if (!strcmp(argv[i], "-T")) /* launch instantmenu in a toast mode 1861 | that times out after a while */ 1862 | toast = atoi(argv[++i]); 1863 | else if (!strcmp(argv[i], "-ct")) { /* activate instantASSIST mode */ 1864 | commented = 1; 1865 | static char commentprompt[200]; 1866 | prompt = commentprompt + 1; 1867 | strcpy(prompt, "prompts"); 1868 | } else if (!strcmp(argv[i], "-c")) /* centers instantmenu on screen */ 1869 | centered = 1; 1870 | else if (!strcmp(argv[i], "-C")) /* go to mouse position */ 1871 | followcursor = 1; 1872 | else if (!strcmp(argv[i], "-S")) /* confirm using the space key TODO 1873 | actually make that work*/ 1874 | spaceconfirm = 1; 1875 | else if (!strcmp(argv[i], "-I")) /* input only */ 1876 | inputonly = 1; 1877 | else if (!strcmp(argv[i], "-s")) { /* enable smart case */ 1878 | smartcase = 1; 1879 | fstrncmp = strncasecmp; 1880 | fstrstr = cistrstr; 1881 | } else if (!strcmp(argv[i], "-F")) /* disables fuzzy matching */ 1882 | /* disables fuzzy matching */ 1883 | fuzzy = 0; 1884 | else if (!strcmp(argv[i], "-pm")) /* enables pre matching */ 1885 | prematch = 1; 1886 | else if (!strcmp(argv[i], "-E")) { /* enables exact matching */ 1887 | exact = 1; 1888 | fuzzy = 0; 1889 | } else if (!strcmp(argv[i], "-H")) { /* makes instantmenu take the full 1890 | screen height */ 1891 | fullheight = 1; 1892 | } else if (!strcmp(argv[i], 1893 | "-i")) { /* case-insensitive item matching */ 1894 | fstrncmp = strncasecmp; 1895 | fstrstr = cistrstr; 1896 | } else if (!strcmp(argv[i], "-n")) { /* instant select only match */ 1897 | instant = 1; 1898 | } else if (!strcmp(argv[i], "-P")) /* display input as dots */ 1899 | passwd = 1; 1900 | else if (!strcmp(argv[i], "-M")) /* set Monospace font */ 1901 | tempfont = "Fira Code Nerd Font:pixelsize=15"; 1902 | else if (!strcmp(argv[i], "-G")) /* don't grab the keyboard */ 1903 | nograb = 1; 1904 | else if (!strcmp(argv[i], "-A")) /* alt-tab behaviour */ 1905 | alttab = 1; 1906 | else if (!strcmp(argv[i], "-wm")) /* display as managed wm window */ 1907 | managed = 1; 1908 | else if (i + 1 == argc) 1909 | usage(); 1910 | else if (!strcmp(argv[i], 1911 | "-rc")) /* executes command on shift + right arrow */ 1912 | rightcmd = argv[++i]; 1913 | else if (!strcmp(argv[i], 1914 | "-lc")) /* adds prompt to left of input field */ 1915 | leftcmd = argv[++i]; 1916 | /* these options take one argument */ 1917 | else if (!strcmp(argv[i], "-g")) { /* number of columns in grid */ 1918 | columns = atoi(argv[++i]); 1919 | if (columns == 0) 1920 | columns = 1; 1921 | if (lines == 0) 1922 | lines = 1; 1923 | } else if (!strcmp(argv[i], 1924 | "-l")) /* number of lines in vertical list */ 1925 | lines = atoi(argv[++i]); 1926 | else if (!strcmp(argv[i], "-x")) /* window x offset */ 1927 | dmx = atoi(argv[++i]); 1928 | else if (!strcmp(argv[i], "-xr")) { /* window x offset from the right 1929 | side of the screen */ 1930 | rightxoffset = 1; 1931 | dmx = atoi(argv[++i]); 1932 | } else if (!strcmp(argv[i], 1933 | "-y")) /* window y offset (from bottom up if -b) */ 1934 | dmy = atoi(argv[++i]); 1935 | else if (!strcmp(argv[i], "-w")) /* make instantmenu this wide */ 1936 | dmw = atoi(argv[++i]); 1937 | else if (!strcmp(argv[i], "-m")) // select monitor 1938 | mon = atoi(argv[++i]); 1939 | else if (!strcmp(argv[i], 1940 | "-p")) /* adds prompt to left of input field */ 1941 | prompt = argv[++i]; 1942 | else if (!strcmp(argv[i], 1943 | "-q")) /* adds prompt inside of the input field */ 1944 | searchtext = argv[++i]; 1945 | else if (!strcmp(argv[i], "-fn")) /* font or font set */ 1946 | tempfont = argv[++i]; 1947 | else if (!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ 1948 | if (!fullheight) { 1949 | lineheight = atoi(argv[++i]); 1950 | lineheight = 1951 | MAX(lineheight, 8); /* reasonable default in case of value 1952 | too small/negative */ 1953 | } 1954 | } else if (!strcmp(argv[i], "-a")) /* animation duration */ 1955 | framecount = atoi(argv[++i]); 1956 | else if (!strcmp(argv[i], "-nb")) /* normal background color */ 1957 | colortemp[SchemeNorm][ColBg] = argv[++i]; 1958 | else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ 1959 | colortemp[SchemeNorm][ColFg] = argv[++i]; 1960 | else if (!strcmp(argv[i], "-sb")) /* selected background color */ 1961 | colortemp[SchemeSel][ColBg] = argv[++i]; 1962 | else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ 1963 | colortemp[SchemeSel][ColFg] = argv[++i]; 1964 | else if (!strcmp(argv[i], "-W")) /* embedding window id */ 1965 | embed = argv[++i]; 1966 | else if (!strcmp(argv[i], "-bw")) 1967 | border_width = atoi(argv[++i]); /* border width */ 1968 | else if (!strcmp(argv[i], "-ps")) { 1969 | /* preselected item */ 1970 | if (*argv[i + 1] == '-') { 1971 | preselected = atoi(argv[++i] + 1); 1972 | } else { 1973 | preselected = atoi(argv[++i]); 1974 | } 1975 | } else if (!strcmp(argv[i], "-it")) { /* initial input text */ 1976 | int tmpnopatch = rejectnomatch; 1977 | rejectnomatch = 0; 1978 | const char *text = argv[++i]; 1979 | insert(text, strlen(text)); 1980 | rejectnomatch = tmpnopatch; 1981 | } else 1982 | usage(); 1983 | 1984 | if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 1985 | fputs("warning: no locale support\n", stderr); 1986 | if (!(dpy = XOpenDisplay(NULL))) 1987 | die("cannot open display"); 1988 | screen = DefaultScreen(dpy); 1989 | root = RootWindow(dpy, screen); 1990 | if (!embed || !(parentwin = strtol(embed, NULL, 0))) 1991 | parentwin = root; 1992 | if (!XGetWindowAttributes(dpy, parentwin, &wa)) 1993 | die("could not get embedding window attributes: 0x%lx", parentwin); 1994 | drw = drw_create(dpy, screen, root, wa.width, wa.height); 1995 | readxresources(); 1996 | /* Now we check whether to override xresources with commandline parameters 1997 | */ 1998 | if (tempfont) 1999 | fonts[0] = strdup(tempfont); 2000 | for (int scheme = 0; scheme < SchemeLast; ++scheme) { 2001 | for (int col = 0; col < ColLast; ++col) { 2002 | if (colortemp[scheme][col]) 2003 | colors[scheme][col] = strdup(colortemp[scheme][col]); 2004 | } 2005 | } 2006 | 2007 | if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) 2008 | die("no fonts could be loaded."); 2009 | 2010 | if (tempfont) 2011 | free(fonts[0]); 2012 | 2013 | lrpad = drw->fonts->h; 2014 | 2015 | if (fullheight || lineheight == -1) 2016 | lineheight = drw->fonts->h * 2.5; 2017 | 2018 | if (prompt && dmw && TEXTW(prompt) + 100 > dmw && dmw < mw - 300) 2019 | dmw += TEXTW(prompt); 2020 | 2021 | #ifdef __OpenBSD__ 2022 | if (pledge("stdio rpath", NULL) == -1) 2023 | die("pledge"); 2024 | #endif 2025 | 2026 | if (fast && !isatty(0)) { 2027 | grabkeyboard(); 2028 | readstdin(); 2029 | } else { 2030 | readstdin(); 2031 | grabkeyboard(); 2032 | } 2033 | 2034 | if (dmw <= -1) { 2035 | int maxw = 2036 | max_textw() * 1.3 * MAX(columns, 1) + (prompt ? TEXTW(prompt) : 0); 2037 | if (dmw * (-1) > maxw) { 2038 | dmw = dmw * (-1); 2039 | } else { 2040 | dmw = maxw; 2041 | } 2042 | } 2043 | setup(); 2044 | run(); 2045 | 2046 | return 1; /* unreachable */ 2047 | } 2048 | --------------------------------------------------------------------------------