22ef71b64b4fea083e47f6980216169586627c9c
[dmenu.git] / main.c
1 /* © 2006-2007 Anselm R. Garbe <garbeam at gmail dot com>
2  * © 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
3  * See LICENSE file for license details. */
4 #include "dmenu.h"
5 #include <ctype.h>
6 #include <locale.h>
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <X11/Xutil.h>
12 #include <X11/keysym.h>
13
14 #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
15
16 typedef struct Item Item;
17 struct Item {
18         Item *next;             /* traverses all items */
19         Item *left, *right;     /* traverses items matching current search pattern */
20         char *text;
21 };
22
23 /* static */
24
25 static char text[4096];
26 static char *prompt = NULL;
27 static int mw, mh;
28 static int ret = 0;
29 static int nitem = 0;
30 static unsigned int cmdw = 0;
31 static unsigned int promptw = 0;
32 static unsigned int numlockmask = 0;
33 static Bool running = True;
34 static Item *allitems = NULL;   /* first of all items */
35 static Item *item = NULL;       /* first of pattern matching items */
36 static Item *sel = NULL;
37 static Item *next = NULL;
38 static Item *prev = NULL;
39 static Item *curr = NULL;
40 static Window root;
41 static Window win;
42
43 static void
44 calcoffsets(void) {
45         unsigned int tw, w;
46
47         if(!curr)
48                 return;
49         w = promptw + cmdw + 2 * SPACE;
50         for(next = curr; next; next=next->right) {
51                 tw = textw(next->text);
52                 if(tw > mw / 3)
53                         tw = mw / 3;
54                 w += tw;
55                 if(w > mw)
56                         break;
57         }
58         w = promptw + cmdw + 2 * SPACE;
59         for(prev = curr; prev && prev->left; prev=prev->left) {
60                 tw = textw(prev->left->text);
61                 if(tw > mw / 3)
62                         tw = mw / 3;
63                 w += tw;
64                 if(w > mw)
65                         break;
66         }
67 }
68
69 static void
70 drawmenu(void) {
71         Item *i;
72
73         dc.x = 0;
74         dc.y = 0;
75         dc.w = mw;
76         dc.h = mh;
77         drawtext(NULL, dc.norm);
78         /* print prompt? */
79         if(promptw) {
80                 dc.w = promptw;
81                 drawtext(prompt, dc.sel);
82         }
83         dc.x += promptw;
84         dc.w = mw - promptw;
85         /* print command */
86         if(cmdw && item)
87                 dc.w = cmdw;
88         drawtext(text[0] ? text : NULL, dc.norm);
89         dc.x += cmdw;
90         if(curr) {
91                 dc.w = SPACE;
92                 drawtext((curr && curr->left) ? "<" : NULL, dc.norm);
93                 dc.x += dc.w;
94                 /* determine maximum items */
95                 for(i = curr; i != next; i=i->right) {
96                         dc.w = textw(i->text);
97                         if(dc.w > mw / 3)
98                                 dc.w = mw / 3;
99                         drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
100                         dc.x += dc.w;
101                 }
102                 dc.x = mw - SPACE;
103                 dc.w = SPACE;
104                 drawtext(next ? ">" : NULL, dc.norm);
105         }
106         XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
107         XFlush(dpy);
108 }
109
110 static Bool
111 grabkeyboard(void) {
112         unsigned int len;
113
114         for(len = 1000; len; len--) {
115                 if(XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime)
116                         == GrabSuccess)
117                         break;
118                 usleep(1000);
119         }
120         return len > 0;
121 }
122
123 static unsigned long
124 initcolor(const char *colstr) {
125         Colormap cmap = DefaultColormap(dpy, screen);
126         XColor color;
127
128         if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
129                 eprint("error, cannot allocate color '%s'\n", colstr);
130         return color.pixel;
131 }
132
133 static void
134 initfont(const char *fontstr) {
135         char *def, **missing;
136         int i, n;
137
138         if(!fontstr || fontstr[0] == '\0')
139                 eprint("error, cannot load font: '%s'\n", fontstr);
140         missing = NULL;
141         if(dc.font.set)
142                 XFreeFontSet(dpy, dc.font.set);
143         dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
144         if(missing)
145                 XFreeStringList(missing);
146         if(dc.font.set) {
147                 XFontSetExtents *font_extents;
148                 XFontStruct **xfonts;
149                 char **font_names;
150                 dc.font.ascent = dc.font.descent = 0;
151                 font_extents = XExtentsOfFontSet(dc.font.set);
152                 n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
153                 for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
154                         if(dc.font.ascent < (*xfonts)->ascent)
155                                 dc.font.ascent = (*xfonts)->ascent;
156                         if(dc.font.descent < (*xfonts)->descent)
157                                 dc.font.descent = (*xfonts)->descent;
158                         xfonts++;
159                 }
160         }
161         else {
162                 if(dc.font.xfont)
163                         XFreeFont(dpy, dc.font.xfont);
164                 dc.font.xfont = NULL;
165                 if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)))
166                         eprint("error, cannot load font: '%s'\n", fontstr);
167                 dc.font.ascent = dc.font.xfont->ascent;
168                 dc.font.descent = dc.font.xfont->descent;
169         }
170         dc.font.height = dc.font.ascent + dc.font.descent;
171 }
172
173 static void
174 match(char *pattern) {
175         unsigned int plen;
176         Item *i, *j;
177
178         if(!pattern)
179                 return;
180         plen = strlen(pattern);
181         item = j = NULL;
182         nitem = 0;
183         for(i = allitems; i; i=i->next)
184                 if(!plen || !strncmp(pattern, i->text, plen)) {
185                         if(!j)
186                                 item = i;
187                         else
188                                 j->right = i;
189                         i->left = j;
190                         i->right = NULL;
191                         j = i;
192                         nitem++;
193                 }
194         for(i = allitems; i; i=i->next)
195                 if(plen && strncmp(pattern, i->text, plen)
196                                 && strstr(i->text, pattern)) {
197                         if(!j)
198                                 item = i;
199                         else
200                                 j->right = i;
201                         i->left = j;
202                         i->right = NULL;
203                         j = i;
204                         nitem++;
205                 }
206         curr = prev = next = sel = item;
207         calcoffsets();
208 }
209
210 static void
211 kpress(XKeyEvent * e) {
212         char buf[32];
213         int i, num;
214         unsigned int len;
215         KeySym ksym;
216
217         len = strlen(text);
218         buf[0] = 0;
219         num = XLookupString(e, buf, sizeof buf, &ksym, 0);
220         if(IsKeypadKey(ksym)) { 
221                 if(ksym == XK_KP_Enter) {
222                         ksym = XK_Return;
223                 } else if(ksym >= XK_KP_0 && ksym <= XK_KP_9) {
224                         ksym = (ksym - XK_KP_0) + XK_0;
225                 }
226         }
227         if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
228                         || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
229                         || IsPrivateKeypadKey(ksym))
230                 return;
231         /* first check if a control mask is omitted */
232         if(e->state & ControlMask) {
233                 switch (ksym) {
234                 default:        /* ignore other control sequences */
235                         return;
236                 case XK_bracketleft:
237                         ksym = XK_Escape;
238                         break;
239                 case XK_h:
240                 case XK_H:
241                         ksym = XK_BackSpace;
242                         break;
243                 case XK_i:
244                 case XK_I:
245                         ksym = XK_Tab;
246                         break;
247                 case XK_j:
248                 case XK_J:
249                         ksym = XK_Return;
250                         break;
251                 case XK_u:
252                 case XK_U:
253                         text[0] = 0;
254                         match(text);
255                         drawmenu();
256                         return;
257                 case XK_w:
258                 case XK_W:
259                         if(len) {
260                                 i = len - 1;
261                                 while(i >= 0 && text[i] == ' ')
262                                         text[i--] = 0;
263                                 while(i >= 0 && text[i] != ' ')
264                                         text[i--] = 0;
265                                 match(text);
266                                 drawmenu();
267                         }
268                         return;
269                 }
270         }
271         if(CLEANMASK(e->state) & Mod1Mask) {
272                 switch(ksym) {
273                 default: return;
274                 case XK_h:
275                         ksym = XK_Left;
276                         break;
277                 case XK_l:
278                         ksym = XK_Right;
279                         break;
280                 case XK_j:
281                         ksym = XK_Next;
282                         break;
283                 case XK_k:
284                         ksym = XK_Prior;
285                         break;
286                 case XK_g:
287                         ksym = XK_Home;
288                         break;
289                 case XK_G:
290                         ksym = XK_End;
291                         break;
292                 }
293         }
294         switch(ksym) {
295         default:
296                 if(num && !iscntrl((int) buf[0])) {
297                         buf[num] = 0;
298                         if(len > 0)
299                                 strncat(text, buf, sizeof text);
300                         else
301                                 strncpy(text, buf, sizeof text);
302                         match(text);
303                 }
304                 break;
305         case XK_BackSpace:
306                 if(len) {
307                         text[--len] = 0;
308                         match(text);
309                 }
310                 break;
311         case XK_End:
312                 if(!item)
313                         return;
314                 while(next) {
315                         sel = curr = next;
316                         calcoffsets();
317                 }
318                 while(sel && sel->right)
319                         sel = sel->right;
320                 break;
321         case XK_Escape:
322                 ret = 1;
323                 running = False;
324                 break;
325         case XK_Home:
326                 if(!item)
327                         return;
328                 sel = curr = item;
329                 calcoffsets();
330                 break;
331         case XK_Left:
332                 if(!(sel && sel->left))
333                         return;
334                 sel=sel->left;
335                 if(sel->right == curr) {
336                         curr = prev;
337                         calcoffsets();
338                 }
339                 break;
340         case XK_Next:
341                 if(!next)
342                         return;
343                 sel = curr = next;
344                 calcoffsets();
345                 break;
346         case XK_Prior:
347                 if(!prev)
348                         return;
349                 sel = curr = prev;
350                 calcoffsets();
351                 break;
352         case XK_Return:
353                 if((e->state & ShiftMask) && text)
354                         fprintf(stdout, "%s", text);
355                 else if(sel)
356                         fprintf(stdout, "%s", sel->text);
357                 else if(text)
358                         fprintf(stdout, "%s", text);
359                 fflush(stdout);
360                 running = False;
361                 break;
362         case XK_Right:
363                 if(!(sel && sel->right))
364                         return;
365                 sel=sel->right;
366                 if(sel == next) {
367                         curr = next;
368                         calcoffsets();
369                 }
370                 break;
371         case XK_Tab:
372                 if(!sel)
373                         return;
374                 strncpy(text, sel->text, sizeof text);
375                 match(text);
376                 break;
377         }
378         drawmenu();
379 }
380
381 static char *
382 readstdin(void) {
383         static char *maxname = NULL;
384         char *p, buf[1024];
385         unsigned int len = 0, max = 0;
386         Item *i, *new;
387
388         i = 0;
389         while(fgets(buf, sizeof buf, stdin)) {
390                 len = strlen(buf);
391                 if (buf[len - 1] == '\n')
392                         buf[len - 1] = 0;
393                 p = estrdup(buf);
394                 if(max < len) {
395                         maxname = p;
396                         max = len;
397                 }
398                 new = emalloc(sizeof(Item));
399                 new->next = new->left = new->right = NULL;
400                 new->text = p;
401                 if(!i)
402                         allitems = new;
403                 else 
404                         i->next = new;
405                 i = new;
406         }
407
408         return maxname;
409 }
410
411 static void
412 usage(void) {
413         eprint("usage: dmenu [-b] [-fn <font>] [-nb <color>] [-nf <color>]\n"
414                 "             [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n");
415 }
416
417 /* extern */
418
419 int screen;
420 Display *dpy;
421 DC dc = {0};
422
423 int
424 main(int argc, char *argv[]) {
425         Bool bottom = False;
426         char *font = FONT;
427         char *maxname;
428         char *normbg = NORMBGCOLOR;
429         char *normfg = NORMFGCOLOR;
430         char *selbg = SELBGCOLOR;
431         char *selfg = SELFGCOLOR;
432         int i, j;
433         Item *itm;
434         XEvent ev;
435         XModifierKeymap *modmap;
436         XSetWindowAttributes wa;
437
438         /* command line args */
439         for(i = 1; i < argc; i++)
440                 if(!strcmp(argv[i], "-b")) {
441                         bottom = True;
442                 }
443                 else if(!strcmp(argv[i], "-fn")) {
444                         if(++i < argc) font = argv[i];
445                 }
446                 else if(!strcmp(argv[i], "-nb")) {
447                         if(++i < argc) normbg = argv[i];
448                 }
449                 else if(!strcmp(argv[i], "-nf")) {
450                         if(++i < argc) normfg = argv[i];
451                 }
452                 else if(!strcmp(argv[i], "-p")) {
453                         if(++i < argc) prompt = argv[i];
454                 }
455                 else if(!strcmp(argv[i], "-sb")) {
456                         if(++i < argc) selbg = argv[i];
457                 }
458                 else if(!strcmp(argv[i], "-sf")) {
459                         if(++i < argc) selfg = argv[i];
460                 }
461                 else if(!strcmp(argv[i], "-v"))
462                         eprint("dmenu-"VERSION", © 2006-2007 Anselm R. Garbe, Sander van Dijk\n");
463                 else
464                         usage();
465         setlocale(LC_CTYPE, "");
466         dpy = XOpenDisplay(0);
467         if(!dpy)
468                 eprint("dmenu: cannot open display\n");
469         screen = DefaultScreen(dpy);
470         root = RootWindow(dpy, screen);
471         if(isatty(STDIN_FILENO)) {
472                 maxname = readstdin();
473                 running = grabkeyboard();
474         }
475         else { /* prevent keypress loss */
476                 running = grabkeyboard();
477                 maxname = readstdin();
478         }
479         /* init modifier map */
480         modmap = XGetModifierMapping(dpy);
481         for (i = 0; i < 8; i++) {
482                 for (j = 0; j < modmap->max_keypermod; j++) {
483                         if(modmap->modifiermap[i * modmap->max_keypermod + j]
484                         == XKeysymToKeycode(dpy, XK_Num_Lock))
485                                 numlockmask = (1 << i);
486                 }
487         }
488         XFreeModifiermap(modmap);
489         /* style */
490         dc.norm[ColBG] = initcolor(normbg);
491         dc.norm[ColFG] = initcolor(normfg);
492         dc.sel[ColBG] = initcolor(selbg);
493         dc.sel[ColFG] = initcolor(selfg);
494         initfont(font);
495         /* menu window */
496         wa.override_redirect = 1;
497         wa.background_pixmap = ParentRelative;
498         wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
499         mw = DisplayWidth(dpy, screen);
500         mh = dc.font.height + 2;
501         win = XCreateWindow(dpy, root, 0,
502                         bottom ? DisplayHeight(dpy, screen) - mh : 0, mw, mh, 0,
503                         DefaultDepth(dpy, screen), CopyFromParent,
504                         DefaultVisual(dpy, screen),
505                         CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
506         /* pixmap */
507         dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
508         dc.gc = XCreateGC(dpy, root, 0, 0);
509         XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
510         if(!dc.font.set)
511                 XSetFont(dpy, dc.gc, dc.font.xfont->fid);
512         if(maxname)
513                 cmdw = textw(maxname);
514         if(cmdw > mw / 3)
515                 cmdw = mw / 3;
516         if(prompt)
517                 promptw = textw(prompt);
518         if(promptw > mw / 5)
519                 promptw = mw / 5;
520         text[0] = 0;
521         match(text);
522         XMapRaised(dpy, win);
523         drawmenu();
524         XSync(dpy, False);
525
526         /* main event loop */
527         while(running && !XNextEvent(dpy, &ev))
528                 switch (ev.type) {
529                 default:        /* ignore all crap */
530                         break;
531                 case KeyPress:
532                         kpress(&ev.xkey);
533                         break;
534                 case Expose:
535                         if(ev.xexpose.count == 0)
536                                 drawmenu();
537                         break;
538                 }
539
540         /* cleanup */
541         while(allitems) {
542                 itm = allitems->next;
543                 free(allitems->text);
544                 free(allitems);
545                 allitems = itm;
546         }
547         if(dc.font.set)
548                 XFreeFontSet(dpy, dc.font.set);
549         else
550                 XFreeFont(dpy, dc.font.xfont);
551         XFreePixmap(dpy, dc.drawable);
552         XFreeGC(dpy, dc.gc);
553         XDestroyWindow(dpy, win);
554         XUngrabKeyboard(dpy, CurrentTime);
555         XCloseDisplay(dpy);
556         return ret;
557 }