Xinqi Bao's Git

608023daf38a005834be2e3f44f16dff09b036b3
[dmenu.git] / dmenu.c
1 /* See LICENSE file for copyright and license details. */
2 #include <ctype.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <X11/Xatom.h>
8 #include <X11/Xlib.h>
9 #include <X11/Xutil.h>
10 #ifdef XINERAMA
11 #include <X11/extensions/Xinerama.h>
12 #endif
13 #include <draw.h>
14
15 #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
16 #define MIN(a,b) ((a) < (b) ? (a) : (b))
17 #define MAX(a,b) ((a) > (b) ? (a) : (b))
18 #define UTF8_CODEPOINT(c) (((c) & 0xc0) != 0x80)
19
20 typedef struct Item Item;
21 struct Item {
22 char *text;
23 Item *next; /* traverses all items */
24 Item *left, *right; /* traverses matching items */
25 };
26
27 static void appenditem(Item *item, Item **list, Item **last);
28 static void calcoffsetsh(void);
29 static void calcoffsetsv(void);
30 static char *cistrstr(const char *s, const char *sub);
31 static void drawmenu(void);
32 static void drawmenuh(void);
33 static void drawmenuv(void);
34 static void grabkeyboard(void);
35 static void insert(const char *s, ssize_t n);
36 static void keypress(XKeyEvent *e);
37 static void match(void);
38 static void paste(void);
39 static void readstdin(void);
40 static void run(void);
41 static void setup(void);
42 static void usage(void);
43
44 static char text[4096];
45 static int promptw;
46 static size_t cursor = 0;
47 static const char *prompt = NULL;
48 static const char *normbgcolor = "#cccccc";
49 static const char *normfgcolor = "#000000";
50 static const char *selbgcolor = "#0066ff";
51 static const char *selfgcolor = "#ffffff";
52 static unsigned int inputw = 0;
53 static unsigned int lines = 0;
54 static unsigned int mw, mh;
55 static unsigned long normcol[ColLast];
56 static unsigned long selcol[ColLast];
57 static Atom utf8;
58 static Bool topbar = True;
59 static DC *dc;
60 static Item *allitems, *matches;
61 static Item *curr, *prev, *next, *sel;
62 static Window root, win;
63
64 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
65 static char *(*fstrstr)(const char *, const char *) = strstr;
66 static void (*calcoffsets)(void) = calcoffsetsh;
67
68 void
69 appenditem(Item *item, Item **list, Item **last) {
70 if(!*last)
71 *list = item;
72 else
73 (*last)->right = item;
74 item->left = *last;
75 item->right = NULL;
76 *last = item;
77 }
78
79 void
80 calcoffsetsh(void) {
81 unsigned int w, x;
82
83 w = promptw + inputw + textw(dc, "<") + textw(dc, ">");
84 for(x = w, next = curr; next; next = next->right)
85 if((x += MIN(textw(dc, next->text), mw / 3)) > mw)
86 break;
87 for(x = w, prev = curr; prev && prev->left; prev = prev->left)
88 if((x += MIN(textw(dc, prev->left->text), mw / 3)) > mw)
89 break;
90 }
91
92 void
93 calcoffsetsv(void) {
94 unsigned int i;
95
96 next = prev = curr;
97 for(i = 0; i < lines && next; i++)
98 next = next->right;
99 for(i = 0; i < lines && prev && prev->left; i++)
100 prev = prev->left;
101 }
102
103 char *
104 cistrstr(const char *s, const char *sub) {
105 size_t len;
106
107 for(len = strlen(sub); *s; s++)
108 if(!strncasecmp(s, sub, len))
109 return (char *)s;
110 return NULL;
111 }
112
113 void
114 drawmenu(void) {
115 dc->x = 0;
116 dc->y = 0;
117 drawrect(dc, 0, 0, mw, mh, BG(dc, normcol));
118 dc->h = dc->font.height + 2;
119 dc->y = topbar ? 0 : mh - dc->h;
120 /* print prompt? */
121 if(prompt) {
122 dc->w = promptw;
123 drawtext(dc, prompt, selcol);
124 dc->x = dc->w;
125 }
126 dc->w = mw - dc->x;
127 /* print input area */
128 if(matches && lines == 0 && textw(dc, text) <= inputw)
129 dc->w = inputw;
130 drawtext(dc, text, normcol);
131 drawrect(dc, textnw(dc, text, cursor) + dc->h/2 - 2, 2, 1, dc->h - 4, FG(dc, normcol));
132 if(lines > 0)
133 drawmenuv();
134 else if(curr && (dc->w == inputw || curr->next))
135 drawmenuh();
136 commitdraw(dc, win);
137 }
138
139 void
140 drawmenuh(void) {
141 Item *item;
142
143 dc->x += inputw;
144 dc->w = textw(dc, "<");
145 if(curr->left)
146 drawtext(dc, "<", normcol);
147 for(item = curr; item != next; item = item->right) {
148 dc->x += dc->w;
149 dc->w = MIN(textw(dc, item->text), mw / 3);
150 drawtext(dc, item->text, (item == sel) ? selcol : normcol);
151 }
152 dc->w = textw(dc, ">");
153 dc->x = mw - dc->w;
154 if(next)
155 drawtext(dc, ">", normcol);
156 }
157
158 void
159 drawmenuv(void) {
160 Item *item;
161
162 dc->y = topbar ? dc->h : 0;
163 dc->w = mw - dc->x;
164 for(item = curr; item != next; item = item->right) {
165 drawtext(dc, item->text, (item == sel) ? selcol : normcol);
166 dc->y += dc->h;
167 }
168 }
169
170 void
171 grabkeyboard(void) {
172 int i;
173
174 for(i = 0; i < 1000; i++) {
175 if(!XGrabKeyboard(dc->dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime))
176 return;
177 usleep(1000);
178 }
179 eprintf("cannot grab keyboard\n");
180 }
181
182 void
183 insert(const char *s, ssize_t n) {
184 memmove(text + cursor + n, text + cursor, sizeof text - cursor - n);
185 if(n > 0)
186 memcpy(text + cursor, s, n);
187 cursor += n;
188 match();
189 }
190
191 void
192 keypress(XKeyEvent *e) {
193 char buf[sizeof text];
194 int n;
195 size_t len;
196 KeySym ksym;
197
198 len = strlen(text);
199 XLookupString(e, buf, sizeof buf, &ksym, NULL);
200 if(e->state & ControlMask) {
201 switch(tolower(ksym)) {
202 default:
203 return;
204 case XK_a:
205 ksym = XK_Home;
206 break;
207 case XK_b:
208 ksym = XK_Left;
209 break;
210 case XK_c:
211 ksym = XK_Escape;
212 break;
213 case XK_e:
214 ksym = XK_End;
215 break;
216 case XK_f:
217 ksym = XK_Right;
218 break;
219 case XK_h:
220 ksym = XK_BackSpace;
221 break;
222 case XK_i:
223 ksym = XK_Tab;
224 break;
225 case XK_j:
226 case XK_m:
227 ksym = XK_Return;
228 break;
229 case XK_k: /* delete right */
230 text[cursor] = '\0';
231 match();
232 break;
233 case XK_n:
234 ksym = XK_Down;
235 break;
236 case XK_p:
237 ksym = XK_Up;
238 break;
239 case XK_u: /* delete left */
240 insert(NULL, -cursor);
241 break;
242 case XK_w: /* delete word */
243 if(cursor == 0)
244 return;
245 n = 0;
246 while(cursor - n++ > 0 && text[cursor - n] == ' ');
247 while(cursor - n++ > 0 && text[cursor - n] != ' ');
248 insert(NULL, 1-n);
249 break;
250 case XK_y: /* paste selection */
251 XConvertSelection(dc->dpy, XA_PRIMARY, utf8, None, win, CurrentTime);
252 /* causes SelectionNotify event */
253 return;
254 }
255 }
256 switch(ksym) {
257 default:
258 if(!iscntrl((int)*buf))
259 insert(buf, MIN(strlen(buf), sizeof text - cursor));
260 break;
261 case XK_BackSpace:
262 if(cursor == 0)
263 return;
264 for(n = 1; cursor - n > 0 && !UTF8_CODEPOINT(text[cursor - n]); n++);
265 insert(NULL, -n);
266 break;
267 case XK_Delete:
268 if(cursor == len)
269 return;
270 for(n = 1; cursor + n < len && !UTF8_CODEPOINT(text[cursor + n]); n++);
271 cursor += n;
272 insert(NULL, -n);
273 break;
274 case XK_End:
275 if(cursor < len) {
276 cursor = len;
277 break;
278 }
279 while(next) {
280 sel = curr = next;
281 calcoffsets();
282 }
283 while(sel && sel->right)
284 sel = sel->right;
285 break;
286 case XK_Escape:
287 exit(EXIT_FAILURE);
288 case XK_Home:
289 if(sel == matches) {
290 cursor = 0;
291 break;
292 }
293 sel = curr = matches;
294 calcoffsets();
295 break;
296 case XK_Left:
297 if(cursor > 0 && (!sel || !sel->left || lines > 0)) {
298 while(cursor-- > 0 && !UTF8_CODEPOINT(text[cursor]));
299 break;
300 }
301 else if(lines > 0)
302 return;
303 case XK_Up:
304 if(!sel || !sel->left)
305 return;
306 sel = sel->left;
307 if(sel->right == curr) {
308 curr = prev;
309 calcoffsets();
310 }
311 break;
312 case XK_Next:
313 if(!next)
314 return;
315 sel = curr = next;
316 calcoffsets();
317 break;
318 case XK_Prior:
319 if(!prev)
320 return;
321 sel = curr = prev;
322 calcoffsets();
323 break;
324 case XK_Return:
325 case XK_KP_Enter:
326 fputs((sel && !(e->state & ShiftMask)) ? sel->text : text, stdout);
327 fflush(stdout);
328 exit(EXIT_SUCCESS);
329 case XK_Right:
330 if(cursor < len) {
331 while(cursor++ < len && !UTF8_CODEPOINT(text[cursor]));
332 break;
333 }
334 else if(lines > 0)
335 return;
336 case XK_Down:
337 if(!sel || !sel->right)
338 return;
339 sel = sel->right;
340 if(sel == next) {
341 curr = next;
342 calcoffsets();
343 }
344 break;
345 case XK_Tab:
346 if(!sel)
347 return;
348 strncpy(text, sel->text, sizeof text);
349 cursor = strlen(text);
350 match();
351 break;
352 }
353 drawmenu();
354 }
355
356 void
357 match(void) {
358 unsigned int len;
359 Item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
360
361 len = strlen(text);
362 matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
363 for(item = allitems; item; item = item->next)
364 if(!fstrncmp(text, item->text, len + 1))
365 appenditem(item, &lexact, &exactend);
366 else if(!fstrncmp(text, item->text, len))
367 appenditem(item, &lprefix, &prefixend);
368 else if(fstrstr(item->text, text))
369 appenditem(item, &lsubstr, &substrend);
370 if(lexact) {
371 matches = lexact;
372 itemend = exactend;
373 }
374 if(lprefix) {
375 if(itemend) {
376 itemend->right = lprefix;
377 lprefix->left = itemend;
378 }
379 else
380 matches = lprefix;
381 itemend = prefixend;
382 }
383 if(lsubstr) {
384 if(itemend) {
385 itemend->right = lsubstr;
386 lsubstr->left = itemend;
387 }
388 else
389 matches = lsubstr;
390 }
391 curr = prev = next = sel = matches;
392 calcoffsets();
393 }
394
395 void
396 paste(void) {
397 char *p, *q;
398 int di;
399 unsigned long dl;
400 Atom da;
401
402 XGetWindowProperty(dc->dpy, win, utf8, 0, sizeof text - cursor, True,
403 utf8, &da, &di, &dl, &dl, (unsigned char **)&p);
404 insert(p, (q = strchr(p, '\n')) ? q-p : strlen(p));
405 XFree(p);
406 drawmenu();
407 }
408
409 void
410 readstdin(void) {
411 char buf[sizeof text], *p;
412 Item *item, *new;
413
414 allitems = NULL;
415 for(item = NULL; fgets(buf, sizeof buf, stdin); item = new) {
416 if((p = strchr(buf, '\n')))
417 *p = '\0';
418 if(!(new = malloc(sizeof *new)))
419 eprintf("cannot malloc %u bytes\n", sizeof *new);
420 if(!(new->text = strdup(buf)))
421 eprintf("cannot strdup %u bytes\n", strlen(buf));
422 inputw = MAX(inputw, textw(dc, new->text));
423 new->next = new->left = new->right = NULL;
424 if(item)
425 item->next = new;
426 else
427 allitems = new;
428 }
429 }
430
431 void
432 run(void) {
433 XEvent ev;
434
435 while(!XNextEvent(dc->dpy, &ev))
436 switch(ev.type) {
437 case Expose:
438 if(ev.xexpose.count == 0)
439 drawmenu();
440 break;
441 case KeyPress:
442 keypress(&ev.xkey);
443 break;
444 case SelectionNotify:
445 if(ev.xselection.property == utf8)
446 paste();
447 break;
448 case VisibilityNotify:
449 if(ev.xvisibility.state != VisibilityUnobscured)
450 XRaiseWindow(dc->dpy, win);
451 break;
452 }
453 }
454
455 void
456 setup(void) {
457 int x, y, screen;
458 XSetWindowAttributes wa;
459 #ifdef XINERAMA
460 int n;
461 XineramaScreenInfo *info;
462 #endif
463
464 screen = DefaultScreen(dc->dpy);
465 root = RootWindow(dc->dpy, screen);
466 utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False);
467
468 normcol[ColBG] = getcolor(dc, normbgcolor);
469 normcol[ColFG] = getcolor(dc, normfgcolor);
470 selcol[ColBG] = getcolor(dc, selbgcolor);
471 selcol[ColFG] = getcolor(dc, selfgcolor);
472
473 /* input window geometry */
474 mh = (dc->font.height + 2) * (lines + 1);
475 #ifdef XINERAMA
476 if((info = XineramaQueryScreens(dc->dpy, &n))) {
477 int i, di;
478 unsigned int du;
479 Window dw;
480
481 XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du);
482 for(i = 0; i < n; i++)
483 if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
484 break;
485 x = info[i].x_org;
486 y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
487 mw = info[i].width;
488 XFree(info);
489 }
490 else
491 #endif
492 {
493 x = 0;
494 y = topbar ? 0 : DisplayHeight(dc->dpy, screen) - mh;
495 mw = DisplayWidth(dc->dpy, screen);
496 }
497
498 /* input window */
499 wa.override_redirect = True;
500 wa.background_pixmap = ParentRelative;
501 wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
502 win = XCreateWindow(dc->dpy, root, x, y, mw, mh, 0,
503 DefaultDepth(dc->dpy, screen), CopyFromParent,
504 DefaultVisual(dc->dpy, screen),
505 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
506
507 grabkeyboard();
508 setcanvas(dc, win, mw, mh);
509 inputw = MIN(inputw, mw/3);
510 promptw = prompt ? MIN(textw(dc, prompt), mw/5) : 0;
511 XMapRaised(dc->dpy, win);
512 text[0] = '\0';
513 match();
514 }
515
516 void
517 usage(void) {
518 fputs("usage: dmenu [-b] [-i] [-l lines] [-p prompt] [-fn font] [-nb color]\n"
519 " [-nf color] [-sb color] [-sf color] [-v]\n", stderr);
520 exit(EXIT_FAILURE);
521 }
522
523 int
524 main(int argc, char *argv[]) {
525 int i;
526
527 progname = "dmenu";
528 dc = initdraw();
529
530 for(i = 1; i < argc; i++)
531 /* single flags */
532 if(!strcmp(argv[i], "-v")) {
533 fputs("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n", stdout);
534 exit(EXIT_SUCCESS);
535 }
536 else if(!strcmp(argv[i], "-b"))
537 topbar = False;
538 else if(!strcmp(argv[i], "-i")) {
539 fstrncmp = strncasecmp;
540 fstrstr = cistrstr;
541 }
542 else if(i == argc-1)
543 usage();
544 /* double flags */
545 else if(!strcmp(argv[i], "-l")) {
546 if((lines = atoi(argv[++i])) > 0)
547 calcoffsets = calcoffsetsv;
548 }
549 else if(!strcmp(argv[i], "-p"))
550 prompt = argv[++i];
551 else if(!strcmp(argv[i], "-fn"))
552 initfont(dc, argv[i++]);
553 else if(!strcmp(argv[i], "-nb"))
554 normbgcolor = argv[++i];
555 else if(!strcmp(argv[i], "-nf"))
556 normfgcolor = argv[++i];
557 else if(!strcmp(argv[i], "-sb"))
558 selbgcolor = argv[++i];
559 else if(!strcmp(argv[i], "-sf"))
560 selfgcolor = argv[++i];
561 else
562 usage();
563
564 readstdin();
565 setup();
566 run();
567
568 return EXIT_FAILURE; /* should not reach */
569 }