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