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