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