From 29f0434b2d5141db1d0b6b20be8967905fe85aa9 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Tue, 19 May 2020 15:58:05 -0400 Subject: ...and lo, a Great Renaming came upon the land, the like of which shall not be seen again --- jsmond.c | 754 --------------------------------------------------------------- 1 file changed, 754 deletions(-) delete mode 100644 jsmond.c (limited to 'jsmond.c') diff --git a/jsmond.c b/jsmond.c deleted file mode 100644 index a7b8d40..0000000 --- a/jsmond.c +++ /dev/null @@ -1,754 +0,0 @@ -/* 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 - -/* command for -x */ -#define X_COMMAND "xscreensaver-command -deactivate" - -/* default "-i 250ms" */ -#define DEFAULT_INTERVAL 250 - -typedef enum { - fm_auto_keycode, fm_keycode, fm_button, fm_motion, fm_cmd -} fake_mode_t; - -/* user options */ -int interval = DEFAULT_INTERVAL; /* -i (always in millisec) */ -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 */ -fake_mode_t fake_mode = fm_auto_keycode; /* -k, -b, -m */ -char *c_command = NULL; /* -c, -x */ -int monitor_joysticks = 1; /* -F */ -int monitor_fullscreen = 0; /* -f, -F */ - -/* joystick stuff */ -char *joynames[MAX_STICKS + 1]; -int last_joyname = 0; -int joyfds[MAX_STICKS + 1]; - -/* executable name, for warnings/errors */ -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; - -/* not going to include pages of usage info duplicating the man page */ -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, -d " EVENTDIR ", -j " 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); -} - -/* XXX: the man page documents XTestFakeRelativeMotionEvent() - as taking 5 arguments (2nd one is screen_number), but Xtst.h - only has 4 (with no screen_number arg). */ -void send_fake_motion(void) { - int oldx, oldy, ignoreme; - Window win_ignoreme; - - if(debug) fprintf(stderr, "sending mouse motion\n"); - - XQueryPointer( - xdisp, - DefaultRootWindow(xdisp), - &win_ignoreme, - &win_ignoreme, - &oldx, - &oldy, - &ignoreme, - &ignoreme, - (unsigned int *)&ignoreme); - - if(debug) fprintf(stderr, "oldx %d, oldy %d\n", oldx, oldy); - - /* move... */ - XTestFakeRelativeMotionEvent(xdisp, 10, 10, 0L); - XSync(xdisp, 0); - - /* ...wait 1/10 sec... */ - usleep(EVENT_DELAY); - - /* ...move back */ - XTestFakeRelativeMotionEvent(xdisp, -10, -10, 0L); - XSync(xdisp, 0); - - /* we've moved all 4 directions now. but if were were too close - to an edge of the screen, the mouse pointer isn't where it - started. so: */ - XWarpPointer( - xdisp, - None, - DefaultRootWindow(xdisp), - 0, - 0, - 0, - 0, - oldx, - oldy); - XSync(xdisp, 0); -} - -void exec_command(void) { - if(!c_command) - die("BUG: c_command is NULL\n"); - - if(debug) fprintf(stderr, "executing \"%s\"\n", c_command); - system(c_command); -} - -void send_fake_x_event(void) { - switch(fake_mode) { - case fm_keycode: - send_fake_key(); - break; - case fm_button: - send_fake_click(); - break; - case fm_motion: - send_fake_motion(); - break; - case fm_cmd: - exec_command(); - break; - default: /* This Never Happens(tm) */ - fprintf(stderr, "%s: BUG: fake_mode %d isn't a valid fake_mode_t!\n", - self, fake_mode); - exit(1); - break; - } -} - -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 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: 0x%x\n", inotify_ev->mask); - } else { - fprintf(stderr, "got event with name %s: 0x%x\n", inotify_ev->name, inotify_ev->mask); - } - } - - /* not much we can do to recover from this: */ - if(inotify_ev->mask & IN_DELETE_SELF) { - die("someone deleted our device directory!"); - } - - /* ...or this: */ - if(inotify_ev->mask & IN_UNMOUNT) { - die("someone unmounted our device directory!"); - } - - usleep(EVENT_DELAY); /* might not need, be paranoid */ - open_joysticks(); - } else { - if(errno != EAGAIN) { - /* not sure what could cause this, so no idea how/if we - recover from it */ - fprintf(stderr, "%s: failed to read from inotify_fd: %s\n", - self, strerror(errno)); - exit(1); - } - } -} - -int get_joystick_activity(void) { - struct js_event e; - int i, 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)); - } - } - - return active; -} - -/* supposedly we could use the X window property _NET_WINDOW_FULLSCREEN - to detect a fullscreen window, but not everything actually sets that. - so a fullscreen window is defined here as: - - visible - - is the size of the root window - - and has input focus. -*/ -int have_fullscreen_window(void) { - Window w; - XWindowAttributes w_attrs, root_attrs; - int revert_to; - - /* man page doesn't document the actual - return value, it says "int", is it really a Status? */ - if(!XGetInputFocus(xdisp, &w, &revert_to)) { - if(debug) fprintf(stderr, "XGetInputFocus() failed\n"); - return 0; - } - - /* no window has focus (not an error) */ - if(w == None) - return 0; - - /* TODO: under what conditions does this fail? if it does fail once, - will it ever succeed again? - - Hopefully, using w_attrs.root will do the right thing in multi-screen - environments. I can't easily test it though. */ - if( XGetWindowAttributes(xdisp, w, &w_attrs) && - XGetWindowAttributes(xdisp, w_attrs.root, &root_attrs) ) - { - - if(debug) - fprintf(stderr, - "window id 0x%x (root 0x%x) has focus, map_state %d, geometry %dx%d\n", - (int)w, (int)w_attrs.root, w_attrs.map_state, w_attrs.width, w_attrs.height); - - if( (w_attrs.map_state == IsViewable) && - (root_attrs.width == w_attrs.width) && - (root_attrs.height == w_attrs.height) ) - { - if(debug) fprintf(stderr, - "window id 0x%x has input focus and looks fullscreen (%dx%d)\n", - (int)w, w_attrs.width, w_attrs.height); - return 1; - } - } else if(debug) { - if(debug) fprintf(stderr, "XGetWindowAttributes() failed\n"); - return 0; - } - - return 0; -} - -/* this is the point of no return. the only way out of main_loop() - is process death. */ -void main_loop(void) { - int sleep_us; - - sleep_us = interval * 1000; - fprintf(stderr, "entering main_loop(), sleep_us %d\n", sleep_us); - - while(1) { - int active = 0; - - /* Xlib will obligingly kill us, if X goes away */ - ping_x_server(); - - /* check_inotify() will close & reopen the joystick fds as needed */ - if(monitor_joysticks) - 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 */ - if(monitor_joysticks) - active += get_joystick_activity(); - - if(monitor_fullscreen) - active += have_fullscreen_window(); - - /* if we got any activity on any of the fds, do our thing */ - if(active) { - if(debug) fprintf(stderr, "*** got activity!\n"); - send_fake_x_event(); - } 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; -} - -char *make_joystick_name(int js) { - static char buf[PATH_MAX + 1]; - sprintf(buf, "%s/%s%d", event_dir, js_node_name, js); - return buf; -} - -/* 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') { - add_joystick_name(make_joystick_name(atoi(name))); - 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; - - for(i = 0; i < MAX_STICKS; i++) { - add_joystick_name(make_joystick_name(i)); - } -} - -void set_fake_mode(fake_mode_t newmode) { - if(fake_mode != fm_auto_keycode) - die("use only one of the -k -b -m options"); - else - fake_mode = newmode; -} - -int set_interval(const char *arg) { - char *end; - - interval = strtol(arg, &end, 0); - - /* no digits at all */ - if(end == arg) return 0; - - /* no 0 or negative */ - if(interval < 1) return 0; - - /* digits only */ - switch(*end) { - case '\0': - /* digits only, try to guess */ - if(interval < 100) { - fprintf(stderr, - "%s: treating -i %d as %d seconds (use -i %dms if you meant %dms)\n", - self, interval, interval, interval, interval); - /* fall through */ - } else { - break; - } - case 's': interval *= 1000; break; /* sec */ - case 'm': break; /* ms, use as-is */ - } - - if(debug) fprintf(stderr, "interval set to %dms\n", interval); - return 1; -} - -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] != '-') { - /* no dash, treat as a joystick device */ - add_joystick_name(*argv); - autodiscover = 0; - } else { - if(argv[0][1] && argv[0][2]) - die("spaces required between options and arguments, please"); - nextarg = argv[1]; - switch(argv[0][1]) { - case 'm': - set_fake_mode(fm_motion); - break; - case 'k': - if(nextarg && - (keycode = atoi(nextarg)) && - (keycode >= MIN_KEYCODE) && - (keycode <= MAX_KEYCODE) ) - { - set_fake_mode(fm_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) ) { - set_fake_mode(fm_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 && set_interval(nextarg)) - 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; - case 'c': - if(nextarg) { - set_fake_mode(fm_cmd); - c_command = nextarg; - argv++, argc--; - } else { - die("-c requires a command argument"); - } - break; - case 'x': - set_fake_mode(fm_cmd); - c_command = X_COMMAND; - break; - case 'F': - monitor_joysticks = 0; /* and fall thru */ - case 'f': - monitor_fullscreen = 1; - break; - default: - die("unrecognized argument, try --help"); - break; - } - } - } -} - -/* 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("/"); -} - -/* helpers for init_* functions */ -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"); - } -} - -/* XXX: XKeycodeToKeysym() is deprecated. I must learn XKB. */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -KeySym get_keysym(int i) { - return XKeycodeToKeysym(xdisp, i, 0); -} -#pragma GCC diagnostic pop - -/* 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); - - for(i = min_code; i <= max_code; i++) { - if(get_keysym(i) == 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; - } -} - -/* the device name check is mostly to guard against typos */ -void check_joyname(int i) { - if(joynames[i] && (strncmp(event_dir, joynames[i], strlen(event_dir)) != 0)) { - fprintf(stderr, "%s: device %s is not in event dir %s\n", - self, joynames[i], event_dir); - exit(1); - } -} - -/* init_* functions. these are only called once each, from main() */ -void init_joysticks(void) { - int i; - - for(i = 0; i < MAX_STICKS; i++) { - joyfds[i] = -1; - check_joyname(i); - } - - open_joysticks(); -} - -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 | IN_DELETE_SELF); - - if(watch_fd < 0) { - fprintf(stderr, "%s: inotify_add_watch(\"%s\") failed: %s\n", - self, event_dir, strerror(errno)); - exit(1); - } - - if(debug) fprintf(stderr, "inotify_add_watch() on %s returned %d\n", event_dir, inotify_fd); -} - -void init_x(void) { - connect_to_x(); - - if(fake_mode == fm_auto_keycode) { - find_keycode(); - fake_mode = fm_keycode; - } - - /* do this now, before daemonizing. if something's wrong, Xlib - will kill us. we'd better let that happen while we still have a - stderr, so the user can see what happened. */ - send_fake_x_event(); -} - -void print_banner() { - const char *m; - - if(monitor_joysticks && monitor_fullscreen) { - m = "joysticks and fullscreen"; - } else if(monitor_joysticks) { - m = "joysticks only"; - } else if(monitor_fullscreen) { - m = "fullscreen only"; - } else { - die("nothing to monitor!"); - } - - fprintf(stderr, "%s: v%s monitoring %s, interval %dms\n", - self, VERSION, m, interval); -} - -int main(int argc, char **argv) { - set_exe_name(argv[0]); - - parse_args(argc, argv); - - print_banner(); - - if(monitor_joysticks) { - if(autodiscover) populate_joynames(); - init_joysticks(); - init_inotify(); - } - - init_x(); - - if(!debug) daemonize(); - - main_loop(); - - return 0; -} -- cgit v1.2.3