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;
187 fprintf (stderr, "%s: dialog box up; delaying hack change.\n",
189 how_long = 30000; /* 30 secs */
193 maybe_reload_init_file (si);
195 fprintf (stderr, "%s: changing graphics hacks.\n", blurb());
196 kill_screenhack (si);
197 spawn_screenhack (si, False);
199 si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
204 fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n",
205 blurb(), how_long, si->cycle_id);
206 #endif /* DEBUG_TIMERS */
211 activate_lock_timer (XtPointer closure, XtIntervalId *id)
213 saver_info *si = (saver_info *) closure;
214 saver_preferences *p = &si->prefs;
217 fprintf (stderr, "%s: timed out; activating lock\n", blurb());
220 #ifdef HAVE_XHPDISABLERESET
223 XHPDisableReset (si->dpy); /* turn off C-Sh-Reset */
230 /* Call this when user activity (or "simulated" activity) has been noticed.
233 reset_timers (saver_info *si)
235 saver_preferences *p = &si->prefs;
236 if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
241 fprintf (stderr, "%s: killing idle_timer (%ld, %ld)\n",
242 blurb(), p->timeout, si->timer_id);
243 #endif /* DEBUG_TIMERS */
246 XtRemoveTimeOut (si->timer_id);
247 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
249 if (si->cycle_id) abort (); /* no cycle timer when inactive */
253 fprintf (stderr, "%s: restarting idle_timer (%ld, %ld)\n",
254 blurb(), p->timeout, si->timer_id);
255 #endif /* DEBUG_TIMERS */
257 si->last_activity_time = time ((time_t *) 0);
260 /* When we aren't using a server extension, this timer is used to periodically
261 wake up and poll the mouse position, which is possibly more reliable than
262 selecting motion events on every window.
265 check_pointer_timer (XtPointer closure, XtIntervalId *id)
268 saver_info *si = (saver_info *) closure;
269 saver_preferences *p = &si->prefs;
270 Bool active_p = False;
272 if (p->use_xidle_extension ||
273 p->use_mit_saver_extension ||
274 p->use_sgi_saver_extension)
275 /* If an extension is in use, we should not be polling the mouse. */
278 si->check_pointer_timer_id =
279 XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
282 for (i = 0; i < si->nscreens; i++)
284 saver_screen_info *ssi = &si->screens[i];
286 int root_x, root_y, x, y;
289 XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
290 &root_x, &root_y, &x, &y, &mask);
292 if (root_x == ssi->poll_mouse_last_root_x &&
293 root_y == ssi->poll_mouse_last_root_y &&
294 child == ssi->poll_mouse_last_child &&
295 mask == ssi->poll_mouse_last_mask)
302 if (root_x == ssi->poll_mouse_last_root_x &&
303 root_y == ssi->poll_mouse_last_root_y &&
304 child == ssi->poll_mouse_last_child)
305 fprintf (stderr, "%s: modifiers changed at %s on screen %d.\n",
306 blurb(), timestring(), i);
308 fprintf (stderr, "%s: pointer moved at %s on screen %d.\n",
309 blurb(), timestring(), i);
310 #endif /* DEBUG_TIMERS */
312 si->last_activity_screen = ssi;
313 ssi->poll_mouse_last_root_x = root_x;
314 ssi->poll_mouse_last_root_y = root_y;
315 ssi->poll_mouse_last_child = child;
316 ssi->poll_mouse_last_mask = mask;
325 dispatch_event (saver_info *si, XEvent *event)
327 /* If this is for the splash dialog, pass it along.
328 Note that the password dialog is handled with its own event loop,
329 so events for that window will never come through here.
331 if (si->splash_dialog && event->xany.window == si->splash_dialog)
332 handle_splash_event (si, event);
334 XtDispatchEvent (event);
339 sleep_until_idle (saver_info *si, Bool until_idle_p)
341 saver_preferences *p = &si->prefs;
346 if (!p->use_mit_saver_extension && !p->use_sgi_saver_extension)
348 /* Wake up periodically to ask the server if we are idle. */
349 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
354 fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
355 blurb(), p->timeout, si->timer_id);
356 #endif /* DEBUG_TIMERS */
359 if (!p->use_xidle_extension &&
360 !p->use_mit_saver_extension &&
361 !p->use_sgi_saver_extension)
362 /* start polling the mouse position */
363 check_pointer_timer ((XtPointer) si, 0);
368 XtAppNextEvent (si->app, &event);
370 switch (event.xany.type) {
371 case 0: /* our synthetic "timeout" event has been signalled */
375 #ifdef HAVE_XIDLE_EXTENSION
376 if (p->use_xidle_extension)
378 if (! XGetIdleTime (si->dpy, &idle))
380 fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
381 saver_exit (si, 1, 0);
385 #endif /* HAVE_XIDLE_EXTENSION */
386 #ifdef HAVE_MIT_SAVER_EXTENSION
387 if (p->use_mit_saver_extension)
389 /* We don't need to do anything in this case - the synthetic
390 event isn't necessary, as we get sent specific events
395 #endif /* HAVE_MIT_SAVER_EXTENSION */
396 #ifdef HAVE_SGI_SAVER_EXTENSION
397 if (p->use_sgi_saver_extension)
399 /* We don't need to do anything in this case - the synthetic
400 event isn't necessary, as we get sent specific events
405 #endif /* HAVE_SGI_SAVER_EXTENSION */
407 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
410 if (idle >= p->timeout)
412 else if (!p->use_mit_saver_extension &&
413 !p->use_sgi_saver_extension)
415 si->timer_id = XtAppAddTimeOut (si->app, p->timeout - idle,
416 idle_timer, (XtPointer) si);
419 fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
420 blurb(), p->timeout - idle, si->timer_id);
421 #endif /* DEBUG_TIMERS */
427 if (handle_clientmessage (si, &event, until_idle_p))
432 if (!p->use_xidle_extension &&
433 !p->use_mit_saver_extension &&
434 !p->use_sgi_saver_extension)
436 start_notice_events_timer (si, event.xcreatewindow.window);
440 "%s: starting notice_events_timer for 0x%X (%lu)\n",
442 (unsigned int) event.xcreatewindow.window,
443 p->notice_events_timeout);
444 #endif /* DEBUG_TIMERS */
457 if (event.xany.type == MotionNotify)
458 fprintf (stderr, "%s: MotionNotify at %s\n",
459 blurb(), timestring ());
460 else if (event.xany.type == KeyPress)
461 fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(),
462 (unsigned int) event.xkey.window, timestring ());
463 else if (event.xany.type == ButtonPress)
464 fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(),
465 (unsigned int) event.xbutton.window, timestring ());
467 #endif /* DEBUG_TIMERS */
469 /* If any widgets want to handle this event, let them. */
470 dispatch_event (si, &event);
472 /* We got a user event.
473 If we're waiting for the user to become active, this is it.
474 If we're waiting until the user becomes idle, reset the timers
475 (since now we have longer to wait.)
480 (event.xany.type == MotionNotify ||
481 event.xany.type == KeyRelease))
482 /* When we're demoing a single hack, mouse motion doesn't
483 cause deactivation. Only clicks and keypresses do. */
486 /* If we're not demoing, then any activity causes deactivation.
497 #ifdef HAVE_MIT_SAVER_EXTENSION
498 if (event.type == si->mit_saver_ext_event_number)
500 XScreenSaverNotifyEvent *sevent =
501 (XScreenSaverNotifyEvent *) &event;
502 if (sevent->state == ScreenSaverOn)
506 fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
509 /* Get the "real" server window(s) out of the way as soon
511 for (i = 0; i < si->nscreens; i++)
513 saver_screen_info *ssi = &si->screens[i];
514 if (ssi->server_mit_saver_window &&
515 window_exists_p (si->dpy,
516 ssi->server_mit_saver_window))
517 XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
520 if (sevent->kind != ScreenSaverExternal)
523 "%s: ScreenSaverOn event wasn't of type External!\n",
530 else if (sevent->state == ScreenSaverOff)
533 fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
540 "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
541 blurb(), sevent->state);
545 #endif /* HAVE_MIT_SAVER_EXTENSION */
548 #ifdef HAVE_SGI_SAVER_EXTENSION
549 if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
552 fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
558 else if (event.type == (si->sgi_saver_ext_event_number +
562 fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
568 #endif /* HAVE_SGI_SAVER_EXTENSION */
570 dispatch_event (si, &event);
576 /* If there's a user event on the queue, swallow it.
577 If we're using a server extension, and the user becomes active, we
578 get the extension event before the user event -- so the keypress or
579 motion or whatever is still on the queue. This makes "unfade" not
580 work, because it sees that event, and bugs out. (This problem
581 doesn't exhibit itself without an extension, because in that case,
582 there's only one event generated by user activity, not two.)
584 XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
588 if (si->check_pointer_timer_id)
590 XtRemoveTimeOut (si->check_pointer_timer_id);
591 si->check_pointer_timer_id = 0;
595 XtRemoveTimeOut (si->timer_id);
599 if (until_idle_p && si->cycle_id) /* no cycle timer when inactive */
606 /* This timer goes off every few minutes, whether the user is idle or not,
607 to try and clean up anything that has gone wrong.
609 It calls disable_builtin_screensaver() so that if xset has been used,
610 or some other program (like xlock) has messed with the XSetScreenSaver()
611 settings, they will be set back to sensible values (if a server extension
612 is in use, messing with xlock can cause xscreensaver to never get a wakeup
613 event, and could cause monitor power-saving to occur, and all manner of
616 If the screen is currently blanked, it raises the window, in case some
617 other window has been mapped on top of it.
619 If the screen is currently blanked, and there is no hack running, it
620 clears the window, in case there is an error message printed on it (we
621 don't want the error message to burn in.)
625 watchdog_timer (XtPointer closure, XtIntervalId *id)
627 saver_info *si = (saver_info *) closure;
629 disable_builtin_screensaver (si, False);
631 if (si->screen_blanked_p)
633 Bool running_p = screenhack_running_p(si);
636 if (si->prefs.verbose_p)
637 fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
638 blurb(), (running_p ? "" : "and clearing "));
639 #endif /* DEBUG_TIMERS */
641 raise_window (si, True, True, running_p);
643 if (!monitor_powered_on_p (si))
645 if (si->prefs.verbose_p)
647 "%s: server reports that monitor has powered down; "
648 "killing running hacks.\n", blurb());
649 kill_screenhack (si);
656 reset_watchdog_timer (saver_info *si, Bool on_p)
658 saver_preferences *p = &si->prefs;
662 XtRemoveTimeOut (si->watchdog_id);
666 if (on_p && p->watchdog_timeout)
668 si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
669 watchdog_timer, (XtPointer) si);
673 fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
674 blurb(), p->watchdog_timeout, si->watchdog_id);
675 #endif /* DEBUG_TIMERS */