From 005fc7dcf69fc112b5ce3d2c4017769cd8e425e8 Mon Sep 17 00:00:00 2001
From: "B. Watson" <yalhcru@gmail.com>
Date: Mon, 18 May 2020 23:59:51 -0400
Subject: detect fullscreen windows (-f/-F)

---
 jsmond.1    |  20 ++++++----
 jsmond.c    | 123 ++++++++++++++++++++++++++++++++++++++++++++++--------------
 jsmond.html |  21 +++++++----
 jsmond.rst  |  20 ++++++----
 4 files changed, 134 insertions(+), 50 deletions(-)

diff --git a/jsmond.1 b/jsmond.1
index fc761bc..8b9513b 100644
--- a/jsmond.1
+++ b/jsmond.1
@@ -36,11 +36,12 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .
 .SH SYNOPSIS
 .sp
-jsmond [\fB\-i interval[s|ms]\fP] [\fB\-m\fP | \fB\-k keycode\fP | \fB\-b button\fP] [\fB\-d dir\fP] [\fB\-j name\fP] [\fB\-D\fP] [\fBjoydev [joydev ...]\fP]
+jsmond [\fB\-i interval[s|ms]\fP] [\fB\-m\fP | \fB\-k keycode\fP | \fB\-b button\fP | \fB\-c command\fP | \fB\-x\fP ] [\fB\-d dir\fP] [\fB\-j name\fP] [\fB\-f\fP] [\fB\-F\fP] [\fB\-D\fP] [\fBjoydev [joydev ...]\fP]
 .SH DESCRIPTION
 .sp
-jsmond lets you play games with your joysticks/gamepads without
-the screen saver activating due to lack of keyboard/mouse input.
+jsmond lets you play games with your joysticks/gamepads without the screen
+saver activating due to lack of keyboard/mouse input. It can also prevent
+the screensaver from activating when a fullscreen window is in use.
 .sp
 Multiple joystick devices can be monitored. By default, jsmond
 monitors up to 16 devices, named /dev/input/js0 through js15.
@@ -96,18 +97,20 @@ to its starting point.
 .BI \-c \ <command>
 Run a command when activity is detected, rather than
 sending a fake keystroke/click/motion. It\(aqs recommended
-to set \fIinterval\fP to at least 1 second, when using this
-option.
+to set \fIinterval\fP to at least 1 second when using this
+option, to avoid excess process\-spawning overhead.
 .TP
 .B \-x
 Same as \fB\-c "xscreensaver\-command \-deactivate" \-i 1s\fP\&.
 .TP
 .B \-f
-Disable screensaver if a full\-screen window is detected.
-This isn\(aqt likely to be 100% reliable.
+Deactivate screensaver if a fullscreen window is detected.
+This isn\(aqt likely to be 100% reliable yet.
 .TP
 .B \-F
 Same as \fB\-f\fP, but also disables joystick monitoring entirely.
+Note that \fB\-j\fP, \fB\-d\fP, and \fBjoydev\fP are ignored
+with this option.
 .UNINDENT
 .sp
 These options are intended for developers and  \fIreally\fP shouldn\(aqt be
@@ -157,6 +160,9 @@ password dialog, same as pressing a key or mouse button would.
 jsmond depends on the XTest extension being present in the X server. If
 you get a "X server doesn\(aqt support XTest extension" error, see your X
 server documentation to find out how to enable XTest.
+.sp
+The fullscreen window monitoring has only been tested on a system with
+a single monitor, and may not work properly in multi\-head environments.
 .SH EXIT STATUS
 .sp
 Without the \-D option, the exit status is 0 (success) if jsmond
diff --git a/jsmond.c b/jsmond.c
index ca3c1a0..643287d 100644
--- a/jsmond.c
+++ b/jsmond.c
@@ -52,6 +52,8 @@ char *js_node_name = JSNODE;     /* -j */
 int autodiscover = 1; /* cleared if user supplies joydev args */
 fake_mode_t fake_mode = fm_auto_keycode;  /* -k, -b, -m */
 char *c_command = NULL;          /* -c, -x */
+int monitor_joysticks = 1;       /* -F */
+int monitor_fullscreen = 0;      /* -f, -F */
 
 /* joystick stuff */
 char *joynames[MAX_STICKS + 1];
@@ -260,45 +262,90 @@ void check_inotify() {
 	}
 }
 
+int get_joystick_activity(void) {
+	struct js_event e;
+	int i, 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));
+		}
+	}
+
+	return active;
+}
+
+int have_fullscreen_window(void) {
+	Window w;
+	XWindowAttributes w_attrs, root_attrs;
+	int revert_to;
+
+	/* TODO: error check? */
+	XGetInputFocus(xdisp, &w, &revert_to);
+
+	if(w != None) {
+		XGetWindowAttributes(xdisp, DefaultRootWindow(xdisp), &root_attrs);
+		XGetWindowAttributes(xdisp, w, &w_attrs);
+
+		/*
+		fprintf(stderr, "window id %d has focus, geometry %dx%d\n",
+				(int)w, w_attrs.width, w_attrs.height);
+				*/
+
+		if( (w_attrs.map_state == IsViewable) &&
+			 (root_attrs.width == w_attrs.width) &&
+		    (root_attrs.height == w_attrs.height) )
+		{
+			if(debug) fprintf(stderr,
+					"window id 0x%x has input focus and looks fullscreen (%dx%d)\n",
+					(int)w, w_attrs.width, w_attrs.height);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
 /* 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 i, active, sleep_us;
-
-	fprintf(stderr, "entering main_loop()\n");
+	int sleep_us;
 
 	sleep_us = interval * 1000;
+	fprintf(stderr, "entering main_loop(), sleep_us %d\n", sleep_us);
+
 	while(1) {
+		int active = 0;
+
 		/* Xlib will obligingly kill us, if X goes away */
 		ping_x_server();
 
 		/* check_inotify() will close & reopen the joystick fds as needed */
-		check_inotify();
+		if(monitor_joysticks)
+			check_inotify();
 
 		/* let the fds gather events as we slumber */
 		if(debug) fprintf(stderr, "sleeping %dms...\n", interval);
 		usleep(sleep_us);
 
 		/* 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;
-
-			if(debug) fprintf(stderr, "reading device %d, fd %d\n", i, joyfds[i]);
+		if(monitor_joysticks)
+			active += get_joystick_activity();
 
-			/* 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(monitor_fullscreen)
+			active += have_fullscreen_window();
 
 		/* if we got any activity on any of the fds, do our thing */
 		if(active) {
@@ -474,9 +521,10 @@ void parse_args(int argc, char **argv) {
 					set_fake_mode(fm_cmd);
 					c_command = X_COMMAND;
 					break;
-				case 'f':
 				case 'F':
-					die("-f and -F not yet implemented");
+					monitor_joysticks = 0; /* and fall thru */
+				case 'f':
+					monitor_fullscreen = 1;
 					break;
 				default:
 					die("unrecognized argument, try --help");
@@ -640,16 +688,35 @@ void init_x(void) {
 	send_fake_x_event();
 }
 
+void print_banner() {
+	const char *m;
+
+	if(monitor_joysticks && monitor_fullscreen) {
+		m = "joysticks and fullscreen";
+	} else if(monitor_joysticks) {
+		m = "joysticks only";
+	} else if(monitor_fullscreen) {
+		m = "fullscreen only";
+	} else {
+		die("nothing to monitor!");
+	}
+
+	fprintf(stderr, "%s: v%s monitoring %s, interval %dms\n",
+			self, VERSION, m, interval);
+}
+
 int main(int argc, char **argv) {
 	set_exe_name(argv[0]);
 
 	parse_args(argc, argv);
 
-	if(autodiscover) populate_joynames();
+	print_banner();
 
-	init_joysticks();
-
-	init_inotify();
+	if(monitor_joysticks) {
+		if(autodiscover) populate_joynames();
+		init_joysticks();
+		init_inotify();
+	}
 
 	init_x();
 
diff --git a/jsmond.html b/jsmond.html
index 4f5d91c..2874b25 100644
--- a/jsmond.html
+++ b/jsmond.html
@@ -382,12 +382,13 @@ ul.auto-toc {
 <!-- rst2man.py jsmond.rst > jsmond.1 -->
 <div class="section" id="synopsis">
 <h1>SYNOPSIS</h1>
-<p>jsmond [<strong>-i interval[s|ms]</strong>] [<strong>-m</strong> | <strong>-k keycode</strong> | <strong>-b button</strong>] [<strong>-d dir</strong>] [<strong>-j name</strong>] [<strong>-D</strong>] [<strong>joydev [joydev ...]</strong>]</p>
+<p>jsmond [<strong>-i interval[s|ms]</strong>] [<strong>-m</strong> | <strong>-k keycode</strong> | <strong>-b button</strong> | <strong>-c command</strong> | <strong>-x</strong> ] [<strong>-d dir</strong>] [<strong>-j name</strong>] [<strong>-f</strong>] [<strong>-F</strong>] [<strong>-D</strong>] [<strong>joydev [joydev ...]</strong>]</p>
 </div>
 <div class="section" id="description">
 <h1>DESCRIPTION</h1>
-<p>jsmond lets you play games with your joysticks/gamepads without
-the screen saver activating due to lack of keyboard/mouse input.</p>
+<p>jsmond lets you play games with your joysticks/gamepads without the screen
+saver activating due to lack of keyboard/mouse input. It can also prevent
+the screensaver from activating when a fullscreen window is in use.</p>
 <p>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
@@ -444,18 +445,20 @@ to its starting point.</td></tr>
 <kbd><span class="option">-c <var>&lt;command&gt;</var></span></kbd></td>
 <td>Run a command when activity is detected, rather than
 sending a fake keystroke/click/motion. It's recommended
-to set <em>interval</em> to at least 1 second, when using this
-option.</td></tr>
+to set <em>interval</em> to at least 1 second when using this
+option, to avoid excess process-spawning overhead.</td></tr>
 <tr><td class="option-group">
 <kbd><span class="option">-x</span></kbd></td>
 <td>Same as <strong>-c &quot;xscreensaver-command -deactivate&quot; -i 1s</strong>.</td></tr>
 <tr><td class="option-group">
 <kbd><span class="option">-f</span></kbd></td>
-<td>Disable screensaver if a full-screen window is detected.
-This isn't likely to be 100% reliable.</td></tr>
+<td>Deactivate screensaver if a fullscreen window is detected.
+This isn't likely to be 100% reliable yet.</td></tr>
 <tr><td class="option-group">
 <kbd><span class="option">-F</span></kbd></td>
-<td>Same as <strong>-f</strong>, but also disables joystick monitoring entirely.</td></tr>
+<td>Same as <strong>-f</strong>, but also disables joystick monitoring entirely.
+Note that <strong>-j</strong>, <strong>-d</strong>, and <strong>joydev</strong> are ignored
+with this option.</td></tr>
 </tbody>
 </table>
 <p>These options are intended for developers and  <em>really</em> shouldn't be
@@ -504,6 +507,8 @@ password dialog, same as pressing a key or mouse button would.</p>
 <p>jsmond depends on the XTest extension being present in the X server. If
 you get a &quot;X server doesn't support XTest extension&quot; error, see your X
 server documentation to find out how to enable XTest.</p>
+<p>The fullscreen window monitoring has only been tested on a system with
+a single monitor, and may not work properly in multi-head environments.</p>
 </div>
 <div class="section" id="exit-status">
 <h1>EXIT STATUS</h1>
diff --git a/jsmond.rst b/jsmond.rst
index 89b47a6..4c0a38d 100644
--- a/jsmond.rst
+++ b/jsmond.rst
@@ -20,13 +20,14 @@ deactivate screensaver on joystick activity
 SYNOPSIS
 ========
 
-jsmond [**-i interval[s|ms]**] [**-m** | **-k keycode** | **-b button**] [**-d dir**] [**-j name**] [**-D**] [**joydev [joydev ...]**]
+jsmond [**-i interval[s|ms]**] [**-m** | **-k keycode** | **-b button** | **-c command** | **-x** ] [**-d dir**] [**-j name**] [**-f**] [**-F**] [**-D**] [**joydev [joydev ...]**]
 
 DESCRIPTION
 ===========
 
-jsmond lets you play games with your joysticks/gamepads without
-the screen saver activating due to lack of keyboard/mouse input.
+jsmond lets you play games with your joysticks/gamepads without the screen
+saver activating due to lack of keyboard/mouse input. It can also prevent
+the screensaver from activating when a fullscreen window is in use.
 
 Multiple joystick devices can be monitored. By default, jsmond
 monitors up to 16 devices, named /dev/input/js0 through js15.
@@ -77,15 +78,17 @@ OPTIONS
 
 -c <command>   Run a command when activity is detected, rather than
                sending a fake keystroke/click/motion. It's recommended
-               to set *interval* to at least 1 second, when using this
-               option.
+               to set *interval* to at least 1 second when using this
+               option, to avoid excess process-spawning overhead.
 
 -x             Same as **-c "xscreensaver-command -deactivate" -i 1s**.
 
--f             Disable screensaver if a full-screen window is detected.
-               This isn't likely to be 100% reliable.
+-f             Deactivate screensaver if a fullscreen window is detected.
+               This isn't likely to be 100% reliable yet.
 
 -F             Same as **-f**, but also disables joystick monitoring entirely.
+               Note that **-j**, **-d**, and **joydev** are ignored
+               with this option.
 
 These options are intended for developers and  *really* shouldn't be
 needed for normal use:
@@ -132,6 +135,9 @@ jsmond depends on the XTest extension being present in the X server. If
 you get a "X server doesn't support XTest extension" error, see your X
 server documentation to find out how to enable XTest.
 
+The fullscreen window monitoring has only been tested on a system with
+a single monitor, and may not work properly in multi-head environments.
+
 EXIT STATUS
 ===========
 
-- 
cgit v1.2.3