Xinqi Bao's Git

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