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