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