From http://www.jwz.org/xscreensaver/xscreensaver-5.35.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 <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 static void
567 check_for_clock_skew (saver_info *si)
568 {
569   saver_preferences *p = &si->prefs;
570   time_t now = time ((time_t *) 0);
571   long shift = now - si->last_wall_clock_time;
572
573   if (p->debug_p)
574     {
575       int i = (si->last_wall_clock_time == 0 ? 0 : shift);
576       fprintf (stderr,
577                "%s: checking wall clock for hibernation (%d:%02d:%02d).\n",
578                blurb(),
579                (i / (60 * 60)), ((i / 60) % 60), (i % 60));
580     }
581
582   if (si->last_wall_clock_time != 0 &&
583       shift > (p->timeout / 1000))
584     {
585       if (p->verbose_p)
586         fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld%s\n",
587                  blurb(),
588                  (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60),
589                  (p->mode == DONT_BLANK ? " while saver disabled" : ""));
590
591       /* If the saver is entirely disabled, there's no need to do the
592          emergency-blank-and-lock thing.
593        */
594       if (p->mode != DONT_BLANK)
595         {
596           si->emergency_lock_p = True;
597           idle_timer ((XtPointer) si, 0);
598         }
599     }
600
601   si->last_wall_clock_time = now;
602 }
603
604
605
606 static void
607 dispatch_event (saver_info *si, XEvent *event)
608 {
609   /* If this is for the splash dialog, pass it along.
610      Note that the password dialog is handled with its own event loop,
611      so events for that window will never come through here.
612    */
613   if (si->splash_dialog && event->xany.window == si->splash_dialog)
614     handle_splash_event (si, event);
615
616   XtDispatchEvent (event);
617 }
618
619
620 static void
621 swallow_unlock_typeahead_events (saver_info *si, XEvent *e)
622 {
623   XEvent event;
624   char buf [100];
625   int i = 0;
626
627   memset (buf, 0, sizeof(buf));
628
629   event = *e;
630
631   do
632     {
633       if (event.xany.type == KeyPress)
634         {
635           char s[2];
636           int size = XLookupString ((XKeyEvent *) &event, s, 1, 0, 0);
637           if (size != 1) continue;
638           switch (*s)
639             {
640             case '\010': case '\177':                   /* Backspace */
641               if (i > 0) i--;
642               break;
643             case '\025': case '\030':                   /* Erase line */
644             case '\012': case '\015':                   /* Enter */
645             case '\033':                                /* ESC */
646               i = 0;
647               break;
648             case '\040':                                /* Space */
649               if (i == 0)
650                 break;  /* ignore space at beginning of line */
651               /* else, fall through */
652             default:
653               buf [i++] = *s;
654               break;
655             }
656         }
657
658     } while (i < sizeof(buf)-1 &&
659              XCheckMaskEvent (si->dpy, KeyPressMask, &event));
660
661   buf[i] = 0;
662
663   if (si->unlock_typeahead)
664     {
665       memset (si->unlock_typeahead, 0, strlen(si->unlock_typeahead));
666       free (si->unlock_typeahead);
667     }
668
669   if (i > 0)
670     si->unlock_typeahead = strdup (buf);
671   else
672     si->unlock_typeahead = 0;
673
674   memset (buf, 0, sizeof(buf));
675 }
676
677
678 /* methods of detecting idleness:
679
680       explicitly informed by SGI SCREEN_SAVER server event;
681       explicitly informed by MIT-SCREEN-SAVER server event;
682       poll server idle time with XIDLE extension;
683       select events on all windows, and note absence of recent events;
684       note that /proc/interrupts has not changed in a while;
685       activated by clientmessage.
686
687    methods of detecting non-idleness:
688
689       read events on the xscreensaver window;
690       explicitly informed by SGI SCREEN_SAVER server event;
691       explicitly informed by MIT-SCREEN-SAVER server event;
692       select events on all windows, and note events on any of them;
693       note that a client updated their window's _NET_WM_USER_TIME property;
694       note that /proc/interrupts has changed;
695       deactivated by clientmessage.
696
697    I trust that explains why this function is a big hairy mess.
698  */
699 void
700 sleep_until_idle (saver_info *si, Bool until_idle_p)
701 {
702   saver_preferences *p = &si->prefs;
703
704   /* We have to go through this union bullshit because gcc-4.4.0 has
705      stricter struct-aliasing rules.  Without this, the optimizer
706      can fuck things up.
707    */
708   union {
709     XEvent x_event;
710 # ifdef HAVE_RANDR
711     XRRScreenChangeNotifyEvent xrr_event;
712 # endif /* HAVE_RANDR */
713 # ifdef HAVE_MIT_SAVER_EXTENSION
714     XScreenSaverNotifyEvent sevent;
715 # endif /* HAVE_MIT_SAVER_EXTENSION */
716   } event;
717
718   /* We need to select events on all windows if we're not using any extensions.
719      Otherwise, we don't need to. */
720   Bool scanning_all_windows = !(si->using_xidle_extension ||
721                                 si->using_mit_saver_extension ||
722                                 si->using_sgi_saver_extension);
723
724   /* We need to periodically wake up and check for idleness if we're not using
725      any extensions, or if we're using the XIDLE extension.  The other two
726      extensions explicitly deliver events when we go idle/non-idle, so we
727      don't need to poll. */
728   Bool polling_for_idleness = !(si->using_mit_saver_extension ||
729                                 si->using_sgi_saver_extension);
730
731   /* Whether we need to periodically wake up and check to see if the mouse has
732      moved.  We only need to do this when not using any extensions.  The reason
733      this isn't the same as `polling_for_idleness' is that the "idleness" poll
734      can happen (for example) 5 minutes from now, whereas the mouse-position
735      poll should happen with low periodicity.  We don't need to poll the mouse
736      position with the XIDLE extension, but we do need to periodically wake up
737      and query the server with that extension.  For our purposes, polling
738      /proc/interrupts is just like polling the mouse position.  It has to
739      happen on the same kind of schedule. */
740   Bool polling_mouse_position = (si->using_proc_interrupts ||
741                                  !(si->using_xidle_extension ||
742                                    si->using_mit_saver_extension ||
743                                    si->using_sgi_saver_extension) ||
744                                    si->using_xinput_extension);
745
746   const char *why = 0;  /* What caused the idle-state to change? */
747
748   if (until_idle_p)
749     {
750       if (polling_for_idleness)
751         /* This causes a no-op event to be delivered to us in a while, so that
752            we come back around through the event loop again.  */
753         schedule_wakeup_event (si, p->timeout, p->debug_p);
754
755       if (polling_mouse_position)
756         /* Check to see if the mouse has moved, and set up a repeating timer
757            to do so periodically (typically, every 5 seconds.) */
758         check_pointer_timer ((XtPointer) si, 0);
759     }
760
761   while (1)
762     {
763       XtAppNextEvent (si->app, &event.x_event);
764
765       switch (event.x_event.xany.type) {
766       case 0:           /* our synthetic "timeout" event has been signalled */
767         if (until_idle_p)
768           {
769             Time idle;
770
771             /* We may be idle; check one last time to see if the mouse has
772                moved, just in case the idle-timer went off within the 5 second
773                window between mouse polling.  If the mouse has moved, then
774                check_pointer_timer() will reset last_activity_time.
775              */
776             if (polling_mouse_position)
777               check_pointer_timer ((XtPointer) si, 0);
778
779 #ifdef HAVE_XIDLE_EXTENSION
780             if (si->using_xidle_extension)
781               {
782                 /* The XIDLE extension uses the synthetic event to prod us into
783                    re-asking the server how long the user has been idle. */
784                 if (! XGetIdleTime (si->dpy, &idle))
785                   {
786                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
787                     saver_exit (si, 1, 0);
788                   }
789               }
790             else
791 #endif /* HAVE_XIDLE_EXTENSION */
792 #ifdef HAVE_MIT_SAVER_EXTENSION
793               if (si->using_mit_saver_extension)
794                 {
795                   /* We don't need to do anything in this case - the synthetic
796                      event isn't necessary, as we get sent specific events
797                      to wake us up.  In fact, this event generally shouldn't
798                      be being delivered when the MIT extension is in use. */
799                   idle = 0;
800                 }
801             else
802 #endif /* HAVE_MIT_SAVER_EXTENSION */
803 #ifdef HAVE_SGI_SAVER_EXTENSION
804               if (si->using_sgi_saver_extension)
805                 {
806                   /* We don't need to do anything in this case - the synthetic
807                      event isn't necessary, as we get sent specific events
808                      to wake us up.  In fact, this event generally shouldn't
809                      be being delivered when the SGI extension is in use. */
810                   idle = 0;
811                 }
812             else
813 #endif /* HAVE_SGI_SAVER_EXTENSION */
814               {
815                 /* Otherwise, no server extension is in use.  The synthetic
816                    event was to tell us to wake up and see if the user is now
817                    idle.  Compute the amount of idle time by comparing the
818                    `last_activity_time' to the wall clock.  The l_a_t was set
819                    by calling `reset_timers()', which is called only in only
820                    two situations: when polling the mouse position has revealed
821                    the the mouse has moved (user activity) or when we have read
822                    an event (again, user activity.)
823                  */
824                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
825               }
826
827             if (idle >= p->timeout)
828               {
829                 /* Look, we've been idle long enough.  We're done. */
830                 why = "timeout";
831                 goto DONE;
832               }
833             else if (si->emergency_lock_p)
834               {
835                 /* Oops, the wall clock has jumped far into the future, so
836                    we need to lock down in a hurry! */
837                 why = "large wall clock change";
838                 goto DONE;
839               }
840             else
841               {
842                 /* The event went off, but it turns out that the user has not
843                    yet been idle for long enough.  So re-signal the event.
844                    Be economical: if we should blank after 5 minutes, and the
845                    user has been idle for 2 minutes, then set this timer to
846                    go off in 3 minutes.
847                  */
848                 if (polling_for_idleness)
849                   schedule_wakeup_event (si, p->timeout - idle, p->debug_p);
850               }
851           }
852         break;
853
854       case ClientMessage:
855         if (handle_clientmessage (si, &event.x_event, until_idle_p))
856           {
857             why = "ClientMessage";
858             goto DONE;
859           }
860         break;
861
862       case CreateNotify:
863         /* A window has been created on the screen somewhere.  If we're
864            supposed to scan all windows for events, prepare this window. */
865         if (scanning_all_windows)
866           {
867             Window w = event.x_event.xcreatewindow.window;
868             start_notice_events_timer (si, w, p->debug_p);
869           }
870         break;
871
872       case KeyPress:
873       case ButtonPress:
874       /* Ignore release events so that hitting ESC at the password dialog
875          doesn't result in the password dialog coming right back again when
876          the fucking release key is seen! */
877       /* case KeyRelease:*/
878       /* case ButtonRelease:*/
879       case MotionNotify:
880
881         if (p->debug_p)
882           {
883             Window root=0, window=0;
884             int x=-1, y=-1;
885             const char *type = 0;
886             if (event.x_event.xany.type == MotionNotify)
887               {
888                 /*type = "MotionNotify";*/
889                 root = event.x_event.xmotion.root;
890                 window = event.x_event.xmotion.window;
891                 x = event.x_event.xmotion.x_root;
892                 y = event.x_event.xmotion.y_root;
893               }
894             else if (event.x_event.xany.type == KeyPress)
895               {
896                 type = "KeyPress";
897                 root = event.x_event.xkey.root;
898                 window = event.x_event.xkey.window;
899                 x = y = -1;
900               }
901             else if (event.x_event.xany.type == ButtonPress)
902               {
903                 type = "ButtonPress";
904                 root = event.x_event.xkey.root;
905                 window = event.x_event.xkey.window;
906                 x = event.x_event.xmotion.x_root;
907                 y = event.x_event.xmotion.y_root;
908               }
909
910             if (type)
911               {
912                 int i;
913                 for (i = 0; i < si->nscreens; i++)
914                   if (root == RootWindowOfScreen (si->screens[i].screen))
915                     break;
916                 fprintf (stderr,"%s: %d: %s on 0x%lx",
917                          blurb(), i, type, (unsigned long) window);
918
919                 /* Be careful never to do this unless in -debug mode, as
920                    this could expose characters from the unlock password. */
921                 if (p->debug_p && event.x_event.xany.type == KeyPress)
922                   {
923                     KeySym keysym;
924                     char c = 0;
925                     XLookupString (&event.x_event.xkey, &c, 1, &keysym, 0);
926                     fprintf (stderr, " (%s%s)",
927                              (event.x_event.xkey.send_event ? "synthetic " : ""),
928                              XKeysymToString (keysym));
929                   }
930
931                 if (x == -1)
932                   fprintf (stderr, "\n");
933                 else
934                   fprintf (stderr, " at %d,%d.\n", x, y);
935               }
936           }
937
938         /* If any widgets want to handle this event, let them. */
939         dispatch_event (si, &event.x_event);
940
941         
942         /* If we got a MotionNotify event, figure out what screen it
943            was on and poll the mouse there: if the mouse hasn't moved
944            far enough to count as "real" motion, then ignore this
945            event.
946          */
947         if (event.x_event.xany.type == MotionNotify)
948           {
949             int i;
950             for (i = 0; i < si->nscreens; i++)
951               if (event.x_event.xmotion.root ==
952                   RootWindowOfScreen (si->screens[i].screen))
953                 break;
954             if (i < si->nscreens)
955               {
956                 if (!pointer_moved_p (&si->screens[i], False))
957                   continue;
958               }
959           }
960
961
962         /* We got a user event.
963            If we're waiting for the user to become active, this is it.
964            If we're waiting until the user becomes idle, reset the timers
965            (since now we have longer to wait.)
966          */
967         if (!until_idle_p)
968           {
969             if (si->demoing_p &&
970                 (event.x_event.xany.type == MotionNotify ||
971                  event.x_event.xany.type == KeyRelease))
972               /* When we're demoing a single hack, mouse motion doesn't
973                  cause deactivation.  Only clicks and keypresses do. */
974               ;
975             else
976               {
977                 /* If we're not demoing, then any activity causes deactivation.
978                  */
979                 why = (event.x_event.xany.type == MotionNotify ?"mouse motion":
980                        event.x_event.xany.type == KeyPress?"keyboard activity":
981                        event.x_event.xany.type == ButtonPress ? "mouse click" :
982                        "unknown user activity");
983                 goto DONE;
984               }
985           }
986         else
987           reset_timers (si);
988
989         break;
990
991       case PropertyNotify:
992
993         /* Starting in late 2014, GNOME programs don't actually select for
994            or receive KeyPress events: they do it behind the scenes through
995            some kind of Input Method magic, even when running in an en_US
996            locale.  However, those applications *do* update the WM_USER_TIME
997            property on their own windows every time they recieve a secret
998            KeyPress, so we must *also* monitor that property on every
999            window, and treat changes to it as identical to KeyPress.
1000
1001            _NET_WM_USER_TIME is documented (such as it is) here:
1002
1003              http://standards.freedesktop.org/wm-spec/latest/ar01s05.html
1004              #idm139870829932528
1005
1006            Specifically:
1007
1008              "Contains the XServer time at which last user activity in this
1009              window took place. [...]  A client [...] might, for example,
1010              use the timestamp of the last KeyPress or ButtonPress event."
1011
1012            As of early 2016, KDE4 does something really stupid, though: some
1013            hidden power management thing reduces the display brightness 150
1014            seconds after the screen is blanked -- and sets a WM_USER_TIME
1015            property on a hidden "kded4" window whose time is in the distant
1016            past (the time at which the X server launched).
1017
1018            So we ignore any WM_USER_TIME whose timestamp is more than a
1019            couple seconds old.
1020          */
1021         if (event.x_event.xproperty.state == PropertyNewValue &&
1022             event.x_event.xproperty.atom == XA_NET_WM_USER_TIME)
1023           {
1024             int threshold = 2; /* seconds */
1025             Bool bogus_p = True;
1026             Window w = event.x_event.xproperty.window;
1027
1028             Atom type;
1029             int format;
1030             unsigned long nitems, bytesafter;
1031             unsigned char *data = 0;
1032             Cardinal user_time = 0;
1033             XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
1034
1035             if (XGetWindowProperty (si->dpy, w, 
1036                                     XA_NET_WM_USER_TIME, 0L, 1L, False,
1037                                     XA_CARDINAL, &type, &format, &nitems,
1038                                     &bytesafter, &data)
1039                 == Success &&
1040                 data &&
1041                 type == XA_CARDINAL &&
1042                 format == 32 &&
1043                 nitems == 1)
1044               {
1045                 long diff;
1046                 user_time = ((Cardinal *) data)[0];
1047                 diff = event.x_event.xproperty.time - user_time;
1048                 if (diff >= 0 && diff < threshold)
1049                   bogus_p = False;
1050               }
1051
1052             if (data) XFree (data);
1053
1054             why = "WM_USER_TIME";
1055
1056             if (p->debug_p)
1057               {
1058                 XWindowAttributes xgwa;
1059                 int i;
1060
1061                 XGetWindowAttributes (si->dpy, w, &xgwa);
1062                 for (i = 0; i < si->nscreens; i++)
1063                   if (xgwa.root == RootWindowOfScreen (si->screens[i].screen))
1064                     break;
1065                 fprintf (stderr,"%s: %d: %s = %ld%s on 0x%lx\n",
1066                          blurb(), i, why, (unsigned long) user_time,
1067                          (bogus_p ? " (bad)" : ""),
1068                          (unsigned long) w);
1069               }
1070
1071             XSync (si->dpy, False);
1072             XSetErrorHandler (old_handler);
1073
1074             if (bogus_p)
1075               break;
1076             else if (until_idle_p)
1077               reset_timers (si);
1078             else
1079               goto DONE;
1080           }
1081         break;
1082
1083       default:
1084
1085 #ifdef HAVE_MIT_SAVER_EXTENSION
1086         if (event.x_event.type == si->mit_saver_ext_event_number)
1087           {
1088             /* This event's number is that of the MIT-SCREEN-SAVER server
1089                extension.  This extension has one event number, and the event
1090                itself contains sub-codes that say what kind of event it was
1091                (an "idle" or "not-idle" event.)
1092              */
1093             if (event.sevent.state == ScreenSaverOn)
1094               {
1095                 int i = 0;
1096                 if (p->verbose_p)
1097                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
1098                            blurb());
1099
1100                 /* Get the "real" server window(s) out of the way as soon
1101                    as possible. */
1102                 for (i = 0; i < si->nscreens; i++)
1103                   {
1104                     saver_screen_info *ssi = &si->screens[i];
1105                     if (ssi->server_mit_saver_window &&
1106                         window_exists_p (si->dpy,
1107                                          ssi->server_mit_saver_window))
1108                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
1109                   }
1110
1111                 if (event.sevent.kind != ScreenSaverExternal)
1112                   {
1113                     fprintf (stderr,
1114                          "%s: ScreenSaverOn event wasn't of type External!\n",
1115                              blurb());
1116                   }
1117
1118                 if (until_idle_p)
1119                   {
1120                     why = "MIT ScreenSaverOn";
1121                     goto DONE;
1122                   }
1123               }
1124             else if (event.sevent.state == ScreenSaverOff)
1125               {
1126                 if (p->verbose_p)
1127                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
1128                            blurb());
1129                 if (!until_idle_p)
1130                   {
1131                     why = "MIT ScreenSaverOff";
1132                     goto DONE;
1133                   }
1134               }
1135             else
1136               fprintf (stderr,
1137                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
1138                        blurb(), event.sevent.state);
1139           }
1140         else
1141
1142 #endif /* HAVE_MIT_SAVER_EXTENSION */
1143
1144
1145 #ifdef HAVE_SGI_SAVER_EXTENSION
1146         if (event.x_event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
1147           {
1148             /* The SGI SCREEN_SAVER server extension has two event numbers,
1149                and this event matches the "idle" event. */
1150             if (p->verbose_p)
1151               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
1152                        blurb());
1153
1154             if (until_idle_p)
1155               {
1156                 why = "SGI ScreenSaverStart";
1157                 goto DONE;
1158               }
1159           }
1160         else if (event.x_event.type == (si->sgi_saver_ext_event_number +
1161                                 ScreenSaverEnd))
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 ScreenSaverEnd event received.\n",
1167                        blurb());
1168             if (!until_idle_p)
1169               {
1170                 why = "SGI ScreenSaverEnd";
1171                 goto DONE;
1172               }
1173           }
1174         else
1175 #endif /* HAVE_SGI_SAVER_EXTENSION */
1176
1177 #ifdef HAVE_XINPUT
1178         /* If we got a MotionNotify event, check to see if the mouse has
1179            moved far enough to count as "real" motion, if not, then ignore
1180            this event.
1181          */
1182         if ((si->num_xinput_devices > 0) &&
1183             (event.x_event.type == si->xinput_DeviceMotionNotify))
1184           {
1185             XDeviceMotionEvent *dme = (XDeviceMotionEvent *) &event;
1186             poll_mouse_data *last_poll_mouse = NULL;
1187             int d;
1188
1189             for (d = 0; d < si->num_xinput_devices; d++)
1190               {
1191                 if (si->xinput_devices[d].device->device_id == dme->deviceid)
1192                   {
1193                     last_poll_mouse = &(si->xinput_devices[d].last_poll_mouse);
1194                     break;
1195                   }
1196               }
1197
1198             if (last_poll_mouse)
1199               {
1200                 poll_mouse_data this_poll_mouse;
1201                 this_poll_mouse.root_x = dme->x_root;
1202                 this_poll_mouse.root_y = dme->y_root;
1203                 this_poll_mouse.child = dme->subwindow;
1204                 this_poll_mouse.mask = dme->device_state;
1205                 this_poll_mouse.time = dme->time / 1000; /* milliseconds */
1206
1207                 if (!device_pointer_moved_p (si, last_poll_mouse,
1208                                              &this_poll_mouse, False,
1209                                              "device", dme->deviceid))
1210                   continue;
1211               }
1212             else if (p->debug_p)
1213               fprintf (stderr,
1214                        "%s: received MotionNotify from unknown device %d\n",
1215                        blurb(), (int) dme->deviceid);
1216           }
1217         
1218         if ((!until_idle_p) &&
1219             (si->num_xinput_devices > 0) &&
1220             (event.x_event.type == si->xinput_DeviceMotionNotify ||
1221              event.x_event.type == si->xinput_DeviceButtonPress))
1222           /* Ignore DeviceButtonRelease, see ButtonRelease comment above. */
1223           {
1224
1225             dispatch_event (si, &event.x_event);
1226             if (si->demoing_p &&
1227                 event.x_event.type == si->xinput_DeviceMotionNotify)
1228               /* When we're demoing a single hack, mouse motion doesn't
1229                  cause deactivation.  Only clicks and keypresses do. */
1230               ;
1231             else
1232               /* If we're not demoing, then any activity causes deactivation.
1233                */
1234               {
1235                 why = (event.x_event.type == si->xinput_DeviceMotionNotify
1236                        ? "XI mouse motion" :
1237                        event.x_event.type == si->xinput_DeviceButtonPress
1238                        ? "XI mouse click" : "unknown XINPUT event");
1239                 goto DONE;
1240               }
1241           }
1242         else
1243 #endif /* HAVE_XINPUT */
1244
1245 #ifdef HAVE_RANDR
1246         if (si->using_randr_extension &&
1247             (event.x_event.type == 
1248              (si->randr_event_number + RRScreenChangeNotify)))
1249           {
1250             /* The Resize and Rotate extension sends an event when the
1251                size, rotation, or refresh rate of any screen has changed.
1252              */
1253             if (p->verbose_p)
1254               {
1255                 /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
1256                 int screen = XRRRootToScreen (si->dpy, event.xrr_event.window);
1257                 fprintf (stderr, "%s: %d: screen change event received\n",
1258                          blurb(), screen);
1259               }
1260
1261 # ifdef RRScreenChangeNotifyMask
1262             /* Inform Xlib that it's ok to update its data structures. */
1263             XRRUpdateConfiguration (&event.x_event); /* Xrandr.h 1.9, 2002/09/29 */
1264 # endif /* RRScreenChangeNotifyMask */
1265
1266             /* Resize the existing xscreensaver windows and cached ssi data. */
1267             if (update_screen_layout (si))
1268               {
1269                 if (p->verbose_p)
1270                   {
1271                     fprintf (stderr, "%s: new layout:\n", blurb());
1272                     describe_monitor_layout (si);
1273                   }
1274                 resize_screensaver_window (si);
1275               }
1276           }
1277         else
1278 #endif /* HAVE_RANDR */
1279
1280           /* Just some random event.  Let the Widgets handle it, if desired. */
1281           dispatch_event (si, &event.x_event);
1282       }
1283     }
1284  DONE:
1285
1286   if (p->verbose_p)
1287     {
1288       if (! why) why = "unknown reason";
1289       fprintf (stderr, "%s: %s (%s)\n", blurb(),
1290                (until_idle_p ? "user is idle" : "user is active"),
1291                why);
1292     }
1293
1294   /* If there's a user event on the queue, swallow it.
1295      If we're using a server extension, and the user becomes active, we
1296      get the extension event before the user event -- so the keypress or
1297      motion or whatever is still on the queue.  This makes "unfade" not
1298      work, because it sees that event, and bugs out.  (This problem
1299      doesn't exhibit itself without an extension, because in that case,
1300      there's only one event generated by user activity, not two.)
1301    */
1302   if (!until_idle_p && si->locked_p)
1303     swallow_unlock_typeahead_events (si, &event.x_event);
1304   else
1305     while (XCheckMaskEvent (si->dpy,
1306                             (KeyPressMask|ButtonPressMask|PointerMotionMask),
1307                      &event.x_event))
1308       ;
1309
1310
1311   if (si->check_pointer_timer_id)
1312     {
1313       XtRemoveTimeOut (si->check_pointer_timer_id);
1314       si->check_pointer_timer_id = 0;
1315     }
1316   if (si->timer_id)
1317     {
1318       XtRemoveTimeOut (si->timer_id);
1319       si->timer_id = 0;
1320     }
1321
1322   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
1323     abort ();
1324 }
1325
1326
1327 \f
1328 /* Some crap for dealing with /proc/interrupts.
1329
1330    On Linux systems, it's possible to see the hardware interrupt count
1331    associated with the keyboard.  We can therefore use that as another method
1332    of detecting idleness.
1333
1334    Why is it a good idea to do this?  Because it lets us detect keyboard
1335    activity that is not associated with X events.  For example, if the user
1336    has switched to another virtual console, it's good for xscreensaver to not
1337    be running graphics hacks on the (non-visible) X display.  The common
1338    complaint that checking /proc/interrupts addresses is that the user is
1339    playing Quake on a non-X console, and the GL hacks are perceptibly slowing
1340    the game...
1341
1342    This is tricky for a number of reasons.
1343
1344      * First, we must be sure to only do this when running on an X server that
1345        is on the local machine (because otherwise, we'd be reacting to the
1346        wrong keyboard.)  The way we do this is by noting that the $DISPLAY is
1347        pointing to display 0 on the local machine.  It *could* be that display
1348        1 is also on the local machine (e.g., two X servers, each on a different
1349        virtual-terminal) but it's also possible that screen 1 is an X terminal,
1350        using this machine as the host.  So we can't take that chance.
1351
1352      * Second, one can only access these interrupt numbers in a completely
1353        and utterly brain-damaged way.  You would think that one would use an
1354        ioctl for this.  But no.  The ONLY way to get this information is to
1355        open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
1356        out of it TEXTUALLY.  Because this is Unix, and all the world's a file,
1357        and the only real data type is the short-line sequence of ASCII bytes.
1358
1359        Now it's all well and good that the /proc/interrupts pseudo-file
1360        exists; that's a clever idea, and a useful API for things that are
1361        already textually oriented, like shell scripts, and users doing
1362        interactive debugging sessions.  But to make a *C PROGRAM* open a file
1363        and parse the textual representation of integers out of it is just
1364        insane.
1365
1366      * Third, you can't just hold the file open, and fseek() back to the
1367        beginning to get updated data!  If you do that, the data never changes.
1368        And I don't want to call open() every five seconds, because I don't want
1369        to risk going to disk for any inodes.  It turns out that if you dup()
1370        it early, then each copy gets fresh data, so we can get around that in
1371        this way (but for how many releases, one might wonder?)
1372
1373      * Fourth, the format of the output of the /proc/interrupts file is
1374        undocumented, and has changed several times already!  In Linux 2.0.33,
1375        even on a multiprocessor machine, it looks like this:
1376
1377           0:  309453991   timer
1378           1:    4771729   keyboard
1379    
1380        but in Linux 2.2 and 2.4 kernels with MP machines, it looks like this:
1381
1382                    CPU0       CPU1
1383           0:    1671450    1672618    IO-APIC-edge  timer
1384           1:      13037      13495    IO-APIC-edge  keyboard
1385
1386        and in Linux 2.6, it's gotten even goofier: now there are two lines
1387        labelled "i8042".  One of them is the keyboard, and one of them is
1388        the PS/2 mouse -- and of course, you can't tell them apart, except
1389        by wiggling the mouse and noting which one changes:
1390
1391                    CPU0       CPU1
1392           1:      32051      30864    IO-APIC-edge  i8042
1393          12:     476577     479913    IO-APIC-edge  i8042
1394
1395        Joy!  So how are we expected to parse that?  Well, this code doesn't
1396        parse it: it saves the first line with the string "keyboard" (or
1397        "i8042") in it, and does a string-comparison to note when it has
1398        changed.  If there are two "i8042" lines, we assume the first is
1399        the keyboard and the second is the mouse (doesn't matter which is
1400        which, really, as long as we don't compare them against each other.)
1401
1402    Thanks to Nat Friedman <nat@nat.org> for figuring out most of this crap.
1403
1404    Note that if you have a serial or USB mouse, or a USB keyboard, it won't
1405    detect it.  That's because there's no way to tell the difference between a
1406    serial mouse and a general serial port, and all USB devices look the same
1407    from here.  It would be somewhat unfortunate to have the screensaver turn
1408    off when the modem on COM1 burped, or when a USB disk was accessed.
1409  */
1410
1411
1412 #ifdef HAVE_PROC_INTERRUPTS
1413
1414 #define PROC_INTERRUPTS "/proc/interrupts"
1415
1416 Bool
1417 query_proc_interrupts_available (saver_info *si, const char **why)
1418 {
1419   /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
1420      "/proc/interrupts" file exists and is readable.
1421    */
1422   FILE *f;
1423   if (why) *why = 0;
1424
1425   if (!display_is_on_console_p (si))
1426     {
1427       if (why) *why = "not on primary console";
1428       return False;
1429     }
1430
1431   f = fopen (PROC_INTERRUPTS, "r");
1432   if (!f)
1433     {
1434       if (why) *why = "does not exist";
1435       return False;
1436     }
1437
1438   fclose (f);
1439   return True;
1440 }
1441
1442
1443 static Bool
1444 proc_interrupts_activity_p (saver_info *si)
1445 {
1446   static FILE *f0 = 0;
1447   FILE *f1 = 0;
1448   int fd;
1449   static char last_kbd_line[255] = { 0, };
1450   static char last_ptr_line[255] = { 0, };
1451   char new_line[sizeof(last_kbd_line)];
1452   Bool checked_kbd = False, kbd_changed = False;
1453   Bool checked_ptr = False, ptr_changed = False;
1454   int i8042_count = 0;
1455
1456   if (!f0)
1457     {
1458       /* First time -- open the file. */
1459       f0 = fopen (PROC_INTERRUPTS, "r");
1460       if (!f0)
1461         {
1462           char buf[255];
1463           sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
1464           perror (buf);
1465           goto FAIL;
1466         }
1467
1468 # if defined(HAVE_FCNTL) && defined(FD_CLOEXEC)
1469       /* Close this fd upon exec instead of inheriting / leaking it. */
1470       if (fcntl (fileno (f0), F_SETFD, FD_CLOEXEC) != 0)
1471         perror ("fcntl: CLOEXEC:");
1472 # endif
1473     }
1474
1475   if (f0 == (FILE *) -1)            /* means we got an error initializing. */
1476     return False;
1477
1478   fd = dup (fileno (f0));
1479   if (fd < 0)
1480     {
1481       char buf[255];
1482       sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
1483       perror (buf);
1484       goto FAIL;
1485     }
1486
1487   f1 = fdopen (fd, "r");
1488   if (!f1)
1489     {
1490       char buf[255];
1491       sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
1492               PROC_INTERRUPTS);
1493       perror (buf);
1494       goto FAIL;
1495     }
1496
1497   /* Actually, I'm unclear on why this fseek() is necessary, given the timing
1498      of the dup() above, but it is. */
1499   if (fseek (f1, 0, SEEK_SET) != 0)
1500     {
1501       char buf[255];
1502       sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
1503       perror (buf);
1504       goto FAIL;
1505     }
1506
1507   /* Now read through the pseudo-file until we find the "keyboard",
1508      "PS/2 mouse", or "i8042" lines. */
1509
1510   while (fgets (new_line, sizeof(new_line)-1, f1))
1511     {
1512       Bool i8042_p = !!strstr (new_line, "i8042");
1513       if (i8042_p) i8042_count++;
1514
1515       if (strchr (new_line, ','))
1516         {
1517           /* Ignore any line that has a comma on it: this is because
1518              a setup like this:
1519
1520                  12:     930935          XT-PIC  usb-uhci, PS/2 Mouse
1521
1522              is really bad news.  It *looks* like we can note mouse
1523              activity from that line, but really, that interrupt gets
1524              fired any time any USB device has activity!  So we have
1525              to ignore any shared IRQs.
1526            */
1527         }
1528       else if (!checked_kbd &&
1529                (strstr (new_line, "keyboard") ||
1530                 (i8042_p && i8042_count == 1)))
1531         {
1532           /* Assume the keyboard interrupt is the line that says "keyboard",
1533              or the *first* line that says "i8042".
1534            */
1535           kbd_changed = (*last_kbd_line && !!strcmp (new_line, last_kbd_line));
1536           strcpy (last_kbd_line, new_line);
1537           checked_kbd = True;
1538         }
1539       else if (!checked_ptr &&
1540                (strstr (new_line, "PS/2 Mouse") ||
1541                 (i8042_p && i8042_count == 2)))
1542         {
1543           /* Assume the mouse interrupt is the line that says "PS/2 mouse",
1544              or the *second* line that says "i8042".
1545            */
1546           ptr_changed = (*last_ptr_line && !!strcmp (new_line, last_ptr_line));
1547           strcpy (last_ptr_line, new_line);
1548           checked_ptr = True;
1549         }
1550
1551       if (checked_kbd && checked_ptr)
1552         break;
1553     }
1554
1555   if (checked_kbd || checked_ptr)
1556     {
1557       fclose (f1);
1558
1559       if (si->prefs.debug_p && (kbd_changed || ptr_changed))
1560         fprintf (stderr, "%s: /proc/interrupts activity: %s\n",
1561                  blurb(),
1562                  ((kbd_changed && ptr_changed) ? "mouse and kbd" :
1563                   kbd_changed ? "kbd" :
1564                   ptr_changed ? "mouse" : "ERR"));
1565
1566       return (kbd_changed || ptr_changed);
1567     }
1568
1569
1570   /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse"
1571      line in the file at all. */
1572   fprintf (stderr, "%s: no keyboard or mouse data in %s?\n",
1573            blurb(), PROC_INTERRUPTS);
1574
1575  FAIL:
1576   if (f1)
1577     fclose (f1);
1578
1579   if (f0 && f0 != (FILE *) -1)
1580     fclose (f0);
1581
1582   f0 = (FILE *) -1;
1583   return False;
1584 }
1585
1586 #endif /* HAVE_PROC_INTERRUPTS */
1587
1588 \f
1589 /* This timer goes off every few minutes, whether the user is idle or not,
1590    to try and clean up anything that has gone wrong.
1591
1592    It calls disable_builtin_screensaver() so that if xset has been used,
1593    or some other program (like xlock) has messed with the XSetScreenSaver()
1594    settings, they will be set back to sensible values (if a server extension
1595    is in use, messing with xlock can cause xscreensaver to never get a wakeup
1596    event, and could cause monitor power-saving to occur, and all manner of
1597    heinousness.)
1598
1599    If the screen is currently blanked, it raises the window, in case some
1600    other window has been mapped on top of it.
1601
1602    If the screen is currently blanked, and there is no hack running, it
1603    clears the window, in case there is an error message printed on it (we
1604    don't want the error message to burn in.)
1605  */
1606
1607 static void
1608 watchdog_timer (XtPointer closure, XtIntervalId *id)
1609 {
1610   saver_info *si = (saver_info *) closure;
1611   saver_preferences *p = &si->prefs;
1612
1613   disable_builtin_screensaver (si, False);
1614
1615   /* If the DPMS settings on the server have changed, change them back to
1616      what ~/.xscreensaver says they should be. */
1617   sync_server_dpms_settings (si->dpy,
1618                              (p->dpms_enabled_p  &&
1619                               p->mode != DONT_BLANK),
1620                              p->dpms_quickoff_p,
1621                              p->dpms_standby / 1000,
1622                              p->dpms_suspend / 1000,
1623                              p->dpms_off / 1000,
1624                              False);
1625
1626   if (si->screen_blanked_p)
1627     {
1628       Bool running_p = screenhack_running_p (si);
1629
1630       if (si->dbox_up_p)
1631         {
1632           if (si->prefs.debug_p)
1633             fprintf (stderr, "%s: dialog box is up: not raising screen.\n",
1634                      blurb());
1635         }
1636       else
1637         {
1638           if (si->prefs.debug_p)
1639             fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
1640                      blurb(), (running_p ? "" : "and clearing "));
1641
1642           raise_window (si, True, True, running_p);
1643         }
1644
1645       if (screenhack_running_p (si) &&
1646           !monitor_powered_on_p (si))
1647         {
1648           int i;
1649           if (si->prefs.verbose_p)
1650             fprintf (stderr,
1651                      "%s: X says monitor has powered down; "
1652                      "killing running hacks.\n", blurb());
1653           for (i = 0; i < si->nscreens; i++)
1654             kill_screenhack (&si->screens[i]);
1655         }
1656
1657       /* Re-schedule this timer.  The watchdog timer defaults to a bit less
1658          than the hack cycle period, but is never longer than one hour.
1659        */
1660       si->watchdog_id = 0;
1661       reset_watchdog_timer (si, True);
1662     }
1663 }
1664
1665
1666 void
1667 reset_watchdog_timer (saver_info *si, Bool on_p)
1668 {
1669   saver_preferences *p = &si->prefs;
1670
1671   if (si->watchdog_id)
1672     {
1673       XtRemoveTimeOut (si->watchdog_id);
1674       si->watchdog_id = 0;
1675     }
1676
1677   if (on_p && p->watchdog_timeout)
1678     {
1679       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
1680                                          watchdog_timer, (XtPointer) si);
1681
1682       if (p->debug_p)
1683         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
1684                  blurb(), p->watchdog_timeout, si->watchdog_id);
1685     }
1686 }
1687
1688
1689 /* It's possible that a race condition could have led to the saver
1690    window being unexpectedly still mapped.  This can happen like so:
1691
1692     - screen is blanked
1693     - hack is launched
1694     - that hack tries to grab a screen image (it does this by
1695       first unmapping the saver window, then remapping it.)
1696     - hack unmaps window
1697     - hack waits
1698     - user becomes active
1699     - hack re-maps window (*)
1700     - driver kills subprocess
1701     - driver unmaps window (**)
1702
1703    The race is that (*) might have been sent to the server before
1704    the client process was killed, but, due to scheduling randomness,
1705    might not have been received by the server until after (**).
1706    In other words, (*) and (**) might happen out of order, meaning
1707    the driver will unmap the window, and then after that, the
1708    recently-dead client will re-map it.  This leaves the user
1709    locked out (it looks like a desktop, but it's not!)
1710
1711    To avoid this: after un-blanking the screen, we launch a timer
1712    that wakes up once a second for ten seconds, and makes damned
1713    sure that the window is still unmapped.
1714  */
1715
1716 void
1717 de_race_timer (XtPointer closure, XtIntervalId *id)
1718 {
1719   saver_info *si = (saver_info *) closure;
1720   saver_preferences *p = &si->prefs;
1721   int secs = 1;
1722
1723   if (id == 0)  /* if id is 0, this is the initialization call. */
1724     {
1725       si->de_race_ticks = 10;
1726       if (p->verbose_p)
1727         fprintf (stderr, "%s: starting de-race timer (%d seconds.)\n",
1728                  blurb(), si->de_race_ticks);
1729     }
1730   else
1731     {
1732       int i;
1733       XSync (si->dpy, False);
1734       for (i = 0; i < si->nscreens; i++)
1735         {
1736           saver_screen_info *ssi = &si->screens[i];
1737           Window w = ssi->screensaver_window;
1738           XWindowAttributes xgwa;
1739           XGetWindowAttributes (si->dpy, w, &xgwa);
1740           if (xgwa.map_state != IsUnmapped)
1741             {
1742               if (p->verbose_p)
1743                 fprintf (stderr,
1744                          "%s: %d: client race! emergency unmap 0x%lx.\n",
1745                          blurb(), i, (unsigned long) w);
1746               XUnmapWindow (si->dpy, w);
1747             }
1748           else if (p->debug_p)
1749             fprintf (stderr, "%s: %d: (de-race of 0x%lx is cool.)\n",
1750                      blurb(), i, (unsigned long) w);
1751         }
1752       XSync (si->dpy, False);
1753
1754       si->de_race_ticks--;
1755     }
1756
1757   if (id && *id == si->de_race_id)
1758     si->de_race_id = 0;
1759
1760   if (si->de_race_id) abort();
1761
1762   if (si->de_race_ticks <= 0)
1763     {
1764       si->de_race_id = 0;
1765       if (p->verbose_p)
1766         fprintf (stderr, "%s: de-race completed.\n", blurb());
1767     }
1768   else
1769     {
1770       si->de_race_id = XtAppAddTimeOut (si->app, secs * 1000,
1771                                         de_race_timer, closure);
1772     }
1773 }