I'm going to be bold for a newcomer and share a proof of concept patch to dmenu.c for two features that I find very useful (useful enough that I hacked the code to add them). I have no expectations that the patch will be applied to dmenu, or even that the code I've written is the right way to do these features; I'm offering it in case other people are interested.
(Among other things, this is hand-extracted from some additional changes so it may not apply without fuzz. If people are actively interested, I'll do a proper patch with dmenu.1 changes and so on.) The new features: - -t enables a shell-like tab completion mode that I think is relatively unobtrusive. If you hit tab and the current text is a prefix of one or more items, dmenu fills in the rest of the common prefix and stops. If there is no text, your text is not a prefix, or if you hit tab a second time, it behaves as now and fills in the current selection. - -P enables pointer-only Xinerama-aware placement (ie, it lets you go back to the pre-changeset-438 behavior) where dmenu picks which Xinerama screen to appear on based purely on the pointer location and ignores what window has focus. -P makes sense for how I use dmenu and how I handle focus; to put it the short way, the location of the currently focused window has nothing to do with where I want the window I'm about to create through dmenu to go. It presumably does make sense for (some) other people, since dmenu's behavior changed to respect focus. (Like everything else about this patch, the switch letters are far from fixed in stone. I'm bad at naming things and I know it.) Patch: diff -r 1659395e4de0 dmenu.c --- a/dmenu.c Thu Jan 19 22:52:17 2012 +0000 +++ b/dmenu.c Thu Feb 02 11:06:11 2012 -0500 @@ -24,6 +24,7 @@ Item *left, *right; }; +static Bool setcommonpref(Bool again); static void appenditem(Item *item, Item **list, Item **last); static void calcoffsets(void); static char *cistrstr(const char *s, const char *sub); @@ -54,6 +55,8 @@ static unsigned long selcol[ColLast]; static Atom clip, utf8; static Bool topbar = True; +static Bool tabcomplete = False; +static Bool pointerscreen = False; static DC *dc; static Item *items = NULL; static Item *matches, *matchend; @@ -83,6 +86,12 @@ fstrncmp = strncasecmp; fstrstr = cistrstr; } + else if(!strcmp(argv[i], "-t")) { /* shell like tab completion */ + tabcomplete = True; + } + else if(!strcmp(argv[i], "-P")) { /* bar opens on Xinerama screen with pointer, regardless of focus */ + pointerscreen = True; + } else if(i+1 == argc) usage(); /* these options take one argument */ @@ -233,22 +242,26 @@ match(); } +static KeySym lastksym = NoSymbol; void keypress(XKeyEvent *ev) { char buf[32]; int len; - KeySym ksym = NoSymbol; + KeySym ksym = NoSymbol, oldksym; Status status; len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); if(status == XBufferOverflow) return; + oldksym = lastksym; + lastksym = ksym; 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; @@ -375,10 +391,13 @@ calcoffsets(); } break; case XK_Tab: if(!sel) return; - strncpy(text, sel->text, sizeof text); + if (!tabcomplete || + !setcommonpref(oldksym == XK_Tab ? False: True)) + strncpy(text, sel->text, sizeof text); cursor = strlen(text); match(); break; @@ -440,6 +459,56 @@ calcoffsets(); } +Bool +setcommonpref(Bool again) { + size_t len, t, start, maxlen; + int c; + Item *item, *prefitem; + + if (!curr || text[0] == 0) { + return False; + } + + /* Find an item that is a suffix of the current text, and the + minimum length of all items that are suffixes of the current + text. */ + maxlen = sizeof(text); + prefitem = NULL; + start = strlen(text); + for (item = curr; item != next; item = item->right) { + if (fstrncmp(item->text, text, start) != 0) + continue; + if (!prefitem) + prefitem = item; + t = strlen(item->text); + if (maxlen > t) + maxlen = t; + break; + } + if (!prefitem) + return False; + + /* Repeatedly attempt to lengthen the common prefix. */ + for (len = start; len < maxlen; len++) { + c = prefitem->text[len]; + for (item = curr; item != next; item = item->right) { + if (fstrncmp(item->text, text, start) != 0) + continue; + if (item->text[len] != c) { + if (len == start) + return again; + else { + strncpy(text, item->text, len); + text[len] = 0; + } + return True; + } + } + } + strncpy(text, prefitem->text, len+1); + return True; +} + size_t nextrune(int inc) { ssize_t n; @@ -545,7 +614,7 @@ XWindowAttributes wa; XGetInputFocus(dc->dpy, &w, &di); - if(w != root && w != PointerRoot && w != None) { + if(w != root && w != PointerRoot && w != None && !pointerscreen) { /* find top-level window containing current input focus */ do { if(XQueryTree(dc->dpy, (pw = w), &dw, &w, &dws, &du) && dws) @@ -602,7 +671,7 @@ void usage(void) { - fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-p prompt] [-fn font]\n" + fputs("usage: dmenu [-b] [-f] [-i] [-P] [-t] [-l lines] [-p prompt] [-fn font]\n" " [-nb color] [-nf color] [-sb color] [-sf color] [-v]\n", stderr); exit(EXIT_FAILURE); } - cks