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