f3f43a772747a098742b86cb6cf9ec0659126aae
[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  /* !NO_LOCKING */
663   si->locking_disabled_p = False;
664
665 # ifdef SCO
666   set_auth_parameters(argc, argv);
667 # endif /* SCO */
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  /* !NO_LOCKING */
675
676 #ifndef NO_SETUID
677   hack_uid (si);
678 #endif /* NO_SETUID */
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   hack_environment (si);
708
709   si->demo_mode_p = initial_demo_mode_p;
710   srandom ((int) time ((time_t *) 0));
711
712   if (p->use_sgi_saver_extension)
713     {
714 #ifdef HAVE_SGI_SAVER_EXTENSION
715       if (! query_sgi_saver_extension (si))
716         {
717           fprintf (stderr,
718          "%s: display %s does not support the SGI SCREEN_SAVER extension.\n",
719                    progname, DisplayString (si->dpy));
720           p->use_sgi_saver_extension = False;
721         }
722       else if (p->use_mit_saver_extension)
723         {
724           fprintf (stderr, "%s: SGI SCREEN_SAVER extension used instead\
725  of MIT-SCREEN-SAVER extension.\n",
726                    progname);
727           p->use_mit_saver_extension = False;
728         }
729       else if (p->use_xidle_extension)
730         {
731           fprintf (stderr,
732          "%s: SGI SCREEN_SAVER extension used instead of XIDLE extension.\n",
733                    progname);
734           p->use_xidle_extension = False;
735         }
736 #else  /* !HAVE_MIT_SAVER_EXTENSION */
737       fprintf (stderr,
738        "%s: not compiled with support for the SGI SCREEN_SAVER extension.\n",
739                progname);
740       p->use_sgi_saver_extension = False;
741 #endif /* !HAVE_SGI_SAVER_EXTENSION */
742     }
743
744   if (p->use_mit_saver_extension)
745     {
746 #ifdef HAVE_MIT_SAVER_EXTENSION
747       if (! query_mit_saver_extension (si))
748         {
749           fprintf (stderr,
750          "%s: display %s does not support the MIT-SCREEN-SAVER extension.\n",
751                    progname, DisplayString (si->dpy));
752           p->use_mit_saver_extension = False;
753         }
754       else if (p->use_xidle_extension)
755         {
756           fprintf (stderr,
757          "%s: MIT-SCREEN-SAVER extension used instead of XIDLE extension.\n",
758                    progname);
759           p->use_xidle_extension = False;
760         }
761 #else  /* !HAVE_MIT_SAVER_EXTENSION */
762       fprintf (stderr,
763        "%s: not compiled with support for the MIT-SCREEN-SAVER extension.\n",
764                progname);
765       p->use_mit_saver_extension = False;
766 #endif /* !HAVE_MIT_SAVER_EXTENSION */
767     }
768
769   if (p->use_xidle_extension)
770     {
771 #ifdef HAVE_XIDLE_EXTENSION
772       int first_event, first_error;
773       if (! XidleQueryExtension (si->dpy, &first_event, &first_error))
774         {
775           fprintf (stderr,
776                    "%s: display %s does not support the XIdle extension.\n",
777                    progname, DisplayString (si->dpy));
778           p->use_xidle_extension = False;
779         }
780 #else  /* !HAVE_XIDLE_EXTENSION */
781       fprintf (stderr, "%s: not compiled with support for XIdle.\n",
782                progname);
783       p->use_xidle_extension = False;
784 #endif /* !HAVE_XIDLE_EXTENSION */
785     }
786
787   /* Call this only after having probed for presence of desired extension. */
788   initialize_screensaver_window (si);
789
790   init_sigchld ();
791
792   disable_builtin_screensaver (si, True);
793
794   if (p->verbose_p && p->use_mit_saver_extension)
795     fprintf (stderr, "%s: using MIT-SCREEN-SAVER server extension.\n",
796              progname);
797   if (p->verbose_p && p->use_sgi_saver_extension)
798     fprintf (stderr, "%s: using SGI SCREEN_SAVER server extension.\n",
799              progname);
800   if (p->verbose_p && p->use_xidle_extension)
801     fprintf (stderr, "%s: using XIdle server extension.\n",
802              progname);
803
804   initialize_stderr (si);
805   XSetErrorHandler (saver_ehandler);
806
807   if (initial_demo_mode_p)
808     /* If the user wants demo mode, don't wait around before doing it. */
809     p->initial_delay = 0;
810
811   if (!p->use_xidle_extension &&
812       !p->use_mit_saver_extension &&
813       !p->use_sgi_saver_extension)
814     {
815       if (p->initial_delay)
816         {
817           if (p->verbose_p)
818             {
819               printf ("%s: waiting for %d second%s...", progname,
820                       (int) p->initial_delay,
821                       (p->initial_delay == 1 ? "" : "s"));
822               fflush (stdout);
823             }
824           sleep (p->initial_delay);
825           if (p->verbose_p)
826             printf (" done.\n");
827         }
828       if (p->verbose_p)
829         {
830           printf ("%s: selecting events on extant windows...", progname);
831           fflush (stdout);
832         }
833
834       /* Select events on the root windows of every screen.  This also selects
835          for window creation events, so that new subwindows will be noticed.
836        */
837       for (i = 0; i < si->nscreens; i++)
838         start_notice_events_timer (si,
839                                    RootWindowOfScreen (si->screens[i].screen));
840
841       if (p->verbose_p)
842         printf (" done.\n");
843     }
844 }
845
846 static void
847 main_loop (saver_info *si)
848 {
849   saver_preferences *p = &si->prefs;
850   while (1)
851     {
852       if (! si->demo_mode_p)
853         sleep_until_idle (si, True);
854
855 #ifndef NO_DEMO_MODE
856       if (si->demo_mode_p)
857         demo_mode (si);
858       else
859 #endif /* !NO_DEMO_MODE */
860         {
861           if (p->verbose_p)
862             printf ("%s: user is idle; waking up at %s.\n", progname,
863                     timestring());
864           blank_screen (si);
865           spawn_screenhack (si, True);
866           if (p->cycle)
867             si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
868                                             (XtPointer) si);
869
870 #ifndef NO_LOCKING
871           if (p->lock_p && p->lock_timeout == 0)
872             si->locked_p = True;
873           if (p->lock_p && !si->locked_p)
874             /* locked_p might be true already because of ClientMessage */
875             si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
876                                            activate_lock_timer,
877                                            (XtPointer) si);
878 #endif /* !NO_LOCKING */
879
880         PASSWD_INVALID:
881
882           sleep_until_idle (si, False); /* until not idle */
883
884 #ifndef NO_LOCKING
885           if (si->locked_p)
886             {
887               Bool val;
888               if (si->locking_disabled_p) abort ();
889               si->dbox_up_p = True;
890
891               /* We used to ungrab the keyboard here, before calling unlock_p()
892                  to pop up the dialog box.  This left the keyboard ungrabbed
893                  for a small window, during an insecure state.  Bennett Todd
894                  was seeing the bahavior that, when the load was high, he could
895                  actually get characters through to a shell under the saver
896                  window (he accidentally typed his password there...)
897
898                  So the ungrab has been moved down into pop_passwd_dialog()
899                  just after the server is grabbed, closing this window
900                  entirely.
901                */
902               /* ungrab_keyboard_and_mouse (si); */
903
904               {
905                 saver_screen_info *ssi = si->default_screen;
906                 suspend_screenhack (si, True);
907                 XUndefineCursor (si->dpy, ssi->screensaver_window);
908                 if (p->verbose_p)
909                   printf ("%s: prompting for password.\n", progname);
910                 val = unlock_p (si);
911                 if (p->verbose_p && val == False)
912                   printf ("%s: password incorrect!\n", progname);
913                 si->dbox_up_p = False;
914                 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
915                 suspend_screenhack (si, False);
916
917                 /* I think this grab is now redundant, but it shouldn't hurt.
918                  */
919                 if (!si->demo_mode_p)
920                   grab_keyboard_and_mouse (si, ssi->screensaver_window,
921                                            ssi->cursor);
922               }
923
924               if (! val)
925                 goto PASSWD_INVALID;
926               si->locked_p = False;
927             }
928 #endif /* !NO_LOCKING */
929
930           /* Let's kill it before unblanking, to get it to stop drawing as
931              soon as possible... */
932           kill_screenhack (si);
933           unblank_screen (si);
934
935           if (si->cycle_id)
936             {
937               XtRemoveTimeOut (si->cycle_id);
938               si->cycle_id = 0;
939             }
940
941 #ifndef NO_LOCKING
942           if (si->lock_id)
943             {
944               XtRemoveTimeOut (si->lock_id);
945               si->lock_id = 0;
946             }
947 #endif /* !NO_LOCKING */
948
949           if (p->verbose_p)
950             printf ("%s: user is active; going to sleep at %s.\n", progname,
951                     timestring ());
952         }
953     }
954 }
955
956 \f
957
958 Bool
959 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
960 {
961   saver_preferences *p = &si->prefs;
962   Atom type = 0;
963   if (event->xclient.message_type != XA_SCREENSAVER)
964     {
965       char *str;
966       str = XGetAtomName (si->dpy, event->xclient.message_type);
967       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
968                progname, (str ? str : "(null)"));
969       if (str) XFree (str);
970       return False;
971     }
972   if (event->xclient.format != 32)
973     {
974       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
975                progname, event->xclient.format);
976       return False;
977     }
978
979   type = event->xclient.data.l[0];
980   if (type == XA_ACTIVATE)
981     {
982       if (until_idle_p)
983         {
984           if (p->verbose_p)
985             printf ("%s: ACTIVATE ClientMessage received.\n", progname);
986           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
987             {
988               XForceScreenSaver (si->dpy, ScreenSaverActive);
989               return False;
990             }
991           else
992             {
993               return True;
994             }
995         }
996       fprintf (stderr,
997                "%s: ClientMessage ACTIVATE received while already active.\n",
998                progname);
999     }
1000   else if (type == XA_DEACTIVATE)
1001     {
1002       if (! until_idle_p)
1003         {
1004           if (p->verbose_p)
1005             printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
1006           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1007             {
1008               XForceScreenSaver (si->dpy, ScreenSaverReset);
1009               return False;
1010             }
1011           else
1012             {
1013               return True;
1014             }
1015         }
1016       fprintf (stderr,
1017                "%s: ClientMessage DEACTIVATE received while inactive.\n",
1018                progname);
1019     }
1020   else if (type == XA_CYCLE)
1021     {
1022       if (! until_idle_p)
1023         {
1024           if (p->verbose_p)
1025             printf ("%s: CYCLE ClientMessage received.\n", progname);
1026           if (si->cycle_id)
1027             XtRemoveTimeOut (si->cycle_id);
1028           si->cycle_id = 0;
1029           cycle_timer ((XtPointer) si, 0);
1030           return False;
1031         }
1032       fprintf (stderr, "%s: ClientMessage CYCLE received while inactive.\n",
1033                progname);
1034     }
1035   else if (type == XA_NEXT || type == XA_PREV)
1036     {
1037       if (p->verbose_p)
1038         printf ("%s: %s ClientMessage received.\n", progname,
1039                 (type == XA_NEXT ? "NEXT" : "PREV"));
1040       si->next_mode_p = 1 + (type == XA_PREV);
1041
1042       if (! until_idle_p)
1043         {
1044           if (si->cycle_id)
1045             XtRemoveTimeOut (si->cycle_id);
1046           si->cycle_id = 0;
1047           cycle_timer ((XtPointer) si, 0);
1048         }
1049       else
1050         return True;
1051     }
1052   else if (type == XA_EXIT)
1053     {
1054       /* Ignore EXIT message if the screen is locked. */
1055       if (until_idle_p || !si->locked_p)
1056         {
1057           if (p->verbose_p)
1058             printf ("%s: EXIT ClientMessage received.\n", progname);
1059           if (! until_idle_p)
1060             {
1061               unblank_screen (si);
1062               kill_screenhack (si);
1063               XSync (si->dpy, False);
1064             }
1065           saver_exit (si, 0);
1066         }
1067       else
1068         fprintf (stderr, "%s: EXIT ClientMessage received while locked.\n",
1069                  progname);
1070     }
1071   else if (type == XA_RESTART)
1072     {
1073       /* The RESTART message works whether the screensaver is active or not,
1074          unless the screen is locked, in which case it doesn't work.
1075        */
1076       if (until_idle_p || !si->locked_p)
1077         {
1078           if (p->verbose_p)
1079             printf ("%s: RESTART ClientMessage received.\n", progname);
1080           if (! until_idle_p)
1081             {
1082               unblank_screen (si);
1083               kill_screenhack (si);
1084               XSync (si->dpy, False);
1085             }
1086
1087           /* make sure error message shows up before exit. */
1088           if (real_stderr && stderr != real_stderr)
1089             dup2 (fileno(real_stderr), fileno(stderr));
1090
1091           restart_process (si);
1092           exit (1);     /* shouldn't get here; but if restarting didn't work,
1093                            make this command be the same as EXIT. */
1094         }
1095       else
1096         fprintf(stderr, "%s: RESTART ClientMessage received while locked.\n",
1097                 progname);
1098     }
1099   else if (type == XA_DEMO)
1100     {
1101 #ifdef NO_DEMO_MODE
1102       fprintf (stderr, "%s: not compiled with support for DEMO mode\n",
1103                progname);
1104 #else
1105       if (until_idle_p)
1106         {
1107           if (p->verbose_p)
1108             printf ("%s: DEMO ClientMessage received.\n", progname);
1109           si->demo_mode_p = True;
1110           return True;
1111         }
1112       fprintf (stderr,
1113                "%s: DEMO ClientMessage received while active.\n", progname);
1114 #endif
1115     }
1116   else if (type == XA_LOCK)
1117     {
1118 #ifdef NO_LOCKING
1119       fprintf (stderr, "%s: not compiled with support for LOCK mode\n",
1120                progname);
1121 #else
1122       if (si->locking_disabled_p)
1123         fprintf (stderr,
1124                "%s: LOCK ClientMessage received, but locking is disabled.\n",
1125                  progname);
1126       else if (si->locked_p)
1127         fprintf (stderr,
1128                "%s: LOCK ClientMessage received while already locked.\n",
1129                  progname);
1130       else
1131         {
1132           si->locked_p = True;
1133           if (p->verbose_p) 
1134             printf ("%s: LOCK ClientMessage received;%s locking.\n",
1135                     progname, until_idle_p ? " activating and" : "");
1136
1137           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1138             {
1139               XtRemoveTimeOut (si->lock_id);
1140               si->lock_id = 0;
1141             }
1142
1143           if (until_idle_p)
1144             {
1145               if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1146                 {
1147                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1148                   return False;
1149                 }
1150               else
1151                 {
1152                   return True;
1153                 }
1154             }
1155         }
1156 #endif
1157     }
1158   else
1159     {
1160       char *str;
1161       str = (type ? XGetAtomName(si->dpy, type) : 0);
1162       if (str)
1163         fprintf (stderr,
1164                  "%s: unrecognised screensaver ClientMessage %s received\n",
1165                  progname, str);
1166       else
1167         fprintf (stderr,
1168                 "%s: unrecognised screensaver ClientMessage 0x%x received\n",
1169                  progname, (unsigned int) event->xclient.data.l[0]);
1170       if (str) XFree (str);
1171     }
1172   return False;
1173 }