rub out text when no item matches it
[xmenu] / xmenu.c
diff --git a/xmenu.c b/xmenu.c
index 88d834f..f597aeb 100644 (file)
--- a/xmenu.c
+++ b/xmenu.c
@@ -5,6 +5,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
+#include <locale.h>
 #include <time.h>
 #include <unistd.h>
 #include <X11/Xlib.h>
 #include <time.h>
 #include <unistd.h>
 #include <X11/Xlib.h>
@@ -30,6 +31,7 @@ static struct Monitor mon;
 static Atom utf8string;
 static Atom wmdelete;
 static Atom netatom[NetLast];
 static Atom utf8string;
 static Atom wmdelete;
 static Atom netatom[NetLast];
+static XIM xim;
 
 /* flags */
 static int iflag = 0;   /* whether to disable icons */
 
 /* flags */
 static int iflag = 0;   /* whether to disable icons */
@@ -372,6 +374,11 @@ allocmenu(struct Menu *parent, struct Item *list, unsigned level)
                                  CWBorderPixel | CWEventMask | CWSaveUnder,
                                  &swa);
 
                                  CWBorderPixel | CWEventMask | CWSaveUnder,
                                  &swa);
 
+       menu->xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+                             XNClientWindow, menu->win, XNFocusWindow, menu->win, NULL);
+       if (menu->xic == NULL)
+               errx(1, "XCreateIC: could not obtain input method");
+
        return menu;
 }
 
        return menu;
 }
 
@@ -769,6 +776,24 @@ grabkeyboard(void)
        errx(1, "could not grab keyboard");
 }
 
        errx(1, "could not grab keyboard");
 }
 
+/* try to grab focus, we may have to wait for another process to ungrab */
+static void
+grabfocus(Window win)
+{
+       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);
+       }
+       errx(1, "cannot grab focus");
+}
+
 /* ungrab pointer and keyboard */
 static void
 ungrab(void)
 /* ungrab pointer and keyboard */
 static void
 ungrab(void)
@@ -1016,6 +1041,7 @@ mapmenu(struct Menu *currmenu)
        }
 
        prevmenu = currmenu;
        }
 
        prevmenu = currmenu;
+       grabfocus(currmenu->win);
 }
 
 /* get menu of given window */
 }
 
 /* get menu of given window */
@@ -1114,24 +1140,66 @@ isclickbutton(unsigned int button)
        return 0;
 }
 
        return 0;
 }
 
+/* append buf into text */
+static int
+append(char *text, char *buf, size_t textsize, size_t buflen)
+{
+       size_t textlen;
+
+       textlen = strlen(text);
+       if (iscntrl(*buf))
+               return 0;
+       if (textlen + buflen > textsize - 1)
+               return 0;
+       if (buflen < 1)
+               return 0;
+       memcpy(text + textlen, buf, buflen);
+       text[textlen + buflen] = '\0';
+       return 1;
+}
+
+/* get item in menu matching text */
+static struct Item *
+matchitem(struct Menu *menu, char *text)
+{
+       struct Item *item;
+       char *s;
+       size_t textlen;
+
+       textlen = strlen(text);
+       for (item = menu->list; item; item = item->next)
+               for (s = item->label; s && *s; s++)
+                       if (strncasecmp(s, text, textlen) == 0)
+                               return item;
+       return NULL;
+}
+
 /* run event loop */
 static void
 run(struct Menu *currmenu)
 {
 /* run event loop */
 static void
 run(struct Menu *currmenu)
 {
+       char text[BUFSIZ];
+       char buf[32];
        struct Menu *menu;
        struct Item *item;
        struct Item *previtem = NULL;
        struct Menu *menu;
        struct Item *item;
        struct Item *previtem = NULL;
-       struct Item *lastitem;
+       struct Item *lastitem, *select;
        KeySym ksym;
        KeySym ksym;
+       Status status;
        XEvent ev;
        XEvent ev;
+       int action;
+       int len;
 
 
+       text[0] = '\0';
        mapmenu(currmenu);
        mapmenu(currmenu);
-
        while (!XNextEvent(dpy, &ev)) {
        while (!XNextEvent(dpy, &ev)) {
+               if (XFilterEvent(&ev, None))
+                       continue;
+               action = ACTION_NOP;
                switch(ev.type) {
                case Expose:
                        if (ev.xexpose.count == 0)
                switch(ev.type) {
                case Expose:
                        if (ev.xexpose.count == 0)
-                               drawmenus(currmenu);
+                               action = ACTION_DRAW;
                        break;
                case MotionNotify:
                        menu = getmenu(currmenu, ev.xbutton.window);
                        break;
                case MotionNotify:
                        menu = getmenu(currmenu, ev.xbutton.window);
@@ -1139,15 +1207,14 @@ run(struct Menu *currmenu)
                        if (menu == NULL || item == NULL || previtem == item)
                                break;
                        previtem = item;
                        if (menu == NULL || item == NULL || previtem == item)
                                break;
                        previtem = item;
-                       menu->selected = item;
+                       select = menu->selected = item;
                        if (item->submenu != NULL) {
                                currmenu = item->submenu;
                        if (item->submenu != NULL) {
                                currmenu = item->submenu;
-                               currmenu->selected = NULL;
+                               select = NULL;
                        } else {
                                currmenu = menu;
                        }
                        } else {
                                currmenu = menu;
                        }
-                       mapmenu(currmenu);
-                       drawmenus(currmenu);
+                       action = ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
                        break;
                case ButtonRelease:
                        if (!isclickbutton(ev.xbutton.button))
                        break;
                case ButtonRelease:
                        if (!isclickbutton(ev.xbutton.button))
@@ -1156,7 +1223,7 @@ run(struct Menu *currmenu)
                        item = getitem(menu, ev.xbutton.y);
                        if (menu == NULL || item == NULL)
                                break;
                        item = getitem(menu, ev.xbutton.y);
                        if (menu == NULL || item == NULL)
                                break;
-selectitem:
+enteritem:
                        if (item->label == NULL)
                                break;  /* ignore separators */
                        if (item->submenu != NULL) {
                        if (item->label == NULL)
                                break;  /* ignore separators */
                        if (item->submenu != NULL) {
@@ -1165,9 +1232,8 @@ selectitem:
                                printf("%s\n", item->output);
                                return;
                        }
                                printf("%s\n", item->output);
                                return;
                        }
-                       mapmenu(currmenu);
-                       currmenu->selected = currmenu->list;
-                       drawmenus(currmenu);
+                       select = currmenu->list;
+                       action = ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
                        break;
                case ButtonPress:
                        menu = getmenu(currmenu, ev.xbutton.window);
                        break;
                case ButtonPress:
                        menu = getmenu(currmenu, ev.xbutton.window);
@@ -1175,7 +1241,16 @@ selectitem:
                                return;
                        break;
                case KeyPress:
                                return;
                        break;
                case KeyPress:
-                       ksym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0);
+                       len = XmbLookupString(currmenu->xic, &ev.xkey, buf, sizeof buf, &ksym, &status);
+                       switch(status) {
+                       default:                /* XLookupNone, XBufferOverflow */
+                               continue;
+                       case XLookupChars:
+                               goto append;
+                       case XLookupKeySym:     /* FALLTHROUGH */
+                       case XLookupBoth:
+                               break;
+                       }
 
                        /* esc closes xmenu when current menu is the root menu */
                        if (ksym == XK_Escape && currmenu->parent == NULL)
 
                        /* esc closes xmenu when current menu is the root menu */
                        if (ksym == XK_Escape && currmenu->parent == NULL)
@@ -1205,21 +1280,31 @@ selectitem:
                        } else if ((ksym == XK_Return || ksym == XK_Right || ksym == KSYMRIGHT) &&
                                    currmenu->selected != NULL) {
                                item = currmenu->selected;
                        } else if ((ksym == XK_Return || ksym == XK_Right || ksym == KSYMRIGHT) &&
                                    currmenu->selected != NULL) {
                                item = currmenu->selected;
-                               goto selectitem;
+                               goto enteritem;
                        } else if ((ksym == XK_Escape || ksym == XK_Left || ksym == KSYMLEFT) &&
                                   currmenu->parent != NULL) {
                                item = currmenu->parent->selected;
                                currmenu = currmenu->parent;
                        } else if ((ksym == XK_Escape || ksym == XK_Left || ksym == KSYMLEFT) &&
                                   currmenu->parent != NULL) {
                                item = currmenu->parent->selected;
                                currmenu = currmenu->parent;
-                               mapmenu(currmenu);
-                       } else
+                               action = ACTION_MAP;
+                       } else {
+append:
+                               if (append(text, buf, sizeof text, len)) {
+                                       if ((currmenu->selected = matchitem(currmenu, text))) {
+                                               action = ACTION_DRAW;
+                                               break;
+                                       }
+                               }
+                               select = NULL;
+                               action = ACTION_SELECT | ACTION_DRAW;
                                break;
                                break;
-                       currmenu->selected = item;
-                       drawmenus(currmenu);
+                       }
+                       select = item;
+                       action |= ACTION_SELECT | ACTION_DRAW;
                        break;
                case LeaveNotify:
                        previtem = NULL;
                        break;
                case LeaveNotify:
                        previtem = NULL;
-                       currmenu->selected = NULL;
-                       drawmenus(currmenu);
+                       select = NULL;
+                       action = ACTION_SELECT | ACTION_DRAW;
                        break;
                case ConfigureNotify:
                        menu = getmenu(currmenu, ev.xconfigure.window);
                        break;
                case ConfigureNotify:
                        menu = getmenu(currmenu, ev.xconfigure.window);
@@ -1236,9 +1321,17 @@ selectitem:
                        if (menu->parent == NULL)
                                return;     /* closing the root menu closes the program */
                        currmenu = menu->parent;
                        if (menu->parent == NULL)
                                return;     /* closing the root menu closes the program */
                        currmenu = menu->parent;
-                       mapmenu(currmenu);
+                       action = ACTION_MAP;
                        break;
                }
                        break;
                }
+               if (action & ACTION_SELECT) {
+                       currmenu->selected = select;
+                       text[0] = '\0';
+               }
+               if (action & ACTION_MAP)
+                       mapmenu(currmenu);
+               if (action & ACTION_DRAW)
+                       drawmenus(currmenu);
        }
 }
 
        }
 }
 
@@ -1295,6 +1388,8 @@ main(int argc, char *argv[])
        XClassHint classh;
 
        /* open connection to server and set X variables */
        XClassHint classh;
 
        /* open connection to server and set X variables */
+       if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+               warnx("warning: no locale support");
        if ((dpy = XOpenDisplay(NULL)) == NULL)
                errx(1, "could not open display");
        screen = DefaultScreen(dpy);
        if ((dpy = XOpenDisplay(NULL)) == NULL)
                errx(1, "could not open display");
        screen = DefaultScreen(dpy);
@@ -1304,6 +1399,8 @@ main(int argc, char *argv[])
        XrmInitialize();
        if ((xrm = XResourceManagerString(dpy)) != NULL)
                xdb = XrmGetStringDatabase(xrm);
        XrmInitialize();
        if ((xrm = XResourceManagerString(dpy)) != NULL)
                xdb = XrmGetStringDatabase(xrm);
+       if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
+               errx(1, "XOpenIM: could not open input device");
 
        /* get configuration */
        getresources();
 
        /* get configuration */
        getresources();