9a68afc73e77af73a3c922a1db8dbf14dd808c66
[xscreensaver] / driver / prefs.c
1 /* dotfile.c --- management of the ~/.xscreensaver file.
2  * xscreensaver, Copyright (c) 1998-2004 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <stdlib.h>
18
19 #ifdef HAVE_UNISTD_H
20 # include <unistd.h>
21 #endif
22
23 #include <stdio.h>
24 #include <ctype.h>
25 #include <string.h>
26 #include <time.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
29
30 #include <X11/Xlib.h>
31 #include <X11/Xresource.h>
32
33 #ifndef VMS
34 # include <pwd.h>
35 #else /* VMS */
36 # include "vms-pwd.h"
37 #endif /* VMS */
38
39
40 /* This file doesn't need the Xt headers, so stub these types out... */
41 #undef XtPointer
42 #define XtAppContext void*
43 #define XtIntervalId void*
44 #define XtPointer    void*
45 #define Widget       void*
46
47
48 /* Just in case there's something pathological about stat.h... */
49 #ifndef  S_IRUSR
50 # define S_IRUSR 00400
51 #endif
52 #ifndef  S_IWUSR
53 # define S_IWUSR 00200
54 #endif
55 #ifndef  S_IXUSR
56 # define S_IXUSR 00100
57 #endif
58 #ifndef  S_IXGRP
59 # define S_IXGRP 00010
60 #endif
61 #ifndef  S_IXOTH
62 # define S_IXOTH 00001
63 #endif
64
65
66 #include "prefs.h"
67 #include "resources.h"
68
69
70 extern char *progname;
71 extern char *progclass;
72 extern const char *blurb (void);
73
74
75
76 static void get_screenhacks (saver_preferences *p);
77 static char *format_command (const char *cmd, Bool wrap_p);
78 static void merge_system_screenhacks (saver_preferences *p,
79                                       screenhack **system_list, int count);
80 static void stop_the_insanity (saver_preferences *p);
81
82
83 static char *
84 chase_symlinks (const char *file)
85 {
86 # ifdef HAVE_REALPATH
87   if (file)
88     {
89       char buf [2048];
90       if (realpath (file, buf))
91         return strdup (buf);
92
93       sprintf (buf, "%.100s: realpath %.200s", blurb(), file);
94       perror(buf);
95     }
96 # endif /* HAVE_REALPATH */
97   return 0;
98 }
99
100
101 static Bool
102 i_am_a_nobody (uid_t uid)
103 {
104   struct passwd *p;
105
106   p = getpwnam ("nobody");
107   if (! p) p = getpwnam ("noaccess");
108   if (! p) p = getpwnam ("daemon");
109
110   if (! p) /* There is no nobody? */
111     return False;
112
113   return (uid == p->pw_uid);
114 }
115
116
117 const char *
118 init_file_name (void)
119 {
120   static char *file = 0;
121
122   if (!file)
123     {
124       uid_t uid = getuid ();
125       struct passwd *p = getpwuid (uid);
126
127       if (i_am_a_nobody (uid))
128         /* If we're running as nobody, then use root's .xscreensaver file
129            (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely
130            to be different -- if we didn't do this, then xscreensaver-demo
131            would appear to have no effect when the luser is running as root.)
132          */
133         uid = 0;
134
135       p = getpwuid (uid);
136
137       if (!p || !p->pw_name || !*p->pw_name)
138         {
139           fprintf (stderr, "%s: couldn't get user info of uid %d\n",
140                    blurb(), getuid ());
141           file = "";
142         }
143       else if (!p->pw_dir || !*p->pw_dir)
144         {
145           fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
146                    blurb(), (p->pw_name ? p->pw_name : "???"));
147           file = "";
148         }
149       else
150         {
151           const char *home = p->pw_dir;
152           const char *name = ".xscreensaver";
153           file = (char *) malloc(strlen(home) + strlen(name) + 2);
154           strcpy(file, home);
155           if (!*home || home[strlen(home)-1] != '/')
156             strcat(file, "/");
157           strcat(file, name);
158         }
159     }
160
161   if (file && *file)
162     return file;
163   else
164     return 0;
165 }
166
167
168 static const char *
169 init_file_tmp_name (void)
170 {
171   static char *file = 0;
172   if (!file)
173     {
174       const char *name = init_file_name();
175       const char *suffix = ".tmp";
176
177       char *n2 = chase_symlinks (name);
178       if (n2) name = n2;
179
180       if (!name || !*name)
181         file = "";
182       else
183         {
184           file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
185           strcpy(file, name);
186           strcat(file, suffix);
187         }
188
189       if (n2) free (n2);
190     }
191
192   if (file && *file)
193     return file;
194   else
195     return 0;
196 }
197
198 static int
199 get_byte_resource (char *name, char *class)
200 {
201   char *s = get_string_resource (name, class);
202   char *s2 = s;
203   int n = 0;
204   if (!s) return 0;
205
206   while (isspace(*s2)) s2++;
207   while (*s2 >= '0' && *s2 <= '9')
208     {
209       n = (n * 10) + (*s2 - '0');
210       s2++;
211     }
212   while (isspace(*s2)) s2++;
213   if      (*s2 == 'k' || *s2 == 'K') n <<= 10;
214   else if (*s2 == 'm' || *s2 == 'M') n <<= 20;
215   else if (*s2 == 'g' || *s2 == 'G') n <<= 30;
216   else if (*s2)
217     {
218     LOSE:
219       fprintf (stderr, "%s: %s must be a number of bytes, not \"%s\".\n",
220                progname, name, s);
221       free (s);
222       return 0;
223     }
224   s2++;
225   if (*s2 == 'b' || *s2 == 'B') s2++;
226   while (isspace(*s2)) s2++;
227   if (*s2) goto LOSE;
228
229   free (s);
230   return n;
231 }
232
233
234 static const char * const prefs[] = {
235   "timeout",
236   "cycle",
237   "lock",
238   "lockVTs",
239   "lockTimeout",
240   "passwdTimeout",
241   "visualID",
242   "installColormap",
243   "verbose",
244   "timestamp",
245   "splash",
246   "splashDuration",
247   "quad",
248   "demoCommand",
249   "prefsCommand",
250   "helpURL",
251   "loadURL",
252   "nice",
253   "memoryLimit",
254   "fade",
255   "unfade",
256   "fadeSeconds",
257   "fadeTicks",
258   "captureStderr",
259   "captureStdout",              /* not saved -- obsolete */
260   "ignoreUninstalledPrograms",
261   "font",
262   "dpmsEnabled",
263   "dpmsStandby",
264   "dpmsSuspend",
265   "dpmsOff",
266   "grabDesktopImages",
267   "grabVideoFrames",
268   "chooseRandomImages",
269   "imageDirectory",
270   "mode",
271   "selected",
272   "",
273   "programs",
274   "",
275   "pointerPollTime",
276   "windowCreationTimeout",
277   "initialDelay",
278   "sgiSaverExtension",
279   "mitSaverExtension",
280   "xidleExtension",
281   "GetViewPortIsFullOfLies",
282   "procInterrupts",
283   "overlayStderr",
284   "overlayTextBackground",      /* not saved -- X resources only */
285   "overlayTextForeground",      /* not saved -- X resources only */
286   "bourneShell",                /* not saved -- X resources only */
287   0
288 };
289
290 static char *
291 strip (char *s)
292 {
293   char *s2;
294   while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
295     s++;
296   for (s2 = s; *s2; s2++)
297     ;
298   for (s2--; s2 >= s; s2--) 
299     if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n') 
300       *s2 = 0;
301     else
302       break;
303   return s;
304 }
305
306 \f
307 /* Reading
308  */
309
310 static int
311 handle_entry (XrmDatabase *db, const char *key, const char *value,
312               const char *filename, int line)
313 {
314   int i;
315   for (i = 0; prefs[i]; i++)
316     if (*prefs[i] && !strcasecmp(key, prefs[i]))
317       {
318         char *val = strdup(value);
319         char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
320         strcpy(spec, progclass);
321         strcat(spec, ".");
322         strcat(spec, prefs[i]);
323
324         XrmPutStringResource (db, spec, val);
325
326         free(spec);
327         free(val);
328         return 0;
329       }
330
331   fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
332           blurb(), filename, line, key);
333   return 1;
334 }
335
336
337 static int
338 parse_init_file (saver_preferences *p)
339 {
340   time_t write_date = 0;
341   const char *name = init_file_name();
342   int line = 0;
343   struct stat st;
344   FILE *in;
345   int buf_size = 1024;
346   char *buf;
347
348   if (!name) return 0;
349
350   if (stat(name, &st) != 0)
351     {
352       p->init_file_date = 0;
353       return 0;
354     }
355
356   in = fopen(name, "r");
357   if (!in)
358     {
359       char *buf = (char *) malloc(1024 + strlen(name));
360       sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
361       perror(buf);
362       free(buf);
363       return -1;
364     }
365
366   if (fstat (fileno(in), &st) == 0)
367     {
368       write_date = st.st_mtime;
369     }
370   else
371     {
372       char *buf = (char *) malloc(1024 + strlen(name));
373       sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
374       perror(buf);
375       free(buf);
376       return -1;
377     }
378
379   buf = (char *) malloc(buf_size);
380
381   while (fgets (buf, buf_size-1, in))
382     {
383       char *key, *value;
384       int L = strlen(buf);
385
386       line++;
387       while (L > 2 &&
388              (buf[L-1] != '\n' ||       /* whole line didn't fit in buffer */
389               buf[L-2] == '\\'))        /* or line ended with backslash */
390         {
391           if (buf[L-2] == '\\')         /* backslash-newline gets swallowed */
392             {
393               buf[L-2] = 0;
394               L -= 2;
395             }
396           buf_size += 1024;
397           buf = (char *) realloc(buf, buf_size);
398           if (!buf) exit(1);
399
400           line++;
401           if (!fgets (buf + L, buf_size-L-1, in))
402             break;
403           L = strlen(buf);
404         }
405
406       /* Now handle other backslash escapes. */
407       {
408         int i, j;
409         for (i = 0; buf[i]; i++)
410           if (buf[i] == '\\')
411             {
412               switch (buf[i+1])
413                 {
414                 case 'n': buf[i] = '\n'; break;
415                 case 'r': buf[i] = '\r'; break;
416                 case 't': buf[i] = '\t'; break;
417                 default:  buf[i] = buf[i+1]; break;
418                 }
419               for (j = i+2; buf[j]; j++)
420                 buf[j-1] = buf[j];
421               buf[j-1] = 0;
422             }
423       }
424
425       key = strip(buf);
426
427       if (*key == '#' || *key == '!' || *key == ';' ||
428           *key == '\n' || *key == 0)
429         continue;
430
431       value = strchr (key, ':');
432       if (!value)
433         {
434           fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
435                   name, line, key);
436           continue;
437         }
438       else
439         {
440           *value++ = 0;
441           value = strip(value);
442         }
443
444       if (!p->db) abort();
445       handle_entry (&p->db, key, value, name, line);
446     }
447   fclose (in);
448   free(buf);
449
450   p->init_file_date = write_date;
451   return 0;
452 }
453
454
455 Bool
456 init_file_changed_p (saver_preferences *p)
457 {
458   const char *name = init_file_name();
459   struct stat st;
460
461   if (!name) return False;
462
463   if (stat(name, &st) != 0)
464     return False;
465
466   if (p->init_file_date == st.st_mtime)
467     return False;
468
469   return True;
470 }
471
472 \f
473 /* Writing
474  */
475
476 static int
477 tab_to (FILE *out, int from, int to)
478 {
479   int tab_width = 8;
480   int to_mod = (to / tab_width) * tab_width;
481   while (from < to_mod)
482     {
483       fprintf(out, "\t");
484       from = (((from / tab_width) + 1) * tab_width);
485     }
486   while (from < to)
487     {
488       fprintf(out, " ");
489       from++;
490     }
491   return from;
492 }
493
494 static char *
495 stab_to (char *out, int from, int to)
496 {
497   int tab_width = 8;
498   int to_mod = (to / tab_width) * tab_width;
499   while (from < to_mod)
500     {
501       *out++ = '\t';
502       from = (((from / tab_width) + 1) * tab_width);
503     }
504   while (from < to)
505     {
506       *out++ = ' ';
507       from++;
508     }
509   return out;
510 }
511
512 static int
513 string_columns (const char *string, int length, int start)
514 {
515   int tab_width = 8;
516   int col = start;
517   const char *end = string + length;
518   while (string < end)
519     {
520       if (*string == '\n')
521         col = 0;
522       else if (*string == '\t')
523         col = (((col / tab_width) + 1) * tab_width);
524       else
525         col++;
526       string++;
527     }
528   return col;
529 }
530
531
532 static void
533 write_entry (FILE *out, const char *key, const char *value)
534 {
535   char *v = strdup(value ? value : "");
536   char *v2 = v;
537   char *nl = 0;
538   int col;
539   Bool programs_p = (!strcmp(key, "programs"));
540   int tab = (programs_p ? 32 : 16);
541   Bool first = True;
542
543   fprintf(out, "%s:", key);
544   col = strlen(key) + 1;
545
546   if (strlen(key) > 14)
547     col = tab_to (out, col, 20);
548
549   while (1)
550     {
551       if (!programs_p)
552         v2 = strip(v2);
553       nl = strchr(v2, '\n');
554       if (nl)
555         *nl = 0;
556
557       if (first && programs_p)
558         {
559           col = tab_to (out, col, 77);
560           fprintf (out, " \\\n");
561           col = 0;
562         }
563
564       if (first)
565         first = False;
566       else
567         {
568           col = tab_to (out, col, 75);
569           fprintf (out, " \\n\\\n");
570           col = 0;
571         }
572
573       if (!programs_p)
574         col = tab_to (out, col, tab);
575
576       if (programs_p &&
577           string_columns(v2, strlen (v2), col) + col > 75)
578         {
579           int L = strlen (v2);
580           int start = 0;
581           int end = start;
582           while (start < L)
583             {
584               while (v2[end] == ' ' || v2[end] == '\t')
585                 end++;
586               while (v2[end] != ' ' && v2[end] != '\t' &&
587                      v2[end] != '\n' && v2[end] != 0)
588                 end++;
589               if (string_columns (v2 + start, (end - start), col) >= 74)
590                 {
591                   col = tab_to (out, col, 75);
592                   fprintf(out, "   \\\n");
593                   col = tab_to (out, 0, tab + 2);
594                   while (v2[start] == ' ' || v2[start] == '\t')
595                     start++;
596                 }
597
598               col = string_columns (v2 + start, (end - start), col);
599               while (start < end)
600                 fputc(v2[start++], out);
601             }
602         }
603       else
604         {
605           fprintf (out, "%s", v2);
606           col += string_columns(v2, strlen (v2), col);
607         }
608
609       if (nl)
610         v2 = nl + 1;
611       else
612         break;
613     }
614
615   fprintf(out, "\n");
616   free(v);
617 }
618
619 int
620 write_init_file (saver_preferences *p, const char *version_string,
621                  Bool verbose_p)
622 {
623   int status = -1;
624   const char *name = init_file_name();
625   const char *tmp_name = init_file_tmp_name();
626   char *n2 = chase_symlinks (name);
627   struct stat st;
628   int i, j;
629
630   /* Kludge, since these aren't in the saver_preferences struct as strings...
631    */
632   char *visual_name;
633   char *programs;
634   Bool overlay_stderr_p;
635   char *stderr_font;
636   FILE *out;
637
638   if (!name) goto END;
639
640   if (n2) name = n2;
641
642   /* Throttle the various timeouts to reasonable values before writing
643      the file to disk. */
644   stop_the_insanity (p);
645
646
647   if (verbose_p)
648     fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
649
650   unlink (tmp_name);
651   out = fopen(tmp_name, "w");
652   if (!out)
653     {
654       char *buf = (char *) malloc(1024 + strlen(name));
655       sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
656       perror(buf);
657       free(buf);
658       goto END;
659     }
660
661   /* Give the new .xscreensaver file the same permissions as the old one;
662      except ensure that it is readable and writable by owner, and not
663      executable.  Extra hack: if we're running as root, make the file
664      be world-readable (so that the daemon, running as "nobody", will
665      still be able to read it.)
666    */
667   if (stat(name, &st) == 0)
668     {
669       mode_t mode = st.st_mode;
670       mode |= S_IRUSR | S_IWUSR;                /* read/write by user */
671       mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);   /* executable by none */
672
673       if (getuid() == (uid_t) 0)                /* read by group/other */
674         mode |= S_IRGRP | S_IROTH;
675
676       if (fchmod (fileno(out), mode) != 0)
677         {
678           char *buf = (char *) malloc(1024 + strlen(name));
679           sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
680                    tmp_name, (unsigned int) mode);
681           perror(buf);
682           free(buf);
683           goto END;
684         }
685     }
686
687   /* Kludge, since these aren't in the saver_preferences struct... */
688   visual_name = get_string_resource ("visualID", "VisualID");
689   programs = 0;
690   overlay_stderr_p = get_boolean_resource ("overlayStderr", "Boolean");
691   stderr_font = get_string_resource ("font", "Font");
692
693   i = 0;
694   {
695     char *ss;
696     char **hack_strings = (char **)
697       calloc (p->screenhacks_count, sizeof(char *));
698
699     for (j = 0; j < p->screenhacks_count; j++)
700       {
701         hack_strings[j] = format_hack (p->screenhacks[j], True);
702         i += strlen (hack_strings[j]);
703         i += 2;
704       }
705
706     ss = programs = (char *) malloc(i + 10);
707     *ss = 0;
708     for (j = 0; j < p->screenhacks_count; j++)
709       {
710         strcat (ss, hack_strings[j]);
711         free (hack_strings[j]);
712         ss += strlen(ss);
713         *ss++ = '\n';
714         *ss = 0;
715       }
716     free (hack_strings);
717   }
718
719   {
720     struct passwd *pw = getpwuid (getuid ());
721     char *whoami = (pw && pw->pw_name && *pw->pw_name
722                     ? pw->pw_name
723                     : "<unknown>");
724     time_t now = time ((time_t *) 0);
725     char *timestr = (char *) ctime (&now);
726     char *nl = (char *) strchr (timestr, '\n');
727     if (nl) *nl = 0;
728     fprintf (out,
729              "# %s Preferences File\n"
730              "# Written by %s %s for %s on %s.\n"
731              "# http://www.jwz.org/xscreensaver/\n"
732              "\n",
733              progclass, progname, version_string, whoami, timestr);
734   }
735
736   for (j = 0; prefs[j]; j++)
737     {
738       char buf[255];
739       const char *pr = prefs[j];
740       enum pref_type { pref_str, pref_int, pref_bool, pref_byte, pref_time
741       } type = pref_str;
742       const char *s = 0;
743       int i = 0;
744       Bool b = False;
745       Time t = 0;
746
747       if (pr && !*pr)
748         {
749           fprintf(out, "\n");
750           continue;
751         }
752
753 # undef CHECK
754 # define CHECK(X) else if (!strcmp(pr, X))
755       if (!pr || !*pr)          ;
756       CHECK("timeout")          type = pref_time, t = p->timeout;
757       CHECK("cycle")            type = pref_time, t = p->cycle;
758       CHECK("lock")             type = pref_bool, b = p->lock_p;
759 # if 0 /* #### not ready yet */
760       CHECK("lockVTs")          type = pref_bool, b = p->lock_vt_p;
761 # else
762       CHECK("lockVTs")          continue;  /* don't save */
763 # endif
764       CHECK("lockTimeout")      type = pref_time, t = p->lock_timeout;
765       CHECK("passwdTimeout")    type = pref_time, t = p->passwd_timeout;
766       CHECK("visualID")         type = pref_str,  s =    visual_name;
767       CHECK("installColormap")  type = pref_bool, b = p->install_cmap_p;
768       CHECK("verbose")          type = pref_bool, b = p->verbose_p;
769       CHECK("timestamp")        type = pref_bool, b = p->timestamp_p;
770       CHECK("splash")           type = pref_bool, b = p->splash_p;
771       CHECK("splashDuration")   type = pref_time, t = p->splash_duration;
772       CHECK("quad")             type = pref_bool, b = p->quad_p;
773       CHECK("demoCommand")      type = pref_str,  s = p->demo_command;
774       CHECK("prefsCommand")     type = pref_str,  s = p->prefs_command;
775 /*    CHECK("helpURL")          type = pref_str,  s = p->help_url; */
776       CHECK("helpURL")          continue;  /* don't save */
777 /*    CHECK("loadURL")          type = pref_str,  s = p->load_url_command; */
778       CHECK("loadURL")          continue;  /* don't save */
779       CHECK("nice")             type = pref_int,  i = p->nice_inferior;
780       CHECK("memoryLimit")      type = pref_byte, i = p->inferior_memory_limit;
781       CHECK("fade")             type = pref_bool, b = p->fade_p;
782       CHECK("unfade")           type = pref_bool, b = p->unfade_p;
783       CHECK("fadeSeconds")      type = pref_time, t = p->fade_seconds;
784       CHECK("fadeTicks")        type = pref_int,  i = p->fade_ticks;
785       CHECK("captureStderr")    type = pref_bool, b = p->capture_stderr_p;
786       CHECK("captureStdout")    continue;  /* don't save */
787       CHECK("ignoreUninstalledPrograms")
788                                 type = pref_bool, b = p->ignore_uninstalled_p;
789
790       CHECK("font")             type = pref_str,  s =    stderr_font;
791
792       CHECK("dpmsEnabled")      type = pref_bool, b = p->dpms_enabled_p;
793       CHECK("dpmsStandby")      type = pref_time, t = p->dpms_standby;
794       CHECK("dpmsSuspend")      type = pref_time, t = p->dpms_suspend;
795       CHECK("dpmsOff")          type = pref_time, t = p->dpms_off;
796
797       CHECK("grabDesktopImages") type =pref_bool, b = p->grab_desktop_p;
798       CHECK("grabVideoFrames")   type =pref_bool, b = p->grab_video_p;
799       CHECK("chooseRandomImages")type =pref_bool, b = p->random_image_p;
800       CHECK("imageDirectory")    type =pref_str,  s = p->image_directory;
801
802       CHECK("mode")             type = pref_str,
803                                 s = (p->mode == ONE_HACK ? "one" :
804                                      p->mode == BLANK_ONLY ? "blank" :
805                                      p->mode == DONT_BLANK ? "off" :
806                                      "random");
807       CHECK("selected")         type = pref_int,  i = p->selected_hack;
808
809       CHECK("programs")         type = pref_str,  s =    programs;
810       CHECK("pointerPollTime")  type = pref_time, t = p->pointer_timeout;
811       CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
812       CHECK("initialDelay")     type = pref_time, t = p->initial_delay;
813       CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension;
814       CHECK("mitSaverExtension")type = pref_bool, b=p->use_mit_saver_extension;
815       CHECK("xidleExtension")   type = pref_bool, b = p->use_xidle_extension;
816       CHECK("procInterrupts")   type = pref_bool, b = p->use_proc_interrupts;
817       CHECK("GetViewPortIsFullOfLies")  type = pref_bool,
818                                         b = p->getviewport_full_of_lies_p;
819       CHECK("overlayStderr")    type = pref_bool, b = overlay_stderr_p;
820       CHECK("overlayTextBackground") continue;  /* don't save */
821       CHECK("overlayTextForeground") continue;  /* don't save */
822       CHECK("bourneShell")      continue;
823       else                      abort();
824 # undef CHECK
825
826       switch (type)
827         {
828         case pref_str:
829           break;
830         case pref_int:
831           sprintf(buf, "%d", i);
832           s = buf;
833           break;
834         case pref_bool:
835           s = b ? "True" : "False";
836           break;
837         case pref_time:
838           {
839             unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
840             if (sec >= 60)
841               {
842                 min += (sec / 60);
843                 sec %= 60;
844               }
845             if (min >= 60)
846               {
847                 hour += (min / 60);
848                 min %= 60;
849               }
850             sprintf (buf, "%u:%02u:%02u", hour, min, sec);
851             s = buf;
852           }
853           break;
854         case pref_byte:
855           {
856             if      (i >= (1<<30) && i == ((i >> 30) << 30))
857               sprintf(buf, "%dG", i >> 30);
858             else if (i >= (1<<20) && i == ((i >> 20) << 20))
859               sprintf(buf, "%dM", i >> 20);
860             else if (i >= (1<<10) && i == ((i >> 10) << 10))
861               sprintf(buf, "%dK", i >> 10);
862             else
863               sprintf(buf, "%d", i);
864             s = buf;
865           }
866           break;
867         default:
868           abort();
869           break;
870         }
871
872       if (pr && !strcmp(pr, "mode")) fprintf(out, "\n");
873
874       write_entry (out, pr, s);
875     }
876
877   fprintf(out, "\n");
878
879   if (visual_name) free(visual_name);
880   if (stderr_font) free(stderr_font);
881   if (programs) free(programs);
882
883   if (fclose(out) == 0)
884     {
885       time_t write_date = 0;
886
887       if (stat(tmp_name, &st) == 0)
888         {
889           write_date = st.st_mtime;
890         }
891       else
892         {
893           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
894           sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
895           perror(buf);
896           unlink (tmp_name);
897           free(buf);
898           goto END;
899         }
900
901       if (rename (tmp_name, name) != 0)
902         {
903           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
904           sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
905                   blurb(), tmp_name, name);
906           perror(buf);
907           unlink (tmp_name);
908           free(buf);
909           goto END;
910         }
911       else
912         {
913           p->init_file_date = write_date;
914
915           /* Since the .xscreensaver file is used for IPC, let's try and make
916              sure that the bits actually land on the disk right away. */
917           sync ();
918
919           status = 0;    /* wrote and renamed successfully! */
920         }
921     }
922   else
923     {
924       char *buf = (char *) malloc(1024 + strlen(name));
925       sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
926       perror(buf);
927       free(buf);
928       unlink (tmp_name);
929       goto END;
930     }
931
932  END:
933   if (n2) free (n2);
934   return status;
935 }
936
937 \f
938 /* Parsing the resource database
939  */
940
941 void
942 free_screenhack (screenhack *hack)
943 {
944   if (hack->visual) free (hack->visual);
945   if (hack->name) free (hack->name);
946   free (hack->command);
947   memset (hack, 0, sizeof(*hack));
948   free (hack);
949 }
950
951 static void
952 free_screenhack_list (screenhack **list, int count)
953 {
954   int i;
955   if (!list) return;
956   for (i = 0; i < count; i++)
957     if (list[i])
958       free_screenhack (list[i]);
959   free (list);
960 }
961
962
963
964 /* Populate `saver_preferences' with the contents of the resource database.
965    Note that this may be called multiple times -- it is re-run each time
966    the ~/.xscreensaver file is reloaded.
967
968    This function can be very noisy, since it issues resource syntax errors
969    and so on.
970  */
971 void
972 load_init_file (saver_preferences *p)
973 {
974   static Bool first_time = True;
975   
976   screenhack **system_default_screenhacks = 0;
977   int system_default_screenhack_count = 0;
978
979   if (first_time)
980     {
981       /* Get the programs resource before the .xscreensaver file has been
982          parsed and merged into the resource database for the first time:
983          this is the value of *programs from the app-defaults file.
984          Then clear it out so that it will be parsed again later, after
985          the init file has been read.
986        */
987       get_screenhacks (p);
988       system_default_screenhacks = p->screenhacks;
989       system_default_screenhack_count = p->screenhacks_count;
990       p->screenhacks = 0;
991       p->screenhacks_count = 0;
992     }
993
994   if (parse_init_file (p) != 0)         /* file might have gone away */
995     if (!first_time) return;
996
997   first_time = False;
998
999   p->xsync_p        = get_boolean_resource ("synchronous", "Synchronous");
1000   p->verbose_p      = get_boolean_resource ("verbose", "Boolean");
1001   p->timestamp_p    = get_boolean_resource ("timestamp", "Boolean");
1002   p->lock_p         = get_boolean_resource ("lock", "Boolean");
1003   p->lock_vt_p      = get_boolean_resource ("lockVTs", "Boolean");
1004   p->fade_p         = get_boolean_resource ("fade", "Boolean");
1005   p->unfade_p       = get_boolean_resource ("unfade", "Boolean");
1006   p->fade_seconds   = 1000 * get_seconds_resource ("fadeSeconds", "Time");
1007   p->fade_ticks     = get_integer_resource ("fadeTicks", "Integer");
1008   p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
1009   p->nice_inferior  = get_integer_resource ("nice", "Nice");
1010   p->inferior_memory_limit = get_byte_resource ("memoryLimit", "MemoryLimit");
1011   p->splash_p       = get_boolean_resource ("splash", "Boolean");
1012   p->quad_p         = get_boolean_resource ("quad", "Boolean");
1013   p->capture_stderr_p = get_boolean_resource ("captureStderr", "Boolean");
1014   p->ignore_uninstalled_p = get_boolean_resource ("ignoreUninstalledPrograms",
1015                                                   "Boolean");
1016
1017   p->initial_delay   = 1000 * get_seconds_resource ("initialDelay", "Time");
1018   p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
1019   p->timeout         = 1000 * get_minutes_resource ("timeout", "Time");
1020   p->lock_timeout    = 1000 * get_minutes_resource ("lockTimeout", "Time");
1021   p->cycle           = 1000 * get_minutes_resource ("cycle", "Time");
1022   p->passwd_timeout  = 1000 * get_seconds_resource ("passwdTimeout", "Time");
1023   p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
1024   p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
1025                                                        "Time");
1026
1027   p->dpms_enabled_p  = get_boolean_resource ("dpmsEnabled", "Boolean");
1028   p->dpms_standby    = 1000 * get_minutes_resource ("dpmsStandby", "Time");
1029   p->dpms_suspend    = 1000 * get_minutes_resource ("dpmsSuspend", "Time");
1030   p->dpms_off        = 1000 * get_minutes_resource ("dpmsOff",     "Time");
1031
1032   p->grab_desktop_p  = get_boolean_resource ("grabDesktopImages",  "Boolean");
1033   p->grab_video_p    = get_boolean_resource ("grabVideoFrames",    "Boolean");
1034   p->random_image_p  = get_boolean_resource ("chooseRandomImages", "Boolean");
1035   p->image_directory = get_string_resource  ("imageDirectory",
1036                                              "ImageDirectory");
1037
1038   p->shell = get_string_resource ("bourneShell", "BourneShell");
1039
1040   p->demo_command = get_string_resource("demoCommand", "URL");
1041   p->prefs_command = get_string_resource("prefsCommand", "URL");
1042   p->help_url = get_string_resource("helpURL", "URL");
1043   p->load_url_command = get_string_resource("loadURL", "LoadURL");
1044
1045
1046   /* If "*splash" is unset, default to true. */
1047   {
1048     char *s = get_string_resource ("splash", "Boolean");
1049     if (s)
1050       free (s);
1051     else
1052       p->splash_p = True;
1053   }
1054
1055   /* If "*grabDesktopImages" is unset, default to true. */
1056   {
1057     char *s = get_string_resource ("grabDesktopImages", "Boolean");
1058     if (s)
1059       free (s);
1060     else
1061       p->grab_desktop_p = True;
1062   }
1063
1064   p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
1065   p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
1066                                                      "Boolean");
1067   p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
1068                                                      "Boolean");
1069   p->use_proc_interrupts = get_boolean_resource ("procInterrupts", "Boolean");
1070
1071   p->getviewport_full_of_lies_p =
1072     get_boolean_resource ("GetViewPortIsFullOfLies", "Boolean");
1073
1074   get_screenhacks (p);                /* Parse the "programs" resource. */
1075
1076   p->selected_hack = get_integer_resource ("selected", "Integer");
1077   if (p->selected_hack < 0 || p->selected_hack >= p->screenhacks_count)
1078     p->selected_hack = -1;
1079
1080   {
1081     char *s = get_string_resource ("mode", "Mode");
1082     if      (s && !strcasecmp (s, "one"))   p->mode = ONE_HACK;
1083     else if (s && !strcasecmp (s, "blank")) p->mode = BLANK_ONLY;
1084     else if (s && !strcasecmp (s, "off"))   p->mode = DONT_BLANK;
1085     else                                    p->mode = RANDOM_HACKS;
1086     if (s) free (s);
1087   }
1088
1089   if (system_default_screenhack_count)  /* note: first_time is also true */
1090     {
1091       merge_system_screenhacks (p, system_default_screenhacks,
1092                                 system_default_screenhack_count);
1093       free_screenhack_list (system_default_screenhacks,
1094                             system_default_screenhack_count);
1095       system_default_screenhacks = 0;
1096       system_default_screenhack_count = 0;
1097     }
1098
1099   if (p->debug_p)
1100     {
1101       p->xsync_p = True;
1102       p->verbose_p = True;
1103       p->timestamp_p = True;
1104       p->initial_delay = 0;
1105     }
1106
1107   /* Throttle the various timeouts to reasonable values after reading the
1108      disk file. */
1109   stop_the_insanity (p);
1110 }
1111
1112
1113 /* If there are any hacks in the system-wide defaults that are not in
1114    the ~/.xscreensaver file, add the new ones to the end of the list.
1115    This does *not* actually save the file.
1116  */
1117 static void
1118 merge_system_screenhacks (saver_preferences *p,
1119                           screenhack **system_list, int system_count)
1120 {
1121   /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
1122      so fuck it. */
1123
1124   int made_space = 0;
1125   int i;
1126   for (i = 0; i < system_count; i++)
1127     {
1128       int j;
1129       Bool matched_p = False;
1130
1131       for (j = 0; j < p->screenhacks_count; j++)
1132         {
1133           char *name;
1134           if (!system_list[i]->name)
1135             system_list[i]->name = make_hack_name (system_list[i]->command);
1136
1137           name = p->screenhacks[j]->name;
1138           if (!name)
1139             name = make_hack_name (p->screenhacks[j]->command);
1140
1141           matched_p = !strcasecmp (name, system_list[i]->name);
1142
1143           if (name != p->screenhacks[j]->name)
1144             free (name);
1145
1146           if (matched_p)
1147             break;
1148         }
1149
1150       if (!matched_p)
1151         {
1152           /* We have an entry in the system-wide list that is not in the
1153              user's .xscreensaver file.  Add it to the end.
1154              Note that p->screenhacks is a single malloc block, not a
1155              linked list, so we have to realloc it.
1156            */
1157           screenhack *oh = system_list[i];
1158           screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
1159
1160           if (made_space == 0)
1161             {
1162               made_space = 10;
1163               p->screenhacks = (screenhack **)
1164                 realloc (p->screenhacks,
1165                          (p->screenhacks_count + made_space + 1)
1166                          * sizeof(screenhack));
1167               if (!p->screenhacks) abort();
1168             }
1169
1170           nh->enabled_p = oh->enabled_p;
1171           nh->visual    = oh->visual  ? strdup(oh->visual)  : 0;
1172           nh->name      = oh->name    ? strdup(oh->name)    : 0;
1173           nh->command   = oh->command ? strdup(oh->command) : 0;
1174
1175           p->screenhacks[p->screenhacks_count++] = nh;
1176           p->screenhacks[p->screenhacks_count] = 0;
1177           made_space--;
1178
1179 #if 0
1180           fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
1181                    (nh->name ? nh->name : make_hack_name (nh->command)));
1182 #endif
1183         }
1184     }
1185 }
1186
1187
1188 \f
1189 /* Parsing the programs resource.
1190  */
1191
1192 screenhack *
1193 parse_screenhack (const char *line)
1194 {
1195   screenhack *h = (screenhack *) calloc (1, sizeof(*h));
1196   const char *s;
1197
1198   h->enabled_p = True;
1199
1200   while (isspace(*line)) line++;                /* skip whitespace */
1201   if (*line == '-')                             /* handle "-" */
1202     {
1203       h->enabled_p = False;
1204       line++;
1205       while (isspace(*line)) line++;            /* skip whitespace */
1206     }
1207
1208   s = line;                                     /* handle "visual:" */
1209   while (*line && *line != ':' && *line != '"' && !isspace(*line))
1210     line++;
1211   if (*line != ':')
1212     line = s;
1213   else
1214     {
1215       h->visual = (char *) malloc (line-s+1);
1216       strncpy (h->visual, s, line-s);
1217       h->visual[line-s] = 0;
1218       if (*line == ':') line++;                 /* skip ":" */
1219       while (isspace(*line)) line++;            /* skip whitespace */
1220     }
1221
1222   if (*line == '"')                             /* handle "name" */
1223     {
1224       line++;
1225       s = line;
1226       while (*line && *line != '"')
1227         line++;
1228       h->name = (char *) malloc (line-s+1);
1229       strncpy (h->name, s, line-s);
1230       h->name[line-s] = 0;
1231       if (*line == '"') line++;                 /* skip "\"" */
1232       while (isspace(*line)) line++;            /* skip whitespace */
1233     }
1234
1235   h->command = format_command (line, False);    /* handle command */
1236   return h;
1237 }
1238
1239
1240 static char *
1241 format_command (const char *cmd, Bool wrap_p)
1242 {
1243   int tab = 30;
1244   int col = tab;
1245   char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
1246   const char *in = cmd;
1247   char *out = cmd2;
1248   while (*in)
1249     {
1250       /* shrink all whitespace to one space, for the benefit of the "demo"
1251          mode display.  We only do this when we can easily tell that the
1252          whitespace is not significant (no shell metachars).
1253        */
1254       switch (*in)
1255         {
1256         case '\'': case '"': case '`': case '\\':
1257           /* Metachars are scary.  Copy the rest of the line unchanged. */
1258           while (*in)
1259             *out++ = *in++, col++;
1260           break;
1261
1262         case ' ': case '\t':
1263           /* Squeeze all other whitespace down to one space. */
1264           while (*in == ' ' || *in == '\t')
1265             in++;
1266           *out++ = ' ', col++;
1267           break;
1268
1269         default:
1270           /* Copy other chars unchanged. */
1271           *out++ = *in++, col++;
1272           break;
1273         }
1274     }
1275
1276   *out = 0;
1277
1278   /* Strip trailing whitespace */
1279   while (out > cmd2 && isspace (out[-1]))
1280     *(--out) = 0;
1281
1282   return cmd2;
1283 }
1284
1285
1286 /* Returns a new string describing the shell command.
1287    This may be just the name of the program, capitalized.
1288    It also may be something from the resource database (gotten
1289    by looking for "hacks.XYZ.name", where XYZ is the program.)
1290  */
1291 char *
1292 make_hack_name (const char *shell_command)
1293 {
1294   char *s = strdup (shell_command);
1295   char *s2;
1296   char res_name[255];
1297
1298   for (s2 = s; *s2; s2++)       /* truncate at first whitespace */
1299     if (isspace (*s2))
1300       {
1301         *s2 = 0;
1302         break;
1303       }
1304
1305   s2 = strrchr (s, '/');        /* if pathname, take last component */
1306   if (s2)
1307     {
1308       s2 = strdup (s2+1);
1309       free (s);
1310       s = s2;
1311     }
1312
1313   if (strlen (s) > 50)          /* 51 is hereby defined as "unreasonable" */
1314     s[50] = 0;
1315
1316   sprintf (res_name, "hacks.%s.name", s);               /* resource? */
1317   s2 = get_string_resource (res_name, res_name);
1318   if (s2)
1319     {
1320       free (s);
1321       return s2;
1322     }
1323
1324   for (s2 = s; *s2; s2++)       /* if it has any capitals, return it */
1325     if (*s2 >= 'A' && *s2 <= 'Z')
1326       return s;
1327
1328   if (s[0] >= 'a' && s[0] <= 'z')                       /* else cap it */
1329     s[0] -= 'a'-'A';
1330   if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z')        /* (magic leading X) */
1331     s[1] -= 'a'-'A';
1332   if (s[0] == 'G' && s[1] == 'l' && 
1333       s[2] >= 'a' && s[2] <= 'z')                      /* (magic leading GL) */
1334     s[1] -= 'a'-'A',
1335     s[2] -= 'a'-'A';
1336   return s;
1337 }
1338
1339
1340 char *
1341 format_hack (screenhack *hack, Bool wrap_p)
1342 {
1343   int tab = 32;
1344   int size;
1345   char *h2, *out, *s;
1346   int col = 0;
1347
1348   char *def_name = make_hack_name (hack->command);
1349
1350   /* Don't ever write out a name for a hack if it's the same as the default.
1351    */
1352   if (hack->name && !strcmp (hack->name, def_name))
1353     {
1354       free (hack->name);
1355       hack->name = 0;
1356     }
1357   free (def_name);
1358
1359   size = (2 * (strlen(hack->command) +
1360                (hack->visual ? strlen(hack->visual) : 0) +
1361                (hack->name ? strlen(hack->name) : 0) +
1362                tab));
1363   h2 = (char *) malloc (size);
1364   out = h2;
1365
1366   if (!hack->enabled_p) *out++ = '-';           /* write disabled flag */
1367
1368   if (hack->visual && *hack->visual)            /* write visual name */
1369     {
1370       if (hack->enabled_p) *out++ = ' ';
1371       *out++ = ' ';
1372       strcpy (out, hack->visual);
1373       out += strlen (hack->visual);
1374       *out++ = ':';
1375       *out++ = ' ';
1376     }
1377
1378   *out = 0;
1379   col = string_columns (h2, strlen (h2), 0);
1380
1381   if (hack->name && *hack->name)                /* write pretty name */
1382     {
1383       int L = (strlen (hack->name) + 2);
1384       if (L + col < tab)
1385         out = stab_to (out, col, tab - L - 2);
1386       else
1387         *out++ = ' ';
1388       *out++ = '"';
1389       strcpy (out, hack->name);
1390       out += strlen (hack->name);
1391       *out++ = '"';
1392       *out = 0;
1393
1394       col = string_columns (h2, strlen (h2), 0);
1395       if (wrap_p && col >= tab)
1396         out = stab_to (out, col, 77);
1397       else
1398         *out++ = ' ';
1399
1400       if (out >= h2+size) abort();
1401     }
1402
1403   *out = 0;
1404   col = string_columns (h2, strlen (h2), 0);
1405   out = stab_to (out, col, tab);                /* indent */
1406
1407   if (out >= h2+size) abort();
1408   s = format_command (hack->command, wrap_p);
1409   strcpy (out, s);
1410   out += strlen (s);
1411   free (s);
1412   *out = 0;
1413
1414   return h2;
1415 }
1416
1417
1418 static void
1419 get_screenhacks (saver_preferences *p)
1420 {
1421   int i, j;
1422   int start = 0;
1423   int end = 0;
1424   int size;
1425   char *d;
1426
1427   d = get_string_resource ("monoPrograms", "MonoPrograms");
1428   if (d && !*d) { free(d); d = 0; }
1429   if (!d)
1430     d = get_string_resource ("colorPrograms", "ColorPrograms");
1431   if (d && !*d) { free(d); d = 0; }
1432
1433   if (d)
1434     {
1435       fprintf (stderr,
1436        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1437         see the manual for details.\n", blurb());
1438       free(d);
1439     }
1440
1441   d = get_string_resource ("programs", "Programs");
1442
1443   free_screenhack_list (p->screenhacks, p->screenhacks_count);
1444   p->screenhacks = 0;
1445   p->screenhacks_count = 0;
1446
1447   if (!d || !*d)
1448     return;
1449
1450   size = strlen (d);
1451
1452
1453   /* Count up the number of newlines (which will be equal to or larger than
1454      one less than the number of hacks.)
1455    */
1456   for (i = j = 0; d[i]; i++)
1457     if (d[i] == '\n')
1458       j++;
1459   j++;
1460
1461   p->screenhacks = (screenhack **) calloc (j + 1, sizeof (screenhack *));
1462
1463   /* Iterate over the lines in `d' (the string with newlines)
1464      and make new strings to stuff into the `screenhacks' array.
1465    */
1466   p->screenhacks_count = 0;
1467   while (start < size)
1468     {
1469       /* skip forward over whitespace. */
1470       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1471         start++;
1472
1473       /* skip forward to newline or end of string. */
1474       end = start;
1475       while (d[end] != 0 && d[end] != '\n')
1476         end++;
1477
1478       /* null terminate. */
1479       d[end] = 0;
1480
1481       p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1482       if (p->screenhacks_count >= i)
1483         abort();
1484
1485       start = end+1;
1486     }
1487
1488   free (d);
1489
1490   if (p->screenhacks_count == 0)
1491     {
1492       free (p->screenhacks);
1493       p->screenhacks = 0;
1494     }
1495 }
1496
1497
1498 /* Make sure all the values in the preferences struct are sane.
1499  */
1500 static void
1501 stop_the_insanity (saver_preferences *p)
1502 {
1503   if (p->passwd_timeout <= 0) p->passwd_timeout = 30000;         /* 30 secs */
1504   if (p->timeout < 15000) p->timeout = 15000;                    /* 15 secs */
1505   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
1506   if (p->pointer_timeout <= 0) p->pointer_timeout = 5000;        /*  5 secs */
1507   if (p->notice_events_timeout <= 0)
1508     p->notice_events_timeout = 10000;                            /* 10 secs */
1509   if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
1510     p->fade_p = False;
1511   if (! p->fade_p) p->unfade_p = False;
1512
1513   /* The DPMS settings may have the value 0.
1514      But if they are negative, or are a range less than 10 seconds,
1515      reset them to sensible defaults.  (Since that must be a mistake.)
1516    */
1517   if (p->dpms_standby != 0 &&
1518       p->dpms_standby < 10 * 1000)
1519     p->dpms_standby =  2 * 60 * 60 * 1000;                       /* 2 hours */
1520   if (p->dpms_suspend != 0 &&
1521       p->dpms_suspend < 10 * 1000)
1522     p->dpms_suspend =  2 * 60 * 60 * 1000;                       /* 2 hours */
1523   if (p->dpms_off != 0 &&
1524       p->dpms_off < 10 * 1000)
1525     p->dpms_off      = 4 * 60 * 60 * 1000;                       /* 4 hours */
1526
1527   if (p->dpms_standby == 0 &&      /* if *all* are 0, then DPMS is disabled */
1528       p->dpms_suspend == 0 &&
1529       p->dpms_off     == 0)
1530     p->dpms_enabled_p = False;
1531
1532   p->watchdog_timeout = p->cycle * 0.6;
1533   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
1534   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
1535 }