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"
155 saver_info *global_si_kludge = 0; /* I hate C so much... */
162 static Atom XA_SCREENSAVER_RESPONSE;
163 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
164 static Atom XA_EXIT, XA_RESTART, XA_LOCK, XA_SELECT;
165 Atom XA_DEMO, XA_PREFS;
168 static XrmOptionDescRec options [] = {
169 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
170 { "-cycle", ".cycle", XrmoptionSepArg, 0 },
171 { "-lock-mode", ".lock", XrmoptionNoArg, "on" },
172 { "-no-lock-mode", ".lock", XrmoptionNoArg, "off" },
173 { "-no-lock", ".lock", XrmoptionNoArg, "off" },
174 { "-lock-timeout", ".lockTimeout", XrmoptionSepArg, 0 },
175 { "-lock-vts", ".lockVTs", XrmoptionNoArg, "on" },
176 { "-no-lock-vts", ".lockVTs", XrmoptionNoArg, "off" },
177 { "-visual", ".visualID", XrmoptionSepArg, 0 },
178 { "-install", ".installColormap", XrmoptionNoArg, "on" },
179 { "-no-install", ".installColormap", XrmoptionNoArg, "off" },
180 { "-verbose", ".verbose", XrmoptionNoArg, "on" },
181 { "-silent", ".verbose", XrmoptionNoArg, "off" },
182 { "-timestamp", ".timestamp", XrmoptionNoArg, "on" },
183 { "-capture-stderr", ".captureStderr", XrmoptionNoArg, "on" },
184 { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" },
185 { "-xidle-extension", ".xidleExtension", XrmoptionNoArg, "on" },
186 { "-no-xidle-extension", ".xidleExtension", XrmoptionNoArg, "off" },
187 { "-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "on" },
188 { "-no-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "off" },
189 { "-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "on" },
190 { "-no-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "off" },
191 { "-splash", ".splash", XrmoptionNoArg, "on" },
192 { "-no-splash", ".splash", XrmoptionNoArg, "off" },
193 { "-nosplash", ".splash", XrmoptionNoArg, "off" },
194 { "-idelay", ".initialDelay", XrmoptionSepArg, 0 },
195 { "-nice", ".nice", XrmoptionSepArg, 0 },
197 /* Actually these are built in to Xt, but just to be sure... */
198 { "-synchronous", ".synchronous", XrmoptionNoArg, "on" },
199 { "-xrm", NULL, XrmoptionResArg, NULL }
202 static char *defaults[] = {
203 #include "XScreenSaver_ad.h"
208 ERROR! You must not include vroot.h in this file.
212 do_help (saver_info *si)
217 xscreensaver %s, copyright (c) 1991-1998 by Jamie Zawinski <jwz@jwz.org>\n\
218 The standard Xt command-line options are accepted; other options include:\n\
220 -timeout <minutes> When the screensaver should activate.\n\
221 -cycle <minutes> How long to let each hack run before switching.\n\
222 -lock-mode Require a password before deactivating.\n\
223 -lock-timeout <minutes> Grace period before locking; default 0.\n\
224 -visual <id-or-class> Which X visual to run on.\n\
225 -install Install a private colormap.\n\
227 -no-splash Don't display a splash-screen at startup.\n\
228 -help This message.\n\
230 See the manual for other options and X resources.\n\
232 The `xscreensaver' program should be left running in the background.\n\
233 Use the `xscreensaver-demo' and `xscreensaver-command' programs to\n\
234 manipulate a running xscreensaver.\n\
236 The `*programs' resource controls which graphics demos will be launched by\n\
237 the screensaver. See `man xscreensaver' or the web page for more details.\n\
239 Just getting started? Try this:\n\
244 For updates, check http://www.jwz.org/xscreensaver/\n\
256 time_t now = time ((time_t *) 0);
257 char *str = (char *) ctime (&now);
258 char *nl = (char *) strchr (str, '\n');
259 if (nl) *nl = 0; /* take off that dang newline */
263 static Bool blurb_timestamp_p = False; /* kludge */
268 if (!blurb_timestamp_p)
272 static char buf[255];
273 char *ct = timestring();
274 int n = strlen(progname);
276 strncpy(buf, progname, n);
279 strncpy(buf+n, ct+11, 8);
280 strcpy(buf+n+9, ": ");
287 saver_ehandler (Display *dpy, XErrorEvent *error)
289 saver_info *si = global_si_kludge; /* I hate C so much... */
291 fprintf (real_stderr, "\n"
292 "#######################################"
293 "#######################################\n\n"
294 "%s: X Error! PLEASE REPORT THIS BUG.\n\n"
295 "#######################################"
296 "#######################################\n\n",
298 if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
300 fprintf (real_stderr, "\n");
301 if (si->prefs.xsync_p)
303 saver_exit (si, -1, "because of synchronous X Error");
308 "%s: to dump a core file, re-run with `-sync'.\n"
309 "%s: see http://www.jwz.org/xscreensaver/bugs.html\n"
310 "\t\tfor bug reporting information.\n\n",
312 saver_exit (si, -1, 0);
316 fprintf (real_stderr, " (nonfatal.)\n");
321 /* The zillions of initializations.
324 /* Set progname, version, etc. This is done very early.
327 set_version_string (saver_info *si, int *argc, char **argv)
329 progclass = "XScreenSaver";
331 /* progname is reset later, after we connect to X. */
332 progname = strrchr(argv[0], '/');
333 if (progname) progname++;
334 else progname = argv[0];
336 if (strlen(progname) > 100) /* keep it short. */
339 /* The X resource database blows up if argv[0] has a "." in it. */
342 while ((s = strchr (s, '.')))
346 si->version = (char *) malloc (5);
347 memcpy (si->version, screensaver_id + 17, 4);
352 /* Initializations that potentially take place as a priveleged user:
353 If the xscreensaver executable is setuid root, then these initializations
354 are run as root, before discarding privileges.
357 privileged_initialization (saver_info *si, int *argc, char **argv)
360 si->locking_disabled_p = True;
361 si->nolock_reason = "not compiled with locking support";
362 #else /* !NO_LOCKING */
363 si->locking_disabled_p = False;
364 /* before hack_uid() for proper permissions */
365 if (! lock_init (*argc, argv, si->prefs.verbose_p))
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->prefs.db = XtDatabase (si->dpy);
392 XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
394 if(strlen(progname) > 100) /* keep it short. */
397 db = si->prefs.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], "-h") ||
437 !strcmp (argv[i], "-help") ||
438 !strcmp (argv[i], "--help"))
443 const char *s = argv[i];
444 fprintf (stderr, "%s: unknown option \"%s\". Try \"-help\".\n",
447 if (s[0] == '-' && s[1] == '-') s++;
448 if (!strcmp (s, "-activate") ||
449 !strcmp (s, "-deactivate") ||
450 !strcmp (s, "-cycle") ||
451 !strcmp (s, "-next") ||
452 !strcmp (s, "-prev") ||
453 !strcmp (s, "-exit") ||
454 !strcmp (s, "-restart") ||
455 !strcmp (s, "-demo") ||
456 !strcmp (s, "-prefs") ||
457 !strcmp (s, "-preferences") ||
458 !strcmp (s, "-lock") ||
459 !strcmp (s, "-version") ||
460 !strcmp (s, "-time"))
463 if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
464 fprintf (stderr, "\n\
465 Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
467 fprintf (stderr, "\n\
468 However, `%s' is an option to the `xscreensaver-command' program.\n", s);
471 The `xscreensaver' program is a daemon that runs in the background.\n\
472 You control a running xscreensaver process by sending it messages\n\
473 with `xscreensaver-demo' or `xscreensaver-command'.\n\
474 . See the man pages for details, or check the web page:\n\
475 http://www.jwz.org/xscreensaver/\n\n");
477 /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
478 suggest that explicitly. */
479 if (!strcmp (s, "-lock"))
481 Or perhaps you meant either the \"-lock-mode\" or the\n\
482 \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
491 /* Print out the xscreensaver banner to the tty if applicable;
492 Issue any other warnings that are called for at this point.
495 print_banner (saver_info *si)
497 saver_preferences *p = &si->prefs;
499 /* This resource gets set some time before the others, so that we know
500 whether to print the banner (and so that the banner gets printed before
501 any resource-database-related error messages.)
503 p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
505 /* Ditto, for the locking_disabled_p message. */
506 p->lock_p = get_boolean_resource ("lock", "Boolean");
510 "%s %s, copyright (c) 1991-1998 "
511 "by Jamie Zawinski <jwz@jwz.org>.\n",
512 progname, si->version);
515 fprintf (stderr, "\n"
516 "%s: Warning: running in DEBUG MODE. Be afraid.\n"
518 "\tNote that in debug mode, the xscreensaver window will only\n"
519 "\tcover the left half of the screen. (The idea is that you\n"
520 "\tcan still see debugging output in a shell, if you position\n"
521 "\tit on the right side of the screen.)\n"
523 "\tDebug mode is NOT SECURE. Do not run with -debug in\n"
524 "\tuntrusted environments.\n"
530 if (!si->uid_message || !*si->uid_message)
531 describe_uids (si, stderr);
534 if (si->orig_uid && *si->orig_uid)
535 fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
536 blurb(), si->orig_uid);
537 fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
540 fprintf (stderr, "%s: in process %lu.\n", blurb(),
541 (unsigned long) getpid());
544 /* If locking was not able to be initalized for some reason, explain why.
545 (This has to be done after we've read the lock_p resource.)
547 if (p->lock_p && si->locking_disabled_p)
550 fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
552 if (strstr (si->nolock_reason, "passw"))
553 fprintf (stderr, "%s: does xscreensaver need to be setuid? "
554 "consult the manual.\n", blurb());
555 else if (strstr (si->nolock_reason, "running as "))
557 "%s: locking only works when xscreensaver is launched\n"
558 "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
559 "\t See the manual for details.\n",
565 /* Examine all of the display's screens, and populate the `saver_screen_info'
569 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
571 Bool found_any_writable_cells = False;
574 si->nscreens = ScreenCount(si->dpy);
575 si->screens = (saver_screen_info *)
576 calloc(sizeof(saver_screen_info), si->nscreens);
578 si->default_screen = &si->screens[DefaultScreen(si->dpy)];
580 for (i = 0; i < si->nscreens; i++)
582 saver_screen_info *ssi = &si->screens[i];
584 ssi->screen = ScreenOfDisplay (si->dpy, i);
586 /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
587 ssi->default_visual =
588 get_visual_resource (ssi->screen, "visualID", "VisualID", False);
590 ssi->current_visual = ssi->default_visual;
591 ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
593 if (ssi == si->default_screen)
594 /* Since this is the default screen, use the one already created. */
595 ssi->toplevel_shell = toplevel_shell;
597 /* Otherwise, each screen must have its own unmapped root widget. */
598 ssi->toplevel_shell =
599 XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
601 XtNscreen, ssi->screen,
602 XtNvisual, ssi->current_visual,
603 XtNdepth, visual_depth (ssi->screen,
604 ssi->current_visual),
607 if (! found_any_writable_cells)
609 /* Check to see whether fading is ever possible -- if any of the
610 screens on the display has a PseudoColor visual, then fading can
611 work (on at least some screens.) If no screen has a PseudoColor
612 visual, then don't bother ever trying to fade, because it will
613 just cause a delay without causing any visible effect.
615 if (has_writable_cells (ssi->screen, ssi->current_visual) ||
616 get_visual (ssi->screen, "PseudoColor", True, False) ||
617 get_visual (ssi->screen, "GrayScale", True, False))
618 found_any_writable_cells = True;
622 si->prefs.fading_possible_p = found_any_writable_cells;
626 /* If any server extensions have been requested, try and initialize them.
627 Issue warnings if requests can't be honored.
630 initialize_server_extensions (saver_info *si)
632 saver_preferences *p = &si->prefs;
634 Bool server_has_xidle_extension_p = False;
635 Bool server_has_sgi_saver_extension_p = False;
636 Bool server_has_mit_saver_extension_p = False;
638 si->using_xidle_extension = p->use_xidle_extension;
639 si->using_sgi_saver_extension = p->use_sgi_saver_extension;
640 si->using_mit_saver_extension = p->use_mit_saver_extension;
642 #ifdef HAVE_XIDLE_EXTENSION
643 server_has_xidle_extension_p = query_xidle_extension (si);
645 #ifdef HAVE_SGI_SAVER_EXTENSION
646 server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
648 #ifdef HAVE_MIT_SAVER_EXTENSION
649 server_has_mit_saver_extension_p = query_mit_saver_extension (si);
652 if (!server_has_xidle_extension_p)
653 si->using_xidle_extension = False;
654 else if (p->verbose_p)
656 if (si->using_xidle_extension)
657 fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
659 fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
662 if (!server_has_sgi_saver_extension_p)
663 si->using_sgi_saver_extension = False;
664 else if (p->verbose_p)
666 if (si->using_sgi_saver_extension)
667 fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
670 "%s: not using server's SGI SCREEN_SAVER extension.\n",
674 if (!server_has_mit_saver_extension_p)
675 si->using_mit_saver_extension = False;
676 else if (p->verbose_p)
678 if (si->using_mit_saver_extension)
679 fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
683 "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
689 /* For the case where we aren't using an server extensions, select user events
690 on all the existing windows, and launch timers to select events on
691 newly-created windows as well.
693 If a server extension is being used, this does nothing.
696 select_events (saver_info *si)
698 saver_preferences *p = &si->prefs;
701 if (si->using_xidle_extension ||
702 si->using_mit_saver_extension ||
703 si->using_sgi_saver_extension)
706 if (p->initial_delay)
710 fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
711 (int) p->initial_delay/1000,
712 (p->initial_delay == 1000 ? "" : "s"));
716 usleep (p->initial_delay);
718 fprintf (stderr, " done.\n");
723 fprintf (stderr, "%s: selecting events on extant windows...", blurb());
728 /* Select events on the root windows of every screen. This also selects
729 for window creation events, so that new subwindows will be noticed.
731 for (i = 0; i < si->nscreens; i++)
732 start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen));
735 fprintf (stderr, " done.\n");
740 maybe_reload_init_file (saver_info *si)
742 saver_preferences *p = &si->prefs;
743 if (init_file_changed_p (p))
746 fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
747 blurb(), init_file_name());
751 /* If a server extension is in use, and p->timeout has changed,
752 we need to inform the server of the new timeout. */
753 disable_builtin_screensaver (si, False);
760 - wait until the user is idle;
762 - wait until the user is active;
763 - unblank the screen;
768 main_loop (saver_info *si)
770 saver_preferences *p = &si->prefs;
775 sleep_until_idle (si, True);
780 fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
781 si->selection_mode, timestring());
784 fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
788 maybe_reload_init_file (si);
791 kill_screenhack (si);
792 spawn_screenhack (si, True);
794 /* Don't start the cycle timer in demo mode. */
795 if (!si->demoing_p && p->cycle)
796 si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
801 if (!si->demoing_p && /* if not going into demo mode */
802 p->lock_p && /* and locking is enabled */
803 !si->locking_disabled_p && /* and locking is possible */
804 p->lock_timeout == 0) /* and locking is not timer-deferred */
805 si->locked_p = True; /* then lock right now. */
807 /* locked_p might be true already because of the above, or because of
808 the LOCK ClientMessage. But if not, and if we're supposed to lock
809 after some time, set up a timer to do so.
814 si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
817 #endif /* !NO_LOCKING */
820 ok_to_unblank = True;
823 sleep_until_idle (si, False); /* until not idle */
824 maybe_reload_init_file (si);
829 saver_screen_info *ssi = si->default_screen;
830 if (si->locking_disabled_p) abort ();
832 si->dbox_up_p = True;
833 suspend_screenhack (si, True);
834 XUndefineCursor (si->dpy, ssi->screensaver_window);
836 ok_to_unblank = unlock_p (si);
838 si->dbox_up_p = False;
839 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
840 suspend_screenhack (si, False); /* resume */
842 #endif /* !NO_LOCKING */
844 } while (!ok_to_unblank);
848 fprintf (stderr, "%s: unblanking screen at %s.\n",
849 blurb(), timestring ());
851 /* Kill before unblanking, to stop drawing as soon as possible. */
852 kill_screenhack (si);
855 si->locked_p = False;
857 si->selection_mode = 0;
861 XtRemoveTimeOut (si->cycle_id);
867 XtRemoveTimeOut (si->lock_id);
872 fprintf (stderr, "%s: awaiting idleness.\n", blurb());
876 static void analyze_display (saver_info *si);
879 main (int argc, char **argv)
883 saver_info *si = &the_si;
884 saver_preferences *p = &si->prefs;
887 memset(si, 0, sizeof(*si));
888 global_si_kludge = si; /* I hate C so much... */
890 srandom ((int) time ((time_t *) 0));
892 save_argv (argc, argv);
893 set_version_string (si, &argc, argv);
894 privileged_initialization (si, &argc, argv);
895 hack_environment (si);
897 shell = connect_to_server (si, &argc, argv);
898 process_command_line (si, &argc, argv);
901 initialize_per_screen_info (si, shell); /* also sets p->fading_possible_p */
903 for (i = 0; i < si->nscreens; i++)
904 if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
909 if (p->xsync_p) XSynchronize (si->dpy, True);
910 blurb_timestamp_p = p->timestamp_p; /* kludge */
912 if (p->verbose_p) analyze_display (si);
913 initialize_server_extensions (si);
914 initialize_screensaver_window (si);
917 disable_builtin_screensaver (si, True);
918 initialize_stderr (si);
920 make_splash_dialog (si);
922 main_loop (si); /* doesn't return */
927 /* Processing ClientMessage events.
931 clientmessage_response (saver_info *si, Window w, Bool error,
932 const char *stderr_msg,
933 const char *protocol_msg)
937 saver_preferences *p = &si->prefs;
938 if (error || p->verbose_p)
939 fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
941 L = strlen(protocol_msg);
942 proto = (char *) malloc (L + 2);
943 proto[0] = (error ? '-' : '+');
944 strcpy (proto+1, protocol_msg);
947 XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
948 PropModeReplace, proto, L);
949 XSync (si->dpy, False);
954 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
957 Window window = event->xclient.window;
959 /* Preferences might affect our handling of client messages. */
960 maybe_reload_init_file (si);
962 if (event->xclient.message_type != XA_SCREENSAVER)
965 str = XGetAtomName (si->dpy, event->xclient.message_type);
966 fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
967 blurb(), (str ? str : "(null)"));
968 if (str) XFree (str);
971 if (event->xclient.format != 32)
973 fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
974 blurb(), event->xclient.format);
978 type = event->xclient.data.l[0];
979 if (type == XA_ACTIVATE)
983 clientmessage_response(si, window, False,
984 "ACTIVATE ClientMessage received.",
986 si->selection_mode = 0;
987 si->demoing_p = False;
988 if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
990 XForceScreenSaver (si->dpy, ScreenSaverActive);
998 clientmessage_response(si, window, True,
999 "ClientMessage ACTIVATE received while already active.",
1002 else if (type == XA_DEACTIVATE)
1006 clientmessage_response(si, window, False,
1007 "DEACTIVATE ClientMessage received.",
1009 if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1011 XForceScreenSaver (si->dpy, ScreenSaverReset);
1019 clientmessage_response(si, window, True,
1020 "ClientMessage DEACTIVATE received while inactive.",
1023 else if (type == XA_CYCLE)
1027 clientmessage_response(si, window, False,
1028 "CYCLE ClientMessage received.",
1030 si->selection_mode = 0; /* 0 means randomize when its time. */
1031 si->demoing_p = False;
1033 XtRemoveTimeOut (si->cycle_id);
1035 cycle_timer ((XtPointer) si, 0);
1038 clientmessage_response(si, window, True,
1039 "ClientMessage CYCLE received while inactive.",
1042 else if (type == XA_NEXT || type == XA_PREV)
1044 clientmessage_response(si, window, False,
1046 ? "NEXT ClientMessage received."
1047 : "PREV ClientMessage received."),
1049 si->selection_mode = (type == XA_NEXT ? -1 : -2);
1050 si->demoing_p = False;
1055 XtRemoveTimeOut (si->cycle_id);
1057 cycle_timer ((XtPointer) si, 0);
1062 else if (type == XA_SELECT)
1066 long which = event->xclient.data.l[1];
1068 sprintf (buf, "SELECT %ld ClientMessage received.", which);
1069 sprintf (buf2, "activating (%ld).", which);
1070 clientmessage_response (si, window, False, buf, buf2);
1072 if (which < 0) which = 0; /* 0 == "random" */
1073 si->selection_mode = which;
1074 si->demoing_p = False;
1079 XtRemoveTimeOut (si->cycle_id);
1081 cycle_timer ((XtPointer) si, 0);
1086 else if (type == XA_EXIT)
1088 /* Ignore EXIT message if the screen is locked. */
1089 if (until_idle_p || !si->locked_p)
1091 clientmessage_response (si, window, False,
1092 "EXIT ClientMessage received.",
1096 unblank_screen (si);
1097 kill_screenhack (si);
1098 XSync (si->dpy, False);
1100 saver_exit (si, 0, 0);
1103 clientmessage_response (si, window, True,
1104 "EXIT ClientMessage received while locked.",
1105 "screen is locked.");
1107 else if (type == XA_RESTART)
1109 /* The RESTART message works whether the screensaver is active or not,
1110 unless the screen is locked, in which case it doesn't work.
1112 if (until_idle_p || !si->locked_p)
1114 clientmessage_response (si, window, False,
1115 "RESTART ClientMessage received.",
1119 unblank_screen (si);
1120 kill_screenhack (si);
1121 XSync (si->dpy, False);
1124 /* make sure error message shows up before exit. */
1125 if (real_stderr && stderr != real_stderr)
1126 dup2 (fileno(real_stderr), fileno(stderr));
1128 restart_process (si);
1129 exit (1); /* shouldn't get here; but if restarting didn't work,
1130 make this command be the same as EXIT. */
1133 clientmessage_response (si, window, True,
1134 "RESTART ClientMessage received while locked.",
1135 "screen is locked.");
1137 else if (type == XA_DEMO)
1139 long arg = event->xclient.data.l[1];
1140 Bool demo_one_hack_p = (arg == 300);
1142 if (demo_one_hack_p)
1146 long which = event->xclient.data.l[2];
1149 sprintf (buf, "DEMO %ld ClientMessage received.", which);
1150 sprintf (buf2, "demoing (%ld).", which);
1151 clientmessage_response (si, window, False, buf, buf2);
1153 if (which < 0) which = 0; /* 0 == "random" */
1154 si->selection_mode = which;
1155 si->demoing_p = True;
1160 clientmessage_response (si, window, True,
1161 "DEMO ClientMessage received while active.",
1166 clientmessage_response (si, window, True,
1167 "obsolete form of DEMO ClientMessage.",
1168 "obsolete form of DEMO ClientMessage.");
1171 else if (type == XA_PREFS)
1173 clientmessage_response (si, window, True,
1174 "the PREFS client-message is obsolete.",
1175 "the PREFS client-message is obsolete.");
1177 else if (type == XA_LOCK)
1180 clientmessage_response (si, window, True,
1181 "not compiled with support for locking.",
1182 "locking not enabled.");
1183 #else /* !NO_LOCKING */
1184 if (si->locking_disabled_p)
1185 clientmessage_response (si, window, True,
1186 "LOCK ClientMessage received, but locking is disabled.",
1187 "locking not enabled.");
1188 else if (si->locked_p)
1189 clientmessage_response (si, window, True,
1190 "LOCK ClientMessage received while already locked.",
1195 char *response = (until_idle_p
1196 ? "activating and locking."
1198 si->locked_p = True;
1199 si->selection_mode = 0;
1200 si->demoing_p = False;
1201 sprintf (buf, "LOCK ClientMessage received; %s", response);
1202 clientmessage_response (si, window, False, buf, response);
1204 if (si->lock_id) /* we're doing it now, so lose the timeout */
1206 XtRemoveTimeOut (si->lock_id);
1212 if (si->using_mit_saver_extension ||
1213 si->using_sgi_saver_extension)
1215 XForceScreenSaver (si->dpy, ScreenSaverActive);
1224 #endif /* !NO_LOCKING */
1230 str = (type ? XGetAtomName(si->dpy, type) : 0);
1234 if (strlen (str) > 80)
1235 strcpy (str+70, "...");
1236 sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1243 "unrecognised screensaver ClientMessage 0x%x received.",
1244 (unsigned int) event->xclient.data.l[0]);
1247 clientmessage_response (si, window, True, buf, buf);
1253 /* Some random diagnostics printed in -verbose mode.
1257 analyze_display (saver_info *si)
1260 static const char *exts[][2] = {
1261 { "SCREEN_SAVER", "SGI Screen-Saver" },
1262 { "SCREEN-SAVER", "SGI Screen-Saver" },
1263 { "MIT-SCREEN-SAVER", "MIT Screen-Saver" },
1264 { "XIDLE", "XIdle" },
1265 { "SGI-VIDEO-CONTROL", "SGI Video-Control" },
1266 { "READDISPLAY", "SGI Read-Display" },
1267 { "MIT-SHM", "Shared Memory" },
1268 { "DOUBLE-BUFFER", "Double-Buffering" },
1269 { "DPMS", "Power Management" },
1273 fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1274 DisplayString(si->dpy));
1275 fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1276 ServerVendor(si->dpy), VendorRelease(si->dpy));
1278 fprintf (stderr, "%s: useful extensions:\n", blurb());
1279 for (i = 0; i < countof(exts); i++)
1281 int op = 0, event = 0, error = 0;
1282 if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error))
1283 fprintf (stderr, "%s: %s\n", blurb(), exts[i][1]);
1286 for (i = 0; i < si->nscreens; i++)
1288 unsigned long colormapped_depths = 0;
1289 unsigned long non_mapped_depths = 0;
1290 XVisualInfo vi_in, *vi_out;
1293 vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1294 if (!vi_out) continue;
1295 for (j = 0; j < out_count; j++)
1296 if (vi_out[j].class == PseudoColor)
1297 colormapped_depths |= (1 << vi_out[j].depth);
1299 non_mapped_depths |= (1 << vi_out[j].depth);
1300 XFree ((char *) vi_out);
1302 if (colormapped_depths)
1304 fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1305 for (j = 0; j < 32; j++)
1306 if (colormapped_depths & (1 << j))
1307 fprintf (stderr, " %d", j);
1308 fprintf (stderr, "\n");
1310 if (non_mapped_depths)
1312 fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1313 for (j = 0; j < 32; j++)
1314 if (non_mapped_depths & (1 << j))
1315 fprintf (stderr, " %d", j);
1316 fprintf (stderr, "\n");