From aaa2f1e410f805794202022fde9df29aa04db30f Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Sun, 17 May 2020 03:56:35 -0400 Subject: 0.2.0, major surgery --- jsmond.c | 342 +++++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 247 insertions(+), 95 deletions(-) (limited to 'jsmond.c') diff --git a/jsmond.c b/jsmond.c index f62da29..abf209f 100644 --- a/jsmond.c +++ b/jsmond.c @@ -1,57 +1,51 @@ /* need this, or else we don't get a prototype for strdup() from string.h */ -#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 500 #include #include #include #include -#include #include #include #include #include #include -#ifdef HAVE_X11 -# include -#endif - -#ifndef PATH_MAX -# define PATH_MAX 1024 -#endif - -/* -c option: */ -const char *command = "xscreensaver-command -deactivate"; -/* -i option: */ -int interval = 60; -/* cleared if user supplies one or more joysticks on command line: */ -int autodiscover = 1; -/* -d option: */ -int debug = 0; +#include +#include +#include +#include +#include + +/* 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; -#ifdef HAVE_X11 +/* X stuff */ Display *xdisp; -int use_x = 1; -#endif + +/* 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 [-c cmd] [-i interval] " -#ifdef HAVE_X11 - "[-x] " -#endif - "[joystick [...]]\n", self); - printf("Build options: " -#ifndef HAVE_X11 - "no " -#endif - "X11 support, MAX_STICKS %d, JSDEVBASE " JSDEVBASE "\n", - MAX_STICKS); + printf("Usage: %s [-i interval] [-b button | -k keycode] [-j name]\n", self); + printf(" [-d dir] [-D] [joystick [...]]\n"); printf("See man page for details\n"); exit(0); } @@ -61,7 +55,6 @@ void die(const char *msg) { exit(1); } -#ifdef HAVE_X11 /* 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. */ @@ -69,44 +62,110 @@ void ping_x_server(void) { (void)XPending(xdisp); if(debug) fprintf(stderr, "X server is still alive\n"); } -#endif +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(100000); + + /* ...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(100000); + + /* ...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(100000); /* 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 joyfds[MAX_STICKS + 1]; - int i, fdcount, active; + int i, active; fprintf(stderr, "entering main_loop()\n"); while(1) { - active = 0; -#ifdef HAVE_X11 - if(use_x) ping_x_server(); -#endif - - /* open all the files named in the list. - doing it this way is more work than opening each one once before - main_loop() and keeping it open, but this lets us easily track - when devices appear and disappear. */ - fdcount = 0; - for(i = 0; i < last_joyname; i++) { - int synthev = 0; - 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, sleeping %d sec\n", fdcount, interval); + /* Xlib will obligingly kill us, if X goes away */ + ping_x_server(); - /* let them gather events as we sleep */ - sleep(interval); + /* check_inotify() will close & reopen the joystick fds as needed */ + check_inotify(); - /* now see if any of the fds got any events while we slept */ + /* let the fds gather events as we slumber */ + if(debug) fprintf(stderr, "sleeping %dms...\n", interval); + usleep(interval * 1000); + + /* 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; @@ -128,15 +187,12 @@ void main_loop(void) { /* if we got any activity on any of the fds, do our thing */ if(active) { - int res; - if(debug) fprintf(stderr, "*** got activity! executing: %s\n", command); - res = system(command); - if(debug) fprintf(stderr, "exit status %d (%s)\n", res, (res ? "failed" : "OK")); + 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"); - - /* close 'em all */ - for(i = 0; i < last_joyname; i++) - close(joyfds[i]); } } @@ -161,7 +217,7 @@ void add_joystick_name(const char *name) { if(*name >= '0' && *name <= '9') { char buf[PATH_MAX + 1]; - sprintf(buf, JSDEVBASE "%d", atoi(name)); + sprintf(buf, "%s/%s%d", event_dir, js_node_name, atoi(name)); add_joystick_name(buf); return; } @@ -175,40 +231,66 @@ void add_joystick_name(const char *name) { last_joyname++; } -void enumerate_names(void) { +void populate_joynames(void) { int i; char buf[PATH_MAX + 1]; for(i = 0; i < MAX_STICKS; i++) { - sprintf(buf, "%s%d", JSDEVBASE, 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] == '-') { - char *nextarg = argv[1]; + if(argv[0][1] && argv[0][2]) + die("spaces required between options and arguments, please"); + nextarg = argv[1]; switch(argv[0][1]) { -#ifdef HAVE_X11 - case 'x': use_x = 0; break; -#endif - case 'c': + case 'k': if(nextarg) { - command = nextarg; + keycode = atoi(nextarg); /* TODO: error check */ + argv++, argc--; + } else { + die("-k requires a keycode argument, 0-255"); + } + break; + case 'b': + if(nextarg && (button = atoi(nextarg))) { + keycode = -1; argv++, argc--; } else { - die("-c requires a command argument"); + die("-b requires a positive integer button argument"); } break; case 'i': if(nextarg && (interval = atoi(nextarg)) > 0) argv++, argc--; else - die("-i requires a positive integer argument in seconds"); + 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: @@ -220,9 +302,14 @@ void parse_args(int argc, char **argv) { autodiscover = 0; } } + + if((keycode > -1) && (button > -1)) + die("can't send both keycode and button events"); } -/* make ourselves a daemon, double-fork technique */ +/* 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; @@ -235,21 +322,26 @@ void daemonize(void) { exit(0); } - /* the only reason for the first fork() was because setsid() can't + /* 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 */ - setsid(); + 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 parent, fork failed */ + /* in 2nd gen parent, fork failed */ fprintf(stderr, "%s: %s\n", self, strerror(errno)); die("failed to daemonize"); } else if(pid) { - /* in parent, fork succeeded */ + /* in 2nd gen parent, fork succeeded */ + fprintf(stderr, "%s: daemonized, PID %u\n", self, pid); exit(0); } - /* we no longer need stdin/out/err */ + /* now we're in the grandchild, and we no longer need stdin/out/err */ close(0); close(1); close(2); @@ -259,25 +351,85 @@ void daemonize(void) { chdir("/"); } -#ifdef HAVE_X11 +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) { - if(debug) fprintf(stderr, "connecting to X display...\n"); + 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 display\n"); + 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 255\n", self); + keycode = 255; + } +} + +void init_x(void) { + connect_to_x(); + if(keycode < 0 && button < 0) find_keycode(); } -#endif int main(int argc, char **argv) { set_exe_name(argv[0]); parse_args(argc, argv); - if(autodiscover) enumerate_names(); + if(autodiscover) populate_joynames(); + + init_joysticks(); + + init_inotify(); -#ifdef HAVE_X11 - if(use_x) connect_to_x(); -#endif + init_x(); if(!debug) daemonize(); -- cgit v1.2.3