From f6690f23148f7bf1445dc6435742fccff0cb1b3e Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Sat, 16 May 2020 05:55:13 -0400 Subject: initial commit --- Makefile | 41 +++++++++ jsmond.1 | 119 +++++++++++++++++++++++++ jsmond.c | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ jsmond.rst | 134 +++++++++++++++++++++++++++++ 4 files changed, 581 insertions(+) create mode 100644 Makefile create mode 100644 jsmond.1 create mode 100644 jsmond.c create mode 100644 jsmond.rst diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c1379f --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +# GNU Makefile for jsmond +PROJ=jsmond + +# the .rst is the authoritative source for the version number. +VERSION=$(shell fgrep '.. |version| replace::' $(PROJ).rst | cut -d' ' -f4) + +# maximum number of devices we can monitor. 16 seems like an awful lot... +MAX_STICKS=16 + +# base name of joystick devices on your OS, gets numbers 0 to MAX_STICKS +# appended to it during autodetection +JSDEVBASE="/dev/input/js" + +# Override this, not CFLAGS +OPTFLAGS=-Wall -ansi -pedantic -std=c89 -g + +# If you can think of a reason to compile without X11 support, +# set this to 0 (or anything other than 1). Note that you can +# compile with X11 and then disable it at runtime with -x. +HAVE_X11=1 + +DEFINES=-DVERSION=\"$(VERSION)\" -DMAX_STICKS=$(MAX_STICKS) -DJSDEVBASE=\"$(JSDEVBASE)\" + +ifeq ($(HAVE_X11),1) +CFLAGS+=$(shell pkg-config --cflags x11) +LDFLAGS+=$(shell pkg-config --libs x11) +DEFINES+=-DHAVE_X11 +endif + + +CFLAGS=$(OPTFLAGS) $(DEFINES) + +all: jsmond + +man: jsmond.rst + rst2man.py jsmond.rst > jsmond.1 + +clean: + rm -f $(PROJ) + +.PHONY: all man clean diff --git a/jsmond.1 b/jsmond.1 new file mode 100644 index 0000000..df25a5e --- /dev/null +++ b/jsmond.1 @@ -0,0 +1,119 @@ +.\" Man page generated from reStructuredText. +. +.TH JSMOND 1 "2020-05-15" "0.1.0" "Urchlay" +.SH NAME +jsmond \- disable screensaver on joystick activity +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.\" RST source for jsmond(1) man page. Convert with: +. +.\" rst2man.py jsmond.rst > jsmond.1 +. +.SH SYNOPSIS +.sp +jsmond [\fB\-c command\fP] [\fB\-i seconds\fP] [\fB\-d\fP] [\fB\-x\fP] [\fBjoydev [joydev ...]\fP] +.SH DESCRIPTION +.sp +jsmond lets you play games with your joysticks/gamepads without +xscreensaver activating due to lack of keyboard/mouse input. +.sp +Up to 16 joystick devices can be monitored. By default, jsmond searches +for these and monitors all the joysticks it finds. You can override +this on the command line by providing one or more \fBjoydev\fP arguments, +in which case no autodetection will be done. +.sp +Every \fIinterval\fP seconds (60, or whatever \fB\-i\fP is set to), jsmond +checks to see if there\(aqs been any activity on any of the devices it\(aqs +monitoring. If so, it runs the \fBxscreensaver\-command \-deactivate\fP, +or whatever the \fB\-c\fP argument is set to. +.sp +The command will be run no more than once every \fIinterval\fP seconds. +.sp +jsmond should be started from your \fB\&.xinitrc\fP or whatever X startup +script your windowmanager or desktop environment uses. By default, it +will exit when the X server does. +.sp +If you can think of a use for jsmond outside of X, give it the \-x argument +so it won\(aqt complain about not being able to connect to the X server. This +will also prevent it from exiting when the X server does. When using +\-x, be careful not to spawn multiple instances of jsmond (although they +won\(aqt hurt anything, just waste resources). +.SH OPTIONS +.INDENT 0.0 +.TP +.B \-\-help +Print usage summary +.TP +.BI \-c \ +Run when activity was detected during the +last \fIinterval\fP\&. +.TP +.BI \-i \ +Interval to check for activity. Should be set lower +than your xscreensaver timeout. Setting this too low +will waste resources. Default: 60. +.TP +.B \-d +Debug mode: print verbose messages and don\(aqt fork() +into the background. +.TP +.B \-x +Don\(aqt try to connect to X server. +.UNINDENT +.SH EXIT STATUS +.sp +Without the \-d option, the exit status is 0 (success) if jsmond +successfully opened at least one joystick and forked into the background. +.sp +A non\-zero exit status means no joysticks were found, or else fork() +failed. No daemon will be running in this case. +.sp +With the \-d option, jsmond never exits until it\(aqs killed. +.SH BUGS +.sp +jsmond only searches for joysticks when it\(aqs started. If USB joysticks are +plugged in while jsmond is running, it won\(aqt detect them. This includes +unplugging the device and plugging it back in: jsmond won\(aqt complain, +but it\(aqll never report activity on that device again. +.sp +jsmond isn\(aqt portable. It only works on Linux, at least for now. +.\" EXAMPLES +. +.\" ======== +. +.SH LICENSE +.sp +jsmond is released under the WTFPL: Do WTF you want with this. +.SH AUTHORS +.sp +jsmond was written by B. Watson <\fI\%yalhcru@gmail.com\fP>. +.SH SEE ALSO +.sp +sdl\-jstest(1), sdl2\-jstest(2) +.\" Generated by docutils manpage writer. +. 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; +} diff --git a/jsmond.rst b/jsmond.rst new file mode 100644 index 0000000..575e76f --- /dev/null +++ b/jsmond.rst @@ -0,0 +1,134 @@ +.. RST source for jsmond(1) man page. Convert with: +.. rst2man.py jsmond.rst > jsmond.1 + +.. |version| replace:: 0.1.0 +.. |date| date:: + +====== +jsmond +====== + +---------------------------------------- +disable screensaver on joystick activity +---------------------------------------- + +:Manual section: 1 +:Manual group: Urchlay +:Date: |date| +:Version: |version| + +SYNOPSIS +======== + +jsmond [**-c command**] [**-i seconds**] [**-d**] [**-x**] [**joydev [joydev ...]**] + +DESCRIPTION +=========== + +jsmond lets you play games with your joysticks/gamepads without +xscreensaver activating due to lack of keyboard/mouse input. + +Multiple joystick devices can be monitored. By default, jsmond +monitors up to 16 devices, named /dev/input/js0 through js15. +These devices don't have to actually exist: they can come and go +as joysticks are plugged in and unplugged. + +Every *interval* seconds (60, or whatever **-i** is set to), jsmond +checks to see if there's been any activity on any of the devices it's +monitoring. If so, it runs the **xscreensaver-command -deactivate** +(or whatever the **-c** argument is set to). The command will be run no +more than once every *interval* seconds. + +It's recommended to let jsmond find the joysticks itself. However, +you can pass one or more device names (or just numbers) if the default +doesn't do the right thing for you. In this case, only these devices +will be monitored (no search is done). + +OPTIONS +======= + +--help Print usage summary + +-c Run when activity was detected during the + last *interval*. + +-i Interval to check for activity. Should be set a minute or + so less than your xscreensaver timeout. Setting this too low + will waste resources. Default: 60. + +-d Debug mode: run in foreground and print verbose messages. + +-x Don't try to connect to X server (and don't exit until killed). + +NOTES +===== + +A space is required between an option and its argument, as shown +above. Use e.g. **-i 120**, not **-i120**. + +By default, jsmond searches for and monitors all the joysticks it can +find, up to MAX_STICKS (normally 16; see the **--help** output to find +the compiled-in default). You can override the search on the command +line by providing one or more **joydev** arguments, in which case only +those devices will be monitored. + +**joydev** arguments can be either a path to a device node (e.g. +*/dev/input/js0* or similar), or a number, which will have the default +device basename prepended to it. This is normally "/dev/input/js", but +can be changed at compile time (see the **--help** output to find the +compiled-in default). + +Note that it's *not* an error to give nonexistent joystick device names. +jsmond will wait for devices to come into existence (e.g. as created +by **udev**). + +jsmond should be started from your **.xinitrc** or whatever X startup +script your windowmanager or desktop environment uses. By default, it +will exit when the X server does. + +If you can think of a use for jsmond outside of X, give it the -x argument +so it won't complain about not being able to connect to the X server. This +will also prevent it from exiting when the X server does. When using +-x, be careful not to spawn multiple instances of jsmond (although they +won't hurt anything, just waste resources). + +There's no PID file. Just use "pkill jsmond". + +EXIT STATUS +=========== + +Without the -d option, the exit status is 0 (success) if jsmond +successfully forked into the background. + +A non-zero exit status means an error in the command line arguments, +or else fork() failed. No daemon will be running in this case. + +With the -d option, jsmond never exits until it's killed. + +BUGS +==== + +There's no way to distinguish between an invalid device name and a +device name that doesn't happen to exist yet because its device hasn't +been plugged in yet. Try to avoid typos, if you really have to use device +names (better to autodetect). + +jsmond isn't portable. It only works on Linux, at least for now. + +.. EXAMPLES +.. ======== + +LICENSE +======= + +jsmond is released under the WTFPL: Do WTF you want with this. + +AUTHORS +======= + +jsmond was written by B. Watson . + +SEE ALSO +======== + +sdl-jstest(1), sdl2-jstest(2) -- cgit v1.2.3