/* need this, or else we don't get a prototype for strdup() from string.h */ #define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* microseconds to wait between sending a keypresss or mouse click event and its release */ #define EVENT_DELAY 100000 /* if we can't find an unused keycode in the X keymap, use this one */ #define FALLBACK_KEYCODE 255 /* min/max allowed keycodes. really this should be queried from the X server, but for Xorg this value is good enough for argument checking. the max is 0xff as keycodes are 8 bit unsigned. */ #define MIN_KEYCODE 8 #define MAX_KEYCODE 255 /* same as above, for mouse buttons. */ #define MIN_BUTTON 1 #define MAX_BUTTON 10 /* user options */ int interval = 250; /* -i */ int debug = 0; /* -D */ int keycode = -1; /* -k */ int button = -1; /* -b */ char *event_dir = EVENTDIR; /* -d */ char *js_node_name = JSNODE; /* -j */ int autodiscover = 1; /* cleared if user supplies joydev args */ /* joystick stuff */ char *joynames[MAX_STICKS + 1]; int last_joyname = 0; int joyfds[MAX_STICKS + 1]; const char *self; /* X stuff */ Display *xdisp; /* inotify stuff */ int inotify_fd, watch_fd; char inotify_buf[sizeof(struct inotify_event) + NAME_MAX + 1]; struct inotify_event *inotify_ev = (struct inotify_event *)inotify_buf; void usage(void) { printf("jsmond v" VERSION " by B. Watson, WTFPL\n"); printf("Usage: %s [-i interval] [-b button | -k keycode] [-j name]\n", self); printf(" [-d dir] [-D] [joystick [...]]\n"); printf("Built-in defaults: MAX_STICKS=%d, EVENTDIR=" EVENTDIR ", JSNODE=" JSNODE "\n", MAX_STICKS); printf("See man page for details\n"); exit(0); } void die(const char *msg) { fprintf(stderr, "%s: %s\n", self, msg); exit(1); } /* make some trivial Xlib call that will result in Xlib killing this process if it fails. XNoOp looks like it was meant for exactly this, but it keeps returning 1 even if the X server is killed. */ void ping_x_server(void) { (void)XPending(xdisp); if(debug) fprintf(stderr, "X server is still alive\n"); } void send_fake_key(void) { if(debug) fprintf(stderr, "sending keycode %d\n", keycode); /* press... */ XTestFakeKeyEvent(xdisp, keycode, 1, 0L); XSync(xdisp, 0); /* ...wait 1/10 sec... */ usleep(EVENT_DELAY); /* ...release */ XTestFakeKeyEvent(xdisp, keycode, 0, 0L); XSync(xdisp, 0); } void send_fake_click(void) { if(debug) fprintf(stderr, "sending button %d click\n", button); /* press... */ XTestFakeButtonEvent(xdisp, button, 1, 0L); XSync(xdisp, 0); /* ...wait 1/10 sec... */ usleep(EVENT_DELAY); /* ...release */ XTestFakeButtonEvent(xdisp, button, 0, 0L); XSync(xdisp, 0); } void open_joysticks(void) { int fdcount, i; struct js_event e; fdcount = 0; for(i = 0; i < last_joyname; i++) { int synthev = 0; if(joyfds[i] > -1) close(joyfds[i]); joyfds[i] = open(joynames[i], O_RDONLY | O_NONBLOCK); if(joyfds[i] > -1) { fdcount++; while( (read(joyfds[i], &e, sizeof(e)) > 0) && (e.type & JS_EVENT_INIT) ) synthev++; if(debug) fprintf(stderr, "opened %s, fd %d, skipped %d synthetic events\n", joynames[i], joyfds[i], synthev); } } if(debug) fprintf(stderr, "opened %d devices\n", fdcount); } void init_joysticks(void) { int i; for(i = 0; i < MAX_STICKS; i++) joyfds[i] = -1; open_joysticks(); } void check_inotify() { int res; res = read(inotify_fd, inotify_buf, sizeof(inotify_buf) + NAME_MAX + 1); if(res > 0) { if(debug) { fprintf(stderr, "read %d bytes from inotify_fd\n", res); if(!inotify_ev->len) { fprintf(stderr, "got event with no name: %x\n", inotify_ev->mask); } else { fprintf(stderr, "got event with name %s: %x\n", inotify_ev->name, inotify_ev->mask); } } usleep(EVENT_DELAY); /* might not need, be paranoid */ open_joysticks(); } else { if(errno != EAGAIN) { fprintf(stderr, "%s: failed to read from inotify_fd: %s\n", self, strerror(errno)); exit(1); } } } /* this is the point of no return. the only way out of main_loop() is process death. */ void main_loop(void) { struct js_event e; int i, active, sleep_us; fprintf(stderr, "entering main_loop()\n"); sleep_us = interval * 1000; while(1) { /* Xlib will obligingly kill us, if X goes away */ ping_x_server(); /* check_inotify() will close & reopen the joystick fds as needed */ check_inotify(); /* let the fds gather events as we slumber */ if(debug) fprintf(stderr, "sleeping %dms...\n", interval); usleep(sleep_us); /* now see if any of them got any events while we took a nap */ active = 0; for(i = 0; i < last_joyname; i++) { if(joyfds[i] < 0) continue; if(debug) fprintf(stderr, "reading device %d, fd %d\n", i, joyfds[i]); /* count events. any we get here *should* be real, but it doesn't hurt to mask out synthetic ones again */ while(read(joyfds[i], &e, sizeof(e)) > 0) { if(!(e.type & JS_EVENT_INIT)) active++; } /* EAGAIN just means there are no more events to read. anything else is a problem, though not fatal */ if(errno != EAGAIN) { if(debug) fprintf(stderr, "got error on device %d: %s\n", i, strerror(errno)); } } /* if we got any activity on any of the fds, do our thing */ if(active) { if(debug) fprintf(stderr, "*** got activity!\n"); if(button > -1) send_fake_click(); else send_fake_key(); } else if(debug) fprintf(stderr, "no activity\n"); } } /* make self point to our executable name without any directory */ void set_exe_name(const char *argv0) { const char *p; self = argv0; for(p = self; *p; p++) if(p[0] == '/' && p[1]) self = p + 1; } /* add a joystick by pathname or number. note this only gets called once, at startup: the list never gets modified after that. */ void add_joystick_name(const char *name) { if(last_joyname >= MAX_STICKS) { fprintf(stderr, "%s: too many device names, monitoring only the first %d\n", self, last_joyname); return; } if(*name >= '0' && *name <= '9') { char buf[PATH_MAX + 1]; sprintf(buf, "%s/%s%d", event_dir, js_node_name, atoi(name)); add_joystick_name(buf); return; } if( !(joynames[last_joyname] = strdup(name)) ) die(strerror(errno)); if(debug) fprintf(stderr, "added device name %d: %s\n", last_joyname, name); last_joyname++; } void populate_joynames(void) { int i; char buf[PATH_MAX + 1]; for(i = 0; i < MAX_STICKS; i++) { sprintf(buf, "%s/%s%d", event_dir, js_node_name, i); add_joystick_name(buf); } } void parse_args(int argc, char **argv) { char *nextarg; if(argc > 1 && strcmp(argv[1], "--help") == 0) usage(); while(++argv, --argc) { if(argv[0][0] == '-') { if(argv[0][1] && argv[0][2]) die("spaces required between options and arguments, please"); nextarg = argv[1]; switch(argv[0][1]) { case 'k': if(nextarg && (keycode = atoi(nextarg)) && (keycode >= MIN_KEYCODE) && (keycode <= MAX_KEYCODE) ) { argv++, argc--; } else { fprintf(stderr, "%s: -k requires a keycode argument, %d-%d\n", self, MIN_KEYCODE, MAX_KEYCODE); exit(1); } break; case 'b': if(nextarg) button = atoi(nextarg); if ( (button >= MIN_BUTTON) && (button <= MAX_BUTTON) ) { argv++, argc--; } else { fprintf(stderr, "%s: -b requires a mouse button argument, %d-%d\n", self, MIN_BUTTON, MAX_BUTTON); exit(1); } break; case 'i': if(nextarg && (interval = atoi(nextarg)) > 0) argv++, argc--; else die("-i requires a positive integer argument in milliseconds"); break; case 'd': if(nextarg) { event_dir = nextarg; argv++, argc--; } else { die("-d requires a directory argument"); } break; case 'j': if(nextarg) { js_node_name = nextarg; argv++, argc--; } else { die("-j requires a string argument"); } break; case 'D': debug = 1; break; default: die("unrecognized argument, try --help"); break; } } else { add_joystick_name(*argv); autodiscover = 0; } } if((keycode > -1) && (button > -1)) die("can't send both keycode and button events"); } /* make ourselves a daemon, double-fork technique. verbose comments are not here because I think you need them, they're here for me so I don't forget what this gibberish is for... */ void daemonize(void) { pid_t pid; if((pid = fork()) < 0) { /* in parent, fork failed */ fprintf(stderr, "%s: %s\n", self, strerror(errno)); die("failed to daemonize"); } else if(pid) { /* in parent, fork succeeded */ exit(0); } /* now we're in the child, aka the 2nd generation parent. the only reason for the first fork() was because setsid() can't be run in the parent process */ if( (setsid() < 0) ) { fprintf(stderr, "%s: %s\n", self, strerror(errno)); die("failed to daemonize"); } /* go on, do it again */ if((pid = fork()) < 0) { /* in 2nd gen parent, fork failed */ fprintf(stderr, "%s: %s\n", self, strerror(errno)); die("failed to daemonize"); } else if(pid) { /* in 2nd gen parent, fork succeeded */ fprintf(stderr, "%s: daemonized, PID %u\n", self, pid); exit(0); } /* now we're in the grandchild, and we no longer need stdin/out/err */ close(0); close(1); close(2); /* do this so we don't keep a filesystem from being umounted because it's still busy (as our cwd) */ chdir("/"); } void init_inotify(void) { inotify_fd = inotify_init1(IN_NONBLOCK); if( inotify_fd < 0) { fprintf(stderr, "%s: inotify_init1() failed: %s", self, strerror(errno)); exit(1); } if(debug) fprintf(stderr, "inotify_init1() returned %d\n", inotify_fd); watch_fd = inotify_add_watch(inotify_fd, event_dir, IN_CREATE | IN_DELETE); if( watch_fd < 0) { fprintf(stderr, "%s: inotify_add_watch() failed: %s", self, strerror(errno)); exit(1); } if(debug) fprintf(stderr, "inotify_add_watch() on %s returned %d\n", event_dir, inotify_fd); } void connect_to_x(void) { int ignoreme; if(debug) fprintf(stderr, "connecting to X server...\n"); xdisp = XOpenDisplay(getenv("DISPLAY")); if(!xdisp) die("can't connect to X server"); if(debug) fprintf(stderr, "connected to X server\n"); if(XTestQueryExtension(xdisp, &ignoreme, &ignoreme, &ignoreme, &ignoreme)) { if(debug) fprintf(stderr, "X server supports XTest extension, good\n"); } else { die("X server doesn't support XTest extension\n"); } } /* the goggles, they do nothing! */ void find_keycode() { int i, min_code = 0, max_code = 0; /* man page doesn't document the return value */ (void)XDisplayKeycodes(xdisp, &min_code, &max_code); if(debug) fprintf(stderr, "XDisplayKeycodes min %d, max %d\n", min_code, max_code); /* XXX: XKeycodeToKeysym() is deprecated. I must learn XKB. */ for(i = min_code; i <= max_code; i++) { KeySym ks = XKeycodeToKeysym(xdisp, i, 0); if(ks == NoSymbol) { keycode = i; if(debug) fprintf(stderr, "using keycode %d\n", keycode); break; } } if(keycode < 0) { fprintf(stderr, "%s: can't find a free keycode in the keymap, using %d\n", self, FALLBACK_KEYCODE); keycode = FALLBACK_KEYCODE; } } void init_x(void) { connect_to_x(); if(keycode < 0 && button < 0) { find_keycode(); } /* do this now, before daemonizing. if the keycode is invalid, Xlib will kill us. we'd better let that happen while we still have a stderr, so the user can see what happened. */ if(keycode > -1) send_fake_key(); /* same for the mouse, just in case */ if(button > -1) send_fake_click(); } int main(int argc, char **argv) { set_exe_name(argv[0]); parse_args(argc, argv); if(autodiscover) populate_joynames(); init_joysticks(); init_inotify(); init_x(); if(!debug) daemonize(); main_loop(); return 0; }