view keynav.c @ 2:640c5e9ea6ad

use root window width and height when hiding mouse cursor, so that it really hides away on the bottom right corner in all cases
author paulo@thepaulopc
date Thu, 17 Jun 2010 00:56:16 -0700
parents 0d5dc7c29c78
children
line source
1 /*
2 * Visual user-directed binary search for something to point your mouse at.
3 */
5 #include "config.h"
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
11 #include <X11/Xlib.h>
12 #include <X11/Xresource.h>
13 #include <X11/Xutil.h>
14 #include <X11/extensions/shape.h>
15 #include <X11/extensions/XTest.h>
17 Display *dpy;
18 Window root;
19 XWindowAttributes attr;
20 int drag = 0;
21 int allmoves = 0;
22 int allhits = 0;
23 int allclicks = 0;
24 int ninegrid_default = 0;
25 int ninegrid_n_switch = 0;
26 int ninegrid = 0;
28 struct /* undo stack */
29 {
30 int x[N_UNDO];
31 int y[N_UNDO];
32 int w[N_UNDO];
33 int h[N_UNDO];
34 int ninegrid[N_UNDO];
35 } u;
37 void toggleninegrid() {
38 if (ninegrid)
39 ninegrid = 0;
40 else
41 ninegrid = 1;
42 }
44 void stats(int hits, int moves) {
45 allhits += hits;
46 allmoves += moves;
47 allclicks++;
48 printf("hits=%d, moves=%d \n", hits, moves);
49 printf("total hits=%d, total moves=%d, total clicks=%d, avg. hit/click=%.2f, avg. moves/click=%.2f \n", allhits, allmoves, allclicks, (float)allhits/(float)allclicks, (float)allmoves/(float)allclicks);
50 fflush(stdout); fflush(stderr); // force buffers to write out
51 }
53 void grab(char *keyname, int mods) {
54 int key;
56 key = XKeysymToKeycode(dpy, XStringToKeysym(keyname));
57 XGrabKey(dpy, key, mods, root, False,
58 GrabModeAsync, GrabModeAsync);
59 XGrabKey(dpy, key, mods | CAPSLOCKMASK, root, False,
60 GrabModeAsync, GrabModeAsync);
61 XGrabKey(dpy, key, mods | NUMLOCKMASK, root, False,
62 GrabModeAsync, GrabModeAsync);
63 XGrabKey(dpy, key, mods | CAPSLOCKMASK | NUMLOCKMASK, root, False,
64 GrabModeAsync, GrabModeAsync);
65 }
67 void ungrab(char *keyname, int mod) {
68 int key;
70 key = XKeysymToKeycode(dpy, XStringToKeysym(keyname));
71 XUngrabKey(dpy, key, mod, root);
72 XUngrabKey(dpy, key, mod | CAPSLOCKMASK, root);
73 XUngrabKey(dpy, key, mod | NUMLOCKMASK, root);
74 XUngrabKey(dpy, key, mod | CAPSLOCKMASK | NUMLOCKMASK, root);
75 }
77 void buttondown(int i) {
78 XTestFakeButtonEvent(dpy, i, True, 50);
79 }
81 void buttonup(int i) {
82 XTestFakeButtonEvent(dpy, i, False, 50);
83 }
85 void warppointer(int x, int y, int w, int h) {
86 XWarpPointer(dpy, None, root, 0, 0, 0, 0, x + w/2, y + h/2);
87 }
89 /* operations for undo */
90 void undo_stack_push(int x, int y, int w, int h) {
91 int i;
93 if (x >= 0 && y >= 0 && w > 0 && h > 0) {
94 fprintf(stderr,"undo_stack_push success: @(%d,%d) #(%d,%d)\n", x, y, w, h);
95 for (i=N_UNDO-1; i>0; i--) {
96 u.x[i] = u.x[i-1];
97 u.y[i] = u.y[i-1];
98 u.w[i] = u.w[i-1];
99 u.h[i] = u.h[i-1];
100 u.ninegrid[i] = u.ninegrid[i-1];
101 }
102 u.x[0] = x;
103 u.y[0] = y;
104 u.w[0] = w;
105 u.h[0] = h;
106 u.ninegrid[0] = ninegrid;
107 }
108 }
110 int undo_stack_pop(int *x, int *y, int *w, int *h, int *ninegrid) {
111 int i;
112 int ret = 0;
114 int rx = u.x[0];
115 int ry = u.y[0];
116 int rw = u.w[0];
117 int rh = u.h[0];
118 int rninegrid = u.ninegrid[0];
120 if (rx >= 0 && ry >= 0 && rw > 0 && rh > 0) {
121 fprintf(stderr,"undo_stack_pop success: @(%d,%d) #(%d,%d)\n", *x, *y, *w, *h);
122 ret = 1; // return success
123 for (i=0; i<N_UNDO-1; i++) {
124 u.x[i] = u.x[i+1];
125 u.y[i] = u.y[i+1];
126 u.w[i] = u.w[i+1];
127 u.h[i] = u.h[i+1];
128 u.ninegrid[i] = u.ninegrid[i+1];
129 }
130 u.x[N_UNDO-1] = -1;
131 u.y[N_UNDO-1] = -1;
132 u.w[N_UNDO-1] = -1;
133 u.h[N_UNDO-1] = -1;
134 u.ninegrid[N_UNDO-1] = -1;
135 *x = rx;
136 *y = ry;
137 *w = rw;
138 *h = rh;
139 *ninegrid = rninegrid;
140 }
141 return ret;
142 }
144 void undo_stack_reset() {
145 int i;
147 for (i=0; i<N_UNDO; i++) {
148 u.x[i] = -1;
149 u.y[i] = -1;
150 u.w[i] = -1;
151 u.h[i] = -1;
152 }
153 }
154 /* =-=-=-=-=-= */
156 int chk_keysym(int keysym, char *str) {
157 int ret = 0;
159 if (XStringToKeysym(str) == keysym)
160 ret = 1;
162 return ret;
163 }
165 GC creategc(Window win) {
166 GC gc;
167 XGCValues values;
169 gc = XCreateGC(dpy, win, 0, &values);
170 XSetForeground(dpy, gc, BlackPixel(dpy, 0));
171 XSetBackground(dpy, gc, WhitePixel(dpy, 0));
172 XSetLineAttributes(dpy, gc, LINEWIDTH, LineSolid, CapButt, JoinBevel);
173 XSetFillStyle(dpy, gc, FillSolid);
175 return gc;
176 }
178 void drawquadrants(Window win, int w, int h) {
179 GC gc;
180 XRectangle clip[20];
181 int idx = 0;
182 Colormap colormap;
183 XColor color;
185 gc = creategc(win);
186 colormap = DefaultColormap(dpy, 0);
188 if (drag)
189 XAllocNamedColor(dpy, colormap, DRAG_COLOR, &color, &color);
190 else
191 XAllocNamedColor(dpy, colormap, NORM_COLOR, &color, &color);
193 /*left*/ clip[idx].x = 0; clip[idx].y = 0; clip[idx].width = BORDER; clip[idx].height = h;
194 idx++;
195 /*right*/ clip[idx].x = w-BORDER; clip[idx].y = 0; clip[idx].width = BORDER; clip[idx].height = h;
196 idx++;
197 /*top*/ clip[idx].x = 0; clip[idx].y = 0; clip[idx].width = w; clip[idx].height = BORDER;
198 idx++;
199 /*bottom*/ clip[idx].x = 0; clip[idx].y = h-BORDER; clip[idx].width = w; clip[idx].height = BORDER;
200 idx++;
202 if (ninegrid) {
203 /*1st horiz*/
204 clip[idx].x = 0; clip[idx].y = h/3 - BORDER/2;
205 clip[idx].width = w; clip[idx].height = BORDER;
206 idx++;
207 /*2nd horiz*/
208 clip[idx].x = 0; clip[idx].y = h*2/3 - BORDER/2;
209 clip[idx].width = w; clip[idx].height = BORDER;
210 idx++;
211 /*1st vert*/
212 clip[idx].x = w/3 - BORDER/2; clip[idx].y = 0;
213 clip[idx].width = BORDER; clip[idx].height = h;
214 idx++;
215 /*2nd vert*/
216 clip[idx].x = w*2/3 - BORDER/2; clip[idx].y = 0;
217 clip[idx].width = BORDER; clip[idx].height = h;
218 idx++;
219 } else {
220 /*horiz*/
221 clip[idx].x = 0; clip[idx].y = h/2 - BORDER/2;
222 clip[idx].width = w; clip[idx].height = BORDER;
223 idx++;
224 /*vert*/
225 clip[idx].x = w/2 - BORDER/2; clip[idx].y = 0;
226 clip[idx].width = BORDER; clip[idx].height = h;
227 idx++;
228 }
230 XShapeCombineRectangles(dpy, win, ShapeBounding, 0, 0, clip, idx, ShapeSet, 0);
232 XFillRectangle(dpy, win, gc, 0, 0, w, h);
234 XSetForeground(dpy, gc, color.pixel);
235 XDrawLine(dpy, win, gc, BORDER - PEN, BORDER - PEN, w - PEN, BORDER - PEN); //top line
236 XDrawLine(dpy, win, gc, BORDER - PEN, h - PEN, w - PEN, h - PEN); //bottom line
237 XDrawLine(dpy, win, gc, BORDER - PEN, BORDER - PEN, BORDER - PEN, h - PEN); //left line
238 XDrawLine(dpy, win, gc, w - PEN, BORDER - PEN, w - PEN, h - PEN); //left line
240 if (ninegrid) {
241 XDrawLine(dpy, win, gc, w/3, 0, w/3, h); // 1st vert line
242 XDrawLine(dpy, win, gc, w*2/3, 0, w*2/3, h); // 2nd vert line
243 XDrawLine(dpy, win, gc, 0, h/3, w, h/3); // 1st horiz line
244 XDrawLine(dpy, win, gc, 0, h*2/3, w, h*2/3); // 2nd horiz line
245 } else {
246 XDrawLine(dpy, win, gc, w/2, 0, w/2, h); // vert line
247 XDrawLine(dpy, win, gc, 0, h/2, w, h/2); // horiz line
248 }
250 XFlush(dpy);
251 }
253 int handlekey(int keysym, int mod, int *moves, int *x, int *y, int *w, int *h) {
254 int ret = 1;
255 int ox = *x;
256 int oy = *y;
257 int ow = *w;
258 int oh = *h;;
260 if (mod & SHIFTMASK || mod & SHIFTMASK & NUMLOCKMASK || mod & SHIFTMASK & CAPSLOCKMASK || mod & SHIFTMASK & CAPSLOCKMASK & NUMLOCKMASK) {
261 if (chk_keysym(keysym, KEY_CENTER))
262 toggleninegrid();
263 if (chk_keysym(keysym, KEY_LEFT) || chk_keysym(keysym, KEY_UPLEFT) || chk_keysym(keysym, KEY_DOWNLEFT)) {
264 if (*x > 0) *x -= *w; // shift left
265 }
266 if (chk_keysym(keysym, KEY_DOWN) || chk_keysym(keysym, KEY_DOWNLEFT) || chk_keysym(keysym, KEY_DOWNRIGHT)) {
267 if ((*y + *h) < attr.height) *y += *h; // shift down
268 }
269 if (chk_keysym(keysym, KEY_UP) || chk_keysym(keysym, KEY_UPLEFT) || chk_keysym(keysym, KEY_UPRIGHT)) {
270 if (*y > 0) *y -= *h; // shift up
271 }
272 if (chk_keysym(keysym, KEY_RIGHT) || chk_keysym(keysym, KEY_UPRIGHT) || chk_keysym(keysym, KEY_DOWNRIGHT)) {
273 if ((*x + *w) < attr.width) *x += *w; // shift right
274 }
275 } else {
276 if (ninegrid) {
277 if (chk_keysym(keysym, KEY_UPLEFT)) {
278 *w /= 3; *h /= 3; // split upper left
279 } else if (chk_keysym(keysym, KEY_UP)) {
280 *w /= 3; *h /= 3; // split up
281 *x += *w;
282 } else if (chk_keysym(keysym, KEY_UPRIGHT)) {
283 *w /= 3; *h /= 3; // split upper right
284 *x += *w * 2;
285 } else if (chk_keysym(keysym, KEY_LEFT)) {
286 *w /= 3; *h /= 3; // split left
287 *y += *h;
288 } else if (chk_keysym(keysym, KEY_CENTER)) {
289 *w /= 3; *h /= 3; // split center
290 *x += *w; *y += *h;
291 } else if (chk_keysym(keysym, KEY_RIGHT)) {
292 *w /= 3; *h /= 3; // split right
293 *x += *w * 2; *y += *h;
294 } else if (chk_keysym(keysym, KEY_DOWNLEFT)) {
295 *w /= 3; *h /= 3; // split bottom left
296 *y += *h * 2;
297 } else if (chk_keysym(keysym, KEY_DOWN)) {
298 *w /= 3; *h /= 3; // split down
299 *x += *w; *y += *h * 2;
300 } else if (chk_keysym(keysym, KEY_DOWNRIGHT)) {
301 *w /= 3; *h /= 3; // split bottom right
302 *x += *w * 2; *y += *h * 2;
303 }
304 } else {
305 if (chk_keysym(keysym, KEY_LEFT) || chk_keysym(keysym, KEY_UPLEFT) || chk_keysym(keysym, KEY_DOWNLEFT)) {
306 *w /= 2; // split left
307 }
308 if (chk_keysym(keysym, KEY_DOWN) || chk_keysym(keysym, KEY_DOWNLEFT) || chk_keysym(keysym, KEY_DOWNRIGHT)) {
309 *h /= 2; // split down
310 *y += *h;
311 }
312 if (chk_keysym(keysym, KEY_UP) || chk_keysym(keysym, KEY_UPLEFT) || chk_keysym(keysym, KEY_UPRIGHT)) {
313 *h /= 2; // split up
314 }
315 if (chk_keysym(keysym, KEY_RIGHT) || chk_keysym(keysym, KEY_UPRIGHT) || chk_keysym(keysym, KEY_DOWNRIGHT)) {
316 *w /= 2; // split right
317 *x += *w;
318 }
319 }
320 }
322 if (ox != *x || oy != *y || ow != *w || oh != *h) {
323 undo_stack_push(ox, oy, ow, oh);
324 *moves += 1;
325 }
326 else if (chk_keysym(keysym, KEY_UNDO)) {
327 int i, j;
329 if (mod & SHIFTMASK) j = 0;
330 else j = SHIFT_N_UNDO-1;
332 for (i=j; i<SHIFT_N_UNDO; i++) {
333 undo_stack_pop(x, y, w, h, &ninegrid);
334 *moves -= 1;
335 }
336 ret = 2;
337 }
339 if (*w < 1 || *h < 1) {
340 fprintf(stderr,"OOPS. Area too small. Giving up :(\n");
341 return 0;
342 }
344 if (drag && !(mod & WARPMASK) || !drag && (mod & WARPMASK))
345 warppointer(*x, *y, *w, *h);
347 fprintf(stderr,"Box: @(%d,%d) #(%d,%d)\n", *x, *y, *w, *h);
349 return ret;
350 }
352 void startmousekey(int from_undo) {
353 int keysym;
354 int done = 0;
355 int x,y,w,h;
356 int warp = 0;
357 int click = 0;
358 int k, hits, moves;
359 int mod;
361 Window zone;
363 // disable trigger keygrab
364 ungrab(KEY_TRIGGER, TRIGGERMASK);
365 ungrab(KEY_UNDO, TRIGGERMASK);
367 // grab keyboard
368 XGrabKeyboard(dpy, root, False, GrabModeAsync, GrabModeAsync, CurrentTime);
370 // init stuff
371 hits = moves = 0;
372 if (from_undo && undo_stack_pop(&x, &y, &w, &h, &ninegrid));
373 /* null -- only check for undo_stack_pop success */
374 else {
375 undo_stack_reset();
376 if (ninegrid_default)
377 ninegrid = 1;
378 x = y = 0;
379 w = attr.width;
380 h = attr.height;
381 }
383 zone = XCreateSimpleWindow(dpy, root, x, y, w, h, 1, BlackPixel(dpy, 0), WhitePixel(dpy, 0));
385 { /* Tell the window manager not to manage us */
386 unsigned long valuemask;
387 XSetWindowAttributes winattr;
388 winattr.override_redirect = 1;
389 XChangeWindowAttributes(dpy, zone, CWOverrideRedirect, &winattr);
390 }
392 drawquadrants(zone, w, h);
393 XMapWindow(dpy, zone);
394 drawquadrants(zone, w, h);
396 fprintf(stderr,"Starting quadrants...\n");
397 while (!done) {
398 XEvent e;
399 XNextEvent(dpy, &e);
400 if (e.type == KeyPress) {
401 keysym = XKeycodeToKeysym(dpy, e.xkey.keycode, 0);
402 mod = e.xkey.state;
403 if (chk_keysym(keysym, KEY_Q_CLICK)) {
404 done++;
405 click = 1;
406 } else if (chk_keysym(keysym, KEY_Q_RCLICK)) {
407 done++;
408 click = 3;
409 } else if (chk_keysym(keysym, KEY_QW_CLICK)) {
410 done++;
411 warp = 1;
412 click = 1;
413 } else if (chk_keysym(keysym, KEY_Q_MCLICK)) {
414 done++;
415 click = 2;
416 } else if (chk_keysym(keysym, KEY_QW_RCLICK)) {
417 done++;
418 warp = 1;
419 click = 3;
420 } else if (chk_keysym(keysym, KEY_HIDE)) {
421 done++;
422 warp = 1;
423 x = attr.width;
424 y = attr.height;
425 } else if (chk_keysym(keysym, KEY_ESCAPE)) {
426 warp = 0;
427 done++;
428 } else if (!drag && chk_keysym(keysym, KEY_ONE)) {
429 buttondown(1);
430 buttonup(1);
431 } else if (!drag && chk_keysym(keysym, KEY_TWO)) {
432 buttondown(2);
433 buttonup(2);
434 } else if (!drag && chk_keysym(keysym, KEY_THREE)) {
435 buttondown(3);
436 buttonup(3);
437 } else {
438 if (k = handlekey(keysym, mod, &moves, &x, &y, &w, &h)) {
439 if (ninegrid_default && ninegrid_n_switch && k < 2 && moves >= ninegrid_n_switch)
440 ninegrid = 0;
441 hits++;
442 XMoveResizeWindow(dpy, zone, x, y, w, h);
443 drawquadrants(zone, w, h);
444 } else
445 done++;
446 }
447 }
448 }
450 // end mouse key
451 XUngrabKeyboard(dpy, CurrentTime);
452 XDestroyWindow(dpy, zone);
453 undo_stack_push(x, y, w, h);
455 if (warp)
456 warppointer(x, y, w, h);
457 if (click == 1) {
458 if (!drag)
459 buttondown(1);
460 if (mod & DRAGMASK && !drag)
461 drag = 1;
462 else if (mod & DRAGMASK) {
463 buttonup(1);
464 buttondown(1);
465 } else {
466 buttonup(1);
467 drag = 0;
468 }
469 } else if (click) {
470 buttondown(click);
471 buttonup(click);
472 }
474 if (click) stats(hits, moves);
476 // loop back if in drag mode
477 if (drag) {
478 usleep(50);
479 startmousekey(0);
480 }
482 // re-enable trigger keygrabs
483 grab(KEY_TRIGGER, TRIGGERMASK);
484 grab(KEY_UNDO, TRIGGERMASK);
485 }
487 int main(int argc, char **argv) {
488 char *pcDisplay;
490 if ((dpy = XOpenDisplay(pcDisplay = getenv("DISPLAY"))) == NULL) {
491 fprintf(stderr,"Error: Can't open display: %s", pcDisplay);
492 exit(1);
493 }
494 fprintf(stderr,"Display: %s\n", pcDisplay);
496 // parse options
497 if (argc > 1)
498 if (argv[1][0] && argv[1][0] == '-')
499 if (argv[1][1] && argv[1][1] == 'n') {
500 ninegrid_default = 1;
501 fprintf(stderr,"nine-grid default mode \n");
502 if (argv[2]) {
503 ninegrid_n_switch = atoi(argv[2]);
504 fprintf(stderr,"nine-grid autoswitch num: %d \n", ninegrid_n_switch);
505 }
506 }
508 root = XDefaultRootWindow(dpy);
509 XGetWindowAttributes(dpy, root, &attr);
511 // these are the trigger keygrabs
512 grab(KEY_TRIGGER, TRIGGERMASK);
513 grab(KEY_UNDO, TRIGGERMASK);
515 while (1) {
516 XEvent e;
517 XNextEvent(dpy, &e);
518 if (e.type == KeyPress)
519 if (chk_keysym(XKeycodeToKeysym(dpy, e.xkey.keycode, 0), KEY_UNDO))
520 startmousekey(1);
521 else
522 startmousekey(0);
523 }
524 }