c9244f735e7655b29c8a968a7175d7885e364b78
[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 (ssi->poll_mouse_last_root_x == -1 ||
390       ssi->poll_mouse_last_root_y == -1 ||
391       root_x == -1 ||
392       root_y == -1)
393     moved_p = True;
394
395   if (p->debug_p && (distance != 0 || moved_p))
396     {
397       fprintf (stderr, "%s: %d: pointer %s", blurb(), ssi->number,
398                (moved_p ? "moved:  " : "ignored:"));
399       if (ssi->poll_mouse_last_root_x == -1)
400         fprintf (stderr, "off screen");
401       else
402         fprintf (stderr, "%d,%d",
403                  ssi->poll_mouse_last_root_x,
404                  ssi->poll_mouse_last_root_y);
405       fprintf (stderr, " -> ");
406       if (root_x == -1)
407         fprintf (stderr, "off screen.");
408       else
409         fprintf (stderr, "%d,%d", root_x, root_y);
410       if (ssi->poll_mouse_last_root_x != -1 && root_x != -1)
411         fprintf (stderr, " (%d,%d; %d/%lu=%d)",
412                  ABS(ssi->poll_mouse_last_root_x - root_x),
413                  ABS(ssi->poll_mouse_last_root_y - root_y),
414                  distance, seconds, dps);
415
416       fprintf (stderr, ".\n");
417     }
418
419   if (!moved_p &&
420       mods_p &&
421       mask != ssi->poll_mouse_last_mask)
422     {
423       moved_p = True;
424
425       if (p->debug_p)
426         fprintf (stderr, "%s: %d: modifiers changed: 0x%04x -> 0x%04x.\n",
427                  blurb(), ssi->number, ssi->poll_mouse_last_mask, mask);
428     }
429
430   si->last_activity_screen   = ssi;
431   ssi->poll_mouse_last_child = child;
432   ssi->poll_mouse_last_mask  = mask;
433
434   if (moved_p || seconds > 0)
435     {
436       ssi->poll_mouse_last_time   = now;
437       ssi->poll_mouse_last_root_x = root_x;
438       ssi->poll_mouse_last_root_y = root_y;
439     }
440
441   return moved_p;
442 }
443
444
445 /* When we aren't using a server extension, this timer is used to periodically
446    wake up and poll the mouse position, which is possibly more reliable than
447    selecting motion events on every window.
448  */
449 static void
450 check_pointer_timer (XtPointer closure, XtIntervalId *id)
451 {
452   int i;
453   saver_info *si = (saver_info *) closure;
454   saver_preferences *p = &si->prefs;
455   Bool active_p = False;
456
457   if (!si->using_proc_interrupts &&
458       (si->using_xidle_extension ||
459        si->using_mit_saver_extension ||
460        si->using_sgi_saver_extension))
461     /* If an extension is in use, we should not be polling the mouse.
462        Unless we're also checking /proc/interrupts, in which case, we should.
463      */
464     abort ();
465
466   if (id && *id == si->check_pointer_timer_id)  /* this is us - it's expired */
467     si->check_pointer_timer_id = 0;
468
469   if (si->check_pointer_timer_id)               /* only queue one at a time */
470     XtRemoveTimeOut (si->check_pointer_timer_id);
471
472   si->check_pointer_timer_id =                  /* now re-queue */
473     XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
474                      (XtPointer) si);
475
476   for (i = 0; i < si->nscreens; i++)
477     {
478       saver_screen_info *ssi = &si->screens[i];
479       if (pointer_moved_p (ssi, True))
480         active_p = True;
481     }
482
483 #ifdef HAVE_PROC_INTERRUPTS
484   if (!active_p &&
485       si->using_proc_interrupts &&
486       proc_interrupts_activity_p (si))
487     {
488       active_p = True;
489     }
490 #endif /* HAVE_PROC_INTERRUPTS */
491
492   if (active_p)
493     reset_timers (si);
494
495   check_for_clock_skew (si);
496 }
497
498
499 /* An unfortunate situation is this: the saver is not active, because the
500    user has been typing.  The machine is a laptop.  The user closes the lid
501    and suspends it.  The CPU halts.  Some hours later, the user opens the
502    lid.  At this point, Xt's timers will fire, and xscreensaver will blank
503    the screen.
504
505    So far so good -- well, not really, but it's the best that we can do,
506    since the OS doesn't send us a signal *before* shutdown -- but if the
507    user had delayed locking (lockTimeout > 0) then we should start off
508    in the locked state, rather than only locking N minutes from when the
509    lid was opened.  Also, eschewing fading is probably a good idea, to
510    clamp down as soon as possible.
511
512    We only do this when we'd be polling the mouse position anyway.
513    This amounts to an assumption that machines with APM support also
514    have /proc/interrupts.
515  */
516 static void
517 check_for_clock_skew (saver_info *si)
518 {
519   saver_preferences *p = &si->prefs;
520   time_t now = time ((time_t *) 0);
521   long shift = now - si->last_wall_clock_time;
522
523   if (p->debug_p)
524     {
525       int i = (si->last_wall_clock_time == 0 ? 0 : shift);
526       fprintf (stderr,
527                "%s: checking wall clock for hibernation (%d:%02d:%02d).\n",
528                blurb(),
529                (i / (60 * 60)), ((i / 60) % 60), (i % 60));
530     }
531
532   if (si->last_wall_clock_time != 0 &&
533       shift > (p->timeout / 1000))
534     {
535       if (p->verbose_p)
536         fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld!\n",
537                  blurb(),
538                  (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60));
539
540       si->emergency_lock_p = True;
541       idle_timer ((XtPointer) si, 0);
542     }
543
544   si->last_wall_clock_time = now;
545 }
546
547
548
549 static void
550 dispatch_event (saver_info *si, XEvent *event)
551 {
552   /* If this is for the splash dialog, pass it along.
553      Note that the password dialog is handled with its own event loop,
554      so events for that window will never come through here.
555    */
556   if (si->splash_dialog && event->xany.window == si->splash_dialog)
557     handle_splash_event (si, event);
558
559   XtDispatchEvent (event);
560 }
561
562
563 static void
564 swallow_unlock_typeahead_events (saver_info *si, XEvent *e)
565 {
566   XEvent event;
567   char buf [100];
568   int i = 0;
569
570   memset (buf, 0, sizeof(buf));
571
572   event = *e;
573
574   do
575     {
576       if (event.xany.type == KeyPress)
577         {
578           char s[2];
579           int size = XLookupString ((XKeyEvent *) &event, s, 1, 0, 0);
580           if (size != 1) continue;
581           switch (*s)
582             {
583             case '\010': case '\177':                   /* Backspace */
584               if (i > 0) i--;
585               break;
586             case '\025': case '\030':                   /* Erase line */
587             case '\012': case '\015':                   /* Enter */
588               i = 0;
589               break;
590             case '\040':                                /* Space */
591               if (i == 0)
592                 break;  /* ignore space at beginning of line */
593               /* else, fall through */
594             default:
595               buf [i++] = *s;
596               break;
597             }
598         }
599
600     } while (i < sizeof(buf)-1 &&
601              XCheckMaskEvent (si->dpy, KeyPressMask, &event));
602
603   buf[i] = 0;
604
605   if (si->unlock_typeahead)
606     {
607       memset (si->unlock_typeahead, 0, strlen(si->unlock_typeahead));
608       free (si->unlock_typeahead);
609     }
610
611   if (i > 0)
612     si->unlock_typeahead = strdup (buf);
613   else
614     si->unlock_typeahead = 0;
615
616   memset (buf, 0, sizeof(buf));
617 }
618
619
620 /* methods of detecting idleness:
621
622       explicitly informed by SGI SCREEN_SAVER server event;
623       explicitly informed by MIT-SCREEN-SAVER server event;
624       poll server idle time with XIDLE extension;
625       select events on all windows, and note absence of recent events;
626       note that /proc/interrupts has not changed in a while;
627       activated by clientmessage.
628
629    methods of detecting non-idleness:
630
631       read events on the xscreensaver window;
632       explicitly informed by SGI SCREEN_SAVER server event;
633       explicitly informed by MIT-SCREEN-SAVER server event;
634       select events on all windows, and note events on any of them;
635       note that /proc/interrupts has changed;
636       deactivated by clientmessage.
637
638    I trust that explains why this function is a big hairy mess.
639  */
640 void
641 sleep_until_idle (saver_info *si, Bool until_idle_p)
642 {
643   saver_preferences *p = &si->prefs;
644   XEvent event;
645
646   /* We need to select events on all windows if we're not using any extensions.
647      Otherwise, we don't need to. */
648   Bool scanning_all_windows = !(si->using_xidle_extension ||
649                                 si->using_mit_saver_extension ||
650                                 si->using_sgi_saver_extension);
651
652   /* We need to periodically wake up and check for idleness if we're not using
653      any extensions, or if we're using the XIDLE extension.  The other two
654      extensions explicitly deliver events when we go idle/non-idle, so we
655      don't need to poll. */
656   Bool polling_for_idleness = !(si->using_mit_saver_extension ||
657                                 si->using_sgi_saver_extension);
658
659   /* Whether we need to periodically wake up and check to see if the mouse has
660      moved.  We only need to do this when not using any extensions.  The reason
661      this isn't the same as `polling_for_idleness' is that the "idleness" poll
662      can happen (for example) 5 minutes from now, whereas the mouse-position
663      poll should happen with low periodicity.  We don't need to poll the mouse
664      position with the XIDLE extension, but we do need to periodically wake up
665      and query the server with that extension.  For our purposes, polling
666      /proc/interrupts is just like polling the mouse position.  It has to
667      happen on the same kind of schedule. */
668   Bool polling_mouse_position = (si->using_proc_interrupts ||
669                                  !(si->using_xidle_extension ||
670                                    si->using_mit_saver_extension ||
671                                    si->using_sgi_saver_extension));
672
673   if (until_idle_p)
674     {
675       if (polling_for_idleness)
676         /* This causes a no-op event to be delivered to us in a while, so that
677            we come back around through the event loop again.  */
678         schedule_wakeup_event (si, p->timeout, p->debug_p);
679
680       if (polling_mouse_position)
681         /* Check to see if the mouse has moved, and set up a repeating timer
682            to do so periodically (typically, every 5 seconds.) */
683         check_pointer_timer ((XtPointer) si, 0);
684     }
685
686   while (1)
687     {
688       XtAppNextEvent (si->app, &event);
689
690       switch (event.xany.type) {
691       case 0:           /* our synthetic "timeout" event has been signalled */
692         if (until_idle_p)
693           {
694             Time idle;
695
696             /* We may be idle; check one last time to see if the mouse has
697                moved, just in case the idle-timer went off within the 5 second
698                window between mouse polling.  If the mouse has moved, then
699                check_pointer_timer() will reset last_activity_time.
700              */
701             if (polling_mouse_position)
702               check_pointer_timer ((XtPointer) si, 0);
703
704 #ifdef HAVE_XIDLE_EXTENSION
705             if (si->using_xidle_extension)
706               {
707                 /* The XIDLE extension uses the synthetic event to prod us into
708                    re-asking the server how long the user has been idle. */
709                 if (! XGetIdleTime (si->dpy, &idle))
710                   {
711                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
712                     saver_exit (si, 1, 0);
713                   }
714               }
715             else
716 #endif /* HAVE_XIDLE_EXTENSION */
717 #ifdef HAVE_MIT_SAVER_EXTENSION
718               if (si->using_mit_saver_extension)
719                 {
720                   /* We don't need to do anything in this case - the synthetic
721                      event isn't necessary, as we get sent specific events
722                      to wake us up.  In fact, this event generally shouldn't
723                      be being delivered when the MIT extension is in use. */
724                   idle = 0;
725                 }
726             else
727 #endif /* HAVE_MIT_SAVER_EXTENSION */
728 #ifdef HAVE_SGI_SAVER_EXTENSION
729               if (si->using_sgi_saver_extension)
730                 {
731                   /* We don't need to do anything in this case - the synthetic
732                      event isn't necessary, as we get sent specific events
733                      to wake us up.  In fact, this event generally shouldn't
734                      be being delivered when the SGI extension is in use. */
735                   idle = 0;
736                 }
737             else
738 #endif /* HAVE_SGI_SAVER_EXTENSION */
739               {
740                 /* Otherwise, no server extension is in use.  The synthetic
741                    event was to tell us to wake up and see if the user is now
742                    idle.  Compute the amount of idle time by comparing the
743                    `last_activity_time' to the wall clock.  The l_a_t was set
744                    by calling `reset_timers()', which is called only in only
745                    two situations: when polling the mouse position has revealed
746                    the the mouse has moved (user activity) or when we have read
747                    an event (again, user activity.)
748                  */
749                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
750               }
751
752             if (idle >= p->timeout)
753               {
754                 /* Look, we've been idle long enough.  We're done. */
755                 goto DONE;
756               }
757             else if (si->emergency_lock_p)
758               {
759                 /* Oops, the wall clock has jumped far into the future, so
760                    we need to lock down in a hurry! */
761                 goto DONE;
762               }
763             else
764               {
765                 /* The event went off, but it turns out that the user has not
766                    yet been idle for long enough.  So re-signal the event.
767                    Be economical: if we should blank after 5 minutes, and the
768                    user has been idle for 2 minutes, then set this timer to
769                    go off in 3 minutes.
770                  */
771                 if (polling_for_idleness)
772                   schedule_wakeup_event (si, p->timeout - idle, p->debug_p);
773               }
774           }
775         break;
776
777       case ClientMessage:
778         if (handle_clientmessage (si, &event, until_idle_p))
779           goto DONE;
780         break;
781
782       case CreateNotify:
783         /* A window has been created on the screen somewhere.  If we're
784            supposed to scan all windows for events, prepare this window. */
785         if (scanning_all_windows)
786           {
787             Window w = event.xcreatewindow.window;
788             start_notice_events_timer (si, w, p->debug_p);
789           }
790         break;
791
792       case KeyPress:
793       case KeyRelease:
794       case ButtonPress:
795       case ButtonRelease:
796       case MotionNotify:
797
798         if (p->debug_p)
799           {
800             Window root=0, window=0;
801             int x=-1, y=-1;
802             const char *type = 0;
803             if (event.xany.type == MotionNotify)
804               {
805                 /*type = "MotionNotify";*/
806                 root = event.xmotion.root;
807                 window = event.xmotion.window;
808                 x = event.xmotion.x_root;
809                 y = event.xmotion.y_root;
810               }
811             else if (event.xany.type == KeyPress)
812               {
813                 type = "KeyPress";
814                 root = event.xkey.root;
815                 window = event.xkey.window;
816                 x = y = -1;
817               }
818             else if (event.xany.type == ButtonPress)
819               {
820                 type = "ButtonPress";
821                 root = event.xkey.root;
822                 window = event.xkey.window;
823                 x = event.xmotion.x_root;
824                 y = event.xmotion.y_root;
825               }
826
827             if (type)
828               {
829                 int i;
830                 for (i = 0; i < si->nscreens; i++)
831                   if (root == RootWindowOfScreen (si->screens[i].screen))
832                     break;
833                 fprintf (stderr,"%s: %d: %s on 0x%lx",
834                          blurb(), i, type, (unsigned long) window);
835
836                 /* Be careful never to do this unless in -debug mode, as
837                    this could expose characters from the unlock password. */
838                 if (p->debug_p && event.xany.type == KeyPress)
839                   {
840                     KeySym keysym;
841                     char c = 0;
842                     XLookupString (&event.xkey, &c, 1, &keysym, 0);
843                     fprintf (stderr, " (%s%s)",
844                              (event.xkey.send_event ? "synthetic " : ""),
845                              XKeysymToString (keysym));
846                   }
847
848                 if (x == -1)
849                   fprintf (stderr, "\n");
850                 else
851                   fprintf (stderr, " at %d,%d.\n", x, y);
852               }
853           }
854
855         /* If any widgets want to handle this event, let them. */
856         dispatch_event (si, &event);
857
858         
859         /* If we got a MotionNotify event, figure out what screen it
860            was on and poll the mouse there: if the mouse hasn't moved
861            far enough to count as "real" motion, then ignore this
862            event.
863          */
864         if (event.xany.type == MotionNotify)
865           {
866             int i;
867             for (i = 0; i < si->nscreens; i++)
868               if (event.xmotion.root ==
869                   RootWindowOfScreen (si->screens[i].screen))
870                 break;
871             if (i < si->nscreens)
872               {
873                 if (!pointer_moved_p (&si->screens[i], False))
874                   continue;
875               }
876           }
877
878
879         /* We got a user event.
880            If we're waiting for the user to become active, this is it.
881            If we're waiting until the user becomes idle, reset the timers
882            (since now we have longer to wait.)
883          */
884         if (!until_idle_p)
885           {
886             if (si->demoing_p &&
887                 (event.xany.type == MotionNotify ||
888                  event.xany.type == KeyRelease))
889               /* When we're demoing a single hack, mouse motion doesn't
890                  cause deactivation.  Only clicks and keypresses do. */
891               ;
892             else
893               /* If we're not demoing, then any activity causes deactivation.
894                */
895               goto DONE;
896           }
897         else
898           reset_timers (si);
899
900         break;
901
902       default:
903
904 #ifdef HAVE_MIT_SAVER_EXTENSION
905         if (event.type == si->mit_saver_ext_event_number)
906           {
907             /* This event's number is that of the MIT-SCREEN-SAVER server
908                extension.  This extension has one event number, and the event
909                itself contains sub-codes that say what kind of event it was
910                (an "idle" or "not-idle" event.)
911              */
912             XScreenSaverNotifyEvent *sevent =
913               (XScreenSaverNotifyEvent *) &event;
914             if (sevent->state == ScreenSaverOn)
915               {
916                 int i = 0;
917                 if (p->verbose_p)
918                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
919                            blurb());
920
921                 /* Get the "real" server window(s) out of the way as soon
922                    as possible. */
923                 for (i = 0; i < si->nscreens; i++)
924                   {
925                     saver_screen_info *ssi = &si->screens[i];
926                     if (ssi->server_mit_saver_window &&
927                         window_exists_p (si->dpy,
928                                          ssi->server_mit_saver_window))
929                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
930                   }
931
932                 if (sevent->kind != ScreenSaverExternal)
933                   {
934                     fprintf (stderr,
935                          "%s: ScreenSaverOn event wasn't of type External!\n",
936                              blurb());
937                   }
938
939                 if (until_idle_p)
940                   goto DONE;
941               }
942             else if (sevent->state == ScreenSaverOff)
943               {
944                 if (p->verbose_p)
945                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
946                            blurb());
947                 if (!until_idle_p)
948                   goto DONE;
949               }
950             else
951               fprintf (stderr,
952                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
953                        blurb(), sevent->state);
954           }
955         else
956
957 #endif /* HAVE_MIT_SAVER_EXTENSION */
958
959
960 #ifdef HAVE_SGI_SAVER_EXTENSION
961         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
962           {
963             /* The SGI SCREEN_SAVER server extension has two event numbers,
964                and this event matches the "idle" event. */
965             if (p->verbose_p)
966               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
967                        blurb());
968
969             if (until_idle_p)
970               goto DONE;
971           }
972         else if (event.type == (si->sgi_saver_ext_event_number +
973                                 ScreenSaverEnd))
974           {
975             /* The SGI SCREEN_SAVER server extension has two event numbers,
976                and this event matches the "idle" event. */
977             if (p->verbose_p)
978               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
979                        blurb());
980             if (!until_idle_p)
981               goto DONE;
982           }
983         else
984 #endif /* HAVE_SGI_SAVER_EXTENSION */
985
986 #ifdef HAVE_RANDR
987         if (event.type == (si->randr_event_number + RRScreenChangeNotify))
988           {
989             /* The Resize and Rotate extension sends an event when the
990                size, rotation, or refresh rate of the screen has changed. */
991
992             XRRScreenChangeNotifyEvent *xrr_event =
993               (XRRScreenChangeNotifyEvent *) &event;
994             /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
995             int screen = XRRRootToScreen (si->dpy, xrr_event->window);
996
997             if (p->verbose_p)
998               {
999                 if (si->screens[screen].width  == xrr_event->width &&
1000                     si->screens[screen].height == xrr_event->height)
1001                   fprintf (stderr,
1002                           "%s: %d: no-op screen size change event (%dx%d)\n",
1003                            blurb(), screen,
1004                            xrr_event->width, xrr_event->height);
1005                 else
1006                   fprintf (stderr,
1007                        "%s: %d: screen size changed from %dx%d to %dx%d\n",
1008                            blurb(), screen,
1009                            si->screens[screen].width,
1010                            si->screens[screen].height,
1011                            xrr_event->width, xrr_event->height);
1012               }
1013
1014 # ifdef RRScreenChangeNotifyMask
1015             /* Inform Xlib that it's ok to update its data structures. */
1016             XRRUpdateConfiguration (&event); /* Xrandr.h 1.9, 2002/09/29 */
1017 # endif /* RRScreenChangeNotifyMask */
1018
1019             /* Resize the existing xscreensaver windows and cached ssi data. */
1020             resize_screensaver_window (si);
1021           }
1022         else
1023 #endif /* HAVE_RANDR */
1024
1025           /* Just some random event.  Let the Widgets handle it, if desired. */
1026           dispatch_event (si, &event);
1027       }
1028     }
1029  DONE:
1030
1031
1032   /* If there's a user event on the queue, swallow it.
1033      If we're using a server extension, and the user becomes active, we
1034      get the extension event before the user event -- so the keypress or
1035      motion or whatever is still on the queue.  This makes "unfade" not
1036      work, because it sees that event, and bugs out.  (This problem
1037      doesn't exhibit itself without an extension, because in that case,
1038      there's only one event generated by user activity, not two.)
1039    */
1040   if (!until_idle_p && si->locked_p)
1041     swallow_unlock_typeahead_events (si, &event);
1042   else
1043     while (XCheckMaskEvent (si->dpy,
1044                             (KeyPressMask|ButtonPressMask|PointerMotionMask),
1045                      &event))
1046       ;
1047
1048
1049   if (si->check_pointer_timer_id)
1050     {
1051       XtRemoveTimeOut (si->check_pointer_timer_id);
1052       si->check_pointer_timer_id = 0;
1053     }
1054   if (si->timer_id)
1055     {
1056       XtRemoveTimeOut (si->timer_id);
1057       si->timer_id = 0;
1058     }
1059
1060   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
1061     abort ();
1062
1063   return;
1064 }
1065
1066
1067 \f
1068 /* Some crap for dealing with /proc/interrupts.
1069
1070    On Linux systems, it's possible to see the hardware interrupt count
1071    associated with the keyboard.  We can therefore use that as another method
1072    of detecting idleness.
1073
1074    Why is it a good idea to do this?  Because it lets us detect keyboard
1075    activity that is not associated with X events.  For example, if the user
1076    has switched to another virtual console, it's good for xscreensaver to not
1077    be running graphics hacks on the (non-visible) X display.  The common
1078    complaint that checking /proc/interrupts addresses is that the user is
1079    playing Quake on a non-X console, and the GL hacks are perceptibly slowing
1080    the game...
1081
1082    This is tricky for a number of reasons.
1083
1084      * First, we must be sure to only do this when running on an X server that
1085        is on the local machine (because otherwise, we'd be reacting to the
1086        wrong keyboard.)  The way we do this is by noting that the $DISPLAY is
1087        pointing to display 0 on the local machine.  It *could* be that display
1088        1 is also on the local machine (e.g., two X servers, each on a different
1089        virtual-terminal) but it's also possible that screen 1 is an X terminal,
1090        using this machine as the host.  So we can't take that chance.
1091
1092      * Second, one can only access these interrupt numbers in a completely
1093        and utterly brain-damaged way.  You would think that one would use an
1094        ioctl for this.  But no.  The ONLY way to get this information is to
1095        open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
1096        out of it TEXTUALLY.  Because this is Unix, and all the world's a file,
1097        and the only real data type is the short-line sequence of ASCII bytes.
1098
1099        Now it's all well and good that the /proc/interrupts pseudo-file
1100        exists; that's a clever idea, and a useful API for things that are
1101        already textually oriented, like shell scripts, and users doing
1102        interactive debugging sessions.  But to make a *C PROGRAM* open a file
1103        and parse the textual representation of integers out of it is just
1104        insane.
1105
1106      * Third, you can't just hold the file open, and fseek() back to the
1107        beginning to get updated data!  If you do that, the data never changes.
1108        And I don't want to call open() every five seconds, because I don't want
1109        to risk going to disk for any inodes.  It turns out that if you dup()
1110        it early, then each copy gets fresh data, so we can get around that in
1111        this way (but for how many releases, one might wonder?)
1112
1113      * Fourth, the format of the output of the /proc/interrupts file is
1114        undocumented, and has changed several times already!  In Linux 2.0.33,
1115        even on a multiprocessor machine, it looks like this:
1116
1117           0:  309453991   timer
1118           1:    4771729   keyboard
1119    
1120        but in Linux 2.2 and 2.4 kernels with MP machines, it looks like this:
1121
1122                    CPU0       CPU1
1123           0:    1671450    1672618    IO-APIC-edge  timer
1124           1:      13037      13495    IO-APIC-edge  keyboard
1125
1126        and in Linux 2.6, it's gotten even goofier: now there are two lines
1127        labelled "i8042".  One of them is the keyboard, and one of them is
1128        the PS/2 mouse -- and of course, you can't tell them apart, except
1129        by wiggling the mouse and noting which one changes:
1130
1131                    CPU0       CPU1
1132           1:      32051      30864    IO-APIC-edge  i8042
1133          12:     476577     479913    IO-APIC-edge  i8042
1134
1135        Joy!  So how are we expected to parse that?  Well, this code doesn't
1136        parse it: it saves the first line with the string "keyboard" (or
1137        "i8042") in it, and does a string-comparison to note when it has
1138        changed.  If there are two "i8042" lines, we assume the first is
1139        the keyboard and the second is the mouse (doesn't matter which is
1140        which, really, as long as we don't compare them against each other.)
1141
1142    Thanks to Nat Friedman <nat@nat.org> for figuring out most of this crap.
1143
1144    Note that if you have a serial or USB mouse, or a USB keyboard, it won't
1145    detect it.  That's because there's no way to tell the difference between a
1146    serial mouse and a general serial port, and all USB devices look the same
1147    from here.  It would be somewhat unfortunate to have the screensaver turn
1148    off when the modem on COM1 burped, or when a USB disk was accessed.
1149  */
1150
1151
1152 #ifdef HAVE_PROC_INTERRUPTS
1153
1154 #define PROC_INTERRUPTS "/proc/interrupts"
1155
1156 Bool
1157 query_proc_interrupts_available (saver_info *si, const char **why)
1158 {
1159   /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
1160      "/proc/interrupts" file exists and is readable.
1161    */
1162   FILE *f;
1163   if (why) *why = 0;
1164
1165   if (!display_is_on_console_p (si))
1166     {
1167       if (why) *why = "not on primary console";
1168       return False;
1169     }
1170
1171   f = fopen (PROC_INTERRUPTS, "r");
1172   if (!f)
1173     return False;
1174
1175   fclose (f);
1176   return True;
1177 }
1178
1179
1180 static Bool
1181 proc_interrupts_activity_p (saver_info *si)
1182 {
1183   static FILE *f0 = 0;
1184   FILE *f1 = 0;
1185   int fd;
1186   static char last_kbd_line[255] = { 0, };
1187   static char last_ptr_line[255] = { 0, };
1188   char new_line[sizeof(last_kbd_line)];
1189   Bool checked_kbd = False, kbd_changed = False;
1190   Bool checked_ptr = False, ptr_changed = False;
1191   int i8042_count = 0;
1192
1193   if (!f0)
1194     {
1195       /* First time -- open the file. */
1196       f0 = fopen (PROC_INTERRUPTS, "r");
1197       if (!f0)
1198         {
1199           char buf[255];
1200           sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
1201           perror (buf);
1202           goto FAIL;
1203         }
1204     }
1205
1206   if (f0 == (FILE *) -1)            /* means we got an error initializing. */
1207     return False;
1208
1209   fd = dup (fileno (f0));
1210   if (fd < 0)
1211     {
1212       char buf[255];
1213       sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
1214       perror (buf);
1215       goto FAIL;
1216     }
1217
1218   f1 = fdopen (fd, "r");
1219   if (!f1)
1220     {
1221       char buf[255];
1222       sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
1223               PROC_INTERRUPTS);
1224       perror (buf);
1225       goto FAIL;
1226     }
1227
1228   /* Actually, I'm unclear on why this fseek() is necessary, given the timing
1229      of the dup() above, but it is. */
1230   if (fseek (f1, 0, SEEK_SET) != 0)
1231     {
1232       char buf[255];
1233       sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
1234       perror (buf);
1235       goto FAIL;
1236     }
1237
1238   /* Now read through the pseudo-file until we find the "keyboard",
1239      "PS/2 mouse", or "i8042" lines. */
1240
1241   while (fgets (new_line, sizeof(new_line)-1, f1))
1242     {
1243       Bool i8042_p = !!strstr (new_line, "i8042");
1244       if (i8042_p) i8042_count++;
1245
1246       if (strchr (new_line, ','))
1247         {
1248           /* Ignore any line that has a comma on it: this is because
1249              a setup like this:
1250
1251                  12:     930935          XT-PIC  usb-uhci, PS/2 Mouse
1252
1253              is really bad news.  It *looks* like we can note mouse
1254              activity from that line, but really, that interrupt gets
1255              fired any time any USB device has activity!  So we have
1256              to ignore any shared IRQs.
1257            */
1258         }
1259       else if (!checked_kbd &&
1260                (strstr (new_line, "keyboard") ||
1261                 (i8042_p && i8042_count == 1)))
1262         {
1263           /* Assume the keyboard interrupt is the line that says "keyboard",
1264              or the *first* line that says "i8042".
1265            */
1266           kbd_changed = (*last_kbd_line && !!strcmp (new_line, last_kbd_line));
1267           strcpy (last_kbd_line, new_line);
1268           checked_kbd = True;
1269         }
1270       else if (!checked_ptr &&
1271                (strstr (new_line, "PS/2 Mouse") ||
1272                 (i8042_p && i8042_count == 2)))
1273         {
1274           /* Assume the mouse interrupt is the line that says "PS/2 mouse",
1275              or the *second* line that says "i8042".
1276            */
1277           ptr_changed = (*last_ptr_line && !!strcmp (new_line, last_ptr_line));
1278           strcpy (last_ptr_line, new_line);
1279           checked_ptr = True;
1280         }
1281
1282       if (checked_kbd && checked_ptr)
1283         break;
1284     }
1285
1286   if (checked_kbd || checked_ptr)
1287     {
1288       fclose (f1);
1289
1290       if (si->prefs.debug_p && (kbd_changed || ptr_changed))
1291         fprintf (stderr, "%s: /proc/interrupts activity: %s\n",
1292                  blurb(),
1293                  ((kbd_changed && ptr_changed) ? "mouse and kbd" :
1294                   kbd_changed ? "kbd" :
1295                   ptr_changed ? "mouse" : "ERR"));
1296
1297       return (kbd_changed || ptr_changed);
1298     }
1299
1300
1301   /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse"
1302      line in the file at all. */
1303   fprintf (stderr, "%s: no keyboard or mouse data in %s?\n",
1304            blurb(), PROC_INTERRUPTS);
1305
1306  FAIL:
1307   if (f1)
1308     fclose (f1);
1309
1310   if (f0 && f0 != (FILE *) -1)
1311     fclose (f0);
1312
1313   f0 = (FILE *) -1;
1314   return False;
1315 }
1316
1317 #endif /* HAVE_PROC_INTERRUPTS */
1318
1319 \f
1320 /* This timer goes off every few minutes, whether the user is idle or not,
1321    to try and clean up anything that has gone wrong.
1322
1323    It calls disable_builtin_screensaver() so that if xset has been used,
1324    or some other program (like xlock) has messed with the XSetScreenSaver()
1325    settings, they will be set back to sensible values (if a server extension
1326    is in use, messing with xlock can cause xscreensaver to never get a wakeup
1327    event, and could cause monitor power-saving to occur, and all manner of
1328    heinousness.)
1329
1330    If the screen is currently blanked, it raises the window, in case some
1331    other window has been mapped on top of it.
1332
1333    If the screen is currently blanked, and there is no hack running, it
1334    clears the window, in case there is an error message printed on it (we
1335    don't want the error message to burn in.)
1336  */
1337
1338 static void
1339 watchdog_timer (XtPointer closure, XtIntervalId *id)
1340 {
1341   saver_info *si = (saver_info *) closure;
1342   saver_preferences *p = &si->prefs;
1343
1344   disable_builtin_screensaver (si, False);
1345
1346   /* If the DPMS settings on the server have changed, change them back to
1347      what ~/.xscreensaver says they should be. */
1348   sync_server_dpms_settings (si->dpy,
1349                              (p->dpms_enabled_p  &&
1350                               p->mode != DONT_BLANK),
1351                              p->dpms_standby / 1000,
1352                              p->dpms_suspend / 1000,
1353                              p->dpms_off / 1000,
1354                              False);
1355
1356   if (si->screen_blanked_p)
1357     {
1358       Bool running_p = screenhack_running_p (si);
1359
1360       if (si->dbox_up_p)
1361         {
1362           if (si->prefs.debug_p)
1363             fprintf (stderr, "%s: dialog box is up: not raising screen.\n",
1364                      blurb());
1365         }
1366       else
1367         {
1368           if (si->prefs.debug_p)
1369             fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
1370                      blurb(), (running_p ? "" : "and clearing "));
1371
1372           raise_window (si, True, True, running_p);
1373         }
1374
1375       if (screenhack_running_p (si) &&
1376           !monitor_powered_on_p (si))
1377         {
1378           if (si->prefs.verbose_p)
1379             fprintf (stderr,
1380                      "%s: X says monitor has powered down; "
1381                      "killing running hacks.\n", blurb());
1382           kill_screenhack (si);
1383         }
1384
1385       /* Re-schedule this timer.  The watchdog timer defaults to a bit less
1386          than the hack cycle period, but is never longer than one hour.
1387        */
1388       si->watchdog_id = 0;
1389       reset_watchdog_timer (si, True);
1390     }
1391 }
1392
1393
1394 void
1395 reset_watchdog_timer (saver_info *si, Bool on_p)
1396 {
1397   saver_preferences *p = &si->prefs;
1398
1399   if (si->watchdog_id)
1400     {
1401       XtRemoveTimeOut (si->watchdog_id);
1402       si->watchdog_id = 0;
1403     }
1404
1405   if (on_p && p->watchdog_timeout)
1406     {
1407       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
1408                                          watchdog_timer, (XtPointer) si);
1409
1410       if (p->debug_p)
1411         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
1412                  blurb(), p->watchdog_timeout, si->watchdog_id);
1413     }
1414 }
1415
1416
1417 /* It's possible that a race condition could have led to the saver
1418    window being unexpectedly still mapped.  This can happen like so:
1419
1420     - screen is blanked
1421     - hack is launched
1422     - that hack tries to grab a screen image (it does this by
1423       first unmapping the saver window, then remapping it.)
1424     - hack unmaps window
1425     - hack waits
1426     - user becomes active
1427     - hack re-maps window (*)
1428     - driver kills subprocess
1429     - driver unmaps window (**)
1430
1431    The race is that (*) might have been sent to the server before
1432    the client process was killed, but, due to scheduling randomness,
1433    might not have been received by the server until after (**).
1434    In other words, (*) and (**) might happen out of order, meaning
1435    the driver will unmap the window, and then after that, the
1436    recently-dead client will re-map it.  This leaves the user
1437    locked out (it looks like a desktop, but it's not!)
1438
1439    To avoid this: after un-blanking the screen, we launch a timer
1440    that wakes up once a second for ten seconds, and makes damned
1441    sure that the window is still unmapped.
1442  */
1443
1444 void
1445 de_race_timer (XtPointer closure, XtIntervalId *id)
1446 {
1447   saver_info *si = (saver_info *) closure;
1448   saver_preferences *p = &si->prefs;
1449   int secs = 1;
1450
1451   if (id == 0)  /* if id is 0, this is the initialization call. */
1452     {
1453       si->de_race_ticks = 10;
1454       if (p->verbose_p)
1455         fprintf (stderr, "%s: starting de-race timer (%d seconds.)\n",
1456                  blurb(), si->de_race_ticks);
1457     }
1458   else
1459     {
1460       int i;
1461       XSync (si->dpy, False);
1462       for (i = 0; i < si->nscreens; i++)
1463         {
1464           saver_screen_info *ssi = &si->screens[i];
1465           Window w = ssi->screensaver_window;
1466           XWindowAttributes xgwa;
1467           XGetWindowAttributes (si->dpy, w, &xgwa);
1468           if (xgwa.map_state != IsUnmapped)
1469             {
1470               if (p->verbose_p)
1471                 fprintf (stderr,
1472                          "%s: %d: client race! emergency unmap 0x%lx.\n",
1473                          blurb(), i, (unsigned long) w);
1474               XUnmapWindow (si->dpy, w);
1475             }
1476           else if (p->debug_p)
1477             fprintf (stderr, "%s: %d: (de-race of 0x%lx is cool.)\n",
1478                      blurb(), i, (unsigned long) w);
1479         }
1480       XSync (si->dpy, False);
1481
1482       si->de_race_ticks--;
1483     }
1484
1485   if (id && *id == si->de_race_id)
1486     si->de_race_id = 0;
1487
1488   if (si->de_race_id) abort();
1489
1490   if (si->de_race_ticks <= 0)
1491     {
1492       si->de_race_id = 0;
1493       if (p->verbose_p)
1494         fprintf (stderr, "%s: de-race completed.\n", blurb());
1495     }
1496   else
1497     {
1498       si->de_race_id = XtAppAddTimeOut (si->app, secs * 1000,
1499                                         de_race_timer, closure);
1500     }
1501 }