1 /* timers.c --- detecting when the user is idle, and other timer-related tasks.
2 * xscreensaver, Copyright (c) 1991-1997, 1998
3 * Jamie Zawinski <jwz@jwz.org>
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation. No representations are made about the suitability of this
10 * software for any purpose. It is provided "as is" without express or
18 /* #define DEBUG_TIMERS */
22 #include <X11/Intrinsic.h>
26 # include <X11/Xmu/Error.h>
28 # include <Xmu/Error.h>
30 # else /* !HAVE_XMU */
32 #endif /* !HAVE_XMU */
34 #ifdef HAVE_XIDLE_EXTENSION
35 #include <X11/extensions/xidle.h>
36 #endif /* HAVE_XIDLE_EXTENSION */
38 #ifdef HAVE_MIT_SAVER_EXTENSION
39 #include <X11/extensions/scrnsaver.h>
40 #endif /* HAVE_MIT_SAVER_EXTENSION */
42 #ifdef HAVE_SGI_SAVER_EXTENSION
43 #include <X11/extensions/XScreenSaver.h>
44 #endif /* HAVE_SGI_SAVER_EXTENSION */
46 #ifdef HAVE_XHPDISABLERESET
47 # include <X11/XHPlib.h>
48 extern Bool hp_locked_p; /* from windows.c */
49 #endif /* HAVE_XHPDISABLERESET */
51 #include "xscreensaver.h"
55 idle_timer (XtPointer closure, XtIntervalId *id)
57 saver_info *si = (saver_info *) closure;
59 /* What an amazingly shitty design. Not only does Xt execute timeout
60 events from XtAppNextEvent() instead of from XtDispatchEvent(), but
61 there is no way to tell Xt to block until there is an X event OR a
62 timeout happens. Once your timeout proc is called, XtAppNextEvent()
63 still won't return until a "real" X event comes in.
65 So this function pushes a stupid, gratuitous, unnecessary event back
66 on the event queue to force XtAppNextEvent to return Right Fucking Now.
67 When the code in sleep_until_idle() sees an event of type XAnyEvent,
68 which the server never generates, it knows that a timeout has occurred.
71 fake_event.type = 0; /* XAnyEvent type, ignored. */
72 fake_event.xany.display = si->dpy;
73 fake_event.xany.window = 0;
74 XPutBackEvent (si->dpy, &fake_event);
79 notice_events (saver_info *si, Window window, Bool top_p)
81 saver_preferences *p = &si->prefs;
82 XWindowAttributes attrs;
84 Window root, parent, *kids;
87 if (XtWindowToWidget (si->dpy, window))
88 /* If it's one of ours, don't mess up its event mask. */
91 if (!XQueryTree (si->dpy, window, &root, &parent, &kids, &nkids))
96 XGetWindowAttributes (si->dpy, window, &attrs);
97 events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
100 /* Select for SubstructureNotify on all windows.
101 Select for KeyPress on all windows that already have it selected.
102 Do we need to select for ButtonRelease? I don't think so.
104 XSelectInput (si->dpy, window, SubstructureNotifyMask | events);
106 if (top_p && p->verbose_p && (events & KeyPressMask))
108 /* Only mention one window per tree (hack hack). */
109 fprintf (stderr, "%s: selected KeyPress on 0x%lX\n", blurb(),
110 (unsigned long) window);
117 notice_events (si, kids [--nkids], top_p);
118 XFree ((char *) kids);
124 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
126 /* When we notice a window being created, we spawn a timer that waits
127 30 seconds or so, and then selects events on that window. This error
128 handler is used so that we can cope with the fact that the window
129 may have been destroyed <30 seconds after it was created.
131 if (error->error_code == BadWindow ||
132 error->error_code == BadMatch ||
133 error->error_code == BadDrawable)
136 return saver_ehandler (dpy, error);
140 struct notice_events_timer_arg {
146 notice_events_timer (XtPointer closure, XtIntervalId *id)
148 struct notice_events_timer_arg *arg =
149 (struct notice_events_timer_arg *) closure;
151 XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
153 saver_info *si = arg->si;
154 Window window = arg->w;
157 notice_events (si, window, True);
158 XSync (si->dpy, False);
159 XSetErrorHandler (old_handler);
163 start_notice_events_timer (saver_info *si, Window w)
165 saver_preferences *p = &si->prefs;
166 struct notice_events_timer_arg *arg =
167 (struct notice_events_timer_arg *) malloc(sizeof(*arg));
170 XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer,
175 /* When the screensaver is active, this timer will periodically change
179 cycle_timer (XtPointer closure, XtIntervalId *id)
181 saver_info *si = (saver_info *) closure;
182 saver_preferences *p = &si->prefs;
183 Time how_long = p->cycle;
184 if (si->dbox_up_p || si->question_up_p)
187 fprintf (stderr, "%s: dialog box up; delaying hack change.\n",
189 how_long = 30000; /* 30 secs */
194 fprintf (stderr, "%s: changing graphics hacks.\n", blurb());
195 kill_screenhack (si);
196 spawn_screenhack (si, False);
198 si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
203 fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n",
204 blurb(), how_long, si->cycle_id);
205 #endif /* DEBUG_TIMERS */
210 activate_lock_timer (XtPointer closure, XtIntervalId *id)
212 saver_info *si = (saver_info *) closure;
213 saver_preferences *p = &si->prefs;
216 fprintf (stderr, "%s: timed out; activating lock\n", blurb());
219 #ifdef HAVE_XHPDISABLERESET
222 XHPDisableReset (si->dpy); /* turn off C-Sh-Reset */
229 /* Call this when user activity (or "simulated" activity) has been noticed.
232 reset_timers (saver_info *si)
234 saver_preferences *p = &si->prefs;
235 if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
240 fprintf (stderr, "%s: killing idle_timer (%ld, %ld)\n",
241 blurb(), p->timeout, si->timer_id);
242 #endif /* DEBUG_TIMERS */
244 XtRemoveTimeOut (si->timer_id);
245 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
247 if (si->cycle_id) abort ();
251 fprintf (stderr, "%s: restarting idle_timer (%ld, %ld)\n",
252 blurb(), p->timeout, si->timer_id);
253 #endif /* DEBUG_TIMERS */
255 si->last_activity_time = time ((time_t *) 0);
258 /* When we aren't using a server extension, this timer is used to periodically
259 wake up and poll the mouse position, which is possibly more reliable than
260 selecting motion events on every window.
263 check_pointer_timer (XtPointer closure, XtIntervalId *id)
266 saver_info *si = (saver_info *) closure;
267 saver_preferences *p = &si->prefs;
268 Bool active_p = False;
270 if (p->use_xidle_extension ||
271 p->use_mit_saver_extension ||
272 p->use_sgi_saver_extension)
275 si->check_pointer_timer_id =
276 XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
279 for (i = 0; i < si->nscreens; i++)
281 saver_screen_info *ssi = &si->screens[i];
283 int root_x, root_y, x, y;
286 XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
287 &root_x, &root_y, &x, &y, &mask);
289 if (root_x == ssi->poll_mouse_last_root_x &&
290 root_y == ssi->poll_mouse_last_root_y &&
291 child == ssi->poll_mouse_last_child &&
292 mask == ssi->poll_mouse_last_mask)
299 if (root_x == ssi->poll_mouse_last_root_x &&
300 root_y == ssi->poll_mouse_last_root_y &&
301 child == ssi->poll_mouse_last_child)
302 fprintf (stderr, "%s: modifiers changed at %s on screen %d.\n",
303 blurb(), timestring(), i);
305 fprintf (stderr, "%s: pointer moved at %s on screen %d.\n",
306 blurb(), timestring(), i);
307 #endif /* DEBUG_TIMERS */
309 si->last_activity_screen = ssi;
310 ssi->poll_mouse_last_root_x = root_x;
311 ssi->poll_mouse_last_root_y = root_y;
312 ssi->poll_mouse_last_child = child;
313 ssi->poll_mouse_last_mask = mask;
322 sleep_until_idle (saver_info *si, Bool until_idle_p)
324 saver_preferences *p = &si->prefs;
329 if (!p->use_mit_saver_extension && !p->use_sgi_saver_extension)
331 /* Wake up periodically to ask the server if we are idle. */
332 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
337 fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
338 blurb(), p->timeout, si->timer_id);
339 #endif /* DEBUG_TIMERS */
342 if (!p->use_xidle_extension &&
343 !p->use_mit_saver_extension &&
344 !p->use_sgi_saver_extension)
345 /* start polling the mouse position */
346 check_pointer_timer ((XtPointer) si, 0);
351 XtAppNextEvent (si->app, &event);
353 switch (event.xany.type) {
354 case 0: /* our synthetic "timeout" event has been signalled */
358 #ifdef HAVE_XIDLE_EXTENSION
359 if (p->use_xidle_extension)
361 if (! XGetIdleTime (si->dpy, &idle))
363 fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
364 saver_exit (si, 1, 0);
368 #endif /* HAVE_XIDLE_EXTENSION */
369 #ifdef HAVE_MIT_SAVER_EXTENSION
370 if (p->use_mit_saver_extension)
372 /* We don't need to do anything in this case - the synthetic
373 event isn't necessary, as we get sent specific events
378 #endif /* HAVE_MIT_SAVER_EXTENSION */
379 #ifdef HAVE_SGI_SAVER_EXTENSION
380 if (p->use_sgi_saver_extension)
382 /* We don't need to do anything in this case - the synthetic
383 event isn't necessary, as we get sent specific events
388 #endif /* HAVE_SGI_SAVER_EXTENSION */
390 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
393 if (idle >= p->timeout)
395 else if (!p->use_mit_saver_extension &&
396 !p->use_sgi_saver_extension)
398 si->timer_id = XtAppAddTimeOut (si->app, p->timeout - idle,
399 idle_timer, (XtPointer) si);
402 fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
403 blurb(), p->timeout - idle, si->timer_id);
404 #endif /* DEBUG_TIMERS */
410 if (handle_clientmessage (si, &event, until_idle_p))
415 if (!p->use_xidle_extension &&
416 !p->use_mit_saver_extension &&
417 !p->use_sgi_saver_extension)
419 start_notice_events_timer (si, event.xcreatewindow.window);
423 "%s: starting notice_events_timer for 0x%X (%lu)\n",
425 (unsigned int) event.xcreatewindow.window,
426 p->notice_events_timeout);
427 #endif /* DEBUG_TIMERS */
440 if (event.xany.type == MotionNotify)
441 fprintf (stderr, "%s: MotionNotify at %s\n",
442 blurb(), timestring ());
443 else if (event.xany.type == KeyPress)
444 fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(),
445 (unsigned int) event.xkey.window, timestring ());
446 else if (event.xany.type == ButtonPress)
447 fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(),
448 (unsigned int) event.xbutton.window, timestring ());
450 #endif /* DEBUG_TIMERS */
452 /* If any widgets want to handle this event, let them. */
453 XtDispatchEvent (&event);
455 /* We got a user event */
464 #ifdef HAVE_MIT_SAVER_EXTENSION
465 if (event.type == si->mit_saver_ext_event_number)
467 XScreenSaverNotifyEvent *sevent =
468 (XScreenSaverNotifyEvent *) &event;
469 if (sevent->state == ScreenSaverOn)
473 fprintf (stderr, "%s: ScreenSaverOn event received at %s\n",
474 blurb(), timestring ());
475 # endif /* DEBUG_TIMERS */
477 /* Get the "real" server window(s) out of the way as soon
480 for (i = 0; i < si->nscreens; i++)
482 saver_screen_info *ssi = &si->screens[i];
483 if (ssi->server_mit_saver_window &&
484 window_exists_p (si->dpy,
485 ssi->server_mit_saver_window))
486 XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
489 if (sevent->kind != ScreenSaverExternal)
493 "%s: ScreenSaverOn event wasn't of type External!\n",
495 # endif /* DEBUG_TIMERS */
501 else if (sevent->state == ScreenSaverOff)
505 fprintf (stderr, "%s: ScreenSaverOff event received at %s\n",
506 blurb(), timestring ());
507 # endif /* DEBUG_TIMERS */
512 else if (p->verbose_p)
514 "%s: unknown MIT-SCREEN-SAVER event received at %s\n",
515 blurb(), timestring ());
516 # endif /* DEBUG_TIMERS */
520 #endif /* HAVE_MIT_SAVER_EXTENSION */
523 #ifdef HAVE_SGI_SAVER_EXTENSION
524 if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
528 fprintf (stderr, "%s: ScreenSaverStart event received at %s\n",
529 blurb(), timestring ());
530 # endif /* DEBUG_TIMERS */
535 else if (event.type == (si->sgi_saver_ext_event_number +
540 fprintf (stderr, "%s: ScreenSaverEnd event received at %s\n",
541 blurb(), timestring ());
542 # endif /* DEBUG_TIMERS */
547 #endif /* HAVE_SGI_SAVER_EXTENSION */
549 XtDispatchEvent (&event);
555 /* If there's a user event on the queue, swallow it.
556 If we're using a server extension, and the user becomes active, we
557 get the extension event before the user event -- so the keypress or
558 motion or whatever is still on the queue. This makes "unfade" not
559 work, because it sees that event, and bugs out. (This problem
560 doesn't exhibit itself without an extension, because in that case,
561 there's only one event generated by user activity, not two.)
563 XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
567 if (si->check_pointer_timer_id)
569 XtRemoveTimeOut (si->check_pointer_timer_id);
570 si->check_pointer_timer_id = 0;
574 XtRemoveTimeOut (si->timer_id);
578 if (until_idle_p && si->cycle_id)
585 /* This timer goes off every few minutes, whether the user is idle or not,
586 to try and clean up anything that has gone wrong.
588 It calls disable_builtin_screensaver() so that if xset has been used,
589 or some other program (like xlock) has messed with the XSetScreenSaver()
590 settings, they will be set back to sensible values (if a server extension
591 is in use, messing with xlock can cause xscreensaver to never get a wakeup
592 event, and could cause monitor power-saving to occur, and all manner of
595 If the screen is currently blanked, it raises the window, in case some
596 other window has been mapped on top of it.
598 If the screen is currently blanked, and there is no hack running, it
599 clears the window, in case there is an error message printed on it (we
600 don't want the error message to burn in.)
604 watchdog_timer (XtPointer closure, XtIntervalId *id)
606 saver_info *si = (saver_info *) closure;
607 if (!si->demo_mode_p)
609 disable_builtin_screensaver (si, False);
610 if (si->screen_blanked_p)
612 Bool running_p = screenhack_running_p(si);
615 if (si->prefs.verbose_p)
616 fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
617 blurb(), (running_p ? "" : "and clearing "));
618 #endif /* DEBUG_TIMERS */
620 raise_window (si, True, True, running_p);
622 if (!monitor_powered_on_p (si))
624 if (si->prefs.verbose_p)
626 "%s: server reports that monitor has powered down; "
627 "killing running hacks.\n", blurb());
628 kill_screenhack (si);
636 reset_watchdog_timer (saver_info *si, Bool on_p)
638 saver_preferences *p = &si->prefs;
642 XtRemoveTimeOut (si->watchdog_id);
646 if (on_p && p->watchdog_timeout)
648 si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
649 watchdog_timer, (XtPointer) si);
653 fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
654 blurb(), p->watchdog_timeout, si->watchdog_id);
655 #endif /* DEBUG_TIMERS */