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