0c4f0e9a3330ca3f40d39c9d701838a998aa8ebf
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-2007 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-2006 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-2006 "
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 /* Examine all of the display's screens, and populate the `saver_screen_info'
759    structures.  Make sure this is called after hack_environment() sets $PATH.
760  */
761 static void
762 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
763 {
764   Bool found_any_writable_cells = False;
765   int i;
766
767 # ifdef HAVE_XINERAMA
768   {
769     int event, error;
770     si->xinerama_p = (XineramaQueryExtension (si->dpy, &event, &error) &&
771                       XineramaIsActive (si->dpy));
772   }
773
774   if (si->xinerama_p && ScreenCount (si->dpy) != 1)
775     {
776       si->xinerama_p = False;
777       if (si->prefs.verbose_p)
778         fprintf (stderr,
779                  "%s: Xinerama AND %d screens?  Disabling Xinerama support!\n",
780                  blurb(), ScreenCount(si->dpy));
781     }
782
783   if (si->xinerama_p)
784     {
785       XineramaScreenInfo *xsi = XineramaQueryScreens (si->dpy, &si->nscreens);
786       if (!xsi)
787         si->xinerama_p = False;
788       else
789         {
790           si->screens = (saver_screen_info *)
791             calloc(sizeof(saver_screen_info), si->nscreens);
792           for (i = 0; i < si->nscreens; i++)
793             {
794               si->screens[i].x      = xsi[i].x_org;
795               si->screens[i].y      = xsi[i].y_org;
796               si->screens[i].width  = xsi[i].width;
797               si->screens[i].height = xsi[i].height;
798             }
799           XFree (xsi);
800         }
801       si->default_screen = &si->screens[0];
802       si->default_screen->real_screen_p = True;
803     }
804 # endif /* !HAVE_XINERAMA */
805
806   if (!si->xinerama_p)
807     {
808       si->nscreens = ScreenCount(si->dpy);
809       si->screens = (saver_screen_info *)
810         calloc(sizeof(saver_screen_info), si->nscreens);
811       si->default_screen = &si->screens[DefaultScreen(si->dpy)];
812
813       for (i = 0; i < si->nscreens; i++)
814         {
815           saver_screen_info *ssi = &si->screens[i];
816           ssi->width  = DisplayWidth  (si->dpy, i);
817           ssi->height = DisplayHeight (si->dpy, i);
818           ssi->real_screen_p = True;
819           ssi->real_screen_number = i;
820         }
821     }
822
823
824 # ifdef QUAD_MODE
825   /* In "quad mode", we use the Xinerama code to pretend that there are 4
826      screens for every physical screen, and run four times as many hacks...
827    */
828   if (si->prefs.quad_p)
829     {
830       int ns2 = si->nscreens * 4;
831       saver_screen_info *ssi2 = (saver_screen_info *)
832         calloc(sizeof(saver_screen_info), ns2);
833
834       for (i = 0; i < si->nscreens; i++)
835         {
836           saver_screen_info *old = &si->screens[i];
837
838           if (si->prefs.debug_p) old->width = old->width / 2;
839
840           ssi2[i*4  ] = *old;
841           ssi2[i*4+1] = *old;
842           ssi2[i*4+2] = *old;
843           ssi2[i*4+3] = *old;
844
845           ssi2[i*4  ].width  /= 2;
846           ssi2[i*4  ].height /= 2;
847
848           ssi2[i*4+1].x      += ssi2[i*4  ].width;
849           ssi2[i*4+1].width  -= ssi2[i*4  ].width;
850           ssi2[i*4+1].height /= 2;
851
852           ssi2[i*4+2].y      += ssi2[i*4  ].height;
853           ssi2[i*4+2].width  /= 2;
854           ssi2[i*4+2].height -= ssi2[i*4  ].height;
855
856           ssi2[i*4+3].x      += ssi2[i*4+2].width;
857           ssi2[i*4+3].y      += ssi2[i*4+2].height;
858           ssi2[i*4+3].width  -= ssi2[i*4+2].width;
859           ssi2[i*4+3].height -= ssi2[i*4+2].height;
860
861           ssi2[i*4+1].real_screen_p = False;
862           ssi2[i*4+2].real_screen_p = False;
863           ssi2[i*4+3].real_screen_p = False;
864         }
865
866       si->nscreens = ns2;
867       free (si->screens);
868       si->screens = ssi2;
869       si->default_screen = &si->screens[DefaultScreen(si->dpy) * 4];
870       si->xinerama_p = True;
871     }
872 # endif /* QUAD_MODE */
873
874   /* finish initializing the screens.
875    */
876   for (i = 0; i < si->nscreens; i++)
877     {
878       saver_screen_info *ssi = &si->screens[i];
879       ssi->global = si;
880
881       ssi->number = i;
882       ssi->screen = ScreenOfDisplay (si->dpy, ssi->real_screen_number);
883       ssi->poll_mouse_last_root_x = -1;
884       ssi->poll_mouse_last_root_y = -1;
885
886       if (!si->xinerama_p)
887         {
888           ssi->width  = WidthOfScreen  (ssi->screen);
889           ssi->height = HeightOfScreen (ssi->screen);
890         }
891
892       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
893       ssi->default_visual =
894         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
895
896       ssi->current_visual = ssi->default_visual;
897       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
898
899       /* Execute a subprocess to find the GL visual. */
900       ssi->best_gl_visual = get_best_gl_visual (ssi);
901
902       if (ssi == si->default_screen)
903         /* Since this is the default screen, use the one already created. */
904         ssi->toplevel_shell = toplevel_shell;
905       else
906         /* Otherwise, each screen must have its own unmapped root widget. */
907         ssi->toplevel_shell =
908           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
909                               si->dpy,
910                               XtNscreen, ssi->screen,
911                               XtNvisual, ssi->current_visual,
912                               XtNdepth,  visual_depth (ssi->screen,
913                                                        ssi->current_visual),
914                               NULL);
915
916       if (! found_any_writable_cells)
917         {
918           /* Check to see whether fading is ever possible -- if any of the
919              screens on the display has a PseudoColor visual, then fading can
920              work (on at least some screens.)  If no screen has a PseudoColor
921              visual, then don't bother ever trying to fade, because it will
922              just cause a delay without causing any visible effect.
923           */
924           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
925               get_visual (ssi->screen, "PseudoColor", True, False) ||
926               get_visual (ssi->screen, "GrayScale", True, False))
927             found_any_writable_cells = True;
928         }
929     }
930
931   si->fading_possible_p = found_any_writable_cells;
932
933 #ifdef HAVE_XF86VMODE_GAMMA
934   si->fading_possible_p = True;  /* if we can gamma fade, go for it */
935 #endif
936 }
937
938
939 /* If any server extensions have been requested, try and initialize them.
940    Issue warnings if requests can't be honored.
941  */
942 static void
943 initialize_server_extensions (saver_info *si)
944 {
945   saver_preferences *p = &si->prefs;
946
947   Bool server_has_xidle_extension_p = False;
948   Bool server_has_sgi_saver_extension_p = False;
949   Bool server_has_mit_saver_extension_p = False;
950   Bool system_has_proc_interrupts_p = False;
951   const char *piwhy = 0;
952
953   si->using_xidle_extension = p->use_xidle_extension;
954   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
955   si->using_mit_saver_extension = p->use_mit_saver_extension;
956   si->using_proc_interrupts = p->use_proc_interrupts;
957
958 #ifdef HAVE_XIDLE_EXTENSION
959   server_has_xidle_extension_p = query_xidle_extension (si);
960 #endif
961 #ifdef HAVE_SGI_SAVER_EXTENSION
962   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
963 #endif
964 #ifdef HAVE_MIT_SAVER_EXTENSION
965   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
966 #endif
967 #ifdef HAVE_PROC_INTERRUPTS
968   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
969 #endif
970
971   if (!server_has_xidle_extension_p)
972     si->using_xidle_extension = False;
973   else if (p->verbose_p)
974     {
975       if (si->using_xidle_extension)
976         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
977       else
978         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
979     }
980
981   if (!server_has_sgi_saver_extension_p)
982     si->using_sgi_saver_extension = False;
983   else if (p->verbose_p)
984     {
985       if (si->using_sgi_saver_extension)
986         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
987       else
988         fprintf (stderr,
989                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
990                  blurb());
991     }
992
993   if (!server_has_mit_saver_extension_p)
994     si->using_mit_saver_extension = False;
995   else if (p->verbose_p)
996     {
997       if (si->using_mit_saver_extension)
998         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
999                  blurb());
1000       else
1001         fprintf (stderr,
1002                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
1003                  blurb());
1004     }
1005
1006   /* These are incompatible (or at least, our support for them is...) */
1007   if (si->xinerama_p && si->using_mit_saver_extension)
1008     {
1009       si->using_mit_saver_extension = False;
1010       if (p->verbose_p)
1011         fprintf (stderr, "%s: Xinerama in use: disabling MIT-SCREEN-SAVER.\n",
1012                  blurb());
1013     }
1014
1015 #ifdef HAVE_RANDR
1016   query_randr_extension (si);
1017 #endif
1018
1019   if (!system_has_proc_interrupts_p)
1020     {
1021       si->using_proc_interrupts = False;
1022       if (p->verbose_p && piwhy)
1023         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
1024                  piwhy);
1025     }
1026   else if (p->verbose_p)
1027     {
1028       if (si->using_proc_interrupts)
1029         fprintf (stderr,
1030                  "%s: consulting /proc/interrupts for keyboard activity.\n",
1031                  blurb());
1032       else
1033         fprintf (stderr,
1034                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
1035                  blurb());
1036     }
1037 }
1038
1039
1040 /* For the case where we aren't using an server extensions, select user events
1041    on all the existing windows, and launch timers to select events on
1042    newly-created windows as well.
1043
1044    If a server extension is being used, this does nothing.
1045  */
1046 static void
1047 select_events (saver_info *si)
1048 {
1049   saver_preferences *p = &si->prefs;
1050   int i;
1051
1052   if (si->using_xidle_extension ||
1053       si->using_mit_saver_extension ||
1054       si->using_sgi_saver_extension)
1055     return;
1056
1057   if (p->initial_delay)
1058     {
1059       if (p->verbose_p)
1060         {
1061           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
1062                    (int) p->initial_delay/1000,
1063                    (p->initial_delay == 1000 ? "" : "s"));
1064           fflush (stderr);
1065           fflush (stdout);
1066         }
1067       usleep (p->initial_delay);
1068       if (p->verbose_p)
1069         fprintf (stderr, " done.\n");
1070     }
1071
1072   if (p->verbose_p)
1073     {
1074       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
1075       fflush (stderr);
1076       fflush (stdout);
1077     }
1078
1079   /* Select events on the root windows of every screen.  This also selects
1080      for window creation events, so that new subwindows will be noticed.
1081    */
1082   for (i = 0; i < si->nscreens; i++)
1083     {
1084       saver_screen_info *ssi = &si->screens[i];
1085       if (ssi->real_screen_p)
1086         start_notice_events_timer (si,
1087            RootWindowOfScreen (si->screens[i].screen), False);
1088     }
1089
1090   if (p->verbose_p)
1091     fprintf (stderr, " done.\n");
1092 }
1093
1094
1095 void
1096 maybe_reload_init_file (saver_info *si)
1097 {
1098   saver_preferences *p = &si->prefs;
1099   if (init_file_changed_p (p))
1100     {
1101       if (p->verbose_p)
1102         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
1103                  blurb(), init_file_name());
1104
1105       load_init_file (si->dpy, p);
1106
1107       /* If a server extension is in use, and p->timeout has changed,
1108          we need to inform the server of the new timeout. */
1109       disable_builtin_screensaver (si, False);
1110
1111       /* If the DPMS settings in the init file have changed,
1112          change the settings on the server to match. */
1113       sync_server_dpms_settings (si->dpy,
1114                                  (p->dpms_enabled_p  &&
1115                                   p->mode != DONT_BLANK),
1116                                  p->dpms_standby / 1000,
1117                                  p->dpms_suspend / 1000,
1118                                  p->dpms_off / 1000,
1119                                  False);
1120     }
1121 }
1122
1123
1124 /* Loop forever:
1125
1126        - wait until the user is idle;
1127        - blank the screen;
1128        - wait until the user is active;
1129        - unblank the screen;
1130        - repeat.
1131
1132  */
1133 static void
1134 main_loop (saver_info *si)
1135 {
1136   saver_preferences *p = &si->prefs;
1137   Bool ok_to_unblank;
1138
1139   while (1)
1140     {
1141       Bool was_locked = False;
1142
1143       if (p->verbose_p)
1144         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1145
1146       check_for_leaks ("unblanked A");
1147       sleep_until_idle (si, True);
1148       check_for_leaks ("unblanked B");
1149
1150       if (p->verbose_p)
1151         {
1152           if (si->demoing_p)
1153             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
1154                      si->selection_mode, timestring());
1155           else
1156             fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
1157                      timestring());
1158         }
1159
1160       maybe_reload_init_file (si);
1161
1162       if (p->mode == DONT_BLANK)
1163         {
1164           if (p->verbose_p)
1165             fprintf (stderr, "%s: idle with blanking disabled at %s.\n",
1166                      blurb(), timestring());
1167
1168           /* Go around the loop and wait for the next bout of idleness,
1169              or for the init file to change, or for a remote command to
1170              come in, or something.
1171
1172              But, if locked_p is true, go ahead.  This can only happen
1173              if we're in "disabled" mode but a "lock" clientmessage came
1174              in: in that case, we should go ahead and blank/lock the screen.
1175            */
1176           if (!si->locked_p)
1177             continue;
1178         }
1179
1180       /* Since we're about to blank the screen, kill the de-race timer,
1181          if any.  It might still be running if we have unblanked and then
1182          re-blanked in a short period (e.g., when using the "next" button
1183          in xscreensaver-demo.)
1184        */
1185       if (si->de_race_id)
1186         {
1187           if (p->verbose_p)
1188             fprintf (stderr, "%s: stopping de-race timer (%d remaining.)\n",
1189                      blurb(), si->de_race_ticks);
1190           XtRemoveTimeOut (si->de_race_id);
1191           si->de_race_id = 0;
1192         }
1193
1194
1195       /* Now, try to blank.
1196        */
1197
1198       if (! blank_screen (si))
1199         {
1200           /* We were unable to grab either the keyboard or mouse.
1201              This means we did not (and must not) blank the screen.
1202              If we were to blank the screen while some other program
1203              is holding both the mouse and keyboard grabbed, then
1204              we would never be able to un-blank it!  We would never
1205              see any events, and the display would be wedged.
1206
1207              So, just go around the loop again and wait for the
1208              next bout of idleness.  (If the user remains idle, we
1209              will next try to blank the screen again in no more than
1210              60 seconds.)
1211           */
1212           Time retry = 60 * 1000;
1213           if (p->timeout < retry)
1214             retry = p->timeout;
1215
1216           if (p->debug_p)
1217             {
1218               fprintf (stderr,
1219                   "%s: DEBUG MODE: unable to grab -- BLANKING ANYWAY.\n",
1220                        blurb());
1221             }
1222           else
1223             {
1224               fprintf (stderr,
1225                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
1226                        blurb());
1227
1228               schedule_wakeup_event (si, retry, p->debug_p);
1229               continue;
1230             }
1231         }
1232
1233       kill_screenhack (si);
1234
1235       if (!si->throttled_p)
1236         spawn_screenhack (si, True);
1237       else if (p->verbose_p)
1238         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
1239
1240       /* Don't start the cycle timer in demo mode. */
1241       if (!si->demoing_p && p->cycle)
1242         si->cycle_id = XtAppAddTimeOut (si->app,
1243                                         (si->selection_mode
1244                                          /* see comment in cycle_timer() */
1245                                          ? 1000 * 60 * 60
1246                                          : p->cycle),
1247                                         cycle_timer,
1248                                         (XtPointer) si);
1249
1250
1251 #ifndef NO_LOCKING
1252       /* Maybe start locking the screen.
1253        */
1254       {
1255         Time lock_timeout = p->lock_timeout;
1256
1257         if (si->emergency_lock_p && p->lock_p && lock_timeout)
1258           {
1259             int secs = p->lock_timeout / 1000;
1260             if (p->verbose_p)
1261               fprintf (stderr,
1262                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
1263                        blurb(),
1264                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
1265             lock_timeout = 0;
1266           }
1267
1268         si->emergency_lock_p = False;
1269
1270         if (!si->demoing_p &&           /* if not going into demo mode */
1271             p->lock_p &&                /* and locking is enabled */
1272             !si->locking_disabled_p &&  /* and locking is possible */
1273             lock_timeout == 0)          /* and locking is not timer-deferred */
1274           set_locked_p (si, True);      /* then lock right now. */
1275
1276         /* locked_p might be true already because of the above, or because of
1277            the LOCK ClientMessage.  But if not, and if we're supposed to lock
1278            after some time, set up a timer to do so.
1279         */
1280         if (p->lock_p &&
1281             !si->locked_p &&
1282             lock_timeout > 0)
1283           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
1284                                          activate_lock_timer,
1285                                          (XtPointer) si);
1286       }
1287 #endif /* !NO_LOCKING */
1288
1289
1290       ok_to_unblank = True;
1291       do {
1292
1293         check_for_leaks ("blanked A");
1294         sleep_until_idle (si, False);           /* until not idle */
1295         check_for_leaks ("blanked B");
1296
1297         maybe_reload_init_file (si);
1298
1299 #ifndef NO_LOCKING
1300         /* Maybe unlock the screen.
1301          */
1302         if (si->locked_p)
1303           {
1304             saver_screen_info *ssi = si->default_screen;
1305             if (si->locking_disabled_p) abort ();
1306
1307             was_locked = True;
1308             si->dbox_up_p = True;
1309             suspend_screenhack (si, True);
1310             XUndefineCursor (si->dpy, ssi->screensaver_window);
1311
1312             ok_to_unblank = unlock_p (si);
1313
1314             si->dbox_up_p = False;
1315             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
1316             suspend_screenhack (si, False);     /* resume */
1317
1318             if (!ok_to_unblank &&
1319                 !screenhack_running_p (si))
1320               {
1321                 /* If the lock dialog has been dismissed and we're not about to
1322                    unlock the screen, and there is currently no hack running,
1323                    then launch one.  (There might be no hack running if DPMS
1324                    had kicked in.  But DPMS is off now, so bring back the hack)
1325                  */
1326                 if (si->cycle_id)
1327                   XtRemoveTimeOut (si->cycle_id);
1328                 si->cycle_id = 0;
1329                 cycle_timer ((XtPointer) si, 0);
1330               }
1331           }
1332 #endif /* !NO_LOCKING */
1333
1334         } while (!ok_to_unblank);
1335
1336
1337       if (p->verbose_p)
1338         fprintf (stderr, "%s: unblanking screen at %s.\n",
1339                  blurb(), timestring ());
1340
1341       /* Kill before unblanking, to stop drawing as soon as possible. */
1342       kill_screenhack (si);
1343       unblank_screen (si);
1344
1345       set_locked_p (si, False);
1346       si->emergency_lock_p = False;
1347       si->demoing_p = 0;
1348       si->selection_mode = 0;
1349
1350       /* If we're throttled, and the user has explicitly unlocked the screen,
1351          then unthrottle.  If we weren't locked, then don't unthrottle
1352          automatically, because someone might have just bumped the desk... */
1353       if (was_locked)
1354         {
1355           if (si->throttled_p && p->verbose_p)
1356             fprintf (stderr, "%s: unthrottled.\n", blurb());
1357           si->throttled_p = False;
1358         }
1359
1360       if (si->cycle_id)
1361         {
1362           XtRemoveTimeOut (si->cycle_id);
1363           si->cycle_id = 0;
1364         }
1365
1366       if (si->lock_id)
1367         {
1368           XtRemoveTimeOut (si->lock_id);
1369           si->lock_id = 0;
1370         }
1371
1372       /* Since we're unblanked now, break race conditions and make
1373          sure we stay that way (see comment in timers.c.) */
1374       if (! si->de_race_id)
1375         de_race_timer ((XtPointer) si, 0);
1376     }
1377 }
1378
1379 static void analyze_display (saver_info *si);
1380 static void fix_fds (void);
1381
1382 int
1383 main (int argc, char **argv)
1384 {
1385   Widget shell;
1386   saver_info the_si;
1387   saver_info *si = &the_si;
1388   saver_preferences *p = &si->prefs;
1389   struct passwd *spasswd;
1390   int i;
1391
1392   memset(si, 0, sizeof(*si));
1393   global_si_kludge = si;        /* I hate C so much... */
1394
1395   fix_fds();
1396
1397 # undef ya_rand_init
1398   ya_rand_init (0);
1399
1400   save_argv (argc, argv);
1401   set_version_string (si, &argc, argv);
1402   privileged_initialization (si, &argc, argv);
1403   hack_environment (si);
1404
1405   spasswd = getpwuid(getuid());
1406   if (!spasswd)
1407     {
1408       fprintf(stderr, "Could not figure out who the current user is!\n");
1409       return 1;
1410     }
1411
1412   si->user = strdup(spasswd->pw_name ? spasswd->pw_name : "(unknown)");
1413
1414 # ifndef NO_LOCKING
1415   si->unlock_cb = gui_auth_conv;
1416   si->auth_finished_cb = auth_finished_cb;
1417 # endif /* !NO_LOCKING */
1418
1419   shell = connect_to_server (si, &argc, argv);
1420   process_command_line (si, &argc, argv);
1421   print_banner (si);
1422
1423   load_init_file(si->dpy, p); /* must be before initialize_per_screen_info() */
1424   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1425   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1426
1427   /* We can only issue this warning now. */
1428   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1429     fprintf (stderr,
1430              "%s: there are no PseudoColor or GrayScale visuals.\n"
1431              "%s: ignoring the request for fading/unfading.\n",
1432              blurb(), blurb());
1433
1434   for (i = 0; i < si->nscreens; i++)
1435     {
1436       saver_screen_info *ssi = &si->screens[i];
1437       if (ssi->real_screen_p)
1438         if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1439           exit (1);
1440     }
1441
1442   lock_initialization (si, &argc, argv);
1443   print_lock_failure_banner (si);
1444
1445   if (p->xsync_p) XSynchronize (si->dpy, True);
1446
1447   if (p->verbose_p) analyze_display (si);
1448   initialize_server_extensions (si);
1449
1450   si->blank_time = time ((time_t) 0); /* must be before ..._window */
1451   initialize_screensaver_window (si);
1452
1453   select_events (si);
1454   init_sigchld ();
1455
1456   disable_builtin_screensaver (si, True);
1457   sync_server_dpms_settings (si->dpy,
1458                              (p->dpms_enabled_p  &&
1459                               p->mode != DONT_BLANK),
1460                              p->dpms_standby / 1000,
1461                              p->dpms_suspend / 1000,
1462                              p->dpms_off / 1000,
1463                              False);
1464
1465   initialize_stderr (si);
1466   handle_signals (si);
1467
1468   make_splash_dialog (si);
1469
1470   main_loop (si);               /* doesn't return */
1471   return 0;
1472 }
1473
1474 static void
1475 fix_fds (void)
1476 {
1477   /* Bad Things Happen if stdin, stdout, and stderr have been closed
1478      (as by the `sh incantation "xscreensaver >&- 2>&-").  When you do
1479      that, the X connection gets allocated to one of these fds, and
1480      then some random library writes to stderr, and random bits get
1481      stuffed down the X pipe, causing "Xlib: sequence lost" errors.
1482      So, we cause the first three file descriptors to be open to
1483      /dev/null if they aren't open to something else already.  This
1484      must be done before any other files are opened (or the closing
1485      of that other file will again free up one of the "magic" first
1486      three FDs.)
1487
1488      We do this by opening /dev/null three times, and then closing
1489      those fds, *unless* any of them got allocated as #0, #1, or #2,
1490      in which case we leave them open.  Gag.
1491
1492      Really, this crap is technically required of *every* X program,
1493      if you want it to be robust in the face of "2>&-".
1494    */
1495   int fd0 = open ("/dev/null", O_RDWR);
1496   int fd1 = open ("/dev/null", O_RDWR);
1497   int fd2 = open ("/dev/null", O_RDWR);
1498   if (fd0 > 2) close (fd0);
1499   if (fd1 > 2) close (fd1);
1500   if (fd2 > 2) close (fd2);
1501 }
1502
1503
1504 \f
1505 /* Processing ClientMessage events.
1506  */
1507
1508
1509 static Bool error_handler_hit_p = False;
1510
1511 static int
1512 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
1513 {
1514   error_handler_hit_p = True;
1515   return 0;
1516 }
1517
1518 /* Sometimes some systems send us ClientMessage events with bogus atoms in
1519    them.  We only look up the atom names for printing warning messages,
1520    so don't bomb out when it happens...
1521  */
1522 static char *
1523 XGetAtomName_safe (Display *dpy, Atom atom)
1524 {
1525   char *result;
1526   XErrorHandler old_handler;
1527   if (!atom) return 0;
1528
1529   XSync (dpy, False);
1530   error_handler_hit_p = False;
1531   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1532   result = XGetAtomName (dpy, atom);
1533   XSync (dpy, False);
1534   XSetErrorHandler (old_handler);
1535   XSync (dpy, False);
1536   if (error_handler_hit_p) result = 0;
1537
1538   if (result)
1539     return result;
1540   else
1541     {
1542       char buf[100];
1543       sprintf (buf, "<<undefined atom 0x%04X>>", (unsigned int) atom);
1544       return strdup (buf);
1545     }
1546 }
1547
1548
1549 static void
1550 clientmessage_response (saver_info *si, Window w, Bool error,
1551                         const char *stderr_msg,
1552                         const char *protocol_msg)
1553 {
1554   char *proto;
1555   int L;
1556   saver_preferences *p = &si->prefs;
1557   XErrorHandler old_handler;
1558
1559   if (error || p->verbose_p)
1560     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1561
1562   L = strlen(protocol_msg);
1563   proto = (char *) malloc (L + 2);
1564   proto[0] = (error ? '-' : '+');
1565   strcpy (proto+1, protocol_msg);
1566   L++;
1567
1568   /* Ignore all X errors while sending a response to a ClientMessage.
1569      Pretty much the only way we could get an error here is if the
1570      window we're trying to send the reply on has been deleted, in
1571      which case, the sender of the ClientMessage won't see our response
1572      anyway.
1573    */
1574   XSync (si->dpy, False);
1575   error_handler_hit_p = False;
1576   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1577
1578   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1579                    PropModeReplace, (unsigned char *) proto, L);
1580
1581   XSync (si->dpy, False);
1582   XSetErrorHandler (old_handler);
1583   XSync (si->dpy, False);
1584
1585   free (proto);
1586 }
1587
1588
1589 static void
1590 bogus_clientmessage_warning (saver_info *si, XEvent *event)
1591 {
1592   saver_preferences *p = &si->prefs;
1593   char *str = XGetAtomName_safe (si->dpy, event->xclient.message_type);
1594   Window w = event->xclient.window;
1595   char wdesc[255];
1596   int screen = 0;
1597   Bool root_p = False;
1598
1599   *wdesc = 0;
1600   for (screen = 0; screen < si->nscreens; screen++)
1601     if (w == si->screens[screen].screensaver_window)
1602       {
1603         strcpy (wdesc, "xscreensaver");
1604         break;
1605       }
1606     else if (w == RootWindow (si->dpy, screen))
1607       {
1608         strcpy (wdesc, "root");
1609         root_p = True;
1610         break;
1611       }
1612
1613   /* If this ClientMessage was sent to the real root window instead of to the
1614      xscreensaver window, then it might be intended for someone else who is
1615      listening on the root window (e.g., the window manager).  So only print
1616      the warning if: we are in debug mode; or if the bogus message was
1617      actually sent to one of the xscreensaver-created windows.
1618    */
1619   if (root_p && !p->debug_p)
1620     return;
1621
1622   if (!*wdesc)
1623     {
1624       XErrorHandler old_handler;
1625       XClassHint hint;
1626       XWindowAttributes xgwa;
1627       memset (&hint, 0, sizeof(hint));
1628       memset (&xgwa, 0, sizeof(xgwa));
1629
1630       XSync (si->dpy, False);
1631       old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1632       XGetClassHint (si->dpy, w, &hint);
1633       XGetWindowAttributes (si->dpy, w, &xgwa);
1634       XSync (si->dpy, False);
1635       XSetErrorHandler (old_handler);
1636       XSync (si->dpy, False);
1637
1638       screen = (xgwa.screen ? screen_number (xgwa.screen) : -1);
1639
1640       sprintf (wdesc, "%.20s / %.20s",
1641                (hint.res_name  ? hint.res_name  : "(null)"),
1642                (hint.res_class ? hint.res_class : "(null)"));
1643       if (hint.res_name)  XFree (hint.res_name);
1644       if (hint.res_class) XFree (hint.res_class);
1645     }
1646
1647   fprintf (stderr, "%s: %d: unrecognised ClientMessage \"%s\" received\n",
1648            blurb(), screen, (str ? str : "(null)"));
1649   fprintf (stderr, "%s: %d: for window 0x%lx (%s)\n",
1650            blurb(), screen, (unsigned long) w, wdesc);
1651   if (str) XFree (str);
1652 }
1653
1654 Bool
1655 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1656 {
1657   saver_preferences *p = &si->prefs;
1658   Atom type = 0;
1659   Window window = event->xclient.window;
1660
1661   /* Preferences might affect our handling of client messages. */
1662   maybe_reload_init_file (si);
1663
1664   if (event->xclient.message_type != XA_SCREENSAVER ||
1665       event->xclient.format != 32)
1666     {
1667       bogus_clientmessage_warning (si, event);
1668       return False;
1669     }
1670
1671   type = event->xclient.data.l[0];
1672   if (type == XA_ACTIVATE)
1673     {
1674       if (until_idle_p)
1675         {
1676           if (p->mode == DONT_BLANK)
1677             {
1678               clientmessage_response(si, window, True,
1679                          "ACTIVATE ClientMessage received in DONT_BLANK mode.",
1680                                      "screen blanking is currently disabled.");
1681               return False;
1682             }
1683
1684           clientmessage_response(si, window, False,
1685                                  "ACTIVATE ClientMessage received.",
1686                                  "activating.");
1687           si->selection_mode = 0;
1688           si->demoing_p = False;
1689
1690           if (si->throttled_p && p->verbose_p)
1691             fprintf (stderr, "%s: unthrottled.\n", blurb());
1692           si->throttled_p = False;
1693
1694           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1695             {
1696               XForceScreenSaver (si->dpy, ScreenSaverActive);
1697               return False;
1698             }
1699           else
1700             {
1701               return True;
1702             }
1703         }
1704       clientmessage_response(si, window, True,
1705                        "ClientMessage ACTIVATE received while already active.",
1706                              "already active.");
1707     }
1708   else if (type == XA_DEACTIVATE)
1709     {
1710       if (! until_idle_p)
1711         {
1712           if (si->throttled_p && p->verbose_p)
1713             fprintf (stderr, "%s: unthrottled.\n", blurb());
1714           si->throttled_p = False;
1715
1716           clientmessage_response(si, window, False,
1717                                  "DEACTIVATE ClientMessage received.",
1718                                  "deactivating.");
1719           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1720             {
1721               XForceScreenSaver (si->dpy, ScreenSaverReset);
1722               return False;
1723             }
1724           else
1725             {
1726               return True;
1727             }
1728         }
1729       clientmessage_response(si, window, False,
1730      "ClientMessage DEACTIVATE received while inactive: resetting idle timer.",
1731                              "not active: idle timer reset.");
1732       reset_timers (si);
1733     }
1734   else if (type == XA_CYCLE)
1735     {
1736       if (! until_idle_p)
1737         {
1738           clientmessage_response(si, window, False,
1739                                  "CYCLE ClientMessage received.",
1740                                  "cycling.");
1741           si->selection_mode = 0;       /* 0 means randomize when its time. */
1742           si->demoing_p = False;
1743
1744           if (si->throttled_p && p->verbose_p)
1745             fprintf (stderr, "%s: unthrottled.\n", blurb());
1746           si->throttled_p = False;
1747
1748           if (si->cycle_id)
1749             XtRemoveTimeOut (si->cycle_id);
1750           si->cycle_id = 0;
1751           cycle_timer ((XtPointer) si, 0);
1752           return False;
1753         }
1754       clientmessage_response(si, window, True,
1755                              "ClientMessage CYCLE received while inactive.",
1756                              "not active.");
1757     }
1758   else if (type == XA_NEXT || type == XA_PREV)
1759     {
1760       clientmessage_response(si, window, False,
1761                              (type == XA_NEXT
1762                               ? "NEXT ClientMessage received."
1763                               : "PREV ClientMessage received."),
1764                              "cycling.");
1765       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1766       si->demoing_p = False;
1767
1768       if (si->throttled_p && p->verbose_p)
1769         fprintf (stderr, "%s: unthrottled.\n", blurb());
1770       si->throttled_p = False;
1771
1772       if (! until_idle_p)
1773         {
1774           if (si->cycle_id)
1775             XtRemoveTimeOut (si->cycle_id);
1776           si->cycle_id = 0;
1777           cycle_timer ((XtPointer) si, 0);
1778         }
1779       else
1780         return True;
1781     }
1782   else if (type == XA_SELECT)
1783     {
1784       char buf [255];
1785       char buf2 [255];
1786       long which = event->xclient.data.l[1];
1787
1788       if (p->mode == DONT_BLANK)
1789         {
1790           clientmessage_response(si, window, True,
1791                            "SELECT ClientMessage received in DONT_BLANK mode.",
1792                                  "screen blanking is currently disabled.");
1793           return False;
1794         }
1795
1796       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1797       sprintf (buf2, "activating (%ld).", which);
1798       clientmessage_response (si, window, False, buf, buf2);
1799
1800       if (which < 0) which = 0;         /* 0 == "random" */
1801       si->selection_mode = which;
1802       si->demoing_p = False;
1803
1804       if (si->throttled_p && p->verbose_p)
1805         fprintf (stderr, "%s: unthrottled.\n", blurb());
1806       si->throttled_p = False;
1807
1808       if (! until_idle_p)
1809         {
1810           if (si->cycle_id)
1811             XtRemoveTimeOut (si->cycle_id);
1812           si->cycle_id = 0;
1813           cycle_timer ((XtPointer) si, 0);
1814         }
1815       else
1816         return True;
1817     }
1818   else if (type == XA_EXIT)
1819     {
1820       /* Ignore EXIT message if the screen is locked. */
1821       if (until_idle_p || !si->locked_p)
1822         {
1823           clientmessage_response (si, window, False,
1824                                   "EXIT ClientMessage received.",
1825                                   "exiting.");
1826           if (! until_idle_p)
1827             {
1828               unblank_screen (si);
1829               kill_screenhack (si);
1830               XSync (si->dpy, False);
1831             }
1832           saver_exit (si, 0, 0);
1833         }
1834       else
1835         clientmessage_response (si, window, True,
1836                                 "EXIT ClientMessage received while locked.",
1837                                 "screen is locked.");
1838     }
1839   else if (type == XA_RESTART)
1840     {
1841       /* The RESTART message works whether the screensaver is active or not,
1842          unless the screen is locked, in which case it doesn't work.
1843        */
1844       if (until_idle_p || !si->locked_p)
1845         {
1846           clientmessage_response (si, window, False,
1847                                   "RESTART ClientMessage received.",
1848                                   "restarting.");
1849           if (! until_idle_p)
1850             {
1851               unblank_screen (si);
1852               kill_screenhack (si);
1853               XSync (si->dpy, False);
1854             }
1855
1856           restart_process (si);  /* does not return */
1857           abort();
1858         }
1859       else
1860         clientmessage_response (si, window, True,
1861                                 "RESTART ClientMessage received while locked.",
1862                                 "screen is locked.");
1863     }
1864   else if (type == XA_DEMO)
1865     {
1866       long arg = event->xclient.data.l[1];
1867       Bool demo_one_hack_p = (arg == 5000);
1868
1869       if (demo_one_hack_p)
1870         {
1871           if (until_idle_p)
1872             {
1873               long which = event->xclient.data.l[2];
1874               char buf [255];
1875               char buf2 [255];
1876               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1877               sprintf (buf2, "demoing (%ld).", which);
1878               clientmessage_response (si, window, False, buf, buf2);
1879
1880               if (which < 0) which = 0;         /* 0 == "random" */
1881               si->selection_mode = which;
1882               si->demoing_p = True;
1883
1884               if (si->throttled_p && p->verbose_p)
1885                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1886               si->throttled_p = False;
1887
1888               return True;
1889             }
1890
1891           clientmessage_response (si, window, True,
1892                                   "DEMO ClientMessage received while active.",
1893                                   "already active.");
1894         }
1895       else
1896         {
1897           clientmessage_response (si, window, True,
1898                                   "obsolete form of DEMO ClientMessage.",
1899                                   "obsolete form of DEMO ClientMessage.");
1900         }
1901     }
1902   else if (type == XA_PREFS)
1903     {
1904       clientmessage_response (si, window, True,
1905                               "the PREFS client-message is obsolete.",
1906                               "the PREFS client-message is obsolete.");
1907     }
1908   else if (type == XA_LOCK)
1909     {
1910 #ifdef NO_LOCKING
1911       clientmessage_response (si, window, True,
1912                               "not compiled with support for locking.",
1913                               "locking not enabled.");
1914 #else /* !NO_LOCKING */
1915       if (si->locking_disabled_p)
1916         clientmessage_response (si, window, True,
1917                       "LOCK ClientMessage received, but locking is disabled.",
1918                               "locking not enabled.");
1919       else if (si->locked_p)
1920         clientmessage_response (si, window, True,
1921                            "LOCK ClientMessage received while already locked.",
1922                                 "already locked.");
1923       else
1924         {
1925           char buf [255];
1926           char *response = (until_idle_p
1927                             ? "activating and locking."
1928                             : "locking.");
1929           sprintf (buf, "LOCK ClientMessage received; %s", response);
1930           clientmessage_response (si, window, False, buf, response);
1931           set_locked_p (si, True);
1932           si->selection_mode = 0;
1933           si->demoing_p = False;
1934
1935           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1936             {
1937               XtRemoveTimeOut (si->lock_id);
1938               si->lock_id = 0;
1939             }
1940
1941           if (until_idle_p)
1942             {
1943               if (si->using_mit_saver_extension ||
1944                   si->using_sgi_saver_extension)
1945                 {
1946                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1947                   return False;
1948                 }
1949               else
1950                 {
1951                   return True;
1952                 }
1953             }
1954         }
1955 #endif /* !NO_LOCKING */
1956     }
1957   else if (type == XA_THROTTLE)
1958     {
1959       /* The THROTTLE command is deprecated -- it predates the XDPMS
1960          extension.  Instead of using -throttle, users should instead
1961          just power off the monitor (e.g., "xset dpms force off".)
1962          In a few minutes, xscreensaver will notice that the monitor
1963          is off, and cease running hacks.
1964        */
1965       if (si->throttled_p)
1966         clientmessage_response (si, window, True,
1967                                 "THROTTLE ClientMessage received, but "
1968                                 "already throttled.",
1969                                 "already throttled.");
1970       else
1971         {
1972           char buf [255];
1973           char *response = "throttled.";
1974           si->throttled_p = True;
1975           si->selection_mode = 0;
1976           si->demoing_p = False;
1977           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
1978           clientmessage_response (si, window, False, buf, response);
1979
1980           if (! until_idle_p)
1981             {
1982               if (si->cycle_id)
1983                 XtRemoveTimeOut (si->cycle_id);
1984               si->cycle_id = 0;
1985               cycle_timer ((XtPointer) si, 0);
1986             }
1987         }
1988     }
1989   else if (type == XA_UNTHROTTLE)
1990     {
1991       if (! si->throttled_p)
1992         clientmessage_response (si, window, True,
1993                                 "UNTHROTTLE ClientMessage received, but "
1994                                 "not throttled.",
1995                                 "not throttled.");
1996       else
1997         {
1998           char buf [255];
1999           char *response = "unthrottled.";
2000           si->throttled_p = False;
2001           si->selection_mode = 0;
2002           si->demoing_p = False;
2003           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
2004           clientmessage_response (si, window, False, buf, response);
2005
2006           if (! until_idle_p)
2007             {
2008               if (si->cycle_id)
2009                 XtRemoveTimeOut (si->cycle_id);
2010               si->cycle_id = 0;
2011               cycle_timer ((XtPointer) si, 0);
2012             }
2013         }
2014     }
2015   else
2016     {
2017       char buf [1024];
2018       char *str;
2019       str = XGetAtomName_safe (si->dpy, type);
2020
2021       if (str)
2022         {
2023           if (strlen (str) > 80)
2024             strcpy (str+70, "...");
2025           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
2026                    str);
2027           free (str);
2028         }
2029       else
2030         {
2031           sprintf (buf,
2032                    "unrecognised screensaver ClientMessage 0x%x received.",
2033                    (unsigned int) event->xclient.data.l[0]);
2034         }
2035
2036       clientmessage_response (si, window, True, buf, buf);
2037     }
2038   return False;
2039 }
2040
2041 \f
2042 /* Some random diagnostics printed in -verbose mode.
2043  */
2044
2045 static void
2046 analyze_display (saver_info *si)
2047 {
2048   int i, j;
2049   static struct {
2050     const char *name; const char *desc; Bool useful_p;
2051   } exts[] = {
2052
2053    { "SCREEN_SAVER", /* underscore */           "SGI Screen-Saver",
2054 #     ifdef HAVE_SGI_SAVER_EXTENSION
2055         True
2056 #     else
2057         False
2058 #     endif
2059    }, { "SCREEN-SAVER", /* dash */              "SGI Screen-Saver",
2060 #     ifdef HAVE_SGI_SAVER_EXTENSION
2061         True
2062 #     else
2063         False
2064 #     endif
2065    }, { "MIT-SCREEN-SAVER",                     "MIT Screen-Saver",
2066 #     ifdef HAVE_MIT_SAVER_EXTENSION
2067         True
2068 #     else
2069         False
2070 #     endif
2071    }, { "XIDLE",                                "XIdle",           
2072 #     ifdef HAVE_XIDLE_EXTENSION
2073         True
2074 #     else
2075         False
2076 #     endif
2077    }, { "SGI-VIDEO-CONTROL",                    "SGI Video-Control",
2078 #     ifdef HAVE_SGI_VC_EXTENSION
2079         True
2080 #     else
2081         False
2082 #     endif
2083    }, { "READDISPLAY",                          "SGI Read-Display",
2084 #     ifdef HAVE_READ_DISPLAY_EXTENSION
2085         True
2086 #     else
2087         False
2088 #     endif
2089    }, { "MIT-SHM",                              "Shared Memory",   
2090 #     ifdef HAVE_XSHM_EXTENSION
2091         True
2092 #     else
2093         False
2094 #     endif
2095    }, { "DOUBLE-BUFFER",                        "Double-Buffering",
2096 #     ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2097         True
2098 #     else
2099         False
2100 #     endif
2101    }, { "DPMS",                                 "Power Management",
2102 #     ifdef HAVE_DPMS_EXTENSION
2103         True
2104 #     else
2105         False
2106 #     endif
2107    }, { "GLX",                                  "GLX",             
2108 #     ifdef HAVE_GL
2109         True
2110 #     else
2111         False
2112 #     endif
2113    }, { "XFree86-VidModeExtension",             "XF86 Video-Mode", 
2114 #     ifdef HAVE_XF86VMODE
2115         True
2116 #     else
2117         False
2118 #     endif
2119    }, { "XINERAMA",                             "Xinerama",
2120 #     ifdef HAVE_XINERAMA
2121         True
2122 #     else
2123         False
2124 #     endif
2125    }, { "RANDR",                                "Resize-and-Rotate",
2126 #     ifdef HAVE_RANDR
2127         True
2128 #     else
2129         False
2130 #     endif
2131    }, { "Apple-DRI",                            "Apple-DRI (XDarwin)",
2132         True
2133    },
2134   };
2135
2136   fprintf (stderr, "%s: running on display \"%s\" (%d %sscreen%s).\n",
2137            blurb(),
2138            DisplayString(si->dpy),
2139            si->nscreens,
2140            (si->xinerama_p ? "Xinerama " : ""),
2141            (si->nscreens == 1 ? "" : "s"));
2142   fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(),
2143            ServerVendor(si->dpy), VendorRelease(si->dpy));
2144
2145   fprintf (stderr, "%s: useful extensions:\n", blurb());
2146   for (i = 0; i < countof(exts); i++)
2147     {
2148       int op = 0, event = 0, error = 0;
2149       char buf [255];
2150       int j;
2151       if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
2152         continue;
2153       sprintf (buf, "%s:   ", blurb());
2154       j = strlen (buf);
2155       strcat (buf, exts[i].desc);
2156       if (!exts[i].useful_p)
2157         {
2158           int k = j + 18;
2159           while (strlen (buf) < k) strcat (buf, " ");
2160           strcat (buf, "<-- not supported at compile time!");
2161         }
2162       fprintf (stderr, "%s\n", buf);
2163     }
2164
2165   for (i = 0; i < si->nscreens; i++)
2166     {
2167       saver_screen_info *ssi = &si->screens[i];
2168       unsigned long colormapped_depths = 0;
2169       unsigned long non_mapped_depths = 0;
2170       XVisualInfo vi_in, *vi_out;
2171       int out_count;
2172
2173       if (!ssi->real_screen_p) continue;
2174
2175       vi_in.screen = ssi->real_screen_number;
2176       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
2177       if (!vi_out) continue;
2178       for (j = 0; j < out_count; j++)
2179         if (vi_out[j].class == PseudoColor)
2180           colormapped_depths |= (1 << vi_out[j].depth);
2181         else
2182           non_mapped_depths  |= (1 << vi_out[j].depth);
2183       XFree ((char *) vi_out);
2184
2185       if (colormapped_depths)
2186         {
2187           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(),
2188                    ssi->real_screen_number);
2189           for (j = 0; j < 32; j++)
2190             if (colormapped_depths & (1 << j))
2191               fprintf (stderr, " %d", j);
2192           fprintf (stderr, ".\n");
2193         }
2194       if (non_mapped_depths)
2195         {
2196           fprintf (stderr, "%s: screen %d non-colormapped depths:",
2197                    blurb(), ssi->real_screen_number);
2198           for (j = 0; j < 32; j++)
2199             if (non_mapped_depths & (1 << j))
2200               fprintf (stderr, " %d", j);
2201           fprintf (stderr, ".\n");
2202         }
2203     }
2204
2205   if (si->xinerama_p)
2206     {
2207       fprintf (stderr, "%s: Xinerama layout:\n", blurb());
2208       for (i = 0; i < si->nscreens; i++)
2209         {
2210           saver_screen_info *ssi = &si->screens[i];
2211           fprintf (stderr, "%s:   %c %d/%d: %dx%d+%d+%d\n",
2212                    blurb(),
2213                    (ssi->real_screen_p ? '+' : ' '),
2214                    ssi->number, ssi->real_screen_number,
2215                    ssi->width, ssi->height, ssi->x, ssi->y);
2216         }
2217     }
2218 }
2219
2220 Bool
2221 display_is_on_console_p (saver_info *si)
2222 {
2223   Bool not_on_console = True;
2224   char *dpystr = DisplayString (si->dpy);
2225   char *tail = (char *) strchr (dpystr, ':');
2226   if (! tail || strncmp (tail, ":0", 2))
2227     not_on_console = True;
2228   else
2229     {
2230       char dpyname[255], localname[255];
2231       strncpy (dpyname, dpystr, tail-dpystr);
2232       dpyname [tail-dpystr] = 0;
2233       if (!*dpyname ||
2234           !strcmp(dpyname, "unix") ||
2235           !strcmp(dpyname, "localhost"))
2236         not_on_console = False;
2237       else if (gethostname (localname, sizeof (localname)))
2238         not_on_console = True;  /* can't find hostname? */
2239       else
2240         {
2241           /* We have to call gethostbyname() on the result of gethostname()
2242              because the two aren't guarenteed to be the same name for the
2243              same host: on some losing systems, one is a FQDN and the other
2244              is not.  Here in the wide wonderful world of Unix it's rocket
2245              science to obtain the local hostname in a portable fashion.
2246              
2247              And don't forget, gethostbyname() reuses the structure it
2248              returns, so we have to copy the fucker before calling it again.
2249              Thank you master, may I have another.
2250            */
2251           struct hostent *h = gethostbyname (dpyname);
2252           if (!h)
2253             not_on_console = True;
2254           else
2255             {
2256               char hn [255];
2257               struct hostent *l;
2258               strcpy (hn, h->h_name);
2259               l = gethostbyname (localname);
2260               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
2261             }
2262         }
2263     }
2264   return !not_on_console;
2265 }
2266
2267
2268 /* Do a little bit of heap introspection...
2269  */
2270 void
2271 check_for_leaks (const char *where)
2272 {
2273 #if defined(HAVE_SBRK) && defined(LEAK_PARANOIA)
2274   static unsigned long last_brk = 0;
2275   int b = (unsigned long) sbrk(0);
2276   if (last_brk && last_brk < b)
2277     fprintf (stderr, "%s: %s: brk grew by %luK.\n",
2278              blurb(), where,
2279              (((b - last_brk) + 1023) / 1024));
2280   last_brk = b;
2281 #endif /* HAVE_SBRK */
2282 }