c909287cc2968290ced54bb7fb8cb2b173df555a
[xscreensaver] / driver / timers.c
1 /* timers.c --- detecting when the user is idle, and other timer-related tasks.
2  * xscreensaver, Copyright (c) 1991-1997, 1998
3  *  Jamie Zawinski <jwz@jwz.org>
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  */
13
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17
18 /* #define DEBUG_TIMERS */
19
20 #include <stdio.h>
21 #include <X11/Xlib.h>
22 #include <X11/Intrinsic.h>
23 #include <X11/Xos.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_XHPDISABLERESET
47 # include <X11/XHPlib.h>
48   extern Bool hp_locked_p;      /* from windows.c */
49 #endif /* HAVE_XHPDISABLERESET */
50
51 #include "xscreensaver.h"
52
53
54 void
55 idle_timer (XtPointer closure, XtIntervalId *id)
56 {
57   saver_info *si = (saver_info *) closure;
58
59   /* What an amazingly shitty design.  Not only does Xt execute timeout
60      events from XtAppNextEvent() instead of from XtDispatchEvent(), but
61      there is no way to tell Xt to block until there is an X event OR a
62      timeout happens.  Once your timeout proc is called, XtAppNextEvent()
63      still won't return until a "real" X event comes in.
64
65      So this function pushes a stupid, gratuitous, unnecessary event back
66      on the event queue to force XtAppNextEvent to return Right Fucking Now.
67      When the code in sleep_until_idle() sees an event of type XAnyEvent,
68      which the server never generates, it knows that a timeout has occurred.
69    */
70   XEvent fake_event;
71   fake_event.type = 0;  /* XAnyEvent type, ignored. */
72   fake_event.xany.display = si->dpy;
73   fake_event.xany.window  = 0;
74   XPutBackEvent (si->dpy, &fake_event);
75 }
76
77
78 static void
79 notice_events (saver_info *si, Window window, Bool top_p)
80 {
81   saver_preferences *p = &si->prefs;
82   XWindowAttributes attrs;
83   unsigned long events;
84   Window root, parent, *kids;
85   unsigned int nkids;
86
87   if (XtWindowToWidget (si->dpy, window))
88     /* If it's one of ours, don't mess up its event mask. */
89     return;
90
91   if (!XQueryTree (si->dpy, window, &root, &parent, &kids, &nkids))
92     return;
93   if (window == root)
94     top_p = False;
95
96   XGetWindowAttributes (si->dpy, window, &attrs);
97   events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
98             & KeyPressMask);
99
100   /* Select for SubstructureNotify on all windows.
101      Select for KeyPress on all windows that already have it selected.
102      Do we need to select for ButtonRelease?  I don't think so.
103    */
104   XSelectInput (si->dpy, window, SubstructureNotifyMask | events);
105
106   if (top_p && p->verbose_p && (events & KeyPressMask))
107     {
108       /* Only mention one window per tree (hack hack). */
109       fprintf (stderr, "%s: selected KeyPress on 0x%lX\n", blurb(),
110                (unsigned long) window);
111       top_p = False;
112     }
113
114   if (kids)
115     {
116       while (nkids)
117         notice_events (si, kids [--nkids], top_p);
118       XFree ((char *) kids);
119     }
120 }
121
122
123 int
124 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
125 {
126   /* When we notice a window being created, we spawn a timer that waits
127      30 seconds or so, and then selects events on that window.  This error
128      handler is used so that we can cope with the fact that the window
129      may have been destroyed <30 seconds after it was created.
130    */
131   if (error->error_code == BadWindow ||
132       error->error_code == BadMatch ||
133       error->error_code == BadDrawable)
134     return 0;
135   else
136     return saver_ehandler (dpy, error);
137 }
138
139
140 struct notice_events_timer_arg {
141   saver_info *si;
142   Window w;
143 };
144
145 static void
146 notice_events_timer (XtPointer closure, XtIntervalId *id)
147 {
148   struct notice_events_timer_arg *arg =
149     (struct notice_events_timer_arg *) closure;
150
151   XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
152
153   saver_info *si = arg->si;
154   Window window = arg->w;
155
156   free(arg);
157   notice_events (si, window, True);
158   XSync (si->dpy, False);
159   XSetErrorHandler (old_handler);
160 }
161
162 void
163 start_notice_events_timer (saver_info *si, Window w)
164 {
165   saver_preferences *p = &si->prefs;
166   struct notice_events_timer_arg *arg =
167     (struct notice_events_timer_arg *) malloc(sizeof(*arg));
168   arg->si = si;
169   arg->w = w;
170   XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer,
171                    (XtPointer) arg);
172 }
173
174
175 /* When the screensaver is active, this timer will periodically change
176    the running program.
177  */
178 void
179 cycle_timer (XtPointer closure, XtIntervalId *id)
180 {
181   saver_info *si = (saver_info *) closure;
182   saver_preferences *p = &si->prefs;
183   Time how_long = p->cycle;
184   if (si->dbox_up_p)
185     {
186       if (p->verbose_p)
187         fprintf (stderr, "%s: dialog box up; delaying hack change.\n",
188                  blurb());
189       how_long = 30000; /* 30 secs */
190     }
191   else
192     {
193       maybe_reload_init_file (si);
194       if (p->verbose_p)
195         fprintf (stderr, "%s: changing graphics hacks.\n", blurb());
196       kill_screenhack (si);
197       spawn_screenhack (si, False);
198     }
199   si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
200                                   (XtPointer) si);
201
202 #ifdef DEBUG_TIMERS
203   if (p->verbose_p)
204     fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n",
205             blurb(), how_long, si->cycle_id);
206 #endif /* DEBUG_TIMERS */
207 }
208
209
210 void
211 activate_lock_timer (XtPointer closure, XtIntervalId *id)
212 {
213   saver_info *si = (saver_info *) closure;
214   saver_preferences *p = &si->prefs;
215
216   if (p->verbose_p)
217     fprintf (stderr, "%s: timed out; activating lock\n", blurb());
218   si->locked_p = True;
219
220 #ifdef HAVE_XHPDISABLERESET
221   if (!hp_locked_p)
222     {
223       XHPDisableReset (si->dpy);        /* turn off C-Sh-Reset */
224       hp_locked_p = True;
225     }
226 #endif
227 }
228
229
230 /* Call this when user activity (or "simulated" activity) has been noticed.
231  */
232 static void
233 reset_timers (saver_info *si)
234 {
235   saver_preferences *p = &si->prefs;
236   if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
237     return;
238
239 #ifdef DEBUG_TIMERS
240   if (p->verbose_p)
241     fprintf (stderr, "%s:   killing idle_timer    (%ld, %ld)\n",
242              blurb(), p->timeout, si->timer_id);
243 #endif /* DEBUG_TIMERS */
244
245   if (si->timer_id)
246     XtRemoveTimeOut (si->timer_id);
247   si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
248                                   (XtPointer) si);
249   if (si->cycle_id) abort ();   /* no cycle timer when inactive */
250
251 #ifdef DEBUG_TIMERS
252   if (p->verbose_p)
253     fprintf (stderr, "%s:   restarting idle_timer (%ld, %ld)\n",
254              blurb(), p->timeout, si->timer_id);
255 #endif /* DEBUG_TIMERS */
256
257   si->last_activity_time = time ((time_t *) 0);
258 }
259
260 /* When we aren't using a server extension, this timer is used to periodically
261    wake up and poll the mouse position, which is possibly more reliable than
262    selecting motion events on every window.
263  */
264 static void
265 check_pointer_timer (XtPointer closure, XtIntervalId *id)
266 {
267   int i;
268   saver_info *si = (saver_info *) closure;
269   saver_preferences *p = &si->prefs;
270   Bool active_p = False;
271
272   if (p->use_xidle_extension ||
273       p->use_mit_saver_extension ||
274       p->use_sgi_saver_extension)
275     /* If an extension is in use, we should not be polling the mouse. */
276     abort ();
277
278   si->check_pointer_timer_id =
279     XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
280                      (XtPointer) si);
281
282   for (i = 0; i < si->nscreens; i++)
283     {
284       saver_screen_info *ssi = &si->screens[i];
285       Window root, child;
286       int root_x, root_y, x, y;
287       unsigned int mask;
288
289       XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
290                      &root_x, &root_y, &x, &y, &mask);
291
292       if (root_x == ssi->poll_mouse_last_root_x &&
293           root_y == ssi->poll_mouse_last_root_y &&
294           child  == ssi->poll_mouse_last_child &&
295           mask   == ssi->poll_mouse_last_mask)
296         continue;
297
298       active_p = True;
299
300 #ifdef DEBUG_TIMERS
301       if (p->verbose_p)
302         if (root_x == ssi->poll_mouse_last_root_x &&
303             root_y == ssi->poll_mouse_last_root_y &&
304             child  == ssi->poll_mouse_last_child)
305           fprintf (stderr, "%s: modifiers changed at %s on screen %d.\n",
306                    blurb(), timestring(), i);
307         else
308           fprintf (stderr, "%s: pointer moved at %s on screen %d.\n",
309                    blurb(), timestring(), i);
310 #endif /* DEBUG_TIMERS */
311
312       si->last_activity_screen    = ssi;
313       ssi->poll_mouse_last_root_x = root_x;
314       ssi->poll_mouse_last_root_y = root_y;
315       ssi->poll_mouse_last_child  = child;
316       ssi->poll_mouse_last_mask   = mask;
317     }
318
319   if (active_p)
320     reset_timers (si);
321 }
322
323
324 static void
325 dispatch_event (saver_info *si, XEvent *event)
326 {
327   /* If this is for the splash dialog, pass it along.
328      Note that the password dialog is handled with its own event loop,
329      so events for that window will never come through here.
330    */
331   if (si->splash_dialog && event->xany.window == si->splash_dialog)
332     handle_splash_event (si, event);
333
334   XtDispatchEvent (event);
335 }
336
337
338 void
339 sleep_until_idle (saver_info *si, Bool until_idle_p)
340 {
341   saver_preferences *p = &si->prefs;
342   XEvent event;
343
344   if (until_idle_p)
345     {
346       if (!p->use_mit_saver_extension && !p->use_sgi_saver_extension)
347         {
348           /* Wake up periodically to ask the server if we are idle. */
349           si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
350                                           (XtPointer) si);
351
352 #ifdef DEBUG_TIMERS
353           if (p->verbose_p)
354             fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
355                      blurb(), p->timeout, si->timer_id);
356 #endif /* DEBUG_TIMERS */
357         }
358
359       if (!p->use_xidle_extension &&
360           !p->use_mit_saver_extension &&
361           !p->use_sgi_saver_extension)
362         /* start polling the mouse position */
363         check_pointer_timer ((XtPointer) si, 0);
364     }
365
366   while (1)
367     {
368       XtAppNextEvent (si->app, &event);
369
370       switch (event.xany.type) {
371       case 0:           /* our synthetic "timeout" event has been signalled */
372         if (until_idle_p)
373           {
374             Time idle;
375 #ifdef HAVE_XIDLE_EXTENSION
376             if (p->use_xidle_extension)
377               {
378                 if (! XGetIdleTime (si->dpy, &idle))
379                   {
380                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
381                     saver_exit (si, 1, 0);
382                   }
383               }
384             else
385 #endif /* HAVE_XIDLE_EXTENSION */
386 #ifdef HAVE_MIT_SAVER_EXTENSION
387               if (p->use_mit_saver_extension)
388                 {
389                   /* We don't need to do anything in this case - the synthetic
390                      event isn't necessary, as we get sent specific events
391                      to wake us up. */
392                   idle = 0;
393                 }
394             else
395 #endif /* HAVE_MIT_SAVER_EXTENSION */
396 #ifdef HAVE_SGI_SAVER_EXTENSION
397               if (p->use_sgi_saver_extension)
398                 {
399                   /* We don't need to do anything in this case - the synthetic
400                      event isn't necessary, as we get sent specific events
401                      to wake us up. */
402                   idle = 0;
403                 }
404             else
405 #endif /* HAVE_SGI_SAVER_EXTENSION */
406               {
407                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
408               }
409             
410             if (idle >= p->timeout)
411               goto DONE;
412             else if (!p->use_mit_saver_extension &&
413                      !p->use_sgi_saver_extension)
414               {
415                 si->timer_id = XtAppAddTimeOut (si->app, p->timeout - idle,
416                                                 idle_timer, (XtPointer) si);
417 #ifdef DEBUG_TIMERS
418                 if (p->verbose_p)
419                   fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
420                            blurb(), p->timeout - idle, si->timer_id);
421 #endif /* DEBUG_TIMERS */
422               }
423           }
424         break;
425
426       case ClientMessage:
427         if (handle_clientmessage (si, &event, until_idle_p))
428           goto DONE;
429         break;
430
431       case CreateNotify:
432         if (!p->use_xidle_extension &&
433             !p->use_mit_saver_extension &&
434             !p->use_sgi_saver_extension)
435           {
436             start_notice_events_timer (si, event.xcreatewindow.window);
437 #ifdef DEBUG_TIMERS
438             if (p->verbose_p)
439               fprintf (stderr,
440                        "%s: starting notice_events_timer for 0x%X (%lu)\n",
441                        blurb(),
442                        (unsigned int) event.xcreatewindow.window,
443                        p->notice_events_timeout);
444 #endif /* DEBUG_TIMERS */
445           }
446         break;
447
448       case KeyPress:
449       case KeyRelease:
450       case ButtonPress:
451       case ButtonRelease:
452       case MotionNotify:
453
454 #ifdef DEBUG_TIMERS
455         if (p->verbose_p)
456           {
457             if (event.xany.type == MotionNotify)
458               fprintf (stderr, "%s: MotionNotify at %s\n",
459                        blurb(), timestring ());
460             else if (event.xany.type == KeyPress)
461               fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(),
462                        (unsigned int) event.xkey.window, timestring ());
463             else if (event.xany.type == ButtonPress)
464               fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(),
465                        (unsigned int) event.xbutton.window, timestring ());
466           }
467 #endif /* DEBUG_TIMERS */
468
469         /* If any widgets want to handle this event, let them. */
470         dispatch_event (si, &event);
471
472         /* We got a user event.
473            If we're waiting for the user to become active, this is it.
474            If we're waiting until the user becomes idle, reset the timers
475            (since now we have longer to wait.)
476          */
477         if (!until_idle_p)
478           {
479             if (si->demoing_p && event.xany.type == MotionNotify)
480               /* When we're demoing a single hack, mouse motion doesn't
481                  cause deactivation.  Only clicks and keypresses do. */
482               ;
483             else
484               /* If we're not demoing, then any activity causes deactivation.
485                */
486               goto DONE;
487           }
488         else
489           reset_timers (si);
490
491         break;
492
493       default:
494
495 #ifdef HAVE_MIT_SAVER_EXTENSION
496         if (event.type == si->mit_saver_ext_event_number)
497           {
498             XScreenSaverNotifyEvent *sevent =
499               (XScreenSaverNotifyEvent *) &event;
500             if (sevent->state == ScreenSaverOn)
501               {
502                 int i = 0;
503                 if (p->verbose_p)
504                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
505                            blurb());
506
507                 /* Get the "real" server window(s) out of the way as soon
508                    as possible. */
509                 for (i = 0; i < si->nscreens; i++)
510                   {
511                     saver_screen_info *ssi = &si->screens[i];
512                     if (ssi->server_mit_saver_window &&
513                         window_exists_p (si->dpy,
514                                          ssi->server_mit_saver_window))
515                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
516                   }
517
518                 if (sevent->kind != ScreenSaverExternal)
519                   {
520                     fprintf (stderr,
521                          "%s: ScreenSaverOn event wasn't of type External!\n",
522                              blurb());
523                   }
524
525                 if (until_idle_p)
526                   goto DONE;
527               }
528             else if (sevent->state == ScreenSaverOff)
529               {
530                 if (p->verbose_p)
531                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
532                            blurb());
533                 if (!until_idle_p)
534                   goto DONE;
535               }
536             else
537               fprintf (stderr,
538                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
539                        blurb(), sevent->state);
540           }
541         else
542
543 #endif /* HAVE_MIT_SAVER_EXTENSION */
544
545
546 #ifdef HAVE_SGI_SAVER_EXTENSION
547         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
548           {
549             if (p->verbose_p)
550               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
551                        blurb());
552
553             if (until_idle_p)
554               goto DONE;
555           }
556         else if (event.type == (si->sgi_saver_ext_event_number +
557                                 ScreenSaverEnd))
558           {
559             if (p->verbose_p)
560               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
561                        blurb());
562             if (!until_idle_p)
563               goto DONE;
564           }
565         else
566 #endif /* HAVE_SGI_SAVER_EXTENSION */
567
568           dispatch_event (si, &event);
569       }
570     }
571  DONE:
572
573
574   /* If there's a user event on the queue, swallow it.
575      If we're using a server extension, and the user becomes active, we
576      get the extension event before the user event -- so the keypress or
577      motion or whatever is still on the queue.  This makes "unfade" not
578      work, because it sees that event, and bugs out.  (This problem
579      doesn't exhibit itself without an extension, because in that case,
580      there's only one event generated by user activity, not two.)
581    */
582   XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
583                    &event);
584
585
586   if (si->check_pointer_timer_id)
587     {
588       XtRemoveTimeOut (si->check_pointer_timer_id);
589       si->check_pointer_timer_id = 0;
590     }
591   if (si->timer_id)
592     {
593       XtRemoveTimeOut (si->timer_id);
594       si->timer_id = 0;
595     }
596
597   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
598     abort ();
599
600   return;
601 }
602
603
604 /* This timer goes off every few minutes, whether the user is idle or not,
605    to try and clean up anything that has gone wrong.
606
607    It calls disable_builtin_screensaver() so that if xset has been used,
608    or some other program (like xlock) has messed with the XSetScreenSaver()
609    settings, they will be set back to sensible values (if a server extension
610    is in use, messing with xlock can cause xscreensaver to never get a wakeup
611    event, and could cause monitor power-saving to occur, and all manner of
612    heinousness.)
613
614    If the screen is currently blanked, it raises the window, in case some
615    other window has been mapped on top of it.
616
617    If the screen is currently blanked, and there is no hack running, it
618    clears the window, in case there is an error message printed on it (we
619    don't want the error message to burn in.)
620  */
621
622 static void
623 watchdog_timer (XtPointer closure, XtIntervalId *id)
624 {
625   saver_info *si = (saver_info *) closure;
626
627   disable_builtin_screensaver (si, False);
628
629   if (si->screen_blanked_p)
630     {
631       Bool running_p = screenhack_running_p(si);
632
633 #ifdef DEBUG_TIMERS
634       if (si->prefs.verbose_p)
635         fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
636                  blurb(), (running_p ? "" : "and clearing "));
637 #endif /* DEBUG_TIMERS */
638
639       raise_window (si, True, True, running_p);
640
641       if (!monitor_powered_on_p (si))
642         {
643           if (si->prefs.verbose_p)
644             fprintf (stderr,
645                      "%s: server reports that monitor has powered down; "
646                      "killing running hacks.\n", blurb());
647           kill_screenhack (si);
648         }
649     }
650 }
651
652
653 void
654 reset_watchdog_timer (saver_info *si, Bool on_p)
655 {
656   saver_preferences *p = &si->prefs;
657
658   if (si->watchdog_id)
659     {
660       XtRemoveTimeOut (si->watchdog_id);
661       si->watchdog_id = 0;
662     }
663
664   if (on_p && p->watchdog_timeout)
665     {
666       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
667                                          watchdog_timer, (XtPointer) si);
668
669 #ifdef DEBUG_TIMERS
670       if (p->verbose_p)
671         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
672                  blurb(), p->watchdog_timeout, si->watchdog_id);
673 #endif /* DEBUG_TIMERS */
674
675     }
676 }