http://ftp.x.org/contrib/applications/xscreensaver-3.19.tar.gz
[xscreensaver] / driver / timers.c
1 /* timers.c --- detecting when the user is idle, and other timer-related tasks.
2  * xscreensaver, Copyright (c) 1991-1997, 1998
3  *  Jamie Zawinski <jwz@jwz.org>
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  */
13
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17
18 /* #define DEBUG_TIMERS */
19
20 #include <stdio.h>
21 #include <X11/Xlib.h>
22 #include <X11/Intrinsic.h>
23 #include <X11/Xos.h>
24 #ifdef HAVE_XMU
25 # ifndef VMS
26 #  include <X11/Xmu/Error.h>
27 # else /* VMS */
28 #  include <Xmu/Error.h>
29 # endif /* VMS */
30 # else /* !HAVE_XMU */
31 # include "xmu.h"
32 #endif /* !HAVE_XMU */
33
34 #ifdef HAVE_XIDLE_EXTENSION
35 #include <X11/extensions/xidle.h>
36 #endif /* HAVE_XIDLE_EXTENSION */
37
38 #ifdef HAVE_MIT_SAVER_EXTENSION
39 #include <X11/extensions/scrnsaver.h>
40 #endif /* HAVE_MIT_SAVER_EXTENSION */
41
42 #ifdef HAVE_SGI_SAVER_EXTENSION
43 #include <X11/extensions/XScreenSaver.h>
44 #endif /* HAVE_SGI_SAVER_EXTENSION */
45
46 #include "xscreensaver.h"
47
48 #ifdef HAVE_PROC_INTERRUPTS
49 static Bool proc_interrupts_activity_p (saver_info *si);
50 #endif /* HAVE_PROC_INTERRUPTS */
51
52 static void check_for_clock_skew (saver_info *si);
53
54
55 void
56 idle_timer (XtPointer closure, XtIntervalId *id)
57 {
58   saver_info *si = (saver_info *) closure;
59
60   /* What an amazingly shitty design.  Not only does Xt execute timeout
61      events from XtAppNextEvent() instead of from XtDispatchEvent(), but
62      there is no way to tell Xt to block until there is an X event OR a
63      timeout happens.  Once your timeout proc is called, XtAppNextEvent()
64      still won't return until a "real" X event comes in.
65
66      So this function pushes a stupid, gratuitous, unnecessary event back
67      on the event queue to force XtAppNextEvent to return Right Fucking Now.
68      When the code in sleep_until_idle() sees an event of type XAnyEvent,
69      which the server never generates, it knows that a timeout has occurred.
70    */
71   XEvent fake_event;
72   fake_event.type = 0;  /* XAnyEvent type, ignored. */
73   fake_event.xany.display = si->dpy;
74   fake_event.xany.window  = 0;
75   XPutBackEvent (si->dpy, &fake_event);
76 }
77
78
79 static void
80 schedule_wakeup_event (saver_info *si, Time when, Bool verbose_p)
81 {
82   /* Wake up periodically to ask the server if we are idle. */
83   si->timer_id = XtAppAddTimeOut (si->app, when, idle_timer,
84                                   (XtPointer) si);
85
86 #ifdef DEBUG_TIMERS
87   if (verbose_p)
88     fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
89              blurb(), when, si->timer_id);
90 #endif /* DEBUG_TIMERS */
91 }
92
93
94 static void
95 notice_events (saver_info *si, Window window, Bool top_p)
96 {
97   saver_preferences *p = &si->prefs;
98   XWindowAttributes attrs;
99   unsigned long events;
100   Window root, parent, *kids;
101   unsigned int nkids;
102
103   if (XtWindowToWidget (si->dpy, window))
104     /* If it's one of ours, don't mess up its event mask. */
105     return;
106
107   if (!XQueryTree (si->dpy, window, &root, &parent, &kids, &nkids))
108     return;
109   if (window == root)
110     top_p = False;
111
112   XGetWindowAttributes (si->dpy, window, &attrs);
113   events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
114             & KeyPressMask);
115
116   /* Select for SubstructureNotify on all windows.
117      Select for KeyPress on all windows that already have it selected.
118
119      Note that we can't select for ButtonPress, because of X braindamage:
120      only one client at a time may select for ButtonPress on a given
121      window, though any number can select for KeyPress.  Someone explain
122      *that* to me.
123
124      So, if the user spends a while clicking the mouse without ever moving
125      the mouse or touching the keyboard, we won't know that they've been
126      active, and the screensaver will come on.  That sucks, but I don't
127      know how to get around it.
128    */
129   XSelectInput (si->dpy, window, SubstructureNotifyMask | events);
130
131   if (top_p && p->verbose_p && (events & KeyPressMask))
132     {
133       /* Only mention one window per tree (hack hack). */
134       fprintf (stderr, "%s: selected KeyPress on 0x%lX\n", blurb(),
135                (unsigned long) window);
136       top_p = False;
137     }
138
139   if (kids)
140     {
141       while (nkids)
142         notice_events (si, kids [--nkids], top_p);
143       XFree ((char *) kids);
144     }
145 }
146
147
148 int
149 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
150 {
151   /* When we notice a window being created, we spawn a timer that waits
152      30 seconds or so, and then selects events on that window.  This error
153      handler is used so that we can cope with the fact that the window
154      may have been destroyed <30 seconds after it was created.
155    */
156   if (error->error_code == BadWindow ||
157       error->error_code == BadMatch ||
158       error->error_code == BadDrawable)
159     return 0;
160   else
161     return saver_ehandler (dpy, error);
162 }
163
164
165 struct notice_events_timer_arg {
166   saver_info *si;
167   Window w;
168 };
169
170 static void
171 notice_events_timer (XtPointer closure, XtIntervalId *id)
172 {
173   struct notice_events_timer_arg *arg =
174     (struct notice_events_timer_arg *) closure;
175
176   XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
177
178   saver_info *si = arg->si;
179   Window window = arg->w;
180
181   free(arg);
182   notice_events (si, window, True);
183   XSync (si->dpy, False);
184   XSetErrorHandler (old_handler);
185 }
186
187 void
188 start_notice_events_timer (saver_info *si, Window w, Bool verbose_p)
189 {
190   saver_preferences *p = &si->prefs;
191   struct notice_events_timer_arg *arg =
192     (struct notice_events_timer_arg *) malloc(sizeof(*arg));
193   arg->si = si;
194   arg->w = w;
195   XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer,
196                    (XtPointer) arg);
197
198   if (verbose_p)
199     fprintf (stderr, "%s: starting notice_events_timer for 0x%X (%lu)\n",
200              blurb(), (unsigned int) w, p->notice_events_timeout);
201 }
202
203
204 /* When the screensaver is active, this timer will periodically change
205    the running program.
206  */
207 void
208 cycle_timer (XtPointer closure, XtIntervalId *id)
209 {
210   saver_info *si = (saver_info *) closure;
211   saver_preferences *p = &si->prefs;
212   Time how_long = p->cycle;
213
214   if (si->selection_mode > 0 &&
215       screenhack_running_p (si))
216     /* If we're in "SELECT n" mode, the cycle timer going off will just
217        restart this same hack again.  There's not much point in doing this
218        every 5 or 10 minutes, but on the other hand, leaving one hack running
219        for days is probably not a great idea, since they tend to leak and/or
220        crash.  So, restart the thing once an hour. */
221     how_long = 1000 * 60 * 60;
222
223   if (si->dbox_up_p)
224     {
225       if (p->verbose_p)
226         fprintf (stderr, "%s: dialog box up; delaying hack change.\n",
227                  blurb());
228       how_long = 30000; /* 30 secs */
229     }
230   else
231     {
232       maybe_reload_init_file (si);
233       if (p->verbose_p)
234         fprintf (stderr, "%s: changing graphics hacks.\n", blurb());
235       kill_screenhack (si);
236
237       if (!si->throttled_p)
238         spawn_screenhack (si, False);
239       else
240         {
241           raise_window (si, True, True, False);
242           if (p->verbose_p)
243             fprintf (stderr, "%s: not launching new hack (throttled.)\n",
244                      blurb());
245         }
246     }
247
248   si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
249                                   (XtPointer) si);
250
251 #ifdef DEBUG_TIMERS
252   if (p->verbose_p)
253     fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n",
254             blurb(), how_long, si->cycle_id);
255 #endif /* DEBUG_TIMERS */
256 }
257
258
259 void
260 activate_lock_timer (XtPointer closure, XtIntervalId *id)
261 {
262   saver_info *si = (saver_info *) closure;
263   saver_preferences *p = &si->prefs;
264
265   if (p->verbose_p)
266     fprintf (stderr, "%s: timed out; activating lock.\n", blurb());
267   set_locked_p (si, True);
268 }
269
270
271 /* Call this when user activity (or "simulated" activity) has been noticed.
272  */
273 static void
274 reset_timers (saver_info *si)
275 {
276   saver_preferences *p = &si->prefs;
277   if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
278     return;
279
280   if (si->timer_id)
281     {
282 #ifdef DEBUG_TIMERS
283       if (p->verbose_p)
284         fprintf (stderr, "%s: killing idle_timer  (%ld, %ld)\n",
285                  blurb(), p->timeout, si->timer_id);
286 #endif /* DEBUG_TIMERS */
287       XtRemoveTimeOut (si->timer_id);
288     }
289
290   schedule_wakeup_event (si, p->timeout, p->verbose_p); /* sets si->timer_id */
291
292   if (si->cycle_id) abort ();   /* no cycle timer when inactive */
293
294   si->last_activity_time = time ((time_t *) 0);
295 }
296
297
298 /* When we aren't using a server extension, this timer is used to periodically
299    wake up and poll the mouse position, which is possibly more reliable than
300    selecting motion events on every window.
301  */
302 static void
303 check_pointer_timer (XtPointer closure, XtIntervalId *id)
304 {
305   int i;
306   saver_info *si = (saver_info *) closure;
307   saver_preferences *p = &si->prefs;
308   Bool active_p = False;
309
310   if (!si->using_proc_interrupts &&
311       (si->using_xidle_extension ||
312        si->using_mit_saver_extension ||
313        si->using_sgi_saver_extension))
314     /* If an extension is in use, we should not be polling the mouse.
315        Unless we're also checking /proc/interrupts, in which case, we should.
316      */
317     abort ();
318
319   si->check_pointer_timer_id =
320     XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
321                      (XtPointer) si);
322
323   for (i = 0; i < si->nscreens; i++)
324     {
325       saver_screen_info *ssi = &si->screens[i];
326       Window root, child;
327       int root_x, root_y, x, y;
328       unsigned int mask;
329
330       XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
331                      &root_x, &root_y, &x, &y, &mask);
332
333       if (root_x == ssi->poll_mouse_last_root_x &&
334           root_y == ssi->poll_mouse_last_root_y &&
335           child  == ssi->poll_mouse_last_child &&
336           mask   == ssi->poll_mouse_last_mask)
337         continue;
338
339       active_p = True;
340
341 #ifdef DEBUG_TIMERS
342       if (p->verbose_p)
343         if (root_x == ssi->poll_mouse_last_root_x &&
344             root_y == ssi->poll_mouse_last_root_y &&
345             child  == ssi->poll_mouse_last_child)
346           fprintf (stderr, "%s: modifiers changed at %s on screen %d.\n",
347                    blurb(), timestring(), i);
348         else
349           fprintf (stderr, "%s: pointer moved at %s on screen %d.\n",
350                    blurb(), timestring(), i);
351 #endif /* DEBUG_TIMERS */
352
353       si->last_activity_screen    = ssi;
354       ssi->poll_mouse_last_root_x = root_x;
355       ssi->poll_mouse_last_root_y = root_y;
356       ssi->poll_mouse_last_child  = child;
357       ssi->poll_mouse_last_mask   = mask;
358     }
359
360 #ifdef HAVE_PROC_INTERRUPTS
361   if (!active_p &&
362       si->using_proc_interrupts &&
363       proc_interrupts_activity_p (si))
364     {
365 # ifdef DEBUG_TIMERS
366       if (p->verbose_p)
367         fprintf (stderr, "%s: /proc/interrupts activity at %s.\n",
368                  blurb(), timestring());
369 # endif /* DEBUG_TIMERS */
370       active_p = True;
371     }
372 #endif /* HAVE_PROC_INTERRUPTS */
373
374
375   if (active_p)
376     reset_timers (si);
377
378   check_for_clock_skew (si);
379 }
380
381
382 /* An unfortunate situation is this: the saver is not active, because the
383    user has been typing.  The machine is a laptop.  The user closes the lid
384    and suspends it.  The CPU halts.  Some hours later, the user opens the
385    lid.  At this point, Xt's timers will fire, and xscreensaver will blank
386    the screen.
387
388    So far so good -- well, not really, but it's the best that we can do,
389    since the OS doesn't send us a signal *before* shutdown -- but if the
390    user had delayed locking (lockTimeout > 0) then we should start off
391    in the locked state, rather than only locking N minutes from when the
392    lid was opened.  Also, eschewing fading is probably a good idea, to
393    clamp down as soon as possible.
394
395    We only do this when we'd be polling the mouse position anyway.
396    This amounts to an assumption that machines with APM support also
397    have /proc/interrupts.
398  */
399 static void
400 check_for_clock_skew (saver_info *si)
401 {
402   saver_preferences *p = &si->prefs;
403   time_t now = time ((time_t *) 0);
404   long shift = now - si->last_wall_clock_time;
405
406 #ifdef DEBUG_TIMERS
407   if (p->verbose_p)
408     fprintf (stderr, "%s: checking wall clock (%d).\n", blurb(),
409              (si->last_wall_clock_time == 0 ? 0 : shift));
410 #endif /* DEBUG_TIMERS */
411
412   if (si->last_wall_clock_time != 0 &&
413       shift > (p->timeout / 1000))
414     {
415       if (p->verbose_p)
416         fprintf (stderr, "%s: wall clock has jumped by %d:%02d:%02d!\n",
417                  blurb(),
418                  (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60));
419
420       si->emergency_lock_p = True;
421       idle_timer ((XtPointer) si, 0);
422     }
423
424   si->last_wall_clock_time = now;
425 }
426
427
428
429 static void
430 dispatch_event (saver_info *si, XEvent *event)
431 {
432   /* If this is for the splash dialog, pass it along.
433      Note that the password dialog is handled with its own event loop,
434      so events for that window will never come through here.
435    */
436   if (si->splash_dialog && event->xany.window == si->splash_dialog)
437     handle_splash_event (si, event);
438
439   XtDispatchEvent (event);
440 }
441
442
443 static void
444 swallow_unlock_typeahead_events (saver_info *si, XEvent *e)
445 {
446   XEvent event;
447   char buf [100];
448   int i = 0;
449
450   memset (buf, 0, sizeof(buf));
451
452   event = *e;
453
454   do
455     {
456       if (event.xany.type == KeyPress)
457         {
458           char s[2];
459           int size = XLookupString (&event, s, 1, 0, 0);
460           if (size != 1) continue;
461           switch (*s)
462             {
463             case '\010': case '\177':                   /* Backspace */
464               if (i > 0) i--;
465               break;
466             case '\025': case '\030':                   /* Erase line */
467             case '\012': case '\015':                   /* Enter */
468               i = 0;
469               break;
470             case '\040':                                /* Space */
471               if (i == 0)
472                 break;  /* ignore space at beginning of line */
473               /* else, fall through */
474             default:
475               buf [i++] = *s;
476               break;
477             }
478         }
479
480     } while (i < sizeof(buf)-1 &&
481              XCheckMaskEvent (si->dpy, KeyPressMask, &event));
482
483   buf[i] = 0;
484
485   if (si->unlock_typeahead)
486     {
487       memset (si->unlock_typeahead, 0, strlen(si->unlock_typeahead));
488       free (si->unlock_typeahead);
489     }
490
491   if (i > 0)
492     si->unlock_typeahead = strdup (buf);
493   else
494     si->unlock_typeahead = 0;
495
496   memset (buf, 0, sizeof(buf));
497 }
498
499
500 /* methods of detecting idleness:
501
502       explicitly informed by SGI SCREEN_SAVER server event;
503       explicitly informed by MIT-SCREEN-SAVER server event;
504       poll server idle time with XIDLE extension;
505       select events on all windows, and note absence of recent events;
506       note that /proc/interrupts has not changed in a while;
507       activated by clientmessage.
508
509    methods of detecting non-idleness:
510
511       read events on the xscreensaver window;
512       explicitly informed by SGI SCREEN_SAVER server event;
513       explicitly informed by MIT-SCREEN-SAVER server event;
514       select events on all windows, and note events on any of them;
515       note that /proc/interrupts has changed;
516       deactivated by clientmessage.
517
518    I trust that explains why this function is a big hairy mess.
519  */
520 void
521 sleep_until_idle (saver_info *si, Bool until_idle_p)
522 {
523   saver_preferences *p = &si->prefs;
524   XEvent event;
525
526   /* We need to select events on all windows if we're not using any extensions.
527      Otherwise, we don't need to. */
528   Bool scanning_all_windows = !(si->using_xidle_extension ||
529                                 si->using_mit_saver_extension ||
530                                 si->using_sgi_saver_extension);
531
532   /* We need to periodically wake up and check for idleness if we're not using
533      any extensions, or if we're using the XIDLE extension.  The other two
534      extensions explicitly deliver events when we go idle/non-idle, so we
535      don't need to poll. */
536   Bool polling_for_idleness = !(si->using_mit_saver_extension ||
537                                 si->using_sgi_saver_extension);
538
539   /* Whether we need to periodically wake up and check to see if the mouse has
540      moved.  We only need to do this when not using any extensions.  The reason
541      this isn't the same as `polling_for_idleness' is that the "idleness" poll
542      can happen (for example) 5 minutes from now, whereas the mouse-position
543      poll should happen with low periodicity.  We don't need to poll the mouse
544      position with the XIDLE extension, but we do need to periodically wake up
545      and query the server with that extension.  For our purposes, polling
546      /proc/interrupts is just like polling the mouse position.  It has to
547      happen on the same kind of schedule. */
548   Bool polling_mouse_position = (si->using_proc_interrupts ||
549                                  !(si->using_xidle_extension ||
550                                    si->using_mit_saver_extension ||
551                                    si->using_sgi_saver_extension));
552
553   if (until_idle_p)
554     {
555       if (polling_for_idleness)
556         /* This causes a no-op event to be delivered to us in a while, so that
557            we come back around through the event loop again.  Use of this timer
558            is economical: for example, if the screensaver should come on in 5
559            minutes, and the user has been idle for 2 minutes, then this
560            timeout will go off no sooner than 3 minutes from now.  */
561         schedule_wakeup_event (si, p->timeout, p->verbose_p);
562
563       if (polling_mouse_position)
564         /* Check to see if the mouse has moved, and set up a repeating timer
565            to do so periodically (typically, every 5 seconds.) */
566         check_pointer_timer ((XtPointer) si, 0);
567     }
568
569   while (1)
570     {
571       XtAppNextEvent (si->app, &event);
572
573       switch (event.xany.type) {
574       case 0:           /* our synthetic "timeout" event has been signalled */
575         if (until_idle_p)
576           {
577             Time idle;
578 #ifdef HAVE_XIDLE_EXTENSION
579             if (si->using_xidle_extension)
580               {
581                 /* The XIDLE extension uses the synthetic event to prod us into
582                    re-asking the server how long the user has been idle. */
583                 if (! XGetIdleTime (si->dpy, &idle))
584                   {
585                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
586                     saver_exit (si, 1, 0);
587                   }
588               }
589             else
590 #endif /* HAVE_XIDLE_EXTENSION */
591 #ifdef HAVE_MIT_SAVER_EXTENSION
592               if (si->using_mit_saver_extension)
593                 {
594                   /* We don't need to do anything in this case - the synthetic
595                      event isn't necessary, as we get sent specific events
596                      to wake us up.  In fact, this event generally shouldn't
597                      be being delivered when the MIT extension is in use. */
598                   idle = 0;
599                 }
600             else
601 #endif /* HAVE_MIT_SAVER_EXTENSION */
602 #ifdef HAVE_SGI_SAVER_EXTENSION
603               if (si->using_sgi_saver_extension)
604                 {
605                   /* We don't need to do anything in this case - the synthetic
606                      event isn't necessary, as we get sent specific events
607                      to wake us up.  In fact, this event generally shouldn't
608                      be being delivered when the SGI extension is in use. */
609                   idle = 0;
610                 }
611             else
612 #endif /* HAVE_SGI_SAVER_EXTENSION */
613               {
614                 /* Otherwise, no server extension is in use.  The synthetic
615                    event was to tell us to wake up and see if the user is now
616                    idle.  Compute the amount of idle time by comparing the
617                    `last_activity_time' to the wall clock.  The l_a_t was set
618                    by calling `reset_timers()', which is called only in only
619                    two situations: when polling the mouse position has revealed
620                    the the mouse has moved (user activity) or when we have read
621                    an event (again, user activity.)
622                  */
623                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
624               }
625
626             if (idle >= p->timeout)
627               {
628                 /* Look, we've been idle long enough.  We're done. */
629                 goto DONE;
630               }
631             else if (si->emergency_lock_p)
632               {
633                 /* Oops, the wall clock has jumped far into the future, so
634                    we need to lock down in a hurry! */
635                 goto DONE;
636               }
637             else
638               {
639                 /* The event went off, but it turns out that the user has not
640                    yet been idle for long enough.  So re-signal the event.
641                    */
642                 if (polling_for_idleness)
643                   schedule_wakeup_event (si, p->timeout - idle, p->verbose_p);
644               }
645           }
646         break;
647
648       case ClientMessage:
649         if (handle_clientmessage (si, &event, until_idle_p))
650           goto DONE;
651         break;
652
653       case CreateNotify:
654         /* A window has been created on the screen somewhere.  If we're
655            supposed to scan all windows for events, prepare this window. */
656         if (scanning_all_windows)
657           {
658             Window w = event.xcreatewindow.window;
659 #ifdef DEBUG_TIMERS
660             start_notice_events_timer (si, w, p->verbose_p);
661 #else  /* !DEBUG_TIMERS */
662             start_notice_events_timer (si, w, False);
663 #endif /* !DEBUG_TIMERS */
664           }
665         break;
666
667       case KeyPress:
668       case KeyRelease:
669       case ButtonPress:
670       case ButtonRelease:
671       case MotionNotify:
672
673 #ifdef DEBUG_TIMERS
674         if (p->verbose_p)
675           {
676             if (event.xany.type == MotionNotify)
677               fprintf (stderr,"%s: MotionNotify at %s\n",blurb(),timestring());
678             else if (event.xany.type == KeyPress)
679               fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(),
680                        (unsigned int) event.xkey.window, timestring ());
681             else if (event.xany.type == ButtonPress)
682               fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(),
683                        (unsigned int) event.xbutton.window, timestring ());
684           }
685 #endif /* DEBUG_TIMERS */
686
687         /* If any widgets want to handle this event, let them. */
688         dispatch_event (si, &event);
689
690         /* We got a user event.
691            If we're waiting for the user to become active, this is it.
692            If we're waiting until the user becomes idle, reset the timers
693            (since now we have longer to wait.)
694          */
695         if (!until_idle_p)
696           {
697             if (si->demoing_p &&
698                 (event.xany.type == MotionNotify ||
699                  event.xany.type == KeyRelease))
700               /* When we're demoing a single hack, mouse motion doesn't
701                  cause deactivation.  Only clicks and keypresses do. */
702               ;
703             else
704               /* If we're not demoing, then any activity causes deactivation.
705                */
706               goto DONE;
707           }
708         else
709           reset_timers (si);
710
711         break;
712
713       default:
714
715 #ifdef HAVE_MIT_SAVER_EXTENSION
716         if (event.type == si->mit_saver_ext_event_number)
717           {
718             /* This event's number is that of the MIT-SCREEN-SAVER server
719                extension.  This extension has one event number, and the event
720                itself contains sub-codes that say what kind of event it was
721                (an "idle" or "not-idle" event.)
722              */
723             XScreenSaverNotifyEvent *sevent =
724               (XScreenSaverNotifyEvent *) &event;
725             if (sevent->state == ScreenSaverOn)
726               {
727                 int i = 0;
728                 if (p->verbose_p)
729                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
730                            blurb());
731
732                 /* Get the "real" server window(s) out of the way as soon
733                    as possible. */
734                 for (i = 0; i < si->nscreens; i++)
735                   {
736                     saver_screen_info *ssi = &si->screens[i];
737                     if (ssi->server_mit_saver_window &&
738                         window_exists_p (si->dpy,
739                                          ssi->server_mit_saver_window))
740                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
741                   }
742
743                 if (sevent->kind != ScreenSaverExternal)
744                   {
745                     fprintf (stderr,
746                          "%s: ScreenSaverOn event wasn't of type External!\n",
747                              blurb());
748                   }
749
750                 if (until_idle_p)
751                   goto DONE;
752               }
753             else if (sevent->state == ScreenSaverOff)
754               {
755                 if (p->verbose_p)
756                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
757                            blurb());
758                 if (!until_idle_p)
759                   goto DONE;
760               }
761             else
762               fprintf (stderr,
763                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
764                        blurb(), sevent->state);
765           }
766         else
767
768 #endif /* HAVE_MIT_SAVER_EXTENSION */
769
770
771 #ifdef HAVE_SGI_SAVER_EXTENSION
772         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
773           {
774             /* The SGI SCREEN_SAVER server extension has two event numbers,
775                and this event matches the "idle" event. */
776             if (p->verbose_p)
777               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
778                        blurb());
779
780             if (until_idle_p)
781               goto DONE;
782           }
783         else if (event.type == (si->sgi_saver_ext_event_number +
784                                 ScreenSaverEnd))
785           {
786             /* The SGI SCREEN_SAVER server extension has two event numbers,
787                and this event matches the "idle" event. */
788             if (p->verbose_p)
789               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
790                        blurb());
791             if (!until_idle_p)
792               goto DONE;
793           }
794         else
795 #endif /* HAVE_SGI_SAVER_EXTENSION */
796
797           /* Just some random event.  Let the Widgets handle it, if desired. */
798           dispatch_event (si, &event);
799       }
800     }
801  DONE:
802
803
804   /* If there's a user event on the queue, swallow it.
805      If we're using a server extension, and the user becomes active, we
806      get the extension event before the user event -- so the keypress or
807      motion or whatever is still on the queue.  This makes "unfade" not
808      work, because it sees that event, and bugs out.  (This problem
809      doesn't exhibit itself without an extension, because in that case,
810      there's only one event generated by user activity, not two.)
811    */
812   if (!until_idle_p && si->locked_p)
813     swallow_unlock_typeahead_events (si, &event);
814   else
815     while (XCheckMaskEvent (si->dpy,
816                             (KeyPressMask|ButtonPressMask|PointerMotionMask),
817                      &event))
818       ;
819
820
821   if (si->check_pointer_timer_id)
822     {
823       XtRemoveTimeOut (si->check_pointer_timer_id);
824       si->check_pointer_timer_id = 0;
825     }
826   if (si->timer_id)
827     {
828       XtRemoveTimeOut (si->timer_id);
829       si->timer_id = 0;
830     }
831
832   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
833     abort ();
834
835   return;
836 }
837
838
839 \f
840 /* Some crap for dealing with /proc/interrupts.
841
842    On Linux systems, it's possible to see the hardware interrupt count
843    associated with the keyboard.  We can therefore use that as another method
844    of detecting idleness.
845
846    Why is it a good idea to do this?  Because it lets us detect keyboard
847    activity that is not associated with X events.  For example, if the user
848    has switched to another virtual console, it's good for xscreensaver to not
849    be running graphics hacks on the (non-visible) X display.  The common
850    complaint that checking /proc/interrupts addresses is that the user is
851    playing Quake on a non-X console, and the GL hacks are perceptibly slowing
852    the game...
853
854    This is tricky for a number of reasons.
855
856      * First, we must be sure to only do this when running on an X server that
857        is on the local machine (because otherwise, we'd be reacting to the
858        wrong keyboard.)  The way we do this is by noting that the $DISPLAY is
859        pointing to display 0 on the local machine.  It *could* be that display
860        1 is also on the local machine (e.g., two X servers, each on a different
861        virtual-terminal) but it's also possible that screen 1 is an X terminal,
862        using this machine as the host.  So we can't take that chance.
863
864      * Second, one can only access these interrupt numbers in a completely
865        and utterly brain-damaged way.  You would think that one would use an
866        ioctl for this.  But no.  The ONLY way to get this information is to
867        open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
868        out of it TEXTUALLY.  Because this is Unix, and all the world's a file,
869        and the only real data type is the short-line sequence of ASCII bytes.
870
871        Now it's all well and good that the /proc/interrupts pseudo-file
872        exists; that's a clever idea, and a useful API for things that are
873        already textually oriented, like shell scripts, and users doing
874        interactive debugging sessions.  But to make a *C PROGRAM* open a file
875        and parse the textual representation of integers out of it is just
876        insane.
877
878      * Third, you can't just hold the file open, and fseek() back to the
879        beginning to get updated data!  If you do that, the data never changes.
880        And I don't want to call open() every five seconds, because I don't want
881        to risk going to disk for any inodes.  It turns out that if you dup()
882        it early, then each copy gets fresh data, so we can get around that in
883        this way (but for how many releases, one might wonder?)
884
885      * Fourth, the format of the output of the /proc/interrupts file is
886        undocumented, and has changed several times already!  In Linux 2.0.33,
887        even on a multiprocessor machine, it looks like this:
888
889           0:  309453991   timer
890           1:    4771729   keyboard
891    
892        but on later kernels with MP machines, it looks like this:
893
894                    CPU0       CPU1
895           0:    1671450    1672618    IO-APIC-edge  timer
896           1:      13037      13495    IO-APIC-edge  keyboard
897
898        Joy!  So how are we expected to parse that?  Well, this code doesn't
899        parse it: it saves the last line with the string "keyboard" in it, and
900        does a string-comparison to note when it has changed.
901
902    Thanks to Nat Friedman <nat@nat.org> for figuring out all of this crap.
903
904    Note that this only checks for lines with "keyboard" or "PS/2 Mouse" in
905    them.  If you have a serial mouse, it won't detect that, it will only detect
906    keyboard activity.  That's because there's no way to tell the difference
907    between a serial mouse and a general serial port, and it would be somewhat
908    unfortunate to have the screensaver turn off when the modem on COM1 burped.
909  */
910
911
912 #ifdef HAVE_PROC_INTERRUPTS
913
914 #define PROC_INTERRUPTS "/proc/interrupts"
915
916 Bool
917 query_proc_interrupts_available (saver_info *si, const char **why)
918 {
919   /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
920      "/proc/interrupts" file exists and is readable.
921    */
922   FILE *f;
923   if (why) *why = 0;
924
925   if (!display_is_on_console_p (si))
926     {
927       if (why) *why = "not on primary console";
928       return False;
929     }
930
931   f = fopen (PROC_INTERRUPTS, "r");
932   if (!f)
933     return False;
934
935   fclose (f);
936   return True;
937 }
938
939
940 static Bool
941 proc_interrupts_activity_p (saver_info *si)
942 {
943   static FILE *f0 = 0;
944   FILE *f1 = 0;
945   int fd;
946   static char last_kbd_line[255] = { 0, };
947   static char last_ptr_line[255] = { 0, };
948   char new_line[sizeof(last_kbd_line)];
949   Bool got_kbd = False, kbd_diff = False;
950   Bool got_ptr = False, ptr_diff = False;
951
952   if (!f0)
953     {
954       /* First time -- open the file. */
955       f0 = fopen (PROC_INTERRUPTS, "r");
956       if (!f0)
957         {
958           char buf[255];
959           sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
960           perror (buf);
961           goto FAIL;
962         }
963     }
964
965   if (f0 == (FILE *) -1)            /* means we got an error initializing. */
966     return False;
967
968   fd = dup (fileno (f0));
969   if (fd < 0)
970     {
971       char buf[255];
972       sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
973       perror (buf);
974       goto FAIL;
975     }
976
977   f1 = fdopen (fd, "r");
978   if (!f1)
979     {
980       char buf[255];
981       sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
982               PROC_INTERRUPTS);
983       perror (buf);
984       goto FAIL;
985     }
986
987   /* Actually, I'm unclear on why this fseek() is necessary, given the timing
988      of the dup() above, but it is. */
989   if (fseek (f1, 0, SEEK_SET) != 0)
990     {
991       char buf[255];
992       sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
993       perror (buf);
994       goto FAIL;
995     }
996
997   /* Now read through the pseudo-file until we find the "keyboard" line. */
998
999   while (fgets (new_line, sizeof(new_line)-1, f1))
1000     {
1001       if (!got_kbd && strstr (new_line, "keyboard"))
1002         {
1003           kbd_diff = (*last_kbd_line && !!strcmp (new_line, last_kbd_line));
1004           strcpy (last_kbd_line, new_line);
1005           got_kbd = True;
1006         }
1007       else if (!got_ptr && strstr (new_line, "PS/2 Mouse"))
1008         {
1009           ptr_diff = (*last_ptr_line && !!strcmp (new_line, last_ptr_line));
1010           strcpy (last_ptr_line, new_line);
1011           got_ptr = True;
1012         }
1013
1014       if (got_kbd && got_ptr)
1015         break;
1016     }
1017
1018   if (got_kbd || got_ptr)
1019     {
1020       fclose (f1);
1021       return (kbd_diff || ptr_diff);
1022     }
1023
1024
1025   /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse"
1026      line in the file at all. */
1027   fprintf (stderr, "%s: no keyboard or mouse data in %s?\n",
1028            blurb(), PROC_INTERRUPTS);
1029
1030  FAIL:
1031   if (f1)
1032     fclose (f1);
1033
1034   if (f0 && f0 != (FILE *) -1)
1035     fclose (f0);
1036
1037   f0 = (FILE *) -1;
1038   return False;
1039 }
1040
1041 #endif /* HAVE_PROC_INTERRUPTS */
1042
1043 \f
1044 /* This timer goes off every few minutes, whether the user is idle or not,
1045    to try and clean up anything that has gone wrong.
1046
1047    It calls disable_builtin_screensaver() so that if xset has been used,
1048    or some other program (like xlock) has messed with the XSetScreenSaver()
1049    settings, they will be set back to sensible values (if a server extension
1050    is in use, messing with xlock can cause xscreensaver to never get a wakeup
1051    event, and could cause monitor power-saving to occur, and all manner of
1052    heinousness.)
1053
1054    If the screen is currently blanked, it raises the window, in case some
1055    other window has been mapped on top of it.
1056
1057    If the screen is currently blanked, and there is no hack running, it
1058    clears the window, in case there is an error message printed on it (we
1059    don't want the error message to burn in.)
1060  */
1061
1062 static void
1063 watchdog_timer (XtPointer closure, XtIntervalId *id)
1064 {
1065   saver_info *si = (saver_info *) closure;
1066
1067   disable_builtin_screensaver (si, False);
1068
1069   if (si->screen_blanked_p)
1070     {
1071       Bool running_p = screenhack_running_p (si);
1072
1073       if (si->dbox_up_p)
1074         {
1075 #ifdef DEBUG_TIMERS
1076           if (si->prefs.verbose_p)
1077             fprintf (stderr, "%s: dialog box is up: not raising screen.\n",
1078                      blurb());
1079 #endif /* DEBUG_TIMERS */
1080         }
1081       else
1082         {
1083 #ifdef DEBUG_TIMERS
1084           if (si->prefs.verbose_p)
1085             fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
1086                      blurb(), (running_p ? "" : "and clearing "));
1087 #endif /* DEBUG_TIMERS */
1088
1089           raise_window (si, True, True, running_p);
1090         }
1091
1092       if (!monitor_powered_on_p (si))
1093         {
1094           if (si->prefs.verbose_p)
1095             fprintf (stderr,
1096                      "%s: server reports that monitor has powered down; "
1097                      "killing running hacks.\n", blurb());
1098           kill_screenhack (si);
1099         }
1100
1101       /* Re-schedule this timer.  The watchdog timer defaults to a bit less
1102          than the hack cycle period, but is never longer than one hour.
1103        */
1104       si->watchdog_id = 0;
1105       reset_watchdog_timer (si, True);
1106     }
1107 }
1108
1109
1110 void
1111 reset_watchdog_timer (saver_info *si, Bool on_p)
1112 {
1113   saver_preferences *p = &si->prefs;
1114
1115   if (si->watchdog_id)
1116     {
1117       XtRemoveTimeOut (si->watchdog_id);
1118       si->watchdog_id = 0;
1119     }
1120
1121   if (on_p && p->watchdog_timeout)
1122     {
1123       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
1124                                          watchdog_timer, (XtPointer) si);
1125
1126 #ifdef DEBUG_TIMERS
1127       if (p->verbose_p)
1128         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
1129                  blurb(), p->watchdog_timeout, si->watchdog_id);
1130 #endif /* DEBUG_TIMERS */
1131
1132     }
1133 }