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