http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.02.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 == %d\n",
268                  blurb(), 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 %d:%02d:%02d!\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, window;
709             int x, y;
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%x",
742                          blurb(), i, type, (unsigned long) window);
743                 if (x == -1)
744                   fprintf (stderr, "\n");
745                 else
746                   fprintf (stderr, " at %d,%d.\n", x, y);
747               }
748           }
749
750         /* If any widgets want to handle this event, let them. */
751         dispatch_event (si, &event);
752
753         /* We got a user event.
754            If we're waiting for the user to become active, this is it.
755            If we're waiting until the user becomes idle, reset the timers
756            (since now we have longer to wait.)
757          */
758         if (!until_idle_p)
759           {
760             if (si->demoing_p &&
761                 (event.xany.type == MotionNotify ||
762                  event.xany.type == KeyRelease))
763               /* When we're demoing a single hack, mouse motion doesn't
764                  cause deactivation.  Only clicks and keypresses do. */
765               ;
766             else
767               /* If we're not demoing, then any activity causes deactivation.
768                */
769               goto DONE;
770           }
771         else
772           reset_timers (si);
773
774         break;
775
776       default:
777
778 #ifdef HAVE_MIT_SAVER_EXTENSION
779         if (event.type == si->mit_saver_ext_event_number)
780           {
781             /* This event's number is that of the MIT-SCREEN-SAVER server
782                extension.  This extension has one event number, and the event
783                itself contains sub-codes that say what kind of event it was
784                (an "idle" or "not-idle" event.)
785              */
786             XScreenSaverNotifyEvent *sevent =
787               (XScreenSaverNotifyEvent *) &event;
788             if (sevent->state == ScreenSaverOn)
789               {
790                 int i = 0;
791                 if (p->verbose_p)
792                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
793                            blurb());
794
795                 /* Get the "real" server window(s) out of the way as soon
796                    as possible. */
797                 for (i = 0; i < si->nscreens; i++)
798                   {
799                     saver_screen_info *ssi = &si->screens[i];
800                     if (ssi->server_mit_saver_window &&
801                         window_exists_p (si->dpy,
802                                          ssi->server_mit_saver_window))
803                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
804                   }
805
806                 if (sevent->kind != ScreenSaverExternal)
807                   {
808                     fprintf (stderr,
809                          "%s: ScreenSaverOn event wasn't of type External!\n",
810                              blurb());
811                   }
812
813                 if (until_idle_p)
814                   goto DONE;
815               }
816             else if (sevent->state == ScreenSaverOff)
817               {
818                 if (p->verbose_p)
819                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
820                            blurb());
821                 if (!until_idle_p)
822                   goto DONE;
823               }
824             else
825               fprintf (stderr,
826                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
827                        blurb(), sevent->state);
828           }
829         else
830
831 #endif /* HAVE_MIT_SAVER_EXTENSION */
832
833
834 #ifdef HAVE_SGI_SAVER_EXTENSION
835         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
836           {
837             /* The SGI SCREEN_SAVER server extension has two event numbers,
838                and this event matches the "idle" event. */
839             if (p->verbose_p)
840               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
841                        blurb());
842
843             if (until_idle_p)
844               goto DONE;
845           }
846         else if (event.type == (si->sgi_saver_ext_event_number +
847                                 ScreenSaverEnd))
848           {
849             /* The SGI SCREEN_SAVER server extension has two event numbers,
850                and this event matches the "idle" event. */
851             if (p->verbose_p)
852               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
853                        blurb());
854             if (!until_idle_p)
855               goto DONE;
856           }
857         else
858 #endif /* HAVE_SGI_SAVER_EXTENSION */
859
860           /* Just some random event.  Let the Widgets handle it, if desired. */
861           dispatch_event (si, &event);
862       }
863     }
864  DONE:
865
866
867   /* If there's a user event on the queue, swallow it.
868      If we're using a server extension, and the user becomes active, we
869      get the extension event before the user event -- so the keypress or
870      motion or whatever is still on the queue.  This makes "unfade" not
871      work, because it sees that event, and bugs out.  (This problem
872      doesn't exhibit itself without an extension, because in that case,
873      there's only one event generated by user activity, not two.)
874    */
875   if (!until_idle_p && si->locked_p)
876     swallow_unlock_typeahead_events (si, &event);
877   else
878     while (XCheckMaskEvent (si->dpy,
879                             (KeyPressMask|ButtonPressMask|PointerMotionMask),
880                      &event))
881       ;
882
883
884   if (si->check_pointer_timer_id)
885     {
886       XtRemoveTimeOut (si->check_pointer_timer_id);
887       si->check_pointer_timer_id = 0;
888     }
889   if (si->timer_id)
890     {
891       XtRemoveTimeOut (si->timer_id);
892       si->timer_id = 0;
893     }
894
895   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
896     abort ();
897
898   return;
899 }
900
901
902 \f
903 /* Some crap for dealing with /proc/interrupts.
904
905    On Linux systems, it's possible to see the hardware interrupt count
906    associated with the keyboard.  We can therefore use that as another method
907    of detecting idleness.
908
909    Why is it a good idea to do this?  Because it lets us detect keyboard
910    activity that is not associated with X events.  For example, if the user
911    has switched to another virtual console, it's good for xscreensaver to not
912    be running graphics hacks on the (non-visible) X display.  The common
913    complaint that checking /proc/interrupts addresses is that the user is
914    playing Quake on a non-X console, and the GL hacks are perceptibly slowing
915    the game...
916
917    This is tricky for a number of reasons.
918
919      * First, we must be sure to only do this when running on an X server that
920        is on the local machine (because otherwise, we'd be reacting to the
921        wrong keyboard.)  The way we do this is by noting that the $DISPLAY is
922        pointing to display 0 on the local machine.  It *could* be that display
923        1 is also on the local machine (e.g., two X servers, each on a different
924        virtual-terminal) but it's also possible that screen 1 is an X terminal,
925        using this machine as the host.  So we can't take that chance.
926
927      * Second, one can only access these interrupt numbers in a completely
928        and utterly brain-damaged way.  You would think that one would use an
929        ioctl for this.  But no.  The ONLY way to get this information is to
930        open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
931        out of it TEXTUALLY.  Because this is Unix, and all the world's a file,
932        and the only real data type is the short-line sequence of ASCII bytes.
933
934        Now it's all well and good that the /proc/interrupts pseudo-file
935        exists; that's a clever idea, and a useful API for things that are
936        already textually oriented, like shell scripts, and users doing
937        interactive debugging sessions.  But to make a *C PROGRAM* open a file
938        and parse the textual representation of integers out of it is just
939        insane.
940
941      * Third, you can't just hold the file open, and fseek() back to the
942        beginning to get updated data!  If you do that, the data never changes.
943        And I don't want to call open() every five seconds, because I don't want
944        to risk going to disk for any inodes.  It turns out that if you dup()
945        it early, then each copy gets fresh data, so we can get around that in
946        this way (but for how many releases, one might wonder?)
947
948      * Fourth, the format of the output of the /proc/interrupts file is
949        undocumented, and has changed several times already!  In Linux 2.0.33,
950        even on a multiprocessor machine, it looks like this:
951
952           0:  309453991   timer
953           1:    4771729   keyboard
954    
955        but on later kernels with MP machines, it looks like this:
956
957                    CPU0       CPU1
958           0:    1671450    1672618    IO-APIC-edge  timer
959           1:      13037      13495    IO-APIC-edge  keyboard
960
961        Joy!  So how are we expected to parse that?  Well, this code doesn't
962        parse it: it saves the last line with the string "keyboard" in it, and
963        does a string-comparison to note when it has changed.
964
965    Thanks to Nat Friedman <nat@nat.org> for figuring out all of this crap.
966
967    Note that this only checks for lines with "keyboard" or "PS/2 Mouse" in
968    them.  If you have a serial mouse, it won't detect that, it will only detect
969    keyboard activity.  That's because there's no way to tell the difference
970    between a serial mouse and a general serial port, and it would be somewhat
971    unfortunate to have the screensaver turn off when the modem on COM1 burped.
972  */
973
974
975 #ifdef HAVE_PROC_INTERRUPTS
976
977 #define PROC_INTERRUPTS "/proc/interrupts"
978
979 Bool
980 query_proc_interrupts_available (saver_info *si, const char **why)
981 {
982   /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
983      "/proc/interrupts" file exists and is readable.
984    */
985   FILE *f;
986   if (why) *why = 0;
987
988   if (!display_is_on_console_p (si))
989     {
990       if (why) *why = "not on primary console";
991       return False;
992     }
993
994   f = fopen (PROC_INTERRUPTS, "r");
995   if (!f)
996     return False;
997
998   fclose (f);
999   return True;
1000 }
1001
1002
1003 static Bool
1004 proc_interrupts_activity_p (saver_info *si)
1005 {
1006   static FILE *f0 = 0;
1007   FILE *f1 = 0;
1008   int fd;
1009   static char last_kbd_line[255] = { 0, };
1010   static char last_ptr_line[255] = { 0, };
1011   char new_line[sizeof(last_kbd_line)];
1012   Bool checked_kbd = False, kbd_changed = False;
1013   Bool checked_ptr = False, ptr_changed = False;
1014
1015   if (!f0)
1016     {
1017       /* First time -- open the file. */
1018       f0 = fopen (PROC_INTERRUPTS, "r");
1019       if (!f0)
1020         {
1021           char buf[255];
1022           sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
1023           perror (buf);
1024           goto FAIL;
1025         }
1026     }
1027
1028   if (f0 == (FILE *) -1)            /* means we got an error initializing. */
1029     return False;
1030
1031   fd = dup (fileno (f0));
1032   if (fd < 0)
1033     {
1034       char buf[255];
1035       sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
1036       perror (buf);
1037       goto FAIL;
1038     }
1039
1040   f1 = fdopen (fd, "r");
1041   if (!f1)
1042     {
1043       char buf[255];
1044       sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
1045               PROC_INTERRUPTS);
1046       perror (buf);
1047       goto FAIL;
1048     }
1049
1050   /* Actually, I'm unclear on why this fseek() is necessary, given the timing
1051      of the dup() above, but it is. */
1052   if (fseek (f1, 0, SEEK_SET) != 0)
1053     {
1054       char buf[255];
1055       sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
1056       perror (buf);
1057       goto FAIL;
1058     }
1059
1060   /* Now read through the pseudo-file until we find the "keyboard" line. */
1061
1062   while (fgets (new_line, sizeof(new_line)-1, f1))
1063     {
1064       if (!checked_kbd && strstr (new_line, "keyboard"))
1065         {
1066           kbd_changed = (*last_kbd_line && !!strcmp (new_line, last_kbd_line));
1067           strcpy (last_kbd_line, new_line);
1068           checked_kbd = True;
1069         }
1070       else if (!checked_ptr && strstr (new_line, "PS/2 Mouse"))
1071         {
1072           ptr_changed = (*last_ptr_line && !!strcmp (new_line, last_ptr_line));
1073           strcpy (last_ptr_line, new_line);
1074           checked_ptr = True;
1075         }
1076
1077       if (checked_kbd && checked_ptr)
1078         break;
1079     }
1080
1081   if (checked_kbd || checked_ptr)
1082     {
1083       fclose (f1);
1084
1085       if (si->prefs.debug_p && (kbd_changed || ptr_changed))
1086         fprintf (stderr, "%s: /proc/interrupts activity: %s\n",
1087                  blurb(),
1088                  ((kbd_changed && ptr_changed) ? "mouse and kbd" :
1089                   kbd_changed ? "kbd" :
1090                   ptr_changed ? "mouse" : "ERR"));
1091
1092       return (kbd_changed || ptr_changed);
1093     }
1094
1095
1096   /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse"
1097      line in the file at all. */
1098   fprintf (stderr, "%s: no keyboard or mouse data in %s?\n",
1099            blurb(), PROC_INTERRUPTS);
1100
1101  FAIL:
1102   if (f1)
1103     fclose (f1);
1104
1105   if (f0 && f0 != (FILE *) -1)
1106     fclose (f0);
1107
1108   f0 = (FILE *) -1;
1109   return False;
1110 }
1111
1112 #endif /* HAVE_PROC_INTERRUPTS */
1113
1114 \f
1115 /* This timer goes off every few minutes, whether the user is idle or not,
1116    to try and clean up anything that has gone wrong.
1117
1118    It calls disable_builtin_screensaver() so that if xset has been used,
1119    or some other program (like xlock) has messed with the XSetScreenSaver()
1120    settings, they will be set back to sensible values (if a server extension
1121    is in use, messing with xlock can cause xscreensaver to never get a wakeup
1122    event, and could cause monitor power-saving to occur, and all manner of
1123    heinousness.)
1124
1125    If the screen is currently blanked, it raises the window, in case some
1126    other window has been mapped on top of it.
1127
1128    If the screen is currently blanked, and there is no hack running, it
1129    clears the window, in case there is an error message printed on it (we
1130    don't want the error message to burn in.)
1131  */
1132
1133 static void
1134 watchdog_timer (XtPointer closure, XtIntervalId *id)
1135 {
1136   saver_info *si = (saver_info *) closure;
1137   saver_preferences *p = &si->prefs;
1138
1139   disable_builtin_screensaver (si, False);
1140
1141   /* If the DPMS settings on the server have changed, change them back to
1142      what ~/.xscreensaver says they should be. */
1143   sync_server_dpms_settings (si->dpy,
1144                              (p->dpms_enabled_p  &&
1145                               p->mode != DONT_BLANK),
1146                              p->dpms_standby / 1000,
1147                              p->dpms_suspend / 1000,
1148                              p->dpms_off / 1000,
1149                              False);
1150
1151   if (si->screen_blanked_p)
1152     {
1153       Bool running_p = screenhack_running_p (si);
1154
1155       if (si->dbox_up_p)
1156         {
1157           if (si->prefs.debug_p)
1158             fprintf (stderr, "%s: dialog box is up: not raising screen.\n",
1159                      blurb());
1160         }
1161       else
1162         {
1163           if (si->prefs.debug_p)
1164             fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
1165                      blurb(), (running_p ? "" : "and clearing "));
1166
1167           raise_window (si, True, True, running_p);
1168         }
1169
1170       if (screenhack_running_p (si) &&
1171           !monitor_powered_on_p (si))
1172         {
1173           if (si->prefs.verbose_p)
1174             fprintf (stderr,
1175                      "%s: X says monitor has powered down; "
1176                      "killing running hacks.\n", blurb());
1177           kill_screenhack (si);
1178         }
1179
1180       /* Re-schedule this timer.  The watchdog timer defaults to a bit less
1181          than the hack cycle period, but is never longer than one hour.
1182        */
1183       si->watchdog_id = 0;
1184       reset_watchdog_timer (si, True);
1185     }
1186 }
1187
1188
1189 void
1190 reset_watchdog_timer (saver_info *si, Bool on_p)
1191 {
1192   saver_preferences *p = &si->prefs;
1193
1194   if (si->watchdog_id)
1195     {
1196       XtRemoveTimeOut (si->watchdog_id);
1197       si->watchdog_id = 0;
1198     }
1199
1200   if (on_p && p->watchdog_timeout)
1201     {
1202       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
1203                                          watchdog_timer, (XtPointer) si);
1204
1205       if (p->debug_p)
1206         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
1207                  blurb(), p->watchdog_timeout, si->watchdog_id);
1208     }
1209 }