]> git.corax.cc Git - dwm/commitdiff
Add experimental keyboard pointer
authorMatthias Kruk <m@m10k.eu>
Fri, 19 Mar 2021 07:28:33 +0000 (16:28 +0900)
committerMatthias Kruk <m@m10k.eu>
Fri, 19 Mar 2021 07:28:33 +0000 (16:28 +0900)
The pointer is typically very awkward to use, especially on devices with mice or
similar input devices. This commit adds an experimental keyboard pointer, which
allows the user to move the pointer and perform clicks through the keyboard.
The keyboard pointer follows this basic idea: The user can move the pointer in
four directions: North, East, South, West. The pointer has a horizontal stride
and a vertical stride (the size of a step in horizontal or vertical direction,
respectively). With each step, the respective stride is reduced by half.
The origin of the pointer is the center of the focused window, and the initial
horizontal and vertical stride are a quarter of the window's width and height,
respectively.

For example in a 100x100px window (the point (0, 0) being the top left corner),
the pointer's origin is (50, 50). If the pointer makes one step north, it will
be at (50, 25). Moving another step north will put the pointer at (50, 12.5).
The user can also hold the shift key while moving, which will cause the stride
to be reduced once before the movement and once after the movement (this seemed
useful for targets that are close to the origin).

This pointer behavior has two implications:

 1. If the stride reaches zero, the pointer can't be moved anymore
 2. The pointer can never leave the window (it asymptotically approaches the
    outermost pixel, strictly speaking)

Re-centering the pointer also replenishes the stride. If a user misses their
target, they have to start over from the center of the window. If a user wants
to point at another window, they have to move the focus to that window first.
I'm not sure if this kind of pointer behavior is more or less annoying than
using a traditional pointing device. That's why this feature is experimental.

config.def.h
dwm.c

index db02cd44bb2272dcc053d90b6cd88a0db51b85fb..415600e6afd30932d47ce18e373193485ed4e4c4 100755 (executable)
@@ -83,7 +83,19 @@ static struct key keys[] = {
        { MODKEY|ShiftMask,             XK_x,           killclient,     {0} },
        { MODKEY,                       XK_t,           setlayout,      {.v = &layouts[LAYOUT_BOOKSHELF]} },
        { MODKEY,                       XK_y,           setlayout,      {.v = &layouts[LAYOUT_BOOKSTACK]} },
-       { MODKEY,                       XK_u,           setlayout,      {.v = &layouts[LAYOUT_FLOAT]} },
+       { MODKEY,                       XK_r,           setlayout,      {.v = &layouts[LAYOUT_FLOAT]} },
+       { MODKEY,                       XK_i,           kbptr_move,     {.ui = KBPTR_NORTH} },
+       { MODKEY | ShiftMask,           XK_i,           kbptr_move,     {.ui = KBPTR_NORTH | KBPTR_HALFSTEP} },
+       { MODKEY,                       XK_l,           kbptr_move,     {.ui = KBPTR_EAST} },
+       { MODKEY | ShiftMask,           XK_l,           kbptr_move,     {.ui = KBPTR_EAST | KBPTR_HALFSTEP} },
+       { MODKEY,                       XK_k,           kbptr_move,     {.ui = KBPTR_SOUTH} },
+       { MODKEY | ShiftMask,           XK_k,           kbptr_move,     {.ui = KBPTR_SOUTH | KBPTR_HALFSTEP} },
+       { MODKEY,                       XK_j,           kbptr_move,     {.ui = KBPTR_WEST} },
+       { MODKEY | ShiftMask,           XK_j,           kbptr_move,     {.ui = KBPTR_WEST | KBPTR_HALFSTEP} },
+       { MODKEY,                       XK_u,           kbptr_move,     {.ui = KBPTR_CENTER} },
+       { MODKEY,                       XK_semicolon,   kbptr_click,    {.ui = KBPTR_LEFT} },
+       { MODKEY | ShiftMask,           XK_semicolon,   kbptr_click,    {.ui = KBPTR_RIGHT} },
+       { MODKEY | ControlMask,         XK_semicolon,   kbptr_click,    {.ui = KBPTR_MIDDLE} },
 #ifndef M10K
        { MODKEY,                       XK_space,       setlayout,      {0} },
        { MODKEY|ShiftMask,             XK_space,       togglefloating, {0} },
diff --git a/dwm.c b/dwm.c
index 883e88e4bbcf44619ea8839c660716e694f412a7..7359921f8d6acbc4f2b5ad5b709268002e4e3cd4 100755 (executable)
--- a/dwm.c
+++ b/dwm.c
@@ -21,6 +21,7 @@
  * To understand everything else, start reading main().
  */
 #define M10K 1
+#define _DEFAULT_SOURCE
 
 #include <errno.h>
 #include <locale.h>
@@ -241,6 +242,30 @@ struct rule {
        int monitor;
 };
 
+#define KBPTR_CENTER   0
+#define KBPTR_NORTH    (1 << 1)
+#define KBPTR_EAST     (1 << 2)
+#define KBPTR_SOUTH    (1 << 3)
+#define KBPTR_WEST     (1 << 4)
+#define KBPTR_DMASK    (KBPTR_NORTH | KBPTR_EAST | KBPTR_SOUTH | KBPTR_WEST)
+#define KBPTR_HALFSTEP (1 << 0)
+
+#define KBPTR_LEFT     Button1
+#define KBPTR_MIDDLE   Button2
+#define KBPTR_RIGHT    Button3
+
+struct kbptr {
+       int x;
+       int y;
+       int vstride;
+       int hstride;
+};
+
+static void kbptr_move(const union arg *arg);
+static void kbptr_click(const union arg *arg);
+
+static struct kbptr kbptr = { 0, 0, 0, 0 };
+
 /* function declarations */
 static void applyrules(struct client *c);
 static Bool applysizehints(struct client *c, int *x, int *y,
@@ -2133,13 +2158,157 @@ Bool sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d
        return(exists);
 }
 
+static void kbptr_move(const union arg *arg)
+{
+       struct client *client;
+       unsigned int dir;
+       unsigned int stepsize;
+
+       if(!selmon || !selmon->sel) {
+               return;
+       }
+
+       client = selmon->sel;
+       dir = arg->ui & KBPTR_DMASK;
+       stepsize = arg->ui & KBPTR_HALFSTEP;
+
+       switch(dir) {
+       case KBPTR_CENTER:
+               kbptr.x = client->geom.w / 2;
+               kbptr.y = client->geom.h / 2;
+               kbptr.hstride = kbptr.x / 2;
+               kbptr.vstride = kbptr.y / 2;
+               break;
+
+       case KBPTR_NORTH:
+               kbptr.vstride >>= stepsize;
+               kbptr.y -= kbptr.vstride;
+               kbptr.vstride /= 2;
+               break;
+
+       case KBPTR_EAST:
+               kbptr.hstride >>= stepsize;
+               kbptr.x += kbptr.hstride;
+               kbptr.hstride /= 2;
+               break;
+
+       case KBPTR_SOUTH:
+               kbptr.vstride >>= stepsize;
+               kbptr.y += kbptr.vstride;
+               kbptr.vstride /= 2;
+               break;
+
+       case KBPTR_WEST:
+               kbptr.hstride >>= stepsize;
+               kbptr.x -= kbptr.hstride;
+               kbptr.hstride /= 2;
+               break;
+
+       default:
+               return;
+       }
+
+       XWarpPointer(dpy, None, client->win,
+                    0, 0, 0, 0,
+                    kbptr.x, kbptr.y);
+
+       return;
+}
+
+static int do_button(struct client *client,
+                    const unsigned int button,
+                    const unsigned int pressrelease)
+{
+       XButtonEvent event;
+       unsigned int mask;
+       unsigned int state;
+       int err;
+
+       switch(pressrelease) {
+       case ButtonPress:
+               mask = ButtonPressMask;
+               state = 0;
+               break;
+
+       case ButtonRelease:
+               /*
+                * If the button is released, this means it must have
+                * been pressed. Hence, the button's mask must be set.
+                */
+               mask = ButtonReleaseMask;
+               state = button << 8;
+               break;
+
+       default:
+               return(-EINVAL);
+       }
+
+       err = XQueryPointer(dpy, client->win,
+                           &event.root, &event.window,
+                           &event.x_root, &event.y_root,
+                           &event.x, &event.y,
+                           &event.state);
+
+       if(err < 0) {
+               fprintf(stderr, "Failed to query pointer location\n");
+               return(-EIO);
+       }
+
+       event.type = pressrelease;
+       event.display = dpy;
+       event.window = client->win;
+       event.subwindow = None;
+       event.time = CurrentTime;
+
+       event.state = state;
+       event.button = button;
+       event.same_screen = True;
+
+       err = XSendEvent(dpy, client->win, True, mask, (XEvent*)&event);
+
+       if(err < 0) {
+               fprintf(stderr, "Couldn't send event\n");
+               return(-EIO);
+       }
+
+       err = XFlush(dpy);
+
+       if(err < 0) {
+               fprintf(stderr, "Couldn't flush event queue\n");
+               return(-EIO);
+       }
+
+       return(0);
+}
+
+static void kbptr_click(const union arg *arg)
+{
+       struct client *client;
+
+       if(!selmon || !selmon->sel) {
+               return;
+       }
+
+       client = selmon->sel;
+
+       do_button(client, arg->ui, ButtonPress);
+       usleep(100000);
+       do_button(client, arg->ui, ButtonRelease);
+
+       return;
+}
+
 void setfocus(struct client *c)
 {
+       union arg ptr_dest;
+
        if(!c->neverfocus) {
                XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
        }
 
-       XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->geom.w / 2, c->geom.h / 2);
+       ptr_dest.ui = KBPTR_CENTER;
+       kbptr_move(&ptr_dest);
+
        sendevent(c->win, wmatom[WMTakeFocus], NoEventMask,
                  wmatom[WMTakeFocus], CurrentTime, 0, 0, 0);