dmenu.c (19204B)
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 TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) 26 27 /* enums */ 28 enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ 29 30 struct item { 31 char *text; 32 struct item *left, *right; 33 int out; 34 }; 35 36 static char text[BUFSIZ] = ""; 37 static int bh, mw, mh; 38 static int inputw = 0, promptw; 39 static int lrpad; /* sum of left and right padding */ 40 static size_t cursor; 41 static struct item *items = NULL; 42 static struct item *matches, *matchend; 43 static struct item *prev, *curr, *next, *sel; 44 static volatile sig_atomic_t resized; 45 static int ttyfd = -1; 46 static FILE *ttyout; 47 static struct termios termios_orig; 48 static int termios_saved; 49 #define xic 0 50 51 static Drw *drw; 52 static Clr *scheme[SchemeLast]; 53 54 #include "config.h" 55 56 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; 57 static char *(*fstrstr)(const char *, const char *) = strstr; 58 59 enum { 60 XK_A = 'A', 61 XK_B, 62 XK_C, 63 XK_D, 64 XK_E, 65 XK_F, 66 XK_G, 67 XK_H, 68 XK_I, 69 XK_J, 70 XK_K, 71 XK_L, 72 XK_M, 73 XK_N, 74 XK_O, 75 XK_P, 76 XK_Q, 77 XK_R, 78 XK_S, 79 XK_T, 80 XK_U, 81 XK_V, 82 XK_W, 83 XK_X, 84 XK_Y, 85 XK_Z, 86 XK_a = 'a', 87 XK_b, 88 XK_c, 89 XK_d, 90 XK_e, 91 XK_f, 92 XK_g, 93 XK_h, 94 XK_i, 95 XK_j, 96 XK_k, 97 XK_l, 98 XK_m, 99 XK_n, 100 XK_o, 101 XK_p, 102 XK_q, 103 XK_r, 104 XK_s, 105 XK_t, 106 XK_u, 107 XK_v, 108 XK_w, 109 XK_x, 110 XK_y, 111 XK_z, 112 XK_Left = 0x101, 113 XK_Right, 114 XK_Up, 115 XK_Down, 116 XK_Home, 117 XK_End, 118 XK_Next, 119 XK_Prior, 120 XK_Delete, 121 XK_BackSpace, 122 XK_Return, 123 XK_KP_Left, 124 XK_KP_Right, 125 XK_KP_Up, 126 XK_KP_Down, 127 XK_KP_Home, 128 XK_KP_End, 129 XK_KP_Next, 130 XK_KP_Prior, 131 XK_KP_Delete, 132 XK_KP_Enter = '\n', 133 XK_Escape = 27, 134 XK_Tab = '\t', 135 XK_bracketleft = '[' 136 }; 137 138 static unsigned int 139 textw_clamp(const char *str, unsigned int n) 140 { 141 unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; 142 return MIN(w, n); 143 } 144 145 static void 146 appenditem(struct item *item, struct item **list, struct item **last) 147 { 148 if (*last) 149 (*last)->right = item; 150 else 151 *list = item; 152 153 item->left = *last; 154 item->right = NULL; 155 *last = item; 156 } 157 158 static void 159 calcoffsets(void) 160 { 161 int i, n; 162 163 if (lines > 0) 164 n = lines * bh; 165 else 166 n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); 167 /* calculate which items will begin the next page and previous page */ 168 for (i = 0, next = curr; next; next = next->right) 169 if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n) 170 break; 171 for (i = 0, prev = curr; prev && prev->left; prev = prev->left) 172 if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) 173 break; 174 } 175 176 static void 177 cleanup(void) 178 { 179 size_t i; 180 181 for (i = 0; i < SchemeLast; i++) 182 drw_scm_free(drw, scheme[i], 2); 183 for (i = 0; items && items[i].text; ++i) 184 free(items[i].text); 185 free(items); 186 drw_term_reset(); 187 drw_free(drw); 188 if (termios_saved) 189 tcsetattr(ttyfd, TCSAFLUSH, &termios_orig); 190 if (ttyout) 191 fclose(ttyout); 192 if (ttyfd >= 0) 193 close(ttyfd); 194 } 195 196 static char * 197 cistrstr(const char *h, const char *n) 198 { 199 size_t i; 200 201 if (!n[0]) 202 return (char *)h; 203 204 for (; *h; ++h) { 205 for (i = 0; n[i] && tolower((unsigned char)n[i]) == 206 tolower((unsigned char)h[i]); ++i) 207 ; 208 if (n[i] == '\0') 209 return (char *)h; 210 } 211 return NULL; 212 } 213 214 static int 215 drawitem(struct item *item, int x, int y, int w) 216 { 217 if (item == sel) 218 drw_setscheme(drw, scheme[SchemeSel]); 219 else if (item->out) 220 drw_setscheme(drw, scheme[SchemeOut]); 221 else 222 drw_setscheme(drw, scheme[SchemeNorm]); 223 224 return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); 225 } 226 227 static void 228 drawmenu(void) 229 { 230 unsigned int curpos; 231 struct item *item; 232 int cx; 233 int x = 0, y = 0, w; 234 235 drw_setscheme(drw, scheme[SchemeNorm]); 236 drw_rect(drw, 0, 0, mw, mh, 1, 0); 237 238 if (prompt && *prompt) { 239 drw_setscheme(drw, scheme[SchemeSel]); 240 x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); 241 } 242 /* draw input field */ 243 w = (lines > 0 || !matches) ? mw - x : inputw; 244 drw_setscheme(drw, scheme[SchemeNorm]); 245 drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); 246 247 curpos = TEXTW(text) - TEXTW(&text[cursor]); 248 cx = x; 249 curpos += lrpad / 2; 250 251 if (lines > 0) { 252 /* draw vertical list */ 253 for (item = curr; item != next; item = item->right) 254 drawitem(item, x, y += bh, mw - x); 255 } else if (matches) { 256 /* draw horizontal list */ 257 x += inputw; 258 w = TEXTW("<"); 259 if (curr->left) { 260 drw_setscheme(drw, scheme[SchemeNorm]); 261 drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); 262 } 263 x += w; 264 for (item = curr; item != next; item = item->right) 265 x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); 266 if (next) { 267 w = TEXTW(">"); 268 drw_setscheme(drw, scheme[SchemeNorm]); 269 drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); 270 } 271 } 272 drw_move(drw, MIN(cx + curpos, mw - 1), 0); 273 drw_map(drw, 0, 0, 0, mw, mh); 274 } 275 276 static void 277 match(void) 278 { 279 static char **tokv = NULL; 280 static int tokn = 0; 281 char buf[sizeof text], *s; 282 int i, tokc = 0; 283 size_t len, textsize; 284 struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; 285 286 strcpy(buf, text); 287 /* separate input text into tokens to be matched individually */ 288 for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) 289 if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) 290 die("cannot realloc %zu bytes:", tokn * sizeof *tokv); 291 len = tokc ? strlen(tokv[0]) : 0; 292 293 matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; 294 textsize = strlen(text) + 1; 295 for (item = items; item && item->text; item++) { 296 for (i = 0; i < tokc; i++) 297 if (!fstrstr(item->text, tokv[i])) 298 break; 299 if (i != tokc) /* not all tokens match */ 300 continue; 301 /* exact matches go first, then prefixes, then substrings */ 302 if (!tokc || !fstrncmp(text, item->text, textsize)) 303 appenditem(item, &matches, &matchend); 304 else if (!fstrncmp(tokv[0], item->text, len)) 305 appenditem(item, &lprefix, &prefixend); 306 else 307 appenditem(item, &lsubstr, &substrend); 308 } 309 if (lprefix) { 310 if (matches) { 311 matchend->right = lprefix; 312 lprefix->left = matchend; 313 } else 314 matches = lprefix; 315 matchend = prefixend; 316 } 317 if (lsubstr) { 318 if (matches) { 319 matchend->right = lsubstr; 320 lsubstr->left = matchend; 321 } else 322 matches = lsubstr; 323 matchend = substrend; 324 } 325 curr = sel = matches; 326 calcoffsets(); 327 } 328 329 static void 330 insert(const char *str, ssize_t n) 331 { 332 if (strlen(text) + n > sizeof text - 1) 333 return; 334 /* move existing text out of the way, insert new text, and update cursor */ 335 memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); 336 if (n > 0) 337 memcpy(&text[cursor], str, n); 338 cursor += n; 339 match(); 340 } 341 342 static size_t 343 nextrune(int inc) 344 { 345 ssize_t n; 346 347 /* return location of next utf8 rune in the given direction (+1 or -1) */ 348 for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) 349 ; 350 return n; 351 } 352 353 static void 354 movewordedge(int dir) 355 { 356 if (dir < 0) { /* move cursor to the start of the word*/ 357 while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) 358 cursor = nextrune(-1); 359 while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) 360 cursor = nextrune(-1); 361 } else { /* move cursor to the end of the word */ 362 while (text[cursor] && strchr(worddelimiters, text[cursor])) 363 cursor = nextrune(+1); 364 while (text[cursor] && !strchr(worddelimiters, text[cursor])) 365 cursor = nextrune(+1); 366 } 367 } 368 369 static void 370 resize(void) 371 { 372 struct winsize ws; 373 int th; 374 375 resized = 0; 376 if (ioctl(ttyfd, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0 || ws.ws_row == 0) { 377 mw = 80; 378 th = 24; 379 } else { 380 mw = ws.ws_col; 381 th = ws.ws_row; 382 } 383 bh = drw->fonts->h; 384 mh = lines + 1; 385 drw_setwindow(drw, 0, topbar ? 0 : MAX(th - mh, 0)); 386 promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; 387 inputw = mw / 3; 388 drw_resize(drw, mw, th); 389 calcoffsets(); 390 drawmenu(); 391 } 392 393 static void 394 sigwinch(int unused) 395 { 396 (void)unused; 397 resized = 1; 398 } 399 400 401 typedef enum { 402 XLookupChars, 403 XLookupKeySym, 404 XLookupBoth, 405 XLookupNone, 406 } Status; 407 408 typedef struct { 409 enum { 410 None, 411 ControlMask = 1 << 0, 412 Mod1Mask = 1 << 1, 413 ShiftMask = 1 << 2 414 } state; 415 int ch; 416 char buf[4]; 417 size_t blen; 418 } XKeyEvent; 419 420 typedef int KeySym; 421 enum { 422 NoSymbol 423 }; 424 425 size_t XmbLookupString(int x, XKeyEvent *ev, char buf[static 4], size_t nbuf, int *ksym, Status *status) 426 { 427 if (!ev) { 428 *status = XLookupNone; 429 return 0; 430 } 431 432 *ksym = ev->ch; 433 memcpy(buf, ev->buf, ev->blen); 434 435 if (ev->blen > 1) 436 *status = XLookupChars; 437 else 438 *status = XLookupBoth; 439 440 return ev->blen; 441 } 442 443 static size_t 444 utf8seqlen(unsigned char ch) 445 { 446 if ((ch & 0x80) == 0x00) 447 return 1; 448 if ((ch & 0xE0) == 0xC0) 449 return 2; 450 if ((ch & 0xF0) == 0xE0) 451 return 3; 452 if ((ch & 0xF8) == 0xF0) 453 return 4; 454 return 0; 455 } 456 457 static XKeyEvent * 458 readkey(void) 459 { 460 static XKeyEvent ev; 461 static size_t utf8need; 462 unsigned char ch = 0, seq[3]; 463 struct pollfd pfd = { .fd = ttyfd, .events = POLLIN }; 464 int n; 465 466 ev.state = None; 467 468 n = read(ttyfd, &ch, 1); 469 if (n <= 0) 470 return NULL; 471 if (utf8need) { 472 if ((ch & 0xC0) != 0x80) { 473 utf8need = 0; 474 } else { 475 ev.buf[ev.blen++] = ch; 476 if (ev.blen < utf8need) 477 return NULL; 478 ev.ch = 0; 479 utf8need = 0; 480 return &ev; 481 } 482 } 483 484 ev.blen = 0; 485 486 if (ch == '\r') { 487 ev.ch = XK_KP_Enter; 488 return &ev; 489 } 490 if (ch == 127 || ch == 8) { 491 ev.ch = XK_BackSpace; 492 return &ev; 493 } 494 if (ch < 27) { 495 ev.state = ControlMask; 496 ev.ch = ch + 'a' - 1; 497 return &ev; 498 } 499 if (ch & 0x80) { 500 utf8need = utf8seqlen(ch); 501 if (!utf8need) 502 return NULL; 503 ev.buf[ev.blen++] = (char)ch; 504 if (utf8need > 1) 505 return NULL; 506 ev.blen = 0; 507 ev.ch = ch; 508 utf8need = 0; 509 return &ev; 510 } 511 512 ev.ch = ch; 513 ev.buf[0] = ch; 514 ev.blen = 1; 515 516 if (ch > 27 || poll(&pfd, 1, 25) <= 0 || read(ttyfd, &seq[0], 1) != 1) 517 return &ev; 518 519 if (seq[0] == '[') { 520 if (read(ttyfd, &seq[1], 1) != 1) 521 return &ev; 522 if (seq[1] >= '0' && seq[1] <= '9') { 523 if (read(ttyfd, &seq[2], 1) != 1) 524 if (seq[2] == '~') switch (seq[1]) { 525 case '1': ev.ch = XK_Home; break; 526 case '3': ev.ch = XK_Delete; break; 527 case '4': ev.ch = XK_End; break; 528 case '5': ev.ch = XK_Prior; break; 529 case '6': ev.ch = XK_Next; break; 530 case '7': ev.ch = XK_Home; break; 531 case '8': ev.ch = XK_End; break; 532 } 533 return &ev; 534 } 535 switch (seq[1]) { 536 case 'A': ev.ch = XK_Up; break; 537 case 'B': ev.ch = XK_Down; break; 538 case 'C': ev.ch = XK_Right; break; 539 case 'D': ev.ch = XK_Left; break; 540 case 'H': ev.ch = XK_Home; break; 541 case 'F': ev.ch = XK_End; break; 542 } 543 return &ev; 544 } 545 546 if (seq[0] == 'O') { 547 if (read(ttyfd, &seq[1], 1) != 1) 548 switch (seq[1]) { 549 case 'H': ev.ch = XK_Home; break; 550 case 'F': ev.ch = XK_End; break; 551 } 552 return &ev; 553 } 554 555 ev.state = Mod1Mask; 556 ev.ch = seq[0]; 557 558 return &ev; 559 } 560 561 static void 562 keypress(XKeyEvent *ev) 563 { 564 char buf[64]; 565 int len; 566 KeySym ksym = NoSymbol; 567 Status status; 568 569 len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); 570 switch (status) { 571 default: /* XLookupNone, XBufferOverflow */ 572 return; 573 case XLookupChars: /* composed string from input method */ 574 goto insert; 575 case XLookupKeySym: 576 case XLookupBoth: /* a KeySym and a string are returned: use keysym */ 577 break; 578 } 579 580 if (ev->state & ControlMask) { 581 switch(ksym) { 582 case XK_a: ksym = XK_Home; break; 583 case XK_b: ksym = XK_Left; break; 584 case XK_c: ksym = XK_Escape; break; 585 case XK_d: ksym = XK_Delete; break; 586 case XK_e: ksym = XK_End; break; 587 case XK_f: ksym = XK_Right; break; 588 case XK_g: ksym = XK_Escape; break; 589 case XK_h: ksym = XK_BackSpace; break; 590 case XK_i: ksym = XK_Tab; break; 591 case XK_j: /* fallthrough */ 592 case XK_J: /* fallthrough */ 593 case XK_m: /* fallthrough */ 594 case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; 595 case XK_n: ksym = XK_Down; break; 596 case XK_p: ksym = XK_Up; break; 597 598 case XK_k: /* delete right */ 599 text[cursor] = '\0'; 600 match(); 601 break; 602 case XK_u: /* delete left */ 603 insert(NULL, 0 - cursor); 604 break; 605 case XK_w: /* delete word */ 606 while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) 607 insert(NULL, nextrune(-1) - cursor); 608 while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) 609 insert(NULL, nextrune(-1) - cursor); 610 break; 611 case XK_Left: 612 case XK_KP_Left: 613 movewordedge(-1); 614 goto draw; 615 case XK_Right: 616 case XK_KP_Right: 617 movewordedge(+1); 618 goto draw; 619 case XK_Return: 620 case XK_KP_Enter: 621 break; 622 case XK_bracketleft: 623 drw_clear(drw, mh); 624 drw_cursor_restore(); 625 cleanup(); 626 exit(1); 627 default: 628 return; 629 } 630 } else if (ev->state & Mod1Mask) { 631 switch(ksym) { 632 case XK_b: 633 movewordedge(-1); 634 goto draw; 635 case XK_f: 636 movewordedge(+1); 637 goto draw; 638 case XK_g: ksym = XK_Home; break; 639 case XK_G: ksym = XK_End; break; 640 case XK_h: ksym = XK_Up; break; 641 case XK_j: ksym = XK_Next; break; 642 case XK_k: ksym = XK_Prior; break; 643 case XK_l: ksym = XK_Down; break; 644 default: 645 return; 646 } 647 } 648 649 switch(ksym) { 650 default: 651 insert: 652 if (!iscntrl((unsigned char)*buf)) 653 insert(buf, len); 654 break; 655 case XK_Delete: 656 case XK_KP_Delete: 657 if (text[cursor] == '\0') 658 return; 659 cursor = nextrune(+1); 660 /* fallthrough */ 661 case XK_BackSpace: 662 if (cursor == 0) 663 return; 664 insert(NULL, nextrune(-1) - cursor); 665 break; 666 case XK_End: 667 case XK_KP_End: 668 if (text[cursor] != '\0') { 669 cursor = strlen(text); 670 break; 671 } 672 if (next) { 673 /* jump to end of list and position items in reverse */ 674 curr = matchend; 675 calcoffsets(); 676 curr = prev; 677 calcoffsets(); 678 while (next && (curr = curr->right)) 679 calcoffsets(); 680 } 681 sel = matchend; 682 break; 683 case XK_Escape: 684 drw_clear(drw, mh); 685 drw_cursor_restore(); 686 cleanup(); 687 exit(1); 688 case XK_Home: 689 case XK_KP_Home: 690 if (sel == matches) { 691 cursor = 0; 692 break; 693 } 694 sel = curr = matches; 695 calcoffsets(); 696 break; 697 case XK_Left: 698 case XK_KP_Left: 699 if (cursor > 0 && (!sel || !sel->left || lines > 0)) { 700 cursor = nextrune(-1); 701 break; 702 } 703 if (lines > 0) 704 return; 705 /* fallthrough */ 706 case XK_Up: 707 case XK_KP_Up: 708 if (sel && sel->left && (sel = sel->left)->right == curr) { 709 curr = prev; 710 calcoffsets(); 711 } 712 break; 713 case XK_Next: 714 case XK_KP_Next: 715 if (!next) 716 return; 717 sel = curr = next; 718 calcoffsets(); 719 break; 720 case XK_Prior: 721 case XK_KP_Prior: 722 if (!prev) 723 return; 724 sel = curr = prev; 725 calcoffsets(); 726 break; 727 case XK_Return: 728 case XK_KP_Enter: 729 if (!(ev->state & ControlMask)) { 730 drw_clear(drw, mh); 731 drw_cursor_restore(); 732 } 733 puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 734 if (!(ev->state & ControlMask)) { 735 cleanup(); 736 exit(0); 737 } 738 if (sel) 739 sel->out = 1; 740 break; 741 case XK_Right: 742 case XK_KP_Right: 743 if (text[cursor] != '\0') { 744 cursor = nextrune(+1); 745 break; 746 } 747 if (lines > 0) 748 return; 749 /* fallthrough */ 750 case XK_Down: 751 case XK_KP_Down: 752 if (sel && sel->right && (sel = sel->right) == next) { 753 curr = next; 754 calcoffsets(); 755 } 756 break; 757 case XK_Tab: 758 if (!sel) 759 return; 760 cursor = strnlen(sel->text, sizeof text - 1); 761 memcpy(text, sel->text, cursor); 762 text[cursor] = '\0'; 763 match(); 764 break; 765 } 766 767 draw: 768 drawmenu(); 769 } 770 771 static void 772 readstdin(void) 773 { 774 char *line = NULL; 775 size_t i, itemsiz = 0, linesiz = 0; 776 ssize_t len; 777 778 /* read each line from stdin and add it to the item list */ 779 for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { 780 if (i + 1 >= itemsiz) { 781 itemsiz += 256; 782 if (!(items = realloc(items, itemsiz * sizeof(*items)))) 783 die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); 784 } 785 if (line[len - 1] == '\n') 786 line[len - 1] = '\0'; 787 if (!(items[i].text = strdup(line))) 788 die("strdup:"); 789 790 items[i].out = 0; 791 } 792 free(line); 793 if (items) 794 items[i].text = NULL; 795 lines = MIN(lines, i); 796 } 797 798 static void 799 run(void) 800 { 801 drawmenu(); 802 for (;;) { 803 if (resized) 804 resize(); 805 keypress(readkey()); 806 } 807 } 808 809 static void 810 setup(void) 811 { 812 int i, termerr; 813 struct sigaction sa = { 0 }; 814 struct termios raw; 815 816 ttyfd = open("/dev/tty", O_RDWR | O_CLOEXEC); 817 if (ttyfd < 0) 818 ttyfd = dup(STDERR_FILENO); 819 if (ttyfd < 0) 820 die("open /dev/tty:"); 821 if (!(ttyout = fdopen(dup(ttyfd), "w"))) 822 die("fdopen:"); 823 if (setupterm(NULL, ttyfd, &termerr) == -1 || termerr <= 0) 824 die("setupterm failed"); 825 drw_term_init(ttyout); 826 827 if (tcgetattr(ttyfd, &termios_orig) == -1) 828 die("tcgetattr:"); 829 termios_saved = 1; 830 raw = termios_orig; 831 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 832 raw.c_oflag &= ~(OPOST); 833 raw.c_cflag |= (CS8); 834 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 835 raw.c_cc[VMIN] = 1; 836 raw.c_cc[VTIME] = 0; 837 if (tcsetattr(ttyfd, TCSAFLUSH, &raw) == -1) 838 die("tcsetattr:"); 839 840 /* init appearance */ 841 for (i = 0; i < SchemeLast; i++) 842 scheme[i] = drw_scm_create(drw, colors[i], 2); 843 844 sa.sa_handler = sigwinch; 845 sigemptyset(&sa.sa_mask); 846 sigaction(SIGWINCH, &sa, NULL); 847 match(); 848 drw_cursor_save(); 849 resize(); 850 } 851 852 static void 853 usage(void) 854 { 855 die("usage: tmenu [-biv] [-l lines] [-p prompt]\n" 856 " [-nb color] [-nf color] [-sb color] [-sf color]"); 857 } 858 859 int 860 main(int argc, char *argv[]) 861 { 862 int i; 863 864 for (i = 1; i < argc; i++) 865 /* these options take no arguments */ 866 if (!strcmp(argv[i], "-v")) { /* prints version information */ 867 puts("tmenu-"VERSION); 868 exit(0); 869 } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ 870 topbar = 0; 871 else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ 872 fstrncmp = strncasecmp; 873 fstrstr = cistrstr; 874 } else if (i + 1 == argc) 875 usage(); 876 /* these options take one argument */ 877 else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ 878 lines = atoi(argv[++i]); 879 else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ 880 prompt = argv[++i]; 881 else if (!strcmp(argv[i], "-fn")) /* font or font set */ 882 fonts[0] = argv[++i]; 883 else if (!strcmp(argv[i], "-nb")) /* normal background color */ 884 colors[SchemeNorm][ColBg] = argv[++i]; 885 else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ 886 colors[SchemeNorm][ColFg] = argv[++i]; 887 else if (!strcmp(argv[i], "-sb")) /* selected background color */ 888 colors[SchemeSel][ColBg] = argv[++i]; 889 else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ 890 colors[SchemeSel][ColFg] = argv[++i]; 891 else if (!strcmp(argv[i], "-ob")) /* outline background color */ 892 colors[SchemeOut][ColBg] = argv[++i]; 893 else if (!strcmp(argv[i], "-of")) /* outline foreground color */ 894 colors[SchemeOut][ColFg] = argv[++i]; 895 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "-m") || !strcmp(argv[i], "-w")) /* legacy compatibility */ 896 if (strcmp(argv[i], "-f") && ++i == argc) 897 usage(); 898 else 899 continue; 900 else 901 usage(); 902 903 if (!setlocale(LC_CTYPE, "")) 904 fputs("warning: no locale support\n", stderr); 905 906 drw = drw_create(NULL, 0, 0, 0, 0); 907 if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) 908 die("no fonts could be loaded."); 909 lrpad = drw->fonts->h * 2; 910 911 #ifdef __OpenBSD__ 912 if (pledge("stdio rpath tty", NULL) == -1) 913 die("pledge"); 914 #endif 915 916 readstdin(); 917 setup(); 918 run(); 919 920 return 1; /* unreachable */ 921 }