aboutsummaryrefslogtreecommitdiff
path: root/jsmond.c
diff options
context:
space:
mode:
authorB. Watson <yalhcru@gmail.com>2020-05-16 05:55:13 -0400
committerB. Watson <yalhcru@gmail.com>2020-05-16 05:55:13 -0400
commitf6690f23148f7bf1445dc6435742fccff0cb1b3e (patch)
treedb2fe0a659d49b4f06a8b69e428d988c43e17041 /jsmond.c
downloadunsaver-f6690f23148f7bf1445dc6435742fccff0cb1b3e.tar.gz
initial commit
Diffstat (limited to 'jsmond.c')
-rw-r--r--jsmond.c287
1 files changed, 287 insertions, 0 deletions
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 <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <linux/joystick.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifdef HAVE_X11
+# include <X11/Xlib.h>
+#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;
+}