ftp://ftp.uni-heidelberg.de/pub/X11/contrib/applications/xscreensaver-1.27.tar.Z
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-1996 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 #include "version.h"
13
14 /*   ========================================================================
15  *   First we wait until the keyboard and mouse become idle for the specified
16  *   amount of time.  We do this in one of three different ways: periodically
17  *   checking with the XIdle server extension; selecting key and mouse events
18  *   on (nearly) all windows; or by waiting for the MIT-SCREEN-SAVER extension
19  *   to send us a "you are idle" event.
20  *
21  *   Then, we map a full screen black window (or, in the case of the 
22  *   MIT-SCREEN-SAVER extension, use the one it gave us.)
23  *
24  *   We place a __SWM_VROOT property on this window, so that newly-started
25  *   clients will think that this window is a "virtual root" window.
26  *
27  *   If there is an existing "virtual root" window (one that already had
28  *   an __SWM_VROOT property) then we remove that property from that window.
29  *   Otherwise, clients would see that window (the real virtual root) instead
30  *   of ours (the impostor.)
31  *
32  *   Then we pick a random program to run, and start it.  Two assumptions 
33  *   are made about this program: that it has been specified with whatever
34  *   command-line options are necessary to make it run on the root window;
35  *   and that it has been compiled with vroot.h, so that it is able to find
36  *   the root window when a virtual-root window manager (or this program) is
37  *   running.
38  *
39  *   Then, we wait for keyboard or mouse events to be generated on the window.
40  *   When they are, we kill the inferior process, unmap the window, and restore
41  *   the __SWM_VROOT property to the real virtual root window if there was one.
42  *
43  *   While we are waiting, we also set up timers so that, after a certain 
44  *   amount of time has passed, we can start a different screenhack.  We do
45  *   this by killing the running child process with SIGTERM, and then starting
46  *   a new one in the same way.
47  *
48  *   If there was a real virtual root, meaning that we removed the __SWM_VROOT
49  *   property from it, meaning we must (absolutely must) restore it before we
50  *   exit, then we set up signal handlers for most signals (SIGINT, SIGTERM,
51  *   etc.) that do this.  Most Xlib and Xt routines are not reentrant, so it
52  *   is not generally safe to call them from signal handlers; however, this
53  *   program spends most of its time waiting, so the window of opportunity 
54  *   when code could be called reentrantly is fairly small; and also, the worst
55  *   that could happen is that the call would fail.  If we've gotten one of
56  *   these signals, then we're on our way out anyway.  If we didn't restore the
57  *   __SWM_VROOT property, that would be very bad, so it's worth a shot.  Note
58  *   that this means that, if you're using a virtual-root window manager, you
59  *   can really fuck up the world by killing this process with "kill -9".
60  *
61  *   This program accepts ClientMessages of type SCREENSAVER; these messages
62  *   may contain the atom ACTIVATE or DEACTIVATE, meaning to turn the 
63  *   screensaver on or off now, regardless of the idleness of the user,
64  *   and a few other things.  The included "xscreensaver_command" program
65  *   sends these messsages.
66  *
67  *   If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER
68  *   extensions, then we do the XAutoLock trick: notice every window that
69  *   gets created, and wait 30 seconds or so until its creating process has
70  *   settled down, and then select KeyPress events on those windows which
71  *   already select for KeyPress events.  It's important that we not select
72  *   KeyPress on windows which don't select them, because that would
73  *   interfere with event propagation.  This will break if any program
74  *   changes its event mask to contain KeyRelease or PointerMotion more than
75  *   30 seconds after creating the window, but that's probably pretty rare.
76  *   
77  *   The reason that we can't select KeyPresses on windows that don't have
78  *   them already is that, when dispatching a KeyPress event, X finds the
79  *   lowest (leafmost) window in the hierarchy on which *any* client selects
80  *   for KeyPress, and sends the event to that window.  This means that if a
81  *   client had a window with subwindows, and expected to receive KeyPress
82  *   events on the parent window instead of the subwindows, then that client
83  *   would malfunction if some other client selected KeyPress events on the
84  *   subwindows.  It is an incredible misdesign that one client can make
85  *   another client malfunction in this way.
86  *
87  *   To detect mouse motion, we periodically wake up and poll the mouse
88  *   position and button/modifier state, and notice when something has
89  *   changed.  We make this check every five seconds by default, and since the
90  *   screensaver timeout has a granularity of one minute, this makes the
91  *   chance of a false positive very small.  We could detect mouse motion in
92  *   the same way as keyboard activity, but that would suffer from the same
93  *   "client changing event mask" problem that the KeyPress events hack does.
94  *   I think polling is more reliable.
95  *
96  *   None of this crap happens if we're using one of the extensions, so install
97  *   one of them if the description above sounds just too flaky to live.  It
98  *   is, but those are your choices.
99  *
100  *   A third idle-detection option could be implemented (but is not): when
101  *   running on the console display ($DISPLAY is `localhost`:0) and we're on a
102  *   machine where /dev/tty and /dev/mouse have reasonable last-modification
103  *   times, we could just stat() those.  But the incremental benefit of
104  *   implementing this is really small, so forget I said anything.
105  *
106  *   Debugging hints:
107  *     - Have a second terminal handy.
108  *     - Be careful where you set your breakpoints, you don't want this to
109  *       stop under the debugger with the keyboard grabbed or the blackout
110  *       window exposed.
111  *     - you probably can't set breakpoints in functions that are called on
112  *       the other side of a call to fork() -- if your clients are dying 
113  *       with signal 5, Trace/BPT Trap, you're losing in this way.
114  *     - If you aren't using a server extension, don't leave this stopped
115  *       under the debugger for very long, or the X input buffer will get
116  *       huge because of the keypress events it's selecting for.  This can
117  *       make your X server wedge with "no more input buffers."
118  *       
119  * ======================================================================== */
120
121 #if __STDC__
122 #include <stdlib.h>
123 #include <unistd.h>
124 #endif
125
126 #include <stdio.h>
127 #include <X11/Xlib.h>
128 #include <X11/Xatom.h>
129 #include <X11/Intrinsic.h>
130 #include <X11/Xos.h>
131 #include <X11/Xmu/Error.h>
132
133 #ifdef HAVE_XIDLE_EXTENSION
134 #include <X11/extensions/xidle.h>
135 #endif /* HAVE_XIDLE_EXTENSION */
136
137 #ifdef HAVE_MIT_SAVER_EXTENSION
138 #include <X11/extensions/scrnsaver.h>
139 #endif /* HAVE_MIT_SAVER_EXTENSION */
140
141 #ifdef HAVE_SGI_SAVER_EXTENSION
142 #include <X11/extensions/XScreenSaver.h>
143 #endif /* HAVE_SGI_SAVER_EXTENSION */
144
145 #include "yarandom.h"
146 #include "xscreensaver.h"
147
148 extern char *get_string_resource P((char *, char *));
149 extern Bool get_boolean_resource P((char *, char *));
150 extern int get_integer_resource P((char *, char *));
151 extern unsigned int get_minutes_resource P((char *, char *));
152 extern unsigned int get_seconds_resource P((char *, char *));
153
154 extern Visual *get_visual_resource P((Display *, char *, char *));
155 extern int get_visual_depth P((Display *, Visual *));
156
157 extern void notice_events_timer P((XtPointer closure, XtIntervalId *timer));
158 extern void cycle_timer P((void *junk1, XtPointer junk2));
159 extern void activate_lock_timer P((void *junk1, XtPointer junk2));
160 extern void sleep_until_idle P((Bool until_idle_p));
161
162 extern void ensure_no_screensaver_running P((void));
163 extern void initialize_screensaver_window P((void));
164 extern void disable_builtin_screensaver P((void));
165
166 extern void hack_environment P((void));
167 extern void grab_keyboard_and_mouse P((void));
168 extern void ungrab_keyboard_and_mouse P((void));
169
170 extern void save_argv P((int argc, char **argv));
171
172 extern void initialize_stderr P((void));
173
174 char *screensaver_version;
175 char *progname;
176 char *progclass;
177 XrmDatabase db;
178
179 XtAppContext app;
180
181 Display *dpy;
182 Screen *screen;
183 Visual *visual;
184 int visual_depth;
185
186 Widget toplevel_shell;
187
188 Time lock_timeout;
189
190 extern Time timeout;
191 extern Time cycle;
192 #ifndef NO_LOCKING
193 extern Time passwd_timeout;
194 #endif
195 extern Time pointer_timeout;
196 extern Time notice_events_timeout;
197 extern XtIntervalId lock_id, cycle_id;
198
199 Bool use_xidle_extension;
200 Bool use_mit_saver_extension;
201 Bool use_sgi_saver_extension;
202 Bool verbose_p;
203 Bool lock_p, locked_p;
204
205 extern char **screenhacks;
206 extern int screenhacks_count;
207 extern char *shell;
208 extern int nice_inferior;
209 extern Window screensaver_window;
210 extern Cursor cursor;
211 extern Colormap cmap, cmap2;
212 extern Bool fade_p, unfade_p;
213 extern int fade_seconds, fade_ticks;
214 extern Bool install_cmap_p;
215 extern Bool locking_disabled_p;
216 extern char *nolock_reason;
217 extern Bool demo_mode_p;
218 extern Bool dbox_up_p;
219 extern int next_mode_p;
220
221 #ifdef HAVE_MIT_SAVER_EXTENSION
222 int mit_saver_ext_event_number = 0;
223 int mit_saver_ext_error_number = 0;
224 #endif /* HAVE_MIT_SAVER_EXTENSION */
225
226 #ifdef HAVE_SGI_SAVER_EXTENSION
227 int sgi_saver_ext_event_number = 0;
228 int sgi_saver_ext_error_number = 0;
229 #endif /* HAVE_SGI_SAVER_EXTENSION */
230
231 static time_t initial_delay;
232
233 extern Atom XA_VROOT, XA_XSETROOT_ID;
234 extern Atom XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
235
236 static Atom XA_SCREENSAVER;
237 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
238 static Atom XA_EXIT, XA_RESTART, XA_DEMO, XA_LOCK;
239
240 #ifdef NO_MOTIF /* kludge */
241 Bool demo_mode_p = 0;
242 Bool dbox_up_p = 0;
243 #endif
244
245 \f
246 #ifdef NO_DEMO_MODE
247 # define demo_mode() abort()
248 #else
249 extern void demo_mode P((void));
250 #endif
251 \f
252 static XrmOptionDescRec options [] = {
253   { "-timeout",            ".timeout",          XrmoptionSepArg, 0 },
254   { "-cycle",              ".cycle",            XrmoptionSepArg, 0 },
255   { "-idelay",             ".initialDelay",     XrmoptionSepArg, 0 },
256   { "-nice",               ".nice",             XrmoptionSepArg, 0 },
257   { "-visual",             ".visualID",         XrmoptionSepArg, 0 },
258   { "-lock-timeout",       ".lockTimeout",      XrmoptionSepArg, 0 },
259   { "-install",            ".installColormap",  XrmoptionNoArg, "on" },
260   { "-no-install",         ".installColormap",  XrmoptionNoArg, "off" },
261   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
262   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
263   { "-xidle-extension",    ".xidleExtension",   XrmoptionNoArg, "on" },
264   { "-no-xidle-extension", ".xidleExtension",   XrmoptionNoArg, "off" },
265   { "-mit-extension",      ".mitSaverExtension",XrmoptionNoArg, "on" },
266   { "-no-mit-extension",   ".mitSaverExtension",XrmoptionNoArg, "off" },
267   { "-sgi-extension",      ".sgiSaverExtension",XrmoptionNoArg, "on" },
268   { "-no-sgi-extension",   ".sgiSaverExtension",XrmoptionNoArg, "off" },
269   { "-lock",               ".lock",             XrmoptionNoArg, "on" },
270   { "-no-lock",            ".lock",             XrmoptionNoArg, "off" }
271 };
272
273 static char *defaults[] = {
274 #include "XScreenSaver.ad.h"
275  0
276 };
277
278 static void
279 do_help P((void))
280 {
281   printf ("\
282 xscreensaver %s, copyright (c) 1991-1996 by Jamie Zawinski <jwz@netscape.com>.\n\
283 The standard Xt command-line options are accepted; other options include:\n\
284 \n\
285     -timeout <minutes>         When the screensaver should activate.\n\
286     -cycle <minutes>           How long to let each hack run.\n\
287     -idelay <seconds>          How long to sleep before startup.\n\
288     -visual <id-or-class>      Which X visual to run on.\n\
289     -demo                      Enter interactive demo mode on startup.\n\
290     -install                   Install a private colormap.\n\
291     -no-install                Don't.\n\
292     -verbose                   Be loud.\n\
293     -silent                    Don't.\n\
294     -xidle-extension           Use the R5 XIdle server extension.\n\
295     -no-xidle-extension        Don't.\n\
296     -mit-extension             Use the R6 MIT_SCREEN_SAVER server extension.\n\
297     -no-mit-extension          Don't.\n\
298     -sgi-extension             Use the SGI SCREEN-SAVER server extension.\n\
299     -no-sgi-extension          Don't.\n\
300     -lock                      Require a password before deactivating.\n\
301     -no-lock                   Don't.\n\
302     -lock-timeout <minutes>    Grace period before locking; default 0.\n\
303     -help                      This message.\n\
304 \n\
305 Use the `xscreensaver-command' program to control a running screensaver.\n\
306 \n\
307 The *programs, *colorPrograms, and *monoPrograms resources control which\n\
308 graphics demos will be launched by the screensaver.  See the man page for\n\
309 more details.\n\n",
310           screensaver_version);
311
312 #ifdef NO_LOCKING
313   printf ("Support for locking was not enabled at compile-time.\n");
314 #endif
315 #ifdef NO_DEMO_MODE
316   printf ("Support for demo mode was not enabled at compile-time.\n");
317 #endif
318 #if !defined(HAVE_XIDLE_EXTENSION) && !defined(HAVE_MIT_SAVER_EXTENSION) && !defined(HAVE_SGI_SAVER_EXTENSION)
319   printf ("Support for the XIDLE, SCREEN_SAVER, and MIT-SCREEN-SAVER server\
320  extensions\nwas not enabled at compile-time.\n");
321 #endif /* !HAVE_XIDLE_EXTENSION && !HAVE_MIT_SAVER_EXTENSION && !HAVE_SGI_SAVER_EXTENSION */
322
323   fflush (stdout);
324   exit (1);
325 }
326
327
328 static void
329 get_screenhacks P((void))
330 {
331   char *data[3];
332   int i, hacks_size = 10;
333
334   data[0] = get_string_resource ("programs", "Programs");
335   data[1] = ((CellsOfScreen (screen) <= 2)
336              ? get_string_resource ("monoPrograms", "MonoPrograms")
337              : get_string_resource ("colorPrograms", "ColorPrograms"));
338   data[2] = 0;
339   if (! data[0]) data[0] = data[1], data[1] = 0;
340
341   screenhacks = (char **) malloc (sizeof (char *) * hacks_size);
342   screenhacks_count = 0;
343
344   for (i = 0; data[i]; i++)
345     {
346       int j = 0;
347       char *d = data [i];
348       int size = strlen (d);
349       while (j < size)
350         {
351           int end, start = j;
352           if (d[j] == ' ' || d[j] == '\t' || d[j] == '\n' || d[j] == 0)
353             {
354               j++;
355               continue;
356             }
357           if (hacks_size <= screenhacks_count)
358             screenhacks = (char **) realloc (screenhacks,
359                                              (hacks_size = hacks_size * 2) *
360                                              sizeof (char *));
361           screenhacks [screenhacks_count++] = d + j;
362           while (d[j] != 0 && d[j] != '\n')
363             j++;
364           end = j;
365           while (j > start && (d[j-1] == ' ' || d[j-1] == '\t'))
366             j--;
367           d[j] = 0;
368           j = end + 1;
369         }
370     }
371
372   /* shrink all whitespace to one space, for the benefit of the "demo"
373      mode display.  We only do this when we can easily tell that the
374      whitespace is not significant (no shell metachars).
375    */
376   for (i = 0; i < screenhacks_count; i++)
377     {
378       char *s = screenhacks [i];
379       char *s2;
380       int L = strlen (s);
381       int j, k;
382       for (j = 0; j < L; j++)
383         {
384           switch (s[j])
385             {
386             case '\'': case '"': case '`': case '\\':
387               goto DONE;
388             case '\t':
389               s[j] = ' ';
390             case ' ':
391               k = 0;
392               for (s2 = s+j+1; *s2 == ' ' || *s2 == '\t'; s2++)
393                 k++;
394               if (k > 0)
395                 for (s2 = s + j + 1; *s2; s2++)
396                   s2 [0] = s2 [k];
397               break;
398             }
399         }
400     DONE:
401       ;
402     }
403
404   if (screenhacks_count)
405     {
406       /* Shrink down the screenhacks array to be only as big as it needs to.
407          This doesn't really matter at all. */
408       screenhacks = (char **)
409         realloc (screenhacks, ((screenhacks_count + 1) * sizeof(char *)));
410       screenhacks [screenhacks_count] = 0;
411     }
412   else
413     {
414       free (screenhacks);
415       screenhacks = 0;
416     }
417 }
418
419
420 static void
421 get_resources P((void))
422 {
423   /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
424   visual          = get_visual_resource (dpy, "visualID", "VisualID");
425   timeout         = 1000 * get_minutes_resource ("timeout", "Time");
426   cycle           = 1000 * get_minutes_resource ("cycle",   "Time");
427   lock_timeout    = 1000 * get_minutes_resource ("lockTimeout", "Time");
428   nice_inferior   = get_integer_resource ("nice", "Nice");
429   verbose_p       = get_boolean_resource ("verbose", "Boolean");
430   lock_p          = get_boolean_resource ("lock", "Boolean");
431   install_cmap_p  = get_boolean_resource ("installColormap", "Boolean");
432   fade_p          = get_boolean_resource ("fade", "Boolean");
433   unfade_p        = get_boolean_resource ("unfade", "Boolean");
434   fade_seconds    = get_seconds_resource ("fadeSeconds", "Time");
435   fade_ticks      = get_integer_resource ("fadeTicks", "Integer");
436   shell           = get_string_resource ("bourneShell", "BourneShell");
437   initial_delay   = get_seconds_resource ("initialDelay", "Time");
438   pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
439   notice_events_timeout = 1000 * get_seconds_resource ("windowCreationTimeout",
440                                                        "Time");
441 #ifndef NO_LOCKING
442   passwd_timeout  = 1000 * get_seconds_resource ("passwdTimeout", "Time");
443   if (passwd_timeout == 0) passwd_timeout = 30000;
444 #endif
445   if (timeout < 10000) timeout = 10000;
446   if (cycle != 0 && cycle < 2000) cycle = 2000;
447   if (pointer_timeout == 0) pointer_timeout = 5000;
448   if (notice_events_timeout == 0) notice_events_timeout = 10000;
449   if (fade_seconds == 0 || fade_ticks == 0) fade_p = False;
450   if (! fade_p) unfade_p = False;
451
452   visual_depth = get_visual_depth (dpy, visual);
453
454   if (visual_depth <= 1 || CellsOfScreen (screen) <= 2)
455     install_cmap_p = False;
456
457 #ifdef NO_LOCKING
458   locking_disabled_p = True;
459   nolock_reason = "not compiled with locking support";
460   if (lock_p)
461     {
462       lock_p = False;
463       fprintf (stderr, "%s: %snot compiled with support for locking.\n",
464                progname, (verbose_p ? "## " : ""));
465     }
466 #else  /* ! NO_LOCKING */
467   if (lock_p && locking_disabled_p)
468     {
469       fprintf (stderr, "%s: %slocking is disabled (%s).\n", progname,
470                (verbose_p ? "## " : ""), nolock_reason);
471       lock_p = False;
472     }
473 #endif /* ! NO_LOCKING */
474
475   /* don't set use_xidle_extension unless it is explicitly specified */
476   if (get_string_resource ("xidleExtension", "Boolean"))
477     use_xidle_extension = get_boolean_resource ("xidleExtension", "Boolean");
478   else
479 #ifdef HAVE_XIDLE_EXTENSION     /* pick a default */
480     use_xidle_extension = True;
481 #else  /* !HAVE_XIDLE_EXTENSION */
482     use_xidle_extension = False;
483 #endif /* !HAVE_XIDLE_EXTENSION */
484
485   /* don't set use_saver_extension unless it is explicitly specified */
486   if (get_string_resource ("mitSaverExtension", "Boolean"))
487     use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
488                                                     "Boolean");
489   else
490 #ifdef HAVE_MIT_SAVER_EXTENSION /* pick a default */
491     use_mit_saver_extension = True;
492 #else  /* !HAVE_MIT_SAVER_EXTENSION */
493     use_mit_saver_extension = False;
494 #endif /* !HAVE_MIT_SAVER_EXTENSION */
495
496   /* don't set use_saver_extension unless it is explicitly specified */
497   if (get_string_resource ("sgiSaverExtension", "Boolean"))
498     use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
499                                                     "Boolean");
500   else
501 #ifdef HAVE_SGI_SAVER_EXTENSION /* pick a default */
502     use_sgi_saver_extension = True;
503 #else  /* !HAVE_SGI_SAVER_EXTENSION */
504     use_sgi_saver_extension = False;
505 #endif /* !HAVE_SGI_SAVER_EXTENSION */
506
507   get_screenhacks ();
508 }
509
510 char *
511 timestring P((void))
512 {
513   long now = time ((time_t *) 0);
514   char *str = (char *) ctime (&now);
515   char *nl = (char *) strchr (str, '\n');
516   if (nl) *nl = 0; /* take off that dang newline */
517   return str;
518 }
519
520 #ifdef NO_SETUID
521 # define hack_uid()
522 # define hack_uid_warn()
523 #else /* !NO_SETUID */
524 extern void hack_uid P((void));
525 extern void hack_uid_warn P((void));
526 #endif /* NO_SETUID */
527
528
529 #ifndef NO_LOCKING
530 extern Bool unlock_p P((Widget));
531 extern Bool lock_init P((void));
532 #endif
533
534 static void initialize P((int argc, char **argv));
535 static void main_loop P((void));
536
537 void
538 main (argc, argv)
539      int argc;
540      char **argv;
541 {
542   initialize (argc, argv);
543   main_loop ();
544 }
545
546
547 static int
548 saver_ehandler (dpy, error)
549      Display *dpy;
550      XErrorEvent *error;
551 {
552   fprintf (real_stderr, "\nX error in %s:\n", progname);
553   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
554     exit (-1);
555   else
556     fprintf (real_stderr, " (nonfatal.)\n");
557   return 0;
558 }
559
560 static void
561 #if __STDC__
562 initialize_connection (int argc, char **argv)
563 #else
564 initialize_connection (argc, argv)
565      int argc;
566      char **argv;
567 #endif
568 {
569   toplevel_shell = XtAppInitialize (&app, progclass,
570                                     options, XtNumber (options),
571                                     &argc, argv, defaults, 0, 0);
572
573   dpy = XtDisplay (toplevel_shell);
574   screen = XtScreen (toplevel_shell);
575   db = XtDatabase (dpy);
576   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
577
578   if (argc == 2 && !strcmp (argv[1], "-help"))
579     do_help ();
580   else if (argc > 1)
581     {
582       fprintf (stderr, "%s: unknown option %s\n", progname, argv [1]);
583       exit (1);
584     }
585   get_resources ();
586   hack_uid_warn ();
587   hack_environment ();
588   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
589   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
590   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION", False);
591   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
592   XA_XSETROOT_ID = XInternAtom (dpy, "_XSETROOT_ID", False);
593   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
594   XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
595   XA_RESTART = XInternAtom (dpy, "RESTART", False);
596   XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
597   XA_NEXT = XInternAtom (dpy, "NEXT", False);
598   XA_PREV = XInternAtom (dpy, "PREV", False);
599   XA_EXIT = XInternAtom (dpy, "EXIT", False);
600   XA_DEMO = XInternAtom (dpy, "DEMO", False);
601   XA_LOCK = XInternAtom (dpy, "LOCK", False);
602 }
603
604 #ifdef HAVE_MIT_SAVER_EXTENSION
605
606 static int
607 ignore_all_errors_ehandler (dpy, error)
608      Display *dpy;
609      XErrorEvent *error;
610 {
611   return 0;
612 }
613
614 static void
615 init_mit_saver_extension ()
616 {
617   XID kill_id;
618   Atom kill_type;
619   Window root = RootWindowOfScreen (screen);
620   Pixmap blank_pix = XCreatePixmap (dpy, root, 1, 1, 1);
621
622   /* Kill off the old MIT-SCREEN-SAVER client if there is one.
623      This tends to generate X errors, though (possibly due to a bug
624      in the server extension itself?) so just ignore errors here. */
625   if (XScreenSaverGetRegistered (dpy, XScreenNumberOfScreen (screen),
626                                  &kill_id, &kill_type)
627       && kill_id != blank_pix)
628     {
629       int (*old_handler) ();
630       old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
631       XKillClient (dpy, kill_id);
632       XSync (dpy, False);
633       XSetErrorHandler (old_handler);
634     }
635
636   XScreenSaverSelectInput (dpy, root, ScreenSaverNotifyMask);
637
638   XScreenSaverRegister (dpy, XScreenNumberOfScreen (screen),
639                         (XID) blank_pix, XA_PIXMAP);
640 }
641 #endif /* HAVE_MIT_SAVER_EXTENSION */
642
643
644 #ifdef HAVE_SGI_SAVER_EXTENSION
645
646 static void
647 init_sgi_saver_extension ()
648 {
649   if (! XScreenSaverEnable (dpy, XScreenNumberOfScreen(screen)))
650     {
651       fprintf (stderr,
652        "%s: %sSGI SCREEN_SAVER extension exists, but can't be initialized;\n\
653                 perhaps some other screensaver program is already running?\n",
654                progname, (verbose_p ? "## " : ""));
655       use_sgi_saver_extension = False;
656     }
657 }
658
659 #endif /* HAVE_SGI_SAVER_EXTENSION */
660
661
662 extern void init_sigchld P((void));
663
664 static void
665 initialize (argc, argv)
666      int argc;
667      char **argv;
668 {
669   Bool initial_demo_mode_p = False;
670   screensaver_version = (char *) malloc (5);
671   memcpy (screensaver_version, screensaver_id + 17, 4);
672   screensaver_version [4] = 0;
673   progname = argv[0]; /* reset later; this is for the benefit of lock_init() */
674
675 #ifdef NO_LOCKING
676   locking_disabled_p = True;
677   nolock_reason = "not compiled with locking support";
678 #else
679   locking_disabled_p = False;
680
681 #ifdef SCO
682   set_auth_parameters(argc, argv);
683 #endif
684
685   if (! lock_init ())   /* before hack_uid() for proper permissions */
686     {
687       locking_disabled_p = True;
688       nolock_reason = "error getting password";
689     }
690 #endif
691
692   hack_uid ();
693   progclass = "XScreenSaver";
694
695   /* remove -demo switch before saving argv */
696   {
697     int i;
698     for (i = 1; i < argc; i++)
699       while (!strcmp ("-demo", argv [i]))
700         {
701           int j;
702           initial_demo_mode_p = True;
703           for (j = i; j < argc; j++)
704             argv [j] = argv [j+1];
705           argv [j] = 0;
706           argc--;
707           if (argc <= i) break;
708         }
709   }
710   save_argv (argc, argv);
711   initialize_connection (argc, argv);
712   ensure_no_screensaver_running ();
713
714   if (verbose_p)
715     printf ("\
716 %s %s, copyright (c) 1991-1996 by Jamie Zawinski <jwz@netscape.com>.\n\
717  pid = %d.\n", progname, screensaver_version, getpid ());
718   ensure_no_screensaver_running ();
719
720   demo_mode_p = initial_demo_mode_p;
721   screensaver_window = 0;
722   cursor = 0;
723   srandom ((int) time ((time_t *) 0));
724   cycle_id = 0;
725   lock_id = 0;
726   locked_p = False;
727
728   if (use_sgi_saver_extension)
729     {
730 #ifdef HAVE_SGI_SAVER_EXTENSION
731       if (! XScreenSaverQueryExtension (dpy,
732                                         &sgi_saver_ext_event_number,
733                                         &sgi_saver_ext_error_number))
734         {
735           fprintf (stderr,
736          "%s: %sdisplay %s does not support the SGI SCREEN_SAVER extension.\n",
737                    progname, (verbose_p ? "## " : ""), DisplayString (dpy));
738           use_sgi_saver_extension = False;
739         }
740       else if (use_mit_saver_extension)
741         {
742           fprintf (stderr, "%s: %sSGI SCREEN_SAVER extension used instead\
743  of MIT-SCREEN-SAVER extension.\n",
744                    progname, (verbose_p ? "## " : ""));
745           use_mit_saver_extension = False;
746         }
747       else if (use_xidle_extension)
748         {
749           fprintf (stderr,
750          "%s: %sSGI SCREEN_SAVER extension used instead of XIDLE extension.\n",
751                    progname, (verbose_p ? "## " : ""));
752           use_xidle_extension = False;
753         }
754 #else  /* !HAVE_MIT_SAVER_EXTENSION */
755       fprintf (stderr,
756        "%s: %snot compiled with support for the SGI SCREEN_SAVER extension.\n",
757                progname, (verbose_p ? "## " : ""));
758       use_sgi_saver_extension = False;
759 #endif /* !HAVE_SGI_SAVER_EXTENSION */
760     }
761
762   if (use_mit_saver_extension)
763     {
764 #ifdef HAVE_MIT_SAVER_EXTENSION
765       if (! XScreenSaverQueryExtension (dpy,
766                                         &mit_saver_ext_event_number,
767                                         &mit_saver_ext_error_number))
768         {
769           fprintf (stderr,
770          "%s: %sdisplay %s does not support the MIT-SCREEN-SAVER extension.\n",
771                    progname, (verbose_p ? "## " : ""), DisplayString (dpy));
772           use_mit_saver_extension = False;
773         }
774       else if (use_xidle_extension)
775         {
776           fprintf (stderr,
777          "%s: %sMIT-SCREEN-SAVER extension used instead of XIDLE extension.\n",
778                    progname, (verbose_p ? "## " : ""));
779           use_xidle_extension = False;
780         }
781 #else  /* !HAVE_MIT_SAVER_EXTENSION */
782       fprintf (stderr,
783        "%s: %snot compiled with support for the MIT-SCREEN-SAVER extension.\n",
784                progname, (verbose_p ? "## " : ""));
785       use_mit_saver_extension = False;
786 #endif /* !HAVE_MIT_SAVER_EXTENSION */
787     }
788
789   if (use_xidle_extension)
790     {
791 #ifdef HAVE_XIDLE_EXTENSION
792       int first_event, first_error;
793       if (! XidleQueryExtension (dpy, &first_event, &first_error))
794         {
795           fprintf (stderr,
796                    "%s: %sdisplay %s does not support the XIdle extension.\n",
797                    progname, (verbose_p ? "## " : ""), DisplayString (dpy));
798           use_xidle_extension = False;
799         }
800 #else  /* !HAVE_XIDLE_EXTENSION */
801       fprintf (stderr, "%s: %snot compiled with support for XIdle.\n",
802                progname, (verbose_p ? "## " : ""));
803       use_xidle_extension = False;
804 #endif /* !HAVE_XIDLE_EXTENSION */
805     }
806
807   /* Call this only after having probed for presence of desired extension. */
808   initialize_screensaver_window ();
809
810   init_sigchld ();
811
812   disable_builtin_screensaver ();
813
814 #ifdef HAVE_MIT_SAVER_EXTENSION
815   if (use_mit_saver_extension)
816     init_mit_saver_extension ();
817 #endif /* HAVE_MIT_SAVER_EXTENSION */
818
819 #ifdef HAVE_SGI_SAVER_EXTENSION
820   if (use_sgi_saver_extension)
821     init_sgi_saver_extension ();
822 #endif /* HAVE_SGI_SAVER_EXTENSION */
823
824   if (verbose_p && use_mit_saver_extension)
825     fprintf (stderr, "%s: using MIT-SCREEN-SAVER server extension.\n",
826              progname);
827   if (verbose_p && use_sgi_saver_extension)
828     fprintf (stderr, "%s: using SGI SCREEN_SAVER server extension.\n",
829              progname);
830   if (verbose_p && use_xidle_extension)
831     fprintf (stderr, "%s: using XIdle server extension.\n",
832              progname);
833
834   initialize_stderr ();
835   XSetErrorHandler (saver_ehandler);
836
837   if (initial_demo_mode_p)
838     /* If the user wants demo mode, don't wait around before doing it. */
839     initial_delay = 0;
840
841   if (!use_xidle_extension &&
842       !use_mit_saver_extension &&
843       !use_sgi_saver_extension)
844     {
845       if (initial_delay)
846         {
847           if (verbose_p)
848             {
849               printf ("%s: waiting for %d second%s...", progname,
850                       (int) initial_delay, (initial_delay == 1 ? "" : "s"));
851               fflush (stdout);
852             }
853           sleep (initial_delay);
854           if (verbose_p)
855             printf (" done.\n");
856         }
857       if (verbose_p)
858         {
859           printf ("%s: selecting events on extant windows...", progname);
860           fflush (stdout);
861         }
862       notice_events_timer ((XtPointer)
863                            RootWindowOfScreen (XtScreen (toplevel_shell)),
864                            0);
865       if (verbose_p)
866         printf (" done.\n");
867     }
868 }
869
870
871 extern void suspend_screenhack P((Bool suspend_p));
872
873 static void
874 main_loop ()
875 {
876   while (1)
877     {
878       if (! demo_mode_p)
879         sleep_until_idle (True);
880
881       if (demo_mode_p)
882         demo_mode ();
883       else
884         {
885           if (verbose_p)
886             printf ("%s: user is idle; waking up at %s.\n", progname,
887                     timestring());
888           blank_screen ();
889           spawn_screenhack (True);
890           if (cycle)
891             cycle_id = XtAppAddTimeOut (app, cycle,
892                                         (XtTimerCallbackProc)cycle_timer, 0);
893
894 #ifndef NO_LOCKING
895           if (lock_p && lock_timeout == 0)
896             locked_p = True;
897           if (lock_p && !locked_p)
898             /* locked_p might be true already because of ClientMessage */
899             lock_id = XtAppAddTimeOut (app,lock_timeout,
900                                        (XtTimerCallbackProc)
901                                        activate_lock_timer,0);
902 #endif
903
904         PASSWD_INVALID:
905
906           sleep_until_idle (False); /* until not idle */
907
908 #ifndef NO_LOCKING
909           if (locked_p)
910             {
911               Bool val;
912               if (locking_disabled_p) abort ();
913               dbox_up_p = True;
914
915               /* We used to ungrab the keyboard here, before calling unlock_p()
916                  to pop up the dialog box.  This left the keyboard ungrabbed
917                  for a small window, during an insecure state.  Bennett Todd
918                  was seeing the bahavior that, when the load was high, he could
919                  actually get characters through to a shell under the saver
920                  window (he accidentally typed his password there...)
921
922                  So the ungrab has been moved down into pop_passwd_dialog()
923                  just after the server is grabbed, closing this window
924                  entirely.
925                */
926               /* ungrab_keyboard_and_mouse (); */
927
928               suspend_screenhack (True);
929               XUndefineCursor (dpy, screensaver_window);
930               if (verbose_p)
931                 printf ("%s: prompting for password.\n", progname);
932               val = unlock_p (toplevel_shell);
933               if (verbose_p && val == False)
934                 printf ("%s: password incorrect!\n", progname);
935               dbox_up_p = False;
936               XDefineCursor (dpy, screensaver_window, cursor);
937               suspend_screenhack (False);
938
939               /* I think this grab is now redundant, but it shouldn't hurt. */
940               grab_keyboard_and_mouse ();
941
942               if (! val)
943                 goto PASSWD_INVALID;
944               locked_p = False;
945             }
946 #endif
947           unblank_screen ();
948           kill_screenhack ();
949           if (cycle_id)
950             {
951               XtRemoveTimeOut (cycle_id);
952               cycle_id = 0;
953             }
954 #ifndef NO_LOCKING
955           if (lock_id)
956             {
957               XtRemoveTimeOut (lock_id);
958               lock_id = 0;
959             }
960 #endif
961           if (verbose_p)
962             printf ("%s: user is active; going to sleep at %s.\n", progname,
963                     timestring ());
964         }
965     }
966 }
967
968 \f
969
970 Bool
971 handle_clientmessage (event, until_idle_p)
972      XEvent *event;
973      Bool until_idle_p;
974 {
975   Atom type = 0;
976   if (event->xclient.message_type != XA_SCREENSAVER)
977     {
978       char *str;
979       str = XGetAtomName (dpy, event->xclient.message_type);
980       fprintf (stderr, "%s: %sunrecognised ClientMessage type %s received\n",
981                progname, (verbose_p ? "## " : ""),
982                (str ? str : "(null)"));
983       if (str) XFree (str);
984       return False;
985     }
986   if (event->xclient.format != 32)
987     {
988       fprintf (stderr, "%s: %sClientMessage of format %d received, not 32\n",
989                progname, (verbose_p ? "## " : ""), event->xclient.format);
990       return False;
991     }
992   type = event->xclient.data.l[0];
993   if (type == XA_ACTIVATE)
994     {
995       if (until_idle_p)
996         {
997           if (verbose_p)
998             printf ("%s: ACTIVATE ClientMessage received.\n", progname);
999           if (use_mit_saver_extension || use_sgi_saver_extension)
1000             {
1001               XForceScreenSaver (dpy, ScreenSaverActive);
1002               return False;
1003             }
1004           else
1005             {
1006               return True;
1007             }
1008         }
1009       fprintf (stderr,
1010                "%s: %sClientMessage ACTIVATE received while already active.\n",
1011                progname, (verbose_p ? "## " : ""));
1012     }
1013   else if (type == XA_DEACTIVATE)
1014     {
1015       if (! until_idle_p)
1016         {
1017           if (verbose_p)
1018             printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
1019           if (use_mit_saver_extension || use_sgi_saver_extension)
1020             {
1021               XForceScreenSaver (dpy, ScreenSaverReset);
1022               return False;
1023             }
1024           else
1025             {
1026               return True;
1027             }
1028         }
1029       fprintf (stderr,
1030                "%s: %sClientMessage DEACTIVATE received while inactive.\n",
1031                progname, (verbose_p ? "## " : ""));
1032     }
1033   else if (type == XA_CYCLE)
1034     {
1035       if (! until_idle_p)
1036         {
1037           if (verbose_p)
1038             printf ("%s: CYCLE ClientMessage received.\n", progname);
1039           if (cycle_id)
1040             XtRemoveTimeOut (cycle_id);
1041           cycle_id = 0;
1042           cycle_timer (0, 0);
1043           return False;
1044         }
1045       fprintf (stderr,
1046                "%s: %sClientMessage CYCLE received while inactive.\n",
1047                progname, (verbose_p ? "## " : ""));
1048     }
1049   else if (type == XA_NEXT || type == XA_PREV)
1050     {
1051       if (verbose_p)
1052         printf ("%s: %s ClientMessage received.\n", progname,
1053                 (type == XA_NEXT ? "NEXT" : "PREV"));
1054       next_mode_p = 1 + (type == XA_PREV);
1055
1056       if (! until_idle_p)
1057         {
1058           if (cycle_id)
1059             XtRemoveTimeOut (cycle_id);
1060           cycle_id = 0;
1061           cycle_timer (0, 0);
1062         }
1063       else
1064         return True;
1065     }
1066   else if (type == XA_EXIT)
1067     {
1068       /* Ignore EXIT message if the screen is locked. */
1069       if (until_idle_p || !locked_p)
1070         {
1071           if (verbose_p)
1072             printf ("%s: EXIT ClientMessage received.\n", progname);
1073           if (! until_idle_p)
1074             {
1075               unblank_screen ();
1076               kill_screenhack ();
1077               XSync (dpy, False);
1078             }
1079           exit (0);
1080         }
1081       else
1082         fprintf (stderr, "%s: %sEXIT ClientMessage received while locked.\n",
1083                  progname, (verbose_p ? "## " : ""));
1084     }
1085   else if (type == XA_RESTART)
1086     {
1087       /* The RESTART message works whether the screensaver is active or not,
1088          unless the screen is locked, in which case it doesn't work.
1089        */
1090       if (until_idle_p || !locked_p)
1091         {
1092           if (verbose_p)
1093             printf ("%s: RESTART ClientMessage received.\n", progname);
1094           if (! until_idle_p)
1095             {
1096               unblank_screen ();
1097               kill_screenhack ();
1098               XSync (dpy, False);
1099             }
1100           restart_process ();
1101         }
1102       else
1103         fprintf(stderr, "%s: %sRESTART ClientMessage received while locked.\n",
1104                 progname, (verbose_p ? "## " : ""));
1105     }
1106   else if (type == XA_DEMO)
1107     {
1108 #ifdef NO_DEMO_MODE
1109       fprintf (stderr,
1110                "%s: %snot compiled with support for DEMO mode\n",
1111                progname, (verbose_p ? "## " : ""));
1112 #else
1113       if (until_idle_p)
1114         {
1115           if (verbose_p)
1116             printf ("%s: DEMO ClientMessage received.\n", progname);
1117           demo_mode_p = True;
1118           return True;
1119         }
1120       fprintf (stderr,
1121                "%s: %sDEMO ClientMessage received while active.\n",
1122                progname, (verbose_p ? "## " : ""));
1123 #endif
1124     }
1125   else if (type == XA_LOCK)
1126     {
1127 #ifdef NO_LOCKING
1128       fprintf (stderr, "%s: %snot compiled with support for LOCK mode\n",
1129                progname, (verbose_p ? "## " : ""));
1130 #else
1131       if (locking_disabled_p)
1132         fprintf (stderr,
1133                "%s: %sLOCK ClientMessage received, but locking is disabled.\n",
1134                  progname, (verbose_p ? "## " : ""));
1135       else if (locked_p)
1136         fprintf (stderr,
1137                "%s: %sLOCK ClientMessage received while already locked.\n",
1138                  progname, (verbose_p ? "## " : ""));
1139       else
1140         {
1141           locked_p = True;
1142           if (verbose_p) 
1143             printf ("%s: LOCK ClientMessage received;%s locking.\n",
1144                     progname, until_idle_p ? " activating and" : "");
1145
1146           if (lock_id)  /* we're doing it now, so lose the timeout */
1147             {
1148               XtRemoveTimeOut (lock_id);
1149               lock_id = 0;
1150             }
1151
1152           if (until_idle_p)
1153             {
1154               if (use_mit_saver_extension || use_sgi_saver_extension)
1155                 {
1156                   XForceScreenSaver (dpy, ScreenSaverActive);
1157                   return False;
1158                 }
1159               else
1160                 {
1161                   return True;
1162                 }
1163             }
1164         }
1165 #endif
1166     }
1167   else
1168     {
1169       char *str;
1170       str = (type ? XGetAtomName(dpy, type) : 0);
1171       if (str)
1172         fprintf (stderr,
1173                  "%s: %sunrecognised screensaver ClientMessage %s received\n",
1174                  progname, (verbose_p ? "## " : ""), str);
1175       else
1176         fprintf (stderr,
1177                 "%s: %sunrecognised screensaver ClientMessage 0x%x received\n",
1178                  progname, (verbose_p ? "## " : ""),
1179                  (unsigned int) event->xclient.data.l[0]);
1180       if (str) XFree (str);
1181     }
1182   return False;
1183 }