ae49086429ef5e09e9505d534d8e5838acd542c7
[xscreensaver] / driver / prefs.c
1 /* dotfile.c --- management of the ~/.xscreensaver file.
2  * xscreensaver, Copyright (c) 1998 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 <sys/stat.h>
27 #include <sys/time.h>
28
29 #include <X11/Xlib.h>
30 #include <X11/Xresource.h>
31
32 #ifndef VMS
33 # include <pwd.h>
34 #else /* VMS */
35 # include "vms-pwd.h"
36 #endif /* VMS */
37
38
39 /* This file doesn't need the Xt headers, so stub these types out... */
40 #undef XtPointer
41 #define XtAppContext void*
42 #define XtIntervalId void*
43 #define XtPointer    void*
44 #define Widget       void*
45
46
47 /* Just in case there's something pathological about stat.h... */
48 #ifndef  S_IRUSR
49 # define S_IRUSR 00400
50 #endif
51 #ifndef  S_IWUSR
52 # define S_IWUSR 00200
53 #endif
54 #ifndef  S_IXUSR
55 # define S_IXUSR 00100
56 #endif
57 #ifndef  S_IXGRP
58 # define S_IXGRP 00010
59 #endif
60 #ifndef  S_IXOTH
61 # define S_IXOTH 00001
62 #endif
63
64
65 #include "prefs.h"
66 #include "resources.h"
67
68
69 extern char *progname;
70 extern char *progclass;
71 extern const char *blurb (void);
72
73
74
75 static void get_screenhacks (saver_preferences *p);
76 static char *format_command (const char *cmd, Bool wrap_p);
77
78
79 static char *
80 chase_symlinks (const char *file)
81 {
82 # ifdef HAVE_REALPATH
83   if (file)
84     {
85       char buf [2048];
86       if (realpath (file, buf))
87         return strdup (buf);
88
89       sprintf (buf, "%s: realpath", blurb());
90       perror(buf);
91     }
92 # endif /* HAVE_REALPATH */
93   return 0;
94 }
95
96
97 const char *
98 init_file_name (void)
99 {
100   static char *file = 0;
101
102   if (!file)
103     {
104       struct passwd *p = getpwuid (getuid ());
105
106       if (!p || !p->pw_name || !*p->pw_name)
107         {
108           fprintf (stderr, "%s: couldn't get user info of uid %d\n",
109                    blurb(), getuid ());
110           file = "";
111         }
112       else if (!p->pw_dir || !*p->pw_dir)
113         {
114           fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
115                    blurb(), (p->pw_name ? p->pw_name : "???"));
116           file = "";
117         }
118       else
119         {
120           const char *home = p->pw_dir;
121           const char *name = ".xscreensaver";
122           file = (char *) malloc(strlen(home) + strlen(name) + 2);
123           strcpy(file, home);
124           if (!*home || home[strlen(home)-1] != '/')
125             strcat(file, "/");
126           strcat(file, name);
127         }
128     }
129
130   if (file && *file)
131     return file;
132   else
133     return 0;
134 }
135
136
137 static const char *
138 init_file_tmp_name (void)
139 {
140   static char *file = 0;
141   if (!file)
142     {
143       const char *name = init_file_name();
144       const char *suffix = ".tmp";
145
146       char *n2 = chase_symlinks (name);
147       if (n2) name = n2;
148
149       if (!name || !*name)
150         file = "";
151       else
152         {
153           file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
154           strcpy(file, name);
155           strcat(file, suffix);
156         }
157
158       if (n2) free (n2);
159     }
160
161   if (file && *file)
162     return file;
163   else
164     return 0;
165 }
166
167
168 static const char * const prefs[] = {
169   "timeout",
170   "cycle",
171   "lock",
172   "lockVTs",
173   "lockTimeout",
174   "passwdTimeout",
175   "visualID",
176   "installColormap",
177   "verbose",
178   "timestamp",
179   "splash",                     /* not saved -- same as "splashDuration: 0" */
180   "splashDuration",
181   "demoCommand",
182   "prefsCommand",
183   "helpURL",
184   "loadURL",
185   "nice",
186   "fade",
187   "unfade",
188   "fadeSeconds",
189   "fadeTicks",
190   "captureStderr",
191   "captureStdout",              /* not saved -- obsolete */
192   "font",
193   "",
194   "programs",
195   "",
196   "pointerPollTime",
197   "windowCreationTimeout",
198   "initialDelay",
199   "sgiSaverExtension",
200   "mitSaverExtension",
201   "xidleExtension",
202   "procInterrupts",
203   "overlayStderr",
204   "overlayTextBackground",      /* not saved -- X resources only */
205   "overlayTextForeground",      /* not saved -- X resources only */
206   "bourneShell",                /* not saved -- X resources only */
207   0
208 };
209
210 static char *
211 strip (char *s)
212 {
213   char *s2;
214   while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
215     s++;
216   for (s2 = s; *s2; s2++)
217     ;
218   for (s2--; s2 >= s; s2--) 
219     if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n') 
220       *s2 = 0;
221     else
222       break;
223   return s;
224 }
225
226 \f
227 /* Reading
228  */
229
230 static int
231 handle_entry (XrmDatabase *db, const char *key, const char *value,
232               const char *filename, int line)
233 {
234   int i;
235   for (i = 0; prefs[i]; i++)
236     if (*prefs[i] && !strcasecmp(key, prefs[i]))
237       {
238         char *val = strdup(value);
239         char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
240         strcpy(spec, progclass);
241         strcat(spec, ".");
242         strcat(spec, prefs[i]);
243
244         XrmPutStringResource (db, spec, val);
245
246         free(spec);
247         free(val);
248         return 0;
249       }
250
251   fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
252           blurb(), filename, line, key);
253   return 1;
254 }
255
256
257 static int
258 parse_init_file (saver_preferences *p)
259 {
260   time_t write_date = 0;
261   const char *name = init_file_name();
262   int line = 0;
263   struct stat st;
264   FILE *in;
265   int buf_size = 1024;
266   char *buf;
267
268   if (!name) return 0;
269
270   if (stat(name, &st) != 0)
271     {
272       p->init_file_date = 0;
273       return 0;
274     }
275
276   in = fopen(name, "r");
277   if (!in)
278     {
279       char *buf = (char *) malloc(1024 + strlen(name));
280       sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
281       perror(buf);
282       free(buf);
283       return -1;
284     }
285
286   if (fstat (fileno(in), &st) == 0)
287     {
288       write_date = st.st_mtime;
289     }
290   else
291     {
292       char *buf = (char *) malloc(1024 + strlen(name));
293       sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
294       perror(buf);
295       free(buf);
296       return -1;
297     }
298
299   buf = (char *) malloc(buf_size);
300
301   while (fgets (buf, buf_size-1, in))
302     {
303       char *key, *value;
304       int L = strlen(buf);
305
306       line++;
307       while (L > 2 &&
308              (buf[L-1] != '\n' ||       /* whole line didn't fit in buffer */
309               buf[L-2] == '\\'))        /* or line ended with backslash */
310         {
311           if (buf[L-2] == '\\')         /* backslash-newline gets swallowed */
312             {
313               buf[L-2] = 0;
314               L -= 2;
315             }
316           buf_size += 1024;
317           buf = (char *) realloc(buf, buf_size);
318           if (!buf) exit(1);
319
320           line++;
321           if (!fgets (buf + L, buf_size-L-1, in))
322             break;
323           L = strlen(buf);
324         }
325
326       /* Now handle other backslash escapes. */
327       {
328         int i, j;
329         for (i = 0; buf[i]; i++)
330           if (buf[i] == '\\')
331             {
332               switch (buf[i+1])
333                 {
334                 case 'n': buf[i] = '\n'; break;
335                 case 'r': buf[i] = '\r'; break;
336                 case 't': buf[i] = '\t'; break;
337                 default:  buf[i] = buf[i+1]; break;
338                 }
339               for (j = i+2; buf[j]; j++)
340                 buf[j-1] = buf[j];
341               buf[j-1] = 0;
342             }
343       }
344
345       key = strip(buf);
346
347       if (*key == '#' || *key == '!' || *key == ';' ||
348           *key == '\n' || *key == 0)
349         continue;
350
351       value = strchr (key, ':');
352       if (!value)
353         {
354           fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
355                   name, line, key);
356           continue;
357         }
358       else
359         {
360           *value++ = 0;
361           value = strip(value);
362         }
363
364       if (!p->db) abort();
365       handle_entry (&p->db, key, value, name, line);
366     }
367   fclose (in);
368   free(buf);
369
370   p->init_file_date = write_date;
371   return 0;
372 }
373
374
375 Bool
376 init_file_changed_p (saver_preferences *p)
377 {
378   const char *name = init_file_name();
379   struct stat st;
380
381   if (!name) return False;
382
383   if (stat(name, &st) != 0)
384     return False;
385
386   if (p->init_file_date == st.st_mtime)
387     return False;
388
389   return True;
390 }
391
392 \f
393 /* Writing
394  */
395
396 static int
397 tab_to (FILE *out, int from, int to)
398 {
399   int tab_width = 8;
400   int to_mod = (to / tab_width) * tab_width;
401   while (from < to_mod)
402     {
403       fprintf(out, "\t");
404       from = (((from / tab_width) + 1) * tab_width);
405     }
406   while (from < to)
407     {
408       fprintf(out, " ");
409       from++;
410     }
411   return from;
412 }
413
414 static char *
415 stab_to (char *out, int from, int to)
416 {
417   int tab_width = 8;
418   int to_mod = (to / tab_width) * tab_width;
419   while (from < to_mod)
420     {
421       *out++ = '\t';
422       from = (((from / tab_width) + 1) * tab_width);
423     }
424   while (from < to)
425     {
426       *out++ = ' ';
427       from++;
428     }
429   return out;
430 }
431
432 static int
433 string_columns (const char *string, int length, int start)
434 {
435   int tab_width = 8;
436   int col = start;
437   const char *end = string + length;
438   while (string < end)
439     {
440       if (*string == '\n')
441         col = 0;
442       else if (*string == '\t')
443         col = (((col / tab_width) + 1) * tab_width);
444       else
445         col++;
446       string++;
447     }
448   return col;
449 }
450
451
452 static void
453 write_entry (FILE *out, const char *key, const char *value)
454 {
455   char *v = strdup(value ? value : "");
456   char *v2 = v;
457   char *nl = 0;
458   int col;
459   Bool programs_p = (!strcmp(key, "programs"));
460   int tab = (programs_p ? 32 : 16);
461   Bool first = True;
462
463   fprintf(out, "%s:", key);
464   col = strlen(key) + 1;
465
466   while (1)
467     {
468       if (!programs_p)
469         v2 = strip(v2);
470       nl = strchr(v2, '\n');
471       if (nl)
472         *nl = 0;
473
474       if (first && programs_p)
475         {
476           col = tab_to (out, col, 77);
477           fprintf (out, " \\\n");
478           col = 0;
479         }
480
481       if (first)
482         first = False;
483       else
484         {
485           col = tab_to (out, col, 75);
486           fprintf (out, " \\n\\\n");
487           col = 0;
488         }
489
490       if (!programs_p)
491         col = tab_to (out, col, tab);
492
493       if (programs_p &&
494           string_columns(v2, strlen (v2), col) + col > 75)
495         {
496           int L = strlen (v2);
497           int start = 0;
498           int end = start;
499           while (start < L)
500             {
501               while (v2[end] == ' ' || v2[end] == '\t')
502                 end++;
503               while (v2[end] != ' ' && v2[end] != '\t' &&
504                      v2[end] != '\n' && v2[end] != 0)
505                 end++;
506               if (string_columns (v2 + start, (end - start), col) >= 74)
507                 {
508                   col = tab_to (out, col, 75);
509                   fprintf(out, "   \\\n");
510                   col = tab_to (out, 0, tab + 2);
511                   while (v2[start] == ' ' || v2[start] == '\t')
512                     start++;
513                 }
514
515               col = string_columns (v2 + start, (end - start), col);
516               while (start < end)
517                 fputc(v2[start++], out);
518             }
519         }
520       else
521         {
522           fprintf (out, "%s", v2);
523           col += string_columns(v2, strlen (v2), col);
524         }
525
526       if (nl)
527         v2 = nl + 1;
528       else
529         break;
530     }
531
532   fprintf(out, "\n");
533   free(v);
534 }
535
536 int
537 write_init_file (saver_preferences *p, const char *version_string,
538                  Bool verbose_p)
539 {
540   int status = -1;
541   const char *name = init_file_name();
542   const char *tmp_name = init_file_tmp_name();
543   char *n2 = chase_symlinks (name);
544   struct stat st;
545   int i, j;
546
547   /* Kludge, since these aren't in the saver_preferences struct as strings...
548    */
549   char *visual_name;
550   char *programs;
551   Bool capture_stderr_p;
552   Bool overlay_stderr_p;
553   char *stderr_font;
554   FILE *out;
555
556   if (!name) goto END;
557
558   if (n2) name = n2;
559
560   if (verbose_p)
561     fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
562
563   unlink (tmp_name);
564   out = fopen(tmp_name, "w");
565   if (!out)
566     {
567       char *buf = (char *) malloc(1024 + strlen(name));
568       sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
569       perror(buf);
570       free(buf);
571       goto END;
572     }
573
574   /* Give the new .xscreensaver file the same permissions as the old one;
575      except ensure that it is readable and writable by owner, and not
576      executable.  Extra hack: if we're running as root, make the file
577      be world-readable (so that the daemon, running as "nobody", will
578      still be able to read it.)
579    */
580   if (stat(name, &st) == 0)
581     {
582       mode_t mode = st.st_mode;
583       mode |= S_IRUSR | S_IWUSR;                /* read/write by user */
584       mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);   /* executable by none */
585
586       if (getuid() == (uid_t) 0)                /* read by group/other */
587         mode |= S_IRGRP | S_IROTH;
588
589       if (fchmod (fileno(out), mode) != 0)
590         {
591           char *buf = (char *) malloc(1024 + strlen(name));
592           sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
593                    tmp_name, (unsigned int) mode);
594           perror(buf);
595           free(buf);
596           goto END;
597         }
598     }
599
600   /* Kludge, since these aren't in the saver_preferences struct... */
601   visual_name = get_string_resource ("visualID", "VisualID");
602   programs = 0;
603   capture_stderr_p = get_boolean_resource ("captureStderr", "Boolean");
604   overlay_stderr_p = get_boolean_resource ("overlayStderr", "Boolean");
605   stderr_font = get_string_resource ("font", "Font");
606
607   i = 0;
608   {
609     char *ss;
610     char **hack_strings = (char **)
611       calloc (p->screenhacks_count, sizeof(char *));
612
613     for (j = 0; j < p->screenhacks_count; j++)
614       {
615         hack_strings[j] = format_hack (p->screenhacks[j], True);
616         i += strlen (hack_strings[j]);
617         i += 2;
618       }
619
620     ss = programs = (char *) malloc(i + 10);
621     *ss = 0;
622     for (j = 0; j < p->screenhacks_count; j++)
623       {
624         strcat (ss, hack_strings[j]);
625         free (hack_strings[j]);
626         ss += strlen(ss);
627         *ss++ = '\n';
628         *ss = 0;
629       }
630   }
631
632   {
633     struct passwd *pw = getpwuid (getuid ());
634     char *whoami = (pw && pw->pw_name && *pw->pw_name
635                     ? pw->pw_name
636                     : "<unknown>");
637     time_t now = time ((time_t *) 0);
638     char *timestr = (char *) ctime (&now);
639     char *nl = (char *) strchr (timestr, '\n');
640     if (nl) *nl = 0;
641     fprintf (out,
642              "# %s Preferences File\n"
643              "# Written by %s %s for %s on %s.\n"
644              "# http://www.jwz.org/xscreensaver/\n"
645              "\n",
646              progclass, progname, version_string, whoami, timestr);
647   }
648
649   for (j = 0; prefs[j]; j++)
650     {
651       char buf[255];
652       const char *pr = prefs[j];
653       enum pref_type { pref_str, pref_int, pref_bool, pref_time
654       } type = pref_str;
655       const char *s = 0;
656       int i = 0;
657       Bool b = False;
658       Time t = 0;
659
660       if (pr && !*pr)
661         {
662           fprintf(out, "\n");
663           continue;
664         }
665
666 # undef CHECK
667 # define CHECK(X) else if (!strcmp(pr, X))
668       if (!pr || !*pr)          ;
669       CHECK("timeout")          type = pref_time, t = p->timeout;
670       CHECK("cycle")            type = pref_time, t = p->cycle;
671       CHECK("lock")             type = pref_bool, b = p->lock_p;
672 # if 0 /* #### not ready yet */
673       CHECK("lockVTs")          type = pref_bool, b = p->lock_vt_p;
674 # else
675       CHECK("lockVTs")          continue;  /* don't save */
676 # endif
677       CHECK("lockTimeout")      type = pref_time, t = p->lock_timeout;
678       CHECK("passwdTimeout")    type = pref_time, t = p->passwd_timeout;
679       CHECK("visualID")         type = pref_str,  s =    visual_name;
680       CHECK("installColormap")  type = pref_bool, b = p->install_cmap_p;
681       CHECK("verbose")          type = pref_bool, b = p->verbose_p;
682       CHECK("timestamp")        type = pref_bool, b = p->timestamp_p;
683       CHECK("splash")           continue;  /* don't save */
684       CHECK("splashDuration")   type = pref_time, t = p->splash_duration;
685       CHECK("demoCommand")      type = pref_str,  s = p->demo_command;
686       CHECK("prefsCommand")     type = pref_str,  s = p->prefs_command;
687       CHECK("helpURL")          type = pref_str,  s = p->help_url;
688       CHECK("loadURL")          type = pref_str,  s = p->load_url_command;
689       CHECK("nice")             type = pref_int,  i = p->nice_inferior;
690       CHECK("fade")             type = pref_bool, b = p->fade_p;
691       CHECK("unfade")           type = pref_bool, b = p->unfade_p;
692       CHECK("fadeSeconds")      type = pref_time, t = p->fade_seconds;
693       CHECK("fadeTicks")        type = pref_int,  i = p->fade_ticks;
694       CHECK("captureStderr")    type = pref_bool, b =    capture_stderr_p;
695       CHECK("captureStdout")    continue;  /* don't save */
696       CHECK("font")             type = pref_str,  s =    stderr_font;
697       CHECK("programs")         type = pref_str,  s =    programs;
698       CHECK("pointerPollTime")  type = pref_time, t = p->pointer_timeout;
699       CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
700       CHECK("initialDelay")     type = pref_time, t = p->initial_delay;
701       CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension;
702       CHECK("mitSaverExtension")type = pref_bool, b=p->use_mit_saver_extension;
703       CHECK("xidleExtension")   type = pref_bool, b = p->use_xidle_extension;
704       CHECK("procInterrupts")   type = pref_bool, b = p->use_proc_interrupts;
705       CHECK("overlayStderr")    type = pref_bool, b = overlay_stderr_p;
706       CHECK("overlayTextBackground") continue;  /* don't save */
707       CHECK("overlayTextForeground") continue;  /* don't save */
708       CHECK("bourneShell")      continue;
709       else                      abort();
710 # undef CHECK
711
712       switch (type)
713         {
714         case pref_str:
715           break;
716         case pref_int:
717           sprintf(buf, "%d", i);
718           s = buf;
719           break;
720         case pref_bool:
721           s = b ? "True" : "False";
722           break;
723         case pref_time:
724           {
725             unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
726             if (sec >= 60)
727               {
728                 min += (sec / 60);
729                 sec %= 60;
730               }
731             if (min >= 60)
732               {
733                 hour += (min / 60);
734                 min %= 60;
735               }
736             sprintf (buf, "%u:%02u:%02u", hour, min, sec);
737             s = buf;
738           }
739           break;
740         default:
741           abort();
742           break;
743         }
744       write_entry (out, pr, s);
745     }
746
747   fprintf(out, "\n");
748
749   if (visual_name) free(visual_name);
750   if (stderr_font) free(stderr_font);
751   if (programs) free(programs);
752
753   if (fclose(out) == 0)
754     {
755       time_t write_date = 0;
756
757       if (stat(tmp_name, &st) == 0)
758         {
759           write_date = st.st_mtime;
760         }
761       else
762         {
763           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
764           sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
765           perror(buf);
766           unlink (tmp_name);
767           free(buf);
768           goto END;
769         }
770
771       if (rename (tmp_name, name) != 0)
772         {
773           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
774           sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
775                   blurb(), tmp_name, name);
776           perror(buf);
777           unlink (tmp_name);
778           free(buf);
779           goto END;
780         }
781       else
782         {
783           p->init_file_date = write_date;
784
785           /* Since the .xscreensaver file is used for IPC, let's try and make
786              sure that the bits actually land on the disk right away. */
787           sync ();
788
789           status = 0;    /* wrote and renamed successfully! */
790         }
791     }
792   else
793     {
794       char *buf = (char *) malloc(1024 + strlen(name));
795       sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
796       perror(buf);
797       free(buf);
798       unlink (tmp_name);
799       goto END;
800     }
801
802  END:
803   if (n2) free (n2);
804   return status;
805 }
806
807 \f
808 /* Parsing the resource database
809  */
810
811
812 /* Populate `saver_preferences' with the contents of the resource database.
813    Note that this may be called multiple times -- it is re-run each time
814    the ~/.xscreensaver file is reloaded.
815
816    This function can be very noisy, since it issues resource syntax errors
817    and so on.
818  */
819 void
820 load_init_file (saver_preferences *p)
821 {
822   static Bool first_time = True;
823   
824   if (parse_init_file (p) != 0)         /* file might have gone away */
825     if (!first_time) return;
826
827   first_time = False;
828
829   p->xsync_p        = get_boolean_resource ("synchronous", "Synchronous");
830   p->verbose_p      = get_boolean_resource ("verbose", "Boolean");
831   p->timestamp_p    = get_boolean_resource ("timestamp", "Boolean");
832   p->lock_p         = get_boolean_resource ("lock", "Boolean");
833   p->lock_vt_p      = get_boolean_resource ("lockVTs", "Boolean");
834   p->fade_p         = get_boolean_resource ("fade", "Boolean");
835   p->unfade_p       = get_boolean_resource ("unfade", "Boolean");
836   p->fade_seconds   = 1000 * get_seconds_resource ("fadeSeconds", "Time");
837   p->fade_ticks     = get_integer_resource ("fadeTicks", "Integer");
838   p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
839   p->nice_inferior  = get_integer_resource ("nice", "Nice");
840
841   p->initial_delay   = 1000 * get_seconds_resource ("initialDelay", "Time");
842   p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
843   p->timeout         = 1000 * get_minutes_resource ("timeout", "Time");
844   p->lock_timeout    = 1000 * get_minutes_resource ("lockTimeout", "Time");
845   p->cycle           = 1000 * get_minutes_resource ("cycle", "Time");
846   p->passwd_timeout  = 1000 * get_seconds_resource ("passwdTimeout", "Time");
847   p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
848   p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
849                                                        "Time");
850   p->shell = get_string_resource ("bourneShell", "BourneShell");
851
852   p->demo_command = get_string_resource("demoCommand", "URL");
853   p->prefs_command = get_string_resource("prefsCommand", "URL");
854   p->help_url = get_string_resource("helpURL", "URL");
855   p->load_url_command = get_string_resource("loadURL", "LoadURL");
856
857   {
858     char *s;
859     if ((s = get_string_resource ("splash", "Boolean")))
860       if (!get_boolean_resource("splash", "Boolean"))
861         p->splash_duration = 0;
862     if (s) free (s);
863   }
864
865   p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
866   p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
867                                                      "Boolean");
868   p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
869                                                      "Boolean");
870   p->use_proc_interrupts = get_boolean_resource ("procInterrupts", "Boolean");
871
872   /* Throttle the various timeouts to reasonable values.
873    */
874   if (p->passwd_timeout <= 0) p->passwd_timeout = 30000;         /* 30 secs */
875   if (p->timeout < 10000) p->timeout = 10000;                    /* 10 secs */
876   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
877   if (p->pointer_timeout <= 0) p->pointer_timeout = 5000;        /*  5 secs */
878   if (p->notice_events_timeout <= 0)
879     p->notice_events_timeout = 10000;                            /* 10 secs */
880   if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
881     p->fade_p = False;
882   if (! p->fade_p) p->unfade_p = False;
883
884   p->watchdog_timeout = p->cycle * 0.6;
885   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
886   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
887
888   get_screenhacks (p);
889
890   if (p->debug_p)
891     {
892       p->xsync_p = True;
893       p->verbose_p = True;
894       p->timestamp_p = True;
895       p->initial_delay = 0;
896     }
897 }
898
899 \f
900 /* Parsing the programs resource.
901  */
902
903 screenhack *
904 parse_screenhack (const char *line)
905 {
906   screenhack *h = (screenhack *) calloc (1, sizeof(*h));
907   const char *s;
908
909   h->enabled_p = True;
910
911   while (isspace(*line)) line++;                /* skip whitespace */
912   if (*line == '-')                             /* handle "-" */
913     {
914       h->enabled_p = False;
915       line++;
916       while (isspace(*line)) line++;            /* skip whitespace */
917     }
918
919   s = line;                                     /* handle "visual:" */
920   while (*line && *line != ':' && *line != '"' && !isspace(*line))
921     line++;
922   if (*line != ':')
923     line = s;
924   else
925     {
926       h->visual = (char *) malloc (line-s+1);
927       strncpy (h->visual, s, line-s);
928       h->visual[line-s] = 0;
929       if (*line == ':') line++;                 /* skip ":" */
930       while (isspace(*line)) line++;            /* skip whitespace */
931     }
932
933   if (*line == '"')                             /* handle "name" */
934     {
935       line++;
936       s = line;
937       while (*line && *line != '"')
938         line++;
939       h->name = (char *) malloc (line-s+1);
940       strncpy (h->name, s, line-s);
941       h->name[line-s] = 0;
942       if (*line == '"') line++;                 /* skip "\"" */
943       while (isspace(*line)) line++;            /* skip whitespace */
944     }
945
946   h->command = format_command (line, False);    /* handle command */
947   return h;
948 }
949
950
951 void
952 free_screenhack (screenhack *hack)
953 {
954   if (hack->visual) free (hack->visual);
955   if (hack->name) free (hack->name);
956   free (hack->command);
957   memset (hack, 0, sizeof(*hack));
958   free (hack);
959 }
960
961
962 static char *
963 format_command (const char *cmd, Bool wrap_p)
964 {
965   int tab = 30;
966   int col = tab;
967   char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
968   const char *in = cmd;
969   char *out = cmd2;
970   while (*in)
971     {
972       /* shrink all whitespace to one space, for the benefit of the "demo"
973          mode display.  We only do this when we can easily tell that the
974          whitespace is not significant (no shell metachars).
975        */
976       switch (*in)
977         {
978         case '\'': case '"': case '`': case '\\':
979           /* Metachars are scary.  Copy the rest of the line unchanged. */
980           while (*in)
981             *out++ = *in++, col++;
982           break;
983
984         case ' ': case '\t':
985           /* Squeeze all other whitespace down to one space. */
986           while (*in == ' ' || *in == '\t')
987             in++;
988           *out++ = ' ', col++;
989           break;
990
991         default:
992           /* Copy other chars unchanged. */
993           *out++ = *in++, col++;
994           break;
995         }
996     }
997
998   *out = 0;
999
1000   /* Strip trailing whitespace */
1001   while (out > cmd2 && isspace (out[-1]))
1002     *(--out) = 0;
1003
1004   return cmd2;
1005 }
1006
1007
1008 char *
1009 format_hack (screenhack *hack, Bool wrap_p)
1010 {
1011   int tab = 32;
1012   int size = (2 * (strlen(hack->command) +
1013                    (hack->visual ? strlen(hack->visual) : 0) +
1014                    (hack->name ? strlen(hack->name) : 0) +
1015                    tab));
1016   char *h2 = (char *) malloc (size);
1017   char *out = h2;
1018   char *s;
1019   int col = 0;
1020
1021   if (!hack->enabled_p) *out++ = '-';           /* write disabled flag */
1022
1023   if (hack->visual && *hack->visual)            /* write visual name */
1024     {
1025       if (hack->enabled_p) *out++ = ' ';
1026       *out++ = ' ';
1027       strcpy (out, hack->visual);
1028       out += strlen (hack->visual);
1029       *out++ = ':';
1030       *out++ = ' ';
1031     }
1032
1033   *out = 0;
1034   col = string_columns (h2, strlen (h2), 0);
1035
1036   if (hack->name && *hack->name)                /* write pretty name */
1037     {
1038       int L = (strlen (hack->name) + 2);
1039       if (L + col < tab)
1040         out = stab_to (out, col, tab - L - 2);
1041       else
1042         *out++ = ' ';
1043       *out++ = '"';
1044       strcpy (out, hack->name);
1045       out += strlen (hack->name);
1046       *out++ = '"';
1047       *out = 0;
1048
1049       col = string_columns (h2, strlen (h2), 0);
1050       if (wrap_p && col >= tab)
1051         {
1052           out = stab_to (out, col, 77);
1053           *out += strlen(out);
1054         }
1055       else
1056         *out++ = ' ';
1057
1058       if (out >= h2+size) abort();
1059     }
1060
1061   *out = 0;
1062   col = string_columns (h2, strlen (h2), 0);
1063   out = stab_to (out, col, tab);                /* indent */
1064
1065   if (out >= h2+size) abort();
1066   s = format_command (hack->command, wrap_p);
1067   strcpy (out, s);
1068   out += strlen (s);
1069   free (s);
1070   *out = 0;
1071
1072   return h2;
1073 }
1074
1075
1076 static void
1077 get_screenhacks (saver_preferences *p)
1078 {
1079   int i = 0;
1080   int start = 0;
1081   int end = 0;
1082   int size;
1083   char *d;
1084
1085   d = get_string_resource ("monoPrograms", "MonoPrograms");
1086   if (d && !*d) { free(d); d = 0; }
1087   if (!d)
1088     d = get_string_resource ("colorPrograms", "ColorPrograms");
1089   if (d && !*d) { free(d); d = 0; }
1090
1091   if (d)
1092     {
1093       fprintf (stderr,
1094        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1095         see the manual for details.\n", blurb());
1096       free(d);
1097     }
1098
1099   d = get_string_resource ("programs", "Programs");
1100
1101   if (p->screenhacks)
1102     {
1103       for (i = 0; i < p->screenhacks_count; i++)
1104         if (p->screenhacks[i])
1105           free_screenhack (p->screenhacks[i]);
1106       free(p->screenhacks);
1107       p->screenhacks = 0;
1108     }
1109
1110   if (!d || !*d)
1111     {
1112       p->screenhacks_count = 0;
1113       p->screenhacks = 0;
1114       return;
1115     }
1116
1117   size = strlen (d);
1118
1119
1120   /* Count up the number of newlines (which will be equal to or larger than
1121      the number of hacks.)
1122    */
1123   i = 0;
1124   for (i = 0; d[i]; i++)
1125     if (d[i] == '\n')
1126       i++;
1127   i++;
1128
1129   p->screenhacks = (screenhack **) calloc (sizeof (screenhack *), i+1);
1130
1131   /* Iterate over the lines in `d' (the string with newlines)
1132      and make new strings to stuff into the `screenhacks' array.
1133    */
1134   p->screenhacks_count = 0;
1135   while (start < size)
1136     {
1137       /* skip forward over whitespace. */
1138       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1139         start++;
1140
1141       /* skip forward to newline or end of string. */
1142       end = start;
1143       while (d[end] != 0 && d[end] != '\n')
1144         end++;
1145
1146       /* null terminate. */
1147       d[end] = 0;
1148
1149       p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1150       if (p->screenhacks_count >= i)
1151         abort();
1152
1153       start = end+1;
1154     }
1155
1156   if (p->screenhacks_count == 0)
1157     {
1158       free (p->screenhacks);
1159       p->screenhacks = 0;
1160     }
1161 }