a4d8103ca37db5b213c8dc9baa869b5ab57f97d3
[xscreensaver] / driver / prefs.c
1 /* dotfile.c --- management of the ~/.xscreensaver file.
2  * xscreensaver, Copyright (c) 1998-2005 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <stdlib.h>
18
19 #ifdef HAVE_UNISTD_H
20 # include <unistd.h>
21 #endif
22
23 #include <stdio.h>
24 #include <ctype.h>
25 #include <string.h>
26 #include <time.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
29
30 #include <X11/Xlib.h>
31 #include <X11/Xresource.h>
32
33 #ifndef VMS
34 # include <pwd.h>
35 #else /* VMS */
36 # include "vms-pwd.h"
37 #endif /* VMS */
38
39
40 /* This file doesn't need the Xt headers, so stub these types out... */
41 #undef XtPointer
42 #define XtAppContext void*
43 #define XtIntervalId void*
44 #define XtPointer    void*
45 #define Widget       void*
46
47
48 /* Just in case there's something pathological about stat.h... */
49 #ifndef  S_IRUSR
50 # define S_IRUSR 00400
51 #endif
52 #ifndef  S_IWUSR
53 # define S_IWUSR 00200
54 #endif
55 #ifndef  S_IXUSR
56 # define S_IXUSR 00100
57 #endif
58 #ifndef  S_IXGRP
59 # define S_IXGRP 00010
60 #endif
61 #ifndef  S_IXOTH
62 # define S_IXOTH 00001
63 #endif
64
65
66 #include "prefs.h"
67 #include "resources.h"
68
69
70 extern char *progname;
71 extern char *progclass;
72 extern const char *blurb (void);
73
74
75
76 static void get_screenhacks (saver_preferences *p);
77 static char *format_command (const char *cmd, Bool wrap_p);
78 static void merge_system_screenhacks (saver_preferences *p,
79                                       screenhack **system_list, int count);
80 static void stop_the_insanity (saver_preferences *p);
81
82
83 static char *
84 chase_symlinks (const char *file)
85 {
86 # ifdef HAVE_REALPATH
87   if (file)
88     {
89       char buf [2048];
90       if (realpath (file, buf))
91         return strdup (buf);
92
93       sprintf (buf, "%.100s: realpath %.200s", blurb(), file);
94       perror(buf);
95     }
96 # endif /* HAVE_REALPATH */
97   return 0;
98 }
99
100
101 static Bool
102 i_am_a_nobody (uid_t uid)
103 {
104   struct passwd *p;
105
106   p = getpwnam ("nobody");
107   if (! p) p = getpwnam ("noaccess");
108   if (! p) p = getpwnam ("daemon");
109
110   if (! p) /* There is no nobody? */
111     return False;
112
113   return (uid == p->pw_uid);
114 }
115
116
117 const char *
118 init_file_name (void)
119 {
120   static char *file = 0;
121
122   if (!file)
123     {
124       uid_t uid = getuid ();
125       struct passwd *p = getpwuid (uid);
126
127       if (i_am_a_nobody (uid))
128         /* If we're running as nobody, then use root's .xscreensaver file
129            (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely
130            to be different -- if we didn't do this, then xscreensaver-demo
131            would appear to have no effect when the luser is running as root.)
132          */
133         uid = 0;
134
135       p = getpwuid (uid);
136
137       if (!p || !p->pw_name || !*p->pw_name)
138         {
139           fprintf (stderr, "%s: couldn't get user info of uid %d\n",
140                    blurb(), getuid ());
141           file = "";
142         }
143       else if (!p->pw_dir || !*p->pw_dir)
144         {
145           fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
146                    blurb(), (p->pw_name ? p->pw_name : "???"));
147           file = "";
148         }
149       else
150         {
151           const char *home = p->pw_dir;
152           const char *name = ".xscreensaver";
153           file = (char *) malloc(strlen(home) + strlen(name) + 2);
154           strcpy(file, home);
155           if (!*home || home[strlen(home)-1] != '/')
156             strcat(file, "/");
157           strcat(file, name);
158         }
159     }
160
161   if (file && *file)
162     return file;
163   else
164     return 0;
165 }
166
167
168 static const char *
169 init_file_tmp_name (void)
170 {
171   static char *file = 0;
172   if (!file)
173     {
174       const char *name = init_file_name();
175       const char *suffix = ".tmp";
176
177       char *n2 = chase_symlinks (name);
178       if (n2) name = n2;
179
180       if (!name || !*name)
181         file = "";
182       else
183         {
184           file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
185           strcpy(file, name);
186           strcat(file, suffix);
187         }
188
189       if (n2) free (n2);
190     }
191
192   if (file && *file)
193     return file;
194   else
195     return 0;
196 }
197
198 static int
199 get_byte_resource (char *name, char *class)
200 {
201   char *s = get_string_resource (name, class);
202   char *s2 = s;
203   int n = 0;
204   if (!s) return 0;
205
206   while (isspace(*s2)) s2++;
207   while (*s2 >= '0' && *s2 <= '9')
208     {
209       n = (n * 10) + (*s2 - '0');
210       s2++;
211     }
212   while (isspace(*s2)) s2++;
213   if      (*s2 == 'k' || *s2 == 'K') n <<= 10;
214   else if (*s2 == 'm' || *s2 == 'M') n <<= 20;
215   else if (*s2 == 'g' || *s2 == 'G') n <<= 30;
216   else if (*s2)
217     {
218     LOSE:
219       fprintf (stderr, "%s: %s must be a number of bytes, not \"%s\".\n",
220                progname, name, s);
221       free (s);
222       return 0;
223     }
224   s2++;
225   if (*s2 == 'b' || *s2 == 'B') s2++;
226   while (isspace(*s2)) s2++;
227   if (*s2) goto LOSE;
228
229   free (s);
230   return n;
231 }
232
233
234 static const char * const prefs[] = {
235   "timeout",
236   "cycle",
237   "lock",
238   "lockVTs",                    /* not saved */
239   "lockTimeout",
240   "passwdTimeout",
241   "visualID",
242   "installColormap",
243   "verbose",
244   "timestamp",
245   "splash",
246   "splashDuration",
247   "quad",
248   "demoCommand",
249   "prefsCommand",
250   "newLoginCommand",
251   "helpURL",                    /* not saved */
252   "loadURL",                    /* not saved */
253   "newLoginCommand",            /* not saved */
254   "nice",
255   "memoryLimit",
256   "fade",
257   "unfade",
258   "fadeSeconds",
259   "fadeTicks",
260   "captureStderr",
261   "captureStdout",              /* not saved -- obsolete */
262   "ignoreUninstalledPrograms",
263   "font",
264   "dpmsEnabled",
265   "dpmsStandby",
266   "dpmsSuspend",
267   "dpmsOff",
268   "grabDesktopImages",
269   "grabVideoFrames",
270   "chooseRandomImages",
271   "imageDirectory",
272   "mode",
273   "selected",
274   "textMode",
275   "textLiteral",
276   "textFile",
277   "textProgram",
278   "textURL",
279   "",
280   "programs",
281   "",
282   "pointerPollTime",
283   "pointerHysteresis",
284   "windowCreationTimeout",
285   "initialDelay",
286   "sgiSaverExtension",
287   "mitSaverExtension",          /* not saved -- obsolete */
288   "xidleExtension",
289   "GetViewPortIsFullOfLies",
290   "procInterrupts",
291   "overlayStderr",
292   "overlayTextBackground",      /* not saved -- X resources only */
293   "overlayTextForeground",      /* not saved -- X resources only */
294   "bourneShell",                /* not saved -- X resources only */
295   0
296 };
297
298 static char *
299 strip (char *s)
300 {
301   char *s2;
302   while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
303     s++;
304   for (s2 = s; *s2; s2++)
305     ;
306   for (s2--; s2 >= s; s2--) 
307     if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n') 
308       *s2 = 0;
309     else
310       break;
311   return s;
312 }
313
314 \f
315 /* Reading
316  */
317
318 static int
319 handle_entry (XrmDatabase *db, const char *key, const char *value,
320               const char *filename, int line)
321 {
322   int i;
323   for (i = 0; prefs[i]; i++)
324     if (*prefs[i] && !strcasecmp(key, prefs[i]))
325       {
326         char *val = strdup(value);
327         char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
328         strcpy(spec, progclass);
329         strcat(spec, ".");
330         strcat(spec, prefs[i]);
331
332         XrmPutStringResource (db, spec, val);
333
334         free(spec);
335         free(val);
336         return 0;
337       }
338
339   fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
340           blurb(), filename, line, key);
341   return 1;
342 }
343
344
345 static int
346 parse_init_file (saver_preferences *p)
347 {
348   time_t write_date = 0;
349   const char *name = init_file_name();
350   int line = 0;
351   struct stat st;
352   FILE *in;
353   int buf_size = 1024;
354   char *buf;
355
356   if (!name) return 0;
357
358   if (stat(name, &st) != 0)
359     {
360       p->init_file_date = 0;
361       return 0;
362     }
363
364   in = fopen(name, "r");
365   if (!in)
366     {
367       char *buf = (char *) malloc(1024 + strlen(name));
368       sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
369       perror(buf);
370       free(buf);
371       return -1;
372     }
373
374   if (fstat (fileno(in), &st) == 0)
375     {
376       write_date = st.st_mtime;
377     }
378   else
379     {
380       char *buf = (char *) malloc(1024 + strlen(name));
381       sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
382       perror(buf);
383       free(buf);
384       return -1;
385     }
386
387   buf = (char *) malloc(buf_size);
388
389   while (fgets (buf, buf_size-1, in))
390     {
391       char *key, *value;
392       int L = strlen(buf);
393
394       line++;
395       while (L > 2 &&
396              (buf[L-1] != '\n' ||       /* whole line didn't fit in buffer */
397               buf[L-2] == '\\'))        /* or line ended with backslash */
398         {
399           if (buf[L-2] == '\\')         /* backslash-newline gets swallowed */
400             {
401               buf[L-2] = 0;
402               L -= 2;
403             }
404           buf_size += 1024;
405           buf = (char *) realloc(buf, buf_size);
406           if (!buf) exit(1);
407
408           line++;
409           if (!fgets (buf + L, buf_size-L-1, in))
410             break;
411           L = strlen(buf);
412         }
413
414       /* Now handle other backslash escapes. */
415       {
416         int i, j;
417         for (i = 0; buf[i]; i++)
418           if (buf[i] == '\\')
419             {
420               switch (buf[i+1])
421                 {
422                 case 'n': buf[i] = '\n'; break;
423                 case 'r': buf[i] = '\r'; break;
424                 case 't': buf[i] = '\t'; break;
425                 default:  buf[i] = buf[i+1]; break;
426                 }
427               for (j = i+2; buf[j]; j++)
428                 buf[j-1] = buf[j];
429               buf[j-1] = 0;
430             }
431       }
432
433       key = strip(buf);
434
435       if (*key == '#' || *key == '!' || *key == ';' ||
436           *key == '\n' || *key == 0)
437         continue;
438
439       value = strchr (key, ':');
440       if (!value)
441         {
442           fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
443                   name, line, key);
444           continue;
445         }
446       else
447         {
448           *value++ = 0;
449           value = strip(value);
450         }
451
452       if (!p->db) abort();
453       handle_entry (&p->db, key, value, name, line);
454     }
455   fclose (in);
456   free(buf);
457
458   p->init_file_date = write_date;
459   return 0;
460 }
461
462
463 Bool
464 init_file_changed_p (saver_preferences *p)
465 {
466   const char *name = init_file_name();
467   struct stat st;
468
469   if (!name) return False;
470
471   if (stat(name, &st) != 0)
472     return False;
473
474   if (p->init_file_date == st.st_mtime)
475     return False;
476
477   return True;
478 }
479
480 \f
481 /* Writing
482  */
483
484 static int
485 tab_to (FILE *out, int from, int to)
486 {
487   int tab_width = 8;
488   int to_mod = (to / tab_width) * tab_width;
489   while (from < to_mod)
490     {
491       fprintf(out, "\t");
492       from = (((from / tab_width) + 1) * tab_width);
493     }
494   while (from < to)
495     {
496       fprintf(out, " ");
497       from++;
498     }
499   return from;
500 }
501
502 static char *
503 stab_to (char *out, int from, int to)
504 {
505   int tab_width = 8;
506   int to_mod = (to / tab_width) * tab_width;
507   while (from < to_mod)
508     {
509       *out++ = '\t';
510       from = (((from / tab_width) + 1) * tab_width);
511     }
512   while (from < to)
513     {
514       *out++ = ' ';
515       from++;
516     }
517   return out;
518 }
519
520 static int
521 string_columns (const char *string, int length, int start)
522 {
523   int tab_width = 8;
524   int col = start;
525   const char *end = string + length;
526   while (string < end)
527     {
528       if (*string == '\n')
529         col = 0;
530       else if (*string == '\t')
531         col = (((col / tab_width) + 1) * tab_width);
532       else
533         col++;
534       string++;
535     }
536   return col;
537 }
538
539
540 static void
541 write_entry (FILE *out, const char *key, const char *value)
542 {
543   char *v = strdup(value ? value : "");
544   char *v2 = v;
545   char *nl = 0;
546   int col;
547   Bool programs_p = (!strcmp(key, "programs"));
548   int tab = (programs_p ? 32 : 16);
549   Bool first = True;
550
551   fprintf(out, "%s:", key);
552   col = strlen(key) + 1;
553
554   if (strlen(key) > 14)
555     col = tab_to (out, col, 20);
556
557   while (1)
558     {
559       if (!programs_p)
560         v2 = strip(v2);
561       nl = strchr(v2, '\n');
562       if (nl)
563         *nl = 0;
564
565       if (first && programs_p)
566         {
567           col = tab_to (out, col, 77);
568           fprintf (out, " \\\n");
569           col = 0;
570         }
571
572       if (first)
573         first = False;
574       else
575         {
576           col = tab_to (out, col, 75);
577           fprintf (out, " \\n\\\n");
578           col = 0;
579         }
580
581       if (!programs_p)
582         col = tab_to (out, col, tab);
583
584       if (programs_p &&
585           string_columns(v2, strlen (v2), col) + col > 75)
586         {
587           int L = strlen (v2);
588           int start = 0;
589           int end = start;
590           while (start < L)
591             {
592               while (v2[end] == ' ' || v2[end] == '\t')
593                 end++;
594               while (v2[end] != ' ' && v2[end] != '\t' &&
595                      v2[end] != '\n' && v2[end] != 0)
596                 end++;
597               if (string_columns (v2 + start, (end - start), col) >= 74)
598                 {
599                   col = tab_to (out, col, 75);
600                   fprintf(out, "   \\\n");
601                   col = tab_to (out, 0, tab + 2);
602                   while (v2[start] == ' ' || v2[start] == '\t')
603                     start++;
604                 }
605
606               col = string_columns (v2 + start, (end - start), col);
607               while (start < end)
608                 fputc(v2[start++], out);
609             }
610         }
611       else
612         {
613           fprintf (out, "%s", v2);
614           col += string_columns(v2, strlen (v2), col);
615         }
616
617       if (nl)
618         v2 = nl + 1;
619       else
620         break;
621     }
622
623   fprintf(out, "\n");
624   free(v);
625 }
626
627 int
628 write_init_file (saver_preferences *p, const char *version_string,
629                  Bool verbose_p)
630 {
631   int status = -1;
632   const char *name = init_file_name();
633   const char *tmp_name = init_file_tmp_name();
634   char *n2 = chase_symlinks (name);
635   struct stat st;
636   int i, j;
637
638   /* Kludge, since these aren't in the saver_preferences struct as strings...
639    */
640   char *visual_name;
641   char *programs;
642   Bool overlay_stderr_p;
643   char *stderr_font;
644   FILE *out;
645
646   if (!name) goto END;
647
648   if (n2) name = n2;
649
650   /* Throttle the various timeouts to reasonable values before writing
651      the file to disk. */
652   stop_the_insanity (p);
653
654
655   if (verbose_p)
656     fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
657
658   unlink (tmp_name);
659   out = fopen(tmp_name, "w");
660   if (!out)
661     {
662       char *buf = (char *) malloc(1024 + strlen(name));
663       sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
664       perror(buf);
665       free(buf);
666       goto END;
667     }
668
669   /* Give the new .xscreensaver file the same permissions as the old one;
670      except ensure that it is readable and writable by owner, and not
671      executable.  Extra hack: if we're running as root, make the file
672      be world-readable (so that the daemon, running as "nobody", will
673      still be able to read it.)
674    */
675   if (stat(name, &st) == 0)
676     {
677       mode_t mode = st.st_mode;
678       mode |= S_IRUSR | S_IWUSR;                /* read/write by user */
679       mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);   /* executable by none */
680
681       if (getuid() == (uid_t) 0)                /* read by group/other */
682         mode |= S_IRGRP | S_IROTH;
683
684       if (fchmod (fileno(out), mode) != 0)
685         {
686           char *buf = (char *) malloc(1024 + strlen(name));
687           sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
688                    tmp_name, (unsigned int) mode);
689           perror(buf);
690           free(buf);
691           goto END;
692         }
693     }
694
695   /* Kludge, since these aren't in the saver_preferences struct... */
696   visual_name = get_string_resource ("visualID", "VisualID");
697   programs = 0;
698   overlay_stderr_p = get_boolean_resource ("overlayStderr", "Boolean");
699   stderr_font = get_string_resource ("font", "Font");
700
701   i = 0;
702   {
703     char *ss;
704     char **hack_strings = (char **)
705       calloc (p->screenhacks_count, sizeof(char *));
706
707     for (j = 0; j < p->screenhacks_count; j++)
708       {
709         hack_strings[j] = format_hack (p->screenhacks[j], True);
710         i += strlen (hack_strings[j]);
711         i += 2;
712       }
713
714     ss = programs = (char *) malloc(i + 10);
715     *ss = 0;
716     for (j = 0; j < p->screenhacks_count; j++)
717       {
718         strcat (ss, hack_strings[j]);
719         free (hack_strings[j]);
720         ss += strlen(ss);
721         *ss++ = '\n';
722         *ss = 0;
723       }
724     free (hack_strings);
725   }
726
727   {
728     struct passwd *pw = getpwuid (getuid ());
729     char *whoami = (pw && pw->pw_name && *pw->pw_name
730                     ? pw->pw_name
731                     : "<unknown>");
732     time_t now = time ((time_t *) 0);
733     char *timestr = (char *) ctime (&now);
734     char *nl = (char *) strchr (timestr, '\n');
735     if (nl) *nl = 0;
736     fprintf (out,
737              "# %s Preferences File\n"
738              "# Written by %s %s for %s on %s.\n"
739              "# http://www.jwz.org/xscreensaver/\n"
740              "\n",
741              progclass, progname, version_string, whoami, timestr);
742   }
743
744   for (j = 0; prefs[j]; j++)
745     {
746       char buf[255];
747       const char *pr = prefs[j];
748       enum pref_type { pref_str, pref_int, pref_bool, pref_byte, pref_time
749       } type = pref_str;
750       const char *s = 0;
751       int i = 0;
752       Bool b = False;
753       Time t = 0;
754
755       if (pr && !*pr)
756         {
757           fprintf(out, "\n");
758           continue;
759         }
760
761 # undef CHECK
762 # define CHECK(X) else if (!strcmp(pr, X))
763       if (!pr || !*pr)          ;
764       CHECK("timeout")          type = pref_time, t = p->timeout;
765       CHECK("cycle")            type = pref_time, t = p->cycle;
766       CHECK("lock")             type = pref_bool, b = p->lock_p;
767 # if 0 /* #### not ready yet */
768       CHECK("lockVTs")          type = pref_bool, b = p->lock_vt_p;
769 # else
770       CHECK("lockVTs")          continue;  /* don't save */
771 # endif
772       CHECK("lockTimeout")      type = pref_time, t = p->lock_timeout;
773       CHECK("passwdTimeout")    type = pref_time, t = p->passwd_timeout;
774       CHECK("visualID")         type = pref_str,  s =    visual_name;
775       CHECK("installColormap")  type = pref_bool, b = p->install_cmap_p;
776       CHECK("verbose")          type = pref_bool, b = p->verbose_p;
777       CHECK("timestamp")        type = pref_bool, b = p->timestamp_p;
778       CHECK("splash")           type = pref_bool, b = p->splash_p;
779       CHECK("splashDuration")   type = pref_time, t = p->splash_duration;
780       CHECK("quad")             type = pref_bool, b = p->quad_p;
781       CHECK("demoCommand")      type = pref_str,  s = p->demo_command;
782       CHECK("prefsCommand")     type = pref_str,  s = p->prefs_command;
783 /*    CHECK("helpURL")          type = pref_str,  s = p->help_url; */
784       CHECK("helpURL")          continue;  /* don't save */
785 /*    CHECK("loadURL")          type = pref_str,  s = p->load_url_command; */
786       CHECK("loadURL")          continue;  /* don't save */
787 /*    CHECK("newLoginCommand")  type = pref_str,  s = p->new_login_command; */
788       CHECK("newLoginCommand")  continue;  /* don't save */
789       CHECK("nice")             type = pref_int,  i = p->nice_inferior;
790       CHECK("memoryLimit")      type = pref_byte, i = p->inferior_memory_limit;
791       CHECK("fade")             type = pref_bool, b = p->fade_p;
792       CHECK("unfade")           type = pref_bool, b = p->unfade_p;
793       CHECK("fadeSeconds")      type = pref_time, t = p->fade_seconds;
794       CHECK("fadeTicks")        type = pref_int,  i = p->fade_ticks;
795       CHECK("captureStderr")    type = pref_bool, b = p->capture_stderr_p;
796       CHECK("captureStdout")    continue;  /* don't save */
797       CHECK("ignoreUninstalledPrograms")
798                                 type = pref_bool, b = p->ignore_uninstalled_p;
799
800       CHECK("font")             type = pref_str,  s =    stderr_font;
801
802       CHECK("dpmsEnabled")      type = pref_bool, b = p->dpms_enabled_p;
803       CHECK("dpmsStandby")      type = pref_time, t = p->dpms_standby;
804       CHECK("dpmsSuspend")      type = pref_time, t = p->dpms_suspend;
805       CHECK("dpmsOff")          type = pref_time, t = p->dpms_off;
806
807       CHECK("grabDesktopImages") type =pref_bool, b = p->grab_desktop_p;
808       CHECK("grabVideoFrames")   type =pref_bool, b = p->grab_video_p;
809       CHECK("chooseRandomImages")type =pref_bool, b = p->random_image_p;
810       CHECK("imageDirectory")    type =pref_str,  s = p->image_directory;
811
812       CHECK("mode")             type = pref_str,
813                                 s = (p->mode == ONE_HACK ? "one" :
814                                      p->mode == BLANK_ONLY ? "blank" :
815                                      p->mode == DONT_BLANK ? "off" :
816                                      p->mode == RANDOM_HACKS_SAME
817                                      ? "random-same"
818                                      : "random");
819       CHECK("selected")         type = pref_int,  i = p->selected_hack;
820
821       CHECK("textMode")         type = pref_str,
822                                 s = (p->tmode == TEXT_URL     ? "url" :
823                                      p->tmode == TEXT_LITERAL ? "literal" :
824                                      p->tmode == TEXT_FILE    ? "file" :
825                                      p->tmode == TEXT_PROGRAM ? "program" :
826                                      "date");
827       CHECK("textLiteral")      type = pref_str,  s = p->text_literal;
828       CHECK("textFile")         type = pref_str,  s = p->text_file;
829       CHECK("textProgram")      type = pref_str,  s = p->text_program;
830       CHECK("textURL")          type = pref_str,  s = p->text_url;
831
832       CHECK("programs")         type = pref_str,  s =    programs;
833       CHECK("pointerPollTime")  type = pref_time, t = p->pointer_timeout;
834       CHECK("pointerHysteresis")type = pref_int,  i = p->pointer_hysteresis;
835       CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
836       CHECK("initialDelay")     type = pref_time, t = p->initial_delay;
837       CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension;
838       CHECK("mitSaverExtension") continue;  /* don't save */
839       CHECK("xidleExtension")   type = pref_bool, b = p->use_xidle_extension;
840       CHECK("procInterrupts")   type = pref_bool, b = p->use_proc_interrupts;
841       CHECK("GetViewPortIsFullOfLies")  type = pref_bool,
842                                         b = p->getviewport_full_of_lies_p;
843       CHECK("overlayStderr")    type = pref_bool, b = overlay_stderr_p;
844       CHECK("overlayTextBackground") continue;  /* don't save */
845       CHECK("overlayTextForeground") continue;  /* don't save */
846       CHECK("bourneShell")      continue;
847       else                      abort();
848 # undef CHECK
849
850       switch (type)
851         {
852         case pref_str:
853           break;
854         case pref_int:
855           sprintf(buf, "%d", i);
856           s = buf;
857           break;
858         case pref_bool:
859           s = b ? "True" : "False";
860           break;
861         case pref_time:
862           {
863             unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
864             if (sec >= 60)
865               {
866                 min += (sec / 60);
867                 sec %= 60;
868               }
869             if (min >= 60)
870               {
871                 hour += (min / 60);
872                 min %= 60;
873               }
874             sprintf (buf, "%u:%02u:%02u", hour, min, sec);
875             s = buf;
876           }
877           break;
878         case pref_byte:
879           {
880             if      (i >= (1<<30) && i == ((i >> 30) << 30))
881               sprintf(buf, "%dG", i >> 30);
882             else if (i >= (1<<20) && i == ((i >> 20) << 20))
883               sprintf(buf, "%dM", i >> 20);
884             else if (i >= (1<<10) && i == ((i >> 10) << 10))
885               sprintf(buf, "%dK", i >> 10);
886             else
887               sprintf(buf, "%d", i);
888             s = buf;
889           }
890           break;
891         default:
892           abort();
893           break;
894         }
895
896       if (pr && (!strcmp(pr, "mode") || !strcmp(pr, "textMode")))
897         fprintf(out, "\n");
898
899       write_entry (out, pr, s);
900     }
901
902   fprintf(out, "\n");
903
904   if (visual_name) free(visual_name);
905   if (stderr_font) free(stderr_font);
906   if (programs) free(programs);
907
908   if (fclose(out) == 0)
909     {
910       time_t write_date = 0;
911
912       if (stat(tmp_name, &st) == 0)
913         {
914           write_date = st.st_mtime;
915         }
916       else
917         {
918           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
919           sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
920           perror(buf);
921           unlink (tmp_name);
922           free(buf);
923           goto END;
924         }
925
926       if (rename (tmp_name, name) != 0)
927         {
928           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
929           sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
930                   blurb(), tmp_name, name);
931           perror(buf);
932           unlink (tmp_name);
933           free(buf);
934           goto END;
935         }
936       else
937         {
938           p->init_file_date = write_date;
939
940           /* Since the .xscreensaver file is used for IPC, let's try and make
941              sure that the bits actually land on the disk right away. */
942           sync ();
943
944           status = 0;    /* wrote and renamed successfully! */
945         }
946     }
947   else
948     {
949       char *buf = (char *) malloc(1024 + strlen(name));
950       sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
951       perror(buf);
952       free(buf);
953       unlink (tmp_name);
954       goto END;
955     }
956
957  END:
958   if (n2) free (n2);
959   return status;
960 }
961
962 \f
963 /* Parsing the resource database
964  */
965
966 void
967 free_screenhack (screenhack *hack)
968 {
969   if (hack->visual) free (hack->visual);
970   if (hack->name) free (hack->name);
971   free (hack->command);
972   memset (hack, 0, sizeof(*hack));
973   free (hack);
974 }
975
976 static void
977 free_screenhack_list (screenhack **list, int count)
978 {
979   int i;
980   if (!list) return;
981   for (i = 0; i < count; i++)
982     if (list[i])
983       free_screenhack (list[i]);
984   free (list);
985 }
986
987
988
989 /* Populate `saver_preferences' with the contents of the resource database.
990    Note that this may be called multiple times -- it is re-run each time
991    the ~/.xscreensaver file is reloaded.
992
993    This function can be very noisy, since it issues resource syntax errors
994    and so on.
995  */
996 void
997 load_init_file (saver_preferences *p)
998 {
999   static Bool first_time = True;
1000   
1001   screenhack **system_default_screenhacks = 0;
1002   int system_default_screenhack_count = 0;
1003
1004   if (first_time)
1005     {
1006       /* Get the programs resource before the .xscreensaver file has been
1007          parsed and merged into the resource database for the first time:
1008          this is the value of *programs from the app-defaults file.
1009          Then clear it out so that it will be parsed again later, after
1010          the init file has been read.
1011        */
1012       get_screenhacks (p);
1013       system_default_screenhacks = p->screenhacks;
1014       system_default_screenhack_count = p->screenhacks_count;
1015       p->screenhacks = 0;
1016       p->screenhacks_count = 0;
1017     }
1018
1019   if (parse_init_file (p) != 0)         /* file might have gone away */
1020     if (!first_time) return;
1021
1022   first_time = False;
1023
1024   p->xsync_p        = get_boolean_resource ("synchronous", "Synchronous");
1025   p->verbose_p      = get_boolean_resource ("verbose", "Boolean");
1026   p->timestamp_p    = get_boolean_resource ("timestamp", "Boolean");
1027   p->lock_p         = get_boolean_resource ("lock", "Boolean");
1028   p->lock_vt_p      = get_boolean_resource ("lockVTs", "Boolean");
1029   p->fade_p         = get_boolean_resource ("fade", "Boolean");
1030   p->unfade_p       = get_boolean_resource ("unfade", "Boolean");
1031   p->fade_seconds   = 1000 * get_seconds_resource ("fadeSeconds", "Time");
1032   p->fade_ticks     = get_integer_resource ("fadeTicks", "Integer");
1033   p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
1034   p->nice_inferior  = get_integer_resource ("nice", "Nice");
1035   p->inferior_memory_limit = get_byte_resource ("memoryLimit", "MemoryLimit");
1036   p->splash_p       = get_boolean_resource ("splash", "Boolean");
1037   p->quad_p         = get_boolean_resource ("quad", "Boolean");
1038   p->capture_stderr_p = get_boolean_resource ("captureStderr", "Boolean");
1039   p->ignore_uninstalled_p = get_boolean_resource ("ignoreUninstalledPrograms",
1040                                                   "Boolean");
1041
1042   p->initial_delay   = 1000 * get_seconds_resource ("initialDelay", "Time");
1043   p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
1044   p->timeout         = 1000 * get_minutes_resource ("timeout", "Time");
1045   p->lock_timeout    = 1000 * get_minutes_resource ("lockTimeout", "Time");
1046   p->cycle           = 1000 * get_minutes_resource ("cycle", "Time");
1047   p->passwd_timeout  = 1000 * get_seconds_resource ("passwdTimeout", "Time");
1048   p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
1049   p->pointer_hysteresis = get_integer_resource ("pointerHysteresis","Integer");
1050   p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
1051                                                        "Time");
1052
1053   p->dpms_enabled_p  = get_boolean_resource ("dpmsEnabled", "Boolean");
1054   p->dpms_standby    = 1000 * get_minutes_resource ("dpmsStandby", "Time");
1055   p->dpms_suspend    = 1000 * get_minutes_resource ("dpmsSuspend", "Time");
1056   p->dpms_off        = 1000 * get_minutes_resource ("dpmsOff",     "Time");
1057
1058   p->grab_desktop_p  = get_boolean_resource ("grabDesktopImages",  "Boolean");
1059   p->grab_video_p    = get_boolean_resource ("grabVideoFrames",    "Boolean");
1060   p->random_image_p  = get_boolean_resource ("chooseRandomImages", "Boolean");
1061   p->image_directory = get_string_resource  ("imageDirectory",
1062                                              "ImageDirectory");
1063
1064   p->text_literal = get_string_resource ("textLiteral", "TextLiteral");
1065   p->text_file    = get_string_resource ("textFile",    "TextFile");
1066   p->text_program = get_string_resource ("textProgram", "TextProgram");
1067   p->text_url     = get_string_resource ("textURL",     "TextURL");
1068
1069   p->shell = get_string_resource ("bourneShell", "BourneShell");
1070
1071   p->demo_command = get_string_resource("demoCommand", "URL");
1072   p->prefs_command = get_string_resource("prefsCommand", "URL");
1073   p->help_url = get_string_resource("helpURL", "URL");
1074   p->load_url_command = get_string_resource("loadURL", "LoadURL");
1075   p->new_login_command = get_string_resource("newLoginCommand",
1076                                              "NewLoginCommand");
1077
1078   /* If "*splash" is unset, default to true. */
1079   {
1080     char *s = get_string_resource ("splash", "Boolean");
1081     if (s)
1082       free (s);
1083     else
1084       p->splash_p = True;
1085   }
1086
1087   /* If "*grabDesktopImages" is unset, default to true. */
1088   {
1089     char *s = get_string_resource ("grabDesktopImages", "Boolean");
1090     if (s)
1091       free (s);
1092     else
1093       p->grab_desktop_p = True;
1094   }
1095
1096   p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
1097 #if 0 /* ignore this, it is evil. */
1098   p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
1099                                                      "Boolean");
1100 #endif
1101   p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
1102                                                      "Boolean");
1103   p->use_proc_interrupts = get_boolean_resource ("procInterrupts", "Boolean");
1104
1105   p->getviewport_full_of_lies_p =
1106     get_boolean_resource ("GetViewPortIsFullOfLies", "Boolean");
1107
1108   get_screenhacks (p);                /* Parse the "programs" resource. */
1109
1110   {
1111     char *s = get_string_resource ("selected", "Integer");
1112     if (!s || !*s)
1113       p->selected_hack = -1;
1114     else
1115       p->selected_hack = get_integer_resource ("selected", "Integer");
1116     if (s) free (s);
1117     if (p->selected_hack < 0 || p->selected_hack >= p->screenhacks_count)
1118       p->selected_hack = -1;
1119   }
1120
1121   {
1122     char *s = get_string_resource ("mode", "Mode");
1123     if      (s && !strcasecmp (s, "one"))         p->mode = ONE_HACK;
1124     else if (s && !strcasecmp (s, "blank"))       p->mode = BLANK_ONLY;
1125     else if (s && !strcasecmp (s, "off"))         p->mode = DONT_BLANK;
1126     else if (s && !strcasecmp (s, "random-same")) p->mode = RANDOM_HACKS_SAME;
1127     else                                          p->mode = RANDOM_HACKS;
1128     if (s) free (s);
1129   }
1130
1131   {
1132     char *s = get_string_resource ("textMode", "TextMode");
1133     if      (s && !strcasecmp (s, "url"))         p->tmode = TEXT_URL;
1134     else if (s && !strcasecmp (s, "literal"))     p->tmode = TEXT_LITERAL;
1135     else if (s && !strcasecmp (s, "file"))        p->tmode = TEXT_FILE;
1136     else if (s && !strcasecmp (s, "program"))     p->tmode = TEXT_PROGRAM;
1137     else                                          p->tmode = TEXT_DATE;
1138     if (s) free (s);
1139   }
1140
1141   if (system_default_screenhack_count)  /* note: first_time is also true */
1142     {
1143       merge_system_screenhacks (p, system_default_screenhacks,
1144                                 system_default_screenhack_count);
1145       free_screenhack_list (system_default_screenhacks,
1146                             system_default_screenhack_count);
1147       system_default_screenhacks = 0;
1148       system_default_screenhack_count = 0;
1149     }
1150
1151   if (p->debug_p)
1152     {
1153       p->xsync_p = True;
1154       p->verbose_p = True;
1155       p->timestamp_p = True;
1156       p->initial_delay = 0;
1157     }
1158
1159   /* Throttle the various timeouts to reasonable values after reading the
1160      disk file. */
1161   stop_the_insanity (p);
1162 }
1163
1164
1165 /* If there are any hacks in the system-wide defaults that are not in
1166    the ~/.xscreensaver file, add the new ones to the end of the list.
1167    This does *not* actually save the file.
1168  */
1169 static void
1170 merge_system_screenhacks (saver_preferences *p,
1171                           screenhack **system_list, int system_count)
1172 {
1173   /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
1174      so fuck it. */
1175
1176   int made_space = 0;
1177   int i;
1178   for (i = 0; i < system_count; i++)
1179     {
1180       int j;
1181       Bool matched_p = False;
1182
1183       for (j = 0; j < p->screenhacks_count; j++)
1184         {
1185           char *name;
1186           if (!system_list[i]->name)
1187             system_list[i]->name = make_hack_name (system_list[i]->command);
1188
1189           name = p->screenhacks[j]->name;
1190           if (!name)
1191             name = make_hack_name (p->screenhacks[j]->command);
1192
1193           matched_p = !strcasecmp (name, system_list[i]->name);
1194
1195           if (name != p->screenhacks[j]->name)
1196             free (name);
1197
1198           if (matched_p)
1199             break;
1200         }
1201
1202       if (!matched_p)
1203         {
1204           /* We have an entry in the system-wide list that is not in the
1205              user's .xscreensaver file.  Add it to the end.
1206              Note that p->screenhacks is a single malloc block, not a
1207              linked list, so we have to realloc it.
1208            */
1209           screenhack *oh = system_list[i];
1210           screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
1211
1212           if (made_space == 0)
1213             {
1214               made_space = 10;
1215               p->screenhacks = (screenhack **)
1216                 realloc (p->screenhacks,
1217                          (p->screenhacks_count + made_space + 1)
1218                          * sizeof(screenhack));
1219               if (!p->screenhacks) abort();
1220             }
1221
1222           nh->enabled_p = oh->enabled_p;
1223           nh->visual    = oh->visual  ? strdup(oh->visual)  : 0;
1224           nh->name      = oh->name    ? strdup(oh->name)    : 0;
1225           nh->command   = oh->command ? strdup(oh->command) : 0;
1226
1227           p->screenhacks[p->screenhacks_count++] = nh;
1228           p->screenhacks[p->screenhacks_count] = 0;
1229           made_space--;
1230
1231 #if 0
1232           fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
1233                    (nh->name ? nh->name : make_hack_name (nh->command)));
1234 #endif
1235         }
1236     }
1237 }
1238
1239
1240 \f
1241 /* Parsing the programs resource.
1242  */
1243
1244 screenhack *
1245 parse_screenhack (const char *line)
1246 {
1247   screenhack *h = (screenhack *) calloc (1, sizeof(*h));
1248   const char *s;
1249
1250   h->enabled_p = True;
1251
1252   while (isspace(*line)) line++;                /* skip whitespace */
1253   if (*line == '-')                             /* handle "-" */
1254     {
1255       h->enabled_p = False;
1256       line++;
1257       while (isspace(*line)) line++;            /* skip whitespace */
1258     }
1259
1260   s = line;                                     /* handle "visual:" */
1261   while (*line && *line != ':' && *line != '"' && !isspace(*line))
1262     line++;
1263   if (*line != ':')
1264     line = s;
1265   else
1266     {
1267       h->visual = (char *) malloc (line-s+1);
1268       strncpy (h->visual, s, line-s);
1269       h->visual[line-s] = 0;
1270       if (*line == ':') line++;                 /* skip ":" */
1271       while (isspace(*line)) line++;            /* skip whitespace */
1272     }
1273
1274   if (*line == '"')                             /* handle "name" */
1275     {
1276       line++;
1277       s = line;
1278       while (*line && *line != '"')
1279         line++;
1280       h->name = (char *) malloc (line-s+1);
1281       strncpy (h->name, s, line-s);
1282       h->name[line-s] = 0;
1283       if (*line == '"') line++;                 /* skip "\"" */
1284       while (isspace(*line)) line++;            /* skip whitespace */
1285     }
1286
1287   h->command = format_command (line, False);    /* handle command */
1288   return h;
1289 }
1290
1291
1292 static char *
1293 format_command (const char *cmd, Bool wrap_p)
1294 {
1295   int tab = 30;
1296   int col = tab;
1297   char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
1298   const char *in = cmd;
1299   char *out = cmd2;
1300   while (*in)
1301     {
1302       /* shrink all whitespace to one space, for the benefit of the "demo"
1303          mode display.  We only do this when we can easily tell that the
1304          whitespace is not significant (no shell metachars).
1305        */
1306       switch (*in)
1307         {
1308         case '\'': case '"': case '`': case '\\':
1309           /* Metachars are scary.  Copy the rest of the line unchanged. */
1310           while (*in)
1311             *out++ = *in++, col++;
1312           break;
1313
1314         case ' ': case '\t':
1315           /* Squeeze all other whitespace down to one space. */
1316           while (*in == ' ' || *in == '\t')
1317             in++;
1318           *out++ = ' ', col++;
1319           break;
1320
1321         default:
1322           /* Copy other chars unchanged. */
1323           *out++ = *in++, col++;
1324           break;
1325         }
1326     }
1327
1328   *out = 0;
1329
1330   /* Strip trailing whitespace */
1331   while (out > cmd2 && isspace (out[-1]))
1332     *(--out) = 0;
1333
1334   return cmd2;
1335 }
1336
1337
1338 /* Returns a new string describing the shell command.
1339    This may be just the name of the program, capitalized.
1340    It also may be something from the resource database (gotten
1341    by looking for "hacks.XYZ.name", where XYZ is the program.)
1342  */
1343 char *
1344 make_hack_name (const char *shell_command)
1345 {
1346   char *s = strdup (shell_command);
1347   char *s2;
1348   char res_name[255];
1349
1350   for (s2 = s; *s2; s2++)       /* truncate at first whitespace */
1351     if (isspace (*s2))
1352       {
1353         *s2 = 0;
1354         break;
1355       }
1356
1357   s2 = strrchr (s, '/');        /* if pathname, take last component */
1358   if (s2)
1359     {
1360       s2 = strdup (s2+1);
1361       free (s);
1362       s = s2;
1363     }
1364
1365   if (strlen (s) > 50)          /* 51 is hereby defined as "unreasonable" */
1366     s[50] = 0;
1367
1368   sprintf (res_name, "hacks.%s.name", s);               /* resource? */
1369   s2 = get_string_resource (res_name, res_name);
1370   if (s2)
1371     {
1372       free (s);
1373       return s2;
1374     }
1375
1376   for (s2 = s; *s2; s2++)       /* if it has any capitals, return it */
1377     if (*s2 >= 'A' && *s2 <= 'Z')
1378       return s;
1379
1380   if (s[0] >= 'a' && s[0] <= 'z')                       /* else cap it */
1381     s[0] -= 'a'-'A';
1382   if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z')        /* (magic leading X) */
1383     s[1] -= 'a'-'A';
1384   if (s[0] == 'G' && s[1] == 'l' && 
1385       s[2] >= 'a' && s[2] <= 'z')                      /* (magic leading GL) */
1386     s[1] -= 'a'-'A',
1387     s[2] -= 'a'-'A';
1388   return s;
1389 }
1390
1391
1392 char *
1393 format_hack (screenhack *hack, Bool wrap_p)
1394 {
1395   int tab = 32;
1396   int size;
1397   char *h2, *out, *s;
1398   int col = 0;
1399
1400   char *def_name = make_hack_name (hack->command);
1401
1402   /* Don't ever write out a name for a hack if it's the same as the default.
1403    */
1404   if (hack->name && !strcmp (hack->name, def_name))
1405     {
1406       free (hack->name);
1407       hack->name = 0;
1408     }
1409   free (def_name);
1410
1411   size = (2 * (strlen(hack->command) +
1412                (hack->visual ? strlen(hack->visual) : 0) +
1413                (hack->name ? strlen(hack->name) : 0) +
1414                tab));
1415   h2 = (char *) malloc (size);
1416   out = h2;
1417
1418   if (!hack->enabled_p) *out++ = '-';           /* write disabled flag */
1419
1420   if (hack->visual && *hack->visual)            /* write visual name */
1421     {
1422       if (hack->enabled_p) *out++ = ' ';
1423       *out++ = ' ';
1424       strcpy (out, hack->visual);
1425       out += strlen (hack->visual);
1426       *out++ = ':';
1427       *out++ = ' ';
1428     }
1429
1430   *out = 0;
1431   col = string_columns (h2, strlen (h2), 0);
1432
1433   if (hack->name && *hack->name)                /* write pretty name */
1434     {
1435       int L = (strlen (hack->name) + 2);
1436       if (L + col < tab)
1437         out = stab_to (out, col, tab - L - 2);
1438       else
1439         *out++ = ' ';
1440       *out++ = '"';
1441       strcpy (out, hack->name);
1442       out += strlen (hack->name);
1443       *out++ = '"';
1444       *out = 0;
1445
1446       col = string_columns (h2, strlen (h2), 0);
1447       if (wrap_p && col >= tab)
1448         out = stab_to (out, col, 77);
1449       else
1450         *out++ = ' ';
1451
1452       if (out >= h2+size) abort();
1453     }
1454
1455   *out = 0;
1456   col = string_columns (h2, strlen (h2), 0);
1457   out = stab_to (out, col, tab);                /* indent */
1458
1459   if (out >= h2+size) abort();
1460   s = format_command (hack->command, wrap_p);
1461   strcpy (out, s);
1462   out += strlen (s);
1463   free (s);
1464   *out = 0;
1465
1466   return h2;
1467 }
1468
1469
1470 static void
1471 get_screenhacks (saver_preferences *p)
1472 {
1473   int i, j;
1474   int start = 0;
1475   int end = 0;
1476   int size;
1477   char *d;
1478
1479   d = get_string_resource ("monoPrograms", "MonoPrograms");
1480   if (d && !*d) { free(d); d = 0; }
1481   if (!d)
1482     d = get_string_resource ("colorPrograms", "ColorPrograms");
1483   if (d && !*d) { free(d); d = 0; }
1484
1485   if (d)
1486     {
1487       fprintf (stderr,
1488        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1489         see the manual for details.\n", blurb());
1490       free(d);
1491     }
1492
1493   d = get_string_resource ("programs", "Programs");
1494
1495   free_screenhack_list (p->screenhacks, p->screenhacks_count);
1496   p->screenhacks = 0;
1497   p->screenhacks_count = 0;
1498
1499   if (!d || !*d)
1500     return;
1501
1502   size = strlen (d);
1503
1504
1505   /* Count up the number of newlines (which will be equal to or larger than
1506      one less than the number of hacks.)
1507    */
1508   for (i = j = 0; d[i]; i++)
1509     if (d[i] == '\n')
1510       j++;
1511   j++;
1512
1513   p->screenhacks = (screenhack **) calloc (j + 1, sizeof (screenhack *));
1514
1515   /* Iterate over the lines in `d' (the string with newlines)
1516      and make new strings to stuff into the `screenhacks' array.
1517    */
1518   p->screenhacks_count = 0;
1519   while (start < size)
1520     {
1521       /* skip forward over whitespace. */
1522       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1523         start++;
1524
1525       /* skip forward to newline or end of string. */
1526       end = start;
1527       while (d[end] != 0 && d[end] != '\n')
1528         end++;
1529
1530       /* null terminate. */
1531       d[end] = 0;
1532
1533       p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1534       if (p->screenhacks_count >= i)
1535         abort();
1536
1537       start = end+1;
1538     }
1539
1540   free (d);
1541
1542   if (p->screenhacks_count == 0)
1543     {
1544       free (p->screenhacks);
1545       p->screenhacks = 0;
1546     }
1547 }
1548
1549
1550 /* Make sure all the values in the preferences struct are sane.
1551  */
1552 static void
1553 stop_the_insanity (saver_preferences *p)
1554 {
1555   if (p->passwd_timeout <= 0) p->passwd_timeout = 30000;         /* 30 secs */
1556   if (p->timeout < 15000) p->timeout = 15000;                    /* 15 secs */
1557   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
1558   if (p->pointer_timeout <= 0) p->pointer_timeout = 5000;        /*  5 secs */
1559   if (p->notice_events_timeout <= 0)
1560     p->notice_events_timeout = 10000;                            /* 10 secs */
1561   if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
1562     p->fade_p = False;
1563   if (! p->fade_p) p->unfade_p = False;
1564
1565   /* The DPMS settings may have the value 0.
1566      But if they are negative, or are a range less than 10 seconds,
1567      reset them to sensible defaults.  (Since that must be a mistake.)
1568    */
1569   if (p->dpms_standby != 0 &&
1570       p->dpms_standby < 10 * 1000)
1571     p->dpms_standby =  2 * 60 * 60 * 1000;                       /* 2 hours */
1572   if (p->dpms_suspend != 0 &&
1573       p->dpms_suspend < 10 * 1000)
1574     p->dpms_suspend =  2 * 60 * 60 * 1000;                       /* 2 hours */
1575   if (p->dpms_off != 0 &&
1576       p->dpms_off < 10 * 1000)
1577     p->dpms_off      = 4 * 60 * 60 * 1000;                       /* 4 hours */
1578
1579   /* standby may not be greater than suspend.
1580      suspend may not be greater than off.
1581    */
1582   if (p->dpms_standby > p->dpms_suspend) p->dpms_standby = p->dpms_suspend;
1583   if (p->dpms_suspend > p->dpms_off)     p->dpms_suspend = p->dpms_off;
1584
1585
1586   if (p->dpms_standby == 0 &&      /* if *all* are 0, then DPMS is disabled */
1587       p->dpms_suspend == 0 &&
1588       p->dpms_off     == 0)
1589     p->dpms_enabled_p = False;
1590
1591
1592   p->watchdog_timeout = p->cycle * 0.6;
1593   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
1594   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
1595
1596   if (p->pointer_hysteresis < 0)   p->pointer_hysteresis = 0;
1597   if (p->pointer_hysteresis > 100) p->pointer_hysteresis = 100;
1598 }