From http://www.jwz.org/xscreensaver/xscreensaver-5.34.tar.gz
[xscreensaver] / driver / timers.c
1 /* timers.c --- detecting when the user is idle, and other timer-related tasks.
2  * xscreensaver, Copyright (c) 1991-2014 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 #undef ABS
52 #define ABS(x)((x)<0?-(x):(x))
53
54 #undef MAX
55 #define MAX(x,y)((x)>(y)?(x):(y))
56
57
58 #ifdef HAVE_PROC_INTERRUPTS
59 static Bool proc_interrupts_activity_p (saver_info *si);
60 #endif /* HAVE_PROC_INTERRUPTS */
61
62 static void check_for_clock_skew (saver_info *si);
63
64
65 void
66 idle_timer (XtPointer closure, XtIntervalId *id)
67 {
68   saver_info *si = (saver_info *) closure;
69
70   /* What an amazingly shitty design.  Not only does Xt execute timeout
71      events from XtAppNextEvent() instead of from XtDispatchEvent(), but
72      there is no way to tell Xt to block until there is an X event OR a
73      timeout happens.  Once your timeout proc is called, XtAppNextEvent()
74      still won't return until a "real" X event comes in.
75
76      So this function pushes a stupid, gratuitous, unnecessary event back
77      on the event queue to force XtAppNextEvent to return Right Fucking Now.
78      When the code in sleep_until_idle() sees an event of type XAnyEvent,
79      which the server never generates, it knows that a timeout has occurred.
80    */
81   XEvent fake_event;
82   fake_event.type = 0;  /* XAnyEvent type, ignored. */
83   fake_event.xany.display = si->dpy;
84   fake_event.xany.window  = 0;
85   XPutBackEvent (si->dpy, &fake_event);
86
87   /* If we are the timer that just went off, clear the pointer to the id. */
88   if (id)
89     {
90       if (si->timer_id && *id != si->timer_id)
91         abort();  /* oops, scheduled timer twice?? */
92       si->timer_id = 0;
93     }
94 }
95
96
97 void
98 schedule_wakeup_event (saver_info *si, Time when, Bool verbose_p)
99 {
100   if (si->timer_id)
101     {
102       if (verbose_p)
103         fprintf (stderr, "%s: idle_timer already running\n", blurb());
104       return;
105     }
106
107   /* Wake up periodically to ask the server if we are idle. */
108   si->timer_id = XtAppAddTimeOut (si->app, when, idle_timer,
109                                   (XtPointer) si);
110
111   if (verbose_p)
112     fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
113              blurb(), when, si->timer_id);
114 }
115
116
117 static void
118 notice_events (saver_info *si, Window window, Bool top_p)
119 {
120   saver_preferences *p = &si->prefs;
121   XWindowAttributes attrs;
122   unsigned long events;
123   Window root, parent, *kids;
124   unsigned int nkids;
125   int screen_no;
126
127   if (XtWindowToWidget (si->dpy, window))
128     /* If it's one of ours, don't mess up its event mask. */
129     return;
130
131   if (!XQueryTree (si->dpy, window, &root, &parent, &kids, &nkids))
132     return;
133   if (window == root)
134     top_p = False;
135
136   /* Figure out which screen this window is on, for the diagnostics. */
137   for (screen_no = 0; screen_no < si->nscreens; screen_no++)
138     if (root == RootWindowOfScreen (si->screens[screen_no].screen))
139       break;
140
141   XGetWindowAttributes (si->dpy, window, &attrs);
142   events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
143             & (KeyPressMask | PropertyChangeMask));
144
145   /* Select for SubstructureNotify on all windows.
146      Select for PropertyNotify on all windows.
147      Select for KeyPress on all windows that already have it selected.
148
149      Note that we can't select for ButtonPress, because of X braindamage:
150      only one client at a time may select for ButtonPress on a given
151      window, though any number can select for KeyPress.  Someone explain
152      *that* to me.
153
154      So, if the user spends a while clicking the mouse without ever moving
155      the mouse or touching the keyboard, we won't know that they've been
156      active, and the screensaver will come on.  That sucks, but I don't
157      know how to get around it.
158
159      Since X presents mouse wheels as clicks, this applies to those, too:
160      scrolling through a document using only the mouse wheel doesn't
161      count as activity...  Fortunately, /proc/interrupts helps, on
162      systems that have it.  Oh, if it's a PS/2 mouse, not serial or USB.
163      This sucks!
164    */
165   XSelectInput (si->dpy, window,
166                 SubstructureNotifyMask | PropertyChangeMask | events);
167
168   if (top_p && p->debug_p && (events & KeyPressMask))
169     {
170       /* Only mention one window per tree (hack hack). */
171       fprintf (stderr, "%s: %d: selected KeyPress on 0x%lX\n",
172                blurb(), screen_no, (unsigned long) window);
173       top_p = False;
174     }
175
176   if (kids)
177     {
178       while (nkids)
179         notice_events (si, kids [--nkids], top_p);
180       XFree ((char *) kids);
181     }
182 }
183
184
185 int
186 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
187 {
188   /* When we notice a window being created, we spawn a timer that waits
189      30 seconds or so, and then selects events on that window.  This error
190      handler is used so that we can cope with the fact that the window
191      may have been destroyed <30 seconds after it was created.
192    */
193   if (error->error_code == BadWindow ||
194       error->error_code == BadMatch ||
195       error->error_code == BadDrawable)
196     return 0;
197   else
198     return saver_ehandler (dpy, error);
199 }
200
201
202 struct notice_events_timer_arg {
203   saver_info *si;
204   Window w;
205 };
206
207 static void
208 notice_events_timer (XtPointer closure, XtIntervalId *id)
209 {
210   struct notice_events_timer_arg *arg =
211     (struct notice_events_timer_arg *) closure;
212
213   XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
214
215   saver_info *si = arg->si;
216   Window window = arg->w;
217
218   free(arg);
219   notice_events (si, window, True);
220   XSync (si->dpy, False);
221   XSetErrorHandler (old_handler);
222 }
223
224 void
225 start_notice_events_timer (saver_info *si, Window w, Bool verbose_p)
226 {
227   saver_preferences *p = &si->prefs;
228   struct notice_events_timer_arg *arg =
229     (struct notice_events_timer_arg *) malloc(sizeof(*arg));
230   arg->si = si;
231   arg->w = w;
232   XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer,
233                    (XtPointer) arg);
234
235   if (verbose_p)
236     fprintf (stderr, "%s: starting notice_events_timer for 0x%X (%lu)\n",
237              blurb(), (unsigned int) w, p->notice_events_timeout);
238 }
239
240
241 /* When the screensaver is active, this timer will periodically change
242    the running program.
243  */
244 void
245 cycle_timer (XtPointer closure, XtIntervalId *id)
246 {
247   saver_info *si = (saver_info *) closure;
248   saver_preferences *p = &si->prefs;
249   Time how_long = p->cycle;
250
251   if (si->selection_mode > 0 &&
252       screenhack_running_p (si))
253     /* If we're in "SELECT n" mode, the cycle timer going off will just
254        restart this same hack again.  There's not much point in doing this
255        every 5 or 10 minutes, but on the other hand, leaving one hack running
256        for days is probably not a great idea, since they tend to leak and/or
257        crash.  So, restart the thing once an hour. */
258     how_long = 1000 * 60 * 60;
259
260   if (si->dbox_up_p)
261     {
262       if (p->verbose_p)
263         fprintf (stderr, "%s: dialog box up; delaying hack change.\n",
264                  blurb());
265       how_long = 30000; /* 30 secs */
266     }
267   else
268     {
269       int i;
270       maybe_reload_init_file (si);
271       for (i = 0; i < si->nscreens; i++)
272         kill_screenhack (&si->screens[i]);
273
274       raise_window (si, True, True, False);
275
276       if (!si->throttled_p)
277         for (i = 0; i < si->nscreens; i++)
278           spawn_screenhack (&si->screens[i]);
279       else
280         {
281           if (p->verbose_p)
282             fprintf (stderr, "%s: not launching new hack (throttled.)\n",
283                      blurb());
284         }
285     }
286
287   if (how_long > 0)
288     {
289       si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
290                                       (XtPointer) si);
291
292       if (p->debug_p)
293         fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n",
294                  blurb(), how_long, si->cycle_id);
295     }
296   else
297     {
298       if (p->debug_p)
299         fprintf (stderr, "%s: not starting cycle_timer: how_long == %ld\n",
300                  blurb(), (unsigned long) how_long);
301     }
302 }
303
304
305 void
306 activate_lock_timer (XtPointer closure, XtIntervalId *id)
307 {
308   saver_info *si = (saver_info *) closure;
309   saver_preferences *p = &si->prefs;
310
311   if (p->verbose_p)
312     fprintf (stderr, "%s: timed out; activating lock.\n", blurb());
313   set_locked_p (si, True);
314 }
315
316
317 /* Call this when user activity (or "simulated" activity) has been noticed.
318  */
319 void
320 reset_timers (saver_info *si)
321 {
322   saver_preferences *p = &si->prefs;
323   if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
324     return;
325
326   if (si->timer_id)
327     {
328       if (p->debug_p)
329         fprintf (stderr, "%s: killing idle_timer  (%ld, %ld)\n",
330                  blurb(), p->timeout, si->timer_id);
331       XtRemoveTimeOut (si->timer_id);
332       si->timer_id = 0;
333     }
334
335   schedule_wakeup_event (si, p->timeout, p->debug_p); /* sets si->timer_id */
336
337   if (si->cycle_id) abort ();   /* no cycle timer when inactive */
338
339   si->last_activity_time = time ((time_t *) 0);
340
341   /* This will (hopefully, supposedly) tell the server to re-set its
342      DPMS timer.  Without this, the -deactivate clientmessage would
343      prevent xscreensaver from blanking, but would not prevent the
344      monitor from powering down. */
345 #if 0
346   /* #### With some servers, this causes the screen to flicker every
347      time a key is pressed!  Ok, I surrender.  I give up on ever
348      having DPMS work properly.
349    */
350   XForceScreenSaver (si->dpy, ScreenSaverReset);
351
352   /* And if the monitor is already powered off, turn it on.
353      You'd think the above would do that, but apparently not? */
354   monitor_power_on (si, True);
355 #endif
356
357 }
358
359
360 /* Returns true if a mouse has moved since the last time we checked.
361    Small motions (of less than "hysteresis" pixels/second) are ignored.
362  */
363 static Bool
364 device_pointer_moved_p (saver_info *si, poll_mouse_data *last_poll_mouse,
365                         poll_mouse_data *this_poll_mouse, Bool mods_p,
366                         const char *debug_type, int debug_id)
367 {
368   saver_preferences *p = &si->prefs;
369
370   unsigned int distance, dps;
371   unsigned long seconds = 0;
372   Bool moved_p = False;
373
374   distance = MAX (ABS (last_poll_mouse->root_x - this_poll_mouse->root_x),
375                   ABS (last_poll_mouse->root_y - this_poll_mouse->root_y));
376   seconds = (this_poll_mouse->time - last_poll_mouse->time);
377
378
379   /* When the screen is blanked, we get MotionNotify events, but when not
380      blanked, we poll only every 5 seconds, and that's not enough resolution
381      to do hysteresis based on a 1 second interval.  So, assume that any
382      motion we've seen during the 5 seconds when our eyes were closed happened
383      in the last 1 second instead.
384    */
385   if (seconds > 1) seconds = 1;
386
387   dps = (seconds <= 0 ? distance : (distance / seconds));
388
389   /* Motion only counts if the rate is more than N pixels per second.
390    */
391   if (dps >= p->pointer_hysteresis &&
392       distance > 0)
393     moved_p = True;
394
395   /* If the mouse is not on this screen but used to be, that's motion.
396      If the mouse was not on this screen, but is now, that's motion.
397    */
398   {
399     Bool on_screen_p  = (this_poll_mouse->root_x != -1 &&
400                          this_poll_mouse->root_y != -1);
401     Bool was_on_screen_p = (last_poll_mouse->root_x != -1 &&
402                             last_poll_mouse->root_y != -1);
403
404     if (on_screen_p != was_on_screen_p)
405       moved_p = True;
406   }
407
408   if (p->debug_p && (distance != 0 || moved_p))
409     {
410       fprintf (stderr, "%s: %s %d: pointer %s", blurb(), debug_type, debug_id,
411                (moved_p ? "moved:  " : "ignored:"));
412       if (last_poll_mouse->root_x == -1)
413         fprintf (stderr, "off screen");
414       else
415         fprintf (stderr, "%d,%d",
416                  last_poll_mouse->root_x,
417                  last_poll_mouse->root_y);
418       fprintf (stderr, " -> ");
419       if (this_poll_mouse->root_x == -1)
420         fprintf (stderr, "off screen");
421       else
422         fprintf (stderr, "%d,%d", this_poll_mouse->root_x,
423                  this_poll_mouse->root_y);
424       if (last_poll_mouse->root_x != -1 && this_poll_mouse->root_x != -1)
425         fprintf (stderr, " (%d,%d; %d/%lu=%d)",
426                  ABS(last_poll_mouse->root_x - this_poll_mouse->root_x),
427                  ABS(last_poll_mouse->root_y - this_poll_mouse->root_y),
428                  distance, seconds, dps);
429
430       fprintf (stderr, ".\n");
431     }
432
433   if (!moved_p &&
434       mods_p &&
435       this_poll_mouse->mask != last_poll_mouse->mask)
436     {
437       moved_p = True;
438
439       if (p->debug_p)
440         fprintf (stderr, "%s: %s %d: modifiers changed: 0x%04x -> 0x%04x.\n",
441                  blurb(), debug_type, debug_id,
442                  last_poll_mouse->mask, this_poll_mouse->mask);
443     }
444
445   last_poll_mouse->child = this_poll_mouse->child;
446   last_poll_mouse->mask  = this_poll_mouse->mask;
447
448   if (moved_p || seconds > 0)
449     {
450       last_poll_mouse->time   = this_poll_mouse->time;
451       last_poll_mouse->root_x = this_poll_mouse->root_x;
452       last_poll_mouse->root_y = this_poll_mouse->root_y;
453     }
454
455   return moved_p;
456 }
457
458 /* Returns true if core mouse pointer has moved since the last time we checked.
459  */
460 static Bool
461 pointer_moved_p (saver_screen_info *ssi, Bool mods_p)
462 {
463   saver_info *si = ssi->global;
464
465   Window root;
466   poll_mouse_data this_poll_mouse;
467   int x, y;
468
469   /* don't check xinerama pseudo-screens. */
470   if (!ssi->real_screen_p) return False;
471
472   this_poll_mouse.time = time ((time_t *) 0);
473   
474   if (!XQueryPointer (si->dpy, ssi->screensaver_window, &root,
475                       &this_poll_mouse.child,
476                       &this_poll_mouse.root_x, &this_poll_mouse.root_y,
477                       &x, &y, &this_poll_mouse.mask))
478     {
479       /* If XQueryPointer() returns false, the mouse is not on this screen.
480        */
481       this_poll_mouse.root_x = -1;
482       this_poll_mouse.root_y = -1;
483       this_poll_mouse.child = 0;
484       this_poll_mouse.mask = 0;
485     }
486   else
487     si->last_activity_screen = ssi;
488
489   return device_pointer_moved_p(si, &(ssi->last_poll_mouse), &this_poll_mouse,
490                                 mods_p, "screen", ssi->number);
491 }
492
493
494 /* When we aren't using a server extension, this timer is used to periodically
495    wake up and poll the mouse position, which is possibly more reliable than
496    selecting motion events on every window.
497  */
498 static void
499 check_pointer_timer (XtPointer closure, XtIntervalId *id)
500 {
501   int i;
502   saver_info *si = (saver_info *) closure;
503   saver_preferences *p = &si->prefs;
504   Bool active_p = False;
505
506   if (!si->using_proc_interrupts &&
507       (si->using_xidle_extension ||
508        si->using_mit_saver_extension ||
509        si->using_sgi_saver_extension))
510     /* If an extension is in use, we should not be polling the mouse.
511        Unless we're also checking /proc/interrupts, in which case, we should.
512      */
513     abort ();
514
515   if (id && *id == si->check_pointer_timer_id)  /* this is us - it's expired */
516     si->check_pointer_timer_id = 0;
517
518   if (si->check_pointer_timer_id)               /* only queue one at a time */
519     XtRemoveTimeOut (si->check_pointer_timer_id);
520
521   si->check_pointer_timer_id =                  /* now re-queue */
522     XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
523                      (XtPointer) si);
524
525   for (i = 0; i < si->nscreens; i++)
526     {
527       saver_screen_info *ssi = &si->screens[i];
528       if (pointer_moved_p (ssi, True))
529         active_p = True;
530     }
531
532 #ifdef HAVE_PROC_INTERRUPTS
533   if (!active_p &&
534       si->using_proc_interrupts &&
535       proc_interrupts_activity_p (si))
536     {
537       active_p = True;
538     }
539 #endif /* HAVE_PROC_INTERRUPTS */
540
541   if (active_p)
542     reset_timers (si);
543
544   check_for_clock_skew (si);
545 }
546
547
548 /* An unfortunate situation is this: the saver is not active, because the
549    user has been typing.  The machine is a laptop.  The user closes the lid
550    and suspends it.  The CPU halts.  Some hours later, the user opens the
551    lid.  At this point, Xt's timers will fire, and xscreensaver will blank
552    the screen.
553
554    So far so good -- well, not really, but it's the best that we can do,
555    since the OS doesn't send us a signal *before* shutdown -- but if the
556    user had delayed locking (lockTimeout > 0) then we should start off
557    in the locked state, rather than only locking N minutes from when the
558    lid was opened.  Also, eschewing fading is probably a good idea, to
559    clamp down as soon as possible.
560
561    We only do this when we'd be polling the mouse position anyway.
562    This amounts to an assumption that machines with APM support also
563    have /proc/interrupts.
564  */
565 static void
566 check_for_clock_skew (saver_info *si)
567 {
568   saver_preferences *p = &si->prefs;
569   time_t now = time ((time_t *) 0);
570   long shift = now - si->last_wall_clock_time;
571
572   if (p->debug_p)
573     {
574       int i = (si->last_wall_clock_time == 0 ? 0 : shift);
575       fprintf (stderr,
576                "%s: checking wall clock for hibernation (%d:%02d:%02d).\n",
577                blurb(),
578                (i / (60 * 60)), ((i / 60) % 60), (i % 60));
579     }
580
581   if (si->last_wall_clock_time != 0 &&
582       shift > (p->timeout / 1000))
583     {
584       if (p->verbose_p)
585         fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld%s\n",
586                  blurb(),
587                  (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60),
588                  (p->mode == DONT_BLANK ? " while saver disabled" : ""));
589
590       /* If the saver is entirely disabled, there's no need to do the
591          emergency-blank-and-lock thing.
592        */
593       if (p->mode != DONT_BLANK)
594         {
595           si->emergency_lock_p = True;
596           idle_timer ((XtPointer) si, 0);
597         }
598     }
599
600   si->last_wall_clock_time = now;
601 }
602
603
604
605 static void
606 dispatch_event (saver_info *si, XEvent *event)
607 {
608   /* If this is for the splash dialog, pass it along.
609      Note that the password dialog is handled with its own event loop,
610      so events for that window will never come through here.
611    */
612   if (si->splash_dialog && event->xany.window == si->splash_dialog)
613     handle_splash_event (si, event);
614
615   XtDispatchEvent (event);
616 }
617
618
619 static void
620 swallow_unlock_typeahead_events (saver_info *si, XEvent *e)
621 {
622   XEvent event;
623   char buf [100];
624   int i = 0;
625
626   memset (buf, 0, sizeof(buf));
627
628   event = *e;
629
630   do
631     {
632       if (event.xany.type == KeyPress)
633         {
634           char s[2];
635           int size = XLookupString ((XKeyEvent *) &event, s, 1, 0, 0);
636           if (size != 1) continue;
637           switch (*s)
638             {
639             case '\010': case '\177':                   /* Backspace */
640               if (i > 0) i--;
641               break;
642             case '\025': case '\030':                   /* Erase line */
643             case '\012': case '\015':                   /* Enter */
644             case '\033':                                /* ESC */
645               i = 0;
646               break;
647             case '\040':                                /* Space */
648               if (i == 0)
649                 break;  /* ignore space at beginning of line */
650               /* else, fall through */
651             default:
652               buf [i++] = *s;
653               break;
654             }
655         }
656
657     } while (i < sizeof(buf)-1 &&
658              XCheckMaskEvent (si->dpy, KeyPressMask, &event));
659
660   buf[i] = 0;
661
662   if (si->unlock_typeahead)
663     {
664       memset (si->unlock_typeahead, 0, strlen(si->unlock_typeahead));
665       free (si->unlock_typeahead);
666     }
667
668   if (i > 0)
669     si->unlock_typeahead = strdup (buf);
670   else
671     si->unlock_typeahead = 0;
672
673   memset (buf, 0, sizeof(buf));
674 }
675
676
677 /* methods of detecting idleness:
678
679       explicitly informed by SGI SCREEN_SAVER server event;
680       explicitly informed by MIT-SCREEN-SAVER server event;
681       poll server idle time with XIDLE extension;
682       select events on all windows, and note absence of recent events;
683       note that /proc/interrupts has not changed in a while;
684       activated by clientmessage.
685
686    methods of detecting non-idleness:
687
688       read events on the xscreensaver window;
689       explicitly informed by SGI SCREEN_SAVER server event;
690       explicitly informed by MIT-SCREEN-SAVER server event;
691       select events on all windows, and note events on any of them;
692       note that a client updated their window's _NET_WM_USER_TIME property;
693       note that /proc/interrupts has changed;
694       deactivated by clientmessage.
695
696    I trust that explains why this function is a big hairy mess.
697  */
698 void
699 sleep_until_idle (saver_info *si, Bool until_idle_p)
700 {
701   saver_preferences *p = &si->prefs;
702
703   /* We have to go through this union bullshit because gcc-4.4.0 has
704      stricter struct-aliasing rules.  Without this, the optimizer
705      can fuck things up.
706    */
707   union {
708     XEvent x_event;
709 # ifdef HAVE_RANDR
710     XRRScreenChangeNotifyEvent xrr_event;
711 # endif /* HAVE_RANDR */
712 # ifdef HAVE_MIT_SAVER_EXTENSION
713     XScreenSaverNotifyEvent sevent;
714 # endif /* HAVE_MIT_SAVER_EXTENSION */
715   } event;
716
717   /* We need to select events on all windows if we're not using any extensions.
718      Otherwise, we don't need to. */
719   Bool scanning_all_windows = !(si->using_xidle_extension ||
720                                 si->using_mit_saver_extension ||
721                                 si->using_sgi_saver_extension);
722
723   /* We need to periodically wake up and check for idleness if we're not using
724      any extensions, or if we're using the XIDLE extension.  The other two
725      extensions explicitly deliver events when we go idle/non-idle, so we
726      don't need to poll. */
727   Bool polling_for_idleness = !(si->using_mit_saver_extension ||
728                                 si->using_sgi_saver_extension);
729
730   /* Whether we need to periodically wake up and check to see if the mouse has
731      moved.  We only need to do this when not using any extensions.  The reason
732      this isn't the same as `polling_for_idleness' is that the "idleness" poll
733      can happen (for example) 5 minutes from now, whereas the mouse-position
734      poll should happen with low periodicity.  We don't need to poll the mouse
735      position with the XIDLE extension, but we do need to periodically wake up
736      and query the server with that extension.  For our purposes, polling
737      /proc/interrupts is just like polling the mouse position.  It has to
738      happen on the same kind of schedule. */
739   Bool polling_mouse_position = (si->using_proc_interrupts ||
740                                  !(si->using_xidle_extension ||
741                                    si->using_mit_saver_extension ||
742                                    si->using_sgi_saver_extension) ||
743                                    si->using_xinput_extension);
744
745   const char *why = 0;  /* What caused the idle-state to change? */
746
747   if (until_idle_p)
748     {
749       if (polling_for_idleness)
750         /* This causes a no-op event to be delivered to us in a while, so that
751            we come back around through the event loop again.  */
752         schedule_wakeup_event (si, p->timeout, p->debug_p);
753
754       if (polling_mouse_position)
755         /* Check to see if the mouse has moved, and set up a repeating timer
756            to do so periodically (typically, every 5 seconds.) */
757         check_pointer_timer ((XtPointer) si, 0);
758     }
759
760   while (1)
761     {
762       XtAppNextEvent (si->app, &event.x_event);
763
764       switch (event.x_event.xany.type) {
765       case 0:           /* our synthetic "timeout" event has been signalled */
766         if (until_idle_p)
767           {
768             Time idle;
769
770             /* We may be idle; check one last time to see if the mouse has
771                moved, just in case the idle-timer went off within the 5 second
772                window between mouse polling.  If the mouse has moved, then
773                check_pointer_timer() will reset last_activity_time.
774              */
775             if (polling_mouse_position)
776               check_pointer_timer ((XtPointer) si, 0);
777
778 #ifdef HAVE_XIDLE_EXTENSION
779             if (si->using_xidle_extension)
780               {
781                 /* The XIDLE extension uses the synthetic event to prod us into
782                    re-asking the server how long the user has been idle. */
783                 if (! XGetIdleTime (si->dpy, &idle))
784                   {
785                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
786                     saver_exit (si, 1, 0);
787                   }
788               }
789             else
790 #endif /* HAVE_XIDLE_EXTENSION */
791 #ifdef HAVE_MIT_SAVER_EXTENSION
792               if (si->using_mit_saver_extension)
793                 {
794                   /* We don't need to do anything in this case - the synthetic
795                      event isn't necessary, as we get sent specific events
796                      to wake us up.  In fact, this event generally shouldn't
797                      be being delivered when the MIT extension is in use. */
798                   idle = 0;
799                 }
800             else
801 #endif /* HAVE_MIT_SAVER_EXTENSION */
802 #ifdef HAVE_SGI_SAVER_EXTENSION
803               if (si->using_sgi_saver_extension)
804                 {
805                   /* We don't need to do anything in this case - the synthetic
806                      event isn't necessary, as we get sent specific events
807                      to wake us up.  In fact, this event generally shouldn't
808                      be being delivered when the SGI extension is in use. */
809                   idle = 0;
810                 }
811             else
812 #endif /* HAVE_SGI_SAVER_EXTENSION */
813               {
814                 /* Otherwise, no server extension is in use.  The synthetic
815                    event was to tell us to wake up and see if the user is now
816                    idle.  Compute the amount of idle time by comparing the
817                    `last_activity_time' to the wall clock.  The l_a_t was set
818                    by calling `reset_timers()', which is called only in only
819                    two situations: when polling the mouse position has revealed
820                    the the mouse has moved (user activity) or when we have read
821                    an event (again, user activity.)
822                  */
823                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
824               }
825
826             if (idle >= p->timeout)
827               {
828                 /* Look, we've been idle long enough.  We're done. */
829                 why = "timeout";
830                 goto DONE;
831               }
832             else if (si->emergency_lock_p)
833               {
834                 /* Oops, the wall clock has jumped far into the future, so
835                    we need to lock down in a hurry! */
836                 why = "large wall clock change";
837                 goto DONE;
838               }
839             else
840               {
841                 /* The event went off, but it turns out that the user has not
842                    yet been idle for long enough.  So re-signal the event.
843                    Be economical: if we should blank after 5 minutes, and the
844                    user has been idle for 2 minutes, then set this timer to
845                    go off in 3 minutes.
846                  */
847                 if (polling_for_idleness)
848                   schedule_wakeup_event (si, p->timeout - idle, p->debug_p);
849               }
850           }
851         break;
852
853       case ClientMessage:
854         if (handle_clientmessage (si, &event.x_event, until_idle_p))
855           {
856             why = "ClientMessage";
857             goto DONE;
858           }
859         break;
860
861       case CreateNotify:
862         /* A window has been created on the screen somewhere.  If we're
863            supposed to scan all windows for events, prepare this window. */
864         if (scanning_all_windows)
865           {
866             Window w = event.x_event.xcreatewindow.window;
867             start_notice_events_timer (si, w, p->debug_p);
868           }
869         break;
870
871       case KeyPress:
872       case ButtonPress:
873       /* Ignore release events so that hitting ESC at the password dialog
874          doesn't result in the password dialog coming right back again when
875          the fucking release key is seen! */
876       /* case KeyRelease:*/
877       /* case ButtonRelease:*/
878       case MotionNotify:
879
880         if (p->debug_p)
881           {
882             Window root=0, window=0;
883             int x=-1, y=-1;
884             const char *type = 0;
885             if (event.x_event.xany.type == MotionNotify)
886               {
887                 /*type = "MotionNotify";*/
888                 root = event.x_event.xmotion.root;
889                 window = event.x_event.xmotion.window;
890                 x = event.x_event.xmotion.x_root;
891                 y = event.x_event.xmotion.y_root;
892               }
893             else if (event.x_event.xany.type == KeyPress)
894               {
895                 type = "KeyPress";
896                 root = event.x_event.xkey.root;
897                 window = event.x_event.xkey.window;
898                 x = y = -1;
899               }
900             else if (event.x_event.xany.type == ButtonPress)
901               {
902                 type = "ButtonPress";
903                 root = event.x_event.xkey.root;
904                 window = event.x_event.xkey.window;
905                 x = event.x_event.xmotion.x_root;
906                 y = event.x_event.xmotion.y_root;
907               }
908
909             if (type)
910               {
911                 int i;
912                 for (i = 0; i < si->nscreens; i++)
913                   if (root == RootWindowOfScreen (si->screens[i].screen))
914                     break;
915                 fprintf (stderr,"%s: %d: %s on 0x%lx",
916                          blurb(), i, type, (unsigned long) window);
917
918                 /* Be careful never to do this unless in -debug mode, as
919                    this could expose characters from the unlock password. */
920                 if (p->debug_p && event.x_event.xany.type == KeyPress)
921                   {
922                     KeySym keysym;
923                     char c = 0;
924                     XLookupString (&event.x_event.xkey, &c, 1, &keysym, 0);
925                     fprintf (stderr, " (%s%s)",
926                              (event.x_event.xkey.send_event ? "synthetic " : ""),
927                              XKeysymToString (keysym));
928                   }
929
930                 if (x == -1)
931                   fprintf (stderr, "\n");
932                 else
933                   fprintf (stderr, " at %d,%d.\n", x, y);
934               }
935           }
936
937         /* If any widgets want to handle this event, let them. */
938         dispatch_event (si, &event.x_event);
939
940         
941         /* If we got a MotionNotify event, figure out what screen it
942            was on and poll the mouse there: if the mouse hasn't moved
943            far enough to count as "real" motion, then ignore this
944            event.
945          */
946         if (event.x_event.xany.type == MotionNotify)
947           {
948             int i;
949             for (i = 0; i < si->nscreens; i++)
950               if (event.x_event.xmotion.root ==
951                   RootWindowOfScreen (si->screens[i].screen))
952                 break;
953             if (i < si->nscreens)
954               {
955                 if (!pointer_moved_p (&si->screens[i], False))
956                   continue;
957               }
958           }
959
960
961         /* We got a user event.
962            If we're waiting for the user to become active, this is it.
963            If we're waiting until the user becomes idle, reset the timers
964            (since now we have longer to wait.)
965          */
966         if (!until_idle_p)
967           {
968             if (si->demoing_p &&
969                 (event.x_event.xany.type == MotionNotify ||
970                  event.x_event.xany.type == KeyRelease))
971               /* When we're demoing a single hack, mouse motion doesn't
972                  cause deactivation.  Only clicks and keypresses do. */
973               ;
974             else
975               {
976                 /* If we're not demoing, then any activity causes deactivation.
977                  */
978                 why = (event.x_event.xany.type == MotionNotify ?"mouse motion":
979                        event.x_event.xany.type == KeyPress?"keyboard activity":
980                        event.x_event.xany.type == ButtonPress ? "mouse click" :
981                        "unknown user activity");
982                 goto DONE;
983               }
984           }
985         else
986           reset_timers (si);
987
988         break;
989
990       case PropertyNotify:
991
992         if (event.x_event.xproperty.state == PropertyNewValue &&
993             event.x_event.xproperty.atom == XA_NET_WM_USER_TIME)
994           {
995             /* Let's just assume that they only ever set USER_TIME to the
996                current time, and don't do something stupid like repeatedly
997                setting it to 20 minutes ago. */
998
999             why = "WM_USER_TIME";
1000
1001             if (p->debug_p)
1002               {
1003                 Window w = event.x_event.xproperty.window;
1004                 XWindowAttributes xgwa;
1005                 int i;
1006                 XGetWindowAttributes (si->dpy, w, &xgwa);
1007                 for (i = 0; i < si->nscreens; i++)
1008                   if (xgwa.root == RootWindowOfScreen (si->screens[i].screen))
1009                     break;
1010                 fprintf (stderr,"%s: %d: %s on 0x%lx\n",
1011                          blurb(), i, why, (unsigned long) w);
1012               }
1013
1014             if (until_idle_p)
1015               reset_timers (si);
1016             else
1017               goto DONE;
1018           }
1019         break;
1020
1021       default:
1022
1023 #ifdef HAVE_MIT_SAVER_EXTENSION
1024         if (event.x_event.type == si->mit_saver_ext_event_number)
1025           {
1026             /* This event's number is that of the MIT-SCREEN-SAVER server
1027                extension.  This extension has one event number, and the event
1028                itself contains sub-codes that say what kind of event it was
1029                (an "idle" or "not-idle" event.)
1030              */
1031             if (event.sevent.state == ScreenSaverOn)
1032               {
1033                 int i = 0;
1034                 if (p->verbose_p)
1035                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
1036                            blurb());
1037
1038                 /* Get the "real" server window(s) out of the way as soon
1039                    as possible. */
1040                 for (i = 0; i < si->nscreens; i++)
1041                   {
1042                     saver_screen_info *ssi = &si->screens[i];
1043                     if (ssi->server_mit_saver_window &&
1044                         window_exists_p (si->dpy,
1045                                          ssi->server_mit_saver_window))
1046                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
1047                   }
1048
1049                 if (event.sevent.kind != ScreenSaverExternal)
1050                   {
1051                     fprintf (stderr,
1052                          "%s: ScreenSaverOn event wasn't of type External!\n",
1053                              blurb());
1054                   }
1055
1056                 if (until_idle_p)
1057                   {
1058                     why = "MIT ScreenSaverOn";
1059                     goto DONE;
1060                   }
1061               }
1062             else if (event.sevent.state == ScreenSaverOff)
1063               {
1064                 if (p->verbose_p)
1065                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
1066                            blurb());
1067                 if (!until_idle_p)
1068                   {
1069                     why = "MIT ScreenSaverOff";
1070                     goto DONE;
1071                   }
1072               }
1073             else
1074               fprintf (stderr,
1075                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
1076                        blurb(), event.sevent.state);
1077           }
1078         else
1079
1080 #endif /* HAVE_MIT_SAVER_EXTENSION */
1081
1082
1083 #ifdef HAVE_SGI_SAVER_EXTENSION
1084         if (event.x_event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
1085           {
1086             /* The SGI SCREEN_SAVER server extension has two event numbers,
1087                and this event matches the "idle" event. */
1088             if (p->verbose_p)
1089               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
1090                        blurb());
1091
1092             if (until_idle_p)
1093               {
1094                 why = "SGI ScreenSaverStart";
1095                 goto DONE;
1096               }
1097           }
1098         else if (event.x_event.type == (si->sgi_saver_ext_event_number +
1099                                 ScreenSaverEnd))
1100           {
1101             /* The SGI SCREEN_SAVER server extension has two event numbers,
1102                and this event matches the "idle" event. */
1103             if (p->verbose_p)
1104               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
1105                        blurb());
1106             if (!until_idle_p)
1107               {
1108                 why = "SGI ScreenSaverEnd";
1109                 goto DONE;
1110               }
1111           }
1112         else
1113 #endif /* HAVE_SGI_SAVER_EXTENSION */
1114
1115 #ifdef HAVE_XINPUT
1116         /* If we got a MotionNotify event, check to see if the mouse has
1117            moved far enough to count as "real" motion, if not, then ignore
1118            this event.
1119          */
1120         if ((si->num_xinput_devices > 0) &&
1121             (event.x_event.type == si->xinput_DeviceMotionNotify))
1122           {
1123             XDeviceMotionEvent *dme = (XDeviceMotionEvent *) &event;
1124             poll_mouse_data *last_poll_mouse = NULL;
1125             int d;
1126
1127             for (d = 0; d < si->num_xinput_devices; d++)
1128               {
1129                 if (si->xinput_devices[d].device->device_id == dme->deviceid)
1130                   {
1131                     last_poll_mouse = &(si->xinput_devices[d].last_poll_mouse);
1132                     break;
1133                   }
1134               }
1135
1136             if (last_poll_mouse)
1137               {
1138                 poll_mouse_data this_poll_mouse;
1139                 this_poll_mouse.root_x = dme->x_root;
1140                 this_poll_mouse.root_y = dme->y_root;
1141                 this_poll_mouse.child = dme->subwindow;
1142                 this_poll_mouse.mask = dme->device_state;
1143                 this_poll_mouse.time = dme->time / 1000; /* milliseconds */
1144
1145                 if (!device_pointer_moved_p (si, last_poll_mouse,
1146                                              &this_poll_mouse, False,
1147                                              "device", dme->deviceid))
1148                   continue;
1149               }
1150             else if (p->debug_p)
1151               fprintf (stderr,
1152                        "%s: received MotionNotify from unknown device %d\n",
1153                        blurb(), (int) dme->deviceid);
1154           }
1155         
1156         if ((!until_idle_p) &&
1157             (si->num_xinput_devices > 0) &&
1158             (event.x_event.type == si->xinput_DeviceMotionNotify ||
1159              event.x_event.type == si->xinput_DeviceButtonPress))
1160           /* Ignore DeviceButtonRelease, see ButtonRelease comment above. */
1161           {
1162
1163             dispatch_event (si, &event.x_event);
1164             if (si->demoing_p &&
1165                 event.x_event.type == si->xinput_DeviceMotionNotify)
1166               /* When we're demoing a single hack, mouse motion doesn't
1167                  cause deactivation.  Only clicks and keypresses do. */
1168               ;
1169             else
1170               /* If we're not demoing, then any activity causes deactivation.
1171                */
1172               {
1173                 why = (event.x_event.type == si->xinput_DeviceMotionNotify
1174                        ? "XI mouse motion" :
1175                        event.x_event.type == si->xinput_DeviceButtonPress
1176                        ? "XI mouse click" : "unknown XINPUT event");
1177                 goto DONE;
1178               }
1179           }
1180         else
1181 #endif /* HAVE_XINPUT */
1182
1183 #ifdef HAVE_RANDR
1184         if (si->using_randr_extension &&
1185             (event.x_event.type == 
1186              (si->randr_event_number + RRScreenChangeNotify)))
1187           {
1188             /* The Resize and Rotate extension sends an event when the
1189                size, rotation, or refresh rate of any screen has changed.
1190              */
1191             if (p->verbose_p)
1192               {
1193                 /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
1194                 int screen = XRRRootToScreen (si->dpy, event.xrr_event.window);
1195                 fprintf (stderr, "%s: %d: screen change event received\n",
1196                          blurb(), screen);
1197               }
1198
1199 # ifdef RRScreenChangeNotifyMask
1200             /* Inform Xlib that it's ok to update its data structures. */
1201             XRRUpdateConfiguration (&event.x_event); /* Xrandr.h 1.9, 2002/09/29 */
1202 # endif /* RRScreenChangeNotifyMask */
1203
1204             /* Resize the existing xscreensaver windows and cached ssi data. */
1205             if (update_screen_layout (si))
1206               {
1207                 if (p->verbose_p)
1208                   {
1209                     fprintf (stderr, "%s: new layout:\n", blurb());
1210                     describe_monitor_layout (si);
1211                   }
1212                 resize_screensaver_window (si);
1213               }
1214           }
1215         else
1216 #endif /* HAVE_RANDR */
1217
1218           /* Just some random event.  Let the Widgets handle it, if desired. */
1219           dispatch_event (si, &event.x_event);
1220       }
1221     }
1222  DONE:
1223
1224   if (p->verbose_p)
1225     {
1226       if (! why) why = "unknown reason";
1227       fprintf (stderr, "%s: %s (%s)\n", blurb(),
1228                (until_idle_p ? "user is idle" : "user is active"),
1229                why);
1230     }
1231
1232   /* If there's a user event on the queue, swallow it.
1233      If we're using a server extension, and the user becomes active, we
1234      get the extension event before the user event -- so the keypress or
1235      motion or whatever is still on the queue.  This makes "unfade" not
1236      work, because it sees that event, and bugs out.  (This problem
1237      doesn't exhibit itself without an extension, because in that case,
1238      there's only one event generated by user activity, not two.)
1239    */
1240   if (!until_idle_p && si->locked_p)
1241     swallow_unlock_typeahead_events (si, &event.x_event);
1242   else
1243     while (XCheckMaskEvent (si->dpy,
1244                             (KeyPressMask|ButtonPressMask|PointerMotionMask),
1245                      &event.x_event))
1246       ;
1247
1248
1249   if (si->check_pointer_timer_id)
1250     {
1251       XtRemoveTimeOut (si->check_pointer_timer_id);
1252       si->check_pointer_timer_id = 0;
1253     }
1254   if (si->timer_id)
1255     {
1256       XtRemoveTimeOut (si->timer_id);
1257       si->timer_id = 0;
1258     }
1259
1260   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
1261     abort ();
1262 }
1263
1264
1265 \f
1266 /* Some crap for dealing with /proc/interrupts.
1267
1268    On Linux systems, it's possible to see the hardware interrupt count
1269    associated with the keyboard.  We can therefore use that as another method
1270    of detecting idleness.
1271
1272    Why is it a good idea to do this?  Because it lets us detect keyboard
1273    activity that is not associated with X events.  For example, if the user
1274    has switched to another virtual console, it's good for xscreensaver to not
1275    be running graphics hacks on the (non-visible) X display.  The common
1276    complaint that checking /proc/interrupts addresses is that the user is
1277    playing Quake on a non-X console, and the GL hacks are perceptibly slowing
1278    the game...
1279
1280    This is tricky for a number of reasons.
1281
1282      * First, we must be sure to only do this when running on an X server that
1283        is on the local machine (because otherwise, we'd be reacting to the
1284        wrong keyboard.)  The way we do this is by noting that the $DISPLAY is
1285        pointing to display 0 on the local machine.  It *could* be that display
1286        1 is also on the local machine (e.g., two X servers, each on a different
1287        virtual-terminal) but it's also possible that screen 1 is an X terminal,
1288        using this machine as the host.  So we can't take that chance.
1289
1290      * Second, one can only access these interrupt numbers in a completely
1291        and utterly brain-damaged way.  You would think that one would use an
1292        ioctl for this.  But no.  The ONLY way to get this information is to
1293        open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
1294        out of it TEXTUALLY.  Because this is Unix, and all the world's a file,
1295        and the only real data type is the short-line sequence of ASCII bytes.
1296
1297        Now it's all well and good that the /proc/interrupts pseudo-file
1298        exists; that's a clever idea, and a useful API for things that are
1299        already textually oriented, like shell scripts, and users doing
1300        interactive debugging sessions.  But to make a *C PROGRAM* open a file
1301        and parse the textual representation of integers out of it is just
1302        insane.
1303
1304      * Third, you can't just hold the file open, and fseek() back to the
1305        beginning to get updated data!  If you do that, the data never changes.
1306        And I don't want to call open() every five seconds, because I don't want
1307        to risk going to disk for any inodes.  It turns out that if you dup()
1308        it early, then each copy gets fresh data, so we can get around that in
1309        this way (but for how many releases, one might wonder?)
1310
1311      * Fourth, the format of the output of the /proc/interrupts file is
1312        undocumented, and has changed several times already!  In Linux 2.0.33,
1313        even on a multiprocessor machine, it looks like this:
1314
1315           0:  309453991   timer
1316           1:    4771729   keyboard
1317    
1318        but in Linux 2.2 and 2.4 kernels with MP machines, it looks like this:
1319
1320                    CPU0       CPU1
1321           0:    1671450    1672618    IO-APIC-edge  timer
1322           1:      13037      13495    IO-APIC-edge  keyboard
1323
1324        and in Linux 2.6, it's gotten even goofier: now there are two lines
1325        labelled "i8042".  One of them is the keyboard, and one of them is
1326        the PS/2 mouse -- and of course, you can't tell them apart, except
1327        by wiggling the mouse and noting which one changes:
1328
1329                    CPU0       CPU1
1330           1:      32051      30864    IO-APIC-edge  i8042
1331          12:     476577     479913    IO-APIC-edge  i8042
1332
1333        Joy!  So how are we expected to parse that?  Well, this code doesn't
1334        parse it: it saves the first line with the string "keyboard" (or
1335        "i8042") in it, and does a string-comparison to note when it has
1336        changed.  If there are two "i8042" lines, we assume the first is
1337        the keyboard and the second is the mouse (doesn't matter which is
1338        which, really, as long as we don't compare them against each other.)
1339
1340    Thanks to Nat Friedman <nat@nat.org> for figuring out most of this crap.
1341
1342    Note that if you have a serial or USB mouse, or a USB keyboard, it won't
1343    detect it.  That's because there's no way to tell the difference between a
1344    serial mouse and a general serial port, and all USB devices look the same
1345    from here.  It would be somewhat unfortunate to have the screensaver turn
1346    off when the modem on COM1 burped, or when a USB disk was accessed.
1347  */
1348
1349
1350 #ifdef HAVE_PROC_INTERRUPTS
1351
1352 #define PROC_INTERRUPTS "/proc/interrupts"
1353
1354 Bool
1355 query_proc_interrupts_available (saver_info *si, const char **why)
1356 {
1357   /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
1358      "/proc/interrupts" file exists and is readable.
1359    */
1360   FILE *f;
1361   if (why) *why = 0;
1362
1363   if (!display_is_on_console_p (si))
1364     {
1365       if (why) *why = "not on primary console";
1366       return False;
1367     }
1368
1369   f = fopen (PROC_INTERRUPTS, "r");
1370   if (!f)
1371     {
1372       if (why) *why = "does not exist";
1373       return False;
1374     }
1375
1376   fclose (f);
1377   return True;
1378 }
1379
1380
1381 static Bool
1382 proc_interrupts_activity_p (saver_info *si)
1383 {
1384   static FILE *f0 = 0;
1385   FILE *f1 = 0;
1386   int fd;
1387   static char last_kbd_line[255] = { 0, };
1388   static char last_ptr_line[255] = { 0, };
1389   char new_line[sizeof(last_kbd_line)];
1390   Bool checked_kbd = False, kbd_changed = False;
1391   Bool checked_ptr = False, ptr_changed = False;
1392   int i8042_count = 0;
1393
1394   if (!f0)
1395     {
1396       /* First time -- open the file. */
1397       f0 = fopen (PROC_INTERRUPTS, "r");
1398       if (!f0)
1399         {
1400           char buf[255];
1401           sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
1402           perror (buf);
1403           goto FAIL;
1404         }
1405
1406 # if defined(HAVE_FCNTL) && defined(FD_CLOEXEC)
1407       /* Close this fd upon exec instead of inheriting / leaking it. */
1408       if (fcntl (fileno (f0), F_SETFD, FD_CLOEXEC) != 0)
1409         perror ("fcntl: CLOEXEC:");
1410 # endif
1411     }
1412
1413   if (f0 == (FILE *) -1)            /* means we got an error initializing. */
1414     return False;
1415
1416   fd = dup (fileno (f0));
1417   if (fd < 0)
1418     {
1419       char buf[255];
1420       sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
1421       perror (buf);
1422       goto FAIL;
1423     }
1424
1425   f1 = fdopen (fd, "r");
1426   if (!f1)
1427     {
1428       char buf[255];
1429       sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
1430               PROC_INTERRUPTS);
1431       perror (buf);
1432       goto FAIL;
1433     }
1434
1435   /* Actually, I'm unclear on why this fseek() is necessary, given the timing
1436      of the dup() above, but it is. */
1437   if (fseek (f1, 0, SEEK_SET) != 0)
1438     {
1439       char buf[255];
1440       sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
1441       perror (buf);
1442       goto FAIL;
1443     }
1444
1445   /* Now read through the pseudo-file until we find the "keyboard",
1446      "PS/2 mouse", or "i8042" lines. */
1447
1448   while (fgets (new_line, sizeof(new_line)-1, f1))
1449     {
1450       Bool i8042_p = !!strstr (new_line, "i8042");
1451       if (i8042_p) i8042_count++;
1452
1453       if (strchr (new_line, ','))
1454         {
1455           /* Ignore any line that has a comma on it: this is because
1456              a setup like this:
1457
1458                  12:     930935          XT-PIC  usb-uhci, PS/2 Mouse
1459
1460              is really bad news.  It *looks* like we can note mouse
1461              activity from that line, but really, that interrupt gets
1462              fired any time any USB device has activity!  So we have
1463              to ignore any shared IRQs.
1464            */
1465         }
1466       else if (!checked_kbd &&
1467                (strstr (new_line, "keyboard") ||
1468                 (i8042_p && i8042_count == 1)))
1469         {
1470           /* Assume the keyboard interrupt is the line that says "keyboard",
1471              or the *first* line that says "i8042".
1472            */
1473           kbd_changed = (*last_kbd_line && !!strcmp (new_line, last_kbd_line));
1474           strcpy (last_kbd_line, new_line);
1475           checked_kbd = True;
1476         }
1477       else if (!checked_ptr &&
1478                (strstr (new_line, "PS/2 Mouse") ||
1479                 (i8042_p && i8042_count == 2)))
1480         {
1481           /* Assume the mouse interrupt is the line that says "PS/2 mouse",
1482              or the *second* line that says "i8042".
1483            */
1484           ptr_changed = (*last_ptr_line && !!strcmp (new_line, last_ptr_line));
1485           strcpy (last_ptr_line, new_line);
1486           checked_ptr = True;
1487         }
1488
1489       if (checked_kbd && checked_ptr)
1490         break;
1491     }
1492
1493   if (checked_kbd || checked_ptr)
1494     {
1495       fclose (f1);
1496
1497       if (si->prefs.debug_p && (kbd_changed || ptr_changed))
1498         fprintf (stderr, "%s: /proc/interrupts activity: %s\n",
1499                  blurb(),
1500                  ((kbd_changed && ptr_changed) ? "mouse and kbd" :
1501                   kbd_changed ? "kbd" :
1502                   ptr_changed ? "mouse" : "ERR"));
1503
1504       return (kbd_changed || ptr_changed);
1505     }
1506
1507
1508   /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse"
1509      line in the file at all. */
1510   fprintf (stderr, "%s: no keyboard or mouse data in %s?\n",
1511            blurb(), PROC_INTERRUPTS);
1512
1513  FAIL:
1514   if (f1)
1515     fclose (f1);
1516
1517   if (f0 && f0 != (FILE *) -1)
1518     fclose (f0);
1519
1520   f0 = (FILE *) -1;
1521   return False;
1522 }
1523
1524 #endif /* HAVE_PROC_INTERRUPTS */
1525
1526 \f
1527 /* This timer goes off every few minutes, whether the user is idle or not,
1528    to try and clean up anything that has gone wrong.
1529
1530    It calls disable_builtin_screensaver() so that if xset has been used,
1531    or some other program (like xlock) has messed with the XSetScreenSaver()
1532    settings, they will be set back to sensible values (if a server extension
1533    is in use, messing with xlock can cause xscreensaver to never get a wakeup
1534    event, and could cause monitor power-saving to occur, and all manner of
1535    heinousness.)
1536
1537    If the screen is currently blanked, it raises the window, in case some
1538    other window has been mapped on top of it.
1539
1540    If the screen is currently blanked, and there is no hack running, it
1541    clears the window, in case there is an error message printed on it (we
1542    don't want the error message to burn in.)
1543  */
1544
1545 static void
1546 watchdog_timer (XtPointer closure, XtIntervalId *id)
1547 {
1548   saver_info *si = (saver_info *) closure;
1549   saver_preferences *p = &si->prefs;
1550
1551   disable_builtin_screensaver (si, False);
1552
1553   /* If the DPMS settings on the server have changed, change them back to
1554      what ~/.xscreensaver says they should be. */
1555   sync_server_dpms_settings (si->dpy,
1556                              (p->dpms_enabled_p  &&
1557                               p->mode != DONT_BLANK),
1558                              p->dpms_quickoff_p,
1559                              p->dpms_standby / 1000,
1560                              p->dpms_suspend / 1000,
1561                              p->dpms_off / 1000,
1562                              False);
1563
1564   if (si->screen_blanked_p)
1565     {
1566       Bool running_p = screenhack_running_p (si);
1567
1568       if (si->dbox_up_p)
1569         {
1570           if (si->prefs.debug_p)
1571             fprintf (stderr, "%s: dialog box is up: not raising screen.\n",
1572                      blurb());
1573         }
1574       else
1575         {
1576           if (si->prefs.debug_p)
1577             fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
1578                      blurb(), (running_p ? "" : "and clearing "));
1579
1580           raise_window (si, True, True, running_p);
1581         }
1582
1583       if (screenhack_running_p (si) &&
1584           !monitor_powered_on_p (si))
1585         {
1586           int i;
1587           if (si->prefs.verbose_p)
1588             fprintf (stderr,
1589                      "%s: X says monitor has powered down; "
1590                      "killing running hacks.\n", blurb());
1591           for (i = 0; i < si->nscreens; i++)
1592             kill_screenhack (&si->screens[i]);
1593         }
1594
1595       /* Re-schedule this timer.  The watchdog timer defaults to a bit less
1596          than the hack cycle period, but is never longer than one hour.
1597        */
1598       si->watchdog_id = 0;
1599       reset_watchdog_timer (si, True);
1600     }
1601 }
1602
1603
1604 void
1605 reset_watchdog_timer (saver_info *si, Bool on_p)
1606 {
1607   saver_preferences *p = &si->prefs;
1608
1609   if (si->watchdog_id)
1610     {
1611       XtRemoveTimeOut (si->watchdog_id);
1612       si->watchdog_id = 0;
1613     }
1614
1615   if (on_p && p->watchdog_timeout)
1616     {
1617       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
1618                                          watchdog_timer, (XtPointer) si);
1619
1620       if (p->debug_p)
1621         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
1622                  blurb(), p->watchdog_timeout, si->watchdog_id);
1623     }
1624 }
1625
1626
1627 /* It's possible that a race condition could have led to the saver
1628    window being unexpectedly still mapped.  This can happen like so:
1629
1630     - screen is blanked
1631     - hack is launched
1632     - that hack tries to grab a screen image (it does this by
1633       first unmapping the saver window, then remapping it.)
1634     - hack unmaps window
1635     - hack waits
1636     - user becomes active
1637     - hack re-maps window (*)
1638     - driver kills subprocess
1639     - driver unmaps window (**)
1640
1641    The race is that (*) might have been sent to the server before
1642    the client process was killed, but, due to scheduling randomness,
1643    might not have been received by the server until after (**).
1644    In other words, (*) and (**) might happen out of order, meaning
1645    the driver will unmap the window, and then after that, the
1646    recently-dead client will re-map it.  This leaves the user
1647    locked out (it looks like a desktop, but it's not!)
1648
1649    To avoid this: after un-blanking the screen, we launch a timer
1650    that wakes up once a second for ten seconds, and makes damned
1651    sure that the window is still unmapped.
1652  */
1653
1654 void
1655 de_race_timer (XtPointer closure, XtIntervalId *id)
1656 {
1657   saver_info *si = (saver_info *) closure;
1658   saver_preferences *p = &si->prefs;
1659   int secs = 1;
1660
1661   if (id == 0)  /* if id is 0, this is the initialization call. */
1662     {
1663       si->de_race_ticks = 10;
1664       if (p->verbose_p)
1665         fprintf (stderr, "%s: starting de-race timer (%d seconds.)\n",
1666                  blurb(), si->de_race_ticks);
1667     }
1668   else
1669     {
1670       int i;
1671       XSync (si->dpy, False);
1672       for (i = 0; i < si->nscreens; i++)
1673         {
1674           saver_screen_info *ssi = &si->screens[i];
1675           Window w = ssi->screensaver_window;
1676           XWindowAttributes xgwa;
1677           XGetWindowAttributes (si->dpy, w, &xgwa);
1678           if (xgwa.map_state != IsUnmapped)
1679             {
1680               if (p->verbose_p)
1681                 fprintf (stderr,
1682                          "%s: %d: client race! emergency unmap 0x%lx.\n",
1683                          blurb(), i, (unsigned long) w);
1684               XUnmapWindow (si->dpy, w);
1685             }
1686           else if (p->debug_p)
1687             fprintf (stderr, "%s: %d: (de-race of 0x%lx is cool.)\n",
1688                      blurb(), i, (unsigned long) w);
1689         }
1690       XSync (si->dpy, False);
1691
1692       si->de_race_ticks--;
1693     }
1694
1695   if (id && *id == si->de_race_id)
1696     si->de_race_id = 0;
1697
1698   if (si->de_race_id) abort();
1699
1700   if (si->de_race_ticks <= 0)
1701     {
1702       si->de_race_id = 0;
1703       if (p->verbose_p)
1704         fprintf (stderr, "%s: de-race completed.\n", blurb());
1705     }
1706   else
1707     {
1708       si->de_race_id = XtAppAddTimeOut (si->app, secs * 1000,
1709                                         de_race_timer, closure);
1710     }
1711 }