From f6690f23148f7bf1445dc6435742fccff0cb1b3e Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Sat, 16 May 2020 05:55:13 -0400 Subject: initial commit --- jsmond.c | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 jsmond.c (limited to 'jsmond.c') diff --git a/jsmond.c b/jsmond.c new file mode 100644 index 0000000..f62da29 --- /dev/null +++ b/jsmond.c @@ -0,0 +1,287 @@ +/* need this, or else we don't get a prototype for strdup() from string.h */ +#define _POSIX_C_SOURCE 200809L + +#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; + +char *joynames[MAX_STICKS + 1]; +int last_joyname = 0; + +const char *self; + +#ifdef HAVE_X11 +Display *xdisp; +int use_x = 1; +#endif + +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("See man page for details\n"); + exit(0); +} + +void die(const char *msg) { + fprintf(stderr, "%s: %s\n", self, 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. */ +void ping_x_server(void) { + (void)XPending(xdisp); + if(debug) fprintf(stderr, "X server is still alive\n"); +} +#endif + +void main_loop(void) { + struct js_event e; + int joyfds[MAX_STICKS + 1]; + int i, fdcount, 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); + + /* let them gather events as we sleep */ + sleep(interval); + + /* now see if any of the fds got any events while we slept */ + 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) { + 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")); + } else if(debug) fprintf(stderr, "no activity\n"); + + /* close 'em all */ + for(i = 0; i < last_joyname; i++) + close(joyfds[i]); + } +} + +/* 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, JSDEVBASE "%d", 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 enumerate_names(void) { + int i; + char buf[PATH_MAX + 1]; + + for(i = 0; i < MAX_STICKS; i++) { + sprintf(buf, "%s%d", JSDEVBASE, i); + add_joystick_name(buf); + } +} + +void parse_args(int argc, char **argv) { + if(argc > 1 && strcmp(argv[1], "--help") == 0) usage(); + while(++argv, --argc) { + if(argv[0][0] == '-') { + char *nextarg = argv[1]; + switch(argv[0][1]) { +#ifdef HAVE_X11 + case 'x': use_x = 0; break; +#endif + case 'c': + if(nextarg) { + command = nextarg; + argv++, argc--; + } else { + die("-c requires a command argument"); + } + break; + case 'i': + if(nextarg && (interval = atoi(nextarg)) > 0) + argv++, argc--; + else + die("-i requires a positive integer argument in seconds"); + break; + case 'd': + debug = 1; + break; + default: + die("unrecognized argument, try --help"); + break; + } + } else { + add_joystick_name(*argv); + autodiscover = 0; + } + } +} + +/* make ourselves a daemon, double-fork technique */ +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); + } + + /* the only reason for the first fork() was because setsid() can't + be run in the parent process */ + setsid(); + + /* go on, do it again */ + 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); + } + + /* 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("/"); +} + +#ifdef HAVE_X11 +void connect_to_x(void) { + if(debug) fprintf(stderr, "connecting to X display...\n"); + xdisp = XOpenDisplay(getenv("DISPLAY")); + if(!xdisp) die("can't connect to X server"); + if(debug) fprintf(stderr, "connected to X display\n"); +} +#endif + +int main(int argc, char **argv) { + set_exe_name(argv[0]); + + parse_args(argc, argv); + + if(autodiscover) enumerate_names(); + +#ifdef HAVE_X11 + if(use_x) connect_to_x(); +#endif + + if(!debug) daemonize(); + + main_loop(); + + return 0; +} -- cgit v1.2.3