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