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