http://www.ibiblio.org/pub/historic-linux/ftp-archives/sunsite.unc.edu/Sep-29-1996...
[xscreensaver] / driver / timers.c
1 /* xscreensaver, Copyright (c) 1991-1995 Jamie Zawinski <jwz@netscape.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 == BadMatch ||
145       error->error_code == BadDrawable)
146     return 0;
147   XmuPrintDefaultErrorMessage (dpy, error, stderr);
148   exit (1);
149 }
150
151 void
152 notice_events_timer (closure, timer)
153      XtPointer closure;
154      XtIntervalId *timer;
155 {
156   Window window = (Window) closure;
157   int (*old_handler) ();
158   old_handler = XSetErrorHandler (BadWindow_ehandler);
159   notice_events (window, True);
160   XSync (dpy, False);
161   XSetErrorHandler (old_handler);
162 }
163
164
165 /* When the screensaver is active, this timer will periodically change
166    the running program.
167  */
168 void
169 cycle_timer (junk1, junk2)
170      void *junk1;
171      XtPointer junk2;
172 {
173   Time how_long = cycle;
174   if (dbox_up_p)
175     {
176       if (verbose_p)
177         printf ("%s: dbox up; delaying hack change.\n", progname);
178       how_long = 30000; /* 30 secs */
179     }
180   else
181     {
182       if (verbose_p)
183         printf ("%s: changing graphics hacks.\n", progname);
184       kill_screenhack ();
185       spawn_screenhack (False);
186     }
187   cycle_id = XtAppAddTimeOut (app, how_long, cycle_timer, 0);
188
189 #ifdef DEBUG_TIMERS
190   if (verbose_p)
191     printf ("%s: starting cycle_timer (%ld, %ld)\n",
192             progname, how_long, cycle_id);
193 #endif
194 }
195
196
197 void
198 activate_lock_timer (junk1, junk2)
199      void *junk1;
200      XtPointer junk2;
201 {
202   if (verbose_p)
203     printf ("%s: timed out; activating lock\n", progname);
204   locked_p = True;
205 }
206
207
208 /* Call this when user activity (or "simulated" activity) has been noticed.
209  */
210 static void
211 reset_timers P((void))
212 {
213   if (use_saver_extension)
214     return;
215
216 #ifdef DEBUG_TIMERS
217   if (verbose_p)
218     printf ("%s: restarting idle_timer (%ld, %ld)\n",
219             progname, timeout, timer_id);
220 #endif
221   XtRemoveTimeOut (timer_id);
222   timer_id = XtAppAddTimeOut (app, timeout, idle_timer, 0);
223   if (cycle_id) abort ();
224
225 #ifdef DEBUG_TIMERS
226   if (verbose_p)
227     printf ("%s: starting idle_timer (%ld, %ld)\n",
228             progname, timeout, timer_id);
229 #endif
230
231   last_activity_time = time ((time_t *) 0);
232 }
233
234 /* When we aren't using a server extension, this timer is used to periodically
235    wake up and poll the mouse position, which is possibly more reliable than
236    selecting motion events on every window.
237  */
238 static void
239 check_pointer_timer (closure, this_timer)
240      void *closure;
241      XtPointer this_timer;
242 {
243   static int last_root_x = -1;
244   static int last_root_y = -1;
245   static Window last_child = (Window) -1;
246   static unsigned int last_mask = 0;
247   Window root, child;
248   int root_x, root_y, x, y;
249   unsigned int mask;
250   XtIntervalId *timerP = (XtIntervalId *) closure;
251
252   if (use_xidle_extension || use_saver_extension)
253     abort ();
254
255   *timerP = XtAppAddTimeOut (app, pointer_timeout, check_pointer_timer,
256                              closure);
257
258   XQueryPointer (dpy, screensaver_window, &root, &child,
259                  &root_x, &root_y, &x, &y, &mask);
260   if (root_x == last_root_x && root_y == last_root_y &&
261       child == last_child && mask == last_mask)
262     return;
263
264 #ifdef DEBUG_TIMERS
265   if (verbose_p && this_timer)
266     if (root_x == last_root_x && root_y == last_root_y && child == last_child)
267       printf ("%s: modifiers changed at %s.\n", progname, timestring ());
268     else
269       printf ("%s: pointer moved at %s.\n", progname, timestring ());
270 #endif
271
272   last_root_x = root_x;
273   last_root_y = root_y;
274   last_child = child;
275   last_mask = mask;
276
277   reset_timers ();
278 }
279
280
281 void
282 sleep_until_idle (until_idle_p)
283      Bool until_idle_p;
284 {
285   XEvent event;
286
287   if (until_idle_p)
288     {
289       if (!use_saver_extension)
290         {
291           /* Wake up periodically to ask the server if we are idle. */
292           timer_id = XtAppAddTimeOut (app, timeout, idle_timer, 0);
293 #ifdef DEBUG_TIMERS
294           if (verbose_p)
295             printf ("%s: starting idle_timer (%ld, %ld)\n",
296                     progname, timeout, timer_id);
297 #endif
298         }
299
300       if (!use_xidle_extension && !use_saver_extension)
301         /* start polling the mouse position */
302         check_pointer_timer (&check_pointer_timer_id, 0);
303     }
304
305   while (1)
306     {
307       XtAppNextEvent (app, &event);
308
309       switch (event.xany.type) {
310       case 0:           /* our synthetic "timeout" event has been signalled */
311         if (until_idle_p)
312           {
313             Time idle;
314 #ifdef HAVE_XIDLE_EXTENSION
315             if (use_xidle_extension)
316               {
317                 if (! XGetIdleTime (dpy, &idle))
318                   {
319                     fprintf (stderr, "%s: %sXGetIdleTime() failed.\n",
320                              progname, (verbose_p ? "## " : ""));
321                     exit (1);
322                   }
323               }
324             else
325 #endif /* HAVE_XIDLE_EXTENSION */
326 #ifdef HAVE_SAVER_EXTENSION
327               if (use_saver_extension)
328                 {
329                   /* We don't need to do anything in this case - the synthetic
330                      event isn't necessary, as we get sent specific events
331                      to wake us up. */
332                   idle = 0;
333                 }
334             else
335 #endif /* HAVE_SAVER_EXTENSION */
336               {
337                 idle = 1000 * (last_activity_time - time ((time_t *) 0));
338               }
339             
340             if (idle >= timeout)
341               goto DONE;
342             else if (!use_saver_extension)
343               {
344                 timer_id = XtAppAddTimeOut (app, timeout - idle,
345                                             idle_timer, 0);
346 #ifdef DEBUG_TIMERS
347                 if (verbose_p)
348                   printf ("%s: starting idle_timer (%ld, %ld)\n",
349                           progname, timeout - idle, timer_id);
350 #endif /* DEBUG_TIMERS */
351               }
352           }
353         break;
354
355       case ClientMessage:
356         if (handle_clientmessage (&event, until_idle_p))
357           goto DONE;
358         break;
359
360       case CreateNotify:
361         if (!use_xidle_extension && !use_saver_extension)
362           {
363             XtAppAddTimeOut (app, notice_events_timeout, notice_events_timer,
364                              (XtPointer) event.xcreatewindow.window);
365 #ifdef DEBUG_TIMERS
366             if (verbose_p)
367               printf ("%s: starting notice_events_timer for 0x%X (%lu)\n",
368                       progname,
369                       (unsigned int) event.xcreatewindow.window,
370                       notice_events_timeout);
371 #endif /* DEBUG_TIMERS */
372           }
373         break;
374
375       case KeyPress:
376       case KeyRelease:
377       case ButtonPress:
378       case ButtonRelease:
379       case MotionNotify:
380
381 #ifdef DEBUG_TIMERS
382         if (verbose_p)
383           {
384             if (event.xany.type == MotionNotify)
385               printf ("%s: MotionNotify at %s\n", progname, timestring ());
386             else if (event.xany.type == KeyPress)
387               printf ("%s: KeyPress seen on 0x%X at %s\n", progname,
388                       (unsigned int) event.xkey.window, timestring ());
389           }
390 #endif
391
392         /* We got a user event */
393         if (!until_idle_p)
394           goto DONE;
395         else
396           reset_timers ();
397         break;
398
399       default:
400
401 #ifdef HAVE_SAVER_EXTENSION
402         if (event.type == saver_ext_event_number)
403           {
404             XScreenSaverNotifyEvent *sevent =
405               (XScreenSaverNotifyEvent *) &event;
406             if (sevent->state == ScreenSaverOn)
407               {
408 # ifdef DEBUG_TIMERS
409                 if (verbose_p)
410                   printf ("%s: ScreenSaverOn event received at %s\n",
411                           progname, timestring ());
412 # endif /* DEBUG_TIMERS */
413
414                 /* Get the "real" server window out of the way as soon
415                    as possible. */
416                 if (server_saver_window &&
417                     window_exists_p (dpy, server_saver_window))
418                   XUnmapWindow (dpy, server_saver_window);
419
420                 if (sevent->kind != ScreenSaverExternal)
421                   {
422 # ifdef DEBUG_TIMERS
423                     fprintf (stderr,
424                          "%s: ScreenSaverOn event wasn't of type External!\n",
425                              progname);
426 # endif /* DEBUG_TIMERS */
427                   }
428
429                 if (until_idle_p)
430                   goto DONE;
431               }
432             else if (sevent->state == ScreenSaverOff)
433               {
434 # ifdef DEBUG_TIMERS
435                 if (verbose_p)
436                   printf ("%s: ScreenSaverOff event received at %s\n",
437                           progname, timestring ());
438 # endif /* DEBUG_TIMERS */
439                 if (!until_idle_p)
440                   goto DONE;
441               }
442 # ifdef DEBUG_TIMERS
443             else if (verbose_p)
444               printf ("%s: unknown ScreenSaver event received at %s\n",
445                       progname, timestring ());
446 # endif /* DEBUG_TIMERS */
447           }
448         else
449
450 #endif /* HAVE_SAVER_EXTENSION */
451
452           XtDispatchEvent (&event);
453       }
454     }
455  DONE:
456
457   if (check_pointer_timer_id)
458     {
459       XtRemoveTimeOut (check_pointer_timer_id);
460       check_pointer_timer_id = 0;
461     }
462   if (timer_id)
463     {
464       XtRemoveTimeOut (timer_id);
465       timer_id = 0;
466     }
467
468   if (until_idle_p && cycle_id)
469     abort ();
470
471   return;
472 }