]> git.armaanb.net Git - dmenu.git/blob - dmenu.c
updated to libdraw tip
[dmenu.git] / dmenu.c
1 /* See LICENSE file for copyright and license details. */
2 #include <ctype.h>
3 #include <locale.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <X11/keysym.h>
9 #include <X11/Xlib.h>
10 #include <X11/Xutil.h>
11 #include "dmenu.h"
12
13 typedef struct Item Item;
14 struct Item {
15         char *text;
16         Item *next;         /* traverses all items */
17         Item *left, *right; /* traverses items matching current search pattern */
18 };
19
20 /* forward declarations */
21 static void appenditem(Item *i, Item **list, Item **last);
22 static void calcoffsetsh(void);
23 static void calcoffsetsv(void);
24 static char *cistrstr(const char *s, const char *sub);
25 static void cleanup(void);
26 static void dinput(void);
27 static void drawmenuh(void);
28 static void drawmenuv(void);
29 static void match(void);
30 static void readstdin(void);
31
32 /* variables */
33 static char **argp = NULL;
34 static char *maxname = NULL;
35 static unsigned int cmdw = 0;
36 static unsigned int lines = 0;
37 static Item *allitems = NULL;  /* first of all items */
38 static Item *item = NULL;      /* first of pattern matching items */
39 static Item *sel = NULL;
40 static Item *next = NULL;
41 static Item *prev = NULL;
42 static Item *curr = NULL;
43 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
44 static char *(*fstrstr)(const char *, const char *) = strstr;
45 static void (*calcoffsets)(void) = calcoffsetsh;
46
47 void
48 appenditem(Item *i, Item **list, Item **last) {
49         if(!(*last))
50                 *list = i;
51         else
52                 (*last)->right = i;
53         i->left = *last;
54         i->right = NULL;
55         *last = i;
56 }
57
58 void
59 calcoffsetsh(void) {
60         unsigned int w, x;
61
62         w = promptw + cmdw + textw(&dc, "<") + textw(&dc, ">");
63         for(x = w, next = curr; next; next = next->right)
64                 if((x += MIN(textw(&dc, next->text), mw / 3)) > mw)
65                         break;
66         for(x = w, prev = curr; prev && prev->left; prev = prev->left)
67                 if((x += MIN(textw(&dc, prev->left->text), mw / 3)) > mw)
68                         break;
69 }
70
71 void
72 calcoffsetsv(void) {
73         unsigned int i;
74
75         next = prev = curr;
76         for(i = 0; i < lines && next; i++)
77                 next = next->right;
78         mh = (dc.font.height + 2) * (i + 1);
79         for(i = 0; i < lines && prev && prev->left; i++)
80                 prev = prev->left;
81 }
82
83 char *
84 cistrstr(const char *s, const char *sub) {
85         int c, csub;
86         unsigned int len;
87
88         if(!sub)
89                 return (char *)s;
90         if((c = tolower(*sub++)) != '\0') {
91                 len = strlen(sub);
92                 do {
93                         do {
94                                 if((csub = *s++) == '\0')
95                                         return NULL;
96                         }
97                         while(tolower(csub) != c);
98                 }
99                 while(strncasecmp(s, sub, len) != 0);
100                 s--;
101         }
102         return (char *)s;
103 }
104
105 void
106 cleanup(void) {
107         Item *itm;
108
109         while(allitems) {
110                 itm = allitems->next;
111                 free(allitems->text);
112                 free(allitems);
113                 allitems = itm;
114         }
115         cleanupdraw(&dc);
116         XDestroyWindow(dpy, win);
117         XUngrabKeyboard(dpy, CurrentTime);
118         XCloseDisplay(dpy);
119 }
120
121 void
122 dinput(void) {
123         cleanup();
124         argp[0] = "dinput";
125         argp[1] = text;
126         execvp("dinput", argp);
127         eprint("cannot exec dinput\n");
128 }
129
130 void
131 drawbar(void) {
132         dc.x = 0;
133         dc.y = 0;
134         dc.w = mw;
135         dc.h = mh;
136         drawbox(&dc, normcol);
137         dc.h = dc.font.height + 2;
138         dc.y = topbar ? 0 : mh - dc.h;
139         /* print prompt? */
140         if(prompt) {
141                 dc.w = promptw;
142                 drawtext(&dc, prompt, selcol);
143                 dc.x += dc.w;
144         }
145         dc.w = mw - dc.x;
146         /* print command */
147         if(cmdw && item && lines == 0)
148                 dc.w = cmdw;
149         drawtext(&dc, text, normcol);
150         if(lines > 0)
151                 drawmenuv();
152         else if(curr)
153                 drawmenuh();
154         commitdraw(&dc, win);
155 }
156
157 void
158 drawmenuh(void) {
159         unsigned long *col;
160         Item *i;
161
162         dc.x += cmdw;
163         dc.w = textw(&dc, "<");
164         drawtext(&dc, curr->left ? "<" : NULL, normcol);
165         dc.x += dc.w;
166         for(i = curr; i != next; i = i->right) {
167                 dc.w = MIN(textw(&dc, i->text), mw / 3);
168                 col = (sel == i) ? selcol : normcol;
169                 drawbox(&dc, col);
170                 drawtext(&dc, i->text, col);
171                 dc.x += dc.w;
172         }
173         dc.w = textw(&dc, ">");
174         dc.x = mw - dc.w;
175         drawtext(&dc, next ? ">" : NULL, normcol);
176 }
177
178 void
179 drawmenuv(void) {
180         Item *i;
181         XWindowAttributes wa;
182
183         dc.y = topbar ? dc.h : 0;
184         dc.w = mw - dc.x;
185         for(i = curr; i != next; i = i->right) {
186                 drawtext(&dc, i->text, (sel == i) ? selcol : normcol);
187                 dc.y += dc.h;
188         }
189         if(!XGetWindowAttributes(dpy, win, &wa))
190                 eprint("cannot get window attributes");
191         XMoveResizeWindow(dpy, win, wa.x, wa.y + (topbar ? 0 : wa.height - mh), mw, mh);
192 }
193
194 void
195 kpress(XKeyEvent *e) {
196         char buf[sizeof text];
197         int num;
198         unsigned int i, len;
199         KeySym ksym;
200
201         len = strlen(text);
202         num = XLookupString(e, buf, sizeof buf, &ksym, NULL);
203         if(ksym == XK_KP_Enter)
204                 ksym = XK_Return;
205         else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
206                 ksym = (ksym - XK_KP_0) + XK_0;
207         else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
208         || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
209         || IsPrivateKeypadKey(ksym))
210                 return;
211         /* first check if a control mask is omitted */
212         if(e->state & ControlMask) {
213                 switch(tolower(ksym)) {
214                 default:
215                         return;
216                 case XK_a:
217                         ksym = XK_Home;
218                         break;
219                 case XK_b:
220                         ksym = XK_Left;
221                         break;
222                 case XK_c:
223                         ksym = XK_Escape;
224                         break;
225                 case XK_e:
226                         ksym = XK_End;
227                         break;
228                 case XK_f:
229                         ksym = XK_Right;
230                         break;
231                 case XK_h:
232                         ksym = XK_BackSpace;
233                         break;
234                 case XK_i:
235                         ksym = XK_Tab;
236                         break;
237                 case XK_j:
238                 case XK_m:
239                         ksym = XK_Return;
240                         break;
241                 case XK_n:
242                         ksym = XK_Down;
243                         break;
244                 case XK_p:
245                         ksym = XK_Up;
246                         break;
247                 case XK_u:
248                         text[0] = '\0';
249                         match();
250                         break;
251                 case XK_w:
252                         if(len == 0)
253                                 return;
254                         i = len;
255                         while(i-- > 0 && text[i] == ' ');
256                         while(i-- > 0 && text[i] != ' ');
257                         text[++i] = '\0';
258                         match();
259                         break;
260                 }
261         }
262         switch(ksym) {
263         default:
264                 num = MIN(num, sizeof text);
265                 if(num && !iscntrl((int) buf[0])) {
266                         memcpy(text + len, buf, num + 1);
267                         len += num;
268                         match();
269                 }
270                 break;
271         case XK_BackSpace:
272                 if(len == 0)
273                         return;
274                 for(i = 1; len - i > 0 && !IS_UTF8_1ST_CHAR(text[len - i]); i++);
275                 len -= i;
276                 text[len] = '\0';
277                 match();
278                 break;
279         case XK_End:
280                 while(next) {
281                         sel = curr = next;
282                         calcoffsets();
283                 }
284                 while(sel && sel->right)
285                         sel = sel->right;
286                 break;
287         case XK_Escape:
288                 exit(EXIT_FAILURE);
289         case XK_Home:
290                 sel = curr = item;
291                 calcoffsets();
292                 break;
293         case XK_Left:
294         case XK_Up:
295                 if(!sel || !sel->left)
296                         return;
297                 sel = sel->left;
298                 if(sel->right == curr) {
299                         curr = prev;
300                         calcoffsets();
301                 }
302                 break;
303         case XK_Next:
304                 if(!next)
305                         return;
306                 sel = curr = next;
307                 calcoffsets();
308                 break;
309         case XK_Prior:
310                 if(!prev)
311                         return;
312                 sel = curr = prev;
313                 calcoffsets();
314                 break;
315         case XK_Return:
316                 if(e->state & ShiftMask)
317                         dinput();
318                 fprintf(stdout, "%s", sel ? sel->text : text);
319                 fflush(stdout);
320                 exit(EXIT_SUCCESS);
321         case XK_Right:
322         case XK_Down:
323                 if(!sel || !sel->right)
324                         return;
325                 sel = sel->right;
326                 if(sel == next) {
327                         curr = next;
328                         calcoffsets();
329                 }
330                 break;
331         case XK_Tab:
332                 if(sel)
333                         strncpy(text, sel->text, sizeof text);
334                 dinput();
335                 break;
336         }
337         drawbar();
338 }
339
340 void
341 match(void) {
342         unsigned int len;
343         Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
344
345         len = strlen(text);
346         item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
347         for(i = allitems; i; i = i->next)
348                 if(!fstrncmp(text, i->text, len + 1))
349                         appenditem(i, &lexact, &exactend);
350                 else if(!fstrncmp(text, i->text, len))
351                         appenditem(i, &lprefix, &prefixend);
352                 else if(fstrstr(i->text, text))
353                         appenditem(i, &lsubstr, &substrend);
354         if(lexact) {
355                 item = lexact;
356                 itemend = exactend;
357         }
358         if(lprefix) {
359                 if(itemend) {
360                         itemend->right = lprefix;
361                         lprefix->left = itemend;
362                 }
363                 else
364                         item = lprefix;
365                 itemend = prefixend;
366         }
367         if(lsubstr) {
368                 if(itemend) {
369                         itemend->right = lsubstr;
370                         lsubstr->left = itemend;
371                 }
372                 else
373                         item = lsubstr;
374         }
375         curr = prev = next = sel = item;
376         calcoffsets();
377 }
378
379 void
380 readstdin(void) {
381         char *p, buf[sizeof text];
382         unsigned int len = 0, max = 0;
383         Item *i, *new;
384
385         i = NULL;
386         while(fgets(buf, sizeof buf, stdin)) {
387                 len = strlen(buf);
388                 if(buf[len-1] == '\n')
389                         buf[--len] = '\0';
390                 if(!(p = strdup(buf)))
391                         eprint("cannot strdup %u bytes\n", len);
392                 if((max = MAX(max, len)) == len)
393                         maxname = p;
394                 if(!(new = malloc(sizeof *new)))
395                         eprint("cannot malloc %u bytes\n", sizeof *new);
396                 new->next = new->left = new->right = NULL;
397                 new->text = p;
398                 if(!i)
399                         allitems = new;
400                 else 
401                         i->next = new;
402                 i = new;
403         }
404 }
405
406 int
407 main(int argc, char *argv[]) {
408         unsigned int i;
409
410         /* command line args */
411         progname = "dmenu";
412         for(i = 1; i < argc; i++)
413                 if(!strcmp(argv[i], "-i")) {
414                         fstrncmp = strncasecmp;
415                         fstrstr = cistrstr;
416                 }
417                 else if(!strcmp(argv[i], "-b"))
418                         topbar = False;
419                 else if(!strcmp(argv[i], "-l")) {
420                         if(++i < argc) lines = atoi(argv[i]);
421                         if(lines > 0)
422                                 calcoffsets = calcoffsetsv;
423                 }
424                 else if(!strcmp(argv[i], "-fn")) {
425                         if(++i < argc) font = argv[i];
426                 }
427                 else if(!strcmp(argv[i], "-nb")) {
428                         if(++i < argc) normbgcolor = argv[i];
429                 }
430                 else if(!strcmp(argv[i], "-nf")) {
431                         if(++i < argc) normfgcolor = argv[i];
432                 }
433                 else if(!strcmp(argv[i], "-p")) {
434                         if(++i < argc) prompt = argv[i];
435                 }
436                 else if(!strcmp(argv[i], "-sb")) {
437                         if(++i < argc) selbgcolor = argv[i];
438                 }
439                 else if(!strcmp(argv[i], "-sf")) {
440                         if(++i < argc) selfgcolor = argv[i];
441                 }
442                 else if(!strcmp(argv[i], "-v")) {
443                         printf("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n");
444                         exit(EXIT_SUCCESS);
445                 }
446                 else {
447                         fputs("usage: dmenu [-i] [-b] [-l <lines>] [-fn <font>] [-nb <color>]\n"
448                               "             [-nf <color>] [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n", stderr);
449                         exit(EXIT_FAILURE);
450                 }
451         if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
452                 fprintf(stderr, "dmenu: warning: no locale support\n");
453         if(!(dpy = XOpenDisplay(NULL)))
454                 eprint("cannot open display\n");
455         if(atexit(&cleanup) != 0)
456                 eprint("cannot register cleanup\n");
457         screen = DefaultScreen(dpy);
458         root = RootWindow(dpy, screen);
459         if(!(argp = malloc(sizeof *argp * (argc+2))))
460                 eprint("cannot malloc %u bytes\n", sizeof *argp * (argc+2));
461         memcpy(argp + 2, argv + 1, sizeof *argp * argc);
462
463         readstdin();
464         grabkeyboard();
465         setup(lines);
466         if(maxname)
467                 cmdw = MIN(textw(&dc, maxname), mw / 3);
468         match();
469         run();
470         return 0;
471 }