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