aboutsummaryrefslogtreecommitdiff
path: root/jsmond.c
diff options
context:
space:
mode:
Diffstat (limited to 'jsmond.c')
-rw-r--r--jsmond.c342
1 files changed, 247 insertions, 95 deletions
diff --git a/jsmond.c b/jsmond.c
index f62da29..abf209f 100644
--- a/jsmond.c
+++ b/jsmond.c
@@ -1,57 +1,51 @@
/* need this, or else we don't get a prototype for strdup() from string.h */
-#define _POSIX_C_SOURCE 200809L
+#define _XOPEN_SOURCE 500
#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;
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/XTest.h>
+#include <linux/joystick.h>
+#include <sys/inotify.h>
+
+/* user options */
+int interval = 250; /* -i */
+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 */
+
+/* joystick stuff */
char *joynames[MAX_STICKS + 1];
int last_joyname = 0;
+int joyfds[MAX_STICKS + 1];
const char *self;
-#ifdef HAVE_X11
+/* X stuff */
Display *xdisp;
-int use_x = 1;
-#endif
+
+/* 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;
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("Usage: %s [-i interval] [-b button | -k keycode] [-j name]\n", self);
+ printf(" [-d dir] [-D] [joystick [...]]\n");
printf("See man page for details\n");
exit(0);
}
@@ -61,7 +55,6 @@ void die(const char *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. */
@@ -69,44 +62,110 @@ void ping_x_server(void) {
(void)XPending(xdisp);
if(debug) fprintf(stderr, "X server is still alive\n");
}
-#endif
+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(100000);
+
+ /* ...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(100000);
+
+ /* ...release */
+ XTestFakeButtonEvent(xdisp, button, 0, 0L);
+ XSync(xdisp, 0);
+}
+
+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 init_joysticks(void) {
+ int i;
+ for(i = 0; i < MAX_STICKS; i++)
+ joyfds[i] = -1;
+ open_joysticks();
+}
+
+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: %x\n", inotify_ev->mask);
+ } else {
+ fprintf(stderr, "got event with name %s: %x\n", inotify_ev->name, inotify_ev->mask);
+ }
+ }
+ usleep(100000); /* might not need, be paranoid */
+ open_joysticks();
+ } else {
+ if(errno != EAGAIN) {
+ fprintf(stderr, "%s: failed to read from inotify_fd: %s\n",
+ self, strerror(errno));
+ exit(1);
+ }
+ }
+}
+
+/* this is the point of no return. the only way out of main_loop()
+ is process death. */
void main_loop(void) {
struct js_event e;
- int joyfds[MAX_STICKS + 1];
- int i, fdcount, active;
+ int i, 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);
+ /* Xlib will obligingly kill us, if X goes away */
+ ping_x_server();
- /* let them gather events as we sleep */
- sleep(interval);
+ /* check_inotify() will close & reopen the joystick fds as needed */
+ check_inotify();
- /* now see if any of the fds got any events while we slept */
+ /* let the fds gather events as we slumber */
+ if(debug) fprintf(stderr, "sleeping %dms...\n", interval);
+ usleep(interval * 1000);
+
+ /* now see if any of them got any events while we took a nap */
active = 0;
for(i = 0; i < last_joyname; i++) {
if(joyfds[i] < 0) continue;
@@ -128,15 +187,12 @@ void main_loop(void) {
/* 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"));
+ if(debug) fprintf(stderr, "*** got activity!\n");
+ if(button > -1)
+ send_fake_click();
+ else
+ send_fake_key();
} else if(debug) fprintf(stderr, "no activity\n");
-
- /* close 'em all */
- for(i = 0; i < last_joyname; i++)
- close(joyfds[i]);
}
}
@@ -161,7 +217,7 @@ void add_joystick_name(const char *name) {
if(*name >= '0' && *name <= '9') {
char buf[PATH_MAX + 1];
- sprintf(buf, JSDEVBASE "%d", atoi(name));
+ sprintf(buf, "%s/%s%d", event_dir, js_node_name, atoi(name));
add_joystick_name(buf);
return;
}
@@ -175,40 +231,66 @@ void add_joystick_name(const char *name) {
last_joyname++;
}
-void enumerate_names(void) {
+void populate_joynames(void) {
int i;
char buf[PATH_MAX + 1];
for(i = 0; i < MAX_STICKS; i++) {
- sprintf(buf, "%s%d", JSDEVBASE, i);
+ sprintf(buf, "%s/%s%d", event_dir, js_node_name, i);
add_joystick_name(buf);
}
}
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] == '-') {
- char *nextarg = argv[1];
+ if(argv[0][1] && argv[0][2])
+ die("spaces required between options and arguments, please");
+ nextarg = argv[1];
switch(argv[0][1]) {
-#ifdef HAVE_X11
- case 'x': use_x = 0; break;
-#endif
- case 'c':
+ case 'k':
if(nextarg) {
- command = nextarg;
+ keycode = atoi(nextarg); /* TODO: error check */
+ argv++, argc--;
+ } else {
+ die("-k requires a keycode argument, 0-255");
+ }
+ break;
+ case 'b':
+ if(nextarg && (button = atoi(nextarg))) {
+ keycode = -1;
argv++, argc--;
} else {
- die("-c requires a command argument");
+ die("-b requires a positive integer button argument");
}
break;
case 'i':
if(nextarg && (interval = atoi(nextarg)) > 0)
argv++, argc--;
else
- die("-i requires a positive integer argument in seconds");
+ 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;
default:
@@ -220,9 +302,14 @@ void parse_args(int argc, char **argv) {
autodiscover = 0;
}
}
+
+ if((keycode > -1) && (button > -1))
+ die("can't send both keycode and button events");
}
-/* make ourselves a daemon, double-fork technique */
+/* 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;
@@ -235,21 +322,26 @@ void daemonize(void) {
exit(0);
}
- /* the only reason for the first fork() was because setsid() can't
+ /* 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 */
- setsid();
+ 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 parent, fork failed */
+ /* in 2nd gen parent, fork failed */
fprintf(stderr, "%s: %s\n", self, strerror(errno));
die("failed to daemonize");
} else if(pid) {
- /* in parent, fork succeeded */
+ /* in 2nd gen parent, fork succeeded */
+ fprintf(stderr, "%s: daemonized, PID %u\n", self, pid);
exit(0);
}
- /* we no longer need stdin/out/err */
+ /* now we're in the grandchild, and we no longer need stdin/out/err */
close(0);
close(1);
close(2);
@@ -259,25 +351,85 @@ void daemonize(void) {
chdir("/");
}
-#ifdef HAVE_X11
+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);
+ if( watch_fd < 0) {
+ fprintf(stderr, "%s: inotify_add_watch() failed: %s",
+ self, strerror(errno));
+ exit(1);
+ }
+
+ if(debug) fprintf(stderr, "inotify_add_watch() on %s returned %d\n", event_dir, inotify_fd);
+}
+
void connect_to_x(void) {
- if(debug) fprintf(stderr, "connecting to X display...\n");
+ 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 display\n");
+ 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");
+ }
+}
+
+/* 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);
+
+ /* XXX: XKeycodeToKeysym() is deprecated. I must learn XKB. */
+ for(i = min_code; i <= max_code; i++) {
+ KeySym ks = XKeycodeToKeysym(xdisp, i, 0);
+ if(ks == 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 255\n", self);
+ keycode = 255;
+ }
+}
+
+void init_x(void) {
+ connect_to_x();
+ if(keycode < 0 && button < 0) find_keycode();
}
-#endif
int main(int argc, char **argv) {
set_exe_name(argv[0]);
parse_args(argc, argv);
- if(autodiscover) enumerate_names();
+ if(autodiscover) populate_joynames();
+
+ init_joysticks();
+
+ init_inotify();
-#ifdef HAVE_X11
- if(use_x) connect_to_x();
-#endif
+ init_x();
if(!debug) daemonize();