0263948964f5afc6a7986305917dea016ddb2dd1
[dmenu.git] / main.c
1 /* (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
2  * (C)opyright MMVI Sander van Dijk <a dot h dot vandijk at gmail dot com>
3  * See LICENSE file for license details.
4  */
5 #include "dmenu.h"
6
7 #include <ctype.h>
8 #include <locale.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <sys/select.h>
14 #include <sys/time.h>
15 #include <X11/Xutil.h>
16 #include <X11/keysym.h>
17
18 typedef struct Item Item;
19 struct Item {
20         Item *next;             /* traverses all items */
21         Item *left, *right;     /* traverses items matching current search pattern */
22         char *text;
23 };
24
25 /* static */
26
27 static char text[4096];
28 static int mx, my, mw, mh;
29 static int ret = 0;
30 static int nitem = 0;
31 static unsigned int cmdw = 0;
32 static Bool running = True;
33 static Item *allitems = NULL;   /* first of all items */
34 static Item *item = NULL;       /* first of pattern matching items */
35 static Item *sel = NULL;
36 static Item *next = NULL;
37 static Item *prev = NULL;
38 static Item *curr = NULL;
39 static Window root;
40 static Window win;
41
42 static void
43 calcoffsets(void) {
44         unsigned int tw, w;
45
46         if(!curr)
47                 return;
48         w = cmdw + 2 * SPACE;
49         for(next = curr; next; next=next->right) {
50                 tw = textw(next->text);
51                 if(tw > mw / 3)
52                         tw = mw / 3;
53                 w += tw;
54                 if(w > mw)
55                         break;
56         }
57         w = cmdw + 2 * SPACE;
58         for(prev = curr; prev && prev->left; prev=prev->left) {
59                 tw = textw(prev->left->text);
60                 if(tw > mw / 3)
61                         tw = mw / 3;
62                 w += tw;
63                 if(w > mw)
64                         break;
65         }
66 }
67
68 static void
69 drawmenu(void) {
70         Item *i;
71
72         dc.x = 0;
73         dc.y = 0;
74         dc.w = mw;
75         dc.h = mh;
76         drawtext(NULL, dc.norm);
77         /* print command */
78         if(cmdw && item)
79                 dc.w = cmdw;
80         drawtext(text[0] ? text : NULL, dc.norm);
81         dc.x += cmdw;
82         if(curr) {
83                 dc.w = SPACE;
84                 drawtext((curr && curr->left) ? "<" : NULL, dc.norm);
85                 dc.x += dc.w;
86                 /* determine maximum items */
87                 for(i = curr; i != next; i=i->right) {
88                         dc.w = textw(i->text);
89                         if(dc.w > mw / 3)
90                                 dc.w = mw / 3;
91                         drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
92                         dc.x += dc.w;
93                 }
94                 dc.x = mw - SPACE;
95                 dc.w = SPACE;
96                 drawtext(next ? ">" : NULL, dc.norm);
97         }
98         XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
99         XFlush(dpy);
100 }
101
102 static void
103 match(char *pattern) {
104         unsigned int plen;
105         Item *i, *j;
106
107         if(!pattern)
108                 return;
109         plen = strlen(pattern);
110         item = j = NULL;
111         nitem = 0;
112         for(i = allitems; i; i=i->next)
113                 if(!plen || !strncmp(pattern, i->text, plen)) {
114                         if(!j)
115                                 item = i;
116                         else
117                                 j->right = i;
118                         i->left = j;
119                         i->right = NULL;
120                         j = i;
121                         nitem++;
122                 }
123         for(i = allitems; i; i=i->next)
124                 if(plen && strncmp(pattern, i->text, plen)
125                                 && strstr(i->text, pattern)) {
126                         if(!j)
127                                 item = i;
128                         else
129                                 j->right = i;
130                         i->left = j;
131                         i->right = NULL;
132                         j = i;
133                         nitem++;
134                 }
135         curr = prev = next = sel = item;
136         calcoffsets();
137 }
138
139 static void
140 kpress(XKeyEvent * e) {
141         char buf[32];
142         int num, prev_nitem;
143         unsigned int i, len;
144         KeySym ksym;
145
146         len = strlen(text);
147         buf[0] = 0;
148         num = XLookupString(e, buf, sizeof buf, &ksym, 0);
149         if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
150                         || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
151                         || IsPrivateKeypadKey(ksym))
152                 return;
153         /* first check if a control mask is omitted */
154         if(e->state & ControlMask) {
155                 switch (ksym) {
156                 default:        /* ignore other control sequences */
157                         return;
158                         break;
159                 case XK_h:
160                 case XK_H:
161                         ksym = XK_BackSpace;
162                         break;
163                 case XK_u:
164                 case XK_U:
165                         text[0] = 0;
166                         match(text);
167                         drawmenu();
168                         return;
169                         break;
170                 }
171         }
172         switch(ksym) {
173         default:
174                 if(num && !iscntrl((int) buf[0])) {
175                         buf[num] = 0;
176                         if(len > 0)
177                                 strncat(text, buf, sizeof text);
178                         else
179                                 strncpy(text, buf, sizeof text);
180                         match(text);
181                 }
182                 break;
183         case XK_BackSpace:
184                 if((i = len)) {
185                         prev_nitem = nitem;
186                         do {
187                                 text[--i] = 0;
188                                 match(text);
189                         } while(i && nitem && prev_nitem == nitem);
190                         match(text);
191                 }
192                 break;
193         case XK_End:
194                 while(next) {
195                         sel = curr = next;
196                         calcoffsets();
197                 }
198                 while(sel->right)
199                         sel = sel->right;
200                 break;
201         case XK_Escape:
202                 ret = 1;
203                 running = False;
204                 break;
205         case XK_Home:
206                 sel = curr = item;
207                 calcoffsets();
208                 break;
209         case XK_Left:
210                 if(!(sel && sel->left))
211                         return;
212                 sel=sel->left;
213                 if(sel->right == curr) {
214                         curr = prev;
215                         calcoffsets();
216                 }
217                 break;
218         case XK_Next:
219                 if(next) {
220                         sel = curr = next;
221                         calcoffsets();
222                 }
223                 break;
224         case XK_Prior:
225                 if(prev) {
226                         sel = curr = prev;
227                         calcoffsets();
228                 }
229                 break;
230         case XK_Return:
231                 if((e->state & ShiftMask) && text)
232                         fprintf(stdout, "%s", text);
233                 else if(sel)
234                         fprintf(stdout, "%s", sel->text);
235                 else if(text)
236                         fprintf(stdout, "%s", text);
237                 fflush(stdout);
238                 running = False;
239                 break;
240         case XK_Right:
241                 if(!(sel && sel->right))
242                         return;
243                 sel=sel->right;
244                 if(sel == next) {
245                         curr = next;
246                         calcoffsets();
247                 }
248                 break;
249         case XK_Tab:
250                 if(!sel)
251                         return;
252                 strncpy(text, sel->text, sizeof text);
253                 match(text);
254                 break;
255         }
256         drawmenu();
257 }
258
259 static char *
260 readstdin(void) {
261         static char *maxname = NULL;
262         char *p, buf[1024];
263         unsigned int len = 0, max = 0;
264         Item *i, *new;
265
266         i = 0;
267         while(fgets(buf, sizeof buf, stdin)) {
268                 len = strlen(buf);
269                 if (buf[len - 1] == '\n')
270                         buf[len - 1] = 0;
271                 p = estrdup(buf);
272                 if(max < len) {
273                         maxname = p;
274                         max = len;
275                 }
276                 new = emalloc(sizeof(Item));
277                 new->next = new->left = new->right = NULL;
278                 new->text = p;
279                 if(!i)
280                         allitems = new;
281                 else 
282                         i->next = new;
283                 i = new;
284         }
285
286         return maxname;
287 }
288
289 /* extern */
290
291 int screen;
292 Display *dpy;
293 DC dc = {0};
294
295 int
296 main(int argc, char *argv[]) {
297         char *font = FONT;
298         char *maxname;
299         char *normbg = NORMBGCOLOR;
300         char *normfg = NORMFGCOLOR;
301         char *selbg = SELBGCOLOR;
302         char *selfg = SELFGCOLOR;
303         fd_set rd;
304         int i;
305         struct timeval timeout;
306         Item *itm;
307         XEvent ev;
308         XSetWindowAttributes wa;
309
310         timeout.tv_usec = 0;
311         timeout.tv_sec = 3;
312         /* command line args */
313         for(i = 1; i < argc; i++)
314                 if(!strncmp(argv[i], "-font", 6)) {
315                         if(++i < argc) font = argv[i];
316                 }
317                 else if(!strncmp(argv[i], "-normbg", 8)) {
318                         if(++i < argc) normbg = argv[i];
319                 }
320                 else if(!strncmp(argv[i], "-normfg", 8)) {
321                         if(++i < argc) normfg = argv[i];
322                 }
323                 else if(!strncmp(argv[i], "-selbg", 7)) {
324                         if(++i < argc) selbg = argv[i];
325                 }
326                 else if(!strncmp(argv[i], "-selfg", 7)) {
327                         if(++i < argc) selfg = argv[i];
328                 }
329                 else if(!strncmp(argv[i], "-t", 3)) {
330                         if(++i < argc) timeout.tv_sec = atoi(argv[i]);
331                 }
332                 else if(!strncmp(argv[i], "-v", 3)) {
333                         fputs("dmenu-"VERSION", (C)opyright MMVI Anselm R. Garbe\n", stdout);
334                         exit(EXIT_SUCCESS);
335                 }
336                 else
337                         eprint("usage: dmenu [-font <name>] [-{norm,sel}{bg,fg} <color>] [-t <seconds>] [-v]\n", stdout);
338         setlocale(LC_CTYPE, "");
339         dpy = XOpenDisplay(0);
340         if(!dpy)
341                 eprint("dmenu: cannot open display\n");
342         screen = DefaultScreen(dpy);
343         root = RootWindow(dpy, screen);
344
345         /* Note, the select() construction allows to grab all keypresses as
346          * early as possible, to not loose them. But if there is no standard
347          * input supplied, we will make sure to exit after MAX_WAIT_STDIN
348          * seconds. This is convenience behavior for rapid typers.
349          */ 
350         while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
351                          GrabModeAsync, CurrentTime) != GrabSuccess)
352                 usleep(1000);
353         FD_ZERO(&rd);
354         FD_SET(STDIN_FILENO, &rd);
355         if(select(ConnectionNumber(dpy) + 1, &rd, NULL, NULL, &timeout) < 1)
356                 goto UninitializedEnd;
357         maxname = readstdin();
358         /* style */
359         dc.norm[ColBG] = getcolor(normbg);
360         dc.norm[ColFG] = getcolor(normfg);
361         dc.sel[ColBG] = getcolor(selbg);
362         dc.sel[ColFG] = getcolor(selfg);
363         setfont(font);
364         /* menu window */
365         wa.override_redirect = 1;
366         wa.background_pixmap = ParentRelative;
367         wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
368         mx = my = 0;
369         mw = DisplayWidth(dpy, screen);
370         mh = dc.font.height + 2;
371         win = XCreateWindow(dpy, root, mx, my, mw, mh, 0,
372                         DefaultDepth(dpy, screen), CopyFromParent,
373                         DefaultVisual(dpy, screen),
374                         CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
375         /* pixmap */
376         dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
377         dc.gc = XCreateGC(dpy, root, 0, 0);
378         XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
379         if(maxname)
380                 cmdw = textw(maxname);
381         if(cmdw > mw / 3)
382                 cmdw = mw / 3;
383         text[0] = 0;
384         match(text);
385         XMapRaised(dpy, win);
386         drawmenu();
387         XSync(dpy, False);
388
389         /* main event loop */
390         while(running && !XNextEvent(dpy, &ev))
391                 switch (ev.type) {
392                 default:        /* ignore all crap */
393                         break;
394                 case KeyPress:
395                         kpress(&ev.xkey);
396                         break;
397                 case Expose:
398                         if(ev.xexpose.count == 0)
399                                 drawmenu();
400                         break;
401                 }
402
403         /* cleanup */
404         while(allitems) {
405                 itm = allitems->next;
406                 free(allitems->text);
407                 free(allitems);
408                 allitems = itm;
409         }
410         if(dc.font.set)
411                 XFreeFontSet(dpy, dc.font.set);
412         else
413                 XFreeFont(dpy, dc.font.xfont);
414         XFreePixmap(dpy, dc.drawable);
415         XFreeGC(dpy, dc.gc);
416         XDestroyWindow(dpy, win);
417 UninitializedEnd:
418         XUngrabKeyboard(dpy, CurrentTime);
419         XCloseDisplay(dpy);
420         return ret;
421 }