http://ftp.x.org/contrib/applications/xscreensaver-3.20.tar.gz
[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 void
537 write_init_file (saver_preferences *p, const char *version_string)
538 {
539   const char *name = init_file_name();
540   const char *tmp_name = init_file_tmp_name();
541   char *n2 = chase_symlinks (name);
542   struct stat st;
543   int i, j;
544
545   /* Kludge, since these aren't in the saver_preferences struct as strings...
546    */
547   char *visual_name;
548   char *programs;
549   Bool capture_stderr_p;
550   Bool overlay_stderr_p;
551   char *stderr_font;
552   FILE *out;
553
554   if (!name) goto END;
555
556   if (n2) name = n2;
557
558   if (p->verbose_p)
559     fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
560
561   unlink (tmp_name);
562   out = fopen(tmp_name, "w");
563   if (!out)
564     {
565       char *buf = (char *) malloc(1024 + strlen(name));
566       sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
567       perror(buf);
568       free(buf);
569       goto END;
570     }
571
572   /* Give the new .xscreensaver file the same permissions as the old one;
573      except ensure that it is readable and writable by owner, and not
574      executable.
575    */
576   if (stat(name, &st) == 0)
577     {
578       mode_t mode = st.st_mode;
579       mode |= S_IRUSR | S_IWUSR;
580       mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
581       if (fchmod (fileno(out), mode) != 0)
582         {
583           char *buf = (char *) malloc(1024 + strlen(name));
584           sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
585                    tmp_name, (unsigned int) mode);
586           perror(buf);
587           free(buf);
588           goto END;
589         }
590     }
591
592   /* Kludge, since these aren't in the saver_preferences struct... */
593   visual_name = get_string_resource ("visualID", "VisualID");
594   programs = 0;
595   capture_stderr_p = get_boolean_resource ("captureStderr", "Boolean");
596   overlay_stderr_p = get_boolean_resource ("overlayStderr", "Boolean");
597   stderr_font = get_string_resource ("font", "Font");
598
599   i = 0;
600   {
601     char *ss;
602     char **hack_strings = (char **)
603       calloc (p->screenhacks_count, sizeof(char *));
604
605     for (j = 0; j < p->screenhacks_count; j++)
606       {
607         hack_strings[j] = format_hack (p->screenhacks[j], True);
608         i += strlen (hack_strings[j]);
609         i += 2;
610       }
611
612     ss = programs = (char *) malloc(i + 10);
613     *ss = 0;
614     for (j = 0; j < p->screenhacks_count; j++)
615       {
616         strcat (ss, hack_strings[j]);
617         free (hack_strings[j]);
618         ss += strlen(ss);
619         *ss++ = '\n';
620         *ss = 0;
621       }
622   }
623
624   {
625     struct passwd *pw = getpwuid (getuid ());
626     char *whoami = (pw && pw->pw_name && *pw->pw_name
627                     ? pw->pw_name
628                     : "<unknown>");
629     time_t now = time ((time_t *) 0);
630     char *timestr = (char *) ctime (&now);
631     char *nl = (char *) strchr (timestr, '\n');
632     if (nl) *nl = 0;
633     fprintf (out,
634              "# %s Preferences File\n"
635              "# Written by %s %s for %s on %s.\n"
636              "# http://www.jwz.org/xscreensaver/\n"
637              "\n",
638              progclass, progname, version_string, whoami, timestr);
639   }
640
641   for (j = 0; prefs[j]; j++)
642     {
643       char buf[255];
644       const char *pr = prefs[j];
645       enum pref_type { pref_str, pref_int, pref_bool, pref_time
646       } type = pref_str;
647       const char *s = 0;
648       int i = 0;
649       Bool b = False;
650       Time t = 0;
651
652       if (pr && !*pr)
653         {
654           fprintf(out, "\n");
655           continue;
656         }
657
658 # undef CHECK
659 # define CHECK(X) else if (!strcmp(pr, X))
660       if (!pr || !*pr)          ;
661       CHECK("timeout")          type = pref_time, t = p->timeout;
662       CHECK("cycle")            type = pref_time, t = p->cycle;
663       CHECK("lock")             type = pref_bool, b = p->lock_p;
664 # if 0 /* #### not ready yet */
665       CHECK("lockVTs")          type = pref_bool, b = p->lock_vt_p;
666 # else
667       CHECK("lockVTs")          continue;  /* don't save */
668 # endif
669       CHECK("lockTimeout")      type = pref_time, t = p->lock_timeout;
670       CHECK("passwdTimeout")    type = pref_time, t = p->passwd_timeout;
671       CHECK("visualID")         type = pref_str,  s =    visual_name;
672       CHECK("installColormap")  type = pref_bool, b = p->install_cmap_p;
673       CHECK("verbose")          type = pref_bool, b = p->verbose_p;
674       CHECK("timestamp")        type = pref_bool, b = p->timestamp_p;
675       CHECK("splash")           continue;  /* don't save */
676       CHECK("splashDuration")   type = pref_time, t = p->splash_duration;
677       CHECK("demoCommand")      type = pref_str,  s = p->demo_command;
678       CHECK("prefsCommand")     type = pref_str,  s = p->prefs_command;
679       CHECK("helpURL")          type = pref_str,  s = p->help_url;
680       CHECK("loadURL")          type = pref_str,  s = p->load_url_command;
681       CHECK("nice")             type = pref_int,  i = p->nice_inferior;
682       CHECK("fade")             type = pref_bool, b = p->fade_p;
683       CHECK("unfade")           type = pref_bool, b = p->unfade_p;
684       CHECK("fadeSeconds")      type = pref_time, t = p->fade_seconds;
685       CHECK("fadeTicks")        type = pref_int,  i = p->fade_ticks;
686       CHECK("captureStderr")    type = pref_bool, b =    capture_stderr_p;
687       CHECK("captureStdout")    continue;  /* don't save */
688       CHECK("font")             type = pref_str,  s =    stderr_font;
689       CHECK("programs")         type = pref_str,  s =    programs;
690       CHECK("pointerPollTime")  type = pref_time, t = p->pointer_timeout;
691       CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
692       CHECK("initialDelay")     type = pref_time, t = p->initial_delay;
693       CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension;
694       CHECK("mitSaverExtension")type = pref_bool, b=p->use_mit_saver_extension;
695       CHECK("xidleExtension")   type = pref_bool, b = p->use_xidle_extension;
696       CHECK("procInterrupts")   type = pref_bool, b = p->use_proc_interrupts;
697       CHECK("overlayStderr")    type = pref_bool, b = overlay_stderr_p;
698       CHECK("overlayTextBackground") continue;  /* don't save */
699       CHECK("overlayTextForeground") continue;  /* don't save */
700       CHECK("bourneShell")      continue;
701       else                      abort();
702 # undef CHECK
703
704       switch (type)
705         {
706         case pref_str:
707           break;
708         case pref_int:
709           sprintf(buf, "%d", i);
710           s = buf;
711           break;
712         case pref_bool:
713           s = b ? "True" : "False";
714           break;
715         case pref_time:
716           {
717             unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
718             if (sec >= 60)
719               {
720                 min += (sec / 60);
721                 sec %= 60;
722               }
723             if (min >= 60)
724               {
725                 hour += (min / 60);
726                 min %= 60;
727               }
728             sprintf (buf, "%u:%02u:%02u", hour, min, sec);
729             s = buf;
730           }
731           break;
732         default:
733           abort();
734           break;
735         }
736       write_entry (out, pr, s);
737     }
738
739   fprintf(out, "\n");
740
741   if (visual_name) free(visual_name);
742   if (stderr_font) free(stderr_font);
743   if (programs) free(programs);
744
745   if (fclose(out) == 0)
746     {
747       time_t write_date = 0;
748
749       if (stat(tmp_name, &st) == 0)
750         {
751           write_date = st.st_mtime;
752         }
753       else
754         {
755           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
756           sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
757           perror(buf);
758           unlink (tmp_name);
759           free(buf);
760           goto END;
761         }
762
763       if (rename (tmp_name, name) != 0)
764         {
765           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
766           sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
767                   blurb(), tmp_name, name);
768           perror(buf);
769           unlink (tmp_name);
770           free(buf);
771           goto END;
772         }
773       else
774         {
775           p->init_file_date = write_date;
776
777           /* Since the .xscreensaver file is used for IPC, let's try and make
778              sure that the bits actually land on the disk right away. */
779           sync ();
780         }
781     }
782   else
783     {
784       char *buf = (char *) malloc(1024 + strlen(name));
785       sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
786       perror(buf);
787       free(buf);
788       unlink (tmp_name);
789       goto END;
790     }
791
792  END:
793   if (n2) free (n2);
794 }
795
796 \f
797 /* Parsing the resource database
798  */
799
800
801 /* Populate `saver_preferences' with the contents of the resource database.
802    Note that this may be called multiple times -- it is re-run each time
803    the ~/.xscreensaver file is reloaded.
804
805    This function can be very noisy, since it issues resource syntax errors
806    and so on.
807  */
808 void
809 load_init_file (saver_preferences *p)
810 {
811   static Bool first_time = True;
812   
813   if (parse_init_file (p) != 0)         /* file might have gone away */
814     if (!first_time) return;
815
816   first_time = False;
817
818   p->xsync_p        = get_boolean_resource ("synchronous", "Synchronous");
819   p->verbose_p      = get_boolean_resource ("verbose", "Boolean");
820   p->timestamp_p    = get_boolean_resource ("timestamp", "Boolean");
821   p->lock_p         = get_boolean_resource ("lock", "Boolean");
822   p->lock_vt_p      = get_boolean_resource ("lockVTs", "Boolean");
823   p->fade_p         = get_boolean_resource ("fade", "Boolean");
824   p->unfade_p       = get_boolean_resource ("unfade", "Boolean");
825   p->fade_seconds   = 1000 * get_seconds_resource ("fadeSeconds", "Time");
826   p->fade_ticks     = get_integer_resource ("fadeTicks", "Integer");
827   p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
828   p->nice_inferior  = get_integer_resource ("nice", "Nice");
829
830   p->initial_delay   = 1000 * get_seconds_resource ("initialDelay", "Time");
831   p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
832   p->timeout         = 1000 * get_minutes_resource ("timeout", "Time");
833   p->lock_timeout    = 1000 * get_minutes_resource ("lockTimeout", "Time");
834   p->cycle           = 1000 * get_minutes_resource ("cycle", "Time");
835   p->passwd_timeout  = 1000 * get_seconds_resource ("passwdTimeout", "Time");
836   p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
837   p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
838                                                        "Time");
839   p->shell = get_string_resource ("bourneShell", "BourneShell");
840
841   p->demo_command = get_string_resource("demoCommand", "URL");
842   p->prefs_command = get_string_resource("prefsCommand", "URL");
843   p->help_url = get_string_resource("helpURL", "URL");
844   p->load_url_command = get_string_resource("loadURL", "LoadURL");
845
846   {
847     char *s;
848     if ((s = get_string_resource ("splash", "Boolean")))
849       if (!get_boolean_resource("splash", "Boolean"))
850         p->splash_duration = 0;
851     if (s) free (s);
852   }
853
854   p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
855   p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
856                                                      "Boolean");
857   p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
858                                                      "Boolean");
859   p->use_proc_interrupts = get_boolean_resource ("procInterrupts", "Boolean");
860
861   /* Throttle the various timeouts to reasonable values.
862    */
863   if (p->passwd_timeout <= 0) p->passwd_timeout = 30000;         /* 30 secs */
864   if (p->timeout < 10000) p->timeout = 10000;                    /* 10 secs */
865   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
866   if (p->pointer_timeout <= 0) p->pointer_timeout = 5000;        /*  5 secs */
867   if (p->notice_events_timeout <= 0)
868     p->notice_events_timeout = 10000;                            /* 10 secs */
869   if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
870     p->fade_p = False;
871   if (! p->fade_p) p->unfade_p = False;
872
873   p->watchdog_timeout = p->cycle * 0.6;
874   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
875   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
876
877   get_screenhacks (p);
878
879   if (p->debug_p)
880     {
881       p->xsync_p = True;
882       p->verbose_p = True;
883       p->timestamp_p = True;
884       p->initial_delay = 0;
885     }
886 }
887
888 \f
889 /* Parsing the programs resource.
890  */
891
892 screenhack *
893 parse_screenhack (const char *line)
894 {
895   screenhack *h = (screenhack *) calloc (1, sizeof(*h));
896   const char *s;
897
898   h->enabled_p = True;
899
900   while (isspace(*line)) line++;                /* skip whitespace */
901   if (*line == '-')                             /* handle "-" */
902     {
903       h->enabled_p = False;
904       line++;
905       while (isspace(*line)) line++;            /* skip whitespace */
906     }
907
908   s = line;                                     /* handle "visual:" */
909   while (*line && *line != ':' && *line != '"' && !isspace(*line))
910     line++;
911   if (*line != ':')
912     line = s;
913   else
914     {
915       h->visual = (char *) malloc (line-s+1);
916       strncpy (h->visual, s, line-s);
917       h->visual[line-s] = 0;
918       if (*line == ':') line++;                 /* skip ":" */
919       while (isspace(*line)) line++;            /* skip whitespace */
920     }
921
922   if (*line == '"')                             /* handle "name" */
923     {
924       line++;
925       s = line;
926       while (*line && *line != '"')
927         line++;
928       h->name = (char *) malloc (line-s+1);
929       strncpy (h->name, s, line-s);
930       h->name[line-s] = 0;
931       if (*line == '"') line++;                 /* skip "\"" */
932       while (isspace(*line)) line++;            /* skip whitespace */
933     }
934
935   h->command = format_command (line, False);    /* handle command */
936   return h;
937 }
938
939
940 void
941 free_screenhack (screenhack *hack)
942 {
943   if (hack->visual) free (hack->visual);
944   if (hack->name) free (hack->name);
945   free (hack->command);
946   memset (hack, 0, sizeof(*hack));
947   free (hack);
948 }
949
950
951 static char *
952 format_command (const char *cmd, Bool wrap_p)
953 {
954   int tab = 30;
955   int col = tab;
956   char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
957   const char *in = cmd;
958   char *out = cmd2;
959   while (*in)
960     {
961       /* shrink all whitespace to one space, for the benefit of the "demo"
962          mode display.  We only do this when we can easily tell that the
963          whitespace is not significant (no shell metachars).
964        */
965       switch (*in)
966         {
967         case '\'': case '"': case '`': case '\\':
968           /* Metachars are scary.  Copy the rest of the line unchanged. */
969           while (*in)
970             *out++ = *in++, col++;
971           break;
972
973         case ' ': case '\t':
974           /* Squeeze all other whitespace down to one space. */
975           while (*in == ' ' || *in == '\t')
976             in++;
977           *out++ = ' ', col++;
978           break;
979
980         default:
981           /* Copy other chars unchanged. */
982           *out++ = *in++, col++;
983           break;
984         }
985     }
986
987   *out = 0;
988
989   /* Strip trailing whitespace */
990   while (out > cmd2 && isspace (out[-1]))
991     *(--out) = 0;
992
993   return cmd2;
994 }
995
996
997 char *
998 format_hack (screenhack *hack, Bool wrap_p)
999 {
1000   int tab = 32;
1001   int size = (2 * (strlen(hack->command) +
1002                    (hack->visual ? strlen(hack->visual) : 0) +
1003                    (hack->name ? strlen(hack->name) : 0) +
1004                    tab));
1005   char *h2 = (char *) malloc (size);
1006   char *out = h2;
1007   char *s;
1008   int col = 0;
1009
1010   if (!hack->enabled_p) *out++ = '-';           /* write disabled flag */
1011
1012   if (hack->visual && *hack->visual)            /* write visual name */
1013     {
1014       if (hack->enabled_p) *out++ = ' ';
1015       *out++ = ' ';
1016       strcpy (out, hack->visual);
1017       out += strlen (hack->visual);
1018       *out++ = ':';
1019       *out++ = ' ';
1020     }
1021
1022   *out = 0;
1023   col = string_columns (h2, strlen (h2), 0);
1024
1025   if (hack->name && *hack->name)                /* write pretty name */
1026     {
1027       int L = (strlen (hack->name) + 2);
1028       if (L + col < tab)
1029         out = stab_to (out, col, tab - L - 2);
1030       else
1031         *out++ = ' ';
1032       *out++ = '"';
1033       strcpy (out, hack->name);
1034       out += strlen (hack->name);
1035       *out++ = '"';
1036       *out = 0;
1037
1038       col = string_columns (h2, strlen (h2), 0);
1039       if (wrap_p && col >= tab)
1040         {
1041           out = stab_to (out, col, 77);
1042           *out += strlen(out);
1043         }
1044       else
1045         *out++ = ' ';
1046
1047       if (out >= h2+size) abort();
1048     }
1049
1050   *out = 0;
1051   col = string_columns (h2, strlen (h2), 0);
1052   out = stab_to (out, col, tab);                /* indent */
1053
1054   if (out >= h2+size) abort();
1055   s = format_command (hack->command, wrap_p);
1056   strcpy (out, s);
1057   out += strlen (s);
1058   free (s);
1059   *out = 0;
1060
1061   return h2;
1062 }
1063
1064
1065 static void
1066 get_screenhacks (saver_preferences *p)
1067 {
1068   int i = 0;
1069   int start = 0;
1070   int end = 0;
1071   int size;
1072   char *d;
1073
1074   d = get_string_resource ("monoPrograms", "MonoPrograms");
1075   if (d && !*d) { free(d); d = 0; }
1076   if (!d)
1077     d = get_string_resource ("colorPrograms", "ColorPrograms");
1078   if (d && !*d) { free(d); d = 0; }
1079
1080   if (d)
1081     {
1082       fprintf (stderr,
1083        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1084         see the manual for details.\n", blurb());
1085       free(d);
1086     }
1087
1088   d = get_string_resource ("programs", "Programs");
1089
1090   if (p->screenhacks)
1091     {
1092       for (i = 0; i < p->screenhacks_count; i++)
1093         if (p->screenhacks[i])
1094           free_screenhack (p->screenhacks[i]);
1095       free(p->screenhacks);
1096       p->screenhacks = 0;
1097     }
1098
1099   if (!d || !*d)
1100     {
1101       p->screenhacks_count = 0;
1102       p->screenhacks = 0;
1103       return;
1104     }
1105
1106   size = strlen (d);
1107
1108
1109   /* Count up the number of newlines (which will be equal to or larger than
1110      the number of hacks.)
1111    */
1112   i = 0;
1113   for (i = 0; d[i]; i++)
1114     if (d[i] == '\n')
1115       i++;
1116   i++;
1117
1118   p->screenhacks = (screenhack **) calloc (sizeof (screenhack *), i+1);
1119
1120   /* Iterate over the lines in `d' (the string with newlines)
1121      and make new strings to stuff into the `screenhacks' array.
1122    */
1123   p->screenhacks_count = 0;
1124   while (start < size)
1125     {
1126       /* skip forward over whitespace. */
1127       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1128         start++;
1129
1130       /* skip forward to newline or end of string. */
1131       end = start;
1132       while (d[end] != 0 && d[end] != '\n')
1133         end++;
1134
1135       /* null terminate. */
1136       d[end] = 0;
1137
1138       p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1139       if (p->screenhacks_count >= i)
1140         abort();
1141
1142       start = end+1;
1143     }
1144
1145   if (p->screenhacks_count == 0)
1146     {
1147       free (p->screenhacks);
1148       p->screenhacks = 0;
1149     }
1150 }