]> git.armaanb.net Git - dmenu.git/blob - dmenu.c
resizing vlist, new dinput binding
[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         mh = (dc.font.height + 2) * (i + 1);
113         for(i = 0; i < lines && prev && prev->left; i++)
114                 prev = prev->left;
115 }
116
117 char *
118 cistrstr(const char *s, const char *sub) {
119         int c, csub;
120         unsigned int len;
121
122         if(!sub)
123                 return (char *)s;
124         if((c = tolower(*sub++)) != '\0') {
125                 len = strlen(sub);
126                 do {
127                         do {
128                                 if((csub = *s++) == '\0')
129                                         return NULL;
130                         }
131                         while(tolower(csub) != c);
132                 }
133                 while(strncasecmp(s, sub, len) != 0);
134                 s--;
135         }
136         return (char *)s;
137 }
138
139 void
140 cleanup(void) {
141         Item *itm;
142
143         while(allitems) {
144                 itm = allitems->next;
145                 free(allitems->text);
146                 free(allitems);
147                 allitems = itm;
148         }
149         cleanupdraw(&dc);
150         XDestroyWindow(dpy, win);
151         XUngrabKeyboard(dpy, CurrentTime);
152 }
153
154 void
155 dinput(void) {
156         cleanup();
157         argp[0] = "dinput";
158         argp[1] = text;
159         execvp("dinput", argp);
160         eprint("cannot exec dinput\n");
161 }
162
163 void
164 drawmenu(void) {
165         dc.x = 0;
166         dc.y = 0;
167         dc.w = mw;
168         dc.h = mh;
169         drawtext(&dc, NULL, normcol, False);
170         dc.h = dc.font.height + 2;
171         dc.y = topbar ? 0 : mh - dc.h;
172         /* print prompt? */
173         if(prompt) {
174                 dc.w = promptw;
175                 drawtext(&dc, prompt, selcol, False);
176                 dc.x += dc.w;
177         }
178         dc.w = mw - dc.x;
179         /* print command */
180         if(cmdw && item && lines == 0)
181                 dc.w = cmdw;
182         drawtext(&dc, *text ? text : NULL, normcol, False);
183         if(lines > 0)
184                 drawmenuv();
185         else
186                 drawmenuh();
187         XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
188         XFlush(dpy);
189 }
190
191 void
192 drawmenuh(void) {
193         Item *i;
194
195         dc.x += cmdw;
196         dc.w = spaceitem;
197         drawtext(&dc, curr && curr->left ? "<" : NULL, normcol, False);
198         dc.x += dc.w;
199         for(i = curr; i != next; i = i->right) {
200                 dc.w = MIN(textw(&dc, i->text), mw / 3);
201                 drawtext(&dc, i->text, (sel == i) ? selcol : normcol, False);
202                 dc.x += dc.w;
203         }
204         dc.w = spaceitem;
205         dc.x = mw - dc.w;
206         drawtext(&dc, next ? ">" : NULL, normcol, False);
207 }
208
209 void
210 drawmenuv(void) {
211         Item *i;
212         XWindowAttributes wa;
213
214         dc.y = topbar ? dc.h : 0;
215         dc.w = mw - dc.x;
216         for(i = curr; i != next; i = i->right) {
217                 drawtext(&dc, i->text, (sel == i) ? selcol : normcol, False);
218                 dc.y += dc.h;
219         }
220         if(!XGetWindowAttributes(dpy, win, &wa))
221                 eprint("cannot get window attributes");
222         XMoveResizeWindow(dpy, win, wa.x, wa.y + (topbar ? 0 : wa.height - mh), mw, mh);
223 }
224
225 Bool
226 grabkeyboard(void) {
227         unsigned int len;
228
229         for(len = 1000; len; len--) {
230                 if(XGrabKeyboard(dpy, parent, True, GrabModeAsync, GrabModeAsync, CurrentTime)
231                 == GrabSuccess)
232                         break;
233                 usleep(1000);
234         }
235         return len > 0;
236 }
237
238 void
239 kpress(XKeyEvent *e) {
240         char buf[sizeof text];
241         int num;
242         unsigned int i, len;
243         KeySym ksym;
244
245         len = strlen(text);
246         num = XLookupString(e, buf, sizeof buf, &ksym, NULL);
247         if(ksym == XK_KP_Enter)
248                 ksym = XK_Return;
249         else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
250                 ksym = (ksym - XK_KP_0) + XK_0;
251         else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
252         || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
253         || IsPrivateKeypadKey(ksym))
254                 return;
255         /* first check if a control mask is omitted */
256         if(e->state & ControlMask) {
257                 switch(tolower(ksym)) {
258                 default:
259                         return;
260                 case XK_a:
261                         ksym = XK_Home;
262                         break;
263                 case XK_b:
264                         ksym = XK_Left;
265                         break;
266                 case XK_c:
267                         ksym = XK_Escape;
268                         break;
269                 case XK_e:
270                         ksym = XK_End;
271                         break;
272                 case XK_f:
273                         ksym = XK_Right;
274                         break;
275                 case XK_h:
276                         ksym = XK_BackSpace;
277                         break;
278                 case XK_i:
279                         ksym = XK_Tab;
280                         break;
281                 case XK_j:
282                 case XK_m:
283                         ksym = XK_Return;
284                         break;
285                 case XK_n:
286                         ksym = XK_Down;
287                         break;
288                 case XK_p:
289                         ksym = XK_Up;
290                         break;
291                 case XK_u:
292                         text[0] = '\0';
293                         match(text);
294                         break;
295                 case XK_w:
296                         if(len == 0)
297                                 return;
298                         i = len;
299                         while(i-- > 0 && text[i] == ' ');
300                         while(i-- > 0 && text[i] != ' ');
301                         text[++i] = '\0';
302                         match(text);
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)
363                         dinput();
364                 fprintf(stdout, "%s", sel ? sel->text : text);
365                 fflush(stdout);
366                 running = False;
367                 return;
368         case XK_Right:
369         case XK_Down:
370                 if(!sel || !sel->right)
371                         return;
372                 sel = sel->right;
373                 if(sel == next) {
374                         curr = next;
375                         calcoffsets();
376                 }
377                 break;
378         case XK_Tab:
379                 if(sel)
380                         strncpy(text, sel->text, sizeof text);
381                 dinput();
382                 break;
383         }
384         drawmenu();
385 }
386
387 void
388 match(char *pattern) {
389         unsigned int plen;
390         Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
391
392         if(!pattern)
393                 return;
394         plen = strlen(pattern);
395         item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
396         for(i = allitems; i; i = i->next)
397                 if(!fstrncmp(pattern, i->text, plen + 1))
398                         appenditem(i, &lexact, &exactend);
399                 else if(!fstrncmp(pattern, i->text, plen))
400                         appenditem(i, &lprefix, &prefixend);
401                 else if(fstrstr(i->text, pattern))
402                         appenditem(i, &lsubstr, &substrend);
403         if(lexact) {
404                 item = lexact;
405                 itemend = exactend;
406         }
407         if(lprefix) {
408                 if(itemend) {
409                         itemend->right = lprefix;
410                         lprefix->left = itemend;
411                 }
412                 else
413                         item = lprefix;
414                 itemend = prefixend;
415         }
416         if(lsubstr) {
417                 if(itemend) {
418                         itemend->right = lsubstr;
419                         lsubstr->left = itemend;
420                 }
421                 else
422                         item = lsubstr;
423         }
424         curr = prev = next = sel = item;
425         calcoffsets();
426 }
427
428 void
429 readstdin(void) {
430         char *p, buf[sizeof text];
431         unsigned int len = 0, max = 0;
432         Item *i, *new;
433
434         i = NULL;
435         while(fgets(buf, sizeof buf, stdin)) {
436                 len = strlen(buf);
437                 if(buf[len-1] == '\n')
438                         buf[--len] = '\0';
439                 if(!(p = strdup(buf)))
440                         eprint("cannot strdup %u bytes\n", len);
441                 if((max = MAX(max, len)) == len)
442                         maxname = p;
443                 if(!(new = malloc(sizeof *new)))
444                         eprint("cannot malloc %u bytes\n", sizeof *new);
445                 new->next = new->left = new->right = NULL;
446                 new->text = p;
447                 if(!i)
448                         allitems = new;
449                 else 
450                         i->next = new;
451                 i = new;
452         }
453 }
454
455 void
456 run(void) {
457         XEvent ev;
458
459         /* main event loop */
460         while(running && !XNextEvent(dpy, &ev))
461                 switch(ev.type) {
462                 case KeyPress:
463                         kpress(&ev.xkey);
464                         break;
465                 case Expose:
466                         if(ev.xexpose.count == 0)
467                                 drawmenu();
468                         break;
469                 case VisibilityNotify:
470                         if (ev.xvisibility.state != VisibilityUnobscured)
471                                 XRaiseWindow(dpy, win);
472                         break;
473                 }
474 }
475
476 void
477 setup(void) {
478         int i, j, x, y;
479 #if XINERAMA
480         int n;
481         XineramaScreenInfo *info = NULL;
482 #endif
483         XModifierKeymap *modmap;
484         XSetWindowAttributes wa;
485         XWindowAttributes pwa;
486
487         /* init modifier map */
488         modmap = XGetModifierMapping(dpy);
489         for(i = 0; i < 8; i++)
490                 for(j = 0; j < modmap->max_keypermod; j++) {
491                         if(modmap->modifiermap[i * modmap->max_keypermod + j]
492                         == XKeysymToKeycode(dpy, XK_Num_Lock))
493                                 numlockmask = (1 << i);
494                 }
495         XFreeModifiermap(modmap);
496
497         dc.dpy = dpy;
498         normcol[ColBG] = getcolor(&dc, normbgcolor);
499         normcol[ColFG] = getcolor(&dc, normfgcolor);
500         selcol[ColBG] = getcolor(&dc, selbgcolor);
501         selcol[ColFG] = getcolor(&dc, selfgcolor);
502         initfont(&dc, font);
503
504         /* menu window */
505         wa.override_redirect = True;
506         wa.background_pixmap = ParentRelative;
507         wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
508
509         /* menu window geometry */
510         mh = (dc.font.height + 2) * (lines + 1);
511 #if XINERAMA
512         if(parent == RootWindow(dpy, screen) && XineramaIsActive(dpy) && (info = XineramaQueryScreens(dpy, &n))) {
513                 i = 0;
514                 if(n > 1) {
515                         int di;
516                         unsigned int dui;
517                         Window dummy;
518                         if(XQueryPointer(dpy, parent, &dummy, &dummy, &x, &y, &di, &di, &dui))
519                                 for(i = 0; i < n; i++)
520                                         if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
521                                                 break;
522                 }
523                 x = info[i].x_org;
524                 y = topbar ? info[i].y_org : info[i].y_org + info[i].height - mh;
525                 mw = info[i].width;
526                 XFree(info);
527         }
528         else
529 #endif
530         {
531                 if(!XGetWindowAttributes(dpy, parent, &pwa))
532                         eprint("cannot get window attributes");
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
557         /* command line args */
558         progname = argv[0];
559         for(i = 1; i < argc; i++)
560                 if(!strcmp(argv[i], "-i")) {
561                         fstrncmp = strncasecmp;
562                         fstrstr = cistrstr;
563                 }
564                 else if(!strcmp(argv[i], "-b"))
565                         topbar = False;
566                 else if(!strcmp(argv[i], "-e")) {
567                         if(++i < argc) parent = atoi(argv[i]);
568                 }
569                 else if(!strcmp(argv[i], "-l")) {
570                         if(++i < argc) lines = atoi(argv[i]);
571                         if(lines > 0)
572                                 calcoffsets = calcoffsetsv;
573                 }
574                 else if(!strcmp(argv[i], "-fn")) {
575                         if(++i < argc) font = argv[i];
576                 }
577                 else if(!strcmp(argv[i], "-nb")) {
578                         if(++i < argc) normbgcolor = argv[i];
579                 }
580                 else if(!strcmp(argv[i], "-nf")) {
581                         if(++i < argc) normfgcolor = argv[i];
582                 }
583                 else if(!strcmp(argv[i], "-p")) {
584                         if(++i < argc) prompt = argv[i];
585                 }
586                 else if(!strcmp(argv[i], "-sb")) {
587                         if(++i < argc) selbgcolor = argv[i];
588                 }
589                 else if(!strcmp(argv[i], "-sf")) {
590                         if(++i < argc) selfgcolor = argv[i];
591                 }
592                 else if(!strcmp(argv[i], "-v")) {
593                         printf("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n");
594                         exit(EXIT_SUCCESS);
595                 }
596                 else {
597                         fputs("usage: dmenu [-i] [-b] [-e <xid>] [-l <lines>] [-fn <font>] [-nb <color>]\n"
598                                "             [-nf <color>] [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n", stderr);
599                         exit(EXIT_FAILURE);
600                 }
601         if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
602                 fprintf(stderr, "dmenu: warning: no locale support\n");
603         if(!(dpy = XOpenDisplay(NULL)))
604                 eprint("cannot open display\n");
605         screen = DefaultScreen(dpy);
606         if(!parent)
607                 parent = RootWindow(dpy, screen);
608         if(!(argp = malloc(sizeof *argp * (argc+2))))
609                 eprint("cannot malloc %u bytes\n", sizeof *argp * (argc+2));
610         memcpy(argp + 2, argv + 1, sizeof *argp * argc);
611
612         readstdin();
613         running = grabkeyboard();
614
615         setup();
616         drawmenu();
617         XSync(dpy, False);
618         run();
619         cleanup();
620         XCloseDisplay(dpy);
621         return ret;
622 }