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