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