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