/* dotfile.c --- management of the ~/.xscreensaver file.
 * xscreensaver, Copyright (c) 1998-2016 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 "version.h"
#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 */
  "logFile",			/* not saved */
  "ignoreUninstalledPrograms",
  "font",
  "dpmsEnabled",
  "dpmsQuickOff",
  "dpmsStandby",
  "dpmsSuspend",
  "dpmsOff",
  "grabDesktopImages",
  "grabVideoFrames",
  "chooseRandomImages",
  "imageDirectory",
  "mode",
  "selected",
  "textMode",
  "textLiteral",
  "textFile",
  "textProgram",
  "textURL",
  "",
  "programs",
  "",
  "pointerPollTime",
  "pointerHysteresis",
  "windowCreationTimeout",
  "initialDelay",
  "sgiSaverExtension",		/* not saved -- obsolete */
  "mitSaverExtension",		/* not saved -- obsolete */
  "xidleExtension",		/* not saved -- obsolete */
  "GetViewPortIsFullOfLies",
  "procInterrupts",
  "xinputExtensionDev",
  "overlayStderr",
  "overlayTextBackground",	/* not saved -- X resources only */
  "overlayTextForeground",	/* not saved -- X resources only */
  "bourneShell",		/* not saved -- X resources only */
  "authWarningSlack",
  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;
}


/* 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;
}


/* 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"
	     "# https://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("logFile")		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("dpmsQuickOff")	type = pref_bool, b = p->dpms_quickoff_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") continue;  /* don't save */
      CHECK("mitSaverExtension") continue;  /* don't save */
      CHECK("xidleExtension")	 continue;  /* don't save */
      CHECK("procInterrupts")	type = pref_bool, b = p->use_proc_interrupts;
      CHECK("xinputExtensionDev") type = pref_bool, b = p->use_xinput_extension;
      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;  /* don't save */
      CHECK("authWarningSlack") type = pref_int, i = p->auth_warning_slack;
      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;
}


/* 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_quickoff_p = get_boolean_resource (dpy, "dpmsQuickOff", "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");
  p->auth_warning_slack = get_integer_resource(dpy, "authWarningSlack",
                                               "Integer");

  /* 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 /* obsolete. */
  p->use_sgi_saver_extension = get_boolean_resource (dpy,
                                                     "sgiSaverExtension",
						     "Boolean");
#endif
#ifdef HAVE_XINPUT
  p->use_xinput_extension = get_boolean_resource (dpy, "xinputExtensionDev",
                                                  "Boolean");
#endif
#if 0 /* broken and evil. */
  p->use_mit_saver_extension = get_boolean_resource (dpy, 
                                                     "mitSaverExtension",
						     "Boolean");
#endif

  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
        }
    }
}



/* 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_quickoff_p)           /* ... but we want to do DPMS quick off */
      )
    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;

  if (p->auth_warning_slack < 0)   p->auth_warning_slack = 0;
  if (p->auth_warning_slack > 300) p->auth_warning_slack = 300;
}


Bool
decrepit_p (void)
{
  /* If you are in here because you're planning on disabling this warning
     before redistributing my software, please don't.

     I sincerely request that you do one of the following:

         1: leave this code intact and this warning in place, -OR-

         2: Remove xscreensaver from your distribution.

     I would seriously prefer that you not distribute my software at all
     than that you distribute one version and then never update it for
     years.

     I am *constantly* getting email from users reporting bugs that have
     been fixed for literally years who have no idea that the software
     they are running is years out of date.  Yes, it would be great if we
     lived in the ideal world where people checked that they were running
     the latest release before they report a bug, but we don't.  To most
     people, "running the latest release" is synonymous with "running the
     latest release that my distro packages for me."

     When they even bother to tell me what version they're running, I
     say, "That version is three years old!", and they say "But this is
     the latest version my distro ships".  Then I say, "your distro
     sucks", and they say "but I don't know how to compile from source,
     herp derp I eat paste", and *everybody* goes away unhappy.

     It wastes an enormous amount of my time, but worse than that, it
     does a grave disservice to the users, who are stuck experiencing
     bugs that are already fixed!  These users think they are running the
     latest release, and they are not.  They would like to be running the
     actual latest release, but they don't know how, because their distro
     makes that very difficult for them.  It's terrible for everyone, and
     kind of makes me regret ever having released this software in the
     first place.

     So seriously. I ask that if you're planning on disabling this
     obsolescence warning, that you instead just remove xscreensaver from
     your distro entirely.  Everybody will be happier that way.  Check
     out gnome-screensaver instead, I understand it's really nice.

     Of course, my license allows you to ignore me and do whatever the
     fuck you want, but as the author, I hope you will have the common
     courtesy of complying with my request.

     Thank you!

     jwz, 2014, 2016.

     PS: In particular, since Debian refuses to upgrade software on any
     kind of rational timeline, I have asked that they stop shipping
     xscreensaver at all.  They have refused.  Instead of upgrading the
     software, they simply patched out this warning.

     If you want to witness the sad state of the open source peanut
     gallery, look no farther than the comments on my blog:
     http://jwz.org/b/yiYo

     Many of these people fall back on their go-to argument of, "If it is
     legal, it must be right."  If you believe in that rhetorical device
     then you are a terrible person, and possibly a sociopath.

     There are also the armchair lawyers who say "Well, instead of
     *asking* people to do the right thing out of common courtesy, you
     should just change your license to prohibit them from acting
     amorally."  Again, this is the answer of a sociopath, but that aside,
     if you devote even a second's thought to this you will realize that
     the end result of this would be for distros like Debian to just keep
     shipping the last version with the old license and then never
     upgrading it again -- which would be the worst possible outcome for
     everyone involved, most especially the users.
  */

  time_t now = time ((time_t *) 0);				/*       */
  struct tm *tm = localtime (&now);				/*   d   */
  const char *s = screensaver_id;				/*   o   */
  char mon[4], year[5];						/*   n   */
  int m, y, months;						/*   '   */
  s = strchr (s, ' '); if (!s) abort(); s++;			/*   t   */
  s = strchr (s, '('); if (!s) abort(); s++;			/*       */
  s = strchr (s, '-'); if (!s) abort(); s++;			/*   d   */
  strncpy (mon, s, 3);						/*   o   */
  mon[3] = 0;							/*       */
  s = strchr (s, '-'); if (!s) abort(); s++;			/*   i   */
  strncpy (year, s, 4);						/*   t   */
  year[4] = 0;							/*   ,   */
  y = atoi (year);						/*       */
  if      (!strcmp(mon, "Jan")) m = 0;				/*   s   */
  else if (!strcmp(mon, "Feb")) m = 1;				/*   t   */
  else if (!strcmp(mon, "Mar")) m = 2;				/*   o   */
  else if (!strcmp(mon, "Apr")) m = 3;				/*   p   */
  else if (!strcmp(mon, "May")) m = 4;				/*   ,   */
  else if (!strcmp(mon, "Jun")) m = 5;				/*       */
  else if (!strcmp(mon, "Jul")) m = 6;				/*   s   */
  else if (!strcmp(mon, "Aug")) m = 7;				/*   t   */
  else if (!strcmp(mon, "Sep")) m = 8;				/*   a   */
  else if (!strcmp(mon, "Oct")) m = 9;				/*   a   */
  else if (!strcmp(mon, "Nov")) m = 10;				/*   a   */
  else if (!strcmp(mon, "Dec")) m = 11;				/*   h   */
  else abort();							/*   h   */
  months = ((((tm->tm_year + 1900) * 12) + tm->tm_mon) -	/*   h   */
            (y * 12 + m));					/*   p   */
							  	/*   .   */
  return (months >= 17);					/*       */
}
