xmenu now can be operated by keyboard
authorphillbush <phillbush@cock.li>
Sun, 17 May 2020 20:01:46 +0000 (17:01 -0300)
committerphillbush <phillbush@cock.li>
Sun, 17 May 2020 20:01:46 +0000 (17:01 -0300)
config.mk
xmenu.1
xmenu.c

index d1f00cd..05c1e81 100644 (file)
--- a/config.mk
+++ b/config.mk
@@ -1,4 +1,4 @@
-# progrma name
+# program name
 PROG = xmenu
 
 # paths
 PROG = xmenu
 
 # paths
diff --git a/xmenu.1 b/xmenu.1
index de61a9e..0e830d2 100644 (file)
--- a/xmenu.1
+++ b/xmenu.1
@@ -37,6 +37,28 @@ separating the item above from the item below.
 The command is the string that will be output after selecting the item.
 .IP
 The newline terminates the item specification.
 The command is the string that will be output after selecting the item.
 .IP
 The newline terminates the item specification.
+.SH USAGE
+.B xmenu
+is controlled by the mouse,
+but can also be controlled by the keyboard.
+Items can be selected using the arrow keys,
+Tab (with and without Shift),
+Enter and Esc.
+.TP
+.BR Down ", " Tab
+Cycle through the items in the regular direction.
+.TP
+.BR Up ", " Shift-Tab
+Cycle through the items in the reverse direction.
+.TP
+.BR Right ", " Enter
+Select the highlighted item.
+.TP
+.B Left
+Go to the menu above.
+.TP
+.B Esc
+Go to the menu above or exit xmenu.
 .SH RESOURCES
 .B
 xmenu
 .SH RESOURCES
 .B
 xmenu
@@ -83,7 +105,7 @@ The output is redirected to xargs to make a command to be run by the shell.
 .EX
 #!/bin/sh
 
 .EX
 #!/bin/sh
 
-cat <<EOF | ./xmenu | xargs sh -c
+cat <<EOF | xmenu | xargs sh -c
 Applications
        Web Browser     firefox
        Image editor    gimp
 Applications
        Web Browser     firefox
        Image editor    gimp
diff --git a/xmenu.c b/xmenu.c
index 0afaaa9..738c815 100644 (file)
--- a/xmenu.c
+++ b/xmenu.c
@@ -6,6 +6,10 @@
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 #include <X11/Xresource.h>
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 #include <X11/Xresource.h>
+#include <X11/XKBlib.h>
+
+#define ITEMPREV 0
+#define ITEMNEXT 1
 
 /* macros */
 #define LEN(x) (sizeof (x) / sizeof (x[0]))
 
 /* macros */
 #define LEN(x) (sizeof (x) / sizeof (x[0]))
@@ -49,6 +53,7 @@ struct Item {
        int y;                  /* item y position relative to menu */
        int h;                  /* item height */
        size_t labellen;        /* strlen(label) */
        int y;                  /* item y position relative to menu */
        int h;                  /* item height */
        size_t labellen;        /* strlen(label) */
+       struct Item *prev;      /* previous item */
        struct Item *next;      /* next item */
        struct Menu *submenu;   /* submenu spawned by clicking on item */
 };
        struct Item *next;      /* next item */
        struct Menu *submenu;   /* submenu spawned by clicking on item */
 };
@@ -248,8 +253,13 @@ setupgeom(void)
 static void
 setupgrab(void)
 {
 static void
 setupgrab(void)
 {
-       XGrabPointer(dpy, rootwin, True, ButtonPressMask | ButtonReleaseMask,
-                    GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
+       if (XGrabPointer(dpy, rootwin, True, ButtonPressMask,
+                        GrabModeAsync, GrabModeAsync, None,
+                        None, CurrentTime) != GrabSuccess)
+               errx(1, "cannot grab pointer");
+       if (XGrabKeyboard(dpy, rootwin, True, GrabModeAsync,
+                         GrabModeAsync, CurrentTime) != GrabSuccess)
+               errx(1, "cannot grab keyboard");
 }
 
 /* allocate an item */
 }
 
 /* allocate an item */
@@ -360,6 +370,8 @@ parsestdin(void)
                         rootmenu = menu;
                         prevmenu = menu;
                         count = 1;
                         rootmenu = menu;
                         prevmenu = menu;
                         count = 1;
+                        curritem->prev = NULL;
+                        curritem->next = NULL;
                } else if (level < prevmenu->level) {   /* item is continuation of a parent menu*/
                        for (menu = prevmenu, i = level;
                              menu != NULL && i < prevmenu->level;
                } else if (level < prevmenu->level) {   /* item is continuation of a parent menu*/
                        for (menu = prevmenu, i = level;
                              menu != NULL && i < prevmenu->level;
@@ -373,11 +385,19 @@ parsestdin(void)
                                ;
 
                        item->next = curritem;
                                ;
 
                        item->next = curritem;
+
+                       curritem->prev = item;
+                       curritem->next = NULL;
+
                        prevmenu = menu;
                } else if (level == prevmenu->level) {  /* item is a continuation of current menu */
                        for (item = prevmenu->list; item->next != NULL; item = item->next)
                                ;
                        item->next = curritem;
                        prevmenu = menu;
                } else if (level == prevmenu->level) {  /* item is a continuation of current menu */
                        for (item = prevmenu->list; item->next != NULL; item = item->next)
                                ;
                        item->next = curritem;
+
+                       curritem->prev = item;
+                       curritem->next = NULL;
+
                } else if (level > prevmenu->level) {   /* item begins a new menu */
                        menu = allocmenu(prevmenu, curritem, level);
 
                } else if (level > prevmenu->level) {   /* item begins a new menu */
                        menu = allocmenu(prevmenu, curritem, level);
 
@@ -387,6 +407,9 @@ parsestdin(void)
                        item->submenu = menu;
                        menu->caller = item;
 
                        item->submenu = menu;
                        menu->caller = item;
 
+                       curritem->prev = NULL;
+                       curritem->next = NULL;
+
                        prevmenu = menu;
                }
                count++;
                        prevmenu = menu;
                }
                count++;
@@ -616,6 +639,47 @@ drawmenu(void)
        }
 }
 
        }
 }
 
+/* cycle through the items; non-zero direction is next, zero is prev */
+static struct Item *
+itemcycle(int direction)
+{
+       struct Item *item;
+       struct Item *lastitem;
+
+       item = NULL;
+
+       if (direction == ITEMNEXT) {
+               if (currmenu->selected == NULL)
+                       item = currmenu->list;
+               else if (currmenu->selected->next != NULL)
+                       item = currmenu->selected->next;
+
+               while (item != NULL && item->label == NULL)
+                       item = item->next;
+
+               if (item == NULL)
+                       item = currmenu->list;
+       } else {
+               for (lastitem = currmenu->list;
+                    lastitem != NULL && lastitem->next != NULL;
+                    lastitem = lastitem->next)
+                       ;
+
+               if (currmenu->selected == NULL)
+                       item = lastitem;
+               else if (currmenu->selected->prev != NULL)
+                       item = currmenu->selected->prev;
+
+               while (item != NULL && item->label == NULL)
+                       item = item->prev;
+
+               if (item == NULL)
+                       item = lastitem;
+       }
+
+       return item;
+}
+
 /* run event loop */
 static void
 run(void)
 /* run event loop */
 static void
 run(void)
@@ -623,12 +687,14 @@ run(void)
        struct Menu *menu;
        struct Item *item;
        struct Item *previtem = NULL;
        struct Menu *menu;
        struct Item *item;
        struct Item *previtem = NULL;
+       KeySym ksym;
        XEvent ev;
 
        while (!XNextEvent(dpy, &ev)) {
                switch(ev.type) {
                case Expose:
        XEvent ev;
 
        while (!XNextEvent(dpy, &ev)) {
                switch(ev.type) {
                case Expose:
-                       drawmenu();
+                       if (ev.xexpose.count == 0)
+                               drawmenu();
                        break;
                case MotionNotify:
                        getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item);
                        break;
                case MotionNotify:
                        getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item);
@@ -649,6 +715,7 @@ run(void)
                case ButtonRelease:
                        getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item);
                        if (menu != NULL && item != NULL) {
                case ButtonRelease:
                        getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item);
                        if (menu != NULL && item != NULL) {
+selectitem:
                                if (item->label == NULL)
                                        break;  /* ignore separators */
                                if (item->submenu != NULL) {
                                if (item->label == NULL)
                                        break;  /* ignore separators */
                                if (item->submenu != NULL) {
@@ -657,10 +724,45 @@ run(void)
                                        printf("%s\n", item->output);
                                        return;
                                }
                                        printf("%s\n", item->output);
                                        return;
                                }
+                               currmenu->selected = currmenu->list;
                                drawmenu();
                                drawmenu();
+                               break;
                        } else {
                                return;
                        }
                        } else {
                                return;
                        }
+               case ButtonPress:
+                       getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item);
+                       if (menu == NULL || item == NULL)
+                               return;
+                       break;
+               case KeyPress:
+                       ksym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0);
+
+                       if (ksym == XK_Escape && currmenu == rootmenu)
+                               return;
+
+                       /* Shift-Tab = ISO_Left_Tab */
+                       if (ksym == XK_Tab && (ev.xkey.state & ShiftMask))
+                               ksym = XK_ISO_Left_Tab;
+
+                       /* cycle through menu */
+                       item = NULL;
+                       if (ksym == XK_ISO_Left_Tab || ksym == XK_Up) {
+                               item = itemcycle(ITEMPREV);
+                       } else if (ksym == XK_Tab || ksym == XK_Down) {
+                               item = itemcycle(ITEMNEXT);
+                       } else if ((ksym == XK_Return || ksym == XK_Right) &&
+                                  currmenu->selected != NULL) {
+                               item = currmenu->selected;
+                               goto selectitem;
+                       } else if ((ksym == XK_Escape || ksym == XK_Left) &&
+                                  currmenu->parent != NULL) {
+                               item = currmenu->parent->selected;
+                               setcurrmenu(currmenu->parent);
+                       } else
+                               break;
+                       currmenu->selected = item;
+                       drawmenu();
                        break;
                case LeaveNotify:
                        currmenu->selected = NULL;
                        break;
                case LeaveNotify:
                        currmenu->selected = NULL;