000b9d91d694b171fa0d0ded9539c75368b41669
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-1999 Jamie Zawinski <jwz@jwz.org>
2  *
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 
9  * implied warranty.
10  */
11
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.
18  *
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.)
21  *
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.
24  *
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.)
29  *
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
35  *   running.
36  *
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.
40  *
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.
45  *
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".
58  *
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.
64  *
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.
74  *   
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.
84  *
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.
93  *
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.
97  *
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.
103  *
104  *   Debugging hints:
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
108  *       window exposed.
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."
119  *       
120  * ======================================================================== */
121
122 #ifdef HAVE_CONFIG_H
123 # include "config.h"
124 #endif
125
126 #include <stdio.h>
127 #include <ctype.h>
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>
133 #include <X11/Xos.h>
134 #ifdef HAVE_XMU
135 # ifndef VMS
136 #  include <X11/Xmu/Error.h>
137 # else  /* !VMS */
138 #  include <Xmu/Error.h>
139 # endif /* !VMS */
140 #else  /* !HAVE_XMU */
141 # include "xmu.h"
142 #endif /* !HAVE_XMU */
143
144 #ifdef HAVE_XIDLE_EXTENSION
145 #include <X11/extensions/xidle.h>
146 #endif /* HAVE_XIDLE_EXTENSION */
147
148 #include "xscreensaver.h"
149 #include "version.h"
150 #include "yarandom.h"
151 #include "resources.h"
152 #include "visual.h"
153 #include "usleep.h"
154
155 saver_info *global_si_kludge = 0;       /* I hate C so much... */
156
157 char *progname = 0;
158 char *progclass = 0;
159 XrmDatabase db = 0;
160
161
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;
166
167 \f
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 },
196
197   /* Actually these are built in to Xt, but just to be sure... */
198   { "-synchronous",        ".synchronous",      XrmoptionNoArg, "on" },
199   { "-xrm",                NULL,                XrmoptionResArg, NULL }
200 };
201
202 static char *defaults[] = {
203 #include "XScreenSaver_ad.h"
204  0
205 };
206
207 #ifdef _VROOT_H_
208 ERROR!  You must not include vroot.h in this file.
209 #endif
210
211 static void
212 do_help (saver_info *si)
213 {
214   fflush (stdout);
215   fflush (stderr);
216   fprintf (stdout, "\
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\
219 \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\
226     -verbose                 Be loud.\n\
227     -no-splash               Don't display a splash-screen at startup.\n\
228     -help                    This message.\n\
229 \n\
230 See the manual for other options and X resources.\n\
231 \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\
235 \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\
238 \n\
239 Just getting started?  Try this:\n\
240 \n\
241         xscreensaver &\n\
242         xscreensaver-demo\n\
243 \n\
244 For updates, check http://www.jwz.org/xscreensaver/\n\
245 \n",
246           si->version);
247   fflush (stdout);
248   fflush (stderr);
249   exit (1);
250 }
251
252
253 char *
254 timestring (void)
255 {
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 */
260   return str;
261 }
262
263 static Bool blurb_timestamp_p = False;   /* kludge */
264
265 const char *
266 blurb (void)
267 {
268   if (!blurb_timestamp_p)
269     return progname;
270   else
271     {
272       static char buf[255];
273       char *ct = timestring();
274       int n = strlen(progname);
275       if (n > 100) n = 99;
276       strncpy(buf, progname, n);
277       buf[n++] = ':';
278       buf[n++] = ' ';
279       strncpy(buf+n, ct+11, 8);
280       strcpy(buf+n+9, ": ");
281       return buf;
282     }
283 }
284
285
286 int
287 saver_ehandler (Display *dpy, XErrorEvent *error)
288 {
289   saver_info *si = global_si_kludge;    /* I hate C so much... */
290
291   if (!real_stderr) real_stderr = stderr;
292
293   fprintf (real_stderr, "\n"
294            "#######################################"
295            "#######################################\n\n"
296            "%s: X Error!  PLEASE REPORT THIS BUG.\n\n"
297            "#######################################"
298            "#######################################\n\n",
299            blurb());
300   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
301     {
302       fprintf (real_stderr, "\n");
303       if (si->prefs.xsync_p)
304         {
305           saver_exit (si, -1, "because of synchronous X Error");
306         }
307       else
308         {
309           fprintf (real_stderr,
310                    "#######################################"
311                    "#######################################\n\n");
312           fprintf (real_stderr,
313    "    If at all possible, please re-run xscreensaver with the command line\n"
314    "    arguments `-sync -verbose', and reproduce this bug.  That will cause\n"
315    "    xscreensaver to dump a `core' file to the current directory.  Please\n"
316    "    include the stack trace from that core file in your bug report.\n"
317    "\n"
318    "    http://www.jwz.org/xscreensaver/bugs.html explains how to create the\n"
319    "    most useful bug reports, and how to examine core files.\n"
320    "\n"
321    "    The more information you can provide, the better.  But please report\n"
322    "    report this bug, regardless!\n"
323    "\n");
324           fprintf (real_stderr,
325                    "#######################################"
326                    "#######################################\n\n");
327
328           saver_exit (si, -1, 0);
329         }
330     }
331   else
332     fprintf (real_stderr, " (nonfatal.)\n");
333   return 0;
334 }
335
336
337 /* This error handler is used only while the X connection is being set up;
338    after we've got a connection, we don't use this handler again.  The only
339    reason for having this is so that we can present a more idiot-proof error
340    message than "cannot open display."
341  */
342 static void 
343 startup_ehandler (String name, String type, String class,
344                   String defalt,  /* one can't even spel properly
345                                      in this joke of a language */
346                   String *av, Cardinal *ac)
347 {
348   char fmt[512];
349   String p[10];
350   saver_info *si = global_si_kludge;    /* I hate C so much... */
351   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
352   *fmt = 0;
353   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
354                             fmt, sizeof(fmt)-1, *db);
355
356   fprintf (stderr, "%s: ", blurb());
357
358   memset (p, 0, sizeof(p));
359   if (*ac > countof (p)) *ac = countof (p);
360   memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
361   fprintf (stderr, fmt,         /* Did I mention that I hate C? */
362            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
363   fprintf (stderr, "\n");
364
365   describe_uids (si, stderr);
366   fprintf (stderr, "\n"
367            "%s: Errors at startup are usually authorization problems.\n"
368            "              Did you read the manual?  Specifically, the parts\n"
369            "              that talk about XAUTH, XDM, and root logins?\n"
370            "\n"
371            "              http://www.jwz.org/xscreensaver/man.html\n"
372            "\n",
373            blurb());
374
375   fflush (stderr);
376   fflush (stdout);
377   exit (1);
378 }
379
380 \f
381 /* The zillions of initializations.
382  */
383
384 /* Set progname, version, etc.  This is done very early.
385  */
386 static void
387 set_version_string (saver_info *si, int *argc, char **argv)
388 {
389   progclass = "XScreenSaver";
390
391   /* progname is reset later, after we connect to X. */
392   progname = strrchr(argv[0], '/');
393   if (progname) progname++;
394   else progname = argv[0];
395
396   if (strlen(progname) > 100)   /* keep it short. */
397     progname[99] = 0;
398
399   /* The X resource database blows up if argv[0] has a "." in it. */
400   {
401     char *s = argv[0];
402     while ((s = strchr (s, '.')))
403       *s = '_';
404   }
405
406   si->version = (char *) malloc (5);
407   memcpy (si->version, screensaver_id + 17, 4);
408   si->version [4] = 0;
409 }
410
411
412 /* Initializations that potentially take place as a priveleged user:
413    If the xscreensaver executable is setuid root, then these initializations
414    are run as root, before discarding privileges.
415  */
416 static void
417 privileged_initialization (saver_info *si, int *argc, char **argv)
418 {
419 #ifdef NO_LOCKING
420   si->locking_disabled_p = True;
421   si->nolock_reason = "not compiled with locking support";
422 #else /* !NO_LOCKING */
423   si->locking_disabled_p = False;
424   /* before hack_uid() for proper permissions */
425   if (! lock_init (*argc, argv, si->prefs.verbose_p))
426     {
427       si->locking_disabled_p = True;
428       si->nolock_reason = "error getting password";
429     }
430 #endif /* NO_LOCKING */
431
432 #ifndef NO_SETUID
433   hack_uid (si);
434 #endif /* NO_SETUID */
435 }
436
437
438 /* Open the connection to the X server, and intern our Atoms.
439  */
440 static Widget
441 connect_to_server (saver_info *si, int *argc, char **argv)
442 {
443   Widget toplevel_shell;
444
445   XSetErrorHandler (saver_ehandler);
446
447   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
448   toplevel_shell = XtAppInitialize (&si->app, progclass,
449                                     options, XtNumber (options),
450                                     argc, argv, defaults, 0, 0);
451   XtAppSetErrorMsgHandler (si->app, 0);
452
453   si->dpy = XtDisplay (toplevel_shell);
454   si->prefs.db = XtDatabase (si->dpy);
455   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
456
457   if(strlen(progname) > 100)    /* keep it short. */
458     progname [99] = 0;
459
460   db = si->prefs.db;    /* resources.c needs this */
461
462   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
463   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
464   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
465   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
466   XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
467   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
468                                          False);
469   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
470   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
471   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
472   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
473   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
474   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
475   XA_PREV = XInternAtom (si->dpy, "PREV", False);
476   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
477   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
478   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
479   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
480   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
481
482   return toplevel_shell;
483 }
484
485
486 /* Handle the command-line arguments that were not handled for us by Xt.
487    Issue an error message and exit if there are unknown options.
488  */
489 static void
490 process_command_line (saver_info *si, int *argc, char **argv)
491 {
492   int i;
493   for (i = 1; i < *argc; i++)
494     {
495       if (!strcmp (argv[i], "-debug"))
496         /* no resource for this one, out of paranoia. */
497         si->prefs.debug_p = True;
498
499       else if (!strcmp (argv[i], "-h") ||
500                !strcmp (argv[i], "-help") ||
501                !strcmp (argv[i], "--help"))
502         do_help (si);
503
504       else
505         {
506           const char *s = argv[i];
507           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
508                    blurb(), s);
509
510           if (s[0] == '-' && s[1] == '-') s++;
511           if (!strcmp (s, "-activate") ||
512               !strcmp (s, "-deactivate") ||
513               !strcmp (s, "-cycle") ||
514               !strcmp (s, "-next") ||
515               !strcmp (s, "-prev") ||
516               !strcmp (s, "-exit") ||
517               !strcmp (s, "-restart") ||
518               !strcmp (s, "-demo") ||
519               !strcmp (s, "-prefs") ||
520               !strcmp (s, "-preferences") ||
521               !strcmp (s, "-lock") ||
522               !strcmp (s, "-version") ||
523               !strcmp (s, "-time"))
524             {
525
526               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
527                 fprintf (stderr, "\n\
528     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
529               else
530                 fprintf (stderr, "\n\
531     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
532
533               fprintf (stderr, "\
534     The `xscreensaver' program is a daemon that runs in the background.\n\
535     You control a running xscreensaver process by sending it messages\n\
536     with `xscreensaver-demo' or `xscreensaver-command'.\n\
537 .   See the man pages for details, or check the web page:\n\
538     http://www.jwz.org/xscreensaver/\n\n");
539
540               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
541                  suggest that explicitly. */
542               if (!strcmp (s, "-lock"))
543                 fprintf (stderr, "\
544     Or perhaps you meant either the \"-lock-mode\" or the\n\
545     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
546             }
547
548           exit (1);
549         }
550     }
551 }
552
553
554 /* Print out the xscreensaver banner to the tty if applicable;
555    Issue any other warnings that are called for at this point.
556  */
557 static void
558 print_banner (saver_info *si)
559 {
560   saver_preferences *p = &si->prefs;
561
562   /* This resource gets set some time before the others, so that we know
563      whether to print the banner (and so that the banner gets printed before
564      any resource-database-related error messages.)
565    */
566   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
567
568   /* Ditto, for the locking_disabled_p message. */
569   p->lock_p = get_boolean_resource ("lock", "Boolean");
570
571   if (p->verbose_p)
572     fprintf (stderr,
573              "%s %s, copyright (c) 1991-1998 "
574              "by Jamie Zawinski <jwz@jwz.org>.\n",
575              progname, si->version);
576
577   if (p->debug_p)
578     fprintf (stderr, "\n"
579              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
580              "\n"
581              "\tNote that in debug mode, the xscreensaver window will only\n"
582              "\tcover the left half of the screen.  (The idea is that you\n"
583              "\tcan still see debugging output in a shell, if you position\n"
584              "\tit on the right side of the screen.)\n"
585              "\n"
586              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
587              "\tuntrusted environments.\n"
588              "\n",
589              blurb());
590
591   if (p->verbose_p)
592     {
593       if (!si->uid_message || !*si->uid_message)
594         describe_uids (si, stderr);
595       else
596         {
597           if (si->orig_uid && *si->orig_uid)
598             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
599                      blurb(), si->orig_uid);
600           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
601         }
602
603       fprintf (stderr, "%s: in process %lu.\n", blurb(),
604                (unsigned long) getpid());
605     }
606
607   /* If locking was not able to be initalized for some reason, explain why.
608      (This has to be done after we've read the lock_p resource.)
609    */
610   if (p->lock_p && si->locking_disabled_p)
611     {
612       p->lock_p = False;
613       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
614                si->nolock_reason);
615       if (strstr (si->nolock_reason, "passw"))
616         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
617                  "consult the manual.\n", blurb());
618       else if (strstr (si->nolock_reason, "running as "))
619         fprintf (stderr, 
620                  "%s: locking only works when xscreensaver is launched\n"
621                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
622                  "\t See the manual for details.\n",
623                  blurb());
624     }
625 }
626
627
628 /* Examine all of the display's screens, and populate the `saver_screen_info'
629    structures.
630  */
631 static void
632 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
633 {
634   Bool found_any_writable_cells = False;
635   int i;
636
637   si->nscreens = ScreenCount(si->dpy);
638   si->screens = (saver_screen_info *)
639     calloc(sizeof(saver_screen_info), si->nscreens);
640
641   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
642
643   for (i = 0; i < si->nscreens; i++)
644     {
645       saver_screen_info *ssi = &si->screens[i];
646       ssi->global = si;
647       ssi->screen = ScreenOfDisplay (si->dpy, i);
648
649       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
650       ssi->default_visual =
651         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
652
653       ssi->current_visual = ssi->default_visual;
654       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
655
656       if (ssi == si->default_screen)
657         /* Since this is the default screen, use the one already created. */
658         ssi->toplevel_shell = toplevel_shell;
659       else
660         /* Otherwise, each screen must have its own unmapped root widget. */
661         ssi->toplevel_shell =
662           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
663                               si->dpy,
664                               XtNscreen, ssi->screen,
665                               XtNvisual, ssi->current_visual,
666                               XtNdepth,  visual_depth (ssi->screen,
667                                                        ssi->current_visual),
668                               0);
669
670       if (! found_any_writable_cells)
671         {
672           /* Check to see whether fading is ever possible -- if any of the
673              screens on the display has a PseudoColor visual, then fading can
674              work (on at least some screens.)  If no screen has a PseudoColor
675              visual, then don't bother ever trying to fade, because it will
676              just cause a delay without causing any visible effect.
677           */
678           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
679               get_visual (ssi->screen, "PseudoColor", True, False) ||
680               get_visual (ssi->screen, "GrayScale", True, False))
681             found_any_writable_cells = True;
682         }
683     }
684
685   si->prefs.fading_possible_p = found_any_writable_cells;
686 }
687
688
689 /* If any server extensions have been requested, try and initialize them.
690    Issue warnings if requests can't be honored.
691  */
692 static void
693 initialize_server_extensions (saver_info *si)
694 {
695   saver_preferences *p = &si->prefs;
696
697   Bool server_has_xidle_extension_p = False;
698   Bool server_has_sgi_saver_extension_p = False;
699   Bool server_has_mit_saver_extension_p = False;
700
701   si->using_xidle_extension = p->use_xidle_extension;
702   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
703   si->using_mit_saver_extension = p->use_mit_saver_extension;
704
705 #ifdef HAVE_XIDLE_EXTENSION
706   server_has_xidle_extension_p = query_xidle_extension (si);
707 #endif
708 #ifdef HAVE_SGI_SAVER_EXTENSION
709   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
710 #endif
711 #ifdef HAVE_MIT_SAVER_EXTENSION
712   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
713 #endif
714
715   if (!server_has_xidle_extension_p)
716     si->using_xidle_extension = False;
717   else if (p->verbose_p)
718     {
719       if (si->using_xidle_extension)
720         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
721       else
722         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
723     }
724
725   if (!server_has_sgi_saver_extension_p)
726     si->using_sgi_saver_extension = False;
727   else if (p->verbose_p)
728     {
729       if (si->using_sgi_saver_extension)
730         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
731       else
732         fprintf (stderr,
733                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
734                  blurb());
735     }
736
737   if (!server_has_mit_saver_extension_p)
738     si->using_mit_saver_extension = False;
739   else if (p->verbose_p)
740     {
741       if (si->using_mit_saver_extension)
742         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
743                  blurb());
744       else
745         fprintf (stderr,
746                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
747                  blurb());
748     }
749 }
750
751
752 /* For the case where we aren't using an server extensions, select user events
753    on all the existing windows, and launch timers to select events on
754    newly-created windows as well.
755
756    If a server extension is being used, this does nothing.
757  */
758 static void
759 select_events (saver_info *si)
760 {
761   saver_preferences *p = &si->prefs;
762   int i;
763
764   if (si->using_xidle_extension ||
765       si->using_mit_saver_extension ||
766       si->using_sgi_saver_extension)
767     return;
768
769   if (p->initial_delay)
770     {
771       if (p->verbose_p)
772         {
773           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
774                    (int) p->initial_delay/1000,
775                    (p->initial_delay == 1000 ? "" : "s"));
776           fflush (stderr);
777           fflush (stdout);
778         }
779       usleep (p->initial_delay);
780       if (p->verbose_p)
781         fprintf (stderr, " done.\n");
782     }
783
784   if (p->verbose_p)
785     {
786       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
787       fflush (stderr);
788       fflush (stdout);
789     }
790
791   /* Select events on the root windows of every screen.  This also selects
792      for window creation events, so that new subwindows will be noticed.
793    */
794   for (i = 0; i < si->nscreens; i++)
795     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen));
796
797   if (p->verbose_p)
798     fprintf (stderr, " done.\n");
799 }
800
801
802 void
803 maybe_reload_init_file (saver_info *si)
804 {
805   saver_preferences *p = &si->prefs;
806   if (init_file_changed_p (p))
807     {
808       if (p->verbose_p)
809         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
810                  blurb(), init_file_name());
811
812       load_init_file (p);
813
814       /* If a server extension is in use, and p->timeout has changed,
815          we need to inform the server of the new timeout. */
816       disable_builtin_screensaver (si, False);
817     }
818 }
819
820
821 /* Loop forever:
822
823        - wait until the user is idle;
824        - blank the screen;
825        - wait until the user is active;
826        - unblank the screen;
827        - repeat.
828
829  */
830 static void
831 main_loop (saver_info *si)
832 {
833   saver_preferences *p = &si->prefs;
834   Bool ok_to_unblank;
835
836   while (1)
837     {
838       sleep_until_idle (si, True);
839
840       if (p->verbose_p)
841         {
842           if (si->demoing_p)
843             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
844                      si->selection_mode, timestring());
845           else
846             if (p->verbose_p)
847               fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
848                        timestring());
849         }
850
851       maybe_reload_init_file (si);
852
853       blank_screen (si);
854       kill_screenhack (si);
855       spawn_screenhack (si, True);
856
857       /* Don't start the cycle timer in demo mode. */
858       if (!si->demoing_p && p->cycle)
859         si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
860                                         (XtPointer) si);
861
862
863 #ifndef NO_LOCKING
864       if (!si->demoing_p &&             /* if not going into demo mode */
865           p->lock_p &&                  /* and locking is enabled */
866           !si->locking_disabled_p &&    /* and locking is possible */
867           p->lock_timeout == 0)         /* and locking is not timer-deferred */
868         si->locked_p = True;            /* then lock right now. */
869
870       /* locked_p might be true already because of the above, or because of
871          the LOCK ClientMessage.  But if not, and if we're supposed to lock
872          after some time, set up a timer to do so.
873        */
874       if (p->lock_p &&
875           !si->locked_p &&
876           p->lock_timeout > 0)
877         si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
878                                        activate_lock_timer,
879                                        (XtPointer) si);
880 #endif /* !NO_LOCKING */
881
882
883       ok_to_unblank = True;
884       do {
885
886         sleep_until_idle (si, False);           /* until not idle */
887         maybe_reload_init_file (si);
888
889 #ifndef NO_LOCKING
890         if (si->locked_p)
891           {
892             saver_screen_info *ssi = si->default_screen;
893             if (si->locking_disabled_p) abort ();
894
895             si->dbox_up_p = True;
896             suspend_screenhack (si, True);
897             XUndefineCursor (si->dpy, ssi->screensaver_window);
898
899             ok_to_unblank = unlock_p (si);
900
901             si->dbox_up_p = False;
902             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
903             suspend_screenhack (si, False);     /* resume */
904           }
905 #endif /* !NO_LOCKING */
906
907         } while (!ok_to_unblank);
908
909
910       if (p->verbose_p)
911         fprintf (stderr, "%s: unblanking screen at %s.\n",
912                  blurb(), timestring ());
913
914       /* Kill before unblanking, to stop drawing as soon as possible. */
915       kill_screenhack (si);
916       unblank_screen (si);
917
918       si->locked_p = False;
919       si->demoing_p = 0;
920       si->selection_mode = 0;
921
922       if (si->cycle_id)
923         {
924           XtRemoveTimeOut (si->cycle_id);
925           si->cycle_id = 0;
926         }
927
928       if (si->lock_id)
929         {
930           XtRemoveTimeOut (si->lock_id);
931           si->lock_id = 0;
932         }
933
934       if (p->verbose_p)
935         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
936     }
937 }
938
939 static void analyze_display (saver_info *si);
940
941 int
942 main (int argc, char **argv)
943 {
944   Widget shell;
945   saver_info the_si;
946   saver_info *si = &the_si;
947   saver_preferences *p = &si->prefs;
948   int i;
949
950   memset(si, 0, sizeof(*si));
951   global_si_kludge = si;        /* I hate C so much... */
952
953   srandom ((int) time ((time_t *) 0));
954
955   save_argv (argc, argv);
956   set_version_string (si, &argc, argv);
957   privileged_initialization (si, &argc, argv);
958   hack_environment (si);
959
960   shell = connect_to_server (si, &argc, argv);
961   process_command_line (si, &argc, argv);
962   print_banner (si);
963
964   initialize_per_screen_info (si, shell);  /* also sets p->fading_possible_p */
965
966   for (i = 0; i < si->nscreens; i++)
967     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
968       exit (1);
969
970   load_init_file (p);
971
972   if (p->xsync_p) XSynchronize (si->dpy, True);
973   blurb_timestamp_p = p->timestamp_p;  /* kludge */
974
975   if (p->verbose_p) analyze_display (si);
976   initialize_server_extensions (si);
977   initialize_screensaver_window (si);
978   select_events (si);
979   init_sigchld ();
980   disable_builtin_screensaver (si, True);
981   initialize_stderr (si);
982
983   make_splash_dialog (si);
984
985   main_loop (si);               /* doesn't return */
986   return 0;
987 }
988
989 \f
990 /* Processing ClientMessage events.
991  */
992
993 static void
994 clientmessage_response (saver_info *si, Window w, Bool error,
995                         const char *stderr_msg,
996                         const char *protocol_msg)
997 {
998   char *proto;
999   int L;
1000   saver_preferences *p = &si->prefs;
1001   if (error || p->verbose_p)
1002     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1003
1004   L = strlen(protocol_msg);
1005   proto = (char *) malloc (L + 2);
1006   proto[0] = (error ? '-' : '+');
1007   strcpy (proto+1, protocol_msg);
1008   L++;
1009
1010   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1011                    PropModeReplace, proto, L);
1012   XSync (si->dpy, False);
1013   free (proto);
1014 }
1015
1016 Bool
1017 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1018 {
1019   Atom type = 0;
1020   Window window = event->xclient.window;
1021
1022   /* Preferences might affect our handling of client messages. */
1023   maybe_reload_init_file (si);
1024
1025   if (event->xclient.message_type != XA_SCREENSAVER)
1026     {
1027       char *str;
1028       str = XGetAtomName (si->dpy, event->xclient.message_type);
1029       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1030                blurb(), (str ? str : "(null)"));
1031       if (str) XFree (str);
1032       return False;
1033     }
1034   if (event->xclient.format != 32)
1035     {
1036       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1037                blurb(), event->xclient.format);
1038       return False;
1039     }
1040
1041   type = event->xclient.data.l[0];
1042   if (type == XA_ACTIVATE)
1043     {
1044       if (until_idle_p)
1045         {
1046           clientmessage_response(si, window, False,
1047                                  "ACTIVATE ClientMessage received.",
1048                                  "activating.");
1049           si->selection_mode = 0;
1050           si->demoing_p = False;
1051           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1052             {
1053               XForceScreenSaver (si->dpy, ScreenSaverActive);
1054               return False;
1055             }
1056           else
1057             {
1058               return True;
1059             }
1060         }
1061       clientmessage_response(si, window, True,
1062                        "ClientMessage ACTIVATE received while already active.",
1063                              "already active.");
1064     }
1065   else if (type == XA_DEACTIVATE)
1066     {
1067       if (! until_idle_p)
1068         {
1069           clientmessage_response(si, window, False,
1070                                  "DEACTIVATE ClientMessage received.",
1071                                  "deactivating.");
1072           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1073             {
1074               XForceScreenSaver (si->dpy, ScreenSaverReset);
1075               return False;
1076             }
1077           else
1078             {
1079               return True;
1080             }
1081         }
1082       clientmessage_response(si, window, True,
1083                            "ClientMessage DEACTIVATE received while inactive.",
1084                              "not active.");
1085     }
1086   else if (type == XA_CYCLE)
1087     {
1088       if (! until_idle_p)
1089         {
1090           clientmessage_response(si, window, False,
1091                                  "CYCLE ClientMessage received.",
1092                                  "cycling.");
1093           si->selection_mode = 0;       /* 0 means randomize when its time. */
1094           si->demoing_p = False;
1095           if (si->cycle_id)
1096             XtRemoveTimeOut (si->cycle_id);
1097           si->cycle_id = 0;
1098           cycle_timer ((XtPointer) si, 0);
1099           return False;
1100         }
1101       clientmessage_response(si, window, True,
1102                              "ClientMessage CYCLE received while inactive.",
1103                              "not active.");
1104     }
1105   else if (type == XA_NEXT || type == XA_PREV)
1106     {
1107       clientmessage_response(si, window, False,
1108                              (type == XA_NEXT
1109                               ? "NEXT ClientMessage received."
1110                               : "PREV ClientMessage received."),
1111                              "cycling.");
1112       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1113       si->demoing_p = False;
1114
1115       if (! until_idle_p)
1116         {
1117           if (si->cycle_id)
1118             XtRemoveTimeOut (si->cycle_id);
1119           si->cycle_id = 0;
1120           cycle_timer ((XtPointer) si, 0);
1121         }
1122       else
1123         return True;
1124     }
1125   else if (type == XA_SELECT)
1126     {
1127       char buf [255];
1128       char buf2 [255];
1129       long which = event->xclient.data.l[1];
1130
1131       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1132       sprintf (buf2, "activating (%ld).", which);
1133       clientmessage_response (si, window, False, buf, buf2);
1134
1135       if (which < 0) which = 0;         /* 0 == "random" */
1136       si->selection_mode = which;
1137       si->demoing_p = False;
1138
1139       if (! until_idle_p)
1140         {
1141           if (si->cycle_id)
1142             XtRemoveTimeOut (si->cycle_id);
1143           si->cycle_id = 0;
1144           cycle_timer ((XtPointer) si, 0);
1145         }
1146       else
1147         return True;
1148     }
1149   else if (type == XA_EXIT)
1150     {
1151       /* Ignore EXIT message if the screen is locked. */
1152       if (until_idle_p || !si->locked_p)
1153         {
1154           clientmessage_response (si, window, False,
1155                                   "EXIT ClientMessage received.",
1156                                   "exiting.");
1157           if (! until_idle_p)
1158             {
1159               unblank_screen (si);
1160               kill_screenhack (si);
1161               XSync (si->dpy, False);
1162             }
1163           saver_exit (si, 0, 0);
1164         }
1165       else
1166         clientmessage_response (si, window, True,
1167                                 "EXIT ClientMessage received while locked.",
1168                                 "screen is locked.");
1169     }
1170   else if (type == XA_RESTART)
1171     {
1172       /* The RESTART message works whether the screensaver is active or not,
1173          unless the screen is locked, in which case it doesn't work.
1174        */
1175       if (until_idle_p || !si->locked_p)
1176         {
1177           clientmessage_response (si, window, False,
1178                                   "RESTART ClientMessage received.",
1179                                   "restarting.");
1180           if (! until_idle_p)
1181             {
1182               unblank_screen (si);
1183               kill_screenhack (si);
1184               XSync (si->dpy, False);
1185             }
1186
1187           /* make sure error message shows up before exit. */
1188           if (real_stderr && stderr != real_stderr)
1189             dup2 (fileno(real_stderr), fileno(stderr));
1190
1191           restart_process (si);
1192           exit (1);     /* shouldn't get here; but if restarting didn't work,
1193                            make this command be the same as EXIT. */
1194         }
1195       else
1196         clientmessage_response (si, window, True,
1197                                 "RESTART ClientMessage received while locked.",
1198                                 "screen is locked.");
1199     }
1200   else if (type == XA_DEMO)
1201     {
1202       long arg = event->xclient.data.l[1];
1203       Bool demo_one_hack_p = (arg == 300);
1204
1205       if (demo_one_hack_p)
1206         {
1207           if (until_idle_p)
1208             {
1209               long which = event->xclient.data.l[2];
1210               char buf [255];
1211               char buf2 [255];
1212               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1213               sprintf (buf2, "demoing (%ld).", which);
1214               clientmessage_response (si, window, False, buf, buf2);
1215
1216               if (which < 0) which = 0;         /* 0 == "random" */
1217               si->selection_mode = which;
1218               si->demoing_p = True;
1219
1220               return True;
1221             }
1222
1223           clientmessage_response (si, window, True,
1224                                   "DEMO ClientMessage received while active.",
1225                                   "already active.");
1226         }
1227       else
1228         {
1229           clientmessage_response (si, window, True,
1230                                   "obsolete form of DEMO ClientMessage.",
1231                                   "obsolete form of DEMO ClientMessage.");
1232         }
1233     }
1234   else if (type == XA_PREFS)
1235     {
1236       clientmessage_response (si, window, True,
1237                               "the PREFS client-message is obsolete.",
1238                               "the PREFS client-message is obsolete.");
1239     }
1240   else if (type == XA_LOCK)
1241     {
1242 #ifdef NO_LOCKING
1243       clientmessage_response (si, window, True,
1244                               "not compiled with support for locking.",
1245                               "locking not enabled.");
1246 #else /* !NO_LOCKING */
1247       if (si->locking_disabled_p)
1248         clientmessage_response (si, window, True,
1249                       "LOCK ClientMessage received, but locking is disabled.",
1250                               "locking not enabled.");
1251       else if (si->locked_p)
1252         clientmessage_response (si, window, True,
1253                            "LOCK ClientMessage received while already locked.",
1254                                 "already locked.");
1255       else
1256         {
1257           char buf [255];
1258           char *response = (until_idle_p
1259                             ? "activating and locking."
1260                             : "locking.");
1261           si->locked_p = True;
1262           si->selection_mode = 0;
1263           si->demoing_p = False;
1264           sprintf (buf, "LOCK ClientMessage received; %s", response);
1265           clientmessage_response (si, window, False, buf, response);
1266
1267           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1268             {
1269               XtRemoveTimeOut (si->lock_id);
1270               si->lock_id = 0;
1271             }
1272
1273           if (until_idle_p)
1274             {
1275               if (si->using_mit_saver_extension ||
1276                   si->using_sgi_saver_extension)
1277                 {
1278                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1279                   return False;
1280                 }
1281               else
1282                 {
1283                   return True;
1284                 }
1285             }
1286         }
1287 #endif /* !NO_LOCKING */
1288     }
1289   else
1290     {
1291       char buf [1024];
1292       char *str;
1293       str = (type ? XGetAtomName(si->dpy, type) : 0);
1294
1295       if (str)
1296         {
1297           if (strlen (str) > 80)
1298             strcpy (str+70, "...");
1299           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1300                    str);
1301           free (str);
1302         }
1303       else
1304         {
1305           sprintf (buf,
1306                    "unrecognised screensaver ClientMessage 0x%x received.",
1307                    (unsigned int) event->xclient.data.l[0]);
1308         }
1309
1310       clientmessage_response (si, window, True, buf, buf);
1311     }
1312   return False;
1313 }
1314
1315 \f
1316 /* Some random diagnostics printed in -verbose mode.
1317  */
1318
1319 static void
1320 analyze_display (saver_info *si)
1321 {
1322   int i, j;
1323   static const char *exts[][2] = {
1324     { "SCREEN_SAVER",      "SGI Screen-Saver" },
1325     { "SCREEN-SAVER",      "SGI Screen-Saver" },
1326     { "MIT-SCREEN-SAVER",  "MIT Screen-Saver" },
1327     { "XIDLE",             "XIdle" },
1328     { "SGI-VIDEO-CONTROL", "SGI Video-Control" },
1329     { "READDISPLAY",       "SGI Read-Display" },
1330     { "MIT-SHM",           "Shared Memory" },
1331     { "DOUBLE-BUFFER",     "Double-Buffering" },
1332     { "DPMS",              "Power Management" },
1333     { "GLX",               "GLX" }
1334   };
1335
1336   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1337            DisplayString(si->dpy));
1338   fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1339            ServerVendor(si->dpy), VendorRelease(si->dpy));
1340
1341   fprintf (stderr, "%s: useful extensions:\n", blurb());
1342   for (i = 0; i < countof(exts); i++)
1343     {
1344       int op = 0, event = 0, error = 0;
1345       if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error))
1346         fprintf (stderr, "%s:   %s\n", blurb(), exts[i][1]);
1347     }
1348
1349   for (i = 0; i < si->nscreens; i++)
1350     {
1351       unsigned long colormapped_depths = 0;
1352       unsigned long non_mapped_depths = 0;
1353       XVisualInfo vi_in, *vi_out;
1354       int out_count;
1355       vi_in.screen = i;
1356       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1357       if (!vi_out) continue;
1358       for (j = 0; j < out_count; j++)
1359         if (vi_out[j].class == PseudoColor)
1360           colormapped_depths |= (1 << vi_out[j].depth);
1361         else
1362           non_mapped_depths  |= (1 << vi_out[j].depth);
1363       XFree ((char *) vi_out);
1364
1365       if (colormapped_depths)
1366         {
1367           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1368           for (j = 0; j < 32; j++)
1369             if (colormapped_depths & (1 << j))
1370               fprintf (stderr, " %d", j);
1371           fprintf (stderr, "\n");
1372         }
1373       if (non_mapped_depths)
1374         {
1375           fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1376           for (j = 0; j < 32; j++)
1377             if (non_mapped_depths & (1 << j))
1378               fprintf (stderr, " %d", j);
1379           fprintf (stderr, "\n");
1380         }
1381     }
1382 }