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