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