1 #include <ctype.h>
2 #include <err.h>
3 #include <math.h>
4 #include <poll.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <X11/Xlib.h>
10 #include <X11/Xatom.h>
11 #include <X11/Xutil.h>
12 #include <X11/Xresource.h>
13 #include <X11/XKBlib.h>
14 #include <X11/Xft/Xft.h>
15 #include <X11/extensions/shape.h>
16 #include <X11/extensions/Xinerama.h>
17 #include <Imlib2.h>
18
19 #define CLASS "PMenu"
20 #define TTPAD 4 /* padding for the tooltip */
21 #define TTVERT 30 /* vertical distance from mouse to place tooltip */
22
23 /* macros */
24 #define LEN(x) (sizeof (x) / sizeof (x[0]))
25 #define MAX(x,y) ((x)>(y)?(x):(y))
26 #define MIN(x,y) ((x)<(y)?(x):(y))
27 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
28
29 /* color enum */
30 enum {ColorFG, ColorBG, ColorLast};
31
32 /* state of command to popen */
33 enum {NO_CMD = 0, CMD_NOTRUN = 1, CMD_RUN = 2};
34
35 /* atoms */
36 enum {
37 NET_WM_WINDOW_TYPE,
38 NET_WM_WINDOW_TYPE_TOOLTIP,
39 NET_WM_WINDOW_TYPE_POPUP_MENU,
40 ATOM_LAST
41 };
42
43 /* configuration structure */
44 struct Config {
45 const char *font;
46 const char *background_color;
47 const char *foreground_color;
48 const char *selbackground_color;
49 const char *selforeground_color;
50 const char *separator_color;
51 const char *border_color;
52 int border_pixels;
53 int separator_pixels;
54 int triangle_width;
55 int triangle_height;
56 int triangle_distance;
57 unsigned diameter_pixels;
58 double separatorbeg;
59 double separatorend;
60 };
61
62 /* draw context structure */
63 struct DC {
64 XftColor normal[ColorLast]; /* color of unselected slice */
65 XftColor selected[ColorLast]; /* color of selected slice */
66 XftColor border; /* color of border */
67 XftColor separator; /* color of the separator */
68
69 GC gc; /* graphics context */
70
71 FcPattern *pattern;
72 XftFont **fonts;
73 size_t nfonts;
74 int fonth;
75
76 XRenderPictureAttributes pictattr;
77 };
78
79 /* pie slice structure */
80 struct Slice {
81 struct Slice *prev, *next;
82 struct Menu *submenu; /* submenu spawned by clicking on slice */
83 struct Menu *parent;
84
85 char *label; /* string to be drawed on the slice */
86 char *output; /* string to be outputed when slice is clicked */
87 char *file; /* filename of the icon */
88 size_t labellen; /* strlen(label) */
89 int iscmd; /* whether output is actually a command to popen */
90
91 unsigned slicen;
92 int x, y; /* position of the pointer of the slice */
93 int labelx, labely; /* position of the label */
94 int iconx, icony; /* position of the icon */
95 double anglea, angleb; /* angle of the borders of the slice */
96
97 int drawn; /* whether the pixmap have been drawn */
98 Drawable pixmap; /* pixmap containing the pie menu with the slice selected */
99 Picture picture; /* XRender picture */
100 Imlib_Image icon; /* icon */
101
102 int ttdrawn; /* whether the pixmap for the tooltip have been drawn */
103 int ttw; /* tooltip width */
104 Window tooltip; /* tooltip that appears when hovering a slice */
105 Drawable ttpix; /* pixmap for the tooltip */
106 };
107
108 /* menu structure */
109 struct Menu {
110 struct Menu *parent; /* parent menu */
111 struct Slice *caller; /* slice that spawned the menu */
112 struct Slice *list; /* list of slices contained by the pie menu */
113 struct Slice *selected; /* slice currently selected in the menu */
114 unsigned nslices; /* number of slices */
115 int x, y; /* menu position */
116 double half; /* angle of half a slice of the pie menu */
117 int level; /* menu level relative to root */
118
119 int drawn; /* whether the pixmap have been drawn */
120 Drawable pixmap; /* pixmap to draw the menu on */
121 Picture picture; /* XRender picture */
122 Window win; /* menu window to map on the screen */
123 };
124
125 /* monitor and cursor geometry structure */
126 struct Monitor {
127 int x, y, w, h; /* monitor geometry */
128 int cursx, cursy;
129 };
130
131 /* geometry of the pie and bitmap that shapes it */
132 struct Pie {
133 GC gc; /* graphic context of the bitmaps */
134 Drawable clip; /* bitmap shaping the clip region (without borders) */
135 Drawable bounding; /* bitmap shaping the bounding region (with borders)*/
136
137 int fulldiameter; /* diameter of the pie + 2*border*/
138 int diameter; /* diameter of the pie */
139 int radius; /* radius of the pie */
140 int border; /* border of the pie */
141 int tooltiph;
142
143 int triangleinner;
144 int triangleouter;
145 int separatorbeg;
146 int separatorend;
147 double triangleangle;
148 double innerangle;
149 double outerangle;
150
151 Picture bg;
152 Picture fg;
153 Picture selbg;
154 Picture selfg;
155 Picture separator;
156 };
157
158 /* X stuff */
159 static Display *dpy;
160 static Visual *visual;
161 static Window rootwin;
162 static Colormap colormap;
163 static XrmDatabase xdb;
164 static XRenderPictFormat *xformat;
165 static char *xrm;
166 static int screen;
167 static int depth;
168 static struct DC dc;
169 static Atom atoms[ATOM_LAST];
170 static XClassHint classh;
171
172 /* The pie bitmap structure */
173 static struct Pie pie;
174
175 /* flags */
176 static int rflag = 0; /* wheter to run in root mode */
177 static int pflag = 0; /* whether to pass click to root window */
178 static int wflag = 0; /* whether to disable pointer warping */
179 static unsigned int button; /* button to trigger pmenu in root mode */
180 static unsigned int modifier; /* modifier to trigger pmenu */
181
182 static char *path_prefix = NULL;
183 static size_t path_prefix_size;
184
185 #include "config.h"
186
187 /* show usage */
188 static void
usage(void)189 usage(void)
190 {
191 (void)fprintf(stderr, "usage: pmenu [-pw] [-m modifier] [-r button] [-P image_prefix]\n");
192 exit(1);
193 }
194
195 /* read xrdb for configuration options */
196 static void
getresources(void)197 getresources(void)
198 {
199 char *type;
200 XrmValue xval;
201
202 if (xrm == NULL || xdb == NULL)
203 return;
204 if (XrmGetResource(xdb, "pmenu.diameterWidth", "*", &type, &xval) == True)
205 config.diameter_pixels = strtoul(xval.addr, NULL, 10);
206 if (XrmGetResource(xdb, "pmenu.borderWidth", "*", &type, &xval) == True)
207 config.border_pixels = strtoul(xval.addr, NULL, 10);
208 if (XrmGetResource(xdb, "pmenu.separatorWidth", "*", &type, &xval) == True)
209 config.separator_pixels = strtoul(xval.addr, NULL, 10);
210 if (XrmGetResource(xdb, "pmenu.background", "*", &type, &xval) == True)
211 config.background_color = xval.addr;
212 if (XrmGetResource(xdb, "pmenu.foreground", "*", &type, &xval) == True)
213 config.foreground_color = xval.addr;
214 if (XrmGetResource(xdb, "pmenu.selbackground", "*", &type, &xval) == True)
215 config.selbackground_color = xval.addr;
216 if (XrmGetResource(xdb, "pmenu.selforeground", "*", &type, &xval) == True)
217 config.selforeground_color = xval.addr;
218 if (XrmGetResource(xdb, "pmenu.separator", "*", &type, &xval) == True)
219 config.separator_color = xval.addr;
220 if (XrmGetResource(xdb, "pmenu.border", "*", &type, &xval) == True)
221 config.border_color = xval.addr;
222 if (XrmGetResource(xdb, "pmenu.font", "*", &type, &xval) == True)
223 config.font = xval.addr;
224 }
225
226 /* get options */
227 static void
getoptions(int argc,char ** argv)228 getoptions(int argc, char **argv)
229 {
230 int ch;
231 char *s;
232
233 classh.res_class = CLASS;
234 classh.res_name = argv[0];
235 if ((s = strrchr(argv[0], '/')) != NULL)
236 classh.res_name = s + 1;
237 while ((ch = getopt(argc, argv, "m:pr:wP:")) != -1) {
238 switch (ch) {
239 case 'm':
240 switch (*optarg) {
241 default:
242 case '1':
243 modifier = Mod1Mask;
244 break;
245 case '2':
246 modifier = Mod2Mask;
247 break;
248 case '3':
249 modifier = Mod3Mask;
250 break;
251 case '4':
252 modifier = Mod4Mask;
253 break;
254 case '5':
255 modifier = Mod5Mask;
256 break;
257 }
258 break;
259 case 'p':
260 pflag = 1;
261 break;
262 case 'r':
263 rflag = 1;
264 switch (*optarg) {
265 case '1':
266 button = Button1;
267 break;
268 case '2':
269 button = Button2;
270 break;
271 default:
272 case '3':
273 button = Button3;
274 break;
275 }
276 break;
277 case 'w':
278 wflag = 1;
279 break;
280 case 'P':
281 path_prefix = optarg;
282 path_prefix_size = strlen(optarg);
283 break;
284 default:
285 usage();
286 break;
287 }
288 }
289 argc -= optind;
290 argv += optind;
291 if (argc > 0) {
292 usage();
293 }
294 }
295
296 /* get color from color string */
297 static void
ealloccolor(const char * s,XftColor * color)298 ealloccolor(const char *s, XftColor *color)
299 {
300 if(!XftColorAllocName(dpy, visual, colormap, s, color))
301 errx(1, "could not allocate color: %s", s);
302 }
303
304 /* parse color string */
305 static void
parsefonts(const char * s)306 parsefonts(const char *s)
307 {
308 const char *p;
309 char buf[1024];
310 size_t nfont = 0;
311
312 dc.nfonts = 1;
313 for (p = s; *p; p++)
314 if (*p == ',')
315 dc.nfonts++;
316
317 if ((dc.fonts = calloc(dc.nfonts, sizeof *dc.fonts)) == NULL)
318 err(1, "calloc");
319
320 p = s;
321 while (*p != '\0') {
322 size_t i;
323
324 i = 0;
325 while (isspace(*p))
326 p++;
327 while (i < sizeof buf && *p != '\0' && *p != ',')
328 buf[i++] = *p++;
329 if (i >= sizeof buf)
330 errx(1, "font name too long");
331 if (*p == ',')
332 p++;
333 buf[i] = '\0';
334 if (nfont == 0)
335 if ((dc.pattern = FcNameParse((FcChar8 *)buf)) == NULL)
336 errx(1, "the first font in the cache must be loaded from a font string");
337 if ((dc.fonts[nfont++] = XftFontOpenName(dpy, screen, buf)) == NULL)
338 errx(1, "could not load font");
339 }
340 }
341
342 /* init draw context */
343 static void
initdc(void)344 initdc(void)
345 {
346 XGCValues values;
347 Pixmap pbg, pfg, pselbg, pselfg, separator;
348 unsigned long valuemask;
349
350 /* get color pixels */
351 ealloccolor(config.background_color, &dc.normal[ColorBG]);
352 ealloccolor(config.foreground_color, &dc.normal[ColorFG]);
353 ealloccolor(config.selbackground_color, &dc.selected[ColorBG]);
354 ealloccolor(config.selforeground_color, &dc.selected[ColorFG]);
355 ealloccolor(config.separator_color, &dc.separator);
356 ealloccolor(config.border_color, &dc.border);
357
358 /* parse fonts */
359 parsefonts(config.font);
360
361 /* create common GC */
362 values.arc_mode = ArcPieSlice;
363 values.line_width = config.separator_pixels;
364 valuemask = GCLineWidth | GCArcMode;
365 dc.gc = XCreateGC(dpy, rootwin, valuemask, &values);
366
367 /* create color source Pictures */
368 dc.pictattr.repeat = 1;
369 dc.pictattr.poly_edge = PolyEdgeSmooth;
370 pbg = XCreatePixmap(dpy, rootwin, 1, 1, depth);
371 pfg = XCreatePixmap(dpy, rootwin, 1, 1, depth);
372 pselbg = XCreatePixmap(dpy, rootwin, 1, 1, depth);
373 pselfg = XCreatePixmap(dpy, rootwin, 1, 1, depth);
374 separator = XCreatePixmap(dpy, rootwin, 1, 1, depth);
375 pie.bg = XRenderCreatePicture(dpy, pbg, xformat, CPRepeat, &dc.pictattr);
376 pie.fg = XRenderCreatePicture(dpy, pfg, xformat, CPRepeat, &dc.pictattr);
377 pie.selbg = XRenderCreatePicture(dpy, pselbg, xformat, CPRepeat, &dc.pictattr);
378 pie.selfg = XRenderCreatePicture(dpy, pselfg, xformat, CPRepeat, &dc.pictattr);
379 pie.separator = XRenderCreatePicture(dpy, separator, xformat, CPRepeat, &dc.pictattr);
380 XRenderFillRectangle(dpy, PictOpOver, pie.bg, &dc.normal[ColorBG].color, 0, 0, 1, 1);
381 XRenderFillRectangle(dpy, PictOpOver, pie.fg, &dc.normal[ColorFG].color, 0, 0, 1, 1);
382 XRenderFillRectangle(dpy, PictOpOver, pie.selbg, &dc.selected[ColorBG].color, 0, 0, 1, 1);
383 XRenderFillRectangle(dpy, PictOpOver, pie.selfg, &dc.selected[ColorFG].color, 0, 0, 1, 1);
384 XRenderFillRectangle(dpy, PictOpOver, pie.separator, &dc.separator.color, 0, 0, 1, 1);
385 XFreePixmap(dpy, pbg);
386 XFreePixmap(dpy, pfg);
387 XFreePixmap(dpy, pselbg);
388 XFreePixmap(dpy, pselfg);
389 XFreePixmap(dpy, separator);
390 }
391
392 /* setup pie */
393 static void
initpie(void)394 initpie(void)
395 {
396 XGCValues values;
397 unsigned long valuemask;
398
399 /* set pie geometry */
400 pie.border = config.border_pixels;
401 pie.diameter = config.diameter_pixels;
402 pie.radius = (pie.diameter + 1) / 2;
403 pie.fulldiameter = pie.diameter + (pie.border * 2);
404 pie.tooltiph = dc.fonts[0]->height + 2 * TTPAD;
405
406 /* set the geometry of the triangle for submenus */
407 pie.triangleouter = pie.radius - config.triangle_distance;
408 pie.triangleinner = pie.radius - config.triangle_distance - config.triangle_width;
409 pie.triangleangle = ((double)config.triangle_height / 2.0) / (double)pie.triangleinner;
410
411 /* set the separator beginning and end */
412 pie.separatorbeg = pie.radius * config.separatorbeg;
413 pie.separatorend = pie.radius * config.separatorend;
414 pie.innerangle = atan(config.separator_pixels / (2.0 * pie.separatorbeg));
415 pie.outerangle = atan(config.separator_pixels / (2.0 * pie.separatorend));
416
417 /* Create a simple bitmap mask (depth = 1) */
418 pie.clip = XCreatePixmap(dpy, rootwin, pie.diameter, pie.diameter, 1);
419 pie.bounding = XCreatePixmap(dpy, rootwin, pie.fulldiameter, pie.fulldiameter, 1);
420
421 /* Create the mask GC */
422 values.background = 1;
423 values.arc_mode = ArcPieSlice;
424 valuemask = GCBackground | GCArcMode;
425 pie.gc = XCreateGC(dpy, pie.clip, valuemask, &values);
426
427 /* clear the bitmap */
428 XSetForeground(dpy, pie.gc, 0);
429 XFillRectangle(dpy, pie.clip, pie.gc, 0, 0, pie.diameter, pie.diameter);
430 XFillRectangle(dpy, pie.bounding, pie.gc, 0, 0, pie.fulldiameter, pie.fulldiameter);
431
432 /* create round shape */
433 XSetForeground(dpy, pie.gc, 1);
434 XFillArc(dpy, pie.clip, pie.gc, 0, 0,
435 pie.diameter, pie.diameter, 0, 360*64);
436 XFillArc(dpy, pie.bounding, pie.gc, 0, 0,
437 pie.fulldiameter, pie.fulldiameter, 0, 360*64);
438 }
439
440 /* intern atoms */
441 static void
initatoms(void)442 initatoms(void)
443 {
444 char *atomnames[ATOM_LAST] = {
445 [NET_WM_WINDOW_TYPE] = "_NET_WM_WINDOW_TYPE",
446 [NET_WM_WINDOW_TYPE_TOOLTIP] = "_NET_WM_WINDOW_TYPE_TOOLTIP",
447 [NET_WM_WINDOW_TYPE_POPUP_MENU] = "_NET_WM_WINDOW_TYPE_POPUP_MENU",
448 };
449
450 XInternAtoms(dpy, atomnames, ATOM_LAST, False, atoms);
451 }
452
453 /* call strdup checking for error */
454 static char *
estrdup(const char * s)455 estrdup(const char *s)
456 {
457 char *t;
458
459 if ((t = strdup(s)) == NULL)
460 err(1, "strdup");
461 return t;
462 }
463
464 /* call malloc checking for error */
465 static void *
emalloc(size_t size)466 emalloc(size_t size)
467 {
468 void *p;
469
470 if ((p = malloc(size)) == NULL)
471 err(1, "malloc");
472 return p;
473 }
474
475 /* as estrdup, but copy a prefix in */
476 static char *
estrdup_prefix(const char * s,const char * prefix,const size_t prefix_size)477 estrdup_prefix(const char *s, const char *prefix, const size_t prefix_size)
478 {
479 char *t = emalloc(strlen(s) + prefix_size + 1);
480
481 strcpy(t, prefix);
482 strcpy(t + prefix_size, s);
483
484 return t;
485 }
486
487 /* allocate an slice */
488 static struct Slice *
allocslice(const char * label,const char * output,char * file)489 allocslice(const char *label, const char *output, char *file)
490 {
491 struct Slice *slice;
492
493 slice = emalloc(sizeof *slice);
494 slice->label = label ? estrdup(label) : NULL;
495 if (!file) {
496 slice->file = NULL;
497 } else if (path_prefix && file[0] != '/' && !(file[0] == '.' && file[1] == '/')) {
498 slice->file = estrdup_prefix(file, path_prefix, path_prefix_size);
499 } else {
500 slice->file = estrdup(file);
501 }
502 slice->y = 0;
503 slice->labellen = (slice->label) ? strlen(slice->label) : 0;
504 slice->next = NULL;
505 slice->submenu = NULL;
506 slice->icon = NULL;
507 if (output && *output == '$') {
508 output++;
509 while (isspace(*output))
510 output++;
511 slice->output = estrdup(output);
512 slice->iscmd = CMD_NOTRUN;
513 } else {
514 slice->output = (label == output) ? slice->label : estrdup(output);
515 slice->iscmd = NO_CMD;
516 }
517 return slice;
518 }
519
520 /* allocate a menu */
521 static struct Menu *
allocmenu(struct Menu * parent,struct Slice * list,int level)522 allocmenu(struct Menu *parent, struct Slice *list, int level)
523 {
524 XSetWindowAttributes swa;
525 XSizeHints sizeh;
526 struct Menu *menu;
527
528 menu = emalloc(sizeof *menu);
529
530 /* create menu window */
531 swa.override_redirect = True;
532 swa.background_pixel = dc.normal[ColorBG].pixel;
533 swa.border_pixel = dc.border.pixel;
534 swa.save_under = True; /* pop-up windows should save_under*/
535 swa.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
536 menu->win = XCreateWindow(dpy, rootwin, 0, 0, pie.diameter, pie.diameter, pie.border,
537 CopyFromParent, CopyFromParent, CopyFromParent,
538 CWOverrideRedirect | CWBackPixel |
539 CWBorderPixel | CWEventMask | CWSaveUnder,
540 &swa);
541
542 /* Set window type */
543 XChangeProperty(dpy, menu->win, atoms[NET_WM_WINDOW_TYPE], XA_ATOM, 32,
544 PropModeReplace, (unsigned char *)&atoms[NET_WM_WINDOW_TYPE_POPUP_MENU], 1);
545
546 XShapeCombineMask(dpy, menu->win, ShapeClip, 0, 0, pie.clip, ShapeSet);
547 XShapeCombineMask(dpy, menu->win, ShapeBounding, -pie.border, -pie.border, pie.bounding, ShapeSet);
548
549 /* set window manager hints */
550 sizeh.flags = USPosition | PMaxSize | PMinSize;
551 sizeh.min_width = sizeh.max_width = pie.diameter;
552 sizeh.min_height = sizeh.max_height = pie.diameter;
553 XSetWMProperties(dpy, menu->win, NULL, NULL, NULL, 0, &sizeh, NULL, &classh);
554
555 /* set menu variables */
556 menu->parent = parent;
557 menu->list = list;
558 menu->caller = NULL;
559 menu->selected = NULL;
560 menu->nslices = 0;
561 menu->x = 0; /* calculated by setupmenu() */
562 menu->y = 0; /* calculated by setupmenu() */
563 menu->level = level;
564
565 /* create pixmap and picture */
566 menu->pixmap = XCreatePixmap(dpy, menu->win, pie.diameter, pie.diameter, depth);
567 menu->picture = XRenderCreatePicture(dpy, menu->pixmap, xformat, CPPolyEdge | CPRepeat, &dc.pictattr);
568 menu->drawn = 0;
569
570 return menu;
571 }
572
573 /* build the menu tree */
574 static struct Menu *
buildmenutree(struct Menu * rootmenu,int level,const char * label,const char * output,char * file)575 buildmenutree(struct Menu *rootmenu, int level, const char *label, const char *output, char *file)
576 {
577 static struct Menu *prevmenu; /* menu the previous slice was added to */
578 struct Slice *currslice = NULL; /* slice currently being read */
579 struct Slice *slice; /* dummy slice for loops */
580 struct Menu *menu; /* dummy menu for loops */
581 int i;
582
583 if (rootmenu == NULL)
584 prevmenu = NULL;
585
586 /* create the slice */
587 currslice = allocslice(label, output, file);
588
589 /* put the slice in the menu tree */
590 if (prevmenu == NULL) { /* there is no menu yet */
591 menu = allocmenu(NULL, currslice, level);
592 rootmenu = menu;
593 prevmenu = menu;
594 currslice->prev = NULL;
595 } else if (level < prevmenu->level) { /* slice is continuation of a parent menu */
596 /* go up the menu tree until find the menu this slice continues */
597 for (menu = prevmenu, i = level;
598 menu != NULL && i != prevmenu->level;
599 menu = menu->parent, i++)
600 ;
601 if (menu == NULL)
602 errx(1, "improper indentation detected");
603
604 /* find last slice in the new menu */
605 for (slice = menu->list; slice->next != NULL; slice = slice->next)
606 ;
607
608 prevmenu = menu;
609 slice->next = currslice;
610 currslice->prev = slice;
611 } else if (level == prevmenu->level) { /* slice is a continuation of current menu */
612 /* find last slice in the previous menu */
613 for (slice = prevmenu->list; slice->next != NULL; slice = slice->next)
614 ;
615
616 slice->next = currslice;
617 currslice->prev = slice;
618 } else if (level > prevmenu->level) { /* slice begins a new menu */
619 menu = allocmenu(prevmenu, currslice, level);
620
621 /* find last slice in the previous menu */
622 for (slice = prevmenu->list; slice->next != NULL; slice = slice->next)
623 ;
624
625 prevmenu = menu;
626 menu->caller = slice;
627 slice->submenu = menu;
628 currslice->prev = NULL;
629 }
630
631 prevmenu->nslices++;
632
633 return rootmenu;
634 }
635
636 /* create menus and slices from the stdin */
637 static struct Menu *
parse(FILE * fp,int initlevel)638 parse(FILE *fp, int initlevel)
639 {
640 struct Menu *rootmenu;
641 char *s, buf[BUFSIZ];
642 char *file, *label, *output;
643 int level;
644
645 rootmenu = NULL;
646 while (fgets(buf, BUFSIZ, fp) != NULL) {
647 /* get the indentation level */
648 level = strspn(buf, "\t");
649
650 /* get the label */
651 s = level + buf;
652 label = strtok(s, "\t\n");
653
654 if (label == NULL)
655 errx(1, "empty item");
656
657 /* get the filename */
658 file = NULL;
659 if (label != NULL && strncmp(label, "IMG:", 4) == 0) {
660 file = label + 4;
661 label = strtok(NULL, "\t\n");
662 }
663
664 /* get the output */
665 output = strtok(NULL, "\n");
666 if (output == NULL) {
667 output = label;
668 } else {
669 while (*output == '\t')
670 output++;
671 }
672
673 rootmenu = buildmenutree(rootmenu, initlevel + level, label, output, file);
674 }
675
676 return rootmenu;
677 }
678
679 /* load image from file and scale it to size; return the image and its size */
680 static Imlib_Image
loadicon(const char * file,int size,int * width_ret,int * height_ret)681 loadicon(const char *file, int size, int *width_ret, int *height_ret)
682 {
683 Imlib_Image icon;
684 Imlib_Load_Error errcode;
685 const char *errstr;
686 int width;
687 int height;
688
689 icon = imlib_load_image_with_error_return(file, &errcode);
690 if (*file == '\0') {
691 errx(1, "could not load icon (file name is blank)");
692 } else if (icon == NULL) {
693 switch (errcode) {
694 case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
695 errstr = "file does not exist";
696 break;
697 case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY:
698 errstr = "file is directory";
699 break;
700 case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ:
701 case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE:
702 errstr = "permission denied";
703 break;
704 case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
705 errstr = "unknown file format";
706 break;
707 case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
708 errstr = "path too long";
709 break;
710 case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT:
711 case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY:
712 case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE:
713 errstr = "improper path";
714 break;
715 case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS:
716 errstr = "too many symbolic links";
717 break;
718 case IMLIB_LOAD_ERROR_OUT_OF_MEMORY:
719 errstr = "out of memory";
720 break;
721 case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS:
722 errstr = "out of file descriptors";
723 break;
724 default:
725 errstr = "unknown error";
726 break;
727 }
728 errx(1, "could not load icon (%s): %s", errstr, file);
729 }
730
731 imlib_context_set_image(icon);
732
733 width = imlib_image_get_width();
734 height = imlib_image_get_height();
735
736 if (width > height) {
737 *width_ret = size;
738 *height_ret = (height * size) / width;
739 } else {
740 *width_ret = (width * size) / height;
741 *height_ret = size;
742 }
743
744 icon = imlib_create_cropped_scaled_image(0, 0, width, height,
745 *width_ret, *height_ret);
746
747 return icon;
748 }
749
750 /* get next utf8 char from s return its codepoint and set next_ret to pointer to end of character */
751 static FcChar32
getnextutf8char(const char * s,const char ** next_ret)752 getnextutf8char(const char *s, const char **next_ret)
753 {
754 static const unsigned char utfbyte[] = {0x80, 0x00, 0xC0, 0xE0, 0xF0};
755 static const unsigned char utfmask[] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
756 static const FcChar32 utfmin[] = {0, 0x00, 0x80, 0x800, 0x10000};
757 static const FcChar32 utfmax[] = {0, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
758 /* 0xFFFD is the replacement character, used to represent unknown characters */
759 static const FcChar32 unknown = 0xFFFD;
760 FcChar32 ucode; /* FcChar32 type holds 32 bits */
761 size_t usize = 0; /* n' of bytes of the utf8 character */
762 size_t i;
763
764 *next_ret = s+1;
765
766 /* get code of first byte of utf8 character */
767 for (i = 0; i < sizeof utfmask; i++) {
768 if (((unsigned char)*s & utfmask[i]) == utfbyte[i]) {
769 usize = i;
770 ucode = (unsigned char)*s & ~utfmask[i];
771 break;
772 }
773 }
774
775 /* if first byte is a continuation byte or is not allowed, return unknown */
776 if (i == sizeof utfmask || usize == 0)
777 return unknown;
778
779 /* check the other usize-1 bytes */
780 s++;
781 for (i = 1; i < usize; i++) {
782 *next_ret = s+1;
783 /* if byte is nul or is not a continuation byte, return unknown */
784 if (*s == '\0' || ((unsigned char)*s & utfmask[0]) != utfbyte[0])
785 return unknown;
786 /* 6 is the number of relevant bits in the continuation byte */
787 ucode = (ucode << 6) | ((unsigned char)*s & ~utfmask[0]);
788 s++;
789 }
790
791 /* check if ucode is invalid or in utf-16 surrogate halves */
792 if (!BETWEEN(ucode, utfmin[usize], utfmax[usize])
793 || BETWEEN (ucode, 0xD800, 0xDFFF))
794 return unknown;
795
796 return ucode;
797 }
798
799 /* get which font contains a given code point */
800 static XftFont *
getfontucode(FcChar32 ucode)801 getfontucode(FcChar32 ucode)
802 {
803 FcCharSet *fccharset = NULL;
804 FcPattern *fcpattern = NULL;
805 FcPattern *match = NULL;
806 XftFont *retfont = NULL;
807 XftResult result;
808 size_t i;
809
810 for (i = 0; i < dc.nfonts; i++)
811 if (XftCharExists(dpy, dc.fonts[i], ucode) == FcTrue)
812 return dc.fonts[i];
813
814 /* create a charset containing our code point */
815 fccharset = FcCharSetCreate();
816 FcCharSetAddChar(fccharset, ucode);
817
818 /* create a pattern akin to the dc.pattern but containing our charset */
819 if (fccharset) {
820 fcpattern = FcPatternDuplicate(dc.pattern);
821 FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
822 }
823
824 /* find pattern matching fcpattern */
825 if (fcpattern) {
826 FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
827 FcDefaultSubstitute(fcpattern);
828 match = XftFontMatch(dpy, screen, fcpattern, &result);
829 }
830
831 /* if found a pattern, open its font */
832 if (match) {
833 retfont = XftFontOpenPattern(dpy, match);
834 if (retfont && XftCharExists(dpy, retfont, ucode) == FcTrue) {
835 if ((dc.fonts = realloc(dc.fonts, dc.nfonts+1)) == NULL)
836 err(1, "realloc");
837 dc.fonts[dc.nfonts] = retfont;
838 return dc.fonts[dc.nfonts++];
839 } else {
840 XftFontClose(dpy, retfont);
841 }
842 }
843
844 /* in case no fount was found, return the first one */
845 return dc.fonts[0];
846 }
847
848 /* draw text into XftDraw */
849 static int
drawtext(XftDraw * draw,XftColor * color,int x,int y,const char * text)850 drawtext(XftDraw *draw, XftColor *color, int x, int y, const char *text)
851 {
852 int textwidth = 0;
853
854 while (*text) {
855 XftFont *currfont;
856 XGlyphInfo ext;
857 FcChar32 ucode;
858 const char *next;
859 size_t len;
860
861 ucode = getnextutf8char(text, &next);
862 currfont = getfontucode(ucode);
863
864 len = next - text;
865 XftTextExtentsUtf8(dpy, currfont, (XftChar8 *)text, len, &ext);
866 textwidth += ext.xOff;
867
868 if (draw) {
869 int texty;
870
871 texty = y + (currfont->ascent - currfont->descent)/2;
872 XftDrawStringUtf8(draw, color, currfont, x, texty, (XftChar8 *)text, len);
873 x += ext.xOff;
874 }
875
876 text = next;
877 }
878
879 return textwidth;
880 }
881
882 /* setup position of and content of menu's slices */
883 /* recursivelly setup menu configuration and its pixmap */
884 static void
setslices(struct Menu * menu)885 setslices(struct Menu *menu)
886 {
887 XSetWindowAttributes swa;
888 struct Slice *slice;
889 double a = 0.0;
890 unsigned n = 0;
891 int textwidth;
892
893 menu->half = M_PI / menu->nslices;
894 swa.override_redirect = True;
895 swa.background_pixel = dc.normal[ColorBG].pixel;
896 swa.save_under = True; /* pop-up windows should save_under*/
897 swa.event_mask = ExposureMask;
898 for (slice = menu->list; slice; slice = slice->next) {
899 slice->parent = menu;
900 slice->slicen = n++;
901
902 slice->anglea = a - menu->half;
903 slice->angleb = a + menu->half;
904
905 /* get length of slice->label rendered in the font */
906 textwidth = (slice->label) ? drawtext(NULL, NULL, 0, 0, slice->label): 0;
907
908 /* get position of slice's label */
909 slice->labelx = pie.radius + ((pie.radius*2)/3 * cos(a)) - (textwidth / 2);
910 slice->labely = pie.radius - ((pie.radius*2)/3 * sin(a));
911
912 /* get position of submenu */
913 slice->x = pie.radius + (pie.diameter * (cos(a) * 0.9));
914 slice->y = pie.radius - (pie.diameter * (sin(a) * 0.9));
915
916 /* create icon */
917 if (slice->file != NULL) {
918 int maxiconsize = (pie.radius + 1) / 2;
919 int iconw, iconh; /* icon width and height */
920 int iconsize; /* requested icon size */
921 int xdiff, ydiff;
922
923 xdiff = pie.radius * 0.5 - (pie.radius * (cos(menu->half) * 0.8));
924 ydiff = pie.radius * (sin(menu->half) * 0.8);
925
926 iconsize = sqrt(xdiff * xdiff + ydiff * ydiff);
927 iconsize = MIN(maxiconsize, iconsize);
928
929 slice->icon = loadicon(slice->file, iconsize, &iconw, &iconh);
930
931 slice->iconx = pie.radius + (pie.radius * (cos(a) * 0.6)) - iconw / 2;
932 slice->icony = pie.radius - (pie.radius * (sin(a) * 0.6)) - iconh / 2;
933 }
934
935 /* create pixmap */
936 slice->pixmap = XCreatePixmap(dpy, menu->win, pie.diameter, pie.diameter, depth);
937 slice->picture = XRenderCreatePicture(dpy, slice->pixmap, xformat, CPPolyEdge | CPRepeat, &dc.pictattr);
938 slice->drawn = 0;
939
940 /* create tooltip */
941 slice->ttdrawn = 0;
942 if (textwidth > 0) {
943 slice->ttw = textwidth + 2 * TTPAD;
944 slice->tooltip = XCreateWindow(dpy, rootwin, 0, 0, slice->ttw, pie.tooltiph, 0,
945 CopyFromParent, CopyFromParent, CopyFromParent,
946 CWOverrideRedirect | CWBackPixel | CWEventMask | CWSaveUnder,
947 &swa);
948 slice->ttpix = XCreatePixmap(dpy, slice->tooltip, slice->ttw, pie.tooltiph, depth);
949 XChangeProperty(dpy, slice->tooltip, atoms[NET_WM_WINDOW_TYPE], XA_ATOM, 32,
950 PropModeReplace, (unsigned char *)&atoms[NET_WM_WINDOW_TYPE_TOOLTIP], 1);
951
952 } else {
953 slice->ttw = 0;
954 slice->tooltip = None;
955 slice->ttpix = None;
956 }
957
958 /* call recursivelly */
959 if (slice->submenu != NULL) {
960 setslices(slice->submenu);
961 }
962
963 a += menu->half * 2;
964 }
965 }
966
967 /* query monitor information and cursor position */
968 static void
getmonitor(struct Monitor * mon)969 getmonitor(struct Monitor *mon)
970 {
971 XineramaScreenInfo *info = NULL;
972 Window dw; /* dummy variable */
973 int di; /* dummy variable */
974 unsigned du; /* dummy variable */
975 int nmons;
976 int i;
977
978 XQueryPointer(dpy, rootwin, &dw, &dw, &mon->cursx, &mon->cursy, &di, &di, &du);
979
980 mon->x = mon->y = 0;
981 mon->w = DisplayWidth(dpy, screen);
982 mon->h = DisplayHeight(dpy, screen);
983
984 if ((info = XineramaQueryScreens(dpy, &nmons)) != NULL) {
985 int selmon = 0;
986
987 for (i = 0; i < nmons; i++) {
988 if (BETWEEN(mon->cursx, info[i].x_org, info[i].x_org + info[i].width) &&
989 BETWEEN(mon->cursy, info[i].y_org, info[i].y_org + info[i].height)) {
990 selmon = i;
991 break;
992 }
993 }
994
995 mon->x = info[selmon].x_org;
996 mon->y = info[selmon].y_org;
997 mon->w = info[selmon].width;
998 mon->h = info[selmon].height;
999
1000 XFree(info);
1001 }
1002 }
1003
1004 /* try to grab pointer, we may have to wait for another process to ungrab */
1005 static void
grabpointer(void)1006 grabpointer(void)
1007 {
1008 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
1009 int i;
1010
1011 for (i = 0; i < 1000; i++) {
1012 if (XGrabPointer(dpy, rootwin, True, ButtonPressMask,
1013 GrabModeAsync, GrabModeAsync, None,
1014 None, CurrentTime) == GrabSuccess)
1015 return;
1016 nanosleep(&ts, NULL);
1017 }
1018 errx(1, "could not grab pointer");
1019 }
1020
1021 /* try to grab keyboard, we may have to wait for another process to ungrab */
1022 static void
grabkeyboard(void)1023 grabkeyboard(void)
1024 {
1025 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
1026 int i;
1027
1028 for (i = 0; i < 1000; i++) {
1029 if (XGrabKeyboard(dpy, rootwin, True, GrabModeAsync,
1030 GrabModeAsync, CurrentTime) == GrabSuccess)
1031 return;
1032 nanosleep(&ts, NULL);
1033 }
1034 errx(1, "could not grab keyboard");
1035 }
1036
1037 /* setup the position of a menu */
1038 static void
placemenu(struct Monitor * mon,struct Menu * menu)1039 placemenu(struct Monitor *mon, struct Menu *menu)
1040 {
1041 struct Slice *slice;
1042 XWindowChanges changes;
1043 Window w1; /* dummy variable */
1044 int x, y; /* position of the center of the menu */
1045 Bool ret;
1046
1047 if (menu->parent == NULL) {
1048 x = mon->cursx;
1049 y = mon->cursy;
1050 } else {
1051 ret = XTranslateCoordinates(dpy, menu->parent->win, rootwin,
1052 menu->caller->x, menu->caller->y,
1053 &x, &y, &w1);
1054 if (ret == False)
1055 errx(EXIT_FAILURE, "menus are on different screens");
1056 }
1057 menu->x = mon->x;
1058 menu->y = mon->y;
1059 if (x - mon->x >= pie.radius) {
1060 if (mon->x + mon->w - x >= pie.radius)
1061 menu->x = x - pie.radius - pie.border;
1062 else if (mon->x + mon->w >= pie.fulldiameter)
1063 menu->x = mon->x + mon->w - pie.fulldiameter;
1064 }
1065 if (y - mon->y >= pie.radius) {
1066 if (mon->y + mon->h - y >= pie.radius)
1067 menu->y = y - pie.radius - pie.border;
1068 else if (mon->y + mon->h >= pie.fulldiameter)
1069 menu->y = mon->y + mon->h - pie.fulldiameter;
1070 }
1071 changes.x = menu->x;
1072 changes.y = menu->y;
1073 XConfigureWindow(dpy, menu->win, CWX | CWY, &changes);
1074 for (slice = menu->list; slice != NULL; slice = slice->next) {
1075 if (slice->submenu != NULL) {
1076 placemenu(mon, slice->submenu);
1077 }
1078 }
1079 }
1080
1081 /* get menu of given window */
1082 static struct Menu *
getmenu(struct Menu * currmenu,Window win)1083 getmenu(struct Menu *currmenu, Window win)
1084 {
1085 struct Menu *menu;
1086
1087 for (menu = currmenu; menu != NULL; menu = menu->parent)
1088 if (menu->win == win)
1089 return menu;
1090
1091 return NULL;
1092 }
1093
1094 /* get slice of given menu and position */
1095 static struct Slice *
getslice(struct Menu * menu,int x,int y)1096 getslice(struct Menu *menu, int x, int y)
1097 {
1098 struct Slice *slice;
1099 double angle;
1100 int r;
1101
1102 if (menu == NULL)
1103 return NULL;
1104
1105 x -= pie.radius;
1106 y -= pie.radius;
1107 y = -y;
1108
1109 /* if the cursor is in the middle circle, it is in no slice */
1110 r = sqrt(x * x + y * y);
1111 if (r <= pie.separatorbeg)
1112 return NULL;
1113
1114 angle = atan2(y, x);
1115 if (angle < 0.0) {
1116 if (angle > -menu->half)
1117 return menu->list;
1118 angle = (2 * M_PI) + angle;
1119 }
1120 for (slice = menu->list; slice; slice = slice->next)
1121 if (angle >= slice->anglea && angle < slice->angleb)
1122 return slice;
1123
1124 return NULL;
1125 }
1126
1127 /* map tooltip and place it on given position */
1128 static void
maptooltip(struct Monitor * mon,struct Slice * slice,int x,int y)1129 maptooltip(struct Monitor *mon, struct Slice *slice, int x, int y)
1130 {
1131 y += TTVERT;
1132 if (slice->icon == NULL || slice->label == NULL)
1133 return;
1134 if (y + pie.tooltiph > mon->y + mon->h)
1135 y = mon->y + mon->h - pie.tooltiph;
1136 if (x + slice->ttw > mon->x + mon->w)
1137 x = mon->x + mon->w - slice->ttw;
1138 XMoveWindow(dpy, slice->tooltip, x, y);
1139 XMapRaised(dpy, slice->tooltip);
1140 }
1141
1142 /* unmap tooltip if mapped, set mapped to zero */
1143 static void
unmaptooltip(struct Slice * slice)1144 unmaptooltip(struct Slice *slice)
1145 {
1146 if (slice == NULL || slice->icon == NULL || slice->label == NULL)
1147 return;
1148 XUnmapWindow(dpy, slice->tooltip);
1149 }
1150
1151 /* umap previous menus; map current menu and its parents */
1152 static struct Menu *
mapmenu(struct Menu * currmenu,struct Menu * prevmenu)1153 mapmenu(struct Menu *currmenu, struct Menu *prevmenu)
1154 {
1155 struct Menu *menu, *menu_;
1156 struct Menu *lcamenu; /* lowest common ancestor menu */
1157 int minlevel; /* level of the closest to root menu */
1158 int maxlevel; /* level of the closest to root menu */
1159
1160 /* do not remap current menu if it wasn't updated*/
1161 if (prevmenu == currmenu)
1162 goto done;
1163
1164 /* if this is the first time mapping, skip calculations */
1165 if (prevmenu == NULL) {
1166 XMapRaised(dpy, currmenu->win);
1167 goto done;
1168 }
1169
1170 /* find lowest common ancestor menu */
1171 minlevel = MIN(currmenu->level, prevmenu->level);
1172 maxlevel = MAX(currmenu->level, prevmenu->level);
1173 if (currmenu->level == maxlevel) {
1174 menu = currmenu;
1175 menu_ = prevmenu;
1176 } else {
1177 menu = prevmenu;
1178 menu_ = currmenu;
1179 }
1180 while (menu->level > minlevel)
1181 menu = menu->parent;
1182 while (menu != menu_) {
1183 menu = menu->parent;
1184 menu_ = menu_->parent;
1185 }
1186 lcamenu = menu;
1187
1188 /* unmap menus from currmenu (inclusive) until lcamenu (exclusive) */
1189 for (menu = prevmenu; menu != lcamenu; menu = menu->parent) {
1190 menu->selected = NULL;
1191 XUnmapWindow(dpy, menu->win);
1192 }
1193
1194 /* map menus from currmenu (inclusive) until lcamenu (exclusive) */
1195 for (menu = currmenu; menu != lcamenu; menu = menu->parent)
1196 XMapRaised(dpy, menu->win);
1197
1198 done:
1199 return currmenu;
1200 }
1201
1202 /* umap urrent menu and its parents */
1203 static void
unmapmenu(struct Menu * currmenu)1204 unmapmenu(struct Menu *currmenu)
1205 {
1206 struct Menu *menu;
1207
1208 /* unmap menus from currmenu (inclusive) until lcamenu (exclusive) */
1209 for (menu = currmenu; menu; menu = menu->parent) {
1210 menu->selected = NULL;
1211 XUnmapWindow(dpy, menu->win);
1212 }
1213 }
1214
1215 /* draw background of selected slice */
1216 static void
drawslice(struct Menu * menu,struct Slice * slice)1217 drawslice(struct Menu *menu, struct Slice *slice)
1218 {
1219 XPointDouble *p;
1220 int i, outer, inner, npoints;
1221 double h, a, b;
1222
1223 /* determine number of segments to draw */
1224 h = hypot(pie.radius, pie.radius)/2;
1225 outer = ((2 * M_PI) / (menu->nslices * acos(h/(h+1.0)))) + 0.5;
1226 outer = (outer < 3) ? 3 : outer;
1227 h = hypot(pie.separatorbeg, pie.separatorbeg)/2;
1228 inner = ((2 * M_PI) / (menu->nslices * acos(h/(h+1.0)))) + 0.5;
1229 inner = (inner < 3) ? 3 : inner;
1230 npoints = inner + outer + 2;
1231 p = emalloc(npoints * sizeof *p);
1232
1233 b = ((2 * M_PI) / menu->nslices) * slice->slicen;
1234
1235 /* outer points */
1236 a = ((2 * M_PI) / (menu->nslices * outer));
1237 for (i = 0; i <= outer; i++) {
1238 p[i].x = pie.radius + (pie.radius + 1) * cos((i - (outer / 2.0)) * a - b);
1239 p[i].y = pie.radius + (pie.radius + 1) * sin((i - (outer / 2.0)) * a - b);
1240 }
1241
1242 /* inner points */
1243 a = ((2 * M_PI) / (menu->nslices * inner));
1244 for (i = 0; i <= inner; i++) {
1245 p[i + outer + 1].x = pie.radius + pie.separatorbeg * cos(((inner - i) - (inner / 2.0)) * a - b);
1246 p[i + outer + 1].y = pie.radius + pie.separatorbeg * sin(((inner - i) - (inner / 2.0)) * a - b);
1247 }
1248
1249 XRenderCompositeDoublePoly(dpy, PictOpOver, pie.selbg, slice->picture,
1250 XRenderFindStandardFormat(dpy, PictStandardA8),
1251 0, 0, 0, 0, p, npoints, 0);
1252
1253 free(p);
1254 }
1255
1256 /* draw separator before slice */
1257 static void
drawseparator(Picture picture,struct Menu * menu,struct Slice * slice)1258 drawseparator(Picture picture, struct Menu *menu, struct Slice *slice)
1259 {
1260 XPointDouble p[4];
1261 double a;
1262
1263 a = -((M_PI + 2 * M_PI * slice->slicen) / menu->nslices);
1264 p[0].x = pie.radius + pie.separatorbeg * cos(a - pie.innerangle);
1265 p[0].y = pie.radius + pie.separatorbeg * sin(a - pie.innerangle);
1266 p[1].x = pie.radius + pie.separatorbeg * cos(a + pie.innerangle);
1267 p[1].y = pie.radius + pie.separatorbeg * sin(a + pie.innerangle);
1268 p[2].x = pie.radius + pie.separatorend * cos(a + pie.outerangle);
1269 p[2].y = pie.radius + pie.separatorend * sin(a + pie.outerangle);
1270 p[3].x = pie.radius + pie.separatorend * cos(a - pie.outerangle);
1271 p[3].y = pie.radius + pie.separatorend * sin(a - pie.outerangle);
1272 XRenderCompositeDoublePoly(dpy, PictOpOver, pie.separator, picture,
1273 XRenderFindStandardFormat(dpy, PictStandardA8),
1274 0, 0, 0, 0, p, 4, 0);
1275 }
1276
1277 /* draw triangle for slice with submenu */
1278 static void
drawtriangle(Picture source,Picture picture,struct Menu * menu,struct Slice * slice)1279 drawtriangle(Picture source, Picture picture, struct Menu *menu, struct Slice *slice)
1280 {
1281 XPointDouble p[3];
1282 double a;
1283
1284 a = - (((2 * M_PI) / menu->nslices) * slice->slicen);
1285 p[0].x = pie.radius + pie.triangleinner * cos(a - pie.triangleangle);
1286 p[0].y = pie.radius + pie.triangleinner * sin(a - pie.triangleangle);
1287 p[1].x = pie.radius + pie.triangleouter * cos(a);
1288 p[1].y = pie.radius + pie.triangleouter * sin(a);
1289 p[2].x = pie.radius + pie.triangleinner * cos(a + pie.triangleangle);
1290 p[2].y = pie.radius + pie.triangleinner * sin(a + pie.triangleangle);
1291 XRenderCompositeDoublePoly(dpy, PictOpOver, source, picture,
1292 XRenderFindStandardFormat(dpy, PictStandardA8),
1293 0, 0, 0, 0, p, 3, 0);
1294 }
1295
1296 /* draw regular slice */
1297 static void
drawmenu(struct Menu * menu,struct Slice * selected)1298 drawmenu(struct Menu *menu, struct Slice *selected)
1299 {
1300 struct Slice *slice;
1301 XftColor *color;
1302 XftDraw *draw;
1303 Drawable pixmap;
1304 Picture picture;
1305 Picture source;
1306
1307 if (selected) {
1308 pixmap = selected->pixmap;
1309 picture = selected->picture;
1310 selected->drawn = 1;
1311 } else {
1312 pixmap = menu->pixmap;
1313 picture = menu->picture;
1314 menu->drawn = 1;
1315 }
1316
1317 /* draw background */
1318 XSetForeground(dpy, dc.gc, dc.normal[ColorBG].pixel);
1319 XFillRectangle(dpy, pixmap, dc.gc, 0, 0, pie.diameter, pie.diameter);
1320 if (selected)
1321 drawslice(menu, selected);
1322
1323 /* draw slice foreground */
1324 for (slice = menu->list; slice; slice = slice->next) {
1325 if (slice == selected) {
1326 color = dc.selected;
1327 source = pie.selfg;
1328 } else {
1329 color = dc.normal;
1330 source = pie.fg;
1331 }
1332
1333 if (slice->file) { /* if there is an icon, draw it */
1334 imlib_context_set_drawable(pixmap);
1335 imlib_context_set_image(slice->icon);
1336 imlib_render_image_on_drawable(slice->iconx, slice->icony);
1337 } else { /* otherwise, draw the label */
1338 draw = XftDrawCreate(dpy, pixmap, visual, colormap);
1339 XSetForeground(dpy, dc.gc, color[ColorFG].pixel);
1340 drawtext(draw, &color[ColorFG], slice->labelx, slice->labely, slice->label);
1341 XftDrawDestroy(draw);
1342 }
1343
1344 /* draw separator */
1345 drawseparator(picture, menu, slice);
1346
1347 /* draw triangle */
1348 if (slice->submenu || slice->iscmd) {
1349 drawtriangle(source, picture, menu, slice);
1350 }
1351 }
1352 }
1353
1354 /* draw tooltip of slice */
1355 static void
drawtooltip(struct Slice * slice)1356 drawtooltip(struct Slice *slice)
1357 {
1358 XftDraw *draw;
1359
1360 XSetForeground(dpy, dc.gc, dc.normal[ColorBG].pixel);
1361 XFillRectangle(dpy, slice->ttpix, dc.gc, 0, 0, slice->ttw, pie.tooltiph);
1362 draw = XftDrawCreate(dpy, slice->ttpix, visual, colormap);
1363 XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
1364 drawtext(draw, &dc.normal[ColorFG], TTPAD, pie.tooltiph / 2, slice->label);
1365 XftDrawDestroy(draw);
1366 slice->ttdrawn = 1;
1367 }
1368
1369 /* draw slices of the current menu and of its ancestors */
1370 static void
copymenu(struct Menu * currmenu)1371 copymenu(struct Menu *currmenu)
1372 {
1373 struct Menu *menu;
1374 Drawable pixmap;
1375
1376 for (menu = currmenu; menu != NULL; menu = menu->parent) {
1377 if (menu->selected) {
1378 pixmap = menu->selected->pixmap;
1379 if (!menu->selected->drawn)
1380 drawmenu(menu, menu->selected);
1381 } else {
1382 pixmap = menu->pixmap;
1383 if (!menu->drawn)
1384 drawmenu(menu, NULL);
1385 }
1386 XCopyArea(dpy, pixmap, menu->win, dc.gc, 0, 0, pie.diameter, pie.diameter, 0, 0);
1387 }
1388 }
1389
1390 /* draw slice's tooltip */
1391 static void
copytooltip(struct Slice * slice)1392 copytooltip(struct Slice *slice)
1393 {
1394 if (slice->icon == NULL || slice->label == NULL)
1395 return;
1396 if (!slice->ttdrawn)
1397 drawtooltip(slice);
1398 XCopyArea(dpy, slice->ttpix, slice->tooltip, dc.gc, 0, 0, slice->ttw, pie.tooltiph, 0, 0);
1399 }
1400
1401 /* cycle through the slices; non-zero direction is next, zero is prev */
1402 static struct Slice *
slicecycle(struct Menu * currmenu,int clockwise)1403 slicecycle(struct Menu *currmenu, int clockwise)
1404 {
1405 struct Slice *slice;
1406 struct Slice *lastslice;
1407
1408 slice = NULL;
1409 if (clockwise) {
1410 for (lastslice = currmenu->list;
1411 lastslice != NULL && lastslice->next != NULL;
1412 lastslice = lastslice->next)
1413 ;
1414 if (currmenu->selected == NULL)
1415 slice = currmenu->list;
1416 else if (currmenu->selected->prev != NULL)
1417 slice = currmenu->selected->prev;
1418 if (slice == NULL)
1419 slice = lastslice;
1420 } else {
1421 if (currmenu->selected == NULL)
1422 slice = currmenu->list;
1423 else if (currmenu->selected->next != NULL)
1424 slice = currmenu->selected->next;
1425 if (slice == NULL)
1426 slice = currmenu->list;
1427 }
1428 return slice;
1429 }
1430
1431 /* recursivelly free pixmaps and destroy windows */
1432 static void
cleanmenu(struct Menu * menu)1433 cleanmenu(struct Menu *menu)
1434 {
1435 struct Slice *slice;
1436 struct Slice *tmp;
1437
1438 slice = menu->list;
1439 while (slice != NULL) {
1440 if (slice->submenu != NULL)
1441 cleanmenu(slice->submenu);
1442 tmp = slice;
1443 if (tmp->label != tmp->output)
1444 free(tmp->label);
1445 free(tmp->output);
1446 XFreePixmap(dpy, slice->pixmap);
1447 if (slice->tooltip != None)
1448 XDestroyWindow(dpy, slice->tooltip);
1449 if (slice->ttpix != None)
1450 XFreePixmap(dpy, slice->ttpix);
1451 if (tmp->file != NULL) {
1452 free(tmp->file);
1453 if (tmp->icon != NULL) {
1454 imlib_context_set_image(tmp->icon);
1455 imlib_free_image();
1456 }
1457 }
1458 slice = slice->next;
1459 free(tmp);
1460 }
1461
1462 XFreePixmap(dpy, menu->pixmap);
1463 XDestroyWindow(dpy, menu->win);
1464 free(menu);
1465 }
1466
1467 /* clear menus generated via genmenu */
1468 static void
cleangenmenu(struct Menu * menu)1469 cleangenmenu(struct Menu *menu)
1470 {
1471 struct Slice *slice;
1472
1473 for (slice = menu->list; slice; slice = slice->next) {
1474 if (slice->submenu != NULL)
1475 cleangenmenu(slice->submenu);
1476 if (slice->iscmd == CMD_RUN) {
1477 cleanmenu(slice->submenu);
1478 slice->iscmd = CMD_NOTRUN;
1479 slice->submenu = NULL;
1480 }
1481 }
1482 }
1483
1484 /* run command of slice to generate a submenu */
1485 static struct Menu *
genmenu(struct Monitor * mon,struct Menu * menu,struct Slice * slice)1486 genmenu(struct Monitor *mon, struct Menu *menu, struct Slice *slice)
1487 {
1488 FILE *fp;
1489
1490 if ((fp = popen(slice->output, "r")) == NULL) {
1491 warnx("could not run: %s", slice->output);
1492 return NULL;
1493 }
1494 if ((slice->submenu = parse(fp, menu->level + 1)) == NULL)
1495 return NULL;
1496 pclose(fp);
1497 slice->submenu->parent = menu;
1498 slice->submenu->caller = slice;
1499 slice->iscmd = CMD_RUN;
1500 setslices(slice->submenu);
1501 placemenu(mon, slice->submenu);
1502 if (slice->submenu->list == NULL) {
1503 cleanmenu(slice->submenu);
1504 return NULL;
1505 }
1506 return slice->submenu;
1507 }
1508
1509 /* ungrab pointer and keyboard */
1510 static void
ungrab(void)1511 ungrab(void)
1512 {
1513 XUngrabPointer(dpy, CurrentTime);
1514 XUngrabKeyboard(dpy, CurrentTime);
1515 }
1516
1517 /* create tooltip */
1518 static void
tooltip(struct Menu * currmenu,XEvent * ev)1519 tooltip(struct Menu *currmenu, XEvent *ev)
1520 {
1521 struct Menu *menu = NULL;
1522 struct Slice *slice = NULL;
1523
1524 while (!XNextEvent(dpy, ev)) {
1525 switch (ev->type) {
1526 case Expose:
1527 if (ev->xexpose.count == 0) {
1528 if (ev->xexpose.window == currmenu->selected->tooltip) {
1529 copytooltip(currmenu->selected);
1530 } else if (ev->xexpose.window == currmenu->win) {
1531 copymenu(currmenu);
1532 }
1533 }
1534 break;
1535 case MotionNotify:
1536 menu = getmenu(currmenu, ev->xmotion.window);
1537 slice = getslice(menu, ev->xmotion.x, ev->xmotion.y);
1538 if ((menu != NULL && menu != currmenu) || slice != currmenu->selected) {
1539 /* motion off selected slice */
1540 return;
1541 }
1542 break;
1543 case ButtonRelease:
1544 case ButtonPress:
1545 case KeyPress:
1546 case ConfigureNotify:
1547 return;
1548 }
1549 }
1550 }
1551
1552 /* run event loop */
1553 static void
run(struct pollfd * pfd,struct Monitor * mon,struct Menu * rootmenu)1554 run(struct pollfd *pfd, struct Monitor *mon, struct Menu *rootmenu)
1555 {
1556 struct Menu *currmenu;
1557 struct Menu *prevmenu;
1558 struct Menu *menu = NULL;
1559 struct Slice *slice = NULL;
1560 KeySym ksym;
1561 XEvent ev;
1562 int timeout;
1563 int nready;
1564 int ttx, tty;
1565
1566 nready = 3;
1567 timeout = -1;
1568 ttx = tty = 0;
1569 prevmenu = currmenu = rootmenu;
1570 while (XPending(dpy) || (nready = poll(pfd, 1, timeout)) != -1) {
1571 if (nready == 0 && currmenu != NULL && currmenu->selected != NULL) {
1572 maptooltip(mon, currmenu->selected, ttx, tty);
1573 tooltip(currmenu, &ev);
1574 unmaptooltip(currmenu->selected);
1575 } else {
1576 XNextEvent(dpy, &ev);
1577 }
1578 switch (ev.type) {
1579 case Expose:
1580 if (currmenu != NULL && ev.xexpose.count == 0)
1581 copymenu(currmenu);
1582 break;
1583 case MotionNotify:
1584 timeout = -1;
1585 menu = getmenu(currmenu, ev.xmotion.window);
1586 slice = getslice(menu, ev.xmotion.x, ev.xmotion.y);
1587 if (menu == NULL)
1588 break;
1589 if (currmenu != rootmenu && menu != currmenu) {
1590 /* motion off a non-root menu */
1591 currmenu = currmenu->parent;
1592 prevmenu = mapmenu(currmenu, prevmenu);
1593 currmenu->selected = NULL;
1594 copymenu(currmenu);
1595 }
1596 if (menu == currmenu) {
1597 /* motion inside a menu */
1598 currmenu->selected = slice;
1599 timeout = 1000;
1600 ttx = ev.xmotion.x_root;
1601 tty = ev.xmotion.y_root;
1602 }
1603 copymenu(currmenu);
1604 break;
1605 case ButtonRelease:
1606 timeout = -1;
1607 if (ev.xbutton.button != Button1 && ev.xbutton.button != Button3)
1608 break;
1609 menu = getmenu(currmenu, ev.xbutton.window);
1610 slice = getslice(menu, ev.xbutton.x, ev.xbutton.y);
1611 if (menu == NULL || slice == NULL)
1612 break;
1613 selectslice:
1614 if (slice->submenu) {
1615 currmenu = slice->submenu;
1616 } else if (slice->iscmd == CMD_NOTRUN) {
1617 if ((menu = genmenu(mon, menu, slice)) != NULL) {
1618 currmenu = menu;
1619 }
1620 } else {
1621 printf("%s\n", slice->output);
1622 fflush(stdout);
1623 goto done;
1624 }
1625 prevmenu = mapmenu(currmenu, prevmenu);
1626 currmenu->selected = NULL;
1627 copymenu(currmenu);
1628 if (!wflag)
1629 XWarpPointer(dpy, None, currmenu->win, 0, 0, 0, 0, pie.radius, pie.radius);
1630 break;
1631 case ButtonPress:
1632 timeout = -1;
1633 if (ev.xbutton.button != Button1 && ev.xbutton.button != Button3)
1634 break;
1635 menu = getmenu(currmenu, ev.xbutton.window);
1636 slice = getslice(menu, ev.xbutton.x, ev.xbutton.y);
1637 if (menu == NULL || slice == NULL)
1638 goto done;
1639 break;
1640 case KeyPress:
1641 timeout = -1;
1642 ksym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0);
1643
1644 /* esc closes pmenu when current menu is the root menu */
1645 if (ksym == XK_Escape && currmenu->parent == NULL)
1646 goto done;
1647
1648 /* Shift-Tab = ISO_Left_Tab */
1649 if (ksym == XK_Tab && (ev.xkey.state & ShiftMask))
1650 ksym = XK_ISO_Left_Tab;
1651
1652 /* cycle through menu */
1653 slice = NULL;
1654 if (ksym == XK_Tab) {
1655 slice = slicecycle(currmenu, 1);
1656 } else if (ksym == XK_ISO_Left_Tab) {
1657 slice = slicecycle(currmenu, 0);
1658 } else if ((ksym == XK_Return) &&
1659 currmenu->selected != NULL) {
1660 slice = currmenu->selected;
1661 goto selectslice;
1662 } else if ((ksym == XK_Escape) &&
1663 currmenu->parent != NULL) {
1664 slice = currmenu->parent->selected;
1665 currmenu = currmenu->parent;
1666 prevmenu = mapmenu(currmenu, prevmenu);
1667 } else
1668 break;
1669 currmenu->selected = slice;
1670 copymenu(currmenu);
1671 break;
1672 case ConfigureNotify:
1673 menu = getmenu(currmenu, ev.xconfigure.window);
1674 if (menu == NULL)
1675 break;
1676 menu->x = ev.xconfigure.x;
1677 menu->y = ev.xconfigure.y;
1678 break;
1679 }
1680 XFlush(dpy);
1681 }
1682 if (nready == -1)
1683 err(1, "poll");
1684 done:
1685 unmapmenu(currmenu);
1686 ungrab();
1687 cleangenmenu(rootmenu);
1688 }
1689
1690 /* free pictures */
1691 static void
cleanpictures(void)1692 cleanpictures(void)
1693 {
1694 XRenderFreePicture(dpy, pie.bg);
1695 XRenderFreePicture(dpy, pie.fg);
1696 XRenderFreePicture(dpy, pie.selbg);
1697 XRenderFreePicture(dpy, pie.selfg);
1698 XRenderFreePicture(dpy, pie.separator);
1699 }
1700
1701 /* cleanup drawing context */
1702 static void
cleandc(void)1703 cleandc(void)
1704 {
1705 XftColorFree(dpy, visual, colormap, &dc.normal[ColorBG]);
1706 XftColorFree(dpy, visual, colormap, &dc.normal[ColorFG]);
1707 XftColorFree(dpy, visual, colormap, &dc.selected[ColorBG]);
1708 XftColorFree(dpy, visual, colormap, &dc.selected[ColorFG]);
1709 XftColorFree(dpy, visual, colormap, &dc.separator);
1710 XftColorFree(dpy, visual, colormap, &dc.border);
1711 XFreeGC(dpy, dc.gc);
1712 }
1713
1714 /* pmenu: generate a pie menu from stdin and print selected entry to stdout */
1715 int
main(int argc,char * argv[])1716 main(int argc, char *argv[])
1717 {
1718 struct pollfd pfd;
1719 struct Menu *rootmenu;
1720 struct Monitor mon;
1721 XEvent ev;
1722
1723 /* open connection to server and set X variables */
1724 if ((dpy = XOpenDisplay(NULL)) == NULL)
1725 errx(1, "could not open display");
1726 screen = DefaultScreen(dpy);
1727 visual = DefaultVisual(dpy, screen);
1728 rootwin = RootWindow(dpy, screen);
1729 colormap = DefaultColormap(dpy, screen);
1730 depth = DefaultDepth(dpy, screen);
1731 xformat = XRenderFindVisualFormat(dpy, visual);
1732 if ((xrm = XResourceManagerString(dpy)) != NULL)
1733 xdb = XrmGetStringDatabase(xrm);
1734
1735 /* get configuration */
1736 getresources();
1737 getoptions(argc, argv);
1738
1739 /* imlib2 stuff */
1740 imlib_set_cache_size(2048 * 1024);
1741 imlib_context_set_dither(1);
1742 imlib_context_set_display(dpy);
1743 imlib_context_set_visual(visual);
1744 imlib_context_set_colormap(colormap);
1745
1746 /* initializers */
1747 initdc();
1748 initpie();
1749 initatoms();
1750
1751 /* if running in root mode, get button presses from root window */
1752 if (rflag)
1753 XGrabButton(dpy, button, AnyModifier, rootwin, False, ButtonPressMask, GrabModeSync, GrabModeSync, None, None);
1754
1755 /* generate menus and set them up */
1756 rootmenu = parse(stdin, 0);
1757 if (rootmenu == NULL)
1758 errx(1, "no menu generated");
1759 setslices(rootmenu);
1760
1761 pfd.fd = XConnectionNumber(dpy);
1762 pfd.events = POLLIN;
1763 do {
1764 if (rflag)
1765 XNextEvent(dpy, &ev);
1766 if (!rflag ||
1767 (ev.type == ButtonPress &&
1768 ((modifier && ev.xbutton.state == modifier) ||
1769 (ev.xbutton.subwindow == None)))) {
1770 if (rflag && pflag) {
1771 XAllowEvents(dpy, ReplayPointer, CurrentTime);
1772 }
1773 getmonitor(&mon);
1774 grabpointer();
1775 grabkeyboard();
1776 placemenu(&mon, rootmenu);
1777 mapmenu(rootmenu, NULL);
1778 XWarpPointer(dpy, None, rootmenu->win, 0, 0, 0, 0, pie.radius, pie.radius);
1779 XFlush(dpy);
1780 run(&pfd, &mon, rootmenu);
1781 } else {
1782 XAllowEvents(dpy, ReplayPointer, CurrentTime);
1783 }
1784 } while (rflag);
1785
1786 /* freeing stuff */
1787 cleanmenu(rootmenu);
1788 cleanpictures();
1789 cleandc();
1790 XCloseDisplay(dpy);
1791
1792 return 0;
1793 }
1794