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