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