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