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