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