http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.31.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   /* The DPMS settings may have the value 0.
968      But if they are negative, or are a range less than 10 seconds,
969      reset them to sensible defaults.  (Since that must be a mistake.)
970    */
971   if (p->dpms_standby != 0 &&
972       p->dpms_standby < 10 * 1000)
973     p->dpms_standby =  2 * 60 * 60 * 1000;                       /* 2 hours */
974   if (p->dpms_suspend != 0 &&
975       p->dpms_suspend < 10 * 1000)
976     p->dpms_suspend =  2 * 60 * 60 * 1000;                       /* 2 hours */
977   if (p->dpms_off != 0 &&
978       p->dpms_off < 10 * 1000)
979     p->dpms_off      = 4 * 60 * 60 * 1000;                       /* 4 hours */
980
981   if (p->dpms_standby == 0 &&      /* if *all* are 0, then DPMS is disabled */
982       p->dpms_suspend == 0 &&
983       p->dpms_off     == 0)
984     p->dpms_enabled_p = False;
985
986
987   p->watchdog_timeout = p->cycle * 0.6;
988   if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000;   /* 30 secs */
989   if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /*  1 hr */
990
991   get_screenhacks (p);
992
993   if (system_default_screenhack_count)  /* note: first_time is also true */
994     {
995       merge_system_screenhacks (p, system_default_screenhacks,
996                                 system_default_screenhack_count);
997       free_screenhack_list (system_default_screenhacks,
998                             system_default_screenhack_count);
999       system_default_screenhacks = 0;
1000       system_default_screenhack_count = 0;
1001     }
1002
1003   if (p->debug_p)
1004     {
1005       p->xsync_p = True;
1006       p->verbose_p = True;
1007       p->timestamp_p = True;
1008       p->initial_delay = 0;
1009     }
1010 }
1011
1012
1013 /* If there are any hacks in the system-wide defaults that are not in
1014    the ~/.xscreensaver file, add the new ones to the end of the list.
1015    This does *not* actually save the file.
1016  */
1017 static void
1018 merge_system_screenhacks (saver_preferences *p,
1019                           screenhack **system_list, int system_count)
1020 {
1021   /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
1022      so fuck it. */
1023
1024   int made_space = 0;
1025   int i;
1026   for (i = 0; i < system_count; i++)
1027     {
1028       int j;
1029       Bool matched_p = False;
1030
1031       for (j = 0; j < p->screenhacks_count; j++)
1032         {
1033           char *name;
1034           if (!system_list[i]->name)
1035             system_list[i]->name = make_hack_name (system_list[i]->command);
1036
1037           name = p->screenhacks[j]->name;
1038           if (!name)
1039             name = make_hack_name (p->screenhacks[j]->command);
1040
1041           matched_p = !strcasecmp (name, system_list[i]->name);
1042
1043           if (name != p->screenhacks[j]->name)
1044             free (name);
1045
1046           if (matched_p)
1047             break;
1048         }
1049
1050       if (!matched_p)
1051         {
1052           /* We have an entry in the system-wide list that is not in the
1053              user's .xscreensaver file.  Add it to the end.
1054              Note that p->screenhacks is a single malloc block, not a
1055              linked list, so we have to realloc it.
1056            */
1057           screenhack *oh = system_list[i];
1058           screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
1059
1060           if (made_space == 0)
1061             {
1062               made_space = 10;
1063               p->screenhacks = (screenhack **)
1064                 realloc (p->screenhacks,
1065                          (p->screenhacks_count + made_space) 
1066                          * sizeof(screenhack));
1067               if (!p->screenhacks) abort();
1068             }
1069
1070           nh->enabled_p = oh->enabled_p;
1071           nh->visual    = oh->visual  ? strdup(oh->visual)  : 0;
1072           nh->name      = oh->name    ? strdup(oh->name)    : 0;
1073           nh->command   = oh->command ? strdup(oh->command) : 0;
1074
1075           p->screenhacks[p->screenhacks_count++] = nh;
1076           made_space--;
1077
1078 #if 0
1079           fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
1080                    (nh->name ? nh->name : make_hack_name (nh->command)));
1081 #endif
1082         }
1083     }
1084 }
1085
1086
1087 \f
1088 /* Parsing the programs resource.
1089  */
1090
1091 screenhack *
1092 parse_screenhack (const char *line)
1093 {
1094   screenhack *h = (screenhack *) calloc (1, sizeof(*h));
1095   const char *s;
1096
1097   h->enabled_p = True;
1098
1099   while (isspace(*line)) line++;                /* skip whitespace */
1100   if (*line == '-')                             /* handle "-" */
1101     {
1102       h->enabled_p = False;
1103       line++;
1104       while (isspace(*line)) line++;            /* skip whitespace */
1105     }
1106
1107   s = line;                                     /* handle "visual:" */
1108   while (*line && *line != ':' && *line != '"' && !isspace(*line))
1109     line++;
1110   if (*line != ':')
1111     line = s;
1112   else
1113     {
1114       h->visual = (char *) malloc (line-s+1);
1115       strncpy (h->visual, s, line-s);
1116       h->visual[line-s] = 0;
1117       if (*line == ':') line++;                 /* skip ":" */
1118       while (isspace(*line)) line++;            /* skip whitespace */
1119     }
1120
1121   if (*line == '"')                             /* handle "name" */
1122     {
1123       line++;
1124       s = line;
1125       while (*line && *line != '"')
1126         line++;
1127       h->name = (char *) malloc (line-s+1);
1128       strncpy (h->name, s, line-s);
1129       h->name[line-s] = 0;
1130       if (*line == '"') line++;                 /* skip "\"" */
1131       while (isspace(*line)) line++;            /* skip whitespace */
1132     }
1133
1134   h->command = format_command (line, False);    /* handle command */
1135   return h;
1136 }
1137
1138
1139 static char *
1140 format_command (const char *cmd, Bool wrap_p)
1141 {
1142   int tab = 30;
1143   int col = tab;
1144   char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
1145   const char *in = cmd;
1146   char *out = cmd2;
1147   while (*in)
1148     {
1149       /* shrink all whitespace to one space, for the benefit of the "demo"
1150          mode display.  We only do this when we can easily tell that the
1151          whitespace is not significant (no shell metachars).
1152        */
1153       switch (*in)
1154         {
1155         case '\'': case '"': case '`': case '\\':
1156           /* Metachars are scary.  Copy the rest of the line unchanged. */
1157           while (*in)
1158             *out++ = *in++, col++;
1159           break;
1160
1161         case ' ': case '\t':
1162           /* Squeeze all other whitespace down to one space. */
1163           while (*in == ' ' || *in == '\t')
1164             in++;
1165           *out++ = ' ', col++;
1166           break;
1167
1168         default:
1169           /* Copy other chars unchanged. */
1170           *out++ = *in++, col++;
1171           break;
1172         }
1173     }
1174
1175   *out = 0;
1176
1177   /* Strip trailing whitespace */
1178   while (out > cmd2 && isspace (out[-1]))
1179     *(--out) = 0;
1180
1181   return cmd2;
1182 }
1183
1184
1185 /* Returns a new string describing the shell command.
1186    This may be just the name of the program, capitalized.
1187    It also may be something from the resource database (gotten
1188    by looking for "hacks.XYZ.name", where XYZ is the program.)
1189  */
1190 char *
1191 make_hack_name (const char *shell_command)
1192 {
1193   char *s = strdup (shell_command);
1194   char *s2;
1195   char res_name[255];
1196
1197   for (s2 = s; *s2; s2++)       /* truncate at first whitespace */
1198     if (isspace (*s2))
1199       {
1200         *s2 = 0;
1201         break;
1202       }
1203
1204   s2 = strrchr (s, '/');        /* if pathname, take last component */
1205   if (s2)
1206     {
1207       s2 = strdup (s2+1);
1208       free (s);
1209       s = s2;
1210     }
1211
1212   if (strlen (s) > 50)          /* 51 is hereby defined as "unreasonable" */
1213     s[50] = 0;
1214
1215   sprintf (res_name, "hacks.%s.name", s);               /* resource? */
1216   s2 = get_string_resource (res_name, res_name);
1217   if (s2)
1218     return s2;
1219
1220   for (s2 = s; *s2; s2++)       /* if it has any capitals, return it */
1221     if (*s2 >= 'A' && *s2 <= 'Z')
1222       return s;
1223
1224   if (s[0] >= 'a' && s[0] <= 'z')                       /* else cap it */
1225     s[0] -= 'a'-'A';
1226   if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z')        /* (magic leading X) */
1227     s[1] -= 'a'-'A';
1228   return s;
1229 }
1230
1231
1232 char *
1233 format_hack (screenhack *hack, Bool wrap_p)
1234 {
1235   int tab = 32;
1236   int size = (2 * (strlen(hack->command) +
1237                    (hack->visual ? strlen(hack->visual) : 0) +
1238                    (hack->name ? strlen(hack->name) : 0) +
1239                    tab));
1240   char *h2 = (char *) malloc (size);
1241   char *out = h2;
1242   char *s;
1243   int col = 0;
1244
1245   if (!hack->enabled_p) *out++ = '-';           /* write disabled flag */
1246
1247   if (hack->visual && *hack->visual)            /* write visual name */
1248     {
1249       if (hack->enabled_p) *out++ = ' ';
1250       *out++ = ' ';
1251       strcpy (out, hack->visual);
1252       out += strlen (hack->visual);
1253       *out++ = ':';
1254       *out++ = ' ';
1255     }
1256
1257   *out = 0;
1258   col = string_columns (h2, strlen (h2), 0);
1259
1260   if (hack->name && *hack->name)                /* write pretty name */
1261     {
1262       int L = (strlen (hack->name) + 2);
1263       if (L + col < tab)
1264         out = stab_to (out, col, tab - L - 2);
1265       else
1266         *out++ = ' ';
1267       *out++ = '"';
1268       strcpy (out, hack->name);
1269       out += strlen (hack->name);
1270       *out++ = '"';
1271       *out = 0;
1272
1273       col = string_columns (h2, strlen (h2), 0);
1274       if (wrap_p && col >= tab)
1275         {
1276           out = stab_to (out, col, 77);
1277           *out += strlen(out);
1278         }
1279       else
1280         *out++ = ' ';
1281
1282       if (out >= h2+size) abort();
1283     }
1284
1285   *out = 0;
1286   col = string_columns (h2, strlen (h2), 0);
1287   out = stab_to (out, col, tab);                /* indent */
1288
1289   if (out >= h2+size) abort();
1290   s = format_command (hack->command, wrap_p);
1291   strcpy (out, s);
1292   out += strlen (s);
1293   free (s);
1294   *out = 0;
1295
1296   return h2;
1297 }
1298
1299
1300 static void
1301 get_screenhacks (saver_preferences *p)
1302 {
1303   int i = 0;
1304   int start = 0;
1305   int end = 0;
1306   int size;
1307   char *d;
1308
1309   d = get_string_resource ("monoPrograms", "MonoPrograms");
1310   if (d && !*d) { free(d); d = 0; }
1311   if (!d)
1312     d = get_string_resource ("colorPrograms", "ColorPrograms");
1313   if (d && !*d) { free(d); d = 0; }
1314
1315   if (d)
1316     {
1317       fprintf (stderr,
1318        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1319         see the manual for details.\n", blurb());
1320       free(d);
1321     }
1322
1323   d = get_string_resource ("programs", "Programs");
1324
1325   free_screenhack_list (p->screenhacks, p->screenhacks_count);
1326   p->screenhacks = 0;
1327   p->screenhacks_count = 0;
1328
1329   if (!d || !*d)
1330     return;
1331
1332   size = strlen (d);
1333
1334
1335   /* Count up the number of newlines (which will be equal to or larger than
1336      the number of hacks.)
1337    */
1338   i = 0;
1339   for (i = 0; d[i]; i++)
1340     if (d[i] == '\n')
1341       i++;
1342   i++;
1343
1344   p->screenhacks = (screenhack **) calloc (sizeof (screenhack *), i+1);
1345
1346   /* Iterate over the lines in `d' (the string with newlines)
1347      and make new strings to stuff into the `screenhacks' array.
1348    */
1349   p->screenhacks_count = 0;
1350   while (start < size)
1351     {
1352       /* skip forward over whitespace. */
1353       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1354         start++;
1355
1356       /* skip forward to newline or end of string. */
1357       end = start;
1358       while (d[end] != 0 && d[end] != '\n')
1359         end++;
1360
1361       /* null terminate. */
1362       d[end] = 0;
1363
1364       p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1365       if (p->screenhacks_count >= i)
1366         abort();
1367
1368       start = end+1;
1369     }
1370
1371   if (p->screenhacks_count == 0)
1372     {
1373       free (p->screenhacks);
1374       p->screenhacks = 0;
1375     }
1376 }