X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Fxscreensaver.c;h=3f0c7409edb20e9a5a062f2731cccb8819282e0d;hp=d0efd9b0359f7942e5c2bef28f37c4e81c8c0ba8;hb=c494fd2e6b3b25582375d62e40f4f5cc984ca424;hpb=c31d10b6605cd8dc1a7b61fef4256f06198767e5 diff --git a/driver/xscreensaver.c b/driver/xscreensaver.c index d0efd9b0..3f0c7409 100644 --- a/driver/xscreensaver.c +++ b/driver/xscreensaver.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1991-1998 Jamie Zawinski +/* xscreensaver, Copyright (c) 1991-2006 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -16,11 +16,11 @@ * on (nearly) all windows; or by waiting for the MIT-SCREEN-SAVER extension * to send us a "you are idle" event. * - * Then, we map a full screen black window (or, in the case of the - * MIT-SCREEN-SAVER extension, use the one it gave us.) + * Then, we map a full screen black window. * * We place a __SWM_VROOT property on this window, so that newly-started - * clients will think that this window is a "virtual root" window. + * clients will think that this window is a "virtual root" window (as per + * the logic in the historical "vroot.h" header.) * * If there is an existing "virtual root" window (one that already had * an __SWM_VROOT property) then we remove that property from that window. @@ -38,10 +38,18 @@ * When they are, we kill the inferior process, unmap the window, and restore * the __SWM_VROOT property to the real virtual root window if there was one. * - * While we are waiting, we also set up timers so that, after a certain - * amount of time has passed, we can start a different screenhack. We do - * this by killing the running child process with SIGTERM, and then starting - * a new one in the same way. + * On multi-screen systems, we do the above on each screen, and start + * multiple programs, each with a different value of $DISPLAY. + * + * On Xinerama systems, we do a similar thing, but instead create multiple + * windows on the (only) display, and tell the subprocess which one to use + * via the $XSCREENSAVER_WINDOW environment variable -- this trick requires + * a recent (Aug 2003) revision of vroot.h. + * + * While we are waiting for user activity, we also set up timers so that, + * after a certain amount of time has passed, we can start a different + * screenhack. We do this by killing the running child process with + * SIGTERM, and then starting a new one in the same way. * * If there was a real virtual root, meaning that we removed the __SWM_VROOT * property from it, meaning we must (absolutely must) restore it before we @@ -57,9 +65,9 @@ * can really fuck up the world by killing this process with "kill -9". * * This program accepts ClientMessages of type SCREENSAVER; these messages - * may contain the atom ACTIVATE or DEACTIVATE, meaning to turn the + * may contain the atoms ACTIVATE, DEACTIVATE, etc, meaning to turn the * screensaver on or off now, regardless of the idleness of the user, - * and a few other things. The included "xscreensaver_command" program + * and a few other things. The included "xscreensaver-command" program * sends these messsages. * * If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER @@ -70,8 +78,9 @@ * KeyPress on windows which don't select them, because that would * interfere with event propagation. This will break if any program * changes its event mask to contain KeyRelease or PointerMotion more than - * 30 seconds after creating the window, but that's probably pretty rare. - * + * 30 seconds after creating the window, but such programs do not seem to + * occur in nature (I've never seen it happen in all these years.) + * * The reason that we can't select KeyPresses on windows that don't have * them already is that, when dispatching a KeyPress event, X finds the * lowest (leafmost) window in the hierarchy on which *any* client selects @@ -91,9 +100,15 @@ * "client changing event mask" problem that the KeyPress events hack does. * I think polling is more reliable. * - * None of this crap happens if we're using one of the extensions, so install - * one of them if the description above sounds just too flaky to live. It - * is, but those are your choices. + * On systems with /proc/interrupts (Linux) we poll that file and note when + * the interrupt counter numbers on the "keyboard" and "PS/2" lines change. + * (There is no reliable way, using /proc/interrupts, to detect non-PS/2 + * mice, so it doesn't help for serial or USB mice.) + * + * None of this crap happens if we're using one of the extensions. Sadly, + * the XIdle extension hasn't been available for many years; the SGI + * extension only exists on SGIs; and the MIT extension, while widely + * deployed, is garbage in several ways. * * A third idle-detection option could be implemented (but is not): when * running on the console display ($DISPLAY is `localhost`:0) and we're on a @@ -110,13 +125,13 @@ * to keep your emacs window alive even when xscreensaver has grabbed. * - Go read the code related to `debug_p'. * - You probably can't set breakpoints in functions that are called on - * the other side of a call to fork() -- if your clients are dying - * with signal 5, Trace/BPT Trap, you're losing in this way. + * the other side of a call to fork() -- if your subprocesses are + * dying with signal 5, Trace/BPT Trap, you're losing in this way. * - If you aren't using a server extension, don't leave this stopped * under the debugger for very long, or the X input buffer will get * huge because of the keypress events it's selecting for. This can * make your X server wedge with "no more input buffers." - * + * * ======================================================================== */ #ifdef HAVE_CONFIG_H @@ -126,11 +141,19 @@ #include #include #include + +#include + #include #include #include #include #include +#include +#include +#include /* for gethostbyname() */ +#include +#include #ifdef HAVE_XMU # ifndef VMS # include @@ -142,14 +165,20 @@ #endif /* !HAVE_XMU */ #ifdef HAVE_XIDLE_EXTENSION -#include +# include #endif /* HAVE_XIDLE_EXTENSION */ +#ifdef HAVE_XINERAMA +# include +#endif /* HAVE_XINERAMA */ + #include "xscreensaver.h" #include "version.h" #include "yarandom.h" #include "resources.h" #include "visual.h" +#include "usleep.h" +#include "auth.h" saver_info *global_si_kludge = 0; /* I hate C so much... */ @@ -160,44 +189,30 @@ XrmDatabase db = 0; static Atom XA_SCREENSAVER_RESPONSE; static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV; -static Atom XA_EXIT, XA_RESTART, XA_LOCK, XA_SELECT; -Atom XA_DEMO, XA_PREFS; +static Atom XA_RESTART, XA_SELECT; +static Atom XA_THROTTLE, XA_UNTHROTTLE; +Atom XA_DEMO, XA_PREFS, XA_EXIT, XA_LOCK, XA_BLANK; static XrmOptionDescRec options [] = { - { "-timeout", ".timeout", XrmoptionSepArg, 0 }, - { "-cycle", ".cycle", XrmoptionSepArg, 0 }, - { "-lock-mode", ".lock", XrmoptionNoArg, "on" }, - { "-no-lock-mode", ".lock", XrmoptionNoArg, "off" }, - { "-no-lock", ".lock", XrmoptionNoArg, "off" }, - { "-lock-timeout", ".lockTimeout", XrmoptionSepArg, 0 }, - { "-lock-vts", ".lockVTs", XrmoptionNoArg, "on" }, - { "-no-lock-vts", ".lockVTs", XrmoptionNoArg, "off" }, - { "-visual", ".visualID", XrmoptionSepArg, 0 }, - { "-install", ".installColormap", XrmoptionNoArg, "on" }, - { "-no-install", ".installColormap", XrmoptionNoArg, "off" }, + { "-verbose", ".verbose", XrmoptionNoArg, "on" }, { "-silent", ".verbose", XrmoptionNoArg, "off" }, - { "-timestamp", ".timestamp", XrmoptionNoArg, "on" }, - { "-capture-stderr", ".captureStderr", XrmoptionNoArg, "on" }, - { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" }, - { "-xidle-extension", ".xidleExtension", XrmoptionNoArg, "on" }, - { "-no-xidle-extension", ".xidleExtension", XrmoptionNoArg, "off" }, - { "-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "on" }, - { "-no-mit-extension", ".mitSaverExtension",XrmoptionNoArg, "off" }, - { "-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "on" }, - { "-no-sgi-extension", ".sgiSaverExtension",XrmoptionNoArg, "off" }, - { "-splash", ".splash", XrmoptionNoArg, "on" }, - { "-no-splash", ".splash", XrmoptionNoArg, "off" }, + + /* xscreensaver-demo uses this one */ { "-nosplash", ".splash", XrmoptionNoArg, "off" }, - { "-idelay", ".initialDelay", XrmoptionSepArg, 0 }, - { "-nice", ".nice", XrmoptionSepArg, 0 }, + { "-no-splash", ".splash", XrmoptionNoArg, "off" }, - /* Actually these are built in to Xt, but just to be sure... */ - { "-synchronous", ".synchronous", XrmoptionNoArg, "on" }, - { "-xrm", NULL, XrmoptionResArg, NULL } + /* useful for debugging */ + { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" }, }; +#ifdef __GNUC__ + __extension__ /* shut up about "string length is greater than the length + ISO C89 compilers are required to support" when including + the .ad file... */ +#endif + static char *defaults[] = { #include "XScreenSaver_ad.h" 0 @@ -213,36 +228,25 @@ do_help (saver_info *si) fflush (stdout); fflush (stderr); fprintf (stdout, "\ -xscreensaver %s, copyright (c) 1991-1998 by Jamie Zawinski \n\ -The standard Xt command-line options are accepted; other options include:\n\ -\n\ - -timeout When the screensaver should activate.\n\ - -cycle How long to let each hack run before switching.\n\ - -lock-mode Require a password before deactivating.\n\ - -lock-timeout Grace period before locking; default 0.\n\ - -visual Which X visual to run on.\n\ - -install Install a private colormap.\n\ - -verbose Be loud.\n\ - -no-splash Don't display a splash-screen at startup.\n\ - -help This message.\n\ -\n\ -See the manual for other options and X resources.\n\ -\n\ -The `xscreensaver' program should be left running in the background.\n\ -Use the `xscreensaver-demo' and `xscreensaver-command' programs to\n\ -manipulate a running xscreensaver.\n\ +xscreensaver %s, copyright (c) 1991-2006 by Jamie Zawinski \n\ \n\ -The `*programs' resource controls which graphics demos will be launched by\n\ -the screensaver. See `man xscreensaver' or the web page for more details.\n\ -\n\ -Just getting started? Try this:\n\ + All xscreensaver configuration is via the `~/.xscreensaver' file.\n\ + Rather than editing that file by hand, just run `xscreensaver-demo':\n\ + that program lets you configure the screen saver graphically,\n\ + including timeouts, locking, and display modes.\n\ +\n", + si->version); + fprintf (stdout, "\ + Just getting started? Try this:\n\ \n\ xscreensaver &\n\ xscreensaver-demo\n\ \n\ -For updates, check http://www.jwz.org/xscreensaver/\n\ -\n", - si->version); + For updates, online manual, and FAQ, please see the web page:\n\ +\n\ + http://www.jwz.org/xscreensaver/\n\ +\n"); + fflush (stdout); fflush (stderr); exit (1); @@ -286,36 +290,141 @@ int saver_ehandler (Display *dpy, XErrorEvent *error) { saver_info *si = global_si_kludge; /* I hate C so much... */ + int i; + Bool fatal_p; + + if (!real_stderr) real_stderr = stderr; fprintf (real_stderr, "\n" "#######################################" "#######################################\n\n" - "%s: X Error! PLEASE REPORT THIS BUG.\n\n" - "#######################################" - "#######################################\n\n", + "%s: X Error! PLEASE REPORT THIS BUG.\n", blurb()); - if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr)) + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + fprintf (real_stderr, "%s: screen %d/%d: 0x%x, 0x%x, 0x%x\n", + blurb(), ssi->real_screen_number, ssi->number, + (unsigned int) RootWindowOfScreen (si->screens[i].screen), + (unsigned int) si->screens[i].real_vroot, + (unsigned int) si->screens[i].screensaver_window); + } + + fprintf (real_stderr, "\n" + "#######################################" + "#######################################\n\n"); + + fatal_p = XmuPrintDefaultErrorMessage (dpy, error, real_stderr); + + fatal_p = True; /* The only time I've ever seen a supposedly nonfatal error, + it has been BadImplementation / Xlib sequence lost, which + are in truth pretty damned fatal. + */ + + fprintf (real_stderr, "\n"); + + if (! fatal_p) + fprintf (real_stderr, "%s: nonfatal error.\n\n", blurb()); + else { - fprintf (real_stderr, "\n"); if (si->prefs.xsync_p) { saver_exit (si, -1, "because of synchronous X Error"); } else { - fprintf(real_stderr, - "%s: to dump a core file, re-run with `-sync'.\n" - "%s: see http://www.jwz.org/xscreensaver/bugs.html\n" - "\t\tfor bug reporting information.\n\n", - blurb(), blurb()); + fprintf (real_stderr, + "#######################################" + "#######################################\n\n"); + fprintf (real_stderr, + " If at all possible, please re-run xscreensaver with the command\n" + " line arguments `-sync -verbose -no-capture', and reproduce this\n" + " bug. That will cause xscreensaver to dump a `core' file to the\n" + " current directory. Please include the stack trace from that core\n" + " file in your bug report. *DO NOT* mail the core file itself!\n" + " That won't work.\n"); + fprintf (real_stderr, + "\n" + " http://www.jwz.org/xscreensaver/bugs.html explains how to create\n" + " the most useful bug reports, and how to examine core files.\n" + "\n" + " The more information you can provide, the better. But please\n" + " report this bug, regardless!\n" + "\n"); + fprintf (real_stderr, + "#######################################" + "#######################################\n\n"); + saver_exit (si, -1, 0); } } - else - fprintf (real_stderr, " (nonfatal.)\n"); + return 0; } + +/* This error handler is used only while the X connection is being set up; + after we've got a connection, we don't use this handler again. The only + reason for having this is so that we can present a more idiot-proof error + message than "cannot open display." + */ +static void +startup_ehandler (String name, String type, String class, + String defalt, /* one can't even spel properly + in this joke of a language */ + String *av, Cardinal *ac) +{ + char fmt[512]; + String p[10]; + saver_info *si = global_si_kludge; /* I hate C so much... */ + XrmDatabase *db = XtAppGetErrorDatabase(si->app); + *fmt = 0; + XtAppGetErrorDatabaseText(si->app, name, type, class, defalt, + fmt, sizeof(fmt)-1, *db); + + fprintf (stderr, "%s: ", blurb()); + + memset (p, 0, sizeof(p)); + if (*ac > countof (p)) *ac = countof (p); + memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av)); + fprintf (stderr, fmt, /* Did I mention that I hate C? */ + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]); + fprintf (stderr, "\n"); + + describe_uids (si, stderr); + + if (si->orig_uid && !strncmp (si->orig_uid, "root/", 5)) + { + fprintf (stderr, "\n" + "%s: This is probably because you're logging in as root. You\n" +" shouldn't log in as root: you should log in as a normal user,\n" +" and then `su' as needed. If you insist on logging in as\n" +" root, you will have to turn off X's security features before\n" +" xscreensaver will work.\n" + "\n" +" Please read the manual and FAQ for more information:\n", + blurb()); + } + else + { + fprintf (stderr, "\n" + "%s: Errors at startup are usually authorization problems.\n" +" But you're not logging in as root (good!) so something\n" +" else must be wrong. Did you read the manual and the FAQ?\n", + blurb()); + } + + fprintf (stderr, "\n" + " http://www.jwz.org/xscreensaver/faq.html\n" + " http://www.jwz.org/xscreensaver/man.html\n" + "\n"); + + fflush (stderr); + fflush (stdout); + exit (1); +} + /* The zillions of initializations. */ @@ -355,21 +464,73 @@ set_version_string (saver_info *si, int *argc, char **argv) static void privileged_initialization (saver_info *si, int *argc, char **argv) { +#ifndef NO_LOCKING + /* before hack_uid() for proper permissions */ + lock_priv_init (*argc, argv, si->prefs.verbose_p); +#endif /* NO_LOCKING */ + + hack_uid (si); +} + + +/* Figure out what locking mechanisms are supported. + */ +static void +lock_initialization (saver_info *si, int *argc, char **argv) +{ #ifdef NO_LOCKING si->locking_disabled_p = True; si->nolock_reason = "not compiled with locking support"; #else /* !NO_LOCKING */ - si->locking_disabled_p = False; - if (! lock_init (*argc, argv)) /* before hack_uid() for proper permissions */ + + /* Finish initializing locking, now that we're out of privileged code. */ + if (! lock_init (*argc, argv, si->prefs.verbose_p)) { si->locking_disabled_p = True; si->nolock_reason = "error getting password"; } -#endif /* NO_LOCKING */ -#ifndef NO_SETUID - hack_uid (si); -#endif /* NO_SETUID */ + /* If locking is currently enabled, but the environment indicates that + we have been launched as GDM's "Background" program, then disable + locking just in case. + */ + if (!si->locking_disabled_p && getenv ("RUNNING_UNDER_GDM")) + { + si->locking_disabled_p = True; + si->nolock_reason = "running under GDM"; + } + + /* If the server is XDarwin (MacOS X) then disable locking. + (X grabs only affect X programs, so you can use Command-Tab + to bring any other Mac program to the front, e.g., Terminal.) + */ + if (!si->locking_disabled_p) + { + int op = 0, event = 0, error = 0; + Bool macos_p = False; + +#ifdef __APPLE__ + /* Disable locking if *running* on Apple hardware, since we have no + reliable way to determine whether the server is running on MacOS. + Hopefully __APPLE__ means "MacOS" and not "Linux on Mac hardware" + but I'm not really sure about that. + */ + macos_p = True; +#endif + + if (!macos_p) + /* This extension exists on the Apple X11 server, but not + on earlier versions of the XDarwin server. */ + macos_p = XQueryExtension (si->dpy, "Apple-DRI", &op, &event, &error); + + if (macos_p) + { + si->locking_disabled_p = True; + si->nolock_reason = "Cannot lock securely on MacOS X"; + } + } + +#endif /* NO_LOCKING */ } @@ -380,10 +541,31 @@ connect_to_server (saver_info *si, int *argc, char **argv) { Widget toplevel_shell; +#ifdef HAVE_PUTENV + char *d = getenv ("DISPLAY"); + if (!d || !*d) + { + char *ndpy = strdup("DISPLAY=:0.0"); + /* if (si->prefs.verbose_p) */ /* sigh, too early to test this... */ + fprintf (stderr, + "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n", + blurb(), ndpy+8); + if (putenv (ndpy)) + abort (); + /* don't free (ndpy) -- some implementations of putenv (BSD 4.4, + glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) + do not. So we must leak it (and/or the previous setting). Yay. + */ + } +#endif /* HAVE_PUTENV */ + XSetErrorHandler (saver_ehandler); + + XtAppSetErrorMsgHandler (si->app, startup_ehandler); toplevel_shell = XtAppInitialize (&si->app, progclass, options, XtNumber (options), argc, argv, defaults, 0, 0); + XtAppSetErrorMsgHandler (si->app, 0); si->dpy = XtDisplay (toplevel_shell); si->prefs.db = XtDatabase (si->dpy); @@ -398,10 +580,12 @@ connect_to_server (saver_info *si, int *argc, char **argv) XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False); XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False); XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False); - XA_SCREENSAVER_TIME = XInternAtom (si->dpy, "_SCREENSAVER_TIME", False); + XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False); XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE", False); XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False); + XA_ESETROOT_PMAP_ID = XInternAtom (si->dpy, "ESETROOT_PMAP_ID", False); + XA_XROOTPMAP_ID = XInternAtom (si->dpy, "_XROOTPMAP_ID", False); XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False); XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False); XA_RESTART = XInternAtom (si->dpy, "RESTART", False); @@ -413,6 +597,9 @@ connect_to_server (saver_info *si, int *argc, char **argv) XA_DEMO = XInternAtom (si->dpy, "DEMO", False); XA_PREFS = XInternAtom (si->dpy, "PREFS", False); XA_LOCK = XInternAtom (si->dpy, "LOCK", False); + XA_BLANK = XInternAtom (si->dpy, "BLANK", False); + XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False); + XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False); return toplevel_shell; } @@ -498,14 +685,15 @@ print_banner (saver_info *si) whether to print the banner (and so that the banner gets printed before any resource-database-related error messages.) */ - p->verbose_p = (p->debug_p || get_boolean_resource ("verbose", "Boolean")); + p->verbose_p = (p->debug_p || + get_boolean_resource (si->dpy, "verbose", "Boolean")); /* Ditto, for the locking_disabled_p message. */ - p->lock_p = get_boolean_resource ("lock", "Boolean"); + p->lock_p = get_boolean_resource (si->dpy, "lock", "Boolean"); if (p->verbose_p) fprintf (stderr, - "%s %s, copyright (c) 1991-1998 " + "%s %s, copyright (c) 1991-2006 " "by Jamie Zawinski .\n", progname, si->version); @@ -538,11 +726,17 @@ print_banner (saver_info *si) fprintf (stderr, "%s: in process %lu.\n", blurb(), (unsigned long) getpid()); } +} + +static void +print_lock_failure_banner (saver_info *si) +{ + saver_preferences *p = &si->prefs; /* If locking was not able to be initalized for some reason, explain why. (This has to be done after we've read the lock_p resource.) */ - if (p->lock_p && si->locking_disabled_p) + if (si->locking_disabled_p) { p->lock_p = False; fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(), @@ -557,11 +751,12 @@ print_banner (saver_info *si) "\t See the manual for details.\n", blurb()); } + } /* Examine all of the display's screens, and populate the `saver_screen_info' - structures. + structures. Make sure this is called after hack_environment() sets $PATH. */ static void initialize_per_screen_info (saver_info *si, Widget toplevel_shell) @@ -569,17 +764,130 @@ initialize_per_screen_info (saver_info *si, Widget toplevel_shell) Bool found_any_writable_cells = False; int i; - si->nscreens = ScreenCount(si->dpy); - si->screens = (saver_screen_info *) - calloc(sizeof(saver_screen_info), si->nscreens); +# ifdef HAVE_XINERAMA + { + int event, error; + si->xinerama_p = (XineramaQueryExtension (si->dpy, &event, &error) && + XineramaIsActive (si->dpy)); + } + + if (si->xinerama_p && ScreenCount (si->dpy) != 1) + { + si->xinerama_p = False; + if (si->prefs.verbose_p) + fprintf (stderr, + "%s: Xinerama AND %d screens? Disabling Xinerama support!\n", + blurb(), ScreenCount(si->dpy)); + } + + if (si->xinerama_p) + { + XineramaScreenInfo *xsi = XineramaQueryScreens (si->dpy, &si->nscreens); + if (!xsi) + si->xinerama_p = False; + else + { + si->screens = (saver_screen_info *) + calloc(sizeof(saver_screen_info), si->nscreens); + for (i = 0; i < si->nscreens; i++) + { + si->screens[i].x = xsi[i].x_org; + si->screens[i].y = xsi[i].y_org; + si->screens[i].width = xsi[i].width; + si->screens[i].height = xsi[i].height; + } + XFree (xsi); + } + si->default_screen = &si->screens[0]; + si->default_screen->real_screen_p = True; + } +# endif /* !HAVE_XINERAMA */ + + if (!si->xinerama_p) + { + si->nscreens = ScreenCount(si->dpy); + si->screens = (saver_screen_info *) + calloc(sizeof(saver_screen_info), si->nscreens); + si->default_screen = &si->screens[DefaultScreen(si->dpy)]; + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + ssi->width = DisplayWidth (si->dpy, i); + ssi->height = DisplayHeight (si->dpy, i); + ssi->real_screen_p = True; + ssi->real_screen_number = i; + } + } - si->default_screen = &si->screens[DefaultScreen(si->dpy)]; +# ifdef QUAD_MODE + /* In "quad mode", we use the Xinerama code to pretend that there are 4 + screens for every physical screen, and run four times as many hacks... + */ + if (si->prefs.quad_p) + { + int ns2 = si->nscreens * 4; + saver_screen_info *ssi2 = (saver_screen_info *) + calloc(sizeof(saver_screen_info), ns2); + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *old = &si->screens[i]; + + if (si->prefs.debug_p) old->width = old->width / 2; + + ssi2[i*4 ] = *old; + ssi2[i*4+1] = *old; + ssi2[i*4+2] = *old; + ssi2[i*4+3] = *old; + + ssi2[i*4 ].width /= 2; + ssi2[i*4 ].height /= 2; + + ssi2[i*4+1].x += ssi2[i*4 ].width; + ssi2[i*4+1].width -= ssi2[i*4 ].width; + ssi2[i*4+1].height /= 2; + + ssi2[i*4+2].y += ssi2[i*4 ].height; + ssi2[i*4+2].width /= 2; + ssi2[i*4+2].height -= ssi2[i*4 ].height; + + ssi2[i*4+3].x += ssi2[i*4+2].width; + ssi2[i*4+3].y += ssi2[i*4+2].height; + ssi2[i*4+3].width -= ssi2[i*4+2].width; + ssi2[i*4+3].height -= ssi2[i*4+2].height; + + ssi2[i*4+1].real_screen_p = False; + ssi2[i*4+2].real_screen_p = False; + ssi2[i*4+3].real_screen_p = False; + } + + si->nscreens = ns2; + free (si->screens); + si->screens = ssi2; + si->default_screen = &si->screens[DefaultScreen(si->dpy) * 4]; + si->xinerama_p = True; + } +# endif /* QUAD_MODE */ + + /* finish initializing the screens. + */ for (i = 0; i < si->nscreens; i++) { saver_screen_info *ssi = &si->screens[i]; ssi->global = si; - ssi->screen = ScreenOfDisplay (si->dpy, i); + + ssi->number = i; + ssi->screen = ScreenOfDisplay (si->dpy, ssi->real_screen_number); + ssi->poll_mouse_last_root_x = -1; + ssi->poll_mouse_last_root_y = -1; + + if (!si->xinerama_p) + { + ssi->width = WidthOfScreen (ssi->screen); + ssi->height = HeightOfScreen (ssi->screen); + } /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */ ssi->default_visual = @@ -588,6 +896,9 @@ initialize_per_screen_info (saver_info *si, Widget toplevel_shell) ssi->current_visual = ssi->default_visual; ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual); + /* Execute a subprocess to find the GL visual. */ + ssi->best_gl_visual = get_best_gl_visual (ssi); + if (ssi == si->default_screen) /* Since this is the default screen, use the one already created. */ ssi->toplevel_shell = toplevel_shell; @@ -600,7 +911,7 @@ initialize_per_screen_info (saver_info *si, Widget toplevel_shell) XtNvisual, ssi->current_visual, XtNdepth, visual_depth (ssi->screen, ssi->current_visual), - 0); + NULL); if (! found_any_writable_cells) { @@ -617,7 +928,11 @@ initialize_per_screen_info (saver_info *si, Widget toplevel_shell) } } - si->prefs.fading_possible_p = found_any_writable_cells; + si->fading_possible_p = found_any_writable_cells; + +#ifdef HAVE_XF86VMODE_GAMMA + si->fading_possible_p = True; /* if we can gamma fade, go for it */ +#endif } @@ -632,6 +947,13 @@ initialize_server_extensions (saver_info *si) Bool server_has_xidle_extension_p = False; Bool server_has_sgi_saver_extension_p = False; Bool server_has_mit_saver_extension_p = False; + Bool system_has_proc_interrupts_p = False; + const char *piwhy = 0; + + si->using_xidle_extension = p->use_xidle_extension; + si->using_sgi_saver_extension = p->use_sgi_saver_extension; + si->using_mit_saver_extension = p->use_mit_saver_extension; + si->using_proc_interrupts = p->use_proc_interrupts; #ifdef HAVE_XIDLE_EXTENSION server_has_xidle_extension_p = query_xidle_extension (si); @@ -642,22 +964,25 @@ initialize_server_extensions (saver_info *si) #ifdef HAVE_MIT_SAVER_EXTENSION server_has_mit_saver_extension_p = query_mit_saver_extension (si); #endif +#ifdef HAVE_PROC_INTERRUPTS + system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy); +#endif if (!server_has_xidle_extension_p) - p->use_xidle_extension = False; + si->using_xidle_extension = False; else if (p->verbose_p) { - if (p->use_xidle_extension) + if (si->using_xidle_extension) fprintf (stderr, "%s: using XIDLE extension.\n", blurb()); else fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb()); } if (!server_has_sgi_saver_extension_p) - p->use_sgi_saver_extension = False; + si->using_sgi_saver_extension = False; else if (p->verbose_p) { - if (p->use_sgi_saver_extension) + if (si->using_sgi_saver_extension) fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb()); else fprintf (stderr, @@ -666,10 +991,10 @@ initialize_server_extensions (saver_info *si) } if (!server_has_mit_saver_extension_p) - p->use_mit_saver_extension = False; + si->using_mit_saver_extension = False; else if (p->verbose_p) { - if (p->use_mit_saver_extension) + if (si->using_mit_saver_extension) fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n", blurb()); else @@ -677,6 +1002,38 @@ initialize_server_extensions (saver_info *si) "%s: not using server's lame MIT-SCREEN-SAVER extension.\n", blurb()); } + + /* These are incompatible (or at least, our support for them is...) */ + if (si->xinerama_p && si->using_mit_saver_extension) + { + si->using_mit_saver_extension = False; + if (p->verbose_p) + fprintf (stderr, "%s: Xinerama in use: disabling MIT-SCREEN-SAVER.\n", + blurb()); + } + +#ifdef HAVE_RANDR + query_randr_extension (si); +#endif + + if (!system_has_proc_interrupts_p) + { + si->using_proc_interrupts = False; + if (p->verbose_p && piwhy) + fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(), + piwhy); + } + else if (p->verbose_p) + { + if (si->using_proc_interrupts) + fprintf (stderr, + "%s: consulting /proc/interrupts for keyboard activity.\n", + blurb()); + else + fprintf (stderr, + "%s: not consulting /proc/interrupts for keyboard activity.\n", + blurb()); + } } @@ -692,9 +1049,9 @@ select_events (saver_info *si) saver_preferences *p = &si->prefs; int i; - if (p->use_xidle_extension || - p->use_mit_saver_extension || - p->use_sgi_saver_extension) + if (si->using_xidle_extension || + si->using_mit_saver_extension || + si->using_sgi_saver_extension) return; if (p->initial_delay) @@ -723,7 +1080,12 @@ select_events (saver_info *si) for window creation events, so that new subwindows will be noticed. */ for (i = 0; i < si->nscreens; i++) - start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen)); + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->real_screen_p) + start_notice_events_timer (si, + RootWindowOfScreen (si->screens[i].screen), False); + } if (p->verbose_p) fprintf (stderr, " done.\n"); @@ -740,11 +1102,21 @@ maybe_reload_init_file (saver_info *si) fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n", blurb(), init_file_name()); - load_init_file (p); + load_init_file (si->dpy, p); /* If a server extension is in use, and p->timeout has changed, we need to inform the server of the new timeout. */ disable_builtin_screensaver (si, False); + + /* If the DPMS settings in the init file have changed, + change the settings on the server to match. */ + sync_server_dpms_settings (si->dpy, + (p->dpms_enabled_p && + p->mode != DONT_BLANK), + p->dpms_standby / 1000, + p->dpms_suspend / 1000, + p->dpms_off / 1000, + False); } } @@ -766,7 +1138,14 @@ main_loop (saver_info *si) while (1) { + Bool was_locked = False; + + if (p->verbose_p) + fprintf (stderr, "%s: awaiting idleness.\n", blurb()); + + check_for_leaks ("unblanked A"); sleep_until_idle (si, True); + check_for_leaks ("unblanked B"); if (p->verbose_p) { @@ -774,55 +1153,158 @@ main_loop (saver_info *si) fprintf (stderr, "%s: demoing %d at %s.\n", blurb(), si->selection_mode, timestring()); else - if (p->verbose_p) - fprintf (stderr, "%s: blanking screen at %s.\n", blurb(), - timestring()); + fprintf (stderr, "%s: blanking screen at %s.\n", blurb(), + timestring()); } maybe_reload_init_file (si); - blank_screen (si); + if (p->mode == DONT_BLANK) + { + if (p->verbose_p) + fprintf (stderr, "%s: idle with blanking disabled at %s.\n", + blurb(), timestring()); + + /* Go around the loop and wait for the next bout of idleness, + or for the init file to change, or for a remote command to + come in, or something. + + But, if locked_p is true, go ahead. This can only happen + if we're in "disabled" mode but a "lock" clientmessage came + in: in that case, we should go ahead and blank/lock the screen. + */ + if (!si->locked_p) + continue; + } + + /* Since we're about to blank the screen, kill the de-race timer, + if any. It might still be running if we have unblanked and then + re-blanked in a short period (e.g., when using the "next" button + in xscreensaver-demo.) + */ + if (si->de_race_id) + { + if (p->verbose_p) + fprintf (stderr, "%s: stopping de-race timer (%d remaining.)\n", + blurb(), si->de_race_ticks); + XtRemoveTimeOut (si->de_race_id); + si->de_race_id = 0; + } + + + /* Now, try to blank. + */ + + if (! blank_screen (si)) + { + /* We were unable to grab either the keyboard or mouse. + This means we did not (and must not) blank the screen. + If we were to blank the screen while some other program + is holding both the mouse and keyboard grabbed, then + we would never be able to un-blank it! We would never + see any events, and the display would be wedged. + + So, just go around the loop again and wait for the + next bout of idleness. (If the user remains idle, we + will next try to blank the screen again in no more than + 60 seconds.) + */ + Time retry = 60 * 1000; + if (p->timeout < retry) + retry = p->timeout; + + if (p->debug_p) + { + fprintf (stderr, + "%s: DEBUG MODE: unable to grab -- BLANKING ANYWAY.\n", + blurb()); + } + else + { + fprintf (stderr, + "%s: unable to grab keyboard or mouse! Blanking aborted.\n", + blurb()); + + schedule_wakeup_event (si, retry, p->debug_p); + continue; + } + } + kill_screenhack (si); - spawn_screenhack (si, True); + + if (!si->throttled_p) + spawn_screenhack (si, True); + else if (p->verbose_p) + fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb()); /* Don't start the cycle timer in demo mode. */ if (!si->demoing_p && p->cycle) - si->cycle_id = XtAppAddTimeOut (si->app, p->cycle, cycle_timer, + si->cycle_id = XtAppAddTimeOut (si->app, + (si->selection_mode + /* see comment in cycle_timer() */ + ? 1000 * 60 * 60 + : p->cycle), + cycle_timer, (XtPointer) si); #ifndef NO_LOCKING - if (!si->demoing_p && /* if not going into demo mode */ - p->lock_p && /* and locking is enabled */ - !si->locking_disabled_p && /* and locking is possible */ - p->lock_timeout == 0) /* and locking is not timer-deferred */ - si->locked_p = True; /* then lock right now. */ - - /* locked_p might be true already because of the above, or because of - the LOCK ClientMessage. But if not, and if we're supposed to lock - after some time, set up a timer to do so. + /* Maybe start locking the screen. */ - if (p->lock_p && - !si->locked_p && - p->lock_timeout > 0) - si->lock_id = XtAppAddTimeOut (si->app, p->lock_timeout, - activate_lock_timer, - (XtPointer) si); + { + Time lock_timeout = p->lock_timeout; + + if (si->emergency_lock_p && p->lock_p && lock_timeout) + { + int secs = p->lock_timeout / 1000; + if (p->verbose_p) + fprintf (stderr, + "%s: locking now, instead of waiting for %d:%02d:%02d.\n", + blurb(), + (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60)); + lock_timeout = 0; + } + + si->emergency_lock_p = False; + + if (!si->demoing_p && /* if not going into demo mode */ + p->lock_p && /* and locking is enabled */ + !si->locking_disabled_p && /* and locking is possible */ + lock_timeout == 0) /* and locking is not timer-deferred */ + set_locked_p (si, True); /* then lock right now. */ + + /* locked_p might be true already because of the above, or because of + the LOCK ClientMessage. But if not, and if we're supposed to lock + after some time, set up a timer to do so. + */ + if (p->lock_p && + !si->locked_p && + lock_timeout > 0) + si->lock_id = XtAppAddTimeOut (si->app, lock_timeout, + activate_lock_timer, + (XtPointer) si); + } #endif /* !NO_LOCKING */ ok_to_unblank = True; do { + check_for_leaks ("blanked A"); sleep_until_idle (si, False); /* until not idle */ + check_for_leaks ("blanked B"); + maybe_reload_init_file (si); #ifndef NO_LOCKING + /* Maybe unlock the screen. + */ if (si->locked_p) { saver_screen_info *ssi = si->default_screen; if (si->locking_disabled_p) abort (); + was_locked = True; si->dbox_up_p = True; suspend_screenhack (si, True); XUndefineCursor (si->dpy, ssi->screensaver_window); @@ -832,6 +1314,20 @@ main_loop (saver_info *si) si->dbox_up_p = False; XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor); suspend_screenhack (si, False); /* resume */ + + if (!ok_to_unblank && + !screenhack_running_p (si)) + { + /* If the lock dialog has been dismissed and we're not about to + unlock the screen, and there is currently no hack running, + then launch one. (There might be no hack running if DPMS + had kicked in. But DPMS is off now, so bring back the hack) + */ + if (si->cycle_id) + XtRemoveTimeOut (si->cycle_id); + si->cycle_id = 0; + cycle_timer ((XtPointer) si, 0); + } } #endif /* !NO_LOCKING */ @@ -846,10 +1342,21 @@ main_loop (saver_info *si) kill_screenhack (si); unblank_screen (si); - si->locked_p = False; + set_locked_p (si, False); + si->emergency_lock_p = False; si->demoing_p = 0; si->selection_mode = 0; + /* If we're throttled, and the user has explicitly unlocked the screen, + then unthrottle. If we weren't locked, then don't unthrottle + automatically, because someone might have just bumped the desk... */ + if (was_locked) + { + if (si->throttled_p && p->verbose_p) + fprintf (stderr, "%s: unthrottled.\n", blurb()); + si->throttled_p = False; + } + if (si->cycle_id) { XtRemoveTimeOut (si->cycle_id); @@ -862,12 +1369,15 @@ main_loop (saver_info *si) si->lock_id = 0; } - if (p->verbose_p) - fprintf (stderr, "%s: awaiting idleness.\n", blurb()); + /* Since we're unblanked now, break race conditions and make + sure we stay that way (see comment in timers.c.) */ + if (! si->de_race_id) + de_race_timer ((XtPointer) si, 0); } } static void analyze_display (saver_info *si); +static void fix_fds (void); int main (int argc, char **argv) @@ -876,40 +1386,85 @@ main (int argc, char **argv) saver_info the_si; saver_info *si = &the_si; saver_preferences *p = &si->prefs; + struct passwd *spasswd; int i; memset(si, 0, sizeof(*si)); global_si_kludge = si; /* I hate C so much... */ - srandom ((int) time ((time_t *) 0)); + fix_fds(); + +# undef ya_rand_init + ya_rand_init (0); save_argv (argc, argv); set_version_string (si, &argc, argv); privileged_initialization (si, &argc, argv); hack_environment (si); + spasswd = getpwuid(getuid()); + if (!spasswd) + { + fprintf(stderr, "Could not figure out who the current user is!\n"); + fprintf(stderr, "spasswd is %x\n", (unsigned int) spasswd); + return 1; + } + + si->user = strdup(spasswd->pw_name ? spasswd->pw_name : "(unknown)"); + +# ifndef NO_LOCKING + si->unlock_cb = gui_auth_conv; + si->auth_finished_cb = auth_finished_cb; +# endif /* !NO_LOCKING */ + shell = connect_to_server (si, &argc, argv); process_command_line (si, &argc, argv); print_banner (si); - initialize_per_screen_info (si, shell); /* also sets p->fading_possible_p */ + load_init_file(si->dpy, p); /* must be before initialize_per_screen_info() */ + blurb_timestamp_p = p->timestamp_p; /* kludge */ + initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */ + + /* We can only issue this warning now. */ + if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p)) + fprintf (stderr, + "%s: there are no PseudoColor or GrayScale visuals.\n" + "%s: ignoring the request for fading/unfading.\n", + blurb(), blurb()); for (i = 0; i < si->nscreens; i++) - if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen)) - exit (1); + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->real_screen_p) + if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen)) + exit (1); + } - load_init_file (p); + lock_initialization (si, &argc, argv); + print_lock_failure_banner (si); if (p->xsync_p) XSynchronize (si->dpy, True); - blurb_timestamp_p = p->timestamp_p; /* kludge */ if (p->verbose_p) analyze_display (si); initialize_server_extensions (si); + + si->blank_time = time ((time_t) 0); /* must be before ..._window */ initialize_screensaver_window (si); + select_events (si); init_sigchld (); + disable_builtin_screensaver (si, True); + sync_server_dpms_settings (si->dpy, + (p->dpms_enabled_p && + p->mode != DONT_BLANK), + p->dpms_standby / 1000, + p->dpms_suspend / 1000, + p->dpms_off / 1000, + False); + initialize_stderr (si); + handle_signals (si); make_splash_dialog (si); @@ -917,10 +1472,81 @@ main (int argc, char **argv) return 0; } +static void +fix_fds (void) +{ + /* Bad Things Happen if stdin, stdout, and stderr have been closed + (as by the `sh incantation "xscreensaver >&- 2>&-"). When you do + that, the X connection gets allocated to one of these fds, and + then some random library writes to stderr, and random bits get + stuffed down the X pipe, causing "Xlib: sequence lost" errors. + So, we cause the first three file descriptors to be open to + /dev/null if they aren't open to something else already. This + must be done before any other files are opened (or the closing + of that other file will again free up one of the "magic" first + three FDs.) + + We do this by opening /dev/null three times, and then closing + those fds, *unless* any of them got allocated as #0, #1, or #2, + in which case we leave them open. Gag. + + Really, this crap is technically required of *every* X program, + if you want it to be robust in the face of "2>&-". + */ + int fd0 = open ("/dev/null", O_RDWR); + int fd1 = open ("/dev/null", O_RDWR); + int fd2 = open ("/dev/null", O_RDWR); + if (fd0 > 2) close (fd0); + if (fd1 > 2) close (fd1); + if (fd2 > 2) close (fd2); +} + + /* Processing ClientMessage events. */ + +static Bool error_handler_hit_p = False; + +static int +ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) +{ + error_handler_hit_p = True; + return 0; +} + +/* Sometimes some systems send us ClientMessage events with bogus atoms in + them. We only look up the atom names for printing warning messages, + so don't bomb out when it happens... + */ +static char * +XGetAtomName_safe (Display *dpy, Atom atom) +{ + char *result; + XErrorHandler old_handler; + if (!atom) return 0; + + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + result = XGetAtomName (dpy, atom); + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + if (error_handler_hit_p) result = 0; + + if (result) + return result; + else + { + char buf[100]; + sprintf (buf, "<>", (unsigned int) atom); + return strdup (buf); + } +} + + static void clientmessage_response (saver_info *si, Window w, Bool error, const char *stderr_msg, @@ -929,6 +1555,8 @@ clientmessage_response (saver_info *si, Window w, Bool error, char *proto; int L; saver_preferences *p = &si->prefs; + XErrorHandler old_handler; + if (error || p->verbose_p) fprintf (stderr, "%s: %s\n", blurb(), stderr_msg); @@ -938,12 +1566,92 @@ clientmessage_response (saver_info *si, Window w, Bool error, strcpy (proto+1, protocol_msg); L++; + /* Ignore all X errors while sending a response to a ClientMessage. + Pretty much the only way we could get an error here is if the + window we're trying to send the reply on has been deleted, in + which case, the sender of the ClientMessage won't see our response + anyway. + */ + XSync (si->dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8, - PropModeReplace, proto, L); + PropModeReplace, (unsigned char *) proto, L); + + XSync (si->dpy, False); + XSetErrorHandler (old_handler); XSync (si->dpy, False); + free (proto); } + +static void +bogus_clientmessage_warning (saver_info *si, XEvent *event) +{ + saver_preferences *p = &si->prefs; + char *str = XGetAtomName_safe (si->dpy, event->xclient.message_type); + Window w = event->xclient.window; + char wdesc[255]; + int screen = 0; + Bool root_p = False; + + *wdesc = 0; + for (screen = 0; screen < si->nscreens; screen++) + if (w == si->screens[screen].screensaver_window) + { + strcpy (wdesc, "xscreensaver"); + break; + } + else if (w == RootWindow (si->dpy, screen)) + { + strcpy (wdesc, "root"); + root_p = True; + break; + } + + /* If this ClientMessage was sent to the real root window instead of to the + xscreensaver window, then it might be intended for someone else who is + listening on the root window (e.g., the window manager). So only print + the warning if: we are in debug mode; or if the bogus message was + actually sent to one of the xscreensaver-created windows. + */ + if (root_p && !p->debug_p) + return; + + if (!*wdesc) + { + XErrorHandler old_handler; + XClassHint hint; + XWindowAttributes xgwa; + memset (&hint, 0, sizeof(hint)); + memset (&xgwa, 0, sizeof(xgwa)); + + XSync (si->dpy, False); + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + XGetClassHint (si->dpy, w, &hint); + XGetWindowAttributes (si->dpy, w, &xgwa); + XSync (si->dpy, False); + XSetErrorHandler (old_handler); + XSync (si->dpy, False); + + screen = (xgwa.screen ? screen_number (xgwa.screen) : -1); + + sprintf (wdesc, "%.20s / %.20s", + (hint.res_name ? hint.res_name : "(null)"), + (hint.res_class ? hint.res_class : "(null)")); + if (hint.res_name) XFree (hint.res_name); + if (hint.res_class) XFree (hint.res_class); + } + + fprintf (stderr, "%s: %d: unrecognised ClientMessage \"%s\" received\n", + blurb(), screen, (str ? str : "(null)")); + fprintf (stderr, "%s: %d: for window 0x%lx (%s)\n", + blurb(), screen, (unsigned long) w, wdesc); + if (str) XFree (str); +} + Bool handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) { @@ -954,19 +1662,10 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) /* Preferences might affect our handling of client messages. */ maybe_reload_init_file (si); - if (event->xclient.message_type != XA_SCREENSAVER) - { - char *str; - str = XGetAtomName (si->dpy, event->xclient.message_type); - fprintf (stderr, "%s: unrecognised ClientMessage type %s received\n", - blurb(), (str ? str : "(null)")); - if (str) XFree (str); - return False; - } - if (event->xclient.format != 32) + if (event->xclient.message_type != XA_SCREENSAVER || + event->xclient.format != 32) { - fprintf (stderr, "%s: ClientMessage of format %d received, not 32\n", - blurb(), event->xclient.format); + bogus_clientmessage_warning (si, event); return False; } @@ -975,12 +1674,25 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) { if (until_idle_p) { + if (p->mode == DONT_BLANK) + { + clientmessage_response(si, window, True, + "ACTIVATE ClientMessage received in DONT_BLANK mode.", + "screen blanking is currently disabled."); + return False; + } + clientmessage_response(si, window, False, "ACTIVATE ClientMessage received.", "activating."); si->selection_mode = 0; si->demoing_p = False; - if (p->use_mit_saver_extension || p->use_sgi_saver_extension) + + if (si->throttled_p && p->verbose_p) + fprintf (stderr, "%s: unthrottled.\n", blurb()); + si->throttled_p = False; + + if (si->using_mit_saver_extension || si->using_sgi_saver_extension) { XForceScreenSaver (si->dpy, ScreenSaverActive); return False; @@ -998,10 +1710,14 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) { if (! until_idle_p) { + if (si->throttled_p && p->verbose_p) + fprintf (stderr, "%s: unthrottled.\n", blurb()); + si->throttled_p = False; + clientmessage_response(si, window, False, "DEACTIVATE ClientMessage received.", "deactivating."); - if (p->use_mit_saver_extension || p->use_sgi_saver_extension) + if (si->using_mit_saver_extension || si->using_sgi_saver_extension) { XForceScreenSaver (si->dpy, ScreenSaverReset); return False; @@ -1011,9 +1727,10 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) return True; } } - clientmessage_response(si, window, True, - "ClientMessage DEACTIVATE received while inactive.", - "not active."); + clientmessage_response(si, window, False, + "ClientMessage DEACTIVATE received while inactive: resetting idle timer.", + "not active: idle timer reset."); + reset_timers (si); } else if (type == XA_CYCLE) { @@ -1024,6 +1741,11 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) "cycling."); si->selection_mode = 0; /* 0 means randomize when its time. */ si->demoing_p = False; + + if (si->throttled_p && p->verbose_p) + fprintf (stderr, "%s: unthrottled.\n", blurb()); + si->throttled_p = False; + if (si->cycle_id) XtRemoveTimeOut (si->cycle_id); si->cycle_id = 0; @@ -1044,6 +1766,10 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) si->selection_mode = (type == XA_NEXT ? -1 : -2); si->demoing_p = False; + if (si->throttled_p && p->verbose_p) + fprintf (stderr, "%s: unthrottled.\n", blurb()); + si->throttled_p = False; + if (! until_idle_p) { if (si->cycle_id) @@ -1060,6 +1786,14 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) char buf2 [255]; long which = event->xclient.data.l[1]; + if (p->mode == DONT_BLANK) + { + clientmessage_response(si, window, True, + "SELECT ClientMessage received in DONT_BLANK mode.", + "screen blanking is currently disabled."); + return False; + } + sprintf (buf, "SELECT %ld ClientMessage received.", which); sprintf (buf2, "activating (%ld).", which); clientmessage_response (si, window, False, buf, buf2); @@ -1068,6 +1802,10 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) si->selection_mode = which; si->demoing_p = False; + if (si->throttled_p && p->verbose_p) + fprintf (stderr, "%s: unthrottled.\n", blurb()); + si->throttled_p = False; + if (! until_idle_p) { if (si->cycle_id) @@ -1116,13 +1854,8 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) XSync (si->dpy, False); } - /* make sure error message shows up before exit. */ - if (real_stderr && stderr != real_stderr) - dup2 (fileno(real_stderr), fileno(stderr)); - - restart_process (si); - exit (1); /* shouldn't get here; but if restarting didn't work, - make this command be the same as EXIT. */ + restart_process (si); /* does not return */ + abort(); } else clientmessage_response (si, window, True, @@ -1132,7 +1865,7 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) else if (type == XA_DEMO) { long arg = event->xclient.data.l[1]; - Bool demo_one_hack_p = (arg == 300); + Bool demo_one_hack_p = (arg == 5000); if (demo_one_hack_p) { @@ -1149,6 +1882,10 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) si->selection_mode = which; si->demoing_p = True; + if (si->throttled_p && p->verbose_p) + fprintf (stderr, "%s: unthrottled.\n", blurb()); + si->throttled_p = False; + return True; } @@ -1190,11 +1927,11 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) char *response = (until_idle_p ? "activating and locking." : "locking."); - si->locked_p = True; - si->selection_mode = 0; - si->demoing_p = False; sprintf (buf, "LOCK ClientMessage received; %s", response); clientmessage_response (si, window, False, buf, response); + set_locked_p (si, True); + si->selection_mode = 0; + si->demoing_p = False; if (si->lock_id) /* we're doing it now, so lose the timeout */ { @@ -1204,7 +1941,8 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) if (until_idle_p) { - if (p->use_mit_saver_extension || p->use_sgi_saver_extension) + if (si->using_mit_saver_extension || + si->using_sgi_saver_extension) { XForceScreenSaver (si->dpy, ScreenSaverActive); return False; @@ -1217,11 +1955,69 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) } #endif /* !NO_LOCKING */ } + else if (type == XA_THROTTLE) + { + /* The THROTTLE command is deprecated -- it predates the XDPMS + extension. Instead of using -throttle, users should instead + just power off the monitor (e.g., "xset dpms force off".) + In a few minutes, xscreensaver will notice that the monitor + is off, and cease running hacks. + */ + if (si->throttled_p) + clientmessage_response (si, window, True, + "THROTTLE ClientMessage received, but " + "already throttled.", + "already throttled."); + else + { + char buf [255]; + char *response = "throttled."; + si->throttled_p = True; + si->selection_mode = 0; + si->demoing_p = False; + sprintf (buf, "THROTTLE ClientMessage received; %s", response); + clientmessage_response (si, window, False, buf, response); + + if (! until_idle_p) + { + if (si->cycle_id) + XtRemoveTimeOut (si->cycle_id); + si->cycle_id = 0; + cycle_timer ((XtPointer) si, 0); + } + } + } + else if (type == XA_UNTHROTTLE) + { + if (! si->throttled_p) + clientmessage_response (si, window, True, + "UNTHROTTLE ClientMessage received, but " + "not throttled.", + "not throttled."); + else + { + char buf [255]; + char *response = "unthrottled."; + si->throttled_p = False; + si->selection_mode = 0; + si->demoing_p = False; + sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response); + clientmessage_response (si, window, False, buf, response); + + if (! until_idle_p) + { + if (si->cycle_id) + XtRemoveTimeOut (si->cycle_id); + si->cycle_id = 0; + cycle_timer ((XtPointer) si, 0); + } + } + } else { char buf [1024]; char *str; - str = (type ? XGetAtomName(si->dpy, type) : 0); + str = XGetAtomName_safe (si->dpy, type); if (str) { @@ -1251,39 +2047,133 @@ static void analyze_display (saver_info *si) { int i, j; - static const char *exts[][2] = { - { "SCREEN_SAVER", "SGI Screen-Saver" }, - { "SCREEN-SAVER", "SGI Screen-Saver" }, - { "MIT-SCREEN-SAVER", "MIT Screen-Saver" }, - { "XIDLE", "XIdle" }, - { "SGI-VIDEO-CONTROL", "SGI Video-Control" }, - { "READDISPLAY", "SGI Read-Display" }, - { "MIT-SHM", "Shared Memory" }, - { "DOUBLE-BUFFER", "Double-Buffering" }, - { "DPMS", "Power Management" }, - { "GLX", "GLX" } + static struct { + const char *name; const char *desc; Bool useful_p; + } exts[] = { + + { "SCREEN_SAVER", /* underscore */ "SGI Screen-Saver", +# ifdef HAVE_SGI_SAVER_EXTENSION + True +# else + False +# endif + }, { "SCREEN-SAVER", /* dash */ "SGI Screen-Saver", +# ifdef HAVE_SGI_SAVER_EXTENSION + True +# else + False +# endif + }, { "MIT-SCREEN-SAVER", "MIT Screen-Saver", +# ifdef HAVE_MIT_SAVER_EXTENSION + True +# else + False +# endif + }, { "XIDLE", "XIdle", +# ifdef HAVE_XIDLE_EXTENSION + True +# else + False +# endif + }, { "SGI-VIDEO-CONTROL", "SGI Video-Control", +# ifdef HAVE_SGI_VC_EXTENSION + True +# else + False +# endif + }, { "READDISPLAY", "SGI Read-Display", +# ifdef HAVE_READ_DISPLAY_EXTENSION + True +# else + False +# endif + }, { "MIT-SHM", "Shared Memory", +# ifdef HAVE_XSHM_EXTENSION + True +# else + False +# endif + }, { "DOUBLE-BUFFER", "Double-Buffering", +# ifdef HAVE_DOUBLE_BUFFER_EXTENSION + True +# else + False +# endif + }, { "DPMS", "Power Management", +# ifdef HAVE_DPMS_EXTENSION + True +# else + False +# endif + }, { "GLX", "GLX", +# ifdef HAVE_GL + True +# else + False +# endif + }, { "XFree86-VidModeExtension", "XF86 Video-Mode", +# ifdef HAVE_XF86VMODE + True +# else + False +# endif + }, { "XINERAMA", "Xinerama", +# ifdef HAVE_XINERAMA + True +# else + False +# endif + }, { "RANDR", "Resize-and-Rotate", +# ifdef HAVE_RANDR + True +# else + False +# endif + }, { "Apple-DRI", "Apple-DRI (XDarwin)", + True + }, }; - fprintf (stderr, "%s: running on display \"%s\"\n", blurb(), - DisplayString(si->dpy)); - fprintf (stderr, "%s: vendor is %s, %d\n", blurb(), + fprintf (stderr, "%s: running on display \"%s\" (%d %sscreen%s).\n", + blurb(), + DisplayString(si->dpy), + si->nscreens, + (si->xinerama_p ? "Xinerama " : ""), + (si->nscreens == 1 ? "" : "s")); + fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(), ServerVendor(si->dpy), VendorRelease(si->dpy)); fprintf (stderr, "%s: useful extensions:\n", blurb()); for (i = 0; i < countof(exts); i++) { int op = 0, event = 0, error = 0; - if (XQueryExtension (si->dpy, exts[i][0], &op, &event, &error)) - fprintf (stderr, "%s: %s\n", blurb(), exts[i][1]); + char buf [255]; + int j; + if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error)) + continue; + sprintf (buf, "%s: ", blurb()); + j = strlen (buf); + strcat (buf, exts[i].desc); + if (!exts[i].useful_p) + { + int k = j + 18; + while (strlen (buf) < k) strcat (buf, " "); + strcat (buf, "<-- not supported at compile time!"); + } + fprintf (stderr, "%s\n", buf); } for (i = 0; i < si->nscreens; i++) { + saver_screen_info *ssi = &si->screens[i]; unsigned long colormapped_depths = 0; unsigned long non_mapped_depths = 0; XVisualInfo vi_in, *vi_out; int out_count; - vi_in.screen = i; + + if (!ssi->real_screen_p) continue; + + vi_in.screen = ssi->real_screen_number; vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count); if (!vi_out) continue; for (j = 0; j < out_count; j++) @@ -1295,19 +2185,99 @@ analyze_display (saver_info *si) if (colormapped_depths) { - fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i); + fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), + ssi->real_screen_number); for (j = 0; j < 32; j++) if (colormapped_depths & (1 << j)) fprintf (stderr, " %d", j); - fprintf (stderr, "\n"); + fprintf (stderr, ".\n"); } if (non_mapped_depths) { - fprintf (stderr, "%s: screen %d non-mapped depths:", blurb(), i); + fprintf (stderr, "%s: screen %d non-colormapped depths:", + blurb(), ssi->real_screen_number); for (j = 0; j < 32; j++) if (non_mapped_depths & (1 << j)) fprintf (stderr, " %d", j); - fprintf (stderr, "\n"); + fprintf (stderr, ".\n"); + } + } + + if (si->xinerama_p) + { + fprintf (stderr, "%s: Xinerama layout:\n", blurb()); + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + fprintf (stderr, "%s: %c %d/%d: %dx%d+%d+%d\n", + blurb(), + (ssi->real_screen_p ? '+' : ' '), + ssi->number, ssi->real_screen_number, + ssi->width, ssi->height, ssi->x, ssi->y); + } + } +} + +Bool +display_is_on_console_p (saver_info *si) +{ + Bool not_on_console = True; + char *dpystr = DisplayString (si->dpy); + char *tail = (char *) strchr (dpystr, ':'); + if (! tail || strncmp (tail, ":0", 2)) + not_on_console = True; + else + { + char dpyname[255], localname[255]; + strncpy (dpyname, dpystr, tail-dpystr); + dpyname [tail-dpystr] = 0; + if (!*dpyname || + !strcmp(dpyname, "unix") || + !strcmp(dpyname, "localhost")) + not_on_console = False; + else if (gethostname (localname, sizeof (localname))) + not_on_console = True; /* can't find hostname? */ + else + { + /* We have to call gethostbyname() on the result of gethostname() + because the two aren't guarenteed to be the same name for the + same host: on some losing systems, one is a FQDN and the other + is not. Here in the wide wonderful world of Unix it's rocket + science to obtain the local hostname in a portable fashion. + + And don't forget, gethostbyname() reuses the structure it + returns, so we have to copy the fucker before calling it again. + Thank you master, may I have another. + */ + struct hostent *h = gethostbyname (dpyname); + if (!h) + not_on_console = True; + else + { + char hn [255]; + struct hostent *l; + strcpy (hn, h->h_name); + l = gethostbyname (localname); + not_on_console = (!l || !!(strcmp (l->h_name, hn))); + } } } + return !not_on_console; +} + + +/* Do a little bit of heap introspection... + */ +void +check_for_leaks (const char *where) +{ +#if defined(HAVE_SBRK) && defined(LEAK_PARANOIA) + static unsigned long last_brk = 0; + int b = (unsigned long) sbrk(0); + if (last_brk && last_brk < b) + fprintf (stderr, "%s: %s: brk grew by %luK.\n", + blurb(), where, + (((b - last_brk) + 1023) / 1024)); + last_brk = b; +#endif /* HAVE_SBRK */ }