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