thingmenu.c - thingmenu - A simple graphical menu launcher for X11.
HTML git clone git://bitreich.org/thingmenu
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR LICENSE
---
thingmenu.c (15423B)
---
1 /*
2 * Copy me if you can.
3 * by 20h
4 */
5 #include <unistd.h>
6 #include <locale.h>
7 #include <signal.h>
8 #include <stdarg.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <stdlib.h>
12 #include <libgen.h>
13 #include <sys/wait.h>
14 #include <X11/keysym.h>
15 #include <X11/Xatom.h>
16 #include <X11/Xlib.h>
17 #include <X11/Xutil.h>
18 #include <X11/Xproto.h>
19 #include <X11/extensions/XTest.h>
20
21 /* macros */
22 #define MAX(a, b) ((a) > (b) ? (a) : (b))
23 #define LENGTH(x) (sizeof x / sizeof x[0])
24
25 /* enums */
26 enum { ColFG, ColBG, ColLast };
27 enum { NetWMWindowType, NetLast };
28
29 /* typedefs */
30 typedef unsigned int uint;
31 typedef unsigned long ulong;
32
33 typedef struct {
34 ulong norm[ColLast];
35 ulong press[ColLast];
36 ulong high[ColLast];
37
38 Drawable drawable;
39 GC gc;
40 struct {
41 int ascent;
42 int descent;
43 int height;
44 XFontSet set;
45 XFontStruct *xfont;
46 } font;
47 } DC; /* draw context */
48
49 typedef struct {
50 char *label;
51 char *cmd;
52 uint width;
53 int x, y, w, h;
54 Bool highlighted;
55 Bool pressed;
56 Bool forceexit;
57 } Entry;
58
59 /* function declarations */
60 static void motionnotify(XEvent *e);
61 static void keyrelease(XEvent *e);
62 static void buttonpress(XEvent *e);
63 static void buttonrelease(XEvent *e);
64 static void cleanup(void);
65 static void configurenotify(XEvent *e);
66 static void unmapnotify(XEvent *e);
67 static void die(const char *errstr, ...);
68 static void drawmenu(void);
69 static void drawentry(Entry *e);
70 static void expose(XEvent *e);
71 static Entry *findentry(int x, int y);
72 static ulong getcolor(const char *colstr);
73 static void initfont(const char *fontstr);
74 static void leavenotify(XEvent *e);
75 static void press(Entry *e);
76 static void run(void);
77 static void setup(void);
78 static void sigchld(int unused);
79 static int textnw(const char *text, uint len);
80 static void unpress(Entry *e);
81 static void updateentries(void);
82
83 /* variables */
84 static int screen;
85 static void (*handler[LASTEvent]) (XEvent *) = {
86 [KeyRelease] = keyrelease,
87 [ButtonPress] = buttonpress,
88 [ButtonRelease] = buttonrelease,
89 [ConfigureNotify] = configurenotify,
90 [UnmapNotify] = unmapnotify,
91 [Expose] = expose,
92 [LeaveNotify] = leavenotify,
93 [MotionNotify] = motionnotify
94 };
95
96 static Display *dpy;
97 static DC dc;
98 static Window root, win;
99 static Bool running = True, horizontal = False;
100 /*
101 * ww = window width; www = wanted window width; wh = window height;
102 * wx = window x position; wy = window y position;
103 */
104 static int ww = 0, www = 0, wh = 0, wx = 0, wy = 0;
105 static char *name = "thingmenu";
106
107 Entry **entries = NULL;
108 int nentries = 0;
109 int exitentry = -1;
110 int oneshot = 1;
111 Bool ispressing = 0;
112
113 char *argv0;
114
115 #include "arg.h"
116
117 /* configuration, allows nested code to access above variables */
118 #include "config.h"
119
120 void
121 motionnotify(XEvent *e)
122 {
123 XPointerMovedEvent *ev = &e->xmotion;
124 int i;
125
126 for(i = 0; i < nentries; i++) {
127 if(ev->x > entries[i]->x
128 && ev->x < entries[i]->x + entries[i]->w
129 && ev->y > entries[i]->y
130 && ev->y < entries[i]->y + entries[i]->h) {
131 if (entries[i]->highlighted != True) {
132 if (ispressing) {
133 entries[i]->pressed = True;
134 } else {
135 entries[i]->highlighted = True;
136 }
137 drawentry(entries[i]);
138 }
139 continue;
140 }
141 if (entries[i]->pressed == True) {
142 entries[i]->pressed = False;
143 drawentry(entries[i]);
144 }
145 if (entries[i]->highlighted == True) {
146 entries[i]->highlighted = False;
147 drawentry(entries[i]);
148 }
149 }
150 }
151
152 void
153 keyrelease(XEvent *e)
154 {
155 int i;
156 XKeyEvent *xkey = &e->xkey;
157 KeySym key = XLookupKeysym(xkey, 0);
158
159 for (i = 0; i < nentries && !entries[i]->highlighted; i++);
160
161 if (key >= XK_0 && key <= XK_9) {
162 i = key - XK_0;
163 key = XK_Return;
164 } else if (key >= XK_KP_0 && key <= XK_KP_9) {
165 i = key - XK_KP_0;
166 key = XK_Return;
167 }
168
169 switch (key) {
170 case XK_KP_Insert:
171 i = 0;
172 key = XK_Return;
173 break;
174 case XK_KP_End:
175 i = 1;
176 key = XK_Return;
177 break;
178 case XK_KP_Down:
179 i = 2;
180 key = XK_Return;
181 break;
182 case XK_KP_Page_Down:
183 i = 3;
184 key = XK_Return;
185 break;
186 case XK_KP_Left:
187 i = 4;
188 key = XK_Return;
189 break;
190 case XK_KP_Begin:
191 i = 5;
192 key = XK_Return;
193 break;
194 case XK_KP_Right:
195 i = 6;
196 key = XK_Return;
197 break;
198 case XK_KP_Home:
199 i = 7;
200 key = XK_Return;
201 break;
202 case XK_KP_Up:
203 i = 8;
204 key = XK_Return;
205 break;
206 case XK_KP_Page_Up:
207 i = 9;
208 key = XK_Return;
209 break;
210 }
211
212 switch (key) {
213 case XK_k:
214 key = XK_Up;
215 case XK_j:
216 if(key == XK_j)
217 key = XK_Down;
218 case XK_Up:
219 case XK_Down:
220 if (i < nentries) {
221 entries[i]->highlighted = False;
222 drawentry(entries[i]);
223 }
224
225 if (key == XK_Up) {
226 i = ((i - 1) + nentries) % nentries;
227 } else if(key == XK_Down) {
228 if (i < nentries) {
229 i = (i + 1) % nentries;
230 } else {
231 i = 0;
232 }
233 }
234
235 entries[i]->highlighted = True;
236 drawentry(entries[i]);
237 break;
238 case XK_period:
239 case XK_KP_Decimal:
240 case XK_KP_Delete:
241 i = exitentry;
242 case XK_Return:
243 case XK_space:
244 if (i < nentries) {
245 press(entries[i]);
246 unpress(entries[i]);
247 }
248 break;
249 case XK_Escape:
250 running = False;
251 break;
252 }
253 }
254
255 void
256 buttonpress(XEvent *e)
257 {
258 XButtonPressedEvent *ev = &e->xbutton;
259 Entry *en;
260
261 if(ev->button != Button1)
262 return;
263
264 ispressing = True;
265
266 if((en = findentry(ev->x, ev->y)))
267 press(en);
268 }
269
270 void
271 buttonrelease(XEvent *e)
272 {
273 XButtonPressedEvent *ev = &e->xbutton;
274 Entry *en;
275
276 if(ev->button != Button1)
277 return;
278
279 ispressing = False;
280
281 if((en = findentry(ev->x, ev->y)))
282 unpress(en);
283 }
284
285 void
286 cleanup(void)
287 {
288 if(dc.font.set)
289 XFreeFontSet(dpy, dc.font.set);
290 else
291 XFreeFont(dpy, dc.font.xfont);
292 XFreePixmap(dpy, dc.drawable);
293 XFreeGC(dpy, dc.gc);
294 XDestroyWindow(dpy, win);
295 XSync(dpy, False);
296 XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
297 }
298
299 void
300 configurenotify(XEvent *e)
301 {
302 XConfigureEvent *ev = &e->xconfigure;
303
304 if(ev->window == win && (ev->width != ww || ev->height != wh)) {
305 ww = ev->width;
306 wh = ev->height;
307 XFreePixmap(dpy, dc.drawable);
308 dc.drawable = XCreatePixmap(dpy, root, ww, wh,
309 DefaultDepth(dpy, screen));
310 updateentries();
311 }
312 }
313
314 void
315 die(const char *errstr, ...)
316 {
317 va_list ap;
318
319 va_start(ap, errstr);
320 vfprintf(stderr, errstr, ap);
321 va_end(ap);
322 exit(EXIT_FAILURE);
323 }
324
325 void
326 drawmenu(void)
327 {
328 int i;
329
330 for(i = 0; i < nentries; i++)
331 drawentry(entries[i]);
332 XSync(dpy, False);
333 }
334
335 void
336 drawentry(Entry *e)
337 {
338 int x, y, h, len;
339 XRectangle r = { e->x, e->y, e->w, e->h };
340 const char *l;
341 ulong *col;
342
343 if(e->pressed)
344 col = dc.press;
345 else if(e->highlighted)
346 col = dc.high;
347 else
348 col = dc.norm;
349
350 XSetForeground(dpy, dc.gc, col[ColBG]);
351 XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
352 XSetForeground(dpy, dc.gc, dc.norm[ColFG]);
353 r.height -= 1;
354 r.width -= 1;
355 XDrawRectangles(dpy, dc.drawable, dc.gc, &r, 1);
356 XSetForeground(dpy, dc.gc, col[ColFG]);
357
358 l = e->label;
359 len = strlen(l);
360 h = dc.font.height;
361 y = e->y + (e->h / 2) - (h / 2) + dc.font.ascent;
362 x = e->x + (e->w / 2) - (textnw(l, len) / 2);
363 if(dc.font.set) {
364 XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, l,
365 len);
366 } else
367 XDrawString(dpy, dc.drawable, dc.gc, x, y, l, len);
368 XCopyArea(dpy, dc.drawable, win, dc.gc, e->x, e->y, e->w, e->h,
369 e->x, e->y);
370 }
371
372 void
373 unmapnotify(XEvent *e)
374 {
375 running = False;
376 }
377
378 void
379 expose(XEvent *e)
380 {
381 XExposeEvent *ev = &e->xexpose;
382
383 if(ev->count == 0 && (ev->window == win))
384 drawmenu();
385 }
386
387 Entry *
388 findentry(int x, int y)
389 {
390 int i;
391
392 for(i = 0; i < nentries; i++) {
393 if(x > entries[i]->x && x < entries[i]->x + entries[i]->w
394 && y > entries[i]->y
395 && y < entries[i]->y + entries[i]->h) {
396 return entries[i];
397 }
398 }
399 return NULL;
400 }
401
402 ulong
403 getcolor(const char *colstr)
404 {
405 Colormap cmap = DefaultColormap(dpy, screen);
406 XColor color;
407
408 if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
409 die("error, cannot allocate color '%s'\n", colstr);
410 return color.pixel;
411 }
412
413 void
414 initfont(const char *fontstr)
415 {
416 char *def, **missing;
417 int i, n;
418
419 missing = NULL;
420 if(dc.font.set)
421 XFreeFontSet(dpy, dc.font.set);
422 dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
423 if(missing) {
424 while(n--) {
425 fprintf(stderr, "thingmenu: missing fontset: %s\n",
426 missing[n]);
427 }
428 XFreeStringList(missing);
429 }
430 if(dc.font.set) {
431 XFontStruct **xfonts;
432 char **font_names;
433 dc.font.ascent = dc.font.descent = 0;
434 n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
435 for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
436 dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent);
437 dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent);
438 xfonts++;
439 }
440 }
441 else {
442 if(dc.font.xfont)
443 XFreeFont(dpy, dc.font.xfont);
444 dc.font.xfont = NULL;
445 if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr))
446 && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed")))
447 die("error, cannot load font: '%s'\n", fontstr);
448 dc.font.ascent = dc.font.xfont->ascent;
449 dc.font.descent = dc.font.xfont->descent;
450 }
451 dc.font.height = dc.font.ascent + dc.font.descent;
452 }
453
454 void
455 leavenotify(XEvent *e)
456 {
457 unpress(NULL);
458 }
459
460 void
461 run(void)
462 {
463 XEvent ev;
464
465 /* main event loop */
466 XSync(dpy, False);
467 while(running) {
468 XNextEvent(dpy, &ev);
469 if(handler[ev.type])
470 (handler[ev.type])(&ev); /* call handler */
471 }
472 }
473
474 void
475 setup(void)
476 {
477 XSetWindowAttributes wa;
478 XTextProperty str;
479 XSizeHints *sizeh;
480 XClassHint *ch;
481 int i, sh, sw, ls;
482
483 /* clean up any zombies immediately */
484 sigchld(0);
485
486 /* init screen */
487 screen = DefaultScreen(dpy);
488 root = RootWindow(dpy, screen);
489 sw = DisplayWidth(dpy, screen) - 1;
490 sh = DisplayHeight(dpy, screen) - 1;
491 initfont(font);
492
493 /* init atoms */
494
495 /* init appearance */
496
497 for (i = 0, www = 0; i < nentries; i++) {
498 ls = textnw(entries[i]->label,
499 strlen(entries[i]->label));
500 if (ls > www)
501 www = ls;
502 }
503 www *= widthscaling;
504
505 if (!ww) {
506 if (horizontal) {
507 ww = www * nentries;
508 } else {
509 ww = www;
510 }
511 }
512 if (!wh) {
513 if (horizontal) {
514 wh = dc.font.height * heightscaling;
515 } else {
516 wh = nentries * dc.font.height * heightscaling;
517 }
518 }
519 if (!wy)
520 wy = (sh - wh) / 2;
521 if (wy < 0)
522 wy = sh + wy - wh;
523 if (!wx)
524 wx = (sw - ww) / 2;
525 if (wx < 0)
526 wx = sw + wx - ww;
527
528 dc.norm[ColBG] = getcolor(normbgcolor);
529 dc.norm[ColFG] = getcolor(normfgcolor);
530 dc.press[ColBG] = getcolor(pressbgcolor);
531 dc.press[ColFG] = getcolor(pressfgcolor);
532 dc.high[ColBG] = getcolor(highlightbgcolor);
533 dc.high[ColFG] = getcolor(highlightfgcolor);
534
535 dc.drawable = XCreatePixmap(dpy, root, ww, wh, DefaultDepth(dpy, screen));
536 dc.gc = XCreateGC(dpy, root, 0, 0);
537 if(!dc.font.set)
538 XSetFont(dpy, dc.gc, dc.font.xfont->fid);
539 for(i = 0; i < nentries; i++)
540 entries[i]->pressed = 0;
541
542 wa.override_redirect = !wmborder;
543 wa.border_pixel = dc.norm[ColFG];
544 wa.background_pixel = dc.norm[ColBG];
545 win = XCreateWindow(dpy, root, wx, wy, ww, wh, 0,
546 CopyFromParent, CopyFromParent, CopyFromParent,
547 CWOverrideRedirect | CWBorderPixel | CWBackingPixel, &wa);
548 XSelectInput(dpy, win, StructureNotifyMask|KeyReleaseMask|
549 ButtonReleaseMask|ButtonPressMask|
550 ExposureMask|LeaveWindowMask|PointerMotionMask);
551
552 sizeh = XAllocSizeHints();
553 sizeh->flags = PMaxSize | PMinSize;
554 sizeh->min_width = sizeh->max_width = ww;
555 sizeh->min_height = sizeh->max_height = wh;
556 XStringListToTextProperty(&name, 1, &str);
557 ch = XAllocClassHint();
558 ch->res_class = name;
559 ch->res_name = name;
560
561 XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, NULL,
562 ch);
563
564 XFree(ch);
565 XFree(str.value);
566 XFree(sizeh);
567
568 XMapRaised(dpy, win);
569 updateentries();
570 drawmenu();
571 }
572
573 void
574 sigchld(int unused)
575 {
576 if (signal(SIGCHLD, sigchld) == SIG_ERR)
577 die("can't install SIGCHLD handler:");
578 while (0 < waitpid(-1, NULL, WNOHANG));
579 }
580
581 int
582 textnw(const char *text, uint len)
583 {
584 XRectangle r;
585
586 if(dc.font.set) {
587 XmbTextExtents(dc.font.set, text, len, NULL, &r);
588 return r.width;
589 }
590 return XTextWidth(dc.font.xfont, text, len);
591 }
592
593 void
594 runentry(Entry *e)
595 {
596 char *shell;
597
598 if (oneshot || e->forceexit)
599 running = False;
600
601 switch (fork()) {
602 case -1:
603 break;
604 case 0:
605 shell = getenv("SHELL");
606 if (!shell)
607 shell = "/bin/sh";
608
609 execlp(shell, basename(shell), "-c", e->cmd, (char *)NULL);
610 break;
611 }
612 }
613
614 void
615 press(Entry *e)
616 {
617 e->pressed = !e->pressed;
618
619 drawentry(e);
620 }
621
622 void
623 unpress(Entry *e)
624 {
625 int i;
626
627 if (e != NULL) {
628 e->pressed = !e->pressed;
629
630 runentry(e);
631 drawentry(e);
632 } else {
633 for(i = 0; i < nentries; i++) {
634 if(entries[i]->pressed) {
635 entries[i]->pressed = 0;
636 drawentry(entries[i]);
637 }
638 }
639 }
640 }
641
642 void
643 updateentries(void)
644 {
645 int i, x, y, h, w;
646
647 x = 0;
648 y = 0;
649
650 if (horizontal) {
651 h = wh;
652 w = www;
653 } else {
654 h = wh / nentries;
655 w = ww;
656 }
657 for(i = 0; i < nentries; i++) {
658 entries[i]->x = x;
659 entries[i]->y = y;
660 entries[i]->w = w;
661 entries[i]->h = h;
662 if (horizontal) {
663 x += w;
664 } else {
665 y += h;
666 }
667 }
668 }
669
670 void
671 usage(void)
672 {
673 fprintf(stderr, "usage: %s [-hnosx] [-g geometry] [-w widthscaling] "
674 "[-e heightscaling] [--] "
675 "label0 cmd0 [label1 cmd1 ...]\n", argv0);
676 exit(1);
677 }
678
679 int
680 main(int argc, char *argv[])
681 {
682 Bool addexit, usenumpad;
683 char *label, *cmd;
684 int i, xr, yr, bitm;
685 unsigned int wr, hr;
686
687 argv0 = argv[0];
688
689 addexit = True;
690 usenumpad = False;
691
692 if (argc < 2)
693 usage();
694
695 ARGBEGIN {
696 case 'g':
697 bitm = XParseGeometry(EARGF(usage()), &xr, &yr, &wr, &hr);
698 if (bitm & XValue)
699 wx = xr;
700 if (bitm & YValue)
701 wy = yr;
702 if (bitm & WidthValue)
703 ww = (int)wr;
704 if (bitm & HeightValue)
705 wh = (int)hr;
706 if (bitm & XNegative && wx == 0)
707 wx = -1;
708 if (bitm & YNegative && wy == 0)
709 wy = -1;
710 break;
711 case 'e':
712 heightscaling = atof(EARGF(usage()));
713 break;
714 case 'n':
715 usenumpad = True;
716 break;
717 case 'o':
718 horizontal = True;
719 break;
720 case 's':
721 oneshot = 0;
722 break;
723 case 'w':
724 widthscaling = atof(EARGF(usage()));
725 break;
726 case 'x':
727 addexit = False;
728 break;
729 default:
730 usage();
731 } ARGEND;
732
733 for (i = 0; argv[i]; i++) {
734 label = argv[i];
735 if (!argv[i+1])
736 break;
737 i++;
738 cmd = argv[i];
739
740 if (!(entries = realloc(entries, sizeof(entries[0])*(++nentries))))
741 die("realloc returned NULL");
742 if (!(entries[nentries-1] = calloc(1, sizeof(*entries[0]))))
743 die("calloc returned NULL");
744 if (usenumpad == True && nentries < 11) {
745 if (!(asprintf(&entries[nentries-1]->label,
746 "%d:%s", nentries-1, strdup(label)))) {
747 die("asprintf returned NULL\n");
748 }
749 } else {
750 if (!(entries[nentries-1]->label = strdup(label)))
751 die("strdup returned NULL\n");
752 }
753 if (!(entries[nentries-1]->cmd = strdup(cmd)))
754 die("strdup returned NULL\n");
755 entries[nentries-1]->forceexit = False;
756 }
757 if (nentries < 1)
758 usage();
759
760 if (addexit) {
761 if (!(entries = realloc(entries, sizeof(entries[0])*(++nentries))))
762 die("realloc returned NULL");
763 if (!(entries[nentries-1] = calloc(1, sizeof(*entries[0]))))
764 die("calloc returned NULL");
765 if (usenumpad == True) {
766 if (!(entries[nentries-1]->label = strdup(".:cancel")))
767 die("strdup returned NULL\n");
768 } else {
769 if (!(entries[nentries-1]->label = strdup("cancel")))
770 die("strdup returned NULL\n");
771 }
772 if (!(entries[nentries-1]->cmd = strdup("exit")))
773 die("strdup returned NULL\n");
774 entries[nentries-1]->forceexit = True;
775 exitentry = nentries - 1;
776 }
777
778 if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
779 fprintf(stderr, "warning: no locale support\n");
780 if(!(dpy = XOpenDisplay(0)))
781 die("thingmenu: cannot open display\n");
782
783 setup();
784 run();
785 cleanup();
786 XCloseDisplay(dpy);
787
788 for (i = 0; i < nentries; i++) {
789 free(entries[i]->label);
790 free(entries[i]->cmd);
791 free(entries[i]);
792 }
793 free(entries);
794
795 return 0;
796 }
797