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