67daea81eed6df9e39dd76203f5b8d31dbd4c811
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-2002 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 such programs do not seem to
74  *   occur in nature (I've never seen it happen in all these years.)
75  *
76  *   The reason that we can't select KeyPresses on windows that don't have
77  *   them already is that, when dispatching a KeyPress event, X finds the
78  *   lowest (leafmost) window in the hierarchy on which *any* client selects
79  *   for KeyPress, and sends the event to that window.  This means that if a
80  *   client had a window with subwindows, and expected to receive KeyPress
81  *   events on the parent window instead of the subwindows, then that client
82  *   would malfunction if some other client selected KeyPress events on the
83  *   subwindows.  It is an incredible misdesign that one client can make
84  *   another client malfunction in this way.
85  *
86  *   To detect mouse motion, we periodically wake up and poll the mouse
87  *   position and button/modifier state, and notice when something has
88  *   changed.  We make this check every five seconds by default, and since the
89  *   screensaver timeout has a granularity of one minute, this makes the
90  *   chance of a false positive very small.  We could detect mouse motion in
91  *   the same way as keyboard activity, but that would suffer from the same
92  *   "client changing event mask" problem that the KeyPress events hack does.
93  *   I think polling is more reliable.
94  *
95  *   None of this crap happens if we're using one of the extensions, so install
96  *   one of them if the description above sounds just too flaky to live.  It
97  *   is, but those are your choices.
98  *
99  *   A third idle-detection option could be implemented (but is not): when
100  *   running on the console display ($DISPLAY is `localhost`:0) and we're on a
101  *   machine where /dev/tty and /dev/mouse have reasonable last-modification
102  *   times, we could just stat() those.  But the incremental benefit of
103  *   implementing this is really small, so forget I said anything.
104  *
105  *   Debugging hints:
106  *     - Have a second terminal handy.
107  *     - Be careful where you set your breakpoints, you don't want this to
108  *       stop under the debugger with the keyboard grabbed or the blackout
109  *       window exposed.
110  *     - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
111  *       to keep your emacs window alive even when xscreensaver has grabbed.
112  *     - Go read the code related to `debug_p'.
113  *     - You probably can't set breakpoints in functions that are called on
114  *       the other side of a call to fork() -- if your subprocesses are
115  *       dying with signal 5, Trace/BPT Trap, you're losing in this way.
116  *     - If you aren't using a server extension, don't leave this stopped
117  *       under the debugger for very long, or the X input buffer will get
118  *       huge because of the keypress events it's selecting for.  This can
119  *       make your X server wedge with "no more input buffers."
120  *
121  * ======================================================================== */
122
123 #ifdef HAVE_CONFIG_H
124 # include "config.h"
125 #endif
126
127 #include <stdio.h>
128 #include <ctype.h>
129 #include <X11/Xlib.h>
130 #include <X11/Xatom.h>
131 #include <X11/Intrinsic.h>
132 #include <X11/StringDefs.h>
133 #include <X11/Shell.h>
134 #include <X11/Xos.h>
135 #include <netdb.h>      /* for gethostbyname() */
136 #ifdef HAVE_XMU
137 # ifndef VMS
138 #  include <X11/Xmu/Error.h>
139 # else  /* !VMS */
140 #  include <Xmu/Error.h>
141 # endif /* !VMS */
142 #else  /* !HAVE_XMU */
143 # include "xmu.h"
144 #endif /* !HAVE_XMU */
145
146 #ifdef HAVE_XIDLE_EXTENSION
147 # include <X11/extensions/xidle.h>
148 #endif /* HAVE_XIDLE_EXTENSION */
149
150 #include "xscreensaver.h"
151 #include "version.h"
152 #include "yarandom.h"
153 #include "resources.h"
154 #include "visual.h"
155 #include "usleep.h"
156
157 saver_info *global_si_kludge = 0;       /* I hate C so much... */
158
159 char *progname = 0;
160 char *progclass = 0;
161 XrmDatabase db = 0;
162
163
164 static Atom XA_SCREENSAVER_RESPONSE;
165 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
166 static Atom XA_RESTART, XA_SELECT;
167 static Atom XA_THROTTLE, XA_UNTHROTTLE;
168 Atom XA_DEMO, XA_PREFS, XA_EXIT, XA_LOCK, XA_BLANK;
169
170 \f
171 static XrmOptionDescRec options [] = {
172
173   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
174   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
175
176   /* xscreensaver-demo uses this one */
177   { "-nosplash",           ".splash",           XrmoptionNoArg, "off" },
178   { "-no-splash",          ".splash",           XrmoptionNoArg, "off" },
179
180   /* useful for debugging */
181   { "-no-capture-stderr",  ".captureStderr",    XrmoptionNoArg, "off" },
182
183   /* There's really no reason to have these command-line args; they just
184      lead to confusion when the .xscreensaver file has conflicting values.
185    */
186 #if 0
187   { "-splash",             ".splash",           XrmoptionNoArg, "on" },
188   { "-capture-stderr",     ".captureStderr",    XrmoptionNoArg, "on" },
189   { "-timeout",            ".timeout",          XrmoptionSepArg, 0 },
190   { "-cycle",              ".cycle",            XrmoptionSepArg, 0 },
191   { "-lock-mode",          ".lock",             XrmoptionNoArg, "on" },
192   { "-no-lock-mode",       ".lock",             XrmoptionNoArg, "off" },
193   { "-no-lock",            ".lock",             XrmoptionNoArg, "off" },
194   { "-lock-timeout",       ".lockTimeout",      XrmoptionSepArg, 0 },
195   { "-lock-vts",           ".lockVTs",          XrmoptionNoArg, "on" },
196   { "-no-lock-vts",        ".lockVTs",          XrmoptionNoArg, "off" },
197   { "-visual",             ".visualID",         XrmoptionSepArg, 0 },
198   { "-install",            ".installColormap",  XrmoptionNoArg, "on" },
199   { "-no-install",         ".installColormap",  XrmoptionNoArg, "off" },
200   { "-timestamp",          ".timestamp",        XrmoptionNoArg, "on" },
201   { "-xidle-extension",    ".xidleExtension",   XrmoptionNoArg, "on" },
202   { "-no-xidle-extension", ".xidleExtension",   XrmoptionNoArg, "off" },
203   { "-mit-extension",      ".mitSaverExtension",XrmoptionNoArg, "on" },
204   { "-no-mit-extension",   ".mitSaverExtension",XrmoptionNoArg, "off" },
205   { "-sgi-extension",      ".sgiSaverExtension",XrmoptionNoArg, "on" },
206   { "-no-sgi-extension",   ".sgiSaverExtension",XrmoptionNoArg, "off" },
207   { "-proc-interrupts",    ".procInterrupts",   XrmoptionNoArg, "on" },
208   { "-no-proc-interrupts", ".procInterrupts",   XrmoptionNoArg, "off" },
209   { "-idelay",             ".initialDelay",     XrmoptionSepArg, 0 },
210   { "-nice",               ".nice",             XrmoptionSepArg, 0 },
211 #endif /* 0 */
212 };
213
214 static char *defaults[] = {
215 #include "XScreenSaver_ad.h"
216  0
217 };
218
219 #ifdef _VROOT_H_
220 ERROR!  You must not include vroot.h in this file.
221 #endif
222
223 static void
224 do_help (saver_info *si)
225 {
226   fflush (stdout);
227   fflush (stderr);
228   fprintf (stdout, "\
229 xscreensaver %s, copyright (c) 1991-2002 by Jamie Zawinski <jwz@jwz.org>\n\
230 \n\
231   All xscreensaver configuration is via the `~/.xscreensaver' file.\n\
232   Rather than editing that file by hand, just run `xscreensaver-demo':\n\
233   that program lets you configure the screen saver graphically,\n\
234   including timeouts, locking, and display modes.\n\
235 \n\
236   Just getting started?  Try this:\n\
237 \n\
238         xscreensaver &\n\
239         xscreensaver-demo\n\
240 \n\
241   For updates, online manual, and FAQ, please see the web page:\n\
242 \n\
243        http://www.jwz.org/xscreensaver/\n\
244 \n",
245           si->version);
246   fflush (stdout);
247   fflush (stderr);
248   exit (1);
249 }
250
251
252 char *
253 timestring (void)
254 {
255   time_t now = time ((time_t *) 0);
256   char *str = (char *) ctime (&now);
257   char *nl = (char *) strchr (str, '\n');
258   if (nl) *nl = 0; /* take off that dang newline */
259   return str;
260 }
261
262 static Bool blurb_timestamp_p = False;   /* kludge */
263
264 const char *
265 blurb (void)
266 {
267   if (!blurb_timestamp_p)
268     return progname;
269   else
270     {
271       static char buf[255];
272       char *ct = timestring();
273       int n = strlen(progname);
274       if (n > 100) n = 99;
275       strncpy(buf, progname, n);
276       buf[n++] = ':';
277       buf[n++] = ' ';
278       strncpy(buf+n, ct+11, 8);
279       strcpy(buf+n+9, ": ");
280       return buf;
281     }
282 }
283
284
285 int
286 saver_ehandler (Display *dpy, XErrorEvent *error)
287 {
288   saver_info *si = global_si_kludge;    /* I hate C so much... */
289   int i;
290
291   if (!real_stderr) real_stderr = stderr;
292
293   fprintf (real_stderr, "\n"
294            "#######################################"
295            "#######################################\n\n"
296            "%s: X Error!  PLEASE REPORT THIS BUG.\n",
297            blurb());
298
299   for (i = 0; i < si->nscreens; i++)
300     fprintf (real_stderr, "%s: screen %d: 0x%x, 0x%x, 0x%x\n",
301              blurb(), i,
302              RootWindowOfScreen (si->screens[i].screen),
303              si->screens[i].real_vroot,
304              si->screens[i].screensaver_window);
305
306   fprintf (real_stderr, "\n"
307            "#######################################"
308            "#######################################\n\n");
309
310   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
311     {
312       fprintf (real_stderr, "\n");
313       if (si->prefs.xsync_p)
314         {
315           saver_exit (si, -1, "because of synchronous X Error");
316         }
317       else
318         {
319           fprintf (real_stderr,
320                    "#######################################"
321                    "#######################################\n\n");
322           fprintf (real_stderr,
323    "    If at all possible, please re-run xscreensaver with the command\n"
324    "    line arguments `-sync -verbose -no-capture', and reproduce this\n"
325    "    bug.  That will cause xscreensaver to dump a `core' file to the\n"
326    "    current directory.  Please include the stack trace from that core\n"
327    "    file in your bug report.  *DO NOT* mail the core file itself!\n"
328    "    That won't work.\n"
329    "\n"
330    "    http://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
331    "    the most useful bug reports, and how to examine core files.\n"
332    "\n"
333    "    The more information you can provide, the better.  But please\n"
334    "    report this bug, regardless!\n"
335    "\n");
336           fprintf (real_stderr,
337                    "#######################################"
338                    "#######################################\n\n");
339
340           saver_exit (si, -1, 0);
341         }
342     }
343   else
344     fprintf (real_stderr, " (nonfatal.)\n");
345   return 0;
346 }
347
348
349 /* This error handler is used only while the X connection is being set up;
350    after we've got a connection, we don't use this handler again.  The only
351    reason for having this is so that we can present a more idiot-proof error
352    message than "cannot open display."
353  */
354 static void 
355 startup_ehandler (String name, String type, String class,
356                   String defalt,  /* one can't even spel properly
357                                      in this joke of a language */
358                   String *av, Cardinal *ac)
359 {
360   char fmt[512];
361   String p[10];
362   saver_info *si = global_si_kludge;    /* I hate C so much... */
363   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
364   *fmt = 0;
365   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
366                             fmt, sizeof(fmt)-1, *db);
367
368   fprintf (stderr, "%s: ", blurb());
369
370   memset (p, 0, sizeof(p));
371   if (*ac > countof (p)) *ac = countof (p);
372   memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
373   fprintf (stderr, fmt,         /* Did I mention that I hate C? */
374            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
375   fprintf (stderr, "\n");
376
377   describe_uids (si, stderr);
378
379   if (si->orig_uid && !strncmp (si->orig_uid, "root/", 5))
380     {
381       fprintf (stderr, "\n"
382           "%s: This is probably because you're logging in as root.  You\n"
383 "              shouldn't log in as root: you should log in as a normal user,\n"
384 "              and then `su' as needed.  If you insist on logging in as\n"
385 "              root, you will have to turn off X's security features before\n"
386 "              xscreensaver will work.\n"
387                "\n"
388 "              Please read the manual and FAQ for more information:\n",
389                blurb());
390     }
391   else
392     {
393       fprintf (stderr, "\n"
394           "%s: Errors at startup are usually authorization problems.\n"
395 "              But you're not logging in as root (good!) so something\n"
396 "              else must be wrong.  Did you read the manual and the FAQ?\n",
397            blurb());
398     }
399
400   fprintf (stderr, "\n"
401           "              http://www.jwz.org/xscreensaver/faq.html\n"
402           "              http://www.jwz.org/xscreensaver/man.html\n"
403           "\n");
404
405   fflush (stderr);
406   fflush (stdout);
407   exit (1);
408 }
409
410 \f
411 /* The zillions of initializations.
412  */
413
414 /* Set progname, version, etc.  This is done very early.
415  */
416 static void
417 set_version_string (saver_info *si, int *argc, char **argv)
418 {
419   progclass = "XScreenSaver";
420
421   /* progname is reset later, after we connect to X. */
422   progname = strrchr(argv[0], '/');
423   if (progname) progname++;
424   else progname = argv[0];
425
426   if (strlen(progname) > 100)   /* keep it short. */
427     progname[99] = 0;
428
429   /* The X resource database blows up if argv[0] has a "." in it. */
430   {
431     char *s = argv[0];
432     while ((s = strchr (s, '.')))
433       *s = '_';
434   }
435
436   si->version = (char *) malloc (5);
437   memcpy (si->version, screensaver_id + 17, 4);
438   si->version [4] = 0;
439 }
440
441
442 /* Initializations that potentially take place as a priveleged user:
443    If the xscreensaver executable is setuid root, then these initializations
444    are run as root, before discarding privileges.
445  */
446 static void
447 privileged_initialization (saver_info *si, int *argc, char **argv)
448 {
449 #ifndef NO_LOCKING
450   /* before hack_uid() for proper permissions */
451   lock_priv_init (*argc, argv, si->prefs.verbose_p);
452 #endif /* NO_LOCKING */
453
454   hack_uid (si);
455 }
456
457
458 /* Figure out what locking mechanisms are supported.
459  */
460 static void
461 lock_initialization (saver_info *si, int *argc, char **argv)
462 {
463 #ifdef NO_LOCKING
464   si->locking_disabled_p = True;
465   si->nolock_reason = "not compiled with locking support";
466 #else /* !NO_LOCKING */
467
468   /* Finish initializing locking, now that we're out of privileged code. */
469   if (! lock_init (*argc, argv, si->prefs.verbose_p))
470     {
471       si->locking_disabled_p = True;
472       si->nolock_reason = "error getting password";
473     }
474 #endif /* NO_LOCKING */
475
476   hack_uid (si);
477 }
478
479
480 /* Open the connection to the X server, and intern our Atoms.
481  */
482 static Widget
483 connect_to_server (saver_info *si, int *argc, char **argv)
484 {
485   Widget toplevel_shell;
486
487 #ifdef HAVE_PUTENV
488   char *d = getenv ("DISPLAY");
489   if (!d || !*d)
490     {
491       char ndpy[] = "DISPLAY=:0.0";
492       /* if (si->prefs.verbose_p) */      /* sigh, too early to test this... */
493         fprintf (stderr,
494                  "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
495                  blurb(), ndpy+8);
496       if (putenv (ndpy))
497         abort ();
498     }
499 #endif /* HAVE_PUTENV */
500
501   XSetErrorHandler (saver_ehandler);
502
503   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
504   toplevel_shell = XtAppInitialize (&si->app, progclass,
505                                     options, XtNumber (options),
506                                     argc, argv, defaults, 0, 0);
507   XtAppSetErrorMsgHandler (si->app, 0);
508
509   si->dpy = XtDisplay (toplevel_shell);
510   si->prefs.db = XtDatabase (si->dpy);
511   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
512
513   if(strlen(progname) > 100)    /* keep it short. */
514     progname [99] = 0;
515
516   db = si->prefs.db;    /* resources.c needs this */
517
518   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
519   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
520   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
521   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
522   XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False);
523   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
524                                          False);
525   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
526   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
527   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
528   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
529   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
530   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
531   XA_PREV = XInternAtom (si->dpy, "PREV", False);
532   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
533   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
534   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
535   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
536   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
537   XA_BLANK = XInternAtom (si->dpy, "BLANK", False);
538   XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
539   XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
540
541   return toplevel_shell;
542 }
543
544
545 /* Handle the command-line arguments that were not handled for us by Xt.
546    Issue an error message and exit if there are unknown options.
547  */
548 static void
549 process_command_line (saver_info *si, int *argc, char **argv)
550 {
551   int i;
552   for (i = 1; i < *argc; i++)
553     {
554       if (!strcmp (argv[i], "-debug"))
555         /* no resource for this one, out of paranoia. */
556         si->prefs.debug_p = True;
557
558       else if (!strcmp (argv[i], "-h") ||
559                !strcmp (argv[i], "-help") ||
560                !strcmp (argv[i], "--help"))
561         do_help (si);
562
563       else
564         {
565           const char *s = argv[i];
566           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
567                    blurb(), s);
568
569           if (s[0] == '-' && s[1] == '-') s++;
570           if (!strcmp (s, "-activate") ||
571               !strcmp (s, "-deactivate") ||
572               !strcmp (s, "-cycle") ||
573               !strcmp (s, "-next") ||
574               !strcmp (s, "-prev") ||
575               !strcmp (s, "-exit") ||
576               !strcmp (s, "-restart") ||
577               !strcmp (s, "-demo") ||
578               !strcmp (s, "-prefs") ||
579               !strcmp (s, "-preferences") ||
580               !strcmp (s, "-lock") ||
581               !strcmp (s, "-version") ||
582               !strcmp (s, "-time"))
583             {
584
585               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
586                 fprintf (stderr, "\n\
587     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
588               else
589                 fprintf (stderr, "\n\
590     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
591
592               fprintf (stderr, "\
593     The `xscreensaver' program is a daemon that runs in the background.\n\
594     You control a running xscreensaver process by sending it messages\n\
595     with `xscreensaver-demo' or `xscreensaver-command'.\n\
596 .   See the man pages for details, or check the web page:\n\
597     http://www.jwz.org/xscreensaver/\n\n");
598
599               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
600                  suggest that explicitly. */
601               if (!strcmp (s, "-lock"))
602                 fprintf (stderr, "\
603     Or perhaps you meant either the \"-lock-mode\" or the\n\
604     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
605             }
606
607           exit (1);
608         }
609     }
610 }
611
612
613 /* Print out the xscreensaver banner to the tty if applicable;
614    Issue any other warnings that are called for at this point.
615  */
616 static void
617 print_banner (saver_info *si)
618 {
619   saver_preferences *p = &si->prefs;
620
621   /* This resource gets set some time before the others, so that we know
622      whether to print the banner (and so that the banner gets printed before
623      any resource-database-related error messages.)
624    */
625   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
626
627   /* Ditto, for the locking_disabled_p message. */
628   p->lock_p = get_boolean_resource ("lock", "Boolean");
629
630   if (p->verbose_p)
631     fprintf (stderr,
632              "%s %s, copyright (c) 1991-2002 "
633              "by Jamie Zawinski <jwz@jwz.org>.\n",
634              progname, si->version);
635
636   if (p->debug_p)
637     fprintf (stderr, "\n"
638              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
639              "\n"
640              "\tNote that in debug mode, the xscreensaver window will only\n"
641              "\tcover the left half of the screen.  (The idea is that you\n"
642              "\tcan still see debugging output in a shell, if you position\n"
643              "\tit on the right side of the screen.)\n"
644              "\n"
645              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
646              "\tuntrusted environments.\n"
647              "\n",
648              blurb());
649
650   if (p->verbose_p)
651     {
652       if (!si->uid_message || !*si->uid_message)
653         describe_uids (si, stderr);
654       else
655         {
656           if (si->orig_uid && *si->orig_uid)
657             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
658                      blurb(), si->orig_uid);
659           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
660         }
661
662       fprintf (stderr, "%s: in process %lu.\n", blurb(),
663                (unsigned long) getpid());
664     }
665
666   /* If locking was not able to be initalized for some reason, explain why.
667      (This has to be done after we've read the lock_p resource.)
668    */
669   if (p->lock_p && si->locking_disabled_p)
670     {
671       p->lock_p = False;
672       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
673                si->nolock_reason);
674       if (strstr (si->nolock_reason, "passw"))
675         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
676                  "consult the manual.\n", blurb());
677       else if (strstr (si->nolock_reason, "running as "))
678         fprintf (stderr, 
679                  "%s: locking only works when xscreensaver is launched\n"
680                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
681                  "\t See the manual for details.\n",
682                  blurb());
683     }
684 }
685
686
687 /* Examine all of the display's screens, and populate the `saver_screen_info'
688    structures.  Make sure this is called after hack_environment() sets $PATH.
689  */
690 static void
691 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
692 {
693   Bool found_any_writable_cells = False;
694   int i;
695
696   si->nscreens = ScreenCount(si->dpy);
697   si->screens = (saver_screen_info *)
698     calloc(sizeof(saver_screen_info), si->nscreens);
699
700   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
701
702   for (i = 0; i < si->nscreens; i++)
703     {
704       saver_screen_info *ssi = &si->screens[i];
705       ssi->global = si;
706       ssi->screen = ScreenOfDisplay (si->dpy, i);
707       ssi->number = 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       /* Execute a subprocess to find the GL visual. */
717       ssi->best_gl_visual = get_best_gl_visual (ssi);
718
719       if (ssi == si->default_screen)
720         /* Since this is the default screen, use the one already created. */
721         ssi->toplevel_shell = toplevel_shell;
722       else
723         /* Otherwise, each screen must have its own unmapped root widget. */
724         ssi->toplevel_shell =
725           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
726                               si->dpy,
727                               XtNscreen, ssi->screen,
728                               XtNvisual, ssi->current_visual,
729                               XtNdepth,  visual_depth (ssi->screen,
730                                                        ssi->current_visual),
731                               0);
732
733       if (! found_any_writable_cells)
734         {
735           /* Check to see whether fading is ever possible -- if any of the
736              screens on the display has a PseudoColor visual, then fading can
737              work (on at least some screens.)  If no screen has a PseudoColor
738              visual, then don't bother ever trying to fade, because it will
739              just cause a delay without causing any visible effect.
740           */
741           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
742               get_visual (ssi->screen, "PseudoColor", True, False) ||
743               get_visual (ssi->screen, "GrayScale", True, False))
744             found_any_writable_cells = True;
745         }
746     }
747
748   si->fading_possible_p = found_any_writable_cells;
749
750 #ifdef HAVE_XF86VMODE_GAMMA
751   si->fading_possible_p = True;  /* if we can gamma fade, go for it */
752 #endif
753 }
754
755
756 /* If any server extensions have been requested, try and initialize them.
757    Issue warnings if requests can't be honored.
758  */
759 static void
760 initialize_server_extensions (saver_info *si)
761 {
762   saver_preferences *p = &si->prefs;
763
764   Bool server_has_xidle_extension_p = False;
765   Bool server_has_sgi_saver_extension_p = False;
766   Bool server_has_mit_saver_extension_p = False;
767   Bool system_has_proc_interrupts_p = False;
768   const char *piwhy = 0;
769
770   si->using_xidle_extension = p->use_xidle_extension;
771   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
772   si->using_mit_saver_extension = p->use_mit_saver_extension;
773   si->using_proc_interrupts = p->use_proc_interrupts;
774
775 #ifdef HAVE_XIDLE_EXTENSION
776   server_has_xidle_extension_p = query_xidle_extension (si);
777 #endif
778 #ifdef HAVE_SGI_SAVER_EXTENSION
779   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
780 #endif
781 #ifdef HAVE_MIT_SAVER_EXTENSION
782   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
783 #endif
784 #ifdef HAVE_PROC_INTERRUPTS
785   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
786 #endif
787
788   if (!server_has_xidle_extension_p)
789     si->using_xidle_extension = False;
790   else if (p->verbose_p)
791     {
792       if (si->using_xidle_extension)
793         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
794       else
795         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
796     }
797
798   if (!server_has_sgi_saver_extension_p)
799     si->using_sgi_saver_extension = False;
800   else if (p->verbose_p)
801     {
802       if (si->using_sgi_saver_extension)
803         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
804       else
805         fprintf (stderr,
806                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
807                  blurb());
808     }
809
810   if (!server_has_mit_saver_extension_p)
811     si->using_mit_saver_extension = False;
812   else if (p->verbose_p)
813     {
814       if (si->using_mit_saver_extension)
815         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
816                  blurb());
817       else
818         fprintf (stderr,
819                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
820                  blurb());
821     }
822
823   if (!system_has_proc_interrupts_p)
824     {
825       si->using_proc_interrupts = False;
826       if (p->verbose_p && piwhy)
827         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
828                  piwhy);
829     }
830   else if (p->verbose_p)
831     {
832       if (si->using_proc_interrupts)
833         fprintf (stderr,
834                  "%s: consulting /proc/interrupts for keyboard activity.\n",
835                  blurb());
836       else
837         fprintf (stderr,
838                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
839                  blurb());
840     }
841 }
842
843
844 /* For the case where we aren't using an server extensions, select user events
845    on all the existing windows, and launch timers to select events on
846    newly-created windows as well.
847
848    If a server extension is being used, this does nothing.
849  */
850 static void
851 select_events (saver_info *si)
852 {
853   saver_preferences *p = &si->prefs;
854   int i;
855
856   if (si->using_xidle_extension ||
857       si->using_mit_saver_extension ||
858       si->using_sgi_saver_extension)
859     return;
860
861   if (p->initial_delay)
862     {
863       if (p->verbose_p)
864         {
865           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
866                    (int) p->initial_delay/1000,
867                    (p->initial_delay == 1000 ? "" : "s"));
868           fflush (stderr);
869           fflush (stdout);
870         }
871       usleep (p->initial_delay);
872       if (p->verbose_p)
873         fprintf (stderr, " done.\n");
874     }
875
876   if (p->verbose_p)
877     {
878       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
879       fflush (stderr);
880       fflush (stdout);
881     }
882
883   /* Select events on the root windows of every screen.  This also selects
884      for window creation events, so that new subwindows will be noticed.
885    */
886   for (i = 0; i < si->nscreens; i++)
887     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
888                                False);
889
890   if (p->verbose_p)
891     fprintf (stderr, " done.\n");
892 }
893
894
895 void
896 maybe_reload_init_file (saver_info *si)
897 {
898   saver_preferences *p = &si->prefs;
899   if (init_file_changed_p (p))
900     {
901       if (p->verbose_p)
902         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
903                  blurb(), init_file_name());
904
905       load_init_file (p);
906
907       /* If a server extension is in use, and p->timeout has changed,
908          we need to inform the server of the new timeout. */
909       disable_builtin_screensaver (si, False);
910
911       /* If the DPMS settings in the init file have changed,
912          change the settings on the server to match. */
913       sync_server_dpms_settings (si->dpy,
914                                  (p->dpms_enabled_p  &&
915                                   p->mode != DONT_BLANK),
916                                  p->dpms_standby / 1000,
917                                  p->dpms_suspend / 1000,
918                                  p->dpms_off / 1000,
919                                  False);
920     }
921 }
922
923
924 /* Loop forever:
925
926        - wait until the user is idle;
927        - blank the screen;
928        - wait until the user is active;
929        - unblank the screen;
930        - repeat.
931
932  */
933 static void
934 main_loop (saver_info *si)
935 {
936   saver_preferences *p = &si->prefs;
937   Bool ok_to_unblank;
938
939   while (1)
940     {
941       Bool was_locked = False;
942       sleep_until_idle (si, True);
943
944       if (p->verbose_p)
945         {
946           if (si->demoing_p)
947             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
948                      si->selection_mode, timestring());
949           else
950             fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
951                      timestring());
952         }
953
954       maybe_reload_init_file (si);
955
956       if (p->mode == DONT_BLANK)
957         {
958           if (p->verbose_p)
959             fprintf (stderr, "%s: idle with blanking disabled at %s.\n",
960                      blurb(), timestring());
961
962           /* Go around the loop and wait for the next bout of idleness,
963              or for the init file to change, or for a remote command to
964              come in, or something.
965            */
966           continue;
967         }
968
969       if (! blank_screen (si))
970         {
971           /* We were unable to grab either the keyboard or mouse.
972              This means we did not (and must not) blank the screen.
973              If we were to blank the screen while some other program
974              is holding both the mouse and keyboard grabbed, then
975              we would never be able to un-blank it!  We would never
976              see any events, and the display would be wedged.
977
978              So, just go around the loop again and wait for the
979              next bout of idleness.
980           */
981
982           fprintf (stderr,
983                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
984                    blurb());
985           continue;
986         }
987
988       kill_screenhack (si);
989
990       if (!si->throttled_p)
991         spawn_screenhack (si, True);
992       else if (p->verbose_p)
993         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
994
995       /* Don't start the cycle timer in demo mode. */
996       if (!si->demoing_p && p->cycle)
997         si->cycle_id = XtAppAddTimeOut (si->app,
998                                         (si->selection_mode
999                                          /* see comment in cycle_timer() */
1000                                          ? 1000 * 60 * 60
1001                                          : p->cycle),
1002                                         cycle_timer,
1003                                         (XtPointer) si);
1004
1005
1006 #ifndef NO_LOCKING
1007       /* Maybe start locking the screen.
1008        */
1009       {
1010         Time lock_timeout = p->lock_timeout;
1011
1012         if (si->emergency_lock_p && p->lock_p && lock_timeout)
1013           {
1014             int secs = p->lock_timeout / 1000;
1015             if (p->verbose_p)
1016               fprintf (stderr,
1017                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
1018                        blurb(),
1019                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
1020             lock_timeout = 0;
1021           }
1022
1023         si->emergency_lock_p = False;
1024
1025         if (!si->demoing_p &&           /* if not going into demo mode */
1026             p->lock_p &&                /* and locking is enabled */
1027             !si->locking_disabled_p &&  /* and locking is possible */
1028             lock_timeout == 0)          /* and locking is not timer-deferred */
1029           set_locked_p (si, True);      /* then lock right now. */
1030
1031         /* locked_p might be true already because of the above, or because of
1032            the LOCK ClientMessage.  But if not, and if we're supposed to lock
1033            after some time, set up a timer to do so.
1034         */
1035         if (p->lock_p &&
1036             !si->locked_p &&
1037             lock_timeout > 0)
1038           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
1039                                          activate_lock_timer,
1040                                          (XtPointer) si);
1041       }
1042 #endif /* !NO_LOCKING */
1043
1044
1045       ok_to_unblank = True;
1046       do {
1047
1048         sleep_until_idle (si, False);           /* until not idle */
1049         maybe_reload_init_file (si);
1050
1051 #ifndef NO_LOCKING
1052         /* Maybe unlock the screen.
1053          */
1054         if (si->locked_p)
1055           {
1056             saver_screen_info *ssi = si->default_screen;
1057             if (si->locking_disabled_p) abort ();
1058
1059             was_locked = True;
1060             si->dbox_up_p = True;
1061             suspend_screenhack (si, True);
1062             XUndefineCursor (si->dpy, ssi->screensaver_window);
1063
1064             ok_to_unblank = unlock_p (si);
1065
1066             si->dbox_up_p = False;
1067             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
1068             suspend_screenhack (si, False);     /* resume */
1069
1070             if (!ok_to_unblank &&
1071                 !screenhack_running_p (si))
1072               {
1073                 /* If the lock dialog has been dismissed and we're not about to
1074                    unlock the screen, and there is currently no hack running,
1075                    then launch one.  (There might be no hack running if DPMS
1076                    had kicked in.  But DPMS is off now, so bring back the hack)
1077                  */
1078                 if (si->cycle_id)
1079                   XtRemoveTimeOut (si->cycle_id);
1080                 si->cycle_id = 0;
1081                 cycle_timer ((XtPointer) si, 0);
1082               }
1083           }
1084 #endif /* !NO_LOCKING */
1085
1086         } while (!ok_to_unblank);
1087
1088
1089       if (p->verbose_p)
1090         fprintf (stderr, "%s: unblanking screen at %s.\n",
1091                  blurb(), timestring ());
1092
1093       /* Kill before unblanking, to stop drawing as soon as possible. */
1094       kill_screenhack (si);
1095       unblank_screen (si);
1096
1097       set_locked_p (si, False);
1098       si->emergency_lock_p = False;
1099       si->demoing_p = 0;
1100       si->selection_mode = 0;
1101
1102       /* If we're throttled, and the user has explicitly unlocked the screen,
1103          then unthrottle.  If we weren't locked, then don't unthrottle
1104          automatically, because someone might have just bumped the desk... */
1105       if (was_locked)
1106         {
1107           if (si->throttled_p && p->verbose_p)
1108             fprintf (stderr, "%s: unthrottled.\n", blurb());
1109           si->throttled_p = False;
1110         }
1111
1112       if (si->cycle_id)
1113         {
1114           XtRemoveTimeOut (si->cycle_id);
1115           si->cycle_id = 0;
1116         }
1117
1118       if (si->lock_id)
1119         {
1120           XtRemoveTimeOut (si->lock_id);
1121           si->lock_id = 0;
1122         }
1123
1124       if (p->verbose_p)
1125         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1126     }
1127 }
1128
1129 static void analyze_display (saver_info *si);
1130
1131 int
1132 main (int argc, char **argv)
1133 {
1134   Widget shell;
1135   saver_info the_si;
1136   saver_info *si = &the_si;
1137   saver_preferences *p = &si->prefs;
1138   int i;
1139
1140   memset(si, 0, sizeof(*si));
1141   global_si_kludge = si;        /* I hate C so much... */
1142
1143 # undef ya_rand_init
1144   ya_rand_init (0);
1145
1146   save_argv (argc, argv);
1147   set_version_string (si, &argc, argv);
1148   privileged_initialization (si, &argc, argv);
1149   hack_environment (si);
1150
1151   shell = connect_to_server (si, &argc, argv);
1152   process_command_line (si, &argc, argv);
1153   print_banner (si);
1154
1155   load_init_file (p);  /* must be before initialize_per_screen_info() */
1156   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1157   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1158
1159   /* We can only issue this warnings now. */
1160   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1161     fprintf (stderr,
1162              "%s: there are no PseudoColor or GrayScale visuals.\n"
1163              "%s: ignoring the request for fading/unfading.\n",
1164              blurb(), blurb());
1165
1166   for (i = 0; i < si->nscreens; i++)
1167     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1168       exit (1);
1169
1170   lock_initialization (si, &argc, argv);
1171
1172   if (p->xsync_p) XSynchronize (si->dpy, True);
1173
1174   if (p->verbose_p) analyze_display (si);
1175   initialize_server_extensions (si);
1176
1177   si->blank_time = time ((time_t) 0); /* must be before ..._window */
1178   initialize_screensaver_window (si);
1179
1180   select_events (si);
1181   init_sigchld ();
1182
1183   disable_builtin_screensaver (si, True);
1184   sync_server_dpms_settings (si->dpy,
1185                              (p->dpms_enabled_p  &&
1186                               p->mode != DONT_BLANK),
1187                              p->dpms_standby / 1000,
1188                              p->dpms_suspend / 1000,
1189                              p->dpms_off / 1000,
1190                              False);
1191
1192   initialize_stderr (si);
1193
1194   make_splash_dialog (si);
1195
1196   main_loop (si);               /* doesn't return */
1197   return 0;
1198 }
1199
1200 \f
1201 /* Processing ClientMessage events.
1202  */
1203
1204
1205 static Bool error_handler_hit_p = False;
1206
1207 static int
1208 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
1209 {
1210   error_handler_hit_p = True;
1211   return 0;
1212 }
1213
1214 /* Sometimes some systems send us ClientMessage events with bogus atoms in
1215    them.  We only look up the atom names for printing warning messages,
1216    so don't bomb out when it happens...
1217  */
1218 static char *
1219 XGetAtomName_safe (Display *dpy, Atom atom)
1220 {
1221   char *result;
1222   XErrorHandler old_handler;
1223   if (!atom) return 0;
1224
1225   XSync (dpy, False);
1226   error_handler_hit_p = False;
1227   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1228   result = XGetAtomName (dpy, atom);
1229   XSync (dpy, False);
1230   XSetErrorHandler (old_handler);
1231   XSync (dpy, False);
1232   if (error_handler_hit_p) result = 0;
1233
1234   if (result)
1235     return result;
1236   else
1237     {
1238       char buf[100];
1239       sprintf (buf, "<<undefined atom 0x%04X>>", (unsigned long) atom);
1240       return strdup (buf);
1241     }
1242 }
1243
1244
1245 static void
1246 clientmessage_response (saver_info *si, Window w, Bool error,
1247                         const char *stderr_msg,
1248                         const char *protocol_msg)
1249 {
1250   char *proto;
1251   int L;
1252   saver_preferences *p = &si->prefs;
1253   XErrorHandler old_handler;
1254
1255   if (error || p->verbose_p)
1256     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1257
1258   L = strlen(protocol_msg);
1259   proto = (char *) malloc (L + 2);
1260   proto[0] = (error ? '-' : '+');
1261   strcpy (proto+1, protocol_msg);
1262   L++;
1263
1264   /* Ignore all X errors while sending a response to a ClientMessage.
1265      Pretty much the only way we could get an error here is if the
1266      window we're trying to send the reply on has been deleted, in
1267      which case, the sender of the ClientMessage won't see our response
1268      anyway.
1269    */
1270   XSync (si->dpy, False);
1271   error_handler_hit_p = False;
1272   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1273
1274   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1275                    PropModeReplace, (unsigned char *) proto, L);
1276
1277   XSync (si->dpy, False);
1278   XSetErrorHandler (old_handler);
1279   XSync (si->dpy, False);
1280
1281   free (proto);
1282 }
1283
1284 Bool
1285 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1286 {
1287   saver_preferences *p = &si->prefs;
1288   Atom type = 0;
1289   Window window = event->xclient.window;
1290
1291   /* Preferences might affect our handling of client messages. */
1292   maybe_reload_init_file (si);
1293
1294   if (event->xclient.message_type != XA_SCREENSAVER)
1295     {
1296       char *str;
1297       str = XGetAtomName_safe (si->dpy, event->xclient.message_type);
1298       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1299                blurb(), (str ? str : "(null)"));
1300       if (str) XFree (str);
1301       return False;
1302     }
1303   if (event->xclient.format != 32)
1304     {
1305       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1306                blurb(), event->xclient.format);
1307       return False;
1308     }
1309
1310   type = event->xclient.data.l[0];
1311   if (type == XA_ACTIVATE)
1312     {
1313       if (until_idle_p)
1314         {
1315           clientmessage_response(si, window, False,
1316                                  "ACTIVATE ClientMessage received.",
1317                                  "activating.");
1318           si->selection_mode = 0;
1319           si->demoing_p = False;
1320
1321           if (si->throttled_p && p->verbose_p)
1322             fprintf (stderr, "%s: unthrottled.\n", blurb());
1323           si->throttled_p = False;
1324
1325           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1326             {
1327               XForceScreenSaver (si->dpy, ScreenSaverActive);
1328               return False;
1329             }
1330           else
1331             {
1332               return True;
1333             }
1334         }
1335       clientmessage_response(si, window, True,
1336                        "ClientMessage ACTIVATE received while already active.",
1337                              "already active.");
1338     }
1339   else if (type == XA_DEACTIVATE)
1340     {
1341       if (! until_idle_p)
1342         {
1343           if (si->throttled_p && p->verbose_p)
1344             fprintf (stderr, "%s: unthrottled.\n", blurb());
1345           si->throttled_p = False;
1346
1347           clientmessage_response(si, window, False,
1348                                  "DEACTIVATE ClientMessage received.",
1349                                  "deactivating.");
1350           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1351             {
1352               XForceScreenSaver (si->dpy, ScreenSaverReset);
1353               return False;
1354             }
1355           else
1356             {
1357               return True;
1358             }
1359         }
1360       clientmessage_response(si, window, False,
1361      "ClientMessage DEACTIVATE received while inactive: resetting idle timer.",
1362                              "not active: idle timer reset.");
1363       reset_timers (si);
1364     }
1365   else if (type == XA_CYCLE)
1366     {
1367       if (! until_idle_p)
1368         {
1369           clientmessage_response(si, window, False,
1370                                  "CYCLE ClientMessage received.",
1371                                  "cycling.");
1372           si->selection_mode = 0;       /* 0 means randomize when its time. */
1373           si->demoing_p = False;
1374
1375           if (si->throttled_p && p->verbose_p)
1376             fprintf (stderr, "%s: unthrottled.\n", blurb());
1377           si->throttled_p = False;
1378
1379           if (si->cycle_id)
1380             XtRemoveTimeOut (si->cycle_id);
1381           si->cycle_id = 0;
1382           cycle_timer ((XtPointer) si, 0);
1383           return False;
1384         }
1385       clientmessage_response(si, window, True,
1386                              "ClientMessage CYCLE received while inactive.",
1387                              "not active.");
1388     }
1389   else if (type == XA_NEXT || type == XA_PREV)
1390     {
1391       clientmessage_response(si, window, False,
1392                              (type == XA_NEXT
1393                               ? "NEXT ClientMessage received."
1394                               : "PREV ClientMessage received."),
1395                              "cycling.");
1396       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1397       si->demoing_p = False;
1398
1399       if (si->throttled_p && p->verbose_p)
1400         fprintf (stderr, "%s: unthrottled.\n", blurb());
1401       si->throttled_p = False;
1402
1403       if (! until_idle_p)
1404         {
1405           if (si->cycle_id)
1406             XtRemoveTimeOut (si->cycle_id);
1407           si->cycle_id = 0;
1408           cycle_timer ((XtPointer) si, 0);
1409         }
1410       else
1411         return True;
1412     }
1413   else if (type == XA_SELECT)
1414     {
1415       char buf [255];
1416       char buf2 [255];
1417       long which = event->xclient.data.l[1];
1418
1419       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1420       sprintf (buf2, "activating (%ld).", which);
1421       clientmessage_response (si, window, False, buf, buf2);
1422
1423       if (which < 0) which = 0;         /* 0 == "random" */
1424       si->selection_mode = which;
1425       si->demoing_p = False;
1426
1427       if (si->throttled_p && p->verbose_p)
1428         fprintf (stderr, "%s: unthrottled.\n", blurb());
1429       si->throttled_p = False;
1430
1431       if (! until_idle_p)
1432         {
1433           if (si->cycle_id)
1434             XtRemoveTimeOut (si->cycle_id);
1435           si->cycle_id = 0;
1436           cycle_timer ((XtPointer) si, 0);
1437         }
1438       else
1439         return True;
1440     }
1441   else if (type == XA_EXIT)
1442     {
1443       /* Ignore EXIT message if the screen is locked. */
1444       if (until_idle_p || !si->locked_p)
1445         {
1446           clientmessage_response (si, window, False,
1447                                   "EXIT ClientMessage received.",
1448                                   "exiting.");
1449           if (! until_idle_p)
1450             {
1451               unblank_screen (si);
1452               kill_screenhack (si);
1453               XSync (si->dpy, False);
1454             }
1455           saver_exit (si, 0, 0);
1456         }
1457       else
1458         clientmessage_response (si, window, True,
1459                                 "EXIT ClientMessage received while locked.",
1460                                 "screen is locked.");
1461     }
1462   else if (type == XA_RESTART)
1463     {
1464       /* The RESTART message works whether the screensaver is active or not,
1465          unless the screen is locked, in which case it doesn't work.
1466        */
1467       if (until_idle_p || !si->locked_p)
1468         {
1469           clientmessage_response (si, window, False,
1470                                   "RESTART ClientMessage received.",
1471                                   "restarting.");
1472           if (! until_idle_p)
1473             {
1474               unblank_screen (si);
1475               kill_screenhack (si);
1476               XSync (si->dpy, False);
1477             }
1478
1479           fflush (stdout);
1480           fflush (stderr);
1481           if (real_stdout) fflush (real_stdout);
1482           if (real_stderr) fflush (real_stderr);
1483           /* make sure error message shows up before exit. */
1484           if (real_stderr && stderr != real_stderr)
1485             dup2 (fileno(real_stderr), fileno(stderr));
1486
1487           restart_process (si);
1488           exit (1);     /* shouldn't get here; but if restarting didn't work,
1489                            make this command be the same as EXIT. */
1490         }
1491       else
1492         clientmessage_response (si, window, True,
1493                                 "RESTART ClientMessage received while locked.",
1494                                 "screen is locked.");
1495     }
1496   else if (type == XA_DEMO)
1497     {
1498       long arg = event->xclient.data.l[1];
1499       Bool demo_one_hack_p = (arg == 300);
1500
1501       if (demo_one_hack_p)
1502         {
1503           if (until_idle_p)
1504             {
1505               long which = event->xclient.data.l[2];
1506               char buf [255];
1507               char buf2 [255];
1508               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1509               sprintf (buf2, "demoing (%ld).", which);
1510               clientmessage_response (si, window, False, buf, buf2);
1511
1512               if (which < 0) which = 0;         /* 0 == "random" */
1513               si->selection_mode = which;
1514               si->demoing_p = True;
1515
1516               if (si->throttled_p && p->verbose_p)
1517                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1518               si->throttled_p = False;
1519
1520               return True;
1521             }
1522
1523           clientmessage_response (si, window, True,
1524                                   "DEMO ClientMessage received while active.",
1525                                   "already active.");
1526         }
1527       else
1528         {
1529           clientmessage_response (si, window, True,
1530                                   "obsolete form of DEMO ClientMessage.",
1531                                   "obsolete form of DEMO ClientMessage.");
1532         }
1533     }
1534   else if (type == XA_PREFS)
1535     {
1536       clientmessage_response (si, window, True,
1537                               "the PREFS client-message is obsolete.",
1538                               "the PREFS client-message is obsolete.");
1539     }
1540   else if (type == XA_LOCK)
1541     {
1542 #ifdef NO_LOCKING
1543       clientmessage_response (si, window, True,
1544                               "not compiled with support for locking.",
1545                               "locking not enabled.");
1546 #else /* !NO_LOCKING */
1547       if (si->locking_disabled_p)
1548         clientmessage_response (si, window, True,
1549                       "LOCK ClientMessage received, but locking is disabled.",
1550                               "locking not enabled.");
1551       else if (si->locked_p)
1552         clientmessage_response (si, window, True,
1553                            "LOCK ClientMessage received while already locked.",
1554                                 "already locked.");
1555       else
1556         {
1557           char buf [255];
1558           char *response = (until_idle_p
1559                             ? "activating and locking."
1560                             : "locking.");
1561           sprintf (buf, "LOCK ClientMessage received; %s", response);
1562           clientmessage_response (si, window, False, buf, response);
1563           set_locked_p (si, True);
1564           si->selection_mode = 0;
1565           si->demoing_p = False;
1566
1567           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1568             {
1569               XtRemoveTimeOut (si->lock_id);
1570               si->lock_id = 0;
1571             }
1572
1573           if (until_idle_p)
1574             {
1575               if (si->using_mit_saver_extension ||
1576                   si->using_sgi_saver_extension)
1577                 {
1578                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1579                   return False;
1580                 }
1581               else
1582                 {
1583                   return True;
1584                 }
1585             }
1586         }
1587 #endif /* !NO_LOCKING */
1588     }
1589   else if (type == XA_THROTTLE)
1590     {
1591       if (si->throttled_p)
1592         clientmessage_response (si, window, True,
1593                                 "THROTTLE ClientMessage received, but "
1594                                 "already throttled.",
1595                                 "already throttled.");
1596       else
1597         {
1598           char buf [255];
1599           char *response = "throttled.";
1600           si->throttled_p = True;
1601           si->selection_mode = 0;
1602           si->demoing_p = False;
1603           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
1604           clientmessage_response (si, window, False, buf, response);
1605
1606           if (! until_idle_p)
1607             {
1608               if (si->cycle_id)
1609                 XtRemoveTimeOut (si->cycle_id);
1610               si->cycle_id = 0;
1611               cycle_timer ((XtPointer) si, 0);
1612             }
1613         }
1614     }
1615   else if (type == XA_UNTHROTTLE)
1616     {
1617       if (! si->throttled_p)
1618         clientmessage_response (si, window, True,
1619                                 "UNTHROTTLE ClientMessage received, but "
1620                                 "not throttled.",
1621                                 "not throttled.");
1622       else
1623         {
1624           char buf [255];
1625           char *response = "unthrottled.";
1626           si->throttled_p = False;
1627           si->selection_mode = 0;
1628           si->demoing_p = False;
1629           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
1630           clientmessage_response (si, window, False, buf, response);
1631
1632           if (! until_idle_p)
1633             {
1634               if (si->cycle_id)
1635                 XtRemoveTimeOut (si->cycle_id);
1636               si->cycle_id = 0;
1637               cycle_timer ((XtPointer) si, 0);
1638             }
1639         }
1640     }
1641   else
1642     {
1643       char buf [1024];
1644       char *str;
1645       str = XGetAtomName_safe (si->dpy, type);
1646
1647       if (str)
1648         {
1649           if (strlen (str) > 80)
1650             strcpy (str+70, "...");
1651           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1652                    str);
1653           free (str);
1654         }
1655       else
1656         {
1657           sprintf (buf,
1658                    "unrecognised screensaver ClientMessage 0x%x received.",
1659                    (unsigned int) event->xclient.data.l[0]);
1660         }
1661
1662       clientmessage_response (si, window, True, buf, buf);
1663     }
1664   return False;
1665 }
1666
1667 \f
1668 /* Some random diagnostics printed in -verbose mode.
1669  */
1670
1671 static void
1672 analyze_display (saver_info *si)
1673 {
1674   int i, j;
1675   static struct {
1676     const char *name; const char *desc; Bool useful_p;
1677   } exts[] = {
1678
1679    { "SCREEN_SAVER", /* underscore */           "SGI Screen-Saver",
1680 #     ifdef HAVE_SGI_SAVER_EXTENSION
1681         True
1682 #     else
1683         False
1684 #     endif
1685    }, { "SCREEN-SAVER", /* dash */              "SGI Screen-Saver",
1686 #     ifdef HAVE_SGI_SAVER_EXTENSION
1687         True
1688 #     else
1689         False
1690 #     endif
1691    }, { "MIT-SCREEN-SAVER",                     "MIT Screen-Saver",
1692 #     ifdef HAVE_MIT_SAVER_EXTENSION
1693         True
1694 #     else
1695         False
1696 #     endif
1697    }, { "XIDLE",                                "XIdle",           
1698 #     ifdef HAVE_XIDLE_EXTENSION
1699         True
1700 #     else
1701         False
1702 #     endif
1703    }, { "SGI-VIDEO-CONTROL",                    "SGI Video-Control",
1704 #     ifdef HAVE_SGI_VC_EXTENSION
1705         True
1706 #     else
1707         False
1708 #     endif
1709    }, { "READDISPLAY",                          "SGI Read-Display",
1710 #     ifdef HAVE_READ_DISPLAY_EXTENSION
1711         True
1712 #     else
1713         False
1714 #     endif
1715    }, { "MIT-SHM",                              "Shared Memory",   
1716 #     ifdef HAVE_XSHM_EXTENSION
1717         True
1718 #     else
1719         False
1720 #     endif
1721    }, { "DOUBLE-BUFFER",                        "Double-Buffering",
1722 #     ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1723         True
1724 #     else
1725         False
1726 #     endif
1727    }, { "DPMS",                                 "Power Management",
1728 #     ifdef HAVE_DPMS_EXTENSION
1729         True
1730 #     else
1731         False
1732 #     endif
1733    }, { "GLX",                                  "GLX",             
1734 #     ifdef HAVE_GL
1735         True
1736 #     else
1737         False
1738 #     endif
1739    }, { "XFree86-VidModeExtension",             "XF86 Video-Mode", 
1740 #     ifdef HAVE_XF86VMODE
1741         True
1742 #     else
1743         False
1744 #     endif
1745    }, { "XINERAMA",                             "Xinerama",
1746         True
1747    },
1748   };
1749
1750   fprintf (stderr, "%s: running on display \"%s\" (%d screen%s).\n",
1751            blurb(),
1752            DisplayString(si->dpy),
1753            si->nscreens, (si->nscreens == 1 ? "" : "s"));
1754   fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(),
1755            ServerVendor(si->dpy), VendorRelease(si->dpy));
1756
1757   fprintf (stderr, "%s: useful extensions:\n", blurb());
1758   for (i = 0; i < countof(exts); i++)
1759     {
1760       int op = 0, event = 0, error = 0;
1761       char buf [255];
1762       int j;
1763       if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
1764         continue;
1765       sprintf (buf, "%s:   ", blurb());
1766       j = strlen (buf);
1767       strcat (buf, exts[i].desc);
1768       if (!exts[i].useful_p)
1769         {
1770           int k = j + 18;
1771           while (strlen (buf) < k) strcat (buf, " ");
1772           strcat (buf, "<-- not supported at compile time!");
1773         }
1774       fprintf (stderr, "%s\n", buf);
1775     }
1776
1777   for (i = 0; i < si->nscreens; i++)
1778     {
1779       unsigned long colormapped_depths = 0;
1780       unsigned long non_mapped_depths = 0;
1781       XVisualInfo vi_in, *vi_out;
1782       int out_count;
1783       vi_in.screen = i;
1784       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1785       if (!vi_out) continue;
1786       for (j = 0; j < out_count; j++)
1787         if (vi_out[j].class == PseudoColor)
1788           colormapped_depths |= (1 << vi_out[j].depth);
1789         else
1790           non_mapped_depths  |= (1 << vi_out[j].depth);
1791       XFree ((char *) vi_out);
1792
1793       if (colormapped_depths)
1794         {
1795           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1796           for (j = 0; j < 32; j++)
1797             if (colormapped_depths & (1 << j))
1798               fprintf (stderr, " %d", j);
1799           fprintf (stderr, ".\n");
1800         }
1801       if (non_mapped_depths)
1802         {
1803           fprintf (stderr, "%s: screen %d non-colormapped depths:",
1804                    blurb(), i);
1805           for (j = 0; j < 32; j++)
1806             if (non_mapped_depths & (1 << j))
1807               fprintf (stderr, " %d", j);
1808           fprintf (stderr, ".\n");
1809         }
1810     }
1811 }
1812
1813 Bool
1814 display_is_on_console_p (saver_info *si)
1815 {
1816   Bool not_on_console = True;
1817   char *dpystr = DisplayString (si->dpy);
1818   char *tail = (char *) strchr (dpystr, ':');
1819   if (! tail || strncmp (tail, ":0", 2))
1820     not_on_console = True;
1821   else
1822     {
1823       char dpyname[255], localname[255];
1824       strncpy (dpyname, dpystr, tail-dpystr);
1825       dpyname [tail-dpystr] = 0;
1826       if (!*dpyname ||
1827           !strcmp(dpyname, "unix") ||
1828           !strcmp(dpyname, "localhost"))
1829         not_on_console = False;
1830       else if (gethostname (localname, sizeof (localname)))
1831         not_on_console = True;  /* can't find hostname? */
1832       else
1833         {
1834           /* We have to call gethostbyname() on the result of gethostname()
1835              because the two aren't guarenteed to be the same name for the
1836              same host: on some losing systems, one is a FQDN and the other
1837              is not.  Here in the wide wonderful world of Unix it's rocket
1838              science to obtain the local hostname in a portable fashion.
1839              
1840              And don't forget, gethostbyname() reuses the structure it
1841              returns, so we have to copy the fucker before calling it again.
1842              Thank you master, may I have another.
1843            */
1844           struct hostent *h = gethostbyname (dpyname);
1845           if (!h)
1846             not_on_console = True;
1847           else
1848             {
1849               char hn [255];
1850               struct hostent *l;
1851               strcpy (hn, h->h_name);
1852               l = gethostbyname (localname);
1853               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1854             }
1855         }
1856     }
1857   return !not_on_console;
1858 }