http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[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   "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",          /* not saved -- obsolete */
301   "mitSaverExtension",          /* not saved -- obsolete */
302   "xidleExtension",             /* not saved -- obsolete */
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") continue;  /* don't save */
855       CHECK("mitSaverExtension") continue;  /* don't save */
856       CHECK("xidleExtension")    continue;  /* don't save */
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;  /* don't save */
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 /* obsolete. */
1122   p->use_sgi_saver_extension = get_boolean_resource (dpy,
1123                                                      "sgiSaverExtension",
1124                                                      "Boolean");
1125 #endif
1126 #if 0 /* obsolete. */
1127   p->use_xinput_extension = get_boolean_resource (dpy, "xinputExtensionDev",
1128                                                   "Boolean");
1129 #endif
1130 #if 0 /* broken and evil. */
1131   p->use_mit_saver_extension = get_boolean_resource (dpy, 
1132                                                      "mitSaverExtension",
1133                                                      "Boolean");
1134 #endif
1135
1136   p->use_proc_interrupts = get_boolean_resource (dpy,
1137                                                  "procInterrupts", "Boolean");
1138
1139   p->getviewport_full_of_lies_p =
1140     get_boolean_resource (dpy, "GetViewPortIsFullOfLies", "Boolean");
1141
1142   get_screenhacks (dpy, p);             /* Parse the "programs" resource. */
1143
1144   {
1145     char *s = get_string_resource (dpy, "selected", "Integer");
1146     if (!s || !*s)
1147       p->selected_hack = -1;
1148     else
1149       p->selected_hack = get_integer_resource (dpy, "selected", "Integer");
1150     if (s) free (s);
1151     if (p->selected_hack < 0 || p->selected_hack >= p->screenhacks_count)
1152       p->selected_hack = -1;
1153   }
1154
1155   {
1156     char *s = get_string_resource (dpy, "mode", "Mode");
1157     if      (s && !strcasecmp (s, "one"))         p->mode = ONE_HACK;
1158     else if (s && !strcasecmp (s, "blank"))       p->mode = BLANK_ONLY;
1159     else if (s && !strcasecmp (s, "off"))         p->mode = DONT_BLANK;
1160     else if (s && !strcasecmp (s, "random-same")) p->mode = RANDOM_HACKS_SAME;
1161     else                                          p->mode = RANDOM_HACKS;
1162     if (s) free (s);
1163   }
1164
1165   {
1166     char *s = get_string_resource (dpy, "textMode", "TextMode");
1167     if      (s && !strcasecmp (s, "url"))         p->tmode = TEXT_URL;
1168     else if (s && !strcasecmp (s, "literal"))     p->tmode = TEXT_LITERAL;
1169     else if (s && !strcasecmp (s, "file"))        p->tmode = TEXT_FILE;
1170     else if (s && !strcasecmp (s, "program"))     p->tmode = TEXT_PROGRAM;
1171     else                                          p->tmode = TEXT_DATE;
1172     if (s) free (s);
1173   }
1174
1175   if (system_default_screenhack_count)  /* note: first_time is also true */
1176     {
1177       merge_system_screenhacks (dpy, p, system_default_screenhacks,
1178                                 system_default_screenhack_count);
1179       free_screenhack_list (system_default_screenhacks,
1180                             system_default_screenhack_count);
1181       system_default_screenhacks = 0;
1182       system_default_screenhack_count = 0;
1183     }
1184
1185   if (p->debug_p)
1186     {
1187       p->xsync_p = True;
1188       p->verbose_p = True;
1189       p->timestamp_p = True;
1190       p->initial_delay = 0;
1191     }
1192
1193   /* Throttle the various timeouts to reasonable values after reading the
1194      disk file. */
1195   stop_the_insanity (p);
1196 }
1197
1198
1199 /* If there are any hacks in the system-wide defaults that are not in
1200    the ~/.xscreensaver file, add the new ones to the end of the list.
1201    This does *not* actually save the file.
1202  */
1203 static void
1204 merge_system_screenhacks (Display *dpy, saver_preferences *p,
1205                           screenhack **system_list, int system_count)
1206 {
1207   /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
1208      so fuck it. */
1209
1210   int made_space = 0;
1211   int i;
1212   for (i = 0; i < system_count; i++)
1213     {
1214       int j;
1215       Bool matched_p = False;
1216
1217       for (j = 0; j < p->screenhacks_count; j++)
1218         {
1219           char *name;
1220           if (!system_list[i]->name)
1221             system_list[i]->name = make_hack_name (dpy, 
1222                                                    system_list[i]->command);
1223
1224           name = p->screenhacks[j]->name;
1225           if (!name)
1226             name = make_hack_name (dpy, p->screenhacks[j]->command);
1227
1228           matched_p = !strcasecmp (name, system_list[i]->name);
1229
1230           if (name != p->screenhacks[j]->name)
1231             free (name);
1232
1233           if (matched_p)
1234             break;
1235         }
1236
1237       if (!matched_p)
1238         {
1239           /* We have an entry in the system-wide list that is not in the
1240              user's .xscreensaver file.  Add it to the end.
1241              Note that p->screenhacks is a single malloc block, not a
1242              linked list, so we have to realloc it.
1243            */
1244           screenhack *oh = system_list[i];
1245           screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
1246
1247           if (made_space == 0)
1248             {
1249               made_space = 10;
1250               p->screenhacks = (screenhack **)
1251                 realloc (p->screenhacks,
1252                          (p->screenhacks_count + made_space + 1)
1253                          * sizeof(screenhack));
1254               if (!p->screenhacks) abort();
1255             }
1256
1257           nh->enabled_p = oh->enabled_p;
1258           nh->visual    = oh->visual  ? strdup(oh->visual)  : 0;
1259           nh->name      = oh->name    ? strdup(oh->name)    : 0;
1260           nh->command   = oh->command ? strdup(oh->command) : 0;
1261
1262           p->screenhacks[p->screenhacks_count++] = nh;
1263           p->screenhacks[p->screenhacks_count] = 0;
1264           made_space--;
1265
1266 #if 0
1267           fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
1268                    (nh->name ? nh->name : make_hack_name (dpy, nh->command)));
1269 #endif
1270         }
1271     }
1272 }
1273
1274
1275 \f
1276 /* Parsing the programs resource.
1277  */
1278
1279 screenhack *
1280 parse_screenhack (const char *line)
1281 {
1282   screenhack *h = (screenhack *) calloc (1, sizeof(*h));
1283   const char *s;
1284
1285   h->enabled_p = True;
1286
1287   while (isspace(*line)) line++;                /* skip whitespace */
1288   if (*line == '-')                             /* handle "-" */
1289     {
1290       h->enabled_p = False;
1291       line++;
1292       while (isspace(*line)) line++;            /* skip whitespace */
1293     }
1294
1295   s = line;                                     /* handle "visual:" */
1296   while (*line && *line != ':' && *line != '"' && !isspace(*line))
1297     line++;
1298   if (*line != ':')
1299     line = s;
1300   else
1301     {
1302       h->visual = (char *) malloc (line-s+1);
1303       strncpy (h->visual, s, line-s);
1304       h->visual[line-s] = 0;
1305       if (*line == ':') line++;                 /* skip ":" */
1306       while (isspace(*line)) line++;            /* skip whitespace */
1307     }
1308
1309   if (*line == '"')                             /* handle "name" */
1310     {
1311       line++;
1312       s = line;
1313       while (*line && *line != '"')
1314         line++;
1315       h->name = (char *) malloc (line-s+1);
1316       strncpy (h->name, s, line-s);
1317       h->name[line-s] = 0;
1318       if (*line == '"') line++;                 /* skip "\"" */
1319       while (isspace(*line)) line++;            /* skip whitespace */
1320     }
1321
1322   h->command = format_command (line, False);    /* handle command */
1323   return h;
1324 }
1325
1326
1327 static char *
1328 format_command (const char *cmd, Bool wrap_p)
1329 {
1330   int tab = 30;
1331   int col = tab;
1332   char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
1333   const char *in = cmd;
1334   char *out = cmd2;
1335   while (*in)
1336     {
1337       /* shrink all whitespace to one space, for the benefit of the "demo"
1338          mode display.  We only do this when we can easily tell that the
1339          whitespace is not significant (no shell metachars).
1340        */
1341       switch (*in)
1342         {
1343         case '\'': case '"': case '`': case '\\':
1344           /* Metachars are scary.  Copy the rest of the line unchanged. */
1345           while (*in)
1346             *out++ = *in++, col++;
1347           break;
1348
1349         case ' ': case '\t':
1350           /* Squeeze all other whitespace down to one space. */
1351           while (*in == ' ' || *in == '\t')
1352             in++;
1353           *out++ = ' ', col++;
1354           break;
1355
1356         default:
1357           /* Copy other chars unchanged. */
1358           *out++ = *in++, col++;
1359           break;
1360         }
1361     }
1362
1363   *out = 0;
1364
1365   /* Strip trailing whitespace */
1366   while (out > cmd2 && isspace (out[-1]))
1367     *(--out) = 0;
1368
1369   return cmd2;
1370 }
1371
1372
1373 /* Returns a new string describing the shell command.
1374    This may be just the name of the program, capitalized.
1375    It also may be something from the resource database (gotten
1376    by looking for "hacks.XYZ.name", where XYZ is the program.)
1377  */
1378 char *
1379 make_hack_name (Display *dpy, const char *shell_command)
1380 {
1381   char *s = strdup (shell_command);
1382   char *s2;
1383   char res_name[255];
1384
1385   for (s2 = s; *s2; s2++)       /* truncate at first whitespace */
1386     if (isspace (*s2))
1387       {
1388         *s2 = 0;
1389         break;
1390       }
1391
1392   s2 = strrchr (s, '/');        /* if pathname, take last component */
1393   if (s2)
1394     {
1395       s2 = strdup (s2+1);
1396       free (s);
1397       s = s2;
1398     }
1399
1400   if (strlen (s) > 50)          /* 51 is hereby defined as "unreasonable" */
1401     s[50] = 0;
1402
1403   sprintf (res_name, "hacks.%s.name", s);               /* resource? */
1404   s2 = get_string_resource (dpy, res_name, res_name);
1405   if (s2)
1406     {
1407       free (s);
1408       return s2;
1409     }
1410
1411   for (s2 = s; *s2; s2++)       /* if it has any capitals, return it */
1412     if (*s2 >= 'A' && *s2 <= 'Z')
1413       return s;
1414
1415   if (s[0] >= 'a' && s[0] <= 'z')                       /* else cap it */
1416     s[0] -= 'a'-'A';
1417   if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z')        /* (magic leading X) */
1418     s[1] -= 'a'-'A';
1419   if (s[0] == 'G' && s[1] == 'l' && 
1420       s[2] >= 'a' && s[2] <= 'z')                      /* (magic leading GL) */
1421     s[1] -= 'a'-'A',
1422     s[2] -= 'a'-'A';
1423   return s;
1424 }
1425
1426
1427 char *
1428 format_hack (Display *dpy, screenhack *hack, Bool wrap_p)
1429 {
1430   int tab = 32;
1431   int size;
1432   char *h2, *out, *s;
1433   int col = 0;
1434
1435   char *def_name = make_hack_name (dpy, hack->command);
1436
1437   /* Don't ever write out a name for a hack if it's the same as the default.
1438    */
1439   if (hack->name && !strcmp (hack->name, def_name))
1440     {
1441       free (hack->name);
1442       hack->name = 0;
1443     }
1444   free (def_name);
1445
1446   size = (2 * (strlen(hack->command) +
1447                (hack->visual ? strlen(hack->visual) : 0) +
1448                (hack->name ? strlen(hack->name) : 0) +
1449                tab));
1450   h2 = (char *) malloc (size);
1451   out = h2;
1452
1453   if (!hack->enabled_p) *out++ = '-';           /* write disabled flag */
1454
1455   if (hack->visual && *hack->visual)            /* write visual name */
1456     {
1457       if (hack->enabled_p) *out++ = ' ';
1458       *out++ = ' ';
1459       strcpy (out, hack->visual);
1460       out += strlen (hack->visual);
1461       *out++ = ':';
1462       *out++ = ' ';
1463     }
1464
1465   *out = 0;
1466   col = string_columns (h2, strlen (h2), 0);
1467
1468   if (hack->name && *hack->name)                /* write pretty name */
1469     {
1470       int L = (strlen (hack->name) + 2);
1471       if (L + col < tab)
1472         out = stab_to (out, col, tab - L - 2);
1473       else
1474         *out++ = ' ';
1475       *out++ = '"';
1476       strcpy (out, hack->name);
1477       out += strlen (hack->name);
1478       *out++ = '"';
1479       *out = 0;
1480
1481       col = string_columns (h2, strlen (h2), 0);
1482       if (wrap_p && col >= tab)
1483         out = stab_to (out, col, 77);
1484       else
1485         *out++ = ' ';
1486
1487       if (out >= h2+size) abort();
1488     }
1489
1490   *out = 0;
1491   col = string_columns (h2, strlen (h2), 0);
1492   out = stab_to (out, col, tab);                /* indent */
1493
1494   if (out >= h2+size) abort();
1495   s = format_command (hack->command, wrap_p);
1496   strcpy (out, s);
1497   out += strlen (s);
1498   free (s);
1499   *out = 0;
1500
1501   return h2;
1502 }
1503
1504
1505 static void
1506 get_screenhacks (Display *dpy, saver_preferences *p)
1507 {
1508   int i, j;
1509   int start = 0;
1510   int end = 0;
1511   int size;
1512   char *d;
1513
1514   d = get_string_resource (dpy, "monoPrograms", "MonoPrograms");
1515   if (d && !*d) { free(d); d = 0; }
1516   if (!d)
1517     d = get_string_resource (dpy, "colorPrograms", "ColorPrograms");
1518   if (d && !*d) { free(d); d = 0; }
1519
1520   if (d)
1521     {
1522       fprintf (stderr,
1523        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1524         see the manual for details.\n", blurb());
1525       free(d);
1526     }
1527
1528   d = get_string_resource (dpy, "programs", "Programs");
1529
1530   free_screenhack_list (p->screenhacks, p->screenhacks_count);
1531   p->screenhacks = 0;
1532   p->screenhacks_count = 0;
1533
1534   if (!d || !*d)
1535     return;
1536
1537   size = strlen (d);
1538
1539
1540   /* Count up the number of newlines (which will be equal to or larger than
1541      one less than the number of hacks.)
1542    */
1543   for (i = j = 0; d[i]; i++)
1544     if (d[i] == '\n')
1545       j++;
1546   j++;
1547
1548   p->screenhacks = (screenhack **) calloc (j + 1, sizeof (screenhack *));
1549
1550   /* Iterate over the lines in `d' (the string with newlines)
1551      and make new strings to stuff into the `screenhacks' array.
1552    */
1553   p->screenhacks_count = 0;
1554   while (start < size)
1555     {
1556       /* skip forward over whitespace. */
1557       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1558         start++;
1559
1560       /* skip forward to newline or end of string. */
1561       end = start;
1562       while (d[end] != 0 && d[end] != '\n')
1563         end++;
1564
1565       /* null terminate. */
1566       d[end] = 0;
1567
1568       p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1569       if (p->screenhacks_count >= i)
1570         abort();
1571
1572       start = end+1;
1573     }
1574
1575   free (d);
1576
1577   if (p->screenhacks_count == 0)
1578     {
1579       free (p->screenhacks);
1580       p->screenhacks = 0;
1581     }
1582 }
1583
1584
1585 /* Make sure all the values in the preferences struct are sane.
1586  */
1587 static void
1588 stop_the_insanity (saver_preferences *p)
1589 {
1590   if (p->passwd_timeout <= 0) p->passwd_timeout = 30000;         /* 30 secs */
1591   if (p->timeout < 15000) p->timeout = 15000;                    /* 15 secs */
1592   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
1593   if (p->pointer_timeout <= 0) p->pointer_timeout = 5000;        /*  5 secs */
1594   if (p->notice_events_timeout <= 0)
1595     p->notice_events_timeout = 10000;                            /* 10 secs */
1596   if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
1597     p->fade_p = False;
1598   if (! p->fade_p) p->unfade_p = False;
1599
1600   /* The DPMS settings may have the value 0.
1601      But if they are negative, or are a range less than 10 seconds,
1602      reset them to sensible defaults.  (Since that must be a mistake.)
1603    */
1604   if (p->dpms_standby != 0 &&
1605       p->dpms_standby < 10 * 1000)
1606     p->dpms_standby =  2 * 60 * 60 * 1000;                       /* 2 hours */
1607   if (p->dpms_suspend != 0 &&
1608       p->dpms_suspend < 10 * 1000)
1609     p->dpms_suspend =  2 * 60 * 60 * 1000;                       /* 2 hours */
1610   if (p->dpms_off != 0 &&
1611       p->dpms_off < 10 * 1000)
1612     p->dpms_off      = 4 * 60 * 60 * 1000;                       /* 4 hours */
1613
1614   /* suspend may not be greater than off, unless off is 0.
1615      standby may not be greater than suspend, unless suspend is 0.
1616    */
1617   if (p->dpms_off != 0 &&
1618       p->dpms_suspend > p->dpms_off)
1619     p->dpms_suspend = p->dpms_off;
1620   if (p->dpms_suspend != 0 &&
1621       p->dpms_standby > p->dpms_suspend)
1622     p->dpms_standby = p->dpms_suspend;
1623
1624   /* These fixes above ignores the case
1625      suspend = 0 and standby > off ...
1626    */
1627   if (p->dpms_off != 0 &&
1628       p->dpms_standby > p->dpms_off)
1629     p->dpms_standby = p->dpms_off;
1630
1631
1632   if (p->dpms_standby == 0 &&      /* if *all* are 0, then DPMS is disabled */
1633       p->dpms_suspend == 0 &&
1634       p->dpms_off     == 0)
1635     p->dpms_enabled_p = False;
1636
1637
1638   /* Set watchdog timeout to about half of the cycle timeout, but
1639      don't let it be faster than 1/2 minute or slower than 1 minute.
1640    */
1641   p->watchdog_timeout = p->cycle * 0.6;
1642   if (p->watchdog_timeout < 27000) p->watchdog_timeout = 27000;   /* 27 secs */
1643   if (p->watchdog_timeout > 57000) p->watchdog_timeout = 57000;   /* 57 secs */
1644
1645   if (p->pointer_hysteresis < 0)   p->pointer_hysteresis = 0;
1646   if (p->pointer_hysteresis > 100) p->pointer_hysteresis = 100;
1647 }