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 */
245 XtRemoveTimeOut (si->timer_id);
246 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
248 if (si->cycle_id) abort (); /* no cycle timer when inactive */
252 fprintf (stderr, "%s: restarting idle_timer (%ld, %ld)\n",
253 blurb(), p->timeout, si->timer_id);
254 #endif /* DEBUG_TIMERS */
256 si->last_activity_time = time ((time_t *) 0);
259 /* When we aren't using a server extension, this timer is used to periodically
260 wake up and poll the mouse position, which is possibly more reliable than
261 selecting motion events on every window.
264 check_pointer_timer (XtPointer closure, XtIntervalId *id)
267 saver_info *si = (saver_info *) closure;
268 saver_preferences *p = &si->prefs;
269 Bool active_p = False;
271 if (p->use_xidle_extension ||
272 p->use_mit_saver_extension ||
273 p->use_sgi_saver_extension)
274 /* If an extension is in use, we should not be polling the mouse. */
277 si->check_pointer_timer_id =
278 XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
281 for (i = 0; i < si->nscreens; i++)
283 saver_screen_info *ssi = &si->screens[i];
285 int root_x, root_y, x, y;
288 XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
289 &root_x, &root_y, &x, &y, &mask);
291 if (root_x == ssi->poll_mouse_last_root_x &&
292 root_y == ssi->poll_mouse_last_root_y &&
293 child == ssi->poll_mouse_last_child &&
294 mask == ssi->poll_mouse_last_mask)
301 if (root_x == ssi->poll_mouse_last_root_x &&
302 root_y == ssi->poll_mouse_last_root_y &&
303 child == ssi->poll_mouse_last_child)
304 fprintf (stderr, "%s: modifiers changed at %s on screen %d.\n",
305 blurb(), timestring(), i);
307 fprintf (stderr, "%s: pointer moved at %s on screen %d.\n",
308 blurb(), timestring(), i);
309 #endif /* DEBUG_TIMERS */
311 si->last_activity_screen = ssi;
312 ssi->poll_mouse_last_root_x = root_x;
313 ssi->poll_mouse_last_root_y = root_y;
314 ssi->poll_mouse_last_child = child;
315 ssi->poll_mouse_last_mask = mask;
324 dispatch_event (saver_info *si, XEvent *event)
326 /* If this is for the splash dialog, pass it along.
327 Note that the password dialog is handled with its own event loop,
328 so events for that window will never come through here.
330 if (si->splash_dialog && event->xany.window == si->splash_dialog)
331 handle_splash_event (si, event);
333 XtDispatchEvent (event);
338 sleep_until_idle (saver_info *si, Bool until_idle_p)
340 saver_preferences *p = &si->prefs;
345 if (!p->use_mit_saver_extension && !p->use_sgi_saver_extension)
347 /* Wake up periodically to ask the server if we are idle. */
348 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
353 fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
354 blurb(), p->timeout, si->timer_id);
355 #endif /* DEBUG_TIMERS */
358 if (!p->use_xidle_extension &&
359 !p->use_mit_saver_extension &&
360 !p->use_sgi_saver_extension)
361 /* start polling the mouse position */
362 check_pointer_timer ((XtPointer) si, 0);
367 XtAppNextEvent (si->app, &event);
369 switch (event.xany.type) {
370 case 0: /* our synthetic "timeout" event has been signalled */
374 #ifdef HAVE_XIDLE_EXTENSION
375 if (p->use_xidle_extension)
377 if (! XGetIdleTime (si->dpy, &idle))
379 fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
380 saver_exit (si, 1, 0);
384 #endif /* HAVE_XIDLE_EXTENSION */
385 #ifdef HAVE_MIT_SAVER_EXTENSION
386 if (p->use_mit_saver_extension)
388 /* We don't need to do anything in this case - the synthetic
389 event isn't necessary, as we get sent specific events
394 #endif /* HAVE_MIT_SAVER_EXTENSION */
395 #ifdef HAVE_SGI_SAVER_EXTENSION
396 if (p->use_sgi_saver_extension)
398 /* We don't need to do anything in this case - the synthetic
399 event isn't necessary, as we get sent specific events
404 #endif /* HAVE_SGI_SAVER_EXTENSION */
406 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
409 if (idle >= p->timeout)
411 else if (!p->use_mit_saver_extension &&
412 !p->use_sgi_saver_extension)
414 si->timer_id = XtAppAddTimeOut (si->app, p->timeout - idle,
415 idle_timer, (XtPointer) si);
418 fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
419 blurb(), p->timeout - idle, si->timer_id);
420 #endif /* DEBUG_TIMERS */
426 if (handle_clientmessage (si, &event, until_idle_p))
431 if (!p->use_xidle_extension &&
432 !p->use_mit_saver_extension &&
433 !p->use_sgi_saver_extension)
435 start_notice_events_timer (si, event.xcreatewindow.window);
439 "%s: starting notice_events_timer for 0x%X (%lu)\n",
441 (unsigned int) event.xcreatewindow.window,
442 p->notice_events_timeout);
443 #endif /* DEBUG_TIMERS */
456 if (event.xany.type == MotionNotify)
457 fprintf (stderr, "%s: MotionNotify at %s\n",
458 blurb(), timestring ());
459 else if (event.xany.type == KeyPress)
460 fprintf (stderr, "%s: KeyPress seen on 0x%X at %s\n", blurb(),
461 (unsigned int) event.xkey.window, timestring ());
462 else if (event.xany.type == ButtonPress)
463 fprintf (stderr, "%s: ButtonPress seen on 0x%X at %s\n", blurb(),
464 (unsigned int) event.xbutton.window, timestring ());
466 #endif /* DEBUG_TIMERS */
468 /* If any widgets want to handle this event, let them. */
469 dispatch_event (si, &event);
471 /* We got a user event */
480 #ifdef HAVE_MIT_SAVER_EXTENSION
481 if (event.type == si->mit_saver_ext_event_number)
483 XScreenSaverNotifyEvent *sevent =
484 (XScreenSaverNotifyEvent *) &event;
485 if (sevent->state == ScreenSaverOn)
489 fprintf (stderr, "%s: ScreenSaverOn event received at %s\n",
490 blurb(), timestring ());
491 # endif /* DEBUG_TIMERS */
493 /* Get the "real" server window(s) out of the way as soon
496 for (i = 0; i < si->nscreens; i++)
498 saver_screen_info *ssi = &si->screens[i];
499 if (ssi->server_mit_saver_window &&
500 window_exists_p (si->dpy,
501 ssi->server_mit_saver_window))
502 XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
505 if (sevent->kind != ScreenSaverExternal)
509 "%s: ScreenSaverOn event wasn't of type External!\n",
511 # endif /* DEBUG_TIMERS */
517 else if (sevent->state == ScreenSaverOff)
521 fprintf (stderr, "%s: ScreenSaverOff event received at %s\n",
522 blurb(), timestring ());
523 # endif /* DEBUG_TIMERS */
528 else if (p->verbose_p)
530 "%s: unknown MIT-SCREEN-SAVER event received at %s\n",
531 blurb(), timestring ());
532 # endif /* DEBUG_TIMERS */
536 #endif /* HAVE_MIT_SAVER_EXTENSION */
539 #ifdef HAVE_SGI_SAVER_EXTENSION
540 if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
544 fprintf (stderr, "%s: ScreenSaverStart event received at %s\n",
545 blurb(), timestring ());
546 # endif /* DEBUG_TIMERS */
551 else if (event.type == (si->sgi_saver_ext_event_number +
556 fprintf (stderr, "%s: ScreenSaverEnd event received at %s\n",
557 blurb(), timestring ());
558 # endif /* DEBUG_TIMERS */
563 #endif /* HAVE_SGI_SAVER_EXTENSION */
565 dispatch_event (si, &event);
571 /* If there's a user event on the queue, swallow it.
572 If we're using a server extension, and the user becomes active, we
573 get the extension event before the user event -- so the keypress or
574 motion or whatever is still on the queue. This makes "unfade" not
575 work, because it sees that event, and bugs out. (This problem
576 doesn't exhibit itself without an extension, because in that case,
577 there's only one event generated by user activity, not two.)
579 XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
583 if (si->check_pointer_timer_id)
585 XtRemoveTimeOut (si->check_pointer_timer_id);
586 si->check_pointer_timer_id = 0;
590 XtRemoveTimeOut (si->timer_id);
594 if (until_idle_p && si->cycle_id) /* no cycle timer when inactive */
601 /* This timer goes off every few minutes, whether the user is idle or not,
602 to try and clean up anything that has gone wrong.
604 It calls disable_builtin_screensaver() so that if xset has been used,
605 or some other program (like xlock) has messed with the XSetScreenSaver()
606 settings, they will be set back to sensible values (if a server extension
607 is in use, messing with xlock can cause xscreensaver to never get a wakeup
608 event, and could cause monitor power-saving to occur, and all manner of
611 If the screen is currently blanked, it raises the window, in case some
612 other window has been mapped on top of it.
614 If the screen is currently blanked, and there is no hack running, it
615 clears the window, in case there is an error message printed on it (we
616 don't want the error message to burn in.)
620 watchdog_timer (XtPointer closure, XtIntervalId *id)
622 saver_info *si = (saver_info *) closure;
623 if (!si->demo_mode_p)
625 disable_builtin_screensaver (si, False);
626 if (si->screen_blanked_p)
628 Bool running_p = screenhack_running_p(si);
631 if (si->prefs.verbose_p)
632 fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
633 blurb(), (running_p ? "" : "and clearing "));
634 #endif /* DEBUG_TIMERS */
636 raise_window (si, True, True, running_p);
638 if (!monitor_powered_on_p (si))
640 if (si->prefs.verbose_p)
642 "%s: server reports that monitor has powered down; "
643 "killing running hacks.\n", blurb());
644 kill_screenhack (si);
652 reset_watchdog_timer (saver_info *si, Bool on_p)
654 saver_preferences *p = &si->prefs;
658 XtRemoveTimeOut (si->watchdog_id);
662 if (on_p && p->watchdog_timeout)
664 si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
665 watchdog_timer, (XtPointer) si);
669 fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
670 blurb(), p->watchdog_timeout, si->watchdog_id);
671 #endif /* DEBUG_TIMERS */