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