]> git.armaanb.net Git - dmenu.git/blob - main.c
fixing arg handling in dmenu (thanks to Sander for his report)
[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 <stdlib.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <sys/select.h>
13 #include <sys/time.h>
14 #include <X11/cursorfont.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         case XK_Left:
174                 if(!(sel && sel->left))
175                         return;
176                 sel=sel->left;
177                 if(sel->right == curr) {
178                         curr = prev;
179                         calcoffsets();
180                 }
181                 break;
182         case XK_Tab:
183                 if(!sel)
184                         return;
185                 strncpy(text, sel->text, sizeof(text));
186                 match(text);
187                 break;
188         case XK_Right:
189                 if(!(sel && sel->right))
190                         return;
191                 sel=sel->right;
192                 if(sel == next) {
193                         curr = next;
194                         calcoffsets();
195                 }
196                 break;
197         case XK_Return:
198                 if((e->state & ShiftMask) && text)
199                         fprintf(stdout, "%s", text);
200                 else if(sel)
201                         fprintf(stdout, "%s", sel->text);
202                 else if(text)
203                         fprintf(stdout, "%s", text);
204                 fflush(stdout);
205                 running = False;
206                 break;
207         case XK_Escape:
208                 ret = 1;
209                 running = False;
210                 break;
211         case XK_BackSpace:
212                 if((i = len)) {
213                         prev_nitem = nitem;
214                         do {
215                                 text[--i] = 0;
216                                 match(text);
217                         } while(i && nitem && prev_nitem == nitem);
218                         match(text);
219                 }
220                 break;
221         default:
222                 if(num && !iscntrl((int) buf[0])) {
223                         buf[num] = 0;
224                         if(len > 0)
225                                 strncat(text, buf, sizeof(text));
226                         else
227                                 strncpy(text, buf, sizeof(text));
228                         match(text);
229                 }
230         }
231         drawmenu();
232 }
233
234 static char *
235 readstdin(void) {
236         static char *maxname = NULL;
237         char *p, buf[1024];
238         unsigned int len = 0, max = 0;
239         Item *i, *new;
240
241         i = 0;
242         while(fgets(buf, sizeof(buf), stdin)) {
243                 len = strlen(buf);
244                 if (buf[len - 1] == '\n')
245                         buf[len - 1] = 0;
246                 p = estrdup(buf);
247                 if(max < len) {
248                         maxname = p;
249                         max = len;
250                 }
251                 new = emalloc(sizeof(Item));
252                 new->next = new->left = new->right = NULL;
253                 new->text = p;
254                 if(!i)
255                         allitems = new;
256                 else 
257                         i->next = new;
258                 i = new;
259         }
260
261         return maxname;
262 }
263
264 /* extern */
265
266 int screen;
267 Display *dpy;
268 DC dc = {0};
269
270 int
271 main(int argc, char *argv[]) {
272         char *font = FONT;
273         char *maxname;
274         char *normbg = NORMBGCOLOR;
275         char *normfg = NORMFGCOLOR;
276         char *selbg = SELBGCOLOR;
277         char *selfg = SELFGCOLOR;
278         fd_set rd;
279         int i;
280         struct timeval timeout;
281         Item *itm;
282         XEvent ev;
283         XSetWindowAttributes wa;
284
285         timeout.tv_usec = 0;
286         timeout.tv_sec = 3;
287         /* command line args */
288         for(i = 1; i < argc; i++)
289                 if(!strncmp(argv[i], "-font", 6)) {
290                         if(++i < argc) font = argv[i];
291                 }
292                 else if(!strncmp(argv[i], "-normbg", 8)) {
293                         if(++i < argc) normbg = argv[i];
294                 }
295                 else if(!strncmp(argv[i], "-normfg", 8)) {
296                         if(++i < argc) normfg = argv[i];
297                 }
298                 else if(!strncmp(argv[i], "-selbg", 7)) {
299                         if(++i < argc) selbg = argv[i];
300                 }
301                 else if(!strncmp(argv[i], "-selfg", 7)) {
302                         if(++i < argc) selfg = argv[i];
303                 }
304                 else if(!strncmp(argv[i], "-t", 3)) {
305                         if(++i < argc) timeout.tv_sec = atoi(argv[i]);
306                 }
307                 else if(!strncmp(argv[i], "-v", 3)) {
308                         fputs("dmenu-"VERSION", (C)opyright MMVI Anselm R. Garbe\n", stdout);
309                         exit(EXIT_SUCCESS);
310                 }
311                 else
312                         eprint("usage: dmenu [-font <name>] [-{norm,sel}{bg,fg} <color>] [-t <seconds>] [-v]\n", stdout);
313         dpy = XOpenDisplay(0);
314         if(!dpy)
315                 eprint("dmenu: cannot open display\n");
316         screen = DefaultScreen(dpy);
317         root = RootWindow(dpy, screen);
318
319         /* Note, the select() construction allows to grab all keypresses as
320          * early as possible, to not loose them. But if there is no standard
321          * input supplied, we will make sure to exit after MAX_WAIT_STDIN
322          * seconds. This is convenience behavior for rapid typers.
323          */ 
324         while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
325                          GrabModeAsync, CurrentTime) != GrabSuccess)
326                 usleep(1000);
327         FD_ZERO(&rd);
328         FD_SET(STDIN_FILENO, &rd);
329         if(select(ConnectionNumber(dpy) + 1, &rd, NULL, NULL, &timeout) < 1)
330                 goto UninitializedEnd;
331         maxname = readstdin();
332         /* style */
333         dc.norm[ColBG] = getcolor(normbg);
334         dc.norm[ColFG] = getcolor(normfg);
335         dc.sel[ColBG] = getcolor(selbg);
336         dc.sel[ColFG] = getcolor(selfg);
337         setfont(font);
338         /* menu window */
339         wa.override_redirect = 1;
340         wa.background_pixmap = ParentRelative;
341         wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
342         mx = my = 0;
343         mw = DisplayWidth(dpy, screen);
344         mh = dc.font.height + 2;
345         win = XCreateWindow(dpy, root, mx, my, mw, mh, 0,
346                         DefaultDepth(dpy, screen), CopyFromParent,
347                         DefaultVisual(dpy, screen),
348                         CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
349         XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm));
350         /* pixmap */
351         dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
352         dc.gc = XCreateGC(dpy, root, 0, 0);
353         XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
354         if(maxname)
355                 cmdw = textw(maxname);
356         if(cmdw > mw / 3)
357                 cmdw = mw / 3;
358         text[0] = 0;
359         match(text);
360         XMapRaised(dpy, win);
361         drawmenu();
362         XSync(dpy, False);
363
364         /* main event loop */
365         while(running && !XNextEvent(dpy, &ev))
366                 switch (ev.type) {
367                 default:        /* ignore all crap */
368                         break;
369                 case KeyPress:
370                         kpress(&ev.xkey);
371                         break;
372                 case Expose:
373                         if(ev.xexpose.count == 0)
374                                 drawmenu();
375                         break;
376                 }
377
378         /* cleanup */
379         while(allitems) {
380                 itm = allitems->next;
381                 free(allitems->text);
382                 free(allitems);
383                 allitems = itm;
384         }
385         if(dc.font.set)
386                 XFreeFontSet(dpy, dc.font.set);
387         else
388                 XFreeFont(dpy, dc.font.xfont);
389         XFreePixmap(dpy, dc.drawable);
390         XFreeGC(dpy, dc.gc);
391         XDestroyWindow(dpy, win);
392 UninitializedEnd:
393         XUngrabKeyboard(dpy, CurrentTime);
394         XCloseDisplay(dpy);
395         return ret;
396 }