X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Ftimers.c;h=ea97f34629cc5b793fd689e953dcbfeac1576fbd;hp=b079492c9d5aaede661b97498bbc0df5613e0997;hb=4361b69d3178d7fc98d0388f9a223af6c2651aba;hpb=50be9bb40dc60130c99ffa568e6677779904ff70 diff --git a/driver/timers.c b/driver/timers.c index b079492c..ea97f346 100644 --- a/driver/timers.c +++ b/driver/timers.c @@ -1,5 +1,5 @@ /* timers.c --- detecting when the user is idle, and other timer-related tasks. - * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski + * xscreensaver, Copyright (c) 1991-2017 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #ifdef HAVE_XMU @@ -140,9 +141,10 @@ notice_events (saver_info *si, Window window, Bool top_p) 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: @@ -161,7 +163,8 @@ notice_events (saver_info *si, Window window, Bool top_p) 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)) { @@ -349,46 +352,29 @@ reset_timers (saver_info *si) /* 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 @@ -411,9 +397,10 @@ pointer_moved_p (saver_screen_info *ssi, Bool mods_p) 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; @@ -421,23 +408,24 @@ pointer_moved_p (saver_screen_info *ssi, Bool mods_p) 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"); @@ -445,29 +433,64 @@ pointer_moved_p (saver_screen_info *ssi, Bool mods_p) 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 @@ -539,6 +562,21 @@ check_pointer_timer (XtPointer closure, XtIntervalId *id) 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) @@ -560,12 +598,19 @@ 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; @@ -660,6 +705,7 @@ swallow_unlock_typeahead_events (saver_info *si, XEvent *e) 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. @@ -712,6 +758,8 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) 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 (polling_for_idleness) @@ -794,12 +842,14 @@ sleep_until_idle (saver_info *si, Bool 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 @@ -818,7 +868,10 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) case ClientMessage: if (handle_clientmessage (si, &event.x_event, until_idle_p)) - goto DONE; + { + why = "ClientMessage"; + goto DONE; + } break; case CreateNotify: @@ -935,15 +988,113 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) 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 @@ -980,7 +1131,10 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) } if (until_idle_p) - goto DONE; + { + why = "MIT ScreenSaverOn"; + goto DONE; + } } else if (event.sevent.state == ScreenSaverOff) { @@ -988,7 +1142,10 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n", blurb()); if (!until_idle_p) - goto DONE; + { + why = "MIT ScreenSaverOff"; + goto DONE; + } } else fprintf (stderr, @@ -1010,7 +1167,10 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) 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)) @@ -1021,29 +1181,78 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) 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 ((!until_idle_p) && (si->num_xinput_devices > 0) && + /* 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 || - event.x_event.type == si->xinput_DeviceButtonRelease )) + 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 || - event.x_event.type == si->xinput_DeviceButtonRelease) ) + 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. */ - goto DONE; + { + 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 */ @@ -1089,6 +1298,13 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) } 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 @@ -1263,6 +1479,12 @@ proc_interrupts_activity_p (saver_info *si) 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. */ @@ -1410,6 +1632,7 @@ watchdog_timer (XtPointer closure, XtIntervalId *id) 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,