http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.32.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-2001 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-2001 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\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
374   if (si->orig_uid && !strncmp (si->orig_uid, "root/", 5))
375     {
376       fprintf (stderr, "\n"
377           "%s: This is probably because you're logging in as root.  You\n"
378 "              shouldn't log in as root: you should log in as a normal user,\n"
379 "              and then `su' as needed.  If you insist on logging in as\n"
380 "              root, you will have to turn off X's security features before\n"
381 "              xscreensaver will work.\n"
382                "\n"
383 "              Please read the manual and FAQ for more information:\n",
384                blurb());
385     }
386   else
387     {
388       fprintf (stderr, "\n"
389           "%s: Errors at startup are usually authorization problems.\n"
390 "              But you're not logging in as root (good!) so something\n"
391 "              else must be wrong.  Did you read the manual and the FAQ?\n",
392            blurb());
393     }
394
395   fprintf (stderr, "\n"
396           "              http://www.jwz.org/xscreensaver/faq.html\n"
397           "              http://www.jwz.org/xscreensaver/man.html\n"
398           "\n");
399
400   fflush (stderr);
401   fflush (stdout);
402   exit (1);
403 }
404
405 \f
406 /* The zillions of initializations.
407  */
408
409 /* Set progname, version, etc.  This is done very early.
410  */
411 static void
412 set_version_string (saver_info *si, int *argc, char **argv)
413 {
414   progclass = "XScreenSaver";
415
416   /* progname is reset later, after we connect to X. */
417   progname = strrchr(argv[0], '/');
418   if (progname) progname++;
419   else progname = argv[0];
420
421   if (strlen(progname) > 100)   /* keep it short. */
422     progname[99] = 0;
423
424   /* The X resource database blows up if argv[0] has a "." in it. */
425   {
426     char *s = argv[0];
427     while ((s = strchr (s, '.')))
428       *s = '_';
429   }
430
431   si->version = (char *) malloc (5);
432   memcpy (si->version, screensaver_id + 17, 4);
433   si->version [4] = 0;
434 }
435
436
437 /* Initializations that potentially take place as a priveleged user:
438    If the xscreensaver executable is setuid root, then these initializations
439    are run as root, before discarding privileges.
440  */
441 static void
442 privileged_initialization (saver_info *si, int *argc, char **argv)
443 {
444 #ifndef NO_LOCKING
445   /* before hack_uid() for proper permissions */
446   lock_priv_init (*argc, argv, si->prefs.verbose_p);
447 #endif /* NO_LOCKING */
448
449   hack_uid (si);
450 }
451
452
453 /* Figure out what locking mechanisms are supported.
454  */
455 static void
456 lock_initialization (saver_info *si, int *argc, char **argv)
457 {
458 #ifdef NO_LOCKING
459   si->locking_disabled_p = True;
460   si->nolock_reason = "not compiled with locking support";
461 #else /* !NO_LOCKING */
462
463   /* Finish initializing locking, now that we're out of privileged code. */
464   if (! lock_init (*argc, argv, si->prefs.verbose_p))
465     {
466       si->locking_disabled_p = True;
467       si->nolock_reason = "error getting password";
468     }
469 #endif /* NO_LOCKING */
470
471   hack_uid (si);
472 }
473
474
475 /* Open the connection to the X server, and intern our Atoms.
476  */
477 static Widget
478 connect_to_server (saver_info *si, int *argc, char **argv)
479 {
480   Widget toplevel_shell;
481
482 #ifdef HAVE_PUTENV
483   char *d = getenv ("DISPLAY");
484   if (!d || !*d)
485     {
486       char ndpy[] = "DISPLAY=:0.0";
487       /* if (si->prefs.verbose_p) */      /* sigh, too early to test this... */
488         fprintf (stderr,
489                  "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
490                  blurb(), ndpy+8);
491       if (putenv (ndpy))
492         abort ();
493     }
494 #endif /* HAVE_PUTENV */
495
496   XSetErrorHandler (saver_ehandler);
497
498   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
499   toplevel_shell = XtAppInitialize (&si->app, progclass,
500                                     options, XtNumber (options),
501                                     argc, argv, defaults, 0, 0);
502   XtAppSetErrorMsgHandler (si->app, 0);
503
504   si->dpy = XtDisplay (toplevel_shell);
505   si->prefs.db = XtDatabase (si->dpy);
506   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
507
508   if(strlen(progname) > 100)    /* keep it short. */
509     progname [99] = 0;
510
511   db = si->prefs.db;    /* resources.c needs this */
512
513   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
514   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
515   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
516   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
517   XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False);
518   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
519                                          False);
520   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
521   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
522   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
523   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
524   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
525   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
526   XA_PREV = XInternAtom (si->dpy, "PREV", False);
527   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
528   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
529   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
530   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
531   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
532   XA_BLANK = XInternAtom (si->dpy, "BLANK", False);
533   XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
534   XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
535
536   return toplevel_shell;
537 }
538
539
540 /* Handle the command-line arguments that were not handled for us by Xt.
541    Issue an error message and exit if there are unknown options.
542  */
543 static void
544 process_command_line (saver_info *si, int *argc, char **argv)
545 {
546   int i;
547   for (i = 1; i < *argc; i++)
548     {
549       if (!strcmp (argv[i], "-debug"))
550         /* no resource for this one, out of paranoia. */
551         si->prefs.debug_p = True;
552
553       else if (!strcmp (argv[i], "-h") ||
554                !strcmp (argv[i], "-help") ||
555                !strcmp (argv[i], "--help"))
556         do_help (si);
557
558       else
559         {
560           const char *s = argv[i];
561           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
562                    blurb(), s);
563
564           if (s[0] == '-' && s[1] == '-') s++;
565           if (!strcmp (s, "-activate") ||
566               !strcmp (s, "-deactivate") ||
567               !strcmp (s, "-cycle") ||
568               !strcmp (s, "-next") ||
569               !strcmp (s, "-prev") ||
570               !strcmp (s, "-exit") ||
571               !strcmp (s, "-restart") ||
572               !strcmp (s, "-demo") ||
573               !strcmp (s, "-prefs") ||
574               !strcmp (s, "-preferences") ||
575               !strcmp (s, "-lock") ||
576               !strcmp (s, "-version") ||
577               !strcmp (s, "-time"))
578             {
579
580               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
581                 fprintf (stderr, "\n\
582     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
583               else
584                 fprintf (stderr, "\n\
585     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
586
587               fprintf (stderr, "\
588     The `xscreensaver' program is a daemon that runs in the background.\n\
589     You control a running xscreensaver process by sending it messages\n\
590     with `xscreensaver-demo' or `xscreensaver-command'.\n\
591 .   See the man pages for details, or check the web page:\n\
592     http://www.jwz.org/xscreensaver/\n\n");
593
594               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
595                  suggest that explicitly. */
596               if (!strcmp (s, "-lock"))
597                 fprintf (stderr, "\
598     Or perhaps you meant either the \"-lock-mode\" or the\n\
599     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
600             }
601
602           exit (1);
603         }
604     }
605 }
606
607
608 /* Print out the xscreensaver banner to the tty if applicable;
609    Issue any other warnings that are called for at this point.
610  */
611 static void
612 print_banner (saver_info *si)
613 {
614   saver_preferences *p = &si->prefs;
615
616   /* This resource gets set some time before the others, so that we know
617      whether to print the banner (and so that the banner gets printed before
618      any resource-database-related error messages.)
619    */
620   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
621
622   /* Ditto, for the locking_disabled_p message. */
623   p->lock_p = get_boolean_resource ("lock", "Boolean");
624
625   if (p->verbose_p)
626     fprintf (stderr,
627              "%s %s, copyright (c) 1991-2001 "
628              "by Jamie Zawinski <jwz@jwz.org>.\n",
629              progname, si->version);
630
631   if (p->debug_p)
632     fprintf (stderr, "\n"
633              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
634              "\n"
635              "\tNote that in debug mode, the xscreensaver window will only\n"
636              "\tcover the left half of the screen.  (The idea is that you\n"
637              "\tcan still see debugging output in a shell, if you position\n"
638              "\tit on the right side of the screen.)\n"
639              "\n"
640              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
641              "\tuntrusted environments.\n"
642              "\n",
643              blurb());
644
645   if (p->verbose_p)
646     {
647       if (!si->uid_message || !*si->uid_message)
648         describe_uids (si, stderr);
649       else
650         {
651           if (si->orig_uid && *si->orig_uid)
652             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
653                      blurb(), si->orig_uid);
654           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
655         }
656
657       fprintf (stderr, "%s: in process %lu.\n", blurb(),
658                (unsigned long) getpid());
659     }
660
661   /* If locking was not able to be initalized for some reason, explain why.
662      (This has to be done after we've read the lock_p resource.)
663    */
664   if (p->lock_p && si->locking_disabled_p)
665     {
666       p->lock_p = False;
667       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
668                si->nolock_reason);
669       if (strstr (si->nolock_reason, "passw"))
670         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
671                  "consult the manual.\n", blurb());
672       else if (strstr (si->nolock_reason, "running as "))
673         fprintf (stderr, 
674                  "%s: locking only works when xscreensaver is launched\n"
675                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
676                  "\t See the manual for details.\n",
677                  blurb());
678     }
679 }
680
681
682 /* Examine all of the display's screens, and populate the `saver_screen_info'
683    structures.  Make sure this is called after hack_environment() sets $PATH.
684  */
685 static void
686 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
687 {
688   Bool found_any_writable_cells = False;
689   int i;
690
691   si->nscreens = ScreenCount(si->dpy);
692   si->screens = (saver_screen_info *)
693     calloc(sizeof(saver_screen_info), si->nscreens);
694
695   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
696
697   for (i = 0; i < si->nscreens; i++)
698     {
699       saver_screen_info *ssi = &si->screens[i];
700       ssi->global = si;
701       ssi->screen = ScreenOfDisplay (si->dpy, i);
702
703       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
704       ssi->default_visual =
705         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
706
707       ssi->current_visual = ssi->default_visual;
708       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
709
710       /* Execute a subprocess to find the GL visual. */
711       ssi->best_gl_visual = get_best_gl_visual (ssi);
712
713       if (ssi == si->default_screen)
714         /* Since this is the default screen, use the one already created. */
715         ssi->toplevel_shell = toplevel_shell;
716       else
717         /* Otherwise, each screen must have its own unmapped root widget. */
718         ssi->toplevel_shell =
719           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
720                               si->dpy,
721                               XtNscreen, ssi->screen,
722                               XtNvisual, ssi->current_visual,
723                               XtNdepth,  visual_depth (ssi->screen,
724                                                        ssi->current_visual),
725                               0);
726
727       if (! found_any_writable_cells)
728         {
729           /* Check to see whether fading is ever possible -- if any of the
730              screens on the display has a PseudoColor visual, then fading can
731              work (on at least some screens.)  If no screen has a PseudoColor
732              visual, then don't bother ever trying to fade, because it will
733              just cause a delay without causing any visible effect.
734           */
735           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
736               get_visual (ssi->screen, "PseudoColor", True, False) ||
737               get_visual (ssi->screen, "GrayScale", True, False))
738             found_any_writable_cells = True;
739         }
740     }
741
742   si->fading_possible_p = found_any_writable_cells;
743
744 #ifdef HAVE_XF86VMODE_GAMMA
745   si->fading_possible_p = True;  /* if we can gamma fade, go for it */
746 #endif
747 }
748
749
750 /* If any server extensions have been requested, try and initialize them.
751    Issue warnings if requests can't be honored.
752  */
753 static void
754 initialize_server_extensions (saver_info *si)
755 {
756   saver_preferences *p = &si->prefs;
757
758   Bool server_has_xidle_extension_p = False;
759   Bool server_has_sgi_saver_extension_p = False;
760   Bool server_has_mit_saver_extension_p = False;
761   Bool system_has_proc_interrupts_p = False;
762   const char *piwhy = 0;
763
764   si->using_xidle_extension = p->use_xidle_extension;
765   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
766   si->using_mit_saver_extension = p->use_mit_saver_extension;
767   si->using_proc_interrupts = p->use_proc_interrupts;
768
769 #ifdef HAVE_XIDLE_EXTENSION
770   server_has_xidle_extension_p = query_xidle_extension (si);
771 #endif
772 #ifdef HAVE_SGI_SAVER_EXTENSION
773   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
774 #endif
775 #ifdef HAVE_MIT_SAVER_EXTENSION
776   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
777 #endif
778 #ifdef HAVE_PROC_INTERRUPTS
779   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
780 #endif
781
782   if (!server_has_xidle_extension_p)
783     si->using_xidle_extension = False;
784   else if (p->verbose_p)
785     {
786       if (si->using_xidle_extension)
787         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
788       else
789         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
790     }
791
792   if (!server_has_sgi_saver_extension_p)
793     si->using_sgi_saver_extension = False;
794   else if (p->verbose_p)
795     {
796       if (si->using_sgi_saver_extension)
797         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
798       else
799         fprintf (stderr,
800                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
801                  blurb());
802     }
803
804   if (!server_has_mit_saver_extension_p)
805     si->using_mit_saver_extension = False;
806   else if (p->verbose_p)
807     {
808       if (si->using_mit_saver_extension)
809         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
810                  blurb());
811       else
812         fprintf (stderr,
813                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
814                  blurb());
815     }
816
817   if (!system_has_proc_interrupts_p)
818     {
819       si->using_proc_interrupts = False;
820       if (p->verbose_p && piwhy)
821         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
822                  piwhy);
823     }
824   else if (p->verbose_p)
825     {
826       if (si->using_proc_interrupts)
827         fprintf (stderr,
828                  "%s: consulting /proc/interrupts for keyboard activity.\n",
829                  blurb());
830       else
831         fprintf (stderr,
832                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
833                  blurb());
834     }
835 }
836
837
838 /* For the case where we aren't using an server extensions, select user events
839    on all the existing windows, and launch timers to select events on
840    newly-created windows as well.
841
842    If a server extension is being used, this does nothing.
843  */
844 static void
845 select_events (saver_info *si)
846 {
847   saver_preferences *p = &si->prefs;
848   int i;
849
850   if (si->using_xidle_extension ||
851       si->using_mit_saver_extension ||
852       si->using_sgi_saver_extension)
853     return;
854
855   if (p->initial_delay)
856     {
857       if (p->verbose_p)
858         {
859           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
860                    (int) p->initial_delay/1000,
861                    (p->initial_delay == 1000 ? "" : "s"));
862           fflush (stderr);
863           fflush (stdout);
864         }
865       usleep (p->initial_delay);
866       if (p->verbose_p)
867         fprintf (stderr, " done.\n");
868     }
869
870   if (p->verbose_p)
871     {
872       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
873       fflush (stderr);
874       fflush (stdout);
875     }
876
877   /* Select events on the root windows of every screen.  This also selects
878      for window creation events, so that new subwindows will be noticed.
879    */
880   for (i = 0; i < si->nscreens; i++)
881     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
882                                False);
883
884   if (p->verbose_p)
885     fprintf (stderr, " done.\n");
886 }
887
888
889 void
890 maybe_reload_init_file (saver_info *si)
891 {
892   saver_preferences *p = &si->prefs;
893   if (init_file_changed_p (p))
894     {
895       if (p->verbose_p)
896         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
897                  blurb(), init_file_name());
898
899       load_init_file (p);
900
901       /* If a server extension is in use, and p->timeout has changed,
902          we need to inform the server of the new timeout. */
903       disable_builtin_screensaver (si, False);
904
905       /* If the DPMS settings in the init file have changed,
906          change the settings on the server to match. */
907       sync_server_dpms_settings (si->dpy, p->dpms_enabled_p,
908                                  p->dpms_standby / 1000,
909                                  p->dpms_suspend / 1000,
910                                  p->dpms_off / 1000,
911                                  False);
912     }
913 }
914
915
916 /* Loop forever:
917
918        - wait until the user is idle;
919        - blank the screen;
920        - wait until the user is active;
921        - unblank the screen;
922        - repeat.
923
924  */
925 static void
926 main_loop (saver_info *si)
927 {
928   saver_preferences *p = &si->prefs;
929   Bool ok_to_unblank;
930
931   while (1)
932     {
933       Bool was_locked = False;
934       sleep_until_idle (si, True);
935
936       if (p->verbose_p)
937         {
938           if (si->demoing_p)
939             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
940                      si->selection_mode, timestring());
941           else
942             if (p->verbose_p)
943               fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
944                        timestring());
945         }
946
947       maybe_reload_init_file (si);
948
949       if (! blank_screen (si))
950         {
951           /* We were unable to grab either the keyboard or mouse.
952              This means we did not (and must not) blank the screen.
953              If we were to blank the screen while some other program
954              is holding both the mouse and keyboard grabbed, then
955              we would never be able to un-blank it!  We would never
956              see any events, and the display would be wedged.
957
958              So, just go around the loop again and wait for the
959              next bout of idleness.
960           */
961
962           fprintf (stderr,
963                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
964                    blurb());
965           continue;
966         }
967
968       kill_screenhack (si);
969
970       if (!si->throttled_p)
971         spawn_screenhack (si, True);
972       else if (p->verbose_p)
973         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
974
975       /* Don't start the cycle timer in demo mode. */
976       if (!si->demoing_p && p->cycle)
977         si->cycle_id = XtAppAddTimeOut (si->app,
978                                         (si->selection_mode
979                                          /* see comment in cycle_timer() */
980                                          ? 1000 * 60 * 60
981                                          : p->cycle),
982                                         cycle_timer,
983                                         (XtPointer) si);
984
985
986 #ifndef NO_LOCKING
987       {
988         Time lock_timeout = p->lock_timeout;
989
990         if (si->emergency_lock_p && p->lock_p && lock_timeout)
991           {
992             int secs = p->lock_timeout / 1000;
993             if (p->verbose_p)
994               fprintf (stderr,
995                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
996                        blurb(),
997                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
998             lock_timeout = 0;
999           }
1000
1001         si->emergency_lock_p = False;
1002
1003         if (!si->demoing_p &&           /* if not going into demo mode */
1004             p->lock_p &&                /* and locking is enabled */
1005             !si->locking_disabled_p &&  /* and locking is possible */
1006             lock_timeout == 0)          /* and locking is not timer-deferred */
1007           set_locked_p (si, True);      /* then lock right now. */
1008
1009         /* locked_p might be true already because of the above, or because of
1010            the LOCK ClientMessage.  But if not, and if we're supposed to lock
1011            after some time, set up a timer to do so.
1012         */
1013         if (p->lock_p &&
1014             !si->locked_p &&
1015             lock_timeout > 0)
1016           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
1017                                          activate_lock_timer,
1018                                          (XtPointer) si);
1019       }
1020 #endif /* !NO_LOCKING */
1021
1022
1023       ok_to_unblank = True;
1024       do {
1025
1026         sleep_until_idle (si, False);           /* until not idle */
1027         maybe_reload_init_file (si);
1028
1029 #ifndef NO_LOCKING
1030         if (si->locked_p)
1031           {
1032             saver_screen_info *ssi = si->default_screen;
1033             if (si->locking_disabled_p) abort ();
1034
1035             was_locked = True;
1036             si->dbox_up_p = True;
1037             suspend_screenhack (si, True);
1038             XUndefineCursor (si->dpy, ssi->screensaver_window);
1039
1040             ok_to_unblank = unlock_p (si);
1041
1042             si->dbox_up_p = False;
1043             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
1044             suspend_screenhack (si, False);     /* resume */
1045
1046             if (!ok_to_unblank &&
1047                 !screenhack_running_p (si))
1048               {
1049                 /* If the lock dialog has been dismissed and we're not about to
1050                    unlock the screen, and there is currently no hack running,
1051                    then launch one.  (There might be no hack running if DPMS
1052                    had kicked in.  But DPMS is off now, so bring back the hack)
1053                  */
1054                 if (si->cycle_id)
1055                   XtRemoveTimeOut (si->cycle_id);
1056                 si->cycle_id = 0;
1057                 cycle_timer ((XtPointer) si, 0);
1058               }
1059           }
1060 #endif /* !NO_LOCKING */
1061
1062         } while (!ok_to_unblank);
1063
1064
1065       if (p->verbose_p)
1066         fprintf (stderr, "%s: unblanking screen at %s.\n",
1067                  blurb(), timestring ());
1068
1069       /* Kill before unblanking, to stop drawing as soon as possible. */
1070       kill_screenhack (si);
1071       unblank_screen (si);
1072
1073       set_locked_p (si, False);
1074       si->emergency_lock_p = False;
1075       si->demoing_p = 0;
1076       si->selection_mode = 0;
1077
1078       /* If we're throttled, and the user has explicitly unlocked the screen,
1079          then unthrottle.  If we weren't locked, then don't unthrottle
1080          automatically, because someone might have just bumped the desk... */
1081       if (was_locked)
1082         {
1083           if (si->throttled_p && p->verbose_p)
1084             fprintf (stderr, "%s: unthrottled.\n", blurb());
1085           si->throttled_p = False;
1086         }
1087
1088       if (si->cycle_id)
1089         {
1090           XtRemoveTimeOut (si->cycle_id);
1091           si->cycle_id = 0;
1092         }
1093
1094       if (si->lock_id)
1095         {
1096           XtRemoveTimeOut (si->lock_id);
1097           si->lock_id = 0;
1098         }
1099
1100       if (p->verbose_p)
1101         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1102     }
1103 }
1104
1105 static void analyze_display (saver_info *si);
1106
1107 int
1108 main (int argc, char **argv)
1109 {
1110   Widget shell;
1111   saver_info the_si;
1112   saver_info *si = &the_si;
1113   saver_preferences *p = &si->prefs;
1114   int i;
1115
1116   memset(si, 0, sizeof(*si));
1117   global_si_kludge = si;        /* I hate C so much... */
1118
1119 # undef ya_rand_init
1120   ya_rand_init (0);
1121
1122   save_argv (argc, argv);
1123   set_version_string (si, &argc, argv);
1124   privileged_initialization (si, &argc, argv);
1125   hack_environment (si);
1126
1127   shell = connect_to_server (si, &argc, argv);
1128   process_command_line (si, &argc, argv);
1129   print_banner (si);
1130
1131   load_init_file (p);  /* must be before initialize_per_screen_info() */
1132   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1133
1134   /* We can only issue this warnings now. */
1135   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1136     fprintf (stderr,
1137              "%s: there are no PseudoColor or GrayScale visuals.\n"
1138              "%s: ignoring the request for fading/unfading.\n",
1139              blurb(), blurb());
1140
1141   for (i = 0; i < si->nscreens; i++)
1142     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1143       exit (1);
1144
1145   lock_initialization (si, &argc, argv);
1146
1147   if (p->xsync_p) XSynchronize (si->dpy, True);
1148   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1149
1150   if (p->verbose_p) analyze_display (si);
1151   initialize_server_extensions (si);
1152
1153   si->blank_time = time ((time_t) 0); /* must be before ..._window */
1154   initialize_screensaver_window (si);
1155
1156   select_events (si);
1157   init_sigchld ();
1158
1159   disable_builtin_screensaver (si, True);
1160   sync_server_dpms_settings (si->dpy, p->dpms_enabled_p,
1161                              p->dpms_standby / 1000,
1162                              p->dpms_suspend / 1000,
1163                              p->dpms_off / 1000,
1164                              False);
1165
1166   initialize_stderr (si);
1167
1168   make_splash_dialog (si);
1169
1170   main_loop (si);               /* doesn't return */
1171   return 0;
1172 }
1173
1174 \f
1175 /* Processing ClientMessage events.
1176  */
1177
1178
1179 static int
1180 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
1181 {
1182   return 0;
1183 }
1184
1185 /* Sometimes some systems send us ClientMessage events with bogus atoms in
1186    them.  We only look up the atom names for printing warning messages,
1187    so don't bomb out when it happens...
1188  */
1189 static char *
1190 XGetAtomName_safe (Display *dpy, Atom atom)
1191 {
1192   char *result;
1193   XErrorHandler old_handler;
1194   if (!atom) return 0;
1195
1196   XSync (dpy, False);
1197   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1198   result = XGetAtomName (dpy, atom);
1199   XSync (dpy, False);
1200   XSetErrorHandler (old_handler);
1201   XSync (dpy, False);
1202
1203   if (result)
1204     return result;
1205   else
1206     {
1207       char buf[100];
1208       sprintf (buf, "<<undefined atom 0x%04X>>", (unsigned long) atom);
1209       return strdup (buf);
1210     }
1211 }
1212
1213
1214 static void
1215 clientmessage_response (saver_info *si, Window w, Bool error,
1216                         const char *stderr_msg,
1217                         const char *protocol_msg)
1218 {
1219   char *proto;
1220   int L;
1221   saver_preferences *p = &si->prefs;
1222   if (error || p->verbose_p)
1223     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1224
1225   L = strlen(protocol_msg);
1226   proto = (char *) malloc (L + 2);
1227   proto[0] = (error ? '-' : '+');
1228   strcpy (proto+1, protocol_msg);
1229   L++;
1230
1231   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1232                    PropModeReplace, (unsigned char *) proto, L);
1233   XSync (si->dpy, False);
1234   free (proto);
1235 }
1236
1237 Bool
1238 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1239 {
1240   saver_preferences *p = &si->prefs;
1241   Atom type = 0;
1242   Window window = event->xclient.window;
1243
1244   /* Preferences might affect our handling of client messages. */
1245   maybe_reload_init_file (si);
1246
1247   if (event->xclient.message_type != XA_SCREENSAVER)
1248     {
1249       char *str;
1250       str = XGetAtomName_safe (si->dpy, event->xclient.message_type);
1251       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1252                blurb(), (str ? str : "(null)"));
1253       if (str) XFree (str);
1254       return False;
1255     }
1256   if (event->xclient.format != 32)
1257     {
1258       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1259                blurb(), event->xclient.format);
1260       return False;
1261     }
1262
1263   type = event->xclient.data.l[0];
1264   if (type == XA_ACTIVATE)
1265     {
1266       if (until_idle_p)
1267         {
1268           clientmessage_response(si, window, False,
1269                                  "ACTIVATE ClientMessage received.",
1270                                  "activating.");
1271           si->selection_mode = 0;
1272           si->demoing_p = False;
1273
1274           if (si->throttled_p && p->verbose_p)
1275             fprintf (stderr, "%s: unthrottled.\n", blurb());
1276           si->throttled_p = False;
1277
1278           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1279             {
1280               XForceScreenSaver (si->dpy, ScreenSaverActive);
1281               return False;
1282             }
1283           else
1284             {
1285               return True;
1286             }
1287         }
1288       clientmessage_response(si, window, True,
1289                        "ClientMessage ACTIVATE received while already active.",
1290                              "already active.");
1291     }
1292   else if (type == XA_DEACTIVATE)
1293     {
1294       if (! until_idle_p)
1295         {
1296           if (si->throttled_p && p->verbose_p)
1297             fprintf (stderr, "%s: unthrottled.\n", blurb());
1298           si->throttled_p = False;
1299
1300           clientmessage_response(si, window, False,
1301                                  "DEACTIVATE ClientMessage received.",
1302                                  "deactivating.");
1303           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1304             {
1305               XForceScreenSaver (si->dpy, ScreenSaverReset);
1306               return False;
1307             }
1308           else
1309             {
1310               return True;
1311             }
1312         }
1313       clientmessage_response(si, window, True,
1314                            "ClientMessage DEACTIVATE received while inactive.",
1315                              "not active.");
1316     }
1317   else if (type == XA_CYCLE)
1318     {
1319       if (! until_idle_p)
1320         {
1321           clientmessage_response(si, window, False,
1322                                  "CYCLE ClientMessage received.",
1323                                  "cycling.");
1324           si->selection_mode = 0;       /* 0 means randomize when its time. */
1325           si->demoing_p = False;
1326
1327           if (si->throttled_p && p->verbose_p)
1328             fprintf (stderr, "%s: unthrottled.\n", blurb());
1329           si->throttled_p = False;
1330
1331           if (si->cycle_id)
1332             XtRemoveTimeOut (si->cycle_id);
1333           si->cycle_id = 0;
1334           cycle_timer ((XtPointer) si, 0);
1335           return False;
1336         }
1337       clientmessage_response(si, window, True,
1338                              "ClientMessage CYCLE received while inactive.",
1339                              "not active.");
1340     }
1341   else if (type == XA_NEXT || type == XA_PREV)
1342     {
1343       clientmessage_response(si, window, False,
1344                              (type == XA_NEXT
1345                               ? "NEXT ClientMessage received."
1346                               : "PREV ClientMessage received."),
1347                              "cycling.");
1348       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1349       si->demoing_p = False;
1350
1351       if (si->throttled_p && p->verbose_p)
1352         fprintf (stderr, "%s: unthrottled.\n", blurb());
1353       si->throttled_p = False;
1354
1355       if (! until_idle_p)
1356         {
1357           if (si->cycle_id)
1358             XtRemoveTimeOut (si->cycle_id);
1359           si->cycle_id = 0;
1360           cycle_timer ((XtPointer) si, 0);
1361         }
1362       else
1363         return True;
1364     }
1365   else if (type == XA_SELECT)
1366     {
1367       char buf [255];
1368       char buf2 [255];
1369       long which = event->xclient.data.l[1];
1370
1371       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1372       sprintf (buf2, "activating (%ld).", which);
1373       clientmessage_response (si, window, False, buf, buf2);
1374
1375       if (which < 0) which = 0;         /* 0 == "random" */
1376       si->selection_mode = which;
1377       si->demoing_p = False;
1378
1379       if (si->throttled_p && p->verbose_p)
1380         fprintf (stderr, "%s: unthrottled.\n", blurb());
1381       si->throttled_p = False;
1382
1383       if (! until_idle_p)
1384         {
1385           if (si->cycle_id)
1386             XtRemoveTimeOut (si->cycle_id);
1387           si->cycle_id = 0;
1388           cycle_timer ((XtPointer) si, 0);
1389         }
1390       else
1391         return True;
1392     }
1393   else if (type == XA_EXIT)
1394     {
1395       /* Ignore EXIT message if the screen is locked. */
1396       if (until_idle_p || !si->locked_p)
1397         {
1398           clientmessage_response (si, window, False,
1399                                   "EXIT ClientMessage received.",
1400                                   "exiting.");
1401           if (! until_idle_p)
1402             {
1403               unblank_screen (si);
1404               kill_screenhack (si);
1405               XSync (si->dpy, False);
1406             }
1407           saver_exit (si, 0, 0);
1408         }
1409       else
1410         clientmessage_response (si, window, True,
1411                                 "EXIT ClientMessage received while locked.",
1412                                 "screen is locked.");
1413     }
1414   else if (type == XA_RESTART)
1415     {
1416       /* The RESTART message works whether the screensaver is active or not,
1417          unless the screen is locked, in which case it doesn't work.
1418        */
1419       if (until_idle_p || !si->locked_p)
1420         {
1421           clientmessage_response (si, window, False,
1422                                   "RESTART ClientMessage received.",
1423                                   "restarting.");
1424           if (! until_idle_p)
1425             {
1426               unblank_screen (si);
1427               kill_screenhack (si);
1428               XSync (si->dpy, False);
1429             }
1430
1431           fflush (stdout);
1432           fflush (stderr);
1433           if (real_stdout) fflush (real_stdout);
1434           if (real_stderr) fflush (real_stderr);
1435           /* make sure error message shows up before exit. */
1436           if (real_stderr && stderr != real_stderr)
1437             dup2 (fileno(real_stderr), fileno(stderr));
1438
1439           restart_process (si);
1440           exit (1);     /* shouldn't get here; but if restarting didn't work,
1441                            make this command be the same as EXIT. */
1442         }
1443       else
1444         clientmessage_response (si, window, True,
1445                                 "RESTART ClientMessage received while locked.",
1446                                 "screen is locked.");
1447     }
1448   else if (type == XA_DEMO)
1449     {
1450       long arg = event->xclient.data.l[1];
1451       Bool demo_one_hack_p = (arg == 300);
1452
1453       if (demo_one_hack_p)
1454         {
1455           if (until_idle_p)
1456             {
1457               long which = event->xclient.data.l[2];
1458               char buf [255];
1459               char buf2 [255];
1460               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1461               sprintf (buf2, "demoing (%ld).", which);
1462               clientmessage_response (si, window, False, buf, buf2);
1463
1464               if (which < 0) which = 0;         /* 0 == "random" */
1465               si->selection_mode = which;
1466               si->demoing_p = True;
1467
1468               if (si->throttled_p && p->verbose_p)
1469                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1470               si->throttled_p = False;
1471
1472               return True;
1473             }
1474
1475           clientmessage_response (si, window, True,
1476                                   "DEMO ClientMessage received while active.",
1477                                   "already active.");
1478         }
1479       else
1480         {
1481           clientmessage_response (si, window, True,
1482                                   "obsolete form of DEMO ClientMessage.",
1483                                   "obsolete form of DEMO ClientMessage.");
1484         }
1485     }
1486   else if (type == XA_PREFS)
1487     {
1488       clientmessage_response (si, window, True,
1489                               "the PREFS client-message is obsolete.",
1490                               "the PREFS client-message is obsolete.");
1491     }
1492   else if (type == XA_LOCK)
1493     {
1494 #ifdef NO_LOCKING
1495       clientmessage_response (si, window, True,
1496                               "not compiled with support for locking.",
1497                               "locking not enabled.");
1498 #else /* !NO_LOCKING */
1499       if (si->locking_disabled_p)
1500         clientmessage_response (si, window, True,
1501                       "LOCK ClientMessage received, but locking is disabled.",
1502                               "locking not enabled.");
1503       else if (si->locked_p)
1504         clientmessage_response (si, window, True,
1505                            "LOCK ClientMessage received while already locked.",
1506                                 "already locked.");
1507       else
1508         {
1509           char buf [255];
1510           char *response = (until_idle_p
1511                             ? "activating and locking."
1512                             : "locking.");
1513           sprintf (buf, "LOCK ClientMessage received; %s", response);
1514           clientmessage_response (si, window, False, buf, response);
1515           set_locked_p (si, True);
1516           si->selection_mode = 0;
1517           si->demoing_p = False;
1518
1519           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1520             {
1521               XtRemoveTimeOut (si->lock_id);
1522               si->lock_id = 0;
1523             }
1524
1525           if (until_idle_p)
1526             {
1527               if (si->using_mit_saver_extension ||
1528                   si->using_sgi_saver_extension)
1529                 {
1530                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1531                   return False;
1532                 }
1533               else
1534                 {
1535                   return True;
1536                 }
1537             }
1538         }
1539 #endif /* !NO_LOCKING */
1540     }
1541   else if (type == XA_THROTTLE)
1542     {
1543       if (si->throttled_p)
1544         clientmessage_response (si, window, True,
1545                                 "THROTTLE ClientMessage received, but "
1546                                 "already throttled.",
1547                                 "already throttled.");
1548       else
1549         {
1550           char buf [255];
1551           char *response = "throttled.";
1552           si->throttled_p = True;
1553           si->selection_mode = 0;
1554           si->demoing_p = False;
1555           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
1556           clientmessage_response (si, window, False, buf, response);
1557
1558           if (! until_idle_p)
1559             {
1560               if (si->cycle_id)
1561                 XtRemoveTimeOut (si->cycle_id);
1562               si->cycle_id = 0;
1563               cycle_timer ((XtPointer) si, 0);
1564             }
1565         }
1566     }
1567   else if (type == XA_UNTHROTTLE)
1568     {
1569       if (! si->throttled_p)
1570         clientmessage_response (si, window, True,
1571                                 "UNTHROTTLE ClientMessage received, but "
1572                                 "not throttled.",
1573                                 "not throttled.");
1574       else
1575         {
1576           char buf [255];
1577           char *response = "unthrottled.";
1578           si->throttled_p = False;
1579           si->selection_mode = 0;
1580           si->demoing_p = False;
1581           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
1582           clientmessage_response (si, window, False, buf, response);
1583
1584           if (! until_idle_p)
1585             {
1586               if (si->cycle_id)
1587                 XtRemoveTimeOut (si->cycle_id);
1588               si->cycle_id = 0;
1589               cycle_timer ((XtPointer) si, 0);
1590             }
1591         }
1592     }
1593   else
1594     {
1595       char buf [1024];
1596       char *str;
1597       str = XGetAtomName_safe (si->dpy, type);
1598
1599       if (str)
1600         {
1601           if (strlen (str) > 80)
1602             strcpy (str+70, "...");
1603           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1604                    str);
1605           free (str);
1606         }
1607       else
1608         {
1609           sprintf (buf,
1610                    "unrecognised screensaver ClientMessage 0x%x received.",
1611                    (unsigned int) event->xclient.data.l[0]);
1612         }
1613
1614       clientmessage_response (si, window, True, buf, buf);
1615     }
1616   return False;
1617 }
1618
1619 \f
1620 /* Some random diagnostics printed in -verbose mode.
1621  */
1622
1623 static void
1624 analyze_display (saver_info *si)
1625 {
1626   int i, j;
1627   static struct {
1628     const char *name; const char *desc; Bool useful_p;
1629   } exts[] = {
1630
1631    { "SCREEN_SAVER",                            "SGI Screen-Saver",
1632 #     ifdef HAVE_SGI_SAVER_EXTENSION
1633         True
1634 #     else
1635         False
1636 #     endif
1637    }, { "SCREEN-SAVER",                         "SGI Screen-Saver",
1638 #     ifdef HAVE_SGI_SAVER_EXTENSION
1639         True
1640 #     else
1641         False
1642 #     endif
1643    }, { "MIT-SCREEN-SAVER",                     "MIT Screen-Saver",
1644 #     ifdef HAVE_MIT_SAVER_EXTENSION
1645         True
1646 #     else
1647         False
1648 #     endif
1649    }, { "XIDLE",                                "XIdle",           
1650 #     ifdef HAVE_XIDLE_EXTENSION
1651         True
1652 #     else
1653         False
1654 #     endif
1655    }, { "SGI-VIDEO-CONTROL",                    "SGI Video-Control",
1656 #     ifdef HAVE_SGI_VC_EXTENSION
1657         True
1658 #     else
1659         False
1660 #     endif
1661    }, { "READDISPLAY",                          "SGI Read-Display",
1662 #     ifdef HAVE_READ_DISPLAY_EXTENSION
1663         True
1664 #     else
1665         False
1666 #     endif
1667    }, { "MIT-SHM",                              "Shared Memory",   
1668 #     ifdef HAVE_XSHM_EXTENSION
1669         True
1670 #     else
1671         False
1672 #     endif
1673    }, { "DOUBLE-BUFFER",                        "Double-Buffering",
1674 #     ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1675         True
1676 #     else
1677         False
1678 #     endif
1679    }, { "DPMS",                                 "Power Management",
1680 #     ifdef HAVE_DPMS_EXTENSION
1681         True
1682 #     else
1683         False
1684 #     endif
1685    }, { "GLX",                                  "GLX",             
1686 #     ifdef HAVE_GL
1687         True
1688 #     else
1689         False
1690 #     endif
1691    }, { "XFree86-VidModeExtension",             "XF86 Video-Mode", 
1692 #     ifdef HAVE_XF86VMODE
1693         True
1694 #     else
1695         False
1696 #     endif
1697    }, { "XINERAMA",                             "Xinerama",
1698         True
1699    },
1700   };
1701
1702   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1703            DisplayString(si->dpy));
1704   fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1705            ServerVendor(si->dpy), VendorRelease(si->dpy));
1706
1707   fprintf (stderr, "%s: useful extensions:\n", blurb());
1708   for (i = 0; i < countof(exts); i++)
1709     {
1710       int op = 0, event = 0, error = 0;
1711       if (XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
1712         fprintf (stderr, "%s:  %s%s\n", blurb(),
1713                  exts[i].desc,
1714                  (exts[i].useful_p ? "" :
1715                   "       \t<== unsupported at compile-time!"));
1716     }
1717
1718   for (i = 0; i < si->nscreens; i++)
1719     {
1720       unsigned long colormapped_depths = 0;
1721       unsigned long non_mapped_depths = 0;
1722       XVisualInfo vi_in, *vi_out;
1723       int out_count;
1724       vi_in.screen = i;
1725       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1726       if (!vi_out) continue;
1727       for (j = 0; j < out_count; j++)
1728         if (vi_out[j].class == PseudoColor)
1729           colormapped_depths |= (1 << vi_out[j].depth);
1730         else
1731           non_mapped_depths  |= (1 << vi_out[j].depth);
1732       XFree ((char *) vi_out);
1733
1734       if (colormapped_depths)
1735         {
1736           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1737           for (j = 0; j < 32; j++)
1738             if (colormapped_depths & (1 << j))
1739               fprintf (stderr, " %d", j);
1740           fprintf (stderr, "\n");
1741         }
1742       if (non_mapped_depths)
1743         {
1744           fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1745           for (j = 0; j < 32; j++)
1746             if (non_mapped_depths & (1 << j))
1747               fprintf (stderr, " %d", j);
1748           fprintf (stderr, "\n");
1749         }
1750     }
1751 }
1752
1753 Bool
1754 display_is_on_console_p (saver_info *si)
1755 {
1756   Bool not_on_console = True;
1757   char *dpystr = DisplayString (si->dpy);
1758   char *tail = (char *) strchr (dpystr, ':');
1759   if (! tail || strncmp (tail, ":0", 2))
1760     not_on_console = True;
1761   else
1762     {
1763       char dpyname[255], localname[255];
1764       strncpy (dpyname, dpystr, tail-dpystr);
1765       dpyname [tail-dpystr] = 0;
1766       if (!*dpyname ||
1767           !strcmp(dpyname, "unix") ||
1768           !strcmp(dpyname, "localhost"))
1769         not_on_console = False;
1770       else if (gethostname (localname, sizeof (localname)))
1771         not_on_console = True;  /* can't find hostname? */
1772       else
1773         {
1774           /* We have to call gethostbyname() on the result of gethostname()
1775              because the two aren't guarenteed to be the same name for the
1776              same host: on some losing systems, one is a FQDN and the other
1777              is not.  Here in the wide wonderful world of Unix it's rocket
1778              science to obtain the local hostname in a portable fashion.
1779              
1780              And don't forget, gethostbyname() reuses the structure it
1781              returns, so we have to copy the fucker before calling it again.
1782              Thank you master, may I have another.
1783            */
1784           struct hostent *h = gethostbyname (dpyname);
1785           if (!h)
1786             not_on_console = True;
1787           else
1788             {
1789               char hn [255];
1790               struct hostent *l;
1791               strcpy (hn, h->h_name);
1792               l = gethostbyname (localname);
1793               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1794             }
1795         }
1796     }
1797   return !not_on_console;
1798 }