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