http://packetstorm.tacticalflex.com/UNIX/admin/xscreensaver-3.27.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-2001 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 such programs do not seem to
74  *   occur in nature (I've never seen it happen in all these years.)
75  *
76  *   The reason that we can't select KeyPresses on windows that don't have
77  *   them already is that, when dispatching a KeyPress event, X finds the
78  *   lowest (leafmost) window in the hierarchy on which *any* client selects
79  *   for KeyPress, and sends the event to that window.  This means that if a
80  *   client had a window with subwindows, and expected to receive KeyPress
81  *   events on the parent window instead of the subwindows, then that client
82  *   would malfunction if some other client selected KeyPress events on the
83  *   subwindows.  It is an incredible misdesign that one client can make
84  *   another client malfunction in this way.
85  *
86  *   To detect mouse motion, we periodically wake up and poll the mouse
87  *   position and button/modifier state, and notice when something has
88  *   changed.  We make this check every five seconds by default, and since the
89  *   screensaver timeout has a granularity of one minute, this makes the
90  *   chance of a false positive very small.  We could detect mouse motion in
91  *   the same way as keyboard activity, but that would suffer from the same
92  *   "client changing event mask" problem that the KeyPress events hack does.
93  *   I think polling is more reliable.
94  *
95  *   None of this crap happens if we're using one of the extensions, so install
96  *   one of them if the description above sounds just too flaky to live.  It
97  *   is, but those are your choices.
98  *
99  *   A third idle-detection option could be implemented (but is not): when
100  *   running on the console display ($DISPLAY is `localhost`:0) and we're on a
101  *   machine where /dev/tty and /dev/mouse have reasonable last-modification
102  *   times, we could just stat() those.  But the incremental benefit of
103  *   implementing this is really small, so forget I said anything.
104  *
105  *   Debugging hints:
106  *     - Have a second terminal handy.
107  *     - Be careful where you set your breakpoints, you don't want this to
108  *       stop under the debugger with the keyboard grabbed or the blackout
109  *       window exposed.
110  *     - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
111  *       to keep your emacs window alive even when xscreensaver has grabbed.
112  *     - Go read the code related to `debug_p'.
113  *     - You probably can't set breakpoints in functions that are called on
114  *       the other side of a call to fork() -- if your subprocesses are
115  *       dying with signal 5, Trace/BPT Trap, you're losing in this way.
116  *     - If you aren't using a server extension, don't leave this stopped
117  *       under the debugger for very long, or the X input buffer will get
118  *       huge because of the keypress events it's selecting for.  This can
119  *       make your X server wedge with "no more input buffers."
120  *
121  * ======================================================================== */
122
123 #ifdef HAVE_CONFIG_H
124 # include "config.h"
125 #endif
126
127 #include <stdio.h>
128 #include <ctype.h>
129 #include <X11/Xlib.h>
130 #include <X11/Xatom.h>
131 #include <X11/Intrinsic.h>
132 #include <X11/StringDefs.h>
133 #include <X11/Shell.h>
134 #include <X11/Xos.h>
135 #include <netdb.h>      /* for gethostbyname() */
136 #ifdef HAVE_XMU
137 # ifndef VMS
138 #  include <X11/Xmu/Error.h>
139 # else  /* !VMS */
140 #  include <Xmu/Error.h>
141 # endif /* !VMS */
142 #else  /* !HAVE_XMU */
143 # include "xmu.h"
144 #endif /* !HAVE_XMU */
145
146 #ifdef HAVE_XIDLE_EXTENSION
147 # include <X11/extensions/xidle.h>
148 #endif /* HAVE_XIDLE_EXTENSION */
149
150 #include "xscreensaver.h"
151 #include "version.h"
152 #include "yarandom.h"
153 #include "resources.h"
154 #include "visual.h"
155 #include "usleep.h"
156
157 saver_info *global_si_kludge = 0;       /* I hate C so much... */
158
159 char *progname = 0;
160 char *progclass = 0;
161 XrmDatabase db = 0;
162
163
164 static Atom XA_SCREENSAVER_RESPONSE;
165 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
166 static Atom XA_RESTART, XA_SELECT;
167 static Atom XA_THROTTLE, XA_UNTHROTTLE;
168 Atom XA_DEMO, XA_PREFS, XA_EXIT, XA_LOCK, XA_BLANK;
169
170 \f
171 static XrmOptionDescRec options [] = {
172   { "-timeout",            ".timeout",          XrmoptionSepArg, 0 },
173   { "-cycle",              ".cycle",            XrmoptionSepArg, 0 },
174   { "-lock-mode",          ".lock",             XrmoptionNoArg, "on" },
175   { "-no-lock-mode",       ".lock",             XrmoptionNoArg, "off" },
176   { "-no-lock",            ".lock",             XrmoptionNoArg, "off" },
177   { "-lock-timeout",       ".lockTimeout",      XrmoptionSepArg, 0 },
178   { "-lock-vts",           ".lockVTs",          XrmoptionNoArg, "on" },
179   { "-no-lock-vts",        ".lockVTs",          XrmoptionNoArg, "off" },
180   { "-visual",             ".visualID",         XrmoptionSepArg, 0 },
181   { "-install",            ".installColormap",  XrmoptionNoArg, "on" },
182   { "-no-install",         ".installColormap",  XrmoptionNoArg, "off" },
183   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
184   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
185   { "-timestamp",          ".timestamp",        XrmoptionNoArg, "on" },
186   { "-capture-stderr",     ".captureStderr",    XrmoptionNoArg, "on" },
187   { "-no-capture-stderr",  ".captureStderr",    XrmoptionNoArg, "off" },
188   { "-xidle-extension",    ".xidleExtension",   XrmoptionNoArg, "on" },
189   { "-no-xidle-extension", ".xidleExtension",   XrmoptionNoArg, "off" },
190   { "-mit-extension",      ".mitSaverExtension",XrmoptionNoArg, "on" },
191   { "-no-mit-extension",   ".mitSaverExtension",XrmoptionNoArg, "off" },
192   { "-sgi-extension",      ".sgiSaverExtension",XrmoptionNoArg, "on" },
193   { "-no-sgi-extension",   ".sgiSaverExtension",XrmoptionNoArg, "off" },
194   { "-proc-interrupts",    ".procInterrupts",   XrmoptionNoArg, "on" },
195   { "-no-proc-interrupts", ".procInterrupts",   XrmoptionNoArg, "off" },
196   { "-splash",             ".splash",           XrmoptionNoArg, "on" },
197   { "-no-splash",          ".splash",           XrmoptionNoArg, "off" },
198   { "-nosplash",           ".splash",           XrmoptionNoArg, "off" },
199   { "-idelay",             ".initialDelay",     XrmoptionSepArg, 0 },
200   { "-nice",               ".nice",             XrmoptionSepArg, 0 },
201
202   /* Actually these are built in to Xt, but just to be sure... */
203   { "-synchronous",        ".synchronous",      XrmoptionNoArg, "on" },
204   { "-xrm",                NULL,                XrmoptionResArg, NULL }
205 };
206
207 static char *defaults[] = {
208 #include "XScreenSaver_ad.h"
209  0
210 };
211
212 #ifdef _VROOT_H_
213 ERROR!  You must not include vroot.h in this file.
214 #endif
215
216 static void
217 do_help (saver_info *si)
218 {
219   fflush (stdout);
220   fflush (stderr);
221   fprintf (stdout, "\
222 xscreensaver %s, copyright (c) 1991-2001 by Jamie Zawinski <jwz@jwz.org>\n\
223 The standard Xt command-line options are accepted; other options include:\n\
224 \n\
225     -timeout <minutes>       When the screensaver should activate.\n\
226     -cycle <minutes>         How long to let each hack run before switching.\n\
227     -lock-mode               Require a password before deactivating.\n\
228     -lock-timeout <minutes>  Grace period before locking; default 0.\n\
229     -visual <id-or-class>    Which X visual to run on.\n\
230     -install                 Install a private colormap.\n\
231     -verbose                 Be loud.\n\
232     -no-splash               Don't display a splash-screen at startup.\n\
233     -help                    This message.\n\
234 \n\
235 See the manual for other options and X resources.\n\
236 \n\
237 The `xscreensaver' program should be left running in the background.\n\
238 Use the `xscreensaver-demo' and `xscreensaver-command' programs to\n\
239 manipulate a running xscreensaver.\n\
240 \n\
241 The `*programs' resource controls which graphics demos will be launched by\n\
242 the screensaver.  See `man xscreensaver' or the web page for more details.\n\
243 \n\
244 Just getting started?  Try this:\n\
245 \n\
246         xscreensaver &\n\
247         xscreensaver-demo\n\
248 \n\
249 For updates, check http://www.jwz.org/xscreensaver/\n\
250 \n",
251           si->version);
252   fflush (stdout);
253   fflush (stderr);
254   exit (1);
255 }
256
257
258 char *
259 timestring (void)
260 {
261   time_t now = time ((time_t *) 0);
262   char *str = (char *) ctime (&now);
263   char *nl = (char *) strchr (str, '\n');
264   if (nl) *nl = 0; /* take off that dang newline */
265   return str;
266 }
267
268 static Bool blurb_timestamp_p = False;   /* kludge */
269
270 const char *
271 blurb (void)
272 {
273   if (!blurb_timestamp_p)
274     return progname;
275   else
276     {
277       static char buf[255];
278       char *ct = timestring();
279       int n = strlen(progname);
280       if (n > 100) n = 99;
281       strncpy(buf, progname, n);
282       buf[n++] = ':';
283       buf[n++] = ' ';
284       strncpy(buf+n, ct+11, 8);
285       strcpy(buf+n+9, ": ");
286       return buf;
287     }
288 }
289
290
291 int
292 saver_ehandler (Display *dpy, XErrorEvent *error)
293 {
294   saver_info *si = global_si_kludge;    /* I hate C so much... */
295
296   if (!real_stderr) real_stderr = stderr;
297
298   fprintf (real_stderr, "\n"
299            "#######################################"
300            "#######################################\n\n"
301            "%s: X Error!  PLEASE REPORT THIS BUG.\n\n"
302            "#######################################"
303            "#######################################\n\n",
304            blurb());
305   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
306     {
307       fprintf (real_stderr, "\n");
308       if (si->prefs.xsync_p)
309         {
310           saver_exit (si, -1, "because of synchronous X Error");
311         }
312       else
313         {
314           fprintf (real_stderr,
315                    "#######################################"
316                    "#######################################\n\n");
317           fprintf (real_stderr,
318    "    If at all possible, please re-run xscreensaver with the command\ e\n"
319    "    line arguments `-sync -verbose -no-capture', and reproduce this\n"
320    "    bug.  That will cause xscreensaver to dump a `core' file to the\n"
321    "    current directory.  Please include the stack trace from that core\n"
322    "    file in your bug report.  *DO NOT* mail the core file itself!\n"
323    "    That won't work.\n"
324    "\n"
325    "    http://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
326    "    the most useful bug reports, and how to examine core files.\n"
327    "\n"
328    "    The more information you can provide, the better.  But please\n"
329    "    report this bug, regardless!\n"
330    "\n");
331           fprintf (real_stderr,
332                    "#######################################"
333                    "#######################################\n\n");
334
335           saver_exit (si, -1, 0);
336         }
337     }
338   else
339     fprintf (real_stderr, " (nonfatal.)\n");
340   return 0;
341 }
342
343
344 /* This error handler is used only while the X connection is being set up;
345    after we've got a connection, we don't use this handler again.  The only
346    reason for having this is so that we can present a more idiot-proof error
347    message than "cannot open display."
348  */
349 static void 
350 startup_ehandler (String name, String type, String class,
351                   String defalt,  /* one can't even spel properly
352                                      in this joke of a language */
353                   String *av, Cardinal *ac)
354 {
355   char fmt[512];
356   String p[10];
357   saver_info *si = global_si_kludge;    /* I hate C so much... */
358   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
359   *fmt = 0;
360   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
361                             fmt, sizeof(fmt)-1, *db);
362
363   fprintf (stderr, "%s: ", blurb());
364
365   memset (p, 0, sizeof(p));
366   if (*ac > countof (p)) *ac = countof (p);
367   memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
368   fprintf (stderr, fmt,         /* Did I mention that I hate C? */
369            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
370   fprintf (stderr, "\n");
371
372   describe_uids (si, stderr);
373
374   if (si->orig_uid && !strncmp (si->orig_uid, "root/", 5))
375     {
376       fprintf (stderr, "\n"
377           "%s: This is probably because you're logging in as root.  You\n"
378 "              shouldn't log in as root: you should log in as a normal user,\n"
379 "              and then `su' as needed.  If you insist on logging in as\n"
380 "              root, you will have to turn off X's security features before\n"
381 "              xscreensaver will work.\n"
382                "\n"
383 "              Please read the manual and FAQ for more information:\n",
384                blurb());
385     }
386   else
387     {
388       fprintf (stderr, "\n"
389           "%s: Errors at startup are usually authorization problems.\n"
390 "              But you're not logging in as root (good!) so something\n"
391 "              else must be wrong.  Did you read the manual and the FAQ?\n",
392            blurb());
393     }
394
395   fprintf (stderr, "\n"
396           "              http://www.jwz.org/xscreensaver/faq.html\n"
397           "              http://www.jwz.org/xscreensaver/man.html\n"
398           "\n");
399
400   fflush (stderr);
401   fflush (stdout);
402   exit (1);
403 }
404
405 \f
406 /* The zillions of initializations.
407  */
408
409 /* Set progname, version, etc.  This is done very early.
410  */
411 static void
412 set_version_string (saver_info *si, int *argc, char **argv)
413 {
414   progclass = "XScreenSaver";
415
416   /* progname is reset later, after we connect to X. */
417   progname = strrchr(argv[0], '/');
418   if (progname) progname++;
419   else progname = argv[0];
420
421   if (strlen(progname) > 100)   /* keep it short. */
422     progname[99] = 0;
423
424   /* The X resource database blows up if argv[0] has a "." in it. */
425   {
426     char *s = argv[0];
427     while ((s = strchr (s, '.')))
428       *s = '_';
429   }
430
431   si->version = (char *) malloc (5);
432   memcpy (si->version, screensaver_id + 17, 4);
433   si->version [4] = 0;
434 }
435
436
437 /* Initializations that potentially take place as a priveleged user:
438    If the xscreensaver executable is setuid root, then these initializations
439    are run as root, before discarding privileges.
440  */
441 static void
442 privileged_initialization (saver_info *si, int *argc, char **argv)
443 {
444 #ifndef NO_LOCKING
445   /* before hack_uid() for proper permissions */
446   lock_priv_init (*argc, argv, si->prefs.verbose_p);
447 #endif /* NO_LOCKING */
448
449   hack_uid (si);
450 }
451
452
453 /* Figure out what locking mechanisms are supported.
454  */
455 static void
456 lock_initialization (saver_info *si, int *argc, char **argv)
457 {
458 #ifdef NO_LOCKING
459   si->locking_disabled_p = True;
460   si->nolock_reason = "not compiled with locking support";
461 #else /* !NO_LOCKING */
462
463   /* Finish initializing locking, now that we're out of privileged code. */
464   if (! lock_init (*argc, argv, si->prefs.verbose_p))
465     {
466       si->locking_disabled_p = True;
467       si->nolock_reason = "error getting password";
468     }
469 #endif /* NO_LOCKING */
470
471   hack_uid (si);
472 }
473
474
475 /* Open the connection to the X server, and intern our Atoms.
476  */
477 static Widget
478 connect_to_server (saver_info *si, int *argc, char **argv)
479 {
480   Widget toplevel_shell;
481
482 #ifdef HAVE_PUTENV
483   char *d = getenv ("DISPLAY");
484   if (!d || !*d)
485     {
486       const char ndpy[] = "DISPLAY=:0.0";
487       /* if (si->prefs.verbose_p) */      /* sigh, too early to test this... */
488         fprintf (stderr,
489                  "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
490                  blurb(), ndpy+8);
491       if (putenv (ndpy))
492         abort ();
493     }
494 #endif /* HAVE_PUTENV */
495
496   XSetErrorHandler (saver_ehandler);
497
498   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
499   toplevel_shell = XtAppInitialize (&si->app, progclass,
500                                     options, XtNumber (options),
501                                     argc, argv, defaults, 0, 0);
502   XtAppSetErrorMsgHandler (si->app, 0);
503
504   si->dpy = XtDisplay (toplevel_shell);
505   si->prefs.db = XtDatabase (si->dpy);
506   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
507
508   if(strlen(progname) > 100)    /* keep it short. */
509     progname [99] = 0;
510
511   db = si->prefs.db;    /* resources.c needs this */
512
513   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
514   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
515   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
516   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
517   XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False);
518   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
519                                          False);
520   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
521   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
522   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
523   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
524   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
525   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
526   XA_PREV = XInternAtom (si->dpy, "PREV", False);
527   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
528   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
529   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
530   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
531   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
532   XA_BLANK = XInternAtom (si->dpy, "BLANK", False);
533   XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
534   XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
535
536   return toplevel_shell;
537 }
538
539
540 /* Handle the command-line arguments that were not handled for us by Xt.
541    Issue an error message and exit if there are unknown options.
542  */
543 static void
544 process_command_line (saver_info *si, int *argc, char **argv)
545 {
546   int i;
547   for (i = 1; i < *argc; i++)
548     {
549       if (!strcmp (argv[i], "-debug"))
550         /* no resource for this one, out of paranoia. */
551         si->prefs.debug_p = True;
552
553       else if (!strcmp (argv[i], "-h") ||
554                !strcmp (argv[i], "-help") ||
555                !strcmp (argv[i], "--help"))
556         do_help (si);
557
558       else
559         {
560           const char *s = argv[i];
561           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
562                    blurb(), s);
563
564           if (s[0] == '-' && s[1] == '-') s++;
565           if (!strcmp (s, "-activate") ||
566               !strcmp (s, "-deactivate") ||
567               !strcmp (s, "-cycle") ||
568               !strcmp (s, "-next") ||
569               !strcmp (s, "-prev") ||
570               !strcmp (s, "-exit") ||
571               !strcmp (s, "-restart") ||
572               !strcmp (s, "-demo") ||
573               !strcmp (s, "-prefs") ||
574               !strcmp (s, "-preferences") ||
575               !strcmp (s, "-lock") ||
576               !strcmp (s, "-version") ||
577               !strcmp (s, "-time"))
578             {
579
580               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
581                 fprintf (stderr, "\n\
582     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
583               else
584                 fprintf (stderr, "\n\
585     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
586
587               fprintf (stderr, "\
588     The `xscreensaver' program is a daemon that runs in the background.\n\
589     You control a running xscreensaver process by sending it messages\n\
590     with `xscreensaver-demo' or `xscreensaver-command'.\n\
591 .   See the man pages for details, or check the web page:\n\
592     http://www.jwz.org/xscreensaver/\n\n");
593
594               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
595                  suggest that explicitly. */
596               if (!strcmp (s, "-lock"))
597                 fprintf (stderr, "\
598     Or perhaps you meant either the \"-lock-mode\" or the\n\
599     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
600             }
601
602           exit (1);
603         }
604     }
605 }
606
607
608 /* Print out the xscreensaver banner to the tty if applicable;
609    Issue any other warnings that are called for at this point.
610  */
611 static void
612 print_banner (saver_info *si)
613 {
614   saver_preferences *p = &si->prefs;
615
616   /* This resource gets set some time before the others, so that we know
617      whether to print the banner (and so that the banner gets printed before
618      any resource-database-related error messages.)
619    */
620   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
621
622   /* Ditto, for the locking_disabled_p message. */
623   p->lock_p = get_boolean_resource ("lock", "Boolean");
624
625   if (p->verbose_p)
626     fprintf (stderr,
627              "%s %s, copyright (c) 1991-2001 "
628              "by Jamie Zawinski <jwz@jwz.org>.\n",
629              progname, si->version);
630
631   if (p->debug_p)
632     fprintf (stderr, "\n"
633              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
634              "\n"
635              "\tNote that in debug mode, the xscreensaver window will only\n"
636              "\tcover the left half of the screen.  (The idea is that you\n"
637              "\tcan still see debugging output in a shell, if you position\n"
638              "\tit on the right side of the screen.)\n"
639              "\n"
640              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
641              "\tuntrusted environments.\n"
642              "\n",
643              blurb());
644
645   if (p->verbose_p)
646     {
647       if (!si->uid_message || !*si->uid_message)
648         describe_uids (si, stderr);
649       else
650         {
651           if (si->orig_uid && *si->orig_uid)
652             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
653                      blurb(), si->orig_uid);
654           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
655         }
656
657       fprintf (stderr, "%s: in process %lu.\n", blurb(),
658                (unsigned long) getpid());
659     }
660
661   /* If locking was not able to be initalized for some reason, explain why.
662      (This has to be done after we've read the lock_p resource.)
663    */
664   if (p->lock_p && si->locking_disabled_p)
665     {
666       p->lock_p = False;
667       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
668                si->nolock_reason);
669       if (strstr (si->nolock_reason, "passw"))
670         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
671                  "consult the manual.\n", blurb());
672       else if (strstr (si->nolock_reason, "running as "))
673         fprintf (stderr, 
674                  "%s: locking only works when xscreensaver is launched\n"
675                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
676                  "\t See the manual for details.\n",
677                  blurb());
678     }
679 }
680
681
682 /* Examine all of the display's screens, and populate the `saver_screen_info'
683    structures.  Make sure this is called after hack_environment() sets $PATH.
684  */
685 static void
686 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
687 {
688   Bool found_any_writable_cells = False;
689   int i;
690
691   si->nscreens = ScreenCount(si->dpy);
692   si->screens = (saver_screen_info *)
693     calloc(sizeof(saver_screen_info), si->nscreens);
694
695   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
696
697   for (i = 0; i < si->nscreens; i++)
698     {
699       saver_screen_info *ssi = &si->screens[i];
700       ssi->global = si;
701       ssi->screen = ScreenOfDisplay (si->dpy, i);
702
703       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
704       ssi->default_visual =
705         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
706
707       ssi->current_visual = ssi->default_visual;
708       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
709
710       /* Execute a subprocess to find the GL visual. */
711       ssi->best_gl_visual = get_best_gl_visual (ssi);
712
713       if (ssi == si->default_screen)
714         /* Since this is the default screen, use the one already created. */
715         ssi->toplevel_shell = toplevel_shell;
716       else
717         /* Otherwise, each screen must have its own unmapped root widget. */
718         ssi->toplevel_shell =
719           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
720                               si->dpy,
721                               XtNscreen, ssi->screen,
722                               XtNvisual, ssi->current_visual,
723                               XtNdepth,  visual_depth (ssi->screen,
724                                                        ssi->current_visual),
725                               0);
726
727       if (! found_any_writable_cells)
728         {
729           /* Check to see whether fading is ever possible -- if any of the
730              screens on the display has a PseudoColor visual, then fading can
731              work (on at least some screens.)  If no screen has a PseudoColor
732              visual, then don't bother ever trying to fade, because it will
733              just cause a delay without causing any visible effect.
734           */
735           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
736               get_visual (ssi->screen, "PseudoColor", True, False) ||
737               get_visual (ssi->screen, "GrayScale", True, False))
738             found_any_writable_cells = True;
739         }
740     }
741
742   si->fading_possible_p = found_any_writable_cells;
743 }
744
745
746 /* If any server extensions have been requested, try and initialize them.
747    Issue warnings if requests can't be honored.
748  */
749 static void
750 initialize_server_extensions (saver_info *si)
751 {
752   saver_preferences *p = &si->prefs;
753
754   Bool server_has_xidle_extension_p = False;
755   Bool server_has_sgi_saver_extension_p = False;
756   Bool server_has_mit_saver_extension_p = False;
757   Bool system_has_proc_interrupts_p = False;
758   const char *piwhy = 0;
759
760   si->using_xidle_extension = p->use_xidle_extension;
761   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
762   si->using_mit_saver_extension = p->use_mit_saver_extension;
763   si->using_proc_interrupts = p->use_proc_interrupts;
764
765 #ifdef HAVE_XIDLE_EXTENSION
766   server_has_xidle_extension_p = query_xidle_extension (si);
767 #endif
768 #ifdef HAVE_SGI_SAVER_EXTENSION
769   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
770 #endif
771 #ifdef HAVE_MIT_SAVER_EXTENSION
772   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
773 #endif
774 #ifdef HAVE_PROC_INTERRUPTS
775   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
776 #endif
777
778   if (!server_has_xidle_extension_p)
779     si->using_xidle_extension = False;
780   else if (p->verbose_p)
781     {
782       if (si->using_xidle_extension)
783         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
784       else
785         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
786     }
787
788   if (!server_has_sgi_saver_extension_p)
789     si->using_sgi_saver_extension = False;
790   else if (p->verbose_p)
791     {
792       if (si->using_sgi_saver_extension)
793         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
794       else
795         fprintf (stderr,
796                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
797                  blurb());
798     }
799
800   if (!server_has_mit_saver_extension_p)
801     si->using_mit_saver_extension = False;
802   else if (p->verbose_p)
803     {
804       if (si->using_mit_saver_extension)
805         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
806                  blurb());
807       else
808         fprintf (stderr,
809                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
810                  blurb());
811     }
812
813   if (!system_has_proc_interrupts_p)
814     {
815       si->using_proc_interrupts = False;
816       if (p->verbose_p && piwhy)
817         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
818                  piwhy);
819     }
820   else if (p->verbose_p)
821     {
822       if (si->using_proc_interrupts)
823         fprintf (stderr,
824                  "%s: consulting /proc/interrupts for keyboard activity.\n",
825                  blurb());
826       else
827         fprintf (stderr,
828                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
829                  blurb());
830     }
831 }
832
833
834 /* For the case where we aren't using an server extensions, select user events
835    on all the existing windows, and launch timers to select events on
836    newly-created windows as well.
837
838    If a server extension is being used, this does nothing.
839  */
840 static void
841 select_events (saver_info *si)
842 {
843   saver_preferences *p = &si->prefs;
844   int i;
845
846   if (si->using_xidle_extension ||
847       si->using_mit_saver_extension ||
848       si->using_sgi_saver_extension)
849     return;
850
851   if (p->initial_delay)
852     {
853       if (p->verbose_p)
854         {
855           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
856                    (int) p->initial_delay/1000,
857                    (p->initial_delay == 1000 ? "" : "s"));
858           fflush (stderr);
859           fflush (stdout);
860         }
861       usleep (p->initial_delay);
862       if (p->verbose_p)
863         fprintf (stderr, " done.\n");
864     }
865
866   if (p->verbose_p)
867     {
868       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
869       fflush (stderr);
870       fflush (stdout);
871     }
872
873   /* Select events on the root windows of every screen.  This also selects
874      for window creation events, so that new subwindows will be noticed.
875    */
876   for (i = 0; i < si->nscreens; i++)
877     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
878                                False);
879
880   if (p->verbose_p)
881     fprintf (stderr, " done.\n");
882 }
883
884
885 void
886 maybe_reload_init_file (saver_info *si)
887 {
888   saver_preferences *p = &si->prefs;
889   if (init_file_changed_p (p))
890     {
891       if (p->verbose_p)
892         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
893                  blurb(), init_file_name());
894
895       load_init_file (p);
896
897       /* If a server extension is in use, and p->timeout has changed,
898          we need to inform the server of the new timeout. */
899       disable_builtin_screensaver (si, False);
900     }
901 }
902
903
904 /* Loop forever:
905
906        - wait until the user is idle;
907        - blank the screen;
908        - wait until the user is active;
909        - unblank the screen;
910        - repeat.
911
912  */
913 static void
914 main_loop (saver_info *si)
915 {
916   saver_preferences *p = &si->prefs;
917   Bool ok_to_unblank;
918
919   while (1)
920     {
921       Bool was_locked = False;
922       sleep_until_idle (si, True);
923
924       if (p->verbose_p)
925         {
926           if (si->demoing_p)
927             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
928                      si->selection_mode, timestring());
929           else
930             if (p->verbose_p)
931               fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
932                        timestring());
933         }
934
935       maybe_reload_init_file (si);
936
937       if (! blank_screen (si))
938         {
939           /* We were unable to grab either the keyboard or mouse.
940              This means we did not (and must not) blank the screen.
941              If we were to blank the screen while some other program
942              is holding both the mouse and keyboard grabbed, then
943              we would never be able to un-blank it!  We would never
944              see any events, and the display would be wedged.
945
946              So, just go around the loop again and wait for the
947              next bout of idleness.
948           */
949
950           fprintf (stderr,
951                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
952                    blurb());
953           continue;
954         }
955
956       kill_screenhack (si);
957
958       if (!si->throttled_p)
959         spawn_screenhack (si, True);
960       else if (p->verbose_p)
961         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
962
963       /* Don't start the cycle timer in demo mode. */
964       if (!si->demoing_p && p->cycle)
965         si->cycle_id = XtAppAddTimeOut (si->app,
966                                         (si->selection_mode
967                                          /* see comment in cycle_timer() */
968                                          ? 1000 * 60 * 60
969                                          : p->cycle),
970                                         cycle_timer,
971                                         (XtPointer) si);
972
973
974 #ifndef NO_LOCKING
975       {
976         Time lock_timeout = p->lock_timeout;
977
978         if (si->emergency_lock_p && p->lock_p && lock_timeout)
979           {
980             int secs = p->lock_timeout / 1000;
981             if (p->verbose_p)
982               fprintf (stderr,
983                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
984                        blurb(),
985                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
986             lock_timeout = 0;
987           }
988
989         si->emergency_lock_p = False;
990
991         if (!si->demoing_p &&           /* if not going into demo mode */
992             p->lock_p &&                /* and locking is enabled */
993             !si->locking_disabled_p &&  /* and locking is possible */
994             lock_timeout == 0)          /* and locking is not timer-deferred */
995           set_locked_p (si, True);      /* then lock right now. */
996
997         /* locked_p might be true already because of the above, or because of
998            the LOCK ClientMessage.  But if not, and if we're supposed to lock
999            after some time, set up a timer to do so.
1000         */
1001         if (p->lock_p &&
1002             !si->locked_p &&
1003             lock_timeout > 0)
1004           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
1005                                          activate_lock_timer,
1006                                          (XtPointer) si);
1007       }
1008 #endif /* !NO_LOCKING */
1009
1010
1011       ok_to_unblank = True;
1012       do {
1013
1014         sleep_until_idle (si, False);           /* until not idle */
1015         maybe_reload_init_file (si);
1016
1017 #ifndef NO_LOCKING
1018         if (si->locked_p)
1019           {
1020             saver_screen_info *ssi = si->default_screen;
1021             if (si->locking_disabled_p) abort ();
1022
1023             was_locked = True;
1024             si->dbox_up_p = True;
1025             suspend_screenhack (si, True);
1026             XUndefineCursor (si->dpy, ssi->screensaver_window);
1027
1028             ok_to_unblank = unlock_p (si);
1029
1030             si->dbox_up_p = False;
1031             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
1032             suspend_screenhack (si, False);     /* resume */
1033
1034             if (!ok_to_unblank &&
1035                 !screenhack_running_p (si))
1036               {
1037                 /* If the lock dialog has been dismissed and we're not about to
1038                    unlock the screen, and there is currently no hack running,
1039                    then launch one.  (There might be no hack running if DPMS
1040                    had kicked in.  But DPMS is off now, so bring back the hack)
1041                  */
1042                 if (si->cycle_id)
1043                   XtRemoveTimeOut (si->cycle_id);
1044                 si->cycle_id = 0;
1045                 cycle_timer ((XtPointer) si, 0);
1046               }
1047           }
1048 #endif /* !NO_LOCKING */
1049
1050         } while (!ok_to_unblank);
1051
1052
1053       if (p->verbose_p)
1054         fprintf (stderr, "%s: unblanking screen at %s.\n",
1055                  blurb(), timestring ());
1056
1057       /* Kill before unblanking, to stop drawing as soon as possible. */
1058       kill_screenhack (si);
1059       unblank_screen (si);
1060
1061       set_locked_p (si, False);
1062       si->emergency_lock_p = False;
1063       si->demoing_p = 0;
1064       si->selection_mode = 0;
1065
1066       /* If we're throttled, and the user has explicitly unlocked the screen,
1067          then unthrottle.  If we weren't locked, then don't unthrottle
1068          automatically, because someone might have just bumped the desk... */
1069       if (was_locked)
1070         {
1071           if (si->throttled_p && p->verbose_p)
1072             fprintf (stderr, "%s: unthrottled.\n", blurb());
1073           si->throttled_p = False;
1074         }
1075
1076       if (si->cycle_id)
1077         {
1078           XtRemoveTimeOut (si->cycle_id);
1079           si->cycle_id = 0;
1080         }
1081
1082       if (si->lock_id)
1083         {
1084           XtRemoveTimeOut (si->lock_id);
1085           si->lock_id = 0;
1086         }
1087
1088       if (p->verbose_p)
1089         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1090     }
1091 }
1092
1093 static void analyze_display (saver_info *si);
1094
1095 int
1096 main (int argc, char **argv)
1097 {
1098   Widget shell;
1099   saver_info the_si;
1100   saver_info *si = &the_si;
1101   saver_preferences *p = &si->prefs;
1102   int i;
1103
1104   memset(si, 0, sizeof(*si));
1105   global_si_kludge = si;        /* I hate C so much... */
1106
1107 # undef ya_rand_init
1108   ya_rand_init (0);
1109
1110   save_argv (argc, argv);
1111   set_version_string (si, &argc, argv);
1112   privileged_initialization (si, &argc, argv);
1113   hack_environment (si);
1114
1115   shell = connect_to_server (si, &argc, argv);
1116   process_command_line (si, &argc, argv);
1117   print_banner (si);
1118
1119   load_init_file (p);  /* must be before initialize_per_screen_info() */
1120   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1121
1122   /* We can only issue this warnings now. */
1123   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1124     fprintf (stderr,
1125              "%s: there are no PseudoColor or GrayScale visuals.\n"
1126              "%s: ignoring the request for fading/unfading.\n",
1127              blurb(), blurb());
1128
1129   for (i = 0; i < si->nscreens; i++)
1130     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1131       exit (1);
1132
1133   lock_initialization (si, &argc, argv);
1134
1135   if (p->xsync_p) XSynchronize (si->dpy, True);
1136   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1137
1138   if (p->verbose_p) analyze_display (si);
1139   initialize_server_extensions (si);
1140
1141   si->blank_time = time ((time_t) 0); /* must be before ..._window */
1142   initialize_screensaver_window (si);
1143
1144   select_events (si);
1145   init_sigchld ();
1146   disable_builtin_screensaver (si, True);
1147   initialize_stderr (si);
1148
1149   make_splash_dialog (si);
1150
1151   main_loop (si);               /* doesn't return */
1152   return 0;
1153 }
1154
1155 \f
1156 /* Processing ClientMessage events.
1157  */
1158
1159 static void
1160 clientmessage_response (saver_info *si, Window w, Bool error,
1161                         const char *stderr_msg,
1162                         const char *protocol_msg)
1163 {
1164   char *proto;
1165   int L;
1166   saver_preferences *p = &si->prefs;
1167   if (error || p->verbose_p)
1168     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1169
1170   L = strlen(protocol_msg);
1171   proto = (char *) malloc (L + 2);
1172   proto[0] = (error ? '-' : '+');
1173   strcpy (proto+1, protocol_msg);
1174   L++;
1175
1176   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1177                    PropModeReplace, (unsigned char *) proto, L);
1178   XSync (si->dpy, False);
1179   free (proto);
1180 }
1181
1182 Bool
1183 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1184 {
1185   saver_preferences *p = &si->prefs;
1186   Atom type = 0;
1187   Window window = event->xclient.window;
1188
1189   /* Preferences might affect our handling of client messages. */
1190   maybe_reload_init_file (si);
1191
1192   if (event->xclient.message_type != XA_SCREENSAVER)
1193     {
1194       char *str;
1195       str = XGetAtomName (si->dpy, event->xclient.message_type);
1196       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1197                blurb(), (str ? str : "(null)"));
1198       if (str) XFree (str);
1199       return False;
1200     }
1201   if (event->xclient.format != 32)
1202     {
1203       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1204                blurb(), event->xclient.format);
1205       return False;
1206     }
1207
1208   type = event->xclient.data.l[0];
1209   if (type == XA_ACTIVATE)
1210     {
1211       if (until_idle_p)
1212         {
1213           clientmessage_response(si, window, False,
1214                                  "ACTIVATE ClientMessage received.",
1215                                  "activating.");
1216           si->selection_mode = 0;
1217           si->demoing_p = False;
1218
1219           if (si->throttled_p && p->verbose_p)
1220             fprintf (stderr, "%s: unthrottled.\n", blurb());
1221           si->throttled_p = False;
1222
1223           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1224             {
1225               XForceScreenSaver (si->dpy, ScreenSaverActive);
1226               return False;
1227             }
1228           else
1229             {
1230               return True;
1231             }
1232         }
1233       clientmessage_response(si, window, True,
1234                        "ClientMessage ACTIVATE received while already active.",
1235                              "already active.");
1236     }
1237   else if (type == XA_DEACTIVATE)
1238     {
1239       if (! until_idle_p)
1240         {
1241           if (si->throttled_p && p->verbose_p)
1242             fprintf (stderr, "%s: unthrottled.\n", blurb());
1243           si->throttled_p = False;
1244
1245           clientmessage_response(si, window, False,
1246                                  "DEACTIVATE ClientMessage received.",
1247                                  "deactivating.");
1248           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1249             {
1250               XForceScreenSaver (si->dpy, ScreenSaverReset);
1251               return False;
1252             }
1253           else
1254             {
1255               return True;
1256             }
1257         }
1258       clientmessage_response(si, window, True,
1259                            "ClientMessage DEACTIVATE received while inactive.",
1260                              "not active.");
1261     }
1262   else if (type == XA_CYCLE)
1263     {
1264       if (! until_idle_p)
1265         {
1266           clientmessage_response(si, window, False,
1267                                  "CYCLE ClientMessage received.",
1268                                  "cycling.");
1269           si->selection_mode = 0;       /* 0 means randomize when its time. */
1270           si->demoing_p = False;
1271
1272           if (si->throttled_p && p->verbose_p)
1273             fprintf (stderr, "%s: unthrottled.\n", blurb());
1274           si->throttled_p = False;
1275
1276           if (si->cycle_id)
1277             XtRemoveTimeOut (si->cycle_id);
1278           si->cycle_id = 0;
1279           cycle_timer ((XtPointer) si, 0);
1280           return False;
1281         }
1282       clientmessage_response(si, window, True,
1283                              "ClientMessage CYCLE received while inactive.",
1284                              "not active.");
1285     }
1286   else if (type == XA_NEXT || type == XA_PREV)
1287     {
1288       clientmessage_response(si, window, False,
1289                              (type == XA_NEXT
1290                               ? "NEXT ClientMessage received."
1291                               : "PREV ClientMessage received."),
1292                              "cycling.");
1293       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1294       si->demoing_p = False;
1295
1296       if (si->throttled_p && p->verbose_p)
1297         fprintf (stderr, "%s: unthrottled.\n", blurb());
1298       si->throttled_p = False;
1299
1300       if (! until_idle_p)
1301         {
1302           if (si->cycle_id)
1303             XtRemoveTimeOut (si->cycle_id);
1304           si->cycle_id = 0;
1305           cycle_timer ((XtPointer) si, 0);
1306         }
1307       else
1308         return True;
1309     }
1310   else if (type == XA_SELECT)
1311     {
1312       char buf [255];
1313       char buf2 [255];
1314       long which = event->xclient.data.l[1];
1315
1316       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1317       sprintf (buf2, "activating (%ld).", which);
1318       clientmessage_response (si, window, False, buf, buf2);
1319
1320       if (which < 0) which = 0;         /* 0 == "random" */
1321       si->selection_mode = which;
1322       si->demoing_p = False;
1323
1324       if (si->throttled_p && p->verbose_p)
1325         fprintf (stderr, "%s: unthrottled.\n", blurb());
1326       si->throttled_p = False;
1327
1328       if (! until_idle_p)
1329         {
1330           if (si->cycle_id)
1331             XtRemoveTimeOut (si->cycle_id);
1332           si->cycle_id = 0;
1333           cycle_timer ((XtPointer) si, 0);
1334         }
1335       else
1336         return True;
1337     }
1338   else if (type == XA_EXIT)
1339     {
1340       /* Ignore EXIT message if the screen is locked. */
1341       if (until_idle_p || !si->locked_p)
1342         {
1343           clientmessage_response (si, window, False,
1344                                   "EXIT ClientMessage received.",
1345                                   "exiting.");
1346           if (! until_idle_p)
1347             {
1348               unblank_screen (si);
1349               kill_screenhack (si);
1350               XSync (si->dpy, False);
1351             }
1352           saver_exit (si, 0, 0);
1353         }
1354       else
1355         clientmessage_response (si, window, True,
1356                                 "EXIT ClientMessage received while locked.",
1357                                 "screen is locked.");
1358     }
1359   else if (type == XA_RESTART)
1360     {
1361       /* The RESTART message works whether the screensaver is active or not,
1362          unless the screen is locked, in which case it doesn't work.
1363        */
1364       if (until_idle_p || !si->locked_p)
1365         {
1366           clientmessage_response (si, window, False,
1367                                   "RESTART ClientMessage received.",
1368                                   "restarting.");
1369           if (! until_idle_p)
1370             {
1371               unblank_screen (si);
1372               kill_screenhack (si);
1373               XSync (si->dpy, False);
1374             }
1375
1376           fflush (stdout);
1377           fflush (stderr);
1378           if (real_stdout) fflush (real_stdout);
1379           if (real_stderr) fflush (real_stderr);
1380           /* make sure error message shows up before exit. */
1381           if (real_stderr && stderr != real_stderr)
1382             dup2 (fileno(real_stderr), fileno(stderr));
1383
1384           restart_process (si);
1385           exit (1);     /* shouldn't get here; but if restarting didn't work,
1386                            make this command be the same as EXIT. */
1387         }
1388       else
1389         clientmessage_response (si, window, True,
1390                                 "RESTART ClientMessage received while locked.",
1391                                 "screen is locked.");
1392     }
1393   else if (type == XA_DEMO)
1394     {
1395       long arg = event->xclient.data.l[1];
1396       Bool demo_one_hack_p = (arg == 300);
1397
1398       if (demo_one_hack_p)
1399         {
1400           if (until_idle_p)
1401             {
1402               long which = event->xclient.data.l[2];
1403               char buf [255];
1404               char buf2 [255];
1405               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1406               sprintf (buf2, "demoing (%ld).", which);
1407               clientmessage_response (si, window, False, buf, buf2);
1408
1409               if (which < 0) which = 0;         /* 0 == "random" */
1410               si->selection_mode = which;
1411               si->demoing_p = True;
1412
1413               if (si->throttled_p && p->verbose_p)
1414                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1415               si->throttled_p = False;
1416
1417               return True;
1418             }
1419
1420           clientmessage_response (si, window, True,
1421                                   "DEMO ClientMessage received while active.",
1422                                   "already active.");
1423         }
1424       else
1425         {
1426           clientmessage_response (si, window, True,
1427                                   "obsolete form of DEMO ClientMessage.",
1428                                   "obsolete form of DEMO ClientMessage.");
1429         }
1430     }
1431   else if (type == XA_PREFS)
1432     {
1433       clientmessage_response (si, window, True,
1434                               "the PREFS client-message is obsolete.",
1435                               "the PREFS client-message is obsolete.");
1436     }
1437   else if (type == XA_LOCK)
1438     {
1439 #ifdef NO_LOCKING
1440       clientmessage_response (si, window, True,
1441                               "not compiled with support for locking.",
1442                               "locking not enabled.");
1443 #else /* !NO_LOCKING */
1444       if (si->locking_disabled_p)
1445         clientmessage_response (si, window, True,
1446                       "LOCK ClientMessage received, but locking is disabled.",
1447                               "locking not enabled.");
1448       else if (si->locked_p)
1449         clientmessage_response (si, window, True,
1450                            "LOCK ClientMessage received while already locked.",
1451                                 "already locked.");
1452       else
1453         {
1454           char buf [255];
1455           char *response = (until_idle_p
1456                             ? "activating and locking."
1457                             : "locking.");
1458           sprintf (buf, "LOCK ClientMessage received; %s", response);
1459           clientmessage_response (si, window, False, buf, response);
1460           set_locked_p (si, True);
1461           si->selection_mode = 0;
1462           si->demoing_p = False;
1463
1464           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1465             {
1466               XtRemoveTimeOut (si->lock_id);
1467               si->lock_id = 0;
1468             }
1469
1470           if (until_idle_p)
1471             {
1472               if (si->using_mit_saver_extension ||
1473                   si->using_sgi_saver_extension)
1474                 {
1475                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1476                   return False;
1477                 }
1478               else
1479                 {
1480                   return True;
1481                 }
1482             }
1483         }
1484 #endif /* !NO_LOCKING */
1485     }
1486   else if (type == XA_THROTTLE)
1487     {
1488       if (si->throttled_p)
1489         clientmessage_response (si, window, True,
1490                                 "THROTTLE ClientMessage received, but "
1491                                 "already throttled.",
1492                                 "already throttled.");
1493       else
1494         {
1495           char buf [255];
1496           char *response = "throttled.";
1497           si->throttled_p = True;
1498           si->selection_mode = 0;
1499           si->demoing_p = False;
1500           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
1501           clientmessage_response (si, window, False, buf, response);
1502
1503           if (! until_idle_p)
1504             {
1505               if (si->cycle_id)
1506                 XtRemoveTimeOut (si->cycle_id);
1507               si->cycle_id = 0;
1508               cycle_timer ((XtPointer) si, 0);
1509             }
1510         }
1511     }
1512   else if (type == XA_UNTHROTTLE)
1513     {
1514       if (! si->throttled_p)
1515         clientmessage_response (si, window, True,
1516                                 "UNTHROTTLE ClientMessage received, but "
1517                                 "not throttled.",
1518                                 "not throttled.");
1519       else
1520         {
1521           char buf [255];
1522           char *response = "unthrottled.";
1523           si->throttled_p = False;
1524           si->selection_mode = 0;
1525           si->demoing_p = False;
1526           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
1527           clientmessage_response (si, window, False, buf, response);
1528
1529           if (! until_idle_p)
1530             {
1531               if (si->cycle_id)
1532                 XtRemoveTimeOut (si->cycle_id);
1533               si->cycle_id = 0;
1534               cycle_timer ((XtPointer) si, 0);
1535             }
1536         }
1537     }
1538   else
1539     {
1540       char buf [1024];
1541       char *str;
1542       str = (type ? XGetAtomName(si->dpy, type) : 0);
1543
1544       if (str)
1545         {
1546           if (strlen (str) > 80)
1547             strcpy (str+70, "...");
1548           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1549                    str);
1550           free (str);
1551         }
1552       else
1553         {
1554           sprintf (buf,
1555                    "unrecognised screensaver ClientMessage 0x%x received.",
1556                    (unsigned int) event->xclient.data.l[0]);
1557         }
1558
1559       clientmessage_response (si, window, True, buf, buf);
1560     }
1561   return False;
1562 }
1563
1564 \f
1565 /* Some random diagnostics printed in -verbose mode.
1566  */
1567
1568 static void
1569 analyze_display (saver_info *si)
1570 {
1571   int i, j;
1572   static struct {
1573     const char *name; const char *desc; Bool useful_p;
1574   } exts[] = {
1575
1576    { "SCREEN_SAVER",                            "SGI Screen-Saver",
1577 #     ifdef HAVE_SGI_SAVER_EXTENSION
1578         True
1579 #     else
1580         False
1581 #     endif
1582    }, { "SCREEN-SAVER",                         "SGI Screen-Saver",
1583 #     ifdef HAVE_SGI_SAVER_EXTENSION
1584         True
1585 #     else
1586         False
1587 #     endif
1588    }, { "MIT-SCREEN-SAVER",                     "MIT Screen-Saver",
1589 #     ifdef HAVE_MIT_SAVER_EXTENSION
1590         True
1591 #     else
1592         False
1593 #     endif
1594    }, { "XIDLE",                                "XIdle",           
1595 #     ifdef HAVE_XIDLE_EXTENSION
1596         True
1597 #     else
1598         False
1599 #     endif
1600    }, { "SGI-VIDEO-CONTROL",                    "SGI Video-Control",
1601 #     ifdef HAVE_SGI_VC_EXTENSION
1602         True
1603 #     else
1604         False
1605 #     endif
1606    }, { "READDISPLAY",                          "SGI Read-Display",
1607 #     ifdef HAVE_READ_DISPLAY_EXTENSION
1608         True
1609 #     else
1610         False
1611 #     endif
1612    }, { "MIT-SHM",                              "Shared Memory",   
1613 #     ifdef HAVE_XSHM_EXTENSION
1614         True
1615 #     else
1616         False
1617 #     endif
1618    }, { "DOUBLE-BUFFER",                        "Double-Buffering",
1619 #     ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1620         True
1621 #     else
1622         False
1623 #     endif
1624    }, { "DPMS",                                 "Power Management",
1625 #     ifdef HAVE_DPMS_EXTENSION
1626         True
1627 #     else
1628         False
1629 #     endif
1630    }, { "GLX",                                  "GLX",             
1631 #     ifdef HAVE_GL
1632         True
1633 #     else
1634         False
1635 #     endif
1636    }, { "XFree86-VidModeExtension",             "XF86 Video-Mode", 
1637 #     ifdef HAVE_XF86VMODE
1638         True
1639 #     else
1640         False
1641 #     endif
1642    }, { "XINERAMA",                             "Xinerama",
1643         True
1644    },
1645   };
1646
1647   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1648            DisplayString(si->dpy));
1649   fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1650            ServerVendor(si->dpy), VendorRelease(si->dpy));
1651
1652   fprintf (stderr, "%s: useful extensions:\n", blurb());
1653   for (i = 0; i < countof(exts); i++)
1654     {
1655       int op = 0, event = 0, error = 0;
1656       if (XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
1657         fprintf (stderr, "%s:  %s%s\n", blurb(),
1658                  exts[i].desc,
1659                  (exts[i].useful_p ? "" :
1660                   "       \t<== unsupported at compile-time!"));
1661     }
1662
1663   for (i = 0; i < si->nscreens; i++)
1664     {
1665       unsigned long colormapped_depths = 0;
1666       unsigned long non_mapped_depths = 0;
1667       XVisualInfo vi_in, *vi_out;
1668       int out_count;
1669       vi_in.screen = i;
1670       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1671       if (!vi_out) continue;
1672       for (j = 0; j < out_count; j++)
1673         if (vi_out[j].class == PseudoColor)
1674           colormapped_depths |= (1 << vi_out[j].depth);
1675         else
1676           non_mapped_depths  |= (1 << vi_out[j].depth);
1677       XFree ((char *) vi_out);
1678
1679       if (colormapped_depths)
1680         {
1681           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1682           for (j = 0; j < 32; j++)
1683             if (colormapped_depths & (1 << j))
1684               fprintf (stderr, " %d", j);
1685           fprintf (stderr, "\n");
1686         }
1687       if (non_mapped_depths)
1688         {
1689           fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1690           for (j = 0; j < 32; j++)
1691             if (non_mapped_depths & (1 << j))
1692               fprintf (stderr, " %d", j);
1693           fprintf (stderr, "\n");
1694         }
1695     }
1696 }
1697
1698 Bool
1699 display_is_on_console_p (saver_info *si)
1700 {
1701   Bool not_on_console = True;
1702   char *dpystr = DisplayString (si->dpy);
1703   char *tail = (char *) strchr (dpystr, ':');
1704   if (! tail || strncmp (tail, ":0", 2))
1705     not_on_console = True;
1706   else
1707     {
1708       char dpyname[255], localname[255];
1709       strncpy (dpyname, dpystr, tail-dpystr);
1710       dpyname [tail-dpystr] = 0;
1711       if (!*dpyname ||
1712           !strcmp(dpyname, "unix") ||
1713           !strcmp(dpyname, "localhost"))
1714         not_on_console = False;
1715       else if (gethostname (localname, sizeof (localname)))
1716         not_on_console = True;  /* can't find hostname? */
1717       else
1718         {
1719           /* We have to call gethostbyname() on the result of gethostname()
1720              because the two aren't guarenteed to be the same name for the
1721              same host: on some losing systems, one is a FQDN and the other
1722              is not.  Here in the wide wonderful world of Unix it's rocket
1723              science to obtain the local hostname in a portable fashion.
1724              
1725              And don't forget, gethostbyname() reuses the structure it
1726              returns, so we have to copy the fucker before calling it again.
1727              Thank you master, may I have another.
1728            */
1729           struct hostent *h = gethostbyname (dpyname);
1730           if (!h)
1731             not_on_console = True;
1732           else
1733             {
1734               char hn [255];
1735               struct hostent *l;
1736               strcpy (hn, h->h_name);
1737               l = gethostbyname (localname);
1738               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1739             }
1740         }
1741     }
1742   return !not_on_console;
1743 }