dmenu

my fork of dmenu
Index Commits Files Refs README LICENSE
dmenu.c (26923B)
   1 /* See LICENSE file for copyright and license details. */
   2 #include <ctype.h>
   3 #include <locale.h>
   4 #include <stdbool.h>
   5 #include <stdio.h>
   6 #include <stdlib.h>
   7 #include <string.h>
   8 #include <strings.h>
   9 #include <time.h>
  10 #include <unistd.h>
  11 #include <math.h>
  12 
  13 #include <X11/Xlib.h>
  14 #include <X11/Xatom.h>
  15 #include <X11/Xutil.h>
  16 #ifdef XINERAMA
  17 #include <X11/extensions/Xinerama.h>
  18 #endif
  19 #include <X11/Xft/Xft.h>
  20 
  21 #include "drw.h"
  22 #include "util.h"
  23 
  24 /* macros */
  25 #define INTERSECT(x,y,w,h,r)  (MAX(0, MIN((x)+(w),(r).x_org+(r).width)  - MAX((x),(r).x_org)) \
  26                              * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
  27 #define LENGTH(X)             (sizeof X / sizeof X[0])
  28 #define TEXTW(X)              (drw_fontset_getwidth(drw, (X)) + lrpad)
  29 
  30 /* enums */
  31 enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
  32 
  33 struct item {
  34     char *text;
  35     struct item *left, *right;
  36     int out;
  37     double distance;
  38 };
  39 
  40 static char text[BUFSIZ] = "";
  41 static char *embed;
  42 static int bh, mw, mh;
  43 static int dmx = 0; /* put dmenu at this x offset */
  44 static int dmy = 0; /* put dmenu at this y offset (measured from the bottom if topbar is 0) */
  45 static unsigned int dmw = 0; /* make dmenu this wide */
  46 static int inputw = 0, promptw;
  47 static int lrpad; /* sum of left and right padding */
  48 static size_t cursor;
  49 static struct item *items = NULL;
  50 static struct item *matches, *matchend;
  51 static struct item *prev, *curr, *next, *sel;
  52 static int mon = -1, screen;
  53 static bool sortmatches = true;
  54 
  55 static Atom clip, utf8;
  56 static Display *dpy;
  57 static Window root, parentwin, win;
  58 static XIC xic;
  59 
  60 static Drw *drw;
  61 static Clr *scheme[SchemeLast];
  62 
  63 #include "config.h"
  64 
  65 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
  66 static char *(*fstrstr)(const char *, const char *) = strstr;
  67 
  68 static void
  69 appenditem(struct item *item, struct item **list, struct item **last)
  70 {
  71     if (*last)
  72         (*last)->right = item;
  73     else
  74         *list = item;
  75 
  76     item->left = *last;
  77     item->right = NULL;
  78     *last = item;
  79 }
  80 
  81 static void
  82 calcoffsets(void)
  83 {
  84     int i, n;
  85 
  86     if (lines > 0)
  87         n = lines * bh;
  88     else
  89         n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
  90 
  91 /* calculate which items will begin the next page and previous page */
  92     for (i = 0, next = curr; next; next = next->right)
  93         if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
  94             break;
  95 
  96     prev = curr->left;
  97 }
  98 
  99 static void
 100 cleanup(void)
 101 {
 102     size_t i;
 103 
 104     XUngrabKey(dpy, AnyKey, AnyModifier, root);
 105     for (i = 0; i < SchemeLast; i++)
 106         free(scheme[i]);
 107     drw_free(drw);
 108     XSync(dpy, False);
 109     XCloseDisplay(dpy);
 110 }
 111 
 112 static char *
 113 cistrstr(const char *s, const char *sub)
 114 {
 115     size_t len;
 116 
 117     for (len = strlen(sub); *s; s++)
 118         if (!strncasecmp(s, sub, len))
 119             return (char *)s;
 120     return NULL;
 121 }
 122 
 123 static int
 124 drawitem(struct item *item, int x, int y, int w)
 125 {
 126     if (item == sel)
 127         drw_setscheme(drw, scheme[SchemeSel]);
 128     else if (item->out)
 129         drw_setscheme(drw, scheme[SchemeOut]);
 130     else
 131         drw_setscheme(drw, scheme[SchemeNorm]);
 132 
 133     return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
 134 }
 135 
 136 static void
 137 drawmenu(void)
 138 {
 139     unsigned int curpos;
 140     struct item *item;
 141     int x = 0, y = 0, fh = drw->fonts->h, w;
 142 
 143     drw_setscheme(drw, scheme[SchemeNorm]);
 144     drw_rect(drw, 0, 0, mw, mh, 1, 1);
 145 
 146     if (prompt && *prompt) {
 147         drw_setscheme(drw, scheme[SchemeSel]);
 148         x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
 149     }
 150 
 151     /* draw input field */
 152     w = (lines > 0 || !matches) ? mw - x : inputw;
 153 
 154     drw_setscheme(drw, scheme[SchemeNorm]);
 155     drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
 156 
 157     curpos = TEXTW(text) - TEXTW(&text[cursor]);
 158     if ((curpos += lrpad / 2 - 1) < w) {
 159         drw_setscheme(drw, scheme[SchemeNorm]);
 160         drw_rect(drw, x + curpos, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0);
 161     }
 162 
 163     if (lines > 0) {
 164         /* draw vertical list */
 165         for (item = curr; item != next; item = item->right)
 166             drawitem(item, x, y += bh, mw - x);
 167     } else if (matches) {
 168         /* draw horizontal list */
 169         x += inputw;
 170         w = TEXTW("<");
 171         if (curr->left) {
 172             drw_setscheme(drw, scheme[SchemeNorm]);
 173             drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0);
 174         }
 175         x += w;
 176         for (item = curr; item != next; item = item->right)
 177             x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">")));
 178         if (next) {
 179             w = TEXTW(">");
 180             drw_setscheme(drw, scheme[SchemeNorm]);
 181             drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
 182         }
 183     }
 184     drw_map(drw, win, 0, 0, mw, mh);
 185 }
 186 
 187 static void
 188 grabfocus(void)
 189 {
 190     struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000  };
 191     Window focuswin;
 192     int i, revertwin;
 193 
 194     for (i = 0; i < 100; ++i) {
 195         XGetInputFocus(dpy, &focuswin, &revertwin);
 196         if (focuswin == win)
 197             return;
 198         XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
 199         nanosleep(&ts, NULL);
 200     }
 201     die("cannot grab focus");
 202 }
 203 
 204 static void
 205 grabkeyboard(void)
 206 {
 207     struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000  };
 208     int i;
 209 
 210     if (embed)
 211         return;
 212     /* try to grab keyboard, we may have to wait for another process to ungrab */
 213     for (i = 0; i < 1000; i++) {
 214         if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
 215                           GrabModeAsync, CurrentTime) == GrabSuccess)
 216             return;
 217         nanosleep(&ts, NULL);
 218     }
 219     die("cannot grab keyboard");
 220 }
 221 
 222 int
 223 compare_distance(const void *a, const void *b)
 224 {
 225     struct item *da = *(struct item **) a;
 226     struct item *db = *(struct item **) b;
 227 
 228     if (!db)
 229         return 1;
 230     if (!da)
 231         return -1;
 232 
 233     return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
 234 }
 235 
 236 void
 237 fuzzymatch(void)
 238 {
 239     /* bang - we have so much memory */
 240     struct item *it;
 241     struct item **fuzzymatches = NULL;
 242     char c;
 243     int number_of_matches = 0, i, pidx, sidx, eidx;
 244     int text_len = strlen(text), itext_len;
 245 
 246     matches = matchend = NULL;
 247 
 248     /* walk through all items */
 249     for (it = items; it && it->text; it++) {
 250         if (text_len) {
 251             itext_len = strlen(it->text);
 252             pidx = 0; /* pointer */
 253             sidx = eidx = -1; /* start of match, end of match */
 254             /* walk through item text */
 255             for (i = 0; i < itext_len && (c = it->text[i]); i++) {
 256                 /* fuzzy match pattern */
 257                 if (!fstrncmp(&text[pidx], &c, 1)) {
 258                     if(sidx == -1)
 259                         sidx = i;
 260                     pidx++;
 261                     if (pidx == text_len) {
 262                         eidx = i;
 263                         break;
 264                     }
 265                 }
 266             }
 267             /* build list of matches */
 268             if (eidx != -1) {
 269                 /* compute distance */
 270                 /* add penalty if match starts late (log(sidx+2))
 271                  * add penalty for long a match without many matching characters */
 272                 it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
 273                 /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
 274                 appenditem(it, &matches, &matchend);
 275                 number_of_matches++;
 276             }
 277         } else {
 278             appenditem(it, &matches, &matchend);
 279         }
 280     }
 281 
 282     if (number_of_matches) {
 283         /* initialize array with matches */
 284         if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
 285             die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
 286         for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
 287             fuzzymatches[i] = it;
 288         }
 289         /* sort matches according to distance */
 290         qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
 291         /* rebuild list of matches */
 292         matches = matchend = NULL;
 293         for (i = 0, it = fuzzymatches[i];  i < number_of_matches && it && \
 294                 it->text; i++, it = fuzzymatches[i]) {
 295             appenditem(it, &matches, &matchend);
 296         }
 297         free(fuzzymatches);
 298     }
 299     curr = sel = matches;
 300     calcoffsets();
 301 }
 302 
 303 static void
 304 match(void)
 305 {
 306     if (fuzzy) {
 307         fuzzymatch();
 308         return;
 309     }
 310     static char **tokv = NULL;
 311     static int tokn = 0;
 312 
 313     char buf[sizeof text], *s;
 314     int i, tokc = 0;
 315     size_t len, textsize;
 316     struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
 317 
 318     strcpy(buf, text);
 319     /* separate input text into tokens to be matched individually */
 320     for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
 321         if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
 322             die("cannot realloc %u bytes:", tokn * sizeof *tokv);
 323     len = tokc ? strlen(tokv[0]) : 0;
 324 
 325     matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
 326     textsize = strlen(text) + 1;
 327     for (item = items; item && item->text; item++) {
 328         for (i = 0; i < tokc; i++)
 329             if (!fstrstr(item->text, tokv[i]))
 330                 break;
 331         if (i != tokc) /* not all tokens match */
 332             continue;
 333 
 334         if (!sortmatches)
 335             appenditem(item, &matches, &matchend);
 336         else {
 337             /* exact matches go first, then prefixes, then substrings */
 338             if (!tokc || !fstrncmp(text, item->text, textsize))
 339                 appenditem(item, &matches, &matchend);
 340             else if (!fstrncmp(tokv[0], item->text, len))
 341                 appenditem(item, &lprefix, &prefixend);
 342             else
 343                 appenditem(item, &lsubstr, &substrend);
 344         }
 345     }
 346     if (lprefix) {
 347         if (matches) {
 348             matchend->right = lprefix;
 349             lprefix->left = matchend;
 350         } else
 351             matches = lprefix;
 352         matchend = prefixend;
 353     }
 354     if (lsubstr) {
 355         if (matches) {
 356             matchend->right = lsubstr;
 357             lsubstr->left = matchend;
 358         } else
 359             matches = lsubstr;
 360         matchend = substrend;
 361     }
 362     curr = sel = matches;
 363     calcoffsets();
 364 }
 365 
 366 static void
 367 insert(const char *str, ssize_t n)
 368 {
 369     if (strlen(text) + n > sizeof text - 1)
 370         return;
 371     /* move existing text out of the way, insert new text, and update cursor */
 372     memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
 373     if (n > 0)
 374         memcpy(&text[cursor], str, n);
 375     cursor += n;
 376     match();
 377 }
 378 
 379 static size_t
 380 nextrune(int inc)
 381 {
 382     ssize_t n;
 383 
 384     /* return location of next utf8 rune in the given direction (+1 or -1) */
 385     for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
 386         ;
 387     return n;
 388 }
 389 
 390 static void
 391 movewordedge(int dir)
 392 {
 393     if (dir < 0) { /* move cursor to the start of the word*/
 394         while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
 395             cursor = nextrune(-1);
 396         while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
 397             cursor = nextrune(-1);
 398     } else { /* move cursor to the end of the word */
 399         while (text[cursor] && strchr(worddelimiters, text[cursor]))
 400             cursor = nextrune(+1);
 401         while (text[cursor] && !strchr(worddelimiters, text[cursor]))
 402             cursor = nextrune(+1);
 403     }
 404 }
 405 
 406 static void
 407 keypress(XKeyEvent *ev)
 408 {
 409     char buf[32];
 410     int len;
 411     KeySym ksym;
 412     Status status;
 413 
 414     len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
 415     switch (status) {
 416     default: /* XLookupNone, XBufferOverflow */
 417         return;
 418     case XLookupChars:
 419         goto insert;
 420     case XLookupKeySym:
 421     case XLookupBoth:
 422         break;
 423     }
 424 
 425     if (ev->state & ControlMask) {
 426         switch(ksym) {
 427         case XK_a: ksym = XK_Home;      break;
 428         case XK_b: ksym = XK_Left;      break;
 429         case XK_c: ksym = XK_Escape;    break;
 430         case XK_d: ksym = XK_Delete;    break;
 431         case XK_e: ksym = XK_End;       break;
 432         case XK_f: ksym = XK_Right;     break;
 433         case XK_g: ksym = XK_Escape;    break;
 434         case XK_h: ksym = XK_BackSpace; break;
 435         case XK_i: ksym = XK_Tab;       break;
 436         case XK_j: /* fallthrough */
 437         case XK_J: /* fallthrough */
 438         case XK_m: /* fallthrough */
 439         case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break;
 440         case XK_n: ksym = XK_Down;      break;
 441         case XK_p: ksym = XK_Up;        break;
 442 
 443         case XK_k: /* delete right */
 444             text[cursor] = '\0';
 445             match();
 446             break;
 447         case XK_u: /* delete left */
 448             insert(NULL, 0 - cursor);
 449             break;
 450         case XK_w: /* delete word */
 451             while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
 452                 insert(NULL, nextrune(-1) - cursor);
 453             while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
 454                 insert(NULL, nextrune(-1) - cursor);
 455             break;
 456         case XK_y: /* paste selection */
 457         case XK_Y:
 458             XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
 459                               utf8, utf8, win, CurrentTime);
 460             return;
 461         case XK_Left:
 462             movewordedge(-1);
 463             goto draw;
 464         case XK_Right:
 465             movewordedge(+1);
 466             goto draw;
 467         case XK_Return:
 468         case XK_KP_Enter:
 469             break;
 470         case XK_bracketleft:
 471             cleanup();
 472             exit(1);
 473         default:
 474             return;
 475         }
 476     } else if (ev->state & Mod1Mask) {
 477         switch(ksym) {
 478         case XK_b:
 479             movewordedge(-1);
 480             goto draw;
 481         case XK_f:
 482             movewordedge(+1);
 483             goto draw;
 484         case XK_g: ksym = XK_Home;  break;
 485         case XK_G: ksym = XK_End;   break;
 486         case XK_h: ksym = XK_Up;    break;
 487         case XK_j: ksym = XK_Next;  break;
 488         case XK_k: ksym = XK_Prior; break;
 489         case XK_l: ksym = XK_Down;  break;
 490         default:
 491             return;
 492         }
 493     }
 494 
 495     switch(ksym) {
 496     default:
 497 insert:
 498         if (!iscntrl(*buf))
 499             insert(buf, len);
 500         break;
 501     case XK_Delete:
 502         if (text[cursor] == '\0')
 503             return;
 504         cursor = nextrune(+1);
 505         /* fallthrough */
 506     case XK_BackSpace:
 507         if (cursor == 0)
 508             return;
 509         insert(NULL, nextrune(-1) - cursor);
 510         break;
 511     case XK_End:
 512         if (text[cursor] != '\0') {
 513             cursor = strlen(text);
 514             break;
 515         }
 516         if (next) {
 517             /* jump to end of list and position items in reverse */
 518             curr = matchend;
 519             calcoffsets();
 520             curr = prev;
 521             calcoffsets();
 522             while (next && (curr = curr->right))
 523                 calcoffsets();
 524         }
 525         sel = matchend;
 526         break;
 527     case XK_Escape:
 528         cleanup();
 529         exit(1);
 530     case XK_Home:
 531         if (sel == matches) {
 532             cursor = 0;
 533             break;
 534         }
 535         sel = curr = matches;
 536         calcoffsets();
 537         break;
 538     case XK_Left:
 539         if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
 540             cursor = nextrune(-1);
 541             break;
 542         }
 543         if (lines > 0)
 544             return;
 545         /* fallthrough */
 546     case XK_Up:
 547         if (sel && sel->left && (sel = sel->left)->right == curr) {
 548             curr = prev;
 549             calcoffsets();
 550         }
 551         break;
 552     case XK_Next:
 553         if (!next)
 554             return;
 555         sel = curr = next;
 556         calcoffsets();
 557         break;
 558     case XK_Prior:
 559         if (!prev)
 560             return;
 561         sel = curr = prev;
 562         calcoffsets();
 563         break;
 564     case XK_Return:
 565     case XK_KP_Enter:
 566         puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
 567         if (!(ev->state & ControlMask)) {
 568             cleanup();
 569             exit(0);
 570         }
 571         if (sel)
 572             sel->out = 1;
 573         break;
 574     case XK_Right:
 575         if (text[cursor] != '\0') {
 576             cursor = nextrune(+1);
 577             break;
 578         }
 579         if (lines > 0)
 580             return;
 581         /* fallthrough */
 582     case XK_Down:
 583         if (sel && sel->right && (sel = sel->right) == next) {
 584             curr = next;
 585             calcoffsets();
 586         }
 587         break;
 588     case XK_Tab:
 589         if (!sel)
 590             return;
 591         strncpy(text, sel->text, sizeof text - 1);
 592         text[sizeof text - 1] = '\0';
 593         cursor = strlen(text);
 594         match();
 595         break;
 596     }
 597 
 598 draw:
 599     drawmenu();
 600 }
 601 
 602 static void
 603 buttonpress(XEvent *e)
 604 {
 605     struct item *item;
 606     XButtonPressedEvent *ev = &e->xbutton;
 607     int x = 0, y = 0, h = bh, w, i, n;
 608 
 609     if (ev->window != win)
 610         return;
 611 
 612     /* right-click: exit */
 613     if (ev->button == Button3)
 614         exit(1);
 615 
 616     if (prompt && *prompt)
 617         x += promptw;
 618 
 619     /* input field */
 620     w = (lines > 0 || !matches) ? mw - x : inputw;
 621 
 622     /* left-click on input: clear input,
 623      * NOTE: if there is no left-arrow the space for < is reserved so
 624      *       add that to the input width */
 625     if (ev->button == Button1 &&
 626        ((lines <= 0 && ev->x >= 0 && ev->x <= x + w +
 627        ((!prev || !curr->left) ? TEXTW("<") : 0)) ||
 628        (lines > 0 && ev->y >= y && ev->y <= y + h))) {
 629         insert(NULL, -cursor);
 630         drawmenu();
 631         return;
 632     }
 633     /* middle-mouse click: paste selection */
 634     if (ev->button == Button2) {
 635         XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
 636                           utf8, utf8, win, CurrentTime);
 637         drawmenu();
 638         return;
 639     }
 640     /* scroll up */
 641     if (ev->button == Button4 && prev) {
 642         curr = prev;
 643 
 644         if (lines > 0)
 645             n = (lines - 1) * bh;
 646         else
 647             n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
 648 
 649         /* we need to seek the final element */
 650         for (i = 0, next = curr; next->right; next = next->right)
 651             if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
 652                 break;
 653         
 654         if(sel == next->right) {
 655             if(lines > 0) 
 656                 sel = next;
 657             else 
 658                 sel = next->left;
 659         }
 660 
 661         calcoffsets();
 662         drawmenu();
 663         return;
 664     }
 665     /* scroll down */
 666     if (ev->button == Button5 && next) {
 667         curr = curr->right;
 668 
 669         if(sel == curr->left)
 670             sel = curr;
 671 
 672         calcoffsets();
 673         drawmenu();
 674         return;
 675     }
 676     if (ev->button != Button1)
 677         return;
 678     /* disabled below, needs to be fixed */
 679     /*
 680     if (ev->state & ~ControlMask)
 681         return;
 682     */
 683     if (lines > 0) {
 684         /* vertical list: (ctrl)left-click on item */
 685         w = mw - x;
 686         for (item = curr; item != next; item = item->right) {
 687             y += h;
 688             if (ev->y >= y && ev->y <= (y + h)) {
 689                 puts(item->text);
 690                 if (!(ev->state & ControlMask))
 691                     exit(0);
 692                 sel = item;
 693                 if (sel) {
 694                     sel->out = 1;
 695                     drawmenu();
 696                 }
 697                 return;
 698             }
 699         }
 700     } else if (matches) {
 701         /* left-click on left arrow */
 702         x += inputw;
 703         w = TEXTW("<");
 704         if (prev && curr->left) {
 705             if (ev->x >= x && ev->x <= x + w) {
 706                 sel = curr = prev;
 707                 calcoffsets();
 708                 drawmenu();
 709                 return;
 710             }
 711         }
 712         /* horizontal list: (ctrl)left-click on item */
 713         for (item = curr; item != next; item = item->right) {
 714             x += w;
 715             w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
 716             if (ev->x >= x && ev->x <= x + w) {
 717                 puts(item->text);
 718                 if (!(ev->state & ControlMask))
 719                     exit(0);
 720                 sel = item;
 721                 if (sel) {
 722                     sel->out = 1;
 723                     drawmenu();
 724                 }
 725                 return;
 726             }
 727         }
 728         /* left-click on right arrow */
 729         w = TEXTW(">");
 730         x = mw - w;
 731         if (next && ev->x >= x && ev->x <= x + w) {
 732             sel = curr = next;
 733             calcoffsets();
 734             drawmenu();
 735             return;
 736         }
 737     }
 738 }
 739 
 740 static void
 741 mousemove(XEvent *e)
 742 {
 743     struct item *item;
 744     XPointerMovedEvent *ev = &e->xmotion;
 745     int x = 0, y = 0, h = bh, w;
 746 
 747     if (lines > 0) {
 748         w = mw - x;
 749         for (item = curr; item != next; item = item->right) {
 750             y += h;
 751             if (ev->y >= y && ev->y <= (y + h)) {
 752                 sel = item;
 753                 calcoffsets();
 754                 drawmenu();
 755                 return;
 756             }
 757         }
 758     } else if (matches) {
 759         x += inputw + promptw;
 760         w = TEXTW("<");
 761         for (item = curr; item != next; item = item->right) {
 762             x += w;
 763             w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
 764             if (ev->x >= x && ev->x <= x + w) {
 765                 sel = item;
 766                 calcoffsets();
 767                 drawmenu();
 768                 return;
 769             }
 770         }
 771     }
 772 }
 773 
 774 static void
 775 paste(void)
 776 {
 777     char *p, *q;
 778     int di;
 779     unsigned long dl;
 780     Atom da;
 781 
 782     /* we have been given the current selection, now insert it into input */
 783     if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
 784                        utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
 785         == Success && p) {
 786         insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
 787         XFree(p);
 788     }
 789     drawmenu();
 790 }
 791 
 792 static void
 793 readstdin(void)
 794 {
 795     char buf[sizeof text], *p;
 796     size_t i, imax = 0, size = 0;
 797     unsigned int tmpmax = 0;
 798 
 799     /* read each line from stdin and add it to the item list */
 800     for (i = 0; fgets(buf, sizeof buf, stdin); i++) {
 801         if (i + 1 >= size / sizeof *items)
 802             if (!(items = realloc(items, (size += BUFSIZ))))
 803                 die("cannot realloc %u bytes:", size);
 804         if ((p = strchr(buf, '\n')))
 805             *p = '\0';
 806         if (!(items[i].text = strdup(buf)))
 807             die("cannot strdup %u bytes:", strlen(buf) + 1);
 808         items[i].out = 0;
 809         drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL);
 810         if (tmpmax > inputw) {
 811             inputw = tmpmax;
 812             imax = i;
 813         }
 814     }
 815     if (items)
 816         items[i].text = NULL;
 817     inputw = items ? TEXTW(items[imax].text) : 0;
 818     lines = MIN(lines, i);
 819 }
 820 
 821 static void
 822 run(void)
 823 {
 824     XEvent ev;
 825 
 826     while (!XNextEvent(dpy, &ev)) {
 827         if (XFilterEvent(&ev, win))
 828             continue;
 829         switch(ev.type) {
 830         case DestroyNotify:
 831             if (ev.xdestroywindow.window != win)
 832                 break;
 833             cleanup();
 834             exit(1);
 835         case ButtonPress:
 836             buttonpress(&ev);
 837             break;
 838         case MotionNotify:
 839             mousemove(&ev);
 840             break;
 841         case Expose:
 842             if (ev.xexpose.count == 0)
 843                 drw_map(drw, win, 0, 0, mw, mh);
 844             break;
 845         case FocusIn:
 846             /* regrab focus from parent window */
 847             if (ev.xfocus.window != win)
 848                 grabfocus();
 849             break;
 850         case KeyPress:
 851             keypress(&ev.xkey);
 852             break;
 853         case SelectionNotify:
 854             if (ev.xselection.property == utf8)
 855                 paste();
 856             break;
 857         case VisibilityNotify:
 858             if (ev.xvisibility.state != VisibilityUnobscured)
 859                 XRaiseWindow(dpy, win);
 860             break;
 861         }
 862     }
 863 }
 864 
 865 static void
 866 setup(void)
 867 {
 868     int x, y, i, j;
 869     unsigned int du;
 870     XSetWindowAttributes swa;
 871     XIM xim;
 872     Window w, dw, *dws;
 873     XWindowAttributes wa;
 874     XClassHint ch = {"dmenu", "dmenu"};
 875 #ifdef XINERAMA
 876     XineramaScreenInfo *info;
 877     Window pw;
 878     int a, di, n, area = 0;
 879 #endif
 880     /* init appearance */
 881     for (j = 0; j < SchemeLast; j++)
 882         scheme[j] = drw_scm_create(drw, colors[j], 2);
 883 
 884     clip = XInternAtom(dpy, "CLIPBOARD",   False);
 885     utf8 = XInternAtom(dpy, "UTF8_STRING", False);
 886 
 887     /* calculate menu geometry */
 888     bh = drw->fonts->h + 2;
 889     bh = MAX(bh,lineheight);    /* make a menu line AT LEAST 'lineheight' tall */
 890     lines = MAX(lines, 0);
 891     mh = (lines + 1) * bh;
 892     promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
 893 #ifdef XINERAMA
 894     i = 0;
 895     if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
 896         XGetInputFocus(dpy, &w, &di);
 897         if (mon >= 0 && mon < n)
 898             i = mon;
 899         else if (w != root && w != PointerRoot && w != None) {
 900             /* find top-level window containing current input focus */
 901             do {
 902                 if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws)
 903                     XFree(dws);
 904             } while (w != root && w != pw);
 905             /* find xinerama screen with which the window intersects most */
 906             if (XGetWindowAttributes(dpy, pw, &wa))
 907                 for (j = 0; j < n; j++)
 908                     if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
 909                         area = a;
 910                         i = j;
 911                     }
 912         }
 913         /* no focused window is on screen, so use pointer location instead */
 914         if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
 915             for (i = 0; i < n; i++)
 916                 if (INTERSECT(x, y, 1, 1, info[i]) != 0)
 917                     break;
 918 
 919         if (centered) {
 920             //mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width);
 921             mw = (dmw>0 ? dmw : info[i].width);
 922             x = info[i].x_org + ((info[i].width  - mw) / 2);
 923             y = info[i].y_org + ((info[i].height - mh) / 2);
 924 //        x = info[i].x_org + dmx;
 925 //        y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy);
 926 //        mw = (dmw>0 ? dmw : info[i].width);
 927         } else {
 928             x = info[i].x_org;
 929             y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
 930             mw = info[i].width;
 931         }
 932 
 933         XFree(info);
 934     } else
 935 #endif
 936     {
 937         if (!XGetWindowAttributes(dpy, parentwin, &wa))
 938             die("could not get embedding window attributes: 0x%lx",
 939                 parentwin);
 940 
 941         if (centered) {
 942             x = dmx;
 943             y = topbar ? dmy : wa.height - mh - dmy;
 944             mw = (dmw>0 ? dmw : wa.width);
 945             //mw = MIN(MAX(max_textw() + promptw, min_width), wa.width);
 946             //x = (wa.width  - mw) / 2;
 947             //y = (wa.height - mh) / 2;
 948         } else {
 949             x = 0;
 950             y = topbar ? 0 : wa.height - mh;
 951             mw = wa.width;
 952         }
 953     }
 954     inputw = MIN(inputw, mw/3);
 955     match();
 956 
 957     /* create menu window */
 958     swa.override_redirect = True;
 959     swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
 960     swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask |
 961                      ButtonPressMask | PointerMotionMask;
 962     win = XCreateWindow(dpy, parentwin, x, y - (topbar ? 0 : border_width * 2), mw - border_width * 2, mh, border_width,
 963                         CopyFromParent, CopyFromParent, CopyFromParent,
 964                         CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
 965     if (border_width)
 966         XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
 967      XSetClassHint(dpy, win, &ch);
 968 
 969 
 970     /* input methods */
 971     if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
 972         die("XOpenIM failed: could not open input device");
 973 
 974     xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
 975                     XNClientWindow, win, XNFocusWindow, win, NULL);
 976 
 977     XMapRaised(dpy, win);
 978     if (embed) {
 979         XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
 980         if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
 981             for (i = 0; i < du && dws[i] != win; ++i)
 982                 XSelectInput(dpy, dws[i], FocusChangeMask);
 983             XFree(dws);
 984         }
 985         grabfocus();
 986     }
 987     drw_resize(drw, mw, mh);
 988     drawmenu();
 989 }
 990 
 991 static void
 992 usage(void)
 993 {
 994     fputs("usage: dmenu [-bfivS] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
 995           "             [-x xoffset] [-y yoffset] [-z width]\n"
 996           "             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
 997     exit(1);
 998 }
 999 
1000 int
1001 main(int argc, char *argv[])
1002 {
1003     XWindowAttributes wa;
1004     int i, fast = 0;
1005 
1006     for (i = 1; i < argc; i++)
1007         /* these options take no arguments */
1008         if (!strcmp(argv[i], "-v")) {      /* prints version information */
1009             puts("dmenu-"VERSION);
1010             exit(0);
1011         } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
1012             topbar = 0;
1013         else if (!strcmp(argv[i], "-f"))   /* grabs keyboard before reading stdin */
1014             fast = 1;
1015         else if (!strcmp(argv[i], "-F"))   /* grabs keyboard before reading stdin */
1016             fuzzy = 0;
1017         else if (!strcmp(argv[i], "-S"))   /* do not sort matches */
1018             sortmatches = false;
1019         else if (!strcmp(argv[i], "-c"))   /* centers dmenu on screen */
1020             centered = 1;
1021         else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
1022             fstrncmp = strncasecmp;
1023             fstrstr = cistrstr;
1024         } else if (i + 1 == argc)
1025             usage();
1026         /* these options take one argument */
1027         else if (!strcmp(argv[i], "-l"))   /* number of lines in vertical list */
1028             lines = atoi(argv[++i]);
1029         else if (!strcmp(argv[i], "-h")) { /* minimum height of one menu line */
1030             lineheight = atoi(argv[++i]);
1031             lineheight = MAX(lineheight, min_lineheight);
1032         }
1033         else if (!strcmp(argv[i], "-m"))
1034             mon = atoi(argv[++i]);
1035         else if (!strcmp(argv[i], "-p"))   /* adds prompt to left of input field */
1036             prompt = argv[++i];
1037         else if (!strcmp(argv[i], "-fn"))  /* font or font set */
1038             fonts[0] = argv[++i];
1039         else if (!strcmp(argv[i], "-nb"))  /* normal background color */
1040             colors[SchemeNorm][ColBg] = argv[++i];
1041         else if (!strcmp(argv[i], "-nf"))  /* normal foreground color */
1042             colors[SchemeNorm][ColFg] = argv[++i];
1043         else if (!strcmp(argv[i], "-sb"))  /* selected background color */
1044             colors[SchemeSel][ColBg] = argv[++i];
1045         else if (!strcmp(argv[i], "-sf"))  /* selected foreground color */
1046             colors[SchemeSel][ColFg] = argv[++i];
1047         else if (!strcmp(argv[i], "-w"))   /* embedding window id */
1048             embed = argv[++i];
1049         else if (!strcmp(argv[i], "-x"))   /* window x offset */
1050             dmx = atoi(argv[++i]);
1051         else if (!strcmp(argv[i], "-y"))   /* window y offset (from bottom up if -b) */
1052             dmy = atoi(argv[++i]);
1053         else if (!strcmp(argv[i], "-z"))   /* make dmenu this wide */
1054             dmw = atoi(argv[++i]);
1055         else if (!strcmp(argv[i], "-bw"))
1056             border_width = atoi(argv[++i]); /* border width */
1057         else
1058             usage();
1059 
1060     if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
1061         fputs("warning: no locale support\n", stderr);
1062     if (!(dpy = XOpenDisplay(NULL)))
1063         die("cannot open display");
1064     screen = DefaultScreen(dpy);
1065     root = RootWindow(dpy, screen);
1066     if (!embed || !(parentwin = strtol(embed, NULL, 0)))
1067         parentwin = root;
1068     if (!XGetWindowAttributes(dpy, parentwin, &wa))
1069         die("could not get embedding window attributes: 0x%lx",
1070             parentwin);
1071     drw = drw_create(dpy, screen, root, wa.width, wa.height);
1072     if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
1073         die("no fonts could be loaded.");
1074     lrpad = drw->fonts->h;
1075 
1076 #ifdef __OpenBSD__
1077     if (pledge("stdio rpath", NULL) == -1)
1078         die("pledge");
1079 #endif
1080 
1081     if (fast && !isatty(0)) {
1082         grabkeyboard();
1083         readstdin();
1084     } else {
1085         readstdin();
1086         grabkeyboard();
1087     }
1088     setup();
1089     run();
1090 
1091     return 1; /* unreachable */
1092 }