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