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