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