ftp://ftp.uni-heidelberg.de/pub/X11/contrib/applications/xscreensaver-2.07.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-1997 Jamie Zawinski <jwz@netscape.com>
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 -DDEBUG.
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;
162 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
163 static Atom XA_EXIT, XA_RESTART, XA_DEMO, XA_LOCK;
164
165 \f
166 static XrmOptionDescRec options [] = {
167   { "-timeout",            ".timeout",          XrmoptionSepArg, 0 },
168   { "-cycle",              ".cycle",            XrmoptionSepArg, 0 },
169   { "-idelay",             ".initialDelay",     XrmoptionSepArg, 0 },
170   { "-nice",               ".nice",             XrmoptionSepArg, 0 },
171   { "-visual",             ".visualID",         XrmoptionSepArg, 0 },
172   { "-lock-timeout",       ".lockTimeout",      XrmoptionSepArg, 0 },
173   { "-install",            ".installColormap",  XrmoptionNoArg, "on" },
174   { "-no-install",         ".installColormap",  XrmoptionNoArg, "off" },
175   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
176   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
177   { "-xidle-extension",    ".xidleExtension",   XrmoptionNoArg, "on" },
178   { "-no-xidle-extension", ".xidleExtension",   XrmoptionNoArg, "off" },
179   { "-mit-extension",      ".mitSaverExtension",XrmoptionNoArg, "on" },
180   { "-no-mit-extension",   ".mitSaverExtension",XrmoptionNoArg, "off" },
181   { "-sgi-extension",      ".sgiSaverExtension",XrmoptionNoArg, "on" },
182   { "-no-sgi-extension",   ".sgiSaverExtension",XrmoptionNoArg, "off" },
183   { "-lock",               ".lock",             XrmoptionNoArg, "on" },
184   { "-no-lock",            ".lock",             XrmoptionNoArg, "off" }
185 };
186
187 static char *defaults[] = {
188 #include "XScreenSaver_ad.h"
189  0
190 };
191
192 static void
193 do_help (saver_info *si)
194 {
195   printf ("\
196 xscreensaver %s, copyright (c) 1991-1997 by Jamie Zawinski <jwz@netscape.com>\n\
197 The standard Xt command-line options are accepted; other options include:\n\
198 \n\
199     -timeout <minutes>         When the screensaver should activate.\n\
200     -cycle <minutes>           How long to let each hack run.\n\
201     -idelay <seconds>          How long to sleep before startup.\n\
202     -visual <id-or-class>      Which X visual to run on.\n\
203     -demo                      Enter interactive demo mode on startup.\n\
204     -install                   Install a private colormap.\n\
205     -no-install                Don't.\n\
206     -verbose                   Be loud.\n\
207     -silent                    Don't.\n\
208     -xidle-extension           Use the R5 XIdle server extension.\n\
209     -no-xidle-extension        Don't.\n\
210     -mit-extension             Use the R6 MIT_SCREEN_SAVER server extension.\n\
211     -no-mit-extension          Don't.\n\
212     -sgi-extension             Use the SGI SCREEN-SAVER server extension.\n\
213     -no-sgi-extension          Don't.\n\
214     -lock                      Require a password before deactivating.\n\
215     -no-lock                   Don't.\n\
216     -lock-timeout <minutes>    Grace period before locking; default 0.\n\
217     -help                      This message.\n\
218 \n\
219 Use the `xscreensaver-command' program to control a running screensaver.\n\
220 \n\
221 The *programs resource controls which graphics demos will be launched by the\n\
222 screensaver.  See the man page for more details.  For updates, check\n\
223 http://people.netscape.com/jwz/xscreensaver/\n\n",
224           si->version);
225
226 #ifdef NO_LOCKING
227   printf ("Support for locking was not enabled at compile-time.\n");
228 #endif
229 #ifdef NO_DEMO_MODE
230   printf ("Support for demo mode was not enabled at compile-time.\n");
231 #endif
232 #if !defined(HAVE_XIDLE_EXTENSION) && !defined(HAVE_MIT_SAVER_EXTENSION) && !defined(HAVE_SGI_SAVER_EXTENSION)
233   printf ("Support for the XIDLE, SCREEN_SAVER, and MIT-SCREEN-SAVER server\
234  extensions\nwas not enabled at compile-time.\n");
235 #endif /* !HAVE_XIDLE_EXTENSION && !HAVE_MIT_SAVER_EXTENSION && !HAVE_SGI_SAVER_EXTENSION */
236
237   fflush (stdout);
238   exit (1);
239 }
240
241
242 static char *
243 reformat_hack(const char *hack)
244 {
245   int i;
246   const char *in = hack;
247   int indent = 13;
248   char *h2 = (char *) malloc(strlen(in) + indent + 2);
249   char *out = h2;
250
251   while (isspace(*in)) in++;            /* skip whitespace */
252   while (!isspace(*in) && *in != ':')
253     *out++ = *in++;                     /* snarf first token */
254   while (isspace(*in)) in++;            /* skip whitespace */
255
256   if (*in == ':')
257     *out++ = *in++;                     /* copy colon */
258   else
259     {
260       in = hack;
261       out = h2;                         /* reset to beginning */
262     }
263
264   *out = 0;
265
266   while (isspace(*in)) in++;            /* skip whitespace */
267   for (i = strlen(h2); i < indent; i++) /* indent */
268     *out++ = ' ';
269
270   while (*in) *out++ = *in++;           /* copy rest of line */
271   *out = 0;
272
273   return h2;
274 }
275
276
277 static void
278 get_screenhacks (saver_info *si)
279 {
280   saver_preferences *p = &si->prefs;
281   int i = 0;
282   int hacks_size = 60;
283   int size;
284   char *d;
285
286   d = get_string_resource ("monoPrograms", "MonoPrograms");
287   if (d && !*d) { free(d); d = 0; }
288   if (!d)
289     d = get_string_resource ("colorPrograms", "ColorPrograms");
290   if (d && !*d) { free(d); d = 0; }
291
292   if (d)
293     {
294       fprintf (stderr,
295        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
296         see the manual for details.\n", progname);
297       free(d);
298     }
299
300   d = get_string_resource ("programs", "Programs");
301
302   size = strlen (d);
303   p->screenhacks = (char **) malloc (sizeof (char *) * hacks_size);
304   p->screenhacks_count = 0;
305
306   while (i < size)
307     {
308       int end, start = i;
309       if (d[i] == ' ' || d[i] == '\t' || d[i] == '\n' || d[i] == 0)
310         {
311           i++;
312           continue;
313         }
314       if (hacks_size <= p->screenhacks_count)
315         p->screenhacks = (char **) realloc (p->screenhacks,
316                                             (hacks_size = hacks_size * 2) *
317                                             sizeof (char *));
318       p->screenhacks [p->screenhacks_count++] = d + i;
319       while (d[i] != 0 && d[i] != '\n')
320         i++;
321       end = i;
322       while (i > start && (d[i-1] == ' ' || d[i-1] == '\t'))
323         i--;
324       d[i] = 0;
325       i = end + 1;
326     }
327
328   /* shrink all whitespace to one space, for the benefit of the "demo"
329      mode display.  We only do this when we can easily tell that the
330      whitespace is not significant (no shell metachars).
331    */
332   for (i = 0; i < p->screenhacks_count; i++)
333     {
334       char *s = p->screenhacks [i];
335       char *s2;
336       int L = strlen (s);
337       int j, k;
338       for (j = 0; j < L; j++)
339         {
340           switch (s[j])
341             {
342             case '\'': case '"': case '`': case '\\':
343               goto DONE;
344             case '\t':
345               s[j] = ' ';
346             case ' ':
347               k = 0;
348               for (s2 = s+j+1; *s2 == ' ' || *s2 == '\t'; s2++)
349                 k++;
350               if (k > 0)
351                 for (s2 = s + j + 1; *s2; s2++)
352                   s2 [0] = s2 [k];
353               break;
354             }
355         }
356     DONE:
357       p->screenhacks[i] = reformat_hack(s);  /* mallocs */
358     }
359
360   if (p->screenhacks_count)
361     {
362       /* Shrink down the screenhacks array to be only as big as it needs to.
363          This doesn't really matter at all. */
364       p->screenhacks = (char **)
365         realloc (p->screenhacks, ((p->screenhacks_count + 1) *
366                                   sizeof(char *)));
367       p->screenhacks [p->screenhacks_count] = 0;
368     }
369   else
370     {
371       free (p->screenhacks);
372       p->screenhacks = 0;
373     }
374 }
375
376
377 static void
378 get_resources (saver_info *si)
379 {
380   char *s;
381   saver_preferences *p = &si->prefs;
382
383   p->verbose_p      = get_boolean_resource ("verbose", "Boolean");
384   p->lock_p         = get_boolean_resource ("lock", "Boolean");
385   p->fade_p         = get_boolean_resource ("fade", "Boolean");
386   p->unfade_p       = get_boolean_resource ("unfade", "Boolean");
387   p->fade_seconds   = get_seconds_resource ("fadeSeconds", "Time");
388   p->fade_ticks     = get_integer_resource ("fadeTicks", "Integer");
389   p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
390   p->nice_inferior  = get_integer_resource ("nice", "Nice");
391
392   p->initial_delay  = get_seconds_resource ("initialDelay", "Time");
393   p->timeout        = 1000 * get_minutes_resource ("timeout", "Time");
394   p->lock_timeout   = 1000 * get_minutes_resource ("lockTimeout", "Time");
395   p->cycle          = 1000 * get_minutes_resource ("cycle", "Time");
396
397 #ifndef NO_LOCKING
398   p->passwd_timeout = 1000 * get_seconds_resource ("passwdTimeout", "Time");
399 #endif
400
401   p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
402   p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
403                                                        "Time");
404   p->shell = get_string_resource ("bourneShell", "BourneShell");
405
406
407   /* don't set use_xidle_extension unless it is explicitly specified */
408   if ((s = get_string_resource ("xidleExtension", "Boolean")))
409     p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
410   else
411 #ifdef HAVE_XIDLE_EXTENSION             /* pick a default */
412     p->use_xidle_extension = True;      /* if we have it, use it */
413 #else  /* !HAVE_XIDLE_EXTENSION */
414     p->use_xidle_extension = False;
415 #endif /* !HAVE_XIDLE_EXTENSION */
416   if (s) free (s);
417
418   /* don't set use_mit_extension unless it is explicitly specified */
419   if ((s = get_string_resource ("mitSaverExtension", "Boolean")))
420     p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
421                                                        "Boolean");
422   else
423 #ifdef HAVE_MIT_SAVER_EXTENSION         /* pick a default */
424     p->use_mit_saver_extension = False; /* Default false, because it sucks */
425 #else  /* !HAVE_MIT_SAVER_EXTENSION */
426     p->use_mit_saver_extension = False;
427 #endif /* !HAVE_MIT_SAVER_EXTENSION */
428   if (s) free (s);
429
430
431   /* don't set use_mit_extension unless it is explicitly specified */
432   if ((s = get_string_resource ("sgiSaverExtension", "Boolean")))
433     p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
434                                                        "Boolean");
435   else
436 #ifdef HAVE_SGI_SAVER_EXTENSION         /* pick a default */
437     p->use_sgi_saver_extension = True;  /* if we have it, use it */
438 #else  /* !HAVE_SGI_SAVER_EXTENSION */
439     p->use_sgi_saver_extension = False;
440 #endif /* !HAVE_SGI_SAVER_EXTENSION */
441   if (s) free (s);
442
443
444   /* Throttle the various timeouts to reasonable values.
445    */
446 #ifndef NO_LOCKING
447   if (p->passwd_timeout == 0) p->passwd_timeout = 30000;         /* 30 secs */
448 #endif
449   if (p->timeout < 10000) p->timeout = 10000;                    /* 10 secs */
450   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
451   if (p->pointer_timeout == 0) p->pointer_timeout = 5000;        /*  5 secs */
452   if (p->notice_events_timeout == 0)
453     p->notice_events_timeout = 10000;                            /* 10 secs */
454   if (p->fade_seconds == 0 || p->fade_ticks == 0)
455     p->fade_p = False;
456   if (! p->fade_p) p->unfade_p = False;
457
458   p->watchdog_timeout = p->cycle;
459   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
460   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
461
462 #ifdef NO_LOCKING
463   si->locking_disabled_p = True;
464   si->nolock_reason = "not compiled with locking support";
465   if (p->lock_p)
466     {
467       p->lock_p = False;
468       fprintf (stderr, "%s: not compiled with support for locking.\n",
469                progname);
470     }
471 #else  /* ! NO_LOCKING */
472   if (p->lock_p && si->locking_disabled_p)
473     {
474       fprintf (stderr, "%s: locking is disabled (%s).\n", progname,
475                si->nolock_reason);
476       p->lock_p = False;
477     }
478 #endif /* ! NO_LOCKING */
479
480   get_screenhacks (si);
481
482 #ifdef DEBUG
483   if (p->debug_p)
484     {
485       XSynchronize(si->dpy, True);
486       p->verbose_p = True;
487       p->initial_delay = 0;
488     }
489 #endif /* DEBUG */
490 }
491
492
493 char *
494 timestring (void)
495 {
496   time_t now = time ((time_t *) 0);
497   char *str = (char *) ctime (&now);
498   char *nl = (char *) strchr (str, '\n');
499   if (nl) *nl = 0; /* take off that dang newline */
500   return str;
501 }
502
503 static void initialize (saver_info *si, int argc, char **argv);
504 static void main_loop (saver_info *si);
505
506 int
507 main (int argc, char **argv)
508 {
509   saver_info si;
510   memset(&si, 0, sizeof(si));
511   global_si_kludge = &si;       /* I hate C so much... */
512   initialize (&si, argc, argv);
513   main_loop (&si);              /* doesn't return */
514   return 0;
515 }
516
517
518 int
519 saver_ehandler (Display *dpy, XErrorEvent *error)
520 {
521   saver_info *si = global_si_kludge;    /* I hate C so much... */
522
523   fprintf (real_stderr, "\nX error in %s:\n", progname);
524   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
525     saver_exit (si, -1);
526   else
527     fprintf (real_stderr, " (nonfatal.)\n");
528   return 0;
529 }
530
531 static void
532 initialize_connection (saver_info *si, int argc, char **argv)
533 {
534   int i;
535   Widget toplevel_shell = XtAppInitialize (&si->app, progclass,
536                                            options, XtNumber (options),
537                                            &argc, argv, defaults, 0, 0);
538
539   si->dpy = XtDisplay (toplevel_shell);
540   si->db = XtDatabase (si->dpy);
541   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
542
543   db = si->db;  /* resources.c needs this */
544
545   if (argc == 2 && !strcmp (argv[1], "-help"))
546     do_help (si);
547 #ifdef DEBUG
548   else if (argc == 2 && !strcmp (argv[1], "-debug"))
549     si->prefs.debug_p = True;  /* no resource for this one, out of paranoia. */
550 #endif /* DEBUG */
551   else if (argc > 1)
552     {
553       fprintf (stderr, "%s: unknown option %s\n", progname, argv [1]);
554       exit (1);
555     }
556   get_resources (si);
557 #ifndef NO_SETUID
558   hack_uid_warn (si);
559 #endif /* NO_SETUID */
560   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
561   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
562   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
563   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
564   XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
565   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
566   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
567   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
568   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
569   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
570   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
571   XA_PREV = XInternAtom (si->dpy, "PREV", False);
572   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
573   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
574   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
575
576   si->nscreens = ScreenCount(si->dpy);
577   si->screens = (saver_screen_info *)
578     calloc(sizeof(saver_screen_info), si->nscreens);
579
580   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
581
582   for (i = 0; i < si->nscreens; i++)
583     {
584       saver_screen_info *ssi = &si->screens[i];
585       ssi->global = si;
586       ssi->screen = ScreenOfDisplay (si->dpy, i);
587
588       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
589       ssi->default_visual =
590         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
591
592       ssi->current_visual = ssi->default_visual;
593       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
594
595       if (ssi == si->default_screen)
596         /* Since this is the default screen, use the one already created. */
597         ssi->toplevel_shell = toplevel_shell;
598       else
599         /* Otherwise, each screen must have its own unmapped root widget. */
600         ssi->toplevel_shell =
601           XtVaAppCreateShell(progname, progclass, applicationShellWidgetClass,
602                              si->dpy,
603                              XtNscreen, ssi->screen,
604                              XtNvisual, ssi->current_visual,
605                              XtNdepth,  visual_depth(ssi->screen,
606                                                      ssi->current_visual),
607                              0);
608     }
609 }
610
611
612 static void
613 initialize (saver_info *si, int argc, char **argv)
614 {
615   int i;
616   saver_preferences *p = &si->prefs;
617   Bool initial_demo_mode_p = False;
618   si->version = (char *) malloc (5);
619   memcpy (si->version, screensaver_id + 17, 4);
620   si->version [4] = 0;
621   progname = argv[0]; /* reset later; this is for the benefit of lock_init() */
622
623 #ifdef NO_LOCKING
624   si->locking_disabled_p = True;
625   si->nolock_reason = "not compiled with locking support";
626 #else
627   si->locking_disabled_p = False;
628
629 #ifdef SCO
630   set_auth_parameters(argc, argv);
631 #endif
632
633   if (! lock_init (argc, argv)) /* before hack_uid() for proper permissions */
634     {
635       si->locking_disabled_p = True;
636       si->nolock_reason = "error getting password";
637     }
638 #endif
639
640 #ifndef NO_SETUID
641   hack_uid (si);
642 #endif
643
644   progclass = "XScreenSaver";
645
646   /* remove -demo switch before saving argv */
647   for (i = 1; i < argc; i++)
648     while (!strcmp ("-demo", argv [i]))
649       {
650         int j;
651         initial_demo_mode_p = True;
652         for (j = i; j < argc; j++)
653           argv [j] = argv [j+1];
654         argv [j] = 0;
655         argc--;
656         if (argc <= i) break;
657       }
658   save_argv (argc, argv);
659   initialize_connection (si, argc, argv);
660
661   if (p->verbose_p)
662     printf ("\
663 %s %s, copyright (c) 1991-1997 by Jamie Zawinski <jwz@netscape.com>\n\
664  pid = %d.\n", progname, si->version, (int) getpid ());
665
666   
667   for (i = 0; i < si->nscreens; i++)
668     ensure_no_screensaver_running (si->dpy, si->screens[i].screen);
669
670   si->demo_mode_p = initial_demo_mode_p;
671   srandom ((int) time ((time_t *) 0));
672
673   if (p->use_sgi_saver_extension)
674     {
675 #ifdef HAVE_SGI_SAVER_EXTENSION
676       if (! query_sgi_saver_extension (si))
677         {
678           fprintf (stderr,
679          "%s: display %s does not support the SGI SCREEN_SAVER extension.\n",
680                    progname, DisplayString (si->dpy));
681           p->use_sgi_saver_extension = False;
682         }
683       else if (p->use_mit_saver_extension)
684         {
685           fprintf (stderr, "%s: SGI SCREEN_SAVER extension used instead\
686  of MIT-SCREEN-SAVER extension.\n",
687                    progname);
688           p->use_mit_saver_extension = False;
689         }
690       else if (p->use_xidle_extension)
691         {
692           fprintf (stderr,
693          "%s: SGI SCREEN_SAVER extension used instead of XIDLE extension.\n",
694                    progname);
695           p->use_xidle_extension = False;
696         }
697 #else  /* !HAVE_MIT_SAVER_EXTENSION */
698       fprintf (stderr,
699        "%s: not compiled with support for the SGI SCREEN_SAVER extension.\n",
700                progname);
701       p->use_sgi_saver_extension = False;
702 #endif /* !HAVE_SGI_SAVER_EXTENSION */
703     }
704
705   if (p->use_mit_saver_extension)
706     {
707 #ifdef HAVE_MIT_SAVER_EXTENSION
708       if (! query_mit_saver_extension (si))
709         {
710           fprintf (stderr,
711          "%s: display %s does not support the MIT-SCREEN-SAVER extension.\n",
712                    progname, DisplayString (si->dpy));
713           p->use_mit_saver_extension = False;
714         }
715       else if (p->use_xidle_extension)
716         {
717           fprintf (stderr,
718          "%s: MIT-SCREEN-SAVER extension used instead of XIDLE extension.\n",
719                    progname);
720           p->use_xidle_extension = False;
721         }
722 #else  /* !HAVE_MIT_SAVER_EXTENSION */
723       fprintf (stderr,
724        "%s: not compiled with support for the MIT-SCREEN-SAVER extension.\n",
725                progname);
726       p->use_mit_saver_extension = False;
727 #endif /* !HAVE_MIT_SAVER_EXTENSION */
728     }
729
730   if (p->use_xidle_extension)
731     {
732 #ifdef HAVE_XIDLE_EXTENSION
733       int first_event, first_error;
734       if (! XidleQueryExtension (si->dpy, &first_event, &first_error))
735         {
736           fprintf (stderr,
737                    "%s: display %s does not support the XIdle extension.\n",
738                    progname, DisplayString (si->dpy));
739           p->use_xidle_extension = False;
740         }
741 #else  /* !HAVE_XIDLE_EXTENSION */
742       fprintf (stderr, "%s: not compiled with support for XIdle.\n",
743                progname);
744       p->use_xidle_extension = False;
745 #endif /* !HAVE_XIDLE_EXTENSION */
746     }
747
748   /* Call this only after having probed for presence of desired extension. */
749   initialize_screensaver_window (si);
750
751   init_sigchld ();
752
753   disable_builtin_screensaver (si, True);
754
755   if (p->verbose_p && p->use_mit_saver_extension)
756     fprintf (stderr, "%s: using MIT-SCREEN-SAVER server extension.\n",
757              progname);
758   if (p->verbose_p && p->use_sgi_saver_extension)
759     fprintf (stderr, "%s: using SGI SCREEN_SAVER server extension.\n",
760              progname);
761   if (p->verbose_p && p->use_xidle_extension)
762     fprintf (stderr, "%s: using XIdle server extension.\n",
763              progname);
764
765   initialize_stderr (si);
766   XSetErrorHandler (saver_ehandler);
767
768   if (initial_demo_mode_p)
769     /* If the user wants demo mode, don't wait around before doing it. */
770     p->initial_delay = 0;
771
772   if (!p->use_xidle_extension &&
773       !p->use_mit_saver_extension &&
774       !p->use_sgi_saver_extension)
775     {
776       if (p->initial_delay)
777         {
778           if (p->verbose_p)
779             {
780               printf ("%s: waiting for %d second%s...", progname,
781                       (int) p->initial_delay,
782                       (p->initial_delay == 1 ? "" : "s"));
783               fflush (stdout);
784             }
785           sleep (p->initial_delay);
786           if (p->verbose_p)
787             printf (" done.\n");
788         }
789       if (p->verbose_p)
790         {
791           printf ("%s: selecting events on extant windows...", progname);
792           fflush (stdout);
793         }
794
795       /* Select events on the root windows of every screen.  This also selects
796          for window creation events, so that new subwindows will be noticed.
797        */
798       for (i = 0; i < si->nscreens; i++)
799         start_notice_events_timer (si,
800                                    RootWindowOfScreen (si->screens[i].screen));
801
802       if (p->verbose_p)
803         printf (" done.\n");
804     }
805 }
806
807 static void
808 main_loop (saver_info *si)
809 {
810   saver_preferences *p = &si->prefs;
811   while (1)
812     {
813       if (! si->demo_mode_p)
814         sleep_until_idle (si, True);
815
816 #ifndef NO_DEMO_MODE
817       if (si->demo_mode_p)
818         demo_mode (si);
819       else
820 #endif
821         {
822           if (p->verbose_p)
823             printf ("%s: user is idle; waking up at %s.\n", progname,
824                     timestring());
825           blank_screen (si);
826           spawn_screenhack (si, True);
827           if (p->cycle)
828             si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
829                                             (XtPointer) si);
830
831 #ifndef NO_LOCKING
832           if (p->lock_p && p->lock_timeout == 0)
833             si->locked_p = True;
834           if (p->lock_p && !si->locked_p)
835             /* locked_p might be true already because of ClientMessage */
836             si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
837                                            activate_lock_timer,
838                                            (XtPointer) si);
839 #endif
840
841         PASSWD_INVALID:
842
843           sleep_until_idle (si, False); /* until not idle */
844
845 #ifndef NO_LOCKING
846           if (si->locked_p)
847             {
848               Bool val;
849               if (si->locking_disabled_p) abort ();
850               si->dbox_up_p = True;
851
852               /* We used to ungrab the keyboard here, before calling unlock_p()
853                  to pop up the dialog box.  This left the keyboard ungrabbed
854                  for a small window, during an insecure state.  Bennett Todd
855                  was seeing the bahavior that, when the load was high, he could
856                  actually get characters through to a shell under the saver
857                  window (he accidentally typed his password there...)
858
859                  So the ungrab has been moved down into pop_passwd_dialog()
860                  just after the server is grabbed, closing this window
861                  entirely.
862                */
863               /* ungrab_keyboard_and_mouse (); */
864
865               {
866                 saver_screen_info *ssi = si->default_screen;
867                 suspend_screenhack (si, True);
868                 XUndefineCursor (si->dpy, ssi->screensaver_window);
869                 if (p->verbose_p)
870                   printf ("%s: prompting for password.\n", progname);
871                 val = unlock_p (si);
872                 if (p->verbose_p && val == False)
873                   printf ("%s: password incorrect!\n", progname);
874                 si->dbox_up_p = False;
875                 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
876                 suspend_screenhack (si, False);
877
878                 /* I think this grab is now redundant, but it shouldn't hurt.
879                  */
880                 if (!si->demo_mode_p)
881                   grab_keyboard_and_mouse (si->dpy, ssi->screensaver_window,
882                                            ssi->cursor);
883               }
884
885               if (! val)
886                 goto PASSWD_INVALID;
887               si->locked_p = False;
888             }
889 #endif
890           unblank_screen (si);
891           kill_screenhack (si);
892           if (si->cycle_id)
893             {
894               XtRemoveTimeOut (si->cycle_id);
895               si->cycle_id = 0;
896             }
897 #ifndef NO_LOCKING
898           if (si->lock_id)
899             {
900               XtRemoveTimeOut (si->lock_id);
901               si->lock_id = 0;
902             }
903 #endif
904           if (p->verbose_p)
905             printf ("%s: user is active; going to sleep at %s.\n", progname,
906                     timestring ());
907         }
908     }
909 }
910
911 \f
912
913 Bool
914 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
915 {
916   saver_preferences *p = &si->prefs;
917   Atom type = 0;
918   if (event->xclient.message_type != XA_SCREENSAVER)
919     {
920       char *str;
921       str = XGetAtomName (si->dpy, event->xclient.message_type);
922       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
923                progname, (str ? str : "(null)"));
924       if (str) XFree (str);
925       return False;
926     }
927   if (event->xclient.format != 32)
928     {
929       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
930                progname, event->xclient.format);
931       return False;
932     }
933
934   type = event->xclient.data.l[0];
935   if (type == XA_ACTIVATE)
936     {
937       if (until_idle_p)
938         {
939           if (p->verbose_p)
940             printf ("%s: ACTIVATE ClientMessage received.\n", progname);
941           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
942             {
943               XForceScreenSaver (si->dpy, ScreenSaverActive);
944               return False;
945             }
946           else
947             {
948               return True;
949             }
950         }
951       fprintf (stderr,
952                "%s: ClientMessage ACTIVATE received while already active.\n",
953                progname);
954     }
955   else if (type == XA_DEACTIVATE)
956     {
957       if (! until_idle_p)
958         {
959           if (p->verbose_p)
960             printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
961           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
962             {
963               XForceScreenSaver (si->dpy, ScreenSaverReset);
964               return False;
965             }
966           else
967             {
968               return True;
969             }
970         }
971       fprintf (stderr,
972                "%s: ClientMessage DEACTIVATE received while inactive.\n",
973                progname);
974     }
975   else if (type == XA_CYCLE)
976     {
977       if (! until_idle_p)
978         {
979           if (p->verbose_p)
980             printf ("%s: CYCLE ClientMessage received.\n", progname);
981           if (si->cycle_id)
982             XtRemoveTimeOut (si->cycle_id);
983           si->cycle_id = 0;
984           cycle_timer ((XtPointer) si, 0);
985           return False;
986         }
987       fprintf (stderr, "%s: ClientMessage CYCLE received while inactive.\n",
988                progname);
989     }
990   else if (type == XA_NEXT || type == XA_PREV)
991     {
992       if (p->verbose_p)
993         printf ("%s: %s ClientMessage received.\n", progname,
994                 (type == XA_NEXT ? "NEXT" : "PREV"));
995       si->next_mode_p = 1 + (type == XA_PREV);
996
997       if (! until_idle_p)
998         {
999           if (si->cycle_id)
1000             XtRemoveTimeOut (si->cycle_id);
1001           si->cycle_id = 0;
1002           cycle_timer ((XtPointer) si, 0);
1003         }
1004       else
1005         return True;
1006     }
1007   else if (type == XA_EXIT)
1008     {
1009       /* Ignore EXIT message if the screen is locked. */
1010       if (until_idle_p || !si->locked_p)
1011         {
1012           if (p->verbose_p)
1013             printf ("%s: EXIT ClientMessage received.\n", progname);
1014           if (! until_idle_p)
1015             {
1016               unblank_screen (si);
1017               kill_screenhack (si);
1018               XSync (si->dpy, False);
1019             }
1020           saver_exit (si, 0);
1021         }
1022       else
1023         fprintf (stderr, "%s: EXIT ClientMessage received while locked.\n",
1024                  progname);
1025     }
1026   else if (type == XA_RESTART)
1027     {
1028       /* The RESTART message works whether the screensaver is active or not,
1029          unless the screen is locked, in which case it doesn't work.
1030        */
1031       if (until_idle_p || !si->locked_p)
1032         {
1033           if (p->verbose_p)
1034             printf ("%s: RESTART ClientMessage received.\n", progname);
1035           if (! until_idle_p)
1036             {
1037               unblank_screen (si);
1038               kill_screenhack (si);
1039               XSync (si->dpy, False);
1040             }
1041
1042           /* make sure error message shows up before exit. */
1043           if (real_stderr && stderr != real_stderr)
1044             dup2 (fileno(real_stderr), fileno(stderr));
1045
1046           restart_process (si);
1047           exit (1);     /* shouldn't get here; but if restarting didn't work,
1048                            make this command be the same as EXIT. */
1049         }
1050       else
1051         fprintf(stderr, "%s: RESTART ClientMessage received while locked.\n",
1052                 progname);
1053     }
1054   else if (type == XA_DEMO)
1055     {
1056 #ifdef NO_DEMO_MODE
1057       fprintf (stderr, "%s: not compiled with support for DEMO mode\n",
1058                progname);
1059 #else
1060       if (until_idle_p)
1061         {
1062           if (p->verbose_p)
1063             printf ("%s: DEMO ClientMessage received.\n", progname);
1064           si->demo_mode_p = True;
1065           return True;
1066         }
1067       fprintf (stderr,
1068                "%s: DEMO ClientMessage received while active.\n", progname);
1069 #endif
1070     }
1071   else if (type == XA_LOCK)
1072     {
1073 #ifdef NO_LOCKING
1074       fprintf (stderr, "%s: not compiled with support for LOCK mode\n",
1075                progname);
1076 #else
1077       if (si->locking_disabled_p)
1078         fprintf (stderr,
1079                "%s: LOCK ClientMessage received, but locking is disabled.\n",
1080                  progname);
1081       else if (si->locked_p)
1082         fprintf (stderr,
1083                "%s: LOCK ClientMessage received while already locked.\n",
1084                  progname);
1085       else
1086         {
1087           si->locked_p = True;
1088           if (p->verbose_p) 
1089             printf ("%s: LOCK ClientMessage received;%s locking.\n",
1090                     progname, until_idle_p ? " activating and" : "");
1091
1092           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1093             {
1094               XtRemoveTimeOut (si->lock_id);
1095               si->lock_id = 0;
1096             }
1097
1098           if (until_idle_p)
1099             {
1100               if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1101                 {
1102                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1103                   return False;
1104                 }
1105               else
1106                 {
1107                   return True;
1108                 }
1109             }
1110         }
1111 #endif
1112     }
1113   else
1114     {
1115       char *str;
1116       str = (type ? XGetAtomName(si->dpy, type) : 0);
1117       if (str)
1118         fprintf (stderr,
1119                  "%s: unrecognised screensaver ClientMessage %s received\n",
1120                  progname, str);
1121       else
1122         fprintf (stderr,
1123                 "%s: unrecognised screensaver ClientMessage 0x%x received\n",
1124                  progname, (unsigned int) event->xclient.data.l[0]);
1125       if (str) XFree (str);
1126     }
1127   return False;
1128 }