| 1 | #include <X11/Xlib.h> |
| 2 | #include <math.h> |
| 3 | #include <X11/extensions/XTest.h> |
| 4 | #include <stdio.h> |
| 5 | #include <signal.h> |
| 6 | #include <sys/time.h> |
| 7 | #include <time.h> |
| 8 | |
| 9 | #define STATUS_LEFT 0x01 |
| 10 | #define STATUS_RIGHT 0x02 |
| 11 | #define STATUS_UP 0x04 |
| 12 | #define STATUS_DOWN 0x08 |
| 13 | #define STATUS_MOVING 0xF |
| 14 | #define STATUS_TAPJUMP 0x100 |
| 15 | #define STATUS_RUNNING 0x80000000 |
| 16 | |
| 17 | #define MIN(a,b) ({int __a = (a), __b = (b); (__a < __b) ? __a : __b; }) |
| 18 | #define MAX(a,b) ({int __a = (a), __b = (b); (__a > __b) ? __a : __b; }) |
| 19 | |
| 20 | unsigned long long now() |
| 21 | { |
| 22 | struct timeval tv; |
| 23 | gettimeofday(&tv, NULL); |
| 24 | return tv.tv_sec*1000000 + tv.tv_usec; |
| 25 | } |
| 26 | |
| 27 | int main() |
| 28 | { |
| 29 | int e; |
| 30 | XEvent ev; |
| 31 | Display *dpy; |
| 32 | int status = 0, dmy; |
| 33 | char key = 0, lastkey = 0; |
| 34 | unsigned long long lasttime = 0; |
| 35 | int repeats = 1; |
| 36 | int starttime = 0; |
| 37 | |
| 38 | dpy = XOpenDisplay(NULL); |
| 39 | if (!dpy) |
| 40 | { |
| 41 | printf("Display open failed; bailing out\n"); |
| 42 | exit(1); |
| 43 | } |
| 44 | |
| 45 | if (!XTestQueryExtension(dpy, &dmy, &dmy, &dmy, &dmy)) |
| 46 | { |
| 47 | printf("XTest NOT supported\n"); |
| 48 | exit(1); |
| 49 | } |
| 50 | |
| 51 | printf("Grabbing keyboard...\n"); |
| 52 | XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("grave")), Mod1Mask, RootWindow(dpy, DefaultScreen(dpy)), True, GrabModeAsync, GrabModeAsync); |
| 53 | |
| 54 | while (1) |
| 55 | { |
| 56 | while (XCheckMaskEvent(dpy, 0xFFFFFFFF, &ev)) |
| 57 | { |
| 58 | switch (ev.type) |
| 59 | { |
| 60 | case KeyPress: |
| 61 | case KeyRelease: |
| 62 | { |
| 63 | XKeyEvent *ke = (XKeyEvent*)&ev; |
| 64 | char *keystr = XKeysymToString(XKeycodeToKeysym(dpy, ke->keycode, 0)); |
| 65 | //printf("Key %s: %s\n", ev.type == KeyPress ? "PRESS " : "RELEASE", keystr); |
| 66 | |
| 67 | if (!strcmp(keystr, "grave")) { |
| 68 | if (ev.type == KeyPress) |
| 69 | { |
| 70 | XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("grave")), AnyModifier, RootWindow(dpy, DefaultScreen(dpy)), True, GrabModeAsync, GrabModeAsync); |
| 71 | XAutoRepeatOff(dpy); |
| 72 | status |= STATUS_RUNNING; |
| 73 | } else { |
| 74 | XUngrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("grave")), AnyModifier, RootWindow(dpy, DefaultScreen(dpy))); |
| 75 | XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("grave")), Mod1Mask, RootWindow(dpy, DefaultScreen(dpy)), True, GrabModeAsync, GrabModeAsync); |
| 76 | XAutoRepeatOn(dpy); |
| 77 | status &= ~STATUS_RUNNING; |
| 78 | } |
| 79 | } else if (!strcmp(keystr, "u")) |
| 80 | XTestFakeButtonEvent(dpy, 1, ev.type == KeyPress, 0); |
| 81 | else if (!strcmp(keystr, "o")) |
| 82 | XTestFakeButtonEvent(dpy, 3, ev.type == KeyPress, 0); |
| 83 | else if (!strcmp(keystr, "p")) |
| 84 | XTestFakeButtonEvent(dpy, 2, ev.type == KeyPress, 0); |
| 85 | else if (!strcmp(keystr, "apostrophe") || !strcmp(keystr, "semicolon")) { |
| 86 | if (ev.type == KeyPress) |
| 87 | status |= STATUS_TAPJUMP; |
| 88 | else |
| 89 | status &= ~STATUS_TAPJUMP; |
| 90 | repeats = 1; |
| 91 | } else if (!strcmp(keystr, "j")) { |
| 92 | if (ev.type == KeyPress) |
| 93 | { |
| 94 | status |= STATUS_LEFT; |
| 95 | if ((lastkey == 'j') && ((now() - lasttime) < 250000) && (status & STATUS_TAPJUMP)) |
| 96 | XTestFakeRelativeMotionEvent(dpy, -100.0 * pow(repeats++, 0.8), 0, 0); |
| 97 | } else |
| 98 | status &= ~STATUS_LEFT; |
| 99 | } else if (!strcmp(keystr, "l")) { |
| 100 | if (ev.type == KeyPress) |
| 101 | { |
| 102 | status |= STATUS_RIGHT; |
| 103 | if ((lastkey == 'l') && ((now() - lasttime) < 250000) && (status & STATUS_TAPJUMP)) |
| 104 | XTestFakeRelativeMotionEvent(dpy, 100.0 * pow(repeats++, 0.8), 0, 0); |
| 105 | } else |
| 106 | status &= ~STATUS_RIGHT; |
| 107 | } else if (!strcmp(keystr, "i")) { |
| 108 | if (ev.type == KeyPress) |
| 109 | { |
| 110 | status |= STATUS_UP; |
| 111 | if ((lastkey == 'i') && ((now() - lasttime) < 250000) && (status & STATUS_TAPJUMP)) |
| 112 | XTestFakeRelativeMotionEvent(dpy, 0, -100.0 * pow(repeats++, 0.8), 0); |
| 113 | } else |
| 114 | status &= ~STATUS_UP; |
| 115 | } else if (!strcmp(keystr, "k")) { |
| 116 | if (ev.type == KeyPress) |
| 117 | { |
| 118 | status |= STATUS_DOWN; |
| 119 | if (lastkey == 'k' && (now() - lasttime) < 250000 && (status & STATUS_TAPJUMP)) |
| 120 | XTestFakeRelativeMotionEvent(dpy, 0, 100.0 * pow(repeats++, 0.8), 0); |
| 121 | } else |
| 122 | status &= ~STATUS_DOWN; |
| 123 | } |
| 124 | if (strlen(keystr) == 1 && ev.type == KeyPress) |
| 125 | { |
| 126 | lastkey = keystr[0]; |
| 127 | lasttime = now(); |
| 128 | } else if (ev.type == KeyPress) { |
| 129 | lastkey = 0; |
| 130 | repeats = 0; |
| 131 | } |
| 132 | break; |
| 133 | } |
| 134 | default: |
| 135 | printf("NFI what %d is\n", ev.type); |
| 136 | } |
| 137 | } |
| 138 | if (!(status & STATUS_RUNNING)) |
| 139 | status = 0; |
| 140 | if (!(status & STATUS_MOVING)) // if we haven't anything to do, don't sit and suck up cycles |
| 141 | { |
| 142 | XPeekEvent(dpy, &ev); |
| 143 | starttime = now(); |
| 144 | } |
| 145 | usleep(20000 - MIN(MAX(pow(13 * (now() - starttime), 0.6), 0), 18000)); |
| 146 | if (now() - lasttime > 250000) |
| 147 | repeats = 1; |
| 148 | if (status & STATUS_LEFT) |
| 149 | XTestFakeRelativeMotionEvent(dpy, -2, 0, 0); |
| 150 | if (status & STATUS_RIGHT) |
| 151 | XTestFakeRelativeMotionEvent(dpy, 2, 0, 0); |
| 152 | if (status & STATUS_UP) |
| 153 | XTestFakeRelativeMotionEvent(dpy, 0, -2, 0); |
| 154 | if (status & STATUS_DOWN) |
| 155 | XTestFakeRelativeMotionEvent(dpy, 0, 2, 0); |
| 156 | } |
| 157 | |
| 158 | exit(0); |
| 159 | } |