dmenu

Simple dynamic menu for X
git clone https://git.sinitax.com/suckless/dmenu
Log | Files | Refs | README | LICENSE | sfeed.txt

dmenu.c (19487B)


      1/* See LICENSE file for copyright and license details. */
      2#include <ctype.h>
      3#include <locale.h>
      4#include <stdio.h>
      5#include <stdlib.h>
      6#include <string.h>
      7#include <strings.h>
      8#include <time.h>
      9#include <unistd.h>
     10
     11#include <X11/Xlib.h>
     12#include <X11/Xatom.h>
     13#include <X11/Xutil.h>
     14#ifdef XINERAMA
     15#include <X11/extensions/Xinerama.h>
     16#endif
     17#include <X11/Xft/Xft.h>
     18
     19#include "drw.h"
     20#include "util.h"
     21
     22/* macros */
     23#define INTERSECT(x,y,w,h,r)  (MAX(0, MIN((x)+(w),(r).x_org+(r).width)  - MAX((x),(r).x_org)) \
     24                             * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
     25#define TEXTW(X)              (drw_fontset_getwidth(drw, (X)) + lrpad)
     26
     27/* enums */
     28enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
     29
     30struct item {
     31	char *text;
     32	struct item *left, *right;
     33	int out;
     34};
     35
     36static char text[BUFSIZ] = "";
     37static char *embed;
     38static int bh, mw, mh;
     39static int inputw = 0, promptw;
     40static int lrpad; /* sum of left and right padding */
     41static size_t cursor;
     42static struct item *items = NULL;
     43static struct item *matches, *matchend;
     44static struct item *prev, *curr, *next, *sel;
     45static int mon = -1, screen;
     46
     47static Atom clip, utf8;
     48static Display *dpy;
     49static Window root, parentwin, win;
     50static XIC xic;
     51
     52static Drw *drw;
     53static Clr *scheme[SchemeLast];
     54
     55#include "config.h"
     56
     57static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
     58static char *(*fstrstr)(const char *, const char *) = strstr;
     59
     60static unsigned int
     61textw_clamp(const char *str, unsigned int n)
     62{
     63	unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
     64	return MIN(w, n);
     65}
     66
     67static void
     68appenditem(struct item *item, struct item **list, struct item **last)
     69{
     70	if (*last)
     71		(*last)->right = item;
     72	else
     73		*list = item;
     74
     75	item->left = *last;
     76	item->right = NULL;
     77	*last = item;
     78}
     79
     80static void
     81calcoffsets(void)
     82{
     83	int i, n;
     84
     85	if (lines > 0)
     86		n = lines * bh;
     87	else
     88		n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
     89	/* calculate which items will begin the next page and previous page */
     90	for (i = 0, next = curr; next; next = next->right)
     91		if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
     92			break;
     93	for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
     94		if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
     95			break;
     96}
     97
     98static void
     99cleanup(void)
    100{
    101	size_t i;
    102
    103	XUngrabKey(dpy, AnyKey, AnyModifier, root);
    104	for (i = 0; i < SchemeLast; i++)
    105		free(scheme[i]);
    106	for (i = 0; items && items[i].text; ++i)
    107		free(items[i].text);
    108	free(items);
    109	drw_free(drw);
    110	XSync(dpy, False);
    111	XCloseDisplay(dpy);
    112}
    113
    114static char *
    115cistrstr(const char *h, const char *n)
    116{
    117	size_t i;
    118
    119	if (!n[0])
    120		return (char *)h;
    121
    122	for (; *h; ++h) {
    123		for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
    124		            tolower((unsigned char)h[i]); ++i)
    125			;
    126		if (n[i] == '\0')
    127			return (char *)h;
    128	}
    129	return NULL;
    130}
    131
    132static int
    133drawitem(struct item *item, int x, int y, int w)
    134{
    135	if (item == sel)
    136		drw_setscheme(drw, scheme[SchemeSel]);
    137	else if (item->out)
    138		drw_setscheme(drw, scheme[SchemeOut]);
    139	else
    140		drw_setscheme(drw, scheme[SchemeNorm]);
    141
    142	return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
    143}
    144
    145static void
    146drawmenu(void)
    147{
    148	unsigned int curpos;
    149	struct item *item;
    150	int x = 0, y = 0, w;
    151
    152	drw_setscheme(drw, scheme[SchemeNorm]);
    153	drw_rect(drw, 0, 0, mw, mh, 1, 1);
    154
    155	if (prompt && *prompt) {
    156		drw_setscheme(drw, scheme[SchemeSel]);
    157		x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
    158	}
    159	/* draw input field */
    160	w = (lines > 0 || !matches) ? mw - x : inputw;
    161	drw_setscheme(drw, scheme[SchemeNorm]);
    162	drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
    163
    164	curpos = TEXTW(text) - TEXTW(&text[cursor]);
    165	if ((curpos += lrpad / 2 - 1) < w) {
    166		drw_setscheme(drw, scheme[SchemeNorm]);
    167		drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
    168	}
    169
    170	if (lines > 0) {
    171		/* draw vertical list */
    172		for (item = curr; item != next; item = item->right)
    173			drawitem(item, x, y += bh, mw - x);
    174	} else if (matches) {
    175		/* draw horizontal list */
    176		x += inputw;
    177		w = TEXTW("<");
    178		if (curr->left) {
    179			drw_setscheme(drw, scheme[SchemeNorm]);
    180			drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0);
    181		}
    182		x += w;
    183		for (item = curr; item != next; item = item->right)
    184			x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">")));
    185		if (next) {
    186			w = TEXTW(">");
    187			drw_setscheme(drw, scheme[SchemeNorm]);
    188			drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
    189		}
    190	}
    191	drw_map(drw, win, 0, 0, mw, mh);
    192}
    193
    194static void
    195grabfocus(void)
    196{
    197	struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000  };
    198	Window focuswin;
    199	int i, revertwin;
    200
    201	for (i = 0; i < 100; ++i) {
    202		XGetInputFocus(dpy, &focuswin, &revertwin);
    203		if (focuswin == win)
    204			return;
    205		XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
    206		nanosleep(&ts, NULL);
    207	}
    208	die("cannot grab focus");
    209}
    210
    211static void
    212grabkeyboard(void)
    213{
    214	struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000  };
    215	int i;
    216
    217	if (embed)
    218		return;
    219	/* try to grab keyboard, we may have to wait for another process to ungrab */
    220	for (i = 0; i < 1000; i++) {
    221		if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
    222		                  GrabModeAsync, CurrentTime) == GrabSuccess)
    223			return;
    224		nanosleep(&ts, NULL);
    225	}
    226	die("cannot grab keyboard");
    227}
    228
    229static void
    230match(void)
    231{
    232	static char **tokv = NULL;
    233	static int tokn = 0;
    234
    235	char buf[sizeof text], *s;
    236	int i, tokc = 0;
    237	size_t len, textsize;
    238	struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
    239
    240	strcpy(buf, text);
    241	/* separate input text into tokens to be matched individually */
    242	for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
    243		if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
    244			die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
    245	len = tokc ? strlen(tokv[0]) : 0;
    246
    247	matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
    248	textsize = strlen(text) + 1;
    249	for (item = items; item && item->text; item++) {
    250		for (i = 0; i < tokc; i++)
    251			if (!fstrstr(item->text, tokv[i]))
    252				break;
    253		if (i != tokc) /* not all tokens match */
    254			continue;
    255		/* exact matches go first, then prefixes, then substrings */
    256		if (!tokc || !fstrncmp(text, item->text, textsize))
    257			appenditem(item, &matches, &matchend);
    258		else if (!fstrncmp(tokv[0], item->text, len))
    259			appenditem(item, &lprefix, &prefixend);
    260		else
    261			appenditem(item, &lsubstr, &substrend);
    262	}
    263	if (lprefix) {
    264		if (matches) {
    265			matchend->right = lprefix;
    266			lprefix->left = matchend;
    267		} else
    268			matches = lprefix;
    269		matchend = prefixend;
    270	}
    271	if (lsubstr) {
    272		if (matches) {
    273			matchend->right = lsubstr;
    274			lsubstr->left = matchend;
    275		} else
    276			matches = lsubstr;
    277		matchend = substrend;
    278	}
    279	curr = sel = matches;
    280	calcoffsets();
    281}
    282
    283static void
    284insert(const char *str, ssize_t n)
    285{
    286	if (strlen(text) + n > sizeof text - 1)
    287		return;
    288	/* move existing text out of the way, insert new text, and update cursor */
    289	memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
    290	if (n > 0)
    291		memcpy(&text[cursor], str, n);
    292	cursor += n;
    293	match();
    294}
    295
    296static size_t
    297nextrune(int inc)
    298{
    299	ssize_t n;
    300
    301	/* return location of next utf8 rune in the given direction (+1 or -1) */
    302	for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
    303		;
    304	return n;
    305}
    306
    307static void
    308movewordedge(int dir)
    309{
    310	if (dir < 0) { /* move cursor to the start of the word*/
    311		while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
    312			cursor = nextrune(-1);
    313		while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
    314			cursor = nextrune(-1);
    315	} else { /* move cursor to the end of the word */
    316		while (text[cursor] && strchr(worddelimiters, text[cursor]))
    317			cursor = nextrune(+1);
    318		while (text[cursor] && !strchr(worddelimiters, text[cursor]))
    319			cursor = nextrune(+1);
    320	}
    321}
    322
    323static void
    324keypress(XKeyEvent *ev)
    325{
    326	char buf[64];
    327	int len;
    328	KeySym ksym = NoSymbol;
    329	Status status;
    330
    331	len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
    332	switch (status) {
    333	default: /* XLookupNone, XBufferOverflow */
    334		return;
    335	case XLookupChars: /* composed string from input method */
    336		goto insert;
    337	case XLookupKeySym:
    338	case XLookupBoth: /* a KeySym and a string are returned: use keysym */
    339		break;
    340	}
    341
    342	if (ev->state & ControlMask) {
    343		switch(ksym) {
    344		case XK_a: ksym = XK_Home;      break;
    345		case XK_b: ksym = XK_Left;      break;
    346		case XK_c: ksym = XK_Escape;    break;
    347		case XK_d: ksym = XK_Delete;    break;
    348		case XK_e: ksym = XK_End;       break;
    349		case XK_f: ksym = XK_Right;     break;
    350		case XK_g: ksym = XK_Escape;    break;
    351		case XK_h: ksym = XK_BackSpace; break;
    352		case XK_i: ksym = XK_Tab;       break;
    353		case XK_j: /* fallthrough */
    354		case XK_J: /* fallthrough */
    355		case XK_m: /* fallthrough */
    356		case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break;
    357		case XK_n: ksym = XK_Down;      break;
    358		case XK_p: ksym = XK_Up;        break;
    359
    360		case XK_k: /* delete right */
    361			text[cursor] = '\0';
    362			match();
    363			break;
    364		case XK_u: /* delete left */
    365			insert(NULL, 0 - cursor);
    366			break;
    367		case XK_w: /* delete word */
    368			while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
    369				insert(NULL, nextrune(-1) - cursor);
    370			while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
    371				insert(NULL, nextrune(-1) - cursor);
    372			break;
    373		case XK_y: /* paste selection */
    374		case XK_Y:
    375			XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
    376			                  utf8, utf8, win, CurrentTime);
    377			return;
    378		case XK_Left:
    379		case XK_KP_Left:
    380			movewordedge(-1);
    381			goto draw;
    382		case XK_Right:
    383		case XK_KP_Right:
    384			movewordedge(+1);
    385			goto draw;
    386		case XK_Return:
    387		case XK_KP_Enter:
    388			break;
    389		case XK_bracketleft:
    390			cleanup();
    391			exit(1);
    392		default:
    393			return;
    394		}
    395	} else if (ev->state & Mod1Mask) {
    396		switch(ksym) {
    397		case XK_b:
    398			movewordedge(-1);
    399			goto draw;
    400		case XK_f:
    401			movewordedge(+1);
    402			goto draw;
    403		case XK_g: ksym = XK_Home;  break;
    404		case XK_G: ksym = XK_End;   break;
    405		case XK_h: ksym = XK_Up;    break;
    406		case XK_j: ksym = XK_Next;  break;
    407		case XK_k: ksym = XK_Prior; break;
    408		case XK_l: ksym = XK_Down;  break;
    409		default:
    410			return;
    411		}
    412	}
    413
    414	switch(ksym) {
    415	default:
    416insert:
    417		if (!iscntrl((unsigned char)*buf))
    418			insert(buf, len);
    419		break;
    420	case XK_Delete:
    421	case XK_KP_Delete:
    422		if (text[cursor] == '\0')
    423			return;
    424		cursor = nextrune(+1);
    425		/* fallthrough */
    426	case XK_BackSpace:
    427		if (cursor == 0)
    428			return;
    429		insert(NULL, nextrune(-1) - cursor);
    430		break;
    431	case XK_End:
    432	case XK_KP_End:
    433		if (text[cursor] != '\0') {
    434			cursor = strlen(text);
    435			break;
    436		}
    437		if (next) {
    438			/* jump to end of list and position items in reverse */
    439			curr = matchend;
    440			calcoffsets();
    441			curr = prev;
    442			calcoffsets();
    443			while (next && (curr = curr->right))
    444				calcoffsets();
    445		}
    446		sel = matchend;
    447		break;
    448	case XK_Escape:
    449		cleanup();
    450		exit(1);
    451	case XK_Home:
    452	case XK_KP_Home:
    453		if (sel == matches) {
    454			cursor = 0;
    455			break;
    456		}
    457		sel = curr = matches;
    458		calcoffsets();
    459		break;
    460	case XK_Left:
    461	case XK_KP_Left:
    462		if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
    463			cursor = nextrune(-1);
    464			break;
    465		}
    466		if (lines > 0)
    467			return;
    468		/* fallthrough */
    469	case XK_Up:
    470	case XK_KP_Up:
    471		if (sel && sel->left && (sel = sel->left)->right == curr) {
    472			curr = prev;
    473			calcoffsets();
    474		}
    475		break;
    476	case XK_Next:
    477	case XK_KP_Next:
    478		if (!next)
    479			return;
    480		sel = curr = next;
    481		calcoffsets();
    482		break;
    483	case XK_Prior:
    484	case XK_KP_Prior:
    485		if (!prev)
    486			return;
    487		sel = curr = prev;
    488		calcoffsets();
    489		break;
    490	case XK_Return:
    491	case XK_KP_Enter:
    492		puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
    493		if (!(ev->state & ControlMask)) {
    494			cleanup();
    495			exit(0);
    496		}
    497		if (sel)
    498			sel->out = 1;
    499		break;
    500	case XK_Right:
    501	case XK_KP_Right:
    502		if (text[cursor] != '\0') {
    503			cursor = nextrune(+1);
    504			break;
    505		}
    506		if (lines > 0)
    507			return;
    508		/* fallthrough */
    509	case XK_Down:
    510	case XK_KP_Down:
    511		if (sel && sel->right && (sel = sel->right) == next) {
    512			curr = next;
    513			calcoffsets();
    514		}
    515		break;
    516	case XK_Tab:
    517		if (!sel)
    518			return;
    519		cursor = strnlen(sel->text, sizeof text - 1);
    520		memcpy(text, sel->text, cursor);
    521		text[cursor] = '\0';
    522		match();
    523		break;
    524	}
    525
    526draw:
    527	drawmenu();
    528}
    529
    530static void
    531paste(void)
    532{
    533	char *p, *q;
    534	int di;
    535	unsigned long dl;
    536	Atom da;
    537
    538	/* we have been given the current selection, now insert it into input */
    539	if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
    540	                   utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
    541	    == Success && p) {
    542		insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
    543		XFree(p);
    544	}
    545	drawmenu();
    546}
    547
    548static void
    549readstdin(void)
    550{
    551	char *line = NULL;
    552	size_t i, itemsiz = 0, linesiz = 0;
    553	ssize_t len;
    554
    555	/* read each line from stdin and add it to the item list */
    556	for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) {
    557		if (i + 1 >= itemsiz) {
    558			itemsiz += 256;
    559			if (!(items = realloc(items, itemsiz * sizeof(*items))))
    560				die("cannot realloc %zu bytes:", itemsiz * sizeof(*items));
    561		}
    562		if (line[len - 1] == '\n')
    563			line[len - 1] = '\0';
    564		if (!(items[i].text = strdup(line)))
    565			die("strdup:");
    566
    567		items[i].out = 0;
    568	}
    569	free(line);
    570	if (items)
    571		items[i].text = NULL;
    572	lines = MIN(lines, i);
    573}
    574
    575static void
    576run(void)
    577{
    578	XEvent ev;
    579
    580	while (!XNextEvent(dpy, &ev)) {
    581		if (XFilterEvent(&ev, win))
    582			continue;
    583		switch(ev.type) {
    584		case DestroyNotify:
    585			if (ev.xdestroywindow.window != win)
    586				break;
    587			cleanup();
    588			exit(1);
    589		case Expose:
    590			if (ev.xexpose.count == 0)
    591				drw_map(drw, win, 0, 0, mw, mh);
    592			break;
    593		case FocusIn:
    594			/* regrab focus from parent window */
    595			if (ev.xfocus.window != win)
    596				grabfocus();
    597			break;
    598		case KeyPress:
    599			keypress(&ev.xkey);
    600			break;
    601		case SelectionNotify:
    602			if (ev.xselection.property == utf8)
    603				paste();
    604			break;
    605		case VisibilityNotify:
    606			if (ev.xvisibility.state != VisibilityUnobscured)
    607				XRaiseWindow(dpy, win);
    608			break;
    609		}
    610	}
    611}
    612
    613static void
    614setup(void)
    615{
    616	int x, y, i, j;
    617	unsigned int du;
    618	XSetWindowAttributes swa;
    619	XIM xim;
    620	Window w, dw, *dws;
    621	XWindowAttributes wa;
    622	XClassHint ch = {"dmenu", "dmenu"};
    623#ifdef XINERAMA
    624	XineramaScreenInfo *info;
    625	Window pw;
    626	int a, di, n, area = 0;
    627#endif
    628	/* init appearance */
    629	for (j = 0; j < SchemeLast; j++)
    630		scheme[j] = drw_scm_create(drw, colors[j], 2);
    631
    632	clip = XInternAtom(dpy, "CLIPBOARD",   False);
    633	utf8 = XInternAtom(dpy, "UTF8_STRING", False);
    634
    635	/* calculate menu geometry */
    636	bh = drw->fonts->h + 2;
    637	lines = MAX(lines, 0);
    638	mh = (lines + 1) * bh;
    639#ifdef XINERAMA
    640	i = 0;
    641	if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
    642		XGetInputFocus(dpy, &w, &di);
    643		if (mon >= 0 && mon < n)
    644			i = mon;
    645		else if (w != root && w != PointerRoot && w != None) {
    646			/* find top-level window containing current input focus */
    647			do {
    648				if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws)
    649					XFree(dws);
    650			} while (w != root && w != pw);
    651			/* find xinerama screen with which the window intersects most */
    652			if (XGetWindowAttributes(dpy, pw, &wa))
    653				for (j = 0; j < n; j++)
    654					if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
    655						area = a;
    656						i = j;
    657					}
    658		}
    659		/* no focused window is on screen, so use pointer location instead */
    660		if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
    661			for (i = 0; i < n; i++)
    662				if (INTERSECT(x, y, 1, 1, info[i]) != 0)
    663					break;
    664
    665		x = info[i].x_org;
    666		y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
    667		mw = info[i].width;
    668		XFree(info);
    669	} else
    670#endif
    671	{
    672		if (!XGetWindowAttributes(dpy, parentwin, &wa))
    673			die("could not get embedding window attributes: 0x%lx",
    674			    parentwin);
    675		x = 0;
    676		y = topbar ? 0 : wa.height - mh;
    677		mw = wa.width;
    678	}
    679	promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
    680	inputw = mw / 3; /* input width: ~33% of monitor width */
    681	match();
    682
    683	/* create menu window */
    684	swa.override_redirect = True;
    685	swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
    686	swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
    687	win = XCreateWindow(dpy, root, x, y, mw, mh, 0,
    688	                    CopyFromParent, CopyFromParent, CopyFromParent,
    689	                    CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
    690	XSetClassHint(dpy, win, &ch);
    691
    692
    693	/* input methods */
    694	if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
    695		die("XOpenIM failed: could not open input device");
    696
    697	xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
    698	                XNClientWindow, win, XNFocusWindow, win, NULL);
    699
    700	XMapRaised(dpy, win);
    701	if (embed) {
    702		XReparentWindow(dpy, win, parentwin, x, y);
    703		XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
    704		if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
    705			for (i = 0; i < du && dws[i] != win; ++i)
    706				XSelectInput(dpy, dws[i], FocusChangeMask);
    707			XFree(dws);
    708		}
    709		grabfocus();
    710	}
    711	drw_resize(drw, mw, mh);
    712	drawmenu();
    713}
    714
    715static void
    716usage(void)
    717{
    718	die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
    719	    "             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
    720}
    721
    722int
    723main(int argc, char *argv[])
    724{
    725	XWindowAttributes wa;
    726	int i, fast = 0;
    727
    728	for (i = 1; i < argc; i++)
    729		/* these options take no arguments */
    730		if (!strcmp(argv[i], "-v")) {      /* prints version information */
    731			puts("dmenu-"VERSION);
    732			exit(0);
    733		} else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
    734			topbar = 0;
    735		else if (!strcmp(argv[i], "-f"))   /* grabs keyboard before reading stdin */
    736			fast = 1;
    737		else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
    738			fstrncmp = strncasecmp;
    739			fstrstr = cistrstr;
    740		} else if (i + 1 == argc)
    741			usage();
    742		/* these options take one argument */
    743		else if (!strcmp(argv[i], "-l"))   /* number of lines in vertical list */
    744			lines = atoi(argv[++i]);
    745		else if (!strcmp(argv[i], "-m"))
    746			mon = atoi(argv[++i]);
    747		else if (!strcmp(argv[i], "-p"))   /* adds prompt to left of input field */
    748			prompt = argv[++i];
    749		else if (!strcmp(argv[i], "-fn"))  /* font or font set */
    750			fonts[0] = argv[++i];
    751		else if (!strcmp(argv[i], "-nb"))  /* normal background color */
    752			colors[SchemeNorm][ColBg] = argv[++i];
    753		else if (!strcmp(argv[i], "-nf"))  /* normal foreground color */
    754			colors[SchemeNorm][ColFg] = argv[++i];
    755		else if (!strcmp(argv[i], "-sb"))  /* selected background color */
    756			colors[SchemeSel][ColBg] = argv[++i];
    757		else if (!strcmp(argv[i], "-sf"))  /* selected foreground color */
    758			colors[SchemeSel][ColFg] = argv[++i];
    759		else if (!strcmp(argv[i], "-w"))   /* embedding window id */
    760			embed = argv[++i];
    761		else
    762			usage();
    763
    764	if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
    765		fputs("warning: no locale support\n", stderr);
    766	if (!(dpy = XOpenDisplay(NULL)))
    767		die("cannot open display");
    768	screen = DefaultScreen(dpy);
    769	root = RootWindow(dpy, screen);
    770	if (!embed || !(parentwin = strtol(embed, NULL, 0)))
    771		parentwin = root;
    772	if (!XGetWindowAttributes(dpy, parentwin, &wa))
    773		die("could not get embedding window attributes: 0x%lx",
    774		    parentwin);
    775	drw = drw_create(dpy, screen, root, wa.width, wa.height);
    776	if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
    777		die("no fonts could be loaded.");
    778	lrpad = drw->fonts->h;
    779
    780#ifdef __OpenBSD__
    781	if (pledge("stdio rpath", NULL) == -1)
    782		die("pledge");
    783#endif
    784
    785	if (fast && !isatty(0)) {
    786		grabkeyboard();
    787		readstdin();
    788	} else {
    789		readstdin();
    790		grabkeyboard();
    791	}
    792	setup();
    793	run();
    794
    795	return 1; /* unreachable */
    796}