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