began for maemo
[xscreensaver] / xscreensaver / driver / prefs.c
diff --git a/xscreensaver/driver/prefs.c b/xscreensaver/driver/prefs.c
new file mode 100644 (file)
index 0000000..c8e4ae1
--- /dev/null
@@ -0,0 +1,1636 @@
+/* dotfile.c --- management of the ~/.xscreensaver file.
+ * xscreensaver, Copyright (c) 1998-2006 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/param.h>   /* for PATH_MAX */
+
+#include <X11/Xlib.h>
+#include <X11/Xresource.h>
+
+#ifndef VMS
+# include <pwd.h>
+#else /* VMS */
+# include "vms-pwd.h"
+#endif /* VMS */
+
+
+/* This file doesn't need the Xt headers, so stub these types out... */
+#undef XtPointer
+#define XtAppContext void*
+#define XtIntervalId void*
+#define XtPointer    void*
+#define Widget       void*
+
+
+/* Just in case there's something pathological about stat.h... */
+#ifndef  S_IRUSR
+# define S_IRUSR 00400
+#endif
+#ifndef  S_IWUSR
+# define S_IWUSR 00200
+#endif
+#ifndef  S_IXUSR
+# define S_IXUSR 00100
+#endif
+#ifndef  S_IXGRP
+# define S_IXGRP 00010
+#endif
+#ifndef  S_IXOTH
+# define S_IXOTH 00001
+#endif
+
+
+#include "prefs.h"
+#include "resources.h"
+
+/* don't use realpath() on fedora system */
+#ifdef _FORTIFY_SOURCE
+#undef HAVE_REALPATH
+#endif
+
+
+extern char *progname;
+extern char *progclass;
+extern const char *blurb (void);
+
+
+
+static void get_screenhacks (Display *, saver_preferences *);
+static char *format_command (const char *cmd, Bool wrap_p);
+static void merge_system_screenhacks (Display *, saver_preferences *,
+                                      screenhack **system_list, int count);
+static void stop_the_insanity (saver_preferences *p);
+
+
+static char *
+chase_symlinks (const char *file)
+{
+# ifdef HAVE_REALPATH
+  if (file)
+    {
+# ifndef PATH_MAX
+#  ifdef MAXPATHLEN
+#   define PATH_MAX MAXPATHLEN
+#  else
+#   define PATH_MAX 2048
+#  endif
+# endif
+      char buf[PATH_MAX];
+      if (realpath (file, buf))
+        return strdup (buf);
+
+/*      sprintf (buf, "%.100s: realpath %.200s", blurb(), file);
+      perror(buf);*/
+    }
+# endif /* HAVE_REALPATH */
+  return 0;
+}
+
+
+static Bool
+i_am_a_nobody (uid_t uid)
+{
+  struct passwd *p;
+
+  p = getpwnam ("nobody");
+  if (! p) p = getpwnam ("noaccess");
+  if (! p) p = getpwnam ("daemon");
+
+  if (! p) /* There is no nobody? */
+    return False;
+
+  return (uid == p->pw_uid);
+}
+
+
+const char *
+init_file_name (void)
+{
+  static char *file = 0;
+
+  if (!file)
+    {
+      uid_t uid = getuid ();
+      struct passwd *p = getpwuid (uid);
+
+      if (i_am_a_nobody (uid))
+        /* If we're running as nobody, then use root's .xscreensaver file
+           (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely
+           to be different -- if we didn't do this, then xscreensaver-demo
+           would appear to have no effect when the luser is running as root.)
+         */
+        uid = 0;
+
+      p = getpwuid (uid);
+
+      if (!p || !p->pw_name || !*p->pw_name)
+       {
+         fprintf (stderr, "%s: couldn't get user info of uid %d\n",
+                  blurb(), getuid ());
+         file = "";
+       }
+      else if (!p->pw_dir || !*p->pw_dir)
+       {
+         fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
+                  blurb(), (p->pw_name ? p->pw_name : "???"));
+         file = "";
+       }
+      else
+       {
+         const char *home = p->pw_dir;
+         const char *name = ".xscreensaver";
+         file = (char *) malloc(strlen(home) + strlen(name) + 2);
+         strcpy(file, home);
+         if (!*home || home[strlen(home)-1] != '/')
+           strcat(file, "/");
+         strcat(file, name);
+       }
+    }
+
+  if (file && *file)
+    return file;
+  else
+    return 0;
+}
+
+
+static const char *
+init_file_tmp_name (void)
+{
+  static char *file = 0;
+  if (!file)
+    {
+      const char *name = init_file_name();
+      const char *suffix = ".tmp";
+
+      char *n2 = chase_symlinks (name);
+      if (n2) name = n2;
+
+      if (!name || !*name)
+       file = "";
+      else
+       {
+         file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
+         strcpy(file, name);
+         strcat(file, suffix);
+       }
+
+      if (n2) free (n2);
+    }
+
+  if (file && *file)
+    return file;
+  else
+    return 0;
+}
+
+static int
+get_byte_resource (Display *dpy, char *name, char *class)
+{
+  char *s = get_string_resource (dpy, name, class);
+  char *s2 = s;
+  int n = 0;
+  if (!s) return 0;
+
+  while (isspace(*s2)) s2++;
+  while (*s2 >= '0' && *s2 <= '9')
+    {
+      n = (n * 10) + (*s2 - '0');
+      s2++;
+    }
+  while (isspace(*s2)) s2++;
+  if      (*s2 == 'k' || *s2 == 'K') n <<= 10;
+  else if (*s2 == 'm' || *s2 == 'M') n <<= 20;
+  else if (*s2 == 'g' || *s2 == 'G') n <<= 30;
+  else if (*s2)
+    {
+    LOSE:
+      fprintf (stderr, "%s: %s must be a number of bytes, not \"%s\".\n",
+               progname, name, s);
+      free (s);
+      return 0;
+    }
+  s2++;
+  if (*s2 == 'b' || *s2 == 'B') s2++;
+  while (isspace(*s2)) s2++;
+  if (*s2) goto LOSE;
+
+  free (s);
+  return n;
+}
+
+
+static const char * const prefs[] = {
+  "timeout",
+  "cycle",
+  "lock",
+  "lockVTs",                   /* not saved */
+  "lockTimeout",
+  "passwdTimeout",
+  "visualID",
+  "installColormap",
+  "verbose",
+  "timestamp",
+  "splash",
+  "splashDuration",
+  "quad",
+  "demoCommand",
+  "prefsCommand",
+  "newLoginCommand",
+  "helpURL",                   /* not saved */
+  "loadURL",                   /* not saved */
+  "newLoginCommand",           /* not saved */
+  "nice",
+  "memoryLimit",
+  "fade",
+  "unfade",
+  "fadeSeconds",
+  "fadeTicks",
+  "captureStderr",
+  "captureStdout",             /* not saved -- obsolete */
+  "ignoreUninstalledPrograms",
+  "font",
+  "dpmsEnabled",
+  "dpmsStandby",
+  "dpmsSuspend",
+  "dpmsOff",
+  "grabDesktopImages",
+  "grabVideoFrames",
+  "chooseRandomImages",
+  "imageDirectory",
+  "mode",
+  "selected",
+  "textMode",
+  "textLiteral",
+  "textFile",
+  "textProgram",
+  "textURL",
+  "",
+  "programs",
+  "",
+  "pointerPollTime",
+  "pointerHysteresis",
+  "windowCreationTimeout",
+  "initialDelay",
+  "sgiSaverExtension",
+  "mitSaverExtension",         /* not saved -- obsolete */
+  "xidleExtension",
+  "GetViewPortIsFullOfLies",
+  "procInterrupts",
+  "overlayStderr",
+  "overlayTextBackground",     /* not saved -- X resources only */
+  "overlayTextForeground",     /* not saved -- X resources only */
+  "bourneShell",               /* not saved -- X resources only */
+  0
+};
+
+static char *
+strip (char *s)
+{
+  char *s2;
+  while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
+    s++;
+  for (s2 = s; *s2; s2++)
+    ;
+  for (s2--; s2 >= s; s2--) 
+    if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n') 
+      *s2 = 0;
+    else
+      break;
+  return s;
+}
+
+\f
+/* Reading
+ */
+
+static int
+handle_entry (XrmDatabase *db, const char *key, const char *value,
+             const char *filename, int line)
+{
+  int i;
+  for (i = 0; prefs[i]; i++)
+    if (*prefs[i] && !strcasecmp(key, prefs[i]))
+      {
+       char *val = strdup(value);
+       char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
+       strcpy(spec, progclass);
+       strcat(spec, ".");
+       strcat(spec, prefs[i]);
+
+       XrmPutStringResource (db, spec, val);
+
+       free(spec);
+       free(val);
+       return 0;
+      }
+
+  fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
+         blurb(), filename, line, key);
+  return 1;
+}
+
+
+static int
+parse_init_file (saver_preferences *p)
+{
+  time_t write_date = 0;
+  const char *name = init_file_name();
+  int line = 0;
+  struct stat st;
+  FILE *in;
+  int buf_size = 1024;
+  char *buf;
+
+  if (!name) return 0;
+
+  if (stat(name, &st) != 0)
+    {
+      p->init_file_date = 0;
+      return 0;
+    }
+
+  in = fopen(name, "r");
+  if (!in)
+    {
+      char *buf = (char *) malloc(1024 + strlen(name));
+      sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
+      perror(buf);
+      free(buf);
+      return -1;
+    }
+
+  if (fstat (fileno(in), &st) == 0)
+    {
+      write_date = st.st_mtime;
+    }
+  else
+    {
+      char *buf = (char *) malloc(1024 + strlen(name));
+      sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
+      perror(buf);
+      free(buf);
+      return -1;
+    }
+
+  buf = (char *) malloc(buf_size);
+
+  while (fgets (buf, buf_size-1, in))
+    {
+      char *key, *value;
+      int L = strlen(buf);
+
+      line++;
+      while (L > 2 &&
+            (buf[L-1] != '\n' ||       /* whole line didn't fit in buffer */
+             buf[L-2] == '\\'))        /* or line ended with backslash */
+       {
+         if (buf[L-2] == '\\')         /* backslash-newline gets swallowed */
+           {
+             buf[L-2] = 0;
+             L -= 2;
+           }
+         buf_size += 1024;
+         buf = (char *) realloc(buf, buf_size);
+         if (!buf) exit(1);
+
+         line++;
+         if (!fgets (buf + L, buf_size-L-1, in))
+           break;
+         L = strlen(buf);
+       }
+
+      /* Now handle other backslash escapes. */
+      {
+       int i, j;
+       for (i = 0; buf[i]; i++)
+         if (buf[i] == '\\')
+           {
+             switch (buf[i+1])
+               {
+               case 'n': buf[i] = '\n'; break;
+               case 'r': buf[i] = '\r'; break;
+               case 't': buf[i] = '\t'; break;
+               default:  buf[i] = buf[i+1]; break;
+               }
+             for (j = i+2; buf[j]; j++)
+               buf[j-1] = buf[j];
+             buf[j-1] = 0;
+           }
+      }
+
+      key = strip(buf);
+
+      if (*key == '#' || *key == '!' || *key == ';' ||
+         *key == '\n' || *key == 0)
+       continue;
+
+      value = strchr (key, ':');
+      if (!value)
+       {
+         fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
+                 name, line, key);
+         continue;
+       }
+      else
+       {
+         *value++ = 0;
+         value = strip(value);
+       }
+
+      if (!p->db) abort();
+      handle_entry (&p->db, key, value, name, line);
+    }
+  fclose (in);
+  free(buf);
+
+  p->init_file_date = write_date;
+  return 0;
+}
+
+
+Bool
+init_file_changed_p (saver_preferences *p)
+{
+  const char *name = init_file_name();
+  struct stat st;
+
+  if (!name) return False;
+
+  if (stat(name, &st) != 0)
+    return False;
+
+  if (p->init_file_date == st.st_mtime)
+    return False;
+
+  return True;
+}
+
+\f
+/* Writing
+ */
+
+static int
+tab_to (FILE *out, int from, int to)
+{
+  int tab_width = 8;
+  int to_mod = (to / tab_width) * tab_width;
+  while (from < to_mod)
+    {
+      fprintf(out, "\t");
+      from = (((from / tab_width) + 1) * tab_width);
+    }
+  while (from < to)
+    {
+      fprintf(out, " ");
+      from++;
+    }
+  return from;
+}
+
+static char *
+stab_to (char *out, int from, int to)
+{
+  int tab_width = 8;
+  int to_mod = (to / tab_width) * tab_width;
+  while (from < to_mod)
+    {
+      *out++ = '\t';
+      from = (((from / tab_width) + 1) * tab_width);
+    }
+  while (from < to)
+    {
+      *out++ = ' ';
+      from++;
+    }
+  return out;
+}
+
+static int
+string_columns (const char *string, int length, int start)
+{
+  int tab_width = 8;
+  int col = start;
+  const char *end = string + length;
+  while (string < end)
+    {
+      if (*string == '\n')
+        col = 0;
+      else if (*string == '\t')
+        col = (((col / tab_width) + 1) * tab_width);
+      else
+        col++;
+      string++;
+    }
+  return col;
+}
+
+
+static void
+write_entry (FILE *out, const char *key, const char *value)
+{
+  char *v = strdup(value ? value : "");
+  char *v2 = v;
+  char *nl = 0;
+  int col;
+  Bool programs_p = (!strcmp(key, "programs"));
+  int tab = (programs_p ? 32 : 16);
+  Bool first = True;
+
+  fprintf(out, "%s:", key);
+  col = strlen(key) + 1;
+
+  if (strlen(key) > 14)
+    col = tab_to (out, col, 20);
+
+  while (1)
+    {
+      if (!programs_p)
+        v2 = strip(v2);
+      nl = strchr(v2, '\n');
+      if (nl)
+       *nl = 0;
+
+      if (first && programs_p)
+        {
+         col = tab_to (out, col, 77);
+         fprintf (out, " \\\n");
+         col = 0;
+        }
+
+      if (first)
+       first = False;
+      else
+       {
+         col = tab_to (out, col, 75);
+         fprintf (out, " \\n\\\n");
+         col = 0;
+       }
+
+      if (!programs_p)
+        col = tab_to (out, col, tab);
+
+      if (programs_p &&
+         string_columns(v2, strlen (v2), col) + col > 75)
+       {
+         int L = strlen (v2);
+         int start = 0;
+         int end = start;
+         while (start < L)
+           {
+             while (v2[end] == ' ' || v2[end] == '\t')
+               end++;
+             while (v2[end] != ' ' && v2[end] != '\t' &&
+                    v2[end] != '\n' && v2[end] != 0)
+               end++;
+             if (string_columns (v2 + start, (end - start), col) >= 74)
+               {
+                 col = tab_to (out, col, 75);
+                 fprintf(out, "   \\\n");
+                 col = tab_to (out, 0, tab + 2);
+                 while (v2[start] == ' ' || v2[start] == '\t')
+                   start++;
+               }
+
+              col = string_columns (v2 + start, (end - start), col);
+             while (start < end)
+                fputc(v2[start++], out);
+           }
+       }
+      else
+       {
+         fprintf (out, "%s", v2);
+         col += string_columns(v2, strlen (v2), col);
+       }
+
+      if (nl)
+       v2 = nl + 1;
+      else
+       break;
+    }
+
+  fprintf(out, "\n");
+  free(v);
+}
+
+int
+write_init_file (Display *dpy,
+                 saver_preferences *p, const char *version_string,
+                 Bool verbose_p)
+{
+  int status = -1;
+  const char *name = init_file_name();
+  const char *tmp_name = init_file_tmp_name();
+  char *n2 = chase_symlinks (name);
+  struct stat st;
+  int i, j;
+
+  /* Kludge, since these aren't in the saver_preferences struct as strings...
+   */
+  char *visual_name;
+  char *programs;
+  Bool overlay_stderr_p;
+  char *stderr_font;
+  FILE *out;
+
+  if (!name) goto END;
+
+  if (n2) name = n2;
+
+  /* Throttle the various timeouts to reasonable values before writing
+     the file to disk. */
+  stop_the_insanity (p);
+
+
+  if (verbose_p)
+    fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
+
+  unlink (tmp_name);
+  out = fopen(tmp_name, "w");
+  if (!out)
+    {
+      char *buf = (char *) malloc(1024 + strlen(name));
+      sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
+      perror(buf);
+      free(buf);
+      goto END;
+    }
+
+  /* Give the new .xscreensaver file the same permissions as the old one;
+     except ensure that it is readable and writable by owner, and not
+     executable.  Extra hack: if we're running as root, make the file
+     be world-readable (so that the daemon, running as "nobody", will
+     still be able to read it.)
+   */
+  if (stat(name, &st) == 0)
+    {
+      mode_t mode = st.st_mode;
+      mode |= S_IRUSR | S_IWUSR;               /* read/write by user */
+      mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);  /* executable by none */
+
+      if (getuid() == (uid_t) 0)               /* read by group/other */
+        mode |= S_IRGRP | S_IROTH;
+
+      if (fchmod (fileno(out), mode) != 0)
+       {
+         char *buf = (char *) malloc(1024 + strlen(name));
+         sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
+                  tmp_name, (unsigned int) mode);
+         perror(buf);
+         free(buf);
+         goto END;
+       }
+    }
+
+  /* Kludge, since these aren't in the saver_preferences struct... */
+  visual_name = get_string_resource (dpy, "visualID", "VisualID");
+  programs = 0;
+  overlay_stderr_p = get_boolean_resource (dpy, "overlayStderr", "Boolean");
+  stderr_font = get_string_resource (dpy, "font", "Font");
+
+  i = 0;
+  {
+    char *ss;
+    char **hack_strings = (char **)
+      calloc (p->screenhacks_count, sizeof(char *));
+
+    for (j = 0; j < p->screenhacks_count; j++)
+      {
+        hack_strings[j] = format_hack (dpy, p->screenhacks[j], True);
+        i += strlen (hack_strings[j]);
+        i += 2;
+      }
+
+    ss = programs = (char *) malloc(i + 10);
+    *ss = 0;
+    for (j = 0; j < p->screenhacks_count; j++)
+      {
+        strcat (ss, hack_strings[j]);
+        free (hack_strings[j]);
+       ss += strlen(ss);
+       *ss++ = '\n';
+       *ss = 0;
+      }
+    free (hack_strings);
+  }
+
+  {
+    struct passwd *pw = getpwuid (getuid ());
+    char *whoami = (pw && pw->pw_name && *pw->pw_name
+                   ? pw->pw_name
+                   : "<unknown>");
+    time_t now = time ((time_t *) 0);
+    char *timestr = (char *) ctime (&now);
+    char *nl = (char *) strchr (timestr, '\n');
+    if (nl) *nl = 0;
+    fprintf (out,
+            "# %s Preferences File\n"
+            "# Written by %s %s for %s on %s.\n"
+            "# http://www.jwz.org/xscreensaver/\n"
+            "\n",
+            progclass, progname, version_string, whoami, timestr);
+  }
+
+  for (j = 0; prefs[j]; j++)
+    {
+      char buf[255];
+      const char *pr = prefs[j];
+      enum pref_type { pref_str, pref_int, pref_bool, pref_byte, pref_time
+      } type = pref_str;
+      const char *s = 0;
+      int i = 0;
+      Bool b = False;
+      Time t = 0;
+
+      if (pr && !*pr)
+       {
+         fprintf(out, "\n");
+         continue;
+       }
+
+# undef CHECK
+# define CHECK(X) else if (!strcmp(pr, X))
+      if (!pr || !*pr)         ;
+      CHECK("timeout")         type = pref_time, t = p->timeout;
+      CHECK("cycle")           type = pref_time, t = p->cycle;
+      CHECK("lock")            type = pref_bool, b = p->lock_p;
+      CHECK("lockVTs")         continue;  /* don't save, unused */
+      CHECK("lockTimeout")     type = pref_time, t = p->lock_timeout;
+      CHECK("passwdTimeout")   type = pref_time, t = p->passwd_timeout;
+      CHECK("visualID")                type = pref_str,  s =    visual_name;
+      CHECK("installColormap") type = pref_bool, b = p->install_cmap_p;
+      CHECK("verbose")         type = pref_bool, b = p->verbose_p;
+      CHECK("timestamp")       type = pref_bool, b = p->timestamp_p;
+      CHECK("splash")          type = pref_bool, b = p->splash_p;
+      CHECK("splashDuration")  type = pref_time, t = p->splash_duration;
+# ifdef QUAD_MODE
+      CHECK("quad")            type = pref_bool, b = p->quad_p;
+# else  /* !QUAD_MODE */
+      CHECK("quad")            continue;  /* don't save */
+# endif /* !QUAD_MODE */
+      CHECK("demoCommand")     type = pref_str,  s = p->demo_command;
+      CHECK("prefsCommand")    type = pref_str,  s = p->prefs_command;
+/*    CHECK("helpURL")         type = pref_str,  s = p->help_url; */
+      CHECK("helpURL")         continue;  /* don't save */
+/*    CHECK("loadURL")         type = pref_str,  s = p->load_url_command; */
+      CHECK("loadURL")         continue;  /* don't save */
+/*    CHECK("newLoginCommand") type = pref_str,  s = p->new_login_command; */
+      CHECK("newLoginCommand") continue;  /* don't save */
+      CHECK("nice")            type = pref_int,  i = p->nice_inferior;
+      CHECK("memoryLimit")     type = pref_byte, i = p->inferior_memory_limit;
+      CHECK("fade")            type = pref_bool, b = p->fade_p;
+      CHECK("unfade")          type = pref_bool, b = p->unfade_p;
+      CHECK("fadeSeconds")     type = pref_time, t = p->fade_seconds;
+      CHECK("fadeTicks")       type = pref_int,  i = p->fade_ticks;
+      CHECK("captureStderr")   type = pref_bool, b = p->capture_stderr_p;
+      CHECK("captureStdout")   continue;  /* don't save */
+      CHECK("ignoreUninstalledPrograms")
+                                type = pref_bool, b = p->ignore_uninstalled_p;
+
+      CHECK("font")            type = pref_str,  s =    stderr_font;
+
+      CHECK("dpmsEnabled")     type = pref_bool, b = p->dpms_enabled_p;
+      CHECK("dpmsStandby")     type = pref_time, t = p->dpms_standby;
+      CHECK("dpmsSuspend")     type = pref_time, t = p->dpms_suspend;
+      CHECK("dpmsOff")         type = pref_time, t = p->dpms_off;
+
+      CHECK("grabDesktopImages") type =pref_bool, b = p->grab_desktop_p;
+      CHECK("grabVideoFrames")   type =pref_bool, b = p->grab_video_p;
+      CHECK("chooseRandomImages")type =pref_bool, b = p->random_image_p;
+      CHECK("imageDirectory")    type =pref_str,  s = p->image_directory;
+
+      CHECK("mode")             type = pref_str,
+                                s = (p->mode == ONE_HACK ? "one" :
+                                     p->mode == BLANK_ONLY ? "blank" :
+                                     p->mode == DONT_BLANK ? "off" :
+                                     p->mode == RANDOM_HACKS_SAME
+                                     ? "random-same"
+                                     : "random");
+      CHECK("selected")         type = pref_int,  i = p->selected_hack;
+
+      CHECK("textMode")         type = pref_str,
+                                s = (p->tmode == TEXT_URL     ? "url" :
+                                     p->tmode == TEXT_LITERAL ? "literal" :
+                                     p->tmode == TEXT_FILE    ? "file" :
+                                     p->tmode == TEXT_PROGRAM ? "program" :
+                                     "date");
+      CHECK("textLiteral")      type = pref_str,  s = p->text_literal;
+      CHECK("textFile")         type = pref_str,  s = p->text_file;
+      CHECK("textProgram")      type = pref_str,  s = p->text_program;
+      CHECK("textURL")          type = pref_str,  s = p->text_url;
+
+      CHECK("programs")                type = pref_str,  s =    programs;
+      CHECK("pointerPollTime") type = pref_time, t = p->pointer_timeout;
+      CHECK("pointerHysteresis")type = pref_int,  i = p->pointer_hysteresis;
+      CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
+      CHECK("initialDelay")    type = pref_time, t = p->initial_delay;
+      CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension;
+      CHECK("mitSaverExtension") continue;  /* don't save */
+      CHECK("xidleExtension")  type = pref_bool, b = p->use_xidle_extension;
+      CHECK("procInterrupts")  type = pref_bool, b = p->use_proc_interrupts;
+      CHECK("GetViewPortIsFullOfLies")  type = pref_bool,
+                                       b = p->getviewport_full_of_lies_p;
+      CHECK("overlayStderr")   type = pref_bool, b = overlay_stderr_p;
+      CHECK("overlayTextBackground") continue;  /* don't save */
+      CHECK("overlayTextForeground") continue;  /* don't save */
+      CHECK("bourneShell")     continue;
+      else                     abort();
+# undef CHECK
+
+      switch (type)
+       {
+       case pref_str:
+         break;
+       case pref_int:
+         sprintf(buf, "%d", i);
+         s = buf;
+         break;
+       case pref_bool:
+         s = b ? "True" : "False";
+         break;
+       case pref_time:
+         {
+           unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
+           if (sec >= 60)
+             {
+               min += (sec / 60);
+               sec %= 60;
+             }
+           if (min >= 60)
+             {
+               hour += (min / 60);
+               min %= 60;
+             }
+           sprintf (buf, "%u:%02u:%02u", hour, min, sec);
+           s = buf;
+         }
+         break;
+       case pref_byte:
+         {
+            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", i);
+            s = buf;
+          }
+         break;
+       default:
+         abort();
+         break;
+       }
+
+      if (pr && (!strcmp(pr, "mode") || !strcmp(pr, "textMode")))
+        fprintf(out, "\n");
+
+      write_entry (out, pr, s);
+    }
+
+  fprintf(out, "\n");
+
+  if (visual_name) free(visual_name);
+  if (stderr_font) free(stderr_font);
+  if (programs) free(programs);
+
+  if (fclose(out) == 0)
+    {
+      time_t write_date = 0;
+
+      if (stat(tmp_name, &st) == 0)
+       {
+         write_date = st.st_mtime;
+       }
+      else
+       {
+         char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
+         sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
+         perror(buf);
+         unlink (tmp_name);
+         free(buf);
+         goto END;
+       }
+
+      if (rename (tmp_name, name) != 0)
+       {
+         char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
+         sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
+                 blurb(), tmp_name, name);
+         perror(buf);
+         unlink (tmp_name);
+         free(buf);
+         goto END;
+       }
+      else
+       {
+         p->init_file_date = write_date;
+
+         /* Since the .xscreensaver file is used for IPC, let's try and make
+            sure that the bits actually land on the disk right away. */
+         sync ();
+
+          status = 0;    /* wrote and renamed successfully! */
+       }
+    }
+  else
+    {
+      char *buf = (char *) malloc(1024 + strlen(name));
+      sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
+      perror(buf);
+      free(buf);
+      unlink (tmp_name);
+      goto END;
+    }
+
+ END:
+  if (n2) free (n2);
+  return status;
+}
+
+\f
+/* Parsing the resource database
+ */
+
+void
+free_screenhack (screenhack *hack)
+{
+  if (hack->visual) free (hack->visual);
+  if (hack->name) free (hack->name);
+  free (hack->command);
+  memset (hack, 0, sizeof(*hack));
+  free (hack);
+}
+
+static void
+free_screenhack_list (screenhack **list, int count)
+{
+  int i;
+  if (!list) return;
+  for (i = 0; i < count; i++)
+    if (list[i])
+      free_screenhack (list[i]);
+  free (list);
+}
+
+
+
+/* Populate `saver_preferences' with the contents of the resource database.
+   Note that this may be called multiple times -- it is re-run each time
+   the ~/.xscreensaver file is reloaded.
+
+   This function can be very noisy, since it issues resource syntax errors
+   and so on.
+ */
+void
+load_init_file (Display *dpy, saver_preferences *p)
+{
+  static Bool first_time = True;
+  
+  screenhack **system_default_screenhacks = 0;
+  int system_default_screenhack_count = 0;
+
+  if (first_time)
+    {
+      /* Get the programs resource before the .xscreensaver file has been
+         parsed and merged into the resource database for the first time:
+         this is the value of *programs from the app-defaults file.
+         Then clear it out so that it will be parsed again later, after
+         the init file has been read.
+       */
+      get_screenhacks (dpy, p);
+      system_default_screenhacks = p->screenhacks;
+      system_default_screenhack_count = p->screenhacks_count;
+      p->screenhacks = 0;
+      p->screenhacks_count = 0;
+    }
+
+  if (parse_init_file (p) != 0)                /* file might have gone away */
+    if (!first_time) return;
+
+  first_time = False;
+
+  p->xsync_p       = get_boolean_resource (dpy, "synchronous", "Synchronous");
+  p->verbose_p     = get_boolean_resource (dpy, "verbose", "Boolean");
+  p->timestamp_p    = get_boolean_resource (dpy, "timestamp", "Boolean");
+  p->lock_p        = get_boolean_resource (dpy, "lock", "Boolean");
+  p->fade_p        = get_boolean_resource (dpy, "fade", "Boolean");
+  p->unfade_p      = get_boolean_resource (dpy, "unfade", "Boolean");
+  p->fade_seconds   = 1000 * get_seconds_resource (dpy, "fadeSeconds", "Time");
+  p->fade_ticks            = get_integer_resource (dpy, "fadeTicks", "Integer");
+  p->install_cmap_p = get_boolean_resource (dpy, "installColormap", "Boolean");
+  p->nice_inferior  = get_integer_resource (dpy, "nice", "Nice");
+  p->inferior_memory_limit = get_byte_resource (dpy, "memoryLimit",
+                                                "MemoryLimit");
+  p->splash_p       = get_boolean_resource (dpy, "splash", "Boolean");
+# ifdef QUAD_MODE
+  p->quad_p         = get_boolean_resource (dpy, "quad", "Boolean");
+# endif
+  p->capture_stderr_p = get_boolean_resource (dpy, "captureStderr", "Boolean");
+  p->ignore_uninstalled_p = get_boolean_resource (dpy, 
+                                                  "ignoreUninstalledPrograms",
+                                                  "Boolean");
+
+  p->initial_delay   = 1000 * get_seconds_resource (dpy, "initialDelay", "Time");
+  p->splash_duration = 1000 * get_seconds_resource (dpy, "splashDuration", "Time");
+  p->timeout         = 1000 * get_minutes_resource (dpy, "timeout", "Time");
+  p->lock_timeout    = 1000 * get_minutes_resource (dpy, "lockTimeout", "Time");
+  p->cycle           = 1000 * get_minutes_resource (dpy, "cycle", "Time");
+  p->passwd_timeout  = 1000 * get_seconds_resource (dpy, "passwdTimeout", "Time");
+  p->pointer_timeout = 1000 * get_seconds_resource (dpy, "pointerPollTime", "Time");
+  p->pointer_hysteresis = get_integer_resource (dpy, "pointerHysteresis","Integer");
+  p->notice_events_timeout = 1000*get_seconds_resource(dpy,
+                                                       "windowCreationTimeout",
+                                                      "Time");
+
+  p->dpms_enabled_p  = get_boolean_resource (dpy, "dpmsEnabled", "Boolean");
+  p->dpms_standby    = 1000 * get_minutes_resource (dpy, "dpmsStandby", "Time");
+  p->dpms_suspend    = 1000 * get_minutes_resource (dpy, "dpmsSuspend", "Time");
+  p->dpms_off        = 1000 * get_minutes_resource (dpy, "dpmsOff",     "Time");
+
+  p->grab_desktop_p  = get_boolean_resource (dpy, "grabDesktopImages",  "Boolean");
+  p->grab_video_p    = get_boolean_resource (dpy, "grabVideoFrames",    "Boolean");
+  p->random_image_p  = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
+  p->image_directory = get_string_resource  (dpy,
+                                             "imageDirectory",
+                                             "ImageDirectory");
+
+  p->text_literal = get_string_resource (dpy, "textLiteral", "TextLiteral");
+  p->text_file    = get_string_resource (dpy, "textFile",    "TextFile");
+  p->text_program = get_string_resource (dpy, "textProgram", "TextProgram");
+  p->text_url     = get_string_resource (dpy, "textURL",     "TextURL");
+
+  p->shell = get_string_resource (dpy, "bourneShell", "BourneShell");
+
+  p->demo_command = get_string_resource(dpy, "demoCommand", "URL");
+  p->prefs_command = get_string_resource(dpy, "prefsCommand", "URL");
+  p->help_url = get_string_resource(dpy, "helpURL", "URL");
+  p->load_url_command = get_string_resource(dpy, "loadURL", "LoadURL");
+  p->new_login_command = get_string_resource(dpy,
+                                             "newLoginCommand",
+                                             "NewLoginCommand");
+
+  /* If "*splash" is unset, default to true. */
+  {
+    char *s = get_string_resource (dpy, "splash", "Boolean");
+    if (s)
+      free (s);
+    else
+      p->splash_p = True;
+  }
+
+  /* If "*grabDesktopImages" is unset, default to true. */
+  {
+    char *s = get_string_resource (dpy, "grabDesktopImages", "Boolean");
+    if (s)
+      free (s);
+    else
+      p->grab_desktop_p = True;
+  }
+
+  p->use_xidle_extension = get_boolean_resource (dpy, "xidleExtension","Boolean");
+#if 0 /* ignore this, it is evil. */
+  p->use_mit_saver_extension = get_boolean_resource (dpy, 
+                                                     "mitSaverExtension",
+                                                    "Boolean");
+#endif
+  p->use_sgi_saver_extension = get_boolean_resource (dpy,
+                                                     "sgiSaverExtension",
+                                                    "Boolean");
+  p->use_proc_interrupts = get_boolean_resource (dpy,
+                                                 "procInterrupts", "Boolean");
+
+  p->getviewport_full_of_lies_p =
+    get_boolean_resource (dpy, "GetViewPortIsFullOfLies", "Boolean");
+
+  get_screenhacks (dpy, p);             /* Parse the "programs" resource. */
+
+  {
+    char *s = get_string_resource (dpy, "selected", "Integer");
+    if (!s || !*s)
+      p->selected_hack = -1;
+    else
+      p->selected_hack = get_integer_resource (dpy, "selected", "Integer");
+    if (s) free (s);
+    if (p->selected_hack < 0 || p->selected_hack >= p->screenhacks_count)
+      p->selected_hack = -1;
+  }
+
+  {
+    char *s = get_string_resource (dpy, "mode", "Mode");
+    if      (s && !strcasecmp (s, "one"))         p->mode = ONE_HACK;
+    else if (s && !strcasecmp (s, "blank"))       p->mode = BLANK_ONLY;
+    else if (s && !strcasecmp (s, "off"))         p->mode = DONT_BLANK;
+    else if (s && !strcasecmp (s, "random-same")) p->mode = RANDOM_HACKS_SAME;
+    else                                          p->mode = RANDOM_HACKS;
+    if (s) free (s);
+  }
+
+  {
+    char *s = get_string_resource (dpy, "textMode", "TextMode");
+    if      (s && !strcasecmp (s, "url"))         p->tmode = TEXT_URL;
+    else if (s && !strcasecmp (s, "literal"))     p->tmode = TEXT_LITERAL;
+    else if (s && !strcasecmp (s, "file"))        p->tmode = TEXT_FILE;
+    else if (s && !strcasecmp (s, "program"))     p->tmode = TEXT_PROGRAM;
+    else                                          p->tmode = TEXT_DATE;
+    if (s) free (s);
+  }
+
+  if (system_default_screenhack_count)  /* note: first_time is also true */
+    {
+      merge_system_screenhacks (dpy, p, system_default_screenhacks,
+                                system_default_screenhack_count);
+      free_screenhack_list (system_default_screenhacks,
+                            system_default_screenhack_count);
+      system_default_screenhacks = 0;
+      system_default_screenhack_count = 0;
+    }
+
+  if (p->debug_p)
+    {
+      p->xsync_p = True;
+      p->verbose_p = True;
+      p->timestamp_p = True;
+      p->initial_delay = 0;
+    }
+
+  /* Throttle the various timeouts to reasonable values after reading the
+     disk file. */
+  stop_the_insanity (p);
+}
+
+
+/* If there are any hacks in the system-wide defaults that are not in
+   the ~/.xscreensaver file, add the new ones to the end of the list.
+   This does *not* actually save the file.
+ */
+static void
+merge_system_screenhacks (Display *dpy, saver_preferences *p,
+                          screenhack **system_list, int system_count)
+{
+  /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
+     so fuck it. */
+
+  int made_space = 0;
+  int i;
+  for (i = 0; i < system_count; i++)
+    {
+      int j;
+      Bool matched_p = False;
+
+      for (j = 0; j < p->screenhacks_count; j++)
+        {
+          char *name;
+          if (!system_list[i]->name)
+            system_list[i]->name = make_hack_name (dpy, 
+                                                   system_list[i]->command);
+
+          name = p->screenhacks[j]->name;
+          if (!name)
+            name = make_hack_name (dpy, p->screenhacks[j]->command);
+
+          matched_p = !strcasecmp (name, system_list[i]->name);
+
+          if (name != p->screenhacks[j]->name)
+            free (name);
+
+          if (matched_p)
+            break;
+        }
+
+      if (!matched_p)
+        {
+          /* We have an entry in the system-wide list that is not in the
+             user's .xscreensaver file.  Add it to the end.
+             Note that p->screenhacks is a single malloc block, not a
+             linked list, so we have to realloc it.
+           */
+          screenhack *oh = system_list[i];
+          screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
+
+          if (made_space == 0)
+            {
+              made_space = 10;
+              p->screenhacks = (screenhack **)
+                realloc (p->screenhacks,
+                         (p->screenhacks_count + made_space + 1)
+                         * sizeof(screenhack));
+              if (!p->screenhacks) abort();
+            }
+
+          nh->enabled_p = oh->enabled_p;
+          nh->visual    = oh->visual  ? strdup(oh->visual)  : 0;
+          nh->name      = oh->name    ? strdup(oh->name)    : 0;
+          nh->command   = oh->command ? strdup(oh->command) : 0;
+
+          p->screenhacks[p->screenhacks_count++] = nh;
+          p->screenhacks[p->screenhacks_count] = 0;
+          made_space--;
+
+#if 0
+          fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
+                   (nh->name ? nh->name : make_hack_name (dpy, nh->command)));
+#endif
+        }
+    }
+}
+
+
+\f
+/* Parsing the programs resource.
+ */
+
+screenhack *
+parse_screenhack (const char *line)
+{
+  screenhack *h = (screenhack *) calloc (1, sizeof(*h));
+  const char *s;
+
+  h->enabled_p = True;
+
+  while (isspace(*line)) line++;               /* skip whitespace */
+  if (*line == '-')                            /* handle "-" */
+    {
+      h->enabled_p = False;
+      line++;
+      while (isspace(*line)) line++;           /* skip whitespace */
+    }
+
+  s = line;                                    /* handle "visual:" */
+  while (*line && *line != ':' && *line != '"' && !isspace(*line))
+    line++;
+  if (*line != ':')
+    line = s;
+  else
+    {
+      h->visual = (char *) malloc (line-s+1);
+      strncpy (h->visual, s, line-s);
+      h->visual[line-s] = 0;
+      if (*line == ':') line++;                        /* skip ":" */
+      while (isspace(*line)) line++;           /* skip whitespace */
+    }
+
+  if (*line == '"')                            /* handle "name" */
+    {
+      line++;
+      s = line;
+      while (*line && *line != '"')
+        line++;
+      h->name = (char *) malloc (line-s+1);
+      strncpy (h->name, s, line-s);
+      h->name[line-s] = 0;
+      if (*line == '"') line++;                        /* skip "\"" */
+      while (isspace(*line)) line++;           /* skip whitespace */
+    }
+
+  h->command = format_command (line, False);   /* handle command */
+  return h;
+}
+
+
+static char *
+format_command (const char *cmd, Bool wrap_p)
+{
+  int tab = 30;
+  int col = tab;
+  char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
+  const char *in = cmd;
+  char *out = cmd2;
+  while (*in)
+    {
+      /* shrink all whitespace to one space, for the benefit of the "demo"
+        mode display.  We only do this when we can easily tell that the
+        whitespace is not significant (no shell metachars).
+       */
+      switch (*in)
+       {
+       case '\'': case '"': case '`': case '\\':
+          /* Metachars are scary.  Copy the rest of the line unchanged. */
+          while (*in)
+            *out++ = *in++, col++;
+         break;
+
+       case ' ': case '\t':
+          /* Squeeze all other whitespace down to one space. */
+          while (*in == ' ' || *in == '\t')
+            in++;
+          *out++ = ' ', col++;
+         break;
+
+       default:
+          /* Copy other chars unchanged. */
+         *out++ = *in++, col++;
+         break;
+       }
+    }
+
+  *out = 0;
+
+  /* Strip trailing whitespace */
+  while (out > cmd2 && isspace (out[-1]))
+    *(--out) = 0;
+
+  return cmd2;
+}
+
+
+/* Returns a new string describing the shell command.
+   This may be just the name of the program, capitalized.
+   It also may be something from the resource database (gotten
+   by looking for "hacks.XYZ.name", where XYZ is the program.)
+ */
+char *
+make_hack_name (Display *dpy, const char *shell_command)
+{
+  char *s = strdup (shell_command);
+  char *s2;
+  char res_name[255];
+
+  for (s2 = s; *s2; s2++)      /* truncate at first whitespace */
+    if (isspace (*s2))
+      {
+        *s2 = 0;
+        break;
+      }
+
+  s2 = strrchr (s, '/');       /* if pathname, take last component */
+  if (s2)
+    {
+      s2 = strdup (s2+1);
+      free (s);
+      s = s2;
+    }
+
+  if (strlen (s) > 50)         /* 51 is hereby defined as "unreasonable" */
+    s[50] = 0;
+
+  sprintf (res_name, "hacks.%s.name", s);              /* resource? */
+  s2 = get_string_resource (dpy, res_name, res_name);
+  if (s2)
+    {
+      free (s);
+      return s2;
+    }
+
+  for (s2 = s; *s2; s2++)      /* if it has any capitals, return it */
+    if (*s2 >= 'A' && *s2 <= 'Z')
+      return s;
+
+  if (s[0] >= 'a' && s[0] <= 'z')                      /* else cap it */
+    s[0] -= 'a'-'A';
+  if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z')       /* (magic leading X) */
+    s[1] -= 'a'-'A';
+  if (s[0] == 'G' && s[1] == 'l' && 
+      s[2] >= 'a' && s[2] <= 'z')                     /* (magic leading GL) */
+    s[1] -= 'a'-'A',
+    s[2] -= 'a'-'A';
+  return s;
+}
+
+
+char *
+format_hack (Display *dpy, screenhack *hack, Bool wrap_p)
+{
+  int tab = 32;
+  int size;
+  char *h2, *out, *s;
+  int col = 0;
+
+  char *def_name = make_hack_name (dpy, hack->command);
+
+  /* Don't ever write out a name for a hack if it's the same as the default.
+   */
+  if (hack->name && !strcmp (hack->name, def_name))
+    {
+      free (hack->name);
+      hack->name = 0;
+    }
+  free (def_name);
+
+  size = (2 * (strlen(hack->command) +
+               (hack->visual ? strlen(hack->visual) : 0) +
+               (hack->name ? strlen(hack->name) : 0) +
+               tab));
+  h2 = (char *) malloc (size);
+  out = h2;
+
+  if (!hack->enabled_p) *out++ = '-';          /* write disabled flag */
+
+  if (hack->visual && *hack->visual)           /* write visual name */
+    {
+      if (hack->enabled_p) *out++ = ' ';
+      *out++ = ' ';
+      strcpy (out, hack->visual);
+      out += strlen (hack->visual);
+      *out++ = ':';
+      *out++ = ' ';
+    }
+
+  *out = 0;
+  col = string_columns (h2, strlen (h2), 0);
+
+  if (hack->name && *hack->name)               /* write pretty name */
+    {
+      int L = (strlen (hack->name) + 2);
+      if (L + col < tab)
+        out = stab_to (out, col, tab - L - 2);
+      else
+        *out++ = ' ';
+      *out++ = '"';
+      strcpy (out, hack->name);
+      out += strlen (hack->name);
+      *out++ = '"';
+      *out = 0;
+
+      col = string_columns (h2, strlen (h2), 0);
+      if (wrap_p && col >= tab)
+        out = stab_to (out, col, 77);
+      else
+        *out++ = ' ';
+
+      if (out >= h2+size) abort();
+    }
+
+  *out = 0;
+  col = string_columns (h2, strlen (h2), 0);
+  out = stab_to (out, col, tab);               /* indent */
+
+  if (out >= h2+size) abort();
+  s = format_command (hack->command, wrap_p);
+  strcpy (out, s);
+  out += strlen (s);
+  free (s);
+  *out = 0;
+
+  return h2;
+}
+
+
+static void
+get_screenhacks (Display *dpy, saver_preferences *p)
+{
+  int i, j;
+  int start = 0;
+  int end = 0;
+  int size;
+  char *d;
+
+  d = get_string_resource (dpy, "monoPrograms", "MonoPrograms");
+  if (d && !*d) { free(d); d = 0; }
+  if (!d)
+    d = get_string_resource (dpy, "colorPrograms", "ColorPrograms");
+  if (d && !*d) { free(d); d = 0; }
+
+  if (d)
+    {
+      fprintf (stderr,
+       "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
+       see the manual for details.\n", blurb());
+      free(d);
+    }
+
+  d = get_string_resource (dpy, "programs", "Programs");
+
+  free_screenhack_list (p->screenhacks, p->screenhacks_count);
+  p->screenhacks = 0;
+  p->screenhacks_count = 0;
+
+  if (!d || !*d)
+    return;
+
+  size = strlen (d);
+
+
+  /* Count up the number of newlines (which will be equal to or larger than
+     one less than the number of hacks.)
+   */
+  for (i = j = 0; d[i]; i++)
+    if (d[i] == '\n')
+      j++;
+  j++;
+
+  p->screenhacks = (screenhack **) calloc (j + 1, sizeof (screenhack *));
+
+  /* Iterate over the lines in `d' (the string with newlines)
+     and make new strings to stuff into the `screenhacks' array.
+   */
+  p->screenhacks_count = 0;
+  while (start < size)
+    {
+      /* skip forward over whitespace. */
+      while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
+       start++;
+
+      /* skip forward to newline or end of string. */
+      end = start;
+      while (d[end] != 0 && d[end] != '\n')
+       end++;
+
+      /* null terminate. */
+      d[end] = 0;
+
+      p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
+      if (p->screenhacks_count >= i)
+       abort();
+
+      start = end+1;
+    }
+
+  free (d);
+
+  if (p->screenhacks_count == 0)
+    {
+      free (p->screenhacks);
+      p->screenhacks = 0;
+    }
+}
+
+
+/* Make sure all the values in the preferences struct are sane.
+ */
+static void
+stop_the_insanity (saver_preferences *p)
+{
+  if (p->passwd_timeout <= 0) p->passwd_timeout = 30000;        /* 30 secs */
+  if (p->timeout < 15000) p->timeout = 15000;                   /* 15 secs */
+  if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;        /*  2 secs */
+  if (p->pointer_timeout <= 0) p->pointer_timeout = 5000;       /*  5 secs */
+  if (p->notice_events_timeout <= 0)
+    p->notice_events_timeout = 10000;                           /* 10 secs */
+  if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
+    p->fade_p = False;
+  if (! p->fade_p) p->unfade_p = False;
+
+  /* The DPMS settings may have the value 0.
+     But if they are negative, or are a range less than 10 seconds,
+     reset them to sensible defaults.  (Since that must be a mistake.)
+   */
+  if (p->dpms_standby != 0 &&
+      p->dpms_standby < 10 * 1000)
+    p->dpms_standby =  2 * 60 * 60 * 1000;                      /* 2 hours */
+  if (p->dpms_suspend != 0 &&
+      p->dpms_suspend < 10 * 1000)
+    p->dpms_suspend =  2 * 60 * 60 * 1000;                      /* 2 hours */
+  if (p->dpms_off != 0 &&
+      p->dpms_off < 10 * 1000)
+    p->dpms_off      = 4 * 60 * 60 * 1000;                      /* 4 hours */
+
+  /* suspend may not be greater than off, unless off is 0.
+     standby may not be greater than suspend, unless suspend is 0.
+   */
+  if (p->dpms_off != 0 &&
+      p->dpms_suspend > p->dpms_off)
+    p->dpms_suspend = p->dpms_off;
+  if (p->dpms_suspend != 0 &&
+      p->dpms_standby > p->dpms_suspend)
+    p->dpms_standby = p->dpms_suspend;
+
+  /* These fixes above ignores the case
+     suspend = 0 and standby > off ...
+   */
+  if (p->dpms_off != 0 &&
+      p->dpms_standby > p->dpms_off)
+    p->dpms_standby = p->dpms_off;
+
+
+  if (p->dpms_standby == 0 &&     /* if *all* are 0, then DPMS is disabled */
+      p->dpms_suspend == 0 &&
+      p->dpms_off     == 0)
+    p->dpms_enabled_p = False;
+
+
+  /* Set watchdog timeout to about half of the cycle timeout, but
+     don't let it be faster than 1/2 minute or slower than 1 minute.
+   */
+  p->watchdog_timeout = p->cycle * 0.6;
+  if (p->watchdog_timeout < 27000) p->watchdog_timeout = 27000;          /* 27 secs */
+  if (p->watchdog_timeout > 57000) p->watchdog_timeout = 57000;   /* 57 secs */
+
+  if (p->pointer_hysteresis < 0)   p->pointer_hysteresis = 0;
+  if (p->pointer_hysteresis > 100) p->pointer_hysteresis = 100;
+}