removed -t title crap from dmenu
[dmenu.git] / main.c
1 /*
2  * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
3  * (C)opyright MMVI Sander van Dijk <a dot h dot vandijk at gmail dot com>
4  * See LICENSE file for license details.
5  */
6
7 #include "dmenu.h"
8
9 #include <ctype.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.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 done = False;
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()
44 {
45         unsigned int tw, w;
46
47         if(!curr)
48                 return;
49
50         w = cmdw + 2 * SPACE;
51         for(next = curr; next; next=next->right) {
52                 tw = textw(next->text);
53                 if(tw > mw / 3)
54                         tw = mw / 3;
55                 w += tw;
56                 if(w > mw)
57                         break;
58         }
59
60         w = cmdw + 2 * SPACE;
61         for(prev = curr; prev && prev->left; prev=prev->left) {
62                 tw = textw(prev->left->text);
63                 if(tw > mw / 3)
64                         tw = mw / 3;
65                 w += tw;
66                 if(w > mw)
67                         break;
68         }
69 }
70
71 static void
72 drawmenu()
73 {
74         Item *i;
75
76         dc.x = 0;
77         dc.y = 0;
78         dc.w = mw;
79         dc.h = mh;
80         drawtext(NULL, False, False);
81
82         /* print command */
83         if(cmdw && item)
84                 dc.w = cmdw;
85         drawtext(text[0] ? text : NULL, False, False);
86         dc.x += cmdw;
87
88         if(curr) {
89                 dc.w = SPACE;
90                 drawtext((curr && curr->left) ? "<" : NULL, False, False);
91                 dc.x += dc.w;
92
93                 /* determine maximum items */
94                 for(i = curr; i != next; i=i->right) {
95                         dc.border = False;
96                         dc.w = textw(i->text);
97                         if(dc.w > mw / 3)
98                                 dc.w = mw / 3;
99                         drawtext(i->text, sel == i, sel == i);
100                         dc.x += dc.w;
101                 }
102
103                 dc.x = mw - SPACE;
104                 dc.w = SPACE;
105                 drawtext(next ? ">" : NULL, False, False);
106         }
107         XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
108         XFlush(dpy);
109 }
110
111 static void
112 input(char *pattern)
113 {
114         unsigned int plen;
115         Item *i, *j;
116
117         if(!pattern)
118                 return;
119
120         plen = strlen(pattern);
121         item = j = NULL;
122         nitem = 0;
123
124         for(i = allitems; i; i=i->next)
125                 if(!plen || !strncmp(pattern, i->text, plen)) {
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         for(i = allitems; i; i=i->next)
136                 if(plen && strncmp(pattern, i->text, plen)
137                                 && strstr(i->text, pattern)) {
138                         if(!j)
139                                 item = i;
140                         else
141                                 j->right = i;
142                         i->left = j;
143                         i->right = NULL;
144                         j = i;
145                         nitem++;
146                 }
147
148         curr = prev = next = sel = item;
149         calcoffsets();
150 }
151
152 static void
153 kpress(XKeyEvent * e)
154 {
155         char buf[32];
156         int num, prev_nitem;
157         unsigned int i, len;
158         KeySym ksym;
159
160         len = strlen(text);
161         buf[0] = 0;
162         num = XLookupString(e, buf, sizeof(buf), &ksym, 0);
163
164         if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
165                         || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
166                         || IsPrivateKeypadKey(ksym))
167                 return;
168
169         /* first check if a control mask is omitted */
170         if(e->state & ControlMask) {
171                 switch (ksym) {
172                 default:        /* ignore other control sequences */
173                         return;
174                         break;
175                 case XK_h:
176                         ksym = XK_BackSpace;
177                         break;
178                 case XK_U:
179                 case XK_u:
180                         text[0] = 0;
181                         input(text);
182                         drawmenu();
183                         return;
184                         break;
185                 case XK_bracketleft:
186                         ksym = XK_Escape;
187                         break;
188                 }
189         }
190         switch(ksym) {
191         case XK_Left:
192                 if(!(sel && sel->left))
193                         return;
194                 sel=sel->left;
195                 if(sel->right == curr) {
196                         curr = prev;
197                         calcoffsets();
198                 }
199                 break;
200         case XK_Tab:
201                 if(!sel)
202                         return;
203                 strncpy(text, sel->text, sizeof(text));
204                 input(text);
205                 break;
206         case XK_Right:
207                 if(!(sel && sel->right))
208                         return;
209                 sel=sel->right;
210                 if(sel == next) {
211                         curr = next;
212                         calcoffsets();
213                 }
214                 break;
215         case XK_Return:
216                 if(e->state & ShiftMask) {
217                         if(text)
218                                 fprintf(stdout, "%s", text);
219                 }
220                 else if(sel)
221                         fprintf(stdout, "%s", sel->text);
222                 else if(text)
223                         fprintf(stdout, "%s", text);
224                 fflush(stdout);
225                 done = True;
226                 break;
227         case XK_Escape:
228                 ret = 1;
229                 done = True;
230                 break;
231         case XK_BackSpace:
232                 if((i = len)) {
233                         prev_nitem = nitem;
234                         do {
235                                 text[--i] = 0;
236                                 input(text);
237                         } while(i && nitem && prev_nitem == nitem);
238                         input(text);
239                 }
240                 break;
241         default:
242                 if(num && !iscntrl((int) buf[0])) {
243                         buf[num] = 0;
244                         if(len > 0)
245                                 strncat(text, buf, sizeof(text));
246                         else
247                                 strncpy(text, buf, sizeof(text));
248                         input(text);
249                 }
250         }
251         drawmenu();
252 }
253
254 static char *
255 readinput()
256 {
257         static char *maxname = NULL;
258         char *p, buf[1024];
259         unsigned int len = 0, max = 0;
260         Item *i, *new;
261
262         i = 0;
263         while(fgets(buf, sizeof(buf), stdin)) {
264                 len = strlen(buf);
265                 if (buf[len - 1] == '\n')
266                         buf[len - 1] = 0;
267                 p = estrdup(buf);
268                 if(max < len) {
269                         maxname = p;
270                         max = len;
271                 }
272
273                 new = emalloc(sizeof(Item));
274                 new->next = new->left = new->right = NULL;
275                 new->text = p;
276                 if(!i)
277                         allitems = new;
278                 else 
279                         i->next = new;
280                 i = new;
281         }
282
283         return maxname;
284 }
285
286 /* extern */
287
288 int screen;
289 Display *dpy;
290 DC dc = {0};
291
292 int
293 main(int argc, char *argv[])
294 {
295         char *maxname;
296         XEvent ev;
297         XSetWindowAttributes wa;
298
299         if(argc == 2 && !strncmp("-v", argv[1], 3)) {
300                 fputs("dmenu-"VERSION", (C)opyright MMVI Anselm R. Garbe\n", stdout);
301                 exit(EXIT_SUCCESS);
302         }
303         else if(argc != 1)
304                 eprint("usage: dmenu [-v]\n");
305
306         dpy = XOpenDisplay(0);
307         if(!dpy)
308                 eprint("dmenu: cannot open display\n");
309         screen = DefaultScreen(dpy);
310         root = RootWindow(dpy, screen);
311
312         maxname = readinput();
313
314         /* grab as early as possible, but after reading all items!!! */
315         while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
316                          GrabModeAsync, CurrentTime) != GrabSuccess)
317                 usleep(1000);
318
319         /* style */
320         dc.bg = getcolor(BGCOLOR);
321         dc.fg = getcolor(FGCOLOR);
322         dc.border = getcolor(BORDERCOLOR);
323         setfont(FONT);
324
325         wa.override_redirect = 1;
326         wa.background_pixmap = ParentRelative;
327         wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
328
329         mx = my = 0;
330         mw = DisplayWidth(dpy, screen);
331         mh = dc.font.height + 4;
332
333         win = XCreateWindow(dpy, root, mx, my, mw, mh, 0,
334                         DefaultDepth(dpy, screen), CopyFromParent,
335                         DefaultVisual(dpy, screen),
336                         CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
337         XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm));
338
339         /* pixmap */
340         dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
341         dc.gc = XCreateGC(dpy, root, 0, 0);
342
343         if(maxname)
344                 cmdw = textw(maxname);
345         if(cmdw > mw / 3)
346                 cmdw = mw / 3;
347
348         text[0] = 0;
349         input(text);
350         XMapRaised(dpy, win);
351         drawmenu();
352         XSync(dpy, False);
353
354         /* main event loop */
355         while(!done && !XNextEvent(dpy, &ev)) {
356                 switch (ev.type) {
357                 case KeyPress:
358                         kpress(&ev.xkey);
359                         break;
360                 case Expose:
361                         if(ev.xexpose.count == 0)
362                                 drawmenu();
363                         break;
364                 default:
365                         break;
366                 }
367         }
368
369         XUngrabKeyboard(dpy, CurrentTime);
370         XFreePixmap(dpy, dc.drawable);
371         XFreeGC(dpy, dc.gc);
372         XDestroyWindow(dpy, win);
373         XCloseDisplay(dpy);
374
375         return ret;
376 }