/* See LICENSE file for copyright and license details. */ #include #ifndef _EVDEVK #define _EVDEVK(_v) (0x10081000 + (_v)) #endif #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 /* enums */ enum { SchemeNorm, SchemeSel, SchemeOut, SchemeNormHighlight, SchemeSelHighlight, SchemeLast }; /* color schemes */ struct item { char *text; struct item *left, *right; int out; }; static char numbers[NUMBERSBUFSIZE] = ""; 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 size_t cursor; static struct item *items = NULL; static struct item *matches, *matchend; static struct item *prev, *curr, *next, *sel, *nav; static int mon = -1, screen; static int use_text_input = 0; static int initialindex; static int managed = 0; static Atom clip, utf8; static Display *dpy; static Window root, parentwin, win; static XIC xic; static Drw *drw; static Clr *scheme[SchemeLast]; /* Empty arrays to be filled from command line, Xresources, and defaults, in decreasing order of precedence */ static char *colors[SchemeLast][2]; static const char *fonts[3] = { NULL, NULL, NULL }; #include "config.h" static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static char *(*fstrstr)(const char *, const char *) = strstr; 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; 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 Window prevfocuswin = None; static int ignore_xerror(Display *d, XErrorEvent *ee) { (void)d; (void)ee; return 0; } static void cleanup(void) { size_t i; if (xic) { XUnsetICFocus(xic); XDestroyIC(xic); xic = NULL; } XUngrabKey(dpy, AnyKey, AnyModifier, root); XSetErrorHandler(ignore_xerror); if (prevfocuswin && prevfocuswin != win && prevfocuswin != PointerRoot) XSetInputFocus(dpy, prevfocuswin, RevertToParent, CurrentTime); 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 void drawhighlights(struct item *item, int x, int y, int maxw) { char restorechar, tokens[sizeof text], *highlight, *token; int indentx, highlightlen; drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); strcpy(tokens, text); for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) { highlight = fstrstr(item->text, token); while (highlight) { // Move item str end, calc width for highlight indent, & restore highlightlen = highlight - item->text; restorechar = *highlight; item->text[highlightlen] = '\0'; indentx = TEXTW(item->text); item->text[highlightlen] = restorechar; // Move highlight str end, draw highlight, & restore restorechar = highlight[strlen(token)]; highlight[strlen(token)] = '\0'; drw_text( drw, x + indentx - (lrpad / 2) - 1, y, MIN(maxw - indentx, TEXTW(highlight) - lrpad), bh, 0, highlight, 0 ); highlight[strlen(token)] = restorechar; if (strlen(highlight) - strlen(token) < strlen(token)) break; highlight = fstrstr(highlight + strlen(token), token); } } } 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]); int 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++; if (numer == denom) snprintf(numbers, NUMBERSBUFSIZE, "%d", numer); else snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); } static void drawmenu(void) { static int curpos, oldcurlen; struct item *item; int x = 0, y = 0, fh = drw->fonts->h, w; int curlen, rcurlen; drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, 0, 0, mw, mh, 1, 1); /* draw top search bar background in SchemeOut (#383838) */ drw_setscheme(drw, scheme[SchemeOut]); drw_rect(drw, 0, 0, mw, bh, 1, 1); if (prompt && *prompt) { if (colorprompt) drw_setscheme(drw, scheme[SchemeSel]); x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); } recalculatenumbers(); /* draw input field */ w = (lines > 0 || !matches) ? mw - x : inputw; w -= lrpad / 2; x += lrpad / 2; rcurlen = drw_fontset_getwidth(drw, text + cursor); curlen = drw_fontset_getwidth(drw, text) - rcurlen; curpos += curlen - oldcurlen; curpos = MIN(w - TEXTW(numbers), MAX(0, curpos)); curpos = MAX(curpos, w - rcurlen - TEXTW(numbers)); curpos = MIN(curpos, curlen); oldcurlen = curlen; drw_setscheme(drw, scheme[SchemeOut]); drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR); drw_text_align(drw, x + curpos, 0, w - curpos - TEXTW(numbers), bh, text + cursor, strlen(text) - cursor, AlignL); drw_rect(drw, x + curpos - 1, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); if (lines > 0) { /* draw vertical list */ x -= lrpad / 2; for (item = curr; item != next; item = item->right) drawitem(item, 0, y += bh, mw); } 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[SchemeOut]); 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 = 1000000 }; Window focuswin; int i, revertwin; for (i = 0; i < 100; ++i) { XGetInputFocus(dpy, &focuswin, &revertwin); if (focuswin == win) return; if (!prevfocuswin && focuswin != None && focuswin != PointerRoot) prevfocuswin = focuswin; XSetInputFocus(dpy, win, RevertToParent, CurrentTime); XGetInputFocus(dpy, &focuswin, &revertwin); if (focuswin == win) return; nanosleep(&ts, NULL); } die("cannot grab focus"); } static void grabkeyboard(void) { struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; int i; if (embed || managed) 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; 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; ksym = NoSymbol; if (xic && ev->keycode == 0) len = Xutf8LookupString(xic, ev, buf, sizeof buf, &ksym, &status); else { len = XLookupString(ev, buf, sizeof buf, &ksym, NULL); status = (len > 0 && ksym != NoSymbol) ? XLookupBoth : (len > 0) ? XLookupChars : (ksym != NoSymbol) ? XLookupKeySym : XLookupNone; } 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: case XF86XK_PowerOff: cleanup(); exit(1); case XF86XK_Back: case XF86XK_KbdLcdMenu2: return; case XK_Home: if (sel == matches) { cursor = 0; break; } sel = curr = matches; calcoffsets(); break; case XK_Left: if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; } if (lines > 0) return; /* fallthrough */ case XF86XK_AudioRaiseVolume: case XK_Up: if (sel && sel->left) { sel = sel->left; if (sel->right == curr) { curr = prev; calcoffsets(); } } else if (wrap_around) { // if there is no previous item, jump to the bottom of the list curr = matchend; calcoffsets(); curr = prev; calcoffsets(); while (next && (curr = curr->right)) calcoffsets(); sel = matchend; } 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_Menu: case XF86XK_KbdLcdMenu1: return; case XF86XK_Phone: case XF86XK_PickupPhone: case XK_Return: case XK_KP_Enter: if (use_text_input) puts((sel && (ev->state & ShiftMask)) ? sel->text : text); 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 (text[cursor] != '\0') { cursor = nextrune(+1); break; } if (lines > 0) return; /* fallthrough */ case XF86XK_AudioLowerVolume: case XK_Down: if (sel && sel->right) { sel = sel->right; if (sel == next) { curr = next; } } else if (wrap_around) { // if there is no next item, jump to the top of the list sel = curr = matches; } 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 motionnotify(XEvent *e) { struct item *item; XMotionEvent *ev = &e->xmotion; int y = 0, h = bh; char *pageup = "Page up", *pagedown = "Page down"; char *gotop = "Go to top", *gobottom = "Go to bottom"; char *alreadytop = "Already at top", *alreadybottom = "Already at bottom"; if (ev->window != win){ return; } if (ev->state & ~Button1Mask) return; if (lines > 0) { /* vertical list: (ctrl)left-click on item */ for (item = curr; item != next; item = item->right) { y += h; if (ev->y >= y && ev->y <= (y + h)) { sel = item; drawmenu(); return; } } /* display page up/down or jump top/bottom as appropriate */ if(ev->y > mh || ev->y < 0){ if (ev->y >= (mh) && ev->y <= (mh + bh*scrollregion )) { nav->text = next ? pagedown : alreadybottom; drw_setscheme(drw, scheme[SchemeSel]); y=mh-bh; } else if (ev->y >= (mh + bh*scrollregion ) && ev->y < (mh + bh*scrollregion*2 )) { nav->text = next ? gobottom : alreadybottom; drw_setscheme(drw, scheme[SchemeSel]); y=mh-bh; } else if (ev->y >= (0 - h*scrollregion) && ev->y <= (0)) { nav->text = curr != matches ? pageup : alreadytop; drw_setscheme(drw, scheme[SchemeSel]); y=0+bh; } else if (ev->y >= (0 - h*scrollregion*2) && ev->y <= (0 - h*scrollregion)) { nav->text = curr != matches ? gotop : alreadytop; drw_setscheme(drw, scheme[SchemeSel]); y=0+bh; } else { drawmenu(); return; } drw_setscheme(drw, scheme[SchemeOut]); drw_text(drw, 0, y, mw, bh, lrpad / 2, nav->text, 0); drw_map(drw, win, 0, 0, mw, mh); XFlush(dpy); return; } } return; } static void buttonpress(XEvent *e) { struct item *item; XButtonPressedEvent *ev = &e->xbutton; int x = 0, y = 0, h = bh, w; 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; } /* jump to top */ if (ev->y >= (0 - h*scrollregion*2) && ev->y <= (0 - h*scrollregion)) { sel = curr = matches; calcoffsets(); drawmenu(); return; } /* scroll up */ if ( (ev->button == Button4 && prev) || (ev->y >= (0 - h*scrollregion) && ev->y <= (0)) ) { sel = curr = prev; calcoffsets(); drawmenu(); return; } /* scroll down */ if ( (ev->button == Button5 && next) || (ev->y >= (mh) && ev->y <= (mh + bh*scrollregion )) ) { if(next) sel = curr = next; calcoffsets(); drawmenu(); return; } /* jump to bottom */ if (ev->y >= (mh + bh*scrollregion ) && ev->y < (mh + bh*scrollregion*2 )) { 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; drawmenu(); return; } if (ev->button != Button1) return; if (ev->state & ~ControlMask & ~Button1Mask) return; if (lines > 0) { /* vertical list: (ctrl)left-click on item */ w = mw - x; for (item = curr; item != next; item = item->right) { 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 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; /* 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; if (lines == 0 && !centered) { 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 && lines == 0 && !centered) ? TEXTW(items[imax].text) : 0; lines = MIN(lines, i); } #include static int issoftkey(unsigned int keycode) { /* X11 keycodes: Power=124, Menu(Left Softkey)=135, Back(Right Softkey)=166, Phone(Call)=177 */ return keycode == 124 || keycode == 135 || keycode == 166 || keycode == 177; } static long long dmenu_now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (long long)ts.tv_sec * 1000LL + (long long)(ts.tv_nsec / 1000000L); } static void write_softkey_suppress(long long until_ms) { char buf[32]; int fd = open("/tmp/sxmo_softkey_suppress", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (fd >= 0) { int len = snprintf(buf, sizeof(buf), "%lld\n", until_ms); (void)write(fd, buf, len); close(fd); } } static int query_softkeys_down(void) { char km[32]; int mask = 0; if (!XQueryKeymap(dpy, km)) return 0; if (km[135 / 8] & (1 << (135 % 8))) mask |= 1; if (km[166 / 8] & (1 << (166 % 8))) mask |= 2; return mask; } static void read_softkey_state(int *mask_out, long long *ms_out) { char buf[64]; int fd = open("/tmp/sxmo_softkey_state", O_RDONLY | O_CLOEXEC); *mask_out = 0; *ms_out = 0; if (fd >= 0) { ssize_t n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n > 0) { buf[n] = '\0'; sscanf(buf, "%d %lld", mask_out, ms_out); } } } static void clear_softkey_state(void) { int fd = open("/tmp/sxmo_softkey_state", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (fd >= 0) { (void)write(fd, "0 0\n", 4); close(fd); } } static void spawn_xdrawer_and_exit(void) { clear_softkey_state(); write_softkey_suppress(dmenu_now_ms() + 500); cleanup(); if (fork() == 0) { int nullfd = open("/dev/null", O_RDWR); if (nullfd >= 0) { dup2(nullfd, 0); dup2(nullfd, 1); dup2(nullfd, 2); if (nullfd > 2) close(nullfd); } setsid(); execlp("xdrawer", "xdrawer", (char *)NULL); execl("/home/user/.local/bin/xdrawer", "xdrawer", (char *)NULL); execl("/usr/bin/xdrawer", "xdrawer", (char *)NULL); _exit(0); } exit(1); } static void run(void) { XEvent ev; int soft_pending = 0; long long soft_deadline = 0; long long start_ignore = dmenu_now_ms() + 40; int initial_released = (query_softkeys_down() == 0); int init_soft_mask = 0; long long init_soft_ms = 0; int xfd = ConnectionNumber(dpy); read_softkey_state(&init_soft_mask, &init_soft_ms); for (;;) { if (!initial_released && query_softkeys_down() == 0) initial_released = 1; while (XPending(dpy)) { if (XNextEvent(dpy, &ev)) return; if (!initial_released && query_softkeys_down() == 0) initial_released = 1; if (ev.type == KeyPress || ev.type == KeyRelease) { KeySym ks = XLookupKeysym(&ev.xkey, 0); int is_left = (ks == XK_Menu || ks == XF86XK_KbdLcdMenu1 || ev.xkey.keycode == 135); int is_right = (ks == XF86XK_Back || ks == XF86XK_KbdLcdMenu2 || ev.xkey.keycode == 166); if (is_left || is_right) { long long now = dmenu_now_ms(); int which = (is_left ? 1 : 0) | (is_right ? 2 : 0); if (ev.type == KeyPress && init_soft_mask != 0 && (init_soft_mask | which) == 3 && (now - init_soft_ms) <= 320) { spawn_xdrawer_and_exit(); } if (ev.type == KeyRelease) { initial_released = 1; continue; } if (!initial_released || now < start_ignore) continue; soft_pending |= which | query_softkeys_down(); if (soft_pending == 3) spawn_xdrawer_and_exit(); if (soft_deadline == 0) soft_deadline = now + 30; continue; } if (!issoftkey(ev.xkey.keycode)) { ev.xkey.window = win; if (XFilterEvent(&ev, None)) continue; } } else if (XFilterEvent(&ev, None)) { continue; } switch(ev.type) { case MotionNotify: motionnotify(&ev); break; case ButtonRelease: buttonpress(&ev); break; 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(); if (xic) XSetICFocus(xic); break; case KeyPress: keypress(&ev.xkey); break; case SelectionNotify: if (ev.xselection.property == utf8) paste(); break; } } if (soft_pending && soft_deadline > 0) { long long now = dmenu_now_ms(); int hw_down = query_softkeys_down(); soft_pending |= hw_down; if (soft_pending == 3) spawn_xdrawer_and_exit(); long long rem = soft_deadline - now; if (rem <= 0) { clear_softkey_state(); write_softkey_suppress(now + 250); if (soft_pending == 1) { puts((sel) ? sel->text : text); cleanup(); exit(0); } else { cleanup(); exit(1); } } struct pollfd pfd = { xfd, POLLIN, 0 }; poll(&pfd, 1, (int)rem); } else { struct pollfd pfd = { xfd, POLLIN, 0 }; poll(&pfd, 1, initial_released ? -1 : 40); } } } 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, (const char**)colors[j], 2); } for (j = 0; j < SchemeLast; ++j) { free(colors[j][ColFg]); free(colors[j][ColBg]); } clip = XInternAtom(dpy, "CLIPBOARD", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False); /* calculate menu geometry */ const int bar_h = 24; bh = drw->fonts->h + 2; bh = MAX(bh, lineheight); 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))) { int avail_h; 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; avail_h = MAX((int)bh * 2, (int)info[i].height - bar_h); if (lines > 0 && (int)mh > avail_h) { lines = MAX(1, (avail_h / (int)bh) - 1); mh = (lines + 1) * bh; } x = info[i].x_org; mw = info[i].width; y = info[i].y_org + (topbar ? bar_h : MAX(bar_h, (int)info[i].height - (int)mh)); XFree(info); } else #endif { int avail_h; if (!XGetWindowAttributes(dpy, parentwin, &wa)) die("could not get embedding window attributes: 0x%lx", parentwin); avail_h = MAX((int)bh * 2, (int)wa.height - bar_h); if (lines > 0 && (int)mh > avail_h) { lines = MAX(1, (avail_h / (int)bh) - 1); mh = (lines + 1) * bh; } x = 0; mw = wa.width; y = topbar ? bar_h : MAX(bar_h, (int)wa.height - (int)mh); } inputw = MIN(inputw, mw/3); match(); /* create menu window */ swa.override_redirect = managed ? False : True; swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; 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); drw_resize(drw, mw, mh); calcoffsets(); for (i = 0; i < initialindex; i++) { if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); } } XMapRaised(dpy, win); drawmenu(); XFlush(dpy); if (managed) { XTextProperty prop; char *windowtitle = prompt != NULL ? (char *)prompt : "dmenu"; Xutf8TextListToTextProperty(dpy, &windowtitle, 1, XUTF8StringStyle, &prop); XSetWMName(dpy, win, &prop); XSetTextProperty(dpy, win, &prop, XInternAtom(dpy, "_NET_WM_NAME", False)); XFree(prop.value); } else { grabkeyboard(); grabfocus(); } /* input methods */ XSetLocaleModifiers(""); if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) { XSetLocaleModifiers("@im=local"); if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) { XSetLocaleModifiers("@im="); xim = XOpenIM(dpy, NULL, NULL, NULL); } } if (xim) { xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, XNFocusWindow, win, NULL); if (xic) XSetICFocus(xic); } 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(); } if (!(nav = malloc(sizeof *nav))) die("cannot malloc %u bytes:", sizeof *nav); nav->out = 1; } static void usage(void) { fputs("usage: dmenu [-bfiv] [-l lines] [-H height] [-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]) fonts[0] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.background", "*", &type, &xval) && !colors[SchemeNorm][ColBg] ) colors[SchemeNorm][ColBg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.foreground", "*", &type, &xval) && !colors[SchemeNorm][ColFg] ) colors[SchemeNorm][ColFg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.selbackground", "*", &type, &xval) && !colors[SchemeSel][ColBg] ) colors[SchemeSel][ColBg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.selforeground", "*", &type, &xval) && !colors[SchemeSel][ColFg] ) colors[SchemeSel][ColFg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.selhighlightbackground", "*", &type, &xval) && !colors[SchemeSelHighlight][ColBg] ) colors[SchemeSelHighlight][ColBg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.selhighlightforeground", "*", &type, &xval) && !colors[SchemeSelHighlight][ColFg] ) colors[SchemeSelHighlight][ColFg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.highlightbackground", "*", &type, &xval) && !colors[SchemeNormHighlight][ColBg] ) colors[SchemeNormHighlight][ColBg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.highlightforeground", "*", &type, &xval) && !colors[SchemeNormHighlight][ColFg] ) colors[SchemeNormHighlight][ColFg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.outbackground", "*", &type, &xval) && !colors[SchemeOut][ColBg] ) colors[SchemeOut][ColBg] = strdup(xval.addr); if (XrmGetResource(xdb, "dmenu.outforeground", "*", &type, &xval) && !colors[SchemeOut][ColFg] ) colors[SchemeOut][ColFg] = strdup(xval.addr); XrmDestroyDatabase(xdb); } } 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], "-nc")) /* prevents centering dmenu on screen */ centered = 0; 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], "-t")) /* favors text input over selection */ use_text_input = 1; else if (!strcmp(argv[i], "-wm")) { /* display as managed wm window */ managed = 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], "-H")) { lineheight = atoi(argv[++i]); lineheight = MAX(lineheight, min_lineheight); } 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] = strdup(argv[++i]); else if (!strcmp(argv[i], "-nb")) /* normal background color */ colors[SchemeNorm][ColBg] = strdup(argv[++i]); else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ colors[SchemeNorm][ColFg] = strdup(argv[++i]); else if (!strcmp(argv[i], "-sb")) /* selected background color */ colors[SchemeSel][ColBg] = strdup(argv[++i]); else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ colors[SchemeSel][ColFg] = strdup(argv[++i]); else if (!strcmp(argv[i], "-w")) /* embedding window id */ embed = argv[++i]; else if ((!strcmp(argv[i], "--index")) || (!strcmp(argv[i], "-I"))) /* initial index selection, 0-indexed (bemenu compatible) */ initialindex = atoi(argv[++i]); else if (!strcmp(argv[i], "-idx")) /* initial index selection, 1-indexed */ initialindex = atoi(argv[++i]) - 1; else usage(); if (getenv("DMENU_WRAP_AROUND")) wrap_around = 1; if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("warning: no locale support\n", stderr); XSetLocaleModifiers(""); 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); /* Apply defaults to font and colors*/ if (!fonts[0]) { fonts[0] = defaultfonts[0]; fonts[1] = defaultfonts[1]; } else { fonts[1] = defaultfonts[1]; } for (i = 0; i < SchemeLast; ++i){ if ( !colors[i][ColBg] ) colors[i][ColBg] = strdup(defaultcolors[i][ColBg]); if ( !colors[i][ColFg] ) colors[i][ColFg] = strdup(defaultcolors[i][ColFg]); } if (!drw_fontset_create(drw, fonts, 2)) die("no fonts could be loaded."); lrpad = drw->fonts->h / 2; #ifdef __OpenBSD__ if (pledge("stdio rpath", NULL) == -1) die("pledge"); #endif readstdin(); setup(); run(); return 1; /* unreachable */ }