http://x.cybermirror.org/R5contrib/xscreensaver-1.21.tar.Z
[xscreensaver] / driver / timers.c
1 /* xscreensaver, Copyright (c) 1991-1993 Jamie Zawinski <jwz@lucid.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 #include <stdio.h>
13 #include <X11/Xlib.h>
14 #include <X11/Intrinsic.h>
15 #include <X11/Xos.h>
16 #include <X11/Xmu/Error.h>
17
18 #ifdef HAVE_XIDLE
19 #include <X11/extensions/xidle.h>
20 #endif
21
22 #include "xscreensaver.h"
23
24 extern XtAppContext app;
25
26 Time cycle;
27 Time timeout;
28 Time pointer_timeout;
29 Time notice_events_timeout;
30
31 extern Bool use_xidle;
32 extern Bool dbox_up_p;
33 extern Bool locked_p;
34 extern Window screensaver_window;
35
36 extern Bool handle_clientmessage P((/*XEvent *, Bool*/));
37
38 static time_t last_activity_time; /* for non-XIdle mode */
39 static XtIntervalId timer_id = 0;
40 static XtIntervalId check_pointer_timer_id = 0;
41 XtIntervalId cycle_id = 0;
42 XtIntervalId lock_id = 0;
43
44 void
45 idle_timer (junk1, junk2)
46      void *junk1;
47      XtPointer junk2;
48 {
49   /* What an amazingly shitty design.  Not only does Xt execute timeout
50      events from XtAppNextEvent() instead of from XtDispatchEvent(), but
51      there is no way to tell Xt to block until there is an X event OR a
52      timeout happens.  Once your timeout proc is called, XtAppNextEvent()
53      still won't return until a "real" X event comes in.
54
55      So this function pushes a stupid, gratuitous, unnecessary event back
56      on the event queue to force XtAppNextEvent to return Right Fucking Now.
57      When the code in sleep_until_idle() sees an event of type XAnyEvent,
58      which the server never generates, it knows that a timeout has occurred.
59    */
60   XEvent fake_event;
61   fake_event.type = 0;  /* XAnyEvent type, ignored. */
62   fake_event.xany.display = dpy;
63   fake_event.xany.window  = 0;
64   XPutBackEvent (dpy, &fake_event);
65 }
66
67
68 static void
69 notice_events (window, top_p)
70      Window window;
71      Bool top_p;
72 {
73   XWindowAttributes attrs;
74   unsigned long events;
75   Window root, parent, *kids;
76   unsigned int nkids;
77
78   if (XtWindowToWidget (dpy, window))
79     /* If it's one of ours, don't mess up its event mask. */
80     return;
81
82   if (!XQueryTree (dpy, window, &root, &parent, &kids, &nkids))
83     return;
84   if (window == root)
85     top_p = False;
86
87   XGetWindowAttributes (dpy, window, &attrs);
88   events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
89             & KeyPressMask);
90
91   /* Select for SubstructureNotify on all windows.
92      Select for KeyPress on all windows that already have it selected.
93      Do we need to select for ButtonRelease?  I don't think so.
94    */
95   XSelectInput (dpy, window, SubstructureNotifyMask | events);
96
97   if (top_p && verbose_p && (events & KeyPressMask))
98     {
99       /* Only mention one window per tree (hack hack). */
100       printf ("%s: selected KeyPress on 0x%X\n", progname, window);
101       top_p = False;
102     }
103
104   if (kids)
105     {
106       while (nkids)
107         notice_events (kids [--nkids], top_p);
108       XFree ((char *) kids);
109     }
110 }
111
112
113 int
114 BadWindow_ehandler (dpy, error)
115      Display *dpy;
116      XErrorEvent *error;
117 {
118   /* When we notice a window being created, we spawn a timer that waits
119      30 seconds or so, and then selects events on that window.  This error
120      handler is used so that we can cope with the fact that the window
121      may have been destroyed <30 seconds after it was created.
122    */
123   if (error->error_code == BadWindow ||
124       error->error_code == BadDrawable)
125     return 0;
126   XmuPrintDefaultErrorMessage (dpy, error, stderr);
127   exit (1);
128 }
129
130 void
131 notice_events_timer (closure, timer)
132      XtPointer closure;
133      void *timer;
134 {
135   Window window = (Window) closure;
136   int (*old_handler) ();
137   old_handler = XSetErrorHandler (BadWindow_ehandler);
138   notice_events (window, True);
139   XSync (dpy, False);
140   XSetErrorHandler (old_handler);
141 }
142
143
144 /* When the screensaver is active, this timer will periodically change
145    the running program.
146  */
147 void
148 cycle_timer (junk1, junk2)
149      void *junk1;
150      XtPointer junk2;
151 {
152   Time how_long = cycle;
153   if (dbox_up_p)
154     {
155       if (verbose_p)
156         printf ("%s: dbox up; delaying hack change.\n", progname);
157       how_long = 30000; /* 30 secs */
158     }
159   else
160     {
161       if (verbose_p)
162         printf ("%s: changing graphics hacks.\n", progname);
163       kill_screenhack ();
164       spawn_screenhack (False);
165     }
166   cycle_id = XtAppAddTimeOut (app, how_long, cycle_timer, 0);
167 }
168
169
170 void
171 activate_lock_timer (junk1, junk2)
172      void *junk1;
173      XtPointer junk2;
174 {
175   if (verbose_p)
176     printf ("%s: timed out; activating lock\n", progname);
177   locked_p = True;
178 }
179
180
181 /* Call this when user activity (or "simulated" activity) has been noticed.
182  */
183 static void
184 reset_timers ()
185 {
186 #ifdef DEBUG_TIMERS
187   if (verbose_p)
188     printf ("%s: restarting idle_timer (%d, %d)\n",
189             progname, timeout, timer_id);
190 #endif
191   XtRemoveTimeOut (timer_id);
192   timer_id = XtAppAddTimeOut (app, timeout, idle_timer, 0);
193   if (cycle_id) abort ();
194
195   last_activity_time = time ((time_t *) 0);
196 }
197
198 /* When we aren't using XIdle, this timer is used to periodically wake up
199    and poll the mouse position, which is possibly more reliable than
200    selecting motion events on every window.
201  */
202 static void
203 check_pointer_timer (closure, this_timer)
204      void *closure;
205      XtPointer this_timer;
206 {
207   static int last_root_x = -1;
208   static int last_root_y = -1;
209   static Window last_child = (Window) -1;
210   static unsigned int last_mask = 0;
211   Window root, child;
212   int root_x, root_y, x, y;
213   unsigned int mask;
214   XtIntervalId *timerP = (XtIntervalId *) closure;
215 #ifdef HAVE_XIDLE
216   if (use_xidle)
217     abort ();
218 #endif
219
220   *timerP = XtAppAddTimeOut (app, pointer_timeout, check_pointer_timer,
221                              closure);
222
223   XQueryPointer (dpy, screensaver_window, &root, &child,
224                  &root_x, &root_y, &x, &y, &mask);
225   if (root_x == last_root_x && root_y == last_root_y &&
226       child == last_child && mask == last_mask)
227     return;
228
229 #ifdef DEBUG_TIMERS
230   if (verbose_p && this_timer)
231     if (root_x == last_root_x && root_y == last_root_y && child == last_child)
232       printf ("%s: modifiers changed at %s.\n", progname, timestring ());
233     else
234       printf ("%s: pointer moved at %s.\n", progname, timestring ());
235 #endif
236
237   last_root_x = root_x;
238   last_root_y = root_y;
239   last_child = child;
240   last_mask = mask;
241
242   reset_timers ();
243 }
244
245
246 void
247 sleep_until_idle (until_idle_p)
248      Bool until_idle_p;
249 {
250   XEvent event;
251
252   if (until_idle_p)
253     {
254       timer_id = XtAppAddTimeOut (app, timeout, idle_timer, 0);
255 #ifdef HAVE_XIDLE
256       if (! use_xidle)
257 #endif
258         /* start polling the mouse position */
259         check_pointer_timer (&check_pointer_timer_id, 0);
260     }
261
262   while (1)
263     {
264       XtAppNextEvent (app, &event);
265
266       switch (event.xany.type) {
267       case 0:           /* our synthetic "timeout" event has been signalled */
268         if (until_idle_p)
269           {
270             Time idle;
271 #ifdef HAVE_XIDLE
272             if (use_xidle)
273               {
274                 if (! XGetIdleTime (dpy, &idle))
275                   {
276                     fprintf (stderr, "%s: %sXGetIdleTime() failed.\n",
277                              progname, (verbose_p ? "## " : ""));
278                     exit (1);
279                   }
280               }
281             else
282 #endif /* HAVE_XIDLE */
283               idle = 1000 * (last_activity_time - time ((time_t *) 0));
284             
285             if (idle >= timeout)
286               goto DONE;
287             else
288               timer_id = XtAppAddTimeOut (app, timeout - idle,
289                                           idle_timer, 0);
290           }
291         break;
292
293       case ClientMessage:
294         if (handle_clientmessage (&event, until_idle_p))
295           goto DONE;
296         break;
297
298       case CreateNotify:
299 #ifdef HAVE_XIDLE
300         if (! use_xidle)
301 #endif
302           XtAppAddTimeOut (app, notice_events_timeout, notice_events_timer,
303                            (XtPointer) event.xcreatewindow.window);
304         break;
305
306       case KeyPress:
307       case KeyRelease:
308       case ButtonPress:
309       case ButtonRelease:
310       case MotionNotify:
311
312 #ifdef DEBUG_TIMERS
313         if (verbose_p)
314           {
315             if (event.xany.type == MotionNotify)
316               printf ("%s: MotionNotify at %s\n", progname, timestring ());
317             else if (event.xany.type == KeyPress)
318               printf ("%s: KeyPress seen on 0x%X at %s\n", progname,
319                       event.xkey.window, timestring ());
320           }
321 #endif
322
323         /* We got a user event */
324         if (!until_idle_p)
325           goto DONE;
326         else
327           reset_timers ();
328         break;
329
330       default:
331         XtDispatchEvent (&event);
332       }
333     }
334  DONE:
335
336   if (check_pointer_timer_id)
337     {
338       XtRemoveTimeOut (check_pointer_timer_id);
339       check_pointer_timer_id = 0;
340     }
341   if (timer_id)
342     {
343       XtRemoveTimeOut (timer_id);
344       timer_id = 0;
345     }
346
347   if (until_idle_p && cycle_id)
348     abort ();
349
350   return;
351 }