d820d0aba2549c07912b3f14848ebdf4a19ea176
[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 -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-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 #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-1998 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 /* !NO_DEMO_MODE */
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 /* !NO_LOCKING */
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 /* !NO_LOCKING */
893
894           /* Let's kill it before unblanking, to get it to stop drawing as
895              soon as possible... */
896           kill_screenhack (si);
897           unblank_screen (si);
898
899           if (si->cycle_id)
900             {
901               XtRemoveTimeOut (si->cycle_id);
902               si->cycle_id = 0;
903             }
904
905 #ifndef NO_LOCKING
906           if (si->lock_id)
907             {
908               XtRemoveTimeOut (si->lock_id);
909               si->lock_id = 0;
910             }
911 #endif /* !NO_LOCKING */
912
913           if (p->verbose_p)
914             printf ("%s: user is active; going to sleep at %s.\n", progname,
915                     timestring ());
916         }
917     }
918 }
919
920 \f
921
922 Bool
923 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
924 {
925   saver_preferences *p = &si->prefs;
926   Atom type = 0;
927   if (event->xclient.message_type != XA_SCREENSAVER)
928     {
929       char *str;
930       str = XGetAtomName (si->dpy, event->xclient.message_type);
931       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
932                progname, (str ? str : "(null)"));
933       if (str) XFree (str);
934       return False;
935     }
936   if (event->xclient.format != 32)
937     {
938       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
939                progname, event->xclient.format);
940       return False;
941     }
942
943   type = event->xclient.data.l[0];
944   if (type == XA_ACTIVATE)
945     {
946       if (until_idle_p)
947         {
948           if (p->verbose_p)
949             printf ("%s: ACTIVATE ClientMessage received.\n", progname);
950           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
951             {
952               XForceScreenSaver (si->dpy, ScreenSaverActive);
953               return False;
954             }
955           else
956             {
957               return True;
958             }
959         }
960       fprintf (stderr,
961                "%s: ClientMessage ACTIVATE received while already active.\n",
962                progname);
963     }
964   else if (type == XA_DEACTIVATE)
965     {
966       if (! until_idle_p)
967         {
968           if (p->verbose_p)
969             printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
970           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
971             {
972               XForceScreenSaver (si->dpy, ScreenSaverReset);
973               return False;
974             }
975           else
976             {
977               return True;
978             }
979         }
980       fprintf (stderr,
981                "%s: ClientMessage DEACTIVATE received while inactive.\n",
982                progname);
983     }
984   else if (type == XA_CYCLE)
985     {
986       if (! until_idle_p)
987         {
988           if (p->verbose_p)
989             printf ("%s: CYCLE ClientMessage received.\n", progname);
990           if (si->cycle_id)
991             XtRemoveTimeOut (si->cycle_id);
992           si->cycle_id = 0;
993           cycle_timer ((XtPointer) si, 0);
994           return False;
995         }
996       fprintf (stderr, "%s: ClientMessage CYCLE received while inactive.\n",
997                progname);
998     }
999   else if (type == XA_NEXT || type == XA_PREV)
1000     {
1001       if (p->verbose_p)
1002         printf ("%s: %s ClientMessage received.\n", progname,
1003                 (type == XA_NEXT ? "NEXT" : "PREV"));
1004       si->next_mode_p = 1 + (type == XA_PREV);
1005
1006       if (! until_idle_p)
1007         {
1008           if (si->cycle_id)
1009             XtRemoveTimeOut (si->cycle_id);
1010           si->cycle_id = 0;
1011           cycle_timer ((XtPointer) si, 0);
1012         }
1013       else
1014         return True;
1015     }
1016   else if (type == XA_EXIT)
1017     {
1018       /* Ignore EXIT message if the screen is locked. */
1019       if (until_idle_p || !si->locked_p)
1020         {
1021           if (p->verbose_p)
1022             printf ("%s: EXIT ClientMessage received.\n", progname);
1023           if (! until_idle_p)
1024             {
1025               unblank_screen (si);
1026               kill_screenhack (si);
1027               XSync (si->dpy, False);
1028             }
1029           saver_exit (si, 0);
1030         }
1031       else
1032         fprintf (stderr, "%s: EXIT ClientMessage received while locked.\n",
1033                  progname);
1034     }
1035   else if (type == XA_RESTART)
1036     {
1037       /* The RESTART message works whether the screensaver is active or not,
1038          unless the screen is locked, in which case it doesn't work.
1039        */
1040       if (until_idle_p || !si->locked_p)
1041         {
1042           if (p->verbose_p)
1043             printf ("%s: RESTART ClientMessage received.\n", progname);
1044           if (! until_idle_p)
1045             {
1046               unblank_screen (si);
1047               kill_screenhack (si);
1048               XSync (si->dpy, False);
1049             }
1050
1051           /* make sure error message shows up before exit. */
1052           if (real_stderr && stderr != real_stderr)
1053             dup2 (fileno(real_stderr), fileno(stderr));
1054
1055           restart_process (si);
1056           exit (1);     /* shouldn't get here; but if restarting didn't work,
1057                            make this command be the same as EXIT. */
1058         }
1059       else
1060         fprintf(stderr, "%s: RESTART ClientMessage received while locked.\n",
1061                 progname);
1062     }
1063   else if (type == XA_DEMO)
1064     {
1065 #ifdef NO_DEMO_MODE
1066       fprintf (stderr, "%s: not compiled with support for DEMO mode\n",
1067                progname);
1068 #else
1069       if (until_idle_p)
1070         {
1071           if (p->verbose_p)
1072             printf ("%s: DEMO ClientMessage received.\n", progname);
1073           si->demo_mode_p = True;
1074           return True;
1075         }
1076       fprintf (stderr,
1077                "%s: DEMO ClientMessage received while active.\n", progname);
1078 #endif
1079     }
1080   else if (type == XA_LOCK)
1081     {
1082 #ifdef NO_LOCKING
1083       fprintf (stderr, "%s: not compiled with support for LOCK mode\n",
1084                progname);
1085 #else
1086       if (si->locking_disabled_p)
1087         fprintf (stderr,
1088                "%s: LOCK ClientMessage received, but locking is disabled.\n",
1089                  progname);
1090       else if (si->locked_p)
1091         fprintf (stderr,
1092                "%s: LOCK ClientMessage received while already locked.\n",
1093                  progname);
1094       else
1095         {
1096           si->locked_p = True;
1097           if (p->verbose_p) 
1098             printf ("%s: LOCK ClientMessage received;%s locking.\n",
1099                     progname, until_idle_p ? " activating and" : "");
1100
1101           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1102             {
1103               XtRemoveTimeOut (si->lock_id);
1104               si->lock_id = 0;
1105             }
1106
1107           if (until_idle_p)
1108             {
1109               if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1110                 {
1111                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1112                   return False;
1113                 }
1114               else
1115                 {
1116                   return True;
1117                 }
1118             }
1119         }
1120 #endif
1121     }
1122   else
1123     {
1124       char *str;
1125       str = (type ? XGetAtomName(si->dpy, type) : 0);
1126       if (str)
1127         fprintf (stderr,
1128                  "%s: unrecognised screensaver ClientMessage %s received\n",
1129                  progname, str);
1130       else
1131         fprintf (stderr,
1132                 "%s: unrecognised screensaver ClientMessage 0x%x received\n",
1133                  progname, (unsigned int) event->xclient.data.l[0]);
1134       if (str) XFree (str);
1135     }
1136   return False;
1137 }