aboutsummaryrefslogtreecommitdiff
path: root/jsmond.c
diff options
context:
space:
mode:
authorB. Watson <yalhcru@gmail.com>2020-05-19 15:58:05 -0400
committerB. Watson <yalhcru@gmail.com>2020-05-19 15:58:05 -0400
commit29f0434b2d5141db1d0b6b20be8967905fe85aa9 (patch)
treeafc10beb28a64a237daad5832a43c81d76a34887 /jsmond.c
parent0951ce5167226f5969698522890a34fd1c414c5c (diff)
downloadunsaver-29f0434b2d5141db1d0b6b20be8967905fe85aa9.tar.gz
...and lo, a Great Renaming came upon the land, the like of which shall not be seen again
Diffstat (limited to 'jsmond.c')
-rw-r--r--jsmond.c754
1 files changed, 0 insertions, 754 deletions
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 <string.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <errno.h>
-
-#include <X11/X.h>
-#include <X11/Xlib.h>
-#include <X11/extensions/XTest.h>
-
-#include <linux/joystick.h>
-#include <sys/inotify.h>
-
-/* 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;
-}