ftp://ftp.swin.edu.au/slackware/slackware-9.1/source/xap/xscreensaver/xscreensaver...
[xscreensaver] / driver / subprocs.c
index 3f43139860243883555f9a910c20f7155c579010..f7d8cc8f09aefdb6f72eb0ff579e62cecd2b2ba7 100644 (file)
@@ -1,6 +1,5 @@
 /* subprocs.c --- choosing, spawning, and killing screenhacks.
 /* subprocs.c --- choosing, spawning, and killing screenhacks.
- * xscreensaver, Copyright (c) 1991, 1992, 1993, 1995, 1997, 1998
- *  Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1991-2003 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
@@ -31,8 +30,8 @@
 # include <sys/wait.h>         /* for waitpid() and associated macros */
 #endif
 
 # include <sys/wait.h>         /* for waitpid() and associated macros */
 #endif
 
-#if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
-# include <sys/resource.h>     /* for setpriority() and PRIO_PROCESS */
+#ifdef HAVE_SETRLIMIT
+# include <sys/resource.h>     /* for setrlimit() and RLIMIT_AS */
 #endif
 
 #ifdef VMS
 #endif
 
 #ifdef VMS
@@ -67,213 +66,79 @@ extern int kill (pid_t, int);              /* signal() is in sys/signal.h... */
 
 #include "xscreensaver.h"
 #include "yarandom.h"
 
 #include "xscreensaver.h"
 #include "yarandom.h"
-
+#include "visual.h"    /* for id_to_visual() */
 
 extern saver_info *global_si_kludge;   /* I hate C so much... */
 
 
 extern saver_info *global_si_kludge;   /* I hate C so much... */
 
-static void hack_subproc_environment (saver_screen_info *ssi);
 
 
+/* RLIMIT_AS (called RLIMIT_VMEM on some systems) controls the maximum size
+   of a process's address space, i.e., the maximal brk(2) and mmap(2) values.
+   Setting this lets you put a cap on how much memory a process can allocate.
+
+   Except the "and mmap()" part kinda makes this useless, since many GL
+   implementations end up using mmap() to pull the whole frame buffer into
+   memory (or something along those lines) making it appear processes are
+   using hundreds of megabytes when in fact they're using very little, and
+   we end up capping their mallocs prematurely.  YAY!
+ */
+#if defined(RLIMIT_VMEM) && !defined(RLIMIT_AS)
+# define RLIMIT_AS RLIMIT_VMEM
+#endif
 
 static void
 
 static void
-nice_subproc (int nice_level)
+limit_subproc_memory (int address_space_limit, Bool verbose_p)
 {
 {
-  if (nice_level == 0)
+
+/* This has caused way more problems than it has solved...
+   Let's just completely ignore the "memoryLimit" option now.
+ */
+#undef HAVE_SETRLIMIT
+
+#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_AS)
+  struct rlimit r;
+
+  if (address_space_limit < 10 * 1024)  /* let's not be crazy */
     return;
 
     return;
 
-#if defined(HAVE_NICE)
-  {
-    int old_nice = nice (0);
-    int n = nice_level - old_nice;
-    errno = 0;
-    if (nice (n) == -1 && errno != 0)
-      {
-       char buf [512];
-       sprintf (buf, "%s: nice(%d) failed", blurb(), n);
-       perror (buf);
-    }
-  }
-#elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
-  if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0)
+  if (getrlimit (RLIMIT_AS, &r) != 0)
     {
       char buf [512];
     {
       char buf [512];
-      sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
-              blurb(), (unsigned long) getpid(), nice_level);
+      sprintf (buf, "%s: getrlimit(RLIMIT_AS) failed", blurb());
       perror (buf);
       perror (buf);
+      return;
     }
     }
-#else
-  fprintf (stderr,
-          "%s: don't know how to change process priority on this system.\n",
-          blurb());
 
 
-#endif
-}
+  r.rlim_cur = address_space_limit;
 
 
-
-#ifndef VMS
-
-static void
-exec_simple_command (const char *command)
-{
-  char *av[1024];
-  int ac = 0;
-  char *token = strtok (strdup(command), " \t");
-  while (token)
+  if (setrlimit (RLIMIT_AS, &r) != 0)
     {
     {
-      av[ac++] = token;
-      token = strtok(0, " \t");
+      char buf [512];
+      sprintf (buf, "%s: setrlimit(RLIMIT_AS, {%lu, %lu}) failed",
+               blurb(), r.rlim_cur, r.rlim_max);
+      perror (buf);
+      return;
     }
     }
-  av[ac] = 0;
-
-  execvp (av[0], av);                  /* shouldn't return. */
-
-  {
-    char buf [512];
-    sprintf (buf, "%s: could not execute \"%s\"", blurb(), av[0]);
-    perror (buf);
-
-    if (errno == ENOENT &&
-       (token = getenv("PATH")))
-      {
-# ifndef PATH_MAX
-#  ifdef MAXPATHLEN
-#   define PATH_MAX MAXPATHLEN
-#  else
-#   define PATH_MAX 2048
-#  endif
-# endif
-       char path[PATH_MAX];
-       fprintf (stderr, "\n");
-       *path = 0;
-# if defined(HAVE_GETCWD)
-       getcwd (path, sizeof(path));
-# elif defined(HAVE_GETWD)
-       getwd (path);
-# endif
-       if (*path)
-         fprintf (stderr, "    Current directory is: %s\n", path);
-       fprintf (stderr, "    PATH is:\n");
-       token = strtok (strdup(token), ":");
-       while (token)
-         {
-           fprintf (stderr, "        %s\n", token);
-           token = strtok(0, ":");
-         }
-       fprintf (stderr, "\n");
-      }
-  }
-  fflush(stderr);
-  fflush(stdout);
-  exit (1);    /* Note that this only exits a child fork.  */
-}
-
-
-static void
-exec_complex_command (const char *shell, const char *command)
-{
-  char *av[5];
-  int ac = 0;
-  char *command2 = (char *) malloc (strlen (command) + 6);
-  memcpy (command2, "exec ", 5);
-  memcpy (command2 + 5, command, strlen (command) + 1);
-
-  /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
-  av [ac++] = (char *) shell;
-  av [ac++] = "-c";
-  av [ac++] = command2;
-  av [ac]   = 0;
-
-  execvp (av[0], av);                  /* shouldn't return. */
-
-  {
-    char buf [512];
-    sprintf (buf, "%s: execvp(\"%s\") failed", blurb(), av[0]);
-    perror (buf);
-    fflush(stderr);
-    fflush(stdout);
-    exit (1);  /* Note that this only exits a child fork.  */
-  }
-}
-
-#else  /* VMS */
 
 
-static void
-exec_vms_command (const char *command)
-{
-  system (command);
-  fflush (stderr);
-  fflush (stdout);
-  exit (1);    /* Note that this only exits a child fork.  */
-}
-
-#endif /* !VMS */
-
-
-static void
-exec_screenhack (saver_info *si, const char *command)
-{
-  /* I don't believe what a sorry excuse for an operating system UNIX is!
-
-     - I want to spawn a process.
-     - I want to know it's pid so that I can kill it.
-     - I would like to receive a message when it dies of natural causes.
-     - I want the spawned process to have user-specified arguments.
-
-     If shell metacharacters are present (wildcards, backquotes, etc), the
-     only way to parse those arguments is to run a shell to do the parsing
-     for you.
-
-     And the only way to know the pid of the process is to fork() and exec()
-     it in the spawned side of the fork.
-
-     But if you're running a shell to parse your arguments, this gives you
-     the pid of the *shell*, not the pid of the *process* that you're
-     actually interested in, which is an *inferior* of the shell.  This also
-     means that the SIGCHLD you get applies to the shell, not its inferior.
-     (Why isn't that sufficient?  I don't remember any more, but it turns
-     out that it isn't.)
-
-     So, the only solution, when metacharacters are present, is to force the
-     shell to exec() its inferior.  What a fucking hack!  We prepend "exec "
-     to the command string, and hope it doesn't contain unquoted semicolons
-     or ampersands (we don't search for them, because we don't want to
-     prohibit their use in quoted strings (messages, for example) and parsing
-     out the various quote characters is too much of a pain.)
-
-     (Actually, Clint Wong <clint@jts.com> points out that process groups
-     might be used to take care of this problem; this may be worth considering
-     some day, except that, 1: this code works now, so why fix it, and 2: from
-     what I've seen in Emacs, dealing with process groups isn't especially
-     portable.)
-   */
-  saver_preferences *p = &si->prefs;
-
-#ifndef VMS
-  Bool hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"");
-
-  if (p->verbose_p)
-    fprintf (stderr, "%s: spawning \"%s\" in pid %lu%s.\n",
-            blurb(), command, (unsigned long) getpid (),
-            (hairy_p ? " (via shell)" : ""));
-
-  if (hairy_p)
-    /* If it contains any shell metacharacters, do it the hard way,
-       and fork a shell to parse the arguments for us. */
-    exec_complex_command (p->shell, command);
-  else
-    /* Otherwise, we can just exec the program directly. */
-    exec_simple_command (command);
+  if (verbose_p)
+    {
+      int i = address_space_limit;
+      char buf[100];
+      if      (i >= (1<<30) && i == ((i >> 30) << 30))
+        sprintf(buf, "%dG", i >> 30);
+      else if (i >= (1<<20) && i == ((i >> 20) << 20))
+        sprintf(buf, "%dM", i >> 20);
+      else if (i >= (1<<10) && i == ((i >> 10) << 10))
+        sprintf(buf, "%dK", i >> 10);
+      else
+        sprintf(buf, "%d bytes", i);
 
 
-#else /* VMS */
-  if (p->verbose_p)
-    fprintf (stderr, "%s: spawning \"%s\" in pid %lu.\n",
-            blurb(), command, getpid());
-  exec_vms_command (command);
-#endif /* VMS */
+      fprintf (stderr, "%s: limited pid %lu address space to %s.\n",
+               blurb(), (unsigned long) getpid (), buf);
+    }
 
 
-  abort();     /* that shouldn't have returned. */
+#endif /* HAVE_SETRLIMIT && RLIMIT_AS */
 }
 
 }
 
-
 \f
 /* Management of child processes, and de-zombification.
  */
 \f
 /* Management of child processes, and de-zombification.
  */
@@ -291,6 +156,7 @@ enum job_status {
 struct screenhack_job {
   char *name;
   pid_t pid;
 struct screenhack_job {
   char *name;
   pid_t pid;
+  int screen;
   enum job_status status;
   struct screenhack_job *next;
 };
   enum job_status status;
   struct screenhack_job *next;
 };
@@ -304,8 +170,9 @@ show_job_list (void)
   struct screenhack_job *job;
   fprintf(stderr, "%s: job list:\n", blurb());
   for (job = jobs; job; job = job->next)
   struct screenhack_job *job;
   fprintf(stderr, "%s: job list:\n", blurb());
   for (job = jobs; job; job = job->next)
-    fprintf (stderr, "  %5ld: (%s) %s\n",
+    fprintf (stderr, "  %5ld: %2d: (%s) %s\n",
             (long) job->pid,
             (long) job->pid,
+             job->screen,
             (job->status == job_running ? "running" :
              job->status == job_stopped ? "stopped" :
              job->status == job_killed  ? " killed" :
             (job->status == job_running ? "running" :
              job->status == job_stopped ? "stopped" :
              job->status == job_killed  ? " killed" :
@@ -318,31 +185,39 @@ show_job_list (void)
 static void clean_job_list (void);
 
 static struct screenhack_job *
 static void clean_job_list (void);
 
 static struct screenhack_job *
-make_job (pid_t pid, const char *cmd)
+make_job (pid_t pid, int screen, const char *cmd)
 {
   struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job));
 
   static char name [1024];
   const char *in = cmd;
   char *out = name;
 {
   struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job));
 
   static char name [1024];
   const char *in = cmd;
   char *out = name;
+  int got_eq = 0;
+  int first = 1;
 
   clean_job_list();
 
 
   clean_job_list();
 
+ AGAIN:
   while (isspace(*in)) in++;           /* skip whitespace */
   while (isspace(*in)) in++;           /* skip whitespace */
-  while (!isspace(*in) && *in != ':')
+  while (!isspace(*in) && *in != ':') {
+    if (*in == '=') got_eq = 1;
     *out++ = *in++;                    /* snarf first token */
     *out++ = *in++;                    /* snarf first token */
-  while (isspace(*in)) in++;           /* skip whitespace */
-  if (*in == ':')                      /* token was a visual name; skip it. */
-    {
-      in++;
+  }
+
+  if (got_eq)                          /* if the first token was FOO=bar */
+    {                                  /* then get the next token instead. */
+      got_eq = 0;
       out = name;
       out = name;
-      while (isspace(*in)) in++;               /* skip whitespace */
-      while (!isspace(*in)) *out++ = *in++;    /* snarf first token */
+      first = 0;
+      goto AGAIN;
     }
     }
+
+  while (isspace(*in)) in++;           /* skip whitespace */
   *out = 0;
 
   job->name = strdup(name);
   job->pid = pid;
   *out = 0;
 
   job->name = strdup(name);
   job->pid = pid;
+  job->screen = screen;
   job->status = job_running;
   job->next = jobs;
   jobs = job;
   job->status = job_running;
   job->next = jobs;
   jobs = job;
@@ -419,26 +294,38 @@ static void describe_dead_child (saver_info *, pid_t, int wait_status);
 static int block_sigchld_handler = 0;
 
 
 static int block_sigchld_handler = 0;
 
 
-static void
+#ifdef HAVE_SIGACTION
+ sigset_t
+#else  /* !HAVE_SIGACTION */
+ int
+#endif /* !HAVE_SIGACTION */
 block_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
   sigset_t child_set;
   sigemptyset (&child_set);
   sigaddset (&child_set, SIGCHLD);
 block_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
   sigset_t child_set;
   sigemptyset (&child_set);
   sigaddset (&child_set, SIGCHLD);
+  sigaddset (&child_set, SIGPIPE);
   sigprocmask (SIG_BLOCK, &child_set, 0);
 #endif /* HAVE_SIGACTION */
 
   block_sigchld_handler++;
   sigprocmask (SIG_BLOCK, &child_set, 0);
 #endif /* HAVE_SIGACTION */
 
   block_sigchld_handler++;
+
+#ifdef HAVE_SIGACTION
+  return child_set;
+#else  /* !HAVE_SIGACTION */
+  return 0;
+#endif /* !HAVE_SIGACTION */
 }
 
 }
 
-static void
+void
 unblock_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
   sigset_t child_set;
   sigemptyset(&child_set);
   sigaddset(&child_set, SIGCHLD);
 unblock_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
   sigset_t child_set;
   sigemptyset(&child_set);
   sigaddset(&child_set, SIGCHLD);
+  sigaddset(&child_set, SIGPIPE);
   sigprocmask(SIG_UNBLOCK, &child_set, 0);
 #endif /* HAVE_SIGACTION */
 
   sigprocmask(SIG_UNBLOCK, &child_set, 0);
 #endif /* HAVE_SIGACTION */
 
@@ -481,31 +368,27 @@ kill_job (saver_info *si, pid_t pid, int signal)
   default: abort();
   }
 
   default: abort();
   }
 
-#ifdef SIGSTOP
-  if (p->verbose_p)
-    fprintf (stderr, "%s: %s pid %lu.\n", blurb(),
-            (signal == SIGTERM ? "killing" :
-             signal == SIGSTOP ? "suspending" :
-             signal == SIGCONT ? "resuming" : "signalling"),
-            (unsigned long) job->pid);
-#else  /* !SIGSTOP */
   if (p->verbose_p)
   if (p->verbose_p)
-    fprintf (stderr, "%s: %s pid %lu.\n", blurb(), "killing",
-            (unsigned long) job->pid);
-#endif /* !SIGSTOP */
+    fprintf (stderr, "%s: %d: %s pid %lu (%s)\n",
+             blurb(), job->screen,
+             (job->status == job_killed  ? "killing" :
+              job->status == job_stopped ? "suspending" : "resuming"),
+             (unsigned long) job->pid,
+             job->name);
 
   status = kill (job->pid, signal);
 
   if (p->verbose_p && status < 0)
     {
       if (errno == ESRCH)
 
   status = kill (job->pid, signal);
 
   if (p->verbose_p && status < 0)
     {
       if (errno == ESRCH)
-       fprintf (stderr, "%s: child process %lu (%s) was already dead.\n",
-                blurb(), job->pid, job->name);
+       fprintf (stderr,
+                 "%s: %d: child process %lu (%s) was already dead.\n",
+                blurb(), job->screen, (unsigned long) job->pid, job->name);
       else
        {
          char buf [1024];
       else
        {
          char buf [1024];
-         sprintf (buf, "%s: couldn't kill child process %lu (%s)",
-                  blurb(), job->pid, job->name);
+         sprintf (buf, "%s: %d: couldn't kill child process %lu (%s)",
+                  blurb(), job->screen, (unsigned long) job->pid, job->name);
          perror (buf);
        }
     }
          perror (buf);
        }
     }
@@ -587,6 +470,7 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
   saver_preferences *p = &si->prefs;
   struct screenhack_job *job = find_job (kid);
   const char *name = job ? job->name : "<unknown>";
   saver_preferences *p = &si->prefs;
   struct screenhack_job *job = find_job (kid);
   const char *name = job ? job->name : "<unknown>";
+  int screen_no = job ? job->screen : 0;
 
   if (WIFEXITED (wait_status))
     {
 
   if (WIFEXITED (wait_status))
     {
@@ -605,11 +489,11 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
          (exit_status != 0 &&
           (p->verbose_p || job->status != job_killed)))
        fprintf (stderr,
          (exit_status != 0 &&
           (p->verbose_p || job->status != job_killed)))
        fprintf (stderr,
-                "%s: child pid %lu (%s) exited abnormally (code %d).\n",
-                blurb(), (unsigned long) kid, name, exit_status);
+                "%s: %d: child pid %lu (%s) exited abnormally (code %d).\n",
+                blurb(), screen_no, (unsigned long) kid, name, exit_status);
       else if (p->verbose_p)
       else if (p->verbose_p)
-       fprintf (stderr, "%s: child pid %lu (%s) exited normally.\n",
-                blurb(), (unsigned long) kid, name);
+       fprintf (stderr, "%s: %d: child pid %lu (%s) exited normally.\n",
+                blurb(), screen_no, (unsigned long) kid, name);
 
       if (job)
        job->status = job_dead;
 
       if (job)
        job->status = job_dead;
@@ -620,8 +504,8 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
          !job ||
          job->status != job_killed ||
          WTERMSIG (wait_status) != SIGTERM)
          !job ||
          job->status != job_killed ||
          WTERMSIG (wait_status) != SIGTERM)
-       fprintf (stderr, "%s: child pid %lu (%s) terminated with %s.\n",
-                blurb(), (unsigned long) kid, name,
+       fprintf (stderr, "%s: %d: child pid %lu (%s) terminated with %s.\n",
+                blurb(), screen_no, (unsigned long) kid, name,
                 signal_name (WTERMSIG(wait_status)));
 
       if (job)
                 signal_name (WTERMSIG(wait_status)));
 
       if (job)
@@ -703,40 +587,74 @@ init_sigchld (void)
 \f
 
 static Bool
 \f
 
 static Bool
-select_visual_of_hack (saver_screen_info *ssi, const char *hack)
+select_visual_of_hack (saver_screen_info *ssi, screenhack *hack)
 {
   saver_info *si = ssi->global;
   saver_preferences *p = &si->prefs;
   Bool selected;
 {
   saver_info *si = ssi->global;
   saver_preferences *p = &si->prefs;
   Bool selected;
-  static char vis [1024];
-  const char *in = hack;
-  char *out = vis;
-  while (isspace(*in)) in++;           /* skip whitespace */
-  while (!isspace(*in) && *in != ':')
-    *out++ = *in++;                    /* snarf first token */
-  while (isspace(*in)) in++;           /* skip whitespace */
-  *out = 0;
 
 
-  if (*in == ':')
-    selected = select_visual(ssi, vis);
+  if (hack->visual && *hack->visual)
+    selected = select_visual(ssi, hack->visual);
   else
     selected = select_visual(ssi, 0);
 
   else
     selected = select_visual(ssi, 0);
 
-  if (!selected && (p->verbose_p || si->demo_mode_p))
-    {
-      if (*in == ':') in++;
-      while (isspace(*in)) in++;
-      fprintf (stderr,
-              (si->demo_mode_p
-               ? "%s: warning, no \"%s\" visual for \"%s\".\n"
-               : "%s: no \"%s\" visual; skipping \"%s\".\n"),
-              blurb(), (vis ? vis : "???"), in);
-    }
+  if (!selected && (p->verbose_p || si->demoing_p))
+    fprintf (stderr,
+             (si->demoing_p
+              ? "%s: warning, no \"%s\" visual for \"%s\".\n"
+              : "%s: no \"%s\" visual; skipping \"%s\".\n"),
+             blurb(),
+             (hack->visual && *hack->visual ? hack->visual : "???"),
+             hack->command);
 
   return selected;
 }
 
 
 
   return selected;
 }
 
 
+static void
+print_path_error (const char *program)
+{
+  char buf [512];
+  char *cmd = strdup (program);
+  char *token = strchr (cmd, ' ');
+
+  if (token) *token = 0;
+  sprintf (buf, "%s: could not execute \"%.100s\"", blurb(), cmd);
+  free (cmd);
+  perror (buf);
+
+  if (errno == ENOENT &&
+      (token = getenv("PATH")))
+    {
+# ifndef PATH_MAX
+#  ifdef MAXPATHLEN
+#   define PATH_MAX MAXPATHLEN
+#  else
+#   define PATH_MAX 2048
+#  endif
+# endif
+      char path[PATH_MAX];
+      fprintf (stderr, "\n");
+      *path = 0;
+# if defined(HAVE_GETCWD)
+      getcwd (path, sizeof(path));
+# elif defined(HAVE_GETWD)
+      getwd (path);
+# endif
+      if (*path)
+        fprintf (stderr, "    Current directory is: %s\n", path);
+      fprintf (stderr, "    PATH is:\n");
+      token = strtok (strdup(token), ":");
+      while (token)
+        {
+          fprintf (stderr, "        %s\n", token);
+          token = strtok(0, ":");
+        }
+      fprintf (stderr, "\n");
+    }
+}
+
+
 static void
 spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
 {
 static void
 spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
 {
@@ -745,81 +663,112 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
   raise_window (si, first_time_p, True, False);
   XFlush (si->dpy);
 
   raise_window (si, first_time_p, True, False);
   XFlush (si->dpy);
 
-  if (p->screenhacks_count || si->demo_mode_p)
+  if (p->screenhacks_count)
     {
     {
-      char *hack;
+      screenhack *hack;
       pid_t forked;
       char buf [255];
       int new_hack;
       pid_t forked;
       char buf [255];
       int new_hack;
-
-      if (si->demo_mode_p)
+      int retry_count = 0;
+      Bool force = False;
+
+    AGAIN:
+
+      if (p->screenhacks_count < 1)
+        {
+          /* No hacks at all */
+          new_hack = -1;
+        }
+      else if (p->screenhacks_count == 1)
+        {
+          /* Exactly one hack in the list */
+          new_hack = 0;
+        }
+      else if (si->selection_mode == -1)
+        {
+          /* Select the next hack, wrapping. */
+          new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
+        }
+      else if (si->selection_mode == -2)
+        {
+          /* Select the previous hack, wrapping. */
+          if (ssi->current_hack < 0)
+            new_hack = p->screenhacks_count - 1;
+          else
+            new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
+                        % p->screenhacks_count);
+        }
+      else if (si->selection_mode > 0)
        {
        {
-         hack = si->demo_hack;
-
-         /* Ignore visual-selection failure if in demo mode. */
-         (void) select_visual_of_hack (ssi, hack);
+          /* Select a specific hack, by number (via the ACTIVATE command.) */
+         new_hack = ((si->selection_mode - 1) % p->screenhacks_count);
+         force = True;
        }
        }
-      else
+      else if (p->mode == ONE_HACK &&
+               p->selected_hack >= 0)
        {
        {
-         int retry_count = 0;
-
-       AGAIN:
-         if (p->screenhacks_count == 1)
-           new_hack = 0;
-         else if (si->selection_mode == -1)
-           new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
-         else if (si->selection_mode == -2)
-           new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
-                       % p->screenhacks_count);
-         else if (si->selection_mode > 0)
-           new_hack = ((si->selection_mode - 1) % p->screenhacks_count);
-         else
-           while ((new_hack = random () % p->screenhacks_count)
-                  == ssi->current_hack)
-             ;
-         ssi->current_hack = new_hack;
-         hack = p->screenhacks[ssi->current_hack];
+          /* Select a specific hack, by number (via "One Saver" mode.) */
+          new_hack = p->selected_hack;
+         force = True;
+       }
+      else if (p->mode == BLANK_ONLY || p->mode == DONT_BLANK)
+        {
+          new_hack = -1;
+        }
+      else  /* (p->mode == RANDOM_HACKS) */
+       {
+         /* Select a random hack (but not the one we just ran.) */
+         while ((new_hack = random () % p->screenhacks_count)
+                == ssi->current_hack)
+           ;
+       }
 
 
-         if (!select_visual_of_hack (ssi, hack))
+      if (new_hack < 0)   /* don't run a hack */
+        {
+          ssi->current_hack = -1;
+          if (si->selection_mode < 0)
+            si->selection_mode = 0;
+          return;
+        }
+
+      ssi->current_hack = new_hack;
+      hack = p->screenhacks[ssi->current_hack];
+
+      /* If the hack is disabled, or there is no visual for this hack,
+        then try again (move forward, or backward, or re-randomize.)
+        Unless this hack was specified explicitly, in which case,
+        use it regardless.
+       */
+      if (force)
+        select_visual_of_hack (ssi, hack);
+        
+      if (!force &&
+         (!hack->enabled_p ||
+          !select_visual_of_hack (ssi, hack)))
+       {
+         if (++retry_count > (p->screenhacks_count*4))
            {
            {
-             if (++retry_count > (p->screenhacks_count*4))
-               {
-                 /* Uh, oops.  Odds are, there are no suitable visuals,
-                    and we're looping.  Give up.  (This is totally lame,
-                    what we should do is make a list of suitable hacks at
-                    the beginning, then only loop over them.)
-                 */
-                 if (p->verbose_p)
-                   fprintf(stderr,
-                           "%s: no suitable visuals for these programs.\n",
-                           blurb());
-                 return;
-               }
-             else
-               goto AGAIN;
+             /* Uh, oops.  Odds are, there are no suitable visuals,
+                and we're looping.  Give up.  (This is totally lame,
+                what we should do is make a list of suitable hacks at
+                the beginning, then only loop over them.)
+             */
+             if (p->verbose_p)
+               fprintf(stderr,
+                     "%s: %d: no programs enabled, or no suitable visuals.\n",
+                       blurb(), ssi->number);
+             return;
            }
            }
+         else
+           goto AGAIN;
        }
 
        }
 
+      /* Turn off "next" and "prev" modes now, but "demo" mode is only
+        turned off by explicit action.
+       */
       if (si->selection_mode < 0)
        si->selection_mode = 0;
 
       if (si->selection_mode < 0)
        si->selection_mode = 0;
 
-
-      /* If there's a visual description on the front of the command, nuke it.
-       */
-      {
-       char *in = hack;
-       while (isspace(*in)) in++;                      /* skip whitespace */
-       hack = in;
-       while (!isspace(*in) && *in != ':') in++;       /* snarf first token */
-       while (isspace(*in)) in++;                      /* skip whitespace */
-       if (*in == ':')
-         {
-           in++;
-           while (isspace(*in)) in++;
-           hack = in;
-         }
-      }
-
       switch ((int) (forked = fork ()))
        {
        case -1:
       switch ((int) (forked = fork ()))
        {
        case -1:
@@ -830,15 +779,28 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
 
        case 0:
          close (ConnectionNumber (si->dpy));   /* close display fd */
 
        case 0:
          close (ConnectionNumber (si->dpy));   /* close display fd */
-         nice_subproc (p->nice_inferior);      /* change process priority */
+         limit_subproc_memory (p->inferior_memory_limit, p->verbose_p);
          hack_subproc_environment (ssi);       /* set $DISPLAY */
          hack_subproc_environment (ssi);       /* set $DISPLAY */
-         exec_screenhack (si, hack);           /* this does not return */
-         abort();
+
+          if (p->verbose_p)
+            fprintf (stderr, "%s: %d: spawning \"%s\" in pid %lu.\n",
+                     blurb(), ssi->number, hack->command,
+                     (unsigned long) getpid ());
+
+         exec_command (p->shell, hack->command, p->nice_inferior);
+
+          /* If that returned, we were unable to exec the subprocess.
+             Print an error message, if desired.
+           */
+          if (! p->ignore_uninstalled_p)
+            print_path_error (hack->command);
+
+          exit (1);  /* exits child fork */
          break;
 
        default:
          ssi->pid = forked;
          break;
 
        default:
          ssi->pid = forked;
-         (void) make_job (forked, hack);
+         (void) make_job (forked, ssi->number, hack->command);
          break;
        }
     }
          break;
        }
     }
@@ -848,22 +810,21 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
 void
 spawn_screenhack (saver_info *si, Bool first_time_p)
 {
 void
 spawn_screenhack (saver_info *si, Bool first_time_p)
 {
-  int i;
-
-  if (!monitor_powered_on_p (si))
+  if (monitor_powered_on_p (si))
     {
     {
-      if (si->prefs.verbose_p)
-       fprintf (stderr,
-                "%s: server reports that monitor has powered down; "
-                "not launching a new hack.\n", blurb());
-      return;
+      int i;
+      for (i = 0; i < si->nscreens; i++)
+        {
+          saver_screen_info *ssi = &si->screens[i];
+          spawn_screenhack_1 (ssi, first_time_p);
+        }
     }
     }
+  else if (si->prefs.verbose_p)
+    fprintf (stderr,
+             "%s: X says monitor has powered down; "
+             "not launching a hack.\n", blurb());
 
 
-  for (i = 0; i < si->nscreens; i++)
-    {
-      saver_screen_info *ssi = &si->screens[i];
-      spawn_screenhack_1 (ssi, first_time_p);
-    }
+  store_saver_status (si);  /* store current hack numbers */
 }
 
 
 }
 
 
@@ -919,15 +880,14 @@ emergency_kill_subproc (saver_info *si)
 Bool
 screenhack_running_p (saver_info *si)
 {
 Bool
 screenhack_running_p (saver_info *si)
 {
-  Bool result = True;
+  Bool any_running_p = False;
   int i;
   for (i = 0; i < si->nscreens; i++)
     {
       saver_screen_info *ssi = &si->screens[i];
   int i;
   for (i = 0; i < si->nscreens; i++)
     {
       saver_screen_info *ssi = &si->screens[i];
-      if (!ssi->pid)
-       result = False;
+      if (ssi->pid) any_running_p = True;
     }
     }
-  return result;
+  return any_running_p;
 }
 
 \f
 }
 
 \f
@@ -953,12 +913,17 @@ hack_environment (saver_info *si)
 
       if (putenv (npath))
        abort ();
 
       if (putenv (npath))
        abort ();
+
+      /* don't free (npath) -- 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 && DEFAULT_PATH_PREFIX */
 }
 
 
     }
 #endif /* HAVE_PUTENV && DEFAULT_PATH_PREFIX */
 }
 
 
-static void
+void
 hack_subproc_environment (saver_screen_info *ssi)
 {
   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
 hack_subproc_environment (saver_screen_info *ssi)
 {
   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
@@ -969,17 +934,17 @@ hack_subproc_environment (saver_screen_info *ssi)
      be the screen on which this particular hack is running -- not the display
      specification which the driver itself is using, since the driver ignores
      its screen number and manages all existing screens.
      be the screen on which this particular hack is running -- not the display
      specification which the driver itself is using, since the driver ignores
      its screen number and manages all existing screens.
+
+     Likewise, store a window ID in $XSCREENSAVER_WINDOW -- this will allow
+     us to (eventually) run multiple hacks in Xinerama mode, where each hack
+     has the same $DISPLAY but a different piece of glass.
    */
   saver_info *si = ssi->global;
   const char *odpy = DisplayString (si->dpy);
    */
   saver_info *si = ssi->global;
   const char *odpy = DisplayString (si->dpy);
-  char *ndpy = (char *) malloc(strlen(odpy) + 20);
-  int screen_number;
+  char *ndpy = (char *) malloc (strlen(odpy) + 20);
+  char *nssw = (char *) malloc (40);
   char *s;
 
   char *s;
 
-  for (screen_number = 0; screen_number < si->nscreens; screen_number++)
-    if (ssi == &si->screens[screen_number])
-      break;
-
   strcpy (ndpy, "DISPLAY=");
   s = ndpy + strlen(ndpy);
   strcpy (s, odpy);
   strcpy (ndpy, "DISPLAY=");
   s = ndpy + strlen(ndpy);
   strcpy (s, odpy);
@@ -989,16 +954,141 @@ hack_subproc_environment (saver_screen_info *ssi)
   while (isdigit(*s)) s++;                     /* skip over dpy number */
   while (*s == '.') s++;                       /* skip over dot */
   if (s[-1] != '.') *s++ = '.';                        /* put on a dot */
   while (isdigit(*s)) s++;                     /* skip over dpy number */
   while (*s == '.') s++;                       /* skip over dot */
   if (s[-1] != '.') *s++ = '.';                        /* put on a dot */
-  sprintf(s, "%d", screen_number);             /* put on screen number */
+  sprintf(s, "%d", ssi->real_screen_number);   /* put on screen number */
+
+  sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX",
+           (unsigned long) ssi->screensaver_window);
 
   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
      any more, right?  It's not Posix, but everyone seems to have it. */
 #ifdef HAVE_PUTENV
   if (putenv (ndpy))
     abort ();
 
   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
      any more, right?  It's not Posix, but everyone seems to have it. */
 #ifdef HAVE_PUTENV
   if (putenv (ndpy))
     abort ();
+  if (putenv (nssw))
+    abort ();
+
+  /* don't free ndpy/nssw -- 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 */
 }
 
 #endif /* HAVE_PUTENV */
 }
 
+\f
+/* GL crap */
+
+Visual *
+get_best_gl_visual (saver_screen_info *ssi)
+{
+  saver_info *si = ssi->global;
+  pid_t forked;
+  int fds [2];
+  int in, out;
+  char buf[1024];
+
+  char *av[10];
+  int ac = 0;
+
+  av[ac++] = "xscreensaver-gl-helper";
+  av[ac] = 0;
+
+  if (pipe (fds))
+    {
+      perror ("error creating pipe:");
+      return 0;
+    }
+
+  in = fds [0];
+  out = fds [1];
+
+  switch ((int) (forked = fork ()))
+    {
+    case -1:
+      {
+        sprintf (buf, "%s: couldn't fork", blurb());
+        perror (buf);
+        saver_exit (si, 1, 0);
+      }
+    case 0:
+      {
+        int stdout_fd = 1;
+
+        close (in);  /* don't need this one */
+        close (ConnectionNumber (si->dpy));    /* close display fd */
+
+        if (dup2 (out, stdout_fd) < 0)         /* pipe stdout */
+          {
+            perror ("could not dup() a new stdout:");
+            return 0;
+          }
+        hack_subproc_environment (ssi);                /* set $DISPLAY */
+
+        execvp (av[0], av);                    /* shouldn't return. */
+
+        if (errno != ENOENT || si->prefs.verbose_p)
+          {
+            /* Ignore "no such file or directory" errors, unless verbose.
+               Issue all other exec errors, though. */
+            sprintf (buf, "%s: running %s", blurb(), av[0]);
+            perror (buf);
+          }
+        exit (1);                               /* exits fork */
+        break;
+      }
+    default:
+      {
+        int result = 0;
+        int wait_status = 0;
+
+        FILE *f = fdopen (in, "r");
+        unsigned long v = 0;
+        char c;
+
+        close (out);  /* don't need this one */
+
+        *buf = 0;
+        fgets (buf, sizeof(buf)-1, f);
+        fclose (f);
+
+        /* Wait for the child to die. */
+        waitpid (-1, &wait_status, 0);
+
+        if (1 == sscanf (buf, "0x%lx %c", &v, &c))
+          result = (int) v;
+
+        if (result == 0)
+          {
+            if (si->prefs.verbose_p)
+              {
+                int L = strlen(buf);
+                fprintf (stderr, "%s: %s did not report a GL visual!\n",
+                         blurb(), av[0]);
+
+                if (L && buf[L-1] == '\n')
+                  buf[--L] = 0;
+                if (*buf)
+                  fprintf (stderr, "%s: %s said: \"%s\"\n",
+                           blurb(), av[0], buf);
+              }
+            return 0;
+          }
+        else
+          {
+            Visual *v = id_to_visual (ssi->screen, result);
+            if (si->prefs.verbose_p)
+              fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s.\n",
+                       blurb(), ssi->number,
+                       av[0], result,
+                       (v == ssi->default_visual ? " (default)" : ""));
+            return v;
+          }
+      }
+    }
+
+  abort();
+}
+
+
 \f
 /* Restarting the xscreensaver process from scratch. */
 
 \f
 /* Restarting the xscreensaver process from scratch. */
 
@@ -1007,7 +1097,6 @@ static char **saved_argv;
 void
 save_argv (int argc, char **argv)
 {
 void
 save_argv (int argc, char **argv)
 {
-  /* Leave room for one more argument, the -initial-demo-mode switch. */
   saved_argv = (char **) calloc (argc+2, sizeof (char *));
   saved_argv [argc] = 0;
   while (argc--)
   saved_argv = (char **) calloc (argc+2, sizeof (char *));
   saved_argv [argc] = 0;
   while (argc--)
@@ -1018,76 +1107,34 @@ save_argv (int argc, char **argv)
     }
 }
 
     }
 }
 
-/* Modifies saved_argv to either contain or not contain "-initial-demo-mode".
+
+/* Re-execs the process with the arguments in saved_argv.  Does not return.
  */
  */
-static void
-hack_saved_argv (Bool demo_mode_p)
+void
+restart_process (saver_info *si)
 {
 {
-  static char *demo_mode_switch = "-initial-demo-mode";
-
-  if (demo_mode_p)             /* We want the switch to be in the args. */
-    {
-      /* See if the switch is there already.  If so, we're done. */
-      int i;
-      for (i = 0; saved_argv[i]; i++)
-       if (!strcmp (saved_argv[i], demo_mode_switch))
-         return;
-
-      /* If it wasn't there, add it to the end.  save_argv() made room. */
-      saved_argv [i] = demo_mode_switch;
-      saved_argv [i+1] = 0;
-    }
-  else                         /* We want the switch to not be in the args. */
+  fflush (stdout);
+  fflush (stderr);
+  shutdown_stderr (si);
+  if (si->prefs.verbose_p)
     {
       int i;
     {
       int i;
+      fprintf (stderr, "%s: re-executing", blurb());
       for (i = 0; saved_argv[i]; i++)
       for (i = 0; saved_argv[i]; i++)
-       while (!strcmp (saved_argv [i], demo_mode_switch))
-         {
-           int j;
-           for (j = i; saved_argv[j]; j++)
-             saved_argv [j] = saved_argv [j+1];
-         }
+       fprintf (stderr, " %s", saved_argv[i]);
+      fprintf (stderr, "\n");
     }
     }
-}
-
+  describe_uids (si, stderr);
+  fprintf (stderr, "\n");
 
 
-/* Re-execs the process with the arguments in saved_argv.
-   Does not return unless there was an error.
- */
-static void
-restart_process_1 (saver_info *si)
-{
-  fflush (real_stdout);
-  fflush (real_stderr);
+  fflush (stdout);
+  fflush (stderr);
   execvp (saved_argv [0], saved_argv); /* shouldn't return */
   {
     char buf [512];
     sprintf (buf, "%s: could not restart process", blurb());
     perror(buf);
     fflush(stderr);
   execvp (saved_argv [0], saved_argv); /* shouldn't return */
   {
     char buf [512];
     sprintf (buf, "%s: could not restart process", blurb());
     perror(buf);
     fflush(stderr);
+    abort();
   }
   }
-  XBell(si->dpy, 0);
-}
-
-
-/* Re-execs the process with the arguments in saved_argv,
-   minus -initial-demo-mode.
-   Does not return unless there was an error.
- */
-void
-restart_process (saver_info *si)
-{
-  hack_saved_argv (True);
-  restart_process_1 (si);
-}
-
-/* Re-execs the process with the arguments in saved_argv,
-   plus -initial-demo-mode.
-   Does not return unless there was an error.
- */
-void
-demo_mode_restart_process (saver_info *si)
-{
-  hack_saved_argv (False);
-  restart_process_1 (si);
 }
 }