http://ftp.x.org/contrib/applications/xscreensaver-2.34.tar.gz
[xscreensaver] / driver / timers.c
1 /* timers.c --- detecting when the user is idle, and other timer-related tasks.
2  * xscreensaver, Copyright (c) 1991-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   XtRemoveTimeOut (si->timer_id);
246   si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
247                                   (XtPointer) si);
248   if (si->cycle_id) abort ();   /* no cycle timer when inactive */
249
250 #ifdef DEBUG_TIMERS
251   if (p->verbose_p)
252     fprintf (stderr, "%s:   restarting idle_timer (%ld, %ld)\n",
253              blurb(), p->timeout, si->timer_id);
254 #endif /* DEBUG_TIMERS */
255
256   si->last_activity_time = time ((time_t *) 0);
257 }
258
259 /* When we aren't using a server extension, this timer is used to periodically
260    wake up and poll the mouse position, which is possibly more reliable than
261    selecting motion events on every window.
262  */
263 static void
264 check_pointer_timer (XtPointer closure, XtIntervalId *id)
265 {
266   int i;
267   saver_info *si = (saver_info *) closure;
268   saver_preferences *p = &si->prefs;
269   Bool active_p = False;
270
271   if (p->use_xidle_extension ||
272       p->use_mit_saver_extension ||
273       p->use_sgi_saver_extension)
274     /* If an extension is in use, we should not be polling the mouse. */
275     abort ();
276
277   si->check_pointer_timer_id =
278     XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
279                      (XtPointer) si);
280
281   for (i = 0; i < si->nscreens; i++)
282     {
283       saver_screen_info *ssi = &si->screens[i];
284       Window root, child;
285       int root_x, root_y, x, y;
286       unsigned int mask;
287
288       XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
289                      &root_x, &root_y, &x, &y, &mask);
290
291       if (root_x == ssi->poll_mouse_last_root_x &&
292           root_y == ssi->poll_mouse_last_root_y &&
293           child  == ssi->poll_mouse_last_child &&
294           mask   == ssi->poll_mouse_last_mask)
295         continue;
296
297       active_p = True;
298
299 #ifdef DEBUG_TIMERS
300       if (p->verbose_p)
301         if (root_x == ssi->poll_mouse_last_root_x &&
302             root_y == ssi->poll_mouse_last_root_y &&
303             child  == ssi->poll_mouse_last_child)
304           fprintf (stderr, "%s: modifiers changed at %s on screen %d.\n",
305                    blurb(), timestring(), i);
306         else
307           fprintf (stderr, "%s: pointer moved at %s on screen %d.\n",
308                    blurb(), timestring(), i);
309 #endif /* DEBUG_TIMERS */
310
311       si->last_activity_screen    = ssi;
312       ssi->poll_mouse_last_root_x = root_x;
313       ssi->poll_mouse_last_root_y = root_y;
314       ssi->poll_mouse_last_child  = child;
315       ssi->poll_mouse_last_mask   = mask;
316     }
317
318   if (active_p)
319     reset_timers (si);
320 }
321
322
323 static void
324 dispatch_event (saver_info *si, XEvent *event)
325 {
326   /* If this is for the splash dialog, pass it along.
327      Note that the password dialog is handled with its own event loop,
328      so events for that window will never come through here.
329    */
330   if (si->splash_dialog && event->xany.window == si->splash_dialog)
331     handle_splash_event (si, event);
332
333   XtDispatchEvent (event);
334 }
335
336
337 void
338 sleep_until_idle (saver_info *si, Bool until_idle_p)
339 {
340   saver_preferences *p = &si->prefs;
341   XEvent event;
342
343   if (until_idle_p)
344     {
345       if (!p->use_mit_saver_extension && !p->use_sgi_saver_extension)
346         {
347           /* Wake up periodically to ask the server if we are idle. */
348           si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
349                                           (XtPointer) si);
350
351 #ifdef DEBUG_TIMERS
352           if (p->verbose_p)
353             fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
354                      blurb(), p->timeout, si->timer_id);
355 #endif /* DEBUG_TIMERS */
356         }
357
358       if (!p->use_xidle_extension &&
359           !p->use_mit_saver_extension &&
360           !p->use_sgi_saver_extension)
361         /* start polling the mouse position */
362         check_pointer_timer ((XtPointer) si, 0);
363     }
364
365   while (1)
366     {
367       XtAppNextEvent (si->app, &event);
368
369       switch (event.xany.type) {
370       case 0:           /* our synthetic "timeout" event has been signalled */
371         if (until_idle_p)
372           {
373             Time idle;
374 #ifdef HAVE_XIDLE_EXTENSION
375             if (p->use_xidle_extension)
376               {
377                 if (! XGetIdleTime (si->dpy, &idle))
378                   {
379                     fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
380                     saver_exit (si, 1, 0);
381                   }
382               }
383             else
384 #endif /* HAVE_XIDLE_EXTENSION */
385 #ifdef HAVE_MIT_SAVER_EXTENSION
386               if (p->use_mit_saver_extension)
387                 {
388                   /* We don't need to do anything in this case - the synthetic
389                      event isn't necessary, as we get sent specific events
390                      to wake us up. */
391                   idle = 0;
392                 }
393             else
394 #endif /* HAVE_MIT_SAVER_EXTENSION */
395 #ifdef HAVE_SGI_SAVER_EXTENSION
396               if (p->use_sgi_saver_extension)
397                 {
398                   /* We don't need to do anything in this case - the synthetic
399                      event isn't necessary, as we get sent specific events
400                      to wake us up. */
401                   idle = 0;
402                 }
403             else
404 #endif /* HAVE_SGI_SAVER_EXTENSION */
405               {
406                 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
407               }
408             
409             if (idle >= p->timeout)
410               goto DONE;
411             else if (!p->use_mit_saver_extension &&
412                      !p->use_sgi_saver_extension)
413               {
414                 si->timer_id = XtAppAddTimeOut (si->app, p->timeout - idle,
415                                                 idle_timer, (XtPointer) si);
416 #ifdef DEBUG_TIMERS
417                 if (p->verbose_p)
418                   fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
419                            blurb(), p->timeout - idle, si->timer_id);
420 #endif /* DEBUG_TIMERS */
421               }
422           }
423         break;
424
425       case ClientMessage:
426         if (handle_clientmessage (si, &event, until_idle_p))
427           goto DONE;
428         break;
429
430       case CreateNotify:
431         if (!p->use_xidle_extension &&
432             !p->use_mit_saver_extension &&
433             !p->use_sgi_saver_extension)
434           {
435             start_notice_events_timer (si, event.xcreatewindow.window);
436 #ifdef DEBUG_TIMERS
437             if (p->verbose_p)
438               fprintf (stderr,
439                        "%s: starting notice_events_timer for 0x%X (%lu)\n",
440                        blurb(),
441                        (unsigned int) event.xcreatewindow.window,
442                        p->notice_events_timeout);
443 #endif /* DEBUG_TIMERS */
444           }
445         break;
446
447       case KeyPress:
448       case KeyRelease:
449       case ButtonPress:
450       case ButtonRelease:
451       case MotionNotify:
452
453 #ifdef DEBUG_TIMERS
454         if (p->verbose_p)
455           {
456             if (event.xany.type == MotionNotify)
457               fprintf (stderr, "%s: MotionNotify at %s\n",
458                        blurb(), timestring ());
459             else if (event.xany.type == KeyPress)
460               fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(),
461                        (unsigned int) event.xkey.window, timestring ());
462             else if (event.xany.type == ButtonPress)
463               fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(),
464                        (unsigned int) event.xbutton.window, timestring ());
465           }
466 #endif /* DEBUG_TIMERS */
467
468         /* If any widgets want to handle this event, let them. */
469         dispatch_event (si, &event);
470
471         /* We got a user event */
472         if (!until_idle_p)
473           goto DONE;
474         else
475           reset_timers (si);
476         break;
477
478       default:
479
480 #ifdef HAVE_MIT_SAVER_EXTENSION
481         if (event.type == si->mit_saver_ext_event_number)
482           {
483             XScreenSaverNotifyEvent *sevent =
484               (XScreenSaverNotifyEvent *) &event;
485             if (sevent->state == ScreenSaverOn)
486               {
487 # ifdef DEBUG_TIMERS
488                 if (p->verbose_p)
489                   fprintf (stderr, "%s: ScreenSaverOn event received at %s\n",
490                            blurb(), timestring ());
491 # endif /* DEBUG_TIMERS */
492
493                 /* Get the "real" server window(s) out of the way as soon
494                    as possible. */
495                 int i = 0;
496                 for (i = 0; i < si->nscreens; i++)
497                   {
498                     saver_screen_info *ssi = &si->screens[i];
499                     if (ssi->server_mit_saver_window &&
500                         window_exists_p (si->dpy,
501                                          ssi->server_mit_saver_window))
502                       XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
503                   }
504
505                 if (sevent->kind != ScreenSaverExternal)
506                   {
507 # ifdef DEBUG_TIMERS
508                     fprintf (stderr,
509                          "%s: ScreenSaverOn event wasn't of type External!\n",
510                              blurb());
511 # endif /* DEBUG_TIMERS */
512                   }
513
514                 if (until_idle_p)
515                   goto DONE;
516               }
517             else if (sevent->state == ScreenSaverOff)
518               {
519 # ifdef DEBUG_TIMERS
520                 if (p->verbose_p)
521                   fprintf (stderr, "%s: ScreenSaverOff event received at %s\n",
522                            blurb(), timestring ());
523 # endif /* DEBUG_TIMERS */
524                 if (!until_idle_p)
525                   goto DONE;
526               }
527 # ifdef DEBUG_TIMERS
528             else if (p->verbose_p)
529               fprintf (stderr,
530                        "%s: unknown MIT-SCREEN-SAVER event received at %s\n",
531                        blurb(), timestring ());
532 # endif /* DEBUG_TIMERS */
533           }
534         else
535
536 #endif /* HAVE_MIT_SAVER_EXTENSION */
537
538
539 #ifdef HAVE_SGI_SAVER_EXTENSION
540         if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
541           {
542 # ifdef DEBUG_TIMERS
543             if (p->verbose_p)
544               fprintf (stderr, "%s: ScreenSaverStart event received at %s\n",
545                        blurb(), timestring ());
546 # endif /* DEBUG_TIMERS */
547
548             if (until_idle_p)
549               goto DONE;
550           }
551         else if (event.type == (si->sgi_saver_ext_event_number +
552                                 ScreenSaverEnd))
553           {
554 # ifdef DEBUG_TIMERS
555             if (p->verbose_p)
556               fprintf (stderr, "%s: ScreenSaverEnd event received at %s\n",
557                        blurb(), timestring ());
558 # endif /* DEBUG_TIMERS */
559             if (!until_idle_p)
560               goto DONE;
561           }
562         else
563 #endif /* HAVE_SGI_SAVER_EXTENSION */
564
565           dispatch_event (si, &event);
566       }
567     }
568  DONE:
569
570
571   /* If there's a user event on the queue, swallow it.
572      If we're using a server extension, and the user becomes active, we
573      get the extension event before the user event -- so the keypress or
574      motion or whatever is still on the queue.  This makes "unfade" not
575      work, because it sees that event, and bugs out.  (This problem
576      doesn't exhibit itself without an extension, because in that case,
577      there's only one event generated by user activity, not two.)
578    */
579   XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
580                    &event);
581
582
583   if (si->check_pointer_timer_id)
584     {
585       XtRemoveTimeOut (si->check_pointer_timer_id);
586       si->check_pointer_timer_id = 0;
587     }
588   if (si->timer_id)
589     {
590       XtRemoveTimeOut (si->timer_id);
591       si->timer_id = 0;
592     }
593
594   if (until_idle_p && si->cycle_id)     /* no cycle timer when inactive */
595     abort ();
596
597   return;
598 }
599
600
601 /* This timer goes off every few minutes, whether the user is idle or not,
602    to try and clean up anything that has gone wrong.
603
604    It calls disable_builtin_screensaver() so that if xset has been used,
605    or some other program (like xlock) has messed with the XSetScreenSaver()
606    settings, they will be set back to sensible values (if a server extension
607    is in use, messing with xlock can cause xscreensaver to never get a wakeup
608    event, and could cause monitor power-saving to occur, and all manner of
609    heinousness.)
610
611    If the screen is currently blanked, it raises the window, in case some
612    other window has been mapped on top of it.
613
614    If the screen is currently blanked, and there is no hack running, it
615    clears the window, in case there is an error message printed on it (we
616    don't want the error message to burn in.)
617  */
618
619 static void
620 watchdog_timer (XtPointer closure, XtIntervalId *id)
621 {
622   saver_info *si = (saver_info *) closure;
623   if (!si->demo_mode_p)
624     {
625       disable_builtin_screensaver (si, False);
626       if (si->screen_blanked_p)
627         {
628           Bool running_p = screenhack_running_p(si);
629
630 #ifdef DEBUG_TIMERS
631           if (si->prefs.verbose_p)
632             fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
633                      blurb(), (running_p ? "" : "and clearing "));
634 #endif /* DEBUG_TIMERS */
635
636           raise_window (si, True, True, running_p);
637
638           if (!monitor_powered_on_p (si))
639             {
640               if (si->prefs.verbose_p)
641                 fprintf (stderr,
642                          "%s: server reports that monitor has powered down; "
643                          "killing running hacks.\n", blurb());
644               kill_screenhack (si);
645             }
646         }
647     }
648 }
649
650
651 void
652 reset_watchdog_timer (saver_info *si, Bool on_p)
653 {
654   saver_preferences *p = &si->prefs;
655
656   if (si->watchdog_id)
657     {
658       XtRemoveTimeOut (si->watchdog_id);
659       si->watchdog_id = 0;
660     }
661
662   if (on_p && p->watchdog_timeout)
663     {
664       si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
665                                          watchdog_timer, (XtPointer) si);
666
667 #ifdef DEBUG_TIMERS
668       if (p->verbose_p)
669         fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
670                  blurb(), p->watchdog_timeout, si->watchdog_id);
671 #endif /* DEBUG_TIMERS */
672
673     }
674 }