added dmenu.h, common.c
[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         drawtext(&dc, NULL, 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         Item *i;
160
161         dc.x += cmdw;
162         dc.w = textw(&dc, "<");
163         drawtext(&dc, curr->left ? "<" : NULL, normcol);
164         dc.x += dc.w;
165         for(i = curr; i != next; i = i->right) {
166                 dc.w = MIN(textw(&dc, i->text), mw / 3);
167                 drawtext(&dc, i->text, (sel == i) ? selcol : normcol);
168                 dc.x += dc.w;
169         }
170         dc.w = textw(&dc, ">");
171         dc.x = mw - dc.w;
172         drawtext(&dc, next ? ">" : NULL, normcol);
173 }
174
175 void
176 drawmenuv(void) {
177         Item *i;
178         XWindowAttributes wa;
179
180         dc.y = topbar ? dc.h : 0;
181         dc.w = mw - dc.x;
182         for(i = curr; i != next; i = i->right) {
183                 drawtext(&dc, i->text, (sel == i) ? selcol : normcol);
184                 dc.y += dc.h;
185         }
186         if(!XGetWindowAttributes(dpy, win, &wa))
187                 eprint("cannot get window attributes");
188         XMoveResizeWindow(dpy, win, wa.x, wa.y + (topbar ? 0 : wa.height - mh), mw, mh);
189 }
190
191 void
192 kpress(XKeyEvent *e) {
193         char buf[sizeof text];
194         int num;
195         unsigned int i, len;
196         KeySym ksym;
197
198         len = strlen(text);
199         num = XLookupString(e, buf, sizeof buf, &ksym, NULL);
200         if(ksym == XK_KP_Enter)
201                 ksym = XK_Return;
202         else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
203                 ksym = (ksym - XK_KP_0) + XK_0;
204         else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
205         || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
206         || IsPrivateKeypadKey(ksym))
207                 return;
208         /* first check if a control mask is omitted */
209         if(e->state & ControlMask) {
210                 switch(tolower(ksym)) {
211                 default:
212                         return;
213                 case XK_a:
214                         ksym = XK_Home;
215                         break;
216                 case XK_b:
217                         ksym = XK_Left;
218                         break;
219                 case XK_c:
220                         ksym = XK_Escape;
221                         break;
222                 case XK_e:
223                         ksym = XK_End;
224                         break;
225                 case XK_f:
226                         ksym = XK_Right;
227                         break;
228                 case XK_h:
229                         ksym = XK_BackSpace;
230                         break;
231                 case XK_i:
232                         ksym = XK_Tab;
233                         break;
234                 case XK_j:
235                 case XK_m:
236                         ksym = XK_Return;
237                         break;
238                 case XK_n:
239                         ksym = XK_Down;
240                         break;
241                 case XK_p:
242                         ksym = XK_Up;
243                         break;
244                 case XK_u:
245                         text[0] = '\0';
246                         match();
247                         break;
248                 case XK_w:
249                         if(len == 0)
250                                 return;
251                         i = len;
252                         while(i-- > 0 && text[i] == ' ');
253                         while(i-- > 0 && text[i] != ' ');
254                         text[++i] = '\0';
255                         match();
256                         break;
257                 }
258         }
259         switch(ksym) {
260         default:
261                 num = MIN(num, sizeof text);
262                 if(num && !iscntrl((int) buf[0])) {
263                         memcpy(text + len, buf, num + 1);
264                         len += num;
265                         match();
266                 }
267                 break;
268         case XK_BackSpace:
269                 if(len == 0)
270                         return;
271                 for(i = 1; len - i > 0 && !IS_UTF8_1ST_CHAR(text[len - i]); i++);
272                 len -= i;
273                 text[len] = '\0';
274                 match();
275                 break;
276         case XK_End:
277                 while(next) {
278                         sel = curr = next;
279                         calcoffsets();
280                 }
281                 while(sel && sel->right)
282                         sel = sel->right;
283                 break;
284         case XK_Escape:
285                 exit(EXIT_FAILURE);
286         case XK_Home:
287                 sel = curr = item;
288                 calcoffsets();
289                 break;
290         case XK_Left:
291         case XK_Up:
292                 if(!sel || !sel->left)
293                         return;
294                 sel = sel->left;
295                 if(sel->right == curr) {
296                         curr = prev;
297                         calcoffsets();
298                 }
299                 break;
300         case XK_Next:
301                 if(!next)
302                         return;
303                 sel = curr = next;
304                 calcoffsets();
305                 break;
306         case XK_Prior:
307                 if(!prev)
308                         return;
309                 sel = curr = prev;
310                 calcoffsets();
311                 break;
312         case XK_Return:
313                 if(e->state & ShiftMask)
314                         dinput();
315                 fprintf(stdout, "%s", sel ? sel->text : text);
316                 fflush(stdout);
317                 exit(EXIT_SUCCESS);
318         case XK_Right:
319         case XK_Down:
320                 if(!sel || !sel->right)
321                         return;
322                 sel = sel->right;
323                 if(sel == next) {
324                         curr = next;
325                         calcoffsets();
326                 }
327                 break;
328         case XK_Tab:
329                 if(sel)
330                         strncpy(text, sel->text, sizeof text);
331                 dinput();
332                 break;
333         }
334         drawbar();
335 }
336
337 void
338 match(void) {
339         unsigned int len;
340         Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
341
342         len = strlen(text);
343         item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
344         for(i = allitems; i; i = i->next)
345                 if(!fstrncmp(text, i->text, len + 1))
346                         appenditem(i, &lexact, &exactend);
347                 else if(!fstrncmp(text, i->text, len))
348                         appenditem(i, &lprefix, &prefixend);
349                 else if(fstrstr(i->text, text))
350                         appenditem(i, &lsubstr, &substrend);
351         if(lexact) {
352                 item = lexact;
353                 itemend = exactend;
354         }
355         if(lprefix) {
356                 if(itemend) {
357                         itemend->right = lprefix;
358                         lprefix->left = itemend;
359                 }
360                 else
361                         item = lprefix;
362                 itemend = prefixend;
363         }
364         if(lsubstr) {
365                 if(itemend) {
366                         itemend->right = lsubstr;
367                         lsubstr->left = itemend;
368                 }
369                 else
370                         item = lsubstr;
371         }
372         curr = prev = next = sel = item;
373         calcoffsets();
374 }
375
376 void
377 readstdin(void) {
378         char *p, buf[sizeof text];
379         unsigned int len = 0, max = 0;
380         Item *i, *new;
381
382         i = NULL;
383         while(fgets(buf, sizeof buf, stdin)) {
384                 len = strlen(buf);
385                 if(buf[len-1] == '\n')
386                         buf[--len] = '\0';
387                 if(!(p = strdup(buf)))
388                         eprint("cannot strdup %u bytes\n", len);
389                 if((max = MAX(max, len)) == len)
390                         maxname = p;
391                 if(!(new = malloc(sizeof *new)))
392                         eprint("cannot malloc %u bytes\n", sizeof *new);
393                 new->next = new->left = new->right = NULL;
394                 new->text = p;
395                 if(!i)
396                         allitems = new;
397                 else 
398                         i->next = new;
399                 i = new;
400         }
401 }
402
403 int
404 main(int argc, char *argv[]) {
405         unsigned int i;
406
407         /* command line args */
408         progname = "dmenu";
409         for(i = 1; i < argc; i++)
410                 if(!strcmp(argv[i], "-i")) {
411                         fstrncmp = strncasecmp;
412                         fstrstr = cistrstr;
413                 }
414                 else if(!strcmp(argv[i], "-b"))
415                         topbar = False;
416                 else if(!strcmp(argv[i], "-l")) {
417                         if(++i < argc) lines = atoi(argv[i]);
418                         if(lines > 0)
419                                 calcoffsets = calcoffsetsv;
420                 }
421                 else if(!strcmp(argv[i], "-fn")) {
422                         if(++i < argc) font = argv[i];
423                 }
424                 else if(!strcmp(argv[i], "-nb")) {
425                         if(++i < argc) normbgcolor = argv[i];
426                 }
427                 else if(!strcmp(argv[i], "-nf")) {
428                         if(++i < argc) normfgcolor = argv[i];
429                 }
430                 else if(!strcmp(argv[i], "-p")) {
431                         if(++i < argc) prompt = argv[i];
432                 }
433                 else if(!strcmp(argv[i], "-sb")) {
434                         if(++i < argc) selbgcolor = argv[i];
435                 }
436                 else if(!strcmp(argv[i], "-sf")) {
437                         if(++i < argc) selfgcolor = argv[i];
438                 }
439                 else if(!strcmp(argv[i], "-v")) {
440                         printf("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n");
441                         exit(EXIT_SUCCESS);
442                 }
443                 else {
444                         fputs("usage: dmenu [-i] [-b] [-l <lines>] [-fn <font>] [-nb <color>]\n"
445                               "             [-nf <color>] [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n", stderr);
446                         exit(EXIT_FAILURE);
447                 }
448         if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
449                 fprintf(stderr, "dmenu: warning: no locale support\n");
450         if(!(dpy = XOpenDisplay(NULL)))
451                 eprint("cannot open display\n");
452         if(atexit(&cleanup) != 0)
453                 eprint("cannot register cleanup\n");
454         screen = DefaultScreen(dpy);
455         root = RootWindow(dpy, screen);
456         if(!(argp = malloc(sizeof *argp * (argc+2))))
457                 eprint("cannot malloc %u bytes\n", sizeof *argp * (argc+2));
458         memcpy(argp + 2, argv + 1, sizeof *argp * argc);
459
460         readstdin();
461         grabkeyboard();
462         setup(lines);
463         if(maxname)
464                 cmdw = MIN(textw(&dc, maxname), mw / 3);
465         match();
466         run();
467         return 0;
468 }