tmenu

Unnamed repository; edit this file 'description' to name the repository.
git clone https://git.inz.fi/tmenu
Log | Files | Refs | README | LICENSE

dmenu.c (17032B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <ctype.h>
      3 #include <errno.h>
      4 #include <fcntl.h>
      5 #include <locale.h>
      6 #include <poll.h>
      7 #include <signal.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <strings.h>
     12 #include <sys/ioctl.h>
     13 #include <term.h>
     14 #include <termios.h>
     15 #include <unistd.h>
     16 
     17 #include "drw.h"
     18 #include "util.h"
     19 
     20 #ifdef lines
     21 #undef lines
     22 #endif
     23 
     24 /* macros */
     25 #define ITEMGAP               1
     26 #define TEXTW(X)              (drw_fontset_getwidth(drw, (X)) + lrpad)
     27 
     28 /* enums */
     29 enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
     30 
     31 struct item {
     32 	char *text;
     33 	struct item *left, *right;
     34 	int out;
     35 };
     36 
     37 static char text[BUFSIZ] = "";
     38 static int bh, mw, mh;
     39 static int inputw = 0, promptw;
     40 static int lrpad; /* sum of left and right padding */
     41 static int termh, yoff;
     42 static int passwd;
     43 static size_t cursor;
     44 static struct item *items = NULL;
     45 static struct item *matches, *matchend;
     46 static struct item *prev, *curr, *next, *sel;
     47 static volatile sig_atomic_t resized;
     48 static int ttyfd = -1;
     49 static FILE *ttyout;
     50 static struct termios termios_orig;
     51 static int termios_saved;
     52 
     53 static Drw *drw;
     54 static Clr *scheme[SchemeLast];
     55 
     56 #include "config.h"
     57 
     58 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
     59 static char *(*fstrstr)(const char *, const char *) = strstr;
     60 
     61 enum {
     62 	XK_Left = 0x101,
     63 	XK_Right,
     64 	XK_Up,
     65 	XK_Down,
     66 	XK_Home,
     67 	XK_End,
     68 	XK_Next,
     69 	XK_Prior,
     70 	XK_Delete,
     71 	XK_BackSpace,
     72 	XK_Return,
     73 	XK_KP_Left,
     74 	XK_KP_Right,
     75 	XK_KP_Up,
     76 	XK_KP_Down,
     77 	XK_KP_Home,
     78 	XK_KP_End,
     79 	XK_KP_Next,
     80 	XK_KP_Prior,
     81 	XK_KP_Delete,
     82 	XK_KP_Enter,
     83 	XK_Escape = 27,
     84 	XK_Tab = '\t'
     85 };
     86 
     87 static unsigned int
     88 itemw_clamp(const char *str, unsigned int n)
     89 {
     90 	unsigned int w = drw_fontset_getwidth_clamp(drw, str, n > ITEMGAP ? n - ITEMGAP : 0) + lrpad + ITEMGAP;
     91 	return MIN(w, n);
     92 }
     93 
     94 static const char *
     95 inputtext(void)
     96 {
     97 	return passwd ? "" : text;
     98 }
     99 
    100 static void
    101 appenditem(struct item *item, struct item **list, struct item **last)
    102 {
    103 	if (*last)
    104 		(*last)->right = item;
    105 	else
    106 		*list = item;
    107 
    108 	item->left = *last;
    109 	item->right = NULL;
    110 	*last = item;
    111 }
    112 
    113 static void
    114 calcoffsets(void)
    115 {
    116 	int i, n;
    117 
    118 	if (lines > 0)
    119 		n = lines * bh;
    120 	else
    121 		n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
    122 	/* calculate which items will begin the next page and previous page */
    123 	for (i = 0, next = curr; next; next = next->right)
    124 		if ((i += (lines > 0) ? bh : itemw_clamp(next->text, n)) > n)
    125 			break;
    126 	for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
    127 		if ((i += (lines > 0) ? bh : itemw_clamp(prev->left->text, n)) > n)
    128 			break;
    129 }
    130 
    131 static void
    132 cleanup(void)
    133 {
    134 	size_t i;
    135 
    136 	for (i = 0; i < SchemeLast; i++)
    137 		drw_scm_free(drw, scheme[i], 2);
    138 	for (i = 0; items && items[i].text; ++i)
    139 		free(items[i].text);
    140 	free(items);
    141 	drw_cursor_restore();
    142 	drw_term_reset();
    143 	drw_free(drw);
    144 	if (termios_saved)
    145 		tcsetattr(ttyfd, TCSAFLUSH, &termios_orig);
    146 	if (ttyout)
    147 		fclose(ttyout);
    148 	if (ttyfd >= 0)
    149 		close(ttyfd);
    150 }
    151 
    152 static char *
    153 cistrstr(const char *h, const char *n)
    154 {
    155 	size_t i;
    156 
    157 	if (!n[0])
    158 		return (char *)h;
    159 
    160 	for (; *h; ++h) {
    161 		for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
    162 		            tolower((unsigned char)h[i]); ++i)
    163 			;
    164 		if (n[i] == '\0')
    165 			return (char *)h;
    166 	}
    167 	return NULL;
    168 }
    169 
    170 static int
    171 drawitem(struct item *item, int x, int y, int w)
    172 {
    173 	int lpad = lrpad / 2;
    174 	int draww = w;
    175 
    176 	if (item == sel)
    177 		drw_setscheme(drw, scheme[SchemeSel]);
    178 	else if (item->out)
    179 		drw_setscheme(drw, scheme[SchemeOut]);
    180 	else
    181 		drw_setscheme(drw, scheme[SchemeNorm]);
    182 
    183 	if (item == sel) {
    184 		/* Keep selection background tight: one cell before and after text. */
    185 		lpad = 1;
    186 		draww = MIN(w, (int)drw_fontset_getwidth(drw, item->text) + 2);
    187 	}
    188 	return drw_text(drw, x, y, draww, bh, lpad, item->text, 0) + ITEMGAP;
    189 }
    190 
    191 static void
    192 drawmenu(void)
    193 {
    194 	unsigned int curpos;
    195 	const char *displaytext;
    196 	struct item *item;
    197 	int cursorx, x = 0, y = yoff, w;
    198 
    199 	drw_setscheme(drw, scheme[SchemeNorm]);
    200 	drw_rect(drw, 0, yoff, mw, mh, 1, 0);
    201 
    202 	if (prompt && *prompt) {
    203 		drw_setscheme(drw, scheme[SchemeSel]);
    204 		x = drw_text(drw, x, yoff, promptw, bh, lrpad / 2, prompt, 0);
    205 	}
    206 	/* draw input field */
    207 	w = (lines > 0 || !matches) ? mw - x : inputw;
    208 	drw_setscheme(drw, scheme[SchemeNorm]);
    209 	displaytext = inputtext();
    210 	drw_text(drw, x, yoff, w, bh, lrpad / 2, displaytext, 0);
    211 
    212 	curpos = TEXTW(displaytext) - TEXTW(&displaytext[passwd ? 0 : cursor]);
    213 	cursorx = x;
    214 	curpos += lrpad / 2;
    215 
    216 	if (lines > 0) {
    217 		/* draw vertical list */
    218 		for (item = curr; item != next; item = item->right)
    219 			drawitem(item, x, y += bh, mw - x);
    220 	} else if (matches) {
    221 		/* draw horizontal list */
    222 		x += inputw;
    223 		w = TEXTW("<");
    224 		if (curr->left) {
    225 			drw_setscheme(drw, scheme[SchemeNorm]);
    226 			drw_text(drw, x, yoff, w, bh, lrpad / 2, "<", 0);
    227 		}
    228 		x += w;
    229 		for (item = curr; item != next; item = item->right)
    230 			x = drawitem(item, x, yoff, itemw_clamp(item->text, mw - x - TEXTW(">")));
    231 		if (next) {
    232 			w = TEXTW(">");
    233 			drw_setscheme(drw, scheme[SchemeNorm]);
    234 			drw_text(drw, mw - w, yoff, w, bh, lrpad / 2, ">", 0);
    235 		}
    236 	}
    237 	drw_move(drw, MIN(cursorx + (int)curpos, mw - 1), yoff);
    238 	drw_map(drw, 0, 0, 0, mw, mh);
    239 }
    240 
    241 static void
    242 match(void)
    243 {
    244 	static char **tokv = NULL;
    245 	static int tokn = 0;
    246 	char buf[sizeof text], *s;
    247 	int i, tokc = 0;
    248 	size_t len, textsize;
    249 	struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
    250 
    251 	strcpy(buf, text);
    252 	/* separate input text into tokens to be matched individually */
    253 	for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
    254 		if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
    255 			die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
    256 	len = tokc ? strlen(tokv[0]) : 0;
    257 
    258 	matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
    259 	textsize = strlen(text) + 1;
    260 	for (item = items; item && item->text; item++) {
    261 		for (i = 0; i < tokc; i++)
    262 			if (!fstrstr(item->text, tokv[i]))
    263 				break;
    264 		if (i != tokc) /* not all tokens match */
    265 			continue;
    266 		/* exact matches go first, then prefixes, then substrings */
    267 		if (!tokc || !fstrncmp(text, item->text, textsize))
    268 			appenditem(item, &matches, &matchend);
    269 		else if (!fstrncmp(tokv[0], item->text, len))
    270 			appenditem(item, &lprefix, &prefixend);
    271 		else
    272 			appenditem(item, &lsubstr, &substrend);
    273 	}
    274 	if (lprefix) {
    275 		if (matches) {
    276 			matchend->right = lprefix;
    277 			lprefix->left = matchend;
    278 		} else
    279 			matches = lprefix;
    280 		matchend = prefixend;
    281 	}
    282 	if (lsubstr) {
    283 		if (matches) {
    284 			matchend->right = lsubstr;
    285 			lsubstr->left = matchend;
    286 		} else
    287 			matches = lsubstr;
    288 		matchend = substrend;
    289 	}
    290 	curr = sel = matches;
    291 	calcoffsets();
    292 }
    293 
    294 static void
    295 insert(const char *str, ssize_t n)
    296 {
    297 	if (strlen(text) + n > sizeof text - 1)
    298 		return;
    299 	/* move existing text out of the way, insert new text, and update cursor */
    300 	memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
    301 	if (n > 0)
    302 		memcpy(&text[cursor], str, n);
    303 	cursor += n;
    304 	match();
    305 }
    306 
    307 static size_t
    308 nextrune(int inc)
    309 {
    310 	ssize_t n;
    311 
    312 	/* return location of next utf8 rune in the given direction (+1 or -1) */
    313 	for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
    314 		;
    315 	return n;
    316 }
    317 
    318 static void
    319 movewordedge(int dir)
    320 {
    321 	if (dir < 0) { /* move cursor to the start of the word*/
    322 		while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
    323 			cursor = nextrune(-1);
    324 		while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
    325 			cursor = nextrune(-1);
    326 	} else { /* move cursor to the end of the word */
    327 		while (text[cursor] && strchr(worddelimiters, text[cursor]))
    328 			cursor = nextrune(+1);
    329 		while (text[cursor] && !strchr(worddelimiters, text[cursor]))
    330 			cursor = nextrune(+1);
    331 	}
    332 }
    333 
    334 static void
    335 resize(void)
    336 {
    337 	struct winsize ws;
    338 
    339 	resized = 0;
    340 	if (ioctl(ttyfd, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0 || ws.ws_row == 0) {
    341 		mw = 80;
    342 		termh = 24;
    343 	} else {
    344 		mw = ws.ws_col;
    345 		termh = ws.ws_row;
    346 	}
    347 	bh = drw->fonts->h;
    348 	mh = lines + 1;
    349 	yoff = topbar ? 0 : MAX(termh - mh, 0);
    350 	promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
    351 	inputw = mw / 3;
    352 	drw_resize(drw, mw, mh);
    353 	calcoffsets();
    354 	drawmenu();
    355 }
    356 
    357 static void
    358 sigwinch(int unused)
    359 {
    360 	(void)unused;
    361 	resized = 1;
    362 }
    363 
    364 static int
    365 readkey(void)
    366 {
    367 	unsigned char ch = 0, seq[3];
    368 	struct pollfd pfd = { .fd = ttyfd, .events = POLLIN };
    369 	int n;
    370 
    371 	n = read(ttyfd, &ch, 1);
    372 	if (n <= 0) {
    373 		if (errno == EINTR && resized)
    374 			return 0;
    375 		return 0;
    376 	}
    377 	if (ch == '\r')
    378 		return '\n';
    379 	if (ch == 127 || ch == 8)
    380 		return XK_BackSpace;
    381 	if (ch != 27)
    382 		return ch;
    383 
    384 	if (poll(&pfd, 1, 25) <= 0)
    385 		return 27;
    386 	if (read(ttyfd, &seq[0], 1) != 1)
    387 		return 27;
    388 
    389 	if (seq[0] == '[') {
    390 		if (read(ttyfd, &seq[1], 1) != 1)
    391 			return 27;
    392 		if (seq[1] >= '0' && seq[1] <= '9') {
    393 			if (read(ttyfd, &seq[2], 1) != 1)
    394 				return 27;
    395 			if (seq[2] != '~')
    396 				return 27;
    397 			switch (seq[1]) {
    398 			case '1': return XK_Home;
    399 			case '3': return XK_Delete;
    400 			case '4': return XK_End;
    401 			case '5': return XK_Prior;
    402 			case '6': return XK_Next;
    403 			case '7': return XK_Home;
    404 			case '8': return XK_End;
    405 			default: return 27;
    406 			}
    407 		}
    408 		switch (seq[1]) {
    409 		case 'A': return XK_Up;
    410 		case 'B': return XK_Down;
    411 		case 'C': return XK_Right;
    412 		case 'D': return XK_Left;
    413 		case 'H': return XK_Home;
    414 		case 'F': return XK_End;
    415 		default: return 27;
    416 		}
    417 	}
    418 	if (seq[0] == 'O') {
    419 		if (read(ttyfd, &seq[1], 1) != 1)
    420 			return 27;
    421 		switch (seq[1]) {
    422 		case 'H': return XK_Home;
    423 		case 'F': return XK_End;
    424 		default: return 27;
    425 		}
    426 	}
    427 	return 0x200 | seq[0];
    428 }
    429 
    430 static void
    431 keypress(int ch)
    432 {
    433 	char buf[7];
    434 	int len = 0, alt = ch & 0x200;
    435 
    436 	ch &= ~0x200;
    437 	switch (alt ? ch : 0) {
    438 	case 'b':
    439 		movewordedge(-1);
    440 		goto draw;
    441 	case 'f':
    442 		movewordedge(+1);
    443 		goto draw;
    444 	case 'g':
    445 		ch = XK_Home;
    446 		break;
    447 	case 'G':
    448 		ch = XK_End;
    449 		break;
    450 	case 'h':
    451 		ch = XK_Up;
    452 		break;
    453 	case 'j':
    454 		ch = XK_Next;
    455 		break;
    456 	case 'k':
    457 		ch = XK_Prior;
    458 		break;
    459 	case 'l':
    460 		ch = XK_Down;
    461 		break;
    462 	default:
    463 		break;
    464 	}
    465 
    466 	if (!alt && ch >= 1 && ch <= 26) {
    467 		switch (ch) {
    468 		case 1: ch = XK_Home;       break;
    469 		case 2: ch = XK_Left;       break;
    470 		case 3:
    471 		case 7:
    472 			cleanup();
    473 			exit(1);
    474 		case 4: ch = XK_Delete;     break;
    475 		case 5: ch = XK_End;        break;
    476 		case 6: ch = XK_Right;      break;
    477 		case 8: ch = XK_BackSpace;  break;
    478 		case 9: ch = '\t';          break;
    479 		case 10:
    480 		case 13: ch = '\n';         break;
    481 		case 11:
    482 			text[cursor] = '\0';
    483 			match();
    484 			goto draw;
    485 		case 14: ch = XK_Down;      break;
    486 		case 16: ch = XK_Up;        break;
    487 		case 21:
    488 			insert(NULL, 0 - cursor);
    489 			goto draw;
    490 		case 23:
    491 			while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
    492 				insert(NULL, nextrune(-1) - cursor);
    493 			while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
    494 				insert(NULL, nextrune(-1) - cursor);
    495 			goto draw;
    496 		case 25:
    497 			goto draw;
    498 		default:
    499 			return;
    500 		}
    501 	}
    502 
    503 	switch (ch) {
    504 	default:
    505 		if (!iscntrl((unsigned char)ch)) {
    506 			buf[0] = ch;
    507 			len = 1;
    508 			insert(buf, len);
    509 		}
    510 		break;
    511 	case XK_Delete:
    512 	case XK_KP_Delete:
    513 		if (text[cursor] == '\0')
    514 			return;
    515 		cursor = nextrune(+1);
    516 		/* fallthrough */
    517 	case XK_BackSpace:
    518 	case 127:
    519 		if (cursor == 0)
    520 			return;
    521 		insert(NULL, nextrune(-1) - cursor);
    522 		break;
    523 	case XK_End:
    524 	case XK_KP_End:
    525 		if (text[cursor] != '\0') {
    526 			cursor = strlen(text);
    527 			break;
    528 		}
    529 		if (next) {
    530 			/* jump to end of list and position items in reverse */
    531 			curr = matchend;
    532 			calcoffsets();
    533 			curr = prev;
    534 			calcoffsets();
    535 			while (next && (curr = curr->right))
    536 				calcoffsets();
    537 		}
    538 		sel = matchend;
    539 		break;
    540 	case XK_Escape:
    541 		cleanup();
    542 		exit(1);
    543 	case XK_Home:
    544 	case XK_KP_Home:
    545 		if (sel == matches) {
    546 			cursor = 0;
    547 			break;
    548 		}
    549 		sel = curr = matches;
    550 		calcoffsets();
    551 		break;
    552 	case XK_Left:
    553 	case XK_KP_Left:
    554 		if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
    555 			cursor = nextrune(-1);
    556 			break;
    557 		}
    558 		if (lines > 0)
    559 			return;
    560 		/* fallthrough */
    561 	case XK_Up:
    562 	case XK_KP_Up:
    563 		if (sel && sel->left && (sel = sel->left)->right == curr) {
    564 			curr = prev;
    565 			calcoffsets();
    566 		}
    567 		break;
    568 	case XK_Next:
    569 	case XK_KP_Next:
    570 		if (!next)
    571 			return;
    572 		sel = curr = next;
    573 		calcoffsets();
    574 		break;
    575 	case XK_Prior:
    576 	case XK_KP_Prior:
    577 		if (!prev)
    578 			return;
    579 		sel = curr = prev;
    580 		calcoffsets();
    581 		break;
    582 	case '\n':
    583 	case XK_Return:
    584 	case XK_KP_Enter:
    585 		if (sel && sel->text)
    586 			buf[0] = '\0';
    587 		if (sel && sel->text) {
    588 			char *s;
    589 
    590 			if (!(s = strdup(sel->text)))
    591 				die("strdup:");
    592 			cleanup();
    593 			puts(s);
    594 			free(s);
    595 			exit(0);
    596 		}
    597 		cleanup();
    598 		puts(text);
    599 		exit(0);
    600 	case XK_Right:
    601 	case XK_KP_Right:
    602 		if (text[cursor] != '\0') {
    603 			cursor = nextrune(+1);
    604 			break;
    605 		}
    606 		if (lines > 0)
    607 			return;
    608 		/* fallthrough */
    609 	case XK_Down:
    610 	case XK_KP_Down:
    611 		if (sel && sel->right && (sel = sel->right) == next) {
    612 			curr = next;
    613 			calcoffsets();
    614 		}
    615 		break;
    616 	case XK_Tab:
    617 		if (!sel)
    618 			return;
    619 		cursor = strnlen(sel->text, sizeof text - 1);
    620 		memcpy(text, sel->text, cursor);
    621 		text[cursor] = '\0';
    622 		match();
    623 		break;
    624 	}
    625 
    626 draw:
    627 	drawmenu();
    628 }
    629 
    630 static void
    631 readstdin(void)
    632 {
    633 	char *line = NULL;
    634 	size_t i, itemsiz = 0, linesiz = 0;
    635 	ssize_t len;
    636 
    637 	/* read each line from stdin and add it to the item list */
    638 	for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) {
    639 		if (i + 1 >= itemsiz) {
    640 			itemsiz += 256;
    641 			if (!(items = realloc(items, itemsiz * sizeof(*items))))
    642 				die("cannot realloc %zu bytes:", itemsiz * sizeof(*items));
    643 		}
    644 		if (line[len - 1] == '\n')
    645 			line[len - 1] = '\0';
    646 		if (!(items[i].text = strdup(line)))
    647 			die("strdup:");
    648 		items[i].out = 0;
    649 	}
    650 	free(line);
    651 	if (items)
    652 		items[i].text = NULL;
    653 	lines = MIN(lines, i);
    654 }
    655 
    656 static void
    657 run(void)
    658 {
    659 	drawmenu();
    660 	for (;;) {
    661 		if (resized)
    662 			resize();
    663 		keypress(readkey());
    664 	}
    665 }
    666 
    667 static void
    668 setup(void)
    669 {
    670 	int i, termerr;
    671 	struct sigaction sa;
    672 	struct termios raw;
    673 
    674 	ttyfd = open("/dev/tty", O_RDWR | O_CLOEXEC);
    675 	if (ttyfd < 0)
    676 		ttyfd = dup(STDERR_FILENO);
    677 	if (ttyfd < 0)
    678 		die("open /dev/tty:");
    679 	if (!(ttyout = fdopen(dup(ttyfd), "w")))
    680 		die("fdopen:");
    681 	if (setupterm(NULL, ttyfd, &termerr) == -1 || termerr <= 0)
    682 		die("setupterm failed");
    683 	drw_term_init(ttyout);
    684 
    685 	if (tcgetattr(ttyfd, &termios_orig) == -1)
    686 		die("tcgetattr:");
    687 	termios_saved = 1;
    688 	raw = termios_orig;
    689 	raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    690 	raw.c_oflag &= ~(OPOST);
    691 	raw.c_cflag |= (CS8);
    692 	raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    693 	raw.c_cc[VMIN] = 1;
    694 	raw.c_cc[VTIME] = 0;
    695 	if (tcsetattr(ttyfd, TCSAFLUSH, &raw) == -1)
    696 		die("tcsetattr:");
    697 
    698 	/* init appearance */
    699 	for (i = 0; i < SchemeLast; i++)
    700 		scheme[i] = drw_scm_create(drw, colors[i], 2);
    701 
    702 	memset(&sa, 0, sizeof sa);
    703 	sa.sa_handler = sigwinch;
    704 	sigemptyset(&sa.sa_mask);
    705 	sigaction(SIGWINCH, &sa, NULL);
    706 	match();
    707 	drw_cursor_save();
    708 	resize();
    709 }
    710 
    711 static void
    712 usage(void)
    713 {
    714 	die("usage: tmenu [-biv] [-l lines] [-p prompt]\n"
    715 	    "             [-nb color] [-nf color] [-sb color] [-sf color]");
    716 }
    717 
    718 int
    719 main(int argc, char *argv[])
    720 {
    721 	int i;
    722 
    723 	for (i = 1; i < argc; i++)
    724 		/* these options take no arguments */
    725 		if (!strcmp(argv[i], "-v")) {      /* prints version information */
    726 			puts("tmenu-"VERSION);
    727 			exit(0);
    728 		} else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
    729 			topbar = 0;
    730 		else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
    731 			fstrncmp = strncasecmp;
    732 			fstrstr = cistrstr;
    733 		} else if (i + 1 == argc)
    734 			usage();
    735 		/* these options take one argument */
    736 		else if (!strcmp(argv[i], "-l"))   /* number of lines in vertical list */
    737 			lines = atoi(argv[++i]);
    738 		else if (!strcmp(argv[i], "-p"))   /* adds prompt to left of input field */
    739 			prompt = argv[++i];
    740 		else if (!strcmp(argv[i], "-fn"))  /* font or font set */
    741 			fonts[0] = argv[++i];
    742 		else if (!strcmp(argv[i], "-nb"))  /* normal background color */
    743 			colors[SchemeNorm][ColBg] = argv[++i];
    744 		else if (!strcmp(argv[i], "-nf"))  /* normal foreground color */
    745 			colors[SchemeNorm][ColFg] = argv[++i];
    746 		else if (!strcmp(argv[i], "-sb"))  /* selected background color */
    747 			colors[SchemeSel][ColBg] = argv[++i];
    748 		else if (!strcmp(argv[i], "-sf"))  /* selected foreground color */
    749 			colors[SchemeSel][ColFg] = argv[++i];
    750 		else if (!strcmp(argv[i], "-ob"))  /* outline background color */
    751 			colors[SchemeOut][ColBg] = argv[++i];
    752 		else if (!strcmp(argv[i], "-of"))  /* outline foreground color */
    753 			colors[SchemeOut][ColFg] = argv[++i];
    754 		else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "-m") || !strcmp(argv[i], "-w")) /* legacy compatibility */
    755 			if (strcmp(argv[i], "-f") && ++i == argc)
    756 				usage();
    757 			else
    758 				continue;
    759 		else
    760 			usage();
    761 
    762 	if (!setlocale(LC_CTYPE, ""))
    763 		fputs("warning: no locale support\n", stderr);
    764 	passwd = !strcmp(colors[SchemeNorm][ColFg], colors[SchemeNorm][ColBg]);
    765 
    766 	drw = drw_create(NULL, 0, 0, 0, 0);
    767 	if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
    768 		die("no fonts could be loaded.");
    769 	lrpad = drw->fonts->h;
    770 
    771 #ifdef __OpenBSD__
    772 	if (pledge("stdio rpath tty", NULL) == -1)
    773 		die("pledge");
    774 #endif
    775 
    776 	readstdin();
    777 	setup();
    778 	run();
    779 
    780 	return 1; /* unreachable */
    781 }