7b16c898aaab4d95f22aa643084ed3842b2cd772
[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       blank_screen (si);
883       kill_screenhack (si);
884       spawn_screenhack (si, True);
885
886       /* Don't start the cycle timer in demo mode. */
887       if (!si->demoing_p && p->cycle)
888         si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
889                                         (XtPointer) si);
890
891
892 #ifndef NO_LOCKING
893       if (!si->demoing_p &&             /* if not going into demo mode */
894           p->lock_p &&                  /* and locking is enabled */
895           !si->locking_disabled_p &&    /* and locking is possible */
896           p->lock_timeout == 0)         /* and locking is not timer-deferred */
897         si->locked_p = True;            /* then lock right now. */
898
899       /* locked_p might be true already because of the above, or because of
900          the LOCK ClientMessage.  But if not, and if we're supposed to lock
901          after some time, set up a timer to do so.
902        */
903       if (p->lock_p &&
904           !si->locked_p &&
905           p->lock_timeout > 0)
906         si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
907                                        activate_lock_timer,
908                                        (XtPointer) si);
909 #endif /* !NO_LOCKING */
910
911
912       ok_to_unblank = True;
913       do {
914
915         sleep_until_idle (si, False);           /* until not idle */
916         maybe_reload_init_file (si);
917
918 #ifndef NO_LOCKING
919         if (si->locked_p)
920           {
921             saver_screen_info *ssi = si->default_screen;
922             if (si->locking_disabled_p) abort ();
923
924             si->dbox_up_p = True;
925             suspend_screenhack (si, True);
926             XUndefineCursor (si->dpy, ssi->screensaver_window);
927
928             ok_to_unblank = unlock_p (si);
929
930             si->dbox_up_p = False;
931             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
932             suspend_screenhack (si, False);     /* resume */
933           }
934 #endif /* !NO_LOCKING */
935
936         } while (!ok_to_unblank);
937
938
939       if (p->verbose_p)
940         fprintf (stderr, "%s: unblanking screen at %s.\n",
941                  blurb(), timestring ());
942
943       /* Kill before unblanking, to stop drawing as soon as possible. */
944       kill_screenhack (si);
945       unblank_screen (si);
946
947       si->locked_p = False;
948       si->demoing_p = 0;
949       si->selection_mode = 0;
950
951       if (si->cycle_id)
952         {
953           XtRemoveTimeOut (si->cycle_id);
954           si->cycle_id = 0;
955         }
956
957       if (si->lock_id)
958         {
959           XtRemoveTimeOut (si->lock_id);
960           si->lock_id = 0;
961         }
962
963       if (p->verbose_p)
964         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
965     }
966 }
967
968 static void analyze_display (saver_info *si);
969
970 int
971 main (int argc, char **argv)
972 {
973   Widget shell;
974   saver_info the_si;
975   saver_info *si = &the_si;
976   saver_preferences *p = &si->prefs;
977   int i;
978
979   memset(si, 0, sizeof(*si));
980   global_si_kludge = si;        /* I hate C so much... */
981
982   srandom ((int) time ((time_t *) 0));
983
984   save_argv (argc, argv);
985   set_version_string (si, &argc, argv);
986   privileged_initialization (si, &argc, argv);
987   hack_environment (si);
988
989   shell = connect_to_server (si, &argc, argv);
990   process_command_line (si, &argc, argv);
991   print_banner (si);
992
993   initialize_per_screen_info (si, shell);  /* also sets p->fading_possible_p */
994
995   for (i = 0; i < si->nscreens; i++)
996     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
997       exit (1);
998
999   load_init_file (p);
1000
1001   if (p->xsync_p) XSynchronize (si->dpy, True);
1002   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1003
1004   if (p->verbose_p) analyze_display (si);
1005   initialize_server_extensions (si);
1006   initialize_screensaver_window (si);
1007   select_events (si);
1008   init_sigchld ();
1009   disable_builtin_screensaver (si, True);
1010   initialize_stderr (si);
1011
1012   make_splash_dialog (si);
1013
1014   main_loop (si);               /* doesn't return */
1015   return 0;
1016 }
1017
1018 \f
1019 /* Processing ClientMessage events.
1020  */
1021
1022 static void
1023 clientmessage_response (saver_info *si, Window w, Bool error,
1024                         const char *stderr_msg,
1025                         const char *protocol_msg)
1026 {
1027   char *proto;
1028   int L;
1029   saver_preferences *p = &si->prefs;
1030   if (error || p->verbose_p)
1031     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1032
1033   L = strlen(protocol_msg);
1034   proto = (char *) malloc (L + 2);
1035   proto[0] = (error ? '-' : '+');
1036   strcpy (proto+1, protocol_msg);
1037   L++;
1038
1039   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1040                    PropModeReplace, (unsigned char *) proto, L);
1041   XSync (si->dpy, False);
1042   free (proto);
1043 }
1044
1045 Bool
1046 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1047 {
1048   Atom type = 0;
1049   Window window = event->xclient.window;
1050
1051   /* Preferences might affect our handling of client messages. */
1052   maybe_reload_init_file (si);
1053
1054   if (event->xclient.message_type != XA_SCREENSAVER)
1055     {
1056       char *str;
1057       str = XGetAtomName (si->dpy, event->xclient.message_type);
1058       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1059                blurb(), (str ? str : "(null)"));
1060       if (str) XFree (str);
1061       return False;
1062     }
1063   if (event->xclient.format != 32)
1064     {
1065       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1066                blurb(), event->xclient.format);
1067       return False;
1068     }
1069
1070   type = event->xclient.data.l[0];
1071   if (type == XA_ACTIVATE)
1072     {
1073       if (until_idle_p)
1074         {
1075           clientmessage_response(si, window, False,
1076                                  "ACTIVATE ClientMessage received.",
1077                                  "activating.");
1078           si->selection_mode = 0;
1079           si->demoing_p = False;
1080           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1081             {
1082               XForceScreenSaver (si->dpy, ScreenSaverActive);
1083               return False;
1084             }
1085           else
1086             {
1087               return True;
1088             }
1089         }
1090       clientmessage_response(si, window, True,
1091                        "ClientMessage ACTIVATE received while already active.",
1092                              "already active.");
1093     }
1094   else if (type == XA_DEACTIVATE)
1095     {
1096       if (! until_idle_p)
1097         {
1098           clientmessage_response(si, window, False,
1099                                  "DEACTIVATE ClientMessage received.",
1100                                  "deactivating.");
1101           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1102             {
1103               XForceScreenSaver (si->dpy, ScreenSaverReset);
1104               return False;
1105             }
1106           else
1107             {
1108               return True;
1109             }
1110         }
1111       clientmessage_response(si, window, True,
1112                            "ClientMessage DEACTIVATE received while inactive.",
1113                              "not active.");
1114     }
1115   else if (type == XA_CYCLE)
1116     {
1117       if (! until_idle_p)
1118         {
1119           clientmessage_response(si, window, False,
1120                                  "CYCLE ClientMessage received.",
1121                                  "cycling.");
1122           si->selection_mode = 0;       /* 0 means randomize when its time. */
1123           si->demoing_p = False;
1124           if (si->cycle_id)
1125             XtRemoveTimeOut (si->cycle_id);
1126           si->cycle_id = 0;
1127           cycle_timer ((XtPointer) si, 0);
1128           return False;
1129         }
1130       clientmessage_response(si, window, True,
1131                              "ClientMessage CYCLE received while inactive.",
1132                              "not active.");
1133     }
1134   else if (type == XA_NEXT || type == XA_PREV)
1135     {
1136       clientmessage_response(si, window, False,
1137                              (type == XA_NEXT
1138                               ? "NEXT ClientMessage received."
1139                               : "PREV ClientMessage received."),
1140                              "cycling.");
1141       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1142       si->demoing_p = False;
1143
1144       if (! until_idle_p)
1145         {
1146           if (si->cycle_id)
1147             XtRemoveTimeOut (si->cycle_id);
1148           si->cycle_id = 0;
1149           cycle_timer ((XtPointer) si, 0);
1150         }
1151       else
1152         return True;
1153     }
1154   else if (type == XA_SELECT)
1155     {
1156       char buf [255];
1157       char buf2 [255];
1158       long which = event->xclient.data.l[1];
1159
1160       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1161       sprintf (buf2, "activating (%ld).", which);
1162       clientmessage_response (si, window, False, buf, buf2);
1163
1164       if (which < 0) which = 0;         /* 0 == "random" */
1165       si->selection_mode = which;
1166       si->demoing_p = False;
1167
1168       if (! until_idle_p)
1169         {
1170           if (si->cycle_id)
1171             XtRemoveTimeOut (si->cycle_id);
1172           si->cycle_id = 0;
1173           cycle_timer ((XtPointer) si, 0);
1174         }
1175       else
1176         return True;
1177     }
1178   else if (type == XA_EXIT)
1179     {
1180       /* Ignore EXIT message if the screen is locked. */
1181       if (until_idle_p || !si->locked_p)
1182         {
1183           clientmessage_response (si, window, False,
1184                                   "EXIT ClientMessage received.",
1185                                   "exiting.");
1186           if (! until_idle_p)
1187             {
1188               unblank_screen (si);
1189               kill_screenhack (si);
1190               XSync (si->dpy, False);
1191             }
1192           saver_exit (si, 0, 0);
1193         }
1194       else
1195         clientmessage_response (si, window, True,
1196                                 "EXIT ClientMessage received while locked.",
1197                                 "screen is locked.");
1198     }
1199   else if (type == XA_RESTART)
1200     {
1201       /* The RESTART message works whether the screensaver is active or not,
1202          unless the screen is locked, in which case it doesn't work.
1203        */
1204       if (until_idle_p || !si->locked_p)
1205         {
1206           clientmessage_response (si, window, False,
1207                                   "RESTART ClientMessage received.",
1208                                   "restarting.");
1209           if (! until_idle_p)
1210             {
1211               unblank_screen (si);
1212               kill_screenhack (si);
1213               XSync (si->dpy, False);
1214             }
1215
1216           /* make sure error message shows up before exit. */
1217           if (real_stderr && stderr != real_stderr)
1218             dup2 (fileno(real_stderr), fileno(stderr));
1219
1220           restart_process (si);
1221           exit (1);     /* shouldn't get here; but if restarting didn't work,
1222                            make this command be the same as EXIT. */
1223         }
1224       else
1225         clientmessage_response (si, window, True,
1226                                 "RESTART ClientMessage received while locked.",
1227                                 "screen is locked.");
1228     }
1229   else if (type == XA_DEMO)
1230     {
1231       long arg = event->xclient.data.l[1];
1232       Bool demo_one_hack_p = (arg == 300);
1233
1234       if (demo_one_hack_p)
1235         {
1236           if (until_idle_p)
1237             {
1238               long which = event->xclient.data.l[2];
1239               char buf [255];
1240               char buf2 [255];
1241               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1242               sprintf (buf2, "demoing (%ld).", which);
1243               clientmessage_response (si, window, False, buf, buf2);
1244
1245               if (which < 0) which = 0;         /* 0 == "random" */
1246               si->selection_mode = which;
1247               si->demoing_p = True;
1248
1249               return True;
1250             }
1251
1252           clientmessage_response (si, window, True,
1253                                   "DEMO ClientMessage received while active.",
1254                                   "already active.");
1255         }
1256       else
1257         {
1258           clientmessage_response (si, window, True,
1259                                   "obsolete form of DEMO ClientMessage.",
1260                                   "obsolete form of DEMO ClientMessage.");
1261         }
1262     }
1263   else if (type == XA_PREFS)
1264     {
1265       clientmessage_response (si, window, True,
1266                               "the PREFS client-message is obsolete.",
1267                               "the PREFS client-message is obsolete.");
1268     }
1269   else if (type == XA_LOCK)
1270     {
1271 #ifdef NO_LOCKING
1272       clientmessage_response (si, window, True,
1273                               "not compiled with support for locking.",
1274                               "locking not enabled.");
1275 #else /* !NO_LOCKING */
1276       if (si->locking_disabled_p)
1277         clientmessage_response (si, window, True,
1278                       "LOCK ClientMessage received, but locking is disabled.",
1279                               "locking not enabled.");
1280       else if (si->locked_p)
1281         clientmessage_response (si, window, True,
1282                            "LOCK ClientMessage received while already locked.",
1283                                 "already locked.");
1284       else
1285         {
1286           char buf [255];
1287           char *response = (until_idle_p
1288                             ? "activating and locking."
1289                             : "locking.");
1290           si->locked_p = True;
1291           si->selection_mode = 0;
1292           si->demoing_p = False;
1293           sprintf (buf, "LOCK ClientMessage received; %s", response);
1294           clientmessage_response (si, window, False, buf, response);
1295
1296           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1297             {
1298               XtRemoveTimeOut (si->lock_id);
1299               si->lock_id = 0;
1300             }
1301
1302           if (until_idle_p)
1303             {
1304               if (si->using_mit_saver_extension ||
1305                   si->using_sgi_saver_extension)
1306                 {
1307                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1308                   return False;
1309                 }
1310               else
1311                 {
1312                   return True;
1313                 }
1314             }
1315         }
1316 #endif /* !NO_LOCKING */
1317     }
1318   else
1319     {
1320       char buf [1024];
1321       char *str;
1322       str = (type ? XGetAtomName(si->dpy, type) : 0);
1323
1324       if (str)
1325         {
1326           if (strlen (str) > 80)
1327             strcpy (str+70, "...");
1328           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1329                    str);
1330           free (str);
1331         }
1332       else
1333         {
1334           sprintf (buf,
1335                    "unrecognised screensaver ClientMessage 0x%x received.",
1336                    (unsigned int) event->xclient.data.l[0]);
1337         }
1338
1339       clientmessage_response (si, window, True, buf, buf);
1340     }
1341   return False;
1342 }
1343
1344 \f
1345 /* Some random diagnostics printed in -verbose mode.
1346  */
1347
1348 static void
1349 analyze_display (saver_info *si)
1350 {
1351   int i, j;
1352   static const char *exts[][2] = {
1353     { "SCREEN_SAVER",      "SGI Screen-Saver" },
1354     { "SCREEN-SAVER",      "SGI Screen-Saver" },
1355     { "MIT-SCREEN-SAVER",  "MIT Screen-Saver" },
1356     { "XIDLE",             "XIdle" },
1357     { "SGI-VIDEO-CONTROL", "SGI Video-Control" },
1358     { "READDISPLAY",       "SGI Read-Display" },
1359     { "MIT-SHM",           "Shared Memory" },
1360     { "DOUBLE-BUFFER",     "Double-Buffering" },
1361     { "DPMS",              "Power Management" },
1362     { "GLX",               "GLX" }
1363   };
1364
1365   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1366            DisplayString(si->dpy));
1367   fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1368            ServerVendor(si->dpy), VendorRelease(si->dpy));
1369
1370   fprintf (stderr, "%s: useful extensions:\n", blurb());
1371   for (i = 0; i < countof(exts); i++)
1372     {
1373       int op = 0, event = 0, error = 0;
1374       if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error))
1375         fprintf (stderr, "%s:   %s\n", blurb(), exts[i][1]);
1376     }
1377
1378   for (i = 0; i < si->nscreens; i++)
1379     {
1380       unsigned long colormapped_depths = 0;
1381       unsigned long non_mapped_depths = 0;
1382       XVisualInfo vi_in, *vi_out;
1383       int out_count;
1384       vi_in.screen = i;
1385       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1386       if (!vi_out) continue;
1387       for (j = 0; j < out_count; j++)
1388         if (vi_out[j].class == PseudoColor)
1389           colormapped_depths |= (1 << vi_out[j].depth);
1390         else
1391           non_mapped_depths  |= (1 << vi_out[j].depth);
1392       XFree ((char *) vi_out);
1393
1394       if (colormapped_depths)
1395         {
1396           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1397           for (j = 0; j < 32; j++)
1398             if (colormapped_depths & (1 << j))
1399               fprintf (stderr, " %d", j);
1400           fprintf (stderr, "\n");
1401         }
1402       if (non_mapped_depths)
1403         {
1404           fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1405           for (j = 0; j < 32; j++)
1406             if (non_mapped_depths & (1 << j))
1407               fprintf (stderr, " %d", j);
1408           fprintf (stderr, "\n");
1409         }
1410     }
1411 }
1412
1413 Bool
1414 display_is_on_console_p (saver_info *si)
1415 {
1416   Bool not_on_console = True;
1417   char *dpystr = DisplayString (si->dpy);
1418   char *tail = (char *) strchr (dpystr, ':');
1419   if (! tail || strncmp (tail, ":0", 2))
1420     not_on_console = True;
1421   else
1422     {
1423       char dpyname[255], localname[255];
1424       strncpy (dpyname, dpystr, tail-dpystr);
1425       dpyname [tail-dpystr] = 0;
1426       if (!*dpyname ||
1427           !strcmp(dpyname, "unix") ||
1428           !strcmp(dpyname, "localhost"))
1429         not_on_console = False;
1430       else if (gethostname (localname, sizeof (localname)))
1431         not_on_console = True;  /* can't find hostname? */
1432       else
1433         {
1434           /* We have to call gethostbyname() on the result of gethostname()
1435              because the two aren't guarenteed to be the same name for the
1436              same host: on some losing systems, one is a FQDN and the other
1437              is not.  Here in the wide wonderful world of Unix it's rocket
1438              science to obtain the local hostname in a portable fashion.
1439              
1440              And don't forget, gethostbyname() reuses the structure it
1441              returns, so we have to copy the fucker before calling it again.
1442              Thank you master, may I have another.
1443            */
1444           struct hostent *h = gethostbyname (dpyname);
1445           if (!h)
1446             not_on_console = True;
1447           else
1448             {
1449               char hn [255];
1450               struct hostent *l;
1451               strcpy (hn, h->h_name);
1452               l = gethostbyname (localname);
1453               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1454             }
1455         }
1456     }
1457   return !not_on_console;
1458 }