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