Xinqi Bao's Git

cleaned up
[dmenu.git] / dmenu.c
1 /* See LICENSE file for copyright and license details. */
2 #include <ctype.h>
3 #include <locale.h>
4 #include <stdarg.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <strings.h>
9 #include <unistd.h>
10 #include <X11/keysym.h>
11 #include <X11/Xlib.h>
12 #include <X11/Xutil.h>
13 #ifdef XINERAMA
14 #include <X11/extensions/Xinerama.h>
15 #endif
16
17 /* macros */
18 #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
19 #define INRECT(X,Y,RX,RY,RW,RH) ((X) >= (RX) && (X) < (RX) + (RW) && (Y) >= (RY) && (Y) < (RY) + (RH))
20 #define MIN(a, b) ((a) < (b) ? (a) : (b))
21 #define MAX(a, b) ((a) > (b) ? (a) : (b))
22 #define IS_UTF8_1ST_CHAR(c) ((((c) & 0xc0) == 0xc0) || !((c) & 0x80))
23
24 typedef struct Item Item;
25 struct Item {
26 char *text;
27 Item *next; /* traverses all items */
28 Item *left, *right; /* traverses items matching current search pattern */
29 };
30
31 /* forward declarations */
32 static void appenditem(Item *i, Item **list, Item **last);
33 static void calcoffsetsh(void);
34 static void calcoffsetsv(void);
35 static char *cistrstr(const char *s, const char *sub);
36 static void cleanup(void);
37 static void drawmenu(void);
38 static void drawmenuh(void);
39 static void drawmenuv(void);
40 static Bool grabkeyboard(void);
41 static void kpress(XKeyEvent * e);
42 static void match(char *pattern);
43 static void readstdin(void);
44 static void run(void);
45 static void setup(Bool topbar);
46
47 #include "config.h"
48 #include "draw.h"
49
50 /* variables */
51 static char *maxname = NULL;
52 static char *prompt = NULL;
53 static char text[4096];
54 static int cmdw = 0;
55 static int promptw = 0;
56 static int ret = 0;
57 static unsigned int lines = 0;
58 static unsigned int numlockmask = 0;
59 static Bool running = True;
60 static Item *allitems = NULL; /* first of all items */
61 static Item *item = NULL; /* first of pattern matching items */
62 static Item *sel = NULL;
63 static Item *next = NULL;
64 static Item *prev = NULL;
65 static Item *curr = NULL;
66 static Window win;
67 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
68 static char *(*fstrstr)(const char *, const char *) = strstr;
69 static void (*calcoffsets)(void) = calcoffsetsh;
70
71 Display *dpy;
72 DC dc;
73 int screen;
74 unsigned int mw, mh;
75 Window parent;
76
77 void
78 appenditem(Item *i, Item **list, Item **last) {
79 if(!(*last))
80 *list = i;
81 else
82 (*last)->right = i;
83 i->left = *last;
84 i->right = NULL;
85 *last = i;
86 }
87
88 void
89 calcoffsetsh(void) {
90 unsigned int w;
91
92 if(!curr)
93 return;
94 w = promptw + cmdw + 2 * spaceitem;
95 for(next = curr; next && w < mw; next=next->right)
96 w += MIN(textw(next->text), mw / 3);
97 w = promptw + cmdw + 2 * spaceitem;
98 for(prev = curr; prev && prev->left && w < mw; prev=prev->left)
99 w += MIN(textw(prev->left->text), mw / 3);
100 }
101
102 void
103 calcoffsetsv(void) {
104 unsigned int h;
105
106 if(!curr)
107 return;
108 h = (dc.font.height + 2) * lines;
109 for(next = curr; next && h > 0; next = next->right)
110 h -= dc.font.height + 2;
111 h = (dc.font.height + 2) * lines;
112 for(prev = curr; prev && prev->left && h > 0; prev = prev->left)
113 h -= dc.font.height + 2;
114 }
115
116 char *
117 cistrstr(const char *s, const char *sub) {
118 int c, csub;
119 unsigned int len;
120
121 if(!sub)
122 return (char *)s;
123 if((c = tolower(*sub++)) != '\0') {
124 len = strlen(sub);
125 do {
126 do {
127 if((csub = *s++) == '\0')
128 return NULL;
129 }
130 while(tolower(csub) != c);
131 }
132 while(strncasecmp(s, sub, len) != 0);
133 s--;
134 }
135 return (char *)s;
136 }
137
138 void
139 cleanup(void) {
140 Item *itm;
141
142 while(allitems) {
143 itm = allitems->next;
144 free(allitems->text);
145 free(allitems);
146 allitems = itm;
147 }
148 drawcleanup();
149 XDestroyWindow(dpy, win);
150 XUngrabKeyboard(dpy, CurrentTime);
151 }
152
153 void
154 drawmenu(void) {
155 dc.x = 0;
156 dc.y = 0;
157 dc.w = mw;
158 dc.h = mh;
159 drawtext(NULL, dc.norm);
160 /* print prompt? */
161 if(prompt) {
162 dc.w = promptw;
163 drawtext(prompt, dc.sel);
164 dc.x += dc.w;
165 }
166 dc.w = mw - dc.x;
167 /* print command */
168 if(cmdw && item && lines == 0)
169 dc.w = cmdw;
170 drawtext(*text ? text : NULL, dc.norm);
171 if(curr) {
172 if(lines > 0)
173 drawmenuv();
174 else
175 drawmenuh();
176 }
177 XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
178 XFlush(dpy);
179 }
180
181 void
182 drawmenuh(void) {
183 Item *i;
184
185 dc.x += cmdw;
186 dc.w = spaceitem;
187 drawtext(curr->left ? "<" : NULL, dc.norm);
188 dc.x += dc.w;
189 for(i = curr; i != next; i=i->right) {
190 dc.w = MIN(textw(i->text), mw / 3);
191 drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
192 dc.x += dc.w;
193 }
194 dc.w = spaceitem;
195 dc.x = mw - dc.w;
196 drawtext(next ? ">" : NULL, dc.norm);
197 }
198
199 void
200 drawmenuv(void) {
201 Item *i;
202
203 dc.w = mw - dc.x;
204 dc.h = dc.font.height + 2;
205 dc.y = dc.h;
206 for(i = curr; i != next; i=i->right) {
207 drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
208 dc.y += dc.h;
209 }
210 dc.h = mh - dc.y;
211 drawtext(NULL, dc.norm);
212 }
213
214 Bool
215 grabkeyboard(void) {
216 unsigned int len;
217
218 for(len = 1000; len; len--) {
219 if(XGrabKeyboard(dpy, parent, True, GrabModeAsync, GrabModeAsync, CurrentTime)
220 == GrabSuccess)
221 break;
222 usleep(1000);
223 }
224 return len > 0;
225 }
226
227 void
228 kpress(XKeyEvent * e) {
229 char buf[sizeof text];
230 int num;
231 unsigned int i, len;
232 KeySym ksym;
233
234 len = strlen(text);
235 num = XLookupString(e, buf, sizeof buf, &ksym, NULL);
236 if(ksym == XK_KP_Enter)
237 ksym = XK_Return;
238 else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
239 ksym = (ksym - XK_KP_0) + XK_0;
240 else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
241 || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
242 || IsPrivateKeypadKey(ksym))
243 return;
244 /* first check if a control mask is omitted */
245 if(e->state & ControlMask) {
246 switch(tolower(ksym)) {
247 default:
248 return;
249 case XK_a:
250 ksym = XK_Home;
251 break;
252 case XK_b:
253 ksym = XK_Left;
254 break;
255 case XK_c:
256 ksym = XK_Escape;
257 break;
258 case XK_e:
259 ksym = XK_End;
260 break;
261 case XK_f:
262 ksym = XK_Right;
263 break;
264 case XK_h:
265 ksym = XK_BackSpace;
266 break;
267 case XK_i:
268 ksym = XK_Tab;
269 break;
270 case XK_j:
271 ksym = XK_Return;
272 break;
273 case XK_n:
274 ksym = XK_Down;
275 break;
276 case XK_p:
277 ksym = XK_Up;
278 break;
279 case XK_u:
280 text[0] = '\0';
281 match(text);
282 break;
283 case XK_w:
284 if(len == 0)
285 return;
286 i = len;
287 while(i-- > 0 && text[i] == ' ');
288 while(i-- > 0 && text[i] != ' ');
289 text[++i] = '\0';
290 match(text);
291 break;
292 case XK_x:
293 execlp("dinput", "dinput", text, NULL); /* todo: argv */
294 eprint("dmenu: cannot exec dinput:");
295 break;
296 }
297 }
298 switch(ksym) {
299 default:
300 num = MIN(num, sizeof text);
301 if(num && !iscntrl((int) buf[0])) {
302 memcpy(text + len, buf, num + 1);
303 len += num;
304 match(text);
305 }
306 break;
307 case XK_BackSpace:
308 if(len == 0)
309 return;
310 for(i = 1; len - i > 0 && !IS_UTF8_1ST_CHAR(text[len - i]); i++);
311 len -= i;
312 text[len] = '\0';
313 match(text);
314 break;
315 case XK_End:
316 while(next) {
317 sel = curr = next;
318 calcoffsets();
319 }
320 while(sel && sel->right)
321 sel = sel->right;
322 break;
323 case XK_Escape:
324 ret = 1;
325 running = False;
326 return;
327 case XK_Home:
328 sel = curr = item;
329 calcoffsets();
330 break;
331 case XK_Left:
332 case XK_Up:
333 if(!sel || !sel->left)
334 return;
335 sel = sel->left;
336 if(sel->right == curr) {
337 curr = prev;
338 calcoffsets();
339 }
340 break;
341 case XK_Next:
342 if(!next)
343 return;
344 sel = curr = next;
345 calcoffsets();
346 break;
347 case XK_Prior:
348 if(!prev)
349 return;
350 sel = curr = prev;
351 calcoffsets();
352 break;
353 case XK_Return:
354 if((e->state & ShiftMask) || !sel)
355 fprintf(stdout, "%s", text);
356 else
357 fprintf(stdout, "%s", sel->text);
358 fflush(stdout);
359 running = False;
360 return;
361 case XK_Right:
362 case XK_Down:
363 if(!sel || !sel->right)
364 return;
365 sel = sel->right;
366 if(sel == next) {
367 curr = next;
368 calcoffsets();
369 }
370 break;
371 case XK_Tab:
372 if(!sel)
373 return;
374 strncpy(text, sel->text, sizeof text);
375 match(text);
376 break;
377 }
378 drawmenu();
379 }
380
381 void
382 match(char *pattern) {
383 unsigned int plen;
384 Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
385
386 if(!pattern)
387 return;
388 plen = strlen(pattern);
389 item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
390 for(i = allitems; i; i = i->next)
391 if(!fstrncmp(pattern, i->text, plen + 1))
392 appenditem(i, &lexact, &exactend);
393 else if(!fstrncmp(pattern, i->text, plen))
394 appenditem(i, &lprefix, &prefixend);
395 else if(fstrstr(i->text, pattern))
396 appenditem(i, &lsubstr, &substrend);
397 if(lexact) {
398 item = lexact;
399 itemend = exactend;
400 }
401 if(lprefix) {
402 if(itemend) {
403 itemend->right = lprefix;
404 lprefix->left = itemend;
405 }
406 else
407 item = lprefix;
408 itemend = prefixend;
409 }
410 if(lsubstr) {
411 if(itemend) {
412 itemend->right = lsubstr;
413 lsubstr->left = itemend;
414 }
415 else
416 item = lsubstr;
417 }
418 curr = prev = next = sel = item;
419 calcoffsets();
420 }
421
422 void
423 readstdin(void) {
424 char *p, buf[sizeof text];
425 unsigned int len = 0, max = 0;
426 Item *i, *new;
427
428 i = NULL;
429 while(fgets(buf, sizeof buf, stdin)) {
430 len = strlen(buf);
431 if(buf[len-1] == '\n')
432 buf[--len] = '\0';
433 if(!(p = strdup(buf)))
434 eprint("dmenu: cannot strdup %u bytes\n", len);
435 if((max = MAX(max, len)) == len)
436 maxname = p;
437 if(!(new = malloc(sizeof *new)))
438 eprint("dmenu: cannot malloc %u bytes\n", sizeof *new);
439 new->next = new->left = new->right = NULL;
440 new->text = p;
441 if(!i)
442 allitems = new;
443 else
444 i->next = new;
445 i = new;
446 }
447 }
448
449 void
450 run(void) {
451 XEvent ev;
452
453 /* main event loop */
454 while(running && !XNextEvent(dpy, &ev))
455 switch (ev.type) {
456 case KeyPress:
457 kpress(&ev.xkey);
458 break;
459 case Expose:
460 if(ev.xexpose.count == 0)
461 drawmenu();
462 break;
463 case VisibilityNotify:
464 if (ev.xvisibility.state != VisibilityUnobscured)
465 XRaiseWindow(dpy, win);
466 break;
467 }
468 }
469
470 void
471 setup(Bool topbar) {
472 int i, j, x, y;
473 #if XINERAMA
474 int n;
475 XineramaScreenInfo *info = NULL;
476 #endif
477 XModifierKeymap *modmap;
478 XSetWindowAttributes wa;
479 XWindowAttributes pwa;
480
481 /* init modifier map */
482 modmap = XGetModifierMapping(dpy);
483 for(i = 0; i < 8; i++)
484 for(j = 0; j < modmap->max_keypermod; j++) {
485 if(modmap->modifiermap[i * modmap->max_keypermod + j]
486 == XKeysymToKeycode(dpy, XK_Num_Lock))
487 numlockmask = (1 << i);
488 }
489 XFreeModifiermap(modmap);
490
491 initfont(font);
492
493 /* menu window */
494 wa.override_redirect = True;
495 wa.background_pixmap = ParentRelative;
496 wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask | VisibilityChangeMask;
497
498 /* menu window geometry */
499 mh = (dc.font.height + 2) * (lines + 1);
500 #if XINERAMA
501 if(parent == RootWindow(dpy, screen) && XineramaIsActive(dpy) && (info = XineramaQueryScreens(dpy, &n))) {
502 i = 0;
503 if(n > 1) {
504 int di;
505 unsigned int dui;
506 Window dummy;
507 if(XQueryPointer(dpy, parent, &dummy, &dummy, &x, &y, &di, &di, &dui))
508 for(i = 0; i < n; i++)
509 if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
510 break;
511 }
512 x = info[i].x_org;
513 y = topbar ? info[i].y_org : info[i].y_org + info[i].height - mh;
514 mw = info[i].width;
515 XFree(info);
516 }
517 else
518 #endif
519 {
520 XGetWindowAttributes(dpy, parent, &pwa);
521 x = 0;
522 y = topbar ? 0 : pwa.height - mh;
523 mw = pwa.width;
524 }
525
526 win = XCreateWindow(dpy, parent, x, y, mw, mh, 0,
527 DefaultDepth(dpy, screen), CopyFromParent,
528 DefaultVisual(dpy, screen),
529 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
530
531 drawsetup();
532 if(maxname)
533 cmdw = MIN(textw(maxname), mw / 3);
534 if(prompt)
535 promptw = MIN(textw(prompt), mw / 5);
536 text[0] = '\0';
537 match(text);
538 XMapRaised(dpy, win);
539 }
540
541 int
542 main(int argc, char *argv[]) {
543 unsigned int i;
544 Bool topbar = True;
545
546 /* command line args */
547 for(i = 1; i < argc; i++)
548 if(!strcmp(argv[i], "-i")) {
549 fstrncmp = strncasecmp;
550 fstrstr = cistrstr;
551 }
552 else if(!strcmp(argv[i], "-b"))
553 topbar = False;
554 else if(!strcmp(argv[i], "-e")) {
555 if(++i < argc) parent = atoi(argv[i]);
556 }
557 else if(!strcmp(argv[i], "-l")) {
558 if(++i < argc) lines = atoi(argv[i]);
559 if(lines > 0)
560 calcoffsets = calcoffsetsv;
561 }
562 else if(!strcmp(argv[i], "-fn")) {
563 if(++i < argc) font = argv[i];
564 }
565 else if(!strcmp(argv[i], "-nb")) {
566 if(++i < argc) normbgcolor = argv[i];
567 }
568 else if(!strcmp(argv[i], "-nf")) {
569 if(++i < argc) normfgcolor = argv[i];
570 }
571 else if(!strcmp(argv[i], "-p")) {
572 if(++i < argc) prompt = argv[i];
573 }
574 else if(!strcmp(argv[i], "-sb")) {
575 if(++i < argc) selbgcolor = argv[i];
576 }
577 else if(!strcmp(argv[i], "-sf")) {
578 if(++i < argc) selfgcolor = argv[i];
579 }
580 else if(!strcmp(argv[i], "-v"))
581 eprint("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n");
582 else
583 eprint("usage: dmenu [-i] [-b] [-e <xid>] [-l <lines>] [-fn <font>] [-nb <color>]\n"
584 " [-nf <color>] [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n");
585 if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
586 fprintf(stderr, "dmenu: warning: no locale support\n");
587 if(!(dpy = XOpenDisplay(NULL)))
588 eprint("dmenu: cannot open display\n");
589 screen = DefaultScreen(dpy);
590 if(!parent)
591 parent = RootWindow(dpy, screen);
592
593 readstdin();
594 running = grabkeyboard();
595
596 setup(topbar);
597 drawmenu();
598 XSync(dpy, False);
599 run();
600 cleanup();
601 XCloseDisplay(dpy);
602 return ret;
603 }