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