1 #include <ctype.h>
2 #include <err.h>
3 #include <errno.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <limits.h>
8 #include <locale.h>
9 #include <time.h>
10 #include <unistd.h>
11 #include <X11/Xlib.h>
12 #include <X11/Xatom.h>
13 #include <X11/Xutil.h>
14 #include <X11/Xresource.h>
15 #include <X11/XKBlib.h>
16 #include <X11/Xft/Xft.h>
17 #include <X11/extensions/Xinerama.h>
18 #include <Imlib2.h>
19 #include "xmenu.h"
20 
21 /* X stuff */
22 static Display *dpy;
23 static int screen;
24 static Visual *visual;
25 static Window rootwin;
26 static Colormap colormap;
27 static XrmDatabase xdb;
28 static char *xrm;
29 static struct DC dc;
30 static struct Monitor mon;
31 static Atom utf8string;
32 static Atom wmdelete;
33 static Atom netatom[NetLast];
34 static XIM xim;
35 
36 /* flags */
37 static int iflag = 0;   /* whether to disable icons */
38 static int rflag = 0;   /* whether to disable right-click */
39 static int mflag = 0;   /* whether the user specified a monitor with -p */
40 static int pflag = 0;   /* whether the user specified a position with -p */
41 static int wflag = 0;   /* whether to let the window manager control XMenu */
42 
43 /* include config variable */
44 #include "config.h"
45 
46 /* show usage */
47 static void
usage(void)48 usage(void)
49 {
50 	(void)fprintf(stderr, "usage: xmenu [-irw] [-p position] [title]\n");
51 	exit(1);
52 }
53 
54 /* parse position string from -p,
55  * put results on config.posx, config.posy, and config.monitor */
56 static void
parseposition(char * optarg)57 parseposition(char *optarg)
58 {
59 	long n;
60 	char *s = optarg;
61 	char *endp;
62 
63 	n = strtol(s, &endp, 10);
64 	if (errno == ERANGE || n > INT_MAX || n < 0 || endp == s || *endp != 'x')
65 		goto error;
66 	config.posx = n;
67 	s = endp+1;
68 	n = strtol(s, &endp, 10);
69 	if (errno == ERANGE || n > INT_MAX || n < 0 || endp == s)
70 		goto error;
71 	config.posy = n;
72 	if (*endp == ':') {
73 		s = endp+1;
74 		mflag = 1;
75 		if (strncasecmp(s, "CUR", 3) == 0) {
76 			config.monitor = -1;
77 			endp = s+3;
78 		} else {
79 			n = strtol(s, &endp, 10);
80 			if (errno == ERANGE || n > INT_MAX || n < 0 || endp == s || *endp != '\0')
81 				goto error;
82 			config.monitor = n;
83 		}
84 	} else if (*endp != '\0') {
85 		goto error;
86 	}
87 
88 	return;
89 
90 error:
91 	errx(1, "improper position: %s", optarg);
92 }
93 
94 /* get configuration from X resources */
95 static void
getresources(void)96 getresources(void)
97 {
98 	char *type;
99 	XrmValue xval;
100 
101 	if (xrm == NULL || xdb == NULL)
102 		return;
103 
104 	if (XrmGetResource(xdb, "xmenu.borderWidth", "*", &type, &xval) == True)
105 		GETNUM(config.border_pixels, xval.addr)
106 	if (XrmGetResource(xdb, "xmenu.separatorWidth", "*", &type, &xval) == True)
107 		GETNUM(config.separator_pixels, xval.addr)
108 	if (XrmGetResource(xdb, "xmenu.height", "*", &type, &xval) == True)
109 		GETNUM(config.height_pixels, xval.addr)
110 	if (XrmGetResource(xdb, "xmenu.width", "*", &type, &xval) == True)
111 		GETNUM(config.width_pixels, xval.addr)
112 	if (XrmGetResource(xdb, "xmenu.gap", "*", &type, &xval) == True)
113 		GETNUM(config.gap_pixels, xval.addr)
114 	if (XrmGetResource(xdb, "xmenu.background", "*", &type, &xval) == True)
115 		config.background_color = xval.addr;
116 	if (XrmGetResource(xdb, "xmenu.foreground", "*", &type, &xval) == True)
117 		config.foreground_color = xval.addr;
118 	if (XrmGetResource(xdb, "xmenu.selbackground", "*", &type, &xval) == True)
119 		config.selbackground_color = xval.addr;
120 	if (XrmGetResource(xdb, "xmenu.selforeground", "*", &type, &xval) == True)
121 		config.selforeground_color = xval.addr;
122 	if (XrmGetResource(xdb, "xmenu.separator", "*", &type, &xval) == True)
123 		config.separator_color = xval.addr;
124 	if (XrmGetResource(xdb, "xmenu.border", "*", &type, &xval) == True)
125 		config.border_color = xval.addr;
126 	if (XrmGetResource(xdb, "xmenu.font", "*", &type, &xval) == True)
127 		config.font = xval.addr;
128 	if (XrmGetResource(xdb, "xmenu.alignment", "*", &type, &xval) == True) {
129 		if (strcasecmp(xval.addr, "center") == 0)
130 			config.alignment = CenterAlignment;
131 		else if (strcasecmp(xval.addr, "left") == 0)
132 			config.alignment = LeftAlignment;
133 		else if (strcasecmp(xval.addr, "right") == 0)
134 			config.alignment = RightAlignment;
135 	}
136 }
137 
138 /* get configuration from command-line options */
139 static char *
getoptions(int argc,char * argv[])140 getoptions(int argc, char *argv[])
141 {
142 	int ch;
143 
144 	while ((ch = getopt(argc, argv, "ip:rw")) != -1) {
145 		switch (ch) {
146 		case 'i':
147 			iflag = 1;
148 			break;
149 		case 'p':
150 			pflag = 1;
151 			parseposition(optarg);
152 			break;
153 		case 'r':
154 			rflag = 1;
155 			break;
156 		case 'w':
157 			wflag = 1;
158 			break;
159 		default:
160 			usage();
161 			break;
162 		}
163 	}
164 	argc -= optind;
165 	argv += optind;
166 	if (argc > 1)
167 		usage();
168 	else if (argc == 1)
169 		return *argv;
170 	return PROGNAME;
171 }
172 
173 /* parse font string */
174 static void
parsefonts(const char * s)175 parsefonts(const char *s)
176 {
177 	const char *p;
178 	char buf[1024];
179 	size_t nfont = 0;
180 
181 	dc.nfonts = 1;
182 	for (p = s; *p; p++)
183 		if (*p == ',')
184 			dc.nfonts++;
185 
186 	if ((dc.fonts = calloc(dc.nfonts, sizeof *dc.fonts)) == NULL)
187 		err(1, "calloc");
188 
189 	p = s;
190 	while (*p != '\0') {
191 		size_t i;
192 
193 		i = 0;
194 		while (isspace(*p))
195 			p++;
196 		while (i < sizeof buf && *p != '\0' && *p != ',')
197 			buf[i++] = *p++;
198 		if (i >= sizeof buf)
199 			errx(1, "font name too long");
200 		if (*p == ',')
201 			p++;
202 		buf[i] = '\0';
203 		if (nfont == 0)
204 			if ((dc.pattern = FcNameParse((FcChar8 *)buf)) == NULL)
205 				errx(1, "the first font in the cache must be loaded from a font string");
206 		if ((dc.fonts[nfont++] = XftFontOpenName(dpy, screen, buf)) == NULL)
207 			errx(1, "could not load font");
208 	}
209 }
210 
211 /* get color from color string */
212 static void
ealloccolor(const char * s,XftColor * color)213 ealloccolor(const char *s, XftColor *color)
214 {
215 	if(!XftColorAllocName(dpy, visual, colormap, s, color))
216 		errx(1, "could not allocate color: %s", s);
217 }
218 
219 /* query monitor information and cursor position */
220 static void
initmonitor(void)221 initmonitor(void)
222 {
223 	XineramaScreenInfo *info = NULL;
224 	Window dw;          /* dummy variable */
225 	int di;             /* dummy variable */
226 	unsigned du;        /* dummy variable */
227 	int cursx, cursy;   /* cursor position */
228 	int nmons;
229 	int i;
230 
231 	XQueryPointer(dpy, rootwin, &dw, &dw, &cursx, &cursy, &di, &di, &du);
232 
233 	mon.x = mon.y = 0;
234 	mon.w = DisplayWidth(dpy, screen);
235 	mon.h = DisplayHeight(dpy, screen);
236 
237 	if ((info = XineramaQueryScreens(dpy, &nmons)) != NULL) {
238 		int selmon = 0;
239 
240 		if (!mflag || config.monitor < 0 || config.monitor >= nmons) {
241 			for (i = 0; i < nmons; i++) {
242 				if (BETWEEN(cursx, info[i].x_org, info[i].x_org + info[i].width) &&
243 				    BETWEEN(cursy, info[i].y_org, info[i].y_org + info[i].height)) {
244 					selmon = i;
245 					break;
246 				}
247 			}
248 		} else {
249 			selmon = config.monitor;
250 		}
251 
252 		mon.x = info[selmon].x_org;
253 		mon.y = info[selmon].y_org;
254 		mon.w = info[selmon].width;
255 		mon.h = info[selmon].height;
256 
257 		XFree(info);
258 	}
259 
260 	if (!pflag) {
261 		config.posx = cursx;
262 		config.posy = cursy;
263 	} else if (mflag) {
264 		config.posx += mon.x;
265 		config.posy += mon.y;
266 	}
267 }
268 
269 /* init draw context */
270 static void
initdc(void)271 initdc(void)
272 {
273 	/* get color pixels */
274 	ealloccolor(config.background_color,    &dc.normal[ColorBG]);
275 	ealloccolor(config.foreground_color,    &dc.normal[ColorFG]);
276 	ealloccolor(config.selbackground_color, &dc.selected[ColorBG]);
277 	ealloccolor(config.selforeground_color, &dc.selected[ColorFG]);
278 	ealloccolor(config.separator_color,     &dc.separator);
279 	ealloccolor(config.border_color,        &dc.border);
280 
281 	/* parse fonts */
282 	parsefonts(config.font);
283 
284 	/* create common GC */
285 	dc.gc = XCreateGC(dpy, rootwin, 0, NULL);
286 }
287 
288 /* calculate icon size */
289 static void
initiconsize(void)290 initiconsize(void)
291 {
292 	config.iconsize = config.height_pixels - config.iconpadding * 2;
293 }
294 
295 /* intern atoms */
296 static void
initatoms(void)297 initatoms(void)
298 {
299 	utf8string = XInternAtom(dpy, "UTF8_STRING", False);
300 	wmdelete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
301 	netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
302 	netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
303 	netatom[NetWMWindowTypePopupMenu] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_POPUP_MENU", False);
304 }
305 
306 /* allocate an item */
307 static struct Item *
allocitem(const char * label,const char * output,char * file)308 allocitem(const char *label, const char *output, char *file)
309 {
310 	struct Item *item;
311 
312 	if ((item = malloc(sizeof *item)) == NULL)
313 		err(1, "malloc");
314 	if (label == NULL) {
315 		item->label = NULL;
316 		item->output = NULL;
317 	} else {
318 		if ((item->label = strdup(label)) == NULL)
319 			err(1, "strdup");
320 		if (label == output) {
321 			item->output = item->label;
322 		} else {
323 			if ((item->output = strdup(output)) == NULL)
324 				err(1, "strdup");
325 		}
326 	}
327 	if (file == NULL) {
328 		item->file = NULL;
329 	} else {
330 		if ((item->file = strdup(file)) == NULL)
331 			err(1, "strdup");
332 	}
333 	item->y = 0;
334 	item->h = 0;
335 	item->next = NULL;
336 	item->submenu = NULL;
337 	item->icon = NULL;
338 
339 	return item;
340 }
341 
342 /* allocate a menu and create its window */
343 static struct Menu *
allocmenu(struct Menu * parent,struct Item * list,unsigned level)344 allocmenu(struct Menu *parent, struct Item *list, unsigned level)
345 {
346 	XSetWindowAttributes swa;
347 	struct Menu *menu;
348 
349 	if ((menu = malloc(sizeof *menu)) == NULL)
350 		err(1, "malloc");
351 	menu->parent = parent;
352 	menu->list = list;
353 	menu->caller = NULL;
354 	menu->selected = NULL;
355 	menu->w = 0;        /* recalculated by setupmenu() */
356 	menu->h = 0;        /* recalculated by setupmenu() */
357 	menu->x = mon.x;    /* recalculated by setupmenu() */
358 	menu->y = mon.y;    /* recalculated by setupmenu() */
359 	menu->level = level;
360 	menu->drawn = 0;
361 	menu->hasicon = 0;
362 
363 	swa.override_redirect = (wflag) ? False : True;
364 	swa.background_pixel = dc.normal[ColorBG].pixel;
365 	swa.border_pixel = dc.border.pixel;
366 	swa.save_under = True;  /* pop-up windows should save_under*/
367 	swa.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask
368 	               | PointerMotionMask | LeaveWindowMask;
369 	if (wflag)
370 		swa.event_mask |= StructureNotifyMask;
371 	menu->win = XCreateWindow(dpy, rootwin, 0, 0, 1, 1, 0,
372 	                          CopyFromParent, CopyFromParent, CopyFromParent,
373 	                          CWOverrideRedirect | CWBackPixel |
374 	                          CWBorderPixel | CWEventMask | CWSaveUnder,
375 	                          &swa);
376 
377 	menu->xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
378 	                      XNClientWindow, menu->win, XNFocusWindow, menu->win, NULL);
379 	if (menu->xic == NULL)
380 		errx(1, "XCreateIC: could not obtain input method");
381 
382 	return menu;
383 }
384 
385 /* build the menu tree */
386 static struct Menu *
buildmenutree(unsigned level,const char * label,const char * output,char * file)387 buildmenutree(unsigned level, const char *label, const char *output, char *file)
388 {
389 	static struct Menu *prevmenu = NULL;    /* menu the previous item was added to */
390 	static struct Menu *rootmenu = NULL;    /* menu to be returned */
391 	struct Item *curritem = NULL;           /* item currently being read */
392 	struct Item *item;                      /* dummy item for loops */
393 	struct Menu *menu;                      /* dummy menu for loops */
394 	unsigned i;
395 
396 	/* create the item */
397 	curritem = allocitem(label, output, file);
398 
399 	/* put the item in the menu tree */
400 	if (prevmenu == NULL) {                 /* there is no menu yet */
401 		menu = allocmenu(NULL, curritem, level);
402 		rootmenu = menu;
403 		prevmenu = menu;
404 		curritem->prev = NULL;
405 	} else if (level < prevmenu->level) {   /* item is continuation of a parent menu */
406 		/* go up the menu tree until find the menu this item continues */
407 		for (menu = prevmenu, i = level;
408 			  menu != NULL && i != prevmenu->level;
409 			  menu = menu->parent, i++)
410 			;
411 		if (menu == NULL)
412 			errx(1, "improper indentation detected");
413 
414 		/* find last item in the new menu */
415 		for (item = menu->list; item->next != NULL; item = item->next)
416 			;
417 
418 		prevmenu = menu;
419 		item->next = curritem;
420 		curritem->prev = item;
421 	} else if (level == prevmenu->level) {  /* item is a continuation of current menu */
422 		/* find last item in the previous menu */
423 		for (item = prevmenu->list; item->next != NULL; item = item->next)
424 			;
425 
426 		item->next = curritem;
427 		curritem->prev = item;
428 	} else if (level > prevmenu->level) {   /* item begins a new menu */
429 		menu = allocmenu(prevmenu, curritem, level);
430 
431 		/* find last item in the previous menu */
432 		for (item = prevmenu->list; item->next != NULL; item = item->next)
433 			;
434 
435 		/* a separator is no valid root for a submenu */
436 		if (!item->label)
437 			errx(1, "a separator is no valid root for a submenu");
438 
439 		prevmenu = menu;
440 		menu->caller = item;
441 		item->submenu = menu;
442 		curritem->prev = NULL;
443 	}
444 
445 	if (curritem->file)
446 		prevmenu->hasicon = 1;
447 
448 	return rootmenu;
449 }
450 
451 /* create menus and items from the stdin */
452 static struct Menu *
parsestdin(void)453 parsestdin(void)
454 {
455 	struct Menu *rootmenu;
456 	char *s, buf[BUFSIZ];
457 	char *file, *label, *output;
458 	unsigned level = 0;
459 
460 	rootmenu = NULL;
461 
462 	while (fgets(buf, BUFSIZ, stdin) != NULL) {
463 		/* get the indentation level */
464 		level = strspn(buf, "\t");
465 
466 		/* get the label */
467 		s = level + buf;
468 		label = strtok(s, "\t\n");
469 
470 		/* get the filename */
471 		file = NULL;
472 		if (label != NULL && strncmp(label, "IMG:", 4) == 0) {
473 			file = label + 4;
474 			label = strtok(NULL, "\t\n");
475 		}
476 
477 		/* get the output */
478 		output = strtok(NULL, "\n");
479 		if (output == NULL) {
480 			output = label;
481 		} else {
482 			while (*output == '\t')
483 				output++;
484 		}
485 
486 		rootmenu = buildmenutree(level, label, output, file);
487 	}
488 
489 	return rootmenu;
490 }
491 
492 /* get next utf8 char from s return its codepoint and set next_ret to pointer to end of character */
493 static FcChar32
getnextutf8char(const char * s,const char ** next_ret)494 getnextutf8char(const char *s, const char **next_ret)
495 {
496 	static const unsigned char utfbyte[] = {0x80, 0x00, 0xC0, 0xE0, 0xF0};
497 	static const unsigned char utfmask[] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
498 	static const FcChar32 utfmin[] = {0, 0x00,  0x80,  0x800,  0x10000};
499 	static const FcChar32 utfmax[] = {0, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
500 	/* 0xFFFD is the replacement character, used to represent unknown characters */
501 	static const FcChar32 unknown = 0xFFFD;
502 	FcChar32 ucode;         /* FcChar32 type holds 32 bits */
503 	size_t usize = 0;       /* n' of bytes of the utf8 character */
504 	size_t i;
505 
506 	*next_ret = s+1;
507 
508 	/* get code of first byte of utf8 character */
509 	for (i = 0; i < sizeof utfmask; i++) {
510 		if (((unsigned char)*s & utfmask[i]) == utfbyte[i]) {
511 			usize = i;
512 			ucode = (unsigned char)*s & ~utfmask[i];
513 			break;
514 		}
515 	}
516 
517 	/* if first byte is a continuation byte or is not allowed, return unknown */
518 	if (i == sizeof utfmask || usize == 0)
519 		return unknown;
520 
521 	/* check the other usize-1 bytes */
522 	s++;
523 	for (i = 1; i < usize; i++) {
524 		*next_ret = s+1;
525 		/* if byte is nul or is not a continuation byte, return unknown */
526 		if (*s == '\0' || ((unsigned char)*s & utfmask[0]) != utfbyte[0])
527 			return unknown;
528 		/* 6 is the number of relevant bits in the continuation byte */
529 		ucode = (ucode << 6) | ((unsigned char)*s & ~utfmask[0]);
530 		s++;
531 	}
532 
533 	/* check if ucode is invalid or in utf-16 surrogate halves */
534 	if (!BETWEEN(ucode, utfmin[usize], utfmax[usize])
535 	    || BETWEEN (ucode, 0xD800, 0xDFFF))
536 		return unknown;
537 
538 	return ucode;
539 }
540 
541 /* get which font contains a given code point */
542 static XftFont *
getfontucode(FcChar32 ucode)543 getfontucode(FcChar32 ucode)
544 {
545 	FcCharSet *fccharset = NULL;
546 	FcPattern *fcpattern = NULL;
547 	FcPattern *match = NULL;
548 	XftFont *retfont = NULL;
549 	XftResult result;
550 	size_t i;
551 
552 	for (i = 0; i < dc.nfonts; i++)
553 		if (XftCharExists(dpy, dc.fonts[i], ucode) == FcTrue)
554 			return dc.fonts[i];
555 
556 	/* create a charset containing our code point */
557 	fccharset = FcCharSetCreate();
558 	FcCharSetAddChar(fccharset, ucode);
559 
560 	/* create a pattern akin to the dc.pattern but containing our charset */
561 	if (fccharset) {
562 		fcpattern = FcPatternDuplicate(dc.pattern);
563 		FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
564 	}
565 
566 	/* find pattern matching fcpattern */
567 	if (fcpattern) {
568 		FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
569 		FcDefaultSubstitute(fcpattern);
570 		match = XftFontMatch(dpy, screen, fcpattern, &result);
571 	}
572 
573 	/* if found a pattern, open its font */
574 	if (match) {
575 		retfont = XftFontOpenPattern(dpy, match);
576 		if (retfont && XftCharExists(dpy, retfont, ucode) == FcTrue) {
577 			if ((dc.fonts = realloc(dc.fonts, dc.nfonts+1)) == NULL)
578 				err(1, "realloc");
579 			dc.fonts[dc.nfonts] = retfont;
580 			return dc.fonts[dc.nfonts++];
581 		} else {
582 			XftFontClose(dpy, retfont);
583 		}
584 	}
585 
586 	/* in case no fount was found, return the first one */
587 	return dc.fonts[0];
588 }
589 
590 /* draw text into XftDraw, return width of text glyphs */
591 static int
drawtext(XftDraw * draw,XftColor * color,int x,int y,unsigned h,const char * text)592 drawtext(XftDraw *draw, XftColor *color, int x, int y, unsigned h, const char *text)
593 {
594 	int textwidth = 0;
595 
596 	while (*text) {
597 		XftFont *currfont;
598 		XGlyphInfo ext;
599 		FcChar32 ucode;
600 		const char *next;
601 		size_t len;
602 
603 		ucode = getnextutf8char(text, &next);
604 		currfont = getfontucode(ucode);
605 
606 		len = next - text;
607 		XftTextExtentsUtf8(dpy, currfont, (XftChar8 *)text, len, &ext);
608 		textwidth += ext.xOff;
609 
610 		if (draw) {
611 			int texty;
612 
613 			texty = y + (h - (currfont->ascent + currfont->descent))/2 + currfont->ascent;
614 			XftDrawStringUtf8(draw, color, currfont, x, texty, (XftChar8 *)text, len);
615 			x += ext.xOff;
616 		}
617 
618 		text = next;
619 	}
620 
621 	return textwidth;
622 }
623 
624 /* setup the height, width and icon of the items of a menu */
625 static void
setupitems(struct Menu * menu)626 setupitems(struct Menu *menu)
627 {
628 	struct Item *item;
629 	int itemwidth;
630 
631 	menu->w = config.width_pixels;
632 	menu->maxtextw = 0;
633 	for (item = menu->list; item != NULL; item = item->next) {
634 		item->y = menu->h;
635 		if (item->label == NULL)   /* height for separator item */
636 			item->h = config.separator_pixels;
637 		else
638 			item->h = config.height_pixels;
639 		menu->h += item->h;
640 		if (item->label)
641 			item->textw = drawtext(NULL, NULL, 0, 0, 0, item->label);
642 		else
643 			item->textw = 0;
644 
645 		/*
646 		 * set menu width
647 		 *
648 		 * the item width depends on the size of its label (item->textw),
649 		 * and it is only used to calculate the width of the menu (which
650 		 * is equal to the width of the largest item).
651 		 *
652 		 * the horizontal padding appears 4 times through the width of a
653 		 * item: before and after its icon, and before and after its triangle.
654 		 * if the iflag is set (icons are disabled) then the horizontal
655 		 * padding appears 3 times: before the label and around the triangle.
656 		 */
657 		itemwidth = item->textw + config.triangle_width + config.horzpadding * 3;
658 		itemwidth += (iflag || !menu->hasicon) ? 0 : config.iconsize + config.horzpadding;
659 		menu->w = MAX(menu->w, itemwidth);
660 		menu->maxtextw = MAX(menu->maxtextw, item->textw);
661 	}
662 }
663 
664 /* setup the position of a menu */
665 static void
setupmenupos(struct Menu * menu)666 setupmenupos(struct Menu *menu)
667 {
668 	int width, height;
669 
670 	width = menu->w + config.border_pixels * 2;
671 	height = menu->h + config.border_pixels * 2;
672 	if (menu->parent == NULL) { /* if root menu, calculate in respect to cursor */
673 		if (pflag || (config.posx >= mon.x && mon.x + mon.w - config.posx >= width))
674 			menu->x = config.posx;
675 		else if (config.posx > width)
676 			menu->x = config.posx - width;
677 
678 		if (pflag || (config.posy >= mon.y && mon.y + mon.h - config.posy >= height))
679 			menu->y = config.posy;
680 		else if (mon.y + mon.h > height)
681 			menu->y = mon.y + mon.h - height;
682 	} else {                    /* else, calculate in respect to parent menu */
683 		int parentwidth;
684 
685 		parentwidth = menu->parent->x + menu->parent->w + config.border_pixels + config.gap_pixels;
686 
687 		if (mon.x + mon.w - parentwidth >= width)
688 			menu->x = parentwidth;
689 		else if (menu->parent->x > menu->w + config.border_pixels + config.gap_pixels)
690 			menu->x = menu->parent->x - menu->w - config.border_pixels - config.gap_pixels;
691 
692 		if (mon.y + mon.h - (menu->caller->y + menu->parent->y) >= height)
693 			menu->y = menu->caller->y + menu->parent->y;
694 		else if (mon.y + mon.h > height)
695 			menu->y = mon.y + mon.h - height;
696 	}
697 }
698 
699 /* recursivelly setup menu configuration and its pixmap */
700 static void
setupmenu(struct Menu * menu,XClassHint * classh)701 setupmenu(struct Menu *menu, XClassHint *classh)
702 {
703 	char *title;
704 	struct Item *item;
705 	XWindowChanges changes;
706 	XSizeHints sizeh;
707 	XTextProperty wintitle;
708 
709 	/* setup size and position of menus */
710 	setupitems(menu);
711 	setupmenupos(menu);
712 
713 	/* update menu geometry */
714 	changes.border_width = config.border_pixels;
715 	changes.height = menu->h;
716 	changes.width = menu->w;
717 	changes.x = menu->x;
718 	changes.y = menu->y;
719 	XConfigureWindow(dpy, menu->win, CWBorderWidth | CWWidth | CWHeight | CWX | CWY, &changes);
720 
721 	/* set window title (used if wflag is on) */
722 	if (menu->parent == NULL) {
723 		title = classh->res_name;
724 	} else if (menu->caller->output) {
725 		title = menu->caller->output;
726 	} else {
727 		title = "\0";
728 	}
729 	XStringListToTextProperty(&title, 1, &wintitle);
730 
731 	/* set window manager hints */
732 	sizeh.flags = USPosition | PMaxSize | PMinSize;
733 	sizeh.min_width = sizeh.max_width = menu->w;
734 	sizeh.min_height = sizeh.max_height = menu->h;
735 	XSetWMProperties(dpy, menu->win, &wintitle, NULL, NULL, 0, &sizeh, NULL, classh);
736 
737 	/* set WM protocols and ewmh window properties */
738 	XSetWMProtocols(dpy, menu->win, &wmdelete, 1);
739 	XChangeProperty(dpy, menu->win, netatom[NetWMName], utf8string, 8,
740 	                PropModeReplace, (unsigned char *)title, strlen(title));
741 	XChangeProperty(dpy, menu->win, netatom[NetWMWindowType], XA_ATOM, 32,
742 	                PropModeReplace,
743 	                (unsigned char *)&netatom[NetWMWindowTypePopupMenu], 1);
744 
745 	/* calculate positions of submenus */
746 	for (item = menu->list; item != NULL; item = item->next) {
747 		if (item->submenu != NULL)
748 			setupmenu(item->submenu, classh);
749 	}
750 }
751 
752 /* try to grab pointer, we may have to wait for another process to ungrab */
753 static void
grabpointer(void)754 grabpointer(void)
755 {
756 	struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000  };
757 	int i;
758 
759 	for (i = 0; i < 1000; i++) {
760 		if (XGrabPointer(dpy, rootwin, True, ButtonPressMask,
761 		                 GrabModeAsync, GrabModeAsync, None,
762 		                 None, CurrentTime) == GrabSuccess)
763 			return;
764 		nanosleep(&ts, NULL);
765 	}
766 	errx(1, "could not grab pointer");
767 }
768 
769 /* try to grab keyboard, we may have to wait for another process to ungrab */
770 static void
grabkeyboard(void)771 grabkeyboard(void)
772 {
773 	struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000  };
774 	int i;
775 
776 	for (i = 0; i < 1000; i++) {
777 		if (XGrabKeyboard(dpy, rootwin, True, GrabModeAsync,
778 		                  GrabModeAsync, CurrentTime) == GrabSuccess)
779 			return;
780 		nanosleep(&ts, NULL);
781 	}
782 	errx(1, "could not grab keyboard");
783 }
784 
785 /* try to grab focus, we may have to wait for another process to ungrab */
786 static void
grabfocus(Window win)787 grabfocus(Window win)
788 {
789 	struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000  };
790 	Window focuswin;
791 	int i, revertwin;
792 
793 	for (i = 0; i < 100; ++i) {
794 		XGetInputFocus(dpy, &focuswin, &revertwin);
795 		if (focuswin == win)
796 			return;
797 		XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
798 		nanosleep(&ts, NULL);
799 	}
800 	errx(1, "cannot grab focus");
801 }
802 
803 /* ungrab pointer and keyboard */
804 static void
ungrab(void)805 ungrab(void)
806 {
807 	XUngrabPointer(dpy, CurrentTime);
808 	XUngrabKeyboard(dpy, CurrentTime);
809 }
810 
811 /* load and scale icon */
812 static Imlib_Image
loadicon(const char * file)813 loadicon(const char *file)
814 {
815 	Imlib_Image icon;
816 	Imlib_Load_Error errcode;
817 	const char *errstr;
818 	int width;
819 	int height;
820 	int imgsize;
821 
822 	icon = imlib_load_image_with_error_return(file, &errcode);
823 	if (*file == '\0') {
824 		warnx("could not load icon (file name is blank)");
825 		return NULL;
826 	} else if (icon == NULL) {
827 		switch (errcode) {
828 		case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
829 			errstr = "file does not exist";
830 			break;
831 		case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY:
832 			errstr = "file is directory";
833 			break;
834 		case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ:
835 		case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE:
836 			errstr = "permission denied";
837 			break;
838 		case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
839 			errstr = "unknown file format";
840 			break;
841 		case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
842 			errstr = "path too long";
843 			break;
844 		case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT:
845 		case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY:
846 		case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE:
847 			errstr = "improper path";
848 			break;
849 		case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS:
850 			errstr = "too many symbolic links";
851 			break;
852 		case IMLIB_LOAD_ERROR_OUT_OF_MEMORY:
853 			errstr = "out of memory";
854 			break;
855 		case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS:
856 			errstr = "out of file descriptors";
857 			break;
858 		default:
859 			errstr = "unknown error";
860 			break;
861 		}
862 		warnx("could not load icon (%s): %s", errstr, file);
863 		return NULL;
864 	}
865 
866 	imlib_context_set_image(icon);
867 
868 	width = imlib_image_get_width();
869 	height = imlib_image_get_height();
870 	imgsize = MIN(width, height);
871 
872 	icon = imlib_create_cropped_scaled_image(0, 0, imgsize, imgsize,
873 	                                         config.iconsize,
874 	                                         config.iconsize);
875 
876 	return icon;
877 }
878 
879 /* draw pixmap for the selected and unselected version of each item on menu */
880 static void
drawitems(struct Menu * menu)881 drawitems(struct Menu *menu)
882 {
883 	XftDraw *dsel, *dunsel;
884 	struct Item *item;
885 	int textx;
886 	int x, y;
887 
888 	for (item = menu->list; item != NULL; item = item->next) {
889 		item->unsel = XCreatePixmap(dpy, menu->win, menu->w, item->h,
890 		                          DefaultDepth(dpy, screen));
891 
892 		XSetForeground(dpy, dc.gc, dc.normal[ColorBG].pixel);
893 		XFillRectangle(dpy, item->unsel, dc.gc, 0, 0, menu->w, item->h);
894 
895 		if (item->label == NULL) { /* item is separator */
896 			y = item->h/2;
897 			XSetForeground(dpy, dc.gc, dc.separator.pixel);
898 			XDrawLine(dpy, item->unsel, dc.gc, config.horzpadding, y,
899 			          menu->w - config.horzpadding, y);
900 
901 			item->sel = item->unsel;
902 		} else {
903 			item->sel = XCreatePixmap(dpy, menu->win, menu->w, item->h,
904 			                          DefaultDepth(dpy, screen));
905 			XSetForeground(dpy, dc.gc, dc.selected[ColorBG].pixel);
906 			XFillRectangle(dpy, item->sel, dc.gc, 0, 0, menu->w, item->h);
907 
908 			/* draw text */
909 			textx = config.horzpadding;
910 			textx += (iflag || !menu->hasicon) ? 0 : config.horzpadding + config.iconsize;
911 			switch (config.alignment) {
912 			case CenterAlignment:
913 				textx += (menu->maxtextw - item->textw) / 2;
914 				break;
915 			case RightAlignment:
916 				textx += menu->maxtextw - item->textw;
917 				break;
918 			default:
919 				break;
920 			}
921 			dsel = XftDrawCreate(dpy, item->sel, visual, colormap);
922 			dunsel = XftDrawCreate(dpy, item->unsel, visual, colormap);
923 			XSetForeground(dpy, dc.gc, dc.selected[ColorFG].pixel);
924 			drawtext(dsel, &dc.selected[ColorFG], textx, 0, item->h, item->label);
925 			XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
926 			drawtext(dunsel, &dc.normal[ColorFG], textx, 0, item->h, item->label);
927 			XftDrawDestroy(dsel);
928 			XftDrawDestroy(dunsel);
929 
930 			/* draw triangle */
931 			if (item->submenu != NULL) {
932 				x = menu->w - config.triangle_width - config.horzpadding;
933 				y = (item->h - config.triangle_height + 1) / 2;
934 
935 				XPoint triangle[] = {
936 					{x, y},
937 					{x + config.triangle_width, y + config.triangle_height/2},
938 					{x, y + config.triangle_height},
939 					{x, y}
940 				};
941 
942 				XSetForeground(dpy, dc.gc, dc.selected[ColorFG].pixel);
943 				XFillPolygon(dpy, item->sel, dc.gc, triangle, LEN(triangle),
944 				             Convex, CoordModeOrigin);
945 				XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
946 				XFillPolygon(dpy, item->unsel, dc.gc, triangle, LEN(triangle),
947 				             Convex, CoordModeOrigin);
948 			}
949 
950 			/* try to load icon */
951 			if (item->file && !iflag) {
952 				item->icon = loadicon(item->file);
953 				free(item->file);
954 			}
955 
956 			/* draw icon if properly loaded */
957 			if (item->icon) {
958 				imlib_context_set_image(item->icon);
959 				imlib_context_set_drawable(item->sel);
960 				imlib_render_image_on_drawable(config.horzpadding, config.iconpadding);
961 				imlib_context_set_drawable(item->unsel);
962 				imlib_render_image_on_drawable(config.horzpadding, config.iconpadding);
963 				imlib_context_set_image(item->icon);
964 				imlib_free_image();
965 			}
966 		}
967 	}
968 }
969 
970 /* copy pixmaps of items of the current menu and of its ancestors into menu window */
971 static void
drawmenus(struct Menu * currmenu)972 drawmenus(struct Menu *currmenu)
973 {
974 	struct Menu *menu;
975 	struct Item *item;
976 
977 	for (menu = currmenu; menu != NULL; menu = menu->parent) {
978 		if (!menu->drawn) {
979 			drawitems(menu);
980 			menu->drawn = 1;
981 		}
982 		for (item = menu->list; item != NULL; item = item->next) {
983 			if (item == menu->selected)
984 				XCopyArea(dpy, item->sel, menu->win, dc.gc, 0, 0,
985 				          menu->w, item->h, 0, item->y);
986 			else
987 				XCopyArea(dpy, item->unsel, menu->win, dc.gc, 0, 0,
988 				          menu->w, item->h, 0, item->y);
989 		}
990 	}
991 }
992 
993 /* umap previous menus and map current menu and its parents */
994 static void
mapmenu(struct Menu * currmenu)995 mapmenu(struct Menu *currmenu)
996 {
997 	static struct Menu *prevmenu = NULL;
998 	struct Menu *menu, *menu_;
999 	struct Menu *lcamenu;   /* lowest common ancestor menu */
1000 	unsigned minlevel;      /* level of the closest to root menu */
1001 	unsigned maxlevel;      /* level of the closest to root menu */
1002 
1003 	/* do not remap current menu if it wasn't updated*/
1004 	if (prevmenu == currmenu)
1005 		return;
1006 
1007 	/* if this is the first time mapping, skip calculations */
1008 	if (prevmenu == NULL) {
1009 		XMapWindow(dpy, currmenu->win);
1010 		prevmenu = currmenu;
1011 		return;
1012 	}
1013 
1014 	/* find lowest common ancestor menu */
1015 	minlevel = MIN(currmenu->level, prevmenu->level);
1016 	maxlevel = MAX(currmenu->level, prevmenu->level);
1017 	if (currmenu->level == maxlevel) {
1018 		menu = currmenu;
1019 		menu_ = prevmenu;
1020 	} else {
1021 		menu = prevmenu;
1022 		menu_ = currmenu;
1023 	}
1024 	while (menu->level > minlevel)
1025 		menu = menu->parent;
1026 	while (menu != menu_) {
1027 		menu = menu->parent;
1028 		menu_ = menu_->parent;
1029 	}
1030 	lcamenu = menu;
1031 
1032 	/* unmap menus from currmenu (inclusive) until lcamenu (exclusive) */
1033 	for (menu = prevmenu; menu != lcamenu; menu = menu->parent) {
1034 		menu->selected = NULL;
1035 		XUnmapWindow(dpy, menu->win);
1036 	}
1037 
1038 	/* map menus from currmenu (inclusive) until lcamenu (exclusive) */
1039 	for (menu = currmenu; menu != lcamenu; menu = menu->parent) {
1040 
1041 		if (wflag) {
1042 			setupmenupos(menu);
1043 			XMoveWindow(dpy, menu->win, menu->x, menu->y);
1044 		}
1045 
1046 		XMapWindow(dpy, menu->win);
1047 	}
1048 
1049 	prevmenu = currmenu;
1050 	grabfocus(currmenu->win);
1051 }
1052 
1053 /* get menu of given window */
1054 static struct Menu *
getmenu(struct Menu * currmenu,Window win)1055 getmenu(struct Menu *currmenu, Window win)
1056 {
1057 	struct Menu *menu;
1058 
1059 	for (menu = currmenu; menu != NULL; menu = menu->parent)
1060 		if (menu->win == win)
1061 			return menu;
1062 
1063 	return NULL;
1064 }
1065 
1066 /* get item of given menu and position */
1067 static struct Item *
getitem(struct Menu * menu,int y)1068 getitem(struct Menu *menu, int y)
1069 {
1070 	struct Item *item;
1071 
1072 	if (menu == NULL)
1073 		return NULL;
1074 
1075 	for (item = menu->list; item != NULL; item = item->next)
1076 		if (y >= item->y && y <= item->y + item->h)
1077 			return item;
1078 
1079 	return NULL;
1080 }
1081 
1082 /* cycle through the items; non-zero direction is next, zero is prev */
1083 static struct Item *
itemcycle(struct Menu * currmenu,int direction)1084 itemcycle(struct Menu *currmenu, int direction)
1085 {
1086 	struct Item *item = NULL;
1087 	struct Item *lastitem;
1088 
1089 	for (lastitem = currmenu->list; lastitem && lastitem->next; lastitem = lastitem->next)
1090 		;
1091 
1092 	/* select item (either separator or labeled item) in given direction */
1093 	switch (direction) {
1094 	case ITEMNEXT:
1095 		if (currmenu->selected == NULL)
1096 			item = currmenu->list;
1097 		else if (currmenu->selected->next != NULL)
1098 			item = currmenu->selected->next;
1099 		break;
1100 	case ITEMPREV:
1101 		if (currmenu->selected == NULL)
1102 			item = lastitem;
1103 		else if (currmenu->selected->prev != NULL)
1104 			item = currmenu->selected->prev;
1105 		break;
1106 	case ITEMFIRST:
1107 		item = currmenu->list;
1108 		break;
1109 	case ITEMLAST:
1110 		item = lastitem;
1111 		break;
1112 	}
1113 
1114 	/*
1115 	 * the selected item can be a separator
1116 	 * let's select the closest labeled item (ie., one that isn't a separator)
1117 	 */
1118 	switch (direction) {
1119 	case ITEMNEXT:
1120 	case ITEMFIRST:
1121 		while (item != NULL && item->label == NULL)
1122 			item = item->next;
1123 		if (item == NULL)
1124 			item = currmenu->list;
1125 		break;
1126 	case ITEMPREV:
1127 	case ITEMLAST:
1128 		while (item != NULL && item->label == NULL)
1129 			item = item->prev;
1130 		if (item == NULL)
1131 			item = lastitem;
1132 		break;
1133 	}
1134 
1135 	return item;
1136 }
1137 
1138 /* check if button is used to scroll */
1139 static int
isscrollbutton(unsigned int button)1140 isscrollbutton(unsigned int button)
1141 {
1142 	if (button == Button4 || button == Button5)
1143 		return 1;
1144 	return 0;
1145 }
1146 
1147 /* check if button is used to open a item on click */
1148 static int
isclickbutton(unsigned int button)1149 isclickbutton(unsigned int button)
1150 {
1151 	if (button == Button1 || button == Button2)
1152 		return 1;
1153 	if (!rflag && button == Button3)
1154 		return 1;
1155 	return 0;
1156 }
1157 
1158 /* warp pointer to center of selected item */
1159 static void
warppointer(struct Menu * menu,struct Item * item)1160 warppointer(struct Menu *menu, struct Item *item)
1161 {
1162 	if (menu == NULL || item == NULL)
1163 		return;
1164 	if (menu->selected) {
1165 		XWarpPointer(dpy, None, menu->win, 0, 0, 0, 0, menu->w / 2, item->y + item->h / 2);
1166 	}
1167 }
1168 
1169 /* append buf into text */
1170 static int
append(char * text,char * buf,size_t textsize,size_t buflen)1171 append(char *text, char *buf, size_t textsize, size_t buflen)
1172 {
1173 	size_t textlen;
1174 
1175 	textlen = strlen(text);
1176 	if (iscntrl(*buf))
1177 		return 0;
1178 	if (textlen + buflen > textsize - 1)
1179 		return 0;
1180 	if (buflen < 1)
1181 		return 0;
1182 	memcpy(text + textlen, buf, buflen);
1183 	text[textlen + buflen] = '\0';
1184 	return 1;
1185 }
1186 
1187 /* get item in menu matching text from given direction (or from beginning, if dir = 0) */
1188 static struct Item *
matchitem(struct Menu * menu,char * text,int dir)1189 matchitem(struct Menu *menu, char *text, int dir)
1190 {
1191 	struct Item *item, *lastitem;
1192 	char *s;
1193 	size_t textlen;
1194 
1195 	for (lastitem = menu->list; lastitem && lastitem->next; lastitem = lastitem->next)
1196 		;
1197 	textlen = strlen(text);
1198 	if (dir < 0) {
1199 		if (menu->selected && menu->selected->prev)
1200 			item = menu->selected->prev;
1201 		else
1202 			item = lastitem;
1203 	} else if (dir > 0) {
1204 		if (menu->selected && menu->selected->next)
1205 			item = menu->selected->next;
1206 		else
1207 			item = menu->list;
1208 	} else {
1209 		item = menu->list;
1210 	}
1211 	/* find next item from selected item */
1212 	for ( ; item; item = (dir < 0) ? item->prev : item->next)
1213 		for (s = item->label; s && *s; s++)
1214 			if (strncasecmp(s, text, textlen) == 0)
1215 				return item;
1216 	/* if not found, try to find from the beginning/end of list */
1217 	if (dir > 0) {
1218 		for (item = menu->list ; item; item = item->next) {
1219 			for (s = item->label; s && *s; s++) {
1220 				if (strncasecmp(s, text, textlen) == 0) {
1221 					return item;
1222 				}
1223 			}
1224 		}
1225 	} else {
1226 		for (item = lastitem ; item; item = item->prev) {
1227 			for (s = item->label; s && *s; s++) {
1228 				if (strncasecmp(s, text, textlen) == 0) {
1229 					return item;
1230 				}
1231 			}
1232 		}
1233 	}
1234 	return NULL;
1235 }
1236 
1237 /* check keysyms defined on config.h */
1238 static KeySym
normalizeksym(KeySym ksym)1239 normalizeksym(KeySym ksym)
1240 {
1241 	if (ksym == KSYMFIRST)
1242 		return XK_Home;
1243 	if (ksym == KSYMLAST)
1244 		return XK_End;
1245 	if (ksym == KSYMUP)
1246 		return XK_Up;
1247 	if (ksym == KSYMDOWN)
1248 		return XK_Down;
1249 	if (ksym == KSYMLEFT)
1250 		return XK_Left;
1251 	if (ksym == KSYMRIGHT)
1252 		return XK_Right;
1253 	return ksym;
1254 }
1255 
1256 /* run event loop */
1257 static void
run(struct Menu * currmenu)1258 run(struct Menu *currmenu)
1259 {
1260 	char text[BUFSIZ];
1261 	char buf[32];
1262 	struct Menu *menu;
1263 	struct Item *item;
1264 	struct Item *previtem = NULL;
1265 	struct Item *lastitem, *select;
1266 	KeySym ksym;
1267 	Status status;
1268 	XEvent ev;
1269 	int warped = 0;
1270 	int action;
1271 	int len;
1272 	int i;
1273 
1274 	text[0] = '\0';
1275 	mapmenu(currmenu);
1276 	while (!XNextEvent(dpy, &ev)) {
1277 		if (XFilterEvent(&ev, None))
1278 			continue;
1279 		action = ACTION_NOP;
1280 		switch(ev.type) {
1281 		case Expose:
1282 			if (ev.xexpose.count == 0)
1283 				action = ACTION_DRAW;
1284 			break;
1285 		case MotionNotify:
1286 			if (!warped) {
1287 				menu = getmenu(currmenu, ev.xbutton.window);
1288 				item = getitem(menu, ev.xbutton.y);
1289 				if (menu == NULL || item == NULL || previtem == item)
1290 					break;
1291 				previtem = item;
1292 				select = menu->selected = item;
1293 				if (item->submenu != NULL) {
1294 					currmenu = item->submenu;
1295 					select = NULL;
1296 				} else {
1297 					currmenu = menu;
1298 				}
1299 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
1300 			}
1301 			warped = 0;
1302 			break;
1303 		case ButtonRelease:
1304 			if (isscrollbutton(ev.xbutton.button)) {
1305 				if (ev.xbutton.button == Button4)
1306 					select = itemcycle(currmenu, ITEMPREV);
1307 				else
1308 					select = itemcycle(currmenu, ITEMNEXT);
1309 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW | ACTION_WARP;
1310 			} else if (isclickbutton(ev.xbutton.button)) {
1311 				menu = getmenu(currmenu, ev.xbutton.window);
1312 				item = getitem(menu, ev.xbutton.y);
1313 				if (menu == NULL || item == NULL)
1314 					break;
1315 enteritem:
1316 				if (item->label == NULL)
1317 					break;  /* ignore separators */
1318 				if (item->submenu != NULL) {
1319 					currmenu = item->submenu;
1320 				} else {
1321 					printf("%s\n", item->output);
1322 					return;
1323 				}
1324 				select = currmenu->list;
1325 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
1326 				if (ev.xbutton.button == Button2) {
1327 					action |= ACTION_WARP;
1328 				}
1329 			}
1330 			break;
1331 		case ButtonPress:
1332 			menu = getmenu(currmenu, ev.xbutton.window);
1333 			if (menu == NULL)
1334 				return;
1335 			break;
1336 		case KeyPress:
1337 			len = XmbLookupString(currmenu->xic, &ev.xkey, buf, sizeof buf, &ksym, &status);
1338 			switch(status) {
1339 			default:                /* XLookupNone, XBufferOverflow */
1340 				continue;
1341 			case XLookupChars:
1342 				goto append;
1343 			case XLookupKeySym:     /* FALLTHROUGH */
1344 			case XLookupBoth:
1345 				break;
1346 			}
1347 
1348 			/* esc closes xmenu when current menu is the root menu */
1349 			if (ksym == XK_Escape && currmenu->parent == NULL)
1350 				return;
1351 
1352 			/* Shift-Tab = ISO_Left_Tab */
1353 			if (ksym == XK_Tab && (ev.xkey.state & ShiftMask))
1354 				ksym = XK_ISO_Left_Tab;
1355 
1356 			/* cycle through menu */
1357 			select = NULL;
1358 			ksym = normalizeksym(ksym);
1359 			switch (ksym) {
1360 			case XK_Home:
1361 				select = itemcycle(currmenu, ITEMFIRST);
1362 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
1363 				break;
1364 			case XK_End:
1365 				select = itemcycle(currmenu, ITEMLAST);
1366 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
1367 				break;
1368 			case XK_ISO_Left_Tab:
1369 				if (*text) {
1370 					select = matchitem(currmenu, text, -1);
1371 					action = ACTION_SELECT | ACTION_DRAW;
1372 					break;
1373 				}
1374 				/* FALLTHROUGH */
1375 			case XK_Up:
1376 				select = itemcycle(currmenu, ITEMPREV);
1377 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
1378 				break;
1379 			case XK_Tab:
1380 				if (*text) {
1381 					select = matchitem(currmenu, text, 1);
1382 					action = ACTION_SELECT | ACTION_DRAW;
1383 					break;
1384 				}
1385 				/* FALLTHROUGH */
1386 			case XK_Down:
1387 				select = itemcycle(currmenu, ITEMNEXT);
1388 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
1389 				break;
1390 			case XK_1: case XK_2: case XK_3: case XK_4: case XK_5: case XK_6: case XK_7: case XK_8: case XK_9:
1391 				item = itemcycle(currmenu, ITEMFIRST);
1392 				lastitem = itemcycle(currmenu, ITEMLAST);
1393 				for (int i = ksym - XK_1; i > 0 && item != lastitem; i--) {
1394 					currmenu->selected = item;
1395 					item = itemcycle(currmenu, ITEMNEXT);
1396 				}
1397 				select = item;
1398 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
1399 				break;
1400 			case XK_Return: case XK_Right:
1401 				if (currmenu->selected) {
1402 					item = currmenu->selected;
1403 					goto enteritem;
1404 				}
1405 				break;
1406 			case XK_Escape: case XK_Left:
1407 				if (currmenu->parent) {
1408 					select = currmenu->parent->selected;
1409 					currmenu = currmenu->parent;
1410 					action = ACTION_CLEAR | ACTION_MAP | ACTION_SELECT | ACTION_DRAW;
1411 				}
1412 				break;
1413 			case XK_BackSpace: case XK_Clear: case XK_Delete:
1414 				action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
1415 				break;
1416 			default:
1417 append:
1418 				if (*buf == '\0' || iscntrl(*buf))
1419 					break;
1420 				for (i = 0; i < 2; i++) {
1421 					append(text, buf, sizeof text, len);
1422 					if ((select = matchitem(currmenu, text, 0)))
1423 						break;
1424 					text[0] = '\0';
1425 				}
1426 				action = ACTION_SELECT | ACTION_DRAW;
1427 				break;
1428 			}
1429 			break;
1430 		case LeaveNotify:
1431 			previtem = NULL;
1432 			select = NULL;
1433 			action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
1434 			break;
1435 		case ConfigureNotify:
1436 			menu = getmenu(currmenu, ev.xconfigure.window);
1437 			if (menu == NULL)
1438 				break;
1439 			menu->x = ev.xconfigure.x;
1440 			menu->y = ev.xconfigure.y;
1441 			break;
1442 		case ClientMessage:
1443 			if ((unsigned long) ev.xclient.data.l[0] != wmdelete)
1444 				break;
1445 			/* user closed window */
1446 			menu = getmenu(currmenu, ev.xclient.window);
1447 			if (menu->parent == NULL)
1448 				return;     /* closing the root menu closes the program */
1449 			currmenu = menu->parent;
1450 			action = ACTION_MAP;
1451 			break;
1452 		}
1453 		if (action & ACTION_CLEAR)
1454 			text[0] = '\0';
1455 		if (action & ACTION_SELECT)
1456 			currmenu->selected = select;
1457 		if (action & ACTION_MAP)
1458 			mapmenu(currmenu);
1459 		if (action & ACTION_DRAW)
1460 			drawmenus(currmenu);
1461 		if (action & ACTION_WARP) {
1462 			warppointer(currmenu, select);
1463 			warped = 1;
1464 		}
1465 	}
1466 }
1467 
1468 /* recursivelly free pixmaps and destroy windows */
1469 static void
cleanmenu(struct Menu * menu)1470 cleanmenu(struct Menu *menu)
1471 {
1472 	struct Item *item;
1473 	struct Item *tmp;
1474 
1475 	item = menu->list;
1476 	while (item != NULL) {
1477 		if (item->submenu != NULL)
1478 			cleanmenu(item->submenu);
1479 		tmp = item;
1480 		if (menu->drawn) {
1481 			XFreePixmap(dpy, item->unsel);
1482 			if (tmp->label != NULL)
1483 				XFreePixmap(dpy, item->sel);
1484 		}
1485 		if (tmp->label != tmp->output)
1486 			free(tmp->label);
1487 		free(tmp->output);
1488 		item = item->next;
1489 		free(tmp);
1490 	}
1491 
1492 	XDestroyWindow(dpy, menu->win);
1493 	free(menu);
1494 }
1495 
1496 /* cleanup draw context */
1497 static void
cleandc(void)1498 cleandc(void)
1499 {
1500 	size_t i;
1501 
1502 	XftColorFree(dpy, visual, colormap, &dc.normal[ColorBG]);
1503 	XftColorFree(dpy, visual, colormap, &dc.normal[ColorFG]);
1504 	XftColorFree(dpy, visual, colormap, &dc.selected[ColorBG]);
1505 	XftColorFree(dpy, visual, colormap, &dc.selected[ColorFG]);
1506 	XftColorFree(dpy, visual, colormap, &dc.separator);
1507 	XftColorFree(dpy, visual, colormap, &dc.border);
1508 	for (i = 0; i < dc.nfonts; i++)
1509 		XftFontClose(dpy, dc.fonts[i]);
1510 	XFreeGC(dpy, dc.gc);
1511 }
1512 
1513 /* xmenu: generate menu from stdin and print selected entry to stdout */
1514 int
main(int argc,char * argv[])1515 main(int argc, char *argv[])
1516 {
1517 	struct Menu *rootmenu;
1518 	XClassHint classh;
1519 
1520 	/* open connection to server and set X variables */
1521 	if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
1522 		warnx("warning: no locale support");
1523 	if ((dpy = XOpenDisplay(NULL)) == NULL)
1524 		errx(1, "could not open display");
1525 	screen = DefaultScreen(dpy);
1526 	visual = DefaultVisual(dpy, screen);
1527 	rootwin = RootWindow(dpy, screen);
1528 	colormap = DefaultColormap(dpy, screen);
1529 	XrmInitialize();
1530 	if ((xrm = XResourceManagerString(dpy)) != NULL)
1531 		xdb = XrmGetStringDatabase(xrm);
1532 	if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
1533 		errx(1, "XOpenIM: could not open input device");
1534 
1535 	/* process configuration and window class */
1536 	getresources();
1537 	classh.res_class = PROGNAME;
1538 	classh.res_name = getoptions(argc, argv);
1539 
1540 	/* imlib2 stuff */
1541 	if (!iflag) {
1542 		imlib_set_cache_size(2048 * 1024);
1543 		imlib_context_set_dither(1);
1544 		imlib_context_set_display(dpy);
1545 		imlib_context_set_visual(visual);
1546 		imlib_context_set_colormap(colormap);
1547 	}
1548 
1549 	/* initializers */
1550 	initmonitor();
1551 	initdc();
1552 	initiconsize();
1553 	initatoms();
1554 
1555 	/* generate menus and set them up */
1556 	rootmenu = parsestdin();
1557 	if (rootmenu == NULL)
1558 		errx(1, "no menu generated");
1559 	setupmenu(rootmenu, &classh);
1560 
1561 	/* grab mouse and keyboard */
1562 	if (!wflag) {
1563 		grabpointer();
1564 		grabkeyboard();
1565 	}
1566 
1567 	/* run event loop */
1568 	run(rootmenu);
1569 
1570 	/* clean stuff */
1571 	ungrab();
1572 	cleanmenu(rootmenu);
1573 	cleandc();
1574 	XrmDestroyDatabase(xdb);
1575 	XCloseDisplay(dpy);
1576 
1577 	return 0;
1578 }
1579