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