Xinqi Bao's Git

3256f9c59e0090302e42187ed98620c3919bfe57
[dmenu.git] / dmenu.c
1 /* See LICENSE file for copyright and license details. */
2 #include <ctype.h>
3 #include <locale.h>
4 #include <stdarg.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <X11/Xlib.h>
10 #include <X11/Xutil.h>
11 #include <X11/keysym.h>
12
13 /* macros */
14 #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
15
16 /* enums */
17 enum { ColFG, ColBG, ColLast };
18
19 /* typedefs */
20 typedef struct {
21 int x, y, w, h;
22 unsigned long norm[ColLast];
23 unsigned long sel[ColLast];
24 Drawable drawable;
25 GC gc;
26 struct {
27 XFontStruct *xfont;
28 XFontSet set;
29 int ascent;
30 int descent;
31 int height;
32 } font;
33 } DC; /* draw context */
34
35 typedef struct Item Item;
36 struct Item {
37 Item *next; /* traverses all items */
38 Item *left, *right; /* traverses items matching current search pattern */
39 char *text;
40 Bool matched;
41 };
42
43 /* forward declarations */
44 Item *appenditem(Item *i, Item *last);
45 void calcoffsets(void);
46 void cleanup(void);
47 void drawmenu(void);
48 void drawtext(const char *text, unsigned long col[ColLast]);
49 void *emalloc(unsigned int size);
50 void eprint(const char *errstr, ...);
51 char *estrdup(const char *str);
52 unsigned long getcolor(const char *colstr);
53 Bool grabkeyboard(void);
54 void initfont(const char *fontstr);
55 void kpress(XKeyEvent * e);
56 void match(char *pattern);
57 void readstdin(void);
58 void run(void);
59 void setup(Bool bottom);
60 int strcaseido(const char *text, const char *pattern);
61 char *cistrstr(const char *s, const char *sub);
62 unsigned int textnw(const char *text, unsigned int len);
63 unsigned int textw(const char *text);
64
65 #include "config.h"
66
67 /* variables */
68 char *font = FONT;
69 char *maxname = NULL;
70 char *normbg = NORMBGCOLOR;
71 char *normfg = NORMFGCOLOR;
72 char *prompt = NULL;
73 char *selbg = SELBGCOLOR;
74 char *selfg = SELFGCOLOR;
75 char text[4096];
76 int screen;
77 int ret = 0;
78 unsigned int cmdw = 0;
79 unsigned int mw, mh;
80 unsigned int promptw = 0;
81 unsigned int nitem = 0;
82 unsigned int numlockmask = 0;
83 Bool idomatch = False;
84 Bool running = True;
85 Display *dpy;
86 DC dc = {0};
87 Item *allitems = NULL; /* first of all items */
88 Item *item = NULL; /* first of pattern matching items */
89 Item *sel = NULL;
90 Item *next = NULL;
91 Item *prev = NULL;
92 Item *curr = NULL;
93 Window root, win;
94
95 Item *
96 appenditem(Item *i, Item *last) {
97 if(!last)
98 item = i;
99 else
100 last->right = i;
101 i->matched = True;
102 i->left = last;
103 i->right = NULL;
104 last = i;
105 nitem++;
106 return last;
107 }
108
109 void
110 calcoffsets(void) {
111 unsigned int tw, w;
112
113 if(!curr)
114 return;
115 w = promptw + cmdw + 2 * SPACE;
116 for(next = curr; next; next=next->right) {
117 tw = textw(next->text);
118 if(tw > mw / 3)
119 tw = mw / 3;
120 w += tw;
121 if(w > mw)
122 break;
123 }
124 w = promptw + cmdw + 2 * SPACE;
125 for(prev = curr; prev && prev->left; prev=prev->left) {
126 tw = textw(prev->left->text);
127 if(tw > mw / 3)
128 tw = mw / 3;
129 w += tw;
130 if(w > mw)
131 break;
132 }
133 }
134
135 void
136 cleanup(void) {
137 Item *itm;
138
139 while(allitems) {
140 itm = allitems->next;
141 free(allitems->text);
142 free(allitems);
143 allitems = itm;
144 }
145 if(dc.font.set)
146 XFreeFontSet(dpy, dc.font.set);
147 else
148 XFreeFont(dpy, dc.font.xfont);
149 XFreePixmap(dpy, dc.drawable);
150 XFreeGC(dpy, dc.gc);
151 XDestroyWindow(dpy, win);
152 XUngrabKeyboard(dpy, CurrentTime);
153 }
154
155 void
156 drawmenu(void) {
157 Item *i;
158
159 dc.x = 0;
160 dc.y = 0;
161 dc.w = mw;
162 dc.h = mh;
163 drawtext(NULL, dc.norm);
164 /* print prompt? */
165 if(promptw) {
166 dc.w = promptw;
167 drawtext(prompt, dc.sel);
168 }
169 dc.x += promptw;
170 dc.w = mw - promptw;
171 /* print command */
172 if(cmdw && item)
173 dc.w = cmdw;
174 drawtext(text[0] ? text : NULL, dc.norm);
175 dc.x += cmdw;
176 if(curr) {
177 dc.w = SPACE;
178 drawtext((curr && curr->left) ? "<" : NULL, dc.norm);
179 dc.x += dc.w;
180 /* determine maximum items */
181 for(i = curr; i != next; i=i->right) {
182 dc.w = textw(i->text);
183 if(dc.w > mw / 3)
184 dc.w = mw / 3;
185 drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
186 dc.x += dc.w;
187 }
188 dc.x = mw - SPACE;
189 dc.w = SPACE;
190 drawtext(next ? ">" : NULL, dc.norm);
191 }
192 XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
193 XFlush(dpy);
194 }
195
196 void
197 drawtext(const char *text, unsigned long col[ColLast]) {
198 int x, y, w, h;
199 static char buf[256];
200 unsigned int len, olen;
201 XRectangle r = { dc.x, dc.y, dc.w, dc.h };
202
203 XSetForeground(dpy, dc.gc, col[ColBG]);
204 XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
205 if(!text)
206 return;
207 w = 0;
208 olen = len = strlen(text);
209 if(len >= sizeof buf)
210 len = sizeof buf - 1;
211 memcpy(buf, text, len);
212 buf[len] = 0;
213 h = dc.font.ascent + dc.font.descent;
214 y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent;
215 x = dc.x + (h / 2);
216 /* shorten text if necessary */
217 while(len && (w = textnw(buf, len)) > dc.w - h)
218 buf[--len] = 0;
219 if(len < olen) {
220 if(len > 1)
221 buf[len - 1] = '.';
222 if(len > 2)
223 buf[len - 2] = '.';
224 if(len > 3)
225 buf[len - 3] = '.';
226 }
227 if(w > dc.w)
228 return; /* too long */
229 XSetForeground(dpy, dc.gc, col[ColFG]);
230 if(dc.font.set)
231 XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, buf, len);
232 else
233 XDrawString(dpy, dc.drawable, dc.gc, x, y, buf, len);
234 }
235
236 void *
237 emalloc(unsigned int size) {
238 void *res = malloc(size);
239
240 if(!res)
241 eprint("fatal: could not malloc() %u bytes\n", size);
242 return res;
243 }
244
245 void
246 eprint(const char *errstr, ...) {
247 va_list ap;
248
249 va_start(ap, errstr);
250 vfprintf(stderr, errstr, ap);
251 va_end(ap);
252 exit(EXIT_FAILURE);
253 }
254
255 char *
256 estrdup(const char *str) {
257 void *res = strdup(str);
258
259 if(!res)
260 eprint("fatal: could not malloc() %u bytes\n", strlen(str));
261 return res;
262 }
263
264 unsigned long
265 getcolor(const char *colstr) {
266 Colormap cmap = DefaultColormap(dpy, screen);
267 XColor color;
268
269 if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
270 eprint("error, cannot allocate color '%s'\n", colstr);
271 return color.pixel;
272 }
273
274 Bool
275 grabkeyboard(void) {
276 unsigned int len;
277
278 for(len = 1000; len; len--) {
279 if(XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime)
280 == GrabSuccess)
281 break;
282 usleep(1000);
283 }
284 return len > 0;
285 }
286
287 void
288 initfont(const char *fontstr) {
289 char *def, **missing;
290 int i, n;
291
292 if(!fontstr || fontstr[0] == '\0')
293 eprint("error, cannot load font: '%s'\n", fontstr);
294 missing = NULL;
295 if(dc.font.set)
296 XFreeFontSet(dpy, dc.font.set);
297 dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
298 if(missing)
299 XFreeStringList(missing);
300 if(dc.font.set) {
301 XFontSetExtents *font_extents;
302 XFontStruct **xfonts;
303 char **font_names;
304 dc.font.ascent = dc.font.descent = 0;
305 font_extents = XExtentsOfFontSet(dc.font.set);
306 n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
307 for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
308 if(dc.font.ascent < (*xfonts)->ascent)
309 dc.font.ascent = (*xfonts)->ascent;
310 if(dc.font.descent < (*xfonts)->descent)
311 dc.font.descent = (*xfonts)->descent;
312 xfonts++;
313 }
314 }
315 else {
316 if(dc.font.xfont)
317 XFreeFont(dpy, dc.font.xfont);
318 dc.font.xfont = NULL;
319 if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr))
320 && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed")))
321 eprint("error, cannot load font: '%s'\n", fontstr);
322 dc.font.ascent = dc.font.xfont->ascent;
323 dc.font.descent = dc.font.xfont->descent;
324 }
325 dc.font.height = dc.font.ascent + dc.font.descent;
326 }
327
328 void
329 kpress(XKeyEvent * e) {
330 char buf[32];
331 int i, num;
332 unsigned int len;
333 KeySym ksym;
334
335 len = strlen(text);
336 buf[0] = 0;
337 num = XLookupString(e, buf, sizeof buf, &ksym, 0);
338 if(IsKeypadKey(ksym)) {
339 if(ksym == XK_KP_Enter) {
340 ksym = XK_Return;
341 } else if(ksym >= XK_KP_0 && ksym <= XK_KP_9) {
342 ksym = (ksym - XK_KP_0) + XK_0;
343 }
344 }
345 if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
346 || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
347 || IsPrivateKeypadKey(ksym))
348 return;
349 /* first check if a control mask is omitted */
350 if(e->state & ControlMask) {
351 switch (ksym) {
352 default: /* ignore other control sequences */
353 return;
354 case XK_bracketleft:
355 ksym = XK_Escape;
356 break;
357 case XK_h:
358 case XK_H:
359 ksym = XK_BackSpace;
360 break;
361 case XK_i:
362 case XK_I:
363 ksym = XK_Tab;
364 break;
365 case XK_j:
366 case XK_J:
367 ksym = XK_Return;
368 break;
369 case XK_u:
370 case XK_U:
371 text[0] = 0;
372 match(text);
373 drawmenu();
374 return;
375 case XK_w:
376 case XK_W:
377 if(len) {
378 i = len - 1;
379 while(i >= 0 && text[i] == ' ')
380 text[i--] = 0;
381 while(i >= 0 && text[i] != ' ')
382 text[i--] = 0;
383 match(text);
384 drawmenu();
385 }
386 return;
387 }
388 }
389 if(CLEANMASK(e->state) & Mod1Mask) {
390 switch(ksym) {
391 default: return;
392 case XK_h:
393 ksym = XK_Left;
394 break;
395 case XK_l:
396 ksym = XK_Right;
397 break;
398 case XK_j:
399 ksym = XK_Next;
400 break;
401 case XK_k:
402 ksym = XK_Prior;
403 break;
404 case XK_g:
405 ksym = XK_Home;
406 break;
407 case XK_G:
408 ksym = XK_End;
409 break;
410 }
411 }
412 switch(ksym) {
413 default:
414 if(num && !iscntrl((int) buf[0])) {
415 buf[num] = 0;
416 if(len > 0)
417 strncat(text, buf, sizeof text);
418 else
419 strncpy(text, buf, sizeof text);
420 match(text);
421 }
422 break;
423 case XK_BackSpace:
424 if(len) {
425 text[--len] = 0;
426 match(text);
427 }
428 break;
429 case XK_End:
430 if(!item)
431 return;
432 while(next) {
433 sel = curr = next;
434 calcoffsets();
435 }
436 while(sel && sel->right)
437 sel = sel->right;
438 break;
439 case XK_Escape:
440 ret = 1;
441 running = False;
442 break;
443 case XK_Home:
444 if(!item)
445 return;
446 sel = curr = item;
447 calcoffsets();
448 break;
449 case XK_Left:
450 if(!(sel && sel->left))
451 return;
452 sel=sel->left;
453 if(sel->right == curr) {
454 curr = prev;
455 calcoffsets();
456 }
457 break;
458 case XK_Next:
459 if(!next)
460 return;
461 sel = curr = next;
462 calcoffsets();
463 break;
464 case XK_Prior:
465 if(!prev)
466 return;
467 sel = curr = prev;
468 calcoffsets();
469 break;
470 case XK_Return:
471 if((e->state & ShiftMask) && text)
472 fprintf(stdout, "%s", text);
473 else if(sel)
474 fprintf(stdout, "%s", sel->text);
475 else if(text)
476 fprintf(stdout, "%s", text);
477 fflush(stdout);
478 running = False;
479 break;
480 case XK_Right:
481 if(!(sel && sel->right))
482 return;
483 sel=sel->right;
484 if(sel == next) {
485 curr = next;
486 calcoffsets();
487 }
488 break;
489 case XK_Tab:
490 if(!sel)
491 return;
492 strncpy(text, sel->text, sizeof text);
493 match(text);
494 break;
495 }
496 drawmenu();
497 }
498
499 void
500 match(char *pattern) {
501 unsigned int plen;
502 Item *i, *j;
503
504 if(!pattern)
505 return;
506 plen = strlen(pattern);
507 item = j = NULL;
508 nitem = 0;
509 for(i = allitems; i; i=i->next)
510 i->matched = False;
511 for(i = allitems; i; i = i->next)
512 if(!i->matched && !strncasecmp(pattern, i->text, plen))
513 j = appenditem(i, j);
514 for(i = allitems; i; i = i->next)
515 if(!i->matched && cistrstr(i->text, pattern))
516 j = appenditem(i, j);
517 if(idomatch)
518 for(i = allitems; i; i = i->next)
519 if(!i->matched && strcaseido(i->text, pattern))
520 j = appenditem(i, j);
521 curr = prev = next = sel = item;
522 calcoffsets();
523 }
524
525 void
526 readstdin(void) {
527 char *p, buf[1024];
528 unsigned int len = 0, max = 0;
529 Item *i, *new;
530
531 i = 0;
532 while(fgets(buf, sizeof buf, stdin)) {
533 len = strlen(buf);
534 if (buf[len - 1] == '\n')
535 buf[len - 1] = 0;
536 p = estrdup(buf);
537 if(max < len) {
538 maxname = p;
539 max = len;
540 }
541 new = emalloc(sizeof(Item));
542 new->next = new->left = new->right = NULL;
543 new->text = p;
544 if(!i)
545 allitems = new;
546 else
547 i->next = new;
548 i = new;
549 }
550 }
551
552 void
553 run(void) {
554 XEvent ev;
555
556 /* main event loop */
557 while(running && !XNextEvent(dpy, &ev))
558 switch (ev.type) {
559 default: /* ignore all crap */
560 break;
561 case KeyPress:
562 kpress(&ev.xkey);
563 break;
564 case Expose:
565 if(ev.xexpose.count == 0)
566 drawmenu();
567 break;
568 }
569 }
570
571 void
572 setup(Bool bottom) {
573 unsigned int i, j;
574 XModifierKeymap *modmap;
575 XSetWindowAttributes wa;
576
577 /* init modifier map */
578 modmap = XGetModifierMapping(dpy);
579 for(i = 0; i < 8; i++)
580 for(j = 0; j < modmap->max_keypermod; j++) {
581 if(modmap->modifiermap[i * modmap->max_keypermod + j]
582 == XKeysymToKeycode(dpy, XK_Num_Lock))
583 numlockmask = (1 << i);
584 }
585 XFreeModifiermap(modmap);
586
587 /* style */
588 dc.norm[ColBG] = getcolor(normbg);
589 dc.norm[ColFG] = getcolor(normfg);
590 dc.sel[ColBG] = getcolor(selbg);
591 dc.sel[ColFG] = getcolor(selfg);
592 initfont(font);
593
594 /* menu window */
595 wa.override_redirect = 1;
596 wa.background_pixmap = ParentRelative;
597 wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
598 mw = DisplayWidth(dpy, screen);
599 mh = dc.font.height + 2;
600 win = XCreateWindow(dpy, root, 0,
601 bottom ? DisplayHeight(dpy, screen) - mh : 0, mw, mh, 0,
602 DefaultDepth(dpy, screen), CopyFromParent,
603 DefaultVisual(dpy, screen),
604 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
605
606 /* pixmap */
607 dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
608 dc.gc = XCreateGC(dpy, root, 0, 0);
609 XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
610 if(!dc.font.set)
611 XSetFont(dpy, dc.gc, dc.font.xfont->fid);
612 if(maxname)
613 cmdw = textw(maxname);
614 if(cmdw > mw / 3)
615 cmdw = mw / 3;
616 if(prompt)
617 promptw = textw(prompt);
618 if(promptw > mw / 5)
619 promptw = mw / 5;
620 text[0] = 0;
621 match(text);
622 XMapRaised(dpy, win);
623 }
624
625 int
626 strcaseido(const char *text, const char *pattern) {
627 for(; *text && *pattern; text++)
628 if(tolower((int)*text) == tolower((int)*pattern))
629 pattern++;
630 return !*pattern;
631 }
632
633 char *
634 cistrstr(const char *s, const char *sub) {
635 int c, csub;
636 unsigned int len;
637
638 if(!sub)
639 return (char *)s;
640 if((c = *sub++) != 0) {
641 c = tolower(c);
642 len = strlen(sub);
643 do {
644 do {
645 if((csub = *s++) == 0)
646 return (NULL);
647 }
648 while(tolower(csub) != c);
649 }
650 while(strncasecmp(s, sub, len) != 0);
651 s--;
652 }
653 return (char *)s;
654 }
655
656 unsigned int
657 textnw(const char *text, unsigned int len) {
658 XRectangle r;
659
660 if(dc.font.set) {
661 XmbTextExtents(dc.font.set, text, len, NULL, &r);
662 return r.width;
663 }
664 return XTextWidth(dc.font.xfont, text, len);
665 }
666
667 unsigned int
668 textw(const char *text) {
669 return textnw(text, strlen(text)) + dc.font.height;
670 }
671
672 int
673 main(int argc, char *argv[]) {
674 Bool bottom = False;
675 unsigned int i;
676
677 /* command line args */
678 for(i = 1; i < argc; i++)
679 if(!strcmp(argv[i], "-b")) {
680 bottom = True;
681 }
682 else if(!strcmp(argv[i], "-i"))
683 idomatch = True;
684 else if(!strcmp(argv[i], "-fn")) {
685 if(++i < argc) font = argv[i];
686 }
687 else if(!strcmp(argv[i], "-nb")) {
688 if(++i < argc) normbg = argv[i];
689 }
690 else if(!strcmp(argv[i], "-nf")) {
691 if(++i < argc) normfg = argv[i];
692 }
693 else if(!strcmp(argv[i], "-p")) {
694 if(++i < argc) prompt = argv[i];
695 }
696 else if(!strcmp(argv[i], "-sb")) {
697 if(++i < argc) selbg = argv[i];
698 }
699 else if(!strcmp(argv[i], "-sf")) {
700 if(++i < argc) selfg = argv[i];
701 }
702 else if(!strcmp(argv[i], "-v"))
703 eprint("dmenu-"VERSION", © 2006-2007 Anselm R. Garbe, Sander van Dijk, Michał Janeczek\n");
704 else
705 eprint("usage: dmenu [-b] [-i] [-fn <font>] [-nb <color>] [-nf <color>]\n"
706 " [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n");
707 setlocale(LC_CTYPE, "");
708 dpy = XOpenDisplay(0);
709 if(!dpy)
710 eprint("dmenu: cannot open display\n");
711 screen = DefaultScreen(dpy);
712 root = RootWindow(dpy, screen);
713
714 if(isatty(STDIN_FILENO)) {
715 readstdin();
716 running = grabkeyboard();
717 }
718 else { /* prevent keypress loss */
719 running = grabkeyboard();
720 readstdin();
721 }
722
723 setup(bottom);
724 drawmenu();
725 XSync(dpy, False);
726 run();
727 cleanup();
728 XCloseDisplay(dpy);
729 return ret;
730 }