From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[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_quickoff_p,
1142                                  p->dpms_standby / 1000,
1143                                  p->dpms_suspend / 1000,
1144                                  p->dpms_off / 1000,
1145                                  False);
1146     }
1147 }
1148
1149
1150 /* Loop forever:
1151
1152        - wait until the user is idle;
1153        - blank the screen;
1154        - wait until the user is active;
1155        - unblank the screen;
1156        - repeat.
1157
1158  */
1159 static void
1160 main_loop (saver_info *si)
1161 {
1162   saver_preferences *p = &si->prefs;
1163   Bool ok_to_unblank;
1164   int i;
1165
1166   while (1)
1167     {
1168       Bool was_locked = False;
1169
1170       if (p->verbose_p)
1171         fprintf (stderr, "%s: awaiting idleness.\n", blurb());
1172
1173       check_for_leaks ("unblanked A");
1174       sleep_until_idle (si, True);
1175       check_for_leaks ("unblanked B");
1176
1177       if (p->verbose_p)
1178         {
1179           if (si->demoing_p)
1180             fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
1181                      si->selection_mode, timestring());
1182           else
1183             fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
1184                      timestring());
1185         }
1186
1187       maybe_reload_init_file (si);
1188
1189       if (p->mode == DONT_BLANK)
1190         {
1191           if (p->verbose_p)
1192             fprintf (stderr, "%s: idle with blanking disabled at %s.\n",
1193                      blurb(), timestring());
1194
1195           /* Go around the loop and wait for the next bout of idleness,
1196              or for the init file to change, or for a remote command to
1197              come in, or something.
1198
1199              But, if locked_p is true, go ahead.  This can only happen
1200              if we're in "disabled" mode but a "lock" clientmessage came
1201              in: in that case, we should go ahead and blank/lock the screen.
1202            */
1203           if (!si->locked_p)
1204             continue;
1205         }
1206
1207       /* Since we're about to blank the screen, kill the de-race timer,
1208          if any.  It might still be running if we have unblanked and then
1209          re-blanked in a short period (e.g., when using the "next" button
1210          in xscreensaver-demo.)
1211        */
1212       if (si->de_race_id)
1213         {
1214           if (p->verbose_p)
1215             fprintf (stderr, "%s: stopping de-race timer (%d remaining.)\n",
1216                      blurb(), si->de_race_ticks);
1217           XtRemoveTimeOut (si->de_race_id);
1218           si->de_race_id = 0;
1219         }
1220
1221
1222       /* Now, try to blank.
1223        */
1224
1225       if (! blank_screen (si))
1226         {
1227           /* We were unable to grab either the keyboard or mouse.
1228              This means we did not (and must not) blank the screen.
1229              If we were to blank the screen while some other program
1230              is holding both the mouse and keyboard grabbed, then
1231              we would never be able to un-blank it!  We would never
1232              see any events, and the display would be wedged.
1233
1234              In particular, without that keyboard grab, we will be
1235              unable to ever read keypresses on the unlock dialog.
1236              You can't unlock if you can't type your password.
1237
1238              So, just go around the loop again and wait for the
1239              next bout of idleness.  (If the user remains idle, we
1240              will next try to blank the screen again in no more than
1241              60 seconds.)
1242           */
1243           Time retry = 60 * 1000;
1244           if (p->timeout < retry)
1245             retry = p->timeout;
1246
1247           if (p->debug_p)
1248             {
1249               fprintf (stderr,
1250                   "%s: DEBUG MODE: unable to grab -- BLANKING ANYWAY.\n",
1251                        blurb());
1252             }
1253           else
1254             {
1255               fprintf (stderr,
1256                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
1257                        blurb());
1258
1259               schedule_wakeup_event (si, retry, p->debug_p);
1260               continue;
1261             }
1262         }
1263
1264       for (i = 0; i < si->nscreens; i++)
1265         kill_screenhack (&si->screens[i]);
1266
1267       raise_window (si, True, True, False);
1268       if (si->throttled_p)
1269         fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
1270       else
1271         for (i = 0; i < si->nscreens; i++)
1272           spawn_screenhack (&si->screens[i]);
1273
1274       /* If we are blanking only, optionally power down monitor right now. */
1275       if (p->mode == BLANK_ONLY &&
1276           p->dpms_enabled_p && 
1277           p->dpms_quickoff_p)
1278         {
1279           sync_server_dpms_settings (si->dpy, True,
1280                                      p->dpms_quickoff_p,
1281                                      p->dpms_standby / 1000,
1282                                      p->dpms_suspend / 1000,
1283                                      p->dpms_off / 1000,
1284                                      False);
1285           monitor_power_on (si, False);
1286         }
1287
1288       /* Don't start the cycle timer in demo mode. */
1289       if (!si->demoing_p && p->cycle)
1290         si->cycle_id = XtAppAddTimeOut (si->app,
1291                                         (si->selection_mode
1292                                          /* see comment in cycle_timer() */
1293                                          ? 1000 * 60 * 60
1294                                          : p->cycle),
1295                                         cycle_timer,
1296                                         (XtPointer) si);
1297
1298
1299 #ifndef NO_LOCKING
1300       /* Maybe start locking the screen.
1301        */
1302       {
1303         Time lock_timeout = p->lock_timeout;
1304
1305         if (si->emergency_lock_p && p->lock_p && lock_timeout)
1306           {
1307             int secs = p->lock_timeout / 1000;
1308             if (p->verbose_p)
1309               fprintf (stderr,
1310                      "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
1311                        blurb(),
1312                        (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
1313             lock_timeout = 0;
1314           }
1315
1316         si->emergency_lock_p = False;
1317
1318         if (!si->demoing_p &&           /* if not going into demo mode */
1319             p->lock_p &&                /* and locking is enabled */
1320             !si->locking_disabled_p &&  /* and locking is possible */
1321             lock_timeout == 0)          /* and locking is not timer-deferred */
1322           set_locked_p (si, True);      /* then lock right now. */
1323
1324         /* locked_p might be true already because of the above, or because of
1325            the LOCK ClientMessage.  But if not, and if we're supposed to lock
1326            after some time, set up a timer to do so.
1327         */
1328         if (p->lock_p &&
1329             !si->locked_p &&
1330             lock_timeout > 0)
1331           si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
1332                                          activate_lock_timer,
1333                                          (XtPointer) si);
1334       }
1335 #endif /* !NO_LOCKING */
1336
1337
1338       ok_to_unblank = True;
1339       do {
1340
1341         check_for_leaks ("blanked A");
1342         sleep_until_idle (si, False);           /* until not idle */
1343         check_for_leaks ("blanked B");
1344
1345         maybe_reload_init_file (si);
1346
1347 #ifndef NO_LOCKING
1348         /* Maybe unlock the screen.
1349          */
1350         if (si->locked_p)
1351           {
1352             saver_screen_info *ssi = si->default_screen;
1353             if (si->locking_disabled_p) abort ();
1354
1355             was_locked = True;
1356             si->dbox_up_p = True;
1357             for (i = 0; i < si->nscreens; i++)
1358               suspend_screenhack (&si->screens[i], True);         /* suspend */
1359             XUndefineCursor (si->dpy, ssi->screensaver_window);
1360
1361             ok_to_unblank = unlock_p (si);
1362
1363             si->dbox_up_p = False;
1364             XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
1365             for (i = 0; i < si->nscreens; i++)
1366               suspend_screenhack (&si->screens[i], False);         /* resume */
1367
1368             if (!ok_to_unblank &&
1369                 !screenhack_running_p (si))
1370               {
1371                 /* If the lock dialog has been dismissed and we're not about to
1372                    unlock the screen, and there is currently no hack running,
1373                    then launch one.  (There might be no hack running if DPMS
1374                    had kicked in.  But DPMS is off now, so bring back the hack)
1375                  */
1376                 if (si->cycle_id)
1377                   XtRemoveTimeOut (si->cycle_id);
1378                 si->cycle_id = 0;
1379                 cycle_timer ((XtPointer) si, 0);
1380               }
1381           }
1382 #endif /* !NO_LOCKING */
1383
1384         } while (!ok_to_unblank);
1385
1386
1387       if (p->verbose_p)
1388         fprintf (stderr, "%s: unblanking screen at %s.\n",
1389                  blurb(), timestring ());
1390
1391       /* Kill before unblanking, to stop drawing as soon as possible. */
1392       for (i = 0; i < si->nscreens; i++)
1393         kill_screenhack (&si->screens[i]);
1394       unblank_screen (si);
1395
1396       set_locked_p (si, False);
1397       si->emergency_lock_p = False;
1398       si->demoing_p = 0;
1399       si->selection_mode = 0;
1400
1401       /* If we're throttled, and the user has explicitly unlocked the screen,
1402          then unthrottle.  If we weren't locked, then don't unthrottle
1403          automatically, because someone might have just bumped the desk... */
1404       if (was_locked)
1405         {
1406           if (si->throttled_p && p->verbose_p)
1407             fprintf (stderr, "%s: unthrottled.\n", blurb());
1408           si->throttled_p = False;
1409         }
1410
1411       if (si->cycle_id)
1412         {
1413           XtRemoveTimeOut (si->cycle_id);
1414           si->cycle_id = 0;
1415         }
1416
1417       if (si->lock_id)
1418         {
1419           XtRemoveTimeOut (si->lock_id);
1420           si->lock_id = 0;
1421         }
1422
1423       /* Since we're unblanked now, break race conditions and make
1424          sure we stay that way (see comment in timers.c.) */
1425       if (! si->de_race_id)
1426         de_race_timer ((XtPointer) si, 0);
1427     }
1428 }
1429
1430 static void analyze_display (saver_info *si);
1431 static void fix_fds (void);
1432
1433 int
1434 main (int argc, char **argv)
1435 {
1436   Widget shell;
1437   saver_info the_si;
1438   saver_info *si = &the_si;
1439   saver_preferences *p = &si->prefs;
1440   struct passwd *spasswd;
1441   int i;
1442
1443   /* It turns out that if we do setlocale (LC_ALL, "") here, people
1444      running in Japanese locales get font craziness on the password
1445      dialog, presumably because it is displaying Japanese characters
1446      in a non-Japanese font.  However, if we don't call setlocale()
1447      at all, then XLookupString() never returns multi-byte UTF-8
1448      characters when people type non-Latin1 characters on the
1449      keyboard.
1450
1451      The current theory (and at this point, I'm really guessing!) is
1452      that using LC_CTYPE instead of LC_ALL will make XLookupString()
1453      behave usefully, without having the side-effect of screwing up
1454      the fonts on the unlock dialog.
1455
1456      See https://bugs.launchpad.net/ubuntu/+source/xscreensaver/+bug/671923
1457      from comment #20 onward.
1458
1459        -- jwz, 24-Sep-2011
1460    */
1461 #ifdef ENABLE_NLS
1462   if (!setlocale (LC_CTYPE, ""))
1463     fprintf (stderr, "%s: warning: could not set default locale\n",
1464              progname);
1465
1466   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1467   textdomain (GETTEXT_PACKAGE);
1468 #endif /* ENABLE_NLS */
1469
1470   memset(si, 0, sizeof(*si));
1471   global_si_kludge = si;        /* I hate C so much... */
1472
1473   fix_fds();
1474
1475 # undef ya_rand_init
1476   ya_rand_init (0);
1477
1478   save_argv (argc, argv);
1479   set_version_string (si, &argc, argv);
1480   privileged_initialization (si, &argc, argv);
1481   hack_environment (si);
1482
1483   spasswd = getpwuid(getuid());
1484   if (!spasswd)
1485     {
1486       fprintf(stderr, "Could not figure out who the current user is!\n");
1487       return 1;
1488     }
1489
1490   si->user = strdup(spasswd->pw_name ? spasswd->pw_name : "(unknown)");
1491
1492 # ifndef NO_LOCKING
1493   si->unlock_cb = gui_auth_conv;
1494   si->auth_finished_cb = auth_finished_cb;
1495 # endif /* !NO_LOCKING */
1496
1497   shell = connect_to_server (si, &argc, argv);
1498   process_command_line (si, &argc, argv);
1499   stderr_log_file (si);
1500   print_banner (si);
1501
1502   load_init_file(si->dpy, p); /* must be before initialize_per_screen_info() */
1503   blurb_timestamp_p = p->timestamp_p;  /* kludge */
1504   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
1505
1506   /* We can only issue this warning now. */
1507   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
1508     fprintf (stderr,
1509              "%s: there are no PseudoColor or GrayScale visuals.\n"
1510              "%s: ignoring the request for fading/unfading.\n",
1511              blurb(), blurb());
1512
1513   for (i = 0; i < si->nscreens; i++)
1514     {
1515       saver_screen_info *ssi = &si->screens[i];
1516       if (ssi->real_screen_p)
1517         if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
1518           exit (1);
1519     }
1520
1521   lock_initialization (si, &argc, argv);
1522   print_lock_failure_banner (si);
1523
1524   if (p->xsync_p) XSynchronize (si->dpy, True);
1525
1526   if (p->verbose_p) analyze_display (si);
1527   initialize_server_extensions (si);
1528
1529   si->blank_time = time ((time_t) 0); /* must be before ..._window */
1530   initialize_screensaver_window (si);
1531
1532   select_events (si);
1533   init_sigchld ();
1534
1535   disable_builtin_screensaver (si, True);
1536   sync_server_dpms_settings (si->dpy,
1537                              (p->dpms_enabled_p  &&
1538                               p->mode != DONT_BLANK),
1539                              p->dpms_quickoff_p,
1540                              p->dpms_standby / 1000,
1541                              p->dpms_suspend / 1000,
1542                              p->dpms_off / 1000,
1543                              False);
1544
1545   initialize_stderr (si);
1546   handle_signals (si);
1547
1548   make_splash_dialog (si);
1549
1550   main_loop (si);               /* doesn't return */
1551   return 0;
1552 }
1553
1554 static void
1555 fix_fds (void)
1556 {
1557   /* Bad Things Happen if stdin, stdout, and stderr have been closed
1558      (as by the `sh incantation "xscreensaver >&- 2>&-").  When you do
1559      that, the X connection gets allocated to one of these fds, and
1560      then some random library writes to stderr, and random bits get
1561      stuffed down the X pipe, causing "Xlib: sequence lost" errors.
1562      So, we cause the first three file descriptors to be open to
1563      /dev/null if they aren't open to something else already.  This
1564      must be done before any other files are opened (or the closing
1565      of that other file will again free up one of the "magic" first
1566      three FDs.)
1567
1568      We do this by opening /dev/null three times, and then closing
1569      those fds, *unless* any of them got allocated as #0, #1, or #2,
1570      in which case we leave them open.  Gag.
1571
1572      Really, this crap is technically required of *every* X program,
1573      if you want it to be robust in the face of "2>&-".
1574    */
1575   int fd0 = open ("/dev/null", O_RDWR);
1576   int fd1 = open ("/dev/null", O_RDWR);
1577   int fd2 = open ("/dev/null", O_RDWR);
1578   if (fd0 > 2) close (fd0);
1579   if (fd1 > 2) close (fd1);
1580   if (fd2 > 2) close (fd2);
1581 }
1582
1583
1584 \f
1585 /* Processing ClientMessage events.
1586  */
1587
1588
1589 static Bool error_handler_hit_p = False;
1590
1591 static int
1592 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
1593 {
1594   error_handler_hit_p = True;
1595   return 0;
1596 }
1597
1598 /* Sometimes some systems send us ClientMessage events with bogus atoms in
1599    them.  We only look up the atom names for printing warning messages,
1600    so don't bomb out when it happens...
1601  */
1602 static char *
1603 XGetAtomName_safe (Display *dpy, Atom atom)
1604 {
1605   char *result;
1606   XErrorHandler old_handler;
1607   if (!atom) return 0;
1608
1609   XSync (dpy, False);
1610   error_handler_hit_p = False;
1611   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1612   result = XGetAtomName (dpy, atom);
1613   XSync (dpy, False);
1614   XSetErrorHandler (old_handler);
1615   XSync (dpy, False);
1616   if (error_handler_hit_p) result = 0;
1617
1618   if (result)
1619     return result;
1620   else
1621     {
1622       char buf[100];
1623       sprintf (buf, "<<undefined atom 0x%04X>>", (unsigned int) atom);
1624       return strdup (buf);
1625     }
1626 }
1627
1628
1629 static void
1630 clientmessage_response (saver_info *si, Window w, Bool error,
1631                         const char *stderr_msg,
1632                         const char *protocol_msg)
1633 {
1634   char *proto;
1635   int L;
1636   saver_preferences *p = &si->prefs;
1637   XErrorHandler old_handler;
1638
1639   if (error || p->verbose_p)
1640     fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
1641
1642   L = strlen(protocol_msg);
1643   proto = (char *) malloc (L + 2);
1644   proto[0] = (error ? '-' : '+');
1645   strcpy (proto+1, protocol_msg);
1646   L++;
1647
1648   /* Ignore all X errors while sending a response to a ClientMessage.
1649      Pretty much the only way we could get an error here is if the
1650      window we're trying to send the reply on has been deleted, in
1651      which case, the sender of the ClientMessage won't see our response
1652      anyway.
1653    */
1654   XSync (si->dpy, False);
1655   error_handler_hit_p = False;
1656   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1657
1658   XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
1659                    PropModeReplace, (unsigned char *) proto, L);
1660
1661   XSync (si->dpy, False);
1662   XSetErrorHandler (old_handler);
1663   XSync (si->dpy, False);
1664
1665   free (proto);
1666 }
1667
1668
1669 static void
1670 bogus_clientmessage_warning (saver_info *si, XEvent *event)
1671 {
1672 #if 0  /* Oh, fuck it.  GNOME likes to spew random ClientMessages at us
1673           all the time.  This is presumably indicative of an error in
1674           the sender of that ClientMessage: if we're getting it and 
1675           ignoring it, then it's not reaching the intended recipient.
1676           But people complain to me about this all the time ("waaah!
1677           xscreensaver is printing to it's stderr and that gets my
1678           panties all in a bunch!")  And I'm sick of hearing about it.
1679           So we'll just ignore these messages and let GNOME go right
1680           ahead and continue to stumble along in its malfunction.
1681         */
1682
1683   saver_preferences *p = &si->prefs;
1684   char *str = XGetAtomName_safe (si->dpy, event->xclient.message_type);
1685   Window w = event->xclient.window;
1686   char wdesc[255];
1687   int screen = 0;
1688   Bool root_p = False;
1689
1690   *wdesc = 0;
1691   for (screen = 0; screen < si->nscreens; screen++)
1692     if (w == si->screens[screen].screensaver_window)
1693       {
1694         strcpy (wdesc, "xscreensaver");
1695         break;
1696       }
1697     else if (w == RootWindow (si->dpy, screen))
1698       {
1699         strcpy (wdesc, "root");
1700         root_p = True;
1701         break;
1702       }
1703
1704   /* If this ClientMessage was sent to the real root window instead of to the
1705      xscreensaver window, then it might be intended for someone else who is
1706      listening on the root window (e.g., the window manager).  So only print
1707      the warning if: we are in debug mode; or if the bogus message was
1708      actually sent to one of the xscreensaver-created windows.
1709    */
1710   if (root_p && !p->debug_p)
1711     return;
1712
1713   if (!*wdesc)
1714     {
1715       XErrorHandler old_handler;
1716       XClassHint hint;
1717       XWindowAttributes xgwa;
1718       memset (&hint, 0, sizeof(hint));
1719       memset (&xgwa, 0, sizeof(xgwa));
1720
1721       XSync (si->dpy, False);
1722       old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1723       XGetClassHint (si->dpy, w, &hint);
1724       XGetWindowAttributes (si->dpy, w, &xgwa);
1725       XSync (si->dpy, False);
1726       XSetErrorHandler (old_handler);
1727       XSync (si->dpy, False);
1728
1729       screen = (xgwa.screen ? screen_number (xgwa.screen) : -1);
1730
1731       sprintf (wdesc, "%.20s / %.20s",
1732                (hint.res_name  ? hint.res_name  : "(null)"),
1733                (hint.res_class ? hint.res_class : "(null)"));
1734       if (hint.res_name)  XFree (hint.res_name);
1735       if (hint.res_class) XFree (hint.res_class);
1736     }
1737
1738   fprintf (stderr, "%s: %d: unrecognised ClientMessage \"%s\" received\n",
1739            blurb(), screen, (str ? str : "(null)"));
1740   fprintf (stderr, "%s: %d: for window 0x%lx (%s)\n",
1741            blurb(), screen, (unsigned long) w, wdesc);
1742   if (str) XFree (str);
1743
1744 #endif /* 0 */
1745 }
1746
1747
1748 Bool
1749 handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
1750 {
1751   saver_preferences *p = &si->prefs;
1752   Atom type = 0;
1753   Window window = event->xclient.window;
1754
1755   /* Preferences might affect our handling of client messages. */
1756   maybe_reload_init_file (si);
1757
1758   if (event->xclient.message_type != XA_SCREENSAVER ||
1759       event->xclient.format != 32)
1760     {
1761       bogus_clientmessage_warning (si, event);
1762       return False;
1763     }
1764
1765   type = event->xclient.data.l[0];
1766   if (type == XA_ACTIVATE)
1767     {
1768       if (until_idle_p)
1769         {
1770           if (p->mode == DONT_BLANK)
1771             {
1772               clientmessage_response(si, window, True,
1773                          "ACTIVATE ClientMessage received in DONT_BLANK mode.",
1774                                      "screen blanking is currently disabled.");
1775               return False;
1776             }
1777
1778           clientmessage_response(si, window, False,
1779                                  "ACTIVATE ClientMessage received.",
1780                                  "activating.");
1781           si->selection_mode = 0;
1782           si->demoing_p = False;
1783
1784           if (si->throttled_p && p->verbose_p)
1785             fprintf (stderr, "%s: unthrottled.\n", blurb());
1786           si->throttled_p = False;
1787
1788           if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
1789             {
1790               XForceScreenSaver (si->dpy, ScreenSaverActive);
1791               return False;
1792             }
1793           else
1794             {
1795               return True;
1796             }
1797         }
1798       clientmessage_response(si, window, True,
1799                        "ClientMessage ACTIVATE received while already active.",
1800                              "already active.");
1801     }
1802   else if (type == XA_DEACTIVATE)
1803     {
1804 # if 0
1805       /* When -deactivate is received while locked, pop up the dialog box
1806          instead of just ignoring it.  Some people depend on this behavior
1807          to be able to unlock by using e.g. a fingerprint reader without
1808          also having to click the mouse first.
1809        */
1810       if (si->locked_p) 
1811         {
1812           clientmessage_response(si, window, False,
1813               "DEACTIVATE ClientMessage received while locked: ignored.",
1814               "screen is locked.");
1815         }
1816       else
1817 # endif /* 0 */
1818         {
1819           if (! until_idle_p)
1820             {
1821               if (si->throttled_p && p->verbose_p)
1822                 fprintf (stderr, "%s: unthrottled.\n", blurb());
1823               si->throttled_p = False;
1824
1825               clientmessage_response(si, window, False,
1826                                      "DEACTIVATE ClientMessage received.",
1827                                      "deactivating.");
1828               if (si->using_mit_saver_extension ||
1829                   si->using_sgi_saver_extension)
1830                 {
1831                   XForceScreenSaver (si->dpy, ScreenSaverReset);
1832                   return False;
1833                 }
1834               else
1835                 {
1836                   return True;
1837                 }
1838             }
1839           clientmessage_response(si, window, False,
1840                           "ClientMessage DEACTIVATE received while inactive: "
1841                           "resetting idle timer.",
1842                                  "not active: idle timer reset.");
1843           reset_timers (si);
1844         }
1845     }
1846   else if (type == XA_CYCLE)
1847     {
1848       if (! until_idle_p)
1849         {
1850           clientmessage_response(si, window, False,
1851                                  "CYCLE ClientMessage received.",
1852                                  "cycling.");
1853           si->selection_mode = 0;       /* 0 means randomize when its time. */
1854           si->demoing_p = False;
1855
1856           if (si->throttled_p && p->verbose_p)
1857             fprintf (stderr, "%s: unthrottled.\n", blurb());
1858           si->throttled_p = False;
1859
1860           if (si->cycle_id)
1861             XtRemoveTimeOut (si->cycle_id);
1862           si->cycle_id = 0;
1863           cycle_timer ((XtPointer) si, 0);
1864           return False;
1865         }
1866       clientmessage_response(si, window, True,
1867                              "ClientMessage CYCLE received while inactive.",
1868                              "not active.");
1869     }
1870   else if (type == XA_NEXT || type == XA_PREV)
1871     {
1872       clientmessage_response(si, window, False,
1873                              (type == XA_NEXT
1874                               ? "NEXT ClientMessage received."
1875                               : "PREV ClientMessage received."),
1876                              "cycling.");
1877       si->selection_mode = (type == XA_NEXT ? -1 : -2);
1878       si->demoing_p = False;
1879
1880       if (si->throttled_p && p->verbose_p)
1881         fprintf (stderr, "%s: unthrottled.\n", blurb());
1882       si->throttled_p = False;
1883
1884       if (! until_idle_p)
1885         {
1886           if (si->cycle_id)
1887             XtRemoveTimeOut (si->cycle_id);
1888           si->cycle_id = 0;
1889           cycle_timer ((XtPointer) si, 0);
1890         }
1891       else
1892         return True;
1893     }
1894   else if (type == XA_SELECT)
1895     {
1896       char buf [255];
1897       char buf2 [255];
1898       long which = event->xclient.data.l[1];
1899
1900       if (p->mode == DONT_BLANK)
1901         {
1902           clientmessage_response(si, window, True,
1903                            "SELECT ClientMessage received in DONT_BLANK mode.",
1904                                  "screen blanking is currently disabled.");
1905           return False;
1906         }
1907
1908       sprintf (buf, "SELECT %ld ClientMessage received.", which);
1909       sprintf (buf2, "activating (%ld).", which);
1910       clientmessage_response (si, window, False, buf, buf2);
1911
1912       if (which < 0) which = 0;         /* 0 == "random" */
1913       si->selection_mode = which;
1914       si->demoing_p = False;
1915
1916       if (si->throttled_p && p->verbose_p)
1917         fprintf (stderr, "%s: unthrottled.\n", blurb());
1918       si->throttled_p = False;
1919
1920       if (! until_idle_p)
1921         {
1922           if (si->cycle_id)
1923             XtRemoveTimeOut (si->cycle_id);
1924           si->cycle_id = 0;
1925           cycle_timer ((XtPointer) si, 0);
1926         }
1927       else
1928         return True;
1929     }
1930   else if (type == XA_EXIT)
1931     {
1932       /* Ignore EXIT message if the screen is locked. */
1933       if (until_idle_p || !si->locked_p)
1934         {
1935           clientmessage_response (si, window, False,
1936                                   "EXIT ClientMessage received.",
1937                                   "exiting.");
1938           if (! until_idle_p)
1939             {
1940               int i;
1941               for (i = 0; i < si->nscreens; i++)
1942                 kill_screenhack (&si->screens[i]);
1943               unblank_screen (si);
1944               XSync (si->dpy, False);
1945             }
1946           saver_exit (si, 0, 0);
1947         }
1948       else
1949         clientmessage_response (si, window, True,
1950                                 "EXIT ClientMessage received while locked.",
1951                                 "screen is locked.");
1952     }
1953   else if (type == XA_RESTART)
1954     {
1955       /* The RESTART message works whether the screensaver is active or not,
1956          unless the screen is locked, in which case it doesn't work.
1957        */
1958       if (until_idle_p || !si->locked_p)
1959         {
1960           clientmessage_response (si, window, False,
1961                                   "RESTART ClientMessage received.",
1962                                   "restarting.");
1963           if (! until_idle_p)
1964             {
1965               int i;
1966               for (i = 0; i < si->nscreens; i++)
1967                 kill_screenhack (&si->screens[i]);
1968               unblank_screen (si);
1969               XSync (si->dpy, False);
1970             }
1971
1972           restart_process (si);  /* does not return */
1973           abort();
1974         }
1975       else
1976         clientmessage_response (si, window, True,
1977                                 "RESTART ClientMessage received while locked.",
1978                                 "screen is locked.");
1979     }
1980   else if (type == XA_DEMO)
1981     {
1982       long arg = event->xclient.data.l[1];
1983       Bool demo_one_hack_p = (arg == 5000);
1984
1985       if (demo_one_hack_p)
1986         {
1987           if (until_idle_p)
1988             {
1989               long which = event->xclient.data.l[2];
1990               char buf [255];
1991               char buf2 [255];
1992               sprintf (buf, "DEMO %ld ClientMessage received.", which);
1993               sprintf (buf2, "demoing (%ld).", which);
1994               clientmessage_response (si, window, False, buf, buf2);
1995
1996               if (which < 0) which = 0;         /* 0 == "random" */
1997               si->selection_mode = which;
1998               si->demoing_p = True;
1999
2000               if (si->throttled_p && p->verbose_p)
2001                 fprintf (stderr, "%s: unthrottled.\n", blurb());
2002               si->throttled_p = False;
2003
2004               return True;
2005             }
2006
2007           clientmessage_response (si, window, True,
2008                                   "DEMO ClientMessage received while active.",
2009                                   "already active.");
2010         }
2011       else
2012         {
2013           clientmessage_response (si, window, True,
2014                                   "obsolete form of DEMO ClientMessage.",
2015                                   "obsolete form of DEMO ClientMessage.");
2016         }
2017     }
2018   else if (type == XA_PREFS)
2019     {
2020       clientmessage_response (si, window, True,
2021                               "the PREFS client-message is obsolete.",
2022                               "the PREFS client-message is obsolete.");
2023     }
2024   else if (type == XA_LOCK)
2025     {
2026 #ifdef NO_LOCKING
2027       clientmessage_response (si, window, True,
2028                               "not compiled with support for locking.",
2029                               "locking not enabled.");
2030 #else /* !NO_LOCKING */
2031       if (si->locking_disabled_p)
2032         clientmessage_response (si, window, True,
2033                       "LOCK ClientMessage received, but locking is disabled.",
2034                               "locking not enabled.");
2035       else if (si->locked_p)
2036         clientmessage_response (si, window, True,
2037                            "LOCK ClientMessage received while already locked.",
2038                                 "already locked.");
2039       else
2040         {
2041           char buf [255];
2042           char *response = (until_idle_p
2043                             ? "activating and locking."
2044                             : "locking.");
2045           sprintf (buf, "LOCK ClientMessage received; %s", response);
2046           clientmessage_response (si, window, False, buf, response);
2047           set_locked_p (si, True);
2048           si->selection_mode = 0;
2049           si->demoing_p = False;
2050
2051           if (si->lock_id)      /* we're doing it now, so lose the timeout */
2052             {
2053               XtRemoveTimeOut (si->lock_id);
2054               si->lock_id = 0;
2055             }
2056
2057           if (until_idle_p)
2058             {
2059               if (si->using_mit_saver_extension ||
2060                   si->using_sgi_saver_extension)
2061                 {
2062                   XForceScreenSaver (si->dpy, ScreenSaverActive);
2063                   return False;
2064                 }
2065               else
2066                 {
2067                   return True;
2068                 }
2069             }
2070         }
2071 #endif /* !NO_LOCKING */
2072     }
2073   else if (type == XA_THROTTLE)
2074     {
2075       /* The THROTTLE command is deprecated -- it predates the XDPMS
2076          extension.  Instead of using -throttle, users should instead
2077          just power off the monitor (e.g., "xset dpms force off".)
2078          In a few minutes, xscreensaver will notice that the monitor
2079          is off, and cease running hacks.
2080        */
2081       if (si->throttled_p)
2082         clientmessage_response (si, window, True,
2083                                 "THROTTLE ClientMessage received, but "
2084                                 "already throttled.",
2085                                 "already throttled.");
2086       else
2087         {
2088           char buf [255];
2089           char *response = "throttled.";
2090           si->throttled_p = True;
2091           si->selection_mode = 0;
2092           si->demoing_p = False;
2093           sprintf (buf, "THROTTLE ClientMessage received; %s", response);
2094           clientmessage_response (si, window, False, buf, response);
2095
2096           if (! until_idle_p)
2097             {
2098               if (si->cycle_id)
2099                 XtRemoveTimeOut (si->cycle_id);
2100               si->cycle_id = 0;
2101               cycle_timer ((XtPointer) si, 0);
2102             }
2103         }
2104     }
2105   else if (type == XA_UNTHROTTLE)
2106     {
2107       if (! si->throttled_p)
2108         clientmessage_response (si, window, True,
2109                                 "UNTHROTTLE ClientMessage received, but "
2110                                 "not throttled.",
2111                                 "not throttled.");
2112       else
2113         {
2114           char buf [255];
2115           char *response = "unthrottled.";
2116           si->throttled_p = False;
2117           si->selection_mode = 0;
2118           si->demoing_p = False;
2119           sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
2120           clientmessage_response (si, window, False, buf, response);
2121
2122           if (! until_idle_p)
2123             {
2124               if (si->cycle_id)
2125                 XtRemoveTimeOut (si->cycle_id);
2126               si->cycle_id = 0;
2127               cycle_timer ((XtPointer) si, 0);
2128             }
2129         }
2130     }
2131   else
2132     {
2133       char buf [1024];
2134       char *str;
2135       str = XGetAtomName_safe (si->dpy, type);
2136
2137       if (str)
2138         {
2139           if (strlen (str) > 80)
2140             strcpy (str+70, "...");
2141           sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
2142                    str);
2143           free (str);
2144         }
2145       else
2146         {
2147           sprintf (buf,
2148                    "unrecognised screensaver ClientMessage 0x%x received.",
2149                    (unsigned int) event->xclient.data.l[0]);
2150         }
2151
2152       clientmessage_response (si, window, True, buf, buf);
2153     }
2154   return False;
2155 }
2156
2157 \f
2158 /* Some random diagnostics printed in -verbose mode.
2159  */
2160
2161 static void
2162 analyze_display (saver_info *si)
2163 {
2164   int i, j;
2165   static struct {
2166     const char *name; const char *desc; 
2167     Bool useful_p;
2168     Status (*version_fn) (Display *, int *majP, int *minP);
2169   } exts[] = {
2170
2171    { "SCREEN_SAVER", /* underscore */           "SGI Screen-Saver",
2172 #     ifdef HAVE_SGI_SAVER_EXTENSION
2173         True,  0
2174 #     else
2175         False, 0
2176 #     endif
2177    }, { "SCREEN-SAVER", /* dash */              "SGI Screen-Saver",
2178 #     ifdef HAVE_SGI_SAVER_EXTENSION
2179         True,  0
2180 #     else
2181         False, 0
2182 #     endif
2183    }, { "MIT-SCREEN-SAVER",                     "MIT Screen-Saver",
2184 #     ifdef HAVE_MIT_SAVER_EXTENSION
2185         True,  XScreenSaverQueryVersion
2186 #     else
2187         False, 0
2188 #     endif
2189    }, { "XIDLE",                                "XIdle",           
2190 #     ifdef HAVE_XIDLE_EXTENSION
2191         True,  0
2192 #     else
2193         False, 0
2194 #     endif
2195    }, { "SGI-VIDEO-CONTROL",                    "SGI Video-Control",
2196 #     ifdef HAVE_SGI_VC_EXTENSION
2197         True,  XSGIvcQueryVersion
2198 #     else
2199         False, 0
2200 #     endif
2201    }, { "READDISPLAY",                          "SGI Read-Display",
2202 #     ifdef HAVE_READ_DISPLAY_EXTENSION
2203         True,  XReadDisplayQueryVersion
2204 #     else
2205         False, 0
2206 #     endif
2207    }, { "MIT-SHM",                              "Shared Memory",   
2208 #     ifdef HAVE_XSHM_EXTENSION
2209         True, (Status (*) (Display*,int*,int*)) XShmQueryVersion /* 4 args */
2210 #     else
2211         False, 0
2212 #     endif
2213    }, { "DOUBLE-BUFFER",                        "Double-Buffering",
2214 #     ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2215         True, XdbeQueryExtension
2216 #     else
2217         False, 0
2218 #     endif
2219    }, { "DPMS",                                 "Power Management",
2220 #     ifdef HAVE_DPMS_EXTENSION
2221         True,  DPMSGetVersion
2222 #     else
2223         False, 0
2224 #     endif
2225    }, { "GLX",                                  "GLX",             
2226 #     ifdef HAVE_GL
2227         True,  0
2228 #     else
2229         False, 0
2230 #     endif
2231    }, { "XFree86-VidModeExtension",             "XF86 Video-Mode", 
2232 #     ifdef HAVE_XF86VMODE
2233         True,  XF86VidModeQueryVersion
2234 #     else
2235         False, 0
2236 #     endif
2237    }, { "XC-VidModeExtension",                  "XC Video-Mode", 
2238 #     ifdef HAVE_XF86VMODE
2239         True,  XF86VidModeQueryVersion
2240 #     else
2241         False, 0
2242 #     endif
2243    }, { "XFree86-MISC",                         "XF86 Misc", 
2244 #     ifdef HAVE_XF86MISCSETGRABKEYSSTATE
2245         True,  XF86MiscQueryVersion
2246 #     else
2247         False, 0
2248 #     endif
2249    }, { "XC-MISC",                              "XC Misc", 
2250 #     ifdef HAVE_XF86MISCSETGRABKEYSSTATE
2251         True,  XF86MiscQueryVersion
2252 #     else
2253         False, 0
2254 #     endif
2255    }, { "XINERAMA",                             "Xinerama",
2256 #     ifdef HAVE_XINERAMA
2257         True,  XineramaQueryVersion
2258 #     else
2259         False, 0
2260 #     endif
2261    }, { "RANDR",                                "Resize-and-Rotate",
2262 #     ifdef HAVE_RANDR
2263         True,  XRRQueryVersion
2264 #     else
2265         False, 0
2266 #     endif
2267    }, { "DRI",                                  "DRI",
2268         True,  0
2269    }, { "NV-CONTROL",                           "NVidia",
2270         True,  0
2271    }, { "NV-GLX",                               "NVidia GLX",
2272         True,  0
2273    }, { "Apple-DRI",                            "Apple-DRI (XDarwin)",
2274         True,  0
2275    }, { "XInputExtension",                      "XInput",
2276         True,  0
2277    },
2278   };
2279
2280   fprintf (stderr, "%s: running on display \"%s\"\n", blurb(), 
2281            DisplayString(si->dpy));
2282   fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(),
2283            ServerVendor(si->dpy), VendorRelease(si->dpy));
2284
2285   fprintf (stderr, "%s: useful extensions:\n", blurb());
2286   for (i = 0; i < countof(exts); i++)
2287     {
2288       int op = 0, event = 0, error = 0;
2289       char buf [255];
2290       int maj = 0, min = 0;
2291       int dummy1, dummy2, dummy3;
2292       int j;
2293
2294       /* Most of the extension version functions take 3 args,
2295          writing results into args 2 and 3, but some take more.
2296          We only ever care about the first two results, but we
2297          pass in three extra pointers just in case.
2298        */
2299       Status (*version_fn_2) (Display*,int*,int*,int*,int*,int*) =
2300         (Status (*) (Display*,int*,int*,int*,int*,int*)) exts[i].version_fn;
2301
2302       if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
2303         continue;
2304       sprintf (buf, "%s:   ", blurb());
2305       j = strlen (buf);
2306       strcat (buf, exts[i].desc);
2307
2308       if (!version_fn_2)
2309         ;
2310       else if (version_fn_2 (si->dpy, &maj, &min, &dummy1, &dummy2, &dummy3))
2311         sprintf (buf+strlen(buf), " (%d.%d)", maj, min);
2312       else
2313         strcat (buf, " (unavailable)");
2314
2315       if (!exts[i].useful_p)
2316         strcat (buf, " (disabled at compile time)");
2317       fprintf (stderr, "%s\n", buf);
2318     }
2319
2320   for (i = 0; i < si->nscreens; i++)
2321     {
2322       saver_screen_info *ssi = &si->screens[i];
2323       unsigned long colormapped_depths = 0;
2324       unsigned long non_mapped_depths = 0;
2325       XVisualInfo vi_in, *vi_out;
2326       int out_count;
2327
2328       if (!ssi->real_screen_p) continue;
2329
2330       vi_in.screen = ssi->real_screen_number;
2331       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
2332       if (!vi_out) continue;
2333       for (j = 0; j < out_count; j++) {
2334         if (vi_out[j].depth >= 32) continue;
2335         if (vi_out[j].class == PseudoColor)
2336           colormapped_depths |= (1 << vi_out[j].depth);
2337         else
2338           non_mapped_depths  |= (1 << vi_out[j].depth);
2339         }
2340       XFree ((char *) vi_out);
2341
2342       if (colormapped_depths)
2343         {
2344           fprintf (stderr, "%s: screen %d colormapped depths:", blurb(),
2345                    ssi->real_screen_number);
2346           for (j = 0; j < 32; j++)
2347             if (colormapped_depths & (1 << j))
2348               fprintf (stderr, " %d", j);
2349           fprintf (stderr, ".\n");
2350         }
2351       if (non_mapped_depths)
2352         {
2353           fprintf (stderr, "%s: screen %d non-colormapped depths:",
2354                    blurb(), ssi->real_screen_number);
2355           for (j = 0; j < 32; j++)
2356             if (non_mapped_depths & (1 << j))
2357               fprintf (stderr, " %d", j);
2358           fprintf (stderr, ".\n");
2359         }
2360     }
2361
2362   describe_monitor_layout (si);
2363 }
2364
2365
2366 Bool
2367 display_is_on_console_p (saver_info *si)
2368 {
2369   Bool not_on_console = True;
2370   char *dpystr = DisplayString (si->dpy);
2371   char *tail = (char *) strchr (dpystr, ':');
2372   if (! tail || strncmp (tail, ":0", 2))
2373     not_on_console = True;
2374   else
2375     {
2376       char dpyname[255], localname[255];
2377       strncpy (dpyname, dpystr, tail-dpystr);
2378       dpyname [tail-dpystr] = 0;
2379       if (!*dpyname ||
2380           !strcmp(dpyname, "unix") ||
2381           !strcmp(dpyname, "localhost"))
2382         not_on_console = False;
2383       else if (gethostname (localname, sizeof (localname)))
2384         not_on_console = True;  /* can't find hostname? */
2385       else if (!strncmp (dpyname, "/tmp/launch-", 12))  /* MacOS X launchd */
2386         not_on_console = False;
2387       else
2388         {
2389           /* We have to call gethostbyname() on the result of gethostname()
2390              because the two aren't guarenteed to be the same name for the
2391              same host: on some losing systems, one is a FQDN and the other
2392              is not.  Here in the wide wonderful world of Unix it's rocket
2393              science to obtain the local hostname in a portable fashion.
2394              
2395              And don't forget, gethostbyname() reuses the structure it
2396              returns, so we have to copy the fucker before calling it again.
2397              Thank you master, may I have another.
2398            */
2399           struct hostent *h = gethostbyname (dpyname);
2400           if (!h)
2401             not_on_console = True;
2402           else
2403             {
2404               char hn [255];
2405               struct hostent *l;
2406               strcpy (hn, h->h_name);
2407               l = gethostbyname (localname);
2408               not_on_console = (!l || !!(strcmp (l->h_name, hn)));
2409             }
2410         }
2411     }
2412   return !not_on_console;
2413 }
2414
2415
2416 /* Do a little bit of heap introspection...
2417  */
2418 void
2419 check_for_leaks (const char *where)
2420 {
2421 #if defined(HAVE_SBRK) && defined(LEAK_PARANOIA)
2422   static unsigned long last_brk = 0;
2423   int b = (unsigned long) sbrk(0);
2424   if (last_brk && last_brk < b)
2425     fprintf (stderr, "%s: %s: brk grew by %luK.\n",
2426              blurb(), where,
2427              (((b - last_brk) + 1023) / 1024));
2428   last_brk = b;
2429 #endif /* HAVE_SBRK */
2430 }