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