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