5f1bd418fc3b82ec53c6925d71a73181a6b971f4
[xscreensaver] / driver / timers.c
1 /* timers.c --- detecting when the user is idle, and other timer-related tasks.
2  * xscreensaver, Copyright (c) 1991-1997, 1998
3  *  Jamie Zawinski <jwz@jwz.org>
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  */
13
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17
18 /* #define DEBUG_TIMERS */
19
20 #include <stdio.h>
21 #include <X11/Xlib.h>
22 #include <X11/Intrinsic.h>
23 #include <X11/Xos.h>
24 #ifdef HAVE_XMU
25 # ifndef VMS
26 #  include <X11/Xmu/Error.h>
27 # else /* VMS */
28 #  include <Xmu/Error.h>
29 # endif /* VMS */
30 # else /* !HAVE_XMU */
31 # include "xmu.h"
32 #endif /* !HAVE_XMU */
33
34 #ifdef HAVE_XIDLE_EXTENSION
35 #include <X11/extensions/xidle.h>
36 #endif /* HAVE_XIDLE_EXTENSION */
37
38 #ifdef HAVE_MIT_SAVER_EXTENSION
39 #include <X11/extensions/scrnsaver.h>
40 #endif /* HAVE_MIT_SAVER_EXTENSION */
41
42 #ifdef HAVE_SGI_SAVER_EXTENSION
43 #include <X11/extensions/XScreenSaver.h>
44 #endif /* HAVE_SGI_SAVER_EXTENSION */
45
46 #ifdef HAVE_XHPDISABLERESET
47 # include <X11/XHPlib.h>
48   extern Bool hp_locked_p;      /* from windows.c */
49 #endif /* HAVE_XHPDISABLERESET */
50
51 #include "xscreensaver.h"
52
53 #ifdef HAVE_PROC_INTERRUPTS
54 static Bool proc_interrupts_activity_p (saver_info *si);
55 #endif /* HAVE_PROC_INTERRUPTS */
56
57
58 void
59 idle_timer (XtPointer closure, XtIntervalId *id)
60 {
61   saver_info *si = (saver_info *) closure;
62
63   /* What an amazingly shitty design.  Not only does Xt execute timeout
64      events from XtAppNextEvent() instead of from XtDispatchEvent(), but
65      there is no way to tell Xt to block until there is an X event OR a
66      timeout happens.  Once your timeout proc is called, XtAppNextEvent()
67      still won't return until a "real" X event comes in.
68
69      So this function pushes a stupid, gratuitous, unnecessary event back
70      on the event queue to force XtAppNextEvent to return Right Fucking Now.
71      When the code in sleep_until_idle() sees an event of type XAnyEvent,
72      which the server never generates, it knows that a timeout has occurred.
73    */
74   XEvent fake_event;
75   fake_event.type = 0;  /* XAnyEvent type, ignored. */
76   fake_event.xany.display = si->dpy;
77   fake_event.xany.window  = 0;
78   XPutBackEvent (si->dpy, &fake_event);
79 }
80
81
82 static void
83 schedule_wakeup_event (saver_info *si, Time when, Bool verbose_p)
84 {
85   /* Wake up periodically to ask the server if we are idle. */
86   si->timer_id = XtAppAddTimeOut (si->app, when, idle_timer,
87                                   (XtPointer) si);
88
89 #ifdef DEBUG_TIMERS
90   if (verbose_p)
91     fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
92              blurb(), when, si->timer_id);
93 #endif /* DEBUG_TIMERS */
94 }
95
96
97 static void
98 notice_events (saver_info *si, Window window, Bool top_p)
99 {
100   saver_preferences *p = &si->prefs;
101   XWindowAttributes attrs;
102   unsigned long events;
103   Window root, parent, *kids;
104   unsigned int nkids;
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   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   XSelectInput (si->dpy, window, SubstructureNotifyMask | events);
133
134   if (top_p && p->verbose_p && (events & KeyPressMask))
135     {
136       /* Only mention one window per tree (hack hack). */
137       fprintf (stderr, "%s: selected KeyPress on 0x%lX\n", blurb(),
138                (unsigned long) window);
139       top_p = False;
140     }
141
142   if (kids)
143     {
144       while (nkids)
145         notice_events (si, kids [--nkids], top_p);
146       XFree ((char *) kids);
147     }
148 }
149
150
151 int
152 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
153 {
154   /* When we notice a window being created, we spawn a timer that waits
155      30 seconds or so, and then selects events on that window.  This error
156      handler is used so that we can cope with the fact that the window
157      may have been destroyed <30 seconds after it was created.
158    */
159   if (error->error_code == BadWindow ||
160       error->error_code == BadMatch ||
161       error->error_code == BadDrawable)
162     return 0;
163   else
164     return saver_ehandler (dpy, error);
165 }
166
167
168 struct notice_events_timer_arg {
169   saver_info *si;
170   Window w;
171 };
172
173 static void
174 notice_events_timer (XtPointer closure, XtIntervalId *id)
175 {
176   struct notice_events_timer_arg *arg =
177     (struct notice_events_timer_arg *) closure;
178
179   XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
180
181   saver_info *si = arg->si;
182   Window window = arg->w;
183
184   free(arg);
185   notice_events (si, window, True);
186   XSync (si->dpy, False);
187   XSetErrorHandler (old_handler);
188 }
189
190 void
191 start_notice_events_timer (saver_info *si, Window w, Bool verbose_p)
192 {
193   saver_preferences *p = &si->prefs;
194   struct notice_events_timer_arg *arg =
195     (struct notice_events_timer_arg *) malloc(sizeof(*arg));
196   arg->si = si;
197   arg->w = w;
198   XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer,
199                    (XtPointer) arg);
200
201   if (verbose_p)
202     fprintf (stderr, "%s: starting notice_events_timer for 0x%X (%lu)\n",
203              blurb(), (unsigned int) w, p->notice_events_timeout);
204 }
205
206
207 /* When the screensaver is active, this timer will periodically change
208    the running program.
209  */
210 void
211 cycle_timer (XtPointer closure, XtIntervalId *id)
212 {
213   saver_info *si = (saver_info *) closure;
214   saver_preferences *p = &si->prefs;
215   Time how_long = p->cycle;
216   if (si->dbox_up_p)
217     {
218       if (p->verbose_p)
219         fprintf (stderr, "%s: dialog box up; delaying hack change.\n",
220                  blurb());
221       how_long = 30000; /* 30 secs */
222     }
223   else
224     {
225       maybe_reload_init_file (si);
226       if (p->verbose_p)
227         fprintf (stderr, "%s: changing graphics hacks.\n", blurb());
228       kill_screenhack (si);
229       spawn_screenhack (si, False);
230     }
231   si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
232                                   (XtPointer) si);
233
234 #ifdef DEBUG_TIMERS
235   if (p->verbose_p)
236     fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n",
237             blurb(), how_long, si->cycle_id);
238 #endif /* DEBUG_TIMERS */
239 }
240
241
242 void
243 activate_lock_timer (XtPointer closure, XtIntervalId *id)
244 {
245   saver_info *si = (saver_info *) closure;
246   saver_preferences *p = &si->prefs;
247
248   if (p->verbose_p)
249     fprintf (stderr, "%s: timed out; activating lock\n", blurb());
250   si->locked_p = True;
251
252 #ifdef HAVE_XHPDISABLERESET
253   if (!hp_locked_p)
254     {
255       XHPDisableReset (si->dpy);        /* turn off C-Sh-Reset */
256       hp_locked_p = True;
257     }
258 #endif
259 }
260
261
262 /* Call this when user activity (or "simulated" activity) has been noticed.
263  */
264 static void
265 reset_timers (saver_info *si)
266 {
267   saver_preferences *p = &si->prefs;
268   if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
269     return;
270
271   if (si->timer_id)
272     {
273 #ifdef DEBUG_TIMERS
274       if (p->verbose_p)
275         fprintf (stderr, "%s: killing idle_timer  (%ld, %ld)\n",
276                  blurb(), p->timeout, si->timer_id);
277 #endif /* DEBUG_TIMERS */
278       XtRemoveTimeOut (si->timer_id);
279     }
280
281   schedule_wakeup_event (si, p->timeout, p->verbose_p); /* sets si->timer_id */
282
283   if (si->cycle_id) abort ();   /* no cycle timer when inactive */
284
285   si->last_activity_time = time ((time_t *) 0);
286 }
287
288
289 /* When we aren't using a server extension, this timer is used to periodically
290    wake up and poll the mouse position, which is possibly more reliable than
291    selecting motion events on every window.
292  */
293 static void
294 check_pointer_timer (XtPointer closure, XtIntervalId *id)
295 {
296   int i;
297   saver_info *si = (saver_info *) closure;
298   saver_preferences *p = &si->prefs;
299   Bool active_p = False;
300
301   if (!si->using_proc_interrupts &&
302       (si->using_xidle_extension ||
303        si->using_mit_saver_extension ||
304        si->using_sgi_saver_extension))
305     /* If an extension is in use, we should not be polling the mouse.
306        Unless we're also checking /proc/interrupts, in which case, we should.
307      */
308     abort ();
309
310   si->check_pointer_timer_id =
311     XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
312                      (XtPointer) si);
313
314   for (i = 0; i < si->nscreens; i++)
315     {
316       saver_screen_info *ssi = &si->screens[i];
317       Window root, child;
318       int root_x, root_y, x, y;
319       unsigned int mask;
320
321       XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
322                      &root_x, &root_y, &x, &y, &mask);
323
324       if (root_x == ssi->poll_mouse_last_root_x &&
325           root_y == ssi->poll_mouse_last_root_y &&
326           child  == ssi->poll_mouse_last_child &&
327           mask   == ssi->poll_mouse_last_mask)
328         continue;
329
330       active_p = True;
331
332 #ifdef DEBUG_TIMERS
333       if (p->verbose_p)
334         if (root_x == ssi->poll_mouse_last_root_x &&
335             root_y == ssi->poll_mouse_last_root_y &&
336             child  == ssi->poll_mouse_last_child)
337           fprintf (stderr, "%s: modifiers changed at %s on screen %d.\n",
338                    blurb(), timestring(), i);
339         else
340           fprintf (stderr, "%s: pointer moved at %s on screen %d.\n",
341                    blurb(), timestring(), i);
342 #endif /* DEBUG_TIMERS */
343
344       si->last_activity_screen    = ssi;
345       ssi->poll_mouse_last_root_x = root_x;
346       ssi->poll_mouse_last_root_y = root_y;
347       ssi->poll_mouse_last_child  = child;
348       ssi->poll_mouse_last_mask   = mask;
349     }
350
351 #ifdef HAVE_PROC_INTERRUPTS
352   if (!active_p &&
353       si->using_proc_interrupts &&
354       proc_interrupts_activity_p (si))
355     {
356 # ifdef DEBUG_TIMERS
357       if (p->verbose_p)
358         fprintf (stderr, "%s: /proc/interrupts activity at %s.\n",
359                  blurb(), timestring());
360 # endif /* DEBUG_TIMERS */
361       active_p = True;
362     }
363 #endif /* HAVE_PROC_INTERRUPTS */
364
365   if (active_p)
366     reset_timers (si);
367 }
368
369
370 static void
371 dispatch_event (saver_info *si, XEvent *event)
372 {
373   /* If this is for the splash dialog, pass it along.
374      Note that the password dialog is handled with its own event loop,
375      so events for that window will never come through here.
376    */
377   if (si->splash_dialog && event->xany.window == si->splash_dialog)
378     handle_splash_event (si, event);
379
380   XtDispatchEvent (event);
381 }
382
383
384 /* methods of detecting idleness:
385
386       explicitly informed by SGI SCREEN_SAVER server event;
387       explicitly informed by MIT-SCREEN-SAVER server event;
388       poll server idle time with XIDLE extension;
389       select events on all windows, and note absence of recent events;
390       note that /proc/interrupts has not changed in a while;
391       activated by clientmessage.
392
393    methods of detecting non-idleness:
394
395       read events on the xscreensaver window;
396       explicitly informed by SGI SCREEN_SAVER server event;
397       explicitly informed by MIT-SCREEN-SAVER server event;
398       select events on all windows, and note events on any of them;
399       note that /proc/interrupts has changed;
400       deactivated by clientmessage.
401
402    I trust that explains why this function is a big hairy mess.
403  */
404 void
405 sleep_until_idle (saver_info *si, Bool until_idle_p)
406 {
407   saver_preferences *p = &si->prefs;
408   XEvent event;
409
410   /* We need to select events on all windows if we're not using any extensions.
411      Otherwise, we don't need to. */
412   Bool scanning_all_windows = !(si->using_xidle_extension ||
413                                 si->using_mit_saver_extension ||
414                                 si->using_sgi_saver_extension);
415
416   /* We need to periodically wake up and check for idleness if we're not using
417      any extensions, or if we're using the XIDLE extension.  The other two
418      extensions explicitly deliver events when we go idle/non-idle, so we
419      don't need to poll. */
420   Bool polling_for_idleness = !(si->using_mit_saver_extension ||
421                                 si->using_sgi_saver_extension);
422
423   /* Whether we need to periodically wake up and check to see if the mouse has
424      moved.  We only need to do this when not using any extensions.  The reason
425      this isn't the same as `polling_for_idleness' is that the "idleness" poll
426      can happen (for example) 5 minutes from now, whereas the mouse-position
427      poll should happen with low periodicity.  We don't need to poll the mouse
428      position with the XIDLE extension, but we do need to periodically wake up
429      and query the server with that extension.  For our purposes, polling
430      /proc/interrupts is just like polling the mouse position.  It has to
431      happen on the same kind of schedule. */
432   Bool polling_mouse_position = (si->using_proc_interrupts ||
433                                  !(si->using_xidle_extension ||
434                                    si->using_mit_saver_extension ||
435                                    si->using_sgi_saver_extension));
436
437   if (until_idle_p)
438     {
439       if (polling_for_idleness)
440         /* This causes a no-op event to be delivered to us in a while, so that
441            we come back around through the event loop again.  Use of this timer
442            is economical: for example, if the screensaver should come on in 5
443            minutes, and the user has been idle for 2 minutes, then this
444            timeout will go off no sooner than 3 minutes from now.  */
445         schedule_wakeup_event (si, p->timeout, p->verbose_p);
446
447       if (polling_mouse_position)
448         /* Check to see if the mouse has moved, and set up a repeating timer
449            to do so periodically (typically, every 5 seconds.) */
450         check_pointer_timer ((XtPointer) si, 0);
451     }
452
453   while (1)
454     {
455       XtAppNextEvent (si->app, &event);
456
457       switch (event.xany.type) {
458       case 0:           /* our synthetic "timeout" event has been signalled */
459         if (until_idle_p)
460           {
461             Time idle;
462 #ifdef HAVE_XIDLE_EXTENSION
463             if (si->using_xidle_extension)
464               {
465                 /* The XIDLE extension uses the synthetic event to prod us into
466                    re-asking the server how long the user has been idle. */
467                 if (! XGetIdleTime (si->dpy, &idle))
468                   {
469                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
470                     saver_exit (si, 1, 0);
471                   }
472               }
473             else
474 #endif /* HAVE_XIDLE_EXTENSION */
475 #ifdef HAVE_MIT_SAVER_EXTENSION
476               if (si->using_mit_saver_extension)
477                 {
478                   /* We don't need to do anything in this case - the synthetic
479                      event isn't necessary, as we get sent specific events
480                      to wake us up.  In fact, this event generally shouldn't
481                      be being delivered when the MIT extension is in use. */
482                   idle = 0;
483                 }
484             else
485 #endif /* HAVE_MIT_SAVER_EXTENSION */
486 #ifdef HAVE_SGI_SAVER_EXTENSION
487               if (si->using_sgi_saver_extension)
488                 {
489                   /* We don't need to do anything in this case - the synthetic
490                      event isn't necessary, as we get sent specific events
491                      to wake us up.  In fact, this event generally shouldn't
492                      be being delivered when the SGI extension is in use. */
493                   idle = 0;
494                 }
495             else
496 #endif /* HAVE_SGI_SAVER_EXTENSION */
497               {
498                 /* Otherwise, no server extension is in use.  The synthetic
499                    event was to tell us to wake up and see if the user is now
500                    idle.  Compute the amount of idle time by comparing the
501                    `last_activity_time' to the wall clock.  The l_a_t was set
502                    by calling `reset_timers()', which is called only in only
503                    two situations: when polling the mouse position has revealed
504                    the the mouse has moved (user activity) or when we have read
505                    an event (again, user activity.)
506                  */
507                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
508               }
509
510             if (idle >= p->timeout)
511               {
512                 /* Look, we've been idle long enough.  We're done. */
513                 goto DONE;
514               }
515             else
516               {
517                 /* The event went off, but it turns out that the user has not
518                    yet been idle for long enough.  So re-signal the event.
519                    */
520                 if (polling_for_idleness)
521                   schedule_wakeup_event (si, p->timeout - idle, p->verbose_p);
522               }
523           }
524         break;
525
526       case ClientMessage:
527         if (handle_clientmessage (si, &event, until_idle_p))
528           goto DONE;
529         break;
530
531       case CreateNotify:
532         /* A window has been created on the screen somewhere.  If we're
533            supposed to scan all windows for events, prepare this window. */
534         if (scanning_all_windows)
535           {
536             Window w = event.xcreatewindow.window;
537 #ifdef DEBUG_TIMERS
538             start_notice_events_timer (si, w, p->verbose_p);
539 #else  /* !DEBUG_TIMERS */
540             start_notice_events_timer (si, w, False);
541 #endif /* !DEBUG_TIMERS */
542           }
543         break;
544
545       case KeyPress:
546       case KeyRelease:
547       case ButtonPress:
548       case ButtonRelease:
549       case MotionNotify:
550
551 #ifdef DEBUG_TIMERS
552         if (p->verbose_p)
553           {
554             if (event.xany.type == MotionNotify)
555               fprintf (stderr,"%s: MotionNotify at %s\n",blurb(),timestring());
556             else if (event.xany.type == KeyPress)
557               fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(),
558                        (unsigned int) event.xkey.window, timestring ());
559             else if (event.xany.type == ButtonPress)
560               fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(),
561                        (unsigned int) event.xbutton.window, timestring ());
562           }
563 #endif /* DEBUG_TIMERS */
564
565         /* If any widgets want to handle this event, let them. */
566         dispatch_event (si, &event);
567
568         /* We got a user event.
569            If we're waiting for the user to become active, this is it.
570            If we're waiting until the user becomes idle, reset the timers
571            (since now we have longer to wait.)
572          */
573         if (!until_idle_p)
574           {
575             if (si->demoing_p &&
576                 (event.xany.type == MotionNotify ||
577                  event.xany.type == KeyRelease))
578               /* When we're demoing a single hack, mouse motion doesn't
579                  cause deactivation.  Only clicks and keypresses do. */
580               ;
581             else
582               /* If we're not demoing, then any activity causes deactivation.
583                */
584               goto DONE;
585           }
586         else
587           reset_timers (si);
588
589         break;
590
591       default:
592
593 #ifdef HAVE_MIT_SAVER_EXTENSION
594         if (event.type == si->mit_saver_ext_event_number)
595           {
596             /* This event's number is that of the MIT-SCREEN-SAVER server
597                extension.  This extension has one event number, and the event
598                itself contains sub-codes that say what kind of event it was
599                (an "idle" or "not-idle" event.)
600              */
601             XScreenSaverNotifyEvent *sevent =
602               (XScreenSaverNotifyEvent *) &event;
603             if (sevent->state == ScreenSaverOn)
604               {
605                 int i = 0;
606                 if (p->verbose_p)
607                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
608                            blurb());
609
610                 /* Get the "real" server window(s) out of the way as soon
611                    as possible. */
612                 for (i = 0; i < si->nscreens; i++)
613                   {
614                     saver_screen_info *ssi = &si->screens[i];
615                     if (ssi->server_mit_saver_window &&
616                         window_exists_p (si->dpy,
617                                          ssi->server_mit_saver_window))
618                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
619                   }
620
621                 if (sevent->kind != ScreenSaverExternal)
622                   {
623                     fprintf (stderr,
624                          "%s: ScreenSaverOn event wasn't of type External!\n",
625                              blurb());
626                   }
627
628                 if (until_idle_p)
629                   goto DONE;
630               }
631             else if (sevent->state == ScreenSaverOff)
632               {
633                 if (p->verbose_p)
634                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
635                            blurb());
636                 if (!until_idle_p)
637                   goto DONE;
638               }
639             else
640               fprintf (stderr,
641                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
642                        blurb(), sevent->state);
643           }
644         else
645
646 #endif /* HAVE_MIT_SAVER_EXTENSION */
647
648
649 #ifdef HAVE_SGI_SAVER_EXTENSION
650         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
651           {
652             /* The SGI SCREEN_SAVER server extension has two event numbers,
653                and this event matches the "idle" event. */
654             if (p->verbose_p)
655               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
656                        blurb());
657
658             if (until_idle_p)
659               goto DONE;
660           }
661         else if (event.type == (si->sgi_saver_ext_event_number +
662                                 ScreenSaverEnd))
663           {
664             /* The SGI SCREEN_SAVER server extension has two event numbers,
665                and this event matches the "idle" event. */
666             if (p->verbose_p)
667               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
668                        blurb());
669             if (!until_idle_p)
670               goto DONE;
671           }
672         else
673 #endif /* HAVE_SGI_SAVER_EXTENSION */
674
675           /* Just some random event.  Let the Widgets handle it, if desired. */
676           dispatch_event (si, &event);
677       }
678     }
679  DONE:
680
681
682   /* If there's a user event on the queue, swallow it.
683      If we're using a server extension, and the user becomes active, we
684      get the extension event before the user event -- so the keypress or
685      motion or whatever is still on the queue.  This makes "unfade" not
686      work, because it sees that event, and bugs out.  (This problem
687      doesn't exhibit itself without an extension, because in that case,
688      there's only one event generated by user activity, not two.)
689    */
690   XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
691                    &event);
692
693
694   if (si->check_pointer_timer_id)
695     {
696       XtRemoveTimeOut (si->check_pointer_timer_id);
697       si->check_pointer_timer_id = 0;
698     }
699   if (si->timer_id)
700     {
701       XtRemoveTimeOut (si->timer_id);
702       si->timer_id = 0;
703     }
704
705   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
706     abort ();
707
708   return;
709 }
710
711
712 \f
713 /* Some crap for dealing with /proc/interrupts.
714
715    On Linux systems, it's possible to see the hardware interrupt count
716    associated with the keyboard.  We can therefore use that as another method
717    of detecting idleness.
718
719    Why is it a good idea to do this?  Because it lets us detect keyboard
720    activity that is not associated with X events.  For example, if the user
721    has switched to another virtual console, it's good for xscreensaver to not
722    be running graphics hacks on the (non-visible) X display.  The common
723    complaint that checking /proc/interrupts addresses is that the user is
724    playing Quake on a non-X console, and the GL hacks are perceptibly slowing
725    the game...
726
727    This is tricky for a number of reasons.
728
729      * First, we must be sure to only do this when running on an X server that
730        is on the local machine (because otherwise, we'd be reacting to the
731        wrong keyboard.)  The way we do this is by noting that the $DISPLAY is
732        pointing to display 0 on the local machine.  It *could* be that display
733        1 is also on the local machine (e.g., two X servers, each on a different
734        virtual-terminal) but it's also possible that screen 1 is an X terminal,
735        using this machine as the host.  So we can't take that chance.
736
737      * Second, one can only access these interrupt numbers in a completely
738        and utterly brain-damaged way.  You would think that one would use an
739        ioctl for this.  But no.  The ONLY way to get this information is to
740        open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
741        out of it TEXTUALLY.  Because this is Unix, and all the world's a file,
742        and the only real data type is the short-line sequence of ASCII bytes.
743
744        Now it's all well and good that the /proc/interrupts pseudo-file
745        exists; that's a clever idea, and a useful API for things that are
746        already textually oriented, like shell scripts, and users doing
747        interactive debugging sessions.  But to make a *C PROGRAM* open a file
748        and parse the textual representation of integers out of it is just
749        insane.
750
751      * Third, you can't just hold the file open, and fseek() back to the
752        beginning to get updated data!  If you do that, the data never changes.
753        And I don't want to call open() every five seconds, because I don't want
754        to risk going to disk for any inodes.  It turns out that if you dup()
755        it early, then each copy gets fresh data, so we can get around that in
756        this way (but for how many releases, one might wonder?)
757
758      * Fourth, the format of the output of the /proc/interrupts file is
759        undocumented, and has changed several times already!  In Linux 2.0.33,
760        even on a multiprocessor machine, it looks like this:
761
762           0:  309453991   timer
763           1:    4771729   keyboard
764    
765        but on later kernels with MP machines, it looks like this:
766
767                    CPU0       CPU1
768           0:    1671450    1672618    IO-APIC-edge  timer
769           1:      13037      13495    IO-APIC-edge  keyboard
770
771        Joy!  So how are we expected to parse that?  Well, this code doesn't
772        parse it: it saves the last line with the string "keyboard" in it, and
773        does a string-comparison to note when it has changed.
774
775    Thanks to Nat Friedman <nat@nat.org> for figuring out all of this crap.
776
777    Note that this only checks for lines with "keyboard" in them.  Perhaps we
778    should also be checking for lines with "PS/2 Mouse" in them.  But that
779    would obviously fail to work for regular serial mice, and obviously just
780    using COM1 would be bad news (turn off the screensaver because the modem
781    is active, yum.)
782  */
783
784
785 #ifdef HAVE_PROC_INTERRUPTS
786
787 #define PROC_INTERRUPTS "/proc/interrupts"
788
789 Bool
790 query_proc_interrupts_available (saver_info *si, const char **why)
791 {
792   /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
793      "/proc/interrupts" file exists and is readable.
794    */
795   FILE *f;
796   if (why) *why = 0;
797
798   if (!display_is_on_console_p (si))
799     {
800       if (why) *why = "not on primary console";
801       return False;
802     }
803
804   f = fopen (PROC_INTERRUPTS, "r");
805   if (!f)
806     return False;
807
808   fclose (f);
809   return True;
810 }
811
812
813 static Bool
814 proc_interrupts_activity_p (saver_info *si)
815 {
816   static FILE *f0 = 0;
817   FILE *f1 = 0;
818   int fd;
819   static char last_line[255] = { 0, };
820   char new_line[sizeof(last_line)];
821
822   if (!f0)
823     {
824       /* First time -- open the file. */
825       f0 = fopen (PROC_INTERRUPTS, "r");
826       if (!f0)
827         {
828           char buf[255];
829           sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
830           perror (buf);
831           goto FAIL;
832         }
833     }
834
835   if (f0 == (FILE *) -1)            /* means we got an error initializing. */
836     return False;
837
838   fd = dup (fileno (f0));
839   if (fd < 0)
840     {
841       char buf[255];
842       sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
843       perror (buf);
844       goto FAIL;
845     }
846
847   f1 = fdopen (fd, "r");
848   if (!f1)
849     {
850       char buf[255];
851       sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
852               PROC_INTERRUPTS);
853       perror (buf);
854       goto FAIL;
855     }
856
857   /* Actually, I'm unclear on why this fseek() is necessary, given the timing
858      of the dup() above, but it is. */
859   if (fseek (f1, 0, SEEK_SET) != 0)
860     {
861       char buf[255];
862       sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
863       perror (buf);
864       goto FAIL;
865     }
866
867   /* Now read through the pseudo-file until we find the "keyboard" line. */
868
869   while (fgets (new_line, sizeof(new_line)-1, f1))
870     if (strstr (new_line, "keyboard"))
871       {
872         Bool diff = (*last_line &&
873                      !!strcmp (new_line, last_line));
874         strcpy (last_line, new_line);   /* save this line for next time */
875         fclose (f1);
876         return diff;
877       }
878
879   /* If we got here, we didn't find a "keyboard" line in the file at all. */
880   fprintf (stderr, "%s: no keyboard data in %s?\n", blurb(), PROC_INTERRUPTS);
881
882  FAIL:
883   if (f1)
884     fclose (f1);
885
886   if (f0 && f0 != (FILE *) -1)
887     fclose (f0);
888
889   f0 = (FILE *) -1;
890   return False;
891 }
892
893 #endif /* HAVE_PROC_INTERRUPTS */
894
895 \f
896 /* This timer goes off every few minutes, whether the user is idle or not,
897    to try and clean up anything that has gone wrong.
898
899    It calls disable_builtin_screensaver() so that if xset has been used,
900    or some other program (like xlock) has messed with the XSetScreenSaver()
901    settings, they will be set back to sensible values (if a server extension
902    is in use, messing with xlock can cause xscreensaver to never get a wakeup
903    event, and could cause monitor power-saving to occur, and all manner of
904    heinousness.)
905
906    If the screen is currently blanked, it raises the window, in case some
907    other window has been mapped on top of it.
908
909    If the screen is currently blanked, and there is no hack running, it
910    clears the window, in case there is an error message printed on it (we
911    don't want the error message to burn in.)
912  */
913
914 static void
915 watchdog_timer (XtPointer closure, XtIntervalId *id)
916 {
917   saver_info *si = (saver_info *) closure;
918
919   disable_builtin_screensaver (si, False);
920
921   if (si->screen_blanked_p)
922     {
923       Bool running_p = screenhack_running_p(si);
924
925 #ifdef DEBUG_TIMERS
926       if (si->prefs.verbose_p)
927         fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
928                  blurb(), (running_p ? "" : "and clearing "));
929 #endif /* DEBUG_TIMERS */
930
931       raise_window (si, True, True, running_p);
932
933       if (!monitor_powered_on_p (si))
934         {
935           if (si->prefs.verbose_p)
936             fprintf (stderr,
937                      "%s: server reports that monitor has powered down; "
938                      "killing running hacks.\n", blurb());
939           kill_screenhack (si);
940         }
941     }
942 }
943
944
945 void
946 reset_watchdog_timer (saver_info *si, Bool on_p)
947 {
948   saver_preferences *p = &si->prefs;
949
950   if (si->watchdog_id)
951     {
952       XtRemoveTimeOut (si->watchdog_id);
953       si->watchdog_id = 0;
954     }
955
956   if (on_p && p->watchdog_timeout)
957     {
958       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
959                                          watchdog_timer, (XtPointer) si);
960
961 #ifdef DEBUG_TIMERS
962       if (p->verbose_p)
963         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
964                  blurb(), p->watchdog_timeout, si->watchdog_id);
965 #endif /* DEBUG_TIMERS */
966
967     }
968 }