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