/* 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 "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 /* enums */ enum { SchemeNorm, SchemeSel, SchemeNormHighlight, SchemeSelHighlight, SchemeOut, SchemeLast }; /* color schemes */ struct item { char *text; char *text_output; struct item *left, *right; int out; double distance; }; static char numbers[NUMBERSBUFSIZE] = ""; static char text[BUFSIZ] = ""; static char *embed; static char separator; static int separator_greedy; static int separator_reverse; static int bh, mw, mh; static int inputw = 0, promptw; static int lrpad; /* sum of left and right padding */ 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 unsigned int max_lines = 0; static Atom clip, utf8; static Display *dpy; static Window root, parentwin, win; static XIC xic; static Drw *drw; static Clr *scheme[SchemeLast]; /* Xresources preferences */ enum resource_type { STRING = 0, INTEGER = 1, FLOAT = 2 }; typedef struct { char *name; enum resource_type type; void *dst; } ResourcePref; static void load_xresources(void); static void resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst); #include "config.h" /* static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; */ /* static char *(*fstrstr)(const char *, const char *) = strstr; */ static char *cistrstr(const char *s, const char *sub); static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp; static char *(*fstrstr)(const char *, const char *) = cistrstr; 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 * bh; */ n = lines * columns * 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; 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 *h, const char *n) { size_t i; if (!n[0]) return (char *)h; for (; *h; ++h) { for (i = 0; n[i] && tolower((unsigned char)n[i]) == tolower((unsigned char)h[i]); ++i) ; if (n[i] == '\0') return (char *)h; } return NULL; } static void drawhighlights(struct item *item, int x, int y, int maxw) { int i, indent; char *highlight; char c; if (!(strlen(item->text) && strlen(text))) return; drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); for (i = 0, highlight = item->text; *highlight && text[i];) { if (*highlight == text[i]) { /* get indentation */ c = *highlight; *highlight = '\0'; indent = TEXTW(item->text); *highlight = c; /* highlight character */ c = highlight[1]; highlight[1] = '\0'; drw_text(drw, x + indent - (lrpad / 2), y, MIN(maxw - indent, TEXTW(highlight) - lrpad), bh, 0, highlight, 0); highlight[1] = c; i++; } highlight++; } } static int drawitem(struct item *item, int x, int y, int w) { int r; if (item == sel) drw_setscheme(drw, scheme[SchemeSel]); else if (item->out) drw_setscheme(drw, scheme[SchemeOut]); else drw_setscheme(drw, scheme[SchemeNorm]); r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); drawhighlights(item, x, y, w); return r; } 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, w; drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, 0, 0, mw, mh, 1, 1); if (prompt && *prompt) { 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]); 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); } recalculatenumbers(); if (lines > 0) { /* draw grid */ int i = 0; for (item = curr; item != next; item = item->right, i++) drawitem(item, x + ((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(">"))); */ 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, 0, w, bh, lrpad / 2, ">", 0); */ 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 readstdin(FILE *stream); static void refreshoptions() { int dynlen = strlen(dynamic); int cmdlen = dynlen + 4; char *cmd; char *c; char *t = text; while (*t) cmdlen += *t++ == '\'' ? 4 : 1; cmd = malloc(cmdlen); if (cmd == NULL) die("cannot malloc %u bytes:", cmdlen); strcpy(cmd, dynamic); t = text; c = cmd + dynlen; *(c++) = ' '; *(c++) = '\''; while (*t) { // prefix ' with '\' if (*t == '\'') { *(c++) = '\''; *(c++) = '\\'; *(c++) = '\''; } *(c++) = *(t++); } *(c++) = '\''; *(c++) = 0; FILE *stream = popen(cmd, "r"); if (!stream) die("could not popen dynamic command (%s):", cmd); readstdin(stream); int r = pclose(stream); if (r == -1) die("could not pclose dynamic command"); free(cmd); } int compare_distance(const void *a, const void *b) { struct item *da = *(struct item **)a; struct item *db = *(struct item **)b; if (!db) return 1; if (!da) return -1; return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1; } void fuzzymatch(void) { /* bang - we have so much memory */ struct item *it; struct item **fuzzymatches = NULL; char c; int number_of_matches = 0, i, pidx, sidx, eidx; int text_len = strlen(text), itext_len; matches = matchend = NULL; /* walk through all items */ for (it = items; it && it->text; it++) { if (text_len) { itext_len = strlen(it->text); pidx = 0; /* pointer */ sidx = eidx = -1; /* start of match, end of match */ /* walk through item text */ for (i = 0; i < itext_len && (c = it->text[i]); i++) { /* fuzzy match pattern */ if (!fstrncmp(&text[pidx], &c, 1)) { if (sidx == -1) sidx = i; pidx++; if (pidx == text_len) { eidx = i; break; } } } /* build list of matches */ if (eidx != -1) { /* compute distance */ /* add penalty if match starts late (log(sidx+2)) * add penalty for long a match without many matching characters */ it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len); /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */ appenditem(it, &matches, &matchend); number_of_matches++; } } else { appenditem(it, &matches, &matchend); } } if (number_of_matches) { /* initialize array with matches */ if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item *)))) die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item *)); for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) { fuzzymatches[i] = it; } /* sort matches according to distance */ qsort(fuzzymatches, number_of_matches, sizeof(struct item *), compare_distance); /* rebuild list of matches */ matches = matchend = NULL; for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && it->text; i++, it = fuzzymatches[i]) { appenditem(it, &matches, &matchend); } free(fuzzymatches); } curr = sel = matches; calcoffsets(); } static void match(void) { if (fuzzy) { fuzzymatch(); return; } 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; if (dynamic) { refreshoptions(); matches = matchend = NULL; for (item = items; item && item->text; item++) appenditem(item, &matches, &matchend); curr = sel = matches; calcoffsets(); return; } 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(); } static void insert(const char *str, ssize_t n) { if (strlen(text) + n > sizeof text - 1) return; /* 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(); } 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; 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: case XK_KP_Left: movewordedge(-1); goto draw; case XK_Right: case XK_KP_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: case XK_KP_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: case XK_KP_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: case XK_KP_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; } case XK_KP_Left: if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; } if (lines > 0) return; /* fallthrough */ case XK_Up: case XK_KP_Up: if (sel && sel->left && (sel = sel->left)->right == curr) { curr = prev; calcoffsets(); } break; case XK_Next: case XK_KP_Next: if (!next) return; sel = curr = next; calcoffsets(); break; case XK_Prior: case XK_KP_Prior: if (!prev) return; sel = curr = prev; calcoffsets(); break; case XK_Return: case XK_KP_Enter: puts((sel && !(ev->state & ShiftMask)) ? sel->text_output : 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; } case XK_KP_Right: if (text[cursor] != '\0') { cursor = nextrune(+1); break; } if (lines > 0) return; /* fallthrough */ case XK_Down: case XK_KP_Down: if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); } break; case XK_Tab: if (!sel) return; strncpy(text, sel->text, sizeof text - 1); text[sizeof text - 1] = '\0'; cursor = strlen(text); match(); break; } draw: drawmenu(); } 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(FILE *stream) { 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, stream); 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); if (separator && (p = separator_greedy ? strrchr(items[i].text, separator) : strchr(items[i].text, separator))) { *p = '\0'; items[i].text_output = ++p; } else { items[i].text_output = items[i].text; } if (separator_reverse) { p = items[i].text; items[i].text = items[i].text_output; items[i].text_output = p; } items[i].out = 0; 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(max_lines, i); } void resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) { char *sdst = NULL; int *idst = NULL; float *fdst = NULL; sdst = dst; idst = dst; fdst = dst; char fullname[256]; char *type; XrmValue ret; snprintf(fullname, sizeof(fullname), "%s.%s", "dmenu", name); fullname[sizeof(fullname) - 1] = '\0'; XrmGetResource(db, fullname, "*", &type, &ret); if (!(ret.addr == NULL || strncmp("String", type, 64))) { switch (rtype) { case STRING: strcpy(sdst, ret.addr); break; case INTEGER: *idst = strtoul(ret.addr, NULL, 10); break; case FLOAT: *fdst = strtof(ret.addr, NULL); break; } } } void load_xresources(void) { Display *display; char *resm; XrmDatabase db; ResourcePref *p; display = XOpenDisplay(NULL); resm = XResourceManagerString(display); if (!resm) return; db = XrmGetStringDatabase(resm); for (p = resources; p < resources + LENGTH(resources); p++) resource_load(db, p->name, p->type, p->dst); XCloseDisplay(display); } static void run(void) { XEvent ev; 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: 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], 2); 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; } } /* 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; /* x = info[i].x_org; */ /* y = info[i].y_org + (topbar ? 0 : info[i].height - mh); */ /* mw = info[i].width; */ mw = MIN(MAX(max_textw() + promptw, 100), info[i].width); x = info[i].x_org + ((info[i].width - mw) / 2); y = info[i].y_org + ((info[i].height - mh) / 2); XFree(info); } else #endif { if (!XGetWindowAttributes(dpy, parentwin, &wa)) die("could not get embedding window attributes: 0x%lx", parentwin); /* x = 0; */ /* y = topbar ? 0 : wa.height - mh; */ /* mw = wa.width; */ mw = MIN(MAX(max_textw() + promptw, 100), wa.width); x = (wa.width - mw) / 2; y = (wa.height - mh) / 2; } 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.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; /* win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, */ 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"); 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 [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" " [-nb color] [-nf color] [-sb color] [-sf color]\n" " [-nhb color] [-nhf color] [-shb color] [-shf color]\n" " [-d separator] [-D separator] [-dy command] [-w " "windowid]\n", stderr); exit(1); } int main(int argc, char *argv[]) { XWindowAttributes wa; int i, fast = 0; XrmInitialize(); load_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], "-F")) /* grabs keyboard before reading stdin */ fuzzy = 0; else if (!strcmp(argv[i], "-s")) { /* case-sensitive item matching */ fstrncmp = strncmp; fstrstr = strstr; } else if (i + 1 == argc) usage(); /* these options take one argument */ 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], "-nhb")) /* normal hi background color */ colors[SchemeNormHighlight][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */ colors[SchemeNormHighlight][ColFg] = argv[++i]; else if (!strcmp(argv[i], "-shb")) /* selected hi background color */ colors[SchemeSelHighlight][ColBg] = argv[++i]; else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */ colors[SchemeSelHighlight][ColFg] = argv[++i]; else if (!strcmp(argv[i], "-d") || /* field separator */ (separator_greedy = !strcmp(argv[i], "-D"))) { separator = argv[++i][0]; separator_reverse = argv[i][1] == '|'; } else if (!strcmp(argv[i], "-w")) /* embedding window id */ embed = argv[++i]; else if (!strcmp(argv[i], "-dy")) /* dynamic command to run */ dynamic = argv[++i] && *argv[i] ? argv[i] : NULL; else usage(); 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); 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 max_lines = lines; if (fast && !isatty(0)) { grabkeyboard(); if (!dynamic) readstdin(stdin); } else { if (!dynamic) readstdin(stdin); grabkeyboard(); } setup(); run(); return 1; /* unreachable */ }