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