http://apple.doit.wisc.edu/mirrors/amug/linux/linuxppc/sources/tarballs/xscreensaver...
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-1999 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 (or, in the case of the 
20  *   MIT-SCREEN-SAVER extension, use the one it gave us.)
21  *
22  *   We place a __SWM_VROOT property on this window, so that newly-started
23  *   clients will think that this window is a "virtual root" window.
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  *   While we are waiting, we also set up timers so that, after a certain 
42  *   amount of time has passed, we can start a different screenhack.  We do
43  *   this by killing the running child process with SIGTERM, and then starting
44  *   a new one in the same way.
45  *
46  *   If there was a real virtual root, meaning that we removed the __SWM_VROOT
47  *   property from it, meaning we must (absolutely must) restore it before we
48  *   exit, then we set up signal handlers for most signals (SIGINT, SIGTERM,
49  *   etc.) that do this.  Most Xlib and Xt routines are not reentrant, so it
50  *   is not generally safe to call them from signal handlers; however, this
51  *   program spends most of its time waiting, so the window of opportunity 
52  *   when code could be called reentrantly is fairly small; and also, the worst
53  *   that could happen is that the call would fail.  If we've gotten one of
54  *   these signals, then we're on our way out anyway.  If we didn't restore the
55  *   __SWM_VROOT property, that would be very bad, so it's worth a shot.  Note
56  *   that this means that, if you're using a virtual-root window manager, you
57  *   can really fuck up the world by killing this process with "kill -9".
58  *
59  *   This program accepts ClientMessages of type SCREENSAVER; these messages
60  *   may contain the atom ACTIVATE or DEACTIVATE, meaning to turn the 
61  *   screensaver on or off now, regardless of the idleness of the user,
62  *   and a few other things.  The included "xscreensaver_command" program
63  *   sends these messsages.
64  *
65  *   If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER
66  *   extensions, then we do the XAutoLock trick: notice every window that
67  *   gets created, and wait 30 seconds or so until its creating process has
68  *   settled down, and then select KeyPress events on those windows which
69  *   already select for KeyPress events.  It's important that we not select
70  *   KeyPress on windows which don't select them, because that would
71  *   interfere with event propagation.  This will break if any program
72  *   changes its event mask to contain KeyRelease or PointerMotion more than
73  *   30 seconds after creating the window, but that's probably pretty rare.
74  *   
75  *   The reason that we can't select KeyPresses on windows that don't have
76  *   them already is that, when dispatching a KeyPress event, X finds the
77  *   lowest (leafmost) window in the hierarchy on which *any* client selects
78  *   for KeyPress, and sends the event to that window.  This means that if a
79  *   client had a window with subwindows, and expected to receive KeyPress
80  *   events on the parent window instead of the subwindows, then that client
81  *   would malfunction if some other client selected KeyPress events on the
82  *   subwindows.  It is an incredible misdesign that one client can make
83  *   another client malfunction in this way.
84  *
85  *   To detect mouse motion, we periodically wake up and poll the mouse
86  *   position and button/modifier state, and notice when something has
87  *   changed.  We make this check every five seconds by default, and since the
88  *   screensaver timeout has a granularity of one minute, this makes the
89  *   chance of a false positive very small.  We could detect mouse motion in
90  *   the same way as keyboard activity, but that would suffer from the same
91  *   "client changing event mask" problem that the KeyPress events hack does.
92  *   I think polling is more reliable.
93  *
94  *   None of this crap happens if we're using one of the extensions, so install
95  *   one of them if the description above sounds just too flaky to live.  It
96  *   is, but those are your choices.
97  *
98  *   A third idle-detection option could be implemented (but is not): when
99  *   running on the console display ($DISPLAY is `localhost`:0) and we're on a
100  *   machine where /dev/tty and /dev/mouse have reasonable last-modification
101  *   times, we could just stat() those.  But the incremental benefit of
102  *   implementing this is really small, so forget I said anything.
103  *
104  *   Debugging hints:
105  *     - Have a second terminal handy.
106  *     - Be careful where you set your breakpoints, you don't want this to
107  *       stop under the debugger with the keyboard grabbed or the blackout
108  *       window exposed.
109  *     - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
110  *       to keep your emacs window alive even when xscreensaver has grabbed.
111  *     - Go read the code related to `debug_p'.
112  *     - You probably can't set breakpoints in functions that are called on
113  *       the other side of a call to fork() -- if your clients are dying 
114  *       with signal 5, Trace/BPT Trap, you're losing in this way.
115  *     - If you aren't using a server extension, don't leave this stopped
116  *       under the debugger for very long, or the X input buffer will get
117  *       huge because of the keypress events it's selecting for.  This can
118  *       make your X server wedge with "no more input buffers."
119  *       
120  * ======================================================================== */
121
122 #ifdef HAVE_CONFIG_H
123 # include "config.h"
124 #endif
125
126 #include <stdio.h>
127 #include <ctype.h>
128 #include <X11/Xlib.h>
129 #include <X11/Xatom.h>
130 #include <X11/Intrinsic.h>
131 #include <X11/StringDefs.h>
132 #include <X11/Shell.h>
133 #include <X11/Xos.h>
134 #include <netdb.h>      /* for gethostbyname() */
135 #ifdef HAVE_XMU
136 # ifndef VMS
137 #  include <X11/Xmu/Error.h>
138 # else  /* !VMS */
139 #  include <Xmu/Error.h>
140 # endif /* !VMS */
141 #else  /* !HAVE_XMU */
142 # include "xmu.h"
143 #endif /* !HAVE_XMU */
144
145 #ifdef HAVE_XIDLE_EXTENSION
146 # include <X11/extensions/xidle.h>
147 #endif /* HAVE_XIDLE_EXTENSION */
148
149 #include "xscreensaver.h"
150 #include "version.h"
151 #include "yarandom.h"
152 #include "resources.h"
153 #include "visual.h"
154 #include "usleep.h"
155
156 saver_info *global_si_kludge = 0;       /* I hate C so much... */
157
158 char *progname = 0;
159 char *progclass = 0;
160 XrmDatabase db = 0;
161
162
163 static Atom XA_SCREENSAVER_RESPONSE;
164 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
165 static Atom XA_EXIT, XA_RESTART, XA_LOCK, XA_SELECT;
166 static Atom XA_THROTTLE, XA_UNTHROTTLE;
167 Atom XA_DEMO, XA_PREFS;
168
169 \f
170 static XrmOptionDescRec options [] = {
171   { "-timeout",            ".timeout",          XrmoptionSepArg, 0 },
172   { "-cycle",              ".cycle",            XrmoptionSepArg, 0 },
173   { "-lock-mode",          ".lock",             XrmoptionNoArg, "on" },
174   { "-no-lock-mode",       ".lock",             XrmoptionNoArg, "off" },
175   { "-no-lock",            ".lock",             XrmoptionNoArg, "off" },
176   { "-lock-timeout",       ".lockTimeout",      XrmoptionSepArg, 0 },
177   { "-lock-vts",           ".lockVTs",          XrmoptionNoArg, "on" },
178   { "-no-lock-vts",        ".lockVTs",          XrmoptionNoArg, "off" },
179   { "-visual",             ".visualID",         XrmoptionSepArg, 0 },
180   { "-install",            ".installColormap",  XrmoptionNoArg, "on" },
181   { "-no-install",         ".installColormap",  XrmoptionNoArg, "off" },
182   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
183   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
184   { "-timestamp",          ".timestamp",        XrmoptionNoArg, "on" },
185   { "-capture-stderr",     ".captureStderr",    XrmoptionNoArg, "on" },
186   { "-no-capture-stderr",  ".captureStderr",    XrmoptionNoArg, "off" },
187   { "-xidle-extension",    ".xidleExtension",   XrmoptionNoArg, "on" },
188   { "-no-xidle-extension", ".xidleExtension",   XrmoptionNoArg, "off" },
189   { "-mit-extension",      ".mitSaverExtension",XrmoptionNoArg, "on" },
190   { "-no-mit-extension",   ".mitSaverExtension",XrmoptionNoArg, "off" },
191   { "-sgi-extension",      ".sgiSaverExtension",XrmoptionNoArg, "on" },
192   { "-no-sgi-extension",   ".sgiSaverExtension",XrmoptionNoArg, "off" },
193   { "-proc-interrupts",    ".procInterrupts",   XrmoptionNoArg, "on" },
194   { "-no-proc-interrupts", ".procInterrupts",   XrmoptionNoArg, "off" },
195   { "-splash",             ".splash",           XrmoptionNoArg, "on" },
196   { "-no-splash",          ".splash",           XrmoptionNoArg, "off" },
197   { "-nosplash",           ".splash",           XrmoptionNoArg, "off" },
198   { "-idelay",             ".initialDelay",     XrmoptionSepArg, 0 },
199   { "-nice",               ".nice",             XrmoptionSepArg, 0 },
200
201   /* Actually these are built in to Xt, but just to be sure... */
202   { "-synchronous",        ".synchronous",      XrmoptionNoArg, "on" },
203   { "-xrm",                NULL,                XrmoptionResArg, NULL }
204 };
205
206 static char *defaults[] = {
207 #include "XScreenSaver_ad.h"
208  0
209 };
210
211 #ifdef _VROOT_H_
212 ERROR!  You must not include vroot.h in this file.
213 #endif
214
215 static void
216 do_help (saver_info *si)
217 {
218   fflush (stdout);
219   fflush (stderr);
220   fprintf (stdout, "\
221 xscreensaver %s, copyright (c) 1991-1999 by Jamie Zawinski <jwz@jwz.org>\n\
222 The standard Xt command-line options are accepted; other options include:\n\
223 \n\
224     -timeout <minutes>       When the screensaver should activate.\n\
225     -cycle <minutes>         How long to let each hack run before switching.\n\
226     -lock-mode               Require a password before deactivating.\n\
227     -lock-timeout <minutes>  Grace period before locking; default 0.\n\
228     -visual <id-or-class>    Which X visual to run on.\n\
229     -install                 Install a private colormap.\n\
230     -verbose                 Be loud.\n\
231     -no-splash               Don't display a splash-screen at startup.\n\
232     -help                    This message.\n\
233 \n\
234 See the manual for other options and X resources.\n\
235 \n\
236 The `xscreensaver' program should be left running in the background.\n\
237 Use the `xscreensaver-demo' and `xscreensaver-command' programs to\n\
238 manipulate a running xscreensaver.\n\
239 \n\
240 The `*programs' resource controls which graphics demos will be launched by\n\
241 the screensaver.  See `man xscreensaver' or the web page for more details.\n\
242 \n\
243 Just getting started?  Try this:\n\
244 \n\
245         xscreensaver &\n\
246         xscreensaver-demo\n\
247 \n\
248 For updates, check http://www.jwz.org/xscreensaver/\n\
249 \n",
250           si->version);
251   fflush (stdout);
252   fflush (stderr);
253   exit (1);
254 }
255
256
257 char *
258 timestring (void)
259 {
260   time_t now = time ((time_t *) 0);
261   char *str = (char *) ctime (&now);
262   char *nl = (char *) strchr (str, '\n');
263   if (nl) *nl = 0; /* take off that dang newline */
264   return str;
265 }
266
267 static Bool blurb_timestamp_p = False;   /* kludge */
268
269 const char *
270 blurb (void)
271 {
272   if (!blurb_timestamp_p)
273     return progname;
274   else
275     {
276       static char buf[255];
277       char *ct = timestring();
278       int n = strlen(progname);
279       if (n > 100) n = 99;
280       strncpy(buf, progname, n);
281       buf[n++] = ':';
282       buf[n++] = ' ';
283       strncpy(buf+n, ct+11, 8);
284       strcpy(buf+n+9, ": ");
285       return buf;
286     }
287 }
288
289
290 int
291 saver_ehandler (Display *dpy, XErrorEvent *error)
292 {
293   saver_info *si = global_si_kludge;    /* I hate C so much... */
294
295   if (!real_stderr) real_stderr = stderr;
296
297   fprintf (real_stderr, "\n"
298            "#######################################"
299            "#######################################\n\n"
300            "%s: X Error!  PLEASE REPORT THIS BUG.\n\n"
301            "#######################################"
302            "#######################################\n\n",
303            blurb());
304   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
305     {
306       fprintf (real_stderr, "\n");
307       if (si->prefs.xsync_p)
308         {
309           saver_exit (si, -1, "because of synchronous X Error");
310         }
311       else
312         {
313           fprintf (real_stderr,
314                    "#######################################"
315                    "#######################################\n\n");
316           fprintf (real_stderr,
317    "    If at all possible, please re-run xscreensaver with the command line\n"
318    "    arguments `-sync -verbose', and reproduce this bug.  That will cause\n"
319    "    xscreensaver to dump a `core' file to the current directory.  Please\n"
320    "    include the stack trace from that core file in your bug report.\n"
321    "\n"
322    "    http://www.jwz.org/xscreensaver/bugs.html explains how to create the\n"
323    "    most useful bug reports, and how to examine core files.\n"
324    "\n"
325    "    The more information you can provide, the better.  But please report\n"
326    "    report this bug, regardless!\n"
327    "\n");
328           fprintf (real_stderr,
329                    "#######################################"
330                    "#######################################\n\n");
331
332           saver_exit (si, -1, 0);
333         }
334     }
335   else
336     fprintf (real_stderr, " (nonfatal.)\n");
337   return 0;
338 }
339
340
341 /* This error handler is used only while the X connection is being set up;
342    after we've got a connection, we don't use this handler again.  The only
343    reason for having this is so that we can present a more idiot-proof error
344    message than "cannot open display."
345  */
346 static void 
347 startup_ehandler (String name, String type, String class,
348                   String defalt,  /* one can't even spel properly
349                                      in this joke of a language */
350                   String *av, Cardinal *ac)
351 {
352   char fmt[512];
353   String p[10];
354   saver_info *si = global_si_kludge;    /* I hate C so much... */
355   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
356   *fmt = 0;
357   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
358                             fmt, sizeof(fmt)-1, *db);
359
360   fprintf (stderr, "%s: ", blurb());
361
362   memset (p, 0, sizeof(p));
363   if (*ac > countof (p)) *ac = countof (p);
364   memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
365   fprintf (stderr, fmt,         /* Did I mention that I hate C? */
366            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
367   fprintf (stderr, "\n");
368
369   describe_uids (si, stderr);
370   fprintf (stderr, "\n"
371            "%s: Errors at startup are usually authorization problems.\n"
372            "              Did you read the manual?  Specifically, the parts\n"
373            "              that talk about XAUTH, XDM, and root logins?\n"
374            "\n"
375            "              http://www.jwz.org/xscreensaver/man.html\n"
376            "\n",
377            blurb());
378
379   fflush (stderr);
380   fflush (stdout);
381   exit (1);
382 }
383
384 \f
385 /* The zillions of initializations.
386  */
387
388 /* Set progname, version, etc.  This is done very early.
389  */
390 static void
391 set_version_string (saver_info *si, int *argc, char **argv)
392 {
393   progclass = "XScreenSaver";
394
395   /* progname is reset later, after we connect to X. */
396   progname = strrchr(argv[0], '/');
397   if (progname) progname++;
398   else progname = argv[0];
399
400   if (strlen(progname) > 100)   /* keep it short. */
401     progname[99] = 0;
402
403   /* The X resource database blows up if argv[0] has a "." in it. */
404   {
405     char *s = argv[0];
406     while ((s = strchr (s, '.')))
407       *s = '_';
408   }
409
410   si->version = (char *) malloc (5);
411   memcpy (si->version, screensaver_id + 17, 4);
412   si->version [4] = 0;
413 }
414
415
416 /* Initializations that potentially take place as a priveleged user:
417    If the xscreensaver executable is setuid root, then these initializations
418    are run as root, before discarding privileges.
419  */
420 static void
421 privileged_initialization (saver_info *si, int *argc, char **argv)
422 {
423 #ifndef NO_LOCKING
424   /* before hack_uid() for proper permissions */
425   lock_priv_init (*argc, argv, si->prefs.verbose_p);
426 #endif /* NO_LOCKING */
427
428   hack_uid (si);
429 }
430
431
432 /* Figure out what locking mechanisms are supported.
433  */
434 static void
435 lock_initialization (saver_info *si, int *argc, char **argv)
436 {
437 #ifdef NO_LOCKING
438   si->locking_disabled_p = True;
439   si->nolock_reason = "not compiled with locking support";
440 #else /* !NO_LOCKING */
441   si->locking_disabled_p = False;
442
443   /* Finish initializing locking, now that we're out of privileged code. */
444   if (! lock_init (*argc, argv, si->prefs.verbose_p))
445     {
446       si->locking_disabled_p = True;
447       si->nolock_reason = "error getting password";
448     }
449 #endif /* NO_LOCKING */
450
451   hack_uid (si);
452 }
453
454
455 /* Open the connection to the X server, and intern our Atoms.
456  */
457 static Widget
458 connect_to_server (saver_info *si, int *argc, char **argv)
459 {
460   Widget toplevel_shell;
461
462   XSetErrorHandler (saver_ehandler);
463
464   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
465   toplevel_shell = XtAppInitialize (&si->app, progclass,
466                                     options, XtNumber (options),
467                                     argc, argv, defaults, 0, 0);
468   XtAppSetErrorMsgHandler (si->app, 0);
469
470   si->dpy = XtDisplay (toplevel_shell);
471   si->prefs.db = XtDatabase (si->dpy);
472   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
473
474   if(strlen(progname) > 100)    /* keep it short. */
475     progname [99] = 0;
476
477   db = si->prefs.db;    /* resources.c needs this */
478
479   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
480   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
481   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
482   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
483   XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
484   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
485                                          False);
486   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
487   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
488   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
489   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
490   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
491   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
492   XA_PREV = XInternAtom (si->dpy, "PREV", False);
493   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
494   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
495   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
496   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
497   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
498   XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
499   XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
500
501   return toplevel_shell;
502 }
503
504
505 /* Handle the command-line arguments that were not handled for us by Xt.
506    Issue an error message and exit if there are unknown options.
507  */
508 static void
509 process_command_line (saver_info *si, int *argc, char **argv)
510 {
511   int i;
512   for (i = 1; i < *argc; i++)
513     {
514       if (!strcmp (argv[i], "-debug"))
515         /* no resource for this one, out of paranoia. */
516         si->prefs.debug_p = True;
517
518       else if (!strcmp (argv[i], "-h") ||
519                !strcmp (argv[i], "-help") ||
520                !strcmp (argv[i], "--help"))
521         do_help (si);
522
523       else
524         {
525           const char *s = argv[i];
526           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
527                    blurb(), s);
528
529           if (s[0] == '-' && s[1] == '-') s++;
530           if (!strcmp (s, "-activate") ||
531               !strcmp (s, "-deactivate") ||
532               !strcmp (s, "-cycle") ||
533               !strcmp (s, "-next") ||
534               !strcmp (s, "-prev") ||
535               !strcmp (s, "-exit") ||
536               !strcmp (s, "-restart") ||
537               !strcmp (s, "-demo") ||
538               !strcmp (s, "-prefs") ||
539               !strcmp (s, "-preferences") ||
540               !strcmp (s, "-lock") ||
541               !strcmp (s, "-version") ||
542               !strcmp (s, "-time"))
543             {
544
545               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
546                 fprintf (stderr, "\n\
547     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
548               else
549                 fprintf (stderr, "\n\
550     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
551
552               fprintf (stderr, "\
553     The `xscreensaver' program is a daemon that runs in the background.\n\
554     You control a running xscreensaver process by sending it messages\n\
555     with `xscreensaver-demo' or `xscreensaver-command'.\n\
556 .   See the man pages for details, or check the web page:\n\
557     http://www.jwz.org/xscreensaver/\n\n");
558
559               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
560                  suggest that explicitly. */
561               if (!strcmp (s, "-lock"))
562                 fprintf (stderr, "\
563     Or perhaps you meant either the \"-lock-mode\" or the\n\
564     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
565             }
566
567           exit (1);
568         }
569     }
570 }
571
572
573 /* Print out the xscreensaver banner to the tty if applicable;
574    Issue any other warnings that are called for at this point.
575  */
576 static void
577 print_banner (saver_info *si)
578 {
579   saver_preferences *p = &si->prefs;
580
581   /* This resource gets set some time before the others, so that we know
582      whether to print the banner (and so that the banner gets printed before
583      any resource-database-related error messages.)
584    */
585   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
586
587   /* Ditto, for the locking_disabled_p message. */
588   p->lock_p = get_boolean_resource ("lock", "Boolean");
589
590   if (p->verbose_p)
591     fprintf (stderr,
592              "%s %s, copyright (c) 1991-1999 "
593              "by Jamie Zawinski <jwz@jwz.org>.\n",
594              progname, si->version);
595
596   if (p->debug_p)
597     fprintf (stderr, "\n"
598              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
599              "\n"
600              "\tNote that in debug mode, the xscreensaver window will only\n"
601              "\tcover the left half of the screen.  (The idea is that you\n"
602              "\tcan still see debugging output in a shell, if you position\n"
603              "\tit on the right side of the screen.)\n"
604              "\n"
605              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
606              "\tuntrusted environments.\n"
607              "\n",
608              blurb());
609
610   if (p->verbose_p)
611     {
612       if (!si->uid_message || !*si->uid_message)
613         describe_uids (si, stderr);
614       else
615         {
616           if (si->orig_uid && *si->orig_uid)
617             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
618                      blurb(), si->orig_uid);
619           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
620         }
621
622       fprintf (stderr, "%s: in process %lu.\n", blurb(),
623                (unsigned long) getpid());
624     }
625
626   /* If locking was not able to be initalized for some reason, explain why.
627      (This has to be done after we've read the lock_p resource.)
628    */
629   if (p->lock_p && si->locking_disabled_p)
630     {
631       p->lock_p = False;
632       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
633                si->nolock_reason);
634       if (strstr (si->nolock_reason, "passw"))
635         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
636                  "consult the manual.\n", blurb());
637       else if (strstr (si->nolock_reason, "running as "))
638         fprintf (stderr, 
639                  "%s: locking only works when xscreensaver is launched\n"
640                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
641                  "\t See the manual for details.\n",
642                  blurb());
643     }
644 }
645
646
647 /* Examine all of the display's screens, and populate the `saver_screen_info'
648    structures.
649  */
650 static void
651 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
652 {
653   Bool found_any_writable_cells = False;
654   int i;
655
656   si->nscreens = ScreenCount(si->dpy);
657   si->screens = (saver_screen_info *)
658     calloc(sizeof(saver_screen_info), si->nscreens);
659
660   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
661
662   for (i = 0; i < si->nscreens; i++)
663     {
664       saver_screen_info *ssi = &si->screens[i];
665       ssi->global = si;
666       ssi->screen = ScreenOfDisplay (si->dpy, i);
667
668       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
669       ssi->default_visual =
670         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
671
672       ssi->current_visual = ssi->default_visual;
673       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
674
675       if (ssi == si->default_screen)
676         /* Since this is the default screen, use the one already created. */
677         ssi->toplevel_shell = toplevel_shell;
678       else
679         /* Otherwise, each screen must have its own unmapped root widget. */
680         ssi->toplevel_shell =
681           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
682                               si->dpy,
683                               XtNscreen, ssi->screen,
684                               XtNvisual, ssi->current_visual,
685                               XtNdepth,  visual_depth (ssi->screen,
686                                                        ssi->current_visual),
687                               0);
688
689       if (! found_any_writable_cells)
690         {
691           /* Check to see whether fading is ever possible -- if any of the
692              screens on the display has a PseudoColor visual, then fading can
693              work (on at least some screens.)  If no screen has a PseudoColor
694              visual, then don't bother ever trying to fade, because it will
695              just cause a delay without causing any visible effect.
696           */
697           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
698               get_visual (ssi->screen, "PseudoColor", True, False) ||
699               get_visual (ssi->screen, "GrayScale", True, False))
700             found_any_writable_cells = True;
701         }
702     }
703
704   si->fading_possible_p = found_any_writable_cells;
705 }
706
707
708 /* If any server extensions have been requested, try and initialize them.
709    Issue warnings if requests can't be honored.
710  */
711 static void
712 initialize_server_extensions (saver_info *si)
713 {
714   saver_preferences *p = &si->prefs;
715
716   Bool server_has_xidle_extension_p = False;
717   Bool server_has_sgi_saver_extension_p = False;
718   Bool server_has_mit_saver_extension_p = False;
719   Bool system_has_proc_interrupts_p = False;
720   const char *piwhy = 0;
721
722   si->using_xidle_extension = p->use_xidle_extension;
723   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
724   si->using_mit_saver_extension = p->use_mit_saver_extension;
725   si->using_proc_interrupts = p->use_proc_interrupts;
726
727 #ifdef HAVE_XIDLE_EXTENSION
728   server_has_xidle_extension_p = query_xidle_extension (si);
729 #endif
730 #ifdef HAVE_SGI_SAVER_EXTENSION
731   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
732 #endif
733 #ifdef HAVE_MIT_SAVER_EXTENSION
734   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
735 #endif
736 #ifdef HAVE_PROC_INTERRUPTS
737   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
738 #endif
739
740   if (!server_has_xidle_extension_p)
741     si->using_xidle_extension = False;
742   else if (p->verbose_p)
743     {
744       if (si->using_xidle_extension)
745         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
746       else
747         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
748     }
749
750   if (!server_has_sgi_saver_extension_p)
751     si->using_sgi_saver_extension = False;
752   else if (p->verbose_p)
753     {
754       if (si->using_sgi_saver_extension)
755         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
756       else
757         fprintf (stderr,
758                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
759                  blurb());
760     }
761
762   if (!server_has_mit_saver_extension_p)
763     si->using_mit_saver_extension = False;
764   else if (p->verbose_p)
765     {
766       if (si->using_mit_saver_extension)
767         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
768                  blurb());
769       else
770         fprintf (stderr,
771                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
772                  blurb());
773     }
774
775   if (!system_has_proc_interrupts_p)
776     {
777       si->using_proc_interrupts = False;
778       if (p->verbose_p && piwhy)
779         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
780                  piwhy);
781     }
782   else if (p->verbose_p)
783     {
784       if (si->using_proc_interrupts)
785         fprintf (stderr,
786                  "%s: consulting /proc/interrupts for keyboard activity.\n",
787                  blurb());
788       else
789         fprintf (stderr,
790                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
791                  blurb());
792     }
793 }
794
795
796 /* For the case where we aren't using an server extensions, select user events
797    on all the existing windows, and launch timers to select events on
798    newly-created windows as well.
799
800    If a server extension is being used, this does nothing.
801  */
802 static void
803 select_events (saver_info *si)
804 {
805   saver_preferences *p = &si->prefs;
806   int i;
807
808   if (si->using_xidle_extension ||
809       si->using_mit_saver_extension ||
810       si->using_sgi_saver_extension)
811     return;
812
813   if (p->initial_delay)
814     {
815       if (p->verbose_p)
816         {
817           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
818                    (int) p->initial_delay/1000,
819                    (p->initial_delay == 1000 ? "" : "s"));
820           fflush (stderr);
821           fflush (stdout);
822         }
823       usleep (p->initial_delay);
824       if (p->verbose_p)
825         fprintf (stderr, " done.\n");
826     }
827
828   if (p->verbose_p)
829     {
830       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
831       fflush (stderr);
832       fflush (stdout);
833     }
834
835   /* Select events on the root windows of every screen.  This also selects
836      for window creation events, so that new subwindows will be noticed.
837    */
838   for (i = 0; i < si->nscreens; i++)
839     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
840                                False);
841
842   if (p->verbose_p)
843     fprintf (stderr, " done.\n");
844 }
845
846
847 void
848 maybe_reload_init_file (saver_info *si)
849 {
850   saver_preferences *p = &si->prefs;
851   if (init_file_changed_p (p))
852     {
853       if (p->verbose_p)
854         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
855                  blurb(), init_file_name());
856
857       load_init_file (p);
858
859       /* If a server extension is in use, and p->timeout has changed,
860          we need to inform the server of the new timeout. */
861       disable_builtin_screensaver (si, False);
862     }
863 }
864
865
866 /* Loop forever:
867
868        - wait until the user is idle;
869        - blank the screen;
870        - wait until the user is active;
871        - unblank the screen;
872        - repeat.
873
874  */
875 static void
876 main_loop (saver_info *si)
877 {
878   saver_preferences *p = &si->prefs;
879   Bool ok_to_unblank;
880
881   while (1)
882     {
883       Bool was_locked = False;
884       sleep_until_idle (si, True);
885
886       if (p->verbose_p)
887         {
888           if (si->demoing_p)
889             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
890                      si->selection_mode, timestring());
891           else
892             if (p->verbose_p)
893               fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
894                        timestring());
895         }
896
897       maybe_reload_init_file (si);
898
899       if (! blank_screen (si))
900         {
901           /* We were unable to grab either the keyboard or mouse.
902              This means we did not (and must not) blank the screen.
903              If we were to blank the screen while some other program
904              is holding both the mouse and keyboard grabbed, then
905              we would never be able to un-blank it!  We would never
906              see any events, and the display would be wedged.
907
908              So, just go around the loop again and wait for the
909              next bout of idleness.
910           */
911
912           fprintf (stderr,
913                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
914                    blurb());
915           continue;
916         }
917
918       kill_screenhack (si);
919
920       if (!si->throttled_p)
921         spawn_screenhack (si, True);
922       else if (p->verbose_p)
923         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
924
925       /* Don't start the cycle timer in demo mode. */
926       if (!si->demoing_p && p->cycle)
927         si->cycle_id = XtAppAddTimeOut (si->app,
928                                         (si->selection_mode
929                                          /* see comment in cycle_timer() */
930                                          ? 1000 * 60 * 60
931                                          : p->cycle),
932                                         cycle_timer,
933                                         (XtPointer) si);
934
935
936 #ifndef NO_LOCKING
937       {
938         Time lock_timeout = p->lock_timeout;
939
940         if (si->emergency_lock_p && p->lock_p && lock_timeout)
941           {
942             int secs = p->lock_timeout / 1000;
943             if (p->verbose_p)
944               fprintf (stderr,
945                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
946                        blurb(),
947                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
948             lock_timeout = 0;
949           }
950
951         si->emergency_lock_p = False;
952
953         if (!si->demoing_p &&           /* if not going into demo mode */
954             p->lock_p &&                /* and locking is enabled */
955             !si->locking_disabled_p &&  /* and locking is possible */
956             lock_timeout == 0)          /* and locking is not timer-deferred */
957           si->locked_p = True;          /* then lock right now. */
958
959         /* locked_p might be true already because of the above, or because of
960            the LOCK ClientMessage.  But if not, and if we're supposed to lock
961            after some time, set up a timer to do so.
962         */
963         if (p->lock_p &&
964             !si->locked_p &&
965             lock_timeout > 0)
966           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
967                                          activate_lock_timer,
968                                          (XtPointer) si);
969       }
970 #endif /* !NO_LOCKING */
971
972
973       ok_to_unblank = True;
974       do {
975
976         sleep_until_idle (si, False);           /* until not idle */
977         maybe_reload_init_file (si);
978
979 #ifndef NO_LOCKING
980         if (si->locked_p)
981           {
982             saver_screen_info *ssi = si->default_screen;
983             if (si->locking_disabled_p) abort ();
984
985             was_locked = True;
986             si->dbox_up_p = True;
987             suspend_screenhack (si, True);
988             XUndefineCursor (si->dpy, ssi->screensaver_window);
989
990             ok_to_unblank = unlock_p (si);
991
992             si->dbox_up_p = False;
993             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
994             suspend_screenhack (si, False);     /* resume */
995           }
996 #endif /* !NO_LOCKING */
997
998         } while (!ok_to_unblank);
999
1000
1001       if (p->verbose_p)
1002         fprintf (stderr, "%s: unblanking screen at %s.\n",
1003                  blurb(), timestring ());
1004
1005       /* Kill before unblanking, to stop drawing as soon as possible. */
1006       kill_screenhack (si);
1007       unblank_screen (si);
1008
1009       si->locked_p = False;
1010       si->emergency_lock_p = False;
1011       si->demoing_p = 0;
1012       si->selection_mode = 0;
1013
1014       /* If we're throttled, and the user has explicitly unlocked the screen,
1015          then unthrottle.  If we weren't locked, then don't unthrottle
1016          automatically, because someone might have just bumped the desk... */
1017       if (was_locked)
1018         {
1019           if (si->throttled_p && p->verbose_p)
1020             fprintf (stderr, "%s: unthrottled.\n", blurb());
1021           si->throttled_p = False;
1022         }
1023
1024       if (si->cycle_id)
1025         {
1026           XtRemoveTimeOut (si->cycle_id);
1027           si->cycle_id = 0;
1028         }
1029
1030       if (si->lock_id)
1031         {
1032           XtRemoveTimeOut (si->lock_id);
1033           si->lock_id = 0;
1034         }
1035
1036       if (p->verbose_p)
1037         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1038     }
1039 }
1040
1041 static void analyze_display (saver_info *si);
1042
1043 int
1044 main (int argc, char **argv)
1045 {
1046   Widget shell;
1047   saver_info the_si;
1048   saver_info *si = &the_si;
1049   saver_preferences *p = &si->prefs;
1050   int i;
1051
1052   memset(si, 0, sizeof(*si));
1053   global_si_kludge = si;        /* I hate C so much... */
1054
1055   srandom ((int) time ((time_t *) 0));
1056
1057   save_argv (argc, argv);
1058   set_version_string (si, &argc, argv);
1059   privileged_initialization (si, &argc, argv);
1060   hack_environment (si);
1061
1062   shell = connect_to_server (si, &argc, argv);
1063   process_command_line (si, &argc, argv);
1064   print_banner (si);
1065
1066   load_init_file (p);  /* must be before initialize_per_screen_info() */
1067   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1068
1069   /* We can only issue this warnings now. */
1070   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1071     fprintf (stderr,
1072              "%s: there are no PseudoColor or GrayScale visuals.\n"
1073              "%s: ignoring the request for fading/unfading.\n",
1074              blurb(), blurb());
1075
1076   for (i = 0; i < si->nscreens; i++)
1077     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1078       exit (1);
1079
1080   lock_initialization (si, &argc, argv);
1081
1082   if (p->xsync_p) XSynchronize (si->dpy, True);
1083   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1084
1085   if (p->verbose_p) analyze_display (si);
1086   initialize_server_extensions (si);
1087   initialize_screensaver_window (si);
1088   select_events (si);
1089   init_sigchld ();
1090   disable_builtin_screensaver (si, True);
1091   initialize_stderr (si);
1092
1093   make_splash_dialog (si);
1094
1095   main_loop (si);               /* doesn't return */
1096   return 0;
1097 }
1098
1099 \f
1100 /* Processing ClientMessage events.
1101  */
1102
1103 static void
1104 clientmessage_response (saver_info *si, Window w, Bool error,
1105                         const char *stderr_msg,
1106                         const char *protocol_msg)
1107 {
1108   char *proto;
1109   int L;
1110   saver_preferences *p = &si->prefs;
1111   if (error || p->verbose_p)
1112     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1113
1114   L = strlen(protocol_msg);
1115   proto = (char *) malloc (L + 2);
1116   proto[0] = (error ? '-' : '+');
1117   strcpy (proto+1, protocol_msg);
1118   L++;
1119
1120   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1121                    PropModeReplace, (unsigned char *) proto, L);
1122   XSync (si->dpy, False);
1123   free (proto);
1124 }
1125
1126 Bool
1127 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1128 {
1129   saver_preferences *p = &si->prefs;
1130   Atom type = 0;
1131   Window window = event->xclient.window;
1132
1133   /* Preferences might affect our handling of client messages. */
1134   maybe_reload_init_file (si);
1135
1136   if (event->xclient.message_type != XA_SCREENSAVER)
1137     {
1138       char *str;
1139       str = XGetAtomName (si->dpy, event->xclient.message_type);
1140       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1141                blurb(), (str ? str : "(null)"));
1142       if (str) XFree (str);
1143       return False;
1144     }
1145   if (event->xclient.format != 32)
1146     {
1147       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1148                blurb(), event->xclient.format);
1149       return False;
1150     }
1151
1152   type = event->xclient.data.l[0];
1153   if (type == XA_ACTIVATE)
1154     {
1155       if (until_idle_p)
1156         {
1157           clientmessage_response(si, window, False,
1158                                  "ACTIVATE ClientMessage received.",
1159                                  "activating.");
1160           si->selection_mode = 0;
1161           si->demoing_p = False;
1162
1163           if (si->throttled_p && p->verbose_p)
1164             fprintf (stderr, "%s: unthrottled.\n", blurb());
1165           si->throttled_p = False;
1166
1167           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1168             {
1169               XForceScreenSaver (si->dpy, ScreenSaverActive);
1170               return False;
1171             }
1172           else
1173             {
1174               return True;
1175             }
1176         }
1177       clientmessage_response(si, window, True,
1178                        "ClientMessage ACTIVATE received while already active.",
1179                              "already active.");
1180     }
1181   else if (type == XA_DEACTIVATE)
1182     {
1183       if (! until_idle_p)
1184         {
1185           if (si->throttled_p && p->verbose_p)
1186             fprintf (stderr, "%s: unthrottled.\n", blurb());
1187           si->throttled_p = False;
1188
1189           clientmessage_response(si, window, False,
1190                                  "DEACTIVATE ClientMessage received.",
1191                                  "deactivating.");
1192           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1193             {
1194               XForceScreenSaver (si->dpy, ScreenSaverReset);
1195               return False;
1196             }
1197           else
1198             {
1199               return True;
1200             }
1201         }
1202       clientmessage_response(si, window, True,
1203                            "ClientMessage DEACTIVATE received while inactive.",
1204                              "not active.");
1205     }
1206   else if (type == XA_CYCLE)
1207     {
1208       if (! until_idle_p)
1209         {
1210           clientmessage_response(si, window, False,
1211                                  "CYCLE ClientMessage received.",
1212                                  "cycling.");
1213           si->selection_mode = 0;       /* 0 means randomize when its time. */
1214           si->demoing_p = False;
1215
1216           if (si->throttled_p && p->verbose_p)
1217             fprintf (stderr, "%s: unthrottled.\n", blurb());
1218           si->throttled_p = False;
1219
1220           if (si->cycle_id)
1221             XtRemoveTimeOut (si->cycle_id);
1222           si->cycle_id = 0;
1223           cycle_timer ((XtPointer) si, 0);
1224           return False;
1225         }
1226       clientmessage_response(si, window, True,
1227                              "ClientMessage CYCLE received while inactive.",
1228                              "not active.");
1229     }
1230   else if (type == XA_NEXT || type == XA_PREV)
1231     {
1232       clientmessage_response(si, window, False,
1233                              (type == XA_NEXT
1234                               ? "NEXT ClientMessage received."
1235                               : "PREV ClientMessage received."),
1236                              "cycling.");
1237       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1238       si->demoing_p = False;
1239
1240       if (si->throttled_p && p->verbose_p)
1241         fprintf (stderr, "%s: unthrottled.\n", blurb());
1242       si->throttled_p = False;
1243
1244       if (! until_idle_p)
1245         {
1246           if (si->cycle_id)
1247             XtRemoveTimeOut (si->cycle_id);
1248           si->cycle_id = 0;
1249           cycle_timer ((XtPointer) si, 0);
1250         }
1251       else
1252         return True;
1253     }
1254   else if (type == XA_SELECT)
1255     {
1256       char buf [255];
1257       char buf2 [255];
1258       long which = event->xclient.data.l[1];
1259
1260       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1261       sprintf (buf2, "activating (%ld).", which);
1262       clientmessage_response (si, window, False, buf, buf2);
1263
1264       if (which < 0) which = 0;         /* 0 == "random" */
1265       si->selection_mode = which;
1266       si->demoing_p = False;
1267
1268       if (si->throttled_p && p->verbose_p)
1269         fprintf (stderr, "%s: unthrottled.\n", blurb());
1270       si->throttled_p = False;
1271
1272       if (! until_idle_p)
1273         {
1274           if (si->cycle_id)
1275             XtRemoveTimeOut (si->cycle_id);
1276           si->cycle_id = 0;
1277           cycle_timer ((XtPointer) si, 0);
1278         }
1279       else
1280         return True;
1281     }
1282   else if (type == XA_EXIT)
1283     {
1284       /* Ignore EXIT message if the screen is locked. */
1285       if (until_idle_p || !si->locked_p)
1286         {
1287           clientmessage_response (si, window, False,
1288                                   "EXIT ClientMessage received.",
1289                                   "exiting.");
1290           if (! until_idle_p)
1291             {
1292               unblank_screen (si);
1293               kill_screenhack (si);
1294               XSync (si->dpy, False);
1295             }
1296           saver_exit (si, 0, 0);
1297         }
1298       else
1299         clientmessage_response (si, window, True,
1300                                 "EXIT ClientMessage received while locked.",
1301                                 "screen is locked.");
1302     }
1303   else if (type == XA_RESTART)
1304     {
1305       /* The RESTART message works whether the screensaver is active or not,
1306          unless the screen is locked, in which case it doesn't work.
1307        */
1308       if (until_idle_p || !si->locked_p)
1309         {
1310           clientmessage_response (si, window, False,
1311                                   "RESTART ClientMessage received.",
1312                                   "restarting.");
1313           if (! until_idle_p)
1314             {
1315               unblank_screen (si);
1316               kill_screenhack (si);
1317               XSync (si->dpy, False);
1318             }
1319
1320           fflush (stdout);
1321           fflush (stderr);
1322           if (real_stdout) fflush (real_stdout);
1323           if (real_stderr) fflush (real_stderr);
1324           /* make sure error message shows up before exit. */
1325           if (real_stderr && stderr != real_stderr)
1326             dup2 (fileno(real_stderr), fileno(stderr));
1327
1328           restart_process (si);
1329           exit (1);     /* shouldn't get here; but if restarting didn't work,
1330                            make this command be the same as EXIT. */
1331         }
1332       else
1333         clientmessage_response (si, window, True,
1334                                 "RESTART ClientMessage received while locked.",
1335                                 "screen is locked.");
1336     }
1337   else if (type == XA_DEMO)
1338     {
1339       long arg = event->xclient.data.l[1];
1340       Bool demo_one_hack_p = (arg == 300);
1341
1342       if (demo_one_hack_p)
1343         {
1344           if (until_idle_p)
1345             {
1346               long which = event->xclient.data.l[2];
1347               char buf [255];
1348               char buf2 [255];
1349               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1350               sprintf (buf2, "demoing (%ld).", which);
1351               clientmessage_response (si, window, False, buf, buf2);
1352
1353               if (which < 0) which = 0;         /* 0 == "random" */
1354               si->selection_mode = which;
1355               si->demoing_p = True;
1356
1357               if (si->throttled_p && p->verbose_p)
1358                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1359               si->throttled_p = False;
1360
1361               return True;
1362             }
1363
1364           clientmessage_response (si, window, True,
1365                                   "DEMO ClientMessage received while active.",
1366                                   "already active.");
1367         }
1368       else
1369         {
1370           clientmessage_response (si, window, True,
1371                                   "obsolete form of DEMO ClientMessage.",
1372                                   "obsolete form of DEMO ClientMessage.");
1373         }
1374     }
1375   else if (type == XA_PREFS)
1376     {
1377       clientmessage_response (si, window, True,
1378                               "the PREFS client-message is obsolete.",
1379                               "the PREFS client-message is obsolete.");
1380     }
1381   else if (type == XA_LOCK)
1382     {
1383 #ifdef NO_LOCKING
1384       clientmessage_response (si, window, True,
1385                               "not compiled with support for locking.",
1386                               "locking not enabled.");
1387 #else /* !NO_LOCKING */
1388       if (si->locking_disabled_p)
1389         clientmessage_response (si, window, True,
1390                       "LOCK ClientMessage received, but locking is disabled.",
1391                               "locking not enabled.");
1392       else if (si->locked_p)
1393         clientmessage_response (si, window, True,
1394                            "LOCK ClientMessage received while already locked.",
1395                                 "already locked.");
1396       else
1397         {
1398           char buf [255];
1399           char *response = (until_idle_p
1400                             ? "activating and locking."
1401                             : "locking.");
1402           si->locked_p = True;
1403           si->selection_mode = 0;
1404           si->demoing_p = False;
1405           sprintf (buf, "LOCK ClientMessage received; %s", response);
1406           clientmessage_response (si, window, False, buf, response);
1407
1408           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1409             {
1410               XtRemoveTimeOut (si->lock_id);
1411               si->lock_id = 0;
1412             }
1413
1414           if (until_idle_p)
1415             {
1416               if (si->using_mit_saver_extension ||
1417                   si->using_sgi_saver_extension)
1418                 {
1419                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1420                   return False;
1421                 }
1422               else
1423                 {
1424                   return True;
1425                 }
1426             }
1427         }
1428 #endif /* !NO_LOCKING */
1429     }
1430   else if (type == XA_THROTTLE)
1431     {
1432       if (si->throttled_p)
1433         clientmessage_response (si, window, True,
1434                                 "THROTTLE ClientMessage received, but "
1435                                 "already throttled.",
1436                                 "already throttled.");
1437       else
1438         {
1439           char buf [255];
1440           char *response = "throttled.";
1441           si->throttled_p = True;
1442           si->selection_mode = 0;
1443           si->demoing_p = False;
1444           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
1445           clientmessage_response (si, window, False, buf, response);
1446
1447           if (! until_idle_p)
1448             {
1449               if (si->cycle_id)
1450                 XtRemoveTimeOut (si->cycle_id);
1451               si->cycle_id = 0;
1452               cycle_timer ((XtPointer) si, 0);
1453             }
1454         }
1455     }
1456   else if (type == XA_UNTHROTTLE)
1457     {
1458       if (! si->throttled_p)
1459         clientmessage_response (si, window, True,
1460                                 "UNTHROTTLE ClientMessage received, but "
1461                                 "not throttled.",
1462                                 "not throttled.");
1463       else
1464         {
1465           char buf [255];
1466           char *response = "unthrottled.";
1467           si->throttled_p = False;
1468           si->selection_mode = 0;
1469           si->demoing_p = False;
1470           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
1471           clientmessage_response (si, window, False, buf, response);
1472
1473           if (! until_idle_p)
1474             {
1475               if (si->cycle_id)
1476                 XtRemoveTimeOut (si->cycle_id);
1477               si->cycle_id = 0;
1478               cycle_timer ((XtPointer) si, 0);
1479             }
1480         }
1481     }
1482   else
1483     {
1484       char buf [1024];
1485       char *str;
1486       str = (type ? XGetAtomName(si->dpy, type) : 0);
1487
1488       if (str)
1489         {
1490           if (strlen (str) > 80)
1491             strcpy (str+70, "...");
1492           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1493                    str);
1494           free (str);
1495         }
1496       else
1497         {
1498           sprintf (buf,
1499                    "unrecognised screensaver ClientMessage 0x%x received.",
1500                    (unsigned int) event->xclient.data.l[0]);
1501         }
1502
1503       clientmessage_response (si, window, True, buf, buf);
1504     }
1505   return False;
1506 }
1507
1508 \f
1509 /* Some random diagnostics printed in -verbose mode.
1510  */
1511
1512 static void
1513 analyze_display (saver_info *si)
1514 {
1515   int i, j;
1516   static const char *exts[][2] = {
1517     { "SCREEN_SAVER",             "SGI Screen-Saver" },
1518     { "SCREEN-SAVER",             "SGI Screen-Saver" },
1519     { "MIT-SCREEN-SAVER",         "MIT Screen-Saver" },
1520     { "XIDLE",                    "XIdle" },
1521     { "SGI-VIDEO-CONTROL",        "SGI Video-Control" },
1522     { "READDISPLAY",              "SGI Read-Display" },
1523     { "MIT-SHM",                  "Shared Memory" },
1524     { "DOUBLE-BUFFER",            "Double-Buffering" },
1525     { "DPMS",                     "Power Management" },
1526     { "GLX",                      "GLX" },
1527     { "XFree86-VidModeExtension", "XF86 Video-Mode" }
1528   };
1529
1530   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1531            DisplayString(si->dpy));
1532   fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1533            ServerVendor(si->dpy), VendorRelease(si->dpy));
1534
1535   fprintf (stderr, "%s: useful extensions:\n", blurb());
1536   for (i = 0; i < countof(exts); i++)
1537     {
1538       int op = 0, event = 0, error = 0;
1539       if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error))
1540         fprintf (stderr, "%s:   %s\n", blurb(), exts[i][1]);
1541     }
1542
1543   for (i = 0; i < si->nscreens; i++)
1544     {
1545       unsigned long colormapped_depths = 0;
1546       unsigned long non_mapped_depths = 0;
1547       XVisualInfo vi_in, *vi_out;
1548       int out_count;
1549       vi_in.screen = i;
1550       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1551       if (!vi_out) continue;
1552       for (j = 0; j < out_count; j++)
1553         if (vi_out[j].class == PseudoColor)
1554           colormapped_depths |= (1 << vi_out[j].depth);
1555         else
1556           non_mapped_depths  |= (1 << vi_out[j].depth);
1557       XFree ((char *) vi_out);
1558
1559       if (colormapped_depths)
1560         {
1561           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1562           for (j = 0; j < 32; j++)
1563             if (colormapped_depths & (1 << j))
1564               fprintf (stderr, " %d", j);
1565           fprintf (stderr, "\n");
1566         }
1567       if (non_mapped_depths)
1568         {
1569           fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1570           for (j = 0; j < 32; j++)
1571             if (non_mapped_depths & (1 << j))
1572               fprintf (stderr, " %d", j);
1573           fprintf (stderr, "\n");
1574         }
1575     }
1576 }
1577
1578 Bool
1579 display_is_on_console_p (saver_info *si)
1580 {
1581   Bool not_on_console = True;
1582   char *dpystr = DisplayString (si->dpy);
1583   char *tail = (char *) strchr (dpystr, ':');
1584   if (! tail || strncmp (tail, ":0", 2))
1585     not_on_console = True;
1586   else
1587     {
1588       char dpyname[255], localname[255];
1589       strncpy (dpyname, dpystr, tail-dpystr);
1590       dpyname [tail-dpystr] = 0;
1591       if (!*dpyname ||
1592           !strcmp(dpyname, "unix") ||
1593           !strcmp(dpyname, "localhost"))
1594         not_on_console = False;
1595       else if (gethostname (localname, sizeof (localname)))
1596         not_on_console = True;  /* can't find hostname? */
1597       else
1598         {
1599           /* We have to call gethostbyname() on the result of gethostname()
1600              because the two aren't guarenteed to be the same name for the
1601              same host: on some losing systems, one is a FQDN and the other
1602              is not.  Here in the wide wonderful world of Unix it's rocket
1603              science to obtain the local hostname in a portable fashion.
1604              
1605              And don't forget, gethostbyname() reuses the structure it
1606              returns, so we have to copy the fucker before calling it again.
1607              Thank you master, may I have another.
1608            */
1609           struct hostent *h = gethostbyname (dpyname);
1610           if (!h)
1611             not_on_console = True;
1612           else
1613             {
1614               char hn [255];
1615               struct hostent *l;
1616               strcpy (hn, h->h_name);
1617               l = gethostbyname (localname);
1618               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1619             }
1620         }
1621     }
1622   return !not_on_console;
1623 }