From http://www.jwz.org/xscreensaver/xscreensaver-5.33.tar.gz
[xscreensaver] / driver / xscreensaver.c
1 /* xscreensaver, Copyright (c) 1991-2014 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.
20  *
21  *   We place a __SWM_VROOT property on this window, so that newly-started
22  *   clients will think that this window is a "virtual root" window (as per
23  *   the logic in the historical "vroot.h" header.)
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  *   On multi-screen systems, we do the above on each screen, and start
42  *   multiple programs, each with a different value of $DISPLAY.
43  *
44  *   On Xinerama systems, we do a similar thing, but instead create multiple
45  *   windows on the (only) display, and tell the subprocess which one to use
46  *   via the $XSCREENSAVER_WINDOW environment variable -- this trick requires
47  *   a recent (Aug 2003) revision of vroot.h.
48  *
49  *   (See comments in screens.c for more details about Xinerama/RANDR stuff.)
50  *
51  *   While we are waiting for user activity, we also set up timers so that,
52  *   after a certain amount of time has passed, we can start a different
53  *   screenhack.  We do this by killing the running child process with
54  *   SIGTERM, and then starting a new one in the same way.
55  *
56  *   If there was a real virtual root, meaning that we removed the __SWM_VROOT
57  *   property from it, meaning we must (absolutely must) restore it before we
58  *   exit, then we set up signal handlers for most signals (SIGINT, SIGTERM,
59  *   etc.) that do this.  Most Xlib and Xt routines are not reentrant, so it
60  *   is not generally safe to call them from signal handlers; however, this
61  *   program spends most of its time waiting, so the window of opportunity 
62  *   when code could be called reentrantly is fairly small; and also, the worst
63  *   that could happen is that the call would fail.  If we've gotten one of
64  *   these signals, then we're on our way out anyway.  If we didn't restore the
65  *   __SWM_VROOT property, that would be very bad, so it's worth a shot.  Note
66  *   that this means that, if you're using a virtual-root window manager, you
67  *   can really fuck up the world by killing this process with "kill -9".
68  *
69  *   This program accepts ClientMessages of type SCREENSAVER; these messages
70  *   may contain the atoms ACTIVATE, DEACTIVATE, etc, meaning to turn the 
71  *   screensaver on or off now, regardless of the idleness of the user,
72  *   and a few other things.  The included "xscreensaver-command" program
73  *   sends these messsages.
74  *
75  *   If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER
76  *   extensions, then we do the XAutoLock trick: notice every window that
77  *   gets created, and wait 30 seconds or so until its creating process has
78  *   settled down, and then select KeyPress events on those windows which
79  *   already select for KeyPress events.  It's important that we not select
80  *   KeyPress on windows which don't select them, because that would
81  *   interfere with event propagation.  This will break if any program
82  *   changes its event mask to contain KeyRelease or PointerMotion more than
83  *   30 seconds after creating the window, but such programs do not seem to
84  *   occur in nature (I've never seen it happen in all these years.)
85  *
86  *   The reason that we can't select KeyPresses on windows that don't have
87  *   them already is that, when dispatching a KeyPress event, X finds the
88  *   lowest (leafmost) window in the hierarchy on which *any* client selects
89  *   for KeyPress, and sends the event to that window.  This means that if a
90  *   client had a window with subwindows, and expected to receive KeyPress
91  *   events on the parent window instead of the subwindows, then that client
92  *   would malfunction if some other client selected KeyPress events on the
93  *   subwindows.  It is an incredible misdesign that one client can make
94  *   another client malfunction in this way.
95  *
96  *   But here's a new kink that started showing up in late 2014: GNOME programs
97  *   don't actually select for or receive KeyPress events! They do it behind
98  *   the scenes through some kind of Input Method magic, even when running in
99  *   an en_US locale.  However, in that case, those applications *do* seem to
100  *   update the _NET_WM_USER_TIME on their own windows every time they have
101  *   received a secret KeyPress, so we *also* monitor that property on every
102  *   window, and treat changes to it as identical to KeyPress.
103  *
104  *   To detect mouse motion, we periodically wake up and poll the mouse
105  *   position and button/modifier state, and notice when something has
106  *   changed.  We make this check every five seconds by default, and since the
107  *   screensaver timeout has a granularity of one minute, this makes the
108  *   chance of a false positive very small.  We could detect mouse motion in
109  *   the same way as keyboard activity, but that would suffer from the same
110  *   "client changing event mask" problem that the KeyPress events hack does.
111  *   I think polling is more reliable.
112  *
113  *   On systems with /proc/interrupts (Linux) we poll that file and note when
114  *   the interrupt counter numbers on the "keyboard" and "PS/2" lines change.
115  *   (There is no reliable way, using /proc/interrupts, to detect non-PS/2
116  *   mice, so it doesn't help for serial or USB mice.)
117  *
118  *   None of this crap happens if we're using one of the extensions.  Sadly,
119  *   the XIdle extension hasn't been available for many years; the SGI
120  *   extension only exists on SGIs; and the MIT extension, while widely
121  *   deployed, is garbage in several ways.
122  *
123  *   A third idle-detection option could be implemented (but is not): when
124  *   running on the console display ($DISPLAY is `localhost`:0) and we're on a
125  *   machine where /dev/tty and /dev/mouse have reasonable last-modification
126  *   times, we could just stat() those.  But the incremental benefit of
127  *   implementing this is really small, so forget I said anything.
128  *
129  *   Debugging hints:
130  *     - Have a second terminal handy.
131  *     - Be careful where you set your breakpoints, you don't want this to
132  *       stop under the debugger with the keyboard grabbed or the blackout
133  *       window exposed.
134  *     - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
135  *       to keep your emacs window alive even when xscreensaver has grabbed.
136  *     - Go read the code related to `debug_p'.
137  *     - You probably can't set breakpoints in functions that are called on
138  *       the other side of a call to fork() -- if your subprocesses are
139  *       dying with signal 5, Trace/BPT Trap, you're losing in this way.
140  *     - If you aren't using a server extension, don't leave this stopped
141  *       under the debugger for very long, or the X input buffer will get
142  *       huge because of the keypress events it's selecting for.  This can
143  *       make your X server wedge with "no more input buffers."
144  *
145  * ======================================================================== */
146
147 #ifdef HAVE_CONFIG_H
148 # include "config.h"
149 #endif
150
151 #include <stdio.h>
152 #include <ctype.h>
153 #include <X11/Xlib.h>
154
155 #ifdef ENABLE_NLS
156 # include <locale.h>
157 # include <libintl.h>
158 #endif /* ENABLE_NLS */
159
160 #include <X11/Xlibint.h>
161
162 #include <X11/Xatom.h>
163 #include <X11/Intrinsic.h>
164 #include <X11/StringDefs.h>
165 #include <X11/Shell.h>
166 #include <X11/Xos.h>
167 #include <time.h>
168 #include <sys/time.h>
169 #include <netdb.h>      /* for gethostbyname() */
170 #include <sys/types.h>
171 #include <pwd.h>
172 #ifdef HAVE_XMU
173 # ifndef VMS
174 #  include <X11/Xmu/Error.h>
175 # else  /* !VMS */
176 #  include <Xmu/Error.h>
177 # endif /* !VMS */
178 #else  /* !HAVE_XMU */
179 # include "xmu.h"
180 #endif /* !HAVE_XMU */
181
182 #ifdef HAVE_MIT_SAVER_EXTENSION
183 #include <X11/extensions/scrnsaver.h>
184 #endif /* HAVE_MIT_SAVER_EXTENSION */
185
186 #ifdef HAVE_XIDLE_EXTENSION
187 # include <X11/extensions/xidle.h>
188 #endif /* HAVE_XIDLE_EXTENSION */
189
190 #ifdef HAVE_SGI_VC_EXTENSION
191 # include <X11/extensions/XSGIvc.h>
192 #endif /* HAVE_SGI_VC_EXTENSION */
193
194 #ifdef HAVE_READ_DISPLAY_EXTENSION
195 # include <X11/extensions/readdisplay.h>
196 #endif /* HAVE_READ_DISPLAY_EXTENSION */
197
198 #ifdef HAVE_XSHM_EXTENSION
199 # include <X11/extensions/XShm.h>
200 #endif /* HAVE_XSHM_EXTENSION */
201
202 #ifdef HAVE_DPMS_EXTENSION
203 # include <X11/extensions/dpms.h>
204 #endif /* HAVE_DPMS_EXTENSION */
205
206
207 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
208 # include <X11/extensions/Xdbe.h>
209 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
210
211 #ifdef HAVE_XF86VMODE
212 # include <X11/extensions/xf86vmode.h>
213 #endif /* HAVE_XF86VMODE */
214
215 #ifdef HAVE_XF86MISCSETGRABKEYSSTATE
216 # include <X11/extensions/xf86misc.h>
217 #endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
218
219 #ifdef HAVE_XINERAMA
220 # include <X11/extensions/Xinerama.h>
221 #endif /* HAVE_XINERAMA */
222
223 #ifdef HAVE_RANDR
224 # include <X11/extensions/Xrandr.h>
225 #endif /* HAVE_RANDR */
226
227
228 #include "xscreensaver.h"
229 #include "version.h"
230 #include "yarandom.h"
231 #include "resources.h"
232 #include "visual.h"
233 #include "usleep.h"
234 #include "auth.h"
235
236 saver_info *global_si_kludge = 0;       /* I hate C so much... */
237
238 char *progname = 0;
239 char *progclass = 0;
240 XrmDatabase db = 0;
241
242
243 static Atom XA_SCREENSAVER_RESPONSE;
244 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
245 static Atom XA_RESTART, XA_SELECT;
246 static Atom XA_THROTTLE, XA_UNTHROTTLE;
247 Atom XA_DEMO, XA_PREFS, XA_EXIT, XA_LOCK, XA_BLANK;
248
249 \f
250 static XrmOptionDescRec options [] = {
251
252   { "-verbose",            ".verbose",          XrmoptionNoArg, "on" },
253   { "-silent",             ".verbose",          XrmoptionNoArg, "off" },
254
255   /* xscreensaver-demo uses this one */
256   { "-nosplash",           ".splash",           XrmoptionNoArg, "off" },
257   { "-no-splash",          ".splash",           XrmoptionNoArg, "off" },
258
259   /* useful for debugging */
260   { "-no-capture-stderr",  ".captureStderr",    XrmoptionNoArg, "off" },
261   { "-log",                ".logFile",          XrmoptionSepArg, 0 },
262 };
263
264 #ifdef __GNUC__
265  __extension__     /* shut up about "string length is greater than the length
266                       ISO C89 compilers are required to support" when including
267                       the .ad file... */
268 #endif
269
270 static char *defaults[] = {
271 #include "XScreenSaver_ad.h"
272  0
273 };
274
275 #ifdef _VROOT_H_
276 ERROR!  You must not include vroot.h in this file.
277 #endif
278
279 static void
280 do_help (saver_info *si)
281 {
282   char *s, year[5];
283   s = strchr (screensaver_id, '-');
284   s = strrchr (s, '-');
285   s++;
286   strncpy (year, s, 4);
287   year[4] = 0;
288
289   fflush (stdout);
290   fflush (stderr);
291   fprintf (stdout, "\
292 xscreensaver %s, copyright (c) 1991-%s by Jamie Zawinski <jwz@jwz.org>\n\
293 \n\
294   All xscreensaver configuration is via the `~/.xscreensaver' file.\n\
295   Rather than editing that file by hand, just run `xscreensaver-demo':\n\
296   that program lets you configure the screen saver graphically,\n\
297   including timeouts, locking, and display modes.\n\
298 \n",
299           si->version, year);
300   fprintf (stdout, "\
301   Just getting started?  Try this:\n\
302 \n\
303         xscreensaver &\n\
304         xscreensaver-demo\n\
305 \n\
306   For updates, online manual, and FAQ, please see the web page:\n\
307 \n\
308        http://www.jwz.org/xscreensaver/\n\
309 \n");
310
311   fflush (stdout);
312   fflush (stderr);
313   exit (1);
314 }
315
316
317 Bool in_signal_handler_p = 0;   /* I hate C so much... */
318
319 char *
320 timestring (void)
321 {
322   if (in_signal_handler_p)
323     {
324       /* Turns out that ctime() and even localtime_r() call malloc() on Linux!
325          So we can't call them from inside SIGCHLD.  WTF.
326        */
327       static char buf[30];
328       strcpy (buf, "... ... ..   signal ....");
329       return buf;
330     }
331   else
332     {
333       time_t now = time ((time_t *) 0);
334       char *str = (char *) ctime (&now);
335       char *nl = (char *) strchr (str, '\n');
336       if (nl) *nl = 0; /* take off that dang newline */
337       return str;
338     }
339 }
340
341 static Bool blurb_timestamp_p = True;   /* kludge */
342
343 const char *
344 blurb (void)
345 {
346   if (!blurb_timestamp_p)
347     return progname;
348   else
349     {
350       static char buf[255];
351       char *ct = timestring();
352       int n = strlen(progname);
353       if (n > 100) n = 99;
354       strncpy(buf, progname, n);
355       buf[n++] = ':';
356       buf[n++] = ' ';
357       strncpy(buf+n, ct+11, 8);
358       strcpy(buf+n+9, ": ");
359       return buf;
360     }
361 }
362
363
364 int
365 saver_ehandler (Display *dpy, XErrorEvent *error)
366 {
367   saver_info *si = global_si_kludge;    /* I hate C so much... */
368   int i;
369   Bool fatal_p;
370
371   if (!real_stderr) real_stderr = stderr;
372
373   fprintf (real_stderr, "\n"
374            "#######################################"
375            "#######################################\n\n"
376            "%s: X Error!  PLEASE REPORT THIS BUG.\n",
377            blurb());
378
379   for (i = 0; i < si->nscreens; i++)
380     {
381       saver_screen_info *ssi = &si->screens[i];
382       fprintf (real_stderr, "%s: screen %d/%d: 0x%x, 0x%x, 0x%x\n",
383                blurb(), ssi->real_screen_number, ssi->number,
384                (unsigned int) RootWindowOfScreen (si->screens[i].screen),
385                (unsigned int) si->screens[i].real_vroot,
386                (unsigned int) si->screens[i].screensaver_window);
387     }
388
389   fprintf (real_stderr, "\n"
390            "#######################################"
391            "#######################################\n\n");
392
393   fatal_p = XmuPrintDefaultErrorMessage (dpy, error, real_stderr);
394
395   fatal_p = True;  /* The only time I've ever seen a supposedly nonfatal error,
396                       it has been BadImplementation / Xlib sequence lost, which
397                       are in truth pretty damned fatal.
398                     */
399
400   fprintf (real_stderr, "\n");
401
402   if (! fatal_p)
403     fprintf (real_stderr, "%s: nonfatal error.\n\n", blurb());
404   else
405     {
406       if (si->prefs.xsync_p)
407         {
408           saver_exit (si, -1, "because of synchronous X Error");
409         }
410       else
411         {
412 #ifdef __GNUC__
413   __extension__   /* don't warn about "string length is greater than the
414                      length ISO C89 compilers are required to support". */
415 #endif
416           fprintf (real_stderr,
417    "#######################################################################\n"
418    "\n"
419    "    If at all possible, please re-run xscreensaver with the command\n"
420    "    line arguments `-sync -verbose -log log.txt', and reproduce this\n"
421    "    bug.  That will cause xscreensaver to dump a `core' file to the\n"
422    "    current directory.  Please include the stack trace from that core\n"
423    "    file in your bug report.  *DO NOT* mail the core file itself!  That\n"
424    "    won't work.  A \"log.txt\" file will also be written.  Please *do*\n"
425    "    include the complete \"log.txt\" file with your bug report.\n"
426    "\n"
427    "    http://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
428    "    the most useful bug reports, and how to examine core files.\n"
429    "\n"
430    "    The more information you can provide, the better.  But please\n"
431    "    report this bug, regardless!\n"
432    "\n"
433    "#######################################################################\n"
434    "\n"
435    "\n");
436
437           saver_exit (si, -1, 0);
438         }
439     }
440
441   return 0;
442 }
443
444
445 /* This error handler is used only while the X connection is being set up;
446    after we've got a connection, we don't use this handler again.  The only
447    reason for having this is so that we can present a more idiot-proof error
448    message than "cannot open display."
449  */
450 static void 
451 startup_ehandler (String name, String type, String class,
452                   String defalt,  /* one can't even spel properly
453                                      in this joke of a language */
454                   String *av, Cardinal *ac)
455 {
456   char fmt[512];
457   String p[10];
458   saver_info *si = global_si_kludge;    /* I hate C so much... */
459   XrmDatabase *db = XtAppGetErrorDatabase(si->app);
460   *fmt = 0;
461   XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
462                             fmt, sizeof(fmt)-1, *db);
463
464   fprintf (stderr, "%s: ", blurb());
465
466   memset (p, 0, sizeof(p));
467   if (*ac > countof (p)) *ac = countof (p);
468   memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
469   fprintf (stderr, fmt,         /* Did I mention that I hate C? */
470            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
471   fprintf (stderr, "\n");
472
473   describe_uids (si, stderr);
474
475   if (si->orig_uid && !strncmp (si->orig_uid, "root/", 5))
476     {
477       fprintf (stderr, "\n"
478           "%s: This is probably because you're logging in as root.  You\n"
479 "              shouldn't log in as root: you should log in as a normal user,\n"
480 "              and then `su' as needed.  If you insist on logging in as\n"
481 "              root, you will have to turn off X's security features before\n"
482 "              xscreensaver will work.\n"
483                "\n"
484 "              Please read the manual and FAQ for more information:\n",
485                blurb());
486     }
487   else
488     {
489       fprintf (stderr, "\n"
490           "%s: Errors at startup are usually authorization problems.\n"
491 "              But you're not logging in as root (good!) so something\n"
492 "              else must be wrong.  Did you read the manual and the FAQ?\n",
493            blurb());
494     }
495
496   fprintf (stderr, "\n"
497           "              http://www.jwz.org/xscreensaver/faq.html\n"
498           "              http://www.jwz.org/xscreensaver/man.html\n"
499           "\n");
500
501   fflush (stderr);
502   fflush (stdout);
503   exit (1);
504 }
505
506 \f
507 /* The zillions of initializations.
508  */
509
510 /* Set progname, version, etc.  This is done very early.
511  */
512 static void
513 set_version_string (saver_info *si, int *argc, char **argv)
514 {
515   progclass = "XScreenSaver";
516
517   /* progname is reset later, after we connect to X. */
518   progname = strrchr(argv[0], '/');
519   if (progname) progname++;
520   else progname = argv[0];
521
522   if (strlen(progname) > 100)   /* keep it short. */
523     progname[99] = 0;
524
525   /* The X resource database blows up if argv[0] has a "." in it. */
526   {
527     char *s = argv[0];
528     while ((s = strchr (s, '.')))
529       *s = '_';
530   }
531
532   si->version = (char *) malloc (5);
533   memcpy (si->version, screensaver_id + 17, 4);
534   si->version [4] = 0;
535 }
536
537
538 /* Initializations that potentially take place as a priveleged user:
539    If the xscreensaver executable is setuid root, then these initializations
540    are run as root, before discarding privileges.
541  */
542 static void
543 privileged_initialization (saver_info *si, int *argc, char **argv)
544 {
545 #ifndef NO_LOCKING
546   /* before hack_uid() for proper permissions */
547   lock_priv_init (*argc, argv, si->prefs.verbose_p);
548 #endif /* NO_LOCKING */
549
550   hack_uid (si);
551 }
552
553
554 /* Figure out what locking mechanisms are supported.
555  */
556 static void
557 lock_initialization (saver_info *si, int *argc, char **argv)
558 {
559 #ifdef NO_LOCKING
560   si->locking_disabled_p = True;
561   si->nolock_reason = "not compiled with locking support";
562 #else /* !NO_LOCKING */
563
564   /* Finish initializing locking, now that we're out of privileged code. */
565   if (! lock_init (*argc, argv, si->prefs.verbose_p))
566     {
567       si->locking_disabled_p = True;
568       si->nolock_reason = "error getting password";
569     }
570
571   /* If locking is currently enabled, but the environment indicates that
572      we have been launched as GDM's "Background" program, then disable
573      locking just in case.
574    */
575   if (!si->locking_disabled_p && getenv ("RUNNING_UNDER_GDM"))
576     {
577       si->locking_disabled_p = True;
578       si->nolock_reason = "running under GDM";
579     }
580
581   /* If the server is XDarwin (MacOS X) then disable locking.
582      (X grabs only affect X programs, so you can use Command-Tab
583      to bring any other Mac program to the front, e.g., Terminal.)
584    */
585   if (!si->locking_disabled_p)
586     {
587       int op = 0, event = 0, error = 0;
588       Bool macos_p = False;
589
590 #ifdef __APPLE__
591       /* Disable locking if *running* on Apple hardware, since we have no
592          reliable way to determine whether the server is running on MacOS.
593          Hopefully __APPLE__ means "MacOS" and not "Linux on Mac hardware"
594          but I'm not really sure about that.
595        */
596       macos_p = True;
597 #endif
598
599       if (!macos_p)
600         /* This extension exists on the Apple X11 server, but not
601            on earlier versions of the XDarwin server. */
602         macos_p = XQueryExtension (si->dpy, "Apple-DRI", &op, &event, &error);
603
604       if (macos_p)
605         {
606           si->locking_disabled_p = True;
607           si->nolock_reason = "Cannot lock securely on MacOS X";
608         }
609     }
610
611   if (si->prefs.debug_p)    /* But allow locking anyway in debug mode. */
612     si->locking_disabled_p = False;
613
614 #endif /* NO_LOCKING */
615 }
616
617
618 /* Open the connection to the X server, and intern our Atoms.
619  */
620 static Widget
621 connect_to_server (saver_info *si, int *argc, char **argv)
622 {
623   Widget toplevel_shell;
624
625 #ifdef HAVE_PUTENV
626   char *d = getenv ("DISPLAY");
627   if (!d || !*d)
628     {
629       char *ndpy = strdup("DISPLAY=:0.0");
630       /* if (si->prefs.verbose_p) */      /* sigh, too early to test this... */
631         fprintf (stderr,
632                  "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
633                  blurb(), ndpy+8);
634       if (putenv (ndpy))
635         abort ();
636       /* don't free (ndpy) -- some implementations of putenv (BSD 4.4,
637          glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
638          do not.  So we must leak it (and/or the previous setting). Yay.
639        */
640     }
641 #endif /* HAVE_PUTENV */
642
643   XSetErrorHandler (saver_ehandler);
644
645   XtAppSetErrorMsgHandler (si->app, startup_ehandler);
646   toplevel_shell = XtAppInitialize (&si->app, progclass,
647                                     options, XtNumber (options),
648                                     argc, argv, defaults, 0, 0);
649   XtAppSetErrorMsgHandler (si->app, 0);
650
651   si->dpy = XtDisplay (toplevel_shell);
652   si->prefs.db = XtDatabase (si->dpy);
653   XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
654
655   if(strlen(progname) > 100)    /* keep it short. */
656     progname [99] = 0;
657
658   db = si->prefs.db;    /* resources.c needs this */
659
660   XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
661   XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
662   XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
663   XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
664   XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False);
665   XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
666                                          False);
667   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
668   XA_ESETROOT_PMAP_ID = XInternAtom (si->dpy, "ESETROOT_PMAP_ID", False);
669   XA_XROOTPMAP_ID = XInternAtom (si->dpy, "_XROOTPMAP_ID", False);
670   XA_NET_WM_USER_TIME = XInternAtom (si->dpy, "_NET_WM_USER_TIME", False);
671   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
672   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
673   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
674   XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
675   XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
676   XA_PREV = XInternAtom (si->dpy, "PREV", False);
677   XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
678   XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
679   XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
680   XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
681   XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
682   XA_BLANK = XInternAtom (si->dpy, "BLANK", False);
683   XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
684   XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
685
686   return toplevel_shell;
687 }
688
689
690 /* Handle the command-line arguments that were not handled for us by Xt.
691    Issue an error message and exit if there are unknown options.
692  */
693 static void
694 process_command_line (saver_info *si, int *argc, char **argv)
695 {
696   int i;
697   for (i = 1; i < *argc; i++)
698     {
699       if (!strcmp (argv[i], "-debug"))
700         /* no resource for this one, out of paranoia. */
701         si->prefs.debug_p = True;
702
703       else if (!strcmp (argv[i], "-h") ||
704                !strcmp (argv[i], "-help") ||
705                !strcmp (argv[i], "--help"))
706         do_help (si);
707
708       else
709         {
710           const char *s = argv[i];
711           fprintf (stderr, "%s: unknown option \"%s\".  Try \"-help\".\n",
712                    blurb(), s);
713
714           if (s[0] == '-' && s[1] == '-') s++;
715           if (!strcmp (s, "-activate") ||
716               !strcmp (s, "-deactivate") ||
717               !strcmp (s, "-cycle") ||
718               !strcmp (s, "-next") ||
719               !strcmp (s, "-prev") ||
720               !strcmp (s, "-exit") ||
721               !strcmp (s, "-restart") ||
722               !strcmp (s, "-demo") ||
723               !strcmp (s, "-prefs") ||
724               !strcmp (s, "-preferences") ||
725               !strcmp (s, "-lock") ||
726               !strcmp (s, "-version") ||
727               !strcmp (s, "-time"))
728             {
729
730               if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
731                 fprintf (stderr, "\n\
732     Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
733               else
734                 fprintf (stderr, "\n\
735     However, `%s' is an option to the `xscreensaver-command' program.\n", s);
736
737               fprintf (stderr, "\
738     The `xscreensaver' program is a daemon that runs in the background.\n\
739     You control a running xscreensaver process by sending it messages\n\
740     with `xscreensaver-demo' or `xscreensaver-command'.\n\
741 .   See the man pages for details, or check the web page:\n\
742     http://www.jwz.org/xscreensaver/\n\n");
743             }
744
745           exit (1);
746         }
747     }
748 }
749
750
751 /* Print out the xscreensaver banner to the tty if applicable;
752    Issue any other warnings that are called for at this point.
753  */
754 static void
755 print_banner (saver_info *si)
756 {
757   saver_preferences *p = &si->prefs;
758
759   char *s, year[5];
760   s = strchr (screensaver_id, '-');
761   s = strrchr (s, '-');
762   s++;
763   strncpy (year, s, 4);
764   year[4] = 0;
765
766   /* This resource gets set some time before the others, so that we know
767      whether to print the banner (and so that the banner gets printed before
768      any resource-database-related error messages.)
769    */
770   p->verbose_p = (p->debug_p || 
771                   get_boolean_resource (si->dpy, "verbose", "Boolean"));
772
773   /* Ditto, for the locking_disabled_p message. */
774   p->lock_p = get_boolean_resource (si->dpy, "lock", "Boolean");
775
776   if (p->verbose_p)
777     fprintf (stderr,
778              "%s %s, copyright (c) 1991-%s "
779              "by Jamie Zawinski <jwz@jwz.org>.\n",
780              progname, si->version, year);
781
782   if (p->debug_p)
783     fprintf (stderr, "\n"
784              "%s: Warning: running in DEBUG MODE.  Be afraid.\n"
785              "\n"
786              "\tNote that in debug mode, the xscreensaver window will only\n"
787              "\tcover the left half of the screen.  (The idea is that you\n"
788              "\tcan still see debugging output in a shell, if you position\n"
789              "\tit on the right side of the screen.)\n"
790              "\n"
791              "\tDebug mode is NOT SECURE.  Do not run with -debug in\n"
792              "\tuntrusted environments.\n"
793              "\n",
794              blurb());
795
796   if (p->verbose_p && senescent_p ())
797     fprintf (stderr, "\n"
798              "*************************************"
799              "**************************************\n"
800              "%s: Warning: this version of xscreensaver is VERY OLD!\n"
801              "%s: Please upgrade!  http://www.jwz.org/xscreensaver/\n"
802              "*************************************"
803              "**************************************\n"
804              "\n",
805              blurb(), blurb());
806
807   if (p->verbose_p)
808     {
809       if (!si->uid_message || !*si->uid_message)
810         describe_uids (si, stderr);
811       else
812         {
813           if (si->orig_uid && *si->orig_uid)
814             fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
815                      blurb(), si->orig_uid);
816           fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
817         }
818
819       fprintf (stderr, "%s: in process %lu.\n", blurb(),
820                (unsigned long) getpid());
821     }
822 }
823
824 static void
825 print_lock_failure_banner (saver_info *si)
826 {
827   saver_preferences *p = &si->prefs;
828
829   /* If locking was not able to be initalized for some reason, explain why.
830      (This has to be done after we've read the lock_p resource.)
831    */
832   if (si->locking_disabled_p)
833     {
834       p->lock_p = False;
835       fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
836                si->nolock_reason);
837       if (strstr (si->nolock_reason, "passw"))
838         fprintf (stderr, "%s: does xscreensaver need to be setuid?  "
839                  "consult the manual.\n", blurb());
840       else if (strstr (si->nolock_reason, "running as "))
841         fprintf (stderr, 
842                  "%s: locking only works when xscreensaver is launched\n"
843                  "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
844                  "\t See the manual for details.\n",
845                  blurb());
846     }
847
848 }
849
850
851 /* called from screens.c so that all the Xt crud is here. */
852 void
853 initialize_screen_root_widget (saver_screen_info *ssi)
854 {
855   saver_info *si = ssi->global;
856   if (ssi->toplevel_shell)
857     XtDestroyWidget (ssi->toplevel_shell);
858   ssi->toplevel_shell =
859     XtVaAppCreateShell (progname, progclass, 
860                         applicationShellWidgetClass,
861                         si->dpy,
862                         XtNscreen, ssi->screen,
863                         XtNvisual, ssi->current_visual,
864                         XtNdepth,  visual_depth (ssi->screen,
865                                                  ssi->current_visual),
866                         NULL);
867 }
868
869
870 /* Examine all of the display's screens, and populate the `saver_screen_info'
871    structures.  Make sure this is called after hack_environment() sets $PATH.
872  */
873 static void
874 initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
875 {
876   int i;
877
878   update_screen_layout (si);
879
880   /* Check to see whether fading is ever possible -- if any of the
881      screens on the display has a PseudoColor visual, then fading can
882      work (on at least some screens.)  If no screen has a PseudoColor
883      visual, then don't bother ever trying to fade, because it will
884      just cause a delay without causing any visible effect.
885    */
886   for (i = 0; i < si->nscreens; i++)
887     {
888       saver_screen_info *ssi = &si->screens[i];
889       if (has_writable_cells (ssi->screen, ssi->current_visual) ||
890           get_visual (ssi->screen, "PseudoColor", True, False) ||
891           get_visual (ssi->screen, "GrayScale", True, False))
892         {
893           si->fading_possible_p = True;
894           break;
895         }
896     }
897
898 #ifdef HAVE_XF86VMODE_GAMMA
899   si->fading_possible_p = True;  /* if we can gamma fade, go for it */
900 #endif
901 }
902
903
904 /* If any server extensions have been requested, try and initialize them.
905    Issue warnings if requests can't be honored.
906  */
907 static void
908 initialize_server_extensions (saver_info *si)
909 {
910   saver_preferences *p = &si->prefs;
911
912   Bool server_has_xidle_extension_p = False;
913   Bool server_has_sgi_saver_extension_p = False;
914   Bool server_has_mit_saver_extension_p = False;
915   Bool system_has_proc_interrupts_p = False;
916   Bool server_has_xinput_extension_p = False;
917   const char *piwhy = 0;
918
919   si->using_xidle_extension = p->use_xidle_extension;
920   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
921   si->using_mit_saver_extension = p->use_mit_saver_extension;
922   si->using_proc_interrupts = p->use_proc_interrupts;
923   si->using_xinput_extension = p->use_xinput_extension;
924
925 #ifdef HAVE_XIDLE_EXTENSION
926   {
927     int ev, er;
928     server_has_xidle_extension_p = XidleQueryExtension (si->dpy, &ev, &er);
929   }
930 #endif
931 #ifdef HAVE_SGI_SAVER_EXTENSION
932   server_has_sgi_saver_extension_p =
933     XScreenSaverQueryExtension (si->dpy,
934                                 &si->sgi_saver_ext_event_number,
935                                 &si->sgi_saver_ext_error_number);
936 #endif
937 #ifdef HAVE_MIT_SAVER_EXTENSION
938   server_has_mit_saver_extension_p =
939     XScreenSaverQueryExtension (si->dpy,
940                                 &si->mit_saver_ext_event_number,
941                                 &si->mit_saver_ext_error_number);
942 #endif
943 #ifdef HAVE_PROC_INTERRUPTS
944   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
945 #endif
946
947 #ifdef HAVE_XINPUT
948   server_has_xinput_extension_p = query_xinput_extension (si);
949 #endif
950
951   if (!server_has_xidle_extension_p)
952     si->using_xidle_extension = False;
953   else if (p->verbose_p)
954     {
955       if (si->using_xidle_extension)
956         fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
957       else
958         fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
959     }
960
961   if (!server_has_sgi_saver_extension_p)
962     si->using_sgi_saver_extension = False;
963   else if (p->verbose_p)
964     {
965       if (si->using_sgi_saver_extension)
966         fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
967       else
968         fprintf (stderr,
969                  "%s: not using server's SGI SCREEN_SAVER extension.\n",
970                  blurb());
971     }
972
973   if (!server_has_mit_saver_extension_p)
974     si->using_mit_saver_extension = False;
975   else if (p->verbose_p)
976     {
977       if (si->using_mit_saver_extension)
978         fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
979                  blurb());
980       else
981         fprintf (stderr,
982                  "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
983                  blurb());
984     }
985
986 #ifdef HAVE_RANDR
987   if (XRRQueryExtension (si->dpy,
988                          &si->randr_event_number, &si->randr_error_number))
989     {
990       int nscreens = ScreenCount (si->dpy);  /* number of *real* screens */
991       int i;
992
993       si->using_randr_extension = TRUE;
994
995       if (p->verbose_p)
996         fprintf (stderr, "%s: selecting RANDR events\n", blurb());
997       for (i = 0; i < nscreens; i++)
998 #  ifdef RRScreenChangeNotifyMask                 /* randr.h 1.5, 2002/09/29 */
999         XRRSelectInput (si->dpy, RootWindow (si->dpy, i),
1000                         RRScreenChangeNotifyMask);
1001 #  else  /* !RRScreenChangeNotifyMask */          /* Xrandr.h 1.4, 2001/06/07 */
1002         XRRScreenChangeSelectInput (si->dpy, RootWindow (si->dpy, i), True);
1003 #  endif /* !RRScreenChangeNotifyMask */
1004     }
1005 # endif /* HAVE_RANDR */
1006
1007 #ifdef HAVE_XINPUT
1008   if (!server_has_xinput_extension_p)
1009     si->using_xinput_extension = False;
1010   else
1011     {
1012       if (si->using_xinput_extension)
1013         init_xinput_extension(si);
1014
1015       if (p->verbose_p)
1016         {
1017           if (si->using_xinput_extension)
1018             fprintf (stderr,
1019                      "%s: selecting events from %d XInputExtension devices.\n",
1020                      blurb(), si->num_xinput_devices);
1021           else
1022             fprintf (stderr,
1023                      "%s: not using XInputExtension.\n",
1024                      blurb());
1025         }
1026     }
1027 #endif
1028
1029   if (!system_has_proc_interrupts_p)
1030     {
1031       si->using_proc_interrupts = False;
1032       if (p->verbose_p && piwhy)
1033         fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
1034                  piwhy);
1035     }
1036   else if (p->verbose_p)
1037     {
1038       if (si->using_proc_interrupts)
1039         fprintf (stderr,
1040                  "%s: consulting /proc/interrupts for keyboard activity.\n",
1041                  blurb());
1042       else
1043         fprintf (stderr,
1044                 "%s: not consulting /proc/interrupts for keyboard activity.\n",
1045                  blurb());
1046     }
1047 }
1048
1049
1050 #ifdef DEBUG_MULTISCREEN
1051 static void
1052 debug_multiscreen_timer (XtPointer closure, XtIntervalId *id)
1053 {
1054   saver_info *si = (saver_info *) closure;
1055   saver_preferences *p = &si->prefs;
1056   if (update_screen_layout (si))
1057     {
1058       if (p->verbose_p)
1059         {
1060           fprintf (stderr, "%s: new layout:\n", blurb());
1061           describe_monitor_layout (si);
1062         }
1063       resize_screensaver_window (si);
1064     }
1065   XtAppAddTimeOut (si->app, 1000*4, debug_multiscreen_timer, (XtPointer) si);
1066 }
1067 #endif /* DEBUG_MULTISCREEN */
1068
1069
1070 /* For the case where we aren't using an server extensions, select user events
1071    on all the existing windows, and launch timers to select events on
1072    newly-created windows as well.
1073
1074    If a server extension is being used, this does nothing.
1075  */
1076 static void
1077 select_events (saver_info *si)
1078 {
1079   saver_preferences *p = &si->prefs;
1080   int i;
1081
1082   if (si->using_xidle_extension ||
1083       si->using_mit_saver_extension ||
1084       si->using_sgi_saver_extension)
1085     return;
1086
1087   if (p->initial_delay)
1088     {
1089       if (p->verbose_p)
1090         {
1091           fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
1092                    (int) p->initial_delay/1000,
1093                    (p->initial_delay == 1000 ? "" : "s"));
1094           fflush (stderr);
1095           fflush (stdout);
1096         }
1097       usleep (p->initial_delay);
1098       if (p->verbose_p)
1099         fprintf (stderr, " done.\n");
1100     }
1101
1102   if (p->verbose_p)
1103     {
1104       fprintf (stderr, "%s: selecting events on extant windows...", blurb());
1105       fflush (stderr);
1106       fflush (stdout);
1107     }
1108
1109   /* Select events on the root windows of every screen.  This also selects
1110      for window creation events, so that new subwindows will be noticed.
1111    */
1112   for (i = 0; i < si->nscreens; i++)
1113     {
1114       saver_screen_info *ssi = &si->screens[i];
1115       if (ssi->real_screen_p)
1116         start_notice_events_timer (si,
1117            RootWindowOfScreen (si->screens[i].screen), False);
1118     }
1119
1120   if (p->verbose_p)
1121     fprintf (stderr, " done.\n");
1122
1123 # ifdef DEBUG_MULTISCREEN
1124   if (p->debug_p) debug_multiscreen_timer ((XtPointer) si, 0);
1125 # endif
1126 }
1127
1128
1129 void
1130 maybe_reload_init_file (saver_info *si)
1131 {
1132   saver_preferences *p = &si->prefs;
1133   if (init_file_changed_p (p))
1134     {
1135       if (p->verbose_p)
1136         fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
1137                  blurb(), init_file_name());
1138
1139       load_init_file (si->dpy, p);
1140
1141       /* If a server extension is in use, and p->timeout has changed,
1142          we need to inform the server of the new timeout. */
1143       disable_builtin_screensaver (si, False);
1144
1145       /* If the DPMS settings in the init file have changed,
1146          change the settings on the server to match. */
1147       sync_server_dpms_settings (si->dpy,
1148                                  (p->dpms_enabled_p  &&
1149                                   p->mode != DONT_BLANK),
1150                                  p->dpms_quickoff_p,
1151                                  p->dpms_standby / 1000,
1152                                  p->dpms_suspend / 1000,
1153                                  p->dpms_off / 1000,
1154                                  False);
1155     }
1156 }
1157
1158
1159 /* Loop forever:
1160
1161        - wait until the user is idle;
1162        - blank the screen;
1163        - wait until the user is active;
1164        - unblank the screen;
1165        - repeat.
1166
1167  */
1168 static void
1169 main_loop (saver_info *si)
1170 {
1171   saver_preferences *p = &si->prefs;
1172   Bool ok_to_unblank;
1173   int i;
1174
1175   while (1)
1176     {
1177       Bool was_locked = False;
1178
1179       if (p->verbose_p)
1180         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1181
1182       check_for_leaks ("unblanked A");
1183       sleep_until_idle (si, True);
1184       check_for_leaks ("unblanked B");
1185
1186       if (p->verbose_p)
1187         {
1188           if (si->demoing_p)
1189             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
1190                      si->selection_mode, timestring());
1191           else
1192             fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
1193                      timestring());
1194         }
1195
1196       maybe_reload_init_file (si);
1197
1198       if (p->mode == DONT_BLANK)
1199         {
1200           if (p->verbose_p)
1201             fprintf (stderr, "%s: idle with blanking disabled at %s.\n",
1202                      blurb(), timestring());
1203
1204           /* Go around the loop and wait for the next bout of idleness,
1205              or for the init file to change, or for a remote command to
1206              come in, or something.
1207
1208              But, if locked_p is true, go ahead.  This can only happen
1209              if we're in "disabled" mode but a "lock" clientmessage came
1210              in: in that case, we should go ahead and blank/lock the screen.
1211            */
1212           if (!si->locked_p)
1213             continue;
1214         }
1215
1216       /* Since we're about to blank the screen, kill the de-race timer,
1217          if any.  It might still be running if we have unblanked and then
1218          re-blanked in a short period (e.g., when using the "next" button
1219          in xscreensaver-demo.)
1220        */
1221       if (si->de_race_id)
1222         {
1223           if (p->verbose_p)
1224             fprintf (stderr, "%s: stopping de-race timer (%d remaining.)\n",
1225                      blurb(), si->de_race_ticks);
1226           XtRemoveTimeOut (si->de_race_id);
1227           si->de_race_id = 0;
1228         }
1229
1230
1231       /* Now, try to blank.
1232        */
1233
1234       if (! blank_screen (si))
1235         {
1236           /* We were unable to grab either the keyboard or mouse.
1237              This means we did not (and must not) blank the screen.
1238              If we were to blank the screen while some other program
1239              is holding both the mouse and keyboard grabbed, then
1240              we would never be able to un-blank it!  We would never
1241              see any events, and the display would be wedged.
1242
1243              In particular, without that keyboard grab, we will be
1244              unable to ever read keypresses on the unlock dialog.
1245              You can't unlock if you can't type your password.
1246
1247              So, just go around the loop again and wait for the
1248              next bout of idleness.  (If the user remains idle, we
1249              will next try to blank the screen again in no more than
1250              60 seconds.)
1251           */
1252           Time retry = 60 * 1000;
1253           if (p->timeout < retry)
1254             retry = p->timeout;
1255
1256           if (p->debug_p)
1257             {
1258               fprintf (stderr,
1259                   "%s: DEBUG MODE: unable to grab -- BLANKING ANYWAY.\n",
1260                        blurb());
1261             }
1262           else
1263             {
1264               fprintf (stderr,
1265                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
1266                        blurb());
1267
1268               schedule_wakeup_event (si, retry, p->debug_p);
1269               continue;
1270             }
1271         }
1272
1273       for (i = 0; i < si->nscreens; i++)
1274         kill_screenhack (&si->screens[i]);
1275
1276       raise_window (si, True, True, False);
1277       if (si->throttled_p)
1278         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
1279       else
1280         for (i = 0; i < si->nscreens; i++)
1281           spawn_screenhack (&si->screens[i]);
1282
1283       /* If we are blanking only, optionally power down monitor right now. */
1284       if (p->mode == BLANK_ONLY &&
1285           p->dpms_enabled_p && 
1286           p->dpms_quickoff_p)
1287         {
1288           sync_server_dpms_settings (si->dpy, True,
1289                                      p->dpms_quickoff_p,
1290                                      p->dpms_standby / 1000,
1291                                      p->dpms_suspend / 1000,
1292                                      p->dpms_off / 1000,
1293                                      False);
1294           monitor_power_on (si, False);
1295         }
1296
1297       /* Don't start the cycle timer in demo mode. */
1298       if (!si->demoing_p && p->cycle)
1299         si->cycle_id = XtAppAddTimeOut (si->app,
1300                                         (si->selection_mode
1301                                          /* see comment in cycle_timer() */
1302                                          ? 1000 * 60 * 60
1303                                          : p->cycle),
1304                                         cycle_timer,
1305                                         (XtPointer) si);
1306
1307
1308 #ifndef NO_LOCKING
1309       /* Maybe start locking the screen.
1310        */
1311       {
1312         Time lock_timeout = p->lock_timeout;
1313
1314         if (si->emergency_lock_p && p->lock_p && lock_timeout)
1315           {
1316             int secs = p->lock_timeout / 1000;
1317             if (p->verbose_p)
1318               fprintf (stderr,
1319                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
1320                        blurb(),
1321                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
1322             lock_timeout = 0;
1323           }
1324
1325         si->emergency_lock_p = False;
1326
1327         if (!si->demoing_p &&           /* if not going into demo mode */
1328             p->lock_p &&                /* and locking is enabled */
1329             !si->locking_disabled_p &&  /* and locking is possible */
1330             lock_timeout == 0)          /* and locking is not timer-deferred */
1331           set_locked_p (si, True);      /* then lock right now. */
1332
1333         /* locked_p might be true already because of the above, or because of
1334            the LOCK ClientMessage.  But if not, and if we're supposed to lock
1335            after some time, set up a timer to do so.
1336         */
1337         if (p->lock_p &&
1338             !si->locked_p &&
1339             lock_timeout > 0)
1340           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
1341                                          activate_lock_timer,
1342                                          (XtPointer) si);
1343       }
1344 #endif /* !NO_LOCKING */
1345
1346
1347       ok_to_unblank = True;
1348       do {
1349
1350         check_for_leaks ("blanked A");
1351         sleep_until_idle (si, False);           /* until not idle */
1352         check_for_leaks ("blanked B");
1353
1354         maybe_reload_init_file (si);
1355
1356 #ifndef NO_LOCKING
1357         /* Maybe unlock the screen.
1358          */
1359         if (si->locked_p)
1360           {
1361             saver_screen_info *ssi = si->default_screen;
1362             if (si->locking_disabled_p) abort ();
1363
1364             was_locked = True;
1365             si->dbox_up_p = True;
1366             for (i = 0; i < si->nscreens; i++)
1367               suspend_screenhack (&si->screens[i], True);         /* suspend */
1368             XUndefineCursor (si->dpy, ssi->screensaver_window);
1369
1370             ok_to_unblank = unlock_p (si);
1371
1372             si->dbox_up_p = False;
1373             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
1374             for (i = 0; i < si->nscreens; i++)
1375               suspend_screenhack (&si->screens[i], False);         /* resume */
1376
1377             if (!ok_to_unblank &&
1378                 !screenhack_running_p (si))
1379               {
1380                 /* If the lock dialog has been dismissed and we're not about to
1381                    unlock the screen, and there is currently no hack running,
1382                    then launch one.  (There might be no hack running if DPMS
1383                    had kicked in.  But DPMS is off now, so bring back the hack)
1384                  */
1385                 if (si->cycle_id)
1386                   XtRemoveTimeOut (si->cycle_id);
1387                 si->cycle_id = 0;
1388                 cycle_timer ((XtPointer) si, 0);
1389               }
1390           }
1391 #endif /* !NO_LOCKING */
1392
1393         } while (!ok_to_unblank);
1394
1395
1396       if (p->verbose_p)
1397         fprintf (stderr, "%s: unblanking screen at %s.\n",
1398                  blurb(), timestring ());
1399
1400       /* Kill before unblanking, to stop drawing as soon as possible. */
1401       for (i = 0; i < si->nscreens; i++)
1402         kill_screenhack (&si->screens[i]);
1403       unblank_screen (si);
1404
1405       set_locked_p (si, False);
1406       si->emergency_lock_p = False;
1407       si->demoing_p = 0;
1408       si->selection_mode = 0;
1409
1410       /* If we're throttled, and the user has explicitly unlocked the screen,
1411          then unthrottle.  If we weren't locked, then don't unthrottle
1412          automatically, because someone might have just bumped the desk... */
1413       if (was_locked)
1414         {
1415           if (si->throttled_p && p->verbose_p)
1416             fprintf (stderr, "%s: unthrottled.\n", blurb());
1417           si->throttled_p = False;
1418         }
1419
1420       if (si->cycle_id)
1421         {
1422           XtRemoveTimeOut (si->cycle_id);
1423           si->cycle_id = 0;
1424         }
1425
1426       if (si->lock_id)
1427         {
1428           XtRemoveTimeOut (si->lock_id);
1429           si->lock_id = 0;
1430         }
1431
1432       /* Since we're unblanked now, break race conditions and make
1433          sure we stay that way (see comment in timers.c.) */
1434       if (! si->de_race_id)
1435         de_race_timer ((XtPointer) si, 0);
1436     }
1437 }
1438
1439 static void analyze_display (saver_info *si);
1440 static void fix_fds (void);
1441
1442 int
1443 main (int argc, char **argv)
1444 {
1445   Widget shell;
1446   saver_info the_si;
1447   saver_info *si = &the_si;
1448   saver_preferences *p = &si->prefs;
1449   struct passwd *spasswd;
1450   int i;
1451
1452   /* It turns out that if we do setlocale (LC_ALL, "") here, people
1453      running in Japanese locales get font craziness on the password
1454      dialog, presumably because it is displaying Japanese characters
1455      in a non-Japanese font.  However, if we don't call setlocale()
1456      at all, then XLookupString() never returns multi-byte UTF-8
1457      characters when people type non-Latin1 characters on the
1458      keyboard.
1459
1460      The current theory (and at this point, I'm really guessing!) is
1461      that using LC_CTYPE instead of LC_ALL will make XLookupString()
1462      behave usefully, without having the side-effect of screwing up
1463      the fonts on the unlock dialog.
1464
1465      See https://bugs.launchpad.net/ubuntu/+source/xscreensaver/+bug/671923
1466      from comment #20 onward.
1467
1468        -- jwz, 24-Sep-2011
1469    */
1470 #ifdef ENABLE_NLS
1471   if (!setlocale (LC_CTYPE, ""))
1472     fprintf (stderr, "%s: warning: could not set default locale\n",
1473              progname);
1474
1475   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1476   textdomain (GETTEXT_PACKAGE);
1477 #endif /* ENABLE_NLS */
1478
1479   memset(si, 0, sizeof(*si));
1480   global_si_kludge = si;        /* I hate C so much... */
1481
1482   fix_fds();
1483
1484 # undef ya_rand_init
1485   ya_rand_init (0);
1486
1487   save_argv (argc, argv);
1488   set_version_string (si, &argc, argv);
1489   privileged_initialization (si, &argc, argv);
1490   hack_environment (si);
1491
1492   spasswd = getpwuid(getuid());
1493   if (!spasswd)
1494     {
1495       fprintf(stderr, "Could not figure out who the current user is!\n");
1496       return 1;
1497     }
1498
1499   si->user = strdup(spasswd->pw_name ? spasswd->pw_name : "(unknown)");
1500
1501 # ifndef NO_LOCKING
1502   si->unlock_cb = gui_auth_conv;
1503   si->auth_finished_cb = auth_finished_cb;
1504 # endif /* !NO_LOCKING */
1505
1506   shell = connect_to_server (si, &argc, argv);
1507   process_command_line (si, &argc, argv);
1508   stderr_log_file (si);
1509   print_banner (si);
1510
1511   load_init_file(si->dpy, p); /* must be before initialize_per_screen_info() */
1512   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1513   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1514
1515   /* We can only issue this warning now. */
1516   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1517     fprintf (stderr,
1518              "%s: there are no PseudoColor or GrayScale visuals.\n"
1519              "%s: ignoring the request for fading/unfading.\n",
1520              blurb(), blurb());
1521
1522   for (i = 0; i < si->nscreens; i++)
1523     {
1524       saver_screen_info *ssi = &si->screens[i];
1525       if (ssi->real_screen_p)
1526         if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1527           exit (1);
1528     }
1529
1530   lock_initialization (si, &argc, argv);
1531   print_lock_failure_banner (si);
1532
1533   if (p->xsync_p) XSynchronize (si->dpy, True);
1534
1535   if (p->verbose_p) analyze_display (si);
1536   initialize_server_extensions (si);
1537
1538   si->blank_time = time ((time_t *) 0); /* must be before ..._window */
1539   initialize_screensaver_window (si);
1540
1541   select_events (si);
1542   init_sigchld ();
1543
1544   disable_builtin_screensaver (si, True);
1545   sync_server_dpms_settings (si->dpy,
1546                              (p->dpms_enabled_p  &&
1547                               p->mode != DONT_BLANK),
1548                              p->dpms_quickoff_p,
1549                              p->dpms_standby / 1000,
1550                              p->dpms_suspend / 1000,
1551                              p->dpms_off / 1000,
1552                              False);
1553
1554   initialize_stderr (si);
1555   handle_signals (si);
1556
1557   make_splash_dialog (si);
1558
1559   main_loop (si);               /* doesn't return */
1560   return 0;
1561 }
1562
1563 static void
1564 fix_fds (void)
1565 {
1566   /* Bad Things Happen if stdin, stdout, and stderr have been closed
1567      (as by the `sh incantation "xscreensaver >&- 2>&-").  When you do
1568      that, the X connection gets allocated to one of these fds, and
1569      then some random library writes to stderr, and random bits get
1570      stuffed down the X pipe, causing "Xlib: sequence lost" errors.
1571      So, we cause the first three file descriptors to be open to
1572      /dev/null if they aren't open to something else already.  This
1573      must be done before any other files are opened (or the closing
1574      of that other file will again free up one of the "magic" first
1575      three FDs.)
1576
1577      We do this by opening /dev/null three times, and then closing
1578      those fds, *unless* any of them got allocated as #0, #1, or #2,
1579      in which case we leave them open.  Gag.
1580
1581      Really, this crap is technically required of *every* X program,
1582      if you want it to be robust in the face of "2>&-".
1583    */
1584   int fd0 = open ("/dev/null", O_RDWR);
1585   int fd1 = open ("/dev/null", O_RDWR);
1586   int fd2 = open ("/dev/null", O_RDWR);
1587   if (fd0 > 2) close (fd0);
1588   if (fd1 > 2) close (fd1);
1589   if (fd2 > 2) close (fd2);
1590 }
1591
1592
1593 \f
1594 /* Processing ClientMessage events.
1595  */
1596
1597
1598 static Bool error_handler_hit_p = False;
1599
1600 static int
1601 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
1602 {
1603   error_handler_hit_p = True;
1604   return 0;
1605 }
1606
1607 /* Sometimes some systems send us ClientMessage events with bogus atoms in
1608    them.  We only look up the atom names for printing warning messages,
1609    so don't bomb out when it happens...
1610  */
1611 static char *
1612 XGetAtomName_safe (Display *dpy, Atom atom)
1613 {
1614   char *result;
1615   XErrorHandler old_handler;
1616   if (!atom) return 0;
1617
1618   XSync (dpy, False);
1619   error_handler_hit_p = False;
1620   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1621   result = XGetAtomName (dpy, atom);
1622   XSync (dpy, False);
1623   XSetErrorHandler (old_handler);
1624   XSync (dpy, False);
1625   if (error_handler_hit_p) result = 0;
1626
1627   if (result)
1628     return result;
1629   else
1630     {
1631       char buf[100];
1632       sprintf (buf, "<<undefined atom 0x%04X>>", (unsigned int) atom);
1633       return strdup (buf);
1634     }
1635 }
1636
1637
1638 static void
1639 clientmessage_response (saver_info *si, Window w, Bool error,
1640                         const char *stderr_msg,
1641                         const char *protocol_msg)
1642 {
1643   char *proto;
1644   int L;
1645   saver_preferences *p = &si->prefs;
1646   XErrorHandler old_handler;
1647
1648   if (error || p->verbose_p)
1649     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1650
1651   L = strlen(protocol_msg);
1652   proto = (char *) malloc (L + 2);
1653   proto[0] = (error ? '-' : '+');
1654   strcpy (proto+1, protocol_msg);
1655   L++;
1656
1657   /* Ignore all X errors while sending a response to a ClientMessage.
1658      Pretty much the only way we could get an error here is if the
1659      window we're trying to send the reply on has been deleted, in
1660      which case, the sender of the ClientMessage won't see our response
1661      anyway.
1662    */
1663   XSync (si->dpy, False);
1664   error_handler_hit_p = False;
1665   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1666
1667   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1668                    PropModeReplace, (unsigned char *) proto, L);
1669
1670   XSync (si->dpy, False);
1671   XSetErrorHandler (old_handler);
1672   XSync (si->dpy, False);
1673
1674   free (proto);
1675 }
1676
1677
1678 static void
1679 bogus_clientmessage_warning (saver_info *si, XEvent *event)
1680 {
1681 #if 0  /* Oh, fuck it.  GNOME likes to spew random ClientMessages at us
1682           all the time.  This is presumably indicative of an error in
1683           the sender of that ClientMessage: if we're getting it and 
1684           ignoring it, then it's not reaching the intended recipient.
1685           But people complain to me about this all the time ("waaah!
1686           xscreensaver is printing to it's stderr and that gets my
1687           panties all in a bunch!")  And I'm sick of hearing about it.
1688           So we'll just ignore these messages and let GNOME go right
1689           ahead and continue to stumble along in its malfunction.
1690         */
1691
1692   saver_preferences *p = &si->prefs;
1693   char *str = XGetAtomName_safe (si->dpy, event->xclient.message_type);
1694   Window w = event->xclient.window;
1695   char wdesc[255];
1696   int screen = 0;
1697   Bool root_p = False;
1698
1699   *wdesc = 0;
1700   for (screen = 0; screen < si->nscreens; screen++)
1701     if (w == si->screens[screen].screensaver_window)
1702       {
1703         strcpy (wdesc, "xscreensaver");
1704         break;
1705       }
1706     else if (w == RootWindow (si->dpy, screen))
1707       {
1708         strcpy (wdesc, "root");
1709         root_p = True;
1710         break;
1711       }
1712
1713   /* If this ClientMessage was sent to the real root window instead of to the
1714      xscreensaver window, then it might be intended for someone else who is
1715      listening on the root window (e.g., the window manager).  So only print
1716      the warning if: we are in debug mode; or if the bogus message was
1717      actually sent to one of the xscreensaver-created windows.
1718    */
1719   if (root_p && !p->debug_p)
1720     return;
1721
1722   if (!*wdesc)
1723     {
1724       XErrorHandler old_handler;
1725       XClassHint hint;
1726       XWindowAttributes xgwa;
1727       memset (&hint, 0, sizeof(hint));
1728       memset (&xgwa, 0, sizeof(xgwa));
1729
1730       XSync (si->dpy, False);
1731       old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1732       XGetClassHint (si->dpy, w, &hint);
1733       XGetWindowAttributes (si->dpy, w, &xgwa);
1734       XSync (si->dpy, False);
1735       XSetErrorHandler (old_handler);
1736       XSync (si->dpy, False);
1737
1738       screen = (xgwa.screen ? screen_number (xgwa.screen) : -1);
1739
1740       sprintf (wdesc, "%.20s / %.20s",
1741                (hint.res_name  ? hint.res_name  : "(null)"),
1742                (hint.res_class ? hint.res_class : "(null)"));
1743       if (hint.res_name)  XFree (hint.res_name);
1744       if (hint.res_class) XFree (hint.res_class);
1745     }
1746
1747   fprintf (stderr, "%s: %d: unrecognised ClientMessage \"%s\" received\n",
1748            blurb(), screen, (str ? str : "(null)"));
1749   fprintf (stderr, "%s: %d: for window 0x%lx (%s)\n",
1750            blurb(), screen, (unsigned long) w, wdesc);
1751   if (str) XFree (str);
1752
1753 #endif /* 0 */
1754 }
1755
1756
1757 Bool
1758 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1759 {
1760   saver_preferences *p = &si->prefs;
1761   Atom type = 0;
1762   Window window = event->xclient.window;
1763
1764   /* Preferences might affect our handling of client messages. */
1765   maybe_reload_init_file (si);
1766
1767   if (event->xclient.message_type != XA_SCREENSAVER ||
1768       event->xclient.format != 32)
1769     {
1770       bogus_clientmessage_warning (si, event);
1771       return False;
1772     }
1773
1774   type = event->xclient.data.l[0];
1775   if (type == XA_ACTIVATE)
1776     {
1777       if (until_idle_p)
1778         {
1779           if (p->mode == DONT_BLANK)
1780             {
1781               clientmessage_response(si, window, True,
1782                          "ACTIVATE ClientMessage received in DONT_BLANK mode.",
1783                                      "screen blanking is currently disabled.");
1784               return False;
1785             }
1786
1787           clientmessage_response(si, window, False,
1788                                  "ACTIVATE ClientMessage received.",
1789                                  "activating.");
1790           si->selection_mode = 0;
1791           si->demoing_p = False;
1792
1793           if (si->throttled_p && p->verbose_p)
1794             fprintf (stderr, "%s: unthrottled.\n", blurb());
1795           si->throttled_p = False;
1796
1797           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1798             {
1799               XForceScreenSaver (si->dpy, ScreenSaverActive);
1800               return False;
1801             }
1802           else
1803             {
1804               return True;
1805             }
1806         }
1807       clientmessage_response(si, window, True,
1808                        "ClientMessage ACTIVATE received while already active.",
1809                              "already active.");
1810     }
1811   else if (type == XA_DEACTIVATE)
1812     {
1813 # if 0
1814       /* When -deactivate is received while locked, pop up the dialog box
1815          instead of just ignoring it.  Some people depend on this behavior
1816          to be able to unlock by using e.g. a fingerprint reader without
1817          also having to click the mouse first.
1818        */
1819       if (si->locked_p) 
1820         {
1821           clientmessage_response(si, window, False,
1822               "DEACTIVATE ClientMessage received while locked: ignored.",
1823               "screen is locked.");
1824         }
1825       else
1826 # endif /* 0 */
1827         {
1828           if (! until_idle_p)
1829             {
1830               if (si->throttled_p && p->verbose_p)
1831                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1832               si->throttled_p = False;
1833
1834               clientmessage_response(si, window, False,
1835                                      "DEACTIVATE ClientMessage received.",
1836                                      "deactivating.");
1837               if (si->using_mit_saver_extension ||
1838                   si->using_sgi_saver_extension)
1839                 {
1840                   XForceScreenSaver (si->dpy, ScreenSaverReset);
1841                   return False;
1842                 }
1843               else
1844                 {
1845                   return True;
1846                 }
1847             }
1848           clientmessage_response(si, window, False,
1849                           "ClientMessage DEACTIVATE received while inactive: "
1850                           "resetting idle timer.",
1851                                  "not active: idle timer reset.");
1852           reset_timers (si);
1853         }
1854     }
1855   else if (type == XA_CYCLE)
1856     {
1857       if (! until_idle_p)
1858         {
1859           clientmessage_response(si, window, False,
1860                                  "CYCLE ClientMessage received.",
1861                                  "cycling.");
1862           si->selection_mode = 0;       /* 0 means randomize when its time. */
1863           si->demoing_p = False;
1864
1865           if (si->throttled_p && p->verbose_p)
1866             fprintf (stderr, "%s: unthrottled.\n", blurb());
1867           si->throttled_p = False;
1868
1869           if (si->cycle_id)
1870             XtRemoveTimeOut (si->cycle_id);
1871           si->cycle_id = 0;
1872           cycle_timer ((XtPointer) si, 0);
1873           return False;
1874         }
1875       clientmessage_response(si, window, True,
1876                              "ClientMessage CYCLE received while inactive.",
1877                              "not active.");
1878     }
1879   else if (type == XA_NEXT || type == XA_PREV)
1880     {
1881       clientmessage_response(si, window, False,
1882                              (type == XA_NEXT
1883                               ? "NEXT ClientMessage received."
1884                               : "PREV ClientMessage received."),
1885                              "cycling.");
1886       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1887       si->demoing_p = False;
1888
1889       if (si->throttled_p && p->verbose_p)
1890         fprintf (stderr, "%s: unthrottled.\n", blurb());
1891       si->throttled_p = False;
1892
1893       if (! until_idle_p)
1894         {
1895           if (si->cycle_id)
1896             XtRemoveTimeOut (si->cycle_id);
1897           si->cycle_id = 0;
1898           cycle_timer ((XtPointer) si, 0);
1899         }
1900       else
1901         return True;
1902     }
1903   else if (type == XA_SELECT)
1904     {
1905       char buf [255];
1906       char buf2 [255];
1907       long which = event->xclient.data.l[1];
1908
1909       if (p->mode == DONT_BLANK)
1910         {
1911           clientmessage_response(si, window, True,
1912                            "SELECT ClientMessage received in DONT_BLANK mode.",
1913                                  "screen blanking is currently disabled.");
1914           return False;
1915         }
1916
1917       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1918       sprintf (buf2, "activating (%ld).", which);
1919       clientmessage_response (si, window, False, buf, buf2);
1920
1921       if (which < 0) which = 0;         /* 0 == "random" */
1922       si->selection_mode = which;
1923       si->demoing_p = False;
1924
1925       if (si->throttled_p && p->verbose_p)
1926         fprintf (stderr, "%s: unthrottled.\n", blurb());
1927       si->throttled_p = False;
1928
1929       if (! until_idle_p)
1930         {
1931           if (si->cycle_id)
1932             XtRemoveTimeOut (si->cycle_id);
1933           si->cycle_id = 0;
1934           cycle_timer ((XtPointer) si, 0);
1935         }
1936       else
1937         return True;
1938     }
1939   else if (type == XA_EXIT)
1940     {
1941       /* Ignore EXIT message if the screen is locked. */
1942       if (until_idle_p || !si->locked_p)
1943         {
1944           clientmessage_response (si, window, False,
1945                                   "EXIT ClientMessage received.",
1946                                   "exiting.");
1947           if (! until_idle_p)
1948             {
1949               int i;
1950               for (i = 0; i < si->nscreens; i++)
1951                 kill_screenhack (&si->screens[i]);
1952               unblank_screen (si);
1953               XSync (si->dpy, False);
1954             }
1955           saver_exit (si, 0, 0);
1956         }
1957       else
1958         clientmessage_response (si, window, True,
1959                                 "EXIT ClientMessage received while locked.",
1960                                 "screen is locked.");
1961     }
1962   else if (type == XA_RESTART)
1963     {
1964       /* The RESTART message works whether the screensaver is active or not,
1965          unless the screen is locked, in which case it doesn't work.
1966        */
1967       if (until_idle_p || !si->locked_p)
1968         {
1969           clientmessage_response (si, window, False,
1970                                   "RESTART ClientMessage received.",
1971                                   "restarting.");
1972           if (! until_idle_p)
1973             {
1974               int i;
1975               for (i = 0; i < si->nscreens; i++)
1976                 kill_screenhack (&si->screens[i]);
1977               unblank_screen (si);
1978               XSync (si->dpy, False);
1979             }
1980
1981           restart_process (si);  /* does not return */
1982           abort();
1983         }
1984       else
1985         clientmessage_response (si, window, True,
1986                                 "RESTART ClientMessage received while locked.",
1987                                 "screen is locked.");
1988     }
1989   else if (type == XA_DEMO)
1990     {
1991       long arg = event->xclient.data.l[1];
1992       Bool demo_one_hack_p = (arg == 5000);
1993
1994       if (demo_one_hack_p)
1995         {
1996           if (until_idle_p)
1997             {
1998               long which = event->xclient.data.l[2];
1999               char buf [255];
2000               char buf2 [255];
2001               sprintf (buf, "DEMO %ld ClientMessage received.", which);
2002               sprintf (buf2, "demoing (%ld).", which);
2003               clientmessage_response (si, window, False, buf, buf2);
2004
2005               if (which < 0) which = 0;         /* 0 == "random" */
2006               si->selection_mode = which;
2007               si->demoing_p = True;
2008
2009               if (si->throttled_p && p->verbose_p)
2010                 fprintf (stderr, "%s: unthrottled.\n", blurb());
2011               si->throttled_p = False;
2012
2013               return True;
2014             }
2015
2016           clientmessage_response (si, window, True,
2017                                   "DEMO ClientMessage received while active.",
2018                                   "already active.");
2019         }
2020       else
2021         {
2022           clientmessage_response (si, window, True,
2023                                   "obsolete form of DEMO ClientMessage.",
2024                                   "obsolete form of DEMO ClientMessage.");
2025         }
2026     }
2027   else if (type == XA_PREFS)
2028     {
2029       clientmessage_response (si, window, True,
2030                               "the PREFS client-message is obsolete.",
2031                               "the PREFS client-message is obsolete.");
2032     }
2033   else if (type == XA_LOCK)
2034     {
2035 #ifdef NO_LOCKING
2036       clientmessage_response (si, window, True,
2037                               "not compiled with support for locking.",
2038                               "locking not enabled.");
2039 #else /* !NO_LOCKING */
2040       if (si->locking_disabled_p)
2041         clientmessage_response (si, window, True,
2042                       "LOCK ClientMessage received, but locking is disabled.",
2043                               "locking not enabled.");
2044       else if (si->locked_p)
2045         clientmessage_response (si, window, True,
2046                            "LOCK ClientMessage received while already locked.",
2047                                 "already locked.");
2048       else
2049         {
2050           char buf [255];
2051           char *response = (until_idle_p
2052                             ? "activating and locking."
2053                             : "locking.");
2054           sprintf (buf, "LOCK ClientMessage received; %s", response);
2055           clientmessage_response (si, window, False, buf, response);
2056           set_locked_p (si, True);
2057           si->selection_mode = 0;
2058           si->demoing_p = False;
2059
2060           if (si->lock_id)      /* we're doing it now, so lose the timeout */
2061             {
2062               XtRemoveTimeOut (si->lock_id);
2063               si->lock_id = 0;
2064             }
2065
2066           if (until_idle_p)
2067             {
2068               if (si->using_mit_saver_extension ||
2069                   si->using_sgi_saver_extension)
2070                 {
2071                   XForceScreenSaver (si->dpy, ScreenSaverActive);
2072                   return False;
2073                 }
2074               else
2075                 {
2076                   return True;
2077                 }
2078             }
2079         }
2080 #endif /* !NO_LOCKING */
2081     }
2082   else if (type == XA_THROTTLE)
2083     {
2084       /* The THROTTLE command is deprecated -- it predates the XDPMS
2085          extension.  Instead of using -throttle, users should instead
2086          just power off the monitor (e.g., "xset dpms force off".)
2087          In a few minutes, xscreensaver will notice that the monitor
2088          is off, and cease running hacks.
2089        */
2090       if (si->throttled_p)
2091         clientmessage_response (si, window, True,
2092                                 "THROTTLE ClientMessage received, but "
2093                                 "already throttled.",
2094                                 "already throttled.");
2095       else
2096         {
2097           char buf [255];
2098           char *response = "throttled.";
2099           si->throttled_p = True;
2100           si->selection_mode = 0;
2101           si->demoing_p = False;
2102           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
2103           clientmessage_response (si, window, False, buf, response);
2104
2105           if (! until_idle_p)
2106             {
2107               if (si->cycle_id)
2108                 XtRemoveTimeOut (si->cycle_id);
2109               si->cycle_id = 0;
2110               cycle_timer ((XtPointer) si, 0);
2111             }
2112         }
2113     }
2114   else if (type == XA_UNTHROTTLE)
2115     {
2116       if (! si->throttled_p)
2117         clientmessage_response (si, window, True,
2118                                 "UNTHROTTLE ClientMessage received, but "
2119                                 "not throttled.",
2120                                 "not throttled.");
2121       else
2122         {
2123           char buf [255];
2124           char *response = "unthrottled.";
2125           si->throttled_p = False;
2126           si->selection_mode = 0;
2127           si->demoing_p = False;
2128           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
2129           clientmessage_response (si, window, False, buf, response);
2130
2131           if (! until_idle_p)
2132             {
2133               if (si->cycle_id)
2134                 XtRemoveTimeOut (si->cycle_id);
2135               si->cycle_id = 0;
2136               cycle_timer ((XtPointer) si, 0);
2137             }
2138         }
2139     }
2140   else
2141     {
2142       char buf [1024];
2143       char *str;
2144       str = XGetAtomName_safe (si->dpy, type);
2145
2146       if (str)
2147         {
2148           if (strlen (str) > 80)
2149             strcpy (str+70, "...");
2150           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
2151                    str);
2152           free (str);
2153         }
2154       else
2155         {
2156           sprintf (buf,
2157                    "unrecognised screensaver ClientMessage 0x%x received.",
2158                    (unsigned int) event->xclient.data.l[0]);
2159         }
2160
2161       clientmessage_response (si, window, True, buf, buf);
2162     }
2163   return False;
2164 }
2165
2166 \f
2167 /* Some random diagnostics printed in -verbose mode.
2168  */
2169
2170 static void
2171 analyze_display (saver_info *si)
2172 {
2173   int i, j;
2174   static struct {
2175     const char *name; const char *desc; 
2176     Bool useful_p;
2177     Status (*version_fn) (Display *, int *majP, int *minP);
2178   } exts[] = {
2179
2180    { "SCREEN_SAVER", /* underscore */           "SGI Screen-Saver",
2181 #     ifdef HAVE_SGI_SAVER_EXTENSION
2182         True,  0
2183 #     else
2184         False, 0
2185 #     endif
2186    }, { "SCREEN-SAVER", /* dash */              "SGI Screen-Saver",
2187 #     ifdef HAVE_SGI_SAVER_EXTENSION
2188         True,  0
2189 #     else
2190         False, 0
2191 #     endif
2192    }, { "MIT-SCREEN-SAVER",                     "MIT Screen-Saver",
2193 #     ifdef HAVE_MIT_SAVER_EXTENSION
2194         True,  XScreenSaverQueryVersion
2195 #     else
2196         False, 0
2197 #     endif
2198    }, { "XIDLE",                                "XIdle",           
2199 #     ifdef HAVE_XIDLE_EXTENSION
2200         True,  0
2201 #     else
2202         False, 0
2203 #     endif
2204    }, { "SGI-VIDEO-CONTROL",                    "SGI Video-Control",
2205 #     ifdef HAVE_SGI_VC_EXTENSION
2206         True,  XSGIvcQueryVersion
2207 #     else
2208         False, 0
2209 #     endif
2210    }, { "READDISPLAY",                          "SGI Read-Display",
2211 #     ifdef HAVE_READ_DISPLAY_EXTENSION
2212         True,  XReadDisplayQueryVersion
2213 #     else
2214         False, 0
2215 #     endif
2216    }, { "MIT-SHM",                              "Shared Memory",   
2217 #     ifdef HAVE_XSHM_EXTENSION
2218         True, (Status (*) (Display*,int*,int*)) XShmQueryVersion /* 4 args */
2219 #     else
2220         False, 0
2221 #     endif
2222    }, { "DOUBLE-BUFFER",                        "Double-Buffering",
2223 #     ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2224         True, XdbeQueryExtension
2225 #     else
2226         False, 0
2227 #     endif
2228    }, { "DPMS",                                 "Power Management",
2229 #     ifdef HAVE_DPMS_EXTENSION
2230         True,  DPMSGetVersion
2231 #     else
2232         False, 0
2233 #     endif
2234    }, { "GLX",                                  "GLX",             
2235 #     ifdef HAVE_GL
2236         True,  0
2237 #     else
2238         False, 0
2239 #     endif
2240    }, { "XFree86-VidModeExtension",             "XF86 Video-Mode", 
2241 #     ifdef HAVE_XF86VMODE
2242         True,  XF86VidModeQueryVersion
2243 #     else
2244         False, 0
2245 #     endif
2246    }, { "XC-VidModeExtension",                  "XC Video-Mode", 
2247 #     ifdef HAVE_XF86VMODE
2248         True,  XF86VidModeQueryVersion
2249 #     else
2250         False, 0
2251 #     endif
2252    }, { "XFree86-MISC",                         "XF86 Misc", 
2253 #     ifdef HAVE_XF86MISCSETGRABKEYSSTATE
2254         True,  XF86MiscQueryVersion
2255 #     else
2256         False, 0
2257 #     endif
2258    }, { "XC-MISC",                              "XC Misc", 
2259 #     ifdef HAVE_XF86MISCSETGRABKEYSSTATE
2260         True,  XF86MiscQueryVersion
2261 #     else
2262         False, 0
2263 #     endif
2264    }, { "XINERAMA",                             "Xinerama",
2265 #     ifdef HAVE_XINERAMA
2266         True,  XineramaQueryVersion
2267 #     else
2268         False, 0
2269 #     endif
2270    }, { "RANDR",                                "Resize-and-Rotate",
2271 #     ifdef HAVE_RANDR
2272         True,  XRRQueryVersion
2273 #     else
2274         False, 0
2275 #     endif
2276    }, { "DRI",                                  "DRI",
2277         True,  0
2278    }, { "NV-CONTROL",                           "NVidia",
2279         True,  0
2280    }, { "NV-GLX",                               "NVidia GLX",
2281         True,  0
2282    }, { "Apple-DRI",                            "Apple-DRI (XDarwin)",
2283         True,  0
2284    }, { "XInputExtension",                      "XInput",
2285         True,  0
2286    },
2287   };
2288
2289   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(), 
2290            DisplayString(si->dpy));
2291   fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(),
2292            ServerVendor(si->dpy), VendorRelease(si->dpy));
2293
2294   fprintf (stderr, "%s: useful extensions:\n", blurb());
2295   for (i = 0; i < countof(exts); i++)
2296     {
2297       int op = 0, event = 0, error = 0;
2298       char buf [255];
2299       int maj = 0, min = 0;
2300       int dummy1, dummy2, dummy3;
2301       int j;
2302
2303       /* Most of the extension version functions take 3 args,
2304          writing results into args 2 and 3, but some take more.
2305          We only ever care about the first two results, but we
2306          pass in three extra pointers just in case.
2307        */
2308       Status (*version_fn_2) (Display*,int*,int*,int*,int*,int*) =
2309         (Status (*) (Display*,int*,int*,int*,int*,int*)) exts[i].version_fn;
2310
2311       if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
2312         continue;
2313       sprintf (buf, "%s:   ", blurb());
2314       j = strlen (buf);
2315       strcat (buf, exts[i].desc);
2316
2317       if (!version_fn_2)
2318         ;
2319       else if (version_fn_2 (si->dpy, &maj, &min, &dummy1, &dummy2, &dummy3))
2320         sprintf (buf+strlen(buf), " (%d.%d)", maj, min);
2321       else
2322         strcat (buf, " (unavailable)");
2323
2324       if (!exts[i].useful_p)
2325         strcat (buf, " (disabled at compile time)");
2326       fprintf (stderr, "%s\n", buf);
2327     }
2328
2329   for (i = 0; i < si->nscreens; i++)
2330     {
2331       saver_screen_info *ssi = &si->screens[i];
2332       unsigned long colormapped_depths = 0;
2333       unsigned long non_mapped_depths = 0;
2334       XVisualInfo vi_in, *vi_out;
2335       int out_count;
2336
2337       if (!ssi->real_screen_p) continue;
2338
2339       vi_in.screen = ssi->real_screen_number;
2340       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
2341       if (!vi_out) continue;
2342       for (j = 0; j < out_count; j++) {
2343         if (vi_out[j].depth >= 32) continue;
2344         if (vi_out[j].class == PseudoColor)
2345           colormapped_depths |= (1 << vi_out[j].depth);
2346         else
2347           non_mapped_depths  |= (1 << vi_out[j].depth);
2348         }
2349       XFree ((char *) vi_out);
2350
2351       if (colormapped_depths)
2352         {
2353           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(),
2354                    ssi->real_screen_number);
2355           for (j = 0; j < 32; j++)
2356             if (colormapped_depths & (1 << j))
2357               fprintf (stderr, " %d", j);
2358           fprintf (stderr, ".\n");
2359         }
2360       if (non_mapped_depths)
2361         {
2362           fprintf (stderr, "%s: screen %d non-colormapped depths:",
2363                    blurb(), ssi->real_screen_number);
2364           for (j = 0; j < 32; j++)
2365             if (non_mapped_depths & (1 << j))
2366               fprintf (stderr, " %d", j);
2367           fprintf (stderr, ".\n");
2368         }
2369     }
2370
2371   describe_monitor_layout (si);
2372 }
2373
2374
2375 Bool
2376 display_is_on_console_p (saver_info *si)
2377 {
2378   Bool not_on_console = True;
2379   char *dpystr = DisplayString (si->dpy);
2380   char *tail = (char *) strchr (dpystr, ':');
2381   if (! tail || strncmp (tail, ":0", 2))
2382     not_on_console = True;
2383   else
2384     {
2385       char dpyname[255], localname[255];
2386       strncpy (dpyname, dpystr, tail-dpystr);
2387       dpyname [tail-dpystr] = 0;
2388       if (!*dpyname ||
2389           !strcmp(dpyname, "unix") ||
2390           !strcmp(dpyname, "localhost"))
2391         not_on_console = False;
2392       else if (gethostname (localname, sizeof (localname)))
2393         not_on_console = True;  /* can't find hostname? */
2394       else if (!strncmp (dpyname, "/tmp/launch-", 12))  /* MacOS X launchd */
2395         not_on_console = False;
2396       else
2397         {
2398           /* We have to call gethostbyname() on the result of gethostname()
2399              because the two aren't guarenteed to be the same name for the
2400              same host: on some losing systems, one is a FQDN and the other
2401              is not.  Here in the wide wonderful world of Unix it's rocket
2402              science to obtain the local hostname in a portable fashion.
2403              
2404              And don't forget, gethostbyname() reuses the structure it
2405              returns, so we have to copy the fucker before calling it again.
2406              Thank you master, may I have another.
2407            */
2408           struct hostent *h = gethostbyname (dpyname);
2409           if (!h)
2410             not_on_console = True;
2411           else
2412             {
2413               char hn [255];
2414               struct hostent *l;
2415               strcpy (hn, h->h_name);
2416               l = gethostbyname (localname);
2417               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
2418             }
2419         }
2420     }
2421   return !not_on_console;
2422 }
2423
2424
2425 /* Do a little bit of heap introspection...
2426  */
2427 void
2428 check_for_leaks (const char *where)
2429 {
2430 #if defined(HAVE_SBRK) && defined(LEAK_PARANOIA)
2431   static unsigned long last_brk = 0;
2432   int b = (unsigned long) sbrk(0);
2433   if (last_brk && last_brk < b)
2434     fprintf (stderr, "%s: %s: brk grew by %luK.\n",
2435              blurb(), where,
2436              (((b - last_brk) + 1023) / 1024));
2437   last_brk = b;
2438 #endif /* HAVE_SBRK */
2439 }