http://ftp.x.org/contrib/applications/xscreensaver-2.24.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-1998 Jamie Zawinski <jwz@netscape.com>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /*   ========================================================================
13  *   First we wait until the keyboard and mouse become idle for the specified
14  *   amount of time.  We do this in one of three different ways: periodically
15  *   checking with the XIdle server extension; selecting key and mouse events
16  *   on (nearly) all windows; or by waiting for the MIT-SCREEN-SAVER extension
17  *   to send us a "you are idle" event.
18  *
19  *   Then, we map a full screen black window (or, in the case of the 
20  *   MIT-SCREEN-SAVER extension, use the one it gave us.)
21  *
22  *   We place a __SWM_VROOT property on this window, so that newly-started
23  *   clients will think that this window is a "virtual root" window.
24  *
25  *   If there is an existing "virtual root" window (one that already had
26  *   an __SWM_VROOT property) then we remove that property from that window.
27  *   Otherwise, clients would see that window (the real virtual root) instead
28  *   of ours (the impostor.)
29  *
30  *   Then we pick a random program to run, and start it.  Two assumptions 
31  *   are made about this program: that it has been specified with whatever
32  *   command-line options are necessary to make it run on the root window;
33  *   and that it has been compiled with vroot.h, so that it is able to find
34  *   the root window when a virtual-root window manager (or this program) is
35  *   running.
36  *
37  *   Then, we wait for keyboard or mouse events to be generated on the window.
38  *   When they are, we kill the inferior process, unmap the window, and restore
39  *   the __SWM_VROOT property to the real virtual root window if there was one.
40  *
41  *   While we are waiting, we also set up timers so that, after a certain 
42  *   amount of time has passed, we can start a different screenhack.  We do
43  *   this by killing the running child process with SIGTERM, and then starting
44  *   a new one in the same way.
45  *
46  *   If there was a real virtual root, meaning that we removed the __SWM_VROOT
47  *   property from it, meaning we must (absolutely must) restore it before we
48  *   exit, then we set up signal handlers for most signals (SIGINT, SIGTERM,
49  *   etc.) that do this.  Most Xlib and Xt routines are not reentrant, so it
50  *   is not generally safe to call them from signal handlers; however, this
51  *   program spends most of its time waiting, so the window of opportunity 
52  *   when code could be called reentrantly is fairly small; and also, the worst
53  *   that could happen is that the call would fail.  If we've gotten one of
54  *   these signals, then we're on our way out anyway.  If we didn't restore the
55  *   __SWM_VROOT property, that would be very bad, so it's worth a shot.  Note
56  *   that this means that, if you're using a virtual-root window manager, you
57  *   can really fuck up the world by killing this process with "kill -9".
58  *
59  *   This program accepts ClientMessages of type SCREENSAVER; these messages
60  *   may contain the atom ACTIVATE or DEACTIVATE, meaning to turn the 
61  *   screensaver on or off now, regardless of the idleness of the user,
62  *   and a few other things.  The included "xscreensaver_command" program
63  *   sends these messsages.
64  *
65  *   If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER
66  *   extensions, then we do the XAutoLock trick: notice every window that
67  *   gets created, and wait 30 seconds or so until its creating process has
68  *   settled down, and then select KeyPress events on those windows which
69  *   already select for KeyPress events.  It's important that we not select
70  *   KeyPress on windows which don't select them, because that would
71  *   interfere with event propagation.  This will break if any program
72  *   changes its event mask to contain KeyRelease or PointerMotion more than
73  *   30 seconds after creating the window, but that's probably pretty rare.
74  *   
75  *   The reason that we can't select KeyPresses on windows that don't have
76  *   them already is that, when dispatching a KeyPress event, X finds the
77  *   lowest (leafmost) window in the hierarchy on which *any* client selects
78  *   for KeyPress, and sends the event to that window.  This means that if a
79  *   client had a window with subwindows, and expected to receive KeyPress
80  *   events on the parent window instead of the subwindows, then that client
81  *   would malfunction if some other client selected KeyPress events on the
82  *   subwindows.  It is an incredible misdesign that one client can make
83  *   another client malfunction in this way.
84  *
85  *   To detect mouse motion, we periodically wake up and poll the mouse
86  *   position and button/modifier state, and notice when something has
87  *   changed.  We make this check every five seconds by default, and since the
88  *   screensaver timeout has a granularity of one minute, this makes the
89  *   chance of a false positive very small.  We could detect mouse motion in
90  *   the same way as keyboard activity, but that would suffer from the same
91  *   "client changing event mask" problem that the KeyPress events hack does.
92  *   I think polling is more reliable.
93  *
94  *   None of this crap happens if we're using one of the extensions, so install
95  *   one of them if the description above sounds just too flaky to live.  It
96  *   is, but those are your choices.
97  *
98  *   A third idle-detection option could be implemented (but is not): when
99  *   running on the console display ($DISPLAY is `localhost`:0) and we're on a
100  *   machine where /dev/tty and /dev/mouse have reasonable last-modification
101  *   times, we could just stat() those.  But the incremental benefit of
102  *   implementing this is really small, so forget I said anything.
103  *
104  *   Debugging hints:
105  *     - Have a second terminal handy.
106  *     - Be careful where you set your breakpoints, you don't want this to
107  *       stop under the debugger with the keyboard grabbed or the blackout
108  *       window exposed.
109  *     - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
110  *       to keep your emacs window alive even when xscreensaver has grabbed.
111  *     - Go read the code related to `debug_p'.
112  *     - You probably can't set breakpoints in functions that are called on
113  *       the other side of a call to fork() -- if your clients are dying 
114  *       with signal 5, Trace/BPT Trap, you're losing in this way.
115  *     - If you aren't using a server extension, don't leave this stopped
116  *       under the debugger for very long, or the X input buffer will get
117  *       huge because of the keypress events it's selecting for.  This can
118  *       make your X server wedge with "no more input buffers."
119  *       
120  * ======================================================================== */
121
122 #ifdef HAVE_CONFIG_H
123 # include "config.h"
124 #endif
125
126 #include <stdio.h>
127 #include <ctype.h>
128 #include <X11/Xlib.h>
129 #include <X11/Xatom.h>
130 #include <X11/Intrinsic.h>
131 #include <X11/StringDefs.h>
132 #include <X11/Shell.h>
133 #include <X11/Xos.h>
134 #ifdef HAVE_XMU
135 # ifndef VMS
136 #  include <X11/Xmu/Error.h>
137 # else  /* !VMS */
138 #  include <Xmu/Error.h>
139 # endif /* !VMS */
140 #else  /* !HAVE_XMU */
141 # include "xmu.h"
142 #endif /* !HAVE_XMU */
143
144 #ifdef HAVE_XIDLE_EXTENSION
145 #include <X11/extensions/xidle.h>
146 #endif /* HAVE_XIDLE_EXTENSION */
147
148 #include "xscreensaver.h"
149 #include "version.h"
150 #include "yarandom.h"
151 #include "resources.h"
152 #include "visual.h"
153
154 saver_info *global_si_kludge = 0;       /* I hate C so much... */
155
156 char *progname = 0;
157 char *progclass = 0;
158 XrmDatabase db = 0;
159
160
161 static Atom XA_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", blurb());
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 Bool blurb_timestamp_p = False;   /* kludge */
380
381
382 static void
383 get_resources (saver_info *si)
384 {
385   char *s;
386   saver_preferences *p = &si->prefs;
387
388   p->verbose_p      = get_boolean_resource ("verbose", "Boolean");
389   p->timestamp_p    = get_boolean_resource ("timestamp", "Boolean");
390   p->lock_p         = get_boolean_resource ("lock", "Boolean");
391   p->fade_p         = get_boolean_resource ("fade", "Boolean");
392   p->unfade_p       = get_boolean_resource ("unfade", "Boolean");
393   p->fade_seconds   = get_seconds_resource ("fadeSeconds", "Time");
394   p->fade_ticks     = get_integer_resource ("fadeTicks", "Integer");
395   p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
396   p->nice_inferior  = get_integer_resource ("nice", "Nice");
397
398   p->initial_delay  = get_seconds_resource ("initialDelay", "Time");
399   p->timeout        = 1000 * get_minutes_resource ("timeout", "Time");
400   p->lock_timeout   = 1000 * get_minutes_resource ("lockTimeout", "Time");
401   p->cycle          = 1000 * get_minutes_resource ("cycle", "Time");
402
403 #ifndef NO_LOCKING
404   p->passwd_timeout = 1000 * get_seconds_resource ("passwdTimeout", "Time");
405 #endif
406
407   p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
408   p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
409                                                        "Time");
410   p->shell = get_string_resource ("bourneShell", "BourneShell");
411
412
413   /* don't set use_xidle_extension unless it is explicitly specified */
414   if ((s = get_string_resource ("xidleExtension", "Boolean")))
415     p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
416   else
417 #ifdef HAVE_XIDLE_EXTENSION             /* pick a default */
418     p->use_xidle_extension = True;      /* if we have it, use it */
419 #else  /* !HAVE_XIDLE_EXTENSION */
420     p->use_xidle_extension = False;
421 #endif /* !HAVE_XIDLE_EXTENSION */
422   if (s) free (s);
423
424   /* don't set use_mit_extension unless it is explicitly specified */
425   if ((s = get_string_resource ("mitSaverExtension", "Boolean")))
426     p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
427                                                        "Boolean");
428   else
429 #ifdef HAVE_MIT_SAVER_EXTENSION         /* pick a default */
430     p->use_mit_saver_extension = False; /* Default false, because it sucks */
431 #else  /* !HAVE_MIT_SAVER_EXTENSION */
432     p->use_mit_saver_extension = False;
433 #endif /* !HAVE_MIT_SAVER_EXTENSION */
434   if (s) free (s);
435
436
437   /* don't set use_mit_extension unless it is explicitly specified */
438   if ((s = get_string_resource ("sgiSaverExtension", "Boolean")))
439     p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
440                                                        "Boolean");
441   else
442 #ifdef HAVE_SGI_SAVER_EXTENSION         /* pick a default */
443     p->use_sgi_saver_extension = True;  /* if we have it, use it */
444 #else  /* !HAVE_SGI_SAVER_EXTENSION */
445     p->use_sgi_saver_extension = False;
446 #endif /* !HAVE_SGI_SAVER_EXTENSION */
447   if (s) free (s);
448
449
450   /* Throttle the various timeouts to reasonable values.
451    */
452 #ifndef NO_LOCKING
453   if (p->passwd_timeout == 0) p->passwd_timeout = 30000;         /* 30 secs */
454 #endif
455   if (p->timeout < 10000) p->timeout = 10000;                    /* 10 secs */
456   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
457   if (p->pointer_timeout == 0) p->pointer_timeout = 5000;        /*  5 secs */
458   if (p->notice_events_timeout == 0)
459     p->notice_events_timeout = 10000;                            /* 10 secs */
460   if (p->fade_seconds == 0 || p->fade_ticks == 0)
461     p->fade_p = False;
462   if (! p->fade_p) p->unfade_p = False;
463
464   p->watchdog_timeout = p->cycle;
465   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
466   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
467
468 #ifdef NO_LOCKING
469   si->locking_disabled_p = True;
470   si->nolock_reason = "not compiled with locking support";
471   if (p->lock_p)
472     {
473       p->lock_p = False;
474       fprintf (stderr, "%s: not compiled with support for locking.\n",
475                blurb());
476     }
477 #else  /* ! NO_LOCKING */
478   if (p->lock_p && si->locking_disabled_p)
479     {
480       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
481                si->nolock_reason);
482       p->lock_p = False;
483     }
484 #endif /* ! NO_LOCKING */
485
486   get_screenhacks (si);
487
488   if (p->debug_p)
489     {
490       XSynchronize(si->dpy, True);
491       p->verbose_p = True;
492       p->timestamp_p = True;
493       p->initial_delay = 0;
494     }
495
496   blurb_timestamp_p = p->timestamp_p;
497 }
498
499
500 char *
501 timestring (void)
502 {
503   time_t now = time ((time_t *) 0);
504   char *str = (char *) ctime (&now);
505   char *nl = (char *) strchr (str, '\n');
506   if (nl) *nl = 0; /* take off that dang newline */
507   return str;
508 }
509
510 static void initialize (saver_info *si, int argc, char **argv);
511 static void main_loop (saver_info *si);
512
513 int
514 main (int argc, char **argv)
515 {
516   saver_info si;
517   memset(&si, 0, sizeof(si));
518   global_si_kludge = &si;       /* I hate C so much... */
519   initialize (&si, argc, argv);
520   main_loop (&si);              /* doesn't return */
521   return 0;
522 }
523
524
525 int
526 saver_ehandler (Display *dpy, XErrorEvent *error)
527 {
528   saver_info *si = global_si_kludge;    /* I hate C so much... */
529
530   fprintf (real_stderr, "\nX error in %s:\n", blurb());
531   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
532     saver_exit (si, -1);
533   else
534     fprintf (real_stderr, " (nonfatal.)\n");
535   return 0;
536 }
537
538
539 const char *
540 blurb (void)
541 {
542   if (!blurb_timestamp_p)
543     return progname;
544   else
545     {
546       static char buf[255];
547       time_t now = time ((time_t *) 0);
548       char *ct = (char *) ctime (&now);
549       int n = strlen(progname);
550       if (n > 100) n = 99;
551       strncpy(buf, progname, n);
552       buf[n++] = ':';
553       buf[n++] = ' ';
554       strncpy(buf+n, ct+11, 8);
555       strcpy(buf+n+9, ": ");
556       return buf;
557     }
558 }
559
560 static void
561 initialize_connection (saver_info *si, int argc, char **argv)
562 {
563   int i;
564   Widget toplevel_shell;
565
566   /* The X resource database blows up if argv[0] has a "." in it. */
567   {
568     char *s = argv[0];
569     while ((s = strchr (s, '.')))
570       *s = '_';
571   }
572
573   toplevel_shell = XtAppInitialize (&si->app, progclass,
574                                     options, XtNumber (options),
575                                     &argc, argv, defaults, 0, 0);
576
577   si->dpy = XtDisplay (toplevel_shell);
578   si->db = XtDatabase (si->dpy);
579   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
580
581   if(strlen(progname)  > 100) progname [99] = 0;  /* keep it short. */
582
583   db = si->db;  /* resources.c needs this */
584
585   if (argc == 2 && !strcmp (argv[1], "-help"))
586     do_help (si);
587
588   else if (argc == 2 && !strcmp (argv[1], "-debug"))
589     si->prefs.debug_p = True;  /* no resource for this one, out of paranoia. */
590
591   else if (argc > 1)
592     {
593       const char *s = argv[1];
594       fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
595                blurb(), s);
596
597       if (s[0] == '-' && s[1] == '-') s++;
598       if (!strcmp (s, "-activate") ||
599           !strcmp (s, "-deactivate") ||
600           !strcmp (s, "-cycle") ||
601           !strcmp (s, "-next") ||
602           !strcmp (s, "-prev") ||
603           !strcmp (s, "-exit") ||
604           !strcmp (s, "-restart") ||
605           !strcmp (s, "-demo") ||
606           !strcmp (s, "-lock") ||
607           !strcmp (s, "-version") ||
608           !strcmp (s, "-time"))
609         {
610           fprintf (stderr, "\n\
611     However, %s is an option to the `xscreensaver-command' program.\n\
612     The `xscreensaver' program is a daemon that runs in the background.\n\
613     You control a running xscreensaver process by sending it messages\n\
614     with `xscreensaver-command'.  See the man pages for details,\n\
615     or check the web page: http://people.netscape.com/jwz/xscreensaver/\n\n",
616                    s);
617
618           /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
619              suggest that explicitly. */
620           if (!strcmp (s, "-lock"))
621             fprintf (stderr, "\
622     Or perhaps you meant either the \"-lock-mode\" or the\n\
623     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
624         }
625
626       exit (1);
627     }
628   get_resources (si);
629 #ifndef NO_SETUID
630   hack_uid_warn (si);
631 #endif /* NO_SETUID */
632   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
633   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
634   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
635   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
636   XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False);
637   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
638   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
639   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
640   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
641   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
642   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
643   XA_PREV = XInternAtom (si->dpy, "PREV", False);
644   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
645   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
646   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
647
648   si->nscreens = ScreenCount(si->dpy);
649   si->screens = (saver_screen_info *)
650     calloc(sizeof(saver_screen_info), si->nscreens);
651
652   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
653
654   for (i = 0; i < si->nscreens; i++)
655     {
656       saver_screen_info *ssi = &si->screens[i];
657       ssi->global = si;
658       ssi->screen = ScreenOfDisplay (si->dpy, i);
659
660       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
661       ssi->default_visual =
662         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
663
664       ssi->current_visual = ssi->default_visual;
665       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
666
667       if (ssi == si->default_screen)
668         /* Since this is the default screen, use the one already created. */
669         ssi->toplevel_shell = toplevel_shell;
670       else
671         /* Otherwise, each screen must have its own unmapped root widget. */
672         ssi->toplevel_shell =
673           XtVaAppCreateShell(progname, progclass, applicationShellWidgetClass,
674                              si->dpy,
675                              XtNscreen, ssi->screen,
676                              XtNvisual, ssi->current_visual,
677                              XtNdepth,  visual_depth(ssi->screen,
678                                                      ssi->current_visual),
679                              0);
680     }
681 }
682
683
684 static void
685 initialize (saver_info *si, int argc, char **argv)
686 {
687   int i;
688   saver_preferences *p = &si->prefs;
689   Bool initial_demo_mode_p = False;
690   si->version = (char *) malloc (5);
691   memcpy (si->version, screensaver_id + 17, 4);
692   si->version [4] = 0;
693   progname = argv[0]; /* reset later; this is for the benefit of lock_init() */
694
695   if(strlen(progname) > 100) progname[99] = 0;  /* keep it short. */
696
697 #ifdef NO_LOCKING
698   si->locking_disabled_p = True;
699   si->nolock_reason = "not compiled with locking support";
700 #else  /* !NO_LOCKING */
701   si->locking_disabled_p = False;
702
703 # ifdef SCO
704   set_auth_parameters(argc, argv);
705 # endif /* SCO */
706
707   if (! lock_init (argc, argv)) /* before hack_uid() for proper permissions */
708     {
709       si->locking_disabled_p = True;
710       si->nolock_reason = "error getting password";
711     }
712 #endif  /* !NO_LOCKING */
713
714 #ifndef NO_SETUID
715   hack_uid (si);
716 #endif /* NO_SETUID */
717
718   progclass = "XScreenSaver";
719
720   /* remove -initial-demo-mode switch before saving argv */
721   for (i = 1; i < argc; i++)
722     while (!strcmp ("-initial-demo-mode", argv [i]))
723       {
724         int j;
725         initial_demo_mode_p = True;
726         for (j = i; j < argc; j++)
727           argv [j] = argv [j+1];
728         argv [j] = 0;
729         argc--;
730         if (argc <= i) break;
731       }
732   save_argv (argc, argv);
733   initialize_connection (si, argc, argv);
734
735   if (p->verbose_p)
736     printf ("\
737 %s %s, copyright (c) 1991-1998 by Jamie Zawinski <jwz@netscape.com>\n\
738  pid = %d.\n", progname, si->version, (int) getpid ());
739
740   
741   for (i = 0; i < si->nscreens; i++)
742     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
743       exit (1);
744
745   hack_environment (si);
746
747   si->demo_mode_p = initial_demo_mode_p;
748   srandom ((int) time ((time_t *) 0));
749
750   if (p->use_sgi_saver_extension)
751     {
752 #ifdef HAVE_SGI_SAVER_EXTENSION
753       if (! query_sgi_saver_extension (si))
754         {
755           fprintf (stderr,
756          "%s: display %s does not support the SGI SCREEN_SAVER extension.\n",
757                    blurb(), DisplayString (si->dpy));
758           p->use_sgi_saver_extension = False;
759         }
760       else if (p->use_mit_saver_extension)
761         {
762           fprintf (stderr, "%s: SGI SCREEN_SAVER extension used instead\
763  of MIT-SCREEN-SAVER extension.\n",
764                    blurb());
765           p->use_mit_saver_extension = False;
766         }
767       else if (p->use_xidle_extension)
768         {
769           fprintf (stderr,
770          "%s: SGI SCREEN_SAVER extension used instead of XIDLE extension.\n",
771                    blurb());
772           p->use_xidle_extension = False;
773         }
774 #else  /* !HAVE_MIT_SAVER_EXTENSION */
775       fprintf (stderr,
776        "%s: not compiled with support for the SGI SCREEN_SAVER extension.\n",
777                blurb());
778       p->use_sgi_saver_extension = False;
779 #endif /* !HAVE_SGI_SAVER_EXTENSION */
780     }
781
782   if (p->use_mit_saver_extension)
783     {
784 #ifdef HAVE_MIT_SAVER_EXTENSION
785       if (! query_mit_saver_extension (si))
786         {
787           fprintf (stderr,
788          "%s: display %s does not support the MIT-SCREEN-SAVER extension.\n",
789                    blurb(), DisplayString (si->dpy));
790           p->use_mit_saver_extension = False;
791         }
792       else if (p->use_xidle_extension)
793         {
794           fprintf (stderr,
795          "%s: MIT-SCREEN-SAVER extension used instead of XIDLE extension.\n",
796                    blurb());
797           p->use_xidle_extension = False;
798         }
799 #else  /* !HAVE_MIT_SAVER_EXTENSION */
800       fprintf (stderr,
801        "%s: not compiled with support for the MIT-SCREEN-SAVER extension.\n",
802                blurb());
803       p->use_mit_saver_extension = False;
804 #endif /* !HAVE_MIT_SAVER_EXTENSION */
805     }
806
807   if (p->use_xidle_extension)
808     {
809 #ifdef HAVE_XIDLE_EXTENSION
810       int first_event, first_error;
811       if (! XidleQueryExtension (si->dpy, &first_event, &first_error))
812         {
813           fprintf (stderr,
814                    "%s: display %s does not support the XIdle extension.\n",
815                    blurb(), DisplayString (si->dpy));
816           p->use_xidle_extension = False;
817         }
818 #else  /* !HAVE_XIDLE_EXTENSION */
819       fprintf (stderr, "%s: not compiled with support for XIdle.\n",
820                blurb());
821       p->use_xidle_extension = False;
822 #endif /* !HAVE_XIDLE_EXTENSION */
823     }
824
825   /* Call this only after having probed for presence of desired extension. */
826   initialize_screensaver_window (si);
827
828   init_sigchld ();
829
830   disable_builtin_screensaver (si, True);
831
832   if (p->verbose_p && p->use_mit_saver_extension)
833     fprintf (stderr, "%s: using MIT-SCREEN-SAVER server extension.\n",
834              blurb());
835   if (p->verbose_p && p->use_sgi_saver_extension)
836     fprintf (stderr, "%s: using SGI SCREEN_SAVER server extension.\n",
837              blurb());
838   if (p->verbose_p && p->use_xidle_extension)
839     fprintf (stderr, "%s: using XIdle server extension.\n",
840              blurb());
841
842   initialize_stderr (si);
843   XSetErrorHandler (saver_ehandler);
844
845   if (initial_demo_mode_p)
846     /* If the user wants demo mode, don't wait around before doing it. */
847     p->initial_delay = 0;
848
849   if (!p->use_xidle_extension &&
850       !p->use_mit_saver_extension &&
851       !p->use_sgi_saver_extension)
852     {
853       if (p->initial_delay)
854         {
855           if (p->verbose_p)
856             {
857               printf ("%s: waiting for %d second%s...", blurb(),
858                       (int) p->initial_delay,
859                       (p->initial_delay == 1 ? "" : "s"));
860               fflush (stdout);
861             }
862           sleep (p->initial_delay);
863           if (p->verbose_p)
864             printf (" done.\n");
865         }
866       if (p->verbose_p)
867         {
868           printf ("%s: selecting events on extant windows...", blurb());
869           fflush (stdout);
870         }
871
872       /* Select events on the root windows of every screen.  This also selects
873          for window creation events, so that new subwindows will be noticed.
874        */
875       for (i = 0; i < si->nscreens; i++)
876         start_notice_events_timer (si,
877                                    RootWindowOfScreen (si->screens[i].screen));
878
879       if (p->verbose_p)
880         printf (" done.\n");
881     }
882 }
883
884 static void
885 main_loop (saver_info *si)
886 {
887   saver_preferences *p = &si->prefs;
888   while (1)
889     {
890       if (! si->demo_mode_p)
891         sleep_until_idle (si, True);
892
893 #ifndef NO_DEMO_MODE
894       if (si->demo_mode_p)
895         demo_mode (si);
896       else
897 #endif /* !NO_DEMO_MODE */
898         {
899           if (p->verbose_p)
900             printf ("%s: user is idle; waking up at %s.\n", blurb(),
901                     timestring());
902           blank_screen (si);
903           spawn_screenhack (si, True);
904           if (p->cycle)
905             si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer,
906                                             (XtPointer) si);
907
908 #ifndef NO_LOCKING
909           if (p->lock_p && p->lock_timeout == 0)
910             si->locked_p = True;
911           if (p->lock_p && !si->locked_p)
912             /* locked_p might be true already because of ClientMessage */
913             si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout,
914                                            activate_lock_timer,
915                                            (XtPointer) si);
916 #endif /* !NO_LOCKING */
917
918         PASSWD_INVALID:
919
920           sleep_until_idle (si, False); /* until not idle */
921
922 #ifndef NO_LOCKING
923           if (si->locked_p)
924             {
925               Bool val;
926               if (si->locking_disabled_p) abort ();
927               si->dbox_up_p = True;
928
929               /* We used to ungrab the keyboard here, before calling unlock_p()
930                  to pop up the dialog box.  This left the keyboard ungrabbed
931                  for a small window, during an insecure state.  Bennett Todd
932                  was seeing the bahavior that, when the load was high, he could
933                  actually get characters through to a shell under the saver
934                  window (he accidentally typed his password there...)
935
936                  So the ungrab has been moved down into pop_passwd_dialog()
937                  just after the server is grabbed, closing this window
938                  entirely.
939                */
940               /* ungrab_keyboard_and_mouse (si); */
941
942               {
943                 saver_screen_info *ssi = si->default_screen;
944                 suspend_screenhack (si, True);
945                 XUndefineCursor (si->dpy, ssi->screensaver_window);
946                 if (p->verbose_p)
947                   printf ("%s: prompting for password.\n", blurb());
948                 val = unlock_p (si);
949                 if (p->verbose_p && val == False)
950                   printf ("%s: password incorrect!\n", blurb());
951                 si->dbox_up_p = False;
952                 XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
953                 suspend_screenhack (si, False);
954
955                 /* I think this grab is now redundant, but it shouldn't hurt.
956                  */
957                 if (!si->demo_mode_p)
958                   grab_keyboard_and_mouse (si, ssi->screensaver_window,
959                                            ssi->cursor);
960               }
961
962               if (! val)
963                 goto PASSWD_INVALID;
964               si->locked_p = False;
965             }
966 #endif /* !NO_LOCKING */
967
968           /* Let's kill it before unblanking, to get it to stop drawing as
969              soon as possible... */
970           kill_screenhack (si);
971           unblank_screen (si);
972
973           if (si->cycle_id)
974             {
975               XtRemoveTimeOut (si->cycle_id);
976               si->cycle_id = 0;
977             }
978
979 #ifndef NO_LOCKING
980           if (si->lock_id)
981             {
982               XtRemoveTimeOut (si->lock_id);
983               si->lock_id = 0;
984             }
985 #endif /* !NO_LOCKING */
986
987           if (p->verbose_p)
988             printf ("%s: user is active; going to sleep at %s.\n", blurb(),
989                     timestring ());
990         }
991     }
992 }
993
994 \f
995
996 Bool
997 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
998 {
999   saver_preferences *p = &si->prefs;
1000   Atom type = 0;
1001   if (event->xclient.message_type != XA_SCREENSAVER)
1002     {
1003       char *str;
1004       str = XGetAtomName (si->dpy, event->xclient.message_type);
1005       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1006                blurb(), (str ? str : "(null)"));
1007       if (str) XFree (str);
1008       return False;
1009     }
1010   if (event->xclient.format != 32)
1011     {
1012       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1013                blurb(), event->xclient.format);
1014       return False;
1015     }
1016
1017   type = event->xclient.data.l[0];
1018   if (type == XA_ACTIVATE)
1019     {
1020       if (until_idle_p)
1021         {
1022           if (p->verbose_p)
1023             printf ("%s: ACTIVATE ClientMessage received.\n", blurb());
1024           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1025             {
1026               XForceScreenSaver (si->dpy, ScreenSaverActive);
1027               return False;
1028             }
1029           else
1030             {
1031               return True;
1032             }
1033         }
1034       fprintf (stderr,
1035                "%s: ClientMessage ACTIVATE received while already active.\n",
1036                blurb());
1037     }
1038   else if (type == XA_DEACTIVATE)
1039     {
1040       if (! until_idle_p)
1041         {
1042           if (p->verbose_p)
1043             printf ("%s: DEACTIVATE ClientMessage received.\n", blurb());
1044           if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1045             {
1046               XForceScreenSaver (si->dpy, ScreenSaverReset);
1047               return False;
1048             }
1049           else
1050             {
1051               return True;
1052             }
1053         }
1054       fprintf (stderr,
1055                "%s: ClientMessage DEACTIVATE received while inactive.\n",
1056                blurb());
1057     }
1058   else if (type == XA_CYCLE)
1059     {
1060       if (! until_idle_p)
1061         {
1062           if (p->verbose_p)
1063             printf ("%s: CYCLE ClientMessage received.\n", blurb());
1064           if (si->cycle_id)
1065             XtRemoveTimeOut (si->cycle_id);
1066           si->cycle_id = 0;
1067           cycle_timer ((XtPointer) si, 0);
1068           return False;
1069         }
1070       fprintf (stderr, "%s: ClientMessage CYCLE received while inactive.\n",
1071                blurb());
1072     }
1073   else if (type == XA_NEXT || type == XA_PREV)
1074     {
1075       if (p->verbose_p)
1076         printf ("%s: %s ClientMessage received.\n", blurb(),
1077                 (type == XA_NEXT ? "NEXT" : "PREV"));
1078       si->next_mode_p = 1 + (type == XA_PREV);
1079
1080       if (! until_idle_p)
1081         {
1082           if (si->cycle_id)
1083             XtRemoveTimeOut (si->cycle_id);
1084           si->cycle_id = 0;
1085           cycle_timer ((XtPointer) si, 0);
1086         }
1087       else
1088         return True;
1089     }
1090   else if (type == XA_EXIT)
1091     {
1092       /* Ignore EXIT message if the screen is locked. */
1093       if (until_idle_p || !si->locked_p)
1094         {
1095           if (p->verbose_p)
1096             printf ("%s: EXIT ClientMessage received.\n", blurb());
1097           if (! until_idle_p)
1098             {
1099               unblank_screen (si);
1100               kill_screenhack (si);
1101               XSync (si->dpy, False);
1102             }
1103           saver_exit (si, 0);
1104         }
1105       else
1106         fprintf (stderr, "%s: EXIT ClientMessage received while locked.\n",
1107                  blurb());
1108     }
1109   else if (type == XA_RESTART)
1110     {
1111       /* The RESTART message works whether the screensaver is active or not,
1112          unless the screen is locked, in which case it doesn't work.
1113        */
1114       if (until_idle_p || !si->locked_p)
1115         {
1116           if (p->verbose_p)
1117             printf ("%s: RESTART ClientMessage received.\n", blurb());
1118           if (! until_idle_p)
1119             {
1120               unblank_screen (si);
1121               kill_screenhack (si);
1122               XSync (si->dpy, False);
1123             }
1124
1125           /* make sure error message shows up before exit. */
1126           if (real_stderr && stderr != real_stderr)
1127             dup2 (fileno(real_stderr), fileno(stderr));
1128
1129           restart_process (si);
1130           exit (1);     /* shouldn't get here; but if restarting didn't work,
1131                            make this command be the same as EXIT. */
1132         }
1133       else
1134         fprintf(stderr, "%s: RESTART ClientMessage received while locked.\n",
1135                 blurb());
1136     }
1137   else if (type == XA_DEMO)
1138     {
1139 #ifdef NO_DEMO_MODE
1140       fprintf (stderr, "%s: not compiled with support for DEMO mode\n",
1141                blurb());
1142 #else
1143       if (until_idle_p)
1144         {
1145           if (p->verbose_p)
1146             printf ("%s: DEMO ClientMessage received.\n", blurb());
1147           si->demo_mode_p = True;
1148           return True;
1149         }
1150       fprintf (stderr,
1151                "%s: DEMO ClientMessage received while active.\n", blurb());
1152 #endif
1153     }
1154   else if (type == XA_LOCK)
1155     {
1156 #ifdef NO_LOCKING
1157       fprintf (stderr, "%s: not compiled with support for LOCK mode\n",
1158                blurb());
1159 #else
1160       if (si->locking_disabled_p)
1161         fprintf (stderr,
1162                "%s: LOCK ClientMessage received, but locking is disabled.\n",
1163                  blurb());
1164       else if (si->locked_p)
1165         fprintf (stderr,
1166                "%s: LOCK ClientMessage received while already locked.\n",
1167                  blurb());
1168       else
1169         {
1170           si->locked_p = True;
1171           if (p->verbose_p) 
1172             printf ("%s: LOCK ClientMessage received;%s locking.\n",
1173                     blurb(), until_idle_p ? " activating and" : "");
1174
1175           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1176             {
1177               XtRemoveTimeOut (si->lock_id);
1178               si->lock_id = 0;
1179             }
1180
1181           if (until_idle_p)
1182             {
1183               if (p->use_mit_saver_extension || p->use_sgi_saver_extension)
1184                 {
1185                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1186                   return False;
1187                 }
1188               else
1189                 {
1190                   return True;
1191                 }
1192             }
1193         }
1194 #endif
1195     }
1196   else
1197     {
1198       char *str;
1199       str = (type ? XGetAtomName(si->dpy, type) : 0);
1200       if (str)
1201         fprintf (stderr,
1202                  "%s: unrecognised screensaver ClientMessage %s received\n",
1203                  blurb(), str);
1204       else
1205         fprintf (stderr,
1206                 "%s: unrecognised screensaver ClientMessage 0x%x received\n",
1207                  blurb(), (unsigned int) event->xclient.data.l[0]);
1208       if (str) XFree (str);
1209     }
1210   return False;
1211 }