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