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 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>
134 #include <netdb.h> /* for gethostbyname() */
137 # include <X11/Xmu/Error.h>
139 # include <Xmu/Error.h>
141 #else /* !HAVE_XMU */
143 #endif /* !HAVE_XMU */
145 #ifdef HAVE_XIDLE_EXTENSION
146 # include <X11/extensions/xidle.h>
147 #endif /* HAVE_XIDLE_EXTENSION */
149 #include "xscreensaver.h"
151 #include "yarandom.h"
152 #include "resources.h"
156 saver_info *global_si_kludge = 0; /* I hate C so much... */
163 static Atom XA_SCREENSAVER_RESPONSE;
164 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
165 static Atom XA_EXIT, XA_RESTART, XA_LOCK, XA_SELECT;
166 Atom XA_DEMO, XA_PREFS;
169 static XrmOptionDescRec options [] = {
170 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
171 { "-cycle", ".cycle", XrmoptionSepArg, 0 },
172 { "-lock-mode", ".lock", XrmoptionNoArg, "on" },
173 { "-no-lock-mode", ".lock", XrmoptionNoArg, "off" },
174 { "-no-lock", ".lock", XrmoptionNoArg, "off" },
175 { "-lock-timeout", ".lockTimeout", XrmoptionSepArg, 0 },
176 { "-lock-vts", ".lockVTs", XrmoptionNoArg, "on" },
177 { "-no-lock-vts", ".lockVTs", XrmoptionNoArg, "off" },
178 { "-visual", ".visualID", XrmoptionSepArg, 0 },
179 { "-install", ".installColormap", XrmoptionNoArg, "on" },
180 { "-no-install", ".installColormap", XrmoptionNoArg, "off" },
181 { "-verbose", ".verbose", XrmoptionNoArg, "on" },
182 { "-silent", ".verbose", XrmoptionNoArg, "off" },
183 { "-timestamp", ".timestamp", XrmoptionNoArg, "on" },
184 { "-capture-stderr", ".captureStderr", XrmoptionNoArg, "on" },
185 { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" },
186 { "-xidle-extension", ".xidleExtension", XrmoptionNoArg, "on" },
187 { "-no-xidle-extension", ".xidleExtension", XrmoptionNoArg, "off" },
188 { "-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "on" },
189 { "-no-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "off" },
190 { "-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "on" },
191 { "-no-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "off" },
192 { "-proc-interrupts", ".procInterrupts", XrmoptionNoArg, "on" },
193 { "-no-proc-interrupts", ".procInterrupts", XrmoptionNoArg, "off" },
194 { "-splash", ".splash", XrmoptionNoArg, "on" },
195 { "-no-splash", ".splash", XrmoptionNoArg, "off" },
196 { "-nosplash", ".splash", XrmoptionNoArg, "off" },
197 { "-idelay", ".initialDelay", XrmoptionSepArg, 0 },
198 { "-nice", ".nice", XrmoptionSepArg, 0 },
200 /* Actually these are built in to Xt, but just to be sure... */
201 { "-synchronous", ".synchronous", XrmoptionNoArg, "on" },
202 { "-xrm", NULL, XrmoptionResArg, NULL }
205 static char *defaults[] = {
206 #include "XScreenSaver_ad.h"
211 ERROR! You must not include vroot.h in this file.
215 do_help (saver_info *si)
220 xscreensaver %s, copyright (c) 1991-1999 by Jamie Zawinski <jwz@jwz.org>\n\
221 The standard Xt command-line options are accepted; other options include:\n\
223 -timeout <minutes> When the screensaver should activate.\n\
224 -cycle <minutes> How long to let each hack run before switching.\n\
225 -lock-mode Require a password before deactivating.\n\
226 -lock-timeout <minutes> Grace period before locking; default 0.\n\
227 -visual <id-or-class> Which X visual to run on.\n\
228 -install Install a private colormap.\n\
230 -no-splash Don't display a splash-screen at startup.\n\
231 -help This message.\n\
233 See the manual for other options and X resources.\n\
235 The `xscreensaver' program should be left running in the background.\n\
236 Use the `xscreensaver-demo' and `xscreensaver-command' programs to\n\
237 manipulate a running xscreensaver.\n\
239 The `*programs' resource controls which graphics demos will be launched by\n\
240 the screensaver. See `man xscreensaver' or the web page for more details.\n\
242 Just getting started? Try this:\n\
247 For updates, check http://www.jwz.org/xscreensaver/\n\
259 time_t now = time ((time_t *) 0);
260 char *str = (char *) ctime (&now);
261 char *nl = (char *) strchr (str, '\n');
262 if (nl) *nl = 0; /* take off that dang newline */
266 static Bool blurb_timestamp_p = False; /* kludge */
271 if (!blurb_timestamp_p)
275 static char buf[255];
276 char *ct = timestring();
277 int n = strlen(progname);
279 strncpy(buf, progname, n);
282 strncpy(buf+n, ct+11, 8);
283 strcpy(buf+n+9, ": ");
290 saver_ehandler (Display *dpy, XErrorEvent *error)
292 saver_info *si = global_si_kludge; /* I hate C so much... */
294 if (!real_stderr) real_stderr = stderr;
296 fprintf (real_stderr, "\n"
297 "#######################################"
298 "#######################################\n\n"
299 "%s: X Error! PLEASE REPORT THIS BUG.\n\n"
300 "#######################################"
301 "#######################################\n\n",
303 if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
305 fprintf (real_stderr, "\n");
306 if (si->prefs.xsync_p)
308 saver_exit (si, -1, "because of synchronous X Error");
312 fprintf (real_stderr,
313 "#######################################"
314 "#######################################\n\n");
315 fprintf (real_stderr,
316 " If at all possible, please re-run xscreensaver with the command line\n"
317 " arguments `-sync -verbose', and reproduce this bug. That will cause\n"
318 " xscreensaver to dump a `core' file to the current directory. Please\n"
319 " include the stack trace from that core file in your bug report.\n"
321 " http://www.jwz.org/xscreensaver/bugs.html explains how to create the\n"
322 " most useful bug reports, and how to examine core files.\n"
324 " The more information you can provide, the better. But please report\n"
325 " report this bug, regardless!\n"
327 fprintf (real_stderr,
328 "#######################################"
329 "#######################################\n\n");
331 saver_exit (si, -1, 0);
335 fprintf (real_stderr, " (nonfatal.)\n");
340 /* This error handler is used only while the X connection is being set up;
341 after we've got a connection, we don't use this handler again. The only
342 reason for having this is so that we can present a more idiot-proof error
343 message than "cannot open display."
346 startup_ehandler (String name, String type, String class,
347 String defalt, /* one can't even spel properly
348 in this joke of a language */
349 String *av, Cardinal *ac)
353 saver_info *si = global_si_kludge; /* I hate C so much... */
354 XrmDatabase *db = XtAppGetErrorDatabase(si->app);
356 XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
357 fmt, sizeof(fmt)-1, *db);
359 fprintf (stderr, "%s: ", blurb());
361 memset (p, 0, sizeof(p));
362 if (*ac > countof (p)) *ac = countof (p);
363 memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
364 fprintf (stderr, fmt, /* Did I mention that I hate C? */
365 p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
366 fprintf (stderr, "\n");
368 describe_uids (si, stderr);
369 fprintf (stderr, "\n"
370 "%s: Errors at startup are usually authorization problems.\n"
371 " Did you read the manual? Specifically, the parts\n"
372 " that talk about XAUTH, XDM, and root logins?\n"
374 " http://www.jwz.org/xscreensaver/man.html\n"
384 /* The zillions of initializations.
387 /* Set progname, version, etc. This is done very early.
390 set_version_string (saver_info *si, int *argc, char **argv)
392 progclass = "XScreenSaver";
394 /* progname is reset later, after we connect to X. */
395 progname = strrchr(argv[0], '/');
396 if (progname) progname++;
397 else progname = argv[0];
399 if (strlen(progname) > 100) /* keep it short. */
402 /* The X resource database blows up if argv[0] has a "." in it. */
405 while ((s = strchr (s, '.')))
409 si->version = (char *) malloc (5);
410 memcpy (si->version, screensaver_id + 17, 4);
415 /* Initializations that potentially take place as a priveleged user:
416 If the xscreensaver executable is setuid root, then these initializations
417 are run as root, before discarding privileges.
420 privileged_initialization (saver_info *si, int *argc, char **argv)
423 si->locking_disabled_p = True;
424 si->nolock_reason = "not compiled with locking support";
425 #else /* !NO_LOCKING */
426 si->locking_disabled_p = False;
427 /* before hack_uid() for proper permissions */
428 if (! lock_init (*argc, argv, si->prefs.verbose_p))
430 si->locking_disabled_p = True;
431 si->nolock_reason = "error getting password";
433 #endif /* NO_LOCKING */
437 #endif /* NO_SETUID */
441 /* Open the connection to the X server, and intern our Atoms.
444 connect_to_server (saver_info *si, int *argc, char **argv)
446 Widget toplevel_shell;
448 XSetErrorHandler (saver_ehandler);
450 XtAppSetErrorMsgHandler (si->app, startup_ehandler);
451 toplevel_shell = XtAppInitialize (&si->app, progclass,
452 options, XtNumber (options),
453 argc, argv, defaults, 0, 0);
454 XtAppSetErrorMsgHandler (si->app, 0);
456 si->dpy = XtDisplay (toplevel_shell);
457 si->prefs.db = XtDatabase (si->dpy);
458 XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
460 if(strlen(progname) > 100) /* keep it short. */
463 db = si->prefs.db; /* resources.c needs this */
465 XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
466 XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
467 XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
468 XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
469 XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
470 XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
472 XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
473 XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
474 XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
475 XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
476 XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
477 XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
478 XA_PREV = XInternAtom (si->dpy, "PREV", False);
479 XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
480 XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
481 XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
482 XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
483 XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
485 return toplevel_shell;
489 /* Handle the command-line arguments that were not handled for us by Xt.
490 Issue an error message and exit if there are unknown options.
493 process_command_line (saver_info *si, int *argc, char **argv)
496 for (i = 1; i < *argc; i++)
498 if (!strcmp (argv[i], "-debug"))
499 /* no resource for this one, out of paranoia. */
500 si->prefs.debug_p = True;
502 else if (!strcmp (argv[i], "-h") ||
503 !strcmp (argv[i], "-help") ||
504 !strcmp (argv[i], "--help"))
509 const char *s = argv[i];
510 fprintf (stderr, "%s: unknown option \"%s\". Try \"-help\".\n",
513 if (s[0] == '-' && s[1] == '-') s++;
514 if (!strcmp (s, "-activate") ||
515 !strcmp (s, "-deactivate") ||
516 !strcmp (s, "-cycle") ||
517 !strcmp (s, "-next") ||
518 !strcmp (s, "-prev") ||
519 !strcmp (s, "-exit") ||
520 !strcmp (s, "-restart") ||
521 !strcmp (s, "-demo") ||
522 !strcmp (s, "-prefs") ||
523 !strcmp (s, "-preferences") ||
524 !strcmp (s, "-lock") ||
525 !strcmp (s, "-version") ||
526 !strcmp (s, "-time"))
529 if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
530 fprintf (stderr, "\n\
531 Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
533 fprintf (stderr, "\n\
534 However, `%s' is an option to the `xscreensaver-command' program.\n", s);
537 The `xscreensaver' program is a daemon that runs in the background.\n\
538 You control a running xscreensaver process by sending it messages\n\
539 with `xscreensaver-demo' or `xscreensaver-command'.\n\
540 . See the man pages for details, or check the web page:\n\
541 http://www.jwz.org/xscreensaver/\n\n");
543 /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
544 suggest that explicitly. */
545 if (!strcmp (s, "-lock"))
547 Or perhaps you meant either the \"-lock-mode\" or the\n\
548 \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
557 /* Print out the xscreensaver banner to the tty if applicable;
558 Issue any other warnings that are called for at this point.
561 print_banner (saver_info *si)
563 saver_preferences *p = &si->prefs;
565 /* This resource gets set some time before the others, so that we know
566 whether to print the banner (and so that the banner gets printed before
567 any resource-database-related error messages.)
569 p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
571 /* Ditto, for the locking_disabled_p message. */
572 p->lock_p = get_boolean_resource ("lock", "Boolean");
576 "%s %s, copyright (c) 1991-1999 "
577 "by Jamie Zawinski <jwz@jwz.org>.\n",
578 progname, si->version);
581 fprintf (stderr, "\n"
582 "%s: Warning: running in DEBUG MODE. Be afraid.\n"
584 "\tNote that in debug mode, the xscreensaver window will only\n"
585 "\tcover the left half of the screen. (The idea is that you\n"
586 "\tcan still see debugging output in a shell, if you position\n"
587 "\tit on the right side of the screen.)\n"
589 "\tDebug mode is NOT SECURE. Do not run with -debug in\n"
590 "\tuntrusted environments.\n"
596 if (!si->uid_message || !*si->uid_message)
597 describe_uids (si, stderr);
600 if (si->orig_uid && *si->orig_uid)
601 fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
602 blurb(), si->orig_uid);
603 fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
606 fprintf (stderr, "%s: in process %lu.\n", blurb(),
607 (unsigned long) getpid());
610 /* If locking was not able to be initalized for some reason, explain why.
611 (This has to be done after we've read the lock_p resource.)
613 if (p->lock_p && si->locking_disabled_p)
616 fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
618 if (strstr (si->nolock_reason, "passw"))
619 fprintf (stderr, "%s: does xscreensaver need to be setuid? "
620 "consult the manual.\n", blurb());
621 else if (strstr (si->nolock_reason, "running as "))
623 "%s: locking only works when xscreensaver is launched\n"
624 "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
625 "\t See the manual for details.\n",
631 /* Examine all of the display's screens, and populate the `saver_screen_info'
635 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
637 Bool found_any_writable_cells = False;
640 si->nscreens = ScreenCount(si->dpy);
641 si->screens = (saver_screen_info *)
642 calloc(sizeof(saver_screen_info), si->nscreens);
644 si->default_screen = &si->screens[DefaultScreen(si->dpy)];
646 for (i = 0; i < si->nscreens; i++)
648 saver_screen_info *ssi = &si->screens[i];
650 ssi->screen = ScreenOfDisplay (si->dpy, i);
652 /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
653 ssi->default_visual =
654 get_visual_resource (ssi->screen, "visualID", "VisualID", False);
656 ssi->current_visual = ssi->default_visual;
657 ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
659 if (ssi == si->default_screen)
660 /* Since this is the default screen, use the one already created. */
661 ssi->toplevel_shell = toplevel_shell;
663 /* Otherwise, each screen must have its own unmapped root widget. */
664 ssi->toplevel_shell =
665 XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
667 XtNscreen, ssi->screen,
668 XtNvisual, ssi->current_visual,
669 XtNdepth, visual_depth (ssi->screen,
670 ssi->current_visual),
673 if (! found_any_writable_cells)
675 /* Check to see whether fading is ever possible -- if any of the
676 screens on the display has a PseudoColor visual, then fading can
677 work (on at least some screens.) If no screen has a PseudoColor
678 visual, then don't bother ever trying to fade, because it will
679 just cause a delay without causing any visible effect.
681 if (has_writable_cells (ssi->screen, ssi->current_visual) ||
682 get_visual (ssi->screen, "PseudoColor", True, False) ||
683 get_visual (ssi->screen, "GrayScale", True, False))
684 found_any_writable_cells = True;
688 si->prefs.fading_possible_p = found_any_writable_cells;
692 /* If any server extensions have been requested, try and initialize them.
693 Issue warnings if requests can't be honored.
696 initialize_server_extensions (saver_info *si)
698 saver_preferences *p = &si->prefs;
700 Bool server_has_xidle_extension_p = False;
701 Bool server_has_sgi_saver_extension_p = False;
702 Bool server_has_mit_saver_extension_p = False;
703 Bool system_has_proc_interrupts_p = False;
704 const char *piwhy = 0;
706 si->using_xidle_extension = p->use_xidle_extension;
707 si->using_sgi_saver_extension = p->use_sgi_saver_extension;
708 si->using_mit_saver_extension = p->use_mit_saver_extension;
709 si->using_proc_interrupts = p->use_proc_interrupts;
711 #ifdef HAVE_XIDLE_EXTENSION
712 server_has_xidle_extension_p = query_xidle_extension (si);
714 #ifdef HAVE_SGI_SAVER_EXTENSION
715 server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
717 #ifdef HAVE_MIT_SAVER_EXTENSION
718 server_has_mit_saver_extension_p = query_mit_saver_extension (si);
720 #ifdef HAVE_PROC_INTERRUPTS
721 system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
724 if (!server_has_xidle_extension_p)
725 si->using_xidle_extension = False;
726 else if (p->verbose_p)
728 if (si->using_xidle_extension)
729 fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
731 fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
734 if (!server_has_sgi_saver_extension_p)
735 si->using_sgi_saver_extension = False;
736 else if (p->verbose_p)
738 if (si->using_sgi_saver_extension)
739 fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
742 "%s: not using server's SGI SCREEN_SAVER extension.\n",
746 if (!server_has_mit_saver_extension_p)
747 si->using_mit_saver_extension = False;
748 else if (p->verbose_p)
750 if (si->using_mit_saver_extension)
751 fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
755 "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
759 if (!system_has_proc_interrupts_p)
761 si->using_proc_interrupts = False;
762 if (p->verbose_p && piwhy)
763 fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
766 else if (p->verbose_p)
768 if (si->using_proc_interrupts)
770 "%s: consulting /proc/interrupts for keyboard activity.\n",
774 "%s: not consulting /proc/interrupts for keyboard activity.\n",
780 /* For the case where we aren't using an server extensions, select user events
781 on all the existing windows, and launch timers to select events on
782 newly-created windows as well.
784 If a server extension is being used, this does nothing.
787 select_events (saver_info *si)
789 saver_preferences *p = &si->prefs;
792 if (si->using_xidle_extension ||
793 si->using_mit_saver_extension ||
794 si->using_sgi_saver_extension)
797 if (p->initial_delay)
801 fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
802 (int) p->initial_delay/1000,
803 (p->initial_delay == 1000 ? "" : "s"));
807 usleep (p->initial_delay);
809 fprintf (stderr, " done.\n");
814 fprintf (stderr, "%s: selecting events on extant windows...", blurb());
819 /* Select events on the root windows of every screen. This also selects
820 for window creation events, so that new subwindows will be noticed.
822 for (i = 0; i < si->nscreens; i++)
823 start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
827 fprintf (stderr, " done.\n");
832 maybe_reload_init_file (saver_info *si)
834 saver_preferences *p = &si->prefs;
835 if (init_file_changed_p (p))
838 fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
839 blurb(), init_file_name());
843 /* If a server extension is in use, and p->timeout has changed,
844 we need to inform the server of the new timeout. */
845 disable_builtin_screensaver (si, False);
852 - wait until the user is idle;
854 - wait until the user is active;
855 - unblank the screen;
860 main_loop (saver_info *si)
862 saver_preferences *p = &si->prefs;
867 sleep_until_idle (si, True);
872 fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
873 si->selection_mode, timestring());
876 fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
880 maybe_reload_init_file (si);
882 if (! blank_screen (si))
884 /* We were unable to grab either the keyboard or mouse.
885 This means we did not (and must not) blank the screen.
886 If we were to blank the screen while some other program
887 is holding both the mouse and keyboard grabbed, then
888 we would never be able to un-blank it! We would never
889 see any events, and the display would be wedged.
891 So, just go around the loop again and wait for the
892 next bout of idleness.
896 "%s: unable to grab keyboard or mouse! Blanking aborted.\n",
897 blurb(), timestring ());
901 kill_screenhack (si);
902 spawn_screenhack (si, True);
904 /* Don't start the cycle timer in demo mode. */
905 if (!si->demoing_p && p->cycle)
906 si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
911 if (!si->demoing_p && /* if not going into demo mode */
912 p->lock_p && /* and locking is enabled */
913 !si->locking_disabled_p && /* and locking is possible */
914 p->lock_timeout == 0) /* and locking is not timer-deferred */
915 si->locked_p = True; /* then lock right now. */
917 /* locked_p might be true already because of the above, or because of
918 the LOCK ClientMessage. But if not, and if we're supposed to lock
919 after some time, set up a timer to do so.
924 si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
927 #endif /* !NO_LOCKING */
930 ok_to_unblank = True;
933 sleep_until_idle (si, False); /* until not idle */
934 maybe_reload_init_file (si);
939 saver_screen_info *ssi = si->default_screen;
940 if (si->locking_disabled_p) abort ();
942 si->dbox_up_p = True;
943 suspend_screenhack (si, True);
944 XUndefineCursor (si->dpy, ssi->screensaver_window);
946 ok_to_unblank = unlock_p (si);
948 si->dbox_up_p = False;
949 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
950 suspend_screenhack (si, False); /* resume */
952 #endif /* !NO_LOCKING */
954 } while (!ok_to_unblank);
958 fprintf (stderr, "%s: unblanking screen at %s.\n",
959 blurb(), timestring ());
961 /* Kill before unblanking, to stop drawing as soon as possible. */
962 kill_screenhack (si);
965 si->locked_p = False;
967 si->selection_mode = 0;
971 XtRemoveTimeOut (si->cycle_id);
977 XtRemoveTimeOut (si->lock_id);
982 fprintf (stderr, "%s: awaiting idleness.\n", blurb());
986 static void analyze_display (saver_info *si);
989 main (int argc, char **argv)
993 saver_info *si = &the_si;
994 saver_preferences *p = &si->prefs;
997 memset(si, 0, sizeof(*si));
998 global_si_kludge = si; /* I hate C so much... */
1000 srandom ((int) time ((time_t *) 0));
1002 save_argv (argc, argv);
1003 set_version_string (si, &argc, argv);
1004 privileged_initialization (si, &argc, argv);
1005 hack_environment (si);
1007 shell = connect_to_server (si, &argc, argv);
1008 process_command_line (si, &argc, argv);
1011 initialize_per_screen_info (si, shell); /* also sets p->fading_possible_p */
1013 for (i = 0; i < si->nscreens; i++)
1014 if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1019 if (p->xsync_p) XSynchronize (si->dpy, True);
1020 blurb_timestamp_p = p->timestamp_p; /* kludge */
1022 if (p->verbose_p) analyze_display (si);
1023 initialize_server_extensions (si);
1024 initialize_screensaver_window (si);
1027 disable_builtin_screensaver (si, True);
1028 initialize_stderr (si);
1030 make_splash_dialog (si);
1032 main_loop (si); /* doesn't return */
1037 /* Processing ClientMessage events.
1041 clientmessage_response (saver_info *si, Window w, Bool error,
1042 const char *stderr_msg,
1043 const char *protocol_msg)
1047 saver_preferences *p = &si->prefs;
1048 if (error || p->verbose_p)
1049 fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1051 L = strlen(protocol_msg);
1052 proto = (char *) malloc (L + 2);
1053 proto[0] = (error ? '-' : '+');
1054 strcpy (proto+1, protocol_msg);
1057 XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1058 PropModeReplace, (unsigned char *) proto, L);
1059 XSync (si->dpy, False);
1064 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1067 Window window = event->xclient.window;
1069 /* Preferences might affect our handling of client messages. */
1070 maybe_reload_init_file (si);
1072 if (event->xclient.message_type != XA_SCREENSAVER)
1075 str = XGetAtomName (si->dpy, event->xclient.message_type);
1076 fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1077 blurb(), (str ? str : "(null)"));
1078 if (str) XFree (str);
1081 if (event->xclient.format != 32)
1083 fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1084 blurb(), event->xclient.format);
1088 type = event->xclient.data.l[0];
1089 if (type == XA_ACTIVATE)
1093 clientmessage_response(si, window, False,
1094 "ACTIVATE ClientMessage received.",
1096 si->selection_mode = 0;
1097 si->demoing_p = False;
1098 if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1100 XForceScreenSaver (si->dpy, ScreenSaverActive);
1108 clientmessage_response(si, window, True,
1109 "ClientMessage ACTIVATE received while already active.",
1112 else if (type == XA_DEACTIVATE)
1116 clientmessage_response(si, window, False,
1117 "DEACTIVATE ClientMessage received.",
1119 if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1121 XForceScreenSaver (si->dpy, ScreenSaverReset);
1129 clientmessage_response(si, window, True,
1130 "ClientMessage DEACTIVATE received while inactive.",
1133 else if (type == XA_CYCLE)
1137 clientmessage_response(si, window, False,
1138 "CYCLE ClientMessage received.",
1140 si->selection_mode = 0; /* 0 means randomize when its time. */
1141 si->demoing_p = False;
1143 XtRemoveTimeOut (si->cycle_id);
1145 cycle_timer ((XtPointer) si, 0);
1148 clientmessage_response(si, window, True,
1149 "ClientMessage CYCLE received while inactive.",
1152 else if (type == XA_NEXT || type == XA_PREV)
1154 clientmessage_response(si, window, False,
1156 ? "NEXT ClientMessage received."
1157 : "PREV ClientMessage received."),
1159 si->selection_mode = (type == XA_NEXT ? -1 : -2);
1160 si->demoing_p = False;
1165 XtRemoveTimeOut (si->cycle_id);
1167 cycle_timer ((XtPointer) si, 0);
1172 else if (type == XA_SELECT)
1176 long which = event->xclient.data.l[1];
1178 sprintf (buf, "SELECT %ld ClientMessage received.", which);
1179 sprintf (buf2, "activating (%ld).", which);
1180 clientmessage_response (si, window, False, buf, buf2);
1182 if (which < 0) which = 0; /* 0 == "random" */
1183 si->selection_mode = which;
1184 si->demoing_p = False;
1189 XtRemoveTimeOut (si->cycle_id);
1191 cycle_timer ((XtPointer) si, 0);
1196 else if (type == XA_EXIT)
1198 /* Ignore EXIT message if the screen is locked. */
1199 if (until_idle_p || !si->locked_p)
1201 clientmessage_response (si, window, False,
1202 "EXIT ClientMessage received.",
1206 unblank_screen (si);
1207 kill_screenhack (si);
1208 XSync (si->dpy, False);
1210 saver_exit (si, 0, 0);
1213 clientmessage_response (si, window, True,
1214 "EXIT ClientMessage received while locked.",
1215 "screen is locked.");
1217 else if (type == XA_RESTART)
1219 /* The RESTART message works whether the screensaver is active or not,
1220 unless the screen is locked, in which case it doesn't work.
1222 if (until_idle_p || !si->locked_p)
1224 clientmessage_response (si, window, False,
1225 "RESTART ClientMessage received.",
1229 unblank_screen (si);
1230 kill_screenhack (si);
1231 XSync (si->dpy, False);
1234 /* make sure error message shows up before exit. */
1235 if (real_stderr && stderr != real_stderr)
1236 dup2 (fileno(real_stderr), fileno(stderr));
1238 restart_process (si);
1239 exit (1); /* shouldn't get here; but if restarting didn't work,
1240 make this command be the same as EXIT. */
1243 clientmessage_response (si, window, True,
1244 "RESTART ClientMessage received while locked.",
1245 "screen is locked.");
1247 else if (type == XA_DEMO)
1249 long arg = event->xclient.data.l[1];
1250 Bool demo_one_hack_p = (arg == 300);
1252 if (demo_one_hack_p)
1256 long which = event->xclient.data.l[2];
1259 sprintf (buf, "DEMO %ld ClientMessage received.", which);
1260 sprintf (buf2, "demoing (%ld).", which);
1261 clientmessage_response (si, window, False, buf, buf2);
1263 if (which < 0) which = 0; /* 0 == "random" */
1264 si->selection_mode = which;
1265 si->demoing_p = True;
1270 clientmessage_response (si, window, True,
1271 "DEMO ClientMessage received while active.",
1276 clientmessage_response (si, window, True,
1277 "obsolete form of DEMO ClientMessage.",
1278 "obsolete form of DEMO ClientMessage.");
1281 else if (type == XA_PREFS)
1283 clientmessage_response (si, window, True,
1284 "the PREFS client-message is obsolete.",
1285 "the PREFS client-message is obsolete.");
1287 else if (type == XA_LOCK)
1290 clientmessage_response (si, window, True,
1291 "not compiled with support for locking.",
1292 "locking not enabled.");
1293 #else /* !NO_LOCKING */
1294 if (si->locking_disabled_p)
1295 clientmessage_response (si, window, True,
1296 "LOCK ClientMessage received, but locking is disabled.",
1297 "locking not enabled.");
1298 else if (si->locked_p)
1299 clientmessage_response (si, window, True,
1300 "LOCK ClientMessage received while already locked.",
1305 char *response = (until_idle_p
1306 ? "activating and locking."
1308 si->locked_p = True;
1309 si->selection_mode = 0;
1310 si->demoing_p = False;
1311 sprintf (buf, "LOCK ClientMessage received; %s", response);
1312 clientmessage_response (si, window, False, buf, response);
1314 if (si->lock_id) /* we're doing it now, so lose the timeout */
1316 XtRemoveTimeOut (si->lock_id);
1322 if (si->using_mit_saver_extension ||
1323 si->using_sgi_saver_extension)
1325 XForceScreenSaver (si->dpy, ScreenSaverActive);
1334 #endif /* !NO_LOCKING */
1340 str = (type ? XGetAtomName(si->dpy, type) : 0);
1344 if (strlen (str) > 80)
1345 strcpy (str+70, "...");
1346 sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1353 "unrecognised screensaver ClientMessage 0x%x received.",
1354 (unsigned int) event->xclient.data.l[0]);
1357 clientmessage_response (si, window, True, buf, buf);
1363 /* Some random diagnostics printed in -verbose mode.
1367 analyze_display (saver_info *si)
1370 static const char *exts[][2] = {
1371 { "SCREEN_SAVER", "SGI Screen-Saver" },
1372 { "SCREEN-SAVER", "SGI Screen-Saver" },
1373 { "MIT-SCREEN-SAVER", "MIT Screen-Saver" },
1374 { "XIDLE", "XIdle" },
1375 { "SGI-VIDEO-CONTROL", "SGI Video-Control" },
1376 { "READDISPLAY", "SGI Read-Display" },
1377 { "MIT-SHM", "Shared Memory" },
1378 { "DOUBLE-BUFFER", "Double-Buffering" },
1379 { "DPMS", "Power Management" },
1383 fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1384 DisplayString(si->dpy));
1385 fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1386 ServerVendor(si->dpy), VendorRelease(si->dpy));
1388 fprintf (stderr, "%s: useful extensions:\n", blurb());
1389 for (i = 0; i < countof(exts); i++)
1391 int op = 0, event = 0, error = 0;
1392 if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error))
1393 fprintf (stderr, "%s: %s\n", blurb(), exts[i][1]);
1396 for (i = 0; i < si->nscreens; i++)
1398 unsigned long colormapped_depths = 0;
1399 unsigned long non_mapped_depths = 0;
1400 XVisualInfo vi_in, *vi_out;
1403 vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1404 if (!vi_out) continue;
1405 for (j = 0; j < out_count; j++)
1406 if (vi_out[j].class == PseudoColor)
1407 colormapped_depths |= (1 << vi_out[j].depth);
1409 non_mapped_depths |= (1 << vi_out[j].depth);
1410 XFree ((char *) vi_out);
1412 if (colormapped_depths)
1414 fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1415 for (j = 0; j < 32; j++)
1416 if (colormapped_depths & (1 << j))
1417 fprintf (stderr, " %d", j);
1418 fprintf (stderr, "\n");
1420 if (non_mapped_depths)
1422 fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1423 for (j = 0; j < 32; j++)
1424 if (non_mapped_depths & (1 << j))
1425 fprintf (stderr, " %d", j);
1426 fprintf (stderr, "\n");
1432 display_is_on_console_p (saver_info *si)
1434 Bool not_on_console = True;
1435 char *dpystr = DisplayString (si->dpy);
1436 char *tail = (char *) strchr (dpystr, ':');
1437 if (! tail || strncmp (tail, ":0", 2))
1438 not_on_console = True;
1441 char dpyname[255], localname[255];
1442 strncpy (dpyname, dpystr, tail-dpystr);
1443 dpyname [tail-dpystr] = 0;
1445 !strcmp(dpyname, "unix") ||
1446 !strcmp(dpyname, "localhost"))
1447 not_on_console = False;
1448 else if (gethostname (localname, sizeof (localname)))
1449 not_on_console = True; /* can't find hostname? */
1452 /* We have to call gethostbyname() on the result of gethostname()
1453 because the two aren't guarenteed to be the same name for the
1454 same host: on some losing systems, one is a FQDN and the other
1455 is not. Here in the wide wonderful world of Unix it's rocket
1456 science to obtain the local hostname in a portable fashion.
1458 And don't forget, gethostbyname() reuses the structure it
1459 returns, so we have to copy the fucker before calling it again.
1460 Thank you master, may I have another.
1462 struct hostent *h = gethostbyname (dpyname);
1464 not_on_console = True;
1469 strcpy (hn, h->h_name);
1470 l = gethostbyname (localname);
1471 not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1475 return !not_on_console;