7672eac0467f9d3d04ad70354b3c441e60245552
[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 <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <strings.h>
9 #include <unistd.h>
10 #include <X11/keysym.h>
11 #include <X11/Xlib.h>
12 #include <X11/Xutil.h>
13 #ifdef XINERAMA
14 #include <X11/extensions/Xinerama.h>
15 #endif
16
17 /* macros */
18 #define CLEANMASK(mask)         (mask & ~(numlockmask | LockMask))
19 #define INRECT(X,Y,RX,RY,RW,RH) ((X) >= (RX) && (X) < (RX) + (RW) && (Y) >= (RY) && (Y) < (RY) + (RH))
20 #define MIN(a, b)               ((a) < (b) ? (a) : (b))
21 #define MAX(a, b)               ((a) > (b) ? (a) : (b))
22 #define IS_UTF8_1ST_CHAR(c)     ((((c) & 0xc0) == 0xc0) || !((c) & 0x80))
23
24 typedef struct Item Item;
25 struct Item {
26         char *text;
27         Item *next;         /* traverses all items */
28         Item *left, *right; /* traverses items matching current search pattern */
29 };
30
31 /* forward declarations */
32 static void appenditem(Item *i, Item **list, Item **last);
33 static void calcoffsetsh(void);
34 static void calcoffsetsv(void);
35 static char *cistrstr(const char *s, const char *sub);
36 static void cleanup(void);
37 static void drawmenu(void);
38 static void drawmenuh(void);
39 static void drawmenuv(void);
40 static void eprint(const char *errstr, ...);
41 static Bool grabkeyboard(void);
42 static void kpress(XKeyEvent * e);
43 static void match(char *pattern);
44 static void readstdin(void);
45 static void run(void);
46 static void setup(Bool topbar);
47
48 #include "config.h"
49
50 /* variables */
51 static char *maxname = NULL;
52 static char *prompt = NULL;
53 static char text[4096];
54 static int cmdw = 0;
55 static int promptw = 0;
56 static int ret = 0;
57 static int screen;
58 static unsigned int mw, mh;
59 static unsigned int numlockmask = 0;
60 static Bool running = True;
61 static Display *dpy;
62 static Item *allitems = NULL;  /* first of all items */
63 static Item *item = NULL;      /* first of pattern matching items */
64 static Item *sel = NULL;
65 static Item *next = NULL;
66 static Item *prev = NULL;
67 static Item *curr = NULL;
68 static Window parent, win;
69 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
70 static char *(*fstrstr)(const char *, const char *) = strstr;
71 static unsigned int lines = 0;
72 static void (*calcoffsets)(void) = calcoffsetsh;
73
74 #include "draw.c"
75
76 void
77 appenditem(Item *i, Item **list, Item **last) {
78         if(!(*last))
79                 *list = i;
80         else
81                 (*last)->right = i;
82         i->left = *last;
83         i->right = NULL;
84         *last = i;
85 }
86
87 void
88 calcoffsetsh(void) {
89         unsigned int w;
90
91         if(!curr)
92                 return;
93         w = promptw + cmdw + 2 * spaceitem;
94         for(next = curr; next && w < mw; next=next->right)
95                 w += MIN(textw(next->text), mw / 3);
96         w = promptw + cmdw + 2 * spaceitem;
97         for(prev = curr; prev && prev->left && w < mw; prev=prev->left)
98                 w += MIN(textw(prev->left->text), mw / 3);
99 }
100
101 void
102 calcoffsetsv(void) {
103         unsigned int h;
104
105         if(!curr)
106                 return;
107         h = (dc.font.height + 2) * lines;
108         for(next = curr; next && h > 0; next = next->right)
109                 h -= dc.font.height + 2;
110         h = (dc.font.height + 2) * lines;
111         for(prev = curr; prev && prev->left && h > 0; prev = prev->left)
112                 h -= dc.font.height + 2;
113 }
114
115 char *
116 cistrstr(const char *s, const char *sub) {
117         int c, csub;
118         unsigned int len;
119
120         if(!sub)
121                 return (char *)s;
122         if((c = tolower(*sub++)) != '\0') {
123                 len = strlen(sub);
124                 do {
125                         do {
126                                 if((csub = *s++) == '\0')
127                                         return NULL;
128                         }
129                         while(tolower(csub) != c);
130                 }
131                 while(strncasecmp(s, sub, len) != 0);
132                 s--;
133         }
134         return (char *)s;
135 }
136
137 void
138 cleanup(void) {
139         dccleanup();
140         XDestroyWindow(dpy, win);
141         XUngrabKeyboard(dpy, CurrentTime);
142 }
143
144 void
145 drawmenu(void) {
146         dc.x = 0;
147         dc.y = 0;
148         dc.w = mw;
149         dc.h = mh;
150         drawtext(NULL, dc.norm);
151         /* print prompt? */
152         if(prompt) {
153                 dc.w = promptw;
154                 drawtext(prompt, dc.sel);
155                 dc.x += dc.w;
156         }
157         dc.w = mw - dc.x;
158         /* print command */
159         if(cmdw && item && lines == 0)
160                 dc.w = cmdw;
161         drawtext(*text ? text : NULL, dc.norm);
162         if(curr) {
163                 if(lines > 0)
164                         drawmenuv();
165                 else
166                         drawmenuh();
167         }
168         XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
169         XFlush(dpy);
170 }
171
172 void
173 drawmenuh(void) {
174         Item *i;
175
176         dc.x += cmdw;
177         dc.w = spaceitem;
178         drawtext(curr->left ? "<" : NULL, dc.norm);
179         dc.x += dc.w;
180         for(i = curr; i != next; i=i->right) {
181                 dc.w = MIN(textw(i->text), mw / 3);
182                 drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
183                 dc.x += dc.w;
184         }
185         dc.w = spaceitem;
186         dc.x = mw - dc.w;
187         drawtext(next ? ">" : NULL, dc.norm);
188 }
189
190 void
191 drawmenuv(void) {
192         Item *i;
193
194         dc.w = mw - dc.x;
195         dc.h = dc.font.height + 2;
196         dc.y = dc.h;
197         for(i = curr; i != next; i=i->right) {
198                 drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
199                 dc.y += dc.h;
200         }
201         dc.h = mh - dc.y;
202         drawtext(NULL, dc.norm);
203 }
204
205 void
206 eprint(const char *errstr, ...) {
207         va_list ap;
208
209         va_start(ap, errstr);
210         vfprintf(stderr, errstr, ap);
211         va_end(ap);
212         exit(EXIT_FAILURE);
213 }
214
215 Bool
216 grabkeyboard(void) {
217         unsigned int len;
218
219         for(len = 1000; len; len--) {
220                 if(XGrabKeyboard(dpy, parent, True, GrabModeAsync, GrabModeAsync, CurrentTime)
221                 == GrabSuccess)
222                         break;
223                 usleep(1000);
224         }
225         return len > 0;
226 }
227
228 void
229 kpress(XKeyEvent * e) {
230         char buf[sizeof text];
231         int num;
232         unsigned int i, len;
233         KeySym ksym;
234
235         len = strlen(text);
236         num = XLookupString(e, buf, sizeof buf, &ksym, NULL);
237         if(ksym == XK_KP_Enter)
238                 ksym = XK_Return;
239         else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
240                 ksym = (ksym - XK_KP_0) + XK_0;
241         else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
242         || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
243         || IsPrivateKeypadKey(ksym))
244                 return;
245         /* first check if a control mask is omitted */
246         if(e->state & ControlMask) {
247                 switch(tolower(ksym)) {
248                 default:
249                         return;
250                 case XK_a:
251                         ksym = XK_Home;
252                         break;
253                 case XK_b:
254                         ksym = XK_Left;
255                         break;
256                 case XK_c:
257                         ksym = XK_Escape;
258                         break;
259                 case XK_e:
260                         ksym = XK_End;
261                         break;
262                 case XK_f:
263                         ksym = XK_Right;
264                         break;
265                 case XK_h:
266                         ksym = XK_BackSpace;
267                         break;
268                 case XK_i:
269                         ksym = XK_Tab;
270                         break;
271                 case XK_j:
272                         ksym = XK_Return;
273                         break;
274                 case XK_n:
275                         ksym = XK_Down;
276                         break;
277                 case XK_p:
278                         ksym = XK_Up;
279                         break;
280                 case XK_u:
281                         text[0] = '\0';
282                         match(text);
283                         break;
284                 case XK_w:
285                         if(len == 0)
286                                 return;
287                         i = len;
288                         while(i-- > 0 && text[i] == ' ');
289                         while(i-- > 0 && text[i] != ' ');
290                         text[++i] = '\0';
291                         match(text);
292                         break;
293                 case XK_x:
294                         execlp("dinput", "dinput", text, NULL); /* todo: argv */
295                         eprint("dmenu: cannot exec dinput:");
296                         break;
297                 }
298         }
299         switch(ksym) {
300         default:
301                 num = MIN(num, sizeof text);
302                 if(num && !iscntrl((int) buf[0])) {
303                         memcpy(text + len, buf, num + 1);
304                         len += num;
305                         match(text);
306                 }
307                 break;
308         case XK_BackSpace:
309                 if(len == 0)
310                         return;
311                 for(i = 1; len - i > 0 && !IS_UTF8_1ST_CHAR(text[len - i]); i++);
312                 len -= i;
313                 text[len] = '\0';
314                 match(text);
315                 break;
316         case XK_End:
317                 while(next) {
318                         sel = curr = next;
319                         calcoffsets();
320                 }
321                 while(sel && sel->right)
322                         sel = sel->right;
323                 break;
324         case XK_Escape:
325                 ret = 1;
326                 running = False;
327                 return;
328         case XK_Home:
329                 sel = curr = item;
330                 calcoffsets();
331                 break;
332         case XK_Left:
333         case XK_Up:
334                 if(!sel || !sel->left)
335                         return;
336                 sel = sel->left;
337                 if(sel->right == curr) {
338                         curr = prev;
339                         calcoffsets();
340                 }
341                 break;
342         case XK_Next:
343                 if(!next)
344                         return;
345                 sel = curr = next;
346                 calcoffsets();
347                 break;
348         case XK_Prior:
349                 if(!prev)
350                         return;
351                 sel = curr = prev;
352                 calcoffsets();
353                 break;
354         case XK_Return:
355                 if((e->state & ShiftMask) || !sel)
356                         fprintf(stdout, "%s", text);
357                 else
358                         fprintf(stdout, "%s", sel->text);
359                 fflush(stdout);
360                 running = False;
361                 return;
362         case XK_Right:
363         case XK_Down:
364                 if(!sel || !sel->right)
365                         return;
366                 sel = sel->right;
367                 if(sel == next) {
368                         curr = next;
369                         calcoffsets();
370                 }
371                 break;
372         case XK_Tab:
373                 if(!sel)
374                         return;
375                 strncpy(text, sel->text, sizeof text);
376                 match(text);
377                 break;
378         }
379         drawmenu();
380 }
381
382 void
383 match(char *pattern) {
384         unsigned int plen;
385         Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
386
387         if(!pattern)
388                 return;
389         plen = strlen(pattern);
390         item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
391         for(i = allitems; i; i = i->next)
392                 if(!fstrncmp(pattern, i->text, plen + 1))
393                         appenditem(i, &lexact, &exactend);
394                 else if(!fstrncmp(pattern, i->text, plen))
395                         appenditem(i, &lprefix, &prefixend);
396                 else if(fstrstr(i->text, pattern))
397                         appenditem(i, &lsubstr, &substrend);
398         if(lexact) {
399                 item = lexact;
400                 itemend = exactend;
401         }
402         if(lprefix) {
403                 if(itemend) {
404                         itemend->right = lprefix;
405                         lprefix->left = itemend;
406                 }
407                 else
408                         item = lprefix;
409                 itemend = prefixend;
410         }
411         if(lsubstr) {
412                 if(itemend) {
413                         itemend->right = lsubstr;
414                         lsubstr->left = itemend;
415                 }
416                 else
417                         item = lsubstr;
418         }
419         curr = prev = next = sel = item;
420         calcoffsets();
421 }
422
423 void
424 readstdin(void) {
425         char *p, buf[sizeof text];
426         unsigned int len = 0, max = 0;
427         Item *i, *new;
428
429         i = NULL;
430         while(fgets(buf, sizeof buf, stdin)) {
431                 len = strlen(buf);
432                 if(buf[len-1] == '\n')
433                         buf[--len] = '\0';
434                 if(!(p = strdup(buf)))
435                         eprint("dmenu: cannot strdup %u bytes\n", len);
436                 if((max = MAX(max, len)) == len)
437                         maxname = p;
438                 if(!(new = malloc(sizeof *new)))
439                         eprint("dmenu: cannot malloc %u bytes\n", sizeof *new);
440                 new->next = new->left = new->right = NULL;
441                 new->text = p;
442                 if(!i)
443                         allitems = new;
444                 else 
445                         i->next = new;
446                 i = new;
447         }
448 }
449
450 void
451 run(void) {
452         XEvent ev;
453
454         /* main event loop */
455         while(running && !XNextEvent(dpy, &ev))
456                 switch (ev.type) {
457                 case KeyPress:
458                         kpress(&ev.xkey);
459                         break;
460                 case Expose:
461                         if(ev.xexpose.count == 0)
462                                 drawmenu();
463                         break;
464                 case VisibilityNotify:
465                         if (ev.xvisibility.state != VisibilityUnobscured)
466                                 XRaiseWindow(dpy, win);
467                         break;
468                 }
469 }
470
471 void
472 setup(Bool topbar) {
473         int i, j, x, y;
474 #if XINERAMA
475         int n;
476         XineramaScreenInfo *info = NULL;
477 #endif
478         XModifierKeymap *modmap;
479         XSetWindowAttributes wa;
480         XWindowAttributes pwa;
481
482         /* init modifier map */
483         modmap = XGetModifierMapping(dpy);
484         for(i = 0; i < 8; i++)
485                 for(j = 0; j < modmap->max_keypermod; j++) {
486                         if(modmap->modifiermap[i * modmap->max_keypermod + j]
487                         == XKeysymToKeycode(dpy, XK_Num_Lock))
488                                 numlockmask = (1 << i);
489                 }
490         XFreeModifiermap(modmap);
491
492         initfont(font);
493
494         /* menu window */
495         wa.override_redirect = True;
496         wa.background_pixmap = ParentRelative;
497         wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask | VisibilityChangeMask;
498
499         /* menu window geometry */
500         mh = (dc.font.height + 2) * (lines + 1);
501 #if XINERAMA
502         if(parent == RootWindow(dpy, screen) && XineramaIsActive(dpy) && (info = XineramaQueryScreens(dpy, &n))) {
503                 i = 0;
504                 if(n > 1) {
505                         int di;
506                         unsigned int dui;
507                         Window dummy;
508                         if(XQueryPointer(dpy, parent, &dummy, &dummy, &x, &y, &di, &di, &dui))
509                                 for(i = 0; i < n; i++)
510                                         if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
511                                                 break;
512                 }
513                 x = info[i].x_org;
514                 y = topbar ? info[i].y_org : info[i].y_org + info[i].height - mh;
515                 mw = info[i].width;
516                 XFree(info);
517         }
518         else
519 #endif
520         {
521                 XGetWindowAttributes(dpy, parent, &pwa);
522                 x = 0;
523                 y = topbar ? 0 : pwa.height - mh;
524                 mw = pwa.width;
525         }
526
527         win = XCreateWindow(dpy, parent, x, y, mw, mh, 0,
528                         DefaultDepth(dpy, screen), CopyFromParent,
529                         DefaultVisual(dpy, screen),
530                         CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
531
532         dcsetup();
533         if(maxname)
534                 cmdw = MIN(textw(maxname), mw / 3);
535         if(prompt)
536                 promptw = MIN(textw(prompt), mw / 5);
537         text[0] = '\0';
538         match(text);
539         XMapRaised(dpy, win);
540 }
541
542 int
543 main(int argc, char *argv[]) {
544         unsigned int i;
545         Bool topbar = True;
546
547         /* command line args */
548         for(i = 1; i < argc; i++)
549                 if(!strcmp(argv[i], "-i")) {
550                         fstrncmp = strncasecmp;
551                         fstrstr = cistrstr;
552                 }
553                 else if(!strcmp(argv[i], "-b"))
554                         topbar = False;
555                 else if(!strcmp(argv[i], "-e")) {
556                         if(++i < argc) parent = atoi(argv[i]);
557                 }
558                 else if(!strcmp(argv[i], "-l")) {
559                         if(++i < argc) lines = atoi(argv[i]);
560                         if(lines > 0)
561                                 calcoffsets = calcoffsetsv;
562                 }
563                 else if(!strcmp(argv[i], "-fn")) {
564                         if(++i < argc) font = argv[i];
565                 }
566                 else if(!strcmp(argv[i], "-nb")) {
567                         if(++i < argc) normbgcolor = argv[i];
568                 }
569                 else if(!strcmp(argv[i], "-nf")) {
570                         if(++i < argc) normfgcolor = argv[i];
571                 }
572                 else if(!strcmp(argv[i], "-p")) {
573                         if(++i < argc) prompt = argv[i];
574                 }
575                 else if(!strcmp(argv[i], "-sb")) {
576                         if(++i < argc) selbgcolor = argv[i];
577                 }
578                 else if(!strcmp(argv[i], "-sf")) {
579                         if(++i < argc) selfgcolor = argv[i];
580                 }
581                 else if(!strcmp(argv[i], "-v"))
582                         eprint("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n");
583                 else
584                         eprint("usage: dmenu [-i] [-b] [-e <xid>] [-l <lines>] [-fn <font>] [-nb <color>]\n"
585                                "             [-nf <color>] [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n");
586         if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
587                 fprintf(stderr, "dmenu: warning: no locale support\n");
588         if(!(dpy = XOpenDisplay(NULL)))
589                 eprint("dmenu: cannot open display\n");
590         screen = DefaultScreen(dpy);
591         if(!parent)
592                 parent = RootWindow(dpy, screen);
593
594         readstdin();
595         running = grabkeyboard();
596
597         setup(topbar);
598         drawmenu();
599         XSync(dpy, False);
600         run();
601         cleanup();
602         XCloseDisplay(dpy);
603         return ret;
604 }