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