/* timers.c --- detecting when the user is idle, and other timer-related tasks.
- * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1991-2017 Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/Xos.h>
+#include <X11/Xatom.h>
#include <time.h>
#include <sys/time.h>
#ifdef HAVE_XMU
XGetWindowAttributes (si->dpy, window, &attrs);
events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
- & KeyPressMask);
+ & (KeyPressMask | PropertyChangeMask));
/* Select for SubstructureNotify on all windows.
+ Select for PropertyNotify on all windows.
Select for KeyPress on all windows that already have it selected.
Note that we can't select for ButtonPress, because of X braindamage:
systems that have it. Oh, if it's a PS/2 mouse, not serial or USB.
This sucks!
*/
- XSelectInput (si->dpy, window, SubstructureNotifyMask | events);
+ XSelectInput (si->dpy, window,
+ SubstructureNotifyMask | PropertyChangeMask | events);
if (top_p && p->debug_p && (events & KeyPressMask))
{
/* And if the monitor is already powered off, turn it on.
You'd think the above would do that, but apparently not? */
- monitor_power_on (si);
+ monitor_power_on (si, True);
#endif
}
-/* Returns true if the mouse has moved since the last time we checked.
+/* Returns true if a mouse has moved since the last time we checked.
Small motions (of less than "hysteresis" pixels/second) are ignored.
*/
static Bool
-pointer_moved_p (saver_screen_info *ssi, Bool mods_p)
+device_pointer_moved_p (saver_info *si, poll_mouse_data *last_poll_mouse,
+ poll_mouse_data *this_poll_mouse, Bool mods_p,
+ const char *debug_type, int debug_id)
{
- saver_info *si = ssi->global;
saver_preferences *p = &si->prefs;
- Window root, child;
- int root_x, root_y, x, y;
- unsigned int mask;
- time_t now = time ((time_t *) 0);
unsigned int distance, dps;
unsigned long seconds = 0;
Bool moved_p = False;
- /* don't check xinerama pseudo-screens. */
- if (!ssi->real_screen_p) return False;
-
- if (!XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
- &root_x, &root_y, &x, &y, &mask))
- {
- /* If XQueryPointer() returns false, the mouse is not on this screen.
- */
- x = root_x = -1;
- y = root_y = -1;
- root = child = 0;
- mask = 0;
- }
-
- distance = MAX (ABS (ssi->poll_mouse_last_root_x - root_x),
- ABS (ssi->poll_mouse_last_root_y - root_y));
- seconds = (now - ssi->poll_mouse_last_time);
+ distance = MAX (ABS (last_poll_mouse->root_x - this_poll_mouse->root_x),
+ ABS (last_poll_mouse->root_y - this_poll_mouse->root_y));
+ seconds = (this_poll_mouse->time - last_poll_mouse->time);
/* When the screen is blanked, we get MotionNotify events, but when not
If the mouse was not on this screen, but is now, that's motion.
*/
{
- Bool on_screen_p = (root_x != -1 && root_y != -1);
- Bool was_on_screen_p = (ssi->poll_mouse_last_root_x != -1 &&
- ssi->poll_mouse_last_root_y != -1);
+ Bool on_screen_p = (this_poll_mouse->root_x != -1 &&
+ this_poll_mouse->root_y != -1);
+ Bool was_on_screen_p = (last_poll_mouse->root_x != -1 &&
+ last_poll_mouse->root_y != -1);
if (on_screen_p != was_on_screen_p)
moved_p = True;
if (p->debug_p && (distance != 0 || moved_p))
{
- fprintf (stderr, "%s: %d: pointer %s", blurb(), ssi->number,
+ fprintf (stderr, "%s: %s %d: pointer %s", blurb(), debug_type, debug_id,
(moved_p ? "moved: " : "ignored:"));
- if (ssi->poll_mouse_last_root_x == -1)
+ if (last_poll_mouse->root_x == -1)
fprintf (stderr, "off screen");
else
fprintf (stderr, "%d,%d",
- ssi->poll_mouse_last_root_x,
- ssi->poll_mouse_last_root_y);
+ last_poll_mouse->root_x,
+ last_poll_mouse->root_y);
fprintf (stderr, " -> ");
- if (root_x == -1)
+ if (this_poll_mouse->root_x == -1)
fprintf (stderr, "off screen");
else
- fprintf (stderr, "%d,%d", root_x, root_y);
- if (ssi->poll_mouse_last_root_x != -1 && root_x != -1)
+ fprintf (stderr, "%d,%d", this_poll_mouse->root_x,
+ this_poll_mouse->root_y);
+ if (last_poll_mouse->root_x != -1 && this_poll_mouse->root_x != -1)
fprintf (stderr, " (%d,%d; %d/%lu=%d)",
- ABS(ssi->poll_mouse_last_root_x - root_x),
- ABS(ssi->poll_mouse_last_root_y - root_y),
+ ABS(last_poll_mouse->root_x - this_poll_mouse->root_x),
+ ABS(last_poll_mouse->root_y - this_poll_mouse->root_y),
distance, seconds, dps);
fprintf (stderr, ".\n");
if (!moved_p &&
mods_p &&
- mask != ssi->poll_mouse_last_mask)
+ this_poll_mouse->mask != last_poll_mouse->mask)
{
moved_p = True;
if (p->debug_p)
- fprintf (stderr, "%s: %d: modifiers changed: 0x%04x -> 0x%04x.\n",
- blurb(), ssi->number, ssi->poll_mouse_last_mask, mask);
+ fprintf (stderr, "%s: %s %d: modifiers changed: 0x%04x -> 0x%04x.\n",
+ blurb(), debug_type, debug_id,
+ last_poll_mouse->mask, this_poll_mouse->mask);
}
- si->last_activity_screen = ssi;
- ssi->poll_mouse_last_child = child;
- ssi->poll_mouse_last_mask = mask;
+ last_poll_mouse->child = this_poll_mouse->child;
+ last_poll_mouse->mask = this_poll_mouse->mask;
if (moved_p || seconds > 0)
{
- ssi->poll_mouse_last_time = now;
- ssi->poll_mouse_last_root_x = root_x;
- ssi->poll_mouse_last_root_y = root_y;
+ last_poll_mouse->time = this_poll_mouse->time;
+ last_poll_mouse->root_x = this_poll_mouse->root_x;
+ last_poll_mouse->root_y = this_poll_mouse->root_y;
}
return moved_p;
}
+/* Returns true if core mouse pointer has moved since the last time we checked.
+ */
+static Bool
+pointer_moved_p (saver_screen_info *ssi, Bool mods_p)
+{
+ saver_info *si = ssi->global;
+
+ Window root;
+ poll_mouse_data this_poll_mouse;
+ int x, y;
+
+ /* don't check xinerama pseudo-screens. */
+ if (!ssi->real_screen_p) return False;
+
+ this_poll_mouse.time = time ((time_t *) 0);
+
+ if (!XQueryPointer (si->dpy, ssi->screensaver_window, &root,
+ &this_poll_mouse.child,
+ &this_poll_mouse.root_x, &this_poll_mouse.root_y,
+ &x, &y, &this_poll_mouse.mask))
+ {
+ /* If XQueryPointer() returns false, the mouse is not on this screen.
+ */
+ this_poll_mouse.root_x = -1;
+ this_poll_mouse.root_y = -1;
+ this_poll_mouse.child = 0;
+ this_poll_mouse.mask = 0;
+ }
+ else
+ si->last_activity_screen = ssi;
+
+ return device_pointer_moved_p(si, &(ssi->last_poll_mouse), &this_poll_mouse,
+ mods_p, "screen", ssi->number);
+}
+
/* When we aren't using a server extension, this timer is used to periodically
wake up and poll the mouse position, which is possibly more reliable than
We only do this when we'd be polling the mouse position anyway.
This amounts to an assumption that machines with APM support also
have /proc/interrupts.
+
+ Now here's a thing that sucks about this: if the user actually changes
+ the time of the machine, it will either trigger or delay the triggering
+ of a lock. On most systems, that requires root, but I'll bet at least
+ some GUI configs let non-root do it. Also, NTP attacks.
+
+ On Linux 2.6.39+ systems, there exists clock_gettime(CLOCK_BOOTTIME)
+ which would allow us to detect the "laptop CPU had been halted" state
+ independently of changes in wall-clock time. But of course that's not
+ portable.
+
+ When the wall clock changes, what do Xt timers do, anyway? If I have
+ a timer set for 30 seconds from now, and adjust the wall clock +15 seconds,
+ does the timer fire 30 seconds from now or 15? I actually have no idea.
+ It does not appear to be specified.
*/
static void
check_for_clock_skew (saver_info *si)
shift > (p->timeout / 1000))
{
if (p->verbose_p)
- fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld!\n",
+ fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld%s\n",
blurb(),
- (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60));
+ (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60),
+ (p->mode == DONT_BLANK ? " while saver disabled" : ""));
- si->emergency_lock_p = True;
- idle_timer ((XtPointer) si, 0);
+ /* If the saver is entirely disabled, there's no need to do the
+ emergency-blank-and-lock thing.
+ */
+ if (p->mode != DONT_BLANK)
+ {
+ si->emergency_lock_p = True;
+ idle_timer ((XtPointer) si, 0);
+ }
}
si->last_wall_clock_time = now;
explicitly informed by SGI SCREEN_SAVER server event;
explicitly informed by MIT-SCREEN-SAVER server event;
select events on all windows, and note events on any of them;
+ note that a client updated their window's _NET_WM_USER_TIME property;
note that /proc/interrupts has changed;
deactivated by clientmessage.
Bool polling_mouse_position = (si->using_proc_interrupts ||
!(si->using_xidle_extension ||
si->using_mit_saver_extension ||
- si->using_sgi_saver_extension));
+ si->using_sgi_saver_extension) ||
+ si->using_xinput_extension);
+
+ const char *why = 0; /* What caused the idle-state to change? */
if (until_idle_p)
{
if (idle >= p->timeout)
{
/* Look, we've been idle long enough. We're done. */
+ why = "timeout";
goto DONE;
}
else if (si->emergency_lock_p)
{
/* Oops, the wall clock has jumped far into the future, so
we need to lock down in a hurry! */
+ why = "large wall clock change";
goto DONE;
}
else
case ClientMessage:
if (handle_clientmessage (si, &event.x_event, until_idle_p))
- goto DONE;
+ {
+ why = "ClientMessage";
+ goto DONE;
+ }
break;
case CreateNotify:
cause deactivation. Only clicks and keypresses do. */
;
else
- /* If we're not demoing, then any activity causes deactivation.
- */
- goto DONE;
+ {
+ /* If we're not demoing, then any activity causes deactivation.
+ */
+ why = (event.x_event.xany.type == MotionNotify ?"mouse motion":
+ event.x_event.xany.type == KeyPress?"keyboard activity":
+ event.x_event.xany.type == ButtonPress ? "mouse click" :
+ "unknown user activity");
+ goto DONE;
+ }
}
else
reset_timers (si);
break;
+ case PropertyNotify:
+
+ /* Starting in late 2014, GNOME programs don't actually select for
+ or receive KeyPress events: they do it behind the scenes through
+ some kind of Input Method magic, even when running in an en_US
+ locale. However, those applications *do* update the WM_USER_TIME
+ property on their own windows every time they recieve a secret
+ KeyPress, so we must *also* monitor that property on every
+ window, and treat changes to it as identical to KeyPress.
+
+ _NET_WM_USER_TIME is documented (such as it is) here:
+
+ http://standards.freedesktop.org/wm-spec/latest/ar01s05.html
+ #idm139870829932528
+
+ Specifically:
+
+ "Contains the XServer time at which last user activity in this
+ window took place. [...] A client [...] might, for example,
+ use the timestamp of the last KeyPress or ButtonPress event."
+
+ As of early 2016, KDE4 does something really stupid, though: some
+ hidden power management thing reduces the display brightness 150
+ seconds after the screen is blanked -- and sets a WM_USER_TIME
+ property on a hidden "kded4" window whose time is in the distant
+ past (the time at which the X server launched).
+
+ So we ignore any WM_USER_TIME whose timestamp is more than a
+ couple seconds old.
+ */
+ if (event.x_event.xproperty.state == PropertyNewValue &&
+ event.x_event.xproperty.atom == XA_NET_WM_USER_TIME)
+ {
+ int threshold = 2; /* seconds */
+ Bool bogus_p = True;
+ Window w = event.x_event.xproperty.window;
+
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *data = 0;
+ Cardinal user_time = 0;
+ XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
+
+ if (XGetWindowProperty (si->dpy, w,
+ XA_NET_WM_USER_TIME, 0L, 1L, False,
+ XA_CARDINAL, &type, &format, &nitems,
+ &bytesafter, &data)
+ == Success &&
+ data &&
+ type == XA_CARDINAL &&
+ format == 32 &&
+ nitems == 1)
+ {
+ long diff;
+ user_time = ((Cardinal *) data)[0];
+ diff = event.x_event.xproperty.time - user_time;
+ if (diff >= 0 && diff < threshold)
+ bogus_p = False;
+ }
+
+ if (data) XFree (data);
+
+ why = "WM_USER_TIME";
+
+ if (p->debug_p)
+ {
+ XWindowAttributes xgwa;
+ int i;
+
+ XGetWindowAttributes (si->dpy, w, &xgwa);
+ for (i = 0; i < si->nscreens; i++)
+ if (xgwa.root == RootWindowOfScreen (si->screens[i].screen))
+ break;
+ fprintf (stderr,"%s: %d: %s = %ld%s on 0x%lx\n",
+ blurb(), i, why, (unsigned long) user_time,
+ (bogus_p ? " (bad)" : ""),
+ (unsigned long) w);
+ }
+
+ XSync (si->dpy, False);
+ XSetErrorHandler (old_handler);
+
+ if (bogus_p)
+ break;
+ else if (until_idle_p)
+ reset_timers (si);
+ else
+ goto DONE;
+ }
+ break;
+
default:
#ifdef HAVE_MIT_SAVER_EXTENSION
}
if (until_idle_p)
- goto DONE;
+ {
+ why = "MIT ScreenSaverOn";
+ goto DONE;
+ }
}
else if (event.sevent.state == ScreenSaverOff)
{
fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
blurb());
if (!until_idle_p)
- goto DONE;
+ {
+ why = "MIT ScreenSaverOff";
+ goto DONE;
+ }
}
else
fprintf (stderr,
blurb());
if (until_idle_p)
- goto DONE;
+ {
+ why = "SGI ScreenSaverStart";
+ goto DONE;
+ }
}
else if (event.x_event.type == (si->sgi_saver_ext_event_number +
ScreenSaverEnd))
fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
blurb());
if (!until_idle_p)
- goto DONE;
+ {
+ why = "SGI ScreenSaverEnd";
+ goto DONE;
+ }
}
else
#endif /* HAVE_SGI_SAVER_EXTENSION */
+#ifdef HAVE_XINPUT
+ /* If we got a MotionNotify event, check to see if the mouse has
+ moved far enough to count as "real" motion, if not, then ignore
+ this event.
+ */
+ if ((si->num_xinput_devices > 0) &&
+ (event.x_event.type == si->xinput_DeviceMotionNotify))
+ {
+ XDeviceMotionEvent *dme = (XDeviceMotionEvent *) &event;
+ poll_mouse_data *last_poll_mouse = NULL;
+ int d;
+
+ for (d = 0; d < si->num_xinput_devices; d++)
+ {
+ if (si->xinput_devices[d].device->device_id == dme->deviceid)
+ {
+ last_poll_mouse = &(si->xinput_devices[d].last_poll_mouse);
+ break;
+ }
+ }
+
+ if (last_poll_mouse)
+ {
+ poll_mouse_data this_poll_mouse;
+ this_poll_mouse.root_x = dme->x_root;
+ this_poll_mouse.root_y = dme->y_root;
+ this_poll_mouse.child = dme->subwindow;
+ this_poll_mouse.mask = dme->device_state;
+ this_poll_mouse.time = dme->time / 1000; /* milliseconds */
+
+ if (!device_pointer_moved_p (si, last_poll_mouse,
+ &this_poll_mouse, False,
+ "device", dme->deviceid))
+ continue;
+ }
+ else if (p->debug_p)
+ fprintf (stderr,
+ "%s: received MotionNotify from unknown device %d\n",
+ blurb(), (int) dme->deviceid);
+ }
+
+ if ((!until_idle_p) &&
+ (si->num_xinput_devices > 0) &&
+ (event.x_event.type == si->xinput_DeviceMotionNotify ||
+ event.x_event.type == si->xinput_DeviceButtonPress))
+ /* Ignore DeviceButtonRelease, see ButtonRelease comment above. */
+ {
+
+ dispatch_event (si, &event.x_event);
+ if (si->demoing_p &&
+ event.x_event.type == si->xinput_DeviceMotionNotify)
+ /* When we're demoing a single hack, mouse motion doesn't
+ cause deactivation. Only clicks and keypresses do. */
+ ;
+ else
+ /* If we're not demoing, then any activity causes deactivation.
+ */
+ {
+ why = (event.x_event.type == si->xinput_DeviceMotionNotify
+ ? "XI mouse motion" :
+ event.x_event.type == si->xinput_DeviceButtonPress
+ ? "XI mouse click" : "unknown XINPUT event");
+ goto DONE;
+ }
+ }
+ else
+#endif /* HAVE_XINPUT */
+
#ifdef HAVE_RANDR
- if (event.x_event.type == (si->randr_event_number + RRScreenChangeNotify))
+ if (si->using_randr_extension &&
+ (event.x_event.type ==
+ (si->randr_event_number + RRScreenChangeNotify)))
{
/* The Resize and Rotate extension sends an event when the
size, rotation, or refresh rate of any screen has changed.
}
DONE:
+ if (p->verbose_p)
+ {
+ if (! why) why = "unknown reason";
+ fprintf (stderr, "%s: %s (%s)\n", blurb(),
+ (until_idle_p ? "user is idle" : "user is active"),
+ why);
+ }
/* If there's a user event on the queue, swallow it.
If we're using a server extension, and the user becomes active, we
perror (buf);
goto FAIL;
}
+
+# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC)
+ /* Close this fd upon exec instead of inheriting / leaking it. */
+ if (fcntl (fileno (f0), F_SETFD, FD_CLOEXEC) != 0)
+ perror ("fcntl: CLOEXEC:");
+# endif
}
if (f0 == (FILE *) -1) /* means we got an error initializing. */
sync_server_dpms_settings (si->dpy,
(p->dpms_enabled_p &&
p->mode != DONT_BLANK),
+ p->dpms_quickoff_p,
p->dpms_standby / 1000,
p->dpms_suspend / 1000,
p->dpms_off / 1000,