http://ftp.x.org/contrib/applications/xscreensaver-2.17.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-1998 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 `debug_p'.
112  *     - You probably can't set breakpoints in functions that are called on
113  *       the other side of a call to fork() -- if your clients are dying 
114  *       with signal 5, Trace/BPT Trap, you're losing in this way.
115  *     - If you aren't using a server extension, don't leave this stopped
116  *       under the debugger for very long, or the X input buffer will get
117  *       huge because of the keypress events it's selecting for.  This can
118  *       make your X server wedge with "no more input buffers."
119  *       
120  * ======================================================================== */
121
122 #ifdef HAVE_CONFIG_H
123 # include "config.h"
124 #endif
125
126 #include <stdio.h>
127 #include <ctype.h>
128 #include <X11/Xlib.h>
129 #include <X11/Xatom.h>
130 #include <X11/Intrinsic.h>
131 #include <X11/StringDefs.h>
132 #include <X11/Shell.h>
133 #include <X11/Xos.h>
134 #ifdef HAVE_XMU
135 # ifndef VMS
136 #  include <X11/Xmu/Error.h>
137 # else  /* !VMS */
138 #  include <Xmu/Error.h>
139 # endif /* !VMS */
140 #else  /* !HAVE_XMU */
141 # include "xmu.h"
142 #endif /* !HAVE_XMU */
143
144 #ifdef HAVE_XIDLE_EXTENSION
145 #include <X11/extensions/xidle.h>
146 #endif /* HAVE_XIDLE_EXTENSION */
147
148 #include "xscreensaver.h"
149 #include "version.h"
150 #include "yarandom.h"
151 #include "resources.h"
152 #include "visual.h"
153
154 saver_info *global_si_kludge = 0;       /* I hate C so much... */
155
156 char *progname = 0;
157 char *progclass = 0;
158 XrmDatabase db = 0;
159
160
161 static Atom XA_SCREENSAVER;
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-1998 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   if (p->debug_p)
486     {
487       XSynchronize(si->dpy, True);
488       p->verbose_p = True;
489       p->initial_delay = 0;
490     }
491 }
492
493
494 char *
495 timestring (void)
496 {
497   time_t now = time ((time_t *) 0);
498   char *str = (char *) ctime (&now);
499   char *nl = (char *) strchr (str, '\n');
500   if (nl) *nl = 0; /* take off that dang newline */
501   return str;
502 }
503
504 static void initialize (saver_info *si, int argc, char **argv);
505 static void main_loop (saver_info *si);
506
507 int
508 main (int argc, char **argv)
509 {
510   saver_info si;
511   memset(&si, 0, sizeof(si));
512   global_si_kludge = &si;       /* I hate C so much... */
513   initialize (&si, argc, argv);
514   main_loop (&si);              /* doesn't return */
515   return 0;
516 }
517
518
519 int
520 saver_ehandler (Display *dpy, XErrorEvent *error)
521 {
522   saver_info *si = global_si_kludge;    /* I hate C so much... */
523
524   fprintf (real_stderr, "\nX error in %s:\n", progname);
525   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
526     saver_exit (si, -1);
527   else
528     fprintf (real_stderr, " (nonfatal.)\n");
529   return 0;
530 }
531
532 static void
533 initialize_connection (saver_info *si, int argc, char **argv)
534 {
535   int i;
536   Widget toplevel_shell = XtAppInitialize (&si->app, progclass,
537                                            options, XtNumber (options),
538                                            &argc, argv, defaults, 0, 0);
539
540   si->dpy = XtDisplay (toplevel_shell);
541   si->db = XtDatabase (si->dpy);
542   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
543
544   db = si->db;  /* resources.c needs this */
545
546   if (argc == 2 && !strcmp (argv[1], "-help"))
547     do_help (si);
548
549   else if (argc == 2 && !strcmp (argv[1], "-debug"))
550     si->prefs.debug_p = True;  /* no resource for this one, out of paranoia. */
551
552   else if (argc > 1)
553     {
554       fprintf (stderr, "%s: unknown option %s\n", progname, argv [1]);
555       exit (1);
556     }
557   get_resources (si);
558 #ifndef NO_SETUID
559   hack_uid_warn (si);
560 #endif /* NO_SETUID */
561   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
562   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
563   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
564   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
565   XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
566   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
567   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
568   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
569   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
570   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
571   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
572   XA_PREV = XInternAtom (si->dpy, "PREV", False);
573   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
574   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
575   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
576
577   si->nscreens = ScreenCount(si->dpy);
578   si->screens = (saver_screen_info *)
579     calloc(sizeof(saver_screen_info), si->nscreens);
580
581   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
582
583   for (i = 0; i < si->nscreens; i++)
584     {
585       saver_screen_info *ssi = &si->screens[i];
586       ssi->global = si;
587       ssi->screen = ScreenOfDisplay (si->dpy, i);
588
589       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
590       ssi->default_visual =
591         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
592
593       ssi->current_visual = ssi->default_visual;
594       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
595
596       if (ssi == si->default_screen)
597         /* Since this is the default screen, use the one already created. */
598         ssi->toplevel_shell = toplevel_shell;
599       else
600         /* Otherwise, each screen must have its own unmapped root widget. */
601         ssi->toplevel_shell =
602           XtVaAppCreateShell(progname, progclass, applicationShellWidgetClass,
603                              si->dpy,
604                              XtNscreen, ssi->screen,
605                              XtNvisual, ssi->current_visual,
606                              XtNdepth,  visual_depth(ssi->screen,
607                                                      ssi->current_visual),
608                              0);
609     }
610 }
611
612
613 static void
614 initialize (saver_info *si, int argc, char **argv)
615 {
616   int i;
617   saver_preferences *p = &si->prefs;
618   Bool initial_demo_mode_p = False;
619   si->version = (char *) malloc (5);
620   memcpy (si->version, screensaver_id + 17, 4);
621   si->version [4] = 0;
622   progname = argv[0]; /* reset later; this is for the benefit of lock_init() */
623
624 #ifdef NO_LOCKING
625   si->locking_disabled_p = True;
626   si->nolock_reason = "not compiled with locking support";
627 #else
628   si->locking_disabled_p = False;
629
630 #ifdef SCO
631   set_auth_parameters(argc, argv);
632 #endif
633
634   if (! lock_init (argc, argv)) /* before hack_uid() for proper permissions */
635     {
636       si->locking_disabled_p = True;
637       si->nolock_reason = "error getting password";
638     }
639 #endif
640
641 #ifndef NO_SETUID
642   hack_uid (si);
643 #endif
644
645   progclass = "XScreenSaver";
646
647   /* remove -demo switch before saving argv */
648   for (i = 1; i < argc; i++)
649     while (!strcmp ("-demo", argv [i]))
650       {
651         int j;
652         initial_demo_mode_p = True;
653         for (j = i; j < argc; j++)
654           argv [j] = argv [j+1];
655         argv [j] = 0;
656         argc--;
657         if (argc <= i) break;
658       }
659   save_argv (argc, argv);
660   initialize_connection (si, argc, argv);
661
662   if (p->verbose_p)
663     printf ("\
664 %s %s, copyright (c) 1991-1998 by Jamie Zawinski <jwz@netscape.com>\n\
665  pid = %d.\n", progname, si->version, (int) getpid ());
666
667   
668   for (i = 0; i < si->nscreens; i++)
669     ensure_no_screensaver_running (si->dpy, si->screens[i].screen);
670
671   si->demo_mode_p = initial_demo_mode_p;
672   srandom ((int) time ((time_t *) 0));
673
674   if (p->use_sgi_saver_extension)
675     {
676 #ifdef HAVE_SGI_SAVER_EXTENSION
677       if (! query_sgi_saver_extension (si))
678         {
679           fprintf (stderr,
680          "%s: display %s does not support the SGI SCREEN_SAVER extension.\n",
681                    progname, DisplayString (si->dpy));
682           p->use_sgi_saver_extension = False;
683         }
684       else if (p->use_mit_saver_extension)
685         {
686           fprintf (stderr, "%s: SGI SCREEN_SAVER extension used instead\
687  of MIT-SCREEN-SAVER extension.\n",
688                    progname);
689           p->use_mit_saver_extension = False;
690         }
691       else if (p->use_xidle_extension)
692         {
693           fprintf (stderr,
694          "%s: SGI SCREEN_SAVER extension used instead of XIDLE extension.\n",
695                    progname);
696           p->use_xidle_extension = False;
697         }
698 #else  /* !HAVE_MIT_SAVER_EXTENSION */
699       fprintf (stderr,
700        "%s: not compiled with support for the SGI SCREEN_SAVER extension.\n",
701                progname);
702       p->use_sgi_saver_extension = False;
703 #endif /* !HAVE_SGI_SAVER_EXTENSION */
704     }
705
706   if (p->use_mit_saver_extension)
707     {
708 #ifdef HAVE_MIT_SAVER_EXTENSION
709       if (! query_mit_saver_extension (si))
710         {
711           fprintf (stderr,
712          "%s: display %s does not support the MIT-SCREEN-SAVER extension.\n",
713                    progname, DisplayString (si->dpy));
714           p->use_mit_saver_extension = False;
715         }
716       else if (p->use_xidle_extension)
717         {
718           fprintf (stderr,
719          "%s: MIT-SCREEN-SAVER extension used instead of XIDLE extension.\n",
720                    progname);
721           p->use_xidle_extension = False;
722         }
723 #else  /* !HAVE_MIT_SAVER_EXTENSION */
724       fprintf (stderr,
725        "%s: not compiled with support for the MIT-SCREEN-SAVER extension.\n",
726                progname);
727       p->use_mit_saver_extension = False;
728 #endif /* !HAVE_MIT_SAVER_EXTENSION */
729     }
730
731   if (p->use_xidle_extension)
732     {
733 #ifdef HAVE_XIDLE_EXTENSION
734       int first_event, first_error;
735       if (! XidleQueryExtension (si->dpy, &first_event, &first_error))
736         {
737           fprintf (stderr,
738                    "%s: display %s does not support the XIdle extension.\n",
739                    progname, DisplayString (si->dpy));
740           p->use_xidle_extension = False;
741         }
742 #else  /* !HAVE_XIDLE_EXTENSION */
743       fprintf (stderr, "%s: not compiled with support for XIdle.\n",
744                progname);
745       p->use_xidle_extension = False;
746 #endif /* !HAVE_XIDLE_EXTENSION */
747     }
748
749   /* Call this only after having probed for presence of desired extension. */
750   initialize_screensaver_window (si);
751
752   init_sigchld ();
753
754   disable_builtin_screensaver (si, True);
755
756   if (p->verbose_p && p->use_mit_saver_extension)
757     fprintf (stderr, "%s: using MIT-SCREEN-SAVER server extension.\n",
758              progname);
759   if (p->verbose_p && p->use_sgi_saver_extension)
760     fprintf (stderr, "%s: using SGI SCREEN_SAVER server extension.\n",
761              progname);
762   if (p->verbose_p && p->use_xidle_extension)
763     fprintf (stderr, "%s: using XIdle server extension.\n",
764              progname);
765
766   initialize_stderr (si);
767   XSetErrorHandler (saver_ehandler);
768
769   if (initial_demo_mode_p)
770     /* If the user wants demo mode, don't wait around before doing it. */
771     p->initial_delay = 0;
772
773   if (!p->use_xidle_extension &&
774       !p->use_mit_saver_extension &&
775       !p->use_sgi_saver_extension)
776     {
777       if (p->initial_delay)
778         {
779           if (p->verbose_p)
780             {
781               printf ("%s: waiting for %d second%s...", progname,
782                       (int) p->initial_delay,
783                       (p->initial_delay == 1 ? "" : "s"));
784               fflush (stdout);
785             }
786           sleep (p->initial_delay);
787           if (p->verbose_p)
788             printf (" done.\n");
789         }
790       if (p->verbose_p)
791         {
792           printf ("%s: selecting events on extant windows...", progname);
793           fflush (stdout);
794         }
795
796       /* Select events on the root windows of every screen.  This also selects
797          for window creation events, so that new subwindows will be noticed.
798        */
799       for (i = 0; i < si->nscreens; i++)
800         start_notice_events_timer (si,
801                                    RootWindowOfScreen (si->screens[i].screen));
802
803       if (p->verbose_p)
804         printf (" done.\n");
805     }
806 }
807
808 static void
809 main_loop (saver_info *si)
810 {
811   saver_preferences *p = &si->prefs;
812   while (1)
813     {
814       if (! si->demo_mode_p)
815         sleep_until_idle (si, True);
816
817 #ifndef NO_DEMO_MODE
818       if (si->demo_mode_p)
819         demo_mode (si);
820       else
821 #endif /* !NO_DEMO_MODE */
822         {
823           if (p->verbose_p)
824             printf ("%s: user is idle; waking up at %s.\n", progname,
825                     timestring());
826           blank_screen (si);
827           spawn_screenhack (si, True);
828           if (p->cycle)
829             si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
830                                             (XtPointer) si);
831
832 #ifndef NO_LOCKING
833           if (p->lock_p && p->lock_timeout == 0)
834             si->locked_p = True;
835           if (p->lock_p && !si->locked_p)
836             /* locked_p might be true already because of ClientMessage */
837             si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
838                                            activate_lock_timer,
839                                            (XtPointer) si);
840 #endif /* !NO_LOCKING */
841
842         PASSWD_INVALID:
843
844           sleep_until_idle (si, False); /* until not idle */
845
846 #ifndef NO_LOCKING
847           if (si->locked_p)
848             {
849               Bool val;
850               if (si->locking_disabled_p) abort ();
851               si->dbox_up_p = True;
852
853               /* We used to ungrab the keyboard here, before calling unlock_p()
854                  to pop up the dialog box.  This left the keyboard ungrabbed
855                  for a small window, during an insecure state.  Bennett Todd
856                  was seeing the bahavior that, when the load was high, he could
857                  actually get characters through to a shell under the saver
858                  window (he accidentally typed his password there...)
859
860                  So the ungrab has been moved down into pop_passwd_dialog()
861                  just after the server is grabbed, closing this window
862                  entirely.
863                */
864               /* ungrab_keyboard_and_mouse (si); */
865
866               {
867                 saver_screen_info *ssi = si->default_screen;
868                 suspend_screenhack (si, True);
869                 XUndefineCursor (si->dpy, ssi->screensaver_window);
870                 if (p->verbose_p)
871                   printf ("%s: prompting for password.\n", progname);
872                 val = unlock_p (si);
873                 if (p->verbose_p && val == False)
874                   printf ("%s: password incorrect!\n", progname);
875                 si->dbox_up_p = False;
876                 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
877                 suspend_screenhack (si, False);
878
879                 /* I think this grab is now redundant, but it shouldn't hurt.
880                  */
881                 if (!si->demo_mode_p)
882                   grab_keyboard_and_mouse (si, ssi->screensaver_window,
883                                            ssi->cursor);
884               }
885
886               if (! val)
887                 goto PASSWD_INVALID;
888               si->locked_p = False;
889             }
890 #endif /* !NO_LOCKING */
891
892           /* Let's kill it before unblanking, to get it to stop drawing as
893              soon as possible... */
894           kill_screenhack (si);
895           unblank_screen (si);
896
897           if (si->cycle_id)
898             {
899               XtRemoveTimeOut (si->cycle_id);
900               si->cycle_id = 0;
901             }
902
903 #ifndef NO_LOCKING
904           if (si->lock_id)
905             {
906               XtRemoveTimeOut (si->lock_id);
907               si->lock_id = 0;
908             }
909 #endif /* !NO_LOCKING */
910
911           if (p->verbose_p)
912             printf ("%s: user is active; going to sleep at %s.\n", progname,
913                     timestring ());
914         }
915     }
916 }
917
918 \f
919
920 Bool
921 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
922 {
923   saver_preferences *p = &si->prefs;
924   Atom type = 0;
925   if (event->xclient.message_type != XA_SCREENSAVER)
926     {
927       char *str;
928       str = XGetAtomName (si->dpy, event->xclient.message_type);
929       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
930                progname, (str ? str : "(null)"));
931       if (str) XFree (str);
932       return False;
933     }
934   if (event->xclient.format != 32)
935     {
936       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
937                progname, event->xclient.format);
938       return False;
939     }
940
941   type = event->xclient.data.l[0];
942   if (type == XA_ACTIVATE)
943     {
944       if (until_idle_p)
945         {
946           if (p->verbose_p)
947             printf ("%s: ACTIVATE ClientMessage received.\n", progname);
948           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
949             {
950               XForceScreenSaver (si->dpy, ScreenSaverActive);
951               return False;
952             }
953           else
954             {
955               return True;
956             }
957         }
958       fprintf (stderr,
959                "%s: ClientMessage ACTIVATE received while already active.\n",
960                progname);
961     }
962   else if (type == XA_DEACTIVATE)
963     {
964       if (! until_idle_p)
965         {
966           if (p->verbose_p)
967             printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
968           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
969             {
970               XForceScreenSaver (si->dpy, ScreenSaverReset);
971               return False;
972             }
973           else
974             {
975               return True;
976             }
977         }
978       fprintf (stderr,
979                "%s: ClientMessage DEACTIVATE received while inactive.\n",
980                progname);
981     }
982   else if (type == XA_CYCLE)
983     {
984       if (! until_idle_p)
985         {
986           if (p->verbose_p)
987             printf ("%s: CYCLE ClientMessage received.\n", progname);
988           if (si->cycle_id)
989             XtRemoveTimeOut (si->cycle_id);
990           si->cycle_id = 0;
991           cycle_timer ((XtPointer) si, 0);
992           return False;
993         }
994       fprintf (stderr, "%s: ClientMessage CYCLE received while inactive.\n",
995                progname);
996     }
997   else if (type == XA_NEXT || type == XA_PREV)
998     {
999       if (p->verbose_p)
1000         printf ("%s: %s ClientMessage received.\n", progname,
1001                 (type == XA_NEXT ? "NEXT" : "PREV"));
1002       si->next_mode_p = 1 + (type == XA_PREV);
1003
1004       if (! until_idle_p)
1005         {
1006           if (si->cycle_id)
1007             XtRemoveTimeOut (si->cycle_id);
1008           si->cycle_id = 0;
1009           cycle_timer ((XtPointer) si, 0);
1010         }
1011       else
1012         return True;
1013     }
1014   else if (type == XA_EXIT)
1015     {
1016       /* Ignore EXIT message if the screen is locked. */
1017       if (until_idle_p || !si->locked_p)
1018         {
1019           if (p->verbose_p)
1020             printf ("%s: EXIT ClientMessage received.\n", progname);
1021           if (! until_idle_p)
1022             {
1023               unblank_screen (si);
1024               kill_screenhack (si);
1025               XSync (si->dpy, False);
1026             }
1027           saver_exit (si, 0);
1028         }
1029       else
1030         fprintf (stderr, "%s: EXIT ClientMessage received while locked.\n",
1031                  progname);
1032     }
1033   else if (type == XA_RESTART)
1034     {
1035       /* The RESTART message works whether the screensaver is active or not,
1036          unless the screen is locked, in which case it doesn't work.
1037        */
1038       if (until_idle_p || !si->locked_p)
1039         {
1040           if (p->verbose_p)
1041             printf ("%s: RESTART ClientMessage received.\n", progname);
1042           if (! until_idle_p)
1043             {
1044               unblank_screen (si);
1045               kill_screenhack (si);
1046               XSync (si->dpy, False);
1047             }
1048
1049           /* make sure error message shows up before exit. */
1050           if (real_stderr && stderr != real_stderr)
1051             dup2 (fileno(real_stderr), fileno(stderr));
1052
1053           restart_process (si);
1054           exit (1);     /* shouldn't get here; but if restarting didn't work,
1055                            make this command be the same as EXIT. */
1056         }
1057       else
1058         fprintf(stderr, "%s: RESTART ClientMessage received while locked.\n",
1059                 progname);
1060     }
1061   else if (type == XA_DEMO)
1062     {
1063 #ifdef NO_DEMO_MODE
1064       fprintf (stderr, "%s: not compiled with support for DEMO mode\n",
1065                progname);
1066 #else
1067       if (until_idle_p)
1068         {
1069           if (p->verbose_p)
1070             printf ("%s: DEMO ClientMessage received.\n", progname);
1071           si->demo_mode_p = True;
1072           return True;
1073         }
1074       fprintf (stderr,
1075                "%s: DEMO ClientMessage received while active.\n", progname);
1076 #endif
1077     }
1078   else if (type == XA_LOCK)
1079     {
1080 #ifdef NO_LOCKING
1081       fprintf (stderr, "%s: not compiled with support for LOCK mode\n",
1082                progname);
1083 #else
1084       if (si->locking_disabled_p)
1085         fprintf (stderr,
1086                "%s: LOCK ClientMessage received, but locking is disabled.\n",
1087                  progname);
1088       else if (si->locked_p)
1089         fprintf (stderr,
1090                "%s: LOCK ClientMessage received while already locked.\n",
1091                  progname);
1092       else
1093         {
1094           si->locked_p = True;
1095           if (p->verbose_p) 
1096             printf ("%s: LOCK ClientMessage received;%s locking.\n",
1097                     progname, until_idle_p ? " activating and" : "");
1098
1099           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1100             {
1101               XtRemoveTimeOut (si->lock_id);
1102               si->lock_id = 0;
1103             }
1104
1105           if (until_idle_p)
1106             {
1107               if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1108                 {
1109                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1110                   return False;
1111                 }
1112               else
1113                 {
1114                   return True;
1115                 }
1116             }
1117         }
1118 #endif
1119     }
1120   else
1121     {
1122       char *str;
1123       str = (type ? XGetAtomName(si->dpy, type) : 0);
1124       if (str)
1125         fprintf (stderr,
1126                  "%s: unrecognised screensaver ClientMessage %s received\n",
1127                  progname, str);
1128       else
1129         fprintf (stderr,
1130                 "%s: unrecognised screensaver ClientMessage 0x%x received\n",
1131                  progname, (unsigned int) event->xclient.data.l[0]);
1132       if (str) XFree (str);
1133     }
1134   return False;
1135 }