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