micromizing dmenu step 1
[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 }