http://ftp.x.org/contrib/applications/xscreensaver-3.20.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-1999 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   { "-timeout",            ".timeout",          XrmoptionSepArg, 0 },
173   { "-cycle",              ".cycle",            XrmoptionSepArg, 0 },
174   { "-lock-mode",          ".lock",             XrmoptionNoArg, "on" },
175   { "-no-lock-mode",       ".lock",             XrmoptionNoArg, "off" },
176   { "-no-lock",            ".lock",             XrmoptionNoArg, "off" },
177   { "-lock-timeout",       ".lockTimeout",      XrmoptionSepArg, 0 },
178   { "-lock-vts",           ".lockVTs",          XrmoptionNoArg, "on" },
179   { "-no-lock-vts",        ".lockVTs",          XrmoptionNoArg, "off" },
180   { "-visual",             ".visualID",         XrmoptionSepArg, 0 },
181   { "-install",            ".installColormap",  XrmoptionNoArg, "on" },
182   { "-no-install",         ".installColormap",  XrmoptionNoArg, "off" },
183   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
184   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
185   { "-timestamp",          ".timestamp",        XrmoptionNoArg, "on" },
186   { "-capture-stderr",     ".captureStderr",    XrmoptionNoArg, "on" },
187   { "-no-capture-stderr",  ".captureStderr",    XrmoptionNoArg, "off" },
188   { "-xidle-extension",    ".xidleExtension",   XrmoptionNoArg, "on" },
189   { "-no-xidle-extension", ".xidleExtension",   XrmoptionNoArg, "off" },
190   { "-mit-extension",      ".mitSaverExtension",XrmoptionNoArg, "on" },
191   { "-no-mit-extension",   ".mitSaverExtension",XrmoptionNoArg, "off" },
192   { "-sgi-extension",      ".sgiSaverExtension",XrmoptionNoArg, "on" },
193   { "-no-sgi-extension",   ".sgiSaverExtension",XrmoptionNoArg, "off" },
194   { "-proc-interrupts",    ".procInterrupts",   XrmoptionNoArg, "on" },
195   { "-no-proc-interrupts", ".procInterrupts",   XrmoptionNoArg, "off" },
196   { "-splash",             ".splash",           XrmoptionNoArg, "on" },
197   { "-no-splash",          ".splash",           XrmoptionNoArg, "off" },
198   { "-nosplash",           ".splash",           XrmoptionNoArg, "off" },
199   { "-idelay",             ".initialDelay",     XrmoptionSepArg, 0 },
200   { "-nice",               ".nice",             XrmoptionSepArg, 0 },
201
202   /* Actually these are built in to Xt, but just to be sure... */
203   { "-synchronous",        ".synchronous",      XrmoptionNoArg, "on" },
204   { "-xrm",                NULL,                XrmoptionResArg, NULL }
205 };
206
207 static char *defaults[] = {
208 #include "XScreenSaver_ad.h"
209  0
210 };
211
212 #ifdef _VROOT_H_
213 ERROR!  You must not include vroot.h in this file.
214 #endif
215
216 static void
217 do_help (saver_info *si)
218 {
219   fflush (stdout);
220   fflush (stderr);
221   fprintf (stdout, "\
222 xscreensaver %s, copyright (c) 1991-1999 by Jamie Zawinski <jwz@jwz.org>\n\
223 The standard Xt command-line options are accepted; other options include:\n\
224 \n\
225     -timeout <minutes>       When the screensaver should activate.\n\
226     -cycle <minutes>         How long to let each hack run before switching.\n\
227     -lock-mode               Require a password before deactivating.\n\
228     -lock-timeout <minutes>  Grace period before locking; default 0.\n\
229     -visual <id-or-class>    Which X visual to run on.\n\
230     -install                 Install a private colormap.\n\
231     -verbose                 Be loud.\n\
232     -no-splash               Don't display a splash-screen at startup.\n\
233     -help                    This message.\n\
234 \n\
235 See the manual for other options and X resources.\n\
236 \n\
237 The `xscreensaver' program should be left running in the background.\n\
238 Use the `xscreensaver-demo' and `xscreensaver-command' programs to\n\
239 manipulate a running xscreensaver.\n\
240 \n\
241 The `*programs' resource controls which graphics demos will be launched by\n\
242 the screensaver.  See `man xscreensaver' or the web page for more details.\n\
243 \n\
244 Just getting started?  Try this:\n\
245 \n\
246         xscreensaver &\n\
247         xscreensaver-demo\n\
248 \n\
249 For updates, check http://www.jwz.org/xscreensaver/\n\
250 \n",
251           si->version);
252   fflush (stdout);
253   fflush (stderr);
254   exit (1);
255 }
256
257
258 char *
259 timestring (void)
260 {
261   time_t now = time ((time_t *) 0);
262   char *str = (char *) ctime (&now);
263   char *nl = (char *) strchr (str, '\n');
264   if (nl) *nl = 0; /* take off that dang newline */
265   return str;
266 }
267
268 static Bool blurb_timestamp_p = False;   /* kludge */
269
270 const char *
271 blurb (void)
272 {
273   if (!blurb_timestamp_p)
274     return progname;
275   else
276     {
277       static char buf[255];
278       char *ct = timestring();
279       int n = strlen(progname);
280       if (n > 100) n = 99;
281       strncpy(buf, progname, n);
282       buf[n++] = ':';
283       buf[n++] = ' ';
284       strncpy(buf+n, ct+11, 8);
285       strcpy(buf+n+9, ": ");
286       return buf;
287     }
288 }
289
290
291 int
292 saver_ehandler (Display *dpy, XErrorEvent *error)
293 {
294   saver_info *si = global_si_kludge;    /* I hate C so much... */
295
296   if (!real_stderr) real_stderr = stderr;
297
298   fprintf (real_stderr, "\n"
299            "#######################################"
300            "#######################################\n\n"
301            "%s: X Error!  PLEASE REPORT THIS BUG.\n\n"
302            "#######################################"
303            "#######################################\n\n",
304            blurb());
305   if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
306     {
307       fprintf (real_stderr, "\n");
308       if (si->prefs.xsync_p)
309         {
310           saver_exit (si, -1, "because of synchronous X Error");
311         }
312       else
313         {
314           fprintf (real_stderr,
315                    "#######################################"
316                    "#######################################\n\n");
317           fprintf (real_stderr,
318    "    If at all possible, please re-run xscreensaver with the command\ e\n"
319    "    line arguments `-sync -verbose -no-capture', and reproduce this\n"
320    "    bug.  That will cause xscreensaver to dump a `core' file to the\n"
321    "    current directory.  Please include the stack trace from that core\n"
322    "    file in your bug report.  *DO NOT* mail the core file itself!\n"
323    "    That won't work.\n"
324    "\n"
325    "    http://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
326    "    the most useful bug reports, and how to examine core files.\n"
327    "\n"
328    "    The more information you can provide, the better.  But please\n"
329    "    report this bug, regardless!\n"
330    "\n");
331           fprintf (real_stderr,
332                    "#######################################"
333                    "#######################################\n\n");
334
335           saver_exit (si, -1, 0);
336         }
337     }
338   else
339     fprintf (real_stderr, " (nonfatal.)\n");
340   return 0;
341 }
342
343
344 /* This error handler is used only while the X connection is being set up;
345    after we've got a connection, we don't use this handler again.  The only
346    reason for having this is so that we can present a more idiot-proof error
347    message than "cannot open display."
348  */
349 static void 
350 startup_ehandler (String name, String type, String class,
351                   String defalt,  /* one can't even spel properly
352                                      in this joke of a language */
353                   String *av, Cardinal *ac)
354 {
355   char fmt[512];
356   String p[10];
357   saver_info *si = global_si_kludge;    /* I hate C so much... */
358   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
359   *fmt = 0;
360   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
361                             fmt, sizeof(fmt)-1, *db);
362
363   fprintf (stderr, "%s: ", blurb());
364
365   memset (p, 0, sizeof(p));
366   if (*ac > countof (p)) *ac = countof (p);
367   memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
368   fprintf (stderr, fmt,         /* Did I mention that I hate C? */
369            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
370   fprintf (stderr, "\n");
371
372   describe_uids (si, stderr);
373   fprintf (stderr, "\n"
374           "%s: Errors at startup are usually authorization problems.\n"
375           "              Did you read the manual and the FAQ?  Specifically,\n"
376           "              the parts of the manual that talk about XAUTH, XDM,\n"
377           "              and root logins?\n"
378           "\n"
379           "              http://www.jwz.org/xscreensaver/man.html\n"
380           "\n",
381            blurb());
382
383   fflush (stderr);
384   fflush (stdout);
385   exit (1);
386 }
387
388 \f
389 /* The zillions of initializations.
390  */
391
392 /* Set progname, version, etc.  This is done very early.
393  */
394 static void
395 set_version_string (saver_info *si, int *argc, char **argv)
396 {
397   progclass = "XScreenSaver";
398
399   /* progname is reset later, after we connect to X. */
400   progname = strrchr(argv[0], '/');
401   if (progname) progname++;
402   else progname = argv[0];
403
404   if (strlen(progname) > 100)   /* keep it short. */
405     progname[99] = 0;
406
407   /* The X resource database blows up if argv[0] has a "." in it. */
408   {
409     char *s = argv[0];
410     while ((s = strchr (s, '.')))
411       *s = '_';
412   }
413
414   si->version = (char *) malloc (5);
415   memcpy (si->version, screensaver_id + 17, 4);
416   si->version [4] = 0;
417 }
418
419
420 /* Initializations that potentially take place as a priveleged user:
421    If the xscreensaver executable is setuid root, then these initializations
422    are run as root, before discarding privileges.
423  */
424 static void
425 privileged_initialization (saver_info *si, int *argc, char **argv)
426 {
427 #ifndef NO_LOCKING
428   /* before hack_uid() for proper permissions */
429   lock_priv_init (*argc, argv, si->prefs.verbose_p);
430 #endif /* NO_LOCKING */
431
432   hack_uid (si);
433 }
434
435
436 /* Figure out what locking mechanisms are supported.
437  */
438 static void
439 lock_initialization (saver_info *si, int *argc, char **argv)
440 {
441 #ifdef NO_LOCKING
442   si->locking_disabled_p = True;
443   si->nolock_reason = "not compiled with locking support";
444 #else /* !NO_LOCKING */
445
446   /* Finish initializing locking, now that we're out of privileged code. */
447   if (! lock_init (*argc, argv, si->prefs.verbose_p))
448     {
449       si->locking_disabled_p = True;
450       si->nolock_reason = "error getting password";
451     }
452 #endif /* NO_LOCKING */
453
454   hack_uid (si);
455 }
456
457
458 /* Open the connection to the X server, and intern our Atoms.
459  */
460 static Widget
461 connect_to_server (saver_info *si, int *argc, char **argv)
462 {
463   Widget toplevel_shell;
464
465   XSetErrorHandler (saver_ehandler);
466
467   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
468   toplevel_shell = XtAppInitialize (&si->app, progclass,
469                                     options, XtNumber (options),
470                                     argc, argv, defaults, 0, 0);
471   XtAppSetErrorMsgHandler (si->app, 0);
472
473   si->dpy = XtDisplay (toplevel_shell);
474   si->prefs.db = XtDatabase (si->dpy);
475   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
476
477   if(strlen(progname) > 100)    /* keep it short. */
478     progname [99] = 0;
479
480   db = si->prefs.db;    /* resources.c needs this */
481
482   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
483   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
484   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
485   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
486   XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False);
487   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
488                                          False);
489   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
490   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
491   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
492   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
493   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
494   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
495   XA_PREV = XInternAtom (si->dpy, "PREV", False);
496   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
497   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
498   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
499   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
500   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
501   XA_BLANK = XInternAtom (si->dpy, "BLANK", False);
502   XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
503   XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
504
505   return toplevel_shell;
506 }
507
508
509 /* Handle the command-line arguments that were not handled for us by Xt.
510    Issue an error message and exit if there are unknown options.
511  */
512 static void
513 process_command_line (saver_info *si, int *argc, char **argv)
514 {
515   int i;
516   for (i = 1; i < *argc; i++)
517     {
518       if (!strcmp (argv[i], "-debug"))
519         /* no resource for this one, out of paranoia. */
520         si->prefs.debug_p = True;
521
522       else if (!strcmp (argv[i], "-h") ||
523                !strcmp (argv[i], "-help") ||
524                !strcmp (argv[i], "--help"))
525         do_help (si);
526
527       else
528         {
529           const char *s = argv[i];
530           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
531                    blurb(), s);
532
533           if (s[0] == '-' && s[1] == '-') s++;
534           if (!strcmp (s, "-activate") ||
535               !strcmp (s, "-deactivate") ||
536               !strcmp (s, "-cycle") ||
537               !strcmp (s, "-next") ||
538               !strcmp (s, "-prev") ||
539               !strcmp (s, "-exit") ||
540               !strcmp (s, "-restart") ||
541               !strcmp (s, "-demo") ||
542               !strcmp (s, "-prefs") ||
543               !strcmp (s, "-preferences") ||
544               !strcmp (s, "-lock") ||
545               !strcmp (s, "-version") ||
546               !strcmp (s, "-time"))
547             {
548
549               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
550                 fprintf (stderr, "\n\
551     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
552               else
553                 fprintf (stderr, "\n\
554     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
555
556               fprintf (stderr, "\
557     The `xscreensaver' program is a daemon that runs in the background.\n\
558     You control a running xscreensaver process by sending it messages\n\
559     with `xscreensaver-demo' or `xscreensaver-command'.\n\
560 .   See the man pages for details, or check the web page:\n\
561     http://www.jwz.org/xscreensaver/\n\n");
562
563               /* Since version 1.21 renamed the "-lock" option to "-lock-mode",
564                  suggest that explicitly. */
565               if (!strcmp (s, "-lock"))
566                 fprintf (stderr, "\
567     Or perhaps you meant either the \"-lock-mode\" or the\n\
568     \"-lock-timeout <minutes>\" options to xscreensaver?\n\n");
569             }
570
571           exit (1);
572         }
573     }
574 }
575
576
577 /* Print out the xscreensaver banner to the tty if applicable;
578    Issue any other warnings that are called for at this point.
579  */
580 static void
581 print_banner (saver_info *si)
582 {
583   saver_preferences *p = &si->prefs;
584
585   /* This resource gets set some time before the others, so that we know
586      whether to print the banner (and so that the banner gets printed before
587      any resource-database-related error messages.)
588    */
589   p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean"));
590
591   /* Ditto, for the locking_disabled_p message. */
592   p->lock_p = get_boolean_resource ("lock", "Boolean");
593
594   if (p->verbose_p)
595     fprintf (stderr,
596              "%s %s, copyright (c) 1991-1999 "
597              "by Jamie Zawinski <jwz@jwz.org>.\n",
598              progname, si->version);
599
600   if (p->debug_p)
601     fprintf (stderr, "\n"
602              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
603              "\n"
604              "\tNote that in debug mode, the xscreensaver window will only\n"
605              "\tcover the left half of the screen.  (The idea is that you\n"
606              "\tcan still see debugging output in a shell, if you position\n"
607              "\tit on the right side of the screen.)\n"
608              "\n"
609              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
610              "\tuntrusted environments.\n"
611              "\n",
612              blurb());
613
614   if (p->verbose_p)
615     {
616       if (!si->uid_message || !*si->uid_message)
617         describe_uids (si, stderr);
618       else
619         {
620           if (si->orig_uid && *si->orig_uid)
621             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
622                      blurb(), si->orig_uid);
623           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
624         }
625
626       fprintf (stderr, "%s: in process %lu.\n", blurb(),
627                (unsigned long) getpid());
628     }
629
630   /* If locking was not able to be initalized for some reason, explain why.
631      (This has to be done after we've read the lock_p resource.)
632    */
633   if (p->lock_p && si->locking_disabled_p)
634     {
635       p->lock_p = False;
636       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
637                si->nolock_reason);
638       if (strstr (si->nolock_reason, "passw"))
639         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
640                  "consult the manual.\n", blurb());
641       else if (strstr (si->nolock_reason, "running as "))
642         fprintf (stderr, 
643                  "%s: locking only works when xscreensaver is launched\n"
644                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
645                  "\t See the manual for details.\n",
646                  blurb());
647     }
648 }
649
650
651 /* Examine all of the display's screens, and populate the `saver_screen_info'
652    structures.
653  */
654 static void
655 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
656 {
657   Bool found_any_writable_cells = False;
658   int i;
659
660   si->nscreens = ScreenCount(si->dpy);
661   si->screens = (saver_screen_info *)
662     calloc(sizeof(saver_screen_info), si->nscreens);
663
664   si->default_screen = &si->screens[DefaultScreen(si->dpy)];
665
666   for (i = 0; i < si->nscreens; i++)
667     {
668       saver_screen_info *ssi = &si->screens[i];
669       ssi->global = si;
670       ssi->screen = ScreenOfDisplay (si->dpy, i);
671
672       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
673       ssi->default_visual =
674         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
675
676       ssi->current_visual = ssi->default_visual;
677       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
678
679       if (ssi == si->default_screen)
680         /* Since this is the default screen, use the one already created. */
681         ssi->toplevel_shell = toplevel_shell;
682       else
683         /* Otherwise, each screen must have its own unmapped root widget. */
684         ssi->toplevel_shell =
685           XtVaAppCreateShell (progname, progclass, applicationShellWidgetClass,
686                               si->dpy,
687                               XtNscreen, ssi->screen,
688                               XtNvisual, ssi->current_visual,
689                               XtNdepth,  visual_depth (ssi->screen,
690                                                        ssi->current_visual),
691                               0);
692
693       if (! found_any_writable_cells)
694         {
695           /* Check to see whether fading is ever possible -- if any of the
696              screens on the display has a PseudoColor visual, then fading can
697              work (on at least some screens.)  If no screen has a PseudoColor
698              visual, then don't bother ever trying to fade, because it will
699              just cause a delay without causing any visible effect.
700           */
701           if (has_writable_cells (ssi->screen, ssi->current_visual) ||
702               get_visual (ssi->screen, "PseudoColor", True, False) ||
703               get_visual (ssi->screen, "GrayScale", True, False))
704             found_any_writable_cells = True;
705         }
706     }
707
708   si->fading_possible_p = found_any_writable_cells;
709 }
710
711
712 /* If any server extensions have been requested, try and initialize them.
713    Issue warnings if requests can't be honored.
714  */
715 static void
716 initialize_server_extensions (saver_info *si)
717 {
718   saver_preferences *p = &si->prefs;
719
720   Bool server_has_xidle_extension_p = False;
721   Bool server_has_sgi_saver_extension_p = False;
722   Bool server_has_mit_saver_extension_p = False;
723   Bool system_has_proc_interrupts_p = False;
724   const char *piwhy = 0;
725
726   si->using_xidle_extension = p->use_xidle_extension;
727   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
728   si->using_mit_saver_extension = p->use_mit_saver_extension;
729   si->using_proc_interrupts = p->use_proc_interrupts;
730
731 #ifdef HAVE_XIDLE_EXTENSION
732   server_has_xidle_extension_p = query_xidle_extension (si);
733 #endif
734 #ifdef HAVE_SGI_SAVER_EXTENSION
735   server_has_sgi_saver_extension_p = query_sgi_saver_extension (si);
736 #endif
737 #ifdef HAVE_MIT_SAVER_EXTENSION
738   server_has_mit_saver_extension_p = query_mit_saver_extension (si);
739 #endif
740 #ifdef HAVE_PROC_INTERRUPTS
741   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
742 #endif
743
744   if (!server_has_xidle_extension_p)
745     si->using_xidle_extension = False;
746   else if (p->verbose_p)
747     {
748       if (si->using_xidle_extension)
749         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
750       else
751         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
752     }
753
754   if (!server_has_sgi_saver_extension_p)
755     si->using_sgi_saver_extension = False;
756   else if (p->verbose_p)
757     {
758       if (si->using_sgi_saver_extension)
759         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
760       else
761         fprintf (stderr,
762                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
763                  blurb());
764     }
765
766   if (!server_has_mit_saver_extension_p)
767     si->using_mit_saver_extension = False;
768   else if (p->verbose_p)
769     {
770       if (si->using_mit_saver_extension)
771         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
772                  blurb());
773       else
774         fprintf (stderr,
775                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
776                  blurb());
777     }
778
779   if (!system_has_proc_interrupts_p)
780     {
781       si->using_proc_interrupts = False;
782       if (p->verbose_p && piwhy)
783         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
784                  piwhy);
785     }
786   else if (p->verbose_p)
787     {
788       if (si->using_proc_interrupts)
789         fprintf (stderr,
790                  "%s: consulting /proc/interrupts for keyboard activity.\n",
791                  blurb());
792       else
793         fprintf (stderr,
794                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
795                  blurb());
796     }
797 }
798
799
800 /* For the case where we aren't using an server extensions, select user events
801    on all the existing windows, and launch timers to select events on
802    newly-created windows as well.
803
804    If a server extension is being used, this does nothing.
805  */
806 static void
807 select_events (saver_info *si)
808 {
809   saver_preferences *p = &si->prefs;
810   int i;
811
812   if (si->using_xidle_extension ||
813       si->using_mit_saver_extension ||
814       si->using_sgi_saver_extension)
815     return;
816
817   if (p->initial_delay)
818     {
819       if (p->verbose_p)
820         {
821           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
822                    (int) p->initial_delay/1000,
823                    (p->initial_delay == 1000 ? "" : "s"));
824           fflush (stderr);
825           fflush (stdout);
826         }
827       usleep (p->initial_delay);
828       if (p->verbose_p)
829         fprintf (stderr, " done.\n");
830     }
831
832   if (p->verbose_p)
833     {
834       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
835       fflush (stderr);
836       fflush (stdout);
837     }
838
839   /* Select events on the root windows of every screen.  This also selects
840      for window creation events, so that new subwindows will be noticed.
841    */
842   for (i = 0; i < si->nscreens; i++)
843     start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
844                                False);
845
846   if (p->verbose_p)
847     fprintf (stderr, " done.\n");
848 }
849
850
851 void
852 maybe_reload_init_file (saver_info *si)
853 {
854   saver_preferences *p = &si->prefs;
855   if (init_file_changed_p (p))
856     {
857       if (p->verbose_p)
858         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
859                  blurb(), init_file_name());
860
861       load_init_file (p);
862
863       /* If a server extension is in use, and p->timeout has changed,
864          we need to inform the server of the new timeout. */
865       disable_builtin_screensaver (si, False);
866     }
867 }
868
869
870 /* Loop forever:
871
872        - wait until the user is idle;
873        - blank the screen;
874        - wait until the user is active;
875        - unblank the screen;
876        - repeat.
877
878  */
879 static void
880 main_loop (saver_info *si)
881 {
882   saver_preferences *p = &si->prefs;
883   Bool ok_to_unblank;
884
885   while (1)
886     {
887       Bool was_locked = False;
888       sleep_until_idle (si, True);
889
890       if (p->verbose_p)
891         {
892           if (si->demoing_p)
893             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
894                      si->selection_mode, timestring());
895           else
896             if (p->verbose_p)
897               fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
898                        timestring());
899         }
900
901       maybe_reload_init_file (si);
902
903       if (! blank_screen (si))
904         {
905           /* We were unable to grab either the keyboard or mouse.
906              This means we did not (and must not) blank the screen.
907              If we were to blank the screen while some other program
908              is holding both the mouse and keyboard grabbed, then
909              we would never be able to un-blank it!  We would never
910              see any events, and the display would be wedged.
911
912              So, just go around the loop again and wait for the
913              next bout of idleness.
914           */
915
916           fprintf (stderr,
917                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
918                    blurb());
919           continue;
920         }
921
922       kill_screenhack (si);
923
924       if (!si->throttled_p)
925         spawn_screenhack (si, True);
926       else if (p->verbose_p)
927         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
928
929       /* Don't start the cycle timer in demo mode. */
930       if (!si->demoing_p && p->cycle)
931         si->cycle_id = XtAppAddTimeOut (si->app,
932                                         (si->selection_mode
933                                          /* see comment in cycle_timer() */
934                                          ? 1000 * 60 * 60
935                                          : p->cycle),
936                                         cycle_timer,
937                                         (XtPointer) si);
938
939
940 #ifndef NO_LOCKING
941       {
942         Time lock_timeout = p->lock_timeout;
943
944         if (si->emergency_lock_p && p->lock_p && lock_timeout)
945           {
946             int secs = p->lock_timeout / 1000;
947             if (p->verbose_p)
948               fprintf (stderr,
949                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
950                        blurb(),
951                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
952             lock_timeout = 0;
953           }
954
955         si->emergency_lock_p = False;
956
957         if (!si->demoing_p &&           /* if not going into demo mode */
958             p->lock_p &&                /* and locking is enabled */
959             !si->locking_disabled_p &&  /* and locking is possible */
960             lock_timeout == 0)          /* and locking is not timer-deferred */
961           set_locked_p (si, True);      /* then lock right now. */
962
963         /* locked_p might be true already because of the above, or because of
964            the LOCK ClientMessage.  But if not, and if we're supposed to lock
965            after some time, set up a timer to do so.
966         */
967         if (p->lock_p &&
968             !si->locked_p &&
969             lock_timeout > 0)
970           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
971                                          activate_lock_timer,
972                                          (XtPointer) si);
973       }
974 #endif /* !NO_LOCKING */
975
976
977       ok_to_unblank = True;
978       do {
979
980         sleep_until_idle (si, False);           /* until not idle */
981         maybe_reload_init_file (si);
982
983 #ifndef NO_LOCKING
984         if (si->locked_p)
985           {
986             saver_screen_info *ssi = si->default_screen;
987             if (si->locking_disabled_p) abort ();
988
989             was_locked = True;
990             si->dbox_up_p = True;
991             suspend_screenhack (si, True);
992             XUndefineCursor (si->dpy, ssi->screensaver_window);
993
994             ok_to_unblank = unlock_p (si);
995
996             si->dbox_up_p = False;
997             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
998             suspend_screenhack (si, False);     /* resume */
999           }
1000 #endif /* !NO_LOCKING */
1001
1002         } while (!ok_to_unblank);
1003
1004
1005       if (p->verbose_p)
1006         fprintf (stderr, "%s: unblanking screen at %s.\n",
1007                  blurb(), timestring ());
1008
1009       /* Kill before unblanking, to stop drawing as soon as possible. */
1010       kill_screenhack (si);
1011       unblank_screen (si);
1012
1013       set_locked_p (si, False);
1014       si->emergency_lock_p = False;
1015       si->demoing_p = 0;
1016       si->selection_mode = 0;
1017
1018       /* If we're throttled, and the user has explicitly unlocked the screen,
1019          then unthrottle.  If we weren't locked, then don't unthrottle
1020          automatically, because someone might have just bumped the desk... */
1021       if (was_locked)
1022         {
1023           if (si->throttled_p && p->verbose_p)
1024             fprintf (stderr, "%s: unthrottled.\n", blurb());
1025           si->throttled_p = False;
1026         }
1027
1028       if (si->cycle_id)
1029         {
1030           XtRemoveTimeOut (si->cycle_id);
1031           si->cycle_id = 0;
1032         }
1033
1034       if (si->lock_id)
1035         {
1036           XtRemoveTimeOut (si->lock_id);
1037           si->lock_id = 0;
1038         }
1039
1040       if (p->verbose_p)
1041         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1042     }
1043 }
1044
1045 static void analyze_display (saver_info *si);
1046
1047 int
1048 main (int argc, char **argv)
1049 {
1050   Widget shell;
1051   saver_info the_si;
1052   saver_info *si = &the_si;
1053   saver_preferences *p = &si->prefs;
1054   int i;
1055
1056   memset(si, 0, sizeof(*si));
1057   global_si_kludge = si;        /* I hate C so much... */
1058
1059   srandom ((int) time ((time_t *) 0));
1060
1061   save_argv (argc, argv);
1062   set_version_string (si, &argc, argv);
1063   privileged_initialization (si, &argc, argv);
1064   hack_environment (si);
1065
1066   shell = connect_to_server (si, &argc, argv);
1067   process_command_line (si, &argc, argv);
1068   print_banner (si);
1069
1070   load_init_file (p);  /* must be before initialize_per_screen_info() */
1071   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1072
1073   /* We can only issue this warnings now. */
1074   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1075     fprintf (stderr,
1076              "%s: there are no PseudoColor or GrayScale visuals.\n"
1077              "%s: ignoring the request for fading/unfading.\n",
1078              blurb(), blurb());
1079
1080   for (i = 0; i < si->nscreens; i++)
1081     if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1082       exit (1);
1083
1084   lock_initialization (si, &argc, argv);
1085
1086   if (p->xsync_p) XSynchronize (si->dpy, True);
1087   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1088
1089   if (p->verbose_p) analyze_display (si);
1090   initialize_server_extensions (si);
1091
1092   si->blank_time = time ((time_t) 0); /* must be before ..._window */
1093   initialize_screensaver_window (si);
1094
1095   select_events (si);
1096   init_sigchld ();
1097   disable_builtin_screensaver (si, True);
1098   initialize_stderr (si);
1099
1100   make_splash_dialog (si);
1101
1102   main_loop (si);               /* doesn't return */
1103   return 0;
1104 }
1105
1106 \f
1107 /* Processing ClientMessage events.
1108  */
1109
1110 static void
1111 clientmessage_response (saver_info *si, Window w, Bool error,
1112                         const char *stderr_msg,
1113                         const char *protocol_msg)
1114 {
1115   char *proto;
1116   int L;
1117   saver_preferences *p = &si->prefs;
1118   if (error || p->verbose_p)
1119     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1120
1121   L = strlen(protocol_msg);
1122   proto = (char *) malloc (L + 2);
1123   proto[0] = (error ? '-' : '+');
1124   strcpy (proto+1, protocol_msg);
1125   L++;
1126
1127   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1128                    PropModeReplace, (unsigned char *) proto, L);
1129   XSync (si->dpy, False);
1130   free (proto);
1131 }
1132
1133 Bool
1134 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1135 {
1136   saver_preferences *p = &si->prefs;
1137   Atom type = 0;
1138   Window window = event->xclient.window;
1139
1140   /* Preferences might affect our handling of client messages. */
1141   maybe_reload_init_file (si);
1142
1143   if (event->xclient.message_type != XA_SCREENSAVER)
1144     {
1145       char *str;
1146       str = XGetAtomName (si->dpy, event->xclient.message_type);
1147       fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n",
1148                blurb(), (str ? str : "(null)"));
1149       if (str) XFree (str);
1150       return False;
1151     }
1152   if (event->xclient.format != 32)
1153     {
1154       fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n",
1155                blurb(), event->xclient.format);
1156       return False;
1157     }
1158
1159   type = event->xclient.data.l[0];
1160   if (type == XA_ACTIVATE)
1161     {
1162       if (until_idle_p)
1163         {
1164           clientmessage_response(si, window, False,
1165                                  "ACTIVATE ClientMessage received.",
1166                                  "activating.");
1167           si->selection_mode = 0;
1168           si->demoing_p = False;
1169
1170           if (si->throttled_p && p->verbose_p)
1171             fprintf (stderr, "%s: unthrottled.\n", blurb());
1172           si->throttled_p = False;
1173
1174           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1175             {
1176               XForceScreenSaver (si->dpy, ScreenSaverActive);
1177               return False;
1178             }
1179           else
1180             {
1181               return True;
1182             }
1183         }
1184       clientmessage_response(si, window, True,
1185                        "ClientMessage ACTIVATE received while already active.",
1186                              "already active.");
1187     }
1188   else if (type == XA_DEACTIVATE)
1189     {
1190       if (! until_idle_p)
1191         {
1192           if (si->throttled_p && p->verbose_p)
1193             fprintf (stderr, "%s: unthrottled.\n", blurb());
1194           si->throttled_p = False;
1195
1196           clientmessage_response(si, window, False,
1197                                  "DEACTIVATE ClientMessage received.",
1198                                  "deactivating.");
1199           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1200             {
1201               XForceScreenSaver (si->dpy, ScreenSaverReset);
1202               return False;
1203             }
1204           else
1205             {
1206               return True;
1207             }
1208         }
1209       clientmessage_response(si, window, True,
1210                            "ClientMessage DEACTIVATE received while inactive.",
1211                              "not active.");
1212     }
1213   else if (type == XA_CYCLE)
1214     {
1215       if (! until_idle_p)
1216         {
1217           clientmessage_response(si, window, False,
1218                                  "CYCLE ClientMessage received.",
1219                                  "cycling.");
1220           si->selection_mode = 0;       /* 0 means randomize when its time. */
1221           si->demoing_p = False;
1222
1223           if (si->throttled_p && p->verbose_p)
1224             fprintf (stderr, "%s: unthrottled.\n", blurb());
1225           si->throttled_p = False;
1226
1227           if (si->cycle_id)
1228             XtRemoveTimeOut (si->cycle_id);
1229           si->cycle_id = 0;
1230           cycle_timer ((XtPointer) si, 0);
1231           return False;
1232         }
1233       clientmessage_response(si, window, True,
1234                              "ClientMessage CYCLE received while inactive.",
1235                              "not active.");
1236     }
1237   else if (type == XA_NEXT || type == XA_PREV)
1238     {
1239       clientmessage_response(si, window, False,
1240                              (type == XA_NEXT
1241                               ? "NEXT ClientMessage received."
1242                               : "PREV ClientMessage received."),
1243                              "cycling.");
1244       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1245       si->demoing_p = False;
1246
1247       if (si->throttled_p && p->verbose_p)
1248         fprintf (stderr, "%s: unthrottled.\n", blurb());
1249       si->throttled_p = False;
1250
1251       if (! until_idle_p)
1252         {
1253           if (si->cycle_id)
1254             XtRemoveTimeOut (si->cycle_id);
1255           si->cycle_id = 0;
1256           cycle_timer ((XtPointer) si, 0);
1257         }
1258       else
1259         return True;
1260     }
1261   else if (type == XA_SELECT)
1262     {
1263       char buf [255];
1264       char buf2 [255];
1265       long which = event->xclient.data.l[1];
1266
1267       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1268       sprintf (buf2, "activating (%ld).", which);
1269       clientmessage_response (si, window, False, buf, buf2);
1270
1271       if (which < 0) which = 0;         /* 0 == "random" */
1272       si->selection_mode = which;
1273       si->demoing_p = False;
1274
1275       if (si->throttled_p && p->verbose_p)
1276         fprintf (stderr, "%s: unthrottled.\n", blurb());
1277       si->throttled_p = False;
1278
1279       if (! until_idle_p)
1280         {
1281           if (si->cycle_id)
1282             XtRemoveTimeOut (si->cycle_id);
1283           si->cycle_id = 0;
1284           cycle_timer ((XtPointer) si, 0);
1285         }
1286       else
1287         return True;
1288     }
1289   else if (type == XA_EXIT)
1290     {
1291       /* Ignore EXIT message if the screen is locked. */
1292       if (until_idle_p || !si->locked_p)
1293         {
1294           clientmessage_response (si, window, False,
1295                                   "EXIT ClientMessage received.",
1296                                   "exiting.");
1297           if (! until_idle_p)
1298             {
1299               unblank_screen (si);
1300               kill_screenhack (si);
1301               XSync (si->dpy, False);
1302             }
1303           saver_exit (si, 0, 0);
1304         }
1305       else
1306         clientmessage_response (si, window, True,
1307                                 "EXIT ClientMessage received while locked.",
1308                                 "screen is locked.");
1309     }
1310   else if (type == XA_RESTART)
1311     {
1312       /* The RESTART message works whether the screensaver is active or not,
1313          unless the screen is locked, in which case it doesn't work.
1314        */
1315       if (until_idle_p || !si->locked_p)
1316         {
1317           clientmessage_response (si, window, False,
1318                                   "RESTART ClientMessage received.",
1319                                   "restarting.");
1320           if (! until_idle_p)
1321             {
1322               unblank_screen (si);
1323               kill_screenhack (si);
1324               XSync (si->dpy, False);
1325             }
1326
1327           fflush (stdout);
1328           fflush (stderr);
1329           if (real_stdout) fflush (real_stdout);
1330           if (real_stderr) fflush (real_stderr);
1331           /* make sure error message shows up before exit. */
1332           if (real_stderr && stderr != real_stderr)
1333             dup2 (fileno(real_stderr), fileno(stderr));
1334
1335           restart_process (si);
1336           exit (1);     /* shouldn't get here; but if restarting didn't work,
1337                            make this command be the same as EXIT. */
1338         }
1339       else
1340         clientmessage_response (si, window, True,
1341                                 "RESTART ClientMessage received while locked.",
1342                                 "screen is locked.");
1343     }
1344   else if (type == XA_DEMO)
1345     {
1346       long arg = event->xclient.data.l[1];
1347       Bool demo_one_hack_p = (arg == 300);
1348
1349       if (demo_one_hack_p)
1350         {
1351           if (until_idle_p)
1352             {
1353               long which = event->xclient.data.l[2];
1354               char buf [255];
1355               char buf2 [255];
1356               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1357               sprintf (buf2, "demoing (%ld).", which);
1358               clientmessage_response (si, window, False, buf, buf2);
1359
1360               if (which < 0) which = 0;         /* 0 == "random" */
1361               si->selection_mode = which;
1362               si->demoing_p = True;
1363
1364               if (si->throttled_p && p->verbose_p)
1365                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1366               si->throttled_p = False;
1367
1368               return True;
1369             }
1370
1371           clientmessage_response (si, window, True,
1372                                   "DEMO ClientMessage received while active.",
1373                                   "already active.");
1374         }
1375       else
1376         {
1377           clientmessage_response (si, window, True,
1378                                   "obsolete form of DEMO ClientMessage.",
1379                                   "obsolete form of DEMO ClientMessage.");
1380         }
1381     }
1382   else if (type == XA_PREFS)
1383     {
1384       clientmessage_response (si, window, True,
1385                               "the PREFS client-message is obsolete.",
1386                               "the PREFS client-message is obsolete.");
1387     }
1388   else if (type == XA_LOCK)
1389     {
1390 #ifdef NO_LOCKING
1391       clientmessage_response (si, window, True,
1392                               "not compiled with support for locking.",
1393                               "locking not enabled.");
1394 #else /* !NO_LOCKING */
1395       if (si->locking_disabled_p)
1396         clientmessage_response (si, window, True,
1397                       "LOCK ClientMessage received, but locking is disabled.",
1398                               "locking not enabled.");
1399       else if (si->locked_p)
1400         clientmessage_response (si, window, True,
1401                            "LOCK ClientMessage received while already locked.",
1402                                 "already locked.");
1403       else
1404         {
1405           char buf [255];
1406           char *response = (until_idle_p
1407                             ? "activating and locking."
1408                             : "locking.");
1409           sprintf (buf, "LOCK ClientMessage received; %s", response);
1410           clientmessage_response (si, window, False, buf, response);
1411           set_locked_p (si, True);
1412           si->selection_mode = 0;
1413           si->demoing_p = False;
1414
1415           if (si->lock_id)      /* we're doing it now, so lose the timeout */
1416             {
1417               XtRemoveTimeOut (si->lock_id);
1418               si->lock_id = 0;
1419             }
1420
1421           if (until_idle_p)
1422             {
1423               if (si->using_mit_saver_extension ||
1424                   si->using_sgi_saver_extension)
1425                 {
1426                   XForceScreenSaver (si->dpy, ScreenSaverActive);
1427                   return False;
1428                 }
1429               else
1430                 {
1431                   return True;
1432                 }
1433             }
1434         }
1435 #endif /* !NO_LOCKING */
1436     }
1437   else if (type == XA_THROTTLE)
1438     {
1439       if (si->throttled_p)
1440         clientmessage_response (si, window, True,
1441                                 "THROTTLE ClientMessage received, but "
1442                                 "already throttled.",
1443                                 "already throttled.");
1444       else
1445         {
1446           char buf [255];
1447           char *response = "throttled.";
1448           si->throttled_p = True;
1449           si->selection_mode = 0;
1450           si->demoing_p = False;
1451           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
1452           clientmessage_response (si, window, False, buf, response);
1453
1454           if (! until_idle_p)
1455             {
1456               if (si->cycle_id)
1457                 XtRemoveTimeOut (si->cycle_id);
1458               si->cycle_id = 0;
1459               cycle_timer ((XtPointer) si, 0);
1460             }
1461         }
1462     }
1463   else if (type == XA_UNTHROTTLE)
1464     {
1465       if (! si->throttled_p)
1466         clientmessage_response (si, window, True,
1467                                 "UNTHROTTLE ClientMessage received, but "
1468                                 "not throttled.",
1469                                 "not throttled.");
1470       else
1471         {
1472           char buf [255];
1473           char *response = "unthrottled.";
1474           si->throttled_p = False;
1475           si->selection_mode = 0;
1476           si->demoing_p = False;
1477           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
1478           clientmessage_response (si, window, False, buf, response);
1479
1480           if (! until_idle_p)
1481             {
1482               if (si->cycle_id)
1483                 XtRemoveTimeOut (si->cycle_id);
1484               si->cycle_id = 0;
1485               cycle_timer ((XtPointer) si, 0);
1486             }
1487         }
1488     }
1489   else
1490     {
1491       char buf [1024];
1492       char *str;
1493       str = (type ? XGetAtomName(si->dpy, type) : 0);
1494
1495       if (str)
1496         {
1497           if (strlen (str) > 80)
1498             strcpy (str+70, "...");
1499           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
1500                    str);
1501           free (str);
1502         }
1503       else
1504         {
1505           sprintf (buf,
1506                    "unrecognised screensaver ClientMessage 0x%x received.",
1507                    (unsigned int) event->xclient.data.l[0]);
1508         }
1509
1510       clientmessage_response (si, window, True, buf, buf);
1511     }
1512   return False;
1513 }
1514
1515 \f
1516 /* Some random diagnostics printed in -verbose mode.
1517  */
1518
1519 static void
1520 analyze_display (saver_info *si)
1521 {
1522   int i, j;
1523   static const char *exts[][2] = {
1524     { "SCREEN_SAVER",             "SGI Screen-Saver" },
1525     { "SCREEN-SAVER",             "SGI Screen-Saver" },
1526     { "MIT-SCREEN-SAVER",         "MIT Screen-Saver" },
1527     { "XIDLE",                    "XIdle" },
1528     { "SGI-VIDEO-CONTROL",        "SGI Video-Control" },
1529     { "READDISPLAY",              "SGI Read-Display" },
1530     { "MIT-SHM",                  "Shared Memory" },
1531     { "DOUBLE-BUFFER",            "Double-Buffering" },
1532     { "DPMS",                     "Power Management" },
1533     { "GLX",                      "GLX" },
1534     { "XFree86-VidModeExtension", "XF86 Video-Mode" },
1535     { "XINERAMA",                 "Xinerama" }
1536   };
1537
1538   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
1539            DisplayString(si->dpy));
1540   fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
1541            ServerVendor(si->dpy), VendorRelease(si->dpy));
1542
1543   fprintf (stderr, "%s: useful extensions:\n", blurb());
1544   for (i = 0; i < countof(exts); i++)
1545     {
1546       int op = 0, event = 0, error = 0;
1547       if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error))
1548         fprintf (stderr, "%s:   %s\n", blurb(), exts[i][1]);
1549     }
1550
1551   for (i = 0; i < si->nscreens; i++)
1552     {
1553       unsigned long colormapped_depths = 0;
1554       unsigned long non_mapped_depths = 0;
1555       XVisualInfo vi_in, *vi_out;
1556       int out_count;
1557       vi_in.screen = i;
1558       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
1559       if (!vi_out) continue;
1560       for (j = 0; j < out_count; j++)
1561         if (vi_out[j].class == PseudoColor)
1562           colormapped_depths |= (1 << vi_out[j].depth);
1563         else
1564           non_mapped_depths  |= (1 << vi_out[j].depth);
1565       XFree ((char *) vi_out);
1566
1567       if (colormapped_depths)
1568         {
1569           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
1570           for (j = 0; j < 32; j++)
1571             if (colormapped_depths & (1 << j))
1572               fprintf (stderr, " %d", j);
1573           fprintf (stderr, "\n");
1574         }
1575       if (non_mapped_depths)
1576         {
1577           fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i);
1578           for (j = 0; j < 32; j++)
1579             if (non_mapped_depths & (1 << j))
1580               fprintf (stderr, " %d", j);
1581           fprintf (stderr, "\n");
1582         }
1583     }
1584 }
1585
1586 Bool
1587 display_is_on_console_p (saver_info *si)
1588 {
1589   Bool not_on_console = True;
1590   char *dpystr = DisplayString (si->dpy);
1591   char *tail = (char *) strchr (dpystr, ':');
1592   if (! tail || strncmp (tail, ":0", 2))
1593     not_on_console = True;
1594   else
1595     {
1596       char dpyname[255], localname[255];
1597       strncpy (dpyname, dpystr, tail-dpystr);
1598       dpyname [tail-dpystr] = 0;
1599       if (!*dpyname ||
1600           !strcmp(dpyname, "unix") ||
1601           !strcmp(dpyname, "localhost"))
1602         not_on_console = False;
1603       else if (gethostname (localname, sizeof (localname)))
1604         not_on_console = True;  /* can't find hostname? */
1605       else
1606         {
1607           /* We have to call gethostbyname() on the result of gethostname()
1608              because the two aren't guarenteed to be the same name for the
1609              same host: on some losing systems, one is a FQDN and the other
1610              is not.  Here in the wide wonderful world of Unix it's rocket
1611              science to obtain the local hostname in a portable fashion.
1612              
1613              And don't forget, gethostbyname() reuses the structure it
1614              returns, so we have to copy the fucker before calling it again.
1615              Thank you master, may I have another.
1616            */
1617           struct hostent *h = gethostbyname (dpyname);
1618           if (!h)
1619             not_on_console = True;
1620           else
1621             {
1622               char hn [255];
1623               struct hostent *l;
1624               strcpy (hn, h->h_name);
1625               l = gethostbyname (localname);
1626               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
1627             }
1628         }
1629     }
1630   return !not_on_console;
1631 }