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 pointer).
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.
OUTPUT = mwm
-OBJECTS = main.o x.o monitor.o mwm.o workspace.o loop.o client.o common.o layout.o config.o
+OBJECTS = main.o x.o monitor.o mwm.o workspace.o loop.o client.o common.o layout.o config.o kbptr.o
ifeq ($(PREFIX), )
PREFIX = /usr/local
#include "theme.h"
#include "keys.h"
#include "mwm.h"
+#include "kbptr.h"
#define MODKEY Mod4Mask
{ MODKEY, XK_e, MWM_CMD_SHIFT_MONITOR_FOCUS, (void*)+1 },
{ MODKEY | ShiftMask, XK_e, MWM_CMD_SHIFT_WORKSPACE, (void*)+1 },
+ { MODKEY, XK_u, MWM_CMD_KBPTR_MOVE, (void*)KBPTR_CENTER },
+ { MODKEY, XK_i, MWM_CMD_KBPTR_MOVE, (void*)KBPTR_NORTH },
+ { MODKEY, XK_l, MWM_CMD_KBPTR_MOVE, (void*)KBPTR_EAST },
+ { MODKEY, XK_k, MWM_CMD_KBPTR_MOVE, (void*)KBPTR_SOUTH },
+ { MODKEY, XK_j, MWM_CMD_KBPTR_MOVE, (void*)KBPTR_WEST },
+ { MODKEY | ShiftMask, XK_i, MWM_CMD_KBPTR_MOVE, (void*)(KBPTR_NORTH | KBPTR_HALFSTEP) },
+ { MODKEY | ShiftMask, XK_l, MWM_CMD_KBPTR_MOVE, (void*)(KBPTR_EAST | KBPTR_HALFSTEP) },
+ { MODKEY | ShiftMask, XK_k, MWM_CMD_KBPTR_MOVE, (void*)(KBPTR_SOUTH | KBPTR_HALFSTEP) },
+ { MODKEY | ShiftMask, XK_j, MWM_CMD_KBPTR_MOVE, (void*)(KBPTR_WEST | KBPTR_HALFSTEP) },
+ { MODKEY, XK_semicolon, MWM_CMD_KBPTR_CLICK, (void*)KBPTR_LEFT },
+ { MODKEY | ShiftMask, XK_semicolon, MWM_CMD_KBPTR_CLICK, (void*)KBPTR_RIGHT },
+ { MODKEY | ControlMask, XK_semicolon, MWM_CMD_KBPTR_CLICK, (void*)KBPTR_MIDDLE },
+
{ 0, 0, MWM_CMD_MAX, 0 }
};
--- /dev/null
+#define _DEFAULT_SOURCE
+#include <X11/Xlib.h>
+#include <unistd.h>
+#include "mwm.h"
+#include "client.h"
+#include "kbptr.h"
+#include "common.h"
+
+struct kbptr {
+ int x;
+ int y;
+ int hstride;
+ int vstride;
+ struct client *last_client;
+};
+
+static struct kbptr kbptr = {
+ .last_client = NULL
+};
+
+static void do_button(struct mwm *mwm,
+ struct client *client,
+ const unsigned int button,
+ const unsigned int pressrelease)
+{
+ XButtonEvent event;
+ unsigned int mask;
+ unsigned int state;
+ Display *display;
+ Window window;
+
+ display = mwm_get_display(mwm);
+ window = client_get_window(client);
+
+ switch(pressrelease) {
+ case ButtonPress:
+ mask = ButtonPressMask;
+ state = 0;
+ break;
+
+ case ButtonReleaseMask:
+ /*
+ * The button must have been set if it was released,
+ * hence the button's state mask must be set.
+ */
+ mask = ButtonReleaseMask;
+ state = button << 8;
+ break;
+
+ default:
+ return;
+ }
+
+ if(XQueryPointer(display, window,
+ &event.root, &event.window,
+ &event.x_root, &event.y_root,
+ &event.x, &event.y,
+ &event.state) < 0) {
+ return;
+ }
+
+ event.type = pressrelease;
+ event.display = display;
+ event.window = window;
+ event.subwindow = None;
+ event.time = CurrentTime;
+
+ event.state = state;
+ event.button = button;
+ event.same_screen = True;
+
+ if(XSendEvent(display, window, True, mask, (XEvent*)&event) >= 0) {
+ XFlush(display);
+ }
+
+ return;
+}
+
+void kbptr_move(struct mwm *mwm, struct client *client, long direction)
+{
+ struct geom client_geom;
+ unsigned int dir;
+ unsigned int stepsize;
+
+ client_get_geometry(client, &client_geom);
+
+ if(client != kbptr.last_client) {
+ kbptr.x = client_geom.w / 2;
+ kbptr.y = client_geom.h / 2;
+ kbptr.hstride = kbptr.x / 2;
+ kbptr.vstride = kbptr.y / 2;
+ kbptr.last_client = client;
+ }
+
+ dir = direction & KBPTR_DMASK;
+ stepsize = direction & 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(mwm_get_display(mwm), None, client_get_window(client),
+ 0, 0, 0, 0,
+ kbptr.x, kbptr.y);
+
+ return;
+}
+
+void kbptr_click(struct mwm *mwm, struct client *client, long button)
+{
+ do_button(mwm, client, button, ButtonPress);
+ usleep(100000);
+ do_button(mwm, client, button, ButtonRelease);
+
+ return;
+}
--- /dev/null
+#ifndef MWM_KBPTR_H
+#define MWM_KBPTR_H
+
+struct mwm;
+struct client;
+
+#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
+
+void kbptr_move(struct mwm*, struct client *client, long direction);
+void kbptr_click(struct mwm*, struct client *client, long button);
+
+#endif /* MWM_KBPTR_H */
#include "common.h"
#include "client.h"
#include "theme.h"
+#include "kbptr.h"
typedef void (_mwm_xhandler_t)(struct mwm*, XEvent*);
return;
}
+static void _cmd_kbptr_move(struct mwm *mwm, void *arg)
+{
+ struct client *client;
+ long dir;
+
+ dir = (long)arg;
+ client = mwm_get_focused_client(mwm);
+
+ if(client) {
+ kbptr_move(mwm, client, dir);
+ }
+
+ return;
+}
+
+static void _cmd_kbptr_click(struct mwm *mwm, void *arg)
+{
+ struct client *client;
+ long button;
+
+ button = (long)arg;
+ client = mwm_get_focused_client(mwm);
+
+ if(client) {
+ kbptr_click(mwm, client, button);
+ }
+
+ return;
+}
+
static int _xerror_startup(Display *display, XErrorEvent *event)
{
fprintf(stderr, "Looks like I'm not your only window manager\n");
mwm->commands[MWM_CMD_SHIFT_CLIENT] = _cmd_shift_client;
mwm->commands[MWM_CMD_SHIFT_MONITOR_FOCUS] = _cmd_shift_monitor_focus;
mwm->commands[MWM_CMD_SHIFT_WORKSPACE] = _cmd_shift_workspace;
+ mwm->commands[MWM_CMD_KBPTR_MOVE] = _cmd_kbptr_move;
+ mwm->commands[MWM_CMD_KBPTR_CLICK] = _cmd_kbptr_click;
return(0);
}
MWM_CMD_SHIFT_CLIENT,
MWM_CMD_SHIFT_WORKSPACE,
MWM_CMD_SHIFT_MONITOR_FOCUS,
+ MWM_CMD_KBPTR_MOVE,
+ MWM_CMD_KBPTR_CLICK,
MWM_CMD_MAX
} mwm_cmd_t;