[dev] [dmenu] Proof of concept patch for 'tab completion' + Xinerama pointer-relative positioning

From: Chris Siebenmann <cks_AT_cs.toronto.edu>
Date: Thu, 02 Feb 2012 11:25:44 -0500

 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
_AT_@ -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);
_AT_@ -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;
_AT_@ -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 */
_AT_@ -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;
_AT_@ -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;
_AT_@ -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;
_AT_@ -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)
_AT_@ -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
Received on Thu Feb 02 2012 - 17:25:44 CET

This archive was generated by hypermail 2.3.0 : Thu Feb 02 2012 - 17:36:03 CET