http://ftp.x.org/contrib/applications/xscreensaver-3.02.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-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 &&
480                 (event.xany.type == MotionNotify ||
481                  event.xany.type == KeyRelease))
482               /* When we're demoing a single hack, mouse motion doesn't
483                  cause deactivation.  Only clicks and keypresses do. */
484               ;
485             else
486               /* If we're not demoing, then any activity causes deactivation.
487                */
488               goto DONE;
489           }
490         else
491           reset_timers (si);
492
493         break;
494
495       default:
496
497 #ifdef HAVE_MIT_SAVER_EXTENSION
498         if (event.type == si->mit_saver_ext_event_number)
499           {
500             XScreenSaverNotifyEvent *sevent =
501               (XScreenSaverNotifyEvent *) &event;
502             if (sevent->state == ScreenSaverOn)
503               {
504                 int i = 0;
505                 if (p->verbose_p)
506                   fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
507                            blurb());
508
509                 /* Get the "real" server window(s) out of the way as soon
510                    as possible. */
511                 for (i = 0; i < si->nscreens; i++)
512                   {
513                     saver_screen_info *ssi = &si->screens[i];
514                     if (ssi->server_mit_saver_window &&
515                         window_exists_p (si->dpy,
516                                          ssi->server_mit_saver_window))
517                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
518                   }
519
520                 if (sevent->kind != ScreenSaverExternal)
521                   {
522                     fprintf (stderr,
523                          "%s: ScreenSaverOn event wasn't of type External!\n",
524                              blurb());
525                   }
526
527                 if (until_idle_p)
528                   goto DONE;
529               }
530             else if (sevent->state == ScreenSaverOff)
531               {
532                 if (p->verbose_p)
533                   fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
534                            blurb());
535                 if (!until_idle_p)
536                   goto DONE;
537               }
538             else
539               fprintf (stderr,
540                        "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
541                        blurb(), sevent->state);
542           }
543         else
544
545 #endif /* HAVE_MIT_SAVER_EXTENSION */
546
547
548 #ifdef HAVE_SGI_SAVER_EXTENSION
549         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
550           {
551             if (p->verbose_p)
552               fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
553                        blurb());
554
555             if (until_idle_p)
556               goto DONE;
557           }
558         else if (event.type == (si->sgi_saver_ext_event_number +
559                                 ScreenSaverEnd))
560           {
561             if (p->verbose_p)
562               fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
563                        blurb());
564             if (!until_idle_p)
565               goto DONE;
566           }
567         else
568 #endif /* HAVE_SGI_SAVER_EXTENSION */
569
570           dispatch_event (si, &event);
571       }
572     }
573  DONE:
574
575
576   /* If there's a user event on the queue, swallow it.
577      If we're using a server extension, and the user becomes active, we
578      get the extension event before the user event -- so the keypress or
579      motion or whatever is still on the queue.  This makes "unfade" not
580      work, because it sees that event, and bugs out.  (This problem
581      doesn't exhibit itself without an extension, because in that case,
582      there's only one event generated by user activity, not two.)
583    */
584   XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
585                    &event);
586
587
588   if (si->check_pointer_timer_id)
589     {
590       XtRemoveTimeOut (si->check_pointer_timer_id);
591       si->check_pointer_timer_id = 0;
592     }
593   if (si->timer_id)
594     {
595       XtRemoveTimeOut (si->timer_id);
596       si->timer_id = 0;
597     }
598
599   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
600     abort ();
601
602   return;
603 }
604
605
606 /* This timer goes off every few minutes, whether the user is idle or not,
607    to try and clean up anything that has gone wrong.
608
609    It calls disable_builtin_screensaver() so that if xset has been used,
610    or some other program (like xlock) has messed with the XSetScreenSaver()
611    settings, they will be set back to sensible values (if a server extension
612    is in use, messing with xlock can cause xscreensaver to never get a wakeup
613    event, and could cause monitor power-saving to occur, and all manner of
614    heinousness.)
615
616    If the screen is currently blanked, it raises the window, in case some
617    other window has been mapped on top of it.
618
619    If the screen is currently blanked, and there is no hack running, it
620    clears the window, in case there is an error message printed on it (we
621    don't want the error message to burn in.)
622  */
623
624 static void
625 watchdog_timer (XtPointer closure, XtIntervalId *id)
626 {
627   saver_info *si = (saver_info *) closure;
628
629   disable_builtin_screensaver (si, False);
630
631   if (si->screen_blanked_p)
632     {
633       Bool running_p = screenhack_running_p(si);
634
635 #ifdef DEBUG_TIMERS
636       if (si->prefs.verbose_p)
637         fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
638                  blurb(), (running_p ? "" : "and clearing "));
639 #endif /* DEBUG_TIMERS */
640
641       raise_window (si, True, True, running_p);
642
643       if (!monitor_powered_on_p (si))
644         {
645           if (si->prefs.verbose_p)
646             fprintf (stderr,
647                      "%s: server reports that monitor has powered down; "
648                      "killing running hacks.\n", blurb());
649           kill_screenhack (si);
650         }
651     }
652 }
653
654
655 void
656 reset_watchdog_timer (saver_info *si, Bool on_p)
657 {
658   saver_preferences *p = &si->prefs;
659
660   if (si->watchdog_id)
661     {
662       XtRemoveTimeOut (si->watchdog_id);
663       si->watchdog_id = 0;
664     }
665
666   if (on_p && p->watchdog_timeout)
667     {
668       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
669                                          watchdog_timer, (XtPointer) si);
670
671 #ifdef DEBUG_TIMERS
672       if (p->verbose_p)
673         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
674                  blurb(), p->watchdog_timeout, si->watchdog_id);
675 #endif /* DEBUG_TIMERS */
676
677     }
678 }