thingmenu

A simple graphical menu launcher for X11.
git clone git://r-36.net/thingmenu
Log | Files | Refs | LICENSE

thingmenu.c (15423B)


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