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