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