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 }