ftp://ftp.uni-heidelberg.de/pub/X11/contrib/applications/xscreensaver-1.25.tar.Z
[xscreensaver] / driver / timers.c
1 /* xscreensaver, Copyright (c) 1991-1995 Jamie Zawinski <jwz@mcom.com>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* #define DEBUG_TIMERS */
13
14 #include <stdio.h>
15 #include <X11/Xlib.h>
16 #include <X11/Intrinsic.h>
17 #include <X11/Xos.h>
18 #include <X11/Xmu/Error.h>
19
20 #ifdef HAVE_XIDLE_EXTENSION
21 #include <X11/extensions/xidle.h>
22 #endif /* HAVE_XIDLE_EXTENSION */
23
24 #ifdef HAVE_SAVER_EXTENSION
25 #include <X11/extensions/scrnsaver.h>
26 extern int saver_ext_event_number;
27 extern Window server_saver_window;
28 #endif /* HAVE_SAVER_EXTENSION */
29
30 #include "xscreensaver.h"
31
32 #if __STDC__
33 # define P(x)x
34 #else
35 #define P(x)()
36 #endif
37
38 extern XtAppContext app;
39
40 Time cycle;
41 Time timeout;
42 Time pointer_timeout;
43 Time notice_events_timeout;
44
45 extern Bool use_xidle_extension;
46 extern Bool use_saver_extension;
47 extern Bool dbox_up_p;
48 extern Bool locked_p;
49 extern Window screensaver_window;
50
51 extern Bool handle_clientmessage P((XEvent *, Bool));
52
53 static time_t last_activity_time; /* for when we have no server extensions */
54 static XtIntervalId timer_id = 0;
55 static XtIntervalId check_pointer_timer_id = 0;
56 XtIntervalId cycle_id = 0;
57 XtIntervalId lock_id = 0;
58
59 void
60 idle_timer (junk1, junk2)
61      void *junk1;
62      XtPointer junk2;
63 {
64   /* What an amazingly shitty design.  Not only does Xt execute timeout
65      events from XtAppNextEvent() instead of from XtDispatchEvent(), but
66      there is no way to tell Xt to block until there is an X event OR a
67      timeout happens.  Once your timeout proc is called, XtAppNextEvent()
68      still won't return until a "real" X event comes in.
69
70      So this function pushes a stupid, gratuitous, unnecessary event back
71      on the event queue to force XtAppNextEvent to return Right Fucking Now.
72      When the code in sleep_until_idle() sees an event of type XAnyEvent,
73      which the server never generates, it knows that a timeout has occurred.
74    */
75   XEvent fake_event;
76   fake_event.type = 0;  /* XAnyEvent type, ignored. */
77   fake_event.xany.display = dpy;
78   fake_event.xany.window  = 0;
79   XPutBackEvent (dpy, &fake_event);
80 }
81
82
83 static void
84 #if __STDC__
85 notice_events (Window window, Bool top_p)
86 #else
87 notice_events (window, top_p)
88      Window window;
89      Bool top_p;
90 #endif
91 {
92   XWindowAttributes attrs;
93   unsigned long events;
94   Window root, parent, *kids;
95   unsigned int nkids;
96
97   if (XtWindowToWidget (dpy, window))
98     /* If it's one of ours, don't mess up its event mask. */
99     return;
100
101   if (!XQueryTree (dpy, window, &root, &parent, &kids, &nkids))
102     return;
103   if (window == root)
104     top_p = False;
105
106   XGetWindowAttributes (dpy, window, &attrs);
107   events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
108             & KeyPressMask);
109
110   /* Select for SubstructureNotify on all windows.
111      Select for KeyPress on all windows that already have it selected.
112      Do we need to select for ButtonRelease?  I don't think so.
113    */
114   XSelectInput (dpy, window, SubstructureNotifyMask | events);
115
116   if (top_p && verbose_p && (events & KeyPressMask))
117     {
118       /* Only mention one window per tree (hack hack). */
119       printf ("%s: selected KeyPress on 0x%lX\n", progname,
120               (unsigned long) window);
121       top_p = False;
122     }
123
124   if (kids)
125     {
126       while (nkids)
127         notice_events (kids [--nkids], top_p);
128       XFree ((char *) kids);
129     }
130 }
131
132
133 int
134 BadWindow_ehandler (dpy, error)
135      Display *dpy;
136      XErrorEvent *error;
137 {
138   /* When we notice a window being created, we spawn a timer that waits
139      30 seconds or so, and then selects events on that window.  This error
140      handler is used so that we can cope with the fact that the window
141      may have been destroyed <30 seconds after it was created.
142    */
143   if (error->error_code == BadWindow ||
144       error->error_code == BadDrawable)
145     return 0;
146   XmuPrintDefaultErrorMessage (dpy, error, stderr);
147   exit (1);
148 }
149
150 void
151 notice_events_timer (closure, timer)
152      XtPointer closure;
153      XtIntervalId *timer;
154 {
155   Window window = (Window) closure;
156   int (*old_handler) ();
157   old_handler = XSetErrorHandler (BadWindow_ehandler);
158   notice_events (window, True);
159   XSync (dpy, False);
160   XSetErrorHandler (old_handler);
161 }
162
163
164 /* When the screensaver is active, this timer will periodically change
165    the running program.
166  */
167 void
168 cycle_timer (junk1, junk2)
169      void *junk1;
170      XtPointer junk2;
171 {
172   Time how_long = cycle;
173   if (dbox_up_p)
174     {
175       if (verbose_p)
176         printf ("%s: dbox up; delaying hack change.\n", progname);
177       how_long = 30000; /* 30 secs */
178     }
179   else
180     {
181       if (verbose_p)
182         printf ("%s: changing graphics hacks.\n", progname);
183       kill_screenhack ();
184       spawn_screenhack (False);
185     }
186   cycle_id = XtAppAddTimeOut (app, how_long, cycle_timer, 0);
187
188 #ifdef DEBUG_TIMERS
189   if (verbose_p)
190     printf ("%s: starting cycle_timer (%ld, %ld)\n",
191             progname, how_long, cycle_id);
192 #endif
193 }
194
195
196 void
197 activate_lock_timer (junk1, junk2)
198      void *junk1;
199      XtPointer junk2;
200 {
201   if (verbose_p)
202     printf ("%s: timed out; activating lock\n", progname);
203   locked_p = True;
204 }
205
206
207 /* Call this when user activity (or "simulated" activity) has been noticed.
208  */
209 static void
210 reset_timers P((void))
211 {
212   if (use_saver_extension)
213     return;
214
215 #ifdef DEBUG_TIMERS
216   if (verbose_p)
217     printf ("%s: restarting idle_timer (%ld, %ld)\n",
218             progname, timeout, timer_id);
219 #endif
220   XtRemoveTimeOut (timer_id);
221   timer_id = XtAppAddTimeOut (app, timeout, idle_timer, 0);
222   if (cycle_id) abort ();
223
224 #ifdef DEBUG_TIMERS
225   if (verbose_p)
226     printf ("%s: starting idle_timer (%ld, %ld)\n",
227             progname, timeout, timer_id);
228 #endif
229
230   last_activity_time = time ((time_t *) 0);
231 }
232
233 /* When we aren't using a server extension, this timer is used to periodically
234    wake up and poll the mouse position, which is possibly more reliable than
235    selecting motion events on every window.
236  */
237 static void
238 check_pointer_timer (closure, this_timer)
239      void *closure;
240      XtPointer this_timer;
241 {
242   static int last_root_x = -1;
243   static int last_root_y = -1;
244   static Window last_child = (Window) -1;
245   static unsigned int last_mask = 0;
246   Window root, child;
247   int root_x, root_y, x, y;
248   unsigned int mask;
249   XtIntervalId *timerP = (XtIntervalId *) closure;
250
251   if (use_xidle_extension || use_saver_extension)
252     abort ();
253
254   *timerP = XtAppAddTimeOut (app, pointer_timeout, check_pointer_timer,
255                              closure);
256
257   XQueryPointer (dpy, screensaver_window, &root, &child,
258                  &root_x, &root_y, &x, &y, &mask);
259   if (root_x == last_root_x && root_y == last_root_y &&
260       child == last_child && mask == last_mask)
261     return;
262
263 #ifdef DEBUG_TIMERS
264   if (verbose_p && this_timer)
265     if (root_x == last_root_x && root_y == last_root_y && child == last_child)
266       printf ("%s: modifiers changed at %s.\n", progname, timestring ());
267     else
268       printf ("%s: pointer moved at %s.\n", progname, timestring ());
269 #endif
270
271   last_root_x = root_x;
272   last_root_y = root_y;
273   last_child = child;
274   last_mask = mask;
275
276   reset_timers ();
277 }
278
279
280 void
281 sleep_until_idle (until_idle_p)
282      Bool until_idle_p;
283 {
284   XEvent event;
285
286   if (until_idle_p)
287     {
288       if (!use_saver_extension)
289         {
290           /* Wake up periodically to ask the server if we are idle. */
291           timer_id = XtAppAddTimeOut (app, timeout, idle_timer, 0);
292 #ifdef DEBUG_TIMERS
293           if (verbose_p)
294             printf ("%s: starting idle_timer (%ld, %ld)\n",
295                     progname, timeout, timer_id);
296 #endif
297         }
298
299       if (!use_xidle_extension && !use_saver_extension)
300         /* start polling the mouse position */
301         check_pointer_timer (&check_pointer_timer_id, 0);
302     }
303
304   while (1)
305     {
306       XtAppNextEvent (app, &event);
307
308       switch (event.xany.type) {
309       case 0:           /* our synthetic "timeout" event has been signalled */
310         if (until_idle_p)
311           {
312             Time idle;
313 #ifdef HAVE_XIDLE_EXTENSION
314             if (use_xidle_extension)
315               {
316                 if (! XGetIdleTime (dpy, &idle))
317                   {
318                     fprintf (stderr, "%s: %sXGetIdleTime() failed.\n",
319                              progname, (verbose_p ? "## " : ""));
320                     exit (1);
321                   }
322               }
323             else
324 #endif /* HAVE_XIDLE_EXTENSION */
325 #ifdef HAVE_SAVER_EXTENSION
326               if (use_saver_extension)
327                 {
328                   /* We don't need to do anything in this case - the synthetic
329                      event isn't necessary, as we get sent specific events
330                      to wake us up. */
331                   idle = 0;
332                 }
333             else
334 #endif /* HAVE_SAVER_EXTENSION */
335               {
336                 idle = 1000 * (last_activity_time - time ((time_t *) 0));
337               }
338             
339             if (idle >= timeout)
340               goto DONE;
341             else if (!use_saver_extension)
342               {
343                 timer_id = XtAppAddTimeOut (app, timeout - idle,
344                                             idle_timer, 0);
345 #ifdef DEBUG_TIMERS
346                 if (verbose_p)
347                   printf ("%s: starting idle_timer (%ld, %ld)\n",
348                           progname, timeout - idle, timer_id);
349 #endif /* DEBUG_TIMERS */
350               }
351           }
352         break;
353
354       case ClientMessage:
355         if (handle_clientmessage (&event, until_idle_p))
356           goto DONE;
357         break;
358
359       case CreateNotify:
360         if (!use_xidle_extension && !use_saver_extension)
361           {
362             XtAppAddTimeOut (app, notice_events_timeout, notice_events_timer,
363                              (XtPointer) event.xcreatewindow.window);
364 #ifdef DEBUG_TIMERS
365             if (verbose_p)
366               printf ("%s: starting notice_events_timer for 0x%X (%lu)\n",
367                       progname,
368                       (unsigned int) event.xcreatewindow.window,
369                       notice_events_timeout);
370 #endif /* DEBUG_TIMERS */
371           }
372         break;
373
374       case KeyPress:
375       case KeyRelease:
376       case ButtonPress:
377       case ButtonRelease:
378       case MotionNotify:
379
380 #ifdef DEBUG_TIMERS
381         if (verbose_p)
382           {
383             if (event.xany.type == MotionNotify)
384               printf ("%s: MotionNotify at %s\n", progname, timestring ());
385             else if (event.xany.type == KeyPress)
386               printf ("%s: KeyPress seen on 0x%X at %s\n", progname,
387                       (unsigned int) event.xkey.window, timestring ());
388           }
389 #endif
390
391         /* We got a user event */
392         if (!until_idle_p)
393           goto DONE;
394         else
395           reset_timers ();
396         break;
397
398       default:
399
400 #ifdef HAVE_SAVER_EXTENSION
401         if (event.type == saver_ext_event_number)
402           {
403             XScreenSaverNotifyEvent *sevent =
404               (XScreenSaverNotifyEvent *) &event;
405             if (sevent->state == ScreenSaverOn)
406               {
407 # ifdef DEBUG_TIMERS
408                 if (verbose_p)
409                   printf ("%s: ScreenSaverOn event received at %s\n",
410                           progname, timestring ());
411 # endif /* DEBUG_TIMERS */
412
413                 /* Get the "real" server window out of the way as soon
414                    as possible. */
415                 if (server_saver_window &&
416                     window_exists_p (dpy, server_saver_window))
417                   XUnmapWindow (dpy, server_saver_window);
418
419                 if (sevent->kind != ScreenSaverExternal)
420                   {
421 # ifdef DEBUG_TIMERS
422                     fprintf (stderr,
423                          "%s: ScreenSaverOn event wasn't of type External!\n",
424                              progname);
425 # endif /* DEBUG_TIMERS */
426                   }
427
428                 if (until_idle_p)
429                   goto DONE;
430               }
431             else if (sevent->state == ScreenSaverOff)
432               {
433 # ifdef DEBUG_TIMERS
434                 if (verbose_p)
435                   printf ("%s: ScreenSaverOff event received at %s\n",
436                           progname, timestring ());
437 # endif /* DEBUG_TIMERS */
438                 if (!until_idle_p)
439                   goto DONE;
440               }
441 # ifdef DEBUG_TIMERS
442             else if (verbose_p)
443               printf ("%s: unknown ScreenSaver event received at %s\n",
444                       progname, timestring ());
445 # endif /* DEBUG_TIMERS */
446           }
447         else
448
449 #endif /* HAVE_SAVER_EXTENSION */
450
451           XtDispatchEvent (&event);
452       }
453     }
454  DONE:
455
456   if (check_pointer_timer_id)
457     {
458       XtRemoveTimeOut (check_pointer_timer_id);
459       check_pointer_timer_id = 0;
460     }
461   if (timer_id)
462     {
463       XtRemoveTimeOut (timer_id);
464       timer_id = 0;
465     }
466
467   if (until_idle_p && cycle_id)
468     abort ();
469
470   return;
471 }