1 /* xscreensaver, Copyright (c) 1991-1999 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 such programs do not seem to
74 * occur in nature (I've never seen it happen in all these years.)
76 * The reason that we can't select KeyPresses on windows that don't have
77 * them already is that, when dispatching a KeyPress event, X finds the
78 * lowest (leafmost) window in the hierarchy on which *any* client selects
79 * for KeyPress, and sends the event to that window. This means that if a
80 * client had a window with subwindows, and expected to receive KeyPress
81 * events on the parent window instead of the subwindows, then that client
82 * would malfunction if some other client selected KeyPress events on the
83 * subwindows. It is an incredible misdesign that one client can make
84 * another client malfunction in this way.
86 * To detect mouse motion, we periodically wake up and poll the mouse
87 * position and button/modifier state, and notice when something has
88 * changed. We make this check every five seconds by default, and since the
89 * screensaver timeout has a granularity of one minute, this makes the
90 * chance of a false positive very small. We could detect mouse motion in
91 * the same way as keyboard activity, but that would suffer from the same
92 * "client changing event mask" problem that the KeyPress events hack does.
93 * I think polling is more reliable.
95 * None of this crap happens if we're using one of the extensions, so install
96 * one of them if the description above sounds just too flaky to live. It
97 * is, but those are your choices.
99 * A third idle-detection option could be implemented (but is not): when
100 * running on the console display ($DISPLAY is `localhost`:0) and we're on a
101 * machine where /dev/tty and /dev/mouse have reasonable last-modification
102 * times, we could just stat() those. But the incremental benefit of
103 * implementing this is really small, so forget I said anything.
106 * - Have a second terminal handy.
107 * - Be careful where you set your breakpoints, you don't want this to
108 * stop under the debugger with the keyboard grabbed or the blackout
110 * - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
111 * to keep your emacs window alive even when xscreensaver has grabbed.
112 * - Go read the code related to `debug_p'.
113 * - You probably can't set breakpoints in functions that are called on
114 * the other side of a call to fork() -- if your subprocesses are
115 * dying with signal 5, Trace/BPT Trap, you're losing in this way.
116 * - If you aren't using a server extension, don't leave this stopped
117 * under the debugger for very long, or the X input buffer will get
118 * huge because of the keypress events it's selecting for. This can
119 * make your X server wedge with "no more input buffers."
121 * ======================================================================== */
129 #include <X11/Xlib.h>
130 #include <X11/Xatom.h>
131 #include <X11/Intrinsic.h>
132 #include <X11/StringDefs.h>
133 #include <X11/Shell.h>
135 #include <netdb.h> /* for gethostbyname() */
138 # include <X11/Xmu/Error.h>
140 # include <Xmu/Error.h>
142 #else /* !HAVE_XMU */
144 #endif /* !HAVE_XMU */
146 #ifdef HAVE_XIDLE_EXTENSION
147 # include <X11/extensions/xidle.h>
148 #endif /* HAVE_XIDLE_EXTENSION */
150 #include "xscreensaver.h"
152 #include "yarandom.h"
153 #include "resources.h"
157 saver_info *global_si_kludge = 0; /* I hate C so much... */
164 static Atom XA_SCREENSAVER_RESPONSE;
165 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
166 static Atom XA_RESTART, XA_SELECT;
167 static Atom XA_THROTTLE, XA_UNTHROTTLE;
168 Atom XA_DEMO, XA_PREFS, XA_EXIT, XA_LOCK, XA_BLANK;
171 static XrmOptionDescRec options [] = {
172 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
173 { "-cycle", ".cycle", XrmoptionSepArg, 0 },
174 { "-lock-mode", ".lock", XrmoptionNoArg, "on" },
175 { "-no-lock-mode", ".lock", XrmoptionNoArg, "off" },
176 { "-no-lock", ".lock", XrmoptionNoArg, "off" },
177 { "-lock-timeout", ".lockTimeout", XrmoptionSepArg, 0 },
178 { "-lock-vts", ".lockVTs", XrmoptionNoArg, "on" },
179 { "-no-lock-vts", ".lockVTs", XrmoptionNoArg, "off" },
180 { "-visual", ".visualID", XrmoptionSepArg, 0 },
181 { "-install", ".installColormap", XrmoptionNoArg, "on" },
182 { "-no-install", ".installColormap", XrmoptionNoArg, "off" },
183 { "-verbose", ".verbose", XrmoptionNoArg, "on" },
184 { "-silent", ".verbose", XrmoptionNoArg, "off" },
185 { "-timestamp", ".timestamp", XrmoptionNoArg, "on" },
186 { "-capture-stderr", ".captureStderr", XrmoptionNoArg, "on" },
187 { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" },
188 { "-xidle-extension", ".xidleExtension", XrmoptionNoArg, "on" },
189 { "-no-xidle-extension", ".xidleExtension", XrmoptionNoArg, "off" },
190 { "-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "on" },
191 { "-no-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "off" },
192 { "-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "on" },
193 { "-no-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "off" },
194 { "-proc-interrupts", ".procInterrupts", XrmoptionNoArg, "on" },
195 { "-no-proc-interrupts", ".procInterrupts", XrmoptionNoArg, "off" },
196 { "-splash", ".splash", XrmoptionNoArg, "on" },
197 { "-no-splash", ".splash", XrmoptionNoArg, "off" },
198 { "-nosplash", ".splash", XrmoptionNoArg, "off" },
199 { "-idelay", ".initialDelay", XrmoptionSepArg, 0 },
200 { "-nice", ".nice", XrmoptionSepArg, 0 },
202 /* Actually these are built in to Xt, but just to be sure... */
203 { "-synchronous", ".synchronous", XrmoptionNoArg, "on" },
204 { "-xrm", NULL, XrmoptionResArg, NULL }
207 static char *defaults[] = {
208 #include "XScreenSaver_ad.h"
213 ERROR! You must not include vroot.h in this file.
217 do_help (saver_info *si)
222 xscreensaver %s, copyright (c) 1991-1999 by Jamie Zawinski <jwz@jwz.org>\n\
223 The standard Xt command-line options are accepted; other options include:\n\
225 -timeout <minutes> When the screensaver should activate.\n\
226 -cycle <minutes> How long to let each hack run before switching.\n\
227 -lock-mode Require a password before deactivating.\n\
228 -lock-timeout <minutes> Grace period before locking; default 0.\n\
229 -visual <id-or-class> Which X visual to run on.\n\
230 -install Install a private colormap.\n\
232 -no-splash Don't display a splash-screen at startup.\n\
233 -help This message.\n\
235 See the manual for other options and X resources.\n\
237 The `xscreensaver' program should be left running in the background.\n\
238 Use the `xscreensaver-demo' and `xscreensaver-command' programs to\n\
239 manipulate a running xscreensaver.\n\
241 The `*programs' resource controls which graphics demos will be launched by\n\
242 the screensaver. See `man xscreensaver' or the web page for more details.\n\
244 Just getting started? Try this:\n\
249 For updates, check http://www.jwz.org/xscreensaver/\n\
261 time_t now = time ((time_t *) 0);
262 char *str = (char *) ctime (&now);
263 char *nl = (char *) strchr (str, '\n');
264 if (nl) *nl = 0; /* take off that dang newline */
268 static Bool blurb_timestamp_p = False; /* kludge */
273 if (!blurb_timestamp_p)
277 static char buf[255];
278 char *ct = timestring();
279 int n = strlen(progname);
281 strncpy(buf, progname, n);
284 strncpy(buf+n, ct+11, 8);
285 strcpy(buf+n+9, ": ");
292 saver_ehandler (Display *dpy, XErrorEvent *error)
294 saver_info *si = global_si_kludge; /* I hate C so much... */
296 if (!real_stderr) real_stderr = stderr;
298 fprintf (real_stderr, "\n"
299 "#######################################"
300 "#######################################\n\n"
301 "%s: X Error! PLEASE REPORT THIS BUG.\n\n"
302 "#######################################"
303 "#######################################\n\n",
305 if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
307 fprintf (real_stderr, "\n");
308 if (si->prefs.xsync_p)
310 saver_exit (si, -1, "because of synchronous X Error");
314 fprintf (real_stderr,
315 "#######################################"
316 "#######################################\n\n");
317 fprintf (real_stderr,
318 " If at all possible, please re-run xscreensaver with the command
\ e\n"
319 " line arguments `-sync -verbose -no-capture', and reproduce this\n"
320 " bug. That will cause xscreensaver to dump a `core' file to the\n"
321 " current directory. Please include the stack trace from that core\n"
322 " file in your bug report. *DO NOT* mail the core file itself!\n"
323 " That won't work.\n"
325 " http://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
326 " the most useful bug reports, and how to examine core files.\n"
328 " The more information you can provide, the better. But please\n"
329 " report this bug, regardless!\n"
331 fprintf (real_stderr,
332 "#######################################"
333 "#######################################\n\n");
335 saver_exit (si, -1, 0);
339 fprintf (real_stderr, " (nonfatal.)\n");
344 /* This error handler is used only while the X connection is being set up;
345 after we've got a connection, we don't use this handler again. The only
346 reason for having this is so that we can present a more idiot-proof error
347 message than "cannot open display."
350 startup_ehandler (String name, String type, String class,
351 String defalt, /* one can't even spel properly
352 in this joke of a language */
353 String *av, Cardinal *ac)
357 saver_info *si = global_si_kludge; /* I hate C so much... */
358 XrmDatabase *db = XtAppGetErrorDatabase(si->app);
360 XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
361 fmt, sizeof(fmt)-1, *db);
363 fprintf (stderr, "%s: ", blurb());
365 memset (p, 0, sizeof(p));
366 if (*ac > countof (p)) *ac = countof (p);
367 memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
368 fprintf (stderr, fmt, /* Did I mention that I hate C? */
369 p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
370 fprintf (stderr, "\n");
372 describe_uids (si, stderr);
373 fprintf (stderr, "\n"
374 "%s: Errors at startup are usually authorization problems.\n"
375 " Did you read the manual and the FAQ? Specifically,\n"
376 " the parts of the manual that talk about XAUTH, XDM,\n"
377 " and root logins?\n"
379 " http://www.jwz.org/xscreensaver/man.html\n"
389 /* The zillions of initializations.
392 /* Set progname, version, etc. This is done very early.
395 set_version_string (saver_info *si, int *argc, char **argv)
397 progclass = "XScreenSaver";
399 /* progname is reset later, after we connect to X. */
400 progname = strrchr(argv[0], '/');
401 if (progname) progname++;
402 else progname = argv[0];
404 if (strlen(progname) > 100) /* keep it short. */
407 /* The X resource database blows up if argv[0] has a "." in it. */
410 while ((s = strchr (s, '.')))
414 si->version = (char *) malloc (5);
415 memcpy (si->version, screensaver_id + 17, 4);
420 /* Initializations that potentially take place as a priveleged user:
421 If the xscreensaver executable is setuid root, then these initializations
422 are run as root, before discarding privileges.
425 privileged_initialization (saver_info *si, int *argc, char **argv)
428 /* before hack_uid() for proper permissions */
429 lock_priv_init (*argc, argv, si->prefs.verbose_p);
430 #endif /* NO_LOCKING */
436 /* Figure out what locking mechanisms are supported.
439 lock_initialization (saver_info *si, int *argc, char **argv)
442 si->locking_disabled_p = True;
443 si->nolock_reason = "not compiled with locking support";
444 #else /* !NO_LOCKING */
446 /* Finish initializing locking, now that we're out of privileged code. */
447 if (! lock_init (*argc, argv, si->prefs.verbose_p))
449 si->locking_disabled_p = True;
450 si->nolock_reason = "error getting password";
452 #endif /* NO_LOCKING */
458 /* Open the connection to the X server, and intern our Atoms.
461 connect_to_server (saver_info *si, int *argc, char **argv)
463 Widget toplevel_shell;
465 XSetErrorHandler (saver_ehandler);
467 XtAppSetErrorMsgHandler (si->app, startup_ehandler);
468 toplevel_shell = XtAppInitialize (&si->app, progclass,
469 options, XtNumber (options),
470 argc, argv, defaults, 0, 0);
471 XtAppSetErrorMsgHandler (si->app, 0);
473 si->dpy = XtDisplay (toplevel_shell);
474 si->prefs.db = XtDatabase (si->dpy);
475 XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
477 if(strlen(progname) > 100) /* keep it short. */
480 db = si->prefs.db; /* resources.c needs this */
482 XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
483 XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
484 XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
485 XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
486 XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False);
487 XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
489 XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
490 XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
491 XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
492 XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
493 XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
494 XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
495 XA_PREV = XInternAtom (si->dpy, "PREV", False);
496 XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
497 XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
498 XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
499 XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
500 XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
501 XA_BLANK = XInternAtom (si->dpy, "BLANK", False);
502 XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
503 XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
505 return toplevel_shell;
509 /* Handle the command-line arguments that were not handled for us by Xt.
510 Issue an error message and exit if there are unknown options.
513 process_command_line (saver_info *si, int *argc, char **argv)
516 for (i = 1; i < *argc; i++)
518 if (!strcmp (argv[i], "-debug"))
519 /* no resource for this one, out of paranoia. */
520 si->prefs.debug_p = True;
522 else if (!strcmp (argv[i], "-h") ||
523 !strcmp (argv[i], "-help") ||
524 !strcmp (argv[i], "--help"))
529 const char *s = argv[i];
530 fprintf (stderr, "%s: unknown option \"%s\". Try \"-help\".\n",
533 if (s[0] == '-' && s[1] == '-') s++;
534 if (!strcmp (s, "-activate") ||
535 !strcmp (s, "-deactivate") ||
536 !strcmp (s, "-cycle") ||
537 !strcmp (s, "-next") ||
538 !strcmp (s, "-prev") ||
539 !strcmp (s, "-exit") ||
540 !strcmp (s, "-restart") ||
541 !strcmp (s, "-demo") ||
542 !strcmp (s, "-prefs") ||
543 !strcmp (s, "-preferences") ||
544 !strcmp (s, "-lock") ||
545 !strcmp (s, "-version") ||
546 !strcmp (s, "-time"))
549 if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
550 fprintf (stderr, "\n\
551 Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
553 fprintf (stderr, "\n\
554 However, `%s' is an option to the `xscreensaver-command' program.\n", s);
557 The `xscreensaver' program is a daemon that runs in the background.\n\
558 You control a running xscreensaver process by sending it messages\n\
559 with `xscreensaver-demo' or `xscreensaver-command'.\n\
560 . See the man pages for details, or check the web page:\n\
561 http://www.jwz.org/xscreensaver/\n\n");
563 /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
564 suggest that explicitly. */
565 if (!strcmp (s, "-lock"))
567 Or perhaps you meant either the \"-lock-mode\" or the\n\
568 \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
577 /* Print out the xscreensaver banner to the tty if applicable;
578 Issue any other warnings that are called for at this point.
581 print_banner (saver_info *si)
583 saver_preferences *p = &si->prefs;
585 /* This resource gets set some time before the others, so that we know
586 whether to print the banner (and so that the banner gets printed before
587 any resource-database-related error messages.)
589 p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
591 /* Ditto, for the locking_disabled_p message. */
592 p->lock_p = get_boolean_resource ("lock", "Boolean");
596 "%s %s, copyright (c) 1991-1999 "
597 "by Jamie Zawinski <jwz@jwz.org>.\n",
598 progname, si->version);
601 fprintf (stderr, "\n"
602 "%s: Warning: running in DEBUG MODE. Be afraid.\n"
604 "\tNote that in debug mode, the xscreensaver window will only\n"
605 "\tcover the left half of the screen. (The idea is that you\n"
606 "\tcan still see debugging output in a shell, if you position\n"
607 "\tit on the right side of the screen.)\n"
609 "\tDebug mode is NOT SECURE. Do not run with -debug in\n"
610 "\tuntrusted environments.\n"
616 if (!si->uid_message || !*si->uid_message)
617 describe_uids (si, stderr);
620 if (si->orig_uid && *si->orig_uid)
621 fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
622 blurb(), si->orig_uid);
623 fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
626 fprintf (stderr, "%s: in process %lu.\n", blurb(),
627 (unsigned long) getpid());
630 /* If locking was not able to be initalized for some reason, explain why.
631 (This has to be done after we've read the lock_p resource.)
633 if (p->lock_p && si->locking_disabled_p)
636 fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
638 if (strstr (si->nolock_reason, "passw"))
639 fprintf (stderr, "%s: does xscreensaver need to be setuid? "
640 "consult the manual.\n", blurb());
641 else if (strstr (si->nolock_reason, "running as "))
643 "%s: locking only works when xscreensaver is launched\n"
644 "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
645 "\t See the manual for details.\n",
651 /* Examine all of the display's screens, and populate the `saver_screen_info'
655 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
657 Bool found_any_writable_cells = False;
660 si->nscreens = ScreenCount(si->dpy);
661 si->screens = (saver_screen_info *)
662 calloc(sizeof(saver_screen_info), si->nscreens);
664 si->default_screen = &si->screens[DefaultScreen(si->dpy)];
666 for (i = 0; i < si->nscreens; i++)
668 saver_screen_info *ssi = &si->screens[i];
670 ssi->screen = ScreenOfDisplay (si->dpy, i);
672 /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
673 ssi->default_visual =
674 get_visual_resource (ssi->screen, "visualID", "VisualID", False);
676 ssi->current_visual = ssi->default_visual;
677 ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
679 if (ssi == si->default_screen)
680 /* Since this is the default screen, use the one already created. */
681 ssi->toplevel_shell = toplevel_shell;
683 /* Otherwise, each screen must have its own unmapped root widget. */
684 ssi->toplevel_shell =
685 XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
687 XtNscreen, ssi->screen,
688 XtNvisual, ssi->current_visual,
689 XtNdepth, visual_depth (ssi->screen,
690 ssi->current_visual),
693 if (! found_any_writable_cells)
695 /* Check to see whether fading is ever possible -- if any of the
696 screens on the display has a PseudoColor visual, then fading can
697 work (on at least some screens.) If no screen has a PseudoColor
698 visual, then don't bother ever trying to fade, because it will
699 just cause a delay without causing any visible effect.
701 if (has_writable_cells (ssi->screen, ssi->current_visual) ||
702 get_visual (ssi->screen, "PseudoColor", True, False) ||
703 get_visual (ssi->screen, "GrayScale", True, False))
704 found_any_writable_cells = True;
708 si->fading_possible_p = found_any_writable_cells;
712 /* If any server extensions have been requested, try and initialize them.
713 Issue warnings if requests can't be honored.
716 initialize_server_extensions (saver_info *si)
718 saver_preferences *p = &si->prefs;
720 Bool server_has_xidle_extension_p = False;
721 Bool server_has_sgi_saver_extension_p = False;
722 Bool server_has_mit_saver_extension_p = False;
723 Bool system_has_proc_interrupts_p = False;
724 const char *piwhy = 0;
726 si->using_xidle_extension = p->use_xidle_extension;
727 si->using_sgi_saver_extension = p->use_sgi_saver_extension;
728 si->using_mit_saver_extension = p->use_mit_saver_extension;
729 si->using_proc_interrupts = p->use_proc_interrupts;
731 #ifdef HAVE_XIDLE_EXTENSION
732 server_has_xidle_extension_p = query_xidle_extension (si);
734 #ifdef HAVE_SGI_SAVER_EXTENSION
735 server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
737 #ifdef HAVE_MIT_SAVER_EXTENSION
738 server_has_mit_saver_extension_p = query_mit_saver_extension (si);
740 #ifdef HAVE_PROC_INTERRUPTS
741 system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
744 if (!server_has_xidle_extension_p)
745 si->using_xidle_extension = False;
746 else if (p->verbose_p)
748 if (si->using_xidle_extension)
749 fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
751 fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
754 if (!server_has_sgi_saver_extension_p)
755 si->using_sgi_saver_extension = False;
756 else if (p->verbose_p)
758 if (si->using_sgi_saver_extension)
759 fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
762 "%s: not using server's SGI SCREEN_SAVER extension.\n",
766 if (!server_has_mit_saver_extension_p)
767 si->using_mit_saver_extension = False;
768 else if (p->verbose_p)
770 if (si->using_mit_saver_extension)
771 fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
775 "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
779 if (!system_has_proc_interrupts_p)
781 si->using_proc_interrupts = False;
782 if (p->verbose_p && piwhy)
783 fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
786 else if (p->verbose_p)
788 if (si->using_proc_interrupts)
790 "%s: consulting /proc/interrupts for keyboard activity.\n",
794 "%s: not consulting /proc/interrupts for keyboard activity.\n",
800 /* For the case where we aren't using an server extensions, select user events
801 on all the existing windows, and launch timers to select events on
802 newly-created windows as well.
804 If a server extension is being used, this does nothing.
807 select_events (saver_info *si)
809 saver_preferences *p = &si->prefs;
812 if (si->using_xidle_extension ||
813 si->using_mit_saver_extension ||
814 si->using_sgi_saver_extension)
817 if (p->initial_delay)
821 fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
822 (int) p->initial_delay/1000,
823 (p->initial_delay == 1000 ? "" : "s"));
827 usleep (p->initial_delay);
829 fprintf (stderr, " done.\n");
834 fprintf (stderr, "%s: selecting events on extant windows...", blurb());
839 /* Select events on the root windows of every screen. This also selects
840 for window creation events, so that new subwindows will be noticed.
842 for (i = 0; i < si->nscreens; i++)
843 start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
847 fprintf (stderr, " done.\n");
852 maybe_reload_init_file (saver_info *si)
854 saver_preferences *p = &si->prefs;
855 if (init_file_changed_p (p))
858 fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
859 blurb(), init_file_name());
863 /* If a server extension is in use, and p->timeout has changed,
864 we need to inform the server of the new timeout. */
865 disable_builtin_screensaver (si, False);
872 - wait until the user is idle;
874 - wait until the user is active;
875 - unblank the screen;
880 main_loop (saver_info *si)
882 saver_preferences *p = &si->prefs;
887 Bool was_locked = False;
888 sleep_until_idle (si, True);
893 fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
894 si->selection_mode, timestring());
897 fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
901 maybe_reload_init_file (si);
903 if (! blank_screen (si))
905 /* We were unable to grab either the keyboard or mouse.
906 This means we did not (and must not) blank the screen.
907 If we were to blank the screen while some other program
908 is holding both the mouse and keyboard grabbed, then
909 we would never be able to un-blank it! We would never
910 see any events, and the display would be wedged.
912 So, just go around the loop again and wait for the
913 next bout of idleness.
917 "%s: unable to grab keyboard or mouse! Blanking aborted.\n",
922 kill_screenhack (si);
924 if (!si->throttled_p)
925 spawn_screenhack (si, True);
926 else if (p->verbose_p)
927 fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
929 /* Don't start the cycle timer in demo mode. */
930 if (!si->demoing_p && p->cycle)
931 si->cycle_id = XtAppAddTimeOut (si->app,
933 /* see comment in cycle_timer() */
942 Time lock_timeout = p->lock_timeout;
944 if (si->emergency_lock_p && p->lock_p && lock_timeout)
946 int secs = p->lock_timeout / 1000;
949 "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
951 (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
955 si->emergency_lock_p = False;
957 if (!si->demoing_p && /* if not going into demo mode */
958 p->lock_p && /* and locking is enabled */
959 !si->locking_disabled_p && /* and locking is possible */
960 lock_timeout == 0) /* and locking is not timer-deferred */
961 set_locked_p (si, True); /* then lock right now. */
963 /* locked_p might be true already because of the above, or because of
964 the LOCK ClientMessage. But if not, and if we're supposed to lock
965 after some time, set up a timer to do so.
970 si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
974 #endif /* !NO_LOCKING */
977 ok_to_unblank = True;
980 sleep_until_idle (si, False); /* until not idle */
981 maybe_reload_init_file (si);
986 saver_screen_info *ssi = si->default_screen;
987 if (si->locking_disabled_p) abort ();
990 si->dbox_up_p = True;
991 suspend_screenhack (si, True);
992 XUndefineCursor (si->dpy, ssi->screensaver_window);
994 ok_to_unblank = unlock_p (si);
996 si->dbox_up_p = False;
997 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
998 suspend_screenhack (si, False); /* resume */
1000 #endif /* !NO_LOCKING */
1002 } while (!ok_to_unblank);
1006 fprintf (stderr, "%s: unblanking screen at %s.\n",
1007 blurb(), timestring ());
1009 /* Kill before unblanking, to stop drawing as soon as possible. */
1010 kill_screenhack (si);
1011 unblank_screen (si);
1013 set_locked_p (si, False);
1014 si->emergency_lock_p = False;
1016 si->selection_mode = 0;
1018 /* If we're throttled, and the user has explicitly unlocked the screen,
1019 then unthrottle. If we weren't locked, then don't unthrottle
1020 automatically, because someone might have just bumped the desk... */
1023 if (si->throttled_p && p->verbose_p)
1024 fprintf (stderr, "%s: unthrottled.\n", blurb());
1025 si->throttled_p = False;
1030 XtRemoveTimeOut (si->cycle_id);
1036 XtRemoveTimeOut (si->lock_id);
1041 fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1045 static void analyze_display (saver_info *si);
1048 main (int argc, char **argv)
1052 saver_info *si = &the_si;
1053 saver_preferences *p = &si->prefs;
1056 memset(si, 0, sizeof(*si));
1057 global_si_kludge = si; /* I hate C so much... */
1059 # undef ya_rand_init
1060 ya_rand_init ((int) time ((time_t *) 0));
1062 save_argv (argc, argv);
1063 set_version_string (si, &argc, argv);
1064 privileged_initialization (si, &argc, argv);
1065 hack_environment (si);
1067 shell = connect_to_server (si, &argc, argv);
1068 process_command_line (si, &argc, argv);
1071 load_init_file (p); /* must be before initialize_per_screen_info() */
1072 initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1074 /* We can only issue this warnings now. */
1075 if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1077 "%s: there are no PseudoColor or GrayScale visuals.\n"
1078 "%s: ignoring the request for fading/unfading.\n",
1081 for (i = 0; i < si->nscreens; i++)
1082 if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1085 lock_initialization (si, &argc, argv);
1087 if (p->xsync_p) XSynchronize (si->dpy, True);
1088 blurb_timestamp_p = p->timestamp_p; /* kludge */
1090 if (p->verbose_p) analyze_display (si);
1091 initialize_server_extensions (si);
1093 si->blank_time = time ((time_t) 0); /* must be before ..._window */
1094 initialize_screensaver_window (si);
1098 disable_builtin_screensaver (si, True);
1099 initialize_stderr (si);
1101 make_splash_dialog (si);
1103 main_loop (si); /* doesn't return */
1108 /* Processing ClientMessage events.
1112 clientmessage_response (saver_info *si, Window w, Bool error,
1113 const char *stderr_msg,
1114 const char *protocol_msg)
1118 saver_preferences *p = &si->prefs;
1119 if (error || p->verbose_p)
1120 fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1122 L = strlen(protocol_msg);
1123 proto = (char *) malloc (L + 2);
1124 proto[0] = (error ? '-' : '+');
1125 strcpy (proto+1, protocol_msg);
1128 XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1129 PropModeReplace, (unsigned char *) proto, L);
1130 XSync (si->dpy, False);
1135 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1137 saver_preferences *p = &si->prefs;
1139 Window window = event->xclient.window;
1141 /* Preferences might affect our handling of client messages. */
1142 maybe_reload_init_file (si);
1144 if (event->xclient.message_type != XA_SCREENSAVER)
1147 str = XGetAtomName (si->dpy, event->xclient.message_type);
1148 fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1149 blurb(), (str ? str : "(null)"));
1150 if (str) XFree (str);
1153 if (event->xclient.format != 32)
1155 fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1156 blurb(), event->xclient.format);
1160 type = event->xclient.data.l[0];
1161 if (type == XA_ACTIVATE)
1165 clientmessage_response(si, window, False,
1166 "ACTIVATE ClientMessage received.",
1168 si->selection_mode = 0;
1169 si->demoing_p = False;
1171 if (si->throttled_p && p->verbose_p)
1172 fprintf (stderr, "%s: unthrottled.\n", blurb());
1173 si->throttled_p = False;
1175 if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1177 XForceScreenSaver (si->dpy, ScreenSaverActive);
1185 clientmessage_response(si, window, True,
1186 "ClientMessage ACTIVATE received while already active.",
1189 else if (type == XA_DEACTIVATE)
1193 if (si->throttled_p && p->verbose_p)
1194 fprintf (stderr, "%s: unthrottled.\n", blurb());
1195 si->throttled_p = False;
1197 clientmessage_response(si, window, False,
1198 "DEACTIVATE ClientMessage received.",
1200 if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1202 XForceScreenSaver (si->dpy, ScreenSaverReset);
1210 clientmessage_response(si, window, True,
1211 "ClientMessage DEACTIVATE received while inactive.",
1214 else if (type == XA_CYCLE)
1218 clientmessage_response(si, window, False,
1219 "CYCLE ClientMessage received.",
1221 si->selection_mode = 0; /* 0 means randomize when its time. */
1222 si->demoing_p = False;
1224 if (si->throttled_p && p->verbose_p)
1225 fprintf (stderr, "%s: unthrottled.\n", blurb());
1226 si->throttled_p = False;
1229 XtRemoveTimeOut (si->cycle_id);
1231 cycle_timer ((XtPointer) si, 0);
1234 clientmessage_response(si, window, True,
1235 "ClientMessage CYCLE received while inactive.",
1238 else if (type == XA_NEXT || type == XA_PREV)
1240 clientmessage_response(si, window, False,
1242 ? "NEXT ClientMessage received."
1243 : "PREV ClientMessage received."),
1245 si->selection_mode = (type == XA_NEXT ? -1 : -2);
1246 si->demoing_p = False;
1248 if (si->throttled_p && p->verbose_p)
1249 fprintf (stderr, "%s: unthrottled.\n", blurb());
1250 si->throttled_p = False;
1255 XtRemoveTimeOut (si->cycle_id);
1257 cycle_timer ((XtPointer) si, 0);
1262 else if (type == XA_SELECT)
1266 long which = event->xclient.data.l[1];
1268 sprintf (buf, "SELECT %ld ClientMessage received.", which);
1269 sprintf (buf2, "activating (%ld).", which);
1270 clientmessage_response (si, window, False, buf, buf2);
1272 if (which < 0) which = 0; /* 0 == "random" */
1273 si->selection_mode = which;
1274 si->demoing_p = False;
1276 if (si->throttled_p && p->verbose_p)
1277 fprintf (stderr, "%s: unthrottled.\n", blurb());
1278 si->throttled_p = False;
1283 XtRemoveTimeOut (si->cycle_id);
1285 cycle_timer ((XtPointer) si, 0);
1290 else if (type == XA_EXIT)
1292 /* Ignore EXIT message if the screen is locked. */
1293 if (until_idle_p || !si->locked_p)
1295 clientmessage_response (si, window, False,
1296 "EXIT ClientMessage received.",
1300 unblank_screen (si);
1301 kill_screenhack (si);
1302 XSync (si->dpy, False);
1304 saver_exit (si, 0, 0);
1307 clientmessage_response (si, window, True,
1308 "EXIT ClientMessage received while locked.",
1309 "screen is locked.");
1311 else if (type == XA_RESTART)
1313 /* The RESTART message works whether the screensaver is active or not,
1314 unless the screen is locked, in which case it doesn't work.
1316 if (until_idle_p || !si->locked_p)
1318 clientmessage_response (si, window, False,
1319 "RESTART ClientMessage received.",
1323 unblank_screen (si);
1324 kill_screenhack (si);
1325 XSync (si->dpy, False);
1330 if (real_stdout) fflush (real_stdout);
1331 if (real_stderr) fflush (real_stderr);
1332 /* make sure error message shows up before exit. */
1333 if (real_stderr && stderr != real_stderr)
1334 dup2 (fileno(real_stderr), fileno(stderr));
1336 restart_process (si);
1337 exit (1); /* shouldn't get here; but if restarting didn't work,
1338 make this command be the same as EXIT. */
1341 clientmessage_response (si, window, True,
1342 "RESTART ClientMessage received while locked.",
1343 "screen is locked.");
1345 else if (type == XA_DEMO)
1347 long arg = event->xclient.data.l[1];
1348 Bool demo_one_hack_p = (arg == 300);
1350 if (demo_one_hack_p)
1354 long which = event->xclient.data.l[2];
1357 sprintf (buf, "DEMO %ld ClientMessage received.", which);
1358 sprintf (buf2, "demoing (%ld).", which);
1359 clientmessage_response (si, window, False, buf, buf2);
1361 if (which < 0) which = 0; /* 0 == "random" */
1362 si->selection_mode = which;
1363 si->demoing_p = True;
1365 if (si->throttled_p && p->verbose_p)
1366 fprintf (stderr, "%s: unthrottled.\n", blurb());
1367 si->throttled_p = False;
1372 clientmessage_response (si, window, True,
1373 "DEMO ClientMessage received while active.",
1378 clientmessage_response (si, window, True,
1379 "obsolete form of DEMO ClientMessage.",
1380 "obsolete form of DEMO ClientMessage.");
1383 else if (type == XA_PREFS)
1385 clientmessage_response (si, window, True,
1386 "the PREFS client-message is obsolete.",
1387 "the PREFS client-message is obsolete.");
1389 else if (type == XA_LOCK)
1392 clientmessage_response (si, window, True,
1393 "not compiled with support for locking.",
1394 "locking not enabled.");
1395 #else /* !NO_LOCKING */
1396 if (si->locking_disabled_p)
1397 clientmessage_response (si, window, True,
1398 "LOCK ClientMessage received, but locking is disabled.",
1399 "locking not enabled.");
1400 else if (si->locked_p)
1401 clientmessage_response (si, window, True,
1402 "LOCK ClientMessage received while already locked.",
1407 char *response = (until_idle_p
1408 ? "activating and locking."
1410 sprintf (buf, "LOCK ClientMessage received; %s", response);
1411 clientmessage_response (si, window, False, buf, response);
1412 set_locked_p (si, True);
1413 si->selection_mode = 0;
1414 si->demoing_p = False;
1416 if (si->lock_id) /* we're doing it now, so lose the timeout */
1418 XtRemoveTimeOut (si->lock_id);
1424 if (si->using_mit_saver_extension ||
1425 si->using_sgi_saver_extension)
1427 XForceScreenSaver (si->dpy, ScreenSaverActive);
1436 #endif /* !NO_LOCKING */
1438 else if (type == XA_THROTTLE)
1440 if (si->throttled_p)
1441 clientmessage_response (si, window, True,
1442 "THROTTLE ClientMessage received, but "
1443 "already throttled.",
1444 "already throttled.");
1448 char *response = "throttled.";
1449 si->throttled_p = True;
1450 si->selection_mode = 0;
1451 si->demoing_p = False;
1452 sprintf (buf, "THROTTLE ClientMessage received; %s", response);
1453 clientmessage_response (si, window, False, buf, response);
1458 XtRemoveTimeOut (si->cycle_id);
1460 cycle_timer ((XtPointer) si, 0);
1464 else if (type == XA_UNTHROTTLE)
1466 if (! si->throttled_p)
1467 clientmessage_response (si, window, True,
1468 "UNTHROTTLE ClientMessage received, but "
1474 char *response = "unthrottled.";
1475 si->throttled_p = False;
1476 si->selection_mode = 0;
1477 si->demoing_p = False;
1478 sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
1479 clientmessage_response (si, window, False, buf, response);
1484 XtRemoveTimeOut (si->cycle_id);
1486 cycle_timer ((XtPointer) si, 0);
1494 str = (type ? XGetAtomName(si->dpy, type) : 0);
1498 if (strlen (str) > 80)
1499 strcpy (str+70, "...");
1500 sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1507 "unrecognised screensaver ClientMessage 0x%x received.",
1508 (unsigned int) event->xclient.data.l[0]);
1511 clientmessage_response (si, window, True, buf, buf);
1517 /* Some random diagnostics printed in -verbose mode.
1521 analyze_display (saver_info *si)
1524 static const char *exts[][2] = {
1525 { "SCREEN_SAVER", "SGI Screen-Saver" },
1526 { "SCREEN-SAVER", "SGI Screen-Saver" },
1527 { "MIT-SCREEN-SAVER", "MIT Screen-Saver" },
1528 { "XIDLE", "XIdle" },
1529 { "SGI-VIDEO-CONTROL", "SGI Video-Control" },
1530 { "READDISPLAY", "SGI Read-Display" },
1531 { "MIT-SHM", "Shared Memory" },
1532 { "DOUBLE-BUFFER", "Double-Buffering" },
1533 { "DPMS", "Power Management" },
1535 { "XFree86-VidModeExtension", "XF86 Video-Mode" },
1536 { "XINERAMA", "Xinerama" }
1539 fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1540 DisplayString(si->dpy));
1541 fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1542 ServerVendor(si->dpy), VendorRelease(si->dpy));
1544 fprintf (stderr, "%s: useful extensions:\n", blurb());
1545 for (i = 0; i < countof(exts); i++)
1547 int op = 0, event = 0, error = 0;
1548 if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error))
1549 fprintf (stderr, "%s: %s\n", blurb(), exts[i][1]);
1552 for (i = 0; i < si->nscreens; i++)
1554 unsigned long colormapped_depths = 0;
1555 unsigned long non_mapped_depths = 0;
1556 XVisualInfo vi_in, *vi_out;
1559 vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1560 if (!vi_out) continue;
1561 for (j = 0; j < out_count; j++)
1562 if (vi_out[j].class == PseudoColor)
1563 colormapped_depths |= (1 << vi_out[j].depth);
1565 non_mapped_depths |= (1 << vi_out[j].depth);
1566 XFree ((char *) vi_out);
1568 if (colormapped_depths)
1570 fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1571 for (j = 0; j < 32; j++)
1572 if (colormapped_depths & (1 << j))
1573 fprintf (stderr, " %d", j);
1574 fprintf (stderr, "\n");
1576 if (non_mapped_depths)
1578 fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1579 for (j = 0; j < 32; j++)
1580 if (non_mapped_depths & (1 << j))
1581 fprintf (stderr, " %d", j);
1582 fprintf (stderr, "\n");
1588 display_is_on_console_p (saver_info *si)
1590 Bool not_on_console = True;
1591 char *dpystr = DisplayString (si->dpy);
1592 char *tail = (char *) strchr (dpystr, ':');
1593 if (! tail || strncmp (tail, ":0", 2))
1594 not_on_console = True;
1597 char dpyname[255], localname[255];
1598 strncpy (dpyname, dpystr, tail-dpystr);
1599 dpyname [tail-dpystr] = 0;
1601 !strcmp(dpyname, "unix") ||
1602 !strcmp(dpyname, "localhost"))
1603 not_on_console = False;
1604 else if (gethostname (localname, sizeof (localname)))
1605 not_on_console = True; /* can't find hostname? */
1608 /* We have to call gethostbyname() on the result of gethostname()
1609 because the two aren't guarenteed to be the same name for the
1610 same host: on some losing systems, one is a FQDN and the other
1611 is not. Here in the wide wonderful world of Unix it's rocket
1612 science to obtain the local hostname in a portable fashion.
1614 And don't forget, gethostbyname() reuses the structure it
1615 returns, so we have to copy the fucker before calling it again.
1616 Thank you master, may I have another.
1618 struct hostent *h = gethostbyname (dpyname);
1620 not_on_console = True;
1625 strcpy (hn, h->h_name);
1626 l = gethostbyname (localname);
1627 not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1631 return !not_on_console;