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@netscape.com>
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 printf ("%s: selected KeyPress on 0x%lX\n", progname,
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 printf ("%s: dbox up; delaying hack change.\n", progname);
188 how_long = 30000; /* 30 secs */
193 printf ("%s: changing graphics hacks.\n", progname);
194 kill_screenhack (si);
195 spawn_screenhack (si, False);
197 si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
202 printf ("%s: starting cycle_timer (%ld, %ld)\n",
203 progname, how_long, si->cycle_id);
204 #endif /* DEBUG_TIMERS */
209 activate_lock_timer (XtPointer closure, XtIntervalId *id)
211 saver_info *si = (saver_info *) closure;
212 saver_preferences *p = &si->prefs;
215 printf ("%s: timed out; activating lock\n", progname);
218 #ifdef HAVE_XHPDISABLERESET
221 XHPDisableReset (si->dpy); /* turn off C-Sh-Reset */
228 /* Call this when user activity (or "simulated" activity) has been noticed.
231 reset_timers (saver_info *si)
233 saver_preferences *p = &si->prefs;
234 if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
239 printf ("%s: killing idle_timer (%ld, %ld)\n",
240 progname, p->timeout, si->timer_id);
241 #endif /* DEBUG_TIMERS */
243 XtRemoveTimeOut (si->timer_id);
244 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
246 if (si->cycle_id) abort ();
250 printf ("%s: restarting idle_timer (%ld, %ld)\n",
251 progname, p->timeout, si->timer_id);
252 #endif /* DEBUG_TIMERS */
254 si->last_activity_time = time ((time_t *) 0);
257 /* When we aren't using a server extension, this timer is used to periodically
258 wake up and poll the mouse position, which is possibly more reliable than
259 selecting motion events on every window.
262 check_pointer_timer (XtPointer closure, XtIntervalId *id)
265 saver_info *si = (saver_info *) closure;
266 saver_preferences *p = &si->prefs;
267 Bool active_p = False;
269 if (p->use_xidle_extension ||
270 p->use_mit_saver_extension ||
271 p->use_sgi_saver_extension)
274 si->check_pointer_timer_id =
275 XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
278 for (i = 0; i < si->nscreens; i++)
280 saver_screen_info *ssi = &si->screens[i];
282 int root_x, root_y, x, y;
285 XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
286 &root_x, &root_y, &x, &y, &mask);
288 if (root_x == ssi->poll_mouse_last_root_x &&
289 root_y == ssi->poll_mouse_last_root_y &&
290 child == ssi->poll_mouse_last_child &&
291 mask == ssi->poll_mouse_last_mask)
298 if (root_x == ssi->poll_mouse_last_root_x &&
299 root_y == ssi->poll_mouse_last_root_y &&
300 child == ssi->poll_mouse_last_child)
301 printf ("%s: modifiers changed at %s on screen %d.\n",
302 progname, timestring(), i);
304 printf ("%s: pointer moved at %s on screen %d.\n",
305 progname, timestring(), i);
306 #endif /* DEBUG_TIMERS */
308 si->last_activity_screen = ssi;
309 ssi->poll_mouse_last_root_x = root_x;
310 ssi->poll_mouse_last_root_y = root_y;
311 ssi->poll_mouse_last_child = child;
312 ssi->poll_mouse_last_mask = mask;
321 sleep_until_idle (saver_info *si, Bool until_idle_p)
323 saver_preferences *p = &si->prefs;
328 if (!p->use_mit_saver_extension && !p->use_sgi_saver_extension)
330 /* Wake up periodically to ask the server if we are idle. */
331 si->timer_id = XtAppAddTimeOut (si->app, p->timeout, idle_timer,
336 printf ("%s: starting idle_timer (%ld, %ld)\n",
337 progname, p->timeout, si->timer_id);
338 #endif /* DEBUG_TIMERS */
341 if (!p->use_xidle_extension &&
342 !p->use_mit_saver_extension &&
343 !p->use_sgi_saver_extension)
344 /* start polling the mouse position */
345 check_pointer_timer ((XtPointer) si, 0);
350 XtAppNextEvent (si->app, &event);
352 switch (event.xany.type) {
353 case 0: /* our synthetic "timeout" event has been signalled */
357 #ifdef HAVE_XIDLE_EXTENSION
358 if (p->use_xidle_extension)
360 if (! XGetIdleTime (si->dpy, &idle))
362 fprintf (stderr, "%s: XGetIdleTime() failed.\n", progname);
367 #endif /* HAVE_XIDLE_EXTENSION */
368 #ifdef HAVE_MIT_SAVER_EXTENSION
369 if (p->use_mit_saver_extension)
371 /* We don't need to do anything in this case - the synthetic
372 event isn't necessary, as we get sent specific events
377 #endif /* HAVE_MIT_SAVER_EXTENSION */
378 #ifdef HAVE_SGI_SAVER_EXTENSION
379 if (p->use_sgi_saver_extension)
381 /* We don't need to do anything in this case - the synthetic
382 event isn't necessary, as we get sent specific events
387 #endif /* HAVE_SGI_SAVER_EXTENSION */
389 idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
392 if (idle >= p->timeout)
394 else if (!p->use_mit_saver_extension &&
395 !p->use_sgi_saver_extension)
397 si->timer_id = XtAppAddTimeOut (si->app, p->timeout - idle,
398 idle_timer, (XtPointer) si);
401 printf ("%s: starting idle_timer (%ld, %ld)\n",
402 progname, p->timeout - idle, si->timer_id);
403 #endif /* DEBUG_TIMERS */
409 if (handle_clientmessage (si, &event, until_idle_p))
414 if (!p->use_xidle_extension &&
415 !p->use_mit_saver_extension &&
416 !p->use_sgi_saver_extension)
418 start_notice_events_timer (si, event.xcreatewindow.window);
421 printf ("%s: starting notice_events_timer for 0x%X (%lu)\n",
423 (unsigned int) event.xcreatewindow.window,
424 p->notice_events_timeout);
425 #endif /* DEBUG_TIMERS */
438 if (event.xany.type == MotionNotify)
439 printf ("%s: MotionNotify at %s\n", progname, timestring ());
440 else if (event.xany.type == KeyPress)
441 printf ("%s: KeyPress seen on 0x%X at %s\n", progname,
442 (unsigned int) event.xkey.window, timestring ());
444 #endif /* DEBUG_TIMERS */
446 /* We got a user event */
455 #ifdef HAVE_MIT_SAVER_EXTENSION
456 if (event.type == si->mit_saver_ext_event_number)
458 XScreenSaverNotifyEvent *sevent =
459 (XScreenSaverNotifyEvent *) &event;
460 if (sevent->state == ScreenSaverOn)
464 printf ("%s: ScreenSaverOn event received at %s\n",
465 progname, timestring ());
466 # endif /* DEBUG_TIMERS */
468 /* Get the "real" server window(s) out of the way as soon
471 for (i = 0; i < si->nscreens; i++)
473 saver_screen_info *ssi = &si->screens[i];
474 if (ssi->server_mit_saver_window &&
475 window_exists_p (si->dpy,
476 ssi->server_mit_saver_window))
477 XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
480 if (sevent->kind != ScreenSaverExternal)
484 "%s: ScreenSaverOn event wasn't of type External!\n",
486 # endif /* DEBUG_TIMERS */
492 else if (sevent->state == ScreenSaverOff)
496 printf ("%s: ScreenSaverOff event received at %s\n",
497 progname, timestring ());
498 # endif /* DEBUG_TIMERS */
503 else if (p->verbose_p)
504 printf ("%s: unknown MIT-SCREEN-SAVER event received at %s\n",
505 progname, timestring ());
506 # endif /* DEBUG_TIMERS */
510 #endif /* HAVE_MIT_SAVER_EXTENSION */
513 #ifdef HAVE_SGI_SAVER_EXTENSION
514 if (event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
518 printf ("%s: ScreenSaverStart event received at %s\n",
519 progname, timestring ());
520 # endif /* DEBUG_TIMERS */
525 else if (event.type == (si->sgi_saver_ext_event_number +
530 printf ("%s: ScreenSaverEnd event received at %s\n",
531 progname, timestring ());
532 # endif /* DEBUG_TIMERS */
537 #endif /* HAVE_SGI_SAVER_EXTENSION */
539 XtDispatchEvent (&event);
545 /* If there's a user event on the queue, swallow it.
546 If we're using a server extension, and the user becomes active, we
547 get the extension event before the user event -- so the keypress or
548 motion or whatever is still on the queue. This makes "unfade" not
549 work, because it sees that event, and bugs out. (This problem
550 doesn't exhibit itself without an extension, because in that case,
551 there's only one event generated by user activity, not two.)
553 XCheckMaskEvent (si->dpy, (KeyPressMask|ButtonPressMask|PointerMotionMask),
557 if (si->check_pointer_timer_id)
559 XtRemoveTimeOut (si->check_pointer_timer_id);
560 si->check_pointer_timer_id = 0;
564 XtRemoveTimeOut (si->timer_id);
568 if (until_idle_p && si->cycle_id)
575 /* This timer goes off every few minutes, whether the user is idle or not,
576 to try and clean up anything that has gone wrong.
578 It calls disable_builtin_screensaver() so that if xset has been used,
579 or some other program (like xlock) has messed with the XSetScreenSaver()
580 settings, they will be set back to sensible values (if a server extension
581 is in use, messing with xlock can cause xscreensaver to never get a wakeup
582 event, and could cause monitor power-saving to occur, and all manner of
585 If the screen is currently blanked, it raises the window, in case some
586 other window has been mapped on top of it.
588 If the screen is currently blanked, and there is no hack running, it
589 clears the window, in case there is an error message printed on it (we
590 don't want the error message to burn in.)
594 watchdog_timer (XtPointer closure, XtIntervalId *id)
596 saver_info *si = (saver_info *) closure;
597 if (!si->demo_mode_p)
599 disable_builtin_screensaver (si, False);
600 if (si->screen_blanked_p)
602 Bool running_p = screenhack_running_p(si);
605 if (si->prefs.verbose_p)
606 printf ("%s: watchdog timer raising %sscreen.\n",
607 progname, (running_p ? "" : "and clearing "));
608 #endif /* DEBUG_TIMERS */
610 raise_window (si, True, True, running_p);
617 reset_watchdog_timer (saver_info *si, Bool on_p)
619 saver_preferences *p = &si->prefs;
623 XtRemoveTimeOut (si->watchdog_id);
627 if (on_p && p->watchdog_timeout)
629 si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
630 watchdog_timer, (XtPointer) si);
634 printf ("%s: restarting watchdog_timer (%ld, %ld)\n",
635 progname, p->watchdog_timeout, si->watchdog_id);
636 #endif /* DEBUG_TIMERS */