X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Ftimers.c;h=ea97f34629cc5b793fd689e953dcbfeac1576fbd;hp=8c068c023e7fcdaec347c582ab6bb7371e9dee41;hb=4361b69d3178d7fc98d0388f9a223af6c2651aba;hpb=bbd0773f2adde4927a6196361d4061e70bf48cd9 diff --git a/driver/timers.c b/driver/timers.c index 8c068c02..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-2002 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 @@ -42,8 +43,19 @@ #include #endif /* HAVE_SGI_SAVER_EXTENSION */ +#ifdef HAVE_RANDR +#include +#endif /* HAVE_RANDR */ + #include "xscreensaver.h" +#undef ABS +#define ABS(x)((x)<0?-(x):(x)) + +#undef MAX +#define MAX(x,y)((x)>(y)?(x):(y)) + + #ifdef HAVE_PROC_INTERRUPTS static Bool proc_interrupts_activity_p (saver_info *si); #endif /* HAVE_PROC_INTERRUPTS */ @@ -72,12 +84,27 @@ idle_timer (XtPointer closure, XtIntervalId *id) fake_event.xany.display = si->dpy; fake_event.xany.window = 0; XPutBackEvent (si->dpy, &fake_event); + + /* If we are the timer that just went off, clear the pointer to the id. */ + if (id) + { + if (si->timer_id && *id != si->timer_id) + abort(); /* oops, scheduled timer twice?? */ + si->timer_id = 0; + } } -static void +void schedule_wakeup_event (saver_info *si, Time when, Bool verbose_p) { + if (si->timer_id) + { + if (verbose_p) + fprintf (stderr, "%s: idle_timer already running\n", blurb()); + return; + } + /* Wake up periodically to ask the server if we are idle. */ si->timer_id = XtAppAddTimeOut (si->app, when, idle_timer, (XtPointer) si); @@ -114,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: @@ -135,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)) { @@ -238,14 +267,18 @@ cycle_timer (XtPointer closure, XtIntervalId *id) } else { + int i; maybe_reload_init_file (si); - kill_screenhack (si); + for (i = 0; i < si->nscreens; i++) + kill_screenhack (&si->screens[i]); + + raise_window (si, True, True, False); if (!si->throttled_p) - spawn_screenhack (si, False); + for (i = 0; i < si->nscreens; i++) + spawn_screenhack (&si->screens[i]); else { - raise_window (si, True, True, False); if (p->verbose_p) fprintf (stderr, "%s: not launching new hack (throttled.)\n", blurb()); @@ -297,6 +330,7 @@ reset_timers (saver_info *si) fprintf (stderr, "%s: killing idle_timer (%ld, %ld)\n", blurb(), p->timeout, si->timer_id); XtRemoveTimeOut (si->timer_id); + si->timer_id = 0; } schedule_wakeup_event (si, p->timeout, p->debug_p); /* sets si->timer_id */ @@ -304,6 +338,157 @@ reset_timers (saver_info *si) if (si->cycle_id) abort (); /* no cycle timer when inactive */ si->last_activity_time = time ((time_t *) 0); + + /* This will (hopefully, supposedly) tell the server to re-set its + DPMS timer. Without this, the -deactivate clientmessage would + prevent xscreensaver from blanking, but would not prevent the + monitor from powering down. */ +#if 0 + /* #### With some servers, this causes the screen to flicker every + time a key is pressed! Ok, I surrender. I give up on ever + having DPMS work properly. + */ + XForceScreenSaver (si->dpy, ScreenSaverReset); + + /* 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, True); +#endif + +} + + +/* 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 +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_preferences *p = &si->prefs; + + unsigned int distance, dps; + unsigned long seconds = 0; + Bool moved_p = False; + + 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 + blanked, we poll only every 5 seconds, and that's not enough resolution + to do hysteresis based on a 1 second interval. So, assume that any + motion we've seen during the 5 seconds when our eyes were closed happened + in the last 1 second instead. + */ + if (seconds > 1) seconds = 1; + + dps = (seconds <= 0 ? distance : (distance / seconds)); + + /* Motion only counts if the rate is more than N pixels per second. + */ + if (dps >= p->pointer_hysteresis && + distance > 0) + moved_p = True; + + /* If the mouse is not on this screen but used to be, that's motion. + If the mouse was not on this screen, but is now, that's motion. + */ + { + 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: %s %d: pointer %s", blurb(), debug_type, debug_id, + (moved_p ? "moved: " : "ignored:")); + if (last_poll_mouse->root_x == -1) + fprintf (stderr, "off screen"); + else + fprintf (stderr, "%d,%d", + last_poll_mouse->root_x, + last_poll_mouse->root_y); + fprintf (stderr, " -> "); + if (this_poll_mouse->root_x == -1) + fprintf (stderr, "off screen"); + else + 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(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 && + this_poll_mouse->mask != last_poll_mouse->mask) + { + moved_p = True; + + if (p->debug_p) + 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); + } + + last_poll_mouse->child = this_poll_mouse->child; + last_poll_mouse->mask = this_poll_mouse->mask; + + if (moved_p || seconds > 0) + { + 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); } @@ -328,74 +513,21 @@ check_pointer_timer (XtPointer closure, XtIntervalId *id) */ abort (); - si->check_pointer_timer_id = + if (id && *id == si->check_pointer_timer_id) /* this is us - it's expired */ + si->check_pointer_timer_id = 0; + + if (si->check_pointer_timer_id) /* only queue one at a time */ + XtRemoveTimeOut (si->check_pointer_timer_id); + + si->check_pointer_timer_id = /* now re-queue */ XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer, (XtPointer) si); for (i = 0; i < si->nscreens; i++) { saver_screen_info *ssi = &si->screens[i]; - Window root, child; - int root_x, root_y, x, y; - unsigned int mask; - - if (!ssi->real_screen_p) continue; - - 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. - */ - root_x = -1; - root_y = -1; - } - - if (root_x == ssi->poll_mouse_last_root_x && - root_y == ssi->poll_mouse_last_root_y && - child == ssi->poll_mouse_last_child && - mask == ssi->poll_mouse_last_mask) - continue; - - active_p = True; - - if (p->debug_p) - { - if (root_x == ssi->poll_mouse_last_root_x && - root_y == ssi->poll_mouse_last_root_y && - child == ssi->poll_mouse_last_child) - fprintf (stderr, "%s: %d: modifiers changed: 0x%04x -> 0x%04x.\n", - blurb(), i, ssi->poll_mouse_last_mask, mask); - else - { - fprintf (stderr, "%s: %d: pointer moved: ", blurb(), i); - if (ssi->poll_mouse_last_root_x == -1) - fprintf (stderr, "off screen"); - else - fprintf (stderr, "%d,%d", - ssi->poll_mouse_last_root_x, - ssi->poll_mouse_last_root_y); - fprintf (stderr, " -> "); - if (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, ".\n"); - else -# undef ABS -# define ABS(x)((x)<0?-(x):(x)) - fprintf (stderr, " (%d,%d).\n", - ABS(ssi->poll_mouse_last_root_x - root_x), - ABS(ssi->poll_mouse_last_root_y - root_y)); -# undef ABS - } - } - - si->last_activity_screen = ssi; - ssi->poll_mouse_last_root_x = root_x; - ssi->poll_mouse_last_root_y = root_y; - ssi->poll_mouse_last_child = child; - ssi->poll_mouse_last_mask = mask; + if (pointer_moved_p (ssi, True)) + active_p = True; } #ifdef HAVE_PROC_INTERRUPTS @@ -407,7 +539,6 @@ check_pointer_timer (XtPointer closure, XtIntervalId *id) } #endif /* HAVE_PROC_INTERRUPTS */ - if (active_p) reset_timers (si); @@ -431,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) @@ -452,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; @@ -504,6 +657,7 @@ swallow_unlock_typeahead_events (saver_info *si, XEvent *e) break; case '\025': case '\030': /* Erase line */ case '\012': case '\015': /* Enter */ + case '\033': /* ESC */ i = 0; break; case '\040': /* Space */ @@ -551,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. @@ -560,7 +715,20 @@ void sleep_until_idle (saver_info *si, Bool until_idle_p) { saver_preferences *p = &si->prefs; - XEvent event; + + /* We have to go through this union bullshit because gcc-4.4.0 has + stricter struct-aliasing rules. Without this, the optimizer + can fuck things up. + */ + union { + XEvent x_event; +# ifdef HAVE_RANDR + XRRScreenChangeNotifyEvent xrr_event; +# endif /* HAVE_RANDR */ +# ifdef HAVE_MIT_SAVER_EXTENSION + XScreenSaverNotifyEvent sevent; +# endif /* HAVE_MIT_SAVER_EXTENSION */ + } event; /* We need to select events on all windows if we're not using any extensions. Otherwise, we don't need to. */ @@ -587,16 +755,16 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) 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 (polling_for_idleness) /* This causes a no-op event to be delivered to us in a while, so that - we come back around through the event loop again. Use of this timer - is economical: for example, if the screensaver should come on in 5 - minutes, and the user has been idle for 2 minutes, then this - timeout will go off no sooner than 3 minutes from now. */ + we come back around through the event loop again. */ schedule_wakeup_event (si, p->timeout, p->debug_p); if (polling_mouse_position) @@ -607,13 +775,22 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) while (1) { - XtAppNextEvent (si->app, &event); + XtAppNextEvent (si->app, &event.x_event); - switch (event.xany.type) { + switch (event.x_event.xany.type) { case 0: /* our synthetic "timeout" event has been signalled */ if (until_idle_p) { Time idle; + + /* We may be idle; check one last time to see if the mouse has + moved, just in case the idle-timer went off within the 5 second + window between mouse polling. If the mouse has moved, then + check_pointer_timer() will reset last_activity_time. + */ + if (polling_mouse_position) + check_pointer_timer ((XtPointer) si, 0); + #ifdef HAVE_XIDLE_EXTENSION if (si->using_xidle_extension) { @@ -665,19 +842,24 @@ 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 { /* The event went off, but it turns out that the user has not yet been idle for long enough. So re-signal the event. - */ + Be economical: if we should blank after 5 minutes, and the + user has been idle for 2 minutes, then set this timer to + go off in 3 minutes. + */ if (polling_for_idleness) schedule_wakeup_event (si, p->timeout - idle, p->debug_p); } @@ -685,8 +867,11 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) break; case ClientMessage: - if (handle_clientmessage (si, &event, until_idle_p)) - goto DONE; + if (handle_clientmessage (si, &event.x_event, until_idle_p)) + { + why = "ClientMessage"; + goto DONE; + } break; case CreateNotify: @@ -694,15 +879,18 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) supposed to scan all windows for events, prepare this window. */ if (scanning_all_windows) { - Window w = event.xcreatewindow.window; + Window w = event.x_event.xcreatewindow.window; start_notice_events_timer (si, w, p->debug_p); } break; case KeyPress: - case KeyRelease: case ButtonPress: - case ButtonRelease: + /* Ignore release events so that hitting ESC at the password dialog + doesn't result in the password dialog coming right back again when + the fucking release key is seen! */ + /* case KeyRelease:*/ + /* case ButtonRelease:*/ case MotionNotify: if (p->debug_p) @@ -710,28 +898,28 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) Window root=0, window=0; int x=-1, y=-1; const char *type = 0; - if (event.xany.type == MotionNotify) + if (event.x_event.xany.type == MotionNotify) { - type = "MotionNotify"; - root = event.xmotion.root; - window = event.xmotion.window; - x = event.xmotion.x_root; - y = event.xmotion.y_root; + /*type = "MotionNotify";*/ + root = event.x_event.xmotion.root; + window = event.x_event.xmotion.window; + x = event.x_event.xmotion.x_root; + y = event.x_event.xmotion.y_root; } - else if (event.xany.type == KeyPress) + else if (event.x_event.xany.type == KeyPress) { type = "KeyPress"; - root = event.xkey.root; - window = event.xkey.window; + root = event.x_event.xkey.root; + window = event.x_event.xkey.window; x = y = -1; } - else if (event.xany.type == ButtonPress) + else if (event.x_event.xany.type == ButtonPress) { type = "ButtonPress"; - root = event.xkey.root; - window = event.xkey.window; - x = event.xmotion.x_root; - y = event.xmotion.y_root; + root = event.x_event.xkey.root; + window = event.x_event.xkey.window; + x = event.x_event.xmotion.x_root; + y = event.x_event.xmotion.y_root; } if (type) @@ -745,13 +933,13 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) /* Be careful never to do this unless in -debug mode, as this could expose characters from the unlock password. */ - if (p->debug_p && event.xany.type == KeyPress) + if (p->debug_p && event.x_event.xany.type == KeyPress) { KeySym keysym; char c = 0; - XLookupString (&event.xkey, &c, 1, &keysym, 0); + XLookupString (&event.x_event.xkey, &c, 1, &keysym, 0); fprintf (stderr, " (%s%s)", - (event.xkey.send_event ? "synthetic " : ""), + (event.x_event.xkey.send_event ? "synthetic " : ""), XKeysymToString (keysym)); } @@ -763,7 +951,28 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) } /* If any widgets want to handle this event, let them. */ - dispatch_event (si, &event); + dispatch_event (si, &event.x_event); + + + /* If we got a MotionNotify event, figure out what screen it + was on and poll the mouse there: if the mouse hasn't moved + far enough to count as "real" motion, then ignore this + event. + */ + if (event.x_event.xany.type == MotionNotify) + { + int i; + for (i = 0; i < si->nscreens; i++) + if (event.x_event.xmotion.root == + RootWindowOfScreen (si->screens[i].screen)) + break; + if (i < si->nscreens) + { + if (!pointer_moved_p (&si->screens[i], False)) + continue; + } + } + /* We got a user event. If we're waiting for the user to become active, this is it. @@ -773,34 +982,130 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) if (!until_idle_p) { if (si->demoing_p && - (event.xany.type == MotionNotify || - event.xany.type == KeyRelease)) + (event.x_event.xany.type == MotionNotify || + event.x_event.xany.type == KeyRelease)) /* 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; + { + /* 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 (event.type == si->mit_saver_ext_event_number) + if (event.x_event.type == si->mit_saver_ext_event_number) { /* This event's number is that of the MIT-SCREEN-SAVER server extension. This extension has one event number, and the event itself contains sub-codes that say what kind of event it was (an "idle" or "not-idle" event.) */ - XScreenSaverNotifyEvent *sevent = - (XScreenSaverNotifyEvent *) &event; - if (sevent->state == ScreenSaverOn) + if (event.sevent.state == ScreenSaverOn) { int i = 0; if (p->verbose_p) @@ -818,7 +1123,7 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) XUnmapWindow (si->dpy, ssi->server_mit_saver_window); } - if (sevent->kind != ScreenSaverExternal) + if (event.sevent.kind != ScreenSaverExternal) { fprintf (stderr, "%s: ScreenSaverOn event wasn't of type External!\n", @@ -826,20 +1131,26 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) } if (until_idle_p) - goto DONE; + { + why = "MIT ScreenSaverOn"; + goto DONE; + } } - else if (sevent->state == ScreenSaverOff) + else if (event.sevent.state == ScreenSaverOff) { if (p->verbose_p) fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n", blurb()); if (!until_idle_p) - goto DONE; + { + why = "MIT ScreenSaverOff"; + goto DONE; + } } else fprintf (stderr, "%s: unknown MIT-SCREEN-SAVER event %d received!\n", - blurb(), sevent->state); + blurb(), event.sevent.state); } else @@ -847,7 +1158,7 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) #ifdef HAVE_SGI_SAVER_EXTENSION - if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart)) + if (event.x_event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart)) { /* The SGI SCREEN_SAVER server extension has two event numbers, and this event matches the "idle" event. */ @@ -856,9 +1167,12 @@ 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.type == (si->sgi_saver_ext_event_number + + else if (event.x_event.type == (si->sgi_saver_ext_event_number + ScreenSaverEnd)) { /* The SGI SCREEN_SAVER server extension has two event numbers, @@ -867,17 +1181,130 @@ 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 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 (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. + */ + if (p->verbose_p) + { + /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */ + int screen = XRRRootToScreen (si->dpy, event.xrr_event.window); + fprintf (stderr, "%s: %d: screen change event received\n", + blurb(), screen); + } + +# ifdef RRScreenChangeNotifyMask + /* Inform Xlib that it's ok to update its data structures. */ + XRRUpdateConfiguration (&event.x_event); /* Xrandr.h 1.9, 2002/09/29 */ +# endif /* RRScreenChangeNotifyMask */ + + /* Resize the existing xscreensaver windows and cached ssi data. */ + if (update_screen_layout (si)) + { + if (p->verbose_p) + { + fprintf (stderr, "%s: new layout:\n", blurb()); + describe_monitor_layout (si); + } + resize_screensaver_window (si); + } + } + else +#endif /* HAVE_RANDR */ + /* Just some random event. Let the Widgets handle it, if desired. */ - dispatch_event (si, &event); + dispatch_event (si, &event.x_event); } } 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 @@ -888,11 +1315,11 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) there's only one event generated by user activity, not two.) */ if (!until_idle_p && si->locked_p) - swallow_unlock_typeahead_events (si, &event); + swallow_unlock_typeahead_events (si, &event.x_event); else while (XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask), - &event)) + &event.x_event)) ; @@ -909,8 +1336,6 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) if (until_idle_p && si->cycle_id) /* no cycle timer when inactive */ abort (); - - return; } @@ -967,23 +1392,35 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) 0: 309453991 timer 1: 4771729 keyboard - but on later kernels with MP machines, it looks like this: + but in Linux 2.2 and 2.4 kernels with MP machines, it looks like this: CPU0 CPU1 0: 1671450 1672618 IO-APIC-edge timer 1: 13037 13495 IO-APIC-edge keyboard - Joy! So how are we expected to parse that? Well, this code doesn't - parse it: it saves the last line with the string "keyboard" in it, and - does a string-comparison to note when it has changed. + and in Linux 2.6, it's gotten even goofier: now there are two lines + labelled "i8042". One of them is the keyboard, and one of them is + the PS/2 mouse -- and of course, you can't tell them apart, except + by wiggling the mouse and noting which one changes: - Thanks to Nat Friedman for figuring out all of this crap. + CPU0 CPU1 + 1: 32051 30864 IO-APIC-edge i8042 + 12: 476577 479913 IO-APIC-edge i8042 - Note that this only checks for lines with "keyboard" or "PS/2 Mouse" in - them. If you have a serial mouse, it won't detect that, it will only detect - keyboard activity. That's because there's no way to tell the difference - between a serial mouse and a general serial port, and it would be somewhat - unfortunate to have the screensaver turn off when the modem on COM1 burped. + Joy! So how are we expected to parse that? Well, this code doesn't + parse it: it saves the first line with the string "keyboard" (or + "i8042") in it, and does a string-comparison to note when it has + changed. If there are two "i8042" lines, we assume the first is + the keyboard and the second is the mouse (doesn't matter which is + which, really, as long as we don't compare them against each other.) + + Thanks to Nat Friedman for figuring out most of this crap. + + Note that if you have a serial or USB mouse, or a USB keyboard, it won't + detect it. That's because there's no way to tell the difference between a + serial mouse and a general serial port, and all USB devices look the same + from here. It would be somewhat unfortunate to have the screensaver turn + off when the modem on COM1 burped, or when a USB disk was accessed. */ @@ -1008,7 +1445,10 @@ query_proc_interrupts_available (saver_info *si, const char **why) f = fopen (PROC_INTERRUPTS, "r"); if (!f) - return False; + { + if (why) *why = "does not exist"; + return False; + } fclose (f); return True; @@ -1026,6 +1466,7 @@ proc_interrupts_activity_p (saver_info *si) char new_line[sizeof(last_kbd_line)]; Bool checked_kbd = False, kbd_changed = False; Bool checked_ptr = False, ptr_changed = False; + int i8042_count = 0; if (!f0) { @@ -1038,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. */ @@ -1072,18 +1519,45 @@ proc_interrupts_activity_p (saver_info *si) goto FAIL; } - /* Now read through the pseudo-file until we find the "keyboard" line. */ + /* Now read through the pseudo-file until we find the "keyboard", + "PS/2 mouse", or "i8042" lines. */ while (fgets (new_line, sizeof(new_line)-1, f1)) { - if (!checked_kbd && strstr (new_line, "keyboard")) + Bool i8042_p = !!strstr (new_line, "i8042"); + if (i8042_p) i8042_count++; + + if (strchr (new_line, ',')) + { + /* Ignore any line that has a comma on it: this is because + a setup like this: + + 12: 930935 XT-PIC usb-uhci, PS/2 Mouse + + is really bad news. It *looks* like we can note mouse + activity from that line, but really, that interrupt gets + fired any time any USB device has activity! So we have + to ignore any shared IRQs. + */ + } + else if (!checked_kbd && + (strstr (new_line, "keyboard") || + (i8042_p && i8042_count == 1))) { + /* Assume the keyboard interrupt is the line that says "keyboard", + or the *first* line that says "i8042". + */ kbd_changed = (*last_kbd_line && !!strcmp (new_line, last_kbd_line)); strcpy (last_kbd_line, new_line); checked_kbd = True; } - else if (!checked_ptr && strstr (new_line, "PS/2 Mouse")) + else if (!checked_ptr && + (strstr (new_line, "PS/2 Mouse") || + (i8042_p && i8042_count == 2))) { + /* Assume the mouse interrupt is the line that says "PS/2 mouse", + or the *second* line that says "i8042". + */ ptr_changed = (*last_ptr_line && !!strcmp (new_line, last_ptr_line)); strcpy (last_ptr_line, new_line); checked_ptr = True; @@ -1158,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, @@ -1185,11 +1660,13 @@ watchdog_timer (XtPointer closure, XtIntervalId *id) if (screenhack_running_p (si) && !monitor_powered_on_p (si)) { + int i; if (si->prefs.verbose_p) fprintf (stderr, "%s: X says monitor has powered down; " "killing running hacks.\n", blurb()); - kill_screenhack (si); + for (i = 0; i < si->nscreens; i++) + kill_screenhack (&si->screens[i]); } /* Re-schedule this timer. The watchdog timer defaults to a bit less @@ -1222,3 +1699,90 @@ reset_watchdog_timer (saver_info *si, Bool on_p) blurb(), p->watchdog_timeout, si->watchdog_id); } } + + +/* It's possible that a race condition could have led to the saver + window being unexpectedly still mapped. This can happen like so: + + - screen is blanked + - hack is launched + - that hack tries to grab a screen image (it does this by + first unmapping the saver window, then remapping it.) + - hack unmaps window + - hack waits + - user becomes active + - hack re-maps window (*) + - driver kills subprocess + - driver unmaps window (**) + + The race is that (*) might have been sent to the server before + the client process was killed, but, due to scheduling randomness, + might not have been received by the server until after (**). + In other words, (*) and (**) might happen out of order, meaning + the driver will unmap the window, and then after that, the + recently-dead client will re-map it. This leaves the user + locked out (it looks like a desktop, but it's not!) + + To avoid this: after un-blanking the screen, we launch a timer + that wakes up once a second for ten seconds, and makes damned + sure that the window is still unmapped. + */ + +void +de_race_timer (XtPointer closure, XtIntervalId *id) +{ + saver_info *si = (saver_info *) closure; + saver_preferences *p = &si->prefs; + int secs = 1; + + if (id == 0) /* if id is 0, this is the initialization call. */ + { + si->de_race_ticks = 10; + if (p->verbose_p) + fprintf (stderr, "%s: starting de-race timer (%d seconds.)\n", + blurb(), si->de_race_ticks); + } + else + { + int i; + XSync (si->dpy, False); + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + Window w = ssi->screensaver_window; + XWindowAttributes xgwa; + XGetWindowAttributes (si->dpy, w, &xgwa); + if (xgwa.map_state != IsUnmapped) + { + if (p->verbose_p) + fprintf (stderr, + "%s: %d: client race! emergency unmap 0x%lx.\n", + blurb(), i, (unsigned long) w); + XUnmapWindow (si->dpy, w); + } + else if (p->debug_p) + fprintf (stderr, "%s: %d: (de-race of 0x%lx is cool.)\n", + blurb(), i, (unsigned long) w); + } + XSync (si->dpy, False); + + si->de_race_ticks--; + } + + if (id && *id == si->de_race_id) + si->de_race_id = 0; + + if (si->de_race_id) abort(); + + if (si->de_race_ticks <= 0) + { + si->de_race_id = 0; + if (p->verbose_p) + fprintf (stderr, "%s: de-race completed.\n", blurb()); + } + else + { + si->de_race_id = XtAppAddTimeOut (si->app, secs * 1000, + de_race_timer, closure); + } +}