http://ftp.x.org/contrib/applications/xscreensaver-3.09.tar.gz
[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 #include <netdb.h>      /* for gethostbyname() */
135 #ifdef HAVE_XMU
136 # ifndef VMS
137 #  include <X11/Xmu/Error.h>
138 # else  /* !VMS */
139 #  include <Xmu/Error.h>
140 # endif /* !VMS */
141 #else  /* !HAVE_XMU */
142 # include "xmu.h"
143 #endif /* !HAVE_XMU */
144
145 #ifdef HAVE_XIDLE_EXTENSION
146 # include <X11/extensions/xidle.h>
147 #endif /* HAVE_XIDLE_EXTENSION */
148
149 #include "xscreensaver.h"
150 #include "version.h"
151 #include "yarandom.h"
152 #include "resources.h"
153 #include "visual.h"
154 #include "usleep.h"
155
156 saver_info *global_si_kludge = 0;       /* I hate C so much... */
157
158 char *progname = 0;
159 char *progclass = 0;
160 XrmDatabase db = 0;
161
162
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;
167
168 \f
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 },
199
200   /* Actually these are built in to Xt, but just to be sure... */
201   { "-synchronous",        ".synchronous",      XrmoptionNoArg, "on" },
202   { "-xrm",                NULL,                XrmoptionResArg, NULL }
203 };
204
205 static char *defaults[] = {
206 #include "XScreenSaver_ad.h"
207  0
208 };
209
210 #ifdef _VROOT_H_
211 ERROR!  You must not include vroot.h in this file.
212 #endif
213
214 static void
215 do_help (saver_info *si)
216 {
217   fflush (stdout);
218   fflush (stderr);
219   fprintf (stdout, "\
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\
222 \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\
229     -verbose                 Be loud.\n\
230     -no-splash               Don't display a splash-screen at startup.\n\
231     -help                    This message.\n\
232 \n\
233 See the manual for other options and X resources.\n\
234 \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\
238 \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\
241 \n\
242 Just getting started?  Try this:\n\
243 \n\
244         xscreensaver &\n\
245         xscreensaver-demo\n\
246 \n\
247 For updates, check http://www.jwz.org/xscreensaver/\n\
248 \n",
249           si->version);
250   fflush (stdout);
251   fflush (stderr);
252   exit (1);
253 }
254
255
256 char *
257 timestring (void)
258 {
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 */
263   return str;
264 }
265
266 static Bool blurb_timestamp_p = False;   /* kludge */
267
268 const char *
269 blurb (void)
270 {
271   if (!blurb_timestamp_p)
272     return progname;
273   else
274     {
275       static char buf[255];
276       char *ct = timestring();
277       int n = strlen(progname);
278       if (n > 100) n = 99;
279       strncpy(buf, progname, n);
280       buf[n++] = ':';
281       buf[n++] = ' ';
282       strncpy(buf+n, ct+11, 8);
283       strcpy(buf+n+9, ": ");
284       return buf;
285     }
286 }
287
288
289 int
290 saver_ehandler (Display *dpy, XErrorEvent *error)
291 {
292   saver_info *si = global_si_kludge;    /* I hate C so much... */
293
294   if (!real_stderr) real_stderr = stderr;
295
296   fprintf (real_stderr, "\n"
297            "#######################################"
298            "#######################################\n\n"
299            "%s: X Error!  PLEASE REPORT THIS BUG.\n\n"
300            "#######################################"
301            "#######################################\n\n",
302            blurb());
303   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
304     {
305       fprintf (real_stderr, "\n");
306       if (si->prefs.xsync_p)
307         {
308           saver_exit (si, -1, "because of synchronous X Error");
309         }
310       else
311         {
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"
320    "\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"
323    "\n"
324    "    The more information you can provide, the better.  But please report\n"
325    "    report this bug, regardless!\n"
326    "\n");
327           fprintf (real_stderr,
328                    "#######################################"
329                    "#######################################\n\n");
330
331           saver_exit (si, -1, 0);
332         }
333     }
334   else
335     fprintf (real_stderr, " (nonfatal.)\n");
336   return 0;
337 }
338
339
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."
344  */
345 static void 
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)
350 {
351   char fmt[512];
352   String p[10];
353   saver_info *si = global_si_kludge;    /* I hate C so much... */
354   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
355   *fmt = 0;
356   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
357                             fmt, sizeof(fmt)-1, *db);
358
359   fprintf (stderr, "%s: ", blurb());
360
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");
367
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"
373            "\n"
374            "              http://www.jwz.org/xscreensaver/man.html\n"
375            "\n",
376            blurb());
377
378   fflush (stderr);
379   fflush (stdout);
380   exit (1);
381 }
382
383 \f
384 /* The zillions of initializations.
385  */
386
387 /* Set progname, version, etc.  This is done very early.
388  */
389 static void
390 set_version_string (saver_info *si, int *argc, char **argv)
391 {
392   progclass = "XScreenSaver";
393
394   /* progname is reset later, after we connect to X. */
395   progname = strrchr(argv[0], '/');
396   if (progname) progname++;
397   else progname = argv[0];
398
399   if (strlen(progname) > 100)   /* keep it short. */
400     progname[99] = 0;
401
402   /* The X resource database blows up if argv[0] has a "." in it. */
403   {
404     char *s = argv[0];
405     while ((s = strchr (s, '.')))
406       *s = '_';
407   }
408
409   si->version = (char *) malloc (5);
410   memcpy (si->version, screensaver_id + 17, 4);
411   si->version [4] = 0;
412 }
413
414
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.
418  */
419 static void
420 privileged_initialization (saver_info *si, int *argc, char **argv)
421 {
422 #ifdef NO_LOCKING
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))
429     {
430       si->locking_disabled_p = True;
431       si->nolock_reason = "error getting password";
432     }
433 #endif /* NO_LOCKING */
434
435 #ifndef NO_SETUID
436   hack_uid (si);
437 #endif /* NO_SETUID */
438 }
439
440
441 /* Open the connection to the X server, and intern our Atoms.
442  */
443 static Widget
444 connect_to_server (saver_info *si, int *argc, char **argv)
445 {
446   Widget toplevel_shell;
447
448   XSetErrorHandler (saver_ehandler);
449
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);
455
456   si->dpy = XtDisplay (toplevel_shell);
457   si->prefs.db = XtDatabase (si->dpy);
458   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
459
460   if(strlen(progname) > 100)    /* keep it short. */
461     progname [99] = 0;
462
463   db = si->prefs.db;    /* resources.c needs this */
464
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",
471                                          False);
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);
484
485   return toplevel_shell;
486 }
487
488
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.
491  */
492 static void
493 process_command_line (saver_info *si, int *argc, char **argv)
494 {
495   int i;
496   for (i = 1; i < *argc; i++)
497     {
498       if (!strcmp (argv[i], "-debug"))
499         /* no resource for this one, out of paranoia. */
500         si->prefs.debug_p = True;
501
502       else if (!strcmp (argv[i], "-h") ||
503                !strcmp (argv[i], "-help") ||
504                !strcmp (argv[i], "--help"))
505         do_help (si);
506
507       else
508         {
509           const char *s = argv[i];
510           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
511                    blurb(), s);
512
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"))
527             {
528
529               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
530                 fprintf (stderr, "\n\
531     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
532               else
533                 fprintf (stderr, "\n\
534     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
535
536               fprintf (stderr, "\
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");
542
543               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
544                  suggest that explicitly. */
545               if (!strcmp (s, "-lock"))
546                 fprintf (stderr, "\
547     Or perhaps you meant either the \"-lock-mode\" or the\n\
548     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
549             }
550
551           exit (1);
552         }
553     }
554 }
555
556
557 /* Print out the xscreensaver banner to the tty if applicable;
558    Issue any other warnings that are called for at this point.
559  */
560 static void
561 print_banner (saver_info *si)
562 {
563   saver_preferences *p = &si->prefs;
564
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.)
568    */
569   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
570
571   /* Ditto, for the locking_disabled_p message. */
572   p->lock_p = get_boolean_resource ("lock", "Boolean");
573
574   if (p->verbose_p)
575     fprintf (stderr,
576              "%s %s, copyright (c) 1991-1999 "
577              "by Jamie Zawinski <jwz@jwz.org>.\n",
578              progname, si->version);
579
580   if (p->debug_p)
581     fprintf (stderr, "\n"
582              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
583              "\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"
588              "\n"
589              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
590              "\tuntrusted environments.\n"
591              "\n",
592              blurb());
593
594   if (p->verbose_p)
595     {
596       if (!si->uid_message || !*si->uid_message)
597         describe_uids (si, stderr);
598       else
599         {
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);
604         }
605
606       fprintf (stderr, "%s: in process %lu.\n", blurb(),
607                (unsigned long) getpid());
608     }
609
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.)
612    */
613   if (p->lock_p && si->locking_disabled_p)
614     {
615       p->lock_p = False;
616       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
617                si->nolock_reason);
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 "))
622         fprintf (stderr, 
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",
626                  blurb());
627     }
628 }
629
630
631 /* Examine all of the display's screens, and populate the `saver_screen_info'
632    structures.
633  */
634 static void
635 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
636 {
637   Bool found_any_writable_cells = False;
638   int i;
639
640   si->nscreens = ScreenCount(si->dpy);
641   si->screens = (saver_screen_info *)
642     calloc(sizeof(saver_screen_info), si->nscreens);
643
644   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
645
646   for (i = 0; i < si->nscreens; i++)
647     {
648       saver_screen_info *ssi = &si->screens[i];
649       ssi->global = si;
650       ssi->screen = ScreenOfDisplay (si->dpy, i);
651
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);
655
656       ssi->current_visual = ssi->default_visual;
657       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
658
659       if (ssi == si->default_screen)
660         /* Since this is the default screen, use the one already created. */
661         ssi->toplevel_shell = toplevel_shell;
662       else
663         /* Otherwise, each screen must have its own unmapped root widget. */
664         ssi->toplevel_shell =
665           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
666                               si->dpy,
667                               XtNscreen, ssi->screen,
668                               XtNvisual, ssi->current_visual,
669                               XtNdepth,  visual_depth (ssi->screen,
670                                                        ssi->current_visual),
671                               0);
672
673       if (! found_any_writable_cells)
674         {
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.
680           */
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;
685         }
686     }
687
688   si->prefs.fading_possible_p = found_any_writable_cells;
689 }
690
691
692 /* If any server extensions have been requested, try and initialize them.
693    Issue warnings if requests can't be honored.
694  */
695 static void
696 initialize_server_extensions (saver_info *si)
697 {
698   saver_preferences *p = &si->prefs;
699
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;
705
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;
710
711 #ifdef HAVE_XIDLE_EXTENSION
712   server_has_xidle_extension_p = query_xidle_extension (si);
713 #endif
714 #ifdef HAVE_SGI_SAVER_EXTENSION
715   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
716 #endif
717 #ifdef HAVE_MIT_SAVER_EXTENSION
718   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
719 #endif
720 #ifdef HAVE_PROC_INTERRUPTS
721   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
722 #endif
723
724   if (!server_has_xidle_extension_p)
725     si->using_xidle_extension = False;
726   else if (p->verbose_p)
727     {
728       if (si->using_xidle_extension)
729         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
730       else
731         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
732     }
733
734   if (!server_has_sgi_saver_extension_p)
735     si->using_sgi_saver_extension = False;
736   else if (p->verbose_p)
737     {
738       if (si->using_sgi_saver_extension)
739         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
740       else
741         fprintf (stderr,
742                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
743                  blurb());
744     }
745
746   if (!server_has_mit_saver_extension_p)
747     si->using_mit_saver_extension = False;
748   else if (p->verbose_p)
749     {
750       if (si->using_mit_saver_extension)
751         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
752                  blurb());
753       else
754         fprintf (stderr,
755                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
756                  blurb());
757     }
758
759   if (!system_has_proc_interrupts_p)
760     {
761       si->using_proc_interrupts = False;
762       if (p->verbose_p && piwhy)
763         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
764                  piwhy);
765     }
766   else if (p->verbose_p)
767     {
768       if (si->using_proc_interrupts)
769         fprintf (stderr,
770                  "%s: consulting /proc/interrupts for keyboard activity.\n",
771                  blurb());
772       else
773         fprintf (stderr,
774                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
775                  blurb());
776     }
777 }
778
779
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.
783
784    If a server extension is being used, this does nothing.
785  */
786 static void
787 select_events (saver_info *si)
788 {
789   saver_preferences *p = &si->prefs;
790   int i;
791
792   if (si->using_xidle_extension ||
793       si->using_mit_saver_extension ||
794       si->using_sgi_saver_extension)
795     return;
796
797   if (p->initial_delay)
798     {
799       if (p->verbose_p)
800         {
801           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
802                    (int) p->initial_delay/1000,
803                    (p->initial_delay == 1000 ? "" : "s"));
804           fflush (stderr);
805           fflush (stdout);
806         }
807       usleep (p->initial_delay);
808       if (p->verbose_p)
809         fprintf (stderr, " done.\n");
810     }
811
812   if (p->verbose_p)
813     {
814       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
815       fflush (stderr);
816       fflush (stdout);
817     }
818
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.
821    */
822   for (i = 0; i < si->nscreens; i++)
823     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
824                                False);
825
826   if (p->verbose_p)
827     fprintf (stderr, " done.\n");
828 }
829
830
831 void
832 maybe_reload_init_file (saver_info *si)
833 {
834   saver_preferences *p = &si->prefs;
835   if (init_file_changed_p (p))
836     {
837       if (p->verbose_p)
838         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
839                  blurb(), init_file_name());
840
841       load_init_file (p);
842
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);
846     }
847 }
848
849
850 /* Loop forever:
851
852        - wait until the user is idle;
853        - blank the screen;
854        - wait until the user is active;
855        - unblank the screen;
856        - repeat.
857
858  */
859 static void
860 main_loop (saver_info *si)
861 {
862   saver_preferences *p = &si->prefs;
863   Bool ok_to_unblank;
864
865   while (1)
866     {
867       sleep_until_idle (si, True);
868
869       if (p->verbose_p)
870         {
871           if (si->demoing_p)
872             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
873                      si->selection_mode, timestring());
874           else
875             if (p->verbose_p)
876               fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
877                        timestring());
878         }
879
880       maybe_reload_init_file (si);
881
882       if (! blank_screen (si))
883         {
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.
890
891              So, just go around the loop again and wait for the
892              next bout of idleness.
893           */
894
895           fprintf (stderr,
896                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
897                    blurb(), timestring ());
898           continue;
899         }
900
901       kill_screenhack (si);
902       spawn_screenhack (si, True);
903
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,
907                                         (XtPointer) si);
908
909
910 #ifndef NO_LOCKING
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. */
916
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.
920        */
921       if (p->lock_p &&
922           !si->locked_p &&
923           p->lock_timeout > 0)
924         si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
925                                        activate_lock_timer,
926                                        (XtPointer) si);
927 #endif /* !NO_LOCKING */
928
929
930       ok_to_unblank = True;
931       do {
932
933         sleep_until_idle (si, False);           /* until not idle */
934         maybe_reload_init_file (si);
935
936 #ifndef NO_LOCKING
937         if (si->locked_p)
938           {
939             saver_screen_info *ssi = si->default_screen;
940             if (si->locking_disabled_p) abort ();
941
942             si->dbox_up_p = True;
943             suspend_screenhack (si, True);
944             XUndefineCursor (si->dpy, ssi->screensaver_window);
945
946             ok_to_unblank = unlock_p (si);
947
948             si->dbox_up_p = False;
949             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
950             suspend_screenhack (si, False);     /* resume */
951           }
952 #endif /* !NO_LOCKING */
953
954         } while (!ok_to_unblank);
955
956
957       if (p->verbose_p)
958         fprintf (stderr, "%s: unblanking screen at %s.\n",
959                  blurb(), timestring ());
960
961       /* Kill before unblanking, to stop drawing as soon as possible. */
962       kill_screenhack (si);
963       unblank_screen (si);
964
965       si->locked_p = False;
966       si->demoing_p = 0;
967       si->selection_mode = 0;
968
969       if (si->cycle_id)
970         {
971           XtRemoveTimeOut (si->cycle_id);
972           si->cycle_id = 0;
973         }
974
975       if (si->lock_id)
976         {
977           XtRemoveTimeOut (si->lock_id);
978           si->lock_id = 0;
979         }
980
981       if (p->verbose_p)
982         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
983     }
984 }
985
986 static void analyze_display (saver_info *si);
987
988 int
989 main (int argc, char **argv)
990 {
991   Widget shell;
992   saver_info the_si;
993   saver_info *si = &the_si;
994   saver_preferences *p = &si->prefs;
995   int i;
996
997   memset(si, 0, sizeof(*si));
998   global_si_kludge = si;        /* I hate C so much... */
999
1000   srandom ((int) time ((time_t *) 0));
1001
1002   save_argv (argc, argv);
1003   set_version_string (si, &argc, argv);
1004   privileged_initialization (si, &argc, argv);
1005   hack_environment (si);
1006
1007   shell = connect_to_server (si, &argc, argv);
1008   process_command_line (si, &argc, argv);
1009   print_banner (si);
1010
1011   initialize_per_screen_info (si, shell);  /* also sets p->fading_possible_p */
1012
1013   for (i = 0; i < si->nscreens; i++)
1014     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1015       exit (1);
1016
1017   load_init_file (p);
1018
1019   if (p->xsync_p) XSynchronize (si->dpy, True);
1020   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1021
1022   if (p->verbose_p) analyze_display (si);
1023   initialize_server_extensions (si);
1024   initialize_screensaver_window (si);
1025   select_events (si);
1026   init_sigchld ();
1027   disable_builtin_screensaver (si, True);
1028   initialize_stderr (si);
1029
1030   make_splash_dialog (si);
1031
1032   main_loop (si);               /* doesn't return */
1033   return 0;
1034 }
1035
1036 \f
1037 /* Processing ClientMessage events.
1038  */
1039
1040 static void
1041 clientmessage_response (saver_info *si, Window w, Bool error,
1042                         const char *stderr_msg,
1043                         const char *protocol_msg)
1044 {
1045   char *proto;
1046   int L;
1047   saver_preferences *p = &si->prefs;
1048   if (error || p->verbose_p)
1049     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1050
1051   L = strlen(protocol_msg);
1052   proto = (char *) malloc (L + 2);
1053   proto[0] = (error ? '-' : '+');
1054   strcpy (proto+1, protocol_msg);
1055   L++;
1056
1057   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1058                    PropModeReplace, (unsigned char *) proto, L);
1059   XSync (si->dpy, False);
1060   free (proto);
1061 }
1062
1063 Bool
1064 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1065 {
1066   Atom type = 0;
1067   Window window = event->xclient.window;
1068
1069   /* Preferences might affect our handling of client messages. */
1070   maybe_reload_init_file (si);
1071
1072   if (event->xclient.message_type != XA_SCREENSAVER)
1073     {
1074       char *str;
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);
1079       return False;
1080     }
1081   if (event->xclient.format != 32)
1082     {
1083       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1084                blurb(), event->xclient.format);
1085       return False;
1086     }
1087
1088   type = event->xclient.data.l[0];
1089   if (type == XA_ACTIVATE)
1090     {
1091       if (until_idle_p)
1092         {
1093           clientmessage_response(si, window, False,
1094                                  "ACTIVATE ClientMessage received.",
1095                                  "activating.");
1096           si->selection_mode = 0;
1097           si->demoing_p = False;
1098           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1099             {
1100               XForceScreenSaver (si->dpy, ScreenSaverActive);
1101               return False;
1102             }
1103           else
1104             {
1105               return True;
1106             }
1107         }
1108       clientmessage_response(si, window, True,
1109                        "ClientMessage ACTIVATE received while already active.",
1110                              "already active.");
1111     }
1112   else if (type == XA_DEACTIVATE)
1113     {
1114       if (! until_idle_p)
1115         {
1116           clientmessage_response(si, window, False,
1117                                  "DEACTIVATE ClientMessage received.",
1118                                  "deactivating.");
1119           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1120             {
1121               XForceScreenSaver (si->dpy, ScreenSaverReset);
1122               return False;
1123             }
1124           else
1125             {
1126               return True;
1127             }
1128         }
1129       clientmessage_response(si, window, True,
1130                            "ClientMessage DEACTIVATE received while inactive.",
1131                              "not active.");
1132     }
1133   else if (type == XA_CYCLE)
1134     {
1135       if (! until_idle_p)
1136         {
1137           clientmessage_response(si, window, False,
1138                                  "CYCLE ClientMessage received.",
1139                                  "cycling.");
1140           si->selection_mode = 0;       /* 0 means randomize when its time. */
1141           si->demoing_p = False;
1142           if (si->cycle_id)
1143             XtRemoveTimeOut (si->cycle_id);
1144           si->cycle_id = 0;
1145           cycle_timer ((XtPointer) si, 0);
1146           return False;
1147         }
1148       clientmessage_response(si, window, True,
1149                              "ClientMessage CYCLE received while inactive.",
1150                              "not active.");
1151     }
1152   else if (type == XA_NEXT || type == XA_PREV)
1153     {
1154       clientmessage_response(si, window, False,
1155                              (type == XA_NEXT
1156                               ? "NEXT ClientMessage received."
1157                               : "PREV ClientMessage received."),
1158                              "cycling.");
1159       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1160       si->demoing_p = False;
1161
1162       if (! until_idle_p)
1163         {
1164           if (si->cycle_id)
1165             XtRemoveTimeOut (si->cycle_id);
1166           si->cycle_id = 0;
1167           cycle_timer ((XtPointer) si, 0);
1168         }
1169       else
1170         return True;
1171     }
1172   else if (type == XA_SELECT)
1173     {
1174       char buf [255];
1175       char buf2 [255];
1176       long which = event->xclient.data.l[1];
1177
1178       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1179       sprintf (buf2, "activating (%ld).", which);
1180       clientmessage_response (si, window, False, buf, buf2);
1181
1182       if (which < 0) which = 0;         /* 0 == "random" */
1183       si->selection_mode = which;
1184       si->demoing_p = False;
1185
1186       if (! until_idle_p)
1187         {
1188           if (si->cycle_id)
1189             XtRemoveTimeOut (si->cycle_id);
1190           si->cycle_id = 0;
1191           cycle_timer ((XtPointer) si, 0);
1192         }
1193       else
1194         return True;
1195     }
1196   else if (type == XA_EXIT)
1197     {
1198       /* Ignore EXIT message if the screen is locked. */
1199       if (until_idle_p || !si->locked_p)
1200         {
1201           clientmessage_response (si, window, False,
1202                                   "EXIT ClientMessage received.",
1203                                   "exiting.");
1204           if (! until_idle_p)
1205             {
1206               unblank_screen (si);
1207               kill_screenhack (si);
1208               XSync (si->dpy, False);
1209             }
1210           saver_exit (si, 0, 0);
1211         }
1212       else
1213         clientmessage_response (si, window, True,
1214                                 "EXIT ClientMessage received while locked.",
1215                                 "screen is locked.");
1216     }
1217   else if (type == XA_RESTART)
1218     {
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.
1221        */
1222       if (until_idle_p || !si->locked_p)
1223         {
1224           clientmessage_response (si, window, False,
1225                                   "RESTART ClientMessage received.",
1226                                   "restarting.");
1227           if (! until_idle_p)
1228             {
1229               unblank_screen (si);
1230               kill_screenhack (si);
1231               XSync (si->dpy, False);
1232             }
1233
1234           /* make sure error message shows up before exit. */
1235           if (real_stderr && stderr != real_stderr)
1236             dup2 (fileno(real_stderr), fileno(stderr));
1237
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. */
1241         }
1242       else
1243         clientmessage_response (si, window, True,
1244                                 "RESTART ClientMessage received while locked.",
1245                                 "screen is locked.");
1246     }
1247   else if (type == XA_DEMO)
1248     {
1249       long arg = event->xclient.data.l[1];
1250       Bool demo_one_hack_p = (arg == 300);
1251
1252       if (demo_one_hack_p)
1253         {
1254           if (until_idle_p)
1255             {
1256               long which = event->xclient.data.l[2];
1257               char buf [255];
1258               char buf2 [255];
1259               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1260               sprintf (buf2, "demoing (%ld).", which);
1261               clientmessage_response (si, window, False, buf, buf2);
1262
1263               if (which < 0) which = 0;         /* 0 == "random" */
1264               si->selection_mode = which;
1265               si->demoing_p = True;
1266
1267               return True;
1268             }
1269
1270           clientmessage_response (si, window, True,
1271                                   "DEMO ClientMessage received while active.",
1272                                   "already active.");
1273         }
1274       else
1275         {
1276           clientmessage_response (si, window, True,
1277                                   "obsolete form of DEMO ClientMessage.",
1278                                   "obsolete form of DEMO ClientMessage.");
1279         }
1280     }
1281   else if (type == XA_PREFS)
1282     {
1283       clientmessage_response (si, window, True,
1284                               "the PREFS client-message is obsolete.",
1285                               "the PREFS client-message is obsolete.");
1286     }
1287   else if (type == XA_LOCK)
1288     {
1289 #ifdef NO_LOCKING
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.",
1301                                 "already locked.");
1302       else
1303         {
1304           char buf [255];
1305           char *response = (until_idle_p
1306                             ? "activating and locking."
1307                             : "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);
1313
1314           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1315             {
1316               XtRemoveTimeOut (si->lock_id);
1317               si->lock_id = 0;
1318             }
1319
1320           if (until_idle_p)
1321             {
1322               if (si->using_mit_saver_extension ||
1323                   si->using_sgi_saver_extension)
1324                 {
1325                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1326                   return False;
1327                 }
1328               else
1329                 {
1330                   return True;
1331                 }
1332             }
1333         }
1334 #endif /* !NO_LOCKING */
1335     }
1336   else
1337     {
1338       char buf [1024];
1339       char *str;
1340       str = (type ? XGetAtomName(si->dpy, type) : 0);
1341
1342       if (str)
1343         {
1344           if (strlen (str) > 80)
1345             strcpy (str+70, "...");
1346           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1347                    str);
1348           free (str);
1349         }
1350       else
1351         {
1352           sprintf (buf,
1353                    "unrecognised screensaver ClientMessage 0x%x received.",
1354                    (unsigned int) event->xclient.data.l[0]);
1355         }
1356
1357       clientmessage_response (si, window, True, buf, buf);
1358     }
1359   return False;
1360 }
1361
1362 \f
1363 /* Some random diagnostics printed in -verbose mode.
1364  */
1365
1366 static void
1367 analyze_display (saver_info *si)
1368 {
1369   int i, j;
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" },
1380     { "GLX",               "GLX" }
1381   };
1382
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));
1387
1388   fprintf (stderr, "%s: useful extensions:\n", blurb());
1389   for (i = 0; i < countof(exts); i++)
1390     {
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]);
1394     }
1395
1396   for (i = 0; i < si->nscreens; i++)
1397     {
1398       unsigned long colormapped_depths = 0;
1399       unsigned long non_mapped_depths = 0;
1400       XVisualInfo vi_in, *vi_out;
1401       int out_count;
1402       vi_in.screen = i;
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);
1408         else
1409           non_mapped_depths  |= (1 << vi_out[j].depth);
1410       XFree ((char *) vi_out);
1411
1412       if (colormapped_depths)
1413         {
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");
1419         }
1420       if (non_mapped_depths)
1421         {
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");
1427         }
1428     }
1429 }
1430
1431 Bool
1432 display_is_on_console_p (saver_info *si)
1433 {
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;
1439   else
1440     {
1441       char dpyname[255], localname[255];
1442       strncpy (dpyname, dpystr, tail-dpystr);
1443       dpyname [tail-dpystr] = 0;
1444       if (!*dpyname ||
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? */
1450       else
1451         {
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.
1457              
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.
1461            */
1462           struct hostent *h = gethostbyname (dpyname);
1463           if (!h)
1464             not_on_console = True;
1465           else
1466             {
1467               char hn [255];
1468               struct hostent *l;
1469               strcpy (hn, h->h_name);
1470               l = gethostbyname (localname);
1471               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1472             }
1473         }
1474     }
1475   return !not_on_console;
1476 }