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