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