X-Git-Url: https://git.danieliu.xyz/?p=dmenu.git;a=blobdiff_plain;f=dmenu.c;h=9a965e1c872a840dfcc13c5e9a4022fcfd950ed7;hp=e926ecac26ec61f67f1add2e7ed12a6789f3ca50;hb=HEAD;hpb=76eb5783046cf89ba6b7e9f8cdab35fe86d07a82 diff --git a/dmenu.c b/dmenu.c index e926eca..9a965e1 100644 --- a/dmenu.c +++ b/dmenu.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,7 @@ #include #endif #include +#include #include "drw.h" #include "util.h" @@ -34,10 +36,11 @@ struct item { }; static char text[BUFSIZ] = ""; +static char *embed; static int bh, mw, mh; -static int sw, sh; /* X display screen geometry width, height */ 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; @@ -46,12 +49,16 @@ static int mon = -1, screen; static Atom clip, utf8; static Display *dpy; -static Window root, win; +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" static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; @@ -81,13 +88,22 @@ calcoffsets(void) 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 : TEXTW(next->text)) > n) + if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) break; for (i = 0, prev = curr; prev && prev->left; prev = prev->left) - if ((i += (lines > 0) ? bh : TEXTW(prev->left->text)) > n) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) break; } +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; +} + static void cleanup(void) { @@ -144,7 +160,7 @@ drawmenu(void) drw_setscheme(drw, scheme[SchemeNorm]); drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); - drw_font_getexts(drw->fonts, text, cursor, &curpos, NULL); + 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); @@ -174,12 +190,31 @@ drawmenu(void) drw_map(drw, win, 0, 0, mw, mh); } +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); + } + die("cannot grab focus"); +} + static void grabkeyboard(void) { struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; int i; + if (embed) + return; /* 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, @@ -187,7 +222,7 @@ grabkeyboard(void) return; nanosleep(&ts, NULL); } - die("cannot grab keyboard\n"); + die("cannot grab keyboard"); } static void @@ -205,11 +240,11 @@ match(void) /* 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\n", 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); + textsize = strlen(text) + 1; for (item = items; item && item->text; item++) { for (i = 0; i < tokc; i++) if (!fstrstr(item->text, tokv[i])) @@ -249,12 +284,26 @@ insert(const char *str, ssize_t n) { if (strlen(text) + n > sizeof text - 1) return; + + static char last[BUFSIZ] = ""; + if(reject_no_match) { + /* store last text value in case we need to revert it */ + memcpy(last, text, BUFSIZ); + } + /* 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(); + } } static size_t @@ -268,18 +317,42 @@ nextrune(int 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); + } +} + static void keypress(XKeyEvent *ev) { char buf[32]; int len; - KeySym ksym = NoSymbol; + KeySym ksym; Status status; len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); - if (status == XBufferOverflow) + switch (status) { + default: /* XLookupNone, XBufferOverflow */ return; - if (ev->state & ControlMask) + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + break; + } + + if (ev->state & ControlMask) { switch(ksym) { case XK_a: ksym = XK_Home; break; case XK_b: ksym = XK_Left; break; @@ -315,6 +388,12 @@ keypress(XKeyEvent *ev) 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; @@ -324,8 +403,14 @@ keypress(XKeyEvent *ev) default: return; } - else if (ev->state & Mod1Mask) + } 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; @@ -335,8 +420,11 @@ keypress(XKeyEvent *ev) default: return; } + } + switch(ksym) { default: +insert: if (!iscntrl(*buf)) insert(buf, len); break; @@ -436,6 +524,8 @@ keypress(XKeyEvent *ev) match(); break; } + +draw: drawmenu(); } @@ -448,10 +538,12 @@ paste(void) Atom da; /* we have been given the current selection, now insert it into input */ - XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, - utf8, &da, &di, &dl, &dl, (unsigned char **)&p); - insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); - XFree(p); + 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); + } drawmenu(); } @@ -493,10 +585,20 @@ run(void) 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: keypress(&ev.xkey); break; @@ -515,21 +617,26 @@ run(void) static void setup(void) { - int x, y; + 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 w, pw, dw, *dws; - XWindowAttributes wa; - int a, j, di, n, i = 0, area = 0; - unsigned int du; + Window pw; + int a, di, n, area = 0; #endif - /* init appearance */ - scheme[SchemeNorm] = drw_scm_create(drw, colors[SchemeNorm], 2); - scheme[SchemeSel] = drw_scm_create(drw, colors[SchemeSel], 2); - scheme[SchemeOut] = drw_scm_create(drw, colors[SchemeOut], 2); + 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); @@ -538,10 +645,12 @@ setup(void) bh = drw->fonts->h + 2; lines = MAX(lines, 0); mh = (lines + 1) * bh; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; #ifdef XINERAMA - if ((info = XineramaQueryScreens(dpy, &n))) { + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { XGetInputFocus(dpy, &w, &di); - if (mon != -1 && mon < n) + if (mon >= 0 && mon < n) i = mon; else if (w != root && w != PointerRoot && w != None) { /* find top-level window containing current input focus */ @@ -558,23 +667,39 @@ setup(void) } } /* no focused window is on screen, so use pointer location instead */ - if (mon == -1 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + 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; - x = info[i].x_org; - y = info[i].y_org + (topbar ? 0 : info[i].height - mh); - mw = info[i].width; + 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; + } + XFree(info); } else #endif { - x = 0; - y = topbar ? 0 : sh - mh; - mw = sw; + 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; + } } - promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; inputw = MIN(inputw, mw/3); match(); @@ -582,17 +707,30 @@ setup(void) swa.override_redirect = True; swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; - win = XCreateWindow(dpy, root, x, y, mw, mh, 0, - DefaultDepth(dpy, screen), CopyFromParent, - DefaultVisual(dpy, screen), + 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); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); - /* open input methods */ - xim = XOpenIM(dpy, NULL, NULL, NULL); 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(); + } drw_resize(drw, mw, mh); drawmenu(); } @@ -600,14 +738,50 @@ setup(void) static void usage(void) { - fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" - " [-nb color] [-nf color] [-sb color] [-sf color] [-v]\n", stderr); + 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); } +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); + } +} + int main(int argc, char *argv[]) { + XWindowAttributes wa; int i, fast = 0; for (i = 1; i < argc; i++) @@ -619,10 +793,14 @@ main(int argc, char *argv[]) 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 (i + 1 == argc) + } 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 */ @@ -632,32 +810,57 @@ main(int argc, char *argv[]) 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 */ - fonts[0] = argv[++i]; + tempfonts = argv[++i]; else if (!strcmp(argv[i], "-nb")) /* normal background color */ - colors[SchemeNorm][ColBg] = argv[++i]; + colortemp[0] = argv[++i]; else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ - colors[SchemeNorm][ColFg] = argv[++i]; + colortemp[1] = argv[++i]; else if (!strcmp(argv[i], "-sb")) /* selected background color */ - colors[SchemeSel][ColBg] = argv[++i]; + colortemp[2] = argv[++i]; else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ - colors[SchemeSel][ColFg] = argv[++i]; + colortemp[3] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; else usage(); if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("warning: no locale support\n", stderr); if (!(dpy = XOpenDisplay(NULL))) - die("cannot open display\n"); + die("cannot open display"); screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); - sw = DisplayWidth(dpy, screen); - sh = DisplayHeight(dpy, screen); - drw = drw_create(dpy, screen, root, sw, sh); - if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) - die("no fonts could be loaded.\n"); + 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; - if (fast) { +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { grabkeyboard(); readstdin(); } else {