X-Git-Url: https://git.danieliu.xyz/?a=blobdiff_plain;f=dmenu.c;h=9a965e1c872a840dfcc13c5e9a4022fcfd950ed7;hb=HEAD;hp=9f29c19380a0e31da4839ab0a39615ec2a8e0a69;hpb=09db46f54fa671728498e14e8711b20301b266b7;p=dmenu.git diff --git a/dmenu.c b/dmenu.c index 9f29c19..9a965e1 100644 --- a/dmenu.c +++ b/dmenu.c @@ -1,827 +1,874 @@ /* See LICENSE file for copyright and license details. */ #include #include -#include #include #include #include #include +#include #include -#include + #include +#include #include #ifdef XINERAMA #include #endif +#include +#include + +#include "drw.h" +#include "util.h" /* macros */ -#define CLEANMASK(mask) (mask & ~(numlockmask | LockMask)) -#define INRECT(X,Y,RX,RY,RW,RH) ((X) >= (RX) && (X) < (RX) + (RW) && (Y) >= (RY) && (Y) < (RY) + (RH)) -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ + * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) /* enums */ -enum { ColFG, ColBG, ColLast }; - -/* typedefs */ -typedef struct { - int x, y, w, h; - unsigned long norm[ColLast]; - unsigned long sel[ColLast]; - Drawable drawable; - GC gc; - struct { - XFontStruct *xfont; - XFontSet set; - int ascent; - int descent; - int height; - } font; -} DC; /* draw context */ - -typedef struct Item Item; -struct Item { +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { char *text; - Item *next; /* traverses all items */ - Item *left, *right; /* traverses items matching current search pattern */ + struct item *left, *right; + int out; }; -/* forward declarations */ -static void appenditem(Item *i, Item **list, Item **last); -static void calcoffsetsh(void); -static void calcoffsetsv(void); -static char *cistrstr(const char *s, const char *sub); -static void cleanup(void); -static void drawcursor(void); -static void drawmenu(void); -static void drawmenuh(void); -static void drawmenuv(void); -static void drawtext(const char *text, unsigned long col[ColLast]); -static void eprint(const char *errstr, ...); -static unsigned long getcolor(const char *colstr); -static Bool grabkeyboard(void); -static void initfont(const char *fontstr); -static void kpress(XKeyEvent * e); -static void match(char *pattern); -static void readstdin(void); -static void run(void); -static void setup(Bool topbar); -static int textnw(const char *text, unsigned int len); -static int textw(const char *text); +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw; +static int lrpad; /* sum of left and right padding */ +static int reject_no_match = 0; +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +/* Temporary arrays to allow overriding xresources values */ +static char *colortemp[4]; +static char *tempfonts; #include "config.h" -/* variables */ -static char *maxname = NULL; -static char *prompt = NULL; -static char text[4096]; -static int cmdw = 0; -static int promptw = 0; -static int ret = 0; -static int cursor = 0; -static int screen; -static unsigned int mw, mh; -static unsigned int numlockmask = 0; -static Bool running = True; -static Display *dpy; -static DC dc; -static Item *allitems = NULL; /* first of all items */ -static Item *item = NULL; /* first of pattern matching items */ -static Item *sel = NULL; -static Item *next = NULL; -static Item *prev = NULL; -static Item *curr = NULL; -static Window root, win; -static int (*fstrncmp)(const char *, const char *, size_t n) = strncmp; +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static char *(*fstrstr)(const char *, const char *) = strstr; -static Bool vlist = False; -static unsigned int lines = 5; -static void (*calcoffsets)(void) = calcoffsetsh; -void -appenditem(Item *i, Item **list, Item **last) { - if(!(*last)) - *list = i; +static void +appenditem(struct item *item, struct item **list, struct item **last) +{ + if (*last) + (*last)->right = item; else - (*last)->right = i; - i->left = *last; - i->right = NULL; - *last = i; + *list = item; + + item->left = *last; + item->right = NULL; + *last = item; } -void -calcoffsetsh(void) { - int tw; - unsigned int w; +static void +calcoffsets(void) +{ + int i, n; - if(!curr) - return; - w = promptw + cmdw + 2 * spaceitem; - for(next = curr; next; next=next->right) { - tw = MIN(textw(next->text), mw / 3); - w += tw; - if(w > mw) + if (lines > 0) + n = lines * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ + for (i = 0, next = curr; next; next = next->right) + if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) break; - } - w = promptw + cmdw + 2 * spaceitem; - for(prev = curr; prev && prev->left; prev=prev->left) { - tw = MIN(textw(prev->left->text), mw / 3); - w += tw; - if(w > mw) + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) break; - } } -void -calcoffsetsv(void) { - static unsigned int h; +static int +max_textw(void) +{ + int len = 0; + for (struct item *item = items; item && item->text; item++) + len = MAX(TEXTW(item->text), len); + return len; +} - if(!curr) - return; - h = (dc.font.height + 2) * (lines + 1); - for(next = curr; next; next=next->right) { - h -= dc.font.height + 2; - if(h <= 0) - break; - } - h = (dc.font.height + 2) * (lines + 1); - for(prev = curr; prev && prev->left; prev=prev->left) { - h -= dc.font.height + 2; - if(h <= 0) - break; - } +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); } -char * -cistrstr(const char *s, const char *sub) { - int c, csub; - unsigned int len; - - if(!sub) - return (char *)s; - if((c = *sub++) != '\0') { - c = tolower(c); - len = strlen(sub); - do { - do { - if((csub = *s++) == '\0') - return NULL; - } - while(tolower(csub) != c); - } - while(strncasecmp(s, sub, len) != 0); - s--; - } - return (char *)s; +static char * +cistrstr(const char *s, const char *sub) +{ + size_t len; + + for (len = strlen(sub); *s; s++) + if (!strncasecmp(s, sub, len)) + return (char *)s; + return NULL; } -void -cleanup(void) { - Item *itm; - - while(allitems) { - itm = allitems->next; - free(allitems->text); - free(allitems); - allitems = itm; - } - if(dc.font.set) - XFreeFontSet(dpy, dc.font.set); +static int +drawitem(struct item *item, int x, int y, int w) +{ + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); else - XFreeFont(dpy, dc.font.xfont); - XFreePixmap(dpy, dc.drawable); - XFreeGC(dpy, dc.gc); - XDestroyWindow(dpy, win); - XUngrabKeyboard(dpy, CurrentTime); -} + drw_setscheme(drw, scheme[SchemeNorm]); -void -drawcursor(void) { - XRectangle r = { dc.x, dc.y + 2, 1, dc.font.height - 2 }; + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); +} - r.x += textnw(text, cursor) + dc.font.height / 2; +static void +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, w; - XSetForeground(dpy, dc.gc, dc.norm[ColFG]); - XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); -} + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); -void -drawmenu(void) { - dc.x = 0; - dc.y = 0; - dc.w = mw; - dc.h = mh; - drawtext(NULL, dc.norm); - /* print prompt? */ - if(promptw) { - dc.w = promptw; - drawtext(prompt, dc.sel); + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); } - dc.x += promptw; - dc.w = mw - promptw; - /* print command */ - if(cmdw && item && !vlist) - dc.w = cmdw; - drawtext(text[0] ? text : NULL, dc.norm); - drawcursor(); - dc.x += cmdw; - if(curr) { - if(vlist) - drawmenuv(); - else - drawmenuh(); + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); } - XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0); - XFlush(dpy); -} -void -drawmenuh(void) { - Item *i; - - dc.w = spaceitem; - drawtext((curr && curr->left) ? "<" : NULL, dc.norm); - dc.x += dc.w; - /* determine maximum items */ - for(i = curr; i != next; i=i->right) { - dc.w = MIN(textw(i->text), mw / 3); - drawtext(i->text, (sel == i) ? dc.sel : dc.norm); - dc.x += dc.w; + if (lines > 0) { + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } } - dc.x = mw - spaceitem; - dc.w = spaceitem; - drawtext(next ? ">" : NULL, dc.norm); + drw_map(drw, win, 0, 0, mw, mh); } -void -drawmenuv(void) { - Item *i; - - dc.x = 0; - dc.w = mw; - dc.y += dc.font.height + 2; - /* determine maximum items */ - for(i = curr; i != next; i=i->right) { - drawtext(i->text, (sel == i) ? dc.sel : dc.norm); - dc.y += dc.font.height + 2; +static void +grabfocus(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + nanosleep(&ts, NULL); } - drawtext(NULL, dc.norm); + die("cannot grab focus"); } -void -drawtext(const char *text, unsigned long col[ColLast]) { - char buf[256]; - int i, x, y, h, len, olen; - XRectangle r = { dc.x, dc.y, dc.w, dc.h }; - - XSetForeground(dpy, dc.gc, col[ColBG]); - XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); - if(!text) - return; - olen = strlen(text); - h = dc.font.height; - y = dc.y + ((h+2) / 2) - (h / 2) + dc.font.ascent; - x = dc.x + (h / 2); - /* shorten text if necessary */ - for(len = MIN(olen, sizeof buf); len && textnw(text, len) > dc.w - h; len--); - if(!len) +static void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) return; - memcpy(buf, text, len); - if(len < olen) - for(i = len; i && i > len - 3; buf[--i] = '.'); - XSetForeground(dpy, dc.gc, col[ColFG]); - if(dc.font.set) - XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, buf, len); - else - XDrawString(dpy, dc.drawable, dc.gc, x, y, buf, len); + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); } -void -eprint(const char *errstr, ...) { - va_list ap; - - va_start(ap, errstr); - vfprintf(stderr, errstr, ap); - va_end(ap); - exit(EXIT_FAILURE); +static void +match(void) +{ + static char **tokv = NULL; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) + if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + die("cannot realloc %u bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) /* not all tokens match */ + continue; + /* exact matches go first, then prefixes, then substrings */ + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); } -unsigned long -getcolor(const char *colstr) { - Colormap cmap = DefaultColormap(dpy, screen); - XColor color; - - if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) - eprint("error, cannot allocate color '%s'\n", colstr); - return color.pixel; -} +static void +insert(const char *str, ssize_t n) +{ + if (strlen(text) + n > sizeof text - 1) + return; -Bool -grabkeyboard(void) { - unsigned int len; + static char last[BUFSIZ] = ""; + if(reject_no_match) { + /* store last text value in case we need to revert it */ + memcpy(last, text, BUFSIZ); + } - for(len = 1000; len; len--) { - if(XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime) - == GrabSuccess) - break; - usleep(1000); + /* move existing text out of the way, insert new text, and update cursor */ + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); + + if(!matches && reject_no_match) { + /* revert to last text value if theres no match */ + memcpy(text, last, BUFSIZ); + cursor -= n; + match(); } - return len > 0; } -void -initfont(const char *fontstr) { - char *def, **missing; - int i, n; +static size_t +nextrune(int inc) +{ + ssize_t n; - if(!fontstr || fontstr[0] == '\0') - eprint("error, cannot load font: '%s'\n", fontstr); - missing = NULL; - dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def); - if(missing) - XFreeStringList(missing); - if(dc.font.set) { - XFontSetExtents *font_extents; - XFontStruct **xfonts; - char **font_names; - dc.font.ascent = dc.font.descent = 0; - font_extents = XExtentsOfFontSet(dc.font.set); - n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names); - for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) { - dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent); - dc.font.descent = MAX(dc.font.descent, (*xfonts)->descent); - xfonts++; - } - } - else { - if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)) - && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed"))) - eprint("error, cannot load font: '%s'\n", fontstr); - dc.font.ascent = dc.font.xfont->ascent; - dc.font.descent = dc.font.xfont->descent; + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +static void +movewordedge(int dir) +{ + if (dir < 0) { /* move cursor to the start of the word*/ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + } else { /* move cursor to the end of the word */ + while (text[cursor] && strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + while (text[cursor] && !strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); } - dc.font.height = dc.font.ascent + dc.font.descent; } -void -kpress(XKeyEvent * e) { - char buf[sizeof text]; - int i, num; - unsigned int len; +static void +keypress(XKeyEvent *ev) +{ + char buf[32]; + int len; KeySym ksym; + Status status; - len = strlen(text); - buf[0] = '\0'; - num = XLookupString(e, buf, sizeof buf, &ksym, NULL); - if(IsKeypadKey(ksym)) { - if(ksym == XK_KP_Enter) - ksym = XK_Return; - else if(ksym >= XK_KP_0 && ksym <= XK_KP_9) - ksym = (ksym - XK_KP_0) + XK_0; - } - if(IsFunctionKey(ksym) || IsKeypadKey(ksym) - || IsMiscFunctionKey(ksym) || IsPFKey(ksym) - || IsPrivateKeypadKey(ksym)) + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ return; - /* first check if a control mask is omitted */ - if(e->state & ControlMask) { - switch (ksym) { - default: /* ignore other control sequences */ - return; - case XK_a: - case XK_A: - ksym = XK_Home; - break; - case XK_c: - case XK_C: - ksym = XK_Escape; - break; - case XK_e: - case XK_E: - ksym = XK_End; - break; - case XK_h: - case XK_H: - ksym = XK_BackSpace; - break; - case XK_i: - case XK_I: - ksym = XK_Tab; - break; - case XK_j: - case XK_J: - ksym = XK_Return; - break; - case XK_u: - case XK_U: - memmove(text, text + cursor, sizeof text - cursor + 1); - cursor = 0; - match(text); - break; - case XK_w: - case XK_W: - if(cursor > 0) { - i = cursor; - while(i-- > 0 && text[i] == ' '); - while(i-- > 0 && text[i] != ' '); - memmove(text + i + 1, text + cursor, sizeof text - cursor + 1); - cursor = i + 1; - match(text); - } - break; - } + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + break; } - if(CLEANMASK(e->state) & Mod1Mask) { + + if (ev->state & ControlMask) { switch(ksym) { - default: return; - case XK_h: - ksym = XK_Left; + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: /* delete right */ + text[cursor] = '\0'; + match(); break; - case XK_l: - ksym = XK_Right; + case XK_u: /* delete left */ + insert(NULL, 0 - cursor); break; - case XK_j: - ksym = XK_Next; + case XK_w: /* delete word */ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); break; - case XK_k: - ksym = XK_Prior; - break; - case XK_g: - ksym = XK_Home; - break; - case XK_G: - ksym = XK_End; - break; - case XK_p: - { - FILE *fp; - char *s; - if(!(fp = (FILE*)popen("sselp", "r"))) - eprint("dmenu: Could not popen sselp\n"); - s = fgets(buf, sizeof buf, fp); - pclose(fp); - if(s == NULL) - return; - } - num = strlen(buf); - if(num && buf[num-1] == '\n') - buf[--num] = '\0'; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + movewordedge(-1); + goto draw; + case XK_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + case XK_g: ksym = XK_Home; break; + case XK_G: ksym = XK_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; } } + switch(ksym) { default: - num = MIN(num, sizeof text - cursor); - if(num && !iscntrl((int) buf[0])) { - memmove(text + cursor + num, text + cursor, sizeof text - cursor - num); - memcpy(text + cursor, buf, num); - cursor+=num; - match(text); - } - break; - case XK_BackSpace: - if(cursor > 0) { - memmove(text + cursor - 1, text + cursor, sizeof text - cursor + 1); - cursor--; - match(text); - } +insert: + if (!iscntrl(*buf)) + insert(buf, len); break; case XK_Delete: - memmove(text + cursor, text + cursor + 1, sizeof text - cursor); - match(text); + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); break; case XK_End: - if(cursor < len) { - cursor = len; + if (text[cursor] != '\0') { + cursor = strlen(text); break; } - while(next) { - sel = curr = next; + if (next) { + /* jump to end of list and position items in reverse */ + curr = matchend; + calcoffsets(); + curr = prev; calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); } - while(sel && sel->right) - sel = sel->right; + sel = matchend; break; case XK_Escape: - ret = 1; - running = False; - break; + cleanup(); + exit(1); case XK_Home: - if(sel == item) { + if (sel == matches) { cursor = 0; break; } - sel = curr = item; + sel = curr = matches; calcoffsets(); break; case XK_Left: - case XK_Up: - if(sel && sel->left){ - sel=sel->left; - if(sel->right == curr) { - curr = prev; - calcoffsets(); - } + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; } - else if(cursor > 0) - cursor--; - else + if (lines > 0) return; + /* fallthrough */ + case XK_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } break; case XK_Next: - if(!next) + if (!next) return; sel = curr = next; calcoffsets(); break; case XK_Prior: - if(!prev) + if (!prev) return; sel = curr = prev; calcoffsets(); break; case XK_Return: - if((e->state & ShiftMask) || !sel) - fprintf(stdout, "%s", text); - else - fprintf(stdout, "%s", sel->text); - fflush(stdout); - running = False; + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; break; case XK_Right: - case XK_Down: - if(cursor < len) - cursor++; - else if(sel && sel->right) { - sel=sel->right; - if(sel == next) { - curr = next; - calcoffsets(); - } + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; } - else + if (lines > 0) return; + /* fallthrough */ + case XK_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } break; case XK_Tab: - if(!sel) + if (!sel) return; - strncpy(text, sel->text, sizeof text); + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; cursor = strlen(text); - match(text); + match(); break; } + +draw: drawmenu(); } -void -match(char *pattern) { - unsigned int plen; - Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend; - - if(!pattern) - return; - plen = strlen(pattern); - item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL; - for(i = allitems; i; i = i->next) - if(!fstrncmp(pattern, i->text, plen + 1)) - appenditem(i, &lexact, &exactend); - else if(!fstrncmp(pattern, i->text, plen)) - appenditem(i, &lprefix, &prefixend); - else if(fstrstr(i->text, pattern)) - appenditem(i, &lsubstr, &substrend); - if(lexact) { - item = lexact; - itemend = exactend; - } - if(lprefix) { - if(itemend) { - itemend->right = lprefix; - lprefix->left = itemend; - } - else - item = lprefix; - itemend = prefixend; - } - if(lsubstr) { - if(itemend) { - itemend->right = lsubstr; - lsubstr->left = itemend; - } - else - item = lsubstr; +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); } - curr = prev = next = sel = item; - calcoffsets(); + drawmenu(); } -void -readstdin(void) { - char *p, buf[sizeof text]; - unsigned int len = 0, max = 0; - Item *i, *new; - - i = NULL; - while(fgets(buf, sizeof buf, stdin)) { - len = strlen(buf); - if(buf[len-1] == '\n') - buf[--len] = '\0'; - if(!(p = strdup(buf))) - eprint("fatal: could not strdup() %u bytes\n", len); - if(max < len || !maxname) { - maxname = p; - max = len; +static void +readstdin(void) +{ + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stdin); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(items[i].text = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf) + 1); + items[i].out = 0; + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); + if (tmpmax > inputw) { + inputw = tmpmax; + imax = i; } - if(!(new = (Item *)malloc(sizeof(Item)))) - eprint("fatal: could not malloc() %u bytes\n", sizeof(Item)); - new->next = new->left = new->right = NULL; - new->text = p; - if(!i) - allitems = new; - else - i->next = new; - i = new; } + if (items) + items[i].text = NULL; + inputw = items ? TEXTW(items[imax].text) : 0; + lines = MIN(lines, i); } -void -run(void) { +static void +run(void) +{ XEvent ev; - /* main event loop */ - while(running && !XNextEvent(dpy, &ev)) - switch (ev.type) { - default: /* ignore all crap */ + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); break; case KeyPress: - kpress(&ev.xkey); + keypress(&ev.xkey); break; - case Expose: - if(ev.xexpose.count == 0) - drawmenu(); + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); break; case VisibilityNotify: if (ev.xvisibility.state != VisibilityUnobscured) XRaiseWindow(dpy, win); break; } + } } -void -setup(Bool topbar) { - int i, j, x, y; -#if XINERAMA - int n; - XineramaScreenInfo *info = NULL; +static void +setup(void) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; #endif - XModifierKeymap *modmap; - XSetWindowAttributes wa; - - /* init modifier map */ - modmap = XGetModifierMapping(dpy); - for(i = 0; i < 8; i++) - for(j = 0; j < modmap->max_keypermod; j++) { - if(modmap->modifiermap[i * modmap->max_keypermod + j] - == XKeysymToKeycode(dpy, XK_Num_Lock)) - numlockmask = (1 << i); + /* init appearance */ + for (j = 0; j < SchemeLast; j++) { + scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2); + } + for (j = 0; j < SchemeOut; ++j) { + for (i = 0; i < 2; ++i) + free(colors[j][i]); + } + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } } - XFreeModifiermap(modmap); - - /* style */ - dc.norm[ColBG] = getcolor(normbgcolor); - dc.norm[ColFG] = getcolor(normfgcolor); - dc.sel[ColBG] = getcolor(selbgcolor); - dc.sel[ColFG] = getcolor(selfgcolor); - initfont(font); - - /* menu window */ - wa.override_redirect = True; - wa.background_pixmap = ParentRelative; - wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask | VisibilityChangeMask; - - /* menu window geometry */ - mh = dc.font.height + 2; - mh = vlist ? mh * (lines+1) : mh; -#if XINERAMA - if(XineramaIsActive(dpy) && (info = XineramaQueryScreens(dpy, &n))) { - i = 0; - if(n > 1) { - int di; - unsigned int dui; - Window dummy; - if(XQueryPointer(dpy, root, &dummy, &dummy, &x, &y, &di, &di, &dui)) - for(i = 0; i < n; i++) - if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height)) - break; + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i])) + break; + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + } else { + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; } - x = info[i].x_org; - y = topbar ? info[i].y_org : info[i].y_org + info[i].height - mh; - mw = info[i].width; + XFree(info); - } - else + } else #endif { - x = 0; - y = topbar ? 0 : DisplayHeight(dpy, screen) - mh; - mw = DisplayWidth(dpy, screen); + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } else { + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } } + inputw = MIN(inputw, mw/3); + match(); - win = XCreateWindow(dpy, root, x, y, mw, mh, 0, - DefaultDepth(dpy, screen), CopyFromParent, - DefaultVisual(dpy, screen), - CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); - - /* pixmap */ - dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen)); - dc.gc = XCreateGC(dpy, root, 0, NULL); - XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter); - if(!dc.font.set) - XSetFont(dpy, dc.gc, dc.font.xfont->fid); - if(maxname) - cmdw = MIN(textw(maxname), mw / 3); - if(prompt) - promptw = MIN(textw(prompt), mw / 5); - text[0] = '\0'; - match(text); - XMapRaised(dpy, win); -} + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); ++ XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + XSetClassHint(dpy, win, &ch); -int -textnw(const char *text, unsigned int len) { - XRectangle r; - if(dc.font.set) { - XmbTextExtents(dc.font.set, text, len, NULL, &r); - return r.width; + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); } - return XTextWidth(dc.font.xfont, text, len); + drw_resize(drw, mw, mh); + drawmenu(); } -int -textw(const char *text) { - return textnw(text, strlen(text)) + dc.font.height; +static void +usage(void) +{ + fputs("usage: dmenu [-bfirv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); } -int -main(int argc, char *argv[]) { - unsigned int i; - Bool topbar = True; +void +readxresources(void) { + XrmInitialize(); + + char* xrm; + if ((xrm = XResourceManagerString(drw->dpy))) { + char *type; + XrmDatabase xdb = XrmGetStringDatabase(xrm); + XrmValue xval; + + if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval)) + fonts[0] = strdup(xval.addr); + else + fonts[0] = strdup(fonts[0]); + if (XrmGetResource(xdb, "dmenu.background", "*", &type, &xval)) + colors[SchemeNorm][ColBg] = strdup(xval.addr); + else + colors[SchemeNorm][ColBg] = strdup(colors[SchemeNorm][ColBg]); + if (XrmGetResource(xdb, "dmenu.foreground", "*", &type, &xval)) + colors[SchemeNorm][ColFg] = strdup(xval.addr); + else + colors[SchemeNorm][ColFg] = strdup(colors[SchemeNorm][ColFg]); + if (XrmGetResource(xdb, "dmenu.selbackground", "*", &type, &xval)) + colors[SchemeSel][ColBg] = strdup(xval.addr); + else + colors[SchemeSel][ColBg] = strdup(colors[SchemeSel][ColBg]); + if (XrmGetResource(xdb, "dmenu.selforeground", "*", &type, &xval)) + colors[SchemeSel][ColFg] = strdup(xval.addr); + else + colors[SchemeSel][ColFg] = strdup(colors[SchemeSel][ColFg]); + + XrmDestroyDatabase(xdb); + } +} - /* command line args */ - for(i = 1; i < argc; i++) - if(!strcmp(argv[i], "-i")) { +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ + centered = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; fstrstr = cistrstr; - } - else if(!strcmp(argv[i], "-b")) - topbar = False; - else if(!strcmp(argv[i], "-e")) { - if(++i < argc) root = atoi(argv[i]); - } - else if(!strcmp(argv[i], "-l")) { - vlist = True; - calcoffsets = calcoffsetsv; - if(++i < argc) lines = atoi(argv[i]); - } - else if(!strcmp(argv[i], "-fn")) { - if(++i < argc) font = argv[i]; - } - else if(!strcmp(argv[i], "-nb")) { - if(++i < argc) normbgcolor = argv[i]; - } - else if(!strcmp(argv[i], "-nf")) { - if(++i < argc) normfgcolor = argv[i]; - } - else if(!strcmp(argv[i], "-p")) { - if(++i < argc) prompt = argv[i]; - } - else if(!strcmp(argv[i], "-sb")) { - if(++i < argc) selbgcolor = argv[i]; - } - else if(!strcmp(argv[i], "-sf")) { - if(++i < argc) selfgcolor = argv[i]; - } - else if(!strcmp(argv[i], "-v")) - eprint("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n"); + } else if (!strcmp(argv[i], "-r")) /* reject input which results in no match */ + reject_no_match = 1; + else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + tempfonts = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colortemp[0] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colortemp[1] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colortemp[2] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colortemp[3] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; else - eprint("usage: dmenu [-i] [-b] [-e ] [-l ] [-fn ] [-nb ]\n" - " [-nf ] [-p ] [-sb ] [-sf ] [-v]\n"); - if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) - fprintf(stderr, "warning: no locale support\n"); - if(!(dpy = XOpenDisplay(NULL))) - eprint("dmenu: cannot open display\n"); - screen = DefaultScreen(dpy); - if(!root) - root = RootWindow(dpy, screen); + usage(); - readstdin(); - running = grabkeyboard(); + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + readxresources(); + /* Now we check whether to override xresources with commandline parameters */ + if ( tempfonts ) + fonts[0] = strdup(tempfonts); + if ( colortemp[0]) + colors[SchemeNorm][ColBg] = strdup(colortemp[0]); + if ( colortemp[1]) + colors[SchemeNorm][ColFg] = strdup(colortemp[1]); + if ( colortemp[2]) + colors[SchemeSel][ColBg] = strdup(colortemp[2]); + if ( colortemp[3]) + colors[SchemeSel][ColFg] = strdup(colortemp[3]); + + if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + + free(fonts[0]); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif - setup(topbar); - drawmenu(); - XSync(dpy, False); + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); run(); - cleanup(); - XCloseDisplay(dpy); - return ret; + + return 1; /* unreachable */ }