ftp://ftp.smr.ru/pub/0/FreeBSD/releases/distfiles/xscreensaver-3.16.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 /* methods of detecting idleness:
444
445       explicitly informed by SGI SCREEN_SAVER server event;
446       explicitly informed by MIT-SCREEN-SAVER server event;
447       poll server idle time with XIDLE extension;
448       select events on all windows, and note absence of recent events;
449       note that /proc/interrupts has not changed in a while;
450       activated by clientmessage.
451
452    methods of detecting non-idleness:
453
454       read events on the xscreensaver window;
455       explicitly informed by SGI SCREEN_SAVER server event;
456       explicitly informed by MIT-SCREEN-SAVER server event;
457       select events on all windows, and note events on any of them;
458       note that /proc/interrupts has changed;
459       deactivated by clientmessage.
460
461    I trust that explains why this function is a big hairy mess.
462  */
463 void
464 sleep_until_idle (saver_info *si, Bool until_idle_p)
465 {
466   saver_preferences *p = &si->prefs;
467   XEvent event;
468
469   /* We need to select events on all windows if we're not using any extensions.
470      Otherwise, we don't need to. */
471   Bool scanning_all_windows = !(si->using_xidle_extension ||
472                                 si->using_mit_saver_extension ||
473                                 si->using_sgi_saver_extension);
474
475   /* We need to periodically wake up and check for idleness if we're not using
476      any extensions, or if we're using the XIDLE extension.  The other two
477      extensions explicitly deliver events when we go idle/non-idle, so we
478      don't need to poll. */
479   Bool polling_for_idleness = !(si->using_mit_saver_extension ||
480                                 si->using_sgi_saver_extension);
481
482   /* Whether we need to periodically wake up and check to see if the mouse has
483      moved.  We only need to do this when not using any extensions.  The reason
484      this isn't the same as `polling_for_idleness' is that the "idleness" poll
485      can happen (for example) 5 minutes from now, whereas the mouse-position
486      poll should happen with low periodicity.  We don't need to poll the mouse
487      position with the XIDLE extension, but we do need to periodically wake up
488      and query the server with that extension.  For our purposes, polling
489      /proc/interrupts is just like polling the mouse position.  It has to
490      happen on the same kind of schedule. */
491   Bool polling_mouse_position = (si->using_proc_interrupts ||
492                                  !(si->using_xidle_extension ||
493                                    si->using_mit_saver_extension ||
494                                    si->using_sgi_saver_extension));
495
496   if (until_idle_p)
497     {
498       if (polling_for_idleness)
499         /* This causes a no-op event to be delivered to us in a while, so that
500            we come back around through the event loop again.  Use of this timer
501            is economical: for example, if the screensaver should come on in 5
502            minutes, and the user has been idle for 2 minutes, then this
503            timeout will go off no sooner than 3 minutes from now.  */
504         schedule_wakeup_event (si, p->timeout, p->verbose_p);
505
506       if (polling_mouse_position)
507         /* Check to see if the mouse has moved, and set up a repeating timer
508            to do so periodically (typically, every 5 seconds.) */
509         check_pointer_timer ((XtPointer) si, 0);
510     }
511
512   while (1)
513     {
514       XtAppNextEvent (si->app, &event);
515
516       switch (event.xany.type) {
517       case 0:           /* our synthetic "timeout" event has been signalled */
518         if (until_idle_p)
519           {
520             Time idle;
521 #ifdef HAVE_XIDLE_EXTENSION
522             if (si->using_xidle_extension)
523               {
524                 /* The XIDLE extension uses the synthetic event to prod us into
525                    re-asking the server how long the user has been idle. */
526                 if (! XGetIdleTime (si->dpy, &idle))
527                   {
528                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
529                     saver_exit (si, 1, 0);
530                   }
531               }
532             else
533 #endif /* HAVE_XIDLE_EXTENSION */
534 #ifdef HAVE_MIT_SAVER_EXTENSION
535               if (si->using_mit_saver_extension)
536                 {
537                   /* We don't need to do anything in this case - the synthetic
538                      event isn't necessary, as we get sent specific events
539                      to wake us up.  In fact, this event generally shouldn't
540                      be being delivered when the MIT extension is in use. */
541                   idle = 0;
542                 }
543             else
544 #endif /* HAVE_MIT_SAVER_EXTENSION */
545 #ifdef HAVE_SGI_SAVER_EXTENSION
546               if (si->using_sgi_saver_extension)
547                 {
548                   /* We don't need to do anything in this case - the synthetic
549                      event isn't necessary, as we get sent specific events
550                      to wake us up.  In fact, this event generally shouldn't
551                      be being delivered when the SGI extension is in use. */
552                   idle = 0;
553                 }
554             else
555 #endif /* HAVE_SGI_SAVER_EXTENSION */
556               {
557                 /* Otherwise, no server extension is in use.  The synthetic
558                    event was to tell us to wake up and see if the user is now
559                    idle.  Compute the amount of idle time by comparing the
560                    `last_activity_time' to the wall clock.  The l_a_t was set
561                    by calling `reset_timers()', which is called only in only
562                    two situations: when polling the mouse position has revealed
563                    the the mouse has moved (user activity) or when we have read
564                    an event (again, user activity.)
565                  */
566                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
567               }
568
569             if (idle >= p->timeout)
570               {
571                 /* Look, we've been idle long enough.  We're done. */
572                 goto DONE;
573               }
574             else if (si->emergency_lock_p)
575               {
576                 /* Oops, the wall clock has jumped far into the future, so
577                    we need to lock down in a hurry! */
578                 goto DONE;
579               }
580             else
581               {
582                 /* The event went off, but it turns out that the user has not
583                    yet been idle for long enough.  So re-signal the event.
584                    */
585                 if (polling_for_idleness)
586                   schedule_wakeup_event (si, p->timeout - idle, p->verbose_p);
587               }
588           }
589         break;
590
591       case ClientMessage:
592         if (handle_clientmessage (si, &event, until_idle_p))
593           goto DONE;
594         break;
595
596       case CreateNotify:
597         /* A window has been created on the screen somewhere.  If we're
598            supposed to scan all windows for events, prepare this window. */
599         if (scanning_all_windows)
600           {
601             Window w = event.xcreatewindow.window;
602 #ifdef DEBUG_TIMERS
603             start_notice_events_timer (si, w, p->verbose_p);
604 #else  /* !DEBUG_TIMERS */
605             start_notice_events_timer (si, w, False);
606 #endif /* !DEBUG_TIMERS */
607           }
608         break;
609
610       case KeyPress:
611       case KeyRelease:
612       case ButtonPress:
613       case ButtonRelease:
614       case MotionNotify:
615
616 #ifdef DEBUG_TIMERS
617         if (p->verbose_p)
618           {
619             if (event.xany.type == MotionNotify)
620               fprintf (stderr,"%s: MotionNotify at %s\n",blurb(),timestring());
621             else if (event.xany.type == KeyPress)
622               fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(),
623                        (unsigned int) event.xkey.window, timestring ());
624             else if (event.xany.type == ButtonPress)
625               fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(),
626                        (unsigned int) event.xbutton.window, timestring ());
627           }
628 #endif /* DEBUG_TIMERS */
629
630         /* If any widgets want to handle this event, let them. */
631         dispatch_event (si, &event);
632
633         /* We got a user event.
634            If we're waiting for the user to become active, this is it.
635            If we're waiting until the user becomes idle, reset the timers
636            (since now we have longer to wait.)
637          */
638         if (!until_idle_p)
639           {
640             if (si->demoing_p &&
641                 (event.xany.type == MotionNotify ||
642                  event.xany.type == KeyRelease))
643               /* When we're demoing a single hack, mouse motion doesn't
644                  cause deactivation.  Only clicks and keypresses do. */
645               ;
646             else
647               /* If we're not demoing, then any activity causes deactivation.
648                */
649               goto DONE;
650           }
651         else
652           reset_timers (si);
653
654         break;
655
656       default:
657
658 #ifdef HAVE_MIT_SAVER_EXTENSION
659         if (event.type == si->mit_saver_ext_event_number)
660           {
661             /* This event's number is that of the MIT-SCREEN-SAVER server
662                extension.  This extension has one event number, and the event
663                itself contains sub-codes that say what kind of event it was
664                (an "idle" or "not-idle" event.)
665              */
666             XScreenSaverNotifyEvent *sevent =
667               (XScreenSaverNotifyEvent *) &event;
668             if (sevent->state == ScreenSaverOn)
669               {
670                 int i = 0;
671                 if (p->verbose_p)
672                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
673                            blurb());
674
675                 /* Get the "real" server window(s) out of the way as soon
676                    as possible. */
677                 for (i = 0; i < si->nscreens; i++)
678                   {
679                     saver_screen_info *ssi = &si->screens[i];
680                     if (ssi->server_mit_saver_window &&
681                         window_exists_p (si->dpy,
682                                          ssi->server_mit_saver_window))
683                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
684                   }
685
686                 if (sevent->kind != ScreenSaverExternal)
687                   {
688                     fprintf (stderr,
689                          "%s: ScreenSaverOn event wasn't of type External!\n",
690                              blurb());
691                   }
692
693                 if (until_idle_p)
694                   goto DONE;
695               }
696             else if (sevent->state == ScreenSaverOff)
697               {
698                 if (p->verbose_p)
699                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
700                            blurb());
701                 if (!until_idle_p)
702                   goto DONE;
703               }
704             else
705               fprintf (stderr,
706                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
707                        blurb(), sevent->state);
708           }
709         else
710
711 #endif /* HAVE_MIT_SAVER_EXTENSION */
712
713
714 #ifdef HAVE_SGI_SAVER_EXTENSION
715         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
716           {
717             /* The SGI SCREEN_SAVER server extension has two event numbers,
718                and this event matches the "idle" event. */
719             if (p->verbose_p)
720               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
721                        blurb());
722
723             if (until_idle_p)
724               goto DONE;
725           }
726         else if (event.type == (si->sgi_saver_ext_event_number +
727                                 ScreenSaverEnd))
728           {
729             /* The SGI SCREEN_SAVER server extension has two event numbers,
730                and this event matches the "idle" event. */
731             if (p->verbose_p)
732               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
733                        blurb());
734             if (!until_idle_p)
735               goto DONE;
736           }
737         else
738 #endif /* HAVE_SGI_SAVER_EXTENSION */
739
740           /* Just some random event.  Let the Widgets handle it, if desired. */
741           dispatch_event (si, &event);
742       }
743     }
744  DONE:
745
746
747   /* If there's a user event on the queue, swallow it.
748      If we're using a server extension, and the user becomes active, we
749      get the extension event before the user event -- so the keypress or
750      motion or whatever is still on the queue.  This makes "unfade" not
751      work, because it sees that event, and bugs out.  (This problem
752      doesn't exhibit itself without an extension, because in that case,
753      there's only one event generated by user activity, not two.)
754    */
755   XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
756                    &event);
757
758
759   if (si->check_pointer_timer_id)
760     {
761       XtRemoveTimeOut (si->check_pointer_timer_id);
762       si->check_pointer_timer_id = 0;
763     }
764   if (si->timer_id)
765     {
766       XtRemoveTimeOut (si->timer_id);
767       si->timer_id = 0;
768     }
769
770   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
771     abort ();
772
773   return;
774 }
775
776
777 \f
778 /* Some crap for dealing with /proc/interrupts.
779
780    On Linux systems, it's possible to see the hardware interrupt count
781    associated with the keyboard.  We can therefore use that as another method
782    of detecting idleness.
783
784    Why is it a good idea to do this?  Because it lets us detect keyboard
785    activity that is not associated with X events.  For example, if the user
786    has switched to another virtual console, it's good for xscreensaver to not
787    be running graphics hacks on the (non-visible) X display.  The common
788    complaint that checking /proc/interrupts addresses is that the user is
789    playing Quake on a non-X console, and the GL hacks are perceptibly slowing
790    the game...
791
792    This is tricky for a number of reasons.
793
794      * First, we must be sure to only do this when running on an X server that
795        is on the local machine (because otherwise, we'd be reacting to the
796        wrong keyboard.)  The way we do this is by noting that the $DISPLAY is
797        pointing to display 0 on the local machine.  It *could* be that display
798        1 is also on the local machine (e.g., two X servers, each on a different
799        virtual-terminal) but it's also possible that screen 1 is an X terminal,
800        using this machine as the host.  So we can't take that chance.
801
802      * Second, one can only access these interrupt numbers in a completely
803        and utterly brain-damaged way.  You would think that one would use an
804        ioctl for this.  But no.  The ONLY way to get this information is to
805        open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
806        out of it TEXTUALLY.  Because this is Unix, and all the world's a file,
807        and the only real data type is the short-line sequence of ASCII bytes.
808
809        Now it's all well and good that the /proc/interrupts pseudo-file
810        exists; that's a clever idea, and a useful API for things that are
811        already textually oriented, like shell scripts, and users doing
812        interactive debugging sessions.  But to make a *C PROGRAM* open a file
813        and parse the textual representation of integers out of it is just
814        insane.
815
816      * Third, you can't just hold the file open, and fseek() back to the
817        beginning to get updated data!  If you do that, the data never changes.
818        And I don't want to call open() every five seconds, because I don't want
819        to risk going to disk for any inodes.  It turns out that if you dup()
820        it early, then each copy gets fresh data, so we can get around that in
821        this way (but for how many releases, one might wonder?)
822
823      * Fourth, the format of the output of the /proc/interrupts file is
824        undocumented, and has changed several times already!  In Linux 2.0.33,
825        even on a multiprocessor machine, it looks like this:
826
827           0:  309453991   timer
828           1:    4771729   keyboard
829    
830        but on later kernels with MP machines, it looks like this:
831
832                    CPU0       CPU1
833           0:    1671450    1672618    IO-APIC-edge  timer
834           1:      13037      13495    IO-APIC-edge  keyboard
835
836        Joy!  So how are we expected to parse that?  Well, this code doesn't
837        parse it: it saves the last line with the string "keyboard" in it, and
838        does a string-comparison to note when it has changed.
839
840    Thanks to Nat Friedman <nat@nat.org> for figuring out all of this crap.
841
842    Note that this only checks for lines with "keyboard" or "PS/2 Mouse" in
843    them.  If you have a serial mouse, it won't detect that, it will only detect
844    keyboard activity.  That's because there's no way to tell the difference
845    between a serial mouse and a general serial port, and it would be somewhat
846    unfortunate to have the screensaver turn off when the modem on COM1 burped.
847  */
848
849
850 #ifdef HAVE_PROC_INTERRUPTS
851
852 #define PROC_INTERRUPTS "/proc/interrupts"
853
854 Bool
855 query_proc_interrupts_available (saver_info *si, const char **why)
856 {
857   /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
858      "/proc/interrupts" file exists and is readable.
859    */
860   FILE *f;
861   if (why) *why = 0;
862
863   if (!display_is_on_console_p (si))
864     {
865       if (why) *why = "not on primary console";
866       return False;
867     }
868
869   f = fopen (PROC_INTERRUPTS, "r");
870   if (!f)
871     return False;
872
873   fclose (f);
874   return True;
875 }
876
877
878 static Bool
879 proc_interrupts_activity_p (saver_info *si)
880 {
881   static FILE *f0 = 0;
882   FILE *f1 = 0;
883   int fd;
884   static char last_kbd_line[255] = { 0, };
885   static char last_ptr_line[255] = { 0, };
886   char new_line[sizeof(last_kbd_line)];
887   Bool got_kbd = False, kbd_diff = False;
888   Bool got_ptr = False, ptr_diff = False;
889
890   if (!f0)
891     {
892       /* First time -- open the file. */
893       f0 = fopen (PROC_INTERRUPTS, "r");
894       if (!f0)
895         {
896           char buf[255];
897           sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
898           perror (buf);
899           goto FAIL;
900         }
901     }
902
903   if (f0 == (FILE *) -1)            /* means we got an error initializing. */
904     return False;
905
906   fd = dup (fileno (f0));
907   if (fd < 0)
908     {
909       char buf[255];
910       sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
911       perror (buf);
912       goto FAIL;
913     }
914
915   f1 = fdopen (fd, "r");
916   if (!f1)
917     {
918       char buf[255];
919       sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
920               PROC_INTERRUPTS);
921       perror (buf);
922       goto FAIL;
923     }
924
925   /* Actually, I'm unclear on why this fseek() is necessary, given the timing
926      of the dup() above, but it is. */
927   if (fseek (f1, 0, SEEK_SET) != 0)
928     {
929       char buf[255];
930       sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
931       perror (buf);
932       goto FAIL;
933     }
934
935   /* Now read through the pseudo-file until we find the "keyboard" line. */
936
937   while (fgets (new_line, sizeof(new_line)-1, f1))
938     {
939       if (!got_kbd && strstr (new_line, "keyboard"))
940         {
941           kbd_diff = (*last_kbd_line && !!strcmp (new_line, last_kbd_line));
942           strcpy (last_kbd_line, new_line);
943           got_kbd = True;
944         }
945       else if (!got_ptr && strstr (new_line, "PS/2 Mouse"))
946         {
947           ptr_diff = (*last_ptr_line && !!strcmp (new_line, last_ptr_line));
948           strcpy (last_ptr_line, new_line);
949           got_ptr = True;
950         }
951
952       if (got_kbd && got_ptr)
953         break;
954     }
955
956   if (got_kbd || got_ptr)
957     {
958       fclose (f1);
959       return (kbd_diff || ptr_diff);
960     }
961
962
963   /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse"
964      line in the file at all. */
965   fprintf (stderr, "%s: no keyboard or mouse data in %s?\n",
966            blurb(), PROC_INTERRUPTS);
967
968  FAIL:
969   if (f1)
970     fclose (f1);
971
972   if (f0 && f0 != (FILE *) -1)
973     fclose (f0);
974
975   f0 = (FILE *) -1;
976   return False;
977 }
978
979 #endif /* HAVE_PROC_INTERRUPTS */
980
981 \f
982 /* This timer goes off every few minutes, whether the user is idle or not,
983    to try and clean up anything that has gone wrong.
984
985    It calls disable_builtin_screensaver() so that if xset has been used,
986    or some other program (like xlock) has messed with the XSetScreenSaver()
987    settings, they will be set back to sensible values (if a server extension
988    is in use, messing with xlock can cause xscreensaver to never get a wakeup
989    event, and could cause monitor power-saving to occur, and all manner of
990    heinousness.)
991
992    If the screen is currently blanked, it raises the window, in case some
993    other window has been mapped on top of it.
994
995    If the screen is currently blanked, and there is no hack running, it
996    clears the window, in case there is an error message printed on it (we
997    don't want the error message to burn in.)
998  */
999
1000 static void
1001 watchdog_timer (XtPointer closure, XtIntervalId *id)
1002 {
1003   saver_info *si = (saver_info *) closure;
1004
1005   disable_builtin_screensaver (si, False);
1006
1007   if (si->screen_blanked_p)
1008     {
1009       Bool running_p = screenhack_running_p (si);
1010
1011       if (si->dbox_up_p)
1012         {
1013 #ifdef DEBUG_TIMERS
1014           if (si->prefs.verbose_p)
1015             fprintf (stderr, "%s: dialog box is up: not raising screen.\n",
1016                      blurb());
1017 #endif /* DEBUG_TIMERS */
1018         }
1019       else
1020         {
1021 #ifdef DEBUG_TIMERS
1022           if (si->prefs.verbose_p)
1023             fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
1024                      blurb(), (running_p ? "" : "and clearing "));
1025 #endif /* DEBUG_TIMERS */
1026
1027           raise_window (si, True, True, running_p);
1028         }
1029
1030       if (!monitor_powered_on_p (si))
1031         {
1032           if (si->prefs.verbose_p)
1033             fprintf (stderr,
1034                      "%s: server reports that monitor has powered down; "
1035                      "killing running hacks.\n", blurb());
1036           kill_screenhack (si);
1037         }
1038
1039       /* Re-schedule this timer.  The watchdog timer defaults to a bit less
1040          than the hack cycle period, but is never longer than one hour.
1041        */
1042       si->watchdog_id = 0;
1043       reset_watchdog_timer (si, True);
1044     }
1045 }
1046
1047
1048 void
1049 reset_watchdog_timer (saver_info *si, Bool on_p)
1050 {
1051   saver_preferences *p = &si->prefs;
1052
1053   if (si->watchdog_id)
1054     {
1055       XtRemoveTimeOut (si->watchdog_id);
1056       si->watchdog_id = 0;
1057     }
1058
1059   if (on_p && p->watchdog_timeout)
1060     {
1061       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
1062                                          watchdog_timer, (XtPointer) si);
1063
1064 #ifdef DEBUG_TIMERS
1065       if (p->verbose_p)
1066         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
1067                  blurb(), p->watchdog_timeout, si->watchdog_id);
1068 #endif /* DEBUG_TIMERS */
1069
1070     }
1071 }