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