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