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