ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-4.22.tar.gz
[xscreensaver] / driver / timers.c
1 /* timers.c --- detecting when the user is idle, and other timer-related tasks.
2  * xscreensaver, Copyright (c) 1991-2004 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <stdio.h>
18 #include <X11/Xlib.h>
19 #include <X11/Intrinsic.h>
20 #include <X11/Xos.h>
21 #include <time.h>
22 #include <sys/time.h>
23 #ifdef HAVE_XMU
24 # ifndef VMS
25 #  include <X11/Xmu/Error.h>
26 # else /* VMS */
27 #  include <Xmu/Error.h>
28 # endif /* VMS */
29 # else /* !HAVE_XMU */
30 # include "xmu.h"
31 #endif /* !HAVE_XMU */
32
33 #ifdef HAVE_XIDLE_EXTENSION
34 #include <X11/extensions/xidle.h>
35 #endif /* HAVE_XIDLE_EXTENSION */
36
37 #ifdef HAVE_MIT_SAVER_EXTENSION
38 #include <X11/extensions/scrnsaver.h>
39 #endif /* HAVE_MIT_SAVER_EXTENSION */
40
41 #ifdef HAVE_SGI_SAVER_EXTENSION
42 #include <X11/extensions/XScreenSaver.h>
43 #endif /* HAVE_SGI_SAVER_EXTENSION */
44
45 #ifdef HAVE_RANDR
46 #include <X11/extensions/Xrandr.h>
47 #endif /* HAVE_RANDR */
48
49 #include "xscreensaver.h"
50
51 #undef ABS
52 #define ABS(x)((x)<0?-(x):(x))
53
54 #undef MAX
55 #define MAX(x,y)((x)>(y)?(x):(y))
56
57
58 #ifdef HAVE_PROC_INTERRUPTS
59 static Bool proc_interrupts_activity_p (saver_info *si);
60 #endif /* HAVE_PROC_INTERRUPTS */
61
62 static void check_for_clock_skew (saver_info *si);
63
64
65 void
66 idle_timer (XtPointer closure, XtIntervalId *id)
67 {
68   saver_info *si = (saver_info *) closure;
69
70   /* What an amazingly shitty design.  Not only does Xt execute timeout
71      events from XtAppNextEvent() instead of from XtDispatchEvent(), but
72      there is no way to tell Xt to block until there is an X event OR a
73      timeout happens.  Once your timeout proc is called, XtAppNextEvent()
74      still won't return until a "real" X event comes in.
75
76      So this function pushes a stupid, gratuitous, unnecessary event back
77      on the event queue to force XtAppNextEvent to return Right Fucking Now.
78      When the code in sleep_until_idle() sees an event of type XAnyEvent,
79      which the server never generates, it knows that a timeout has occurred.
80    */
81   XEvent fake_event;
82   fake_event.type = 0;  /* XAnyEvent type, ignored. */
83   fake_event.xany.display = si->dpy;
84   fake_event.xany.window  = 0;
85   XPutBackEvent (si->dpy, &fake_event);
86
87   /* If we are the timer that just went off, clear the pointer to the id. */
88   if (id)
89     {
90       if (si->timer_id && *id != si->timer_id)
91         abort();  /* oops, scheduled timer twice?? */
92       si->timer_id = 0;
93     }
94 }
95
96
97 void
98 schedule_wakeup_event (saver_info *si, Time when, Bool verbose_p)
99 {
100   if (si->timer_id)
101     {
102       if (verbose_p)
103         fprintf (stderr, "%s: idle_timer already running\n", blurb());
104       return;
105     }
106
107   /* Wake up periodically to ask the server if we are idle. */
108   si->timer_id = XtAppAddTimeOut (si->app, when, idle_timer,
109                                   (XtPointer) si);
110
111   if (verbose_p)
112     fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
113              blurb(), when, si->timer_id);
114 }
115
116
117 static void
118 notice_events (saver_info *si, Window window, Bool top_p)
119 {
120   saver_preferences *p = &si->prefs;
121   XWindowAttributes attrs;
122   unsigned long events;
123   Window root, parent, *kids;
124   unsigned int nkids;
125   int screen_no;
126
127   if (XtWindowToWidget (si->dpy, window))
128     /* If it's one of ours, don't mess up its event mask. */
129     return;
130
131   if (!XQueryTree (si->dpy, window, &root, &parent, &kids, &nkids))
132     return;
133   if (window == root)
134     top_p = False;
135
136   /* Figure out which screen this window is on, for the diagnostics. */
137   for (screen_no = 0; screen_no < si->nscreens; screen_no++)
138     if (root == RootWindowOfScreen (si->screens[screen_no].screen))
139       break;
140
141   XGetWindowAttributes (si->dpy, window, &attrs);
142   events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
143             & KeyPressMask);
144
145   /* Select for SubstructureNotify on all windows.
146      Select for KeyPress on all windows that already have it selected.
147
148      Note that we can't select for ButtonPress, because of X braindamage:
149      only one client at a time may select for ButtonPress on a given
150      window, though any number can select for KeyPress.  Someone explain
151      *that* to me.
152
153      So, if the user spends a while clicking the mouse without ever moving
154      the mouse or touching the keyboard, we won't know that they've been
155      active, and the screensaver will come on.  That sucks, but I don't
156      know how to get around it.
157
158      Since X presents mouse wheels as clicks, this applies to those, too:
159      scrolling through a document using only the mouse wheel doesn't
160      count as activity...  Fortunately, /proc/interrupts helps, on
161      systems that have it.  Oh, if it's a PS/2 mouse, not serial or USB.
162      This sucks!
163    */
164   XSelectInput (si->dpy, window, SubstructureNotifyMask | events);
165
166   if (top_p && p->debug_p && (events & KeyPressMask))
167     {
168       /* Only mention one window per tree (hack hack). */
169       fprintf (stderr, "%s: %d: selected KeyPress on 0x%lX\n",
170                blurb(), screen_no, (unsigned long) window);
171       top_p = False;
172     }
173
174   if (kids)
175     {
176       while (nkids)
177         notice_events (si, kids [--nkids], top_p);
178       XFree ((char *) kids);
179     }
180 }
181
182
183 int
184 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
185 {
186   /* When we notice a window being created, we spawn a timer that waits
187      30 seconds or so, and then selects events on that window.  This error
188      handler is used so that we can cope with the fact that the window
189      may have been destroyed <30 seconds after it was created.
190    */
191   if (error->error_code == BadWindow ||
192       error->error_code == BadMatch ||
193       error->error_code == BadDrawable)
194     return 0;
195   else
196     return saver_ehandler (dpy, error);
197 }
198
199
200 struct notice_events_timer_arg {
201   saver_info *si;
202   Window w;
203 };
204
205 static void
206 notice_events_timer (XtPointer closure, XtIntervalId *id)
207 {
208   struct notice_events_timer_arg *arg =
209     (struct notice_events_timer_arg *) closure;
210
211   XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
212
213   saver_info *si = arg->si;
214   Window window = arg->w;
215
216   free(arg);
217   notice_events (si, window, True);
218   XSync (si->dpy, False);
219   XSetErrorHandler (old_handler);
220 }
221
222 void
223 start_notice_events_timer (saver_info *si, Window w, Bool verbose_p)
224 {
225   saver_preferences *p = &si->prefs;
226   struct notice_events_timer_arg *arg =
227     (struct notice_events_timer_arg *) malloc(sizeof(*arg));
228   arg->si = si;
229   arg->w = w;
230   XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer,
231                    (XtPointer) arg);
232
233   if (verbose_p)
234     fprintf (stderr, "%s: starting notice_events_timer for 0x%X (%lu)\n",
235              blurb(), (unsigned int) w, p->notice_events_timeout);
236 }
237
238
239 /* When the screensaver is active, this timer will periodically change
240    the running program.
241  */
242 void
243 cycle_timer (XtPointer closure, XtIntervalId *id)
244 {
245   saver_info *si = (saver_info *) closure;
246   saver_preferences *p = &si->prefs;
247   Time how_long = p->cycle;
248
249   if (si->selection_mode > 0 &&
250       screenhack_running_p (si))
251     /* If we're in "SELECT n" mode, the cycle timer going off will just
252        restart this same hack again.  There's not much point in doing this
253        every 5 or 10 minutes, but on the other hand, leaving one hack running
254        for days is probably not a great idea, since they tend to leak and/or
255        crash.  So, restart the thing once an hour. */
256     how_long = 1000 * 60 * 60;
257
258   if (si->dbox_up_p)
259     {
260       if (p->verbose_p)
261         fprintf (stderr, "%s: dialog box up; delaying hack change.\n",
262                  blurb());
263       how_long = 30000; /* 30 secs */
264     }
265   else
266     {
267       maybe_reload_init_file (si);
268       kill_screenhack (si);
269
270       if (!si->throttled_p)
271         spawn_screenhack (si, False);
272       else
273         {
274           raise_window (si, True, True, False);
275           if (p->verbose_p)
276             fprintf (stderr, "%s: not launching new hack (throttled.)\n",
277                      blurb());
278         }
279     }
280
281   if (how_long > 0)
282     {
283       si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
284                                       (XtPointer) si);
285
286       if (p->debug_p)
287         fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n",
288                  blurb(), how_long, si->cycle_id);
289     }
290   else
291     {
292       if (p->debug_p)
293         fprintf (stderr, "%s: not starting cycle_timer: how_long == %ld\n",
294                  blurb(), (unsigned long) how_long);
295     }
296 }
297
298
299 void
300 activate_lock_timer (XtPointer closure, XtIntervalId *id)
301 {
302   saver_info *si = (saver_info *) closure;
303   saver_preferences *p = &si->prefs;
304
305   if (p->verbose_p)
306     fprintf (stderr, "%s: timed out; activating lock.\n", blurb());
307   set_locked_p (si, True);
308 }
309
310
311 /* Call this when user activity (or "simulated" activity) has been noticed.
312  */
313 void
314 reset_timers (saver_info *si)
315 {
316   saver_preferences *p = &si->prefs;
317   if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
318     return;
319
320   if (si->timer_id)
321     {
322       if (p->debug_p)
323         fprintf (stderr, "%s: killing idle_timer  (%ld, %ld)\n",
324                  blurb(), p->timeout, si->timer_id);
325       XtRemoveTimeOut (si->timer_id);
326       si->timer_id = 0;
327     }
328
329   schedule_wakeup_event (si, p->timeout, p->debug_p); /* sets si->timer_id */
330
331   if (si->cycle_id) abort ();   /* no cycle timer when inactive */
332
333   si->last_activity_time = time ((time_t *) 0);
334 }
335
336
337 /* Returns true if the mouse has moved since the last time we checked.
338    Small motions (of less than "hysteresis" pixels/second) are ignored.
339  */
340 static Bool
341 pointer_moved_p (saver_screen_info *ssi, Bool mods_p)
342 {
343   saver_info *si = ssi->global;
344   saver_preferences *p = &si->prefs;
345
346   Window root, child;
347   int root_x, root_y, x, y;
348   unsigned int mask;
349   time_t now = time ((time_t *) 0);
350   unsigned int distance, dps;
351   unsigned long seconds = 0;
352   Bool moved_p = False;
353
354   /* don't check xinerama pseudo-screens. */
355   if (!ssi->real_screen_p) return False;
356
357   if (!XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
358                       &root_x, &root_y, &x, &y, &mask))
359     {
360       /* If XQueryPointer() returns false, the mouse is not on this screen.
361        */
362       x = root_x = -1;
363       y = root_y = -1;
364       root = child = 0;
365       mask = 0;
366     }
367
368   distance = MAX (ABS (ssi->poll_mouse_last_root_x - root_x),
369                   ABS (ssi->poll_mouse_last_root_y - root_y));
370   seconds = (now - ssi->poll_mouse_last_time);
371
372
373   /* When the screen is blanked, we get MotionNotify events, but when not
374      blanked, we poll only every 5 seconds, and that's not enough resolution
375      to do hysteresis based on a 1 second interval.  So, assume that any
376      motion we've seen during the 5 seconds when our eyes were closed happened
377      in the last 1 second instead.
378    */
379   if (seconds > 1) seconds = 1;
380
381   dps = (seconds <= 0 ? distance : (distance / seconds));
382
383   /* Motion only counts if the rate is more than N pixels per second.
384    */
385   if (dps >= p->pointer_hysteresis &&
386       distance > 0)
387     moved_p = True;
388
389   /* If the mouse is not on this screen but used to be, that's motion.
390      If the mouse was not on this screen, but is now, that's motion.
391    */
392   {
393     Bool on_screen_p  = (root_x != -1 && root_y != -1);
394     Bool was_on_screen_p = (ssi->poll_mouse_last_root_x != -1 &&
395                             ssi->poll_mouse_last_root_y != -1);
396
397     if (on_screen_p != was_on_screen_p)
398       moved_p = True;
399   }
400
401   if (p->debug_p && (distance != 0 || moved_p))
402     {
403       fprintf (stderr, "%s: %d: pointer %s", blurb(), ssi->number,
404                (moved_p ? "moved:  " : "ignored:"));
405       if (ssi->poll_mouse_last_root_x == -1)
406         fprintf (stderr, "off screen");
407       else
408         fprintf (stderr, "%d,%d",
409                  ssi->poll_mouse_last_root_x,
410                  ssi->poll_mouse_last_root_y);
411       fprintf (stderr, " -> ");
412       if (root_x == -1)
413         fprintf (stderr, "off screen");
414       else
415         fprintf (stderr, "%d,%d", root_x, root_y);
416       if (ssi->poll_mouse_last_root_x != -1 && root_x != -1)
417         fprintf (stderr, " (%d,%d; %d/%lu=%d)",
418                  ABS(ssi->poll_mouse_last_root_x - root_x),
419                  ABS(ssi->poll_mouse_last_root_y - root_y),
420                  distance, seconds, dps);
421
422       fprintf (stderr, ".\n");
423     }
424
425   if (!moved_p &&
426       mods_p &&
427       mask != ssi->poll_mouse_last_mask)
428     {
429       moved_p = True;
430
431       if (p->debug_p)
432         fprintf (stderr, "%s: %d: modifiers changed: 0x%04x -> 0x%04x.\n",
433                  blurb(), ssi->number, ssi->poll_mouse_last_mask, mask);
434     }
435
436   si->last_activity_screen   = ssi;
437   ssi->poll_mouse_last_child = child;
438   ssi->poll_mouse_last_mask  = mask;
439
440   if (moved_p || seconds > 0)
441     {
442       ssi->poll_mouse_last_time   = now;
443       ssi->poll_mouse_last_root_x = root_x;
444       ssi->poll_mouse_last_root_y = root_y;
445     }
446
447   return moved_p;
448 }
449
450
451 /* When we aren't using a server extension, this timer is used to periodically
452    wake up and poll the mouse position, which is possibly more reliable than
453    selecting motion events on every window.
454  */
455 static void
456 check_pointer_timer (XtPointer closure, XtIntervalId *id)
457 {
458   int i;
459   saver_info *si = (saver_info *) closure;
460   saver_preferences *p = &si->prefs;
461   Bool active_p = False;
462
463   if (!si->using_proc_interrupts &&
464       (si->using_xidle_extension ||
465        si->using_mit_saver_extension ||
466        si->using_sgi_saver_extension))
467     /* If an extension is in use, we should not be polling the mouse.
468        Unless we're also checking /proc/interrupts, in which case, we should.
469      */
470     abort ();
471
472   if (id && *id == si->check_pointer_timer_id)  /* this is us - it's expired */
473     si->check_pointer_timer_id = 0;
474
475   if (si->check_pointer_timer_id)               /* only queue one at a time */
476     XtRemoveTimeOut (si->check_pointer_timer_id);
477
478   si->check_pointer_timer_id =                  /* now re-queue */
479     XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
480                      (XtPointer) si);
481
482   for (i = 0; i < si->nscreens; i++)
483     {
484       saver_screen_info *ssi = &si->screens[i];
485       if (pointer_moved_p (ssi, True))
486         active_p = True;
487     }
488
489 #ifdef HAVE_PROC_INTERRUPTS
490   if (!active_p &&
491       si->using_proc_interrupts &&
492       proc_interrupts_activity_p (si))
493     {
494       active_p = True;
495     }
496 #endif /* HAVE_PROC_INTERRUPTS */
497
498   if (active_p)
499     reset_timers (si);
500
501   check_for_clock_skew (si);
502 }
503
504
505 /* An unfortunate situation is this: the saver is not active, because the
506    user has been typing.  The machine is a laptop.  The user closes the lid
507    and suspends it.  The CPU halts.  Some hours later, the user opens the
508    lid.  At this point, Xt's timers will fire, and xscreensaver will blank
509    the screen.
510
511    So far so good -- well, not really, but it's the best that we can do,
512    since the OS doesn't send us a signal *before* shutdown -- but if the
513    user had delayed locking (lockTimeout > 0) then we should start off
514    in the locked state, rather than only locking N minutes from when the
515    lid was opened.  Also, eschewing fading is probably a good idea, to
516    clamp down as soon as possible.
517
518    We only do this when we'd be polling the mouse position anyway.
519    This amounts to an assumption that machines with APM support also
520    have /proc/interrupts.
521  */
522 static void
523 check_for_clock_skew (saver_info *si)
524 {
525   saver_preferences *p = &si->prefs;
526   time_t now = time ((time_t *) 0);
527   long shift = now - si->last_wall_clock_time;
528
529   if (p->debug_p)
530     {
531       int i = (si->last_wall_clock_time == 0 ? 0 : shift);
532       fprintf (stderr,
533                "%s: checking wall clock for hibernation (%d:%02d:%02d).\n",
534                blurb(),
535                (i / (60 * 60)), ((i / 60) % 60), (i % 60));
536     }
537
538   if (si->last_wall_clock_time != 0 &&
539       shift > (p->timeout / 1000))
540     {
541       if (p->verbose_p)
542         fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld!\n",
543                  blurb(),
544                  (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60));
545
546       si->emergency_lock_p = True;
547       idle_timer ((XtPointer) si, 0);
548     }
549
550   si->last_wall_clock_time = now;
551 }
552
553
554
555 static void
556 dispatch_event (saver_info *si, XEvent *event)
557 {
558   /* If this is for the splash dialog, pass it along.
559      Note that the password dialog is handled with its own event loop,
560      so events for that window will never come through here.
561    */
562   if (si->splash_dialog && event->xany.window == si->splash_dialog)
563     handle_splash_event (si, event);
564
565   XtDispatchEvent (event);
566 }
567
568
569 static void
570 swallow_unlock_typeahead_events (saver_info *si, XEvent *e)
571 {
572   XEvent event;
573   char buf [100];
574   int i = 0;
575
576   memset (buf, 0, sizeof(buf));
577
578   event = *e;
579
580   do
581     {
582       if (event.xany.type == KeyPress)
583         {
584           char s[2];
585           int size = XLookupString ((XKeyEvent *) &event, s, 1, 0, 0);
586           if (size != 1) continue;
587           switch (*s)
588             {
589             case '\010': case '\177':                   /* Backspace */
590               if (i > 0) i--;
591               break;
592             case '\025': case '\030':                   /* Erase line */
593             case '\012': case '\015':                   /* Enter */
594               i = 0;
595               break;
596             case '\040':                                /* Space */
597               if (i == 0)
598                 break;  /* ignore space at beginning of line */
599               /* else, fall through */
600             default:
601               buf [i++] = *s;
602               break;
603             }
604         }
605
606     } while (i < sizeof(buf)-1 &&
607              XCheckMaskEvent (si->dpy, KeyPressMask, &event));
608
609   buf[i] = 0;
610
611   if (si->unlock_typeahead)
612     {
613       memset (si->unlock_typeahead, 0, strlen(si->unlock_typeahead));
614       free (si->unlock_typeahead);
615     }
616
617   if (i > 0)
618     si->unlock_typeahead = strdup (buf);
619   else
620     si->unlock_typeahead = 0;
621
622   memset (buf, 0, sizeof(buf));
623 }
624
625
626 /* methods of detecting idleness:
627
628       explicitly informed by SGI SCREEN_SAVER server event;
629       explicitly informed by MIT-SCREEN-SAVER server event;
630       poll server idle time with XIDLE extension;
631       select events on all windows, and note absence of recent events;
632       note that /proc/interrupts has not changed in a while;
633       activated by clientmessage.
634
635    methods of detecting non-idleness:
636
637       read events on the xscreensaver window;
638       explicitly informed by SGI SCREEN_SAVER server event;
639       explicitly informed by MIT-SCREEN-SAVER server event;
640       select events on all windows, and note events on any of them;
641       note that /proc/interrupts has changed;
642       deactivated by clientmessage.
643
644    I trust that explains why this function is a big hairy mess.
645  */
646 void
647 sleep_until_idle (saver_info *si, Bool until_idle_p)
648 {
649   saver_preferences *p = &si->prefs;
650   XEvent event;
651
652   /* We need to select events on all windows if we're not using any extensions.
653      Otherwise, we don't need to. */
654   Bool scanning_all_windows = !(si->using_xidle_extension ||
655                                 si->using_mit_saver_extension ||
656                                 si->using_sgi_saver_extension);
657
658   /* We need to periodically wake up and check for idleness if we're not using
659      any extensions, or if we're using the XIDLE extension.  The other two
660      extensions explicitly deliver events when we go idle/non-idle, so we
661      don't need to poll. */
662   Bool polling_for_idleness = !(si->using_mit_saver_extension ||
663                                 si->using_sgi_saver_extension);
664
665   /* Whether we need to periodically wake up and check to see if the mouse has
666      moved.  We only need to do this when not using any extensions.  The reason
667      this isn't the same as `polling_for_idleness' is that the "idleness" poll
668      can happen (for example) 5 minutes from now, whereas the mouse-position
669      poll should happen with low periodicity.  We don't need to poll the mouse
670      position with the XIDLE extension, but we do need to periodically wake up
671      and query the server with that extension.  For our purposes, polling
672      /proc/interrupts is just like polling the mouse position.  It has to
673      happen on the same kind of schedule. */
674   Bool polling_mouse_position = (si->using_proc_interrupts ||
675                                  !(si->using_xidle_extension ||
676                                    si->using_mit_saver_extension ||
677                                    si->using_sgi_saver_extension));
678
679   if (until_idle_p)
680     {
681       if (polling_for_idleness)
682         /* This causes a no-op event to be delivered to us in a while, so that
683            we come back around through the event loop again.  */
684         schedule_wakeup_event (si, p->timeout, p->debug_p);
685
686       if (polling_mouse_position)
687         /* Check to see if the mouse has moved, and set up a repeating timer
688            to do so periodically (typically, every 5 seconds.) */
689         check_pointer_timer ((XtPointer) si, 0);
690     }
691
692   while (1)
693     {
694       XtAppNextEvent (si->app, &event);
695
696       switch (event.xany.type) {
697       case 0:           /* our synthetic "timeout" event has been signalled */
698         if (until_idle_p)
699           {
700             Time idle;
701
702             /* We may be idle; check one last time to see if the mouse has
703                moved, just in case the idle-timer went off within the 5 second
704                window between mouse polling.  If the mouse has moved, then
705                check_pointer_timer() will reset last_activity_time.
706              */
707             if (polling_mouse_position)
708               check_pointer_timer ((XtPointer) si, 0);
709
710 #ifdef HAVE_XIDLE_EXTENSION
711             if (si->using_xidle_extension)
712               {
713                 /* The XIDLE extension uses the synthetic event to prod us into
714                    re-asking the server how long the user has been idle. */
715                 if (! XGetIdleTime (si->dpy, &idle))
716                   {
717                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
718                     saver_exit (si, 1, 0);
719                   }
720               }
721             else
722 #endif /* HAVE_XIDLE_EXTENSION */
723 #ifdef HAVE_MIT_SAVER_EXTENSION
724               if (si->using_mit_saver_extension)
725                 {
726                   /* We don't need to do anything in this case - the synthetic
727                      event isn't necessary, as we get sent specific events
728                      to wake us up.  In fact, this event generally shouldn't
729                      be being delivered when the MIT extension is in use. */
730                   idle = 0;
731                 }
732             else
733 #endif /* HAVE_MIT_SAVER_EXTENSION */
734 #ifdef HAVE_SGI_SAVER_EXTENSION
735               if (si->using_sgi_saver_extension)
736                 {
737                   /* We don't need to do anything in this case - the synthetic
738                      event isn't necessary, as we get sent specific events
739                      to wake us up.  In fact, this event generally shouldn't
740                      be being delivered when the SGI extension is in use. */
741                   idle = 0;
742                 }
743             else
744 #endif /* HAVE_SGI_SAVER_EXTENSION */
745               {
746                 /* Otherwise, no server extension is in use.  The synthetic
747                    event was to tell us to wake up and see if the user is now
748                    idle.  Compute the amount of idle time by comparing the
749                    `last_activity_time' to the wall clock.  The l_a_t was set
750                    by calling `reset_timers()', which is called only in only
751                    two situations: when polling the mouse position has revealed
752                    the the mouse has moved (user activity) or when we have read
753                    an event (again, user activity.)
754                  */
755                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
756               }
757
758             if (idle >= p->timeout)
759               {
760                 /* Look, we've been idle long enough.  We're done. */
761                 goto DONE;
762               }
763             else if (si->emergency_lock_p)
764               {
765                 /* Oops, the wall clock has jumped far into the future, so
766                    we need to lock down in a hurry! */
767                 goto DONE;
768               }
769             else
770               {
771                 /* The event went off, but it turns out that the user has not
772                    yet been idle for long enough.  So re-signal the event.
773                    Be economical: if we should blank after 5 minutes, and the
774                    user has been idle for 2 minutes, then set this timer to
775                    go off in 3 minutes.
776                  */
777                 if (polling_for_idleness)
778                   schedule_wakeup_event (si, p->timeout - idle, p->debug_p);
779               }
780           }
781         break;
782
783       case ClientMessage:
784         if (handle_clientmessage (si, &event, until_idle_p))
785           goto DONE;
786         break;
787
788       case CreateNotify:
789         /* A window has been created on the screen somewhere.  If we're
790            supposed to scan all windows for events, prepare this window. */
791         if (scanning_all_windows)
792           {
793             Window w = event.xcreatewindow.window;
794             start_notice_events_timer (si, w, p->debug_p);
795           }
796         break;
797
798       case KeyPress:
799       case KeyRelease:
800       case ButtonPress:
801       case ButtonRelease:
802       case MotionNotify:
803
804         if (p->debug_p)
805           {
806             Window root=0, window=0;
807             int x=-1, y=-1;
808             const char *type = 0;
809             if (event.xany.type == MotionNotify)
810               {
811                 /*type = "MotionNotify";*/
812                 root = event.xmotion.root;
813                 window = event.xmotion.window;
814                 x = event.xmotion.x_root;
815                 y = event.xmotion.y_root;
816               }
817             else if (event.xany.type == KeyPress)
818               {
819                 type = "KeyPress";
820                 root = event.xkey.root;
821                 window = event.xkey.window;
822                 x = y = -1;
823               }
824             else if (event.xany.type == ButtonPress)
825               {
826                 type = "ButtonPress";
827                 root = event.xkey.root;
828                 window = event.xkey.window;
829                 x = event.xmotion.x_root;
830                 y = event.xmotion.y_root;
831               }
832
833             if (type)
834               {
835                 int i;
836                 for (i = 0; i < si->nscreens; i++)
837                   if (root == RootWindowOfScreen (si->screens[i].screen))
838                     break;
839                 fprintf (stderr,"%s: %d: %s on 0x%lx",
840                          blurb(), i, type, (unsigned long) window);
841
842                 /* Be careful never to do this unless in -debug mode, as
843                    this could expose characters from the unlock password. */
844                 if (p->debug_p && event.xany.type == KeyPress)
845                   {
846                     KeySym keysym;
847                     char c = 0;
848                     XLookupString (&event.xkey, &c, 1, &keysym, 0);
849                     fprintf (stderr, " (%s%s)",
850                              (event.xkey.send_event ? "synthetic " : ""),
851                              XKeysymToString (keysym));
852                   }
853
854                 if (x == -1)
855                   fprintf (stderr, "\n");
856                 else
857                   fprintf (stderr, " at %d,%d.\n", x, y);
858               }
859           }
860
861         /* If any widgets want to handle this event, let them. */
862         dispatch_event (si, &event);
863
864         
865         /* If we got a MotionNotify event, figure out what screen it
866            was on and poll the mouse there: if the mouse hasn't moved
867            far enough to count as "real" motion, then ignore this
868            event.
869          */
870         if (event.xany.type == MotionNotify)
871           {
872             int i;
873             for (i = 0; i < si->nscreens; i++)
874               if (event.xmotion.root ==
875                   RootWindowOfScreen (si->screens[i].screen))
876                 break;
877             if (i < si->nscreens)
878               {
879                 if (!pointer_moved_p (&si->screens[i], False))
880                   continue;
881               }
882           }
883
884
885         /* We got a user event.
886            If we're waiting for the user to become active, this is it.
887            If we're waiting until the user becomes idle, reset the timers
888            (since now we have longer to wait.)
889          */
890         if (!until_idle_p)
891           {
892             if (si->demoing_p &&
893                 (event.xany.type == MotionNotify ||
894                  event.xany.type == KeyRelease))
895               /* When we're demoing a single hack, mouse motion doesn't
896                  cause deactivation.  Only clicks and keypresses do. */
897               ;
898             else
899               /* If we're not demoing, then any activity causes deactivation.
900                */
901               goto DONE;
902           }
903         else
904           reset_timers (si);
905
906         break;
907
908       default:
909
910 #ifdef HAVE_MIT_SAVER_EXTENSION
911         if (event.type == si->mit_saver_ext_event_number)
912           {
913             /* This event's number is that of the MIT-SCREEN-SAVER server
914                extension.  This extension has one event number, and the event
915                itself contains sub-codes that say what kind of event it was
916                (an "idle" or "not-idle" event.)
917              */
918             XScreenSaverNotifyEvent *sevent =
919               (XScreenSaverNotifyEvent *) &event;
920             if (sevent->state == ScreenSaverOn)
921               {
922                 int i = 0;
923                 if (p->verbose_p)
924                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
925                            blurb());
926
927                 /* Get the "real" server window(s) out of the way as soon
928                    as possible. */
929                 for (i = 0; i < si->nscreens; i++)
930                   {
931                     saver_screen_info *ssi = &si->screens[i];
932                     if (ssi->server_mit_saver_window &&
933                         window_exists_p (si->dpy,
934                                          ssi->server_mit_saver_window))
935                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
936                   }
937
938                 if (sevent->kind != ScreenSaverExternal)
939                   {
940                     fprintf (stderr,
941                          "%s: ScreenSaverOn event wasn't of type External!\n",
942                              blurb());
943                   }
944
945                 if (until_idle_p)
946                   goto DONE;
947               }
948             else if (sevent->state == ScreenSaverOff)
949               {
950                 if (p->verbose_p)
951                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
952                            blurb());
953                 if (!until_idle_p)
954                   goto DONE;
955               }
956             else
957               fprintf (stderr,
958                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
959                        blurb(), sevent->state);
960           }
961         else
962
963 #endif /* HAVE_MIT_SAVER_EXTENSION */
964
965
966 #ifdef HAVE_SGI_SAVER_EXTENSION
967         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
968           {
969             /* The SGI SCREEN_SAVER server extension has two event numbers,
970                and this event matches the "idle" event. */
971             if (p->verbose_p)
972               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
973                        blurb());
974
975             if (until_idle_p)
976               goto DONE;
977           }
978         else if (event.type == (si->sgi_saver_ext_event_number +
979                                 ScreenSaverEnd))
980           {
981             /* The SGI SCREEN_SAVER server extension has two event numbers,
982                and this event matches the "idle" event. */
983             if (p->verbose_p)
984               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
985                        blurb());
986             if (!until_idle_p)
987               goto DONE;
988           }
989         else
990 #endif /* HAVE_SGI_SAVER_EXTENSION */
991
992 #ifdef HAVE_RANDR
993         if (event.type == (si->randr_event_number + RRScreenChangeNotify))
994           {
995             /* The Resize and Rotate extension sends an event when the
996                size, rotation, or refresh rate of the screen has changed. */
997
998             XRRScreenChangeNotifyEvent *xrr_event =
999               (XRRScreenChangeNotifyEvent *) &event;
1000             /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
1001             int screen = XRRRootToScreen (si->dpy, xrr_event->window);
1002
1003             if (p->verbose_p)
1004               {
1005                 if (si->screens[screen].width  == xrr_event->width &&
1006                     si->screens[screen].height == xrr_event->height)
1007                   fprintf (stderr,
1008                           "%s: %d: no-op screen size change event (%dx%d)\n",
1009                            blurb(), screen,
1010                            xrr_event->width, xrr_event->height);
1011                 else
1012                   fprintf (stderr,
1013                        "%s: %d: screen size changed from %dx%d to %dx%d\n",
1014                            blurb(), screen,
1015                            si->screens[screen].width,
1016                            si->screens[screen].height,
1017                            xrr_event->width, xrr_event->height);
1018               }
1019
1020 # ifdef RRScreenChangeNotifyMask
1021             /* Inform Xlib that it's ok to update its data structures. */
1022             XRRUpdateConfiguration (&event); /* Xrandr.h 1.9, 2002/09/29 */
1023 # endif /* RRScreenChangeNotifyMask */
1024
1025             /* Resize the existing xscreensaver windows and cached ssi data. */
1026             resize_screensaver_window (si);
1027           }
1028         else
1029 #endif /* HAVE_RANDR */
1030
1031           /* Just some random event.  Let the Widgets handle it, if desired. */
1032           dispatch_event (si, &event);
1033       }
1034     }
1035  DONE:
1036
1037
1038   /* If there's a user event on the queue, swallow it.
1039      If we're using a server extension, and the user becomes active, we
1040      get the extension event before the user event -- so the keypress or
1041      motion or whatever is still on the queue.  This makes "unfade" not
1042      work, because it sees that event, and bugs out.  (This problem
1043      doesn't exhibit itself without an extension, because in that case,
1044      there's only one event generated by user activity, not two.)
1045    */
1046   if (!until_idle_p && si->locked_p)
1047     swallow_unlock_typeahead_events (si, &event);
1048   else
1049     while (XCheckMaskEvent (si->dpy,
1050                             (KeyPressMask|ButtonPressMask|PointerMotionMask),
1051                      &event))
1052       ;
1053
1054
1055   if (si->check_pointer_timer_id)
1056     {
1057       XtRemoveTimeOut (si->check_pointer_timer_id);
1058       si->check_pointer_timer_id = 0;
1059     }
1060   if (si->timer_id)
1061     {
1062       XtRemoveTimeOut (si->timer_id);
1063       si->timer_id = 0;
1064     }
1065
1066   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
1067     abort ();
1068
1069   return;
1070 }
1071
1072
1073 \f
1074 /* Some crap for dealing with /proc/interrupts.
1075
1076    On Linux systems, it's possible to see the hardware interrupt count
1077    associated with the keyboard.  We can therefore use that as another method
1078    of detecting idleness.
1079
1080    Why is it a good idea to do this?  Because it lets us detect keyboard
1081    activity that is not associated with X events.  For example, if the user
1082    has switched to another virtual console, it's good for xscreensaver to not
1083    be running graphics hacks on the (non-visible) X display.  The common
1084    complaint that checking /proc/interrupts addresses is that the user is
1085    playing Quake on a non-X console, and the GL hacks are perceptibly slowing
1086    the game...
1087
1088    This is tricky for a number of reasons.
1089
1090      * First, we must be sure to only do this when running on an X server that
1091        is on the local machine (because otherwise, we'd be reacting to the
1092        wrong keyboard.)  The way we do this is by noting that the $DISPLAY is
1093        pointing to display 0 on the local machine.  It *could* be that display
1094        1 is also on the local machine (e.g., two X servers, each on a different
1095        virtual-terminal) but it's also possible that screen 1 is an X terminal,
1096        using this machine as the host.  So we can't take that chance.
1097
1098      * Second, one can only access these interrupt numbers in a completely
1099        and utterly brain-damaged way.  You would think that one would use an
1100        ioctl for this.  But no.  The ONLY way to get this information is to
1101        open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
1102        out of it TEXTUALLY.  Because this is Unix, and all the world's a file,
1103        and the only real data type is the short-line sequence of ASCII bytes.
1104
1105        Now it's all well and good that the /proc/interrupts pseudo-file
1106        exists; that's a clever idea, and a useful API for things that are
1107        already textually oriented, like shell scripts, and users doing
1108        interactive debugging sessions.  But to make a *C PROGRAM* open a file
1109        and parse the textual representation of integers out of it is just
1110        insane.
1111
1112      * Third, you can't just hold the file open, and fseek() back to the
1113        beginning to get updated data!  If you do that, the data never changes.
1114        And I don't want to call open() every five seconds, because I don't want
1115        to risk going to disk for any inodes.  It turns out that if you dup()
1116        it early, then each copy gets fresh data, so we can get around that in
1117        this way (but for how many releases, one might wonder?)
1118
1119      * Fourth, the format of the output of the /proc/interrupts file is
1120        undocumented, and has changed several times already!  In Linux 2.0.33,
1121        even on a multiprocessor machine, it looks like this:
1122
1123           0:  309453991   timer
1124           1:    4771729   keyboard
1125    
1126        but in Linux 2.2 and 2.4 kernels with MP machines, it looks like this:
1127
1128                    CPU0       CPU1
1129           0:    1671450    1672618    IO-APIC-edge  timer
1130           1:      13037      13495    IO-APIC-edge  keyboard
1131
1132        and in Linux 2.6, it's gotten even goofier: now there are two lines
1133        labelled "i8042".  One of them is the keyboard, and one of them is
1134        the PS/2 mouse -- and of course, you can't tell them apart, except
1135        by wiggling the mouse and noting which one changes:
1136
1137                    CPU0       CPU1
1138           1:      32051      30864    IO-APIC-edge  i8042
1139          12:     476577     479913    IO-APIC-edge  i8042
1140
1141        Joy!  So how are we expected to parse that?  Well, this code doesn't
1142        parse it: it saves the first line with the string "keyboard" (or
1143        "i8042") in it, and does a string-comparison to note when it has
1144        changed.  If there are two "i8042" lines, we assume the first is
1145        the keyboard and the second is the mouse (doesn't matter which is
1146        which, really, as long as we don't compare them against each other.)
1147
1148    Thanks to Nat Friedman <nat@nat.org> for figuring out most of this crap.
1149
1150    Note that if you have a serial or USB mouse, or a USB keyboard, it won't
1151    detect it.  That's because there's no way to tell the difference between a
1152    serial mouse and a general serial port, and all USB devices look the same
1153    from here.  It would be somewhat unfortunate to have the screensaver turn
1154    off when the modem on COM1 burped, or when a USB disk was accessed.
1155  */
1156
1157
1158 #ifdef HAVE_PROC_INTERRUPTS
1159
1160 #define PROC_INTERRUPTS "/proc/interrupts"
1161
1162 Bool
1163 query_proc_interrupts_available (saver_info *si, const char **why)
1164 {
1165   /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
1166      "/proc/interrupts" file exists and is readable.
1167    */
1168   FILE *f;
1169   if (why) *why = 0;
1170
1171   if (!display_is_on_console_p (si))
1172     {
1173       if (why) *why = "not on primary console";
1174       return False;
1175     }
1176
1177   f = fopen (PROC_INTERRUPTS, "r");
1178   if (!f)
1179     return False;
1180
1181   fclose (f);
1182   return True;
1183 }
1184
1185
1186 static Bool
1187 proc_interrupts_activity_p (saver_info *si)
1188 {
1189   static FILE *f0 = 0;
1190   FILE *f1 = 0;
1191   int fd;
1192   static char last_kbd_line[255] = { 0, };
1193   static char last_ptr_line[255] = { 0, };
1194   char new_line[sizeof(last_kbd_line)];
1195   Bool checked_kbd = False, kbd_changed = False;
1196   Bool checked_ptr = False, ptr_changed = False;
1197   int i8042_count = 0;
1198
1199   if (!f0)
1200     {
1201       /* First time -- open the file. */
1202       f0 = fopen (PROC_INTERRUPTS, "r");
1203       if (!f0)
1204         {
1205           char buf[255];
1206           sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
1207           perror (buf);
1208           goto FAIL;
1209         }
1210     }
1211
1212   if (f0 == (FILE *) -1)            /* means we got an error initializing. */
1213     return False;
1214
1215   fd = dup (fileno (f0));
1216   if (fd < 0)
1217     {
1218       char buf[255];
1219       sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
1220       perror (buf);
1221       goto FAIL;
1222     }
1223
1224   f1 = fdopen (fd, "r");
1225   if (!f1)
1226     {
1227       char buf[255];
1228       sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
1229               PROC_INTERRUPTS);
1230       perror (buf);
1231       goto FAIL;
1232     }
1233
1234   /* Actually, I'm unclear on why this fseek() is necessary, given the timing
1235      of the dup() above, but it is. */
1236   if (fseek (f1, 0, SEEK_SET) != 0)
1237     {
1238       char buf[255];
1239       sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
1240       perror (buf);
1241       goto FAIL;
1242     }
1243
1244   /* Now read through the pseudo-file until we find the "keyboard",
1245      "PS/2 mouse", or "i8042" lines. */
1246
1247   while (fgets (new_line, sizeof(new_line)-1, f1))
1248     {
1249       Bool i8042_p = !!strstr (new_line, "i8042");
1250       if (i8042_p) i8042_count++;
1251
1252       if (strchr (new_line, ','))
1253         {
1254           /* Ignore any line that has a comma on it: this is because
1255              a setup like this:
1256
1257                  12:     930935          XT-PIC  usb-uhci, PS/2 Mouse
1258
1259              is really bad news.  It *looks* like we can note mouse
1260              activity from that line, but really, that interrupt gets
1261              fired any time any USB device has activity!  So we have
1262              to ignore any shared IRQs.
1263            */
1264         }
1265       else if (!checked_kbd &&
1266                (strstr (new_line, "keyboard") ||
1267                 (i8042_p && i8042_count == 1)))
1268         {
1269           /* Assume the keyboard interrupt is the line that says "keyboard",
1270              or the *first* line that says "i8042".
1271            */
1272           kbd_changed = (*last_kbd_line && !!strcmp (new_line, last_kbd_line));
1273           strcpy (last_kbd_line, new_line);
1274           checked_kbd = True;
1275         }
1276       else if (!checked_ptr &&
1277                (strstr (new_line, "PS/2 Mouse") ||
1278                 (i8042_p && i8042_count == 2)))
1279         {
1280           /* Assume the mouse interrupt is the line that says "PS/2 mouse",
1281              or the *second* line that says "i8042".
1282            */
1283           ptr_changed = (*last_ptr_line && !!strcmp (new_line, last_ptr_line));
1284           strcpy (last_ptr_line, new_line);
1285           checked_ptr = True;
1286         }
1287
1288       if (checked_kbd && checked_ptr)
1289         break;
1290     }
1291
1292   if (checked_kbd || checked_ptr)
1293     {
1294       fclose (f1);
1295
1296       if (si->prefs.debug_p && (kbd_changed || ptr_changed))
1297         fprintf (stderr, "%s: /proc/interrupts activity: %s\n",
1298                  blurb(),
1299                  ((kbd_changed && ptr_changed) ? "mouse and kbd" :
1300                   kbd_changed ? "kbd" :
1301                   ptr_changed ? "mouse" : "ERR"));
1302
1303       return (kbd_changed || ptr_changed);
1304     }
1305
1306
1307   /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse"
1308      line in the file at all. */
1309   fprintf (stderr, "%s: no keyboard or mouse data in %s?\n",
1310            blurb(), PROC_INTERRUPTS);
1311
1312  FAIL:
1313   if (f1)
1314     fclose (f1);
1315
1316   if (f0 && f0 != (FILE *) -1)
1317     fclose (f0);
1318
1319   f0 = (FILE *) -1;
1320   return False;
1321 }
1322
1323 #endif /* HAVE_PROC_INTERRUPTS */
1324
1325 \f
1326 /* This timer goes off every few minutes, whether the user is idle or not,
1327    to try and clean up anything that has gone wrong.
1328
1329    It calls disable_builtin_screensaver() so that if xset has been used,
1330    or some other program (like xlock) has messed with the XSetScreenSaver()
1331    settings, they will be set back to sensible values (if a server extension
1332    is in use, messing with xlock can cause xscreensaver to never get a wakeup
1333    event, and could cause monitor power-saving to occur, and all manner of
1334    heinousness.)
1335
1336    If the screen is currently blanked, it raises the window, in case some
1337    other window has been mapped on top of it.
1338
1339    If the screen is currently blanked, and there is no hack running, it
1340    clears the window, in case there is an error message printed on it (we
1341    don't want the error message to burn in.)
1342  */
1343
1344 static void
1345 watchdog_timer (XtPointer closure, XtIntervalId *id)
1346 {
1347   saver_info *si = (saver_info *) closure;
1348   saver_preferences *p = &si->prefs;
1349
1350   disable_builtin_screensaver (si, False);
1351
1352   /* If the DPMS settings on the server have changed, change them back to
1353      what ~/.xscreensaver says they should be. */
1354   sync_server_dpms_settings (si->dpy,
1355                              (p->dpms_enabled_p  &&
1356                               p->mode != DONT_BLANK),
1357                              p->dpms_standby / 1000,
1358                              p->dpms_suspend / 1000,
1359                              p->dpms_off / 1000,
1360                              False);
1361
1362   if (si->screen_blanked_p)
1363     {
1364       Bool running_p = screenhack_running_p (si);
1365
1366       if (si->dbox_up_p)
1367         {
1368           if (si->prefs.debug_p)
1369             fprintf (stderr, "%s: dialog box is up: not raising screen.\n",
1370                      blurb());
1371         }
1372       else
1373         {
1374           if (si->prefs.debug_p)
1375             fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
1376                      blurb(), (running_p ? "" : "and clearing "));
1377
1378           raise_window (si, True, True, running_p);
1379         }
1380
1381       if (screenhack_running_p (si) &&
1382           !monitor_powered_on_p (si))
1383         {
1384           if (si->prefs.verbose_p)
1385             fprintf (stderr,
1386                      "%s: X says monitor has powered down; "
1387                      "killing running hacks.\n", blurb());
1388           kill_screenhack (si);
1389         }
1390
1391       /* Re-schedule this timer.  The watchdog timer defaults to a bit less
1392          than the hack cycle period, but is never longer than one hour.
1393        */
1394       si->watchdog_id = 0;
1395       reset_watchdog_timer (si, True);
1396     }
1397 }
1398
1399
1400 void
1401 reset_watchdog_timer (saver_info *si, Bool on_p)
1402 {
1403   saver_preferences *p = &si->prefs;
1404
1405   if (si->watchdog_id)
1406     {
1407       XtRemoveTimeOut (si->watchdog_id);
1408       si->watchdog_id = 0;
1409     }
1410
1411   if (on_p && p->watchdog_timeout)
1412     {
1413       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
1414                                          watchdog_timer, (XtPointer) si);
1415
1416       if (p->debug_p)
1417         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
1418                  blurb(), p->watchdog_timeout, si->watchdog_id);
1419     }
1420 }
1421
1422
1423 /* It's possible that a race condition could have led to the saver
1424    window being unexpectedly still mapped.  This can happen like so:
1425
1426     - screen is blanked
1427     - hack is launched
1428     - that hack tries to grab a screen image (it does this by
1429       first unmapping the saver window, then remapping it.)
1430     - hack unmaps window
1431     - hack waits
1432     - user becomes active
1433     - hack re-maps window (*)
1434     - driver kills subprocess
1435     - driver unmaps window (**)
1436
1437    The race is that (*) might have been sent to the server before
1438    the client process was killed, but, due to scheduling randomness,
1439    might not have been received by the server until after (**).
1440    In other words, (*) and (**) might happen out of order, meaning
1441    the driver will unmap the window, and then after that, the
1442    recently-dead client will re-map it.  This leaves the user
1443    locked out (it looks like a desktop, but it's not!)
1444
1445    To avoid this: after un-blanking the screen, we launch a timer
1446    that wakes up once a second for ten seconds, and makes damned
1447    sure that the window is still unmapped.
1448  */
1449
1450 void
1451 de_race_timer (XtPointer closure, XtIntervalId *id)
1452 {
1453   saver_info *si = (saver_info *) closure;
1454   saver_preferences *p = &si->prefs;
1455   int secs = 1;
1456
1457   if (id == 0)  /* if id is 0, this is the initialization call. */
1458     {
1459       si->de_race_ticks = 10;
1460       if (p->verbose_p)
1461         fprintf (stderr, "%s: starting de-race timer (%d seconds.)\n",
1462                  blurb(), si->de_race_ticks);
1463     }
1464   else
1465     {
1466       int i;
1467       XSync (si->dpy, False);
1468       for (i = 0; i < si->nscreens; i++)
1469         {
1470           saver_screen_info *ssi = &si->screens[i];
1471           Window w = ssi->screensaver_window;
1472           XWindowAttributes xgwa;
1473           XGetWindowAttributes (si->dpy, w, &xgwa);
1474           if (xgwa.map_state != IsUnmapped)
1475             {
1476               if (p->verbose_p)
1477                 fprintf (stderr,
1478                          "%s: %d: client race! emergency unmap 0x%lx.\n",
1479                          blurb(), i, (unsigned long) w);
1480               XUnmapWindow (si->dpy, w);
1481             }
1482           else if (p->debug_p)
1483             fprintf (stderr, "%s: %d: (de-race of 0x%lx is cool.)\n",
1484                      blurb(), i, (unsigned long) w);
1485         }
1486       XSync (si->dpy, False);
1487
1488       si->de_race_ticks--;
1489     }
1490
1491   if (id && *id == si->de_race_id)
1492     si->de_race_id = 0;
1493
1494   if (si->de_race_id) abort();
1495
1496   if (si->de_race_ticks <= 0)
1497     {
1498       si->de_race_id = 0;
1499       if (p->verbose_p)
1500         fprintf (stderr, "%s: de-race completed.\n", blurb());
1501     }
1502   else
1503     {
1504       si->de_race_id = XtAppAddTimeOut (si->app, secs * 1000,
1505                                         de_race_timer, closure);
1506     }
1507 }