http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-2008 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.
20  *
21  *   We place a __SWM_VROOT property on this window, so that newly-started
22  *   clients will think that this window is a "virtual root" window (as per
23  *   the logic in the historical "vroot.h" header.)
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  *   On multi-screen systems, we do the above on each screen, and start
42  *   multiple programs, each with a different value of $DISPLAY.
43  *
44  *   On Xinerama systems, we do a similar thing, but instead create multiple
45  *   windows on the (only) display, and tell the subprocess which one to use
46  *   via the $XSCREENSAVER_WINDOW environment variable -- this trick requires
47  *   a recent (Aug 2003) revision of vroot.h.
48  *
49  *   While we are waiting for user activity, we also set up timers so that,
50  *   after a certain amount of time has passed, we can start a different
51  *   screenhack.  We do this by killing the running child process with
52  *   SIGTERM, and then starting a new one in the same way.
53  *
54  *   If there was a real virtual root, meaning that we removed the __SWM_VROOT
55  *   property from it, meaning we must (absolutely must) restore it before we
56  *   exit, then we set up signal handlers for most signals (SIGINT, SIGTERM,
57  *   etc.) that do this.  Most Xlib and Xt routines are not reentrant, so it
58  *   is not generally safe to call them from signal handlers; however, this
59  *   program spends most of its time waiting, so the window of opportunity 
60  *   when code could be called reentrantly is fairly small; and also, the worst
61  *   that could happen is that the call would fail.  If we've gotten one of
62  *   these signals, then we're on our way out anyway.  If we didn't restore the
63  *   __SWM_VROOT property, that would be very bad, so it's worth a shot.  Note
64  *   that this means that, if you're using a virtual-root window manager, you
65  *   can really fuck up the world by killing this process with "kill -9".
66  *
67  *   This program accepts ClientMessages of type SCREENSAVER; these messages
68  *   may contain the atoms ACTIVATE, DEACTIVATE, etc, meaning to turn the 
69  *   screensaver on or off now, regardless of the idleness of the user,
70  *   and a few other things.  The included "xscreensaver-command" program
71  *   sends these messsages.
72  *
73  *   If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER
74  *   extensions, then we do the XAutoLock trick: notice every window that
75  *   gets created, and wait 30 seconds or so until its creating process has
76  *   settled down, and then select KeyPress events on those windows which
77  *   already select for KeyPress events.  It's important that we not select
78  *   KeyPress on windows which don't select them, because that would
79  *   interfere with event propagation.  This will break if any program
80  *   changes its event mask to contain KeyRelease or PointerMotion more than
81  *   30 seconds after creating the window, but such programs do not seem to
82  *   occur in nature (I've never seen it happen in all these years.)
83  *
84  *   The reason that we can't select KeyPresses on windows that don't have
85  *   them already is that, when dispatching a KeyPress event, X finds the
86  *   lowest (leafmost) window in the hierarchy on which *any* client selects
87  *   for KeyPress, and sends the event to that window.  This means that if a
88  *   client had a window with subwindows, and expected to receive KeyPress
89  *   events on the parent window instead of the subwindows, then that client
90  *   would malfunction if some other client selected KeyPress events on the
91  *   subwindows.  It is an incredible misdesign that one client can make
92  *   another client malfunction in this way.
93  *
94  *   To detect mouse motion, we periodically wake up and poll the mouse
95  *   position and button/modifier state, and notice when something has
96  *   changed.  We make this check every five seconds by default, and since the
97  *   screensaver timeout has a granularity of one minute, this makes the
98  *   chance of a false positive very small.  We could detect mouse motion in
99  *   the same way as keyboard activity, but that would suffer from the same
100  *   "client changing event mask" problem that the KeyPress events hack does.
101  *   I think polling is more reliable.
102  *
103  *   On systems with /proc/interrupts (Linux) we poll that file and note when
104  *   the interrupt counter numbers on the "keyboard" and "PS/2" lines change.
105  *   (There is no reliable way, using /proc/interrupts, to detect non-PS/2
106  *   mice, so it doesn't help for serial or USB mice.)
107  *
108  *   None of this crap happens if we're using one of the extensions.  Sadly,
109  *   the XIdle extension hasn't been available for many years; the SGI
110  *   extension only exists on SGIs; and the MIT extension, while widely
111  *   deployed, is garbage in several ways.
112  *
113  *   A third idle-detection option could be implemented (but is not): when
114  *   running on the console display ($DISPLAY is `localhost`:0) and we're on a
115  *   machine where /dev/tty and /dev/mouse have reasonable last-modification
116  *   times, we could just stat() those.  But the incremental benefit of
117  *   implementing this is really small, so forget I said anything.
118  *
119  *   Debugging hints:
120  *     - Have a second terminal handy.
121  *     - Be careful where you set your breakpoints, you don't want this to
122  *       stop under the debugger with the keyboard grabbed or the blackout
123  *       window exposed.
124  *     - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
125  *       to keep your emacs window alive even when xscreensaver has grabbed.
126  *     - Go read the code related to `debug_p'.
127  *     - You probably can't set breakpoints in functions that are called on
128  *       the other side of a call to fork() -- if your subprocesses are
129  *       dying with signal 5, Trace/BPT Trap, you're losing in this way.
130  *     - If you aren't using a server extension, don't leave this stopped
131  *       under the debugger for very long, or the X input buffer will get
132  *       huge because of the keypress events it's selecting for.  This can
133  *       make your X server wedge with "no more input buffers."
134  *
135  * ======================================================================== */
136
137 #ifdef HAVE_CONFIG_H
138 # include "config.h"
139 #endif
140
141 #include <stdio.h>
142 #include <ctype.h>
143 #include <X11/Xlib.h>
144
145 #include <X11/Xlibint.h>
146
147 #include <X11/Xatom.h>
148 #include <X11/Intrinsic.h>
149 #include <X11/StringDefs.h>
150 #include <X11/Shell.h>
151 #include <X11/Xos.h>
152 #include <time.h>
153 #include <sys/time.h>
154 #include <netdb.h>      /* for gethostbyname() */
155 #include <sys/types.h>
156 #include <pwd.h>
157 #ifdef HAVE_XMU
158 # ifndef VMS
159 #  include <X11/Xmu/Error.h>
160 # else  /* !VMS */
161 #  include <Xmu/Error.h>
162 # endif /* !VMS */
163 #else  /* !HAVE_XMU */
164 # include "xmu.h"
165 #endif /* !HAVE_XMU */
166
167 #ifdef HAVE_XIDLE_EXTENSION
168 # include <X11/extensions/xidle.h>
169 #endif /* HAVE_XIDLE_EXTENSION */
170
171 #ifdef HAVE_XINERAMA
172 # include <X11/extensions/Xinerama.h>
173 #endif /* HAVE_XINERAMA */
174
175 #include "xscreensaver.h"
176 #include "version.h"
177 #include "yarandom.h"
178 #include "resources.h"
179 #include "visual.h"
180 #include "usleep.h"
181 #include "auth.h"
182
183 saver_info *global_si_kludge = 0;       /* I hate C so much... */
184
185 char *progname = 0;
186 char *progclass = 0;
187 XrmDatabase db = 0;
188
189
190 static Atom XA_SCREENSAVER_RESPONSE;
191 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
192 static Atom XA_RESTART, XA_SELECT;
193 static Atom XA_THROTTLE, XA_UNTHROTTLE;
194 Atom XA_DEMO, XA_PREFS, XA_EXIT, XA_LOCK, XA_BLANK;
195
196 \f
197 static XrmOptionDescRec options [] = {
198
199   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
200   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
201
202   /* xscreensaver-demo uses this one */
203   { "-nosplash",           ".splash",           XrmoptionNoArg, "off" },
204   { "-no-splash",          ".splash",           XrmoptionNoArg, "off" },
205
206   /* useful for debugging */
207   { "-no-capture-stderr",  ".captureStderr",    XrmoptionNoArg, "off" },
208 };
209
210 #ifdef __GNUC__
211  __extension__     /* shut up about "string length is greater than the length
212                       ISO C89 compilers are required to support" when including
213                       the .ad file... */
214 #endif
215
216 static char *defaults[] = {
217 #include "XScreenSaver_ad.h"
218  0
219 };
220
221 #ifdef _VROOT_H_
222 ERROR!  You must not include vroot.h in this file.
223 #endif
224
225 static void
226 do_help (saver_info *si)
227 {
228   fflush (stdout);
229   fflush (stderr);
230   fprintf (stdout, "\
231 xscreensaver %s, copyright (c) 1991-2008 by Jamie Zawinski <jwz@jwz.org>\n\
232 \n\
233   All xscreensaver configuration is via the `~/.xscreensaver' file.\n\
234   Rather than editing that file by hand, just run `xscreensaver-demo':\n\
235   that program lets you configure the screen saver graphically,\n\
236   including timeouts, locking, and display modes.\n\
237 \n",
238           si->version);
239   fprintf (stdout, "\
240   Just getting started?  Try this:\n\
241 \n\
242         xscreensaver &\n\
243         xscreensaver-demo\n\
244 \n\
245   For updates, online manual, and FAQ, please see the web page:\n\
246 \n\
247        http://www.jwz.org/xscreensaver/\n\
248 \n");
249
250   fflush (stdout);
251   fflush (stderr);
252   exit (1);
253 }
254
255
256 char *
257 timestring (void)
258 {
259   time_t now = time ((time_t *) 0);
260   char *str = (char *) ctime (&now);
261   char *nl = (char *) strchr (str, '\n');
262   if (nl) *nl = 0; /* take off that dang newline */
263   return str;
264 }
265
266 static Bool blurb_timestamp_p = False;   /* kludge */
267
268 const char *
269 blurb (void)
270 {
271   if (!blurb_timestamp_p)
272     return progname;
273   else
274     {
275       static char buf[255];
276       char *ct = timestring();
277       int n = strlen(progname);
278       if (n > 100) n = 99;
279       strncpy(buf, progname, n);
280       buf[n++] = ':';
281       buf[n++] = ' ';
282       strncpy(buf+n, ct+11, 8);
283       strcpy(buf+n+9, ": ");
284       return buf;
285     }
286 }
287
288
289 int
290 saver_ehandler (Display *dpy, XErrorEvent *error)
291 {
292   saver_info *si = global_si_kludge;    /* I hate C so much... */
293   int i;
294   Bool fatal_p;
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",
302            blurb());
303
304   for (i = 0; i < si->nscreens; i++)
305     {
306       saver_screen_info *ssi = &si->screens[i];
307       fprintf (real_stderr, "%s: screen %d/%d: 0x%x, 0x%x, 0x%x\n",
308                blurb(), ssi->real_screen_number, ssi->number,
309                (unsigned int) RootWindowOfScreen (si->screens[i].screen),
310                (unsigned int) si->screens[i].real_vroot,
311                (unsigned int) si->screens[i].screensaver_window);
312     }
313
314   fprintf (real_stderr, "\n"
315            "#######################################"
316            "#######################################\n\n");
317
318   fatal_p = XmuPrintDefaultErrorMessage (dpy, error, real_stderr);
319
320   fatal_p = True;  /* The only time I've ever seen a supposedly nonfatal error,
321                       it has been BadImplementation / Xlib sequence lost, which
322                       are in truth pretty damned fatal.
323                     */
324
325   fprintf (real_stderr, "\n");
326
327   if (! fatal_p)
328     fprintf (real_stderr, "%s: nonfatal error.\n\n", blurb());
329   else
330     {
331       if (si->prefs.xsync_p)
332         {
333           saver_exit (si, -1, "because of synchronous X Error");
334         }
335       else
336         {
337           fprintf (real_stderr,
338                    "#######################################"
339                    "#######################################\n\n");
340           fprintf (real_stderr,
341    "    If at all possible, please re-run xscreensaver with the command\n"
342    "    line arguments `-sync -verbose -no-capture', and reproduce this\n"
343    "    bug.  That will cause xscreensaver to dump a `core' file to the\n"
344    "    current directory.  Please include the stack trace from that core\n"
345    "    file in your bug report.  *DO NOT* mail the core file itself!\n"
346    "    That won't work.\n");
347           fprintf (real_stderr,
348    "\n"
349    "    http://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
350    "    the most useful bug reports, and how to examine core files.\n"
351    "\n"
352    "    The more information you can provide, the better.  But please\n"
353    "    report this bug, regardless!\n"
354    "\n");
355           fprintf (real_stderr,
356                    "#######################################"
357                    "#######################################\n\n");
358
359           saver_exit (si, -1, 0);
360         }
361     }
362
363   return 0;
364 }
365
366
367 /* This error handler is used only while the X connection is being set up;
368    after we've got a connection, we don't use this handler again.  The only
369    reason for having this is so that we can present a more idiot-proof error
370    message than "cannot open display."
371  */
372 static void 
373 startup_ehandler (String name, String type, String class,
374                   String defalt,  /* one can't even spel properly
375                                      in this joke of a language */
376                   String *av, Cardinal *ac)
377 {
378   char fmt[512];
379   String p[10];
380   saver_info *si = global_si_kludge;    /* I hate C so much... */
381   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
382   *fmt = 0;
383   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
384                             fmt, sizeof(fmt)-1, *db);
385
386   fprintf (stderr, "%s: ", blurb());
387
388   memset (p, 0, sizeof(p));
389   if (*ac > countof (p)) *ac = countof (p);
390   memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
391   fprintf (stderr, fmt,         /* Did I mention that I hate C? */
392            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
393   fprintf (stderr, "\n");
394
395   describe_uids (si, stderr);
396
397   if (si->orig_uid && !strncmp (si->orig_uid, "root/", 5))
398     {
399       fprintf (stderr, "\n"
400           "%s: This is probably because you're logging in as root.  You\n"
401 "              shouldn't log in as root: you should log in as a normal user,\n"
402 "              and then `su' as needed.  If you insist on logging in as\n"
403 "              root, you will have to turn off X's security features before\n"
404 "              xscreensaver will work.\n"
405                "\n"
406 "              Please read the manual and FAQ for more information:\n",
407                blurb());
408     }
409   else
410     {
411       fprintf (stderr, "\n"
412           "%s: Errors at startup are usually authorization problems.\n"
413 "              But you're not logging in as root (good!) so something\n"
414 "              else must be wrong.  Did you read the manual and the FAQ?\n",
415            blurb());
416     }
417
418   fprintf (stderr, "\n"
419           "              http://www.jwz.org/xscreensaver/faq.html\n"
420           "              http://www.jwz.org/xscreensaver/man.html\n"
421           "\n");
422
423   fflush (stderr);
424   fflush (stdout);
425   exit (1);
426 }
427
428 \f
429 /* The zillions of initializations.
430  */
431
432 /* Set progname, version, etc.  This is done very early.
433  */
434 static void
435 set_version_string (saver_info *si, int *argc, char **argv)
436 {
437   progclass = "XScreenSaver";
438
439   /* progname is reset later, after we connect to X. */
440   progname = strrchr(argv[0], '/');
441   if (progname) progname++;
442   else progname = argv[0];
443
444   if (strlen(progname) > 100)   /* keep it short. */
445     progname[99] = 0;
446
447   /* The X resource database blows up if argv[0] has a "." in it. */
448   {
449     char *s = argv[0];
450     while ((s = strchr (s, '.')))
451       *s = '_';
452   }
453
454   si->version = (char *) malloc (5);
455   memcpy (si->version, screensaver_id + 17, 4);
456   si->version [4] = 0;
457 }
458
459
460 /* Initializations that potentially take place as a priveleged user:
461    If the xscreensaver executable is setuid root, then these initializations
462    are run as root, before discarding privileges.
463  */
464 static void
465 privileged_initialization (saver_info *si, int *argc, char **argv)
466 {
467 #ifndef NO_LOCKING
468   /* before hack_uid() for proper permissions */
469   lock_priv_init (*argc, argv, si->prefs.verbose_p);
470 #endif /* NO_LOCKING */
471
472   hack_uid (si);
473 }
474
475
476 /* Figure out what locking mechanisms are supported.
477  */
478 static void
479 lock_initialization (saver_info *si, int *argc, char **argv)
480 {
481 #ifdef NO_LOCKING
482   si->locking_disabled_p = True;
483   si->nolock_reason = "not compiled with locking support";
484 #else /* !NO_LOCKING */
485
486   /* Finish initializing locking, now that we're out of privileged code. */
487   if (! lock_init (*argc, argv, si->prefs.verbose_p))
488     {
489       si->locking_disabled_p = True;
490       si->nolock_reason = "error getting password";
491     }
492
493   /* If locking is currently enabled, but the environment indicates that
494      we have been launched as GDM's "Background" program, then disable
495      locking just in case.
496    */
497   if (!si->locking_disabled_p && getenv ("RUNNING_UNDER_GDM"))
498     {
499       si->locking_disabled_p = True;
500       si->nolock_reason = "running under GDM";
501     }
502
503   /* If the server is XDarwin (MacOS X) then disable locking.
504      (X grabs only affect X programs, so you can use Command-Tab
505      to bring any other Mac program to the front, e.g., Terminal.)
506    */
507   if (!si->locking_disabled_p)
508     {
509       int op = 0, event = 0, error = 0;
510       Bool macos_p = False;
511
512 #ifdef __APPLE__
513       /* Disable locking if *running* on Apple hardware, since we have no
514          reliable way to determine whether the server is running on MacOS.
515          Hopefully __APPLE__ means "MacOS" and not "Linux on Mac hardware"
516          but I'm not really sure about that.
517        */
518       macos_p = True;
519 #endif
520
521       if (!macos_p)
522         /* This extension exists on the Apple X11 server, but not
523            on earlier versions of the XDarwin server. */
524         macos_p = XQueryExtension (si->dpy, "Apple-DRI", &op, &event, &error);
525
526       if (macos_p)
527         {
528           si->locking_disabled_p = True;
529           si->nolock_reason = "Cannot lock securely on MacOS X";
530         }
531     }
532
533 #endif /* NO_LOCKING */
534 }
535
536
537 /* Open the connection to the X server, and intern our Atoms.
538  */
539 static Widget
540 connect_to_server (saver_info *si, int *argc, char **argv)
541 {
542   Widget toplevel_shell;
543
544 #ifdef HAVE_PUTENV
545   char *d = getenv ("DISPLAY");
546   if (!d || !*d)
547     {
548       char *ndpy = strdup("DISPLAY=:0.0");
549       /* if (si->prefs.verbose_p) */      /* sigh, too early to test this... */
550         fprintf (stderr,
551                  "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
552                  blurb(), ndpy+8);
553       if (putenv (ndpy))
554         abort ();
555       /* don't free (ndpy) -- some implementations of putenv (BSD 4.4,
556          glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
557          do not.  So we must leak it (and/or the previous setting). Yay.
558        */
559     }
560 #endif /* HAVE_PUTENV */
561
562   XSetErrorHandler (saver_ehandler);
563
564   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
565   toplevel_shell = XtAppInitialize (&si->app, progclass,
566                                     options, XtNumber (options),
567                                     argc, argv, defaults, 0, 0);
568   XtAppSetErrorMsgHandler (si->app, 0);
569
570   si->dpy = XtDisplay (toplevel_shell);
571   si->prefs.db = XtDatabase (si->dpy);
572   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
573
574   if(strlen(progname) > 100)    /* keep it short. */
575     progname [99] = 0;
576
577   db = si->prefs.db;    /* resources.c needs this */
578
579   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
580   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
581   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
582   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
583   XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False);
584   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
585                                          False);
586   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
587   XA_ESETROOT_PMAP_ID = XInternAtom (si->dpy, "ESETROOT_PMAP_ID", False);
588   XA_XROOTPMAP_ID = XInternAtom (si->dpy, "_XROOTPMAP_ID", False);
589   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
590   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
591   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
592   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
593   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
594   XA_PREV = XInternAtom (si->dpy, "PREV", False);
595   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
596   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
597   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
598   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
599   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
600   XA_BLANK = XInternAtom (si->dpy, "BLANK", False);
601   XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
602   XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
603
604   return toplevel_shell;
605 }
606
607
608 /* Handle the command-line arguments that were not handled for us by Xt.
609    Issue an error message and exit if there are unknown options.
610  */
611 static void
612 process_command_line (saver_info *si, int *argc, char **argv)
613 {
614   int i;
615   for (i = 1; i < *argc; i++)
616     {
617       if (!strcmp (argv[i], "-debug"))
618         /* no resource for this one, out of paranoia. */
619         si->prefs.debug_p = True;
620
621       else if (!strcmp (argv[i], "-h") ||
622                !strcmp (argv[i], "-help") ||
623                !strcmp (argv[i], "--help"))
624         do_help (si);
625
626       else
627         {
628           const char *s = argv[i];
629           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
630                    blurb(), s);
631
632           if (s[0] == '-' && s[1] == '-') s++;
633           if (!strcmp (s, "-activate") ||
634               !strcmp (s, "-deactivate") ||
635               !strcmp (s, "-cycle") ||
636               !strcmp (s, "-next") ||
637               !strcmp (s, "-prev") ||
638               !strcmp (s, "-exit") ||
639               !strcmp (s, "-restart") ||
640               !strcmp (s, "-demo") ||
641               !strcmp (s, "-prefs") ||
642               !strcmp (s, "-preferences") ||
643               !strcmp (s, "-lock") ||
644               !strcmp (s, "-version") ||
645               !strcmp (s, "-time"))
646             {
647
648               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
649                 fprintf (stderr, "\n\
650     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
651               else
652                 fprintf (stderr, "\n\
653     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
654
655               fprintf (stderr, "\
656     The `xscreensaver' program is a daemon that runs in the background.\n\
657     You control a running xscreensaver process by sending it messages\n\
658     with `xscreensaver-demo' or `xscreensaver-command'.\n\
659 .   See the man pages for details, or check the web page:\n\
660     http://www.jwz.org/xscreensaver/\n\n");
661
662               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
663                  suggest that explicitly. */
664               if (!strcmp (s, "-lock"))
665                 fprintf (stderr, "\
666     Or perhaps you meant either the \"-lock-mode\" or the\n\
667     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
668             }
669
670           exit (1);
671         }
672     }
673 }
674
675
676 /* Print out the xscreensaver banner to the tty if applicable;
677    Issue any other warnings that are called for at this point.
678  */
679 static void
680 print_banner (saver_info *si)
681 {
682   saver_preferences *p = &si->prefs;
683
684   /* This resource gets set some time before the others, so that we know
685      whether to print the banner (and so that the banner gets printed before
686      any resource-database-related error messages.)
687    */
688   p->verbose_p = (p->debug_p || 
689                   get_boolean_resource (si->dpy, "verbose", "Boolean"));
690
691   /* Ditto, for the locking_disabled_p message. */
692   p->lock_p = get_boolean_resource (si->dpy, "lock", "Boolean");
693
694   if (p->verbose_p)
695     fprintf (stderr,
696              "%s %s, copyright (c) 1991-2008 "
697              "by Jamie Zawinski <jwz@jwz.org>.\n",
698              progname, si->version);
699
700   if (p->debug_p)
701     fprintf (stderr, "\n"
702              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
703              "\n"
704              "\tNote that in debug mode, the xscreensaver window will only\n"
705              "\tcover the left half of the screen.  (The idea is that you\n"
706              "\tcan still see debugging output in a shell, if you position\n"
707              "\tit on the right side of the screen.)\n"
708              "\n"
709              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
710              "\tuntrusted environments.\n"
711              "\n",
712              blurb());
713
714   if (p->verbose_p)
715     {
716       if (!si->uid_message || !*si->uid_message)
717         describe_uids (si, stderr);
718       else
719         {
720           if (si->orig_uid && *si->orig_uid)
721             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
722                      blurb(), si->orig_uid);
723           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
724         }
725
726       fprintf (stderr, "%s: in process %lu.\n", blurb(),
727                (unsigned long) getpid());
728     }
729 }
730
731 static void
732 print_lock_failure_banner (saver_info *si)
733 {
734   saver_preferences *p = &si->prefs;
735
736   /* If locking was not able to be initalized for some reason, explain why.
737      (This has to be done after we've read the lock_p resource.)
738    */
739   if (si->locking_disabled_p)
740     {
741       p->lock_p = False;
742       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
743                si->nolock_reason);
744       if (strstr (si->nolock_reason, "passw"))
745         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
746                  "consult the manual.\n", blurb());
747       else if (strstr (si->nolock_reason, "running as "))
748         fprintf (stderr, 
749                  "%s: locking only works when xscreensaver is launched\n"
750                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
751                  "\t See the manual for details.\n",
752                  blurb());
753     }
754
755 }
756
757
758 #ifdef HAVE_XINERAMA
759
760 static Bool
761 screens_overlap_p (XineramaScreenInfo *a, XineramaScreenInfo *b)
762 {
763   /* Two rectangles overlap if the max of the tops is less than the
764      min of the bottoms and the max of the lefts is less than the min
765      of the rights.
766    */
767 # undef MAX
768 # undef MIN
769 # define MAX(A,B) ((A)>(B)?(A):(B))
770 # define MIN(A,B) ((A)<(B)?(A):(B))
771
772   int maxleft  = MAX(a->x_org, b->x_org);
773   int maxtop   = MAX(a->y_org, b->y_org);
774   int minright = MIN(a->x_org + a->width  - 1, b->x_org + b->width);
775   int minbot   = MIN(a->y_org + a->height - 1, b->y_org + b->height);
776   return (maxtop < minbot && maxleft < minright);
777 }
778
779
780 /* Go through the list of Xinerama screen descriptions, and mark the
781    ones that appear to be insane, so that we don't use them.
782  */
783 static void
784 check_xinerama_sanity (int count, Bool verbose_p, XineramaScreenInfo *xsi)
785 {
786   static Bool printed_p = False;
787   int i, j;
788   char err[1024];
789   *err = 0;
790
791 # define X1 xsi[i].x_org
792 # define X2 xsi[j].x_org
793 # define Y1 xsi[i].y_org
794 # define Y2 xsi[j].y_org
795 # define W1 xsi[i].width
796 # define W2 xsi[j].width
797 # define H1 xsi[i].height
798 # define H2 xsi[j].height
799
800 # define WHINE() do {                                                        \
801     if (verbose_p) {                                                         \
802       if (! printed_p) {                                                     \
803         fprintf (stderr, "%s: compensating for Xinerama braindamage:\n",     \
804                  blurb());                                                   \
805         printed_p = True;                                                    \
806       }                                                                      \
807       fprintf (stderr, "%s:   %d: %s\n", blurb(), xsi[i].screen_number,err); \
808     }                                                                        \
809     xsi[i].screen_number = -1;                                               \
810   } while(0)
811
812   /* If a screen is enclosed by any other screen, that's insane.
813    */
814   for (i = 0; i < count; i++)
815     for (j = 0; j < count; j++)
816       if (i != j &&
817           xsi[i].screen_number >= 0 &&
818           xsi[j].screen_number >= 0 &&
819           X1 >= X2 && Y1 >= Y2 && (X1+W1) <= (X2+W2) && (X1+H1) <= (X2+H2))
820         {
821           sprintf (err, "%dx%d+%d+%d enclosed by %dx%d+%d+%d",
822                    W1, H1, X1, Y1,
823                    W2, H2, X2, Y2);
824           WHINE();
825           continue;
826         }
827
828   /* After checking for enclosure, check for other lossage against earlier
829      screens.  We do enclosure first so that we make sure to pick the
830      larger one.
831    */
832   for (i = 0; i < count; i++)
833     for (j = 0; j < i; j++)
834       {
835         if (xsi[i].screen_number < 0) continue; /* already marked */
836
837         *err = 0;
838         if (X1 == X2 && Y1 == Y2 && W1 == W2 && H1 == H2)
839           sprintf (err, "%dx%d+%d+%d duplicated", W1, H1, X1, Y1);
840
841         else if (screens_overlap_p (&xsi[i], &xsi[j]))
842           sprintf (err, "%dx%d+%d+%d overlaps %dx%d+%d+%d",
843                    W1, H1, X1, Y1,
844                    W2, H2, X2, Y2);
845
846         if (*err) WHINE();
847       }
848
849 # undef X1
850 # undef X2
851 # undef Y1
852 # undef Y2
853 # undef W1
854 # undef W2
855 # undef H1
856 # undef H2
857 }
858
859 #endif /* HAVE_XINERAMA */
860
861
862
863 /* Examine all of the display's screens, and populate the `saver_screen_info'
864    structures.  Make sure this is called after hack_environment() sets $PATH.
865  */
866 static void
867 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
868 {
869   Bool found_any_writable_cells = False;
870   int i;
871
872 # ifdef HAVE_XINERAMA
873   {
874     int event, error;
875     si->xinerama_p = (XineramaQueryExtension (si->dpy, &event, &error) &&
876                       XineramaIsActive (si->dpy));
877   }
878
879   if (si->xinerama_p && ScreenCount (si->dpy) != 1)
880     {
881       si->xinerama_p = False;
882       if (si->prefs.verbose_p)
883         fprintf (stderr,
884                  "%s: Xinerama AND %d screens?  Disabling Xinerama support!\n",
885                  blurb(), ScreenCount(si->dpy));
886     }
887
888   if (si->xinerama_p)
889     {
890       int nscreens = 0;
891       XineramaScreenInfo *xsi = XineramaQueryScreens (si->dpy, &nscreens);
892       if (!xsi)
893         si->xinerama_p = False;
894       else
895         {
896           int j = 0;
897           si->screens = (saver_screen_info *)
898             calloc(sizeof(saver_screen_info), nscreens);
899           check_xinerama_sanity (nscreens, si->prefs.verbose_p, xsi);
900           for (i = 0; i < nscreens; i++)
901             {
902               if (xsi[i].screen_number < 0)  /* deemed insane */
903                 continue;
904               si->screens[j].x      = xsi[i].x_org;
905               si->screens[j].y      = xsi[i].y_org;
906               si->screens[j].width  = xsi[i].width;
907               si->screens[j].height = xsi[i].height;
908               j++;
909             }
910           si->nscreens = j;
911           XFree (xsi);
912         }
913       si->default_screen = &si->screens[0];
914       si->default_screen->real_screen_p = True;
915     }
916 # endif /* !HAVE_XINERAMA */
917
918   if (!si->xinerama_p)
919     {
920       si->nscreens = ScreenCount(si->dpy);
921       si->screens = (saver_screen_info *)
922         calloc(sizeof(saver_screen_info), si->nscreens);
923       si->default_screen = &si->screens[DefaultScreen(si->dpy)];
924
925       for (i = 0; i < si->nscreens; i++)
926         {
927           saver_screen_info *ssi = &si->screens[i];
928           ssi->width  = DisplayWidth  (si->dpy, i);
929           ssi->height = DisplayHeight (si->dpy, i);
930           ssi->real_screen_p = True;
931           ssi->real_screen_number = i;
932         }
933     }
934
935
936 # ifdef QUAD_MODE
937   /* In "quad mode", we use the Xinerama code to pretend that there are 4
938      screens for every physical screen, and run four times as many hacks...
939    */
940   if (si->prefs.quad_p)
941     {
942       int ns2 = si->nscreens * 4;
943       saver_screen_info *ssi2 = (saver_screen_info *)
944         calloc(sizeof(saver_screen_info), ns2);
945
946       for (i = 0; i < si->nscreens; i++)
947         {
948           saver_screen_info *old = &si->screens[i];
949
950           if (si->prefs.debug_p) old->width = old->width / 2;
951
952           ssi2[i*4  ] = *old;
953           ssi2[i*4+1] = *old;
954           ssi2[i*4+2] = *old;
955           ssi2[i*4+3] = *old;
956
957           ssi2[i*4  ].width  /= 2;
958           ssi2[i*4  ].height /= 2;
959
960           ssi2[i*4+1].x      += ssi2[i*4  ].width;
961           ssi2[i*4+1].width  -= ssi2[i*4  ].width;
962           ssi2[i*4+1].height /= 2;
963
964           ssi2[i*4+2].y      += ssi2[i*4  ].height;
965           ssi2[i*4+2].width  /= 2;
966           ssi2[i*4+2].height -= ssi2[i*4  ].height;
967
968           ssi2[i*4+3].x      += ssi2[i*4+2].width;
969           ssi2[i*4+3].y      += ssi2[i*4+2].height;
970           ssi2[i*4+3].width  -= ssi2[i*4+2].width;
971           ssi2[i*4+3].height -= ssi2[i*4+2].height;
972
973           ssi2[i*4+1].real_screen_p = False;
974           ssi2[i*4+2].real_screen_p = False;
975           ssi2[i*4+3].real_screen_p = False;
976         }
977
978       si->nscreens = ns2;
979       free (si->screens);
980       si->screens = ssi2;
981       si->default_screen = &si->screens[DefaultScreen(si->dpy) * 4];
982       si->xinerama_p = True;
983     }
984 # endif /* QUAD_MODE */
985
986   /* finish initializing the screens.
987    */
988   for (i = 0; i < si->nscreens; i++)
989     {
990       saver_screen_info *ssi = &si->screens[i];
991       ssi->global = si;
992
993       ssi->number = i;
994       ssi->screen = ScreenOfDisplay (si->dpy, ssi->real_screen_number);
995       ssi->poll_mouse_last_root_x = -1;
996       ssi->poll_mouse_last_root_y = -1;
997
998       if (!si->xinerama_p)
999         {
1000           ssi->width  = WidthOfScreen  (ssi->screen);
1001           ssi->height = HeightOfScreen (ssi->screen);
1002         }
1003
1004       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
1005       ssi->default_visual =
1006         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
1007
1008       ssi->current_visual = ssi->default_visual;
1009       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
1010
1011       /* Execute a subprocess to find the GL visual. */
1012       ssi->best_gl_visual = get_best_gl_visual (ssi);
1013
1014       if (ssi == si->default_screen)
1015         /* Since this is the default screen, use the one already created. */
1016         ssi->toplevel_shell = toplevel_shell;
1017       else
1018         /* Otherwise, each screen must have its own unmapped root widget. */
1019         ssi->toplevel_shell =
1020           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
1021                               si->dpy,
1022                               XtNscreen, ssi->screen,
1023                               XtNvisual, ssi->current_visual,
1024                               XtNdepth,  visual_depth (ssi->screen,
1025                                                        ssi->current_visual),
1026                               NULL);
1027
1028       if (! found_any_writable_cells)
1029         {
1030           /* Check to see whether fading is ever possible -- if any of the
1031              screens on the display has a PseudoColor visual, then fading can
1032              work (on at least some screens.)  If no screen has a PseudoColor
1033              visual, then don't bother ever trying to fade, because it will
1034              just cause a delay without causing any visible effect.
1035           */
1036           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
1037               get_visual (ssi->screen, "PseudoColor", True, False) ||
1038               get_visual (ssi->screen, "GrayScale", True, False))
1039             found_any_writable_cells = True;
1040         }
1041     }
1042
1043   si->fading_possible_p = found_any_writable_cells;
1044
1045 #ifdef HAVE_XF86VMODE_GAMMA
1046   si->fading_possible_p = True;  /* if we can gamma fade, go for it */
1047 #endif
1048 }
1049
1050
1051 /* If any server extensions have been requested, try and initialize them.
1052    Issue warnings if requests can't be honored.
1053  */
1054 static void
1055 initialize_server_extensions (saver_info *si)
1056 {
1057   saver_preferences *p = &si->prefs;
1058
1059   Bool server_has_xidle_extension_p = False;
1060   Bool server_has_sgi_saver_extension_p = False;
1061   Bool server_has_mit_saver_extension_p = False;
1062   Bool system_has_proc_interrupts_p = False;
1063   const char *piwhy = 0;
1064
1065   si->using_xidle_extension = p->use_xidle_extension;
1066   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
1067   si->using_mit_saver_extension = p->use_mit_saver_extension;
1068   si->using_proc_interrupts = p->use_proc_interrupts;
1069
1070 #ifdef HAVE_XIDLE_EXTENSION
1071   server_has_xidle_extension_p = query_xidle_extension (si);
1072 #endif
1073 #ifdef HAVE_SGI_SAVER_EXTENSION
1074   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
1075 #endif
1076 #ifdef HAVE_MIT_SAVER_EXTENSION
1077   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
1078 #endif
1079 #ifdef HAVE_PROC_INTERRUPTS
1080   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
1081 #endif
1082
1083   if (!server_has_xidle_extension_p)
1084     si->using_xidle_extension = False;
1085   else if (p->verbose_p)
1086     {
1087       if (si->using_xidle_extension)
1088         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
1089       else
1090         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
1091     }
1092
1093   if (!server_has_sgi_saver_extension_p)
1094     si->using_sgi_saver_extension = False;
1095   else if (p->verbose_p)
1096     {
1097       if (si->using_sgi_saver_extension)
1098         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
1099       else
1100         fprintf (stderr,
1101                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
1102                  blurb());
1103     }
1104
1105   if (!server_has_mit_saver_extension_p)
1106     si->using_mit_saver_extension = False;
1107   else if (p->verbose_p)
1108     {
1109       if (si->using_mit_saver_extension)
1110         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
1111                  blurb());
1112       else
1113         fprintf (stderr,
1114                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
1115                  blurb());
1116     }
1117
1118   /* These are incompatible (or at least, our support for them is...) */
1119   if (si->xinerama_p && si->using_mit_saver_extension)
1120     {
1121       si->using_mit_saver_extension = False;
1122       if (p->verbose_p)
1123         fprintf (stderr, "%s: Xinerama in use: disabling MIT-SCREEN-SAVER.\n",
1124                  blurb());
1125     }
1126
1127 #ifdef HAVE_RANDR
1128   query_randr_extension (si);
1129 #endif
1130
1131   if (!system_has_proc_interrupts_p)
1132     {
1133       si->using_proc_interrupts = False;
1134       if (p->verbose_p && piwhy)
1135         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
1136                  piwhy);
1137     }
1138   else if (p->verbose_p)
1139     {
1140       if (si->using_proc_interrupts)
1141         fprintf (stderr,
1142                  "%s: consulting /proc/interrupts for keyboard activity.\n",
1143                  blurb());
1144       else
1145         fprintf (stderr,
1146                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
1147                  blurb());
1148     }
1149 }
1150
1151
1152 /* For the case where we aren't using an server extensions, select user events
1153    on all the existing windows, and launch timers to select events on
1154    newly-created windows as well.
1155
1156    If a server extension is being used, this does nothing.
1157  */
1158 static void
1159 select_events (saver_info *si)
1160 {
1161   saver_preferences *p = &si->prefs;
1162   int i;
1163
1164   if (si->using_xidle_extension ||
1165       si->using_mit_saver_extension ||
1166       si->using_sgi_saver_extension)
1167     return;
1168
1169   if (p->initial_delay)
1170     {
1171       if (p->verbose_p)
1172         {
1173           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
1174                    (int) p->initial_delay/1000,
1175                    (p->initial_delay == 1000 ? "" : "s"));
1176           fflush (stderr);
1177           fflush (stdout);
1178         }
1179       usleep (p->initial_delay);
1180       if (p->verbose_p)
1181         fprintf (stderr, " done.\n");
1182     }
1183
1184   if (p->verbose_p)
1185     {
1186       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
1187       fflush (stderr);
1188       fflush (stdout);
1189     }
1190
1191   /* Select events on the root windows of every screen.  This also selects
1192      for window creation events, so that new subwindows will be noticed.
1193    */
1194   for (i = 0; i < si->nscreens; i++)
1195     {
1196       saver_screen_info *ssi = &si->screens[i];
1197       if (ssi->real_screen_p)
1198         start_notice_events_timer (si,
1199            RootWindowOfScreen (si->screens[i].screen), False);
1200     }
1201
1202   if (p->verbose_p)
1203     fprintf (stderr, " done.\n");
1204 }
1205
1206
1207 void
1208 maybe_reload_init_file (saver_info *si)
1209 {
1210   saver_preferences *p = &si->prefs;
1211   if (init_file_changed_p (p))
1212     {
1213       if (p->verbose_p)
1214         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
1215                  blurb(), init_file_name());
1216
1217       load_init_file (si->dpy, p);
1218
1219       /* If a server extension is in use, and p->timeout has changed,
1220          we need to inform the server of the new timeout. */
1221       disable_builtin_screensaver (si, False);
1222
1223       /* If the DPMS settings in the init file have changed,
1224          change the settings on the server to match. */
1225       sync_server_dpms_settings (si->dpy,
1226                                  (p->dpms_enabled_p  &&
1227                                   p->mode != DONT_BLANK),
1228                                  p->dpms_standby / 1000,
1229                                  p->dpms_suspend / 1000,
1230                                  p->dpms_off / 1000,
1231                                  False);
1232     }
1233 }
1234
1235
1236 /* Loop forever:
1237
1238        - wait until the user is idle;
1239        - blank the screen;
1240        - wait until the user is active;
1241        - unblank the screen;
1242        - repeat.
1243
1244  */
1245 static void
1246 main_loop (saver_info *si)
1247 {
1248   saver_preferences *p = &si->prefs;
1249   Bool ok_to_unblank;
1250
1251   while (1)
1252     {
1253       Bool was_locked = False;
1254
1255       if (p->verbose_p)
1256         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1257
1258       check_for_leaks ("unblanked A");
1259       sleep_until_idle (si, True);
1260       check_for_leaks ("unblanked B");
1261
1262       if (p->verbose_p)
1263         {
1264           if (si->demoing_p)
1265             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
1266                      si->selection_mode, timestring());
1267           else
1268             fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
1269                      timestring());
1270         }
1271
1272       maybe_reload_init_file (si);
1273
1274       if (p->mode == DONT_BLANK)
1275         {
1276           if (p->verbose_p)
1277             fprintf (stderr, "%s: idle with blanking disabled at %s.\n",
1278                      blurb(), timestring());
1279
1280           /* Go around the loop and wait for the next bout of idleness,
1281              or for the init file to change, or for a remote command to
1282              come in, or something.
1283
1284              But, if locked_p is true, go ahead.  This can only happen
1285              if we're in "disabled" mode but a "lock" clientmessage came
1286              in: in that case, we should go ahead and blank/lock the screen.
1287            */
1288           if (!si->locked_p)
1289             continue;
1290         }
1291
1292       /* Since we're about to blank the screen, kill the de-race timer,
1293          if any.  It might still be running if we have unblanked and then
1294          re-blanked in a short period (e.g., when using the "next" button
1295          in xscreensaver-demo.)
1296        */
1297       if (si->de_race_id)
1298         {
1299           if (p->verbose_p)
1300             fprintf (stderr, "%s: stopping de-race timer (%d remaining.)\n",
1301                      blurb(), si->de_race_ticks);
1302           XtRemoveTimeOut (si->de_race_id);
1303           si->de_race_id = 0;
1304         }
1305
1306
1307       /* Now, try to blank.
1308        */
1309
1310       if (! blank_screen (si))
1311         {
1312           /* We were unable to grab either the keyboard or mouse.
1313              This means we did not (and must not) blank the screen.
1314              If we were to blank the screen while some other program
1315              is holding both the mouse and keyboard grabbed, then
1316              we would never be able to un-blank it!  We would never
1317              see any events, and the display would be wedged.
1318
1319              So, just go around the loop again and wait for the
1320              next bout of idleness.  (If the user remains idle, we
1321              will next try to blank the screen again in no more than
1322              60 seconds.)
1323           */
1324           Time retry = 60 * 1000;
1325           if (p->timeout < retry)
1326             retry = p->timeout;
1327
1328           if (p->debug_p)
1329             {
1330               fprintf (stderr,
1331                   "%s: DEBUG MODE: unable to grab -- BLANKING ANYWAY.\n",
1332                        blurb());
1333             }
1334           else
1335             {
1336               fprintf (stderr,
1337                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
1338                        blurb());
1339
1340               schedule_wakeup_event (si, retry, p->debug_p);
1341               continue;
1342             }
1343         }
1344
1345       kill_screenhack (si);
1346
1347       if (!si->throttled_p)
1348         spawn_screenhack (si, True);
1349       else if (p->verbose_p)
1350         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
1351
1352       /* Don't start the cycle timer in demo mode. */
1353       if (!si->demoing_p && p->cycle)
1354         si->cycle_id = XtAppAddTimeOut (si->app,
1355                                         (si->selection_mode
1356                                          /* see comment in cycle_timer() */
1357                                          ? 1000 * 60 * 60
1358                                          : p->cycle),
1359                                         cycle_timer,
1360                                         (XtPointer) si);
1361
1362
1363 #ifndef NO_LOCKING
1364       /* Maybe start locking the screen.
1365        */
1366       {
1367         Time lock_timeout = p->lock_timeout;
1368
1369         if (si->emergency_lock_p && p->lock_p && lock_timeout)
1370           {
1371             int secs = p->lock_timeout / 1000;
1372             if (p->verbose_p)
1373               fprintf (stderr,
1374                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
1375                        blurb(),
1376                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
1377             lock_timeout = 0;
1378           }
1379
1380         si->emergency_lock_p = False;
1381
1382         if (!si->demoing_p &&           /* if not going into demo mode */
1383             p->lock_p &&                /* and locking is enabled */
1384             !si->locking_disabled_p &&  /* and locking is possible */
1385             lock_timeout == 0)          /* and locking is not timer-deferred */
1386           set_locked_p (si, True);      /* then lock right now. */
1387
1388         /* locked_p might be true already because of the above, or because of
1389            the LOCK ClientMessage.  But if not, and if we're supposed to lock
1390            after some time, set up a timer to do so.
1391         */
1392         if (p->lock_p &&
1393             !si->locked_p &&
1394             lock_timeout > 0)
1395           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
1396                                          activate_lock_timer,
1397                                          (XtPointer) si);
1398       }
1399 #endif /* !NO_LOCKING */
1400
1401
1402       ok_to_unblank = True;
1403       do {
1404
1405         check_for_leaks ("blanked A");
1406         sleep_until_idle (si, False);           /* until not idle */
1407         check_for_leaks ("blanked B");
1408
1409         maybe_reload_init_file (si);
1410
1411 #ifndef NO_LOCKING
1412         /* Maybe unlock the screen.
1413          */
1414         if (si->locked_p)
1415           {
1416             saver_screen_info *ssi = si->default_screen;
1417             if (si->locking_disabled_p) abort ();
1418
1419             was_locked = True;
1420             si->dbox_up_p = True;
1421             suspend_screenhack (si, True);
1422             XUndefineCursor (si->dpy, ssi->screensaver_window);
1423
1424             ok_to_unblank = unlock_p (si);
1425
1426             si->dbox_up_p = False;
1427             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
1428             suspend_screenhack (si, False);     /* resume */
1429
1430             if (!ok_to_unblank &&
1431                 !screenhack_running_p (si))
1432               {
1433                 /* If the lock dialog has been dismissed and we're not about to
1434                    unlock the screen, and there is currently no hack running,
1435                    then launch one.  (There might be no hack running if DPMS
1436                    had kicked in.  But DPMS is off now, so bring back the hack)
1437                  */
1438                 if (si->cycle_id)
1439                   XtRemoveTimeOut (si->cycle_id);
1440                 si->cycle_id = 0;
1441                 cycle_timer ((XtPointer) si, 0);
1442               }
1443           }
1444 #endif /* !NO_LOCKING */
1445
1446         } while (!ok_to_unblank);
1447
1448
1449       if (p->verbose_p)
1450         fprintf (stderr, "%s: unblanking screen at %s.\n",
1451                  blurb(), timestring ());
1452
1453       /* Kill before unblanking, to stop drawing as soon as possible. */
1454       kill_screenhack (si);
1455       unblank_screen (si);
1456
1457       set_locked_p (si, False);
1458       si->emergency_lock_p = False;
1459       si->demoing_p = 0;
1460       si->selection_mode = 0;
1461
1462       /* If we're throttled, and the user has explicitly unlocked the screen,
1463          then unthrottle.  If we weren't locked, then don't unthrottle
1464          automatically, because someone might have just bumped the desk... */
1465       if (was_locked)
1466         {
1467           if (si->throttled_p && p->verbose_p)
1468             fprintf (stderr, "%s: unthrottled.\n", blurb());
1469           si->throttled_p = False;
1470         }
1471
1472       if (si->cycle_id)
1473         {
1474           XtRemoveTimeOut (si->cycle_id);
1475           si->cycle_id = 0;
1476         }
1477
1478       if (si->lock_id)
1479         {
1480           XtRemoveTimeOut (si->lock_id);
1481           si->lock_id = 0;
1482         }
1483
1484       /* Since we're unblanked now, break race conditions and make
1485          sure we stay that way (see comment in timers.c.) */
1486       if (! si->de_race_id)
1487         de_race_timer ((XtPointer) si, 0);
1488     }
1489 }
1490
1491 static void analyze_display (saver_info *si);
1492 static void fix_fds (void);
1493
1494 int
1495 main (int argc, char **argv)
1496 {
1497   Widget shell;
1498   saver_info the_si;
1499   saver_info *si = &the_si;
1500   saver_preferences *p = &si->prefs;
1501   struct passwd *spasswd;
1502   int i;
1503
1504   memset(si, 0, sizeof(*si));
1505   global_si_kludge = si;        /* I hate C so much... */
1506
1507   fix_fds();
1508
1509 # undef ya_rand_init
1510   ya_rand_init (0);
1511
1512   save_argv (argc, argv);
1513   set_version_string (si, &argc, argv);
1514   privileged_initialization (si, &argc, argv);
1515   hack_environment (si);
1516
1517   spasswd = getpwuid(getuid());
1518   if (!spasswd)
1519     {
1520       fprintf(stderr, "Could not figure out who the current user is!\n");
1521       return 1;
1522     }
1523
1524   si->user = strdup(spasswd->pw_name ? spasswd->pw_name : "(unknown)");
1525
1526 # ifndef NO_LOCKING
1527   si->unlock_cb = gui_auth_conv;
1528   si->auth_finished_cb = auth_finished_cb;
1529 # endif /* !NO_LOCKING */
1530
1531   shell = connect_to_server (si, &argc, argv);
1532   process_command_line (si, &argc, argv);
1533   print_banner (si);
1534
1535   load_init_file(si->dpy, p); /* must be before initialize_per_screen_info() */
1536   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1537   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1538
1539   /* We can only issue this warning now. */
1540   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1541     fprintf (stderr,
1542              "%s: there are no PseudoColor or GrayScale visuals.\n"
1543              "%s: ignoring the request for fading/unfading.\n",
1544              blurb(), blurb());
1545
1546   for (i = 0; i < si->nscreens; i++)
1547     {
1548       saver_screen_info *ssi = &si->screens[i];
1549       if (ssi->real_screen_p)
1550         if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1551           exit (1);
1552     }
1553
1554   lock_initialization (si, &argc, argv);
1555   print_lock_failure_banner (si);
1556
1557   if (p->xsync_p) XSynchronize (si->dpy, True);
1558
1559   if (p->verbose_p) analyze_display (si);
1560   initialize_server_extensions (si);
1561
1562   si->blank_time = time ((time_t) 0); /* must be before ..._window */
1563   initialize_screensaver_window (si);
1564
1565   select_events (si);
1566   init_sigchld ();
1567
1568   disable_builtin_screensaver (si, True);
1569   sync_server_dpms_settings (si->dpy,
1570                              (p->dpms_enabled_p  &&
1571                               p->mode != DONT_BLANK),
1572                              p->dpms_standby / 1000,
1573                              p->dpms_suspend / 1000,
1574                              p->dpms_off / 1000,
1575                              False);
1576
1577   initialize_stderr (si);
1578   handle_signals (si);
1579
1580   make_splash_dialog (si);
1581
1582   main_loop (si);               /* doesn't return */
1583   return 0;
1584 }
1585
1586 static void
1587 fix_fds (void)
1588 {
1589   /* Bad Things Happen if stdin, stdout, and stderr have been closed
1590      (as by the `sh incantation "xscreensaver >&- 2>&-").  When you do
1591      that, the X connection gets allocated to one of these fds, and
1592      then some random library writes to stderr, and random bits get
1593      stuffed down the X pipe, causing "Xlib: sequence lost" errors.
1594      So, we cause the first three file descriptors to be open to
1595      /dev/null if they aren't open to something else already.  This
1596      must be done before any other files are opened (or the closing
1597      of that other file will again free up one of the "magic" first
1598      three FDs.)
1599
1600      We do this by opening /dev/null three times, and then closing
1601      those fds, *unless* any of them got allocated as #0, #1, or #2,
1602      in which case we leave them open.  Gag.
1603
1604      Really, this crap is technically required of *every* X program,
1605      if you want it to be robust in the face of "2>&-".
1606    */
1607   int fd0 = open ("/dev/null", O_RDWR);
1608   int fd1 = open ("/dev/null", O_RDWR);
1609   int fd2 = open ("/dev/null", O_RDWR);
1610   if (fd0 > 2) close (fd0);
1611   if (fd1 > 2) close (fd1);
1612   if (fd2 > 2) close (fd2);
1613 }
1614
1615
1616 \f
1617 /* Processing ClientMessage events.
1618  */
1619
1620
1621 static Bool error_handler_hit_p = False;
1622
1623 static int
1624 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
1625 {
1626   error_handler_hit_p = True;
1627   return 0;
1628 }
1629
1630 /* Sometimes some systems send us ClientMessage events with bogus atoms in
1631    them.  We only look up the atom names for printing warning messages,
1632    so don't bomb out when it happens...
1633  */
1634 static char *
1635 XGetAtomName_safe (Display *dpy, Atom atom)
1636 {
1637   char *result;
1638   XErrorHandler old_handler;
1639   if (!atom) return 0;
1640
1641   XSync (dpy, False);
1642   error_handler_hit_p = False;
1643   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1644   result = XGetAtomName (dpy, atom);
1645   XSync (dpy, False);
1646   XSetErrorHandler (old_handler);
1647   XSync (dpy, False);
1648   if (error_handler_hit_p) result = 0;
1649
1650   if (result)
1651     return result;
1652   else
1653     {
1654       char buf[100];
1655       sprintf (buf, "<<undefined atom 0x%04X>>", (unsigned int) atom);
1656       return strdup (buf);
1657     }
1658 }
1659
1660
1661 static void
1662 clientmessage_response (saver_info *si, Window w, Bool error,
1663                         const char *stderr_msg,
1664                         const char *protocol_msg)
1665 {
1666   char *proto;
1667   int L;
1668   saver_preferences *p = &si->prefs;
1669   XErrorHandler old_handler;
1670
1671   if (error || p->verbose_p)
1672     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1673
1674   L = strlen(protocol_msg);
1675   proto = (char *) malloc (L + 2);
1676   proto[0] = (error ? '-' : '+');
1677   strcpy (proto+1, protocol_msg);
1678   L++;
1679
1680   /* Ignore all X errors while sending a response to a ClientMessage.
1681      Pretty much the only way we could get an error here is if the
1682      window we're trying to send the reply on has been deleted, in
1683      which case, the sender of the ClientMessage won't see our response
1684      anyway.
1685    */
1686   XSync (si->dpy, False);
1687   error_handler_hit_p = False;
1688   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1689
1690   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1691                    PropModeReplace, (unsigned char *) proto, L);
1692
1693   XSync (si->dpy, False);
1694   XSetErrorHandler (old_handler);
1695   XSync (si->dpy, False);
1696
1697   free (proto);
1698 }
1699
1700
1701 static void
1702 bogus_clientmessage_warning (saver_info *si, XEvent *event)
1703 {
1704 #if 0  /* Oh, fuck it.  GNOME likes to spew random ClientMessages at us
1705           all the time.  This is presumably indicative of an error in
1706           the sender of that ClientMessage: if we're getting it and 
1707           ignoring it, then it's not reaching the intended recipient.
1708           But people complain to me about this all the time ("waaah!
1709           xscreensaver is printing to it's stderr and that gets my
1710           panties all in a bunch!")  And I'm sick of hearing about it.
1711           So we'll just ignore these messages and let GNOME go right
1712           ahead and continue to stumble along in its malfunction.
1713         */
1714
1715   saver_preferences *p = &si->prefs;
1716   char *str = XGetAtomName_safe (si->dpy, event->xclient.message_type);
1717   Window w = event->xclient.window;
1718   char wdesc[255];
1719   int screen = 0;
1720   Bool root_p = False;
1721
1722   *wdesc = 0;
1723   for (screen = 0; screen < si->nscreens; screen++)
1724     if (w == si->screens[screen].screensaver_window)
1725       {
1726         strcpy (wdesc, "xscreensaver");
1727         break;
1728       }
1729     else if (w == RootWindow (si->dpy, screen))
1730       {
1731         strcpy (wdesc, "root");
1732         root_p = True;
1733         break;
1734       }
1735
1736   /* If this ClientMessage was sent to the real root window instead of to the
1737      xscreensaver window, then it might be intended for someone else who is
1738      listening on the root window (e.g., the window manager).  So only print
1739      the warning if: we are in debug mode; or if the bogus message was
1740      actually sent to one of the xscreensaver-created windows.
1741    */
1742   if (root_p && !p->debug_p)
1743     return;
1744
1745   if (!*wdesc)
1746     {
1747       XErrorHandler old_handler;
1748       XClassHint hint;
1749       XWindowAttributes xgwa;
1750       memset (&hint, 0, sizeof(hint));
1751       memset (&xgwa, 0, sizeof(xgwa));
1752
1753       XSync (si->dpy, False);
1754       old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1755       XGetClassHint (si->dpy, w, &hint);
1756       XGetWindowAttributes (si->dpy, w, &xgwa);
1757       XSync (si->dpy, False);
1758       XSetErrorHandler (old_handler);
1759       XSync (si->dpy, False);
1760
1761       screen = (xgwa.screen ? screen_number (xgwa.screen) : -1);
1762
1763       sprintf (wdesc, "%.20s / %.20s",
1764                (hint.res_name  ? hint.res_name  : "(null)"),
1765                (hint.res_class ? hint.res_class : "(null)"));
1766       if (hint.res_name)  XFree (hint.res_name);
1767       if (hint.res_class) XFree (hint.res_class);
1768     }
1769
1770   fprintf (stderr, "%s: %d: unrecognised ClientMessage \"%s\" received\n",
1771            blurb(), screen, (str ? str : "(null)"));
1772   fprintf (stderr, "%s: %d: for window 0x%lx (%s)\n",
1773            blurb(), screen, (unsigned long) w, wdesc);
1774   if (str) XFree (str);
1775
1776 #endif /* 0 */
1777 }
1778
1779
1780 Bool
1781 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1782 {
1783   saver_preferences *p = &si->prefs;
1784   Atom type = 0;
1785   Window window = event->xclient.window;
1786
1787   /* Preferences might affect our handling of client messages. */
1788   maybe_reload_init_file (si);
1789
1790   if (event->xclient.message_type != XA_SCREENSAVER ||
1791       event->xclient.format != 32)
1792     {
1793       bogus_clientmessage_warning (si, event);
1794       return False;
1795     }
1796
1797   type = event->xclient.data.l[0];
1798   if (type == XA_ACTIVATE)
1799     {
1800       if (until_idle_p)
1801         {
1802           if (p->mode == DONT_BLANK)
1803             {
1804               clientmessage_response(si, window, True,
1805                          "ACTIVATE ClientMessage received in DONT_BLANK mode.",
1806                                      "screen blanking is currently disabled.");
1807               return False;
1808             }
1809
1810           clientmessage_response(si, window, False,
1811                                  "ACTIVATE ClientMessage received.",
1812                                  "activating.");
1813           si->selection_mode = 0;
1814           si->demoing_p = False;
1815
1816           if (si->throttled_p && p->verbose_p)
1817             fprintf (stderr, "%s: unthrottled.\n", blurb());
1818           si->throttled_p = False;
1819
1820           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1821             {
1822               XForceScreenSaver (si->dpy, ScreenSaverActive);
1823               return False;
1824             }
1825           else
1826             {
1827               return True;
1828             }
1829         }
1830       clientmessage_response(si, window, True,
1831                        "ClientMessage ACTIVATE received while already active.",
1832                              "already active.");
1833     }
1834   else if (type == XA_DEACTIVATE)
1835     {
1836       if (! until_idle_p)
1837         {
1838           if (si->throttled_p && p->verbose_p)
1839             fprintf (stderr, "%s: unthrottled.\n", blurb());
1840           si->throttled_p = False;
1841
1842           clientmessage_response(si, window, False,
1843                                  "DEACTIVATE ClientMessage received.",
1844                                  "deactivating.");
1845           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1846             {
1847               XForceScreenSaver (si->dpy, ScreenSaverReset);
1848               return False;
1849             }
1850           else
1851             {
1852               return True;
1853             }
1854         }
1855       clientmessage_response(si, window, False,
1856      "ClientMessage DEACTIVATE received while inactive: resetting idle timer.",
1857                              "not active: idle timer reset.");
1858       reset_timers (si);
1859     }
1860   else if (type == XA_CYCLE)
1861     {
1862       if (! until_idle_p)
1863         {
1864           clientmessage_response(si, window, False,
1865                                  "CYCLE ClientMessage received.",
1866                                  "cycling.");
1867           si->selection_mode = 0;       /* 0 means randomize when its time. */
1868           si->demoing_p = False;
1869
1870           if (si->throttled_p && p->verbose_p)
1871             fprintf (stderr, "%s: unthrottled.\n", blurb());
1872           si->throttled_p = False;
1873
1874           if (si->cycle_id)
1875             XtRemoveTimeOut (si->cycle_id);
1876           si->cycle_id = 0;
1877           cycle_timer ((XtPointer) si, 0);
1878           return False;
1879         }
1880       clientmessage_response(si, window, True,
1881                              "ClientMessage CYCLE received while inactive.",
1882                              "not active.");
1883     }
1884   else if (type == XA_NEXT || type == XA_PREV)
1885     {
1886       clientmessage_response(si, window, False,
1887                              (type == XA_NEXT
1888                               ? "NEXT ClientMessage received."
1889                               : "PREV ClientMessage received."),
1890                              "cycling.");
1891       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1892       si->demoing_p = False;
1893
1894       if (si->throttled_p && p->verbose_p)
1895         fprintf (stderr, "%s: unthrottled.\n", blurb());
1896       si->throttled_p = False;
1897
1898       if (! until_idle_p)
1899         {
1900           if (si->cycle_id)
1901             XtRemoveTimeOut (si->cycle_id);
1902           si->cycle_id = 0;
1903           cycle_timer ((XtPointer) si, 0);
1904         }
1905       else
1906         return True;
1907     }
1908   else if (type == XA_SELECT)
1909     {
1910       char buf [255];
1911       char buf2 [255];
1912       long which = event->xclient.data.l[1];
1913
1914       if (p->mode == DONT_BLANK)
1915         {
1916           clientmessage_response(si, window, True,
1917                            "SELECT ClientMessage received in DONT_BLANK mode.",
1918                                  "screen blanking is currently disabled.");
1919           return False;
1920         }
1921
1922       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1923       sprintf (buf2, "activating (%ld).", which);
1924       clientmessage_response (si, window, False, buf, buf2);
1925
1926       if (which < 0) which = 0;         /* 0 == "random" */
1927       si->selection_mode = which;
1928       si->demoing_p = False;
1929
1930       if (si->throttled_p && p->verbose_p)
1931         fprintf (stderr, "%s: unthrottled.\n", blurb());
1932       si->throttled_p = False;
1933
1934       if (! until_idle_p)
1935         {
1936           if (si->cycle_id)
1937             XtRemoveTimeOut (si->cycle_id);
1938           si->cycle_id = 0;
1939           cycle_timer ((XtPointer) si, 0);
1940         }
1941       else
1942         return True;
1943     }
1944   else if (type == XA_EXIT)
1945     {
1946       /* Ignore EXIT message if the screen is locked. */
1947       if (until_idle_p || !si->locked_p)
1948         {
1949           clientmessage_response (si, window, False,
1950                                   "EXIT ClientMessage received.",
1951                                   "exiting.");
1952           if (! until_idle_p)
1953             {
1954               unblank_screen (si);
1955               kill_screenhack (si);
1956               XSync (si->dpy, False);
1957             }
1958           saver_exit (si, 0, 0);
1959         }
1960       else
1961         clientmessage_response (si, window, True,
1962                                 "EXIT ClientMessage received while locked.",
1963                                 "screen is locked.");
1964     }
1965   else if (type == XA_RESTART)
1966     {
1967       /* The RESTART message works whether the screensaver is active or not,
1968          unless the screen is locked, in which case it doesn't work.
1969        */
1970       if (until_idle_p || !si->locked_p)
1971         {
1972           clientmessage_response (si, window, False,
1973                                   "RESTART ClientMessage received.",
1974                                   "restarting.");
1975           if (! until_idle_p)
1976             {
1977               unblank_screen (si);
1978               kill_screenhack (si);
1979               XSync (si->dpy, False);
1980             }
1981
1982           restart_process (si);  /* does not return */
1983           abort();
1984         }
1985       else
1986         clientmessage_response (si, window, True,
1987                                 "RESTART ClientMessage received while locked.",
1988                                 "screen is locked.");
1989     }
1990   else if (type == XA_DEMO)
1991     {
1992       long arg = event->xclient.data.l[1];
1993       Bool demo_one_hack_p = (arg == 5000);
1994
1995       if (demo_one_hack_p)
1996         {
1997           if (until_idle_p)
1998             {
1999               long which = event->xclient.data.l[2];
2000               char buf [255];
2001               char buf2 [255];
2002               sprintf (buf, "DEMO %ld ClientMessage received.", which);
2003               sprintf (buf2, "demoing (%ld).", which);
2004               clientmessage_response (si, window, False, buf, buf2);
2005
2006               if (which < 0) which = 0;         /* 0 == "random" */
2007               si->selection_mode = which;
2008               si->demoing_p = True;
2009
2010               if (si->throttled_p && p->verbose_p)
2011                 fprintf (stderr, "%s: unthrottled.\n", blurb());
2012               si->throttled_p = False;
2013
2014               return True;
2015             }
2016
2017           clientmessage_response (si, window, True,
2018                                   "DEMO ClientMessage received while active.",
2019                                   "already active.");
2020         }
2021       else
2022         {
2023           clientmessage_response (si, window, True,
2024                                   "obsolete form of DEMO ClientMessage.",
2025                                   "obsolete form of DEMO ClientMessage.");
2026         }
2027     }
2028   else if (type == XA_PREFS)
2029     {
2030       clientmessage_response (si, window, True,
2031                               "the PREFS client-message is obsolete.",
2032                               "the PREFS client-message is obsolete.");
2033     }
2034   else if (type == XA_LOCK)
2035     {
2036 #ifdef NO_LOCKING
2037       clientmessage_response (si, window, True,
2038                               "not compiled with support for locking.",
2039                               "locking not enabled.");
2040 #else /* !NO_LOCKING */
2041       if (si->locking_disabled_p)
2042         clientmessage_response (si, window, True,
2043                       "LOCK ClientMessage received, but locking is disabled.",
2044                               "locking not enabled.");
2045       else if (si->locked_p)
2046         clientmessage_response (si, window, True,
2047                            "LOCK ClientMessage received while already locked.",
2048                                 "already locked.");
2049       else
2050         {
2051           char buf [255];
2052           char *response = (until_idle_p
2053                             ? "activating and locking."
2054                             : "locking.");
2055           sprintf (buf, "LOCK ClientMessage received; %s", response);
2056           clientmessage_response (si, window, False, buf, response);
2057           set_locked_p (si, True);
2058           si->selection_mode = 0;
2059           si->demoing_p = False;
2060
2061           if (si->lock_id)      /* we're doing it now, so lose the timeout */
2062             {
2063               XtRemoveTimeOut (si->lock_id);
2064               si->lock_id = 0;
2065             }
2066
2067           if (until_idle_p)
2068             {
2069               if (si->using_mit_saver_extension ||
2070                   si->using_sgi_saver_extension)
2071                 {
2072                   XForceScreenSaver (si->dpy, ScreenSaverActive);
2073                   return False;
2074                 }
2075               else
2076                 {
2077                   return True;
2078                 }
2079             }
2080         }
2081 #endif /* !NO_LOCKING */
2082     }
2083   else if (type == XA_THROTTLE)
2084     {
2085       /* The THROTTLE command is deprecated -- it predates the XDPMS
2086          extension.  Instead of using -throttle, users should instead
2087          just power off the monitor (e.g., "xset dpms force off".)
2088          In a few minutes, xscreensaver will notice that the monitor
2089          is off, and cease running hacks.
2090        */
2091       if (si->throttled_p)
2092         clientmessage_response (si, window, True,
2093                                 "THROTTLE ClientMessage received, but "
2094                                 "already throttled.",
2095                                 "already throttled.");
2096       else
2097         {
2098           char buf [255];
2099           char *response = "throttled.";
2100           si->throttled_p = True;
2101           si->selection_mode = 0;
2102           si->demoing_p = False;
2103           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
2104           clientmessage_response (si, window, False, buf, response);
2105
2106           if (! until_idle_p)
2107             {
2108               if (si->cycle_id)
2109                 XtRemoveTimeOut (si->cycle_id);
2110               si->cycle_id = 0;
2111               cycle_timer ((XtPointer) si, 0);
2112             }
2113         }
2114     }
2115   else if (type == XA_UNTHROTTLE)
2116     {
2117       if (! si->throttled_p)
2118         clientmessage_response (si, window, True,
2119                                 "UNTHROTTLE ClientMessage received, but "
2120                                 "not throttled.",
2121                                 "not throttled.");
2122       else
2123         {
2124           char buf [255];
2125           char *response = "unthrottled.";
2126           si->throttled_p = False;
2127           si->selection_mode = 0;
2128           si->demoing_p = False;
2129           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
2130           clientmessage_response (si, window, False, buf, response);
2131
2132           if (! until_idle_p)
2133             {
2134               if (si->cycle_id)
2135                 XtRemoveTimeOut (si->cycle_id);
2136               si->cycle_id = 0;
2137               cycle_timer ((XtPointer) si, 0);
2138             }
2139         }
2140     }
2141   else
2142     {
2143       char buf [1024];
2144       char *str;
2145       str = XGetAtomName_safe (si->dpy, type);
2146
2147       if (str)
2148         {
2149           if (strlen (str) > 80)
2150             strcpy (str+70, "...");
2151           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
2152                    str);
2153           free (str);
2154         }
2155       else
2156         {
2157           sprintf (buf,
2158                    "unrecognised screensaver ClientMessage 0x%x received.",
2159                    (unsigned int) event->xclient.data.l[0]);
2160         }
2161
2162       clientmessage_response (si, window, True, buf, buf);
2163     }
2164   return False;
2165 }
2166
2167 \f
2168 /* Some random diagnostics printed in -verbose mode.
2169  */
2170
2171 static void
2172 analyze_display (saver_info *si)
2173 {
2174   int i, j;
2175   static struct {
2176     const char *name; const char *desc; Bool useful_p;
2177   } exts[] = {
2178
2179    { "SCREEN_SAVER", /* underscore */           "SGI Screen-Saver",
2180 #     ifdef HAVE_SGI_SAVER_EXTENSION
2181         True
2182 #     else
2183         False
2184 #     endif
2185    }, { "SCREEN-SAVER", /* dash */              "SGI Screen-Saver",
2186 #     ifdef HAVE_SGI_SAVER_EXTENSION
2187         True
2188 #     else
2189         False
2190 #     endif
2191    }, { "MIT-SCREEN-SAVER",                     "MIT Screen-Saver",
2192 #     ifdef HAVE_MIT_SAVER_EXTENSION
2193         True
2194 #     else
2195         False
2196 #     endif
2197    }, { "XIDLE",                                "XIdle",           
2198 #     ifdef HAVE_XIDLE_EXTENSION
2199         True
2200 #     else
2201         False
2202 #     endif
2203    }, { "SGI-VIDEO-CONTROL",                    "SGI Video-Control",
2204 #     ifdef HAVE_SGI_VC_EXTENSION
2205         True
2206 #     else
2207         False
2208 #     endif
2209    }, { "READDISPLAY",                          "SGI Read-Display",
2210 #     ifdef HAVE_READ_DISPLAY_EXTENSION
2211         True
2212 #     else
2213         False
2214 #     endif
2215    }, { "MIT-SHM",                              "Shared Memory",   
2216 #     ifdef HAVE_XSHM_EXTENSION
2217         True
2218 #     else
2219         False
2220 #     endif
2221    }, { "DOUBLE-BUFFER",                        "Double-Buffering",
2222 #     ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2223         True
2224 #     else
2225         False
2226 #     endif
2227    }, { "DPMS",                                 "Power Management",
2228 #     ifdef HAVE_DPMS_EXTENSION
2229         True
2230 #     else
2231         False
2232 #     endif
2233    }, { "GLX",                                  "GLX",             
2234 #     ifdef HAVE_GL
2235         True
2236 #     else
2237         False
2238 #     endif
2239    }, { "XFree86-VidModeExtension",             "XF86 Video-Mode", 
2240 #     ifdef HAVE_XF86VMODE
2241         True
2242 #     else
2243         False
2244 #     endif
2245    }, { "XINERAMA",                             "Xinerama",
2246 #     ifdef HAVE_XINERAMA
2247         True
2248 #     else
2249         False
2250 #     endif
2251    }, { "RANDR",                                "Resize-and-Rotate",
2252 #     ifdef HAVE_RANDR
2253         True
2254 #     else
2255         False
2256 #     endif
2257    }, { "Apple-DRI",                            "Apple-DRI (XDarwin)",
2258         True
2259    },
2260   };
2261
2262   fprintf (stderr, "%s: running on display \"%s\" (%d %sscreen%s).\n",
2263            blurb(),
2264            DisplayString(si->dpy),
2265            si->nscreens,
2266            (si->xinerama_p ? "Xinerama " : ""),
2267            (si->nscreens == 1 ? "" : "s"));
2268   fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(),
2269            ServerVendor(si->dpy), VendorRelease(si->dpy));
2270
2271   fprintf (stderr, "%s: useful extensions:\n", blurb());
2272   for (i = 0; i < countof(exts); i++)
2273     {
2274       int op = 0, event = 0, error = 0;
2275       char buf [255];
2276       int j;
2277       if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
2278         continue;
2279       sprintf (buf, "%s:   ", blurb());
2280       j = strlen (buf);
2281       strcat (buf, exts[i].desc);
2282       if (!exts[i].useful_p)
2283         strcat (buf, " (disabled at compile time)");
2284       fprintf (stderr, "%s\n", buf);
2285     }
2286
2287   for (i = 0; i < si->nscreens; i++)
2288     {
2289       saver_screen_info *ssi = &si->screens[i];
2290       unsigned long colormapped_depths = 0;
2291       unsigned long non_mapped_depths = 0;
2292       XVisualInfo vi_in, *vi_out;
2293       int out_count;
2294
2295       if (!ssi->real_screen_p) continue;
2296
2297       vi_in.screen = ssi->real_screen_number;
2298       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
2299       if (!vi_out) continue;
2300       for (j = 0; j < out_count; j++)
2301         if (vi_out[j].class == PseudoColor)
2302           colormapped_depths |= (1 << vi_out[j].depth);
2303         else
2304           non_mapped_depths  |= (1 << vi_out[j].depth);
2305       XFree ((char *) vi_out);
2306
2307       if (colormapped_depths)
2308         {
2309           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(),
2310                    ssi->real_screen_number);
2311           for (j = 0; j < 32; j++)
2312             if (colormapped_depths & (1 << j))
2313               fprintf (stderr, " %d", j);
2314           fprintf (stderr, ".\n");
2315         }
2316       if (non_mapped_depths)
2317         {
2318           fprintf (stderr, "%s: screen %d non-colormapped depths:",
2319                    blurb(), ssi->real_screen_number);
2320           for (j = 0; j < 32; j++)
2321             if (non_mapped_depths & (1 << j))
2322               fprintf (stderr, " %d", j);
2323           fprintf (stderr, ".\n");
2324         }
2325     }
2326
2327   if (si->xinerama_p)
2328     {
2329       fprintf (stderr, "%s: Xinerama layout:\n", blurb());
2330       for (i = 0; i < si->nscreens; i++)
2331         {
2332           saver_screen_info *ssi = &si->screens[i];
2333           fprintf (stderr, "%s:   %c %d/%d: %dx%d+%d+%d\n",
2334                    blurb(),
2335                    (ssi->real_screen_p ? '+' : ' '),
2336                    ssi->number, ssi->real_screen_number,
2337                    ssi->width, ssi->height, ssi->x, ssi->y);
2338         }
2339     }
2340 }
2341
2342 Bool
2343 display_is_on_console_p (saver_info *si)
2344 {
2345   Bool not_on_console = True;
2346   char *dpystr = DisplayString (si->dpy);
2347   char *tail = (char *) strchr (dpystr, ':');
2348   if (! tail || strncmp (tail, ":0", 2))
2349     not_on_console = True;
2350   else
2351     {
2352       char dpyname[255], localname[255];
2353       strncpy (dpyname, dpystr, tail-dpystr);
2354       dpyname [tail-dpystr] = 0;
2355       if (!*dpyname ||
2356           !strcmp(dpyname, "unix") ||
2357           !strcmp(dpyname, "localhost"))
2358         not_on_console = False;
2359       else if (gethostname (localname, sizeof (localname)))
2360         not_on_console = True;  /* can't find hostname? */
2361       else if (!strncmp (dpyname, "/tmp/launch-", 12))  /* MacOS X launchd */
2362         not_on_console = False;
2363       else
2364         {
2365           /* We have to call gethostbyname() on the result of gethostname()
2366              because the two aren't guarenteed to be the same name for the
2367              same host: on some losing systems, one is a FQDN and the other
2368              is not.  Here in the wide wonderful world of Unix it's rocket
2369              science to obtain the local hostname in a portable fashion.
2370              
2371              And don't forget, gethostbyname() reuses the structure it
2372              returns, so we have to copy the fucker before calling it again.
2373              Thank you master, may I have another.
2374            */
2375           struct hostent *h = gethostbyname (dpyname);
2376           if (!h)
2377             not_on_console = True;
2378           else
2379             {
2380               char hn [255];
2381               struct hostent *l;
2382               strcpy (hn, h->h_name);
2383               l = gethostbyname (localname);
2384               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
2385             }
2386         }
2387     }
2388   return !not_on_console;
2389 }
2390
2391
2392 /* Do a little bit of heap introspection...
2393  */
2394 void
2395 check_for_leaks (const char *where)
2396 {
2397 #if defined(HAVE_SBRK) && defined(LEAK_PARANOIA)
2398   static unsigned long last_brk = 0;
2399   int b = (unsigned long) sbrk(0);
2400   if (last_brk && last_brk < b)
2401     fprintf (stderr, "%s: %s: brk grew by %luK.\n",
2402              blurb(), where,
2403              (((b - last_brk) + 1023) / 1024));
2404   last_brk = b;
2405 #endif /* HAVE_SBRK */
2406 }