1 /* xscreensaver, Copyright (c) 1991-1998 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 /* ========================================================================
13 * First we wait until the keyboard and mouse become idle for the specified
14 * amount of time. We do this in one of three different ways: periodically
15 * checking with the XIdle server extension; selecting key and mouse events
16 * on (nearly) all windows; or by waiting for the MIT-SCREEN-SAVER extension
17 * to send us a "you are idle" event.
19 * Then, we map a full screen black window (or, in the case of the
20 * MIT-SCREEN-SAVER extension, use the one it gave us.)
22 * We place a __SWM_VROOT property on this window, so that newly-started
23 * clients will think that this window is a "virtual root" window.
25 * If there is an existing "virtual root" window (one that already had
26 * an __SWM_VROOT property) then we remove that property from that window.
27 * Otherwise, clients would see that window (the real virtual root) instead
28 * of ours (the impostor.)
30 * Then we pick a random program to run, and start it. Two assumptions
31 * are made about this program: that it has been specified with whatever
32 * command-line options are necessary to make it run on the root window;
33 * and that it has been compiled with vroot.h, so that it is able to find
34 * the root window when a virtual-root window manager (or this program) is
37 * Then, we wait for keyboard or mouse events to be generated on the window.
38 * When they are, we kill the inferior process, unmap the window, and restore
39 * the __SWM_VROOT property to the real virtual root window if there was one.
41 * While we are waiting, we also set up timers so that, after a certain
42 * amount of time has passed, we can start a different screenhack. We do
43 * this by killing the running child process with SIGTERM, and then starting
44 * a new one in the same way.
46 * If there was a real virtual root, meaning that we removed the __SWM_VROOT
47 * property from it, meaning we must (absolutely must) restore it before we
48 * exit, then we set up signal handlers for most signals (SIGINT, SIGTERM,
49 * etc.) that do this. Most Xlib and Xt routines are not reentrant, so it
50 * is not generally safe to call them from signal handlers; however, this
51 * program spends most of its time waiting, so the window of opportunity
52 * when code could be called reentrantly is fairly small; and also, the worst
53 * that could happen is that the call would fail. If we've gotten one of
54 * these signals, then we're on our way out anyway. If we didn't restore the
55 * __SWM_VROOT property, that would be very bad, so it's worth a shot. Note
56 * that this means that, if you're using a virtual-root window manager, you
57 * can really fuck up the world by killing this process with "kill -9".
59 * This program accepts ClientMessages of type SCREENSAVER; these messages
60 * may contain the atom ACTIVATE or DEACTIVATE, meaning to turn the
61 * screensaver on or off now, regardless of the idleness of the user,
62 * and a few other things. The included "xscreensaver_command" program
63 * sends these messsages.
65 * If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER
66 * extensions, then we do the XAutoLock trick: notice every window that
67 * gets created, and wait 30 seconds or so until its creating process has
68 * settled down, and then select KeyPress events on those windows which
69 * already select for KeyPress events. It's important that we not select
70 * KeyPress on windows which don't select them, because that would
71 * interfere with event propagation. This will break if any program
72 * changes its event mask to contain KeyRelease or PointerMotion more than
73 * 30 seconds after creating the window, but that's probably pretty rare.
75 * The reason that we can't select KeyPresses on windows that don't have
76 * them already is that, when dispatching a KeyPress event, X finds the
77 * lowest (leafmost) window in the hierarchy on which *any* client selects
78 * for KeyPress, and sends the event to that window. This means that if a
79 * client had a window with subwindows, and expected to receive KeyPress
80 * events on the parent window instead of the subwindows, then that client
81 * would malfunction if some other client selected KeyPress events on the
82 * subwindows. It is an incredible misdesign that one client can make
83 * another client malfunction in this way.
85 * To detect mouse motion, we periodically wake up and poll the mouse
86 * position and button/modifier state, and notice when something has
87 * changed. We make this check every five seconds by default, and since the
88 * screensaver timeout has a granularity of one minute, this makes the
89 * chance of a false positive very small. We could detect mouse motion in
90 * the same way as keyboard activity, but that would suffer from the same
91 * "client changing event mask" problem that the KeyPress events hack does.
92 * I think polling is more reliable.
94 * None of this crap happens if we're using one of the extensions, so install
95 * one of them if the description above sounds just too flaky to live. It
96 * is, but those are your choices.
98 * A third idle-detection option could be implemented (but is not): when
99 * running on the console display ($DISPLAY is `localhost`:0) and we're on a
100 * machine where /dev/tty and /dev/mouse have reasonable last-modification
101 * times, we could just stat() those. But the incremental benefit of
102 * implementing this is really small, so forget I said anything.
105 * - Have a second terminal handy.
106 * - Be careful where you set your breakpoints, you don't want this to
107 * stop under the debugger with the keyboard grabbed or the blackout
109 * - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
110 * to keep your emacs window alive even when xscreensaver has grabbed.
111 * - Go read the code related to `debug_p'.
112 * - You probably can't set breakpoints in functions that are called on
113 * the other side of a call to fork() -- if your clients are dying
114 * with signal 5, Trace/BPT Trap, you're losing in this way.
115 * - If you aren't using a server extension, don't leave this stopped
116 * under the debugger for very long, or the X input buffer will get
117 * huge because of the keypress events it's selecting for. This can
118 * make your X server wedge with "no more input buffers."
120 * ======================================================================== */
128 #include <X11/Xlib.h>
129 #include <X11/Xatom.h>
130 #include <X11/Intrinsic.h>
131 #include <X11/StringDefs.h>
132 #include <X11/Shell.h>
136 # include <X11/Xmu/Error.h>
138 # include <Xmu/Error.h>
140 #else /* !HAVE_XMU */
142 #endif /* !HAVE_XMU */
144 #ifdef HAVE_XIDLE_EXTENSION
145 #include <X11/extensions/xidle.h>
146 #endif /* HAVE_XIDLE_EXTENSION */
148 #include "xscreensaver.h"
150 #include "yarandom.h"
151 #include "resources.h"
154 saver_info *global_si_kludge = 0; /* I hate C so much... */
161 static Atom XA_SCREENSAVER_RESPONSE;
162 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
163 static Atom XA_EXIT, XA_RESTART, XA_LOCK, XA_SELECT;
164 Atom XA_DEMO, XA_PREFS;
167 static XrmOptionDescRec options [] = {
168 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
169 { "-cycle", ".cycle", XrmoptionSepArg, 0 },
170 { "-lock-mode", ".lock", XrmoptionNoArg, "on" },
171 { "-no-lock-mode", ".lock", XrmoptionNoArg, "off" },
172 { "-lock-timeout", ".lockTimeout", XrmoptionSepArg, 0 },
173 { "-lock-vts", ".lockVTs", XrmoptionNoArg, "on" },
174 { "-no-lock-vts", ".lockVTs", XrmoptionNoArg, "off" },
175 { "-visual", ".visualID", XrmoptionSepArg, 0 },
176 { "-install", ".installColormap", XrmoptionNoArg, "on" },
177 { "-no-install", ".installColormap", XrmoptionNoArg, "off" },
178 { "-verbose", ".verbose", XrmoptionNoArg, "on" },
179 { "-silent", ".verbose", XrmoptionNoArg, "off" },
180 { "-timestamp", ".timestamp", XrmoptionNoArg, "on" },
181 { "-capture-stderr", ".captureStderr", XrmoptionNoArg, "on" },
182 { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" },
183 { "-xidle-extension", ".xidleExtension", XrmoptionNoArg, "on" },
184 { "-no-xidle-extension", ".xidleExtension", XrmoptionNoArg, "off" },
185 { "-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "on" },
186 { "-no-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "off" },
187 { "-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "on" },
188 { "-no-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "off" },
189 { "-splash", ".splash", XrmoptionNoArg, "on" },
190 { "-no-splash", ".splash", XrmoptionNoArg, "off" },
191 { "-nosplash", ".splash", XrmoptionNoArg, "off" },
192 { "-idelay", ".initialDelay", XrmoptionSepArg, 0 },
193 { "-nice", ".nice", XrmoptionSepArg, 0 },
195 /* Actually these are built in to Xt, but just to be sure... */
196 { "-synchronous", ".synchronous", XrmoptionNoArg, "on" },
197 { "-xrm", NULL, XrmoptionResArg, NULL }
200 static char *defaults[] = {
201 #include "XScreenSaver_ad.h"
206 ERROR! You must not include vroot.h in this file.
210 do_help (saver_info *si)
215 xscreensaver %s, copyright (c) 1991-1998 by Jamie Zawinski <jwz@jwz.org>\n\
216 The standard Xt command-line options are accepted; other options include:\n\
218 -timeout <minutes> When the screensaver should activate.\n\
219 -cycle <minutes> How long to let each hack run before switching.\n\
220 -lock-mode Require a password before deactivating.\n\
221 -lock-timeout <minutes> Grace period before locking; default 0.\n\
222 -visual <id-or-class> Which X visual to run on.\n\
223 -install Install a private colormap.\n\
225 -no-splash Don't display a splash-screen at startup.\n\
226 -help This message.\n\
228 See the manual for other options and X resources.\n\
230 The `xscreensaver' program should be left running in the background.\n\
231 Use the `xscreensaver-command' program to manipulate a running xscreensaver.\n\
233 The `*programs' resource controls which graphics demos will be launched by\n\
234 the screensaver. See `man xscreensaver' or the web page for more details.\n\
236 Just getting started? Try this:\n\
239 xscreensaver-command -demo\n\
241 For updates, check http://www.jwz.org/xscreensaver/\n\
253 time_t now = time ((time_t *) 0);
254 char *str = (char *) ctime (&now);
255 char *nl = (char *) strchr (str, '\n');
256 if (nl) *nl = 0; /* take off that dang newline */
260 static Bool blurb_timestamp_p = False; /* kludge */
265 if (!blurb_timestamp_p)
269 static char buf[255];
270 char *ct = timestring();
271 int n = strlen(progname);
273 strncpy(buf, progname, n);
276 strncpy(buf+n, ct+11, 8);
277 strcpy(buf+n+9, ": ");
284 saver_ehandler (Display *dpy, XErrorEvent *error)
286 saver_info *si = global_si_kludge; /* I hate C so much... */
288 fprintf (real_stderr, "\n"
289 "#######################################"
290 "#######################################\n\n"
291 "%s: X Error! PLEASE REPORT THIS BUG.\n\n"
292 "#######################################"
293 "#######################################\n\n",
295 if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
297 fprintf (real_stderr, "\n");
298 if (si->prefs.xsync_p)
300 saver_exit (si, -1, "because of synchronous X Error");
305 "%s: to dump a core file, re-run with `-sync'.\n"
306 "%s: see http://www.jwz.org/xscreensaver/bugs.html\n"
307 "\t\tfor bug reporting information.\n\n",
309 saver_exit (si, -1, 0);
313 fprintf (real_stderr, " (nonfatal.)\n");
318 /* The zillions of initializations.
321 static void get_screenhacks (saver_info *si);
325 /* Set progname, version, etc. This is done very early.
328 set_version_string (saver_info *si, int *argc, char **argv)
330 progclass = "XScreenSaver";
332 /* progname is reset later, after we connect to X. */
333 progname = strrchr(argv[0], '/');
334 if (progname) progname++;
335 else progname = argv[0];
337 if (strlen(progname) > 100) /* keep it short. */
340 /* The X resource database blows up if argv[0] has a "." in it. */
343 while ((s = strchr (s, '.')))
347 si->version = (char *) malloc (5);
348 memcpy (si->version, screensaver_id + 17, 4);
353 /* Initializations that potentially take place as a priveleged user:
354 If the xscreensaver executable is setuid root, then these initializations
355 are run as root, before discarding privileges.
358 privileged_initialization (saver_info *si, int *argc, char **argv)
361 si->locking_disabled_p = True;
362 si->nolock_reason = "not compiled with locking support";
363 #else /* !NO_LOCKING */
364 si->locking_disabled_p = False;
365 if (! lock_init (*argc, argv)) /* before hack_uid() for proper permissions */
367 si->locking_disabled_p = True;
368 si->nolock_reason = "error getting password";
370 #endif /* NO_LOCKING */
374 #endif /* NO_SETUID */
378 /* Open the connection to the X server, and intern our Atoms.
381 connect_to_server (saver_info *si, int *argc, char **argv)
383 Widget toplevel_shell;
385 XSetErrorHandler (saver_ehandler);
386 toplevel_shell = XtAppInitialize (&si->app, progclass,
387 options, XtNumber (options),
388 argc, argv, defaults, 0, 0);
390 si->dpy = XtDisplay (toplevel_shell);
391 si->db = XtDatabase (si->dpy);
392 XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
394 if(strlen(progname) > 100) /* keep it short. */
397 db = si->db; /* resources.c needs this */
399 XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
400 XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
401 XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
402 XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
403 XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
404 XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
406 XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
407 XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
408 XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
409 XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
410 XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
411 XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
412 XA_PREV = XInternAtom (si->dpy, "PREV", False);
413 XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
414 XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
415 XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
416 XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
417 XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
419 return toplevel_shell;
423 /* Handle the command-line arguments that were not handled for us by Xt.
424 Issue an error message and exit if there are unknown options.
427 process_command_line (saver_info *si, int *argc, char **argv)
430 for (i = 1; i < *argc; i++)
432 if (!strcmp (argv[i], "-debug"))
433 /* no resource for this one, out of paranoia. */
434 si->prefs.debug_p = True;
436 else if (!strcmp (argv[i], "-initial-demo-mode"))
437 /* This isn't an advertized option; it is used internally to implement
438 the "Reinitialize" button on the Demo Mode window. */
439 si->demo_mode_p = True;
441 else if (!strcmp (argv[i], "-h") ||
442 !strcmp (argv[i], "-help") ||
443 !strcmp (argv[i], "--help"))
448 const char *s = argv[i];
449 fprintf (stderr, "%s: unknown option \"%s\". Try \"-help\".\n",
452 if (s[0] == '-' && s[1] == '-') s++;
453 if (!strcmp (s, "-activate") ||
454 !strcmp (s, "-deactivate") ||
455 !strcmp (s, "-cycle") ||
456 !strcmp (s, "-next") ||
457 !strcmp (s, "-prev") ||
458 !strcmp (s, "-exit") ||
459 !strcmp (s, "-restart") ||
460 !strcmp (s, "-demo") ||
461 !strcmp (s, "-prefs") ||
462 !strcmp (s, "-preferences") ||
463 !strcmp (s, "-lock") ||
464 !strcmp (s, "-version") ||
465 !strcmp (s, "-time"))
467 fprintf (stderr, "\n\
468 However, %s is an option to the `xscreensaver-command' program.\n\
469 The `xscreensaver' program is a daemon that runs in the background.\n\
470 You control a running xscreensaver process by sending it messages\n\
471 with `xscreensaver-command'. See the man pages for details,\n\
472 or check the web page: http://www.jwz.org/xscreensaver/\n\n",
475 /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
476 suggest that explicitly. */
477 if (!strcmp (s, "-lock"))
479 Or perhaps you meant either the \"-lock-mode\" or the\n\
480 \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
488 /* Print out the xscreensaver banner to the tty if applicable;
489 Issue any other warnings that are called for at this point.
492 print_banner (saver_info *si)
494 saver_preferences *p = &si->prefs;
496 /* This resource gets set some time before the others, so that we know
497 whether to print the banner (and so that the banner gets printed before
498 any resource-database-related error messages.)
500 p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
502 /* Ditto, for the locking_disabled_p message. */
503 p->lock_p = get_boolean_resource ("lock", "Boolean");
507 "%s %s, copyright (c) 1991-1998 by Jamie Zawinski <jwz@jwz.org>\n"
509 blurb(), si->version, (int) getpid ());
512 fprintf (stderr, "\n"
513 "%s: Warning: running in DEBUG MODE. Be afraid.\n"
515 "\tNote that in debug mode, the xscreensaver window will only\n"
516 "\tcover the left half of the screen. (The idea is that you\n"
517 "\tcan still see debugging output in a shell, if you position\n"
518 "\tit on the right side of the screen.)\n"
520 "\tDebug mode is NOT SECURE. Do not run with -debug in\n"
521 "\tuntrusted environments.\n"
527 if (!si->uid_message || !*si->uid_message)
528 describe_uids (si, stderr);
531 if (si->orig_uid && *si->orig_uid)
532 fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
533 blurb(), si->orig_uid);
534 fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
538 /* If locking was not able to be initalized for some reason, explain why.
539 (This has to be done after we've read the lock_p resource.)
541 if (p->lock_p && si->locking_disabled_p)
544 fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
546 if (strstr (si->nolock_reason, "passw"))
547 fprintf (stderr, "%s: does xscreensaver need to be setuid? "
548 "consult the manual.\n", blurb());
549 else if (strstr (si->nolock_reason, "running as "))
551 "%s: locking only works when xscreensaver is launched\n"
552 "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
553 "\t See the manual for details.\n",
559 /* Examine all of the display's screens, and populate the `saver_screen_info'
563 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
565 Bool found_any_writable_cells = False;
568 si->nscreens = ScreenCount(si->dpy);
569 si->screens = (saver_screen_info *)
570 calloc(sizeof(saver_screen_info), si->nscreens);
572 si->default_screen = &si->screens[DefaultScreen(si->dpy)];
574 for (i = 0; i < si->nscreens; i++)
576 saver_screen_info *ssi = &si->screens[i];
578 ssi->screen = ScreenOfDisplay (si->dpy, i);
580 /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
581 ssi->default_visual =
582 get_visual_resource (ssi->screen, "visualID", "VisualID", False);
584 ssi->current_visual = ssi->default_visual;
585 ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
587 if (ssi == si->default_screen)
588 /* Since this is the default screen, use the one already created. */
589 ssi->toplevel_shell = toplevel_shell;
591 /* Otherwise, each screen must have its own unmapped root widget. */
592 ssi->toplevel_shell =
593 XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
595 XtNscreen, ssi->screen,
596 XtNvisual, ssi->current_visual,
597 XtNdepth, visual_depth (ssi->screen,
598 ssi->current_visual),
601 if (! found_any_writable_cells)
603 /* Check to see whether fading is ever possible -- if any of the
604 screens on the display has a PseudoColor visual, then fading can
605 work (on at least some screens.) If no screen has a PseudoColor
606 visual, then don't bother ever trying to fade, because it will
607 just cause a delay without causing any visible effect.
609 if (has_writable_cells (ssi->screen, ssi->current_visual) ||
610 get_visual (ssi->screen, "PseudoColor", True, False) ||
611 get_visual (ssi->screen, "GrayScale", True, False))
612 found_any_writable_cells = True;
616 si->fading_possible_p = found_any_writable_cells;
620 /* Populate `saver_preferences' with the contents of the resource database.
621 Note that this may be called multiple times -- it is re-run each time
622 the ~/.xscreensaver file is reloaded.
624 This function can be very noisy, since it issues resource syntax errors
628 get_resources (saver_info *si)
631 saver_preferences *p = &si->prefs;
633 if (si->init_file_date == 0)
634 /* The date will be 0 the first time this is called; and when this is
635 called subsequent times, the file will have already been reloaded. */
638 p->xsync_p = get_boolean_resource ("synchronous", "Synchronous");
640 XSynchronize(si->dpy, True);
642 p->verbose_p = get_boolean_resource ("verbose", "Boolean");
643 p->timestamp_p = get_boolean_resource ("timestamp", "Boolean");
644 p->lock_p = get_boolean_resource ("lock", "Boolean");
645 p->lock_vt_p = get_boolean_resource ("lockVTs", "Boolean");
646 p->fade_p = get_boolean_resource ("fade", "Boolean");
647 p->unfade_p = get_boolean_resource ("unfade", "Boolean");
648 p->fade_seconds = 1000 * get_seconds_resource ("fadeSeconds", "Time");
649 p->fade_ticks = get_integer_resource ("fadeTicks", "Integer");
650 p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
651 p->nice_inferior = get_integer_resource ("nice", "Nice");
653 p->initial_delay = 1000 * get_seconds_resource ("initialDelay", "Time");
654 p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
655 p->timeout = 1000 * get_minutes_resource ("timeout", "Time");
656 p->lock_timeout = 1000 * get_minutes_resource ("lockTimeout", "Time");
657 p->cycle = 1000 * get_minutes_resource ("cycle", "Time");
658 p->passwd_timeout = 1000 * get_seconds_resource ("passwdTimeout", "Time");
659 p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
660 p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
662 p->shell = get_string_resource ("bourneShell", "BourneShell");
664 p->help_url = get_string_resource("helpURL", "URL");
665 p->load_url_command = get_string_resource("loadURL", "LoadURL");
667 if ((s = get_string_resource ("splash", "Boolean")))
668 if (!get_boolean_resource("splash", "Boolean"))
669 p->splash_duration = 0;
672 if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
676 ? "%s: the screen has no PseudoColor or GrayScale visuals.\n"
677 : "%s: no screens have PseudoColor or GrayScale visuals.\n"),
679 fprintf (stderr, "%s: ignoring the request for fading/unfading.\n",
683 /* don't set use_xidle_extension unless it is explicitly specified */
684 if ((s = get_string_resource ("xidleExtension", "Boolean")))
685 p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
687 #ifdef HAVE_XIDLE_EXTENSION /* pick a default */
688 p->use_xidle_extension = True; /* if we have it, use it */
689 #else /* !HAVE_XIDLE_EXTENSION */
690 p->use_xidle_extension = False;
691 #endif /* !HAVE_XIDLE_EXTENSION */
694 /* don't set use_mit_extension unless it is explicitly specified */
695 if ((s = get_string_resource ("mitSaverExtension", "Boolean")))
696 p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
699 #ifdef HAVE_MIT_SAVER_EXTENSION /* pick a default */
700 p->use_mit_saver_extension = False; /* Default false, because it sucks */
701 #else /* !HAVE_MIT_SAVER_EXTENSION */
702 p->use_mit_saver_extension = False;
703 #endif /* !HAVE_MIT_SAVER_EXTENSION */
707 /* don't set use_mit_extension unless it is explicitly specified */
708 if ((s = get_string_resource ("sgiSaverExtension", "Boolean")))
709 p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
712 #ifdef HAVE_SGI_SAVER_EXTENSION /* pick a default */
713 p->use_sgi_saver_extension = True; /* if we have it, use it */
714 #else /* !HAVE_SGI_SAVER_EXTENSION */
715 p->use_sgi_saver_extension = False;
716 #endif /* !HAVE_SGI_SAVER_EXTENSION */
720 /* Throttle the various timeouts to reasonable values.
722 if (p->passwd_timeout == 0) p->passwd_timeout = 30000; /* 30 secs */
723 if (p->timeout < 10000) p->timeout = 10000; /* 10 secs */
724 if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000; /* 2 secs */
725 if (p->pointer_timeout == 0) p->pointer_timeout = 5000; /* 5 secs */
726 if (p->notice_events_timeout == 0)
727 p->notice_events_timeout = 10000; /* 10 secs */
728 if (p->fade_seconds == 0 || p->fade_ticks == 0)
730 if (! p->fade_p) p->unfade_p = False;
732 p->watchdog_timeout = p->cycle;
733 if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000; /* 30 secs */
734 if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /* 1 hr */
736 get_screenhacks (si);
740 XSynchronize(si->dpy, True);
743 p->timestamp_p = True;
744 p->initial_delay = 0;
747 blurb_timestamp_p = p->timestamp_p;
751 /* If any server extensions have been requested, try and initialize them.
752 Issue warnings if requests can't be honored.
755 initialize_server_extensions (saver_info *si)
757 saver_preferences *p = &si->prefs;
759 if (p->use_sgi_saver_extension)
761 #ifdef HAVE_SGI_SAVER_EXTENSION
762 if (! query_sgi_saver_extension (si))
765 "%s: display %s does not support the SGI SCREEN_SAVER extension.\n",
766 blurb(), DisplayString (si->dpy));
767 p->use_sgi_saver_extension = False;
769 else if (p->use_mit_saver_extension)
772 "%s: SGI SCREEN_SAVER extension used instead"
773 " of MIT-SCREEN-SAVER extension.\n",
775 p->use_mit_saver_extension = False;
777 else if (p->use_xidle_extension)
780 "%s: SGI SCREEN_SAVER extension used instead of XIDLE extension.\n",
782 p->use_xidle_extension = False;
784 #else /* !HAVE_MIT_SAVER_EXTENSION */
786 "%s: not compiled with support for the SGI SCREEN_SAVER"
789 p->use_sgi_saver_extension = False;
790 #endif /* !HAVE_SGI_SAVER_EXTENSION */
793 if (p->use_mit_saver_extension)
795 #ifdef HAVE_MIT_SAVER_EXTENSION
796 if (! query_mit_saver_extension (si))
799 "%s: display %s does not support the MIT-SCREEN-SAVER"
801 blurb(), DisplayString (si->dpy));
802 p->use_mit_saver_extension = False;
804 else if (p->use_xidle_extension)
807 "%s: MIT-SCREEN-SAVER extension used instead of XIDLE"
810 p->use_xidle_extension = False;
812 #else /* !HAVE_MIT_SAVER_EXTENSION */
814 "%s: not compiled with support for the MIT-SCREEN-SAVER"
817 p->use_mit_saver_extension = False;
818 #endif /* !HAVE_MIT_SAVER_EXTENSION */
821 if (p->use_xidle_extension)
823 #ifdef HAVE_XIDLE_EXTENSION
824 int first_event, first_error;
825 if (! XidleQueryExtension (si->dpy, &first_event, &first_error))
828 "%s: display %s does not support the XIdle extension.\n",
829 blurb(), DisplayString (si->dpy));
830 p->use_xidle_extension = False;
832 #else /* !HAVE_XIDLE_EXTENSION */
833 fprintf (stderr, "%s: not compiled with support for XIdle.\n", blurb());
834 p->use_xidle_extension = False;
835 #endif /* !HAVE_XIDLE_EXTENSION */
838 if (p->verbose_p && p->use_mit_saver_extension)
839 fprintf (stderr, "%s: using MIT-SCREEN-SAVER server extension.\n",
841 if (p->verbose_p && p->use_sgi_saver_extension)
842 fprintf (stderr, "%s: using SGI SCREEN_SAVER server extension.\n",
844 if (p->verbose_p && p->use_xidle_extension)
845 fprintf (stderr, "%s: using XIdle server extension.\n",
850 /* For the case where we aren't using an server extensions, select user events
851 on all the existing windows, and launch timers to select events on
852 newly-created windows as well.
854 If a server extension is being used, this does nothing.
857 select_events (saver_info *si)
859 saver_preferences *p = &si->prefs;
862 if (p->use_xidle_extension ||
863 p->use_mit_saver_extension ||
864 p->use_sgi_saver_extension)
867 if (p->initial_delay && !si->demo_mode_p)
871 fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
872 (int) p->initial_delay/1000,
873 (p->initial_delay == 1000 ? "" : "s"));
877 usleep (p->initial_delay);
879 fprintf (stderr, " done.\n");
884 fprintf (stderr, "%s: selecting events on extant windows...", blurb());
889 /* Select events on the root windows of every screen. This also selects
890 for window creation events, so that new subwindows will be noticed.
892 for (i = 0; i < si->nscreens; i++)
893 start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen));
896 fprintf (stderr, " done.\n");
902 - wait until the user is idle;
904 - wait until the user is active;
905 - unblank the screen;
910 main_loop (saver_info *si)
912 saver_preferences *p = &si->prefs;
915 if (! si->demo_mode_p)
916 sleep_until_idle (si, True);
918 maybe_reload_init_file (si);
924 #endif /* !NO_DEMO_MODE */
927 fprintf (stderr, "%s: user is idle; waking up at %s.\n", blurb(),
929 maybe_reload_init_file (si);
931 spawn_screenhack (si, True);
933 si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
938 !si->locking_disabled_p &&
939 p->lock_timeout == 0)
942 if (p->lock_p && !si->locked_p)
943 /* locked_p might be true already because of ClientMessage */
944 si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
947 #endif /* !NO_LOCKING */
951 sleep_until_idle (si, False); /* until not idle */
952 maybe_reload_init_file (si);
958 if (si->locking_disabled_p) abort ();
959 si->dbox_up_p = True;
962 saver_screen_info *ssi = si->default_screen;
963 suspend_screenhack (si, True);
964 XUndefineCursor (si->dpy, ssi->screensaver_window);
966 fprintf (stderr, "%s: prompting for password.\n", blurb());
968 if (p->verbose_p && val == False)
969 fprintf (stderr, "%s: password incorrect!\n", blurb());
970 si->dbox_up_p = False;
971 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
972 suspend_screenhack (si, False);
977 si->locked_p = False;
979 #endif /* !NO_LOCKING */
982 fprintf (stderr, "%s: user is active at %s.\n",
983 blurb(), timestring ());
985 /* Let's kill it before unblanking, to get it to stop drawing as
986 soon as possible... */
987 kill_screenhack (si);
989 si->selection_mode = 0;
993 XtRemoveTimeOut (si->cycle_id);
1000 XtRemoveTimeOut (si->lock_id);
1003 #endif /* !NO_LOCKING */
1006 fprintf (stderr, "%s: going to sleep.\n", blurb());
1013 main (int argc, char **argv)
1017 saver_info *si = &the_si;
1020 memset(si, 0, sizeof(*si));
1021 global_si_kludge = si; /* I hate C so much... */
1023 srandom ((int) time ((time_t *) 0));
1025 set_version_string (si, &argc, argv);
1026 save_argv (argc, argv);
1027 privileged_initialization (si, &argc, argv);
1028 hack_environment (si);
1030 shell = connect_to_server (si, &argc, argv);
1031 process_command_line (si, &argc, argv);
1033 initialize_per_screen_info (si, shell);
1036 for (i = 0; i < si->nscreens; i++)
1037 if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1040 initialize_server_extensions (si);
1041 initialize_screensaver_window (si);
1044 disable_builtin_screensaver (si, True);
1045 initialize_stderr (si);
1047 if (!si->demo_mode_p)
1048 make_splash_dialog (si);
1050 main_loop (si); /* doesn't return */
1055 /* Parsing the programs resource.
1059 reformat_hack (const char *hack)
1062 const char *in = hack;
1064 char *h2 = (char *) malloc(strlen(in) + indent + 2);
1067 while (isspace(*in)) in++; /* skip whitespace */
1068 while (*in && !isspace(*in) && *in != ':')
1069 *out++ = *in++; /* snarf first token */
1070 while (isspace(*in)) in++; /* skip whitespace */
1073 *out++ = *in++; /* copy colon */
1077 out = h2; /* reset to beginning */
1082 while (isspace(*in)) in++; /* skip whitespace */
1083 for (i = strlen(h2); i < indent; i++) /* indent */
1086 /* copy the rest of the line. */
1089 /* shrink all whitespace to one space, for the benefit of the "demo"
1090 mode display. We only do this when we can easily tell that the
1091 whitespace is not significant (no shell metachars).
1095 case '\'': case '"': case '`': case '\\':
1097 /* Metachars are scary. Copy the rest of the line unchanged. */
1102 case ' ': case '\t':
1104 while (*in == ' ' || *in == '\t')
1116 /* strip trailing whitespace. */
1118 while (out > h2 && (*out == ' ' || *out == '\t' || *out == '\n'))
1126 get_screenhacks (saver_info *si)
1128 saver_preferences *p = &si->prefs;
1135 d = get_string_resource ("monoPrograms", "MonoPrograms");
1136 if (d && !*d) { free(d); d = 0; }
1138 d = get_string_resource ("colorPrograms", "ColorPrograms");
1139 if (d && !*d) { free(d); d = 0; }
1144 "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1145 see the manual for details.\n", blurb());
1149 d = get_string_resource ("programs", "Programs");
1153 for (i = 0; i < p->screenhacks_count; i++)
1154 if (p->screenhacks[i])
1155 free (p->screenhacks[i]);
1156 free(p->screenhacks);
1162 p->screenhacks_count = 0;
1170 /* Count up the number of newlines (which will be equal to or larger than
1171 the number of hacks.)
1174 for (i = 0; d[i]; i++)
1179 p->screenhacks = (char **) calloc (sizeof (char *), i+1);
1181 /* Iterate over the lines in `d' (the string with newlines)
1182 and make new strings to stuff into the `screenhacks' array.
1184 p->screenhacks_count = 0;
1185 while (start < size)
1187 /* skip forward over whitespace. */
1188 while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1191 /* skip forward to newline or end of string. */
1193 while (d[end] != 0 && d[end] != '\n')
1196 /* null terminate. */
1199 p->screenhacks[p->screenhacks_count++] = reformat_hack (d + start);
1200 if (p->screenhacks_count >= i)
1206 if (p->screenhacks_count == 0)
1208 free (p->screenhacks);
1215 /* Processing ClientMessage events.
1219 clientmessage_response (saver_info *si, Window w, Bool error,
1220 const char *stderr_msg,
1221 const char *protocol_msg)
1225 saver_preferences *p = &si->prefs;
1226 if (error || p->verbose_p)
1227 fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1229 L = strlen(protocol_msg);
1230 proto = (char *) malloc (L + 2);
1231 proto[0] = (error ? '-' : '+');
1232 strcpy (proto+1, protocol_msg);
1235 XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1236 PropModeReplace, proto, L);
1237 XSync (si->dpy, False);
1242 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1244 saver_preferences *p = &si->prefs;
1246 Window window = event->xclient.window;
1248 /* Preferences might affect our handling of client messages. */
1249 maybe_reload_init_file (si);
1251 if (event->xclient.message_type != XA_SCREENSAVER)
1254 str = XGetAtomName (si->dpy, event->xclient.message_type);
1255 fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1256 blurb(), (str ? str : "(null)"));
1257 if (str) XFree (str);
1260 if (event->xclient.format != 32)
1262 fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1263 blurb(), event->xclient.format);
1267 type = event->xclient.data.l[0];
1268 if (type == XA_ACTIVATE)
1272 clientmessage_response(si, window, False,
1273 "ACTIVATE ClientMessage received.",
1275 si->selection_mode = 0;
1276 if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1278 XForceScreenSaver (si->dpy, ScreenSaverActive);
1286 clientmessage_response(si, window, True,
1287 "ClientMessage ACTIVATE received while already active.",
1290 else if (type == XA_DEACTIVATE)
1294 clientmessage_response(si, window, False,
1295 "DEACTIVATE ClientMessage received.",
1297 if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1299 XForceScreenSaver (si->dpy, ScreenSaverReset);
1307 clientmessage_response(si, window, True,
1308 "ClientMessage DEACTIVATE received while inactive.",
1311 else if (type == XA_CYCLE)
1315 clientmessage_response(si, window, False,
1316 "CYCLE ClientMessage received.",
1318 si->selection_mode = 0; /* 0 means randomize when its time. */
1320 XtRemoveTimeOut (si->cycle_id);
1322 cycle_timer ((XtPointer) si, 0);
1325 clientmessage_response(si, window, True,
1326 "ClientMessage CYCLE received while inactive.",
1329 else if (type == XA_NEXT || type == XA_PREV)
1331 clientmessage_response(si, window, False,
1333 ? "NEXT ClientMessage received."
1334 : "PREV ClientMessage received."),
1336 si->selection_mode = (type == XA_NEXT ? -1 : -2);
1341 XtRemoveTimeOut (si->cycle_id);
1343 cycle_timer ((XtPointer) si, 0);
1348 else if (type == XA_SELECT)
1352 long which = event->xclient.data.l[1];
1354 sprintf (buf, "SELECT %ld ClientMessage received.", which);
1355 sprintf (buf2, "activating (%ld).", which);
1356 clientmessage_response (si, window, False, buf, buf2);
1358 if (which < 0) which = 0; /* 0 == "random" */
1359 si->selection_mode = which;
1364 XtRemoveTimeOut (si->cycle_id);
1366 cycle_timer ((XtPointer) si, 0);
1371 else if (type == XA_EXIT)
1373 /* Ignore EXIT message if the screen is locked. */
1374 if (until_idle_p || !si->locked_p)
1376 clientmessage_response (si, window, False,
1377 "EXIT ClientMessage received.",
1381 unblank_screen (si);
1382 kill_screenhack (si);
1383 XSync (si->dpy, False);
1385 saver_exit (si, 0, 0);
1388 clientmessage_response (si, window, True,
1389 "EXIT ClientMessage received while locked.",
1390 "screen is locked.");
1392 else if (type == XA_RESTART)
1394 /* The RESTART message works whether the screensaver is active or not,
1395 unless the screen is locked, in which case it doesn't work.
1397 if (until_idle_p || !si->locked_p)
1399 clientmessage_response (si, window, False,
1400 "RESTART ClientMessage received.",
1404 unblank_screen (si);
1405 kill_screenhack (si);
1406 XSync (si->dpy, False);
1409 /* make sure error message shows up before exit. */
1410 if (real_stderr && stderr != real_stderr)
1411 dup2 (fileno(real_stderr), fileno(stderr));
1413 restart_process (si);
1414 exit (1); /* shouldn't get here; but if restarting didn't work,
1415 make this command be the same as EXIT. */
1418 clientmessage_response (si, window, True,
1419 "RESTART ClientMessage received while locked.",
1420 "screen is locked.");
1422 else if (type == XA_DEMO)
1425 clientmessage_response (si, window, True,
1426 "not compiled with support for DEMO mode.",
1427 "demo mode not enabled.");
1428 #else /* !NO_DEMO_MODE */
1431 clientmessage_response (si, window, False,
1432 "DEMO ClientMessage received.",
1434 si->demo_mode_p = True;
1437 clientmessage_response (si, window, True,
1438 "DEMO ClientMessage received while active.",
1440 #endif /* !NO_DEMO_MODE */
1442 else if (type == XA_PREFS)
1445 clientmessage_response (si, window, True,
1446 "not compiled with support for DEMO mode.",
1447 "preferences mode not enabled.");
1448 #else /* !NO_DEMO_MODE */
1451 clientmessage_response (si, window, False,
1452 "PREFS ClientMessage received.",
1453 "preferences mode.");
1454 si->demo_mode_p = (Bool) 2; /* kludge, so sue me. */
1457 clientmessage_response (si, window, True,
1458 "PREFS ClientMessage received while active.",
1460 #endif /* !NO_DEMO_MODE */
1462 else if (type == XA_LOCK)
1465 clientmessage_response (si, window, True,
1466 "not compiled with support for locking.",
1467 "locking not enabled.");
1468 #else /* !NO_LOCKING */
1469 if (si->locking_disabled_p)
1470 clientmessage_response (si, window, True,
1471 "LOCK ClientMessage received, but locking is disabled.",
1472 "locking not enabled.");
1473 else if (si->locked_p)
1474 clientmessage_response (si, window, True,
1475 "LOCK ClientMessage received while already locked.",
1480 char *response = (until_idle_p
1481 ? "activating and locking."
1483 si->locked_p = True;
1484 si->selection_mode = 0;
1485 sprintf (buf, "LOCK ClientMessage received; %s", response);
1486 clientmessage_response (si, window, False, buf, response);
1488 if (si->lock_id) /* we're doing it now, so lose the timeout */
1490 XtRemoveTimeOut (si->lock_id);
1496 if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1498 XForceScreenSaver (si->dpy, ScreenSaverActive);
1507 #endif /* !NO_LOCKING */
1513 str = (type ? XGetAtomName(si->dpy, type) : 0);
1517 if (strlen (str) > 80)
1518 strcpy (str+70, "...");
1519 sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1526 "unrecognised screensaver ClientMessage 0x%x received.",
1527 (unsigned int) event->xclient.data.l[0]);
1530 clientmessage_response (si, window, True, buf, buf);