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