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