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