/* 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; }