1 /* timers.c --- detecting when the user is idle, and other timer-related tasks.
2 * xscreensaver, Copyright (c) 1991-1997 Jamie Zawinski <jwz@netscape.com>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
17 /* #define DEBUG_TIMERS */
21 #include <X11/Intrinsic.h>
25 # include <X11/Xmu/Error.h>
27 # include <Xmu/Error.h>
29 # else /* !HAVE_XMU */
31 #endif /* !HAVE_XMU */
33 #ifdef HAVE_XIDLE_EXTENSION
34 #include <X11/extensions/xidle.h>
35 #endif /* HAVE_XIDLE_EXTENSION */
37 #ifdef HAVE_MIT_SAVER_EXTENSION
38 #include <X11/extensions/scrnsaver.h>
39 #endif /* HAVE_MIT_SAVER_EXTENSION */
41 #ifdef HAVE_SGI_SAVER_EXTENSION
42 #include <X11/extensions/XScreenSaver.h>
43 #endif /* HAVE_SGI_SAVER_EXTENSION */
45 #ifdef HAVE_XHPDISABLERESET
46 # include <X11/XHPlib.h>
47 extern Bool hp_locked_p; /* from windows.c */
48 #endif /* HAVE_XHPDISABLERESET */
50 #include "xscreensaver.h"
54 idle_timer (XtPointer closure, XtIntervalId *id)
56 saver_info *si = (saver_info *) closure;
58 /* What an amazingly shitty design. Not only does Xt execute timeout
59 events from XtAppNextEvent() instead of from XtDispatchEvent(), but
60 there is no way to tell Xt to block until there is an X event OR a
61 timeout happens. Once your timeout proc is called, XtAppNextEvent()
62 still won't return until a "real" X event comes in.
64 So this function pushes a stupid, gratuitous, unnecessary event back
65 on the event queue to force XtAppNextEvent to return Right Fucking Now.
66 When the code in sleep_until_idle() sees an event of type XAnyEvent,
67 which the server never generates, it knows that a timeout has occurred.
70 fake_event.type = 0; /* XAnyEvent type, ignored. */
71 fake_event.xany.display = si->dpy;
72 fake_event.xany.window = 0;
73 XPutBackEvent (si->dpy, &fake_event);
78 notice_events (saver_info *si, Window window, Bool top_p)
80 saver_preferences *p = &si->prefs;
81 XWindowAttributes attrs;
83 Window root, parent, *kids;
86 if (XtWindowToWidget (si->dpy, window))
87 /* If it's one of ours, don't mess up its event mask. */
90 if (!XQueryTree (si->dpy, window, &root, &parent, &kids, &nkids))
95 XGetWindowAttributes (si->dpy, window, &attrs);
96 events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
99 /* Select for SubstructureNotify on all windows.
100 Select for KeyPress on all windows that already have it selected.
101 Do we need to select for ButtonRelease? I don't think so.
103 XSelectInput (si->dpy, window, SubstructureNotifyMask | events);
105 if (top_p && p->verbose_p && (events & KeyPressMask))
107 /* Only mention one window per tree (hack hack). */
108 printf ("%s: selected KeyPress on 0x%lX\n", progname,
109 (unsigned long) window);
116 notice_events (si, kids [--nkids], top_p);
117 XFree ((char *) kids);
123 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
125 /* When we notice a window being created, we spawn a timer that waits
126 30 seconds or so, and then selects events on that window. This error
127 handler is used so that we can cope with the fact that the window
128 may have been destroyed <30 seconds after it was created.
130 if (error->error_code == BadWindow ||
131 error->error_code == BadMatch ||
132 error->error_code == BadDrawable)
135 return saver_ehandler (dpy, error);
139 struct notice_events_timer_arg {
145 notice_events_timer (XtPointer closure, XtIntervalId *id)
147 struct notice_events_timer_arg *arg =
148 (struct notice_events_timer_arg *) closure;
150 XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
152 saver_info *si = arg->si;
153 Window window = arg->w;
156 notice_events (si, window, True);
157 XSync (si->dpy, False);
158 XSetErrorHandler (old_handler);
162 start_notice_events_timer (saver_info *si, Window w)
164 saver_preferences *p = &si->prefs;
165 struct notice_events_timer_arg *arg =
166 (struct notice_events_timer_arg *) malloc(sizeof(*arg));
169 XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer,
174 /* When the screensaver is active, this timer will periodically change
178 cycle_timer (XtPointer closure, XtIntervalId *id)
180 saver_info *si = (saver_info *) closure;
181 saver_preferences *p = &si->prefs;
182 Time how_long = p->cycle;
186 printf ("%s: dbox up; delaying hack change.\n", progname);
187 how_long = 30000; /* 30 secs */
192 printf ("%s: changing graphics hacks.\n", progname);
193 kill_screenhack (si);
194 spawn_screenhack (si, False);
196 si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
201 printf ("%s: starting cycle_timer (%ld, %ld)\n",
202 progname, how_long, si->cycle_id);
208 activate_lock_timer (XtPointer closure, XtIntervalId *id)
210 saver_info *si = (saver_info *) closure;
211 saver_preferences *p = &si->prefs;
214 printf ("%s: timed out; activating lock\n", progname);
217 #ifdef HAVE_XHPDISABLERESET
220 XHPDisableReset (si->dpy); /* turn off C-Sh-Reset */
227 /* Call this when user activity (or "simulated" activity) has been noticed.
230 reset_timers (saver_info *si)
232 saver_preferences *p = &si->prefs;
233 if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
238 printf ("%s: killing idle_timer (%ld, %ld)\n",
239 progname, p->timeout, si->timer_id);
241 XtRemoveTimeOut (si->timer_id);
242 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
244 if (si->cycle_id) abort ();
248 printf ("%s: restarting idle_timer (%ld, %ld)\n",
249 progname, p->timeout, si->timer_id);
252 si->last_activity_time = time ((time_t *) 0);
255 /* When we aren't using a server extension, this timer is used to periodically
256 wake up and poll the mouse position, which is possibly more reliable than
257 selecting motion events on every window.
260 check_pointer_timer (XtPointer closure, XtIntervalId *id)
263 saver_info *si = (saver_info *) closure;
264 saver_preferences *p = &si->prefs;
265 Bool active_p = False;
267 if (p->use_xidle_extension ||
268 p->use_mit_saver_extension ||
269 p->use_sgi_saver_extension)
272 si->check_pointer_timer_id =
273 XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
276 for (i = 0; i < si->nscreens; i++)
278 saver_screen_info *ssi = &si->screens[i];
280 int root_x, root_y, x, y;
283 XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
284 &root_x, &root_y, &x, &y, &mask);
286 if (root_x == ssi->poll_mouse_last_root_x &&
287 root_y == ssi->poll_mouse_last_root_y &&
288 child == ssi->poll_mouse_last_child &&
289 mask == ssi->poll_mouse_last_mask)
296 if (root_x == ssi->poll_mouse_last_root_x &&
297 root_y == ssi->poll_mouse_last_root_y &&
298 child == ssi->poll_mouse_last_child)
299 printf ("%s: modifiers changed at %s on screen %d.\n",
300 progname, timestring(), i);
302 printf ("%s: pointer moved at %s on screen %d.\n",
303 progname, timestring(), i);
306 si->last_activity_screen = ssi;
307 ssi->poll_mouse_last_root_x = root_x;
308 ssi->poll_mouse_last_root_y = root_y;
309 ssi->poll_mouse_last_child = child;
310 ssi->poll_mouse_last_mask = mask;
319 sleep_until_idle (saver_info *si, Bool until_idle_p)
321 saver_preferences *p = &si->prefs;
326 if (!p->use_mit_saver_extension && !p->use_sgi_saver_extension)
328 /* Wake up periodically to ask the server if we are idle. */
329 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
333 printf ("%s: starting idle_timer (%ld, %ld)\n",
334 progname, p->timeout, si->timer_id);
338 if (!p->use_xidle_extension &&
339 !p->use_mit_saver_extension &&
340 !p->use_sgi_saver_extension)
341 /* start polling the mouse position */
342 check_pointer_timer ((XtPointer) si, 0);
347 XtAppNextEvent (si->app, &event);
349 switch (event.xany.type) {
350 case 0: /* our synthetic "timeout" event has been signalled */
354 #ifdef HAVE_XIDLE_EXTENSION
355 if (p->use_xidle_extension)
357 if (! XGetIdleTime (si->dpy, &idle))
359 fprintf (stderr, "%s: XGetIdleTime() failed.\n", progname);
364 #endif /* HAVE_XIDLE_EXTENSION */
365 #ifdef HAVE_MIT_SAVER_EXTENSION
366 if (p->use_mit_saver_extension)
368 /* We don't need to do anything in this case - the synthetic
369 event isn't necessary, as we get sent specific events
374 #endif /* HAVE_MIT_SAVER_EXTENSION */
375 #ifdef HAVE_SGI_SAVER_EXTENSION
376 if (p->use_sgi_saver_extension)
378 /* We don't need to do anything in this case - the synthetic
379 event isn't necessary, as we get sent specific events
384 #endif /* HAVE_SGI_SAVER_EXTENSION */
386 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
389 if (idle >= p->timeout)
391 else if (!p->use_mit_saver_extension &&
392 !p->use_sgi_saver_extension)
394 si->timer_id = XtAppAddTimeOut (si->app, p->timeout - idle,
395 idle_timer, (XtPointer) si);
398 printf ("%s: starting idle_timer (%ld, %ld)\n",
399 progname, p->timeout - idle, si->timer_id);
400 #endif /* DEBUG_TIMERS */
406 if (handle_clientmessage (si, &event, until_idle_p))
411 if (!p->use_xidle_extension &&
412 !p->use_mit_saver_extension &&
413 !p->use_sgi_saver_extension)
415 start_notice_events_timer (si, event.xcreatewindow.window);
418 printf ("%s: starting notice_events_timer for 0x%X (%lu)\n",
420 (unsigned int) event.xcreatewindow.window,
421 p->notice_events_timeout);
422 #endif /* DEBUG_TIMERS */
435 if (event.xany.type == MotionNotify)
436 printf ("%s: MotionNotify at %s\n", progname, timestring ());
437 else if (event.xany.type == KeyPress)
438 printf ("%s: KeyPress seen on 0x%X at %s\n", progname,
439 (unsigned int) event.xkey.window, timestring ());
443 /* We got a user event */
452 #ifdef HAVE_MIT_SAVER_EXTENSION
453 if (event.type == si->mit_saver_ext_event_number)
455 XScreenSaverNotifyEvent *sevent =
456 (XScreenSaverNotifyEvent *) &event;
457 if (sevent->state == ScreenSaverOn)
461 printf ("%s: ScreenSaverOn event received at %s\n",
462 progname, timestring ());
463 # endif /* DEBUG_TIMERS */
465 /* Get the "real" server window(s) out of the way as soon
468 for (i = 0; i < si->nscreens; i++)
470 saver_screen_info *ssi = &si->screens[i];
471 if (ssi->server_mit_saver_window &&
472 window_exists_p (si->dpy,
473 ssi->server_mit_saver_window))
474 XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
477 if (sevent->kind != ScreenSaverExternal)
481 "%s: ScreenSaverOn event wasn't of type External!\n",
483 # endif /* DEBUG_TIMERS */
489 else if (sevent->state == ScreenSaverOff)
493 printf ("%s: ScreenSaverOff event received at %s\n",
494 progname, timestring ());
495 # endif /* DEBUG_TIMERS */
500 else if (p->verbose_p)
501 printf ("%s: unknown MIT-SCREEN-SAVER event received at %s\n",
502 progname, timestring ());
503 # endif /* DEBUG_TIMERS */
507 #endif /* HAVE_MIT_SAVER_EXTENSION */
510 #ifdef HAVE_SGI_SAVER_EXTENSION
511 if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
515 printf ("%s: ScreenSaverStart event received at %s\n",
516 progname, timestring ());
517 # endif /* DEBUG_TIMERS */
522 else if (event.type == (si->sgi_saver_ext_event_number +
527 printf ("%s: ScreenSaverEnd event received at %s\n",
528 progname, timestring ());
529 # endif /* DEBUG_TIMERS */
534 #endif /* HAVE_SGI_SAVER_EXTENSION */
536 XtDispatchEvent (&event);
542 /* If there's a user event on the queue, swallow it.
543 If we're using a server extension, and the user becomes active, we
544 get the extension event before the user event -- so the keypress or
545 motion or whatever is still on the queue. This makes "unfade" not
546 work, because it sees that event, and bugs out. (This problem
547 doesn't exhibit itself without an extension, because in that case,
548 there's only one event generated by user activity, not two.)
550 XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
554 if (si->check_pointer_timer_id)
556 XtRemoveTimeOut (si->check_pointer_timer_id);
557 si->check_pointer_timer_id = 0;
561 XtRemoveTimeOut (si->timer_id);
565 if (until_idle_p && si->cycle_id)
572 /* This timer goes off every few minutes, whether the user is idle or not,
573 to try and clean up anything that has gone wrong.
575 It calls disable_builtin_screensaver() so that if xset has been used,
576 or some other program (like xlock) has messed with the XSetScreenSaver()
577 settings, they will be set back to sensible values (if a server extension
578 is in use, messing with xlock can cause xscreensaver to never get a wakeup
579 event, and could cause monitor power-saving to occur, and all manner of
582 If the screen is currently blanked, it raises the window, in case some
583 other window has been mapped on top of it.
585 If the screen is currently blanked, and there is no hack running, it
586 clears the window, in case there is an error message printed on it (we
587 don't want the error message to burn in.)
591 watchdog_timer (XtPointer closure, XtIntervalId *id)
593 saver_info *si = (saver_info *) closure;
594 if (!si->demo_mode_p)
596 disable_builtin_screensaver (si, False);
597 if (si->screen_blanked_p)
599 Bool running_p = screenhack_running_p(si);
601 if (si->prefs.verbose_p)
602 printf ("%s: watchdog timer raising %sscreen.\n",
603 progname, (running_p ? "" : "and clearing "));
605 raise_window (si, True, True, running_p);
612 reset_watchdog_timer (saver_info *si, Bool on_p)
614 saver_preferences *p = &si->prefs;
618 XtRemoveTimeOut (si->watchdog_id);
622 if (on_p && p->watchdog_timeout)
624 si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
625 watchdog_timer, (XtPointer) si);
629 printf ("%s: restarting watchdog_timer (%ld, %ld)\n",
630 progname, p->watchdog_timeout, si->watchdog_id);