From: Matthias Kruk Date: Fri, 19 Mar 2021 07:28:33 +0000 (+0900) Subject: Add experimental keyboard pointer X-Git-Url: https://git.corax.cc/?a=commitdiff_plain;h=8d057a9511e1fe050209c8d18f1fe412176eff27;p=dwm Add experimental keyboard pointer 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. --- diff --git a/config.def.h b/config.def.h index db02cd4..415600e 100755 --- a/config.def.h +++ b/config.def.h @@ -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 883e88e..7359921 100755 --- a/dwm.c +++ b/dwm.c @@ -21,6 +21,7 @@ * To understand everything else, start reading main(). */ #define M10K 1 +#define _DEFAULT_SOURCE #include #include @@ -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);