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