X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Ftimers.c;h=c7434925286b02e2d13c8ba04e0cb689457af9c2;hp=1dbdeff6706481a32ba9a99697a26cf44284bf06;hb=6b1c86cf395f59389e4ece4ea8f4bea2c332745b;hpb=06e9a7886a77cad92f9ddbc169d6d199a4d8b76d diff --git a/driver/timers.c b/driver/timers.c index 1dbdeff6..c7434925 100644 --- a/driver/timers.c +++ b/driver/timers.c @@ -1,6 +1,5 @@ /* timers.c --- detecting when the user is idle, and other timer-related tasks. - * xscreensaver, Copyright (c) 1991-1997, 1998 - * Jamie Zawinski + * xscreensaver, Copyright (c) 1991-2008 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 @@ -15,12 +14,12 @@ # include "config.h" #endif -/* #define DEBUG_TIMERS */ - #include #include #include #include +#include +#include #ifdef HAVE_XMU # ifndef VMS # include @@ -43,8 +42,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 */ @@ -73,21 +83,34 @@ 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); -#ifdef DEBUG_TIMERS if (verbose_p) fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n", blurb(), when, si->timer_id); -#endif /* DEBUG_TIMERS */ } @@ -99,6 +122,7 @@ notice_events (saver_info *si, Window window, Bool top_p) unsigned long events; Window root, parent, *kids; unsigned int nkids; + int screen_no; if (XtWindowToWidget (si->dpy, window)) /* If it's one of ours, don't mess up its event mask. */ @@ -109,6 +133,11 @@ notice_events (saver_info *si, Window window, Bool top_p) if (window == root) top_p = False; + /* Figure out which screen this window is on, for the diagnostics. */ + for (screen_no = 0; screen_no < si->nscreens; screen_no++) + if (root == RootWindowOfScreen (si->screens[screen_no].screen)) + break; + XGetWindowAttributes (si->dpy, window, &attrs); events = ((attrs.all_event_masks | attrs.do_not_propagate_mask) & KeyPressMask); @@ -125,14 +154,20 @@ notice_events (saver_info *si, Window window, Bool top_p) the mouse or touching the keyboard, we won't know that they've been active, and the screensaver will come on. That sucks, but I don't know how to get around it. + + Since X presents mouse wheels as clicks, this applies to those, too: + scrolling through a document using only the mouse wheel doesn't + count as activity... Fortunately, /proc/interrupts helps, on + systems that have it. Oh, if it's a PS/2 mouse, not serial or USB. + This sucks! */ XSelectInput (si->dpy, window, SubstructureNotifyMask | events); - if (top_p && p->verbose_p && (events & KeyPressMask)) + if (top_p && p->debug_p && (events & KeyPressMask)) { /* Only mention one window per tree (hack hack). */ - fprintf (stderr, "%s: selected KeyPress on 0x%lX\n", blurb(), - (unsigned long) window); + fprintf (stderr, "%s: %d: selected KeyPress on 0x%lX\n", + blurb(), screen_no, (unsigned long) window); top_p = False; } @@ -243,14 +278,21 @@ cycle_timer (XtPointer closure, XtIntervalId *id) } } - si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer, - (XtPointer) si); + if (how_long > 0) + { + si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer, + (XtPointer) si); -#ifdef DEBUG_TIMERS - if (p->verbose_p) - fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n", - blurb(), how_long, si->cycle_id); -#endif /* DEBUG_TIMERS */ + if (p->debug_p) + fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n", + blurb(), how_long, si->cycle_id); + } + else + { + if (p->debug_p) + fprintf (stderr, "%s: not starting cycle_timer: how_long == %ld\n", + blurb(), (unsigned long) how_long); + } } @@ -268,7 +310,7 @@ activate_lock_timer (XtPointer closure, XtIntervalId *id) /* Call this when user activity (or "simulated" activity) has been noticed. */ -static void +void reset_timers (saver_info *si) { saver_preferences *p = &si->prefs; @@ -277,19 +319,149 @@ reset_timers (saver_info *si) if (si->timer_id) { -#ifdef DEBUG_TIMERS - if (p->verbose_p) + if (p->debug_p) fprintf (stderr, "%s: killing idle_timer (%ld, %ld)\n", blurb(), p->timeout, si->timer_id); -#endif /* DEBUG_TIMERS */ XtRemoveTimeOut (si->timer_id); + si->timer_id = 0; } - schedule_wakeup_event (si, p->timeout, p->verbose_p); /* sets si->timer_id */ + schedule_wakeup_event (si, p->timeout, p->debug_p); /* sets si->timer_id */ 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); +#endif + +} + + +/* Returns true if the 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) +{ + 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); + + + /* 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 = (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); + + 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, + (moved_p ? "moved: " : "ignored:")); + 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, " (%d,%d; %d/%lu=%d)", + ABS(ssi->poll_mouse_last_root_x - root_x), + ABS(ssi->poll_mouse_last_root_y - root_y), + distance, seconds, dps); + + fprintf (stderr, ".\n"); + } + + if (!moved_p && + mods_p && + mask != ssi->poll_mouse_last_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); + } + + si->last_activity_screen = ssi; + ssi->poll_mouse_last_child = child; + ssi->poll_mouse_last_mask = 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; + } + + return moved_p; } @@ -314,55 +486,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; - - XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child, - &root_x, &root_y, &x, &y, &mask); - - 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; - -#ifdef DEBUG_TIMERS - if (p->verbose_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: modifiers changed at %s on screen %d.\n", - blurb(), timestring(), i); - else - fprintf (stderr, "%s: pointer moved at %s on screen %d.\n", - blurb(), timestring(), i); - -# if 0 - fprintf (stderr, "%s: old: %d %d 0x%x ; new: %d %d 0x%x\n", - blurb(), - ssi->poll_mouse_last_root_x, - ssi->poll_mouse_last_root_y, - (unsigned int) ssi->poll_mouse_last_child, - root_x, root_y, (unsigned int) child); -# endif /* 0 */ - -#endif /* DEBUG_TIMERS */ - - 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 @@ -370,16 +508,10 @@ check_pointer_timer (XtPointer closure, XtIntervalId *id) si->using_proc_interrupts && proc_interrupts_activity_p (si)) { -# ifdef DEBUG_TIMERS - if (p->verbose_p) - fprintf (stderr, "%s: /proc/interrupts activity at %s.\n", - blurb(), timestring()); -# endif /* DEBUG_TIMERS */ active_p = True; } #endif /* HAVE_PROC_INTERRUPTS */ - if (active_p) reset_timers (si); @@ -411,17 +543,20 @@ check_for_clock_skew (saver_info *si) time_t now = time ((time_t *) 0); long shift = now - si->last_wall_clock_time; -#ifdef DEBUG_TIMERS - if (p->verbose_p) - fprintf (stderr, "%s: checking wall clock (%d).\n", blurb(), - (si->last_wall_clock_time == 0 ? 0 : shift)); -#endif /* DEBUG_TIMERS */ + if (p->debug_p) + { + int i = (si->last_wall_clock_time == 0 ? 0 : shift); + fprintf (stderr, + "%s: checking wall clock for hibernation (%d:%02d:%02d).\n", + blurb(), + (i / (60 * 60)), ((i / 60) % 60), (i % 60)); + } if (si->last_wall_clock_time != 0 && shift > (p->timeout / 1000)) { if (p->verbose_p) - fprintf (stderr, "%s: wall clock has jumped by %d:%02d:%02d!\n", + fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld!\n", blurb(), (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60)); @@ -562,11 +697,8 @@ sleep_until_idle (saver_info *si, Bool 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. */ - schedule_wakeup_event (si, p->timeout, p->verbose_p); + we come back around through the event loop again. */ + schedule_wakeup_event (si, p->timeout, p->debug_p); if (polling_mouse_position) /* Check to see if the mouse has moved, and set up a repeating timer @@ -583,6 +715,15 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) 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) { @@ -646,9 +787,12 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) { /* 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->verbose_p); + schedule_wakeup_event (si, p->timeout - idle, p->debug_p); } } break; @@ -664,11 +808,7 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) if (scanning_all_windows) { Window w = event.xcreatewindow.window; -#ifdef DEBUG_TIMERS - start_notice_events_timer (si, w, p->verbose_p); -#else /* !DEBUG_TIMERS */ - start_notice_events_timer (si, w, False); -#endif /* !DEBUG_TIMERS */ + start_notice_events_timer (si, w, p->debug_p); } break; @@ -678,23 +818,87 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) case ButtonRelease: case MotionNotify: -#ifdef DEBUG_TIMERS - if (p->verbose_p) + if (p->debug_p) { + Window root=0, window=0; + int x=-1, y=-1; + const char *type = 0; if (event.xany.type == MotionNotify) - fprintf (stderr,"%s: MotionNotify at %s\n",blurb(),timestring()); + { + /*type = "MotionNotify";*/ + root = event.xmotion.root; + window = event.xmotion.window; + x = event.xmotion.x_root; + y = event.xmotion.y_root; + } else if (event.xany.type == KeyPress) - fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(), - (unsigned int) event.xkey.window, timestring ()); + { + type = "KeyPress"; + root = event.xkey.root; + window = event.xkey.window; + x = y = -1; + } else if (event.xany.type == ButtonPress) - fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(), - (unsigned int) event.xbutton.window, timestring ()); + { + type = "ButtonPress"; + root = event.xkey.root; + window = event.xkey.window; + x = event.xmotion.x_root; + y = event.xmotion.y_root; + } + + if (type) + { + int i; + for (i = 0; i < si->nscreens; i++) + if (root == RootWindowOfScreen (si->screens[i].screen)) + break; + fprintf (stderr,"%s: %d: %s on 0x%lx", + blurb(), i, type, (unsigned long) window); + + /* 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) + { + KeySym keysym; + char c = 0; + XLookupString (&event.xkey, &c, 1, &keysym, 0); + fprintf (stderr, " (%s%s)", + (event.xkey.send_event ? "synthetic " : ""), + XKeysymToString (keysym)); + } + + if (x == -1) + fprintf (stderr, "\n"); + else + fprintf (stderr, " at %d,%d.\n", x, y); + } } -#endif /* DEBUG_TIMERS */ /* If any widgets want to handle this event, let them. */ dispatch_event (si, &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.xany.type == MotionNotify) + { + int i; + for (i = 0; i < si->nscreens; i++) + if (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. If we're waiting until the user becomes idle, reset the timers @@ -802,6 +1006,45 @@ sleep_until_idle (saver_info *si, Bool until_idle_p) else #endif /* HAVE_SGI_SAVER_EXTENSION */ +#ifdef HAVE_RANDR + if (event.type == (si->randr_event_number + RRScreenChangeNotify)) + { + /* The Resize and Rotate extension sends an event when the + size, rotation, or refresh rate of the screen has changed. */ + + XRRScreenChangeNotifyEvent *xrr_event = + (XRRScreenChangeNotifyEvent *) &event; + /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */ + int screen = XRRRootToScreen (si->dpy, xrr_event->window); + + if (p->verbose_p) + { + if (si->screens[screen].width == xrr_event->width && + si->screens[screen].height == xrr_event->height) + fprintf (stderr, + "%s: %d: no-op screen size change event (%dx%d)\n", + blurb(), screen, + xrr_event->width, xrr_event->height); + else + fprintf (stderr, + "%s: %d: screen size changed from %dx%d to %dx%d\n", + blurb(), screen, + si->screens[screen].width, + si->screens[screen].height, + xrr_event->width, xrr_event->height); + } + +# ifdef RRScreenChangeNotifyMask + /* Inform Xlib that it's ok to update its data structures. */ + XRRUpdateConfiguration (&event); /* Xrandr.h 1.9, 2002/09/29 */ +# endif /* RRScreenChangeNotifyMask */ + + /* Resize the existing xscreensaver windows and cached ssi data. */ + resize_screensaver_window (si); + } + else +#endif /* HAVE_RANDR */ + /* Just some random event. Let the Widgets handle it, if desired. */ dispatch_event (si, &event); } @@ -897,23 +1140,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. */ @@ -938,7 +1193,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; @@ -954,8 +1212,9 @@ proc_interrupts_activity_p (saver_info *si) static char last_kbd_line[255] = { 0, }; static char last_ptr_line[255] = { 0, }; char new_line[sizeof(last_kbd_line)]; - Bool got_kbd = False, kbd_diff = False; - Bool got_ptr = False, ptr_diff = False; + Bool checked_kbd = False, kbd_changed = False; + Bool checked_ptr = False, ptr_changed = False; + int i8042_count = 0; if (!f0) { @@ -1002,31 +1261,66 @@ 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 (!got_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))) { - kbd_diff = (*last_kbd_line && !!strcmp (new_line, last_kbd_line)); + /* 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); - got_kbd = True; + checked_kbd = True; } - else if (!got_ptr && strstr (new_line, "PS/2 Mouse")) + else if (!checked_ptr && + (strstr (new_line, "PS/2 Mouse") || + (i8042_p && i8042_count == 2))) { - ptr_diff = (*last_ptr_line && !!strcmp (new_line, last_ptr_line)); + /* 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); - got_ptr = True; + checked_ptr = True; } - if (got_kbd && got_ptr) + if (checked_kbd && checked_ptr) break; } - if (got_kbd || got_ptr) + if (checked_kbd || checked_ptr) { fclose (f1); - return (kbd_diff || ptr_diff); + + if (si->prefs.debug_p && (kbd_changed || ptr_changed)) + fprintf (stderr, "%s: /proc/interrupts activity: %s\n", + blurb(), + ((kbd_changed && ptr_changed) ? "mouse and kbd" : + kbd_changed ? "kbd" : + ptr_changed ? "mouse" : "ERR")); + + return (kbd_changed || ptr_changed); } @@ -1071,28 +1365,35 @@ static void watchdog_timer (XtPointer closure, XtIntervalId *id) { saver_info *si = (saver_info *) closure; + saver_preferences *p = &si->prefs; disable_builtin_screensaver (si, False); + /* If the DPMS settings on the server have changed, change them back to + what ~/.xscreensaver says they should be. */ + sync_server_dpms_settings (si->dpy, + (p->dpms_enabled_p && + p->mode != DONT_BLANK), + p->dpms_standby / 1000, + p->dpms_suspend / 1000, + p->dpms_off / 1000, + False); + if (si->screen_blanked_p) { Bool running_p = screenhack_running_p (si); if (si->dbox_up_p) { -#ifdef DEBUG_TIMERS - if (si->prefs.verbose_p) + if (si->prefs.debug_p) fprintf (stderr, "%s: dialog box is up: not raising screen.\n", blurb()); -#endif /* DEBUG_TIMERS */ } else { -#ifdef DEBUG_TIMERS - if (si->prefs.verbose_p) + if (si->prefs.debug_p) fprintf (stderr, "%s: watchdog timer raising %sscreen.\n", blurb(), (running_p ? "" : "and clearing ")); -#endif /* DEBUG_TIMERS */ raise_window (si, True, True, running_p); } @@ -1132,11 +1433,95 @@ reset_watchdog_timer (saver_info *si, Bool on_p) si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout, watchdog_timer, (XtPointer) si); -#ifdef DEBUG_TIMERS - if (p->verbose_p) + if (p->debug_p) fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n", blurb(), p->watchdog_timeout, si->watchdog_id); -#endif /* DEBUG_TIMERS */ + } +} + + +/* 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); } }