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