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