http://ftp.x.org/contrib/applications/xscreensaver-2.34.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-1998 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 #ifdef HAVE_XMU
135 # ifndef VMS
136 #  include <X11/Xmu/Error.h>
137 # else  /* !VMS */
138 #  include <Xmu/Error.h>
139 # endif /* !VMS */
140 #else  /* !HAVE_XMU */
141 # include "xmu.h"
142 #endif /* !HAVE_XMU */
143
144 #ifdef HAVE_XIDLE_EXTENSION
145 #include <X11/extensions/xidle.h>
146 #endif /* HAVE_XIDLE_EXTENSION */
147
148 #include "xscreensaver.h"
149 #include "version.h"
150 #include "yarandom.h"
151 #include "resources.h"
152 #include "visual.h"
153
154 saver_info *global_si_kludge = 0;       /* I hate C so much... */
155
156 char *progname = 0;
157 char *progclass = 0;
158 XrmDatabase db = 0;
159
160
161 static Atom XA_SCREENSAVER_RESPONSE;
162 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
163 static Atom XA_EXIT, XA_RESTART, XA_LOCK, XA_SELECT;
164 Atom XA_DEMO, XA_PREFS;
165
166 \f
167 static XrmOptionDescRec options [] = {
168   { "-timeout",            ".timeout",          XrmoptionSepArg, 0 },
169   { "-cycle",              ".cycle",            XrmoptionSepArg, 0 },
170   { "-lock-mode",          ".lock",             XrmoptionNoArg, "on" },
171   { "-no-lock-mode",       ".lock",             XrmoptionNoArg, "off" },
172   { "-lock-timeout",       ".lockTimeout",      XrmoptionSepArg, 0 },
173   { "-lock-vts",           ".lockVTs",          XrmoptionNoArg, "on" },
174   { "-no-lock-vts",        ".lockVTs",          XrmoptionNoArg, "off" },
175   { "-visual",             ".visualID",         XrmoptionSepArg, 0 },
176   { "-install",            ".installColormap",  XrmoptionNoArg, "on" },
177   { "-no-install",         ".installColormap",  XrmoptionNoArg, "off" },
178   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
179   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
180   { "-timestamp",          ".timestamp",        XrmoptionNoArg, "on" },
181   { "-capture-stderr",     ".captureStderr",    XrmoptionNoArg, "on" },
182   { "-no-capture-stderr",  ".captureStderr",    XrmoptionNoArg, "off" },
183   { "-xidle-extension",    ".xidleExtension",   XrmoptionNoArg, "on" },
184   { "-no-xidle-extension", ".xidleExtension",   XrmoptionNoArg, "off" },
185   { "-mit-extension",      ".mitSaverExtension",XrmoptionNoArg, "on" },
186   { "-no-mit-extension",   ".mitSaverExtension",XrmoptionNoArg, "off" },
187   { "-sgi-extension",      ".sgiSaverExtension",XrmoptionNoArg, "on" },
188   { "-no-sgi-extension",   ".sgiSaverExtension",XrmoptionNoArg, "off" },
189   { "-splash",             ".splash",           XrmoptionNoArg, "on" },
190   { "-no-splash",          ".splash",           XrmoptionNoArg, "off" },
191   { "-nosplash",           ".splash",           XrmoptionNoArg, "off" },
192   { "-idelay",             ".initialDelay",     XrmoptionSepArg, 0 },
193   { "-nice",               ".nice",             XrmoptionSepArg, 0 },
194
195   /* Actually these are built in to Xt, but just to be sure... */
196   { "-synchronous",        ".synchronous",      XrmoptionNoArg, "on" },
197   { "-xrm",                NULL,                XrmoptionResArg, NULL }
198 };
199
200 static char *defaults[] = {
201 #include "XScreenSaver_ad.h"
202  0
203 };
204
205 #ifdef _VROOT_H_
206 ERROR!  You must not include vroot.h in this file.
207 #endif
208
209 static void
210 do_help (saver_info *si)
211 {
212   fflush (stdout);
213   fflush (stderr);
214   fprintf (stdout, "\
215 xscreensaver %s, copyright (c) 1991-1998 by Jamie Zawinski <jwz@jwz.org>\n\
216 The standard Xt command-line options are accepted; other options include:\n\
217 \n\
218     -timeout <minutes>       When the screensaver should activate.\n\
219     -cycle <minutes>         How long to let each hack run before switching.\n\
220     -lock-mode               Require a password before deactivating.\n\
221     -lock-timeout <minutes>  Grace period before locking; default 0.\n\
222     -visual <id-or-class>    Which X visual to run on.\n\
223     -install                 Install a private colormap.\n\
224     -verbose                 Be loud.\n\
225     -no-splash               Don't display a splash-screen at startup.\n\
226     -help                    This message.\n\
227 \n\
228 See the manual for other options and X resources.\n\
229 \n\
230 The `xscreensaver' program should be left running in the background.\n\
231 Use the `xscreensaver-command' program to manipulate a running xscreensaver.\n\
232 \n\
233 The `*programs' resource controls which graphics demos will be launched by\n\
234 the screensaver.  See `man xscreensaver' or the web page for more details.\n\
235 \n\
236 Just getting started?  Try this:\n\
237 \n\
238         xscreensaver &\n\
239         xscreensaver-command -demo\n\
240 \n\
241 For updates, check http://www.jwz.org/xscreensaver/\n\
242 \n",
243           si->version);
244   fflush (stdout);
245   fflush (stderr);
246   exit (1);
247 }
248
249
250 char *
251 timestring (void)
252 {
253   time_t now = time ((time_t *) 0);
254   char *str = (char *) ctime (&now);
255   char *nl = (char *) strchr (str, '\n');
256   if (nl) *nl = 0; /* take off that dang newline */
257   return str;
258 }
259
260 static Bool blurb_timestamp_p = False;   /* kludge */
261
262 const char *
263 blurb (void)
264 {
265   if (!blurb_timestamp_p)
266     return progname;
267   else
268     {
269       static char buf[255];
270       char *ct = timestring();
271       int n = strlen(progname);
272       if (n > 100) n = 99;
273       strncpy(buf, progname, n);
274       buf[n++] = ':';
275       buf[n++] = ' ';
276       strncpy(buf+n, ct+11, 8);
277       strcpy(buf+n+9, ": ");
278       return buf;
279     }
280 }
281
282
283 int
284 saver_ehandler (Display *dpy, XErrorEvent *error)
285 {
286   saver_info *si = global_si_kludge;    /* I hate C so much... */
287
288   fprintf (real_stderr, "\n"
289            "#######################################"
290            "#######################################\n\n"
291            "%s: X Error!  PLEASE REPORT THIS BUG.\n\n"
292            "#######################################"
293            "#######################################\n\n",
294            blurb());
295   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
296     {
297       fprintf (real_stderr, "\n");
298       if (si->prefs.xsync_p)
299         {
300           saver_exit (si, -1, "because of synchronous X Error");
301         }
302       else
303         {
304           fprintf(real_stderr,
305                   "%s: to dump a core file, re-run with `-sync'.\n"
306                   "%s: see http://www.jwz.org/xscreensaver/bugs.html\n"
307                   "\t\tfor bug reporting information.\n\n",
308                   blurb(), blurb());
309           saver_exit (si, -1, 0);
310         }
311     }
312   else
313     fprintf (real_stderr, " (nonfatal.)\n");
314   return 0;
315 }
316
317 \f
318 /* The zillions of initializations.
319  */
320
321 static void get_screenhacks (saver_info *si);
322
323
324
325 /* Set progname, version, etc.  This is done very early.
326  */
327 static void
328 set_version_string (saver_info *si, int *argc, char **argv)
329 {
330   progclass = "XScreenSaver";
331
332   /* progname is reset later, after we connect to X. */
333   progname = strrchr(argv[0], '/');
334   if (progname) progname++;
335   else progname = argv[0];
336
337   if (strlen(progname) > 100)   /* keep it short. */
338     progname[99] = 0;
339
340   /* The X resource database blows up if argv[0] has a "." in it. */
341   {
342     char *s = argv[0];
343     while ((s = strchr (s, '.')))
344       *s = '_';
345   }
346
347   si->version = (char *) malloc (5);
348   memcpy (si->version, screensaver_id + 17, 4);
349   si->version [4] = 0;
350 }
351
352
353 /* Initializations that potentially take place as a priveleged user:
354    If the xscreensaver executable is setuid root, then these initializations
355    are run as root, before discarding privileges.
356  */
357 static void
358 privileged_initialization (saver_info *si, int *argc, char **argv)
359 {
360 #ifdef NO_LOCKING
361   si->locking_disabled_p = True;
362   si->nolock_reason = "not compiled with locking support";
363 #else /* !NO_LOCKING */
364   si->locking_disabled_p = False;
365   if (! lock_init (*argc, argv)) /* before hack_uid() for proper permissions */
366     {
367       si->locking_disabled_p = True;
368       si->nolock_reason = "error getting password";
369     }
370 #endif /* NO_LOCKING */
371
372 #ifndef NO_SETUID
373   hack_uid (si);
374 #endif /* NO_SETUID */
375 }
376
377
378 /* Open the connection to the X server, and intern our Atoms.
379  */
380 static Widget
381 connect_to_server (saver_info *si, int *argc, char **argv)
382 {
383   Widget toplevel_shell;
384
385   XSetErrorHandler (saver_ehandler);
386   toplevel_shell = XtAppInitialize (&si->app, progclass,
387                                     options, XtNumber (options),
388                                     argc, argv, defaults, 0, 0);
389
390   si->dpy = XtDisplay (toplevel_shell);
391   si->db = XtDatabase (si->dpy);
392   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
393
394   if(strlen(progname) > 100)    /* keep it short. */
395     progname [99] = 0;
396
397   db = si->db;  /* resources.c needs this */
398
399   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
400   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
401   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
402   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
403   XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
404   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
405                                          False);
406   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
407   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
408   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
409   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
410   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
411   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
412   XA_PREV = XInternAtom (si->dpy, "PREV", False);
413   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
414   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
415   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
416   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
417   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
418
419   return toplevel_shell;
420 }
421
422
423 /* Handle the command-line arguments that were not handled for us by Xt.
424    Issue an error message and exit if there are unknown options.
425  */
426 static void
427 process_command_line (saver_info *si, int *argc, char **argv)
428 {
429   int i;
430   for (i = 1; i < *argc; i++)
431     {
432       if (!strcmp (argv[i], "-debug"))
433         /* no resource for this one, out of paranoia. */
434         si->prefs.debug_p = True;
435
436       else if (!strcmp (argv[i], "-initial-demo-mode"))
437         /* This isn't an advertized option; it is used internally to implement
438            the "Reinitialize" button on the Demo Mode window. */
439         si->demo_mode_p = True;
440
441       else if (!strcmp (argv[i], "-h") ||
442                !strcmp (argv[i], "-help") ||
443                !strcmp (argv[i], "--help"))
444         do_help (si);
445
446       else
447         {
448           const char *s = argv[i];
449           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
450                    blurb(), s);
451
452           if (s[0] == '-' && s[1] == '-') s++;
453           if (!strcmp (s, "-activate") ||
454               !strcmp (s, "-deactivate") ||
455               !strcmp (s, "-cycle") ||
456               !strcmp (s, "-next") ||
457               !strcmp (s, "-prev") ||
458               !strcmp (s, "-exit") ||
459               !strcmp (s, "-restart") ||
460               !strcmp (s, "-demo") ||
461               !strcmp (s, "-prefs") ||
462               !strcmp (s, "-preferences") ||
463               !strcmp (s, "-lock") ||
464               !strcmp (s, "-version") ||
465               !strcmp (s, "-time"))
466             {
467               fprintf (stderr, "\n\
468     However, %s is an option to the `xscreensaver-command' program.\n\
469     The `xscreensaver' program is a daemon that runs in the background.\n\
470     You control a running xscreensaver process by sending it messages\n\
471     with `xscreensaver-command'.  See the man pages for details,\n\
472     or check the web page: http://www.jwz.org/xscreensaver/\n\n",
473                        s);
474
475               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
476                  suggest that explicitly. */
477               if (!strcmp (s, "-lock"))
478                 fprintf (stderr, "\
479     Or perhaps you meant either the \"-lock-mode\" or the\n\
480     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
481             }
482           exit (1);
483         }
484     }
485 }
486
487
488 /* Print out the xscreensaver banner to the tty if applicable;
489    Issue any other warnings that are called for at this point.
490  */
491 static void
492 print_banner (saver_info *si)
493 {
494   saver_preferences *p = &si->prefs;
495
496   /* This resource gets set some time before the others, so that we know
497      whether to print the banner (and so that the banner gets printed before
498      any resource-database-related error messages.)
499    */
500   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
501
502   /* Ditto, for the locking_disabled_p message. */
503   p->lock_p = get_boolean_resource ("lock", "Boolean");
504
505   if (p->verbose_p)
506     fprintf (stderr,
507              "%s %s, copyright (c) 1991-1998 by Jamie Zawinski <jwz@jwz.org>\n"
508              " pid = %d.\n",
509              blurb(), si->version, (int) getpid ());
510
511   if (p->debug_p)
512     fprintf (stderr, "\n"
513              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
514              "\n"
515              "\tNote that in debug mode, the xscreensaver window will only\n"
516              "\tcover the left half of the screen.  (The idea is that you\n"
517              "\tcan still see debugging output in a shell, if you position\n"
518              "\tit on the right side of the screen.)\n"
519              "\n"
520              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
521              "\tuntrusted environments.\n"
522              "\n",
523              blurb());
524
525   if (p->verbose_p)
526     {
527       if (!si->uid_message || !*si->uid_message)
528         describe_uids (si, stderr);
529       else
530         {
531           if (si->orig_uid && *si->orig_uid)
532             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
533                      blurb(), si->orig_uid);
534           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
535         }
536     }
537
538   /* If locking was not able to be initalized for some reason, explain why.
539      (This has to be done after we've read the lock_p resource.)
540    */
541   if (p->lock_p && si->locking_disabled_p)
542     {
543       p->lock_p = False;
544       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
545                si->nolock_reason);
546       if (strstr (si->nolock_reason, "passw"))
547         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
548                  "consult the manual.\n", blurb());
549       else if (strstr (si->nolock_reason, "running as "))
550         fprintf (stderr, 
551                  "%s: locking only works when xscreensaver is launched\n"
552                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
553                  "\t See the manual for details.\n",
554                  blurb());
555     }
556 }
557
558
559 /* Examine all of the display's screens, and populate the `saver_screen_info'
560    structures.
561  */
562 static void
563 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
564 {
565   Bool found_any_writable_cells = False;
566   int i;
567
568   si->nscreens = ScreenCount(si->dpy);
569   si->screens = (saver_screen_info *)
570     calloc(sizeof(saver_screen_info), si->nscreens);
571
572   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
573
574   for (i = 0; i < si->nscreens; i++)
575     {
576       saver_screen_info *ssi = &si->screens[i];
577       ssi->global = si;
578       ssi->screen = ScreenOfDisplay (si->dpy, i);
579
580       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
581       ssi->default_visual =
582         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
583
584       ssi->current_visual = ssi->default_visual;
585       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
586
587       if (ssi == si->default_screen)
588         /* Since this is the default screen, use the one already created. */
589         ssi->toplevel_shell = toplevel_shell;
590       else
591         /* Otherwise, each screen must have its own unmapped root widget. */
592         ssi->toplevel_shell =
593           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
594                               si->dpy,
595                               XtNscreen, ssi->screen,
596                               XtNvisual, ssi->current_visual,
597                               XtNdepth,  visual_depth (ssi->screen,
598                                                        ssi->current_visual),
599                               0);
600
601       if (! found_any_writable_cells)
602         {
603           /* Check to see whether fading is ever possible -- if any of the
604              screens on the display has a PseudoColor visual, then fading can
605              work (on at least some screens.)  If no screen has a PseudoColor
606              visual, then don't bother ever trying to fade, because it will
607              just cause a delay without causing any visible effect.
608           */
609           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
610               get_visual (ssi->screen, "PseudoColor", True, False) ||
611               get_visual (ssi->screen, "GrayScale", True, False))
612             found_any_writable_cells = True;
613         }
614     }
615
616   si->fading_possible_p = found_any_writable_cells;
617 }
618
619
620 /* Populate `saver_preferences' with the contents of the resource database.
621    Note that this may be called multiple times -- it is re-run each time
622    the ~/.xscreensaver file is reloaded.
623
624    This function can be very noisy, since it issues resource syntax errors
625    and so on.
626  */
627 void
628 get_resources (saver_info *si)
629 {
630   char *s;
631   saver_preferences *p = &si->prefs;
632
633   if (si->init_file_date == 0)
634     /* The date will be 0 the first time this is called; and when this is
635        called subsequent times, the file will have already been reloaded. */
636     read_init_file (si);
637
638   p->xsync_p        = get_boolean_resource ("synchronous", "Synchronous");
639   if (p->xsync_p)
640     XSynchronize(si->dpy, True);
641
642   p->verbose_p      = get_boolean_resource ("verbose", "Boolean");
643   p->timestamp_p    = get_boolean_resource ("timestamp", "Boolean");
644   p->lock_p         = get_boolean_resource ("lock", "Boolean");
645   p->lock_vt_p      = get_boolean_resource ("lockVTs", "Boolean");
646   p->fade_p         = get_boolean_resource ("fade", "Boolean");
647   p->unfade_p       = get_boolean_resource ("unfade", "Boolean");
648   p->fade_seconds   = 1000 * get_seconds_resource ("fadeSeconds", "Time");
649   p->fade_ticks     = get_integer_resource ("fadeTicks", "Integer");
650   p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
651   p->nice_inferior  = get_integer_resource ("nice", "Nice");
652
653   p->initial_delay   = 1000 * get_seconds_resource ("initialDelay", "Time");
654   p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
655   p->timeout         = 1000 * get_minutes_resource ("timeout", "Time");
656   p->lock_timeout    = 1000 * get_minutes_resource ("lockTimeout", "Time");
657   p->cycle           = 1000 * get_minutes_resource ("cycle", "Time");
658   p->passwd_timeout  = 1000 * get_seconds_resource ("passwdTimeout", "Time");
659   p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
660   p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
661                                                        "Time");
662   p->shell = get_string_resource ("bourneShell", "BourneShell");
663
664   p->help_url = get_string_resource("helpURL", "URL");
665   p->load_url_command = get_string_resource("loadURL", "LoadURL");
666
667   if ((s = get_string_resource ("splash", "Boolean")))
668     if (!get_boolean_resource("splash", "Boolean"))
669       p->splash_duration = 0;
670   if (s) free (s);
671
672   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
673     {
674       fprintf (stderr,
675                (si->nscreens == 1
676                 ? "%s: the screen has no PseudoColor or GrayScale visuals.\n"
677                 : "%s: no screens have PseudoColor or GrayScale visuals.\n"),
678                blurb());
679       fprintf (stderr, "%s: ignoring the request for fading/unfading.\n",
680                blurb());
681     }
682
683   /* don't set use_xidle_extension unless it is explicitly specified */
684   if ((s = get_string_resource ("xidleExtension", "Boolean")))
685     p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
686   else
687 #ifdef HAVE_XIDLE_EXTENSION             /* pick a default */
688     p->use_xidle_extension = True;      /* if we have it, use it */
689 #else  /* !HAVE_XIDLE_EXTENSION */
690     p->use_xidle_extension = False;
691 #endif /* !HAVE_XIDLE_EXTENSION */
692   if (s) free (s);
693
694   /* don't set use_mit_extension unless it is explicitly specified */
695   if ((s = get_string_resource ("mitSaverExtension", "Boolean")))
696     p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
697                                                        "Boolean");
698   else
699 #ifdef HAVE_MIT_SAVER_EXTENSION         /* pick a default */
700     p->use_mit_saver_extension = False; /* Default false, because it sucks */
701 #else  /* !HAVE_MIT_SAVER_EXTENSION */
702     p->use_mit_saver_extension = False;
703 #endif /* !HAVE_MIT_SAVER_EXTENSION */
704   if (s) free (s);
705
706
707   /* don't set use_mit_extension unless it is explicitly specified */
708   if ((s = get_string_resource ("sgiSaverExtension", "Boolean")))
709     p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
710                                                        "Boolean");
711   else
712 #ifdef HAVE_SGI_SAVER_EXTENSION         /* pick a default */
713     p->use_sgi_saver_extension = True;  /* if we have it, use it */
714 #else  /* !HAVE_SGI_SAVER_EXTENSION */
715     p->use_sgi_saver_extension = False;
716 #endif /* !HAVE_SGI_SAVER_EXTENSION */
717   if (s) free (s);
718
719
720   /* Throttle the various timeouts to reasonable values.
721    */
722   if (p->passwd_timeout == 0) p->passwd_timeout = 30000;         /* 30 secs */
723   if (p->timeout < 10000) p->timeout = 10000;                    /* 10 secs */
724   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
725   if (p->pointer_timeout == 0) p->pointer_timeout = 5000;        /*  5 secs */
726   if (p->notice_events_timeout == 0)
727     p->notice_events_timeout = 10000;                            /* 10 secs */
728   if (p->fade_seconds == 0 || p->fade_ticks == 0)
729     p->fade_p = False;
730   if (! p->fade_p) p->unfade_p = False;
731
732   p->watchdog_timeout = p->cycle;
733   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
734   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
735
736   get_screenhacks (si);
737
738   if (p->debug_p)
739     {
740       XSynchronize(si->dpy, True);
741       p->xsync_p = True;
742       p->verbose_p = True;
743       p->timestamp_p = True;
744       p->initial_delay = 0;
745     }
746
747   blurb_timestamp_p = p->timestamp_p;
748 }
749
750
751 /* If any server extensions have been requested, try and initialize them.
752    Issue warnings if requests can't be honored.
753  */
754 static void
755 initialize_server_extensions (saver_info *si)
756 {
757   saver_preferences *p = &si->prefs;
758
759   if (p->use_sgi_saver_extension)
760     {
761 #ifdef HAVE_SGI_SAVER_EXTENSION
762       if (! query_sgi_saver_extension (si))
763         {
764           fprintf (stderr,
765          "%s: display %s does not support the SGI SCREEN_SAVER extension.\n",
766                    blurb(), DisplayString (si->dpy));
767           p->use_sgi_saver_extension = False;
768         }
769       else if (p->use_mit_saver_extension)
770         {
771           fprintf (stderr,
772                    "%s: SGI SCREEN_SAVER extension used instead"
773                    " of MIT-SCREEN-SAVER extension.\n",
774                    blurb());
775           p->use_mit_saver_extension = False;
776         }
777       else if (p->use_xidle_extension)
778         {
779           fprintf (stderr,
780          "%s: SGI SCREEN_SAVER extension used instead of XIDLE extension.\n",
781                    blurb());
782           p->use_xidle_extension = False;
783         }
784 #else  /* !HAVE_MIT_SAVER_EXTENSION */
785       fprintf (stderr,
786                "%s: not compiled with support for the SGI SCREEN_SAVER"
787                " extension.\n",
788                blurb());
789       p->use_sgi_saver_extension = False;
790 #endif /* !HAVE_SGI_SAVER_EXTENSION */
791     }
792
793   if (p->use_mit_saver_extension)
794     {
795 #ifdef HAVE_MIT_SAVER_EXTENSION
796       if (! query_mit_saver_extension (si))
797         {
798           fprintf (stderr,
799                    "%s: display %s does not support the MIT-SCREEN-SAVER"
800                    " extension.\n",
801                    blurb(), DisplayString (si->dpy));
802           p->use_mit_saver_extension = False;
803         }
804       else if (p->use_xidle_extension)
805         {
806           fprintf (stderr,
807                    "%s: MIT-SCREEN-SAVER extension used instead of XIDLE"
808                    " extension.\n",
809                    blurb());
810           p->use_xidle_extension = False;
811         }
812 #else  /* !HAVE_MIT_SAVER_EXTENSION */
813       fprintf (stderr,
814                "%s: not compiled with support for the MIT-SCREEN-SAVER"
815                " extension.\n",
816                blurb());
817       p->use_mit_saver_extension = False;
818 #endif /* !HAVE_MIT_SAVER_EXTENSION */
819     }
820
821   if (p->use_xidle_extension)
822     {
823 #ifdef HAVE_XIDLE_EXTENSION
824       int first_event, first_error;
825       if (! XidleQueryExtension (si->dpy, &first_event, &first_error))
826         {
827           fprintf (stderr,
828                    "%s: display %s does not support the XIdle extension.\n",
829                    blurb(), DisplayString (si->dpy));
830           p->use_xidle_extension = False;
831         }
832 #else  /* !HAVE_XIDLE_EXTENSION */
833       fprintf (stderr, "%s: not compiled with support for XIdle.\n", blurb());
834       p->use_xidle_extension = False;
835 #endif /* !HAVE_XIDLE_EXTENSION */
836     }
837
838   if (p->verbose_p && p->use_mit_saver_extension)
839     fprintf (stderr, "%s: using MIT-SCREEN-SAVER server extension.\n",
840              blurb());
841   if (p->verbose_p && p->use_sgi_saver_extension)
842     fprintf (stderr, "%s: using SGI SCREEN_SAVER server extension.\n",
843              blurb());
844   if (p->verbose_p && p->use_xidle_extension)
845     fprintf (stderr, "%s: using XIdle server extension.\n",
846              blurb());
847 }
848
849
850 /* For the case where we aren't using an server extensions, select user events
851    on all the existing windows, and launch timers to select events on
852    newly-created windows as well.
853
854    If a server extension is being used, this does nothing.
855  */
856 static void
857 select_events (saver_info *si)
858 {
859   saver_preferences *p = &si->prefs;
860   int i;
861
862   if (p->use_xidle_extension ||
863       p->use_mit_saver_extension ||
864       p->use_sgi_saver_extension)
865     return;
866
867   if (p->initial_delay && !si->demo_mode_p)
868     {
869       if (p->verbose_p)
870         {
871           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
872                    (int) p->initial_delay/1000,
873                    (p->initial_delay == 1000 ? "" : "s"));
874           fflush (stderr);
875           fflush (stdout);
876         }
877       usleep (p->initial_delay);
878       if (p->verbose_p)
879         fprintf (stderr, " done.\n");
880     }
881
882   if (p->verbose_p)
883     {
884       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
885       fflush (stderr);
886       fflush (stdout);
887     }
888
889   /* Select events on the root windows of every screen.  This also selects
890      for window creation events, so that new subwindows will be noticed.
891    */
892   for (i = 0; i < si->nscreens; i++)
893     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen));
894
895   if (p->verbose_p)
896     fprintf (stderr, " done.\n");
897 }
898
899
900 /* Loop forever:
901
902        - wait until the user is idle;
903        - blank the screen;
904        - wait until the user is active;
905        - unblank the screen;
906        - repeat.
907
908  */
909 static void
910 main_loop (saver_info *si)
911 {
912   saver_preferences *p = &si->prefs;
913   while (1)
914     {
915       if (! si->demo_mode_p)
916         sleep_until_idle (si, True);
917
918       maybe_reload_init_file (si);
919
920 #ifndef NO_DEMO_MODE
921       if (si->demo_mode_p)
922         demo_mode (si);
923       else
924 #endif /* !NO_DEMO_MODE */
925         {
926           if (p->verbose_p)
927             fprintf (stderr, "%s: user is idle; waking up at %s.\n", blurb(),
928                      timestring());
929           maybe_reload_init_file (si);
930           blank_screen (si);
931           spawn_screenhack (si, True);
932           if (p->cycle)
933             si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
934                                             (XtPointer) si);
935
936 #ifndef NO_LOCKING
937           if (p->lock_p &&
938               !si->locking_disabled_p &&
939               p->lock_timeout == 0)
940             si->locked_p = True;
941
942           if (p->lock_p && !si->locked_p)
943             /* locked_p might be true already because of ClientMessage */
944             si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
945                                            activate_lock_timer,
946                                            (XtPointer) si);
947 #endif /* !NO_LOCKING */
948
949         PASSWD_INVALID:
950
951           sleep_until_idle (si, False); /* until not idle */
952           maybe_reload_init_file (si);
953
954 #ifndef NO_LOCKING
955           if (si->locked_p)
956             {
957               Bool val;
958               if (si->locking_disabled_p) abort ();
959               si->dbox_up_p = True;
960
961               {
962                 saver_screen_info *ssi = si->default_screen;
963                 suspend_screenhack (si, True);
964                 XUndefineCursor (si->dpy, ssi->screensaver_window);
965                 if (p->verbose_p)
966                   fprintf (stderr, "%s: prompting for password.\n", blurb());
967                 val = unlock_p (si);
968                 if (p->verbose_p && val == False)
969                   fprintf (stderr, "%s: password incorrect!\n", blurb());
970                 si->dbox_up_p = False;
971                 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
972                 suspend_screenhack (si, False);
973               }
974
975               if (! val)
976                 goto PASSWD_INVALID;
977               si->locked_p = False;
978             }
979 #endif /* !NO_LOCKING */
980
981           if (p->verbose_p)
982             fprintf (stderr, "%s: user is active at %s.\n",
983                      blurb(), timestring ());
984
985           /* Let's kill it before unblanking, to get it to stop drawing as
986              soon as possible... */
987           kill_screenhack (si);
988           unblank_screen (si);
989           si->selection_mode = 0;
990
991           if (si->cycle_id)
992             {
993               XtRemoveTimeOut (si->cycle_id);
994               si->cycle_id = 0;
995             }
996
997 #ifndef NO_LOCKING
998           if (si->lock_id)
999             {
1000               XtRemoveTimeOut (si->lock_id);
1001               si->lock_id = 0;
1002             }
1003 #endif /* !NO_LOCKING */
1004
1005           if (p->verbose_p)
1006             fprintf (stderr, "%s: going to sleep.\n", blurb());
1007         }
1008     }
1009 }
1010
1011
1012 int
1013 main (int argc, char **argv)
1014 {
1015   Widget shell;
1016   saver_info the_si;
1017   saver_info *si = &the_si;
1018   int i;
1019
1020   memset(si, 0, sizeof(*si));
1021   global_si_kludge = si;        /* I hate C so much... */
1022
1023   srandom ((int) time ((time_t *) 0));
1024
1025   set_version_string (si, &argc, argv);
1026   save_argv (argc, argv);
1027   privileged_initialization (si, &argc, argv);
1028   hack_environment (si);
1029
1030   shell = connect_to_server (si, &argc, argv);
1031   process_command_line (si, &argc, argv);
1032   print_banner (si);
1033   initialize_per_screen_info (si, shell);
1034   get_resources (si);
1035
1036   for (i = 0; i < si->nscreens; i++)
1037     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1038       exit (1);
1039
1040   initialize_server_extensions (si);
1041   initialize_screensaver_window (si);
1042   select_events (si);
1043   init_sigchld ();
1044   disable_builtin_screensaver (si, True);
1045   initialize_stderr (si);
1046
1047   if (!si->demo_mode_p)
1048     make_splash_dialog (si);
1049
1050   main_loop (si);               /* doesn't return */
1051   return 0;
1052 }
1053
1054 \f
1055 /* Parsing the programs resource.
1056  */
1057
1058 static char *
1059 reformat_hack (const char *hack)
1060 {
1061   int i;
1062   const char *in = hack;
1063   int indent = 13;
1064   char *h2 = (char *) malloc(strlen(in) + indent + 2);
1065   char *out = h2;
1066
1067   while (isspace(*in)) in++;            /* skip whitespace */
1068   while (*in && !isspace(*in) && *in != ':')
1069     *out++ = *in++;                     /* snarf first token */
1070   while (isspace(*in)) in++;            /* skip whitespace */
1071
1072   if (*in == ':')
1073     *out++ = *in++;                     /* copy colon */
1074   else
1075     {
1076       in = hack;
1077       out = h2;                         /* reset to beginning */
1078     }
1079
1080   *out = 0;
1081
1082   while (isspace(*in)) in++;            /* skip whitespace */
1083   for (i = strlen(h2); i < indent; i++) /* indent */
1084     *out++ = ' ';
1085
1086   /* copy the rest of the line. */
1087   while (*in)
1088     {
1089       /* shrink all whitespace to one space, for the benefit of the "demo"
1090          mode display.  We only do this when we can easily tell that the
1091          whitespace is not significant (no shell metachars).
1092        */
1093       switch (*in)
1094         {
1095         case '\'': case '"': case '`': case '\\':
1096           {
1097             /* Metachars are scary.  Copy the rest of the line unchanged. */
1098             while (*in)
1099               *out++ = *in++;
1100           }
1101           break;
1102         case ' ': case '\t':
1103           {
1104             while (*in == ' ' || *in == '\t')
1105               in++;
1106             *out++ = ' ';
1107           }
1108           break;
1109         default:
1110           *out++ = *in++;
1111           break;
1112         }
1113     }
1114   *out = 0;
1115
1116   /* strip trailing whitespace. */
1117   out = out-1;
1118   while (out > h2 && (*out == ' ' || *out == '\t' || *out == '\n'))
1119     *out-- = 0;
1120
1121   return h2;
1122 }
1123
1124
1125 static void
1126 get_screenhacks (saver_info *si)
1127 {
1128   saver_preferences *p = &si->prefs;
1129   int i = 0;
1130   int start = 0;
1131   int end = 0;
1132   int size;
1133   char *d;
1134
1135   d = get_string_resource ("monoPrograms", "MonoPrograms");
1136   if (d && !*d) { free(d); d = 0; }
1137   if (!d)
1138     d = get_string_resource ("colorPrograms", "ColorPrograms");
1139   if (d && !*d) { free(d); d = 0; }
1140
1141   if (d)
1142     {
1143       fprintf (stderr,
1144        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1145         see the manual for details.\n", blurb());
1146       free(d);
1147     }
1148
1149   d = get_string_resource ("programs", "Programs");
1150
1151   if (p->screenhacks)
1152     {
1153       for (i = 0; i < p->screenhacks_count; i++)
1154         if (p->screenhacks[i])
1155           free (p->screenhacks[i]);
1156       free(p->screenhacks);
1157       p->screenhacks = 0;
1158     }
1159
1160   if (!d || !*d)
1161     {
1162       p->screenhacks_count = 0;
1163       p->screenhacks = 0;
1164       return;
1165     }
1166
1167   size = strlen (d);
1168
1169
1170   /* Count up the number of newlines (which will be equal to or larger than
1171      the number of hacks.)
1172    */
1173   i = 0;
1174   for (i = 0; d[i]; i++)
1175     if (d[i] == '\n')
1176       i++;
1177   i++;
1178
1179   p->screenhacks = (char **) calloc (sizeof (char *), i+1);
1180
1181   /* Iterate over the lines in `d' (the string with newlines)
1182      and make new strings to stuff into the `screenhacks' array.
1183    */
1184   p->screenhacks_count = 0;
1185   while (start < size)
1186     {
1187       /* skip forward over whitespace. */
1188       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1189         start++;
1190
1191       /* skip forward to newline or end of string. */
1192       end = start;
1193       while (d[end] != 0 && d[end] != '\n')
1194         end++;
1195
1196       /* null terminate. */
1197       d[end] = 0;
1198
1199       p->screenhacks[p->screenhacks_count++] = reformat_hack (d + start);
1200       if (p->screenhacks_count >= i)
1201         abort();
1202
1203       start = end+1;
1204     }
1205
1206   if (p->screenhacks_count == 0)
1207     {
1208       free (p->screenhacks);
1209       p->screenhacks = 0;
1210     }
1211 }
1212
1213
1214 \f
1215 /* Processing ClientMessage events.
1216  */
1217
1218 static void
1219 clientmessage_response (saver_info *si, Window w, Bool error,
1220                         const char *stderr_msg,
1221                         const char *protocol_msg)
1222 {
1223   char *proto;
1224   int L;
1225   saver_preferences *p = &si->prefs;
1226   if (error || p->verbose_p)
1227     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1228
1229   L = strlen(protocol_msg);
1230   proto = (char *) malloc (L + 2);
1231   proto[0] = (error ? '-' : '+');
1232   strcpy (proto+1, protocol_msg);
1233   L++;
1234
1235   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1236                    PropModeReplace, proto, L);
1237   XSync (si->dpy, False);
1238   free (proto);
1239 }
1240
1241 Bool
1242 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1243 {
1244   saver_preferences *p = &si->prefs;
1245   Atom type = 0;
1246   Window window = event->xclient.window;
1247
1248   /* Preferences might affect our handling of client messages. */
1249   maybe_reload_init_file (si);
1250
1251   if (event->xclient.message_type != XA_SCREENSAVER)
1252     {
1253       char *str;
1254       str = XGetAtomName (si->dpy, event->xclient.message_type);
1255       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1256                blurb(), (str ? str : "(null)"));
1257       if (str) XFree (str);
1258       return False;
1259     }
1260   if (event->xclient.format != 32)
1261     {
1262       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1263                blurb(), event->xclient.format);
1264       return False;
1265     }
1266
1267   type = event->xclient.data.l[0];
1268   if (type == XA_ACTIVATE)
1269     {
1270       if (until_idle_p)
1271         {
1272           clientmessage_response(si, window, False,
1273                                  "ACTIVATE ClientMessage received.",
1274                                  "activating.");
1275           si->selection_mode = 0;
1276           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1277             {
1278               XForceScreenSaver (si->dpy, ScreenSaverActive);
1279               return False;
1280             }
1281           else
1282             {
1283               return True;
1284             }
1285         }
1286       clientmessage_response(si, window, True,
1287                        "ClientMessage ACTIVATE received while already active.",
1288                              "already active.");
1289     }
1290   else if (type == XA_DEACTIVATE)
1291     {
1292       if (! until_idle_p)
1293         {
1294           clientmessage_response(si, window, False,
1295                                  "DEACTIVATE ClientMessage received.",
1296                                  "deactivating.");
1297           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1298             {
1299               XForceScreenSaver (si->dpy, ScreenSaverReset);
1300               return False;
1301             }
1302           else
1303             {
1304               return True;
1305             }
1306         }
1307       clientmessage_response(si, window, True,
1308                            "ClientMessage DEACTIVATE received while inactive.",
1309                              "not active.");
1310     }
1311   else if (type == XA_CYCLE)
1312     {
1313       if (! until_idle_p)
1314         {
1315           clientmessage_response(si, window, False,
1316                                  "CYCLE ClientMessage received.",
1317                                  "cycling.");
1318           si->selection_mode = 0;       /* 0 means randomize when its time. */
1319           if (si->cycle_id)
1320             XtRemoveTimeOut (si->cycle_id);
1321           si->cycle_id = 0;
1322           cycle_timer ((XtPointer) si, 0);
1323           return False;
1324         }
1325       clientmessage_response(si, window, True,
1326                              "ClientMessage CYCLE received while inactive.",
1327                              "not active.");
1328     }
1329   else if (type == XA_NEXT || type == XA_PREV)
1330     {
1331       clientmessage_response(si, window, False,
1332                              (type == XA_NEXT
1333                               ? "NEXT ClientMessage received."
1334                               : "PREV ClientMessage received."),
1335                              "cycling.");
1336       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1337
1338       if (! until_idle_p)
1339         {
1340           if (si->cycle_id)
1341             XtRemoveTimeOut (si->cycle_id);
1342           si->cycle_id = 0;
1343           cycle_timer ((XtPointer) si, 0);
1344         }
1345       else
1346         return True;
1347     }
1348   else if (type == XA_SELECT)
1349     {
1350       char buf [255];
1351       char buf2 [255];
1352       long which = event->xclient.data.l[1];
1353
1354       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1355       sprintf (buf2, "activating (%ld).", which);
1356       clientmessage_response (si, window, False, buf, buf2);
1357
1358       if (which < 0) which = 0;         /* 0 == "random" */
1359       si->selection_mode = which;
1360
1361       if (! until_idle_p)
1362         {
1363           if (si->cycle_id)
1364             XtRemoveTimeOut (si->cycle_id);
1365           si->cycle_id = 0;
1366           cycle_timer ((XtPointer) si, 0);
1367         }
1368       else
1369         return True;
1370     }
1371   else if (type == XA_EXIT)
1372     {
1373       /* Ignore EXIT message if the screen is locked. */
1374       if (until_idle_p || !si->locked_p)
1375         {
1376           clientmessage_response (si, window, False,
1377                                   "EXIT ClientMessage received.",
1378                                   "exiting.");
1379           if (! until_idle_p)
1380             {
1381               unblank_screen (si);
1382               kill_screenhack (si);
1383               XSync (si->dpy, False);
1384             }
1385           saver_exit (si, 0, 0);
1386         }
1387       else
1388         clientmessage_response (si, window, True,
1389                                 "EXIT ClientMessage received while locked.",
1390                                 "screen is locked.");
1391     }
1392   else if (type == XA_RESTART)
1393     {
1394       /* The RESTART message works whether the screensaver is active or not,
1395          unless the screen is locked, in which case it doesn't work.
1396        */
1397       if (until_idle_p || !si->locked_p)
1398         {
1399           clientmessage_response (si, window, False,
1400                                   "RESTART ClientMessage received.",
1401                                   "restarting.");
1402           if (! until_idle_p)
1403             {
1404               unblank_screen (si);
1405               kill_screenhack (si);
1406               XSync (si->dpy, False);
1407             }
1408
1409           /* make sure error message shows up before exit. */
1410           if (real_stderr && stderr != real_stderr)
1411             dup2 (fileno(real_stderr), fileno(stderr));
1412
1413           restart_process (si);
1414           exit (1);     /* shouldn't get here; but if restarting didn't work,
1415                            make this command be the same as EXIT. */
1416         }
1417       else
1418         clientmessage_response (si, window, True,
1419                                 "RESTART ClientMessage received while locked.",
1420                                 "screen is locked.");
1421     }
1422   else if (type == XA_DEMO)
1423     {
1424 #ifdef NO_DEMO_MODE
1425       clientmessage_response (si, window, True,
1426                               "not compiled with support for DEMO mode.",
1427                               "demo mode not enabled.");
1428 #else /* !NO_DEMO_MODE */
1429       if (until_idle_p)
1430         {
1431           clientmessage_response (si, window, False,
1432                                   "DEMO ClientMessage received.",
1433                                   "Demo mode.");
1434           si->demo_mode_p = True;
1435           return True;
1436         }
1437       clientmessage_response (si, window, True,
1438                               "DEMO ClientMessage received while active.",
1439                               "already active.");
1440 #endif /* !NO_DEMO_MODE */
1441     }
1442   else if (type == XA_PREFS)
1443     {
1444 #ifdef NO_DEMO_MODE
1445       clientmessage_response (si, window, True,
1446                               "not compiled with support for DEMO mode.",
1447                               "preferences mode not enabled.");
1448 #else /* !NO_DEMO_MODE */
1449       if (until_idle_p)
1450         {
1451           clientmessage_response (si, window, False,
1452                                   "PREFS ClientMessage received.",
1453                                   "preferences mode.");
1454           si->demo_mode_p = (Bool) 2;  /* kludge, so sue me. */
1455           return True;
1456         }
1457       clientmessage_response (si, window, True,
1458                               "PREFS ClientMessage received while active.",
1459                               "already active.");
1460 #endif /* !NO_DEMO_MODE */
1461     }
1462   else if (type == XA_LOCK)
1463     {
1464 #ifdef NO_LOCKING
1465       clientmessage_response (si, window, True,
1466                               "not compiled with support for locking.",
1467                               "locking not enabled.");
1468 #else /* !NO_LOCKING */
1469       if (si->locking_disabled_p)
1470         clientmessage_response (si, window, True,
1471                       "LOCK ClientMessage received, but locking is disabled.",
1472                               "locking not enabled.");
1473       else if (si->locked_p)
1474         clientmessage_response (si, window, True,
1475                            "LOCK ClientMessage received while already locked.",
1476                                 "already locked.");
1477       else
1478         {
1479           char buf [255];
1480           char *response = (until_idle_p
1481                             ? "activating and locking."
1482                             : "locking.");
1483           si->locked_p = True;
1484           si->selection_mode = 0;
1485           sprintf (buf, "LOCK ClientMessage received; %s", response);
1486           clientmessage_response (si, window, False, buf, response);
1487
1488           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1489             {
1490               XtRemoveTimeOut (si->lock_id);
1491               si->lock_id = 0;
1492             }
1493
1494           if (until_idle_p)
1495             {
1496               if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1497                 {
1498                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1499                   return False;
1500                 }
1501               else
1502                 {
1503                   return True;
1504                 }
1505             }
1506         }
1507 #endif /* !NO_LOCKING */
1508     }
1509   else
1510     {
1511       char buf [1024];
1512       char *str;
1513       str = (type ? XGetAtomName(si->dpy, type) : 0);
1514
1515       if (str)
1516         {
1517           if (strlen (str) > 80)
1518             strcpy (str+70, "...");
1519           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1520                    str);
1521           free (str);
1522         }
1523       else
1524         {
1525           sprintf (buf,
1526                    "unrecognised screensaver ClientMessage 0x%x received.",
1527                    (unsigned int) event->xclient.data.l[0]);
1528         }
1529
1530       clientmessage_response (si, window, True, buf, buf);
1531     }
1532   return False;
1533 }