http://ftp.x.org/contrib/applications/xscreensaver-3.19.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 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_EXIT, XA_RESTART, XA_LOCK, XA_SELECT;
167 static Atom XA_THROTTLE, XA_UNTHROTTLE;
168 Atom XA_DEMO, XA_PREFS;
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-1999 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 line\n"
319    "    arguments `-sync -verbose', and reproduce this bug.  That will cause\n"
320    "    xscreensaver to dump a `core' file to the current directory.  Please\n"
321    "    include the stack trace from that core file in your bug report.\n"
322    "    Do NOT mail the core file itself!  That won't work.\n"
323    "\n"
324    "    http://www.jwz.org/xscreensaver/bugs.html explains how to create the\n"
325    "    most useful bug reports, and how to examine core files.\n"
326    "\n"
327    "    The more information you can provide, the better.  But please report\n"
328    "    report this bug, regardless!\n"
329    "\n");
330           fprintf (real_stderr,
331                    "#######################################"
332                    "#######################################\n\n");
333
334           saver_exit (si, -1, 0);
335         }
336     }
337   else
338     fprintf (real_stderr, " (nonfatal.)\n");
339   return 0;
340 }
341
342
343 /* This error handler is used only while the X connection is being set up;
344    after we've got a connection, we don't use this handler again.  The only
345    reason for having this is so that we can present a more idiot-proof error
346    message than "cannot open display."
347  */
348 static void 
349 startup_ehandler (String name, String type, String class,
350                   String defalt,  /* one can't even spel properly
351                                      in this joke of a language */
352                   String *av, Cardinal *ac)
353 {
354   char fmt[512];
355   String p[10];
356   saver_info *si = global_si_kludge;    /* I hate C so much... */
357   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
358   *fmt = 0;
359   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
360                             fmt, sizeof(fmt)-1, *db);
361
362   fprintf (stderr, "%s: ", blurb());
363
364   memset (p, 0, sizeof(p));
365   if (*ac > countof (p)) *ac = countof (p);
366   memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
367   fprintf (stderr, fmt,         /* Did I mention that I hate C? */
368            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
369   fprintf (stderr, "\n");
370
371   describe_uids (si, stderr);
372   fprintf (stderr, "\n"
373            "%s: Errors at startup are usually authorization problems.\n"
374            "              Did you read the manual?  Specifically, the parts\n"
375            "              that talk about XAUTH, XDM, and root logins?\n"
376            "\n"
377            "              http://www.jwz.org/xscreensaver/man.html\n"
378            "\n",
379            blurb());
380
381   fflush (stderr);
382   fflush (stdout);
383   exit (1);
384 }
385
386 \f
387 /* The zillions of initializations.
388  */
389
390 /* Set progname, version, etc.  This is done very early.
391  */
392 static void
393 set_version_string (saver_info *si, int *argc, char **argv)
394 {
395   progclass = "XScreenSaver";
396
397   /* progname is reset later, after we connect to X. */
398   progname = strrchr(argv[0], '/');
399   if (progname) progname++;
400   else progname = argv[0];
401
402   if (strlen(progname) > 100)   /* keep it short. */
403     progname[99] = 0;
404
405   /* The X resource database blows up if argv[0] has a "." in it. */
406   {
407     char *s = argv[0];
408     while ((s = strchr (s, '.')))
409       *s = '_';
410   }
411
412   si->version = (char *) malloc (5);
413   memcpy (si->version, screensaver_id + 17, 4);
414   si->version [4] = 0;
415 }
416
417
418 /* Initializations that potentially take place as a priveleged user:
419    If the xscreensaver executable is setuid root, then these initializations
420    are run as root, before discarding privileges.
421  */
422 static void
423 privileged_initialization (saver_info *si, int *argc, char **argv)
424 {
425 #ifndef NO_LOCKING
426   /* before hack_uid() for proper permissions */
427   lock_priv_init (*argc, argv, si->prefs.verbose_p);
428 #endif /* NO_LOCKING */
429
430   hack_uid (si);
431 }
432
433
434 /* Figure out what locking mechanisms are supported.
435  */
436 static void
437 lock_initialization (saver_info *si, int *argc, char **argv)
438 {
439 #ifdef NO_LOCKING
440   si->locking_disabled_p = True;
441   si->nolock_reason = "not compiled with locking support";
442 #else /* !NO_LOCKING */
443
444   /* Finish initializing locking, now that we're out of privileged code. */
445   if (! lock_init (*argc, argv, si->prefs.verbose_p))
446     {
447       si->locking_disabled_p = True;
448       si->nolock_reason = "error getting password";
449     }
450 #endif /* NO_LOCKING */
451
452   hack_uid (si);
453 }
454
455
456 /* Open the connection to the X server, and intern our Atoms.
457  */
458 static Widget
459 connect_to_server (saver_info *si, int *argc, char **argv)
460 {
461   Widget toplevel_shell;
462
463   XSetErrorHandler (saver_ehandler);
464
465   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
466   toplevel_shell = XtAppInitialize (&si->app, progclass,
467                                     options, XtNumber (options),
468                                     argc, argv, defaults, 0, 0);
469   XtAppSetErrorMsgHandler (si->app, 0);
470
471   si->dpy = XtDisplay (toplevel_shell);
472   si->prefs.db = XtDatabase (si->dpy);
473   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
474
475   if(strlen(progname) > 100)    /* keep it short. */
476     progname [99] = 0;
477
478   db = si->prefs.db;    /* resources.c needs this */
479
480   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
481   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
482   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
483   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
484   XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
485   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
486                                          False);
487   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
488   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
489   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
490   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
491   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
492   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
493   XA_PREV = XInternAtom (si->dpy, "PREV", False);
494   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
495   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
496   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
497   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
498   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
499   XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
500   XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
501
502   return toplevel_shell;
503 }
504
505
506 /* Handle the command-line arguments that were not handled for us by Xt.
507    Issue an error message and exit if there are unknown options.
508  */
509 static void
510 process_command_line (saver_info *si, int *argc, char **argv)
511 {
512   int i;
513   for (i = 1; i < *argc; i++)
514     {
515       if (!strcmp (argv[i], "-debug"))
516         /* no resource for this one, out of paranoia. */
517         si->prefs.debug_p = True;
518
519       else if (!strcmp (argv[i], "-h") ||
520                !strcmp (argv[i], "-help") ||
521                !strcmp (argv[i], "--help"))
522         do_help (si);
523
524       else
525         {
526           const char *s = argv[i];
527           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
528                    blurb(), s);
529
530           if (s[0] == '-' && s[1] == '-') s++;
531           if (!strcmp (s, "-activate") ||
532               !strcmp (s, "-deactivate") ||
533               !strcmp (s, "-cycle") ||
534               !strcmp (s, "-next") ||
535               !strcmp (s, "-prev") ||
536               !strcmp (s, "-exit") ||
537               !strcmp (s, "-restart") ||
538               !strcmp (s, "-demo") ||
539               !strcmp (s, "-prefs") ||
540               !strcmp (s, "-preferences") ||
541               !strcmp (s, "-lock") ||
542               !strcmp (s, "-version") ||
543               !strcmp (s, "-time"))
544             {
545
546               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
547                 fprintf (stderr, "\n\
548     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
549               else
550                 fprintf (stderr, "\n\
551     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
552
553               fprintf (stderr, "\
554     The `xscreensaver' program is a daemon that runs in the background.\n\
555     You control a running xscreensaver process by sending it messages\n\
556     with `xscreensaver-demo' or `xscreensaver-command'.\n\
557 .   See the man pages for details, or check the web page:\n\
558     http://www.jwz.org/xscreensaver/\n\n");
559
560               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
561                  suggest that explicitly. */
562               if (!strcmp (s, "-lock"))
563                 fprintf (stderr, "\
564     Or perhaps you meant either the \"-lock-mode\" or the\n\
565     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
566             }
567
568           exit (1);
569         }
570     }
571 }
572
573
574 /* Print out the xscreensaver banner to the tty if applicable;
575    Issue any other warnings that are called for at this point.
576  */
577 static void
578 print_banner (saver_info *si)
579 {
580   saver_preferences *p = &si->prefs;
581
582   /* This resource gets set some time before the others, so that we know
583      whether to print the banner (and so that the banner gets printed before
584      any resource-database-related error messages.)
585    */
586   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
587
588   /* Ditto, for the locking_disabled_p message. */
589   p->lock_p = get_boolean_resource ("lock", "Boolean");
590
591   if (p->verbose_p)
592     fprintf (stderr,
593              "%s %s, copyright (c) 1991-1999 "
594              "by Jamie Zawinski <jwz@jwz.org>.\n",
595              progname, si->version);
596
597   if (p->debug_p)
598     fprintf (stderr, "\n"
599              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
600              "\n"
601              "\tNote that in debug mode, the xscreensaver window will only\n"
602              "\tcover the left half of the screen.  (The idea is that you\n"
603              "\tcan still see debugging output in a shell, if you position\n"
604              "\tit on the right side of the screen.)\n"
605              "\n"
606              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
607              "\tuntrusted environments.\n"
608              "\n",
609              blurb());
610
611   if (p->verbose_p)
612     {
613       if (!si->uid_message || !*si->uid_message)
614         describe_uids (si, stderr);
615       else
616         {
617           if (si->orig_uid && *si->orig_uid)
618             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
619                      blurb(), si->orig_uid);
620           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
621         }
622
623       fprintf (stderr, "%s: in process %lu.\n", blurb(),
624                (unsigned long) getpid());
625     }
626
627   /* If locking was not able to be initalized for some reason, explain why.
628      (This has to be done after we've read the lock_p resource.)
629    */
630   if (p->lock_p && si->locking_disabled_p)
631     {
632       p->lock_p = False;
633       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
634                si->nolock_reason);
635       if (strstr (si->nolock_reason, "passw"))
636         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
637                  "consult the manual.\n", blurb());
638       else if (strstr (si->nolock_reason, "running as "))
639         fprintf (stderr, 
640                  "%s: locking only works when xscreensaver is launched\n"
641                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
642                  "\t See the manual for details.\n",
643                  blurb());
644     }
645 }
646
647
648 /* Examine all of the display's screens, and populate the `saver_screen_info'
649    structures.
650  */
651 static void
652 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
653 {
654   Bool found_any_writable_cells = False;
655   int i;
656
657   si->nscreens = ScreenCount(si->dpy);
658   si->screens = (saver_screen_info *)
659     calloc(sizeof(saver_screen_info), si->nscreens);
660
661   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
662
663   for (i = 0; i < si->nscreens; i++)
664     {
665       saver_screen_info *ssi = &si->screens[i];
666       ssi->global = si;
667       ssi->screen = ScreenOfDisplay (si->dpy, i);
668
669       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
670       ssi->default_visual =
671         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
672
673       ssi->current_visual = ssi->default_visual;
674       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
675
676       if (ssi == si->default_screen)
677         /* Since this is the default screen, use the one already created. */
678         ssi->toplevel_shell = toplevel_shell;
679       else
680         /* Otherwise, each screen must have its own unmapped root widget. */
681         ssi->toplevel_shell =
682           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
683                               si->dpy,
684                               XtNscreen, ssi->screen,
685                               XtNvisual, ssi->current_visual,
686                               XtNdepth,  visual_depth (ssi->screen,
687                                                        ssi->current_visual),
688                               0);
689
690       if (! found_any_writable_cells)
691         {
692           /* Check to see whether fading is ever possible -- if any of the
693              screens on the display has a PseudoColor visual, then fading can
694              work (on at least some screens.)  If no screen has a PseudoColor
695              visual, then don't bother ever trying to fade, because it will
696              just cause a delay without causing any visible effect.
697           */
698           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
699               get_visual (ssi->screen, "PseudoColor", True, False) ||
700               get_visual (ssi->screen, "GrayScale", True, False))
701             found_any_writable_cells = True;
702         }
703     }
704
705   si->fading_possible_p = found_any_writable_cells;
706 }
707
708
709 /* If any server extensions have been requested, try and initialize them.
710    Issue warnings if requests can't be honored.
711  */
712 static void
713 initialize_server_extensions (saver_info *si)
714 {
715   saver_preferences *p = &si->prefs;
716
717   Bool server_has_xidle_extension_p = False;
718   Bool server_has_sgi_saver_extension_p = False;
719   Bool server_has_mit_saver_extension_p = False;
720   Bool system_has_proc_interrupts_p = False;
721   const char *piwhy = 0;
722
723   si->using_xidle_extension = p->use_xidle_extension;
724   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
725   si->using_mit_saver_extension = p->use_mit_saver_extension;
726   si->using_proc_interrupts = p->use_proc_interrupts;
727
728 #ifdef HAVE_XIDLE_EXTENSION
729   server_has_xidle_extension_p = query_xidle_extension (si);
730 #endif
731 #ifdef HAVE_SGI_SAVER_EXTENSION
732   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
733 #endif
734 #ifdef HAVE_MIT_SAVER_EXTENSION
735   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
736 #endif
737 #ifdef HAVE_PROC_INTERRUPTS
738   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
739 #endif
740
741   if (!server_has_xidle_extension_p)
742     si->using_xidle_extension = False;
743   else if (p->verbose_p)
744     {
745       if (si->using_xidle_extension)
746         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
747       else
748         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
749     }
750
751   if (!server_has_sgi_saver_extension_p)
752     si->using_sgi_saver_extension = False;
753   else if (p->verbose_p)
754     {
755       if (si->using_sgi_saver_extension)
756         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
757       else
758         fprintf (stderr,
759                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
760                  blurb());
761     }
762
763   if (!server_has_mit_saver_extension_p)
764     si->using_mit_saver_extension = False;
765   else if (p->verbose_p)
766     {
767       if (si->using_mit_saver_extension)
768         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
769                  blurb());
770       else
771         fprintf (stderr,
772                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
773                  blurb());
774     }
775
776   if (!system_has_proc_interrupts_p)
777     {
778       si->using_proc_interrupts = False;
779       if (p->verbose_p && piwhy)
780         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
781                  piwhy);
782     }
783   else if (p->verbose_p)
784     {
785       if (si->using_proc_interrupts)
786         fprintf (stderr,
787                  "%s: consulting /proc/interrupts for keyboard activity.\n",
788                  blurb());
789       else
790         fprintf (stderr,
791                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
792                  blurb());
793     }
794 }
795
796
797 /* For the case where we aren't using an server extensions, select user events
798    on all the existing windows, and launch timers to select events on
799    newly-created windows as well.
800
801    If a server extension is being used, this does nothing.
802  */
803 static void
804 select_events (saver_info *si)
805 {
806   saver_preferences *p = &si->prefs;
807   int i;
808
809   if (si->using_xidle_extension ||
810       si->using_mit_saver_extension ||
811       si->using_sgi_saver_extension)
812     return;
813
814   if (p->initial_delay)
815     {
816       if (p->verbose_p)
817         {
818           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
819                    (int) p->initial_delay/1000,
820                    (p->initial_delay == 1000 ? "" : "s"));
821           fflush (stderr);
822           fflush (stdout);
823         }
824       usleep (p->initial_delay);
825       if (p->verbose_p)
826         fprintf (stderr, " done.\n");
827     }
828
829   if (p->verbose_p)
830     {
831       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
832       fflush (stderr);
833       fflush (stdout);
834     }
835
836   /* Select events on the root windows of every screen.  This also selects
837      for window creation events, so that new subwindows will be noticed.
838    */
839   for (i = 0; i < si->nscreens; i++)
840     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
841                                False);
842
843   if (p->verbose_p)
844     fprintf (stderr, " done.\n");
845 }
846
847
848 void
849 maybe_reload_init_file (saver_info *si)
850 {
851   saver_preferences *p = &si->prefs;
852   if (init_file_changed_p (p))
853     {
854       if (p->verbose_p)
855         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
856                  blurb(), init_file_name());
857
858       load_init_file (p);
859
860       /* If a server extension is in use, and p->timeout has changed,
861          we need to inform the server of the new timeout. */
862       disable_builtin_screensaver (si, False);
863     }
864 }
865
866
867 /* Loop forever:
868
869        - wait until the user is idle;
870        - blank the screen;
871        - wait until the user is active;
872        - unblank the screen;
873        - repeat.
874
875  */
876 static void
877 main_loop (saver_info *si)
878 {
879   saver_preferences *p = &si->prefs;
880   Bool ok_to_unblank;
881
882   while (1)
883     {
884       Bool was_locked = False;
885       sleep_until_idle (si, True);
886
887       if (p->verbose_p)
888         {
889           if (si->demoing_p)
890             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
891                      si->selection_mode, timestring());
892           else
893             if (p->verbose_p)
894               fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
895                        timestring());
896         }
897
898       maybe_reload_init_file (si);
899
900       if (! blank_screen (si))
901         {
902           /* We were unable to grab either the keyboard or mouse.
903              This means we did not (and must not) blank the screen.
904              If we were to blank the screen while some other program
905              is holding both the mouse and keyboard grabbed, then
906              we would never be able to un-blank it!  We would never
907              see any events, and the display would be wedged.
908
909              So, just go around the loop again and wait for the
910              next bout of idleness.
911           */
912
913           fprintf (stderr,
914                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
915                    blurb());
916           continue;
917         }
918
919       kill_screenhack (si);
920
921       if (!si->throttled_p)
922         spawn_screenhack (si, True);
923       else if (p->verbose_p)
924         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
925
926       /* Don't start the cycle timer in demo mode. */
927       if (!si->demoing_p && p->cycle)
928         si->cycle_id = XtAppAddTimeOut (si->app,
929                                         (si->selection_mode
930                                          /* see comment in cycle_timer() */
931                                          ? 1000 * 60 * 60
932                                          : p->cycle),
933                                         cycle_timer,
934                                         (XtPointer) si);
935
936
937 #ifndef NO_LOCKING
938       {
939         Time lock_timeout = p->lock_timeout;
940
941         if (si->emergency_lock_p && p->lock_p && lock_timeout)
942           {
943             int secs = p->lock_timeout / 1000;
944             if (p->verbose_p)
945               fprintf (stderr,
946                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
947                        blurb(),
948                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
949             lock_timeout = 0;
950           }
951
952         si->emergency_lock_p = False;
953
954         if (!si->demoing_p &&           /* if not going into demo mode */
955             p->lock_p &&                /* and locking is enabled */
956             !si->locking_disabled_p &&  /* and locking is possible */
957             lock_timeout == 0)          /* and locking is not timer-deferred */
958           set_locked_p (si, True);      /* then lock right now. */
959
960         /* locked_p might be true already because of the above, or because of
961            the LOCK ClientMessage.  But if not, and if we're supposed to lock
962            after some time, set up a timer to do so.
963         */
964         if (p->lock_p &&
965             !si->locked_p &&
966             lock_timeout > 0)
967           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
968                                          activate_lock_timer,
969                                          (XtPointer) si);
970       }
971 #endif /* !NO_LOCKING */
972
973
974       ok_to_unblank = True;
975       do {
976
977         sleep_until_idle (si, False);           /* until not idle */
978         maybe_reload_init_file (si);
979
980 #ifndef NO_LOCKING
981         if (si->locked_p)
982           {
983             saver_screen_info *ssi = si->default_screen;
984             if (si->locking_disabled_p) abort ();
985
986             was_locked = True;
987             si->dbox_up_p = True;
988             suspend_screenhack (si, True);
989             XUndefineCursor (si->dpy, ssi->screensaver_window);
990
991             ok_to_unblank = unlock_p (si);
992
993             si->dbox_up_p = False;
994             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
995             suspend_screenhack (si, False);     /* resume */
996           }
997 #endif /* !NO_LOCKING */
998
999         } while (!ok_to_unblank);
1000
1001
1002       if (p->verbose_p)
1003         fprintf (stderr, "%s: unblanking screen at %s.\n",
1004                  blurb(), timestring ());
1005
1006       /* Kill before unblanking, to stop drawing as soon as possible. */
1007       kill_screenhack (si);
1008       unblank_screen (si);
1009
1010       set_locked_p (si, False);
1011       si->emergency_lock_p = False;
1012       si->demoing_p = 0;
1013       si->selection_mode = 0;
1014
1015       /* If we're throttled, and the user has explicitly unlocked the screen,
1016          then unthrottle.  If we weren't locked, then don't unthrottle
1017          automatically, because someone might have just bumped the desk... */
1018       if (was_locked)
1019         {
1020           if (si->throttled_p && p->verbose_p)
1021             fprintf (stderr, "%s: unthrottled.\n", blurb());
1022           si->throttled_p = False;
1023         }
1024
1025       if (si->cycle_id)
1026         {
1027           XtRemoveTimeOut (si->cycle_id);
1028           si->cycle_id = 0;
1029         }
1030
1031       if (si->lock_id)
1032         {
1033           XtRemoveTimeOut (si->lock_id);
1034           si->lock_id = 0;
1035         }
1036
1037       if (p->verbose_p)
1038         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1039     }
1040 }
1041
1042 static void analyze_display (saver_info *si);
1043
1044 int
1045 main (int argc, char **argv)
1046 {
1047   Widget shell;
1048   saver_info the_si;
1049   saver_info *si = &the_si;
1050   saver_preferences *p = &si->prefs;
1051   int i;
1052
1053   memset(si, 0, sizeof(*si));
1054   global_si_kludge = si;        /* I hate C so much... */
1055
1056   srandom ((int) time ((time_t *) 0));
1057
1058   save_argv (argc, argv);
1059   set_version_string (si, &argc, argv);
1060   privileged_initialization (si, &argc, argv);
1061   hack_environment (si);
1062
1063   shell = connect_to_server (si, &argc, argv);
1064   process_command_line (si, &argc, argv);
1065   print_banner (si);
1066
1067   load_init_file (p);  /* must be before initialize_per_screen_info() */
1068   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1069
1070   /* We can only issue this warnings now. */
1071   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1072     fprintf (stderr,
1073              "%s: there are no PseudoColor or GrayScale visuals.\n"
1074              "%s: ignoring the request for fading/unfading.\n",
1075              blurb(), blurb());
1076
1077   for (i = 0; i < si->nscreens; i++)
1078     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1079       exit (1);
1080
1081   lock_initialization (si, &argc, argv);
1082
1083   if (p->xsync_p) XSynchronize (si->dpy, True);
1084   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1085
1086   if (p->verbose_p) analyze_display (si);
1087   initialize_server_extensions (si);
1088   initialize_screensaver_window (si);
1089   select_events (si);
1090   init_sigchld ();
1091   disable_builtin_screensaver (si, True);
1092   initialize_stderr (si);
1093
1094   make_splash_dialog (si);
1095
1096   main_loop (si);               /* doesn't return */
1097   return 0;
1098 }
1099
1100 \f
1101 /* Processing ClientMessage events.
1102  */
1103
1104 static void
1105 clientmessage_response (saver_info *si, Window w, Bool error,
1106                         const char *stderr_msg,
1107                         const char *protocol_msg)
1108 {
1109   char *proto;
1110   int L;
1111   saver_preferences *p = &si->prefs;
1112   if (error || p->verbose_p)
1113     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1114
1115   L = strlen(protocol_msg);
1116   proto = (char *) malloc (L + 2);
1117   proto[0] = (error ? '-' : '+');
1118   strcpy (proto+1, protocol_msg);
1119   L++;
1120
1121   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1122                    PropModeReplace, (unsigned char *) proto, L);
1123   XSync (si->dpy, False);
1124   free (proto);
1125 }
1126
1127 Bool
1128 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1129 {
1130   saver_preferences *p = &si->prefs;
1131   Atom type = 0;
1132   Window window = event->xclient.window;
1133
1134   /* Preferences might affect our handling of client messages. */
1135   maybe_reload_init_file (si);
1136
1137   if (event->xclient.message_type != XA_SCREENSAVER)
1138     {
1139       char *str;
1140       str = XGetAtomName (si->dpy, event->xclient.message_type);
1141       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1142                blurb(), (str ? str : "(null)"));
1143       if (str) XFree (str);
1144       return False;
1145     }
1146   if (event->xclient.format != 32)
1147     {
1148       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1149                blurb(), event->xclient.format);
1150       return False;
1151     }
1152
1153   type = event->xclient.data.l[0];
1154   if (type == XA_ACTIVATE)
1155     {
1156       if (until_idle_p)
1157         {
1158           clientmessage_response(si, window, False,
1159                                  "ACTIVATE ClientMessage received.",
1160                                  "activating.");
1161           si->selection_mode = 0;
1162           si->demoing_p = False;
1163
1164           if (si->throttled_p && p->verbose_p)
1165             fprintf (stderr, "%s: unthrottled.\n", blurb());
1166           si->throttled_p = False;
1167
1168           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1169             {
1170               XForceScreenSaver (si->dpy, ScreenSaverActive);
1171               return False;
1172             }
1173           else
1174             {
1175               return True;
1176             }
1177         }
1178       clientmessage_response(si, window, True,
1179                        "ClientMessage ACTIVATE received while already active.",
1180                              "already active.");
1181     }
1182   else if (type == XA_DEACTIVATE)
1183     {
1184       if (! until_idle_p)
1185         {
1186           if (si->throttled_p && p->verbose_p)
1187             fprintf (stderr, "%s: unthrottled.\n", blurb());
1188           si->throttled_p = False;
1189
1190           clientmessage_response(si, window, False,
1191                                  "DEACTIVATE ClientMessage received.",
1192                                  "deactivating.");
1193           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1194             {
1195               XForceScreenSaver (si->dpy, ScreenSaverReset);
1196               return False;
1197             }
1198           else
1199             {
1200               return True;
1201             }
1202         }
1203       clientmessage_response(si, window, True,
1204                            "ClientMessage DEACTIVATE received while inactive.",
1205                              "not active.");
1206     }
1207   else if (type == XA_CYCLE)
1208     {
1209       if (! until_idle_p)
1210         {
1211           clientmessage_response(si, window, False,
1212                                  "CYCLE ClientMessage received.",
1213                                  "cycling.");
1214           si->selection_mode = 0;       /* 0 means randomize when its time. */
1215           si->demoing_p = False;
1216
1217           if (si->throttled_p && p->verbose_p)
1218             fprintf (stderr, "%s: unthrottled.\n", blurb());
1219           si->throttled_p = False;
1220
1221           if (si->cycle_id)
1222             XtRemoveTimeOut (si->cycle_id);
1223           si->cycle_id = 0;
1224           cycle_timer ((XtPointer) si, 0);
1225           return False;
1226         }
1227       clientmessage_response(si, window, True,
1228                              "ClientMessage CYCLE received while inactive.",
1229                              "not active.");
1230     }
1231   else if (type == XA_NEXT || type == XA_PREV)
1232     {
1233       clientmessage_response(si, window, False,
1234                              (type == XA_NEXT
1235                               ? "NEXT ClientMessage received."
1236                               : "PREV ClientMessage received."),
1237                              "cycling.");
1238       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1239       si->demoing_p = False;
1240
1241       if (si->throttled_p && p->verbose_p)
1242         fprintf (stderr, "%s: unthrottled.\n", blurb());
1243       si->throttled_p = False;
1244
1245       if (! until_idle_p)
1246         {
1247           if (si->cycle_id)
1248             XtRemoveTimeOut (si->cycle_id);
1249           si->cycle_id = 0;
1250           cycle_timer ((XtPointer) si, 0);
1251         }
1252       else
1253         return True;
1254     }
1255   else if (type == XA_SELECT)
1256     {
1257       char buf [255];
1258       char buf2 [255];
1259       long which = event->xclient.data.l[1];
1260
1261       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1262       sprintf (buf2, "activating (%ld).", which);
1263       clientmessage_response (si, window, False, buf, buf2);
1264
1265       if (which < 0) which = 0;         /* 0 == "random" */
1266       si->selection_mode = which;
1267       si->demoing_p = False;
1268
1269       if (si->throttled_p && p->verbose_p)
1270         fprintf (stderr, "%s: unthrottled.\n", blurb());
1271       si->throttled_p = False;
1272
1273       if (! until_idle_p)
1274         {
1275           if (si->cycle_id)
1276             XtRemoveTimeOut (si->cycle_id);
1277           si->cycle_id = 0;
1278           cycle_timer ((XtPointer) si, 0);
1279         }
1280       else
1281         return True;
1282     }
1283   else if (type == XA_EXIT)
1284     {
1285       /* Ignore EXIT message if the screen is locked. */
1286       if (until_idle_p || !si->locked_p)
1287         {
1288           clientmessage_response (si, window, False,
1289                                   "EXIT ClientMessage received.",
1290                                   "exiting.");
1291           if (! until_idle_p)
1292             {
1293               unblank_screen (si);
1294               kill_screenhack (si);
1295               XSync (si->dpy, False);
1296             }
1297           saver_exit (si, 0, 0);
1298         }
1299       else
1300         clientmessage_response (si, window, True,
1301                                 "EXIT ClientMessage received while locked.",
1302                                 "screen is locked.");
1303     }
1304   else if (type == XA_RESTART)
1305     {
1306       /* The RESTART message works whether the screensaver is active or not,
1307          unless the screen is locked, in which case it doesn't work.
1308        */
1309       if (until_idle_p || !si->locked_p)
1310         {
1311           clientmessage_response (si, window, False,
1312                                   "RESTART ClientMessage received.",
1313                                   "restarting.");
1314           if (! until_idle_p)
1315             {
1316               unblank_screen (si);
1317               kill_screenhack (si);
1318               XSync (si->dpy, False);
1319             }
1320
1321           fflush (stdout);
1322           fflush (stderr);
1323           if (real_stdout) fflush (real_stdout);
1324           if (real_stderr) fflush (real_stderr);
1325           /* make sure error message shows up before exit. */
1326           if (real_stderr && stderr != real_stderr)
1327             dup2 (fileno(real_stderr), fileno(stderr));
1328
1329           restart_process (si);
1330           exit (1);     /* shouldn't get here; but if restarting didn't work,
1331                            make this command be the same as EXIT. */
1332         }
1333       else
1334         clientmessage_response (si, window, True,
1335                                 "RESTART ClientMessage received while locked.",
1336                                 "screen is locked.");
1337     }
1338   else if (type == XA_DEMO)
1339     {
1340       long arg = event->xclient.data.l[1];
1341       Bool demo_one_hack_p = (arg == 300);
1342
1343       if (demo_one_hack_p)
1344         {
1345           if (until_idle_p)
1346             {
1347               long which = event->xclient.data.l[2];
1348               char buf [255];
1349               char buf2 [255];
1350               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1351               sprintf (buf2, "demoing (%ld).", which);
1352               clientmessage_response (si, window, False, buf, buf2);
1353
1354               if (which < 0) which = 0;         /* 0 == "random" */
1355               si->selection_mode = which;
1356               si->demoing_p = True;
1357
1358               if (si->throttled_p && p->verbose_p)
1359                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1360               si->throttled_p = False;
1361
1362               return True;
1363             }
1364
1365           clientmessage_response (si, window, True,
1366                                   "DEMO ClientMessage received while active.",
1367                                   "already active.");
1368         }
1369       else
1370         {
1371           clientmessage_response (si, window, True,
1372                                   "obsolete form of DEMO ClientMessage.",
1373                                   "obsolete form of DEMO ClientMessage.");
1374         }
1375     }
1376   else if (type == XA_PREFS)
1377     {
1378       clientmessage_response (si, window, True,
1379                               "the PREFS client-message is obsolete.",
1380                               "the PREFS client-message is obsolete.");
1381     }
1382   else if (type == XA_LOCK)
1383     {
1384 #ifdef NO_LOCKING
1385       clientmessage_response (si, window, True,
1386                               "not compiled with support for locking.",
1387                               "locking not enabled.");
1388 #else /* !NO_LOCKING */
1389       if (si->locking_disabled_p)
1390         clientmessage_response (si, window, True,
1391                       "LOCK ClientMessage received, but locking is disabled.",
1392                               "locking not enabled.");
1393       else if (si->locked_p)
1394         clientmessage_response (si, window, True,
1395                            "LOCK ClientMessage received while already locked.",
1396                                 "already locked.");
1397       else
1398         {
1399           char buf [255];
1400           char *response = (until_idle_p
1401                             ? "activating and locking."
1402                             : "locking.");
1403           sprintf (buf, "LOCK ClientMessage received; %s", response);
1404           clientmessage_response (si, window, False, buf, response);
1405           set_locked_p (si, True);
1406           si->selection_mode = 0;
1407           si->demoing_p = False;
1408
1409           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1410             {
1411               XtRemoveTimeOut (si->lock_id);
1412               si->lock_id = 0;
1413             }
1414
1415           if (until_idle_p)
1416             {
1417               if (si->using_mit_saver_extension ||
1418                   si->using_sgi_saver_extension)
1419                 {
1420                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1421                   return False;
1422                 }
1423               else
1424                 {
1425                   return True;
1426                 }
1427             }
1428         }
1429 #endif /* !NO_LOCKING */
1430     }
1431   else if (type == XA_THROTTLE)
1432     {
1433       if (si->throttled_p)
1434         clientmessage_response (si, window, True,
1435                                 "THROTTLE ClientMessage received, but "
1436                                 "already throttled.",
1437                                 "already throttled.");
1438       else
1439         {
1440           char buf [255];
1441           char *response = "throttled.";
1442           si->throttled_p = True;
1443           si->selection_mode = 0;
1444           si->demoing_p = False;
1445           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
1446           clientmessage_response (si, window, False, buf, response);
1447
1448           if (! until_idle_p)
1449             {
1450               if (si->cycle_id)
1451                 XtRemoveTimeOut (si->cycle_id);
1452               si->cycle_id = 0;
1453               cycle_timer ((XtPointer) si, 0);
1454             }
1455         }
1456     }
1457   else if (type == XA_UNTHROTTLE)
1458     {
1459       if (! si->throttled_p)
1460         clientmessage_response (si, window, True,
1461                                 "UNTHROTTLE ClientMessage received, but "
1462                                 "not throttled.",
1463                                 "not throttled.");
1464       else
1465         {
1466           char buf [255];
1467           char *response = "unthrottled.";
1468           si->throttled_p = False;
1469           si->selection_mode = 0;
1470           si->demoing_p = False;
1471           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
1472           clientmessage_response (si, window, False, buf, response);
1473
1474           if (! until_idle_p)
1475             {
1476               if (si->cycle_id)
1477                 XtRemoveTimeOut (si->cycle_id);
1478               si->cycle_id = 0;
1479               cycle_timer ((XtPointer) si, 0);
1480             }
1481         }
1482     }
1483   else
1484     {
1485       char buf [1024];
1486       char *str;
1487       str = (type ? XGetAtomName(si->dpy, type) : 0);
1488
1489       if (str)
1490         {
1491           if (strlen (str) > 80)
1492             strcpy (str+70, "...");
1493           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1494                    str);
1495           free (str);
1496         }
1497       else
1498         {
1499           sprintf (buf,
1500                    "unrecognised screensaver ClientMessage 0x%x received.",
1501                    (unsigned int) event->xclient.data.l[0]);
1502         }
1503
1504       clientmessage_response (si, window, True, buf, buf);
1505     }
1506   return False;
1507 }
1508
1509 \f
1510 /* Some random diagnostics printed in -verbose mode.
1511  */
1512
1513 static void
1514 analyze_display (saver_info *si)
1515 {
1516   int i, j;
1517   static const char *exts[][2] = {
1518     { "SCREEN_SAVER",             "SGI Screen-Saver" },
1519     { "SCREEN-SAVER",             "SGI Screen-Saver" },
1520     { "MIT-SCREEN-SAVER",         "MIT Screen-Saver" },
1521     { "XIDLE",                    "XIdle" },
1522     { "SGI-VIDEO-CONTROL",        "SGI Video-Control" },
1523     { "READDISPLAY",              "SGI Read-Display" },
1524     { "MIT-SHM",                  "Shared Memory" },
1525     { "DOUBLE-BUFFER",            "Double-Buffering" },
1526     { "DPMS",                     "Power Management" },
1527     { "GLX",                      "GLX" },
1528     { "XFree86-VidModeExtension", "XF86 Video-Mode" },
1529     { "XINERAMA",                 "Xinerama" }
1530   };
1531
1532   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1533            DisplayString(si->dpy));
1534   fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1535            ServerVendor(si->dpy), VendorRelease(si->dpy));
1536
1537   fprintf (stderr, "%s: useful extensions:\n", blurb());
1538   for (i = 0; i < countof(exts); i++)
1539     {
1540       int op = 0, event = 0, error = 0;
1541       if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error))
1542         fprintf (stderr, "%s:   %s\n", blurb(), exts[i][1]);
1543     }
1544
1545   for (i = 0; i < si->nscreens; i++)
1546     {
1547       unsigned long colormapped_depths = 0;
1548       unsigned long non_mapped_depths = 0;
1549       XVisualInfo vi_in, *vi_out;
1550       int out_count;
1551       vi_in.screen = i;
1552       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1553       if (!vi_out) continue;
1554       for (j = 0; j < out_count; j++)
1555         if (vi_out[j].class == PseudoColor)
1556           colormapped_depths |= (1 << vi_out[j].depth);
1557         else
1558           non_mapped_depths  |= (1 << vi_out[j].depth);
1559       XFree ((char *) vi_out);
1560
1561       if (colormapped_depths)
1562         {
1563           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1564           for (j = 0; j < 32; j++)
1565             if (colormapped_depths & (1 << j))
1566               fprintf (stderr, " %d", j);
1567           fprintf (stderr, "\n");
1568         }
1569       if (non_mapped_depths)
1570         {
1571           fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1572           for (j = 0; j < 32; j++)
1573             if (non_mapped_depths & (1 << j))
1574               fprintf (stderr, " %d", j);
1575           fprintf (stderr, "\n");
1576         }
1577     }
1578 }
1579
1580 Bool
1581 display_is_on_console_p (saver_info *si)
1582 {
1583   Bool not_on_console = True;
1584   char *dpystr = DisplayString (si->dpy);
1585   char *tail = (char *) strchr (dpystr, ':');
1586   if (! tail || strncmp (tail, ":0", 2))
1587     not_on_console = True;
1588   else
1589     {
1590       char dpyname[255], localname[255];
1591       strncpy (dpyname, dpystr, tail-dpystr);
1592       dpyname [tail-dpystr] = 0;
1593       if (!*dpyname ||
1594           !strcmp(dpyname, "unix") ||
1595           !strcmp(dpyname, "localhost"))
1596         not_on_console = False;
1597       else if (gethostname (localname, sizeof (localname)))
1598         not_on_console = True;  /* can't find hostname? */
1599       else
1600         {
1601           /* We have to call gethostbyname() on the result of gethostname()
1602              because the two aren't guarenteed to be the same name for the
1603              same host: on some losing systems, one is a FQDN and the other
1604              is not.  Here in the wide wonderful world of Unix it's rocket
1605              science to obtain the local hostname in a portable fashion.
1606              
1607              And don't forget, gethostbyname() reuses the structure it
1608              returns, so we have to copy the fucker before calling it again.
1609              Thank you master, may I have another.
1610            */
1611           struct hostent *h = gethostbyname (dpyname);
1612           if (!h)
1613             not_on_console = True;
1614           else
1615             {
1616               char hn [255];
1617               struct hostent *l;
1618               strcpy (hn, h->h_name);
1619               l = gethostbyname (localname);
1620               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1621             }
1622         }
1623     }
1624   return !not_on_console;
1625 }