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