├── .hgtags ├── LICENSE ├── Makefile ├── README.md ├── config.mk ├── dmenu.1 ├── dmenu.c ├── dmenu_path.c ├── dmenu_run ├── draw.c └── draw.h /.hgtags: -------------------------------------------------------------------------------- 1 | fcc8a282cb52c6a9343b461026b386825590cd31 0.1 2 | 656be0f47df545dfdd2e1e0663663b8b1b26f031 0.2 3 | d352e9dc112ee96aa5cad961a0ed880ae9ce7276 0.3 4 | 7acf0dde1120542917bae12e0e42293f9d2cc899 0.4 5 | 4a0ecd881c4fc15de4a0bebd79308b064be020ef 0.5 6 | 25f679fb19686140a907684ffcb423b9e9d44b53 0.6 7 | 5fc20d7158bd16b4d5f8d1c25e177680b6d54252 0.7 8 | 409667a57221f7e50ba8b5248f638915cd61b366 0.8 9 | d046c818ea467555cc338751c9bf3024609f1f12 0.9 10 | 9e11140d4cc3eecac3b0ab752f91528fd5e04be8 1.0 11 | e8c1e9733752db12f2dbd1fa93c46f5806242ba9 1.1 12 | bee7fe6d1189174d0204ca3195b83cdc1bb4f82e 1.2 13 | 2eb9997be51cb1b11a8900728ccc0904f9371157 1.3 14 | df3fbb050004c544d14e43c36f6a94cca6ed4a69 1.4 15 | e071fb045bd9e8574947acff7196360bc0270e68 1.5 16 | dcc5427f99f51a978386a0dd770467cd911ac84b 1.6 17 | 58dbef4aef3d45c7a3da6945e53c9667c0f02d5b 1.7 18 | 3696d77aaf02f5d15728dde3b9e35abcaf291496 1.7.1 19 | d3e6fa22ae45b38b1bdb0d813390365e5930360b 1.8 20 | c7f5f4d543170f03d70468e98a3a0ec8d2c4161b 1.9 21 | 1fce5c464fcd870b9f024aa1853d5cf3a3eb371b 2.0 22 | 7656557298c954469a6a9564e6649b1fb5db663e 2.1 23 | 90f0e34e7f118c9ad3227a1606211ee825942b1c 2.2 24 | b6e09682c8adcb6569656bee73c311f9ab457715 2.3 25 | 9e9036cbfb4b7306c6fb366249e81dc0e65bdfde 2.4 26 | 03e83e2788c83ddd63b45a667939d7ec783c98cb 2.4.1 27 | 1ca5d430524e838c52ede912533cb90108c5cd66 2.4.2 28 | 041143e9fc544c62edc58af52cae9ac5237e5945 2.5 29 | 775f761a5647a05038e091d1c99fc35d3034cd68 2.6 30 | fbd9e9d63f202afe6834ccfdf890904f1897ec0b 2.7 31 | dd3d02b07cac44fbafc074a361c1002cebe7aae4 2.8 32 | 59b3024854db49739c6d237fa9077f04a2da847a 3.0 33 | 8f0f917ac988164e1b4446236e3a6ab6cfcb8c67 3.1 34 | e4c81a78ffbad6ba4d1ad119cc654da6eca63a4c 3.2 35 | 709df5a4bad7015a346b2b44b1b3b573ea3088ff 3.3 36 | 9ab649b3b3e5bfccf1c8f352c59e5361e070a25f 3.4 37 | 05e5bd706b3b3e61399d57c4bb43df296a20112d 3.5 38 | 0bc2751d06e8b95e0138854c7815e154c5c3d990 3.6 39 | 0508a3a6ee106f36d9b8ff07bb5b28584edfa89c 3.7 40 | 644b0798fcccd570fd519899e1601c6857496b91 3.8 41 | 21a1ed9a69b9541a355758a57103e294fb722c33 3.9 42 | 78f9f72cc9c6bdb022ff8908486b61ef5e242aad 4.0 43 | 844587572673cf6326c3f61737264a46b728fc0a 4.1 44 | 72749a826cab0baa805620e44a22e54486c97a4e 4.1.1 45 | 379813a051f03a1b20bdbfdc2d2d1d2d794ace48 4.2 46 | abb6579a324fffdf6a23c2fa4c32911277da594a 4.2.1 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2012 Steve Engledow 4 | © 2010 Connor Lane Smith 5 | © 2006-2010 Anselm R Garbe 6 | © 2009 Gottox 7 | © 2009 Markus Schnalke 8 | © 2009 Evan Gates 9 | © 2006-2008 Sander van Dijk 10 | © 2006-2007 Michał Janeczek 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a 13 | copy of this software and associated documentation files (the "Software"), 14 | to deal in the Software without restriction, including without limitation 15 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 16 | and/or sell copies of the Software, and to permit persons to whom the 17 | Software is furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in 20 | all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 25 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 28 | DEALINGS IN THE SOFTWARE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # dmenu - dynamic menu 2 | # See LICENSE file for copyright and license details. 3 | 4 | include config.mk 5 | 6 | all: options dmenu dmenu_path 7 | 8 | options: 9 | @echo dmenu build options: 10 | @echo "CFLAGS = ${CFLAGS}" 11 | @echo "LDFLAGS = ${LDFLAGS}" 12 | @echo "CC = ${CC}" 13 | 14 | dmenu: dmenu.o draw.o 15 | dmenu_path: dmenu_path.o 16 | 17 | .c.o: config.mk 18 | @echo CC -c $< 19 | @${CC} -c $< ${CFLAGS} 20 | 21 | dmenu dmenu_path: 22 | @echo CC -o $@ 23 | @${CC} -o $@ $+ ${LDFLAGS} 24 | 25 | clean: 26 | @echo cleaning 27 | @rm -f dmenu dmenu.o draw.o dmenu_path dmenu_path.o dmenu-${VERSION}.tar.gz 28 | 29 | dist: clean 30 | @echo creating dist tarball 31 | @mkdir -p dmenu-${VERSION} 32 | @cp LICENSE Makefile README config.mk dmenu.1 dmenu.c draw.c draw.h dmenu_path.c dmenu_run dmenu-${VERSION} 33 | @tar -cf dmenu-${VERSION}.tar dmenu-${VERSION} 34 | @gzip dmenu-${VERSION}.tar 35 | @rm -rf dmenu-${VERSION} 36 | 37 | install: all 38 | @echo installing executables to ${DESTDIR}${PREFIX}/bin 39 | @mkdir -p ${DESTDIR}${PREFIX}/bin 40 | @cp -f dmenu dmenu_path dmenu_run ${DESTDIR}${PREFIX}/bin 41 | @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu 42 | @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_path 43 | @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run 44 | @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 45 | @mkdir -p ${DESTDIR}${MANPREFIX}/man1 46 | @sed "s/VERSION/${VERSION}/g" < dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/dmenu.1 47 | @chmod 644 ${DESTDIR}${MANPREFIX}/man1/dmenu.1 48 | 49 | uninstall: 50 | @echo removing executables from ${DESTDIR}${PREFIX}/bin 51 | @rm -f ${DESTDIR}${PREFIX}/bin/dmenu 52 | @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_path 53 | @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run 54 | @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 55 | @rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1 56 | 57 | .PHONY: all options clean dist install uninstall 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dmenu - dynamic menu 2 | ==================== 3 | dmenu is an efficient dynamic menu for X. 4 | 5 | 6 | Requirements 7 | ------------ 8 | In order to build dmenu you need the Xlib header files. 9 | 10 | 11 | Installation 12 | ------------ 13 | Edit config.mk to match your local setup (dmenu is installed into 14 | the /usr/local namespace by default). 15 | 16 | Afterwards enter the following command to build and install dmenu 17 | (if necessary as root): 18 | 19 | make clean install 20 | 21 | 22 | Running dmenu 23 | ------------- 24 | See the man page for details. 25 | 26 | Usage: dmenu [OPTION]... 27 | 28 | Display newline-separated input stdin as a menubar 29 | 30 | -e, --echo display text from stdin with no user 31 | interaction 32 | -ec, --echo-centre same as -e but align text centrally 33 | -er, --echo-right same as -e but align text right 34 | -et, --echo-timeout SECS close the message after SEC seconds 35 | when using -e, -ec, or -er 36 | -b, --bottom dmenu appears at the bottom of the screen 37 | -h, --height N set dmenu to be N pixels high 38 | -i, --insensitive dmenu matches menu items case insensitively 39 | -l, --lines LINES dmenu lists items vertically, within the 40 | given number of lines 41 | -m, --monitor MONITOR dmenu appears on the given Xinerama screen 42 | -p, --prompt PROMPT prompt to be displayed to the left of the 43 | input field 44 | -po, --prompt-only PROMPT same as -p but don't wait for stdin 45 | useful for a prompt with no menu 46 | -r, --return-early return as soon as a single match is found 47 | -fn, --font-name FONT font or font set to be used 48 | -nb, --normal-background COLOR normal background color 49 | #RGB, #RRGGBB, and color names supported 50 | -nf, --normal-foreground COLOR normal foreground color 51 | -sb, --selected-background COLOR selected background color 52 | -sf, --selected-foreground COLOR selected foreground color 53 | -v, --version display version information 54 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # dmenu version 2 | VERSION = 4.2.1 3 | 4 | # Customize below to fit your system 5 | 6 | # paths 7 | PREFIX = /usr/local 8 | MANPREFIX = ${PREFIX}/share/man 9 | 10 | X11INC = /usr/X11R6/include 11 | X11LIB = /usr/X11R6/lib 12 | 13 | # Xinerama, comment if you don't want it 14 | XINERAMALIBS = -lXinerama 15 | XINERAMAFLAGS = -DXINERAMA 16 | 17 | # includes and libs 18 | INCS = -I${X11INC} 19 | LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} 20 | 21 | # flags 22 | CPPFLAGS = -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} 23 | CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} 24 | LDFLAGS = -s ${LIBS} 25 | 26 | # compiler and linker 27 | CC = cc 28 | -------------------------------------------------------------------------------- /dmenu.1: -------------------------------------------------------------------------------- 1 | .TH DMENU 1 dmenu\-VERSION 2 | .SH NAME 3 | dmenu \- dynamic menu 4 | .SH SYNOPSIS 5 | .B dmenu 6 | .RB [ \-b ] 7 | .RB [ \-i ] 8 | .RB [ \-l 9 | .IR lines ] 10 | .RB [ \-m 11 | .IR monitor ] 12 | .RB [ \-p 13 | .IR prompt ] 14 | .RB [ \-fn 15 | .IR font ] 16 | .RB [ \-nb 17 | .IR color ] 18 | .RB [ \-nf 19 | .IR color ] 20 | .RB [ \-sb 21 | .IR color ] 22 | .RB [ \-sf 23 | .IR color ] 24 | .RB [ \-v ] 25 | .P 26 | .BR dmenu_run " ..." 27 | .P 28 | .B dmenu_path 29 | .SH DESCRIPTION 30 | .B dmenu 31 | is a dynamic menu for X, originally designed for 32 | .BR dwm (1). 33 | It manages huge numbers of user-defined menu items efficiently. 34 | .P 35 | dmenu reads a list of newline-separated items from standard input and creates a 36 | menu (or displays a notification if using -e). When the user selects an item or 37 | enters any text and presses Return, their choice is printed to standard output 38 | and dmenu terminates. 39 | .P 40 | .B dmenu_run 41 | is a dmenu script used by dwm which lists programs in the user's PATH and 42 | executes the selected item. 43 | .P 44 | .B dmenu_path 45 | is a program used by dmenu_run to find and cache a list of executables. 46 | .SH OPTIONS 47 | .B \-e 48 | dmenu displays text from stdin with no user interaction. 49 | .TP 50 | .B \-ec 51 | same as -e but text is aligned centrally. 52 | .TP 53 | .B \-er 54 | same as -e but text is aligned to the right. 55 | .TP 56 | .BI \-et " secs " 57 | when using -e, close the message after the given number of seconds 58 | .TP 59 | .B \-b 60 | dmenu appears at the bottom of the screen. 61 | .TP 62 | .BI \-h " height" 63 | dmenu is drawn at least the given number of pixels high. 64 | .TP 65 | .B \-i 66 | dmenu matches menu items case insensitively. 67 | .TP 68 | .BI \-l " lines" 69 | dmenu lists items vertically, within the given number of lines. 70 | .TP 71 | .BI \-m " monitor" 72 | dmenu appears on the given Xinerama screen. 73 | .TP 74 | .BI \-p " prompt" 75 | defines the prompt to be displayed to the left of the input field. 76 | .TP 77 | .BI \-po " prompt" 78 | same as -p but don't wait for stdin - useful for a prompt with no selection options. 79 | .TP 80 | .B \-r 81 | Return as soon as a single match is found. i.e. don't wait for the user to press return. 82 | .TP 83 | .BI \-fn " font" 84 | defines the font or font set used. 85 | .TP 86 | .BI \-nb " color" 87 | defines the normal background color. 88 | .IR #RGB , 89 | .IR #RRGGBB , 90 | and color names are supported. 91 | .TP 92 | .BI \-nf " color" 93 | defines the normal foreground color. 94 | .TP 95 | .BI \-sb " color" 96 | defines the selected background color. 97 | .TP 98 | .BI \-sf " color" 99 | defines the selected foreground color. 100 | .TP 101 | .B \-v 102 | prints version information to standard output, then exits. 103 | .SH USAGE 104 | dmenu is completely controlled by the keyboard. Besides standard Unix line 105 | editing and item selection (Up/Down/Left/Right, PageUp/PageDown, Home/End), the 106 | following keys are recognized: 107 | .TP 108 | .B Tab (Control\-i) 109 | Copy the selected item to the input field. 110 | .TP 111 | .B Return (Control\-j) 112 | Confirm selection. Prints the selected item to standard output and exits, 113 | returning success. 114 | .TP 115 | .B Shift\-Return (Control\-Shift\-j) 116 | Confirm input. Prints the input text to standard output and exits, returning 117 | success. 118 | .TP 119 | .B Escape (Control\-c) 120 | Exit without selecting an item, returning failure. 121 | .TP 122 | .B Control\-y 123 | Paste the current X selection into the input field. 124 | .SH SEE ALSO 125 | .BR dwm (1) 126 | -------------------------------------------------------------------------------- /dmenu.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #ifdef XINERAMA 13 | #include 14 | #endif 15 | #include "draw.h" 16 | 17 | #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) 18 | #define MIN(a,b) ((a) < (b) ? (a) : (b)) 19 | #define MAX(a,b) ((a) > (b) ? (a) : (b)) 20 | 21 | typedef struct Item Item; 22 | struct Item { 23 | char *text; 24 | Item *next; /* traverses all items */ 25 | Item *left, *right; /* traverses matching items */ 26 | }; 27 | 28 | typedef enum { 29 | LEFT, 30 | RIGHT, 31 | CENTRE 32 | } TextPosition; 33 | 34 | static void appenditem(Item *item, Item **list, Item **last); 35 | static void calcoffsets(void); 36 | static void drawmenu(void); 37 | static char *fstrstr(const char *s, const char *sub); 38 | static void grabkeyboard(void); 39 | static void insert(const char *s, ssize_t n); 40 | static void keypress(XKeyEvent *ev); 41 | static void match(void); 42 | static size_t nextrune(int incr); 43 | static void paste(void); 44 | static void readstdin(void); 45 | static void run(void); 46 | static void setup(void); 47 | static void alarmhandler(int signum); 48 | static void handle_return(char* value); 49 | static void usage(void); 50 | 51 | static char text[BUFSIZ]; 52 | static int bh, mw, mh; 53 | static int height = 0; 54 | static int inputw = 0; 55 | static int itemcount = 0; 56 | static int lines = 0; 57 | static int monitor = -1; 58 | static int promptw; 59 | static int timeout = 3; 60 | static size_t cursor = 0; 61 | static const char *font = NULL; 62 | static const char *prompt = NULL; 63 | static const char *normbgcolor = "#222222"; 64 | static const char *normfgcolor = "#bbbbbb"; 65 | static const char *selbgcolor = "#005577"; 66 | static const char *selfgcolor = "#eeeeee"; 67 | static unsigned long normcol[ColLast]; 68 | static unsigned long selcol[ColLast]; 69 | static Atom utf8; 70 | static Bool message = False; 71 | static Bool nostdin = False; 72 | static Bool returnearly = False; 73 | static Bool topbar = True; 74 | static TextPosition messageposition = LEFT; 75 | static DC *dc; 76 | static Item *items = NULL; 77 | static Item *matches, *sel; 78 | static Item *prev, *curr, *next; 79 | static Window root, win; 80 | 81 | static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; 82 | 83 | int 84 | main(int argc, char *argv[]) { 85 | int i; 86 | 87 | progname = "dmenu"; 88 | for(i = 1; i < argc; i++) 89 | /* single flags */ 90 | if(!strcmp(argv[i], "-v") || !strcmp(argv[1], "--version")) { 91 | fputs("dmenu-"VERSION", © 2006-2011 dmenu engineers, see LICENSE for details\n", stdout); 92 | exit(EXIT_SUCCESS); 93 | } 94 | else if(!strcmp(argv[i], "-b") || !strcmp(argv[i], "--bottom")) 95 | topbar = False; 96 | else if(!strcmp(argv[i], "-e") || !strcmp(argv[i], "--echo")) 97 | message = True; 98 | else if(!strcmp(argv[i], "-ec") || !strcmp(argv[i], "--echo-centre")) 99 | message = True, messageposition = CENTRE; 100 | else if(!strcmp(argv[i], "-er") || !strcmp(argv[i], "--echo-right")) 101 | message = True, messageposition = RIGHT; 102 | else if(!strcmp(argv[i], "-i") || !strcmp(argv[i], "--insensitive")) 103 | fstrncmp = strncasecmp; 104 | else if(!strcmp(argv[i], "-r") || !strcmp(argv[i], "--return-early")) 105 | returnearly = True; 106 | else if(i==argc-1) 107 | usage(); 108 | /* opts that need 1 arg */ 109 | else if(!strcmp(argv[i], "-et") || !strcmp(argv[i], "--echo-timeout")) 110 | timeout = atoi(argv[++i]); 111 | else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--height")) 112 | height = atoi(argv[++i]); 113 | else if(!strcmp(argv[i], "-l") || !strcmp(argv[i], "--lines")) 114 | lines = atoi(argv[++i]); 115 | else if(!strcmp(argv[i], "-m") || !strcmp(argv[i], "--monitor")) 116 | monitor = atoi(argv[++i]); 117 | else if(!strcmp(argv[i], "-p") || !strcmp(argv[i], "--prompt")) 118 | prompt = argv[++i]; 119 | else if(!strcmp(argv[i], "-po") || !strcmp(argv[i], "--prompt-only")) 120 | prompt = argv[++i], nostdin = True; 121 | else if(!strcmp(argv[i], "-fn") || !strcmp(argv[i], "--font-name")) 122 | font = argv[++i]; 123 | else if(!strcmp(argv[i], "-nb") || !strcmp(argv[i], "--normal-background")) 124 | normbgcolor = argv[++i]; 125 | else if(!strcmp(argv[i], "-nf") || !strcmp(argv[i], "--normal-foreground")) 126 | normfgcolor = argv[++i]; 127 | else if(!strcmp(argv[i], "-sb") || !strcmp(argv[i], "--selected-background")) 128 | selbgcolor = argv[++i]; 129 | else if(!strcmp(argv[i], "-sf") || !strcmp(argv[i], "--selected-foreground")) 130 | selfgcolor = argv[++i]; 131 | else 132 | usage(); 133 | 134 | if(message) { 135 | signal(SIGALRM, alarmhandler); 136 | alarm(timeout); 137 | } 138 | 139 | dc = initdc(); 140 | initfont(dc, font); 141 | if(!nostdin) { 142 | readstdin(); 143 | } 144 | setup(); 145 | run(); 146 | 147 | return EXIT_FAILURE; /* should not reach */ 148 | } 149 | 150 | void 151 | appenditem(Item *item, Item **list, Item **last) { 152 | if(!*last) 153 | *list = item; 154 | else 155 | (*last)->right = item; 156 | item->left = *last; 157 | item->right = NULL; 158 | *last = item; 159 | } 160 | 161 | void 162 | calcoffsets(void) { 163 | unsigned int i, n; 164 | 165 | if(lines > 0) 166 | n = lines * bh; 167 | else 168 | n = mw - (promptw + inputw + textw(dc, "<") + textw(dc, ">")); 169 | 170 | for(i = 0, next = curr; next; next = next->right) 171 | if((i += (lines > 0) ? bh : MIN(textw(dc, next->text), n)) > n) 172 | break; 173 | for(i = 0, prev = curr; prev && prev->left; prev = prev->left) 174 | if((i += (lines > 0) ? bh : MIN(textw(dc, prev->left->text), n)) > n) 175 | break; 176 | } 177 | 178 | void 179 | drawmenu(void) { 180 | int curpos; 181 | Item *item; 182 | 183 | dc->x = 0; 184 | dc->y = 0; 185 | dc->w = 0; 186 | dc->h = height; 187 | 188 | dc->text_offset_y = 0; 189 | 190 | if(mh < height) { 191 | dc->text_offset_y = (height - mh) / 2; 192 | } 193 | 194 | drawrect(dc, 0, 0, mw, height, True, BG(dc, normcol)); 195 | 196 | if(message) { 197 | if(messageposition == RIGHT || messageposition == CENTRE) { // Find starting position aligned text 198 | dc->x = mw; 199 | for(item = curr; item != next; item = item->right) { 200 | dc->x -= textw(dc, item->text); 201 | } 202 | } 203 | if(messageposition == CENTRE) { 204 | dc->x /= 2; 205 | } 206 | for(item = curr; item != next; item = item->right) { 207 | dc->w = textw(dc, item->text); 208 | drawtext(dc, item->text, normcol); 209 | dc->x += dc->w; 210 | } 211 | } 212 | else { 213 | if(prompt) { 214 | dc->w = promptw; 215 | drawtext(dc, prompt, selcol); 216 | dc->x = dc->w; 217 | } 218 | dc->w = (lines > 0 || !matches) ? mw - dc->x : inputw; 219 | drawtext(dc, text, normcol); 220 | if((curpos = textnw(dc, text, cursor) + mh/2 - 2) < dc->w) 221 | drawrect(dc, curpos, 2 + dc->text_offset_y, 1, mh - 4, True, FG(dc, normcol)); 222 | 223 | if(lines > 0) { 224 | dc->w = mw - dc->x; 225 | for(item = curr; item != next; item = item->right) { 226 | dc->y += dc->h; 227 | drawtext(dc, item->text, (item == sel) ? selcol : normcol); 228 | } 229 | } 230 | else if(matches) { 231 | dc->x += dc->w; 232 | if(curr->left) { 233 | dc->w = textw(dc, "<"); 234 | drawtext(dc, "<", normcol); 235 | dc->x += dc->w; 236 | } 237 | for(item = curr; item != next; item = item->right) { 238 | dc->w = MIN(textw(dc, item->text), mw - dc->x - textw(dc, ">")); 239 | drawtext(dc, item->text, (item == sel) ? selcol : normcol); 240 | dc->x += dc->w; 241 | } 242 | dc->w = textw(dc, ">"); 243 | dc->x = mw - dc->w; 244 | if(next) { 245 | drawtext(dc, ">", normcol); 246 | } 247 | } 248 | } 249 | mapdc(dc, win, mw, height); 250 | } 251 | 252 | char * 253 | fstrstr(const char *s, const char *sub) { 254 | size_t len; 255 | 256 | for(len = strlen(sub); *s; s++) 257 | if(!fstrncmp(s, sub, len)) 258 | return (char *)s; 259 | return NULL; 260 | } 261 | 262 | void 263 | grabkeyboard(void) { 264 | int i; 265 | 266 | if(!message) { 267 | for(i = 0; i < 1000; i++) { 268 | if(!XGrabKeyboard(dc->dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime)) 269 | return; 270 | usleep(1000); 271 | } 272 | eprintf("cannot grab keyboard\n"); 273 | } 274 | } 275 | 276 | void 277 | insert(const char *s, ssize_t n) { 278 | if(strlen(text) + n > sizeof text - 1) 279 | return; 280 | memmove(text + cursor + n, text + cursor, sizeof text - cursor - MAX(n, 0)); 281 | if(n > 0) 282 | memcpy(text + cursor, s, n); 283 | cursor += n; 284 | match(); 285 | } 286 | 287 | void 288 | keypress(XKeyEvent *ev) { 289 | char buf[32]; 290 | size_t len; 291 | KeySym ksym; 292 | 293 | len = strlen(text); 294 | XLookupString(ev, buf, sizeof buf, &ksym, NULL); 295 | if(ev->state & ControlMask) { 296 | switch(tolower(ksym)) { 297 | default: 298 | return; 299 | case XK_a: 300 | ksym = XK_Home; 301 | break; 302 | case XK_b: 303 | ksym = XK_Left; 304 | break; 305 | case XK_c: 306 | ksym = XK_Escape; 307 | break; 308 | case XK_d: 309 | ksym = XK_Delete; 310 | break; 311 | case XK_e: 312 | ksym = XK_End; 313 | break; 314 | case XK_f: 315 | ksym = XK_Right; 316 | break; 317 | case XK_h: 318 | ksym = XK_BackSpace; 319 | break; 320 | case XK_i: 321 | ksym = XK_Tab; 322 | break; 323 | case XK_j: 324 | ksym = XK_Return; 325 | break; 326 | case XK_k: /* delete right */ 327 | text[cursor] = '\0'; 328 | match(); 329 | break; 330 | case XK_n: 331 | ksym = XK_Down; 332 | break; 333 | case XK_p: 334 | ksym = XK_Up; 335 | break; 336 | case XK_u: /* delete left */ 337 | insert(NULL, 0 - cursor); 338 | break; 339 | case XK_w: /* delete word */ 340 | while(cursor > 0 && text[nextrune(-1)] == ' ') 341 | insert(NULL, nextrune(-1) - cursor); 342 | while(cursor > 0 && text[nextrune(-1)] != ' ') 343 | insert(NULL, nextrune(-1) - cursor); 344 | break; 345 | case XK_y: /* paste selection */ 346 | XConvertSelection(dc->dpy, XA_PRIMARY, utf8, utf8, win, CurrentTime); 347 | return; 348 | } 349 | } 350 | switch(ksym) { 351 | default: 352 | if(!iscntrl(*buf)) 353 | insert(buf, strlen(buf)); 354 | break; 355 | case XK_Delete: 356 | if(cursor == len) 357 | return; 358 | cursor = nextrune(+1); 359 | case XK_BackSpace: 360 | if(cursor > 0) 361 | insert(NULL, nextrune(-1) - cursor); 362 | break; 363 | case XK_End: 364 | if(cursor < len) { 365 | cursor = len; 366 | break; 367 | } 368 | while(next) { 369 | sel = curr = next; 370 | calcoffsets(); 371 | } 372 | while(sel && sel->right) 373 | sel = sel->right; 374 | break; 375 | case XK_Escape: 376 | exit(EXIT_FAILURE); 377 | case XK_Home: 378 | if(sel == matches) { 379 | cursor = 0; 380 | break; 381 | } 382 | sel = curr = matches; 383 | calcoffsets(); 384 | break; 385 | case XK_Left: 386 | if(cursor > 0 && (!sel || !sel->left || lines > 0)) { 387 | cursor = nextrune(-1); 388 | break; 389 | } 390 | else if(lines > 0) 391 | return; 392 | case XK_Up: 393 | if(sel && sel->left && (sel = sel->left)->right == curr) { 394 | curr = prev; 395 | calcoffsets(); 396 | } 397 | break; 398 | case XK_Next: 399 | if(!next) 400 | return; 401 | sel = curr = next; 402 | calcoffsets(); 403 | break; 404 | case XK_Prior: 405 | if(!prev) 406 | return; 407 | sel = curr = prev; 408 | calcoffsets(); 409 | break; 410 | case XK_Return: 411 | case XK_KP_Enter: 412 | handle_return((sel && !(ev->state & ShiftMask)) ? sel->text : text); 413 | case XK_Right: 414 | if(cursor < len) { 415 | cursor = nextrune(+1); 416 | break; 417 | } 418 | else if(lines > 0) 419 | return; 420 | case XK_Down: 421 | if(sel && sel->right && (sel = sel->right) == next) { 422 | curr = next; 423 | calcoffsets(); 424 | } 425 | break; 426 | case XK_Tab: 427 | if(!sel) 428 | return; 429 | strncpy(text, sel->text, sizeof text); 430 | cursor = strlen(text); 431 | match(); 432 | break; 433 | } 434 | drawmenu(); 435 | } 436 | 437 | void 438 | match(void) { 439 | size_t len; 440 | Item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend; 441 | 442 | len = strlen(text); 443 | matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL; 444 | for(item = items; item; item = item->next) 445 | if(!fstrncmp(text, item->text, len + 1)) { 446 | appenditem(item, &lexact, &exactend); 447 | } 448 | else if(!fstrncmp(text, item->text, len)) { 449 | appenditem(item, &lprefix, &prefixend); 450 | } 451 | else if(fstrstr(item->text, text)) { 452 | appenditem(item, &lsubstr, &substrend); 453 | } 454 | 455 | if(lexact) { 456 | matches = lexact; 457 | itemend = exactend; 458 | } 459 | if(lprefix) { 460 | if(itemend) { 461 | itemend->right = lprefix; 462 | lprefix->left = itemend; 463 | } 464 | else 465 | matches = lprefix; 466 | itemend = prefixend; 467 | } 468 | if(lsubstr) { 469 | if(itemend) { 470 | itemend->right = lsubstr; 471 | lsubstr->left = itemend; 472 | } 473 | else 474 | matches = lsubstr; 475 | } 476 | curr = prev = next = sel = matches; 477 | calcoffsets(); 478 | 479 | if(returnearly && !curr->right) { 480 | handle_return(curr->text); 481 | } 482 | } 483 | 484 | size_t 485 | nextrune(int incr) { 486 | size_t n, len; 487 | 488 | len = strlen(text); 489 | for(n = cursor + incr; n >= 0 && n < len && (text[n] & 0xc0) == 0x80; n += incr); 490 | return n; 491 | } 492 | 493 | void 494 | paste(void) { 495 | char *p, *q; 496 | int di; 497 | unsigned long dl; 498 | Atom da; 499 | 500 | XGetWindowProperty(dc->dpy, win, utf8, 0, (sizeof text / 4) + 1, False, 501 | utf8, &da, &di, &dl, &dl, (unsigned char **)&p); 502 | insert(p, (q = strchr(p, '\n')) ? q-p : strlen(p)); 503 | XFree(p); 504 | drawmenu(); 505 | } 506 | 507 | void 508 | readstdin(void) { 509 | char buf[sizeof text], *p; 510 | Item *item, **end; 511 | 512 | for(end = &items; fgets(buf, sizeof buf, stdin); *end = item, end = &item->next) { 513 | itemcount++; 514 | 515 | if((p = strchr(buf, '\n'))) { 516 | *p = '\0'; 517 | } 518 | if(!(item = malloc(sizeof *item))) { 519 | eprintf("cannot malloc %u bytes\n", sizeof *item); 520 | } 521 | if(!(item->text = strdup(buf))) { 522 | eprintf("cannot strdup %u bytes\n", strlen(buf)+1); 523 | } 524 | item->next = item->left = item->right = NULL; 525 | inputw = MAX(inputw, textw(dc, item->text)); 526 | } 527 | } 528 | 529 | void 530 | run(void) { 531 | XEvent ev; 532 | 533 | while(!XNextEvent(dc->dpy, &ev)) 534 | switch(ev.type) { 535 | case Expose: 536 | if(ev.xexpose.count == 0) 537 | drawmenu(); 538 | break; 539 | case KeyPress: 540 | keypress(&ev.xkey); 541 | break; 542 | case SelectionNotify: 543 | if(ev.xselection.property == utf8) 544 | paste(); 545 | break; 546 | case VisibilityNotify: 547 | if(ev.xvisibility.state != VisibilityUnobscured) 548 | XRaiseWindow(dc->dpy, win); 549 | break; 550 | } 551 | } 552 | 553 | void 554 | setup(void) { 555 | int x, y, screen; 556 | XSetWindowAttributes wa; 557 | #ifdef XINERAMA 558 | int n; 559 | XineramaScreenInfo *info; 560 | #endif 561 | 562 | screen = DefaultScreen(dc->dpy); 563 | root = RootWindow(dc->dpy, screen); 564 | utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False); 565 | 566 | normcol[ColBG] = getcolor(dc, normbgcolor); 567 | normcol[ColFG] = getcolor(dc, normfgcolor); 568 | selcol[ColBG] = getcolor(dc, selbgcolor); 569 | selcol[ColFG] = getcolor(dc, selfgcolor); 570 | 571 | /* menu geometry */ 572 | bh = dc->font.height + 2; 573 | lines = MAX(lines, 0); 574 | mh = (MAX(MIN(lines + 1, itemcount), 1)) * bh; 575 | 576 | if(height < mh) { 577 | height = mh; 578 | } 579 | #ifdef XINERAMA 580 | if((info = XineramaQueryScreens(dc->dpy, &n))) { 581 | int i, di; 582 | unsigned int du; 583 | Window dw; 584 | 585 | XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du); 586 | for(i = 0; i < n; i++) 587 | if((monitor == info[i].screen_number) 588 | || (monitor < 0 && INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))) 589 | break; 590 | x = info[i].x_org; 591 | y = info[i].y_org + (topbar ? 0 : info[i].height - height); 592 | mw = info[i].width; 593 | XFree(info); 594 | } 595 | else 596 | #endif 597 | { 598 | x = 0; 599 | y = topbar ? 0 : DisplayHeight(dc->dpy, screen) - height; 600 | mw = DisplayWidth(dc->dpy, screen); 601 | } 602 | /* menu window */ 603 | wa.override_redirect = True; 604 | wa.background_pixmap = ParentRelative; 605 | wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; 606 | win = XCreateWindow(dc->dpy, root, x, y, mw, height, 0, 607 | DefaultDepth(dc->dpy, screen), CopyFromParent, 608 | DefaultVisual(dc->dpy, screen), 609 | CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); 610 | 611 | grabkeyboard(); 612 | resizedc(dc, mw, height); 613 | inputw = MIN(inputw, mw/3); 614 | promptw = prompt ? textw(dc, prompt) : 0; 615 | XMapRaised(dc->dpy, win); 616 | text[0] = '\0'; 617 | match(); 618 | } 619 | 620 | void handle_return(char* value) { 621 | fputs(value, stdout); 622 | fflush(stdout); 623 | exit(EXIT_SUCCESS); 624 | } 625 | 626 | void 627 | alarmhandler(int signum) { 628 | exit(EXIT_SUCCESS); 629 | } 630 | 631 | void 632 | usage(void) { 633 | printf("Usage: dmenu [OPTION]...\n"); 634 | printf("Display newline-separated input stdin as a menubar\n"); 635 | printf("\n"); 636 | printf(" -e, --echo display text from stdin with no user\n"); 637 | printf(" interaction\n"); 638 | printf(" -ec, --echo-centre same as -e but align text centrally\n"); 639 | printf(" -er, --echo-right same as -e but align text right\n"); 640 | printf(" -et, --echo-timeout SECS close the message after SEC seconds\n"); 641 | printf(" when using -e, -ec, or -er\n"); 642 | printf(" -b, --bottom dmenu appears at the bottom of the screen\n"); 643 | printf(" -h, --height N set dmenu to be N pixels high\n"); 644 | printf(" -i, --insensitive dmenu matches menu items case insensitively\n"); 645 | printf(" -l, --lines LINES dmenu lists items vertically, within the\n"); 646 | printf(" given number of lines\n"); 647 | printf(" -m, --monitor MONITOR dmenu appears on the given Xinerama screen\n"); 648 | printf(" -p, --prompt PROMPT prompt to be displayed to the left of the\n"); 649 | printf(" input field\n"); 650 | printf(" -po, --prompt-only PROMPT same as -p but don't wait for stdin\n"); 651 | printf(" useful for a prompt with no menu\n"); 652 | printf(" -r, --return-early return as soon as a single match is found\n"); 653 | printf(" -fn, --font-name FONT font or font set to be used\n"); 654 | printf(" -nb, --normal-background COLOR normal background color\n"); 655 | printf(" #RGB, #RRGGBB, and color names supported\n"); 656 | printf(" -nf, --normal-foreground COLOR normal foreground color\n"); 657 | printf(" -sb, --selected-background COLOR selected background color\n"); 658 | printf(" -sf, --selected-foreground COLOR selected foreground color\n"); 659 | printf(" -v, --version display version information\n"); 660 | 661 | exit(EXIT_FAILURE); 662 | } 663 | -------------------------------------------------------------------------------- /dmenu_path.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define CACHE ".dmenu_cache" 11 | 12 | static void die(const char *s); 13 | static int qstrcmp(const void *a, const void *b); 14 | static void scan(void); 15 | static int uptodate(void); 16 | 17 | static char **items = NULL; 18 | static const char *home, *path; 19 | 20 | int 21 | main(void) { 22 | if(!(home = getenv("HOME"))) 23 | die("no $HOME"); 24 | if(!(path = getenv("PATH"))) 25 | die("no $PATH"); 26 | if(chdir(home) < 0) 27 | die("chdir failed"); 28 | if(uptodate()) { 29 | execlp("cat", "cat", CACHE, NULL); 30 | die("exec failed"); 31 | } 32 | scan(); 33 | return EXIT_SUCCESS; 34 | } 35 | 36 | void 37 | die(const char *s) { 38 | fprintf(stderr, "dmenu_path: %s\n", s); 39 | exit(EXIT_FAILURE); 40 | } 41 | 42 | int 43 | qstrcmp(const void *a, const void *b) { 44 | return strcmp(*(const char **)a, *(const char **)b); 45 | } 46 | 47 | void 48 | scan(void) { 49 | char buf[PATH_MAX]; 50 | char *dir, *p; 51 | size_t i, count; 52 | struct dirent *ent; 53 | DIR *dp; 54 | FILE *cache; 55 | 56 | count = 0; 57 | if(!(p = strdup(path))) 58 | die("strdup failed"); 59 | for(dir = strtok(p, ":"); dir; dir = strtok(NULL, ":")) { 60 | if(!(dp = opendir(dir))) 61 | continue; 62 | while((ent = readdir(dp))) { 63 | snprintf(buf, sizeof buf, "%s/%s", dir, ent->d_name); 64 | if(ent->d_name[0] == '.' || access(buf, X_OK) < 0) 65 | continue; 66 | if(!(items = realloc(items, ++count * sizeof *items))) 67 | die("malloc failed"); 68 | if(!(items[count-1] = strdup(ent->d_name))) 69 | die("strdup failed"); 70 | } 71 | closedir(dp); 72 | } 73 | qsort(items, count, sizeof *items, qstrcmp); 74 | if(!(cache = fopen(CACHE, "w"))) 75 | die("open failed"); 76 | for(i = 0; i < count; i++) { 77 | if(i > 0 && !strcmp(items[i], items[i-1])) 78 | continue; 79 | fprintf(cache, "%s\n", items[i]); 80 | fprintf(stdout, "%s\n", items[i]); 81 | } 82 | fclose(cache); 83 | free(p); 84 | } 85 | 86 | int 87 | uptodate(void) { 88 | char *dir, *p; 89 | time_t mtime; 90 | struct stat st; 91 | 92 | if(stat(CACHE, &st) < 0) 93 | return 0; 94 | mtime = st.st_mtime; 95 | if(!(p = strdup(path))) 96 | die("strdup failed"); 97 | for(dir = strtok(p, ":"); dir; dir = strtok(NULL, ":")) 98 | if(!stat(dir, &st) && st.st_mtime > mtime) 99 | return 0; 100 | free(p); 101 | return 1; 102 | } 103 | -------------------------------------------------------------------------------- /dmenu_run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exe=`dmenu_path | dmenu -r ${1+"$@"}` && exec $exe 3 | -------------------------------------------------------------------------------- /draw.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "draw.h" 9 | 10 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 11 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 12 | #define DEFFONT "fixed" 13 | 14 | static Bool loadfont(DC *dc, const char *fontstr); 15 | 16 | void 17 | drawrect(DC *dc, int x, int y, unsigned int w, unsigned int h, Bool fill, unsigned long color) { 18 | XRectangle r = { dc->x + x, dc->y + y, w, h }; 19 | 20 | if(!fill) { 21 | r.width -= 1; 22 | r.height -= 1; 23 | } 24 | XSetForeground(dc->dpy, dc->gc, color); 25 | (fill ? XFillRectangles : XDrawRectangles)(dc->dpy, dc->canvas, dc->gc, &r, 1); 26 | } 27 | 28 | 29 | void 30 | drawtext(DC *dc, const char *text, unsigned long col[ColLast]) { 31 | char buf[256]; 32 | size_t n, mn; 33 | 34 | /* shorten text if necessary */ 35 | n = strlen(text); 36 | for(mn = MIN(n, sizeof buf); textnw(dc, text, mn) > dc->w - dc->font.height/2; mn--) 37 | if(mn == 0) 38 | return; 39 | memcpy(buf, text, mn); 40 | if(mn < n) 41 | for(n = MAX(mn-3, 0); n < mn; buf[n++] = '.'); 42 | 43 | drawrect(dc, 0, 0, dc->w, dc->h, True, BG(dc, col)); 44 | drawtextn(dc, buf, mn, col); 45 | } 46 | 47 | void 48 | drawtextn(DC *dc, const char *text, size_t n, unsigned long col[ColLast]) { 49 | int x, y; 50 | 51 | x = dc->x + dc->font.height/2; 52 | y = dc->y + dc->font.ascent+1 + dc->text_offset_y; 53 | 54 | XSetForeground(dc->dpy, dc->gc, FG(dc, col)); 55 | if(dc->font.set) 56 | XmbDrawString(dc->dpy, dc->canvas, dc->font.set, dc->gc, x, y, text, n); 57 | else { 58 | XSetFont(dc->dpy, dc->gc, dc->font.xfont->fid); 59 | XDrawString(dc->dpy, dc->canvas, dc->gc, x, y, text, n); 60 | } 61 | } 62 | 63 | void 64 | eprintf(const char *fmt, ...) { 65 | va_list ap; 66 | 67 | fprintf(stderr, "%s: ", progname); 68 | va_start(ap, fmt); 69 | vfprintf(stderr, fmt, ap); 70 | va_end(ap); 71 | exit(EXIT_FAILURE); 72 | } 73 | 74 | void 75 | freedc(DC *dc) { 76 | if(dc->font.set) 77 | XFreeFontSet(dc->dpy, dc->font.set); 78 | if(dc->font.xfont) 79 | XFreeFont(dc->dpy, dc->font.xfont); 80 | if(dc->canvas) 81 | XFreePixmap(dc->dpy, dc->canvas); 82 | XFreeGC(dc->dpy, dc->gc); 83 | XCloseDisplay(dc->dpy); 84 | free(dc); 85 | } 86 | 87 | unsigned long 88 | getcolor(DC *dc, const char *colstr) { 89 | Colormap cmap = DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)); 90 | XColor color; 91 | 92 | if(!XAllocNamedColor(dc->dpy, cmap, colstr, &color, &color)) 93 | eprintf("cannot allocate color '%s'\n", colstr); 94 | return color.pixel; 95 | } 96 | 97 | DC * 98 | initdc(void) { 99 | DC *dc; 100 | 101 | if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 102 | weprintf("no locale support\n"); 103 | if(!(dc = malloc(sizeof *dc))) 104 | eprintf("cannot malloc %u bytes\n", sizeof *dc); 105 | if(!(dc->dpy = XOpenDisplay(NULL))) 106 | eprintf("cannot open display\n"); 107 | 108 | dc->gc = XCreateGC(dc->dpy, DefaultRootWindow(dc->dpy), 0, NULL); 109 | XSetLineAttributes(dc->dpy, dc->gc, 1, LineSolid, CapButt, JoinMiter); 110 | dc->font.xfont = NULL; 111 | dc->font.set = NULL; 112 | dc->canvas = None; 113 | return dc; 114 | } 115 | 116 | void 117 | initfont(DC *dc, const char *fontstr) { 118 | if(!loadfont(dc, fontstr ? fontstr : DEFFONT)) { 119 | if(fontstr != NULL) 120 | weprintf("cannot load font '%s'\n", fontstr); 121 | if(fontstr == NULL || !loadfont(dc, DEFFONT)) 122 | eprintf("cannot load font '%s'\n", DEFFONT); 123 | } 124 | dc->font.height = dc->font.ascent + dc->font.descent; 125 | } 126 | 127 | Bool 128 | loadfont(DC *dc, const char *fontstr) { 129 | char *def, **missing; 130 | int i, n; 131 | 132 | if(!*fontstr) 133 | return False; 134 | if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def))) { 135 | char **names; 136 | XFontStruct **xfonts; 137 | 138 | n = XFontsOfFontSet(dc->font.set, &xfonts, &names); 139 | for(i = dc->font.ascent = dc->font.descent = 0; i < n; i++) { 140 | dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent); 141 | dc->font.descent = MAX(dc->font.descent, xfonts[i]->descent); 142 | } 143 | } 144 | else if((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr))) { 145 | dc->font.ascent = dc->font.xfont->ascent; 146 | dc->font.descent = dc->font.xfont->descent; 147 | } 148 | if(missing) 149 | XFreeStringList(missing); 150 | return (dc->font.set || dc->font.xfont); 151 | } 152 | 153 | void 154 | mapdc(DC *dc, Window win, unsigned int w, unsigned int h) { 155 | XCopyArea(dc->dpy, dc->canvas, win, dc->gc, 0, 0, w, h, 0, 0); 156 | } 157 | 158 | void 159 | resizedc(DC *dc, unsigned int w, unsigned int h) { 160 | if(dc->canvas) 161 | XFreePixmap(dc->dpy, dc->canvas); 162 | dc->canvas = XCreatePixmap(dc->dpy, DefaultRootWindow(dc->dpy), w, h, 163 | DefaultDepth(dc->dpy, DefaultScreen(dc->dpy))); 164 | dc->x = dc->y = 0; 165 | dc->w = w; 166 | dc->h = h; 167 | dc->invert = False; 168 | } 169 | 170 | int 171 | textnw(DC *dc, const char *text, size_t len) { 172 | if(dc->font.set) { 173 | XRectangle r; 174 | 175 | XmbTextExtents(dc->font.set, text, len, NULL, &r); 176 | return r.width; 177 | } 178 | return XTextWidth(dc->font.xfont, text, len); 179 | } 180 | 181 | int 182 | textw(DC *dc, const char *text) { 183 | return textnw(dc, text, strlen(text)) + dc->font.height; 184 | } 185 | 186 | void 187 | weprintf(const char *fmt, ...) { 188 | va_list ap; 189 | 190 | fprintf(stderr, "%s: warning: ", progname); 191 | va_start(ap, fmt); 192 | vfprintf(stderr, fmt, ap); 193 | va_end(ap); 194 | } 195 | -------------------------------------------------------------------------------- /draw.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | #define FG(dc, col) ((col)[(dc)->invert ? ColBG : ColFG]) 4 | #define BG(dc, col) ((col)[(dc)->invert ? ColFG : ColBG]) 5 | 6 | enum { ColBG, ColFG, ColBorder, ColLast }; 7 | 8 | typedef struct { 9 | int x, y, w, h; 10 | int text_offset_y; 11 | Bool invert; 12 | Display *dpy; 13 | GC gc; 14 | Pixmap canvas; 15 | struct { 16 | int ascent; 17 | int descent; 18 | int height; 19 | XFontSet set; 20 | XFontStruct *xfont; 21 | } font; 22 | } DC; /* draw context */ 23 | 24 | unsigned long getcolor(DC *dc, const char *colstr); 25 | void drawrect(DC *dc, int x, int y, unsigned int w, unsigned int h, Bool fill, unsigned long color); 26 | void drawtext(DC *dc, const char *text, unsigned long col[ColLast]); 27 | void drawtextn(DC *dc, const char *text, size_t n, unsigned long col[ColLast]); 28 | void initfont(DC *dc, const char *fontstr); 29 | void freedc(DC *dc); 30 | DC *initdc(void); 31 | void mapdc(DC *dc, Window win, unsigned int w, unsigned int h); 32 | void resizedc(DC *dc, unsigned int w, unsigned int h); 33 | int textnw(DC *dc, const char *text, size_t len); 34 | int textw(DC *dc, const char *text); 35 | void eprintf(const char *fmt, ...); 36 | void weprintf(const char *fmt, ...); 37 | 38 | const char *progname; 39 | --------------------------------------------------------------------------------