/* See LICENSE file for copyright and license details. */ #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 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) #define NUMBERSMAXDIGITS 100 #define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) +1 /* define opaqueness */ #define OPAQUE 0xFFU /* enums */ enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ struct item { char *text; struct item *left, *right; int out; int index; }; static char numbers[NUMBERSBUFSIZE] = ""; static char text[BUFSIZ] = ""; static char *embed; static int bh, mw, mh; static int inputw = 0, promptw, passwd = 0; 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 int print_index = 0; static bool sortmatches = true; static Atom clip, utf8; static Display *dpy; static Window root, parentwin, win; static XIC xic; static Drw *drw; static int usergb = 0; static Visual *visual; static int depth; static Colormap cmap; static Clr *scheme[SchemeLast]; #include "config.h" static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static char *(*fstrstr)(const char *, const char *) = strstr; static unsigned int textw_clamp(const char *str, unsigned int n) { unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; return MIN(w, n); } static void xinitvisual() { XVisualInfo *infos; XRenderPictFormat *fmt; int nitems; int i; XVisualInfo tpl = { .screen = screen, .depth = 32, .class = TrueColor }; long masks = VisualScreenMask | VisualDepthMask | VisualClassMask; infos = XGetVisualInfo(dpy, masks, &tpl, &nitems); visual = NULL; for (i = 0; i < nitems; i++){ fmt = XRenderFindVisualFormat(dpy, infos[i].visual); if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { visual = infos[i].visual; depth = infos[i].depth; cmap = XCreateColormap(dpy, root, visual, AllocNone); usergb = 1; break; } } XFree(infos); if (! visual) { visual = DefaultVisual(dpy, screen); depth = DefaultDepth(dpy, screen); cmap = DefaultColormap(dpy, screen); } } static void appenditem(struct item *item, struct item **list, struct item **last) { if (*last) (*last)->right = item; else *list = item; item->left = *last; item->right = NULL; *last = item; } static void calcoffsets(void) { int i, n; if (lines > 0) n = lines * columns * bh; else n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">") + TEXTW(numbers)); /* 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; for (i = 0, prev = curr; prev && prev->left; prev = prev->left) 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) { 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); } 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; } 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 drw_setscheme(drw, scheme[SchemeNorm]); return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); } static void recalculatenumbers() { unsigned int numer = 0, denom = 0; struct item *item; if (matchend) { numer++; for (item = matchend; item && item->left; item = item->left) numer++; } for (item = items; item && item->text; item++) denom++; snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); } static void drawmenu(void) { unsigned int curpos; struct item *item; int x = 0, y = 0, fh = drw->fonts->h, w; char *censort; drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, 0, 0, mw, mh, 1, 1); if (prompt && *prompt) { if (colorprompt) drw_setscheme(drw, scheme[SchemeSel]); x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); } /* draw input field */ w = (lines > 0 || !matches) ? mw - x : inputw; drw_setscheme(drw, scheme[SchemeNorm]); if (passwd) { censort = ecalloc(1, sizeof(text)); memset(censort, '.', strlen(text)); drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0); free(censort); } else 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 + (bh - fh) / 2, 2, fh - 4, 1, 0); } recalculatenumbers(); if (lines > 0) { /* draw grid */ int i = 0; for (item = curr; item != next; item = item->right, i++) drawitem( item, ((i / lines) * ((mw -x) / columns)), y + (((i % lines) + 1) * bh), (mw - x) / columns ); } 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(">") - TEXTW(numbers))); if (next) { w = TEXTW(">"); drw_setscheme(drw, scheme[SchemeNorm]); drw_text(drw, mw - w - TEXTW(numbers), 0, w, bh, lrpad / 2, ">", 0); } } drw_setscheme(drw, scheme[SchemeNorm]); drw_text(drw, mw - TEXTW(numbers), 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0); 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, GrabModeAsync, CurrentTime) == GrabSuccess) return; nanosleep(&ts, NULL); } die("cannot grab keyboard"); } 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; if (use_prefix) { matches = lprefix = matchend = prefixend = NULL; textsize = strlen(text); } else { 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; if (!sortmatches) appenditem(item, &matches, &matchend); else { /* 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 if (!use_prefix) appenditem(item, &lsubstr, &substrend); } } if (lprefix) { if (matches) { matchend->right = lprefix; lprefix->left = matchend; } else matches = lprefix; matchend = prefixend; } if (!use_prefix && lsubstr) { if (matches) { matchend->right = lsubstr; lsubstr->left = matchend; } else matches = lsubstr; matchend = substrend; } curr = sel = matches; calcoffsets(); } static void 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 nextrune(int inc) { ssize_t n; /* 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); } } static void keypress(XKeyEvent *ev) { char buf[32]; int len; struct item * item; KeySym ksym; Status status; int i, offscreen = 0; struct item *tmpsel; len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); switch (status) { default: /* XLookupNone, XBufferOverflow */ return; 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; 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_u: /* delete left */ insert(NULL, 0 - cursor); break; 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_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: insert: if (!iscntrl(*buf)) insert(buf, len); break; case XK_Delete: 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 (text[cursor] != '\0') { cursor = strlen(text); break; } if (next) { /* jump to end of list and position items in reverse */ curr = matchend; calcoffsets(); curr = prev; calcoffsets(); while (next && (curr = curr->right)) calcoffsets(); } sel = matchend; break; case XK_Escape: cleanup(); exit(1); case XK_Home: if (sel == matches) { cursor = 0; break; } sel = curr = matches; calcoffsets(); break; case XK_Left: if (columns > 1) { if (!sel) return; tmpsel = sel; for (i = 0; i < lines; i++) { if (!tmpsel->left || tmpsel->left->right != tmpsel) { if (offscreen) break; return; } if (tmpsel == curr) offscreen = 1; tmpsel = tmpsel->left; } sel = tmpsel; if (offscreen) { curr = prev; calcoffsets(); } break; } if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; } 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) return; sel = curr = next; calcoffsets(); break; case XK_Prior: if (!prev) return; sel = curr = prev; calcoffsets(); break; case XK_Return: case XK_KP_Enter: if (print_index) printf("%d\n", (sel && !(ev->state & ShiftMask)) ? sel->index : -1); else puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); if (!(ev->state & ControlMask)) { cleanup(); exit(0); } if (sel) sel->out = 1; break; case XK_Right: if (columns > 1) { if (!sel) return; tmpsel = sel; for (i = 0; i < lines; i++) { if (!tmpsel->right || tmpsel->right->left != tmpsel) { if (offscreen) break; return; } tmpsel = tmpsel->right; if (tmpsel == next) offscreen = 1; } sel = tmpsel; if (offscreen) { curr = next; calcoffsets(); } break; } if (text[cursor] != '\0') { cursor = nextrune(+1); break; } if (lines > 0) return; /* fallthrough */ case XK_Down: if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); } break; case XK_Tab: if (!matches) break; /* cannot complete no matches */ strncpy(text, matches->text, sizeof text - 1); text[sizeof text - 1] = '\0'; len = cursor = strlen(text); /* length of longest common prefix */ for (item = matches; item && item->text; item = item->right) { cursor = 0; while (cursor < len && text[cursor] == item->text[cursor]) cursor++; len = cursor; } memset(text + len, '\0', strlen(text) - len); break; } draw: drawmenu(); } static void buttonpress(XEvent *e) { struct item *item; XButtonPressedEvent *ev = &e->xbutton; int x = 0, y = 0, h = bh, w, item_num = 0; if (ev->window != win) return; /* right-click: exit */ if (ev->button == Button3) exit(1); if (prompt && *prompt) x += promptw; /* input field */ w = (lines > 0 || !matches) ? mw - x : inputw; /* left-click on input: clear input, * NOTE: if there is no left-arrow the space for < is reserved so * add that to the input width */ if (ev->button == Button1 && ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + ((!prev || !curr->left) ? TEXTW("<") : 0)) || (lines > 0 && ev->y >= y && ev->y <= y + h))) { insert(NULL, -cursor); drawmenu(); return; } /* middle-mouse click: paste selection */ if (ev->button == Button2) { XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, utf8, utf8, win, CurrentTime); drawmenu(); return; } /* scroll up */ if (ev->button == Button4 && prev) { sel = curr = prev; calcoffsets(); drawmenu(); return; } /* scroll down */ if (ev->button == Button5 && next) { sel = curr = next; calcoffsets(); drawmenu(); return; } if (ev->button != Button1) return; if (ev->state & ~ControlMask) return; if (lines > 0) { /* vertical list: (ctrl)left-click on item */ w = mw - x; for (item = curr; item != next; item = item->right) { if (item_num++ == lines) { item_num = 1; x += w / columns; y = 0; } y += h; if (ev->y >= y && ev->y <= (y + h)) { puts(item->text); if (!(ev->state & ControlMask)) exit(0); sel = item; if (sel) { sel->out = 1; drawmenu(); } return; } } } else if (matches) { /* left-click on left arrow */ x += inputw; w = TEXTW("<"); if (prev && curr->left) { if (ev->x >= x && ev->x <= x + w) { sel = curr = prev; calcoffsets(); drawmenu(); return; } } /* horizontal list: (ctrl)left-click on item */ for (item = curr; item != next; item = item->right) { x += w; w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); if (ev->x >= x && ev->x <= x + w) { puts(item->text); if (!(ev->state & ControlMask)) exit(0); sel = item; if (sel) { sel->out = 1; drawmenu(); } return; } } /* left-click on right arrow */ w = TEXTW(">"); x = mw - w; if (next && ev->x >= x && ev->x <= x + w) { sel = curr = next; calcoffsets(); drawmenu(); return; } } } static void motionevent(XButtonEvent *ev) { struct item *it; int xy, ev_xy; if (ev->window != win || matches == 0) return; xy = lines > 0 ? bh : inputw + promptw + TEXTW("<"); ev_xy = lines > 0 ? ev->y : ev->x; for (it = curr; it && it != next; it = it->right) { int wh = lines > 0 ? bh : textw_clamp(it->text, mw - xy - TEXTW(">")); if (ev_xy >= xy && ev_xy < (xy + wh)) { sel = it; calcoffsets(); drawmenu(); break; } xy += wh; } } 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); } drawmenu(); } static void readstdin(void) { char buf[sizeof text], *p; size_t i, imax = 0, size = 0; unsigned int tmpmax = 0; if(passwd){ inputw = lines = 0; return; } /* 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; items[i].index = i; drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); if (tmpmax > inputw) { inputw = tmpmax; imax = i; } } if (items) items[i].text = NULL; inputw = items ? TEXTW(items[imax].text) : 0; lines = MIN(lines, i); } static void run(void) { XEvent ev; int i; while (!XNextEvent(dpy, &ev)) { if (preselected) { for (i = 0; i < preselected; i++) { if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); } } drawmenu(); preselected = 0; } if (XFilterEvent(&ev, win)) continue; switch(ev.type) { case MotionNotify: motionevent(&ev.xbutton); break; case DestroyNotify: if (ev.xdestroywindow.window != win) break; cleanup(); exit(1); case ButtonPress: buttonpress(&ev); break; 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; case SelectionNotify: if (ev.xselection.property == utf8) paste(); break; case VisibilityNotify: if (ev.xvisibility.state != VisibilityUnobscured) XRaiseWindow(dpy, win); break; } } } 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 /* init appearance */ for (j = 0; j < SchemeLast; j++) scheme[j] = drw_scm_create(drw, colors[j], alphas[j], 2); clip = XInternAtom(dpy, "CLIPBOARD", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False); /* calculate menu geometry */ bh = drw->fonts->h + 2; bh = MAX(bh, lineheight); /* make a menu line at least 'lineheight' tall */ 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; } } /* 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; } XFree(info); } else #endif { 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(); /* create menu window */ swa.override_redirect = True; swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; swa.border_pixel = 0; swa.colormap = cmap; swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask | PointerMotionMask; win = XCreateWindow(dpy, parentwin, x, y - (topbar ? 0: border_width * 2), mw - border_width * 2, mh, border_width, depth, InputOutput, visual, CWOverrideRedirect | CWBackPixel | CWColormap | CWEventMask | CWBorderPixel, &swa); XSetClassHint(dpy, win, &ch); /* 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(); } drw_resize(drw, mw, mh); drawmenu(); } static void usage(void) { fputs("usage: dmenu [-bfiPrSvx] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n" " [-n number] [-bw width] [-h height]\n", stderr); exit(1); } void read_Xresources(void) { XrmInitialize(); char* xrm; if ((xrm = XResourceManagerString(drw->dpy))) { char *type; XrmDatabase xdb = XrmGetStringDatabase(xrm); XrmValue xval; if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval) == True) /* font or font set */ fonts[0] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.color0", "*", &type, &xval) == True) /* normal background color */ colors[SchemeNorm][ColBg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.color4", "*", &type, &xval) == True) /* normal foreground color */ colors[SchemeNorm][ColFg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.color4", "*", &type, &xval) == True) /* selected background color */ colors[SchemeSel][ColBg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.color0", "*", &type, &xval) == True) /* selected foreground color */ colors[SchemeSel][ColFg] = strdup(xval.addr); XrmDestroyDatabase(xdb); } } int main(int argc, char *argv[]) { XWindowAttributes wa; int i, fast = 0; 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); xinitvisual(); drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); read_Xresources(); 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], "-S")) /* do not sort matches */ sortmatches = false; 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], "-P")) /* is the input a password */ passwd = 1; else if (!strcmp(argv[i], "-ix")) /* adds ability to return index in list */ print_index = 1; else if (!strcmp(argv[i], "-r")) /* reject input which results in no match */ reject_no_match = 1; else if (!strcmp(argv[i], "-x")) /* invert use_prefix */ use_prefix = !use_prefix; else if (i + 1 == argc) usage(); /* these options take one argument */ else if (!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ lineheight = atoi(argv[++i]); lineheight = MAX(lineheight, min_lineheight); } else if (!strcmp(argv[i], "-g")) { /* number of columns in grid */ columns = atoi(argv[++i]); if (lines == 0) lines = 1; } else if (!strcmp(argv[i], "-l")) { /* number of lines in grid */ lines = atoi(argv[++i]); if (columns == 0) columns = 1; } 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 */ fonts[0] = argv[++i]; else if (!strcmp(argv[i], "-nb")) /* normal background color */ colors[SchemeNorm][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ colors[SchemeNorm][ColFg] = argv[++i]; else if (!strcmp(argv[i], "-sb")) /* selected background color */ colors[SchemeSel][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ colors[SchemeSel][ColFg] = argv[++i]; else if (!strcmp(argv[i], "-w")) /* embedding window id */ embed = argv[++i]; else if (!strcmp(argv[i], "-bw")) /* border width */ border_width = atoi(argv[++i]); else if (!strcmp(argv[i], "-n")) /* preselected item */ preselected = atoi(argv[++i]); else usage(); if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) die("no fonts could be loaded."); lrpad = drw->fonts->h; #ifdef __OpenBSD__ if (pledge("stdio rpath", NULL) == -1) die("pledge"); #endif if (fast && !isatty(0)) { grabkeyboard(); readstdin(); } else { readstdin(); grabkeyboard(); } setup(); run(); return 1; /* unreachable */ }