aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README6
-rw-r--r--dmenu.194
-rw-r--r--dmenu.c488
4 files changed, 271 insertions, 319 deletions
diff --git a/Makefile b/Makefile
index 0d16e38..269bc27 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ include config.mk
6SRC = dmenu.c 6SRC = dmenu.c
7OBJ = ${SRC:.c=.o} 7OBJ = ${SRC:.c=.o}
8 8
9all: options dinput dmenu 9all: options dmenu
10 10
11options: 11options:
12 @echo dmenu build options: 12 @echo dmenu build options:
diff --git a/README b/README
index 3706ef1..9f0583a 100644
--- a/README
+++ b/README
@@ -8,13 +8,11 @@ Requirements
8In order to build dmenu you need the Xlib header files. 8In order to build dmenu you need the Xlib header files.
9You also need libdraw, available from http://hg.suckless.org/libdraw 9You also need libdraw, available from http://hg.suckless.org/libdraw
10 10
11Pasting the X selection to dmenu requires the sselp utility at runtime.
12
13 11
14Installation 12Installation
15------------ 13------------
16Edit config.mk to match your local setup (dmenu is installed into 14Edit config.mk to match your local setup (dmenu is installed into the
17the /usr/local namespace by default). 15/usr/local namespace by default).
18 16
19Afterwards enter the following command to build and install dmenu (if 17Afterwards enter the following command to build and install dmenu (if
20necessary as root): 18necessary as root):
diff --git a/dmenu.1 b/dmenu.1
index f0c45f4..2d8bc20 100644
--- a/dmenu.1
+++ b/dmenu.1
@@ -3,99 +3,103 @@
3dmenu \- dynamic menu 3dmenu \- dynamic menu
4.SH SYNOPSIS 4.SH SYNOPSIS
5.B dmenu 5.B dmenu
6.RB [ \-i ]
7.RB [ \-b ] 6.RB [ \-b ]
8.RB [ \-e " <xid>]" 7.RB [ \-i ]
9.RB [ \-l " <lines>]" 8.RB [ \-l " <lines>]"
9.RB [ \-p " <prompt>]"
10.RB [ \-fn " <font>]" 10.RB [ \-fn " <font>]"
11.RB [ \-nb " <color>]" 11.RB [ \-nb " <color>]"
12.RB [ \-nf " <color>]" 12.RB [ \-nf " <color>]"
13.RB [ \-p " <prompt>]"
14.RB [ \-sb " <color>]" 13.RB [ \-sb " <color>]"
15.RB [ \-sf " <color>]" 14.RB [ \-sf " <color>]"
16.RB [ \-v ] 15.RB [ \-v ]
17 16
18.B dmenu_run 17.B dmenu_run
19[<options...>] 18.RB [ \-b ]
19.RB [ \-i ]
20.RB [ \-l " <lines>]"
21.RB [ \-p " <prompt>]"
22.RB [ \-fn " <font>]"
23.RB [ \-nb " <color>]"
24.RB [ \-nf " <color>]"
25.RB [ \-sb " <color>]"
26.RB [ \-sf " <color>]"
27.RB [ \-v ]
20 28
21.B dmenu_path 29.B dmenu_path
22.SH DESCRIPTION 30.SH DESCRIPTION
23.SS Overview 31.SS Overview
24dmenu is a generic menu for X, originally designed for 32.B dmenu
33is a generic menu for X, originally designed for
25.BR dwm (1). 34.BR dwm (1).
26It manages huge amounts (up to 10.000 and more) of user defined menu items 35It manages huge amounts (10000 and more) of user defined menu items efficiently.
27efficiently. 36.P
28 37.B dmenu_run
29dmenu_run is a dmenu script used by dwm which lists executables in the user's PATH 38is a dmenu script which lists programs in the user's PATH and executes
30and executes the selected item. 39the selected item.
31 40.P
32dmenu_path is a script used by dmenu_run to find and cache a list of executables. 41.B dmenu_path
42is a script used by
43.I dmenu_run
44to find and cache a list of programs.
33.SS Options 45.SS Options
34.TP 46.TP
35.B \-i
36makes dmenu match menu entries case insensitively.
37.TP
38.B \-b 47.B \-b
39defines that dmenu appears at the bottom. 48dmenu appears at the bottom of the screen.
40.TP 49.TP
41.B \-e <xid> 50.B \-i
42reparents dmenu to the window specified by xid. 51dmenu matches menu entries case insensitively.
43.TP 52.TP
44.B \-l <lines> 53.B \-l <lines>
45activates vertical list mode. 54dmenu lists items vertically, with the given number of lines.
46The given number of lines will be displayed. Window height will be adjusted. 55.TP
56.B \-p <prompt>
57sets the prompt to be displayed to the left of the input area.
47.TP 58.TP
48.B \-fn <font> 59.B \-fn <font>
49defines the font. 60sets the font.
50.TP 61.TP
51.B \-nb <color> 62.B \-nb <color>
52defines the normal background color (#RGB, #RRGGBB, and color names are supported). 63sets the background color (#RGB, #RRGGBB, and color names are supported).
53.TP 64.TP
54.B \-nf <color> 65.B \-nf <color>
55defines the normal foreground color (#RGB, #RRGGBB, and color names are supported). 66sets the foreground color (#RGB, #RRGGBB, and color names are supported).
56.TP
57.B \-p <prompt>
58defines a prompt to be displayed before the input area.
59.TP 67.TP
60.B \-sb <color> 68.B \-sb <color>
61defines the selected background color (#RGB, #RRGGBB, and color names are supported). 69sets the background color of selected items (#RGB, #RRGGBB, and color names are
70supported).
62.TP 71.TP
63.B \-sf <color> 72.B \-sf <color>
64defines the selected foreground color (#RGB, #RRGGBB, and color names are supported). 73sets the foreground color of selected items (#RGB, #RRGGBB, and color names are
74supported).
65.TP 75.TP
66.B \-v 76.B \-v
67prints version information to standard output, then exits. 77prints version information to standard output, then exits.
68.SH USAGE 78.SH USAGE
69dmenu reads a list of newline-separated items from standard input and creates a 79dmenu reads a list of newline-separated items from standard input and creates a
70menu. When the user selects an item or enters any text and presses Return, his/her 80menu. When the user selects an item or enters any text and presses Return,
71choice is printed to standard output and dmenu terminates. 81their choice is printed to standard output and dmenu terminates.
72.P 82.P
73dmenu is completely controlled by the keyboard. Besides standard Unix line editing, 83dmenu is completely controlled by the keyboard. Besides standard Unix line
74and item selection (Up/Down or Left/Right, PageUp/PageDown, Home/End), the following 84editing and item selection (Up/Down/Left/Right, PageUp/PageDown, Home/End), the
75keys are recognized: 85following keys are recognized:
76.TP 86.TP
77.B Tab (Control\-i) 87.B Tab (Control\-i)
78Copy the selected item to the input field. 88Copy the selected item to the input field.
79.TP 89.TP
80.B Return (Control\-j) 90.B Return (Control\-j)
81Confirm selection and quit (print the selected item to standard output). Returns 91Confirm selection. Prints the selected item to standard output and exits,
82.B 0 92returning success.
83on termination.
84.TP 93.TP
85.B Shift\-Return (Control\-Shift\-j) 94.B Shift\-Return (Control\-Shift\-j)
86Confirm input and quit (print the text in the input field to standard output). 95Confirm input. Prints the input text to standard output and exits, returning
87Returns 96success.
88.B 0
89on termination.
90.TP 97.TP
91.B Escape (Control\-c) 98.B Escape (Control\-c)
92Quit without selecting an item. Returns 99Quit without selecting an item, returning failure.
93.B 1
94on termination.
95.TP 100.TP
96.B Control\-y 101.B Control\-y
97Pastes the X selection into the input field. This requires 102Paste the current X selection into the input field.
98.BR sselp (1).
99.SH SEE ALSO 103.SH SEE ALSO
100.BR dwm (1), 104.BR dwm (1),
101.BR wmii (1). 105.BR wmii (1).
diff --git a/dmenu.c b/dmenu.c
index 1fdf7f5..12902ac 100644
--- a/dmenu.c
+++ b/dmenu.c
@@ -6,6 +6,7 @@
6#include <string.h> 6#include <string.h>
7#include <unistd.h> 7#include <unistd.h>
8#include <X11/keysym.h> 8#include <X11/keysym.h>
9#include <X11/Xatom.h>
9#include <X11/Xlib.h> 10#include <X11/Xlib.h>
10#include <X11/Xutil.h> 11#include <X11/Xutil.h>
11#ifdef XINERAMA 12#ifdef XINERAMA
@@ -14,78 +15,72 @@
14#include <draw.h> 15#include <draw.h>
15#include "config.h" 16#include "config.h"
16 17
17#define INRECT(x,y,rx,ry,rw,rh) ((rx) < (x) && (x) < (rx)+(rw) && (ry) < (y) && (y) < (ry)+(rh)) 18#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 MIN(a,b) ((a) < (b) ? (a) : (b))
19#define MAX(a,b) ((a) > (b) ? (a) : (b)) 20#define MAX(a,b) ((a) > (b) ? (a) : (b))
20#define IS_UTF8_1ST_CHAR(c) (((c) & 0xc0) == 0xc0 || ((c) & 0x80) == 0x00) 21#define UTF8_CODEPOINT(c) (((c) & 0xc0) != 0x80)
21 22
22typedef struct Item Item; 23typedef struct Item Item;
23struct Item { 24struct Item {
24 char *text; 25 char *text;
25 Item *next; /* traverses all items */ 26 Item *next; /* traverses all items */
26 Item *left, *right; /* traverses items matching current search pattern */ 27 Item *left, *right; /* traverses matching items */
27}; 28};
28 29
29static void appenditem(Item *i, Item **list, Item **last); 30static void appenditem(Item *item, Item **list, Item **last);
30static void calcoffsetsh(void); 31static void calcoffsetsh(void);
31static void calcoffsetsv(void); 32static void calcoffsetsv(void);
32static char *cistrstr(const char *s, const char *sub); 33static char *cistrstr(const char *s, const char *sub);
33static void cleanup(void);
34static void drawitem(const char *s, unsigned long col[ColLast]);
35static void drawmenu(void); 34static void drawmenu(void);
36static void drawmenuh(void); 35static void drawmenuh(void);
37static void drawmenuv(void); 36static void drawmenuv(void);
38static void grabkeyboard(void); 37static void grabkeyboard(void);
38static void insert(const char *s, ssize_t n);
39static void keypress(XKeyEvent *e); 39static void keypress(XKeyEvent *e);
40static void match(void); 40static void match(void);
41static void paste(Atom atom);
41static void readstdin(void); 42static void readstdin(void);
42static void run(void); 43static void run(void);
43static void setup(void); 44static void setup(void);
45static void usage(void);
44 46
45static char **argp = NULL;
46static char *maxname = NULL;
47static char *prompt; 47static char *prompt;
48static char text[4096]; 48static char text[4096];
49static int promptw;
50static int screen; 49static int screen;
51static size_t cur = 0; 50static size_t cursor = 0;
52static unsigned int cmdw = 0; 51static unsigned int inputw = 0;
53static unsigned int lines = 0; 52static unsigned int lines = 0;
54static unsigned int numlockmask;
55static unsigned int mw, mh; 53static unsigned int mw, mh;
54static unsigned int promptw = 0;
56static unsigned long normcol[ColLast]; 55static unsigned long normcol[ColLast];
57static unsigned long selcol[ColLast]; 56static unsigned long selcol[ColLast];
57static Atom utf8;
58static Bool topbar = True; 58static Bool topbar = True;
59static DC dc; 59static DC dc;
60static Display *dpy; 60static Item *allitems, *matches;
61static Item *allitems = NULL; /* first of all items */ 61static Item *curr, *prev, *next, *sel;
62static Item *item = NULL; /* first of pattern matching items */ 62static Window root, win;
63static Item *sel = NULL;
64static Item *next = NULL;
65static Item *prev = NULL;
66static Item *curr = NULL;
67static Window win, root;
68 63
69static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; 64static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
70static char *(*fstrstr)(const char *, const char *) = strstr; 65static char *(*fstrstr)(const char *, const char *) = strstr;
71static void (*calcoffsets)(void) = calcoffsetsh; 66static void (*calcoffsets)(void) = calcoffsetsh;
72 67
73void 68void
74appenditem(Item *i, Item **list, Item **last) { 69appenditem(Item *item, Item **list, Item **last) {
75 if(!(*last)) 70 if(!(*last))
76 *list = i; 71 *list = item;
77 else 72 else
78 (*last)->right = i; 73 (*last)->right = item;
79 i->left = *last; 74 item->left = *last;
80 i->right = NULL; 75 item->right = NULL;
81 *last = i; 76 *last = item;
82} 77}
83 78
84void 79void
85calcoffsetsh(void) { 80calcoffsetsh(void) {
86 unsigned int w, x; 81 unsigned int w, x;
87 82
88 w = promptw + cmdw + textw(&dc, "<") + textw(&dc, ">"); 83 w = promptw + inputw + textw(&dc, "<") + textw(&dc, ">");
89 for(x = w, next = curr; next; next = next->right) 84 for(x = w, next = curr; next; next = next->right)
90 if((x += MIN(textw(&dc, next->text), mw / 3)) > mw) 85 if((x += MIN(textw(&dc, next->text), mw / 3)) > mw)
91 break; 86 break;
@@ -129,33 +124,6 @@ cistrstr(const char *s, const char *sub) {
129} 124}
130 125
131void 126void
132cleanup(void) {
133 Item *itm;
134
135 while(allitems) {
136 itm = allitems->next;
137 free(allitems->text);
138 free(allitems);
139 allitems = itm;
140 }
141 cleanupdraw(&dc);
142 XDestroyWindow(dpy, win);
143 XUngrabKeyboard(dpy, CurrentTime);
144 XCloseDisplay(dpy);
145}
146
147void
148drawitem(const char *s, unsigned long col[ColLast]) {
149 const char *p;
150 unsigned int w = textnw(&dc, text, strlen(text));
151
152 drawbox(&dc, col);
153 drawtext(&dc, s, col);
154 for(p = fstrstr(s, text); *text && (p = fstrstr(p, text)); p++)
155 drawline(&dc, textnw(&dc, s, p-s) + dc.h/2 - 1, dc.h-2, w, 1, col);
156}
157
158void
159drawmenu(void) { 127drawmenu(void) {
160 dc.x = 0; 128 dc.x = 0;
161 dc.y = 0; 129 dc.y = 0;
@@ -172,82 +140,89 @@ drawmenu(void) {
172 dc.x += dc.w; 140 dc.x += dc.w;
173 } 141 }
174 dc.w = mw - dc.x; 142 dc.w = mw - dc.x;
175 /* print command */ 143 /* print input area */
176 if(cmdw && item && lines == 0) 144 if(matches && lines == 0 && textw(&dc, text) <= inputw)
177 dc.w = cmdw; 145 dc.w = inputw;
178 drawtext(&dc, text, normcol); 146 drawtext(&dc, text, normcol);
179 drawline(&dc, textnw(&dc, text, cur) + dc.h/2 - 2, 2, 1, dc.h-4, normcol); 147 drawline(&dc, textnw(&dc, text, cursor) + dc.h/2 - 2, 2, 1, dc.h-4, normcol);
180 if(lines > 0) 148 if(lines > 0)
181 drawmenuv(); 149 drawmenuv();
182 else if(curr) 150 else if(curr && (dc.w == inputw || curr->next))
183 drawmenuh(); 151 drawmenuh();
184 commitdraw(&dc, win); 152 commitdraw(&dc, win);
185} 153}
186 154
187void 155void
188drawmenuh(void) { 156drawmenuh(void) {
189 Item *i; 157 Item *item;
190 158
191 dc.x += cmdw; 159 dc.x += inputw;
192 dc.w = textw(&dc, "<"); 160 dc.w = textw(&dc, "<");
193 drawtext(&dc, curr->left ? "<" : NULL, normcol); 161 if(curr->left)
162 drawtext(&dc, "<", normcol);
194 dc.x += dc.w; 163 dc.x += dc.w;
195 for(i = curr; i != next; i = i->right) { 164 for(item = curr; item != next; item = item->right) {
196 dc.w = MIN(textw(&dc, i->text), mw / 3); 165 dc.w = MIN(textw(&dc, item->text), mw / 3);
197 drawitem(i->text, (sel == i) ? selcol : normcol); 166 if(item == sel)
167 drawbox(&dc, selcol);
168 drawtext(&dc, item->text, (item == sel) ? selcol : normcol);
198 dc.x += dc.w; 169 dc.x += dc.w;
199 } 170 }
200 dc.w = textw(&dc, ">"); 171 dc.w = textw(&dc, ">");
201 dc.x = mw - dc.w; 172 dc.x = mw - dc.w;
202 drawtext(&dc, next ? ">" : NULL, normcol); 173 if(next)
174 drawtext(&dc, ">", normcol);
203} 175}
204 176
205void 177void
206drawmenuv(void) { 178drawmenuv(void) {
207 Item *i; 179 Item *item;
208 XWindowAttributes wa; 180 XWindowAttributes wa;
209 181
210 dc.y = topbar ? dc.h : 0; 182 dc.y = topbar ? dc.h : 0;
211 dc.w = mw - dc.x; 183 dc.w = mw - dc.x;
212 for(i = curr; i != next; i = i->right) { 184 for(item = curr; item != next; item = item->right) {
213 drawitem(i->text, (sel == i) ? selcol : normcol); 185 if(item == sel)
186 drawbox(&dc, selcol);
187 drawtext(&dc, item->text, (item == sel) ? selcol : normcol);
214 dc.y += dc.h; 188 dc.y += dc.h;
215 } 189 }
216 if(!XGetWindowAttributes(dpy, win, &wa)) 190 if(!XGetWindowAttributes(dc.dpy, win, &wa))
217 eprint("cannot get window attributes"); 191 eprintf("cannot get window attributes\n");
218 XMoveResizeWindow(dpy, win, wa.x, wa.y + (topbar ? 0 : wa.height - mh), mw, mh); 192 if(wa.height != mh)
193 XMoveResizeWindow(dc.dpy, win, wa.x, wa.y + (topbar ? 0 : wa.height - mh), mw, mh);
219} 194}
220 195
221void 196void
222grabkeyboard(void) { 197grabkeyboard(void) {
223 unsigned int n; 198 int i;
224 199
225 for(n = 0; n < 1000; n++) { 200 for(i = 0; i < 1000; i++) {
226 if(!XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime)) 201 if(!XGrabKeyboard(dc.dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime))
227 return; 202 return;
228 usleep(1000); 203 usleep(1000);
229 } 204 }
230 exit(EXIT_FAILURE); 205 eprintf("cannot grab keyboard\n");
206}
207
208void
209insert(const char *s, ssize_t n) {
210 memmove(text + cursor + n, text + cursor, sizeof text - cursor - n);
211 if(n > 0)
212 memcpy(text + cursor, s, n);
213 cursor += n;
214 match();
231} 215}
232 216
233void 217void
234keypress(XKeyEvent *e) { 218keypress(XKeyEvent *e) {
235 char buf[sizeof text]; 219 char buf[sizeof text];
236 int num; 220 int n;
237 unsigned int i, len; 221 size_t len;
238 KeySym ksym; 222 KeySym ksym;
239 223
240 len = strlen(text); 224 len = strlen(text);
241 num = XLookupString(e, buf, sizeof buf, &ksym, NULL); 225 XLookupString(e, buf, sizeof buf, &ksym, NULL);
242 if(ksym == XK_KP_Enter)
243 ksym = XK_Return;
244 else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
245 ksym = (ksym - XK_KP_0) + XK_0;
246 else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
247 || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
248 || IsPrivateKeypadKey(ksym))
249 return;
250 /* first check if a control mask is omitted */
251 if(e->state & ControlMask) { 226 if(e->state & ControlMask) {
252 switch(tolower(ksym)) { 227 switch(tolower(ksym)) {
253 default: 228 default:
@@ -277,8 +252,8 @@ keypress(XKeyEvent *e) {
277 case XK_m: 252 case XK_m:
278 ksym = XK_Return; 253 ksym = XK_Return;
279 break; 254 break;
280 case XK_k: 255 case XK_k: /* delete right */
281 text[cur] = '\0'; 256 text[cursor] = '\0';
282 break; 257 break;
283 case XK_n: 258 case XK_n:
284 ksym = XK_Down; 259 ksym = XK_Down;
@@ -286,66 +261,44 @@ keypress(XKeyEvent *e) {
286 case XK_p: 261 case XK_p:
287 ksym = XK_Up; 262 ksym = XK_Up;
288 break; 263 break;
289 case XK_u: 264 case XK_u: /* delete left */
290 memmove(text, text + cur, sizeof text - cur + 1); 265 insert(NULL, -cursor);
291 cur = 0;
292 match();
293 break; 266 break;
294 case XK_w: 267 case XK_w: /* delete word */
295 if(cur == 0) 268 if(cursor == 0)
296 return; 269 return;
297 i = cur; 270 n = 0;
298 while(i-- > 0 && text[i] == ' '); 271 while(cursor - n++ > 0 && text[cursor - n] == ' ');
299 while(i-- > 0 && text[i] != ' '); 272 while(cursor - n++ > 0 && text[cursor - n] != ' ');
300 memmove(text + i + 1, text + cur, sizeof text - cur + 1); 273 insert(NULL, -(--n));
301 cur = i + 1;
302 match();
303 break;
304 case XK_y:
305 {
306 FILE *fp;
307 char *s;
308 if(!(fp = fopen("sselp", "r")))
309 eprint("cannot popen sselp\n");
310 s = fgets(buf, sizeof buf, fp);
311 fclose(fp);
312 if(!s)
313 return;
314 }
315 num = strlen(buf);
316 if(num && buf[num-1] == '\n')
317 buf[--num] = '\0';
318 break; 274 break;
275 case XK_y: /* paste selection */
276 XConvertSelection(dc.dpy, XA_PRIMARY, utf8, None, win, CurrentTime);
277 /* causes SelectionNotify event */
278 return;
319 } 279 }
320 } 280 }
321 switch(ksym) { 281 switch(ksym) {
322 default: 282 default:
323 num = MIN(num, sizeof text); 283 if(!iscntrl((int)*buf))
324 if(num && !iscntrl((int) buf[0])) { 284 insert(buf, MIN(strlen(buf), sizeof text - cursor));
325 memmove(text + cur + num, text + cur, sizeof text - cur - num);
326 memcpy(text + cur, buf, num);
327 cur += num;
328 match();
329 }
330 break; 285 break;
331 case XK_BackSpace: 286 case XK_BackSpace:
332 if(cur == 0) 287 if(cursor == 0)
333 return; 288 return;
334 for(i = 1; len - i > 0 && !IS_UTF8_1ST_CHAR(text[cur - i]); i++); 289 for(n = 1; cursor - n > 0 && !UTF8_CODEPOINT(text[cursor - n]); n++);
335 memmove(text + cur - i, text + cur, sizeof text - cur + i); 290 insert(NULL, -n);
336 cur -= i;
337 match();
338 break; 291 break;
339 case XK_Delete: 292 case XK_Delete:
340 if(cur == len) 293 if(cursor == len)
341 return; 294 return;
342 for(i = 1; cur + i < len && !IS_UTF8_1ST_CHAR(text[cur + i]); i++); 295 for(n = 1; cursor + n < len && !UTF8_CODEPOINT(text[cursor + n]); n++);
343 memmove(text + cur, text + cur + i, sizeof text - cur); 296 cursor += n;
344 match(); 297 insert(NULL, -n);
345 break; 298 break;
346 case XK_End: 299 case XK_End:
347 if(cur < len) { 300 if(cursor < len) {
348 cur = len; 301 cursor = len;
349 break; 302 break;
350 } 303 }
351 while(next) { 304 while(next) {
@@ -358,19 +311,19 @@ keypress(XKeyEvent *e) {
358 case XK_Escape: 311 case XK_Escape:
359 exit(EXIT_FAILURE); 312 exit(EXIT_FAILURE);
360 case XK_Home: 313 case XK_Home:
361 if(sel == item) { 314 if(sel == matches) {
362 cur = 0; 315 cursor = 0;
363 break; 316 break;
364 } 317 }
365 sel = curr = item; 318 sel = curr = matches;
366 calcoffsets(); 319 calcoffsets();
367 break; 320 break;
368 case XK_Left: 321 case XK_Left:
369 if(cur > 0 && (!sel || !sel->left || lines > 0)) { 322 if(cursor > 0 && (!sel || !sel->left || lines > 0)) {
370 while(cur-- > 0 && !IS_UTF8_1ST_CHAR(text[cur])); 323 while(cursor-- > 0 && !UTF8_CODEPOINT(text[cursor]));
371 break; 324 break;
372 } 325 }
373 if(lines > 0) 326 else if(lines > 0)
374 return; 327 return;
375 case XK_Up: 328 case XK_Up:
376 if(!sel || !sel->left) 329 if(!sel || !sel->left)
@@ -394,15 +347,16 @@ keypress(XKeyEvent *e) {
394 calcoffsets(); 347 calcoffsets();
395 break; 348 break;
396 case XK_Return: 349 case XK_Return:
397 fprintf(stdout, "%s", ((e->state & ShiftMask) || sel) ? sel->text : text); 350 case XK_KP_Enter:
351 fputs(((e->state & ShiftMask) || sel) ? sel->text : text, stdout);
398 fflush(stdout); 352 fflush(stdout);
399 exit(EXIT_SUCCESS); 353 exit(EXIT_SUCCESS);
400 case XK_Right: 354 case XK_Right:
401 if(cur < len) { 355 if(cursor < len) {
402 while(cur++ < len && !IS_UTF8_1ST_CHAR(text[cur])); 356 while(cursor++ < len && !UTF8_CODEPOINT(text[cursor]));
403 break; 357 break;
404 } 358 }
405 if(lines > 0) 359 else if(lines > 0)
406 return; 360 return;
407 case XK_Down: 361 case XK_Down:
408 if(!sel || !sel->right) 362 if(!sel || !sel->right)
@@ -417,7 +371,7 @@ keypress(XKeyEvent *e) {
417 if(!sel) 371 if(!sel)
418 return; 372 return;
419 strncpy(text, sel->text, sizeof text); 373 strncpy(text, sel->text, sizeof text);
420 cur = strlen(text); 374 cursor = strlen(text);
421 match(); 375 match();
422 break; 376 break;
423 } 377 }
@@ -427,19 +381,19 @@ keypress(XKeyEvent *e) {
427void 381void
428match(void) { 382match(void) {
429 unsigned int len; 383 unsigned int len;
430 Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend; 384 Item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
431 385
432 len = strlen(text); 386 len = strlen(text);
433 item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL; 387 matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
434 for(i = allitems; i; i = i->next) 388 for(item = allitems; item; item = item->next)
435 if(!fstrncmp(text, i->text, len + 1)) 389 if(!fstrncmp(text, item->text, len + 1))
436 appenditem(i, &lexact, &exactend); 390 appenditem(item, &lexact, &exactend);
437 else if(!fstrncmp(text, i->text, len)) 391 else if(!fstrncmp(text, item->text, len))
438 appenditem(i, &lprefix, &prefixend); 392 appenditem(item, &lprefix, &prefixend);
439 else if(fstrstr(i->text, text)) 393 else if(fstrstr(item->text, text))
440 appenditem(i, &lsubstr, &substrend); 394 appenditem(item, &lsubstr, &substrend);
441 if(lexact) { 395 if(lexact) {
442 item = lexact; 396 matches = lexact;
443 itemend = exactend; 397 itemend = exactend;
444 } 398 }
445 if(lprefix) { 399 if(lprefix) {
@@ -448,7 +402,7 @@ match(void) {
448 lprefix->left = itemend; 402 lprefix->left = itemend;
449 } 403 }
450 else 404 else
451 item = lprefix; 405 matches = lprefix;
452 itemend = prefixend; 406 itemend = prefixend;
453 } 407 }
454 if(lsubstr) { 408 if(lsubstr) {
@@ -457,36 +411,48 @@ match(void) {
457 lsubstr->left = itemend; 411 lsubstr->left = itemend;
458 } 412 }
459 else 413 else
460 item = lsubstr; 414 matches = lsubstr;
461 } 415 }
462 curr = prev = next = sel = item; 416 curr = prev = next = sel = matches;
463 calcoffsets(); 417 calcoffsets();
464} 418}
465 419
466void 420void
421paste(Atom atom)
422{
423 char *p, *q;
424 int di;
425 unsigned long dl;
426 Atom da;
427
428 XGetWindowProperty(dc.dpy, win, atom, 0, sizeof text - cursor, True,
429 utf8, &da, &di, &dl, &dl, (unsigned char **)&p);
430 insert(p, (q = strchr(p, '\n')) ? q-p : strlen(p));
431 XFree(p);
432 drawmenu();
433}
434
435void
467readstdin(void) { 436readstdin(void) {
468 char *p, buf[sizeof text]; 437 char buf[sizeof text];
469 unsigned int len = 0, max = 0; 438 size_t len;
470 Item *i, *new; 439 Item *item, *new;
471 440
472 i = NULL; 441 allitems = NULL;
473 while(fgets(buf, sizeof buf, stdin)) { 442 for(item = NULL; fgets(buf, sizeof buf, stdin); item = new) {
474 len = strlen(buf); 443 len = strlen(buf);
475 if(buf[len-1] == '\n') 444 if(buf[len-1] == '\n')
476 buf[--len] = '\0'; 445 buf[--len] = '\0';
477 if(!(p = strdup(buf)))
478 eprint("cannot strdup %u bytes\n", len);
479 if((max = MAX(max, len)) == len)
480 maxname = p;
481 if(!(new = malloc(sizeof *new))) 446 if(!(new = malloc(sizeof *new)))
482 eprint("cannot malloc %u bytes\n", sizeof *new); 447 eprintf("cannot malloc %u bytes\n", sizeof *new);
448 if(!(new->text = strdup(buf)))
449 eprintf("cannot strdup %u bytes\n", len);
450 inputw = MAX(inputw, textw(&dc, new->text));
483 new->next = new->left = new->right = NULL; 451 new->next = new->left = new->right = NULL;
484 new->text = p; 452 if(item)
485 if(!i) 453 item->next = new;
486 allitems = new;
487 else 454 else
488 i->next = new; 455 allitems = new;
489 i = new;
490 } 456 }
491} 457}
492 458
@@ -494,8 +460,7 @@ void
494run(void) { 460run(void) {
495 XEvent ev; 461 XEvent ev;
496 462
497 XSync(dpy, False); 463 while(!XNextEvent(dc.dpy, &ev))
498 while(!XNextEvent(dpy, &ev))
499 switch(ev.type) { 464 switch(ev.type) {
500 case Expose: 465 case Expose:
501 if(ev.xexpose.count == 0) 466 if(ev.xexpose.count == 0)
@@ -504,62 +469,45 @@ run(void) {
504 case KeyPress: 469 case KeyPress:
505 keypress(&ev.xkey); 470 keypress(&ev.xkey);
506 break; 471 break;
472 case SelectionNotify:
473 if(ev.xselection.property != None)
474 paste(ev.xselection.property);
475 break;
507 case VisibilityNotify: 476 case VisibilityNotify:
508 if(ev.xvisibility.state != VisibilityUnobscured) 477 if(ev.xvisibility.state != VisibilityUnobscured)
509 XRaiseWindow(dpy, win); 478 XRaiseWindow(dc.dpy, win);
510 break; 479 break;
511 } 480 }
512 exit(EXIT_FAILURE);
513} 481}
514 482
515void 483void
516setup(void) { 484setup(void) {
517 int i, j, x, y; 485 int x, y;
518#if XINERAMA 486#ifdef XINERAMA
519 int n; 487 int i, n;
520 XineramaScreenInfo *info = NULL; 488 XineramaScreenInfo *info;
521#endif 489#endif
522 XModifierKeymap *modmap;
523 XSetWindowAttributes wa; 490 XSetWindowAttributes wa;
524 491
525 /* init modifier map */
526 modmap = XGetModifierMapping(dpy);
527 for(i = 0; i < 8; i++)
528 for(j = 0; j < modmap->max_keypermod; j++) {
529 if(modmap->modifiermap[i * modmap->max_keypermod + j]
530 == XKeysymToKeycode(dpy, XK_Num_Lock))
531 numlockmask = (1 << i);
532 }
533 XFreeModifiermap(modmap);
534
535 dc.dpy = dpy;
536 normcol[ColBG] = getcolor(&dc, normbgcolor); 492 normcol[ColBG] = getcolor(&dc, normbgcolor);
537 normcol[ColFG] = getcolor(&dc, normfgcolor); 493 normcol[ColFG] = getcolor(&dc, normfgcolor);
538 selcol[ColBG] = getcolor(&dc, selbgcolor); 494 selcol[ColBG] = getcolor(&dc, selbgcolor);
539 selcol[ColFG] = getcolor(&dc, selfgcolor); 495 selcol[ColFG] = getcolor(&dc, selfgcolor);
540 initfont(&dc, font);
541
542 /* input window */
543 wa.override_redirect = True;
544 wa.background_pixmap = ParentRelative;
545 wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
546 496
547 /* input window geometry */ 497 /* input window geometry */
548 mh = (dc.font.height + 2) * (lines + 1); 498 mh = (dc.font.height + 2) * (lines + 1);
549#if XINERAMA 499#ifdef XINERAMA
550 if(XineramaIsActive(dpy) && (info = XineramaQueryScreens(dpy, &n))) { 500 if((info = XineramaQueryScreens(dc.dpy, &n))) {
551 i = 0; 501 int di;
552 if(n > 1) { 502 unsigned int du;
553 int di; 503 Window dw;
554 unsigned int dui; 504
555 Window dummy; 505 XQueryPointer(dc.dpy, root, &dw, &dw, &x, &y, &di, &di, &du);
556 if(XQueryPointer(dpy, root, &dummy, &dummy, &x, &y, &di, &di, &dui)) 506 for(i = 0; i < n; i++)
557 for(i = 0; i < n; i++) 507 if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
558 if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height)) 508 break;
559 break;
560 }
561 x = info[i].x_org; 509 x = info[i].x_org;
562 y = topbar ? info[i].y_org : info[i].y_org + info[i].height - mh; 510 y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
563 mw = info[i].width; 511 mw = info[i].width;
564 XFree(info); 512 XFree(info);
565 } 513 }
@@ -567,84 +515,86 @@ setup(void) {
567#endif 515#endif
568 { 516 {
569 x = 0; 517 x = 0;
570 y = topbar ? 0 : DisplayHeight(dpy, screen) - mh; 518 y = topbar ? 0 : DisplayHeight(dc.dpy, screen) - mh;
571 mw = DisplayWidth(dpy, screen); 519 mw = DisplayWidth(dc.dpy, screen);
572 } 520 }
573 521
574 win = XCreateWindow(dpy, root, x, y, mw, mh, 0, 522 /* input window */
575 DefaultDepth(dpy, screen), CopyFromParent, 523 wa.override_redirect = True;
576 DefaultVisual(dpy, screen), 524 wa.background_pixmap = ParentRelative;
525 wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
526 win = XCreateWindow(dc.dpy, root, x, y, mw, mh, 0,
527 DefaultDepth(dc.dpy, screen), CopyFromParent,
528 DefaultVisual(dc.dpy, screen),
577 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); 529 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
578 530
531 match();
532 grabkeyboard();
579 setupdraw(&dc, win); 533 setupdraw(&dc, win);
580 if(prompt) 534 inputw = MIN(inputw, mw / 3);
581 promptw = MIN(textw(&dc, prompt), mw / 5); 535 utf8 = XInternAtom(dc.dpy, "UTF8_STRING", False);
582 XMapRaised(dpy, win); 536 XMapRaised(dc.dpy, win);
537}
538
539void
540usage(void) {
541 fputs("usage: dmenu [-b] [-i] [-l <lines>] [-p <prompt>] [-fn <font>] [-nb <color>]\n"
542 " [-nf <color>] [-sb <color>] [-sf <color>] [-v]\n", stderr);
543 exit(EXIT_FAILURE);
583} 544}
584 545
585int 546int
586main(int argc, char *argv[]) { 547main(int argc, char *argv[]) {
587 unsigned int i; 548 int i;
588 549
589 /* command line args */
590 progname = "dmenu"; 550 progname = "dmenu";
591 for(i = 1; i < argc; i++) 551 for(i = 1; i < argc; i++)
592 if(!strcmp(argv[i], "-i")) { 552 /* 1-arg flags */
593 fstrncmp = strncasecmp; 553 if(!strcmp(argv[i], "-v")) {
594 fstrstr = cistrstr; 554 fputs("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n", stdout);
555 exit(EXIT_SUCCESS);
595 } 556 }
596 else if(!strcmp(argv[i], "-b")) 557 else if(!strcmp(argv[i], "-b"))
597 topbar = False; 558 topbar = False;
559 else if(!strcmp(argv[i], "-i")) {
560 fstrncmp = strncasecmp;
561 fstrstr = cistrstr;
562 }
563 else if(i == argc-1)
564 usage();
565 /* 2-arg flags */
598 else if(!strcmp(argv[i], "-l")) { 566 else if(!strcmp(argv[i], "-l")) {
599 if(++i < argc) lines = atoi(argv[i]); 567 if((lines = atoi(argv[++i])) > 0)
600 if(lines > 0)
601 calcoffsets = calcoffsetsv; 568 calcoffsets = calcoffsetsv;
602 } 569 }
603 else if(!strcmp(argv[i], "-fn")) {
604 if(++i < argc) font = argv[i];
605 }
606 else if(!strcmp(argv[i], "-nb")) {
607 if(++i < argc) normbgcolor = argv[i];
608 }
609 else if(!strcmp(argv[i], "-nf")) {
610 if(++i < argc) normfgcolor = argv[i];
611 }
612 else if(!strcmp(argv[i], "-p")) { 570 else if(!strcmp(argv[i], "-p")) {
613 if(++i < argc) prompt = argv[i]; 571 prompt = argv[++i];
614 } 572 promptw = MIN(textw(&dc, prompt), mw/5);
615 else if(!strcmp(argv[i], "-sb")) {
616 if(++i < argc) selbgcolor = argv[i];
617 }
618 else if(!strcmp(argv[i], "-sf")) {
619 if(++i < argc) selfgcolor = argv[i];
620 }
621 else if(!strcmp(argv[i], "-v")) {
622 printf("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n");
623 exit(EXIT_SUCCESS);
624 }
625 else {
626 fputs("usage: dmenu [-i] [-b] [-l <lines>] [-fn <font>] [-nb <color>]\n"
627 " [-nf <color>] [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n", stderr);
628 exit(EXIT_FAILURE);
629 } 573 }
574 else if(!strcmp(argv[i], "-fn"))
575 font = argv[++i];
576 else if(!strcmp(argv[i], "-nb"))
577 normbgcolor = argv[++i];
578 else if(!strcmp(argv[i], "-nf"))
579 normfgcolor = argv[++i];
580 else if(!strcmp(argv[i], "-sb"))
581 selbgcolor = argv[++i];
582 else if(!strcmp(argv[i], "-sf"))
583 selfgcolor = argv[++i];
584 else
585 usage();
586
630 if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 587 if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
631 fprintf(stderr, "dmenu: warning: no locale support\n"); 588 fputs("dmenu: warning: no locale support\n", stderr);
632 if(!(dpy = XOpenDisplay(NULL))) 589 if(!(dc.dpy = XOpenDisplay(NULL)))
633 eprint("cannot open display\n"); 590 eprintf("cannot open display\n");
634 if(atexit(&cleanup) != 0) 591 screen = DefaultScreen(dc.dpy);
635 eprint("cannot register cleanup\n"); 592 root = RootWindow(dc.dpy, screen);
636 screen = DefaultScreen(dpy); 593 initfont(&dc, font);
637 root = RootWindow(dpy, screen);
638 if(!(argp = malloc(sizeof *argp * (argc+2))))
639 eprint("cannot malloc %u bytes\n", sizeof *argp * (argc+2));
640 memcpy(argp + 2, argv + 1, sizeof *argp * argc);
641 594
642 readstdin(); 595 readstdin();
643 grabkeyboard();
644 setup(); 596 setup();
645 if(maxname)
646 cmdw = MIN(textw(&dc, maxname), mw / 3);
647 match();
648 run(); 597 run();
649 return 0; 598
599 return EXIT_FAILURE; /* should not reach */
650} 600}