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