Xinqi Bao's Git

1fdf7f5a20158c733f6c2abc4b8cba1d973c1693
[dmenu.git] / dmenu.c
1 /* See LICENSE file for copyright and license details. */
2 #include <ctype.h>
3 #include <locale.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <X11/keysym.h>
9 #include <X11/Xlib.h>
10 #include <X11/Xutil.h>
11 #ifdef XINERAMA
12 #include <X11/extensions/Xinerama.h>
13 #endif
14 #include <draw.h>
15 #include "config.h"
16
17 #define INRECT(x,y,rx,ry,rw,rh) ((rx) < (x) && (x) < (rx)+(rw) && (ry) < (y) && (y) < (ry)+(rh))
18 #define MIN(a,b) ((a) < (b) ? (a) : (b))
19 #define MAX(a,b) ((a) > (b) ? (a) : (b))
20 #define IS_UTF8_1ST_CHAR(c) (((c) & 0xc0) == 0xc0 || ((c) & 0x80) == 0x00)
21
22 typedef struct Item Item;
23 struct Item {
24 char *text;
25 Item *next; /* traverses all items */
26 Item *left, *right; /* traverses items matching current search pattern */
27 };
28
29 static void appenditem(Item *i, Item **list, Item **last);
30 static void calcoffsetsh(void);
31 static void calcoffsetsv(void);
32 static char *cistrstr(const char *s, const char *sub);
33 static void cleanup(void);
34 static void drawitem(const char *s, unsigned long col[ColLast]);
35 static void drawmenu(void);
36 static void drawmenuh(void);
37 static void drawmenuv(void);
38 static void grabkeyboard(void);
39 static void keypress(XKeyEvent *e);
40 static void match(void);
41 static void readstdin(void);
42 static void run(void);
43 static void setup(void);
44
45 static char **argp = NULL;
46 static char *maxname = NULL;
47 static char *prompt;
48 static char text[4096];
49 static int promptw;
50 static int screen;
51 static size_t cur = 0;
52 static unsigned int cmdw = 0;
53 static unsigned int lines = 0;
54 static unsigned int numlockmask;
55 static unsigned int mw, mh;
56 static unsigned long normcol[ColLast];
57 static unsigned long selcol[ColLast];
58 static Bool topbar = True;
59 static DC dc;
60 static Display *dpy;
61 static Item *allitems = NULL; /* first of all items */
62 static Item *item = NULL; /* first of pattern matching items */
63 static Item *sel = NULL;
64 static Item *next = NULL;
65 static Item *prev = NULL;
66 static Item *curr = NULL;
67 static Window win, root;
68
69 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
70 static char *(*fstrstr)(const char *, const char *) = strstr;
71 static void (*calcoffsets)(void) = calcoffsetsh;
72
73 void
74 appenditem(Item *i, Item **list, Item **last) {
75 if(!(*last))
76 *list = i;
77 else
78 (*last)->right = i;
79 i->left = *last;
80 i->right = NULL;
81 *last = i;
82 }
83
84 void
85 calcoffsetsh(void) {
86 unsigned int w, x;
87
88 w = promptw + cmdw + textw(&dc, "<") + textw(&dc, ">");
89 for(x = w, next = curr; next; next = next->right)
90 if((x += MIN(textw(&dc, next->text), mw / 3)) > mw)
91 break;
92 for(x = w, prev = curr; prev && prev->left; prev = prev->left)
93 if((x += MIN(textw(&dc, prev->left->text), mw / 3)) > mw)
94 break;
95 }
96
97 void
98 calcoffsetsv(void) {
99 unsigned int i;
100
101 next = prev = curr;
102 for(i = 0; i < lines && next; i++)
103 next = next->right;
104 mh = (dc.font.height + 2) * (i + 1);
105 for(i = 0; i < lines && prev && prev->left; i++)
106 prev = prev->left;
107 }
108
109 char *
110 cistrstr(const char *s, const char *sub) {
111 int c, csub;
112 unsigned int len;
113
114 if(!sub)
115 return (char *)s;
116 if((c = tolower(*sub++)) != '\0') {
117 len = strlen(sub);
118 do {
119 do {
120 if((csub = *s++) == '\0')
121 return NULL;
122 }
123 while(tolower(csub) != c);
124 }
125 while(strncasecmp(s, sub, len) != 0);
126 s--;
127 }
128 return (char *)s;
129 }
130
131 void
132 cleanup(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
147 void
148 drawitem(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
158 void
159 drawmenu(void) {
160 dc.x = 0;
161 dc.y = 0;
162 dc.w = mw;
163 dc.h = mh;
164 drawbox(&dc, normcol);
165 dc.h = dc.font.height + 2;
166 dc.y = topbar ? 0 : mh - dc.h;
167 /* print prompt? */
168 if(prompt) {
169 dc.w = promptw;
170 drawbox(&dc, selcol);
171 drawtext(&dc, prompt, selcol);
172 dc.x += dc.w;
173 }
174 dc.w = mw - dc.x;
175 /* print command */
176 if(cmdw && item && lines == 0)
177 dc.w = cmdw;
178 drawtext(&dc, text, normcol);
179 drawline(&dc, textnw(&dc, text, cur) + dc.h/2 - 2, 2, 1, dc.h-4, normcol);
180 if(lines > 0)
181 drawmenuv();
182 else if(curr)
183 drawmenuh();
184 commitdraw(&dc, win);
185 }
186
187 void
188 drawmenuh(void) {
189 Item *i;
190
191 dc.x += cmdw;
192 dc.w = textw(&dc, "<");
193 drawtext(&dc, curr->left ? "<" : NULL, normcol);
194 dc.x += dc.w;
195 for(i = curr; i != next; i = i->right) {
196 dc.w = MIN(textw(&dc, i->text), mw / 3);
197 drawitem(i->text, (sel == i) ? selcol : normcol);
198 dc.x += dc.w;
199 }
200 dc.w = textw(&dc, ">");
201 dc.x = mw - dc.w;
202 drawtext(&dc, next ? ">" : NULL, normcol);
203 }
204
205 void
206 drawmenuv(void) {
207 Item *i;
208 XWindowAttributes wa;
209
210 dc.y = topbar ? dc.h : 0;
211 dc.w = mw - dc.x;
212 for(i = curr; i != next; i = i->right) {
213 drawitem(i->text, (sel == i) ? selcol : normcol);
214 dc.y += dc.h;
215 }
216 if(!XGetWindowAttributes(dpy, win, &wa))
217 eprint("cannot get window attributes");
218 XMoveResizeWindow(dpy, win, wa.x, wa.y + (topbar ? 0 : wa.height - mh), mw, mh);
219 }
220
221 void
222 grabkeyboard(void) {
223 unsigned int n;
224
225 for(n = 0; n < 1000; n++) {
226 if(!XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime))
227 return;
228 usleep(1000);
229 }
230 exit(EXIT_FAILURE);
231 }
232
233 void
234 keypress(XKeyEvent *e) {
235 char buf[sizeof text];
236 int num;
237 unsigned int i, len;
238 KeySym ksym;
239
240 len = strlen(text);
241 num = 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) {
252 switch(tolower(ksym)) {
253 default:
254 return;
255 case XK_a:
256 ksym = XK_Home;
257 break;
258 case XK_b:
259 ksym = XK_Left;
260 break;
261 case XK_c:
262 ksym = XK_Escape;
263 break;
264 case XK_e:
265 ksym = XK_End;
266 break;
267 case XK_f:
268 ksym = XK_Right;
269 break;
270 case XK_h:
271 ksym = XK_BackSpace;
272 break;
273 case XK_i:
274 ksym = XK_Tab;
275 break;
276 case XK_j:
277 case XK_m:
278 ksym = XK_Return;
279 break;
280 case XK_k:
281 text[cur] = '\0';
282 break;
283 case XK_n:
284 ksym = XK_Down;
285 break;
286 case XK_p:
287 ksym = XK_Up;
288 break;
289 case XK_u:
290 memmove(text, text + cur, sizeof text - cur + 1);
291 cur = 0;
292 match();
293 break;
294 case XK_w:
295 if(cur == 0)
296 return;
297 i = cur;
298 while(i-- > 0 && text[i] == ' ');
299 while(i-- > 0 && text[i] != ' ');
300 memmove(text + i + 1, text + cur, sizeof text - cur + 1);
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;
319 }
320 }
321 switch(ksym) {
322 default:
323 num = MIN(num, sizeof text);
324 if(num && !iscntrl((int) buf[0])) {
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;
331 case XK_BackSpace:
332 if(cur == 0)
333 return;
334 for(i = 1; len - i > 0 && !IS_UTF8_1ST_CHAR(text[cur - i]); i++);
335 memmove(text + cur - i, text + cur, sizeof text - cur + i);
336 cur -= i;
337 match();
338 break;
339 case XK_Delete:
340 if(cur == len)
341 return;
342 for(i = 1; cur + i < len && !IS_UTF8_1ST_CHAR(text[cur + i]); i++);
343 memmove(text + cur, text + cur + i, sizeof text - cur);
344 match();
345 break;
346 case XK_End:
347 if(cur < len) {
348 cur = len;
349 break;
350 }
351 while(next) {
352 sel = curr = next;
353 calcoffsets();
354 }
355 while(sel && sel->right)
356 sel = sel->right;
357 break;
358 case XK_Escape:
359 exit(EXIT_FAILURE);
360 case XK_Home:
361 if(sel == item) {
362 cur = 0;
363 break;
364 }
365 sel = curr = item;
366 calcoffsets();
367 break;
368 case XK_Left:
369 if(cur > 0 && (!sel || !sel->left || lines > 0)) {
370 while(cur-- > 0 && !IS_UTF8_1ST_CHAR(text[cur]));
371 break;
372 }
373 if(lines > 0)
374 return;
375 case XK_Up:
376 if(!sel || !sel->left)
377 return;
378 sel = sel->left;
379 if(sel->right == curr) {
380 curr = prev;
381 calcoffsets();
382 }
383 break;
384 case XK_Next:
385 if(!next)
386 return;
387 sel = curr = next;
388 calcoffsets();
389 break;
390 case XK_Prior:
391 if(!prev)
392 return;
393 sel = curr = prev;
394 calcoffsets();
395 break;
396 case XK_Return:
397 fprintf(stdout, "%s", ((e->state & ShiftMask) || sel) ? sel->text : text);
398 fflush(stdout);
399 exit(EXIT_SUCCESS);
400 case XK_Right:
401 if(cur < len) {
402 while(cur++ < len && !IS_UTF8_1ST_CHAR(text[cur]));
403 break;
404 }
405 if(lines > 0)
406 return;
407 case XK_Down:
408 if(!sel || !sel->right)
409 return;
410 sel = sel->right;
411 if(sel == next) {
412 curr = next;
413 calcoffsets();
414 }
415 break;
416 case XK_Tab:
417 if(!sel)
418 return;
419 strncpy(text, sel->text, sizeof text);
420 cur = strlen(text);
421 match();
422 break;
423 }
424 drawmenu();
425 }
426
427 void
428 match(void) {
429 unsigned int len;
430 Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
431
432 len = strlen(text);
433 item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
434 for(i = allitems; i; i = i->next)
435 if(!fstrncmp(text, i->text, len + 1))
436 appenditem(i, &lexact, &exactend);
437 else if(!fstrncmp(text, i->text, len))
438 appenditem(i, &lprefix, &prefixend);
439 else if(fstrstr(i->text, text))
440 appenditem(i, &lsubstr, &substrend);
441 if(lexact) {
442 item = lexact;
443 itemend = exactend;
444 }
445 if(lprefix) {
446 if(itemend) {
447 itemend->right = lprefix;
448 lprefix->left = itemend;
449 }
450 else
451 item = lprefix;
452 itemend = prefixend;
453 }
454 if(lsubstr) {
455 if(itemend) {
456 itemend->right = lsubstr;
457 lsubstr->left = itemend;
458 }
459 else
460 item = lsubstr;
461 }
462 curr = prev = next = sel = item;
463 calcoffsets();
464 }
465
466 void
467 readstdin(void) {
468 char *p, buf[sizeof text];
469 unsigned int len = 0, max = 0;
470 Item *i, *new;
471
472 i = NULL;
473 while(fgets(buf, sizeof buf, stdin)) {
474 len = strlen(buf);
475 if(buf[len-1] == '\n')
476 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)))
482 eprint("cannot malloc %u bytes\n", sizeof *new);
483 new->next = new->left = new->right = NULL;
484 new->text = p;
485 if(!i)
486 allitems = new;
487 else
488 i->next = new;
489 i = new;
490 }
491 }
492
493 void
494 run(void) {
495 XEvent ev;
496
497 XSync(dpy, False);
498 while(!XNextEvent(dpy, &ev))
499 switch(ev.type) {
500 case Expose:
501 if(ev.xexpose.count == 0)
502 drawmenu();
503 break;
504 case KeyPress:
505 keypress(&ev.xkey);
506 break;
507 case VisibilityNotify:
508 if(ev.xvisibility.state != VisibilityUnobscured)
509 XRaiseWindow(dpy, win);
510 break;
511 }
512 exit(EXIT_FAILURE);
513 }
514
515 void
516 setup(void) {
517 int i, j, x, y;
518 #if XINERAMA
519 int n;
520 XineramaScreenInfo *info = NULL;
521 #endif
522 XModifierKeymap *modmap;
523 XSetWindowAttributes wa;
524
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);
537 normcol[ColFG] = getcolor(&dc, normfgcolor);
538 selcol[ColBG] = getcolor(&dc, selbgcolor);
539 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
547 /* input window geometry */
548 mh = (dc.font.height + 2) * (lines + 1);
549 #if XINERAMA
550 if(XineramaIsActive(dpy) && (info = XineramaQueryScreens(dpy, &n))) {
551 i = 0;
552 if(n > 1) {
553 int di;
554 unsigned int dui;
555 Window dummy;
556 if(XQueryPointer(dpy, root, &dummy, &dummy, &x, &y, &di, &di, &dui))
557 for(i = 0; i < n; i++)
558 if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
559 break;
560 }
561 x = info[i].x_org;
562 y = topbar ? info[i].y_org : info[i].y_org + info[i].height - mh;
563 mw = info[i].width;
564 XFree(info);
565 }
566 else
567 #endif
568 {
569 x = 0;
570 y = topbar ? 0 : DisplayHeight(dpy, screen) - mh;
571 mw = DisplayWidth(dpy, screen);
572 }
573
574 win = XCreateWindow(dpy, root, x, y, mw, mh, 0,
575 DefaultDepth(dpy, screen), CopyFromParent,
576 DefaultVisual(dpy, screen),
577 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
578
579 setupdraw(&dc, win);
580 if(prompt)
581 promptw = MIN(textw(&dc, prompt), mw / 5);
582 XMapRaised(dpy, win);
583 }
584
585 int
586 main(int argc, char *argv[]) {
587 unsigned int i;
588
589 /* command line args */
590 progname = "dmenu";
591 for(i = 1; i < argc; i++)
592 if(!strcmp(argv[i], "-i")) {
593 fstrncmp = strncasecmp;
594 fstrstr = cistrstr;
595 }
596 else if(!strcmp(argv[i], "-b"))
597 topbar = False;
598 else if(!strcmp(argv[i], "-l")) {
599 if(++i < argc) lines = atoi(argv[i]);
600 if(lines > 0)
601 calcoffsets = calcoffsetsv;
602 }
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")) {
613 if(++i < argc) prompt = argv[i];
614 }
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 }
630 if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
631 fprintf(stderr, "dmenu: warning: no locale support\n");
632 if(!(dpy = XOpenDisplay(NULL)))
633 eprint("cannot open display\n");
634 if(atexit(&cleanup) != 0)
635 eprint("cannot register cleanup\n");
636 screen = DefaultScreen(dpy);
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
642 readstdin();
643 grabkeyboard();
644 setup();
645 if(maxname)
646 cmdw = MIN(textw(&dc, maxname), mw / 3);
647 match();
648 run();
649 return 0;
650 }