From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / driver / prefs.c
1 /* dotfile.c --- management of the ~/.xscreensaver file.
2  * xscreensaver, Copyright (c) 1998-2016 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 #include <sys/param.h>   /* for PATH_MAX */
30
31 #include <X11/Xlib.h>
32 #include <X11/Xresource.h>
33
34 #ifndef VMS
35 # include <pwd.h>
36 #else /* VMS */
37 # include "vms-pwd.h"
38 #endif /* VMS */
39
40
41 /* This file doesn't need the Xt headers, so stub these types out... */
42 #undef XtPointer
43 #define XtAppContext void*
44 #define XtIntervalId void*
45 #define XtPointer    void*
46 #define Widget       void*
47
48
49 /* Just in case there's something pathological about stat.h... */
50 #ifndef  S_IRUSR
51 # define S_IRUSR 00400
52 #endif
53 #ifndef  S_IWUSR
54 # define S_IWUSR 00200
55 #endif
56 #ifndef  S_IXUSR
57 # define S_IXUSR 00100
58 #endif
59 #ifndef  S_IXGRP
60 # define S_IXGRP 00010
61 #endif
62 #ifndef  S_IXOTH
63 # define S_IXOTH 00001
64 #endif
65
66
67 #include "version.h"
68 #include "prefs.h"
69 #include "resources.h"
70
71 /* don't use realpath() on fedora system */
72 #ifdef _FORTIFY_SOURCE
73 #undef HAVE_REALPATH
74 #endif
75
76
77 extern char *progname;
78 extern char *progclass;
79 extern const char *blurb (void);
80
81
82
83 static void get_screenhacks (Display *, saver_preferences *);
84 static char *format_command (const char *cmd, Bool wrap_p);
85 static void merge_system_screenhacks (Display *, saver_preferences *,
86                                       screenhack **system_list, int count);
87 static void stop_the_insanity (saver_preferences *p);
88
89
90 static char *
91 chase_symlinks (const char *file)
92 {
93 # ifdef HAVE_REALPATH
94   if (file)
95     {
96 # ifndef PATH_MAX
97 #  ifdef MAXPATHLEN
98 #   define PATH_MAX MAXPATHLEN
99 #  else
100 #   define PATH_MAX 2048
101 #  endif
102 # endif
103       char buf[PATH_MAX];
104       if (realpath (file, buf))
105         return strdup (buf);
106
107 /*      sprintf (buf, "%.100s: realpath %.200s", blurb(), file);
108       perror(buf);*/
109     }
110 # endif /* HAVE_REALPATH */
111   return 0;
112 }
113
114
115 static Bool
116 i_am_a_nobody (uid_t uid)
117 {
118   struct passwd *p;
119
120   p = getpwnam ("nobody");
121   if (! p) p = getpwnam ("noaccess");
122   if (! p) p = getpwnam ("daemon");
123
124   if (! p) /* There is no nobody? */
125     return False;
126
127   return (uid == p->pw_uid);
128 }
129
130
131 const char *
132 init_file_name (void)
133 {
134   static char *file = 0;
135
136   if (!file)
137     {
138       uid_t uid = getuid ();
139       struct passwd *p = getpwuid (uid);
140
141       if (i_am_a_nobody (uid))
142         /* If we're running as nobody, then use root's .xscreensaver file
143            (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely
144            to be different -- if we didn't do this, then xscreensaver-demo
145            would appear to have no effect when the luser is running as root.)
146          */
147         uid = 0;
148
149       p = getpwuid (uid);
150
151       if (!p || !p->pw_name || !*p->pw_name)
152         {
153           fprintf (stderr, "%s: couldn't get user info of uid %d\n",
154                    blurb(), getuid ());
155           file = "";
156         }
157       else if (!p->pw_dir || !*p->pw_dir)
158         {
159           fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
160                    blurb(), (p->pw_name ? p->pw_name : "???"));
161           file = "";
162         }
163       else
164         {
165           const char *home = p->pw_dir;
166           const char *name = ".xscreensaver";
167           file = (char *) malloc(strlen(home) + strlen(name) + 2);
168           strcpy(file, home);
169           if (!*home || home[strlen(home)-1] != '/')
170             strcat(file, "/");
171           strcat(file, name);
172         }
173     }
174
175   if (file && *file)
176     return file;
177   else
178     return 0;
179 }
180
181
182 static const char *
183 init_file_tmp_name (void)
184 {
185   static char *file = 0;
186   if (!file)
187     {
188       const char *name = init_file_name();
189       const char *suffix = ".tmp";
190
191       char *n2 = chase_symlinks (name);
192       if (n2) name = n2;
193
194       if (!name || !*name)
195         file = "";
196       else
197         {
198           file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
199           strcpy(file, name);
200           strcat(file, suffix);
201         }
202
203       if (n2) free (n2);
204     }
205
206   if (file && *file)
207     return file;
208   else
209     return 0;
210 }
211
212 static int
213 get_byte_resource (Display *dpy, char *name, char *class)
214 {
215   char *s = get_string_resource (dpy, name, class);
216   char *s2 = s;
217   int n = 0;
218   if (!s) return 0;
219
220   while (isspace(*s2)) s2++;
221   while (*s2 >= '0' && *s2 <= '9')
222     {
223       n = (n * 10) + (*s2 - '0');
224       s2++;
225     }
226   while (isspace(*s2)) s2++;
227   if      (*s2 == 'k' || *s2 == 'K') n <<= 10;
228   else if (*s2 == 'm' || *s2 == 'M') n <<= 20;
229   else if (*s2 == 'g' || *s2 == 'G') n <<= 30;
230   else if (*s2)
231     {
232     LOSE:
233       fprintf (stderr, "%s: %s must be a number of bytes, not \"%s\".\n",
234                progname, name, s);
235       free (s);
236       return 0;
237     }
238   s2++;
239   if (*s2 == 'b' || *s2 == 'B') s2++;
240   while (isspace(*s2)) s2++;
241   if (*s2) goto LOSE;
242
243   free (s);
244   return n;
245 }
246
247
248 static const char * const prefs[] = {
249   "timeout",
250   "cycle",
251   "lock",
252   "lockVTs",                    /* not saved */
253   "lockTimeout",
254   "passwdTimeout",
255   "visualID",
256   "installColormap",
257   "verbose",
258   "timestamp",
259   "splash",
260   "splashDuration",
261   "quad",
262   "demoCommand",
263   "prefsCommand",
264   "newLoginCommand",
265   "helpURL",                    /* not saved */
266   "loadURL",                    /* not saved */
267   "newLoginCommand",            /* not saved */
268   "nice",
269   "memoryLimit",
270   "fade",
271   "unfade",
272   "fadeSeconds",
273   "fadeTicks",
274   "captureStderr",
275   "captureStdout",              /* not saved -- obsolete */
276   "logFile",                    /* not saved */
277   "ignoreUninstalledPrograms",
278   "font",
279   "dpmsEnabled",
280   "dpmsQuickOff",
281   "dpmsStandby",
282   "dpmsSuspend",
283   "dpmsOff",
284   "grabDesktopImages",
285   "grabVideoFrames",
286   "chooseRandomImages",
287   "imageDirectory",
288   "mode",
289   "selected",
290   "textMode",
291   "textLiteral",
292   "textFile",
293   "textProgram",
294   "textURL",
295   "",
296   "programs",
297   "",
298   "pointerPollTime",
299   "pointerHysteresis",
300   "windowCreationTimeout",
301   "initialDelay",
302   "sgiSaverExtension",          /* not saved -- obsolete */
303   "mitSaverExtension",          /* not saved -- obsolete */
304   "xidleExtension",             /* not saved -- obsolete */
305   "GetViewPortIsFullOfLies",
306   "procInterrupts",
307   "xinputExtensionDev",
308   "overlayStderr",
309   "overlayTextBackground",      /* not saved -- X resources only */
310   "overlayTextForeground",      /* not saved -- X resources only */
311   "bourneShell",                /* not saved -- X resources only */
312   "authWarningSlack",
313   0
314 };
315
316 static char *
317 strip (char *s)
318 {
319   char *s2;
320   while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
321     s++;
322   for (s2 = s; *s2; s2++)
323     ;
324   for (s2--; s2 >= s; s2--) 
325     if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n') 
326       *s2 = 0;
327     else
328       break;
329   return s;
330 }
331
332 \f
333 /* Reading
334  */
335
336 static int
337 handle_entry (XrmDatabase *db, const char *key, const char *value,
338               const char *filename, int line)
339 {
340   int i;
341   for (i = 0; prefs[i]; i++)
342     if (*prefs[i] && !strcasecmp(key, prefs[i]))
343       {
344         char *val = strdup(value);
345         char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
346         strcpy(spec, progclass);
347         strcat(spec, ".");
348         strcat(spec, prefs[i]);
349
350         XrmPutStringResource (db, spec, val);
351
352         free(spec);
353         free(val);
354         return 0;
355       }
356
357   fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
358           blurb(), filename, line, key);
359   return 1;
360 }
361
362
363 static int
364 parse_init_file (saver_preferences *p)
365 {
366   time_t write_date = 0;
367   const char *name = init_file_name();
368   int line = 0;
369   struct stat st;
370   FILE *in;
371   int buf_size = 1024;
372   char *buf;
373
374   if (!name) return 0;
375
376   if (stat(name, &st) != 0)
377     {
378       p->init_file_date = 0;
379       return 0;
380     }
381
382   in = fopen(name, "r");
383   if (!in)
384     {
385       char *buf = (char *) malloc(1024 + strlen(name));
386       sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
387       perror(buf);
388       free(buf);
389       return -1;
390     }
391
392   if (fstat (fileno(in), &st) == 0)
393     {
394       write_date = st.st_mtime;
395     }
396   else
397     {
398       char *buf = (char *) malloc(1024 + strlen(name));
399       sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
400       perror(buf);
401       free(buf);
402       return -1;
403     }
404
405   buf = (char *) malloc(buf_size);
406
407   while (fgets (buf, buf_size-1, in))
408     {
409       char *key, *value;
410       int L = strlen(buf);
411
412       line++;
413       while (L > 2 &&
414              (buf[L-1] != '\n' ||       /* whole line didn't fit in buffer */
415               buf[L-2] == '\\'))        /* or line ended with backslash */
416         {
417           if (buf[L-2] == '\\')         /* backslash-newline gets swallowed */
418             {
419               buf[L-2] = 0;
420               L -= 2;
421             }
422           buf_size += 1024;
423           buf = (char *) realloc(buf, buf_size);
424           if (!buf) exit(1);
425
426           line++;
427           if (!fgets (buf + L, buf_size-L-1, in))
428             break;
429           L = strlen(buf);
430         }
431
432       /* Now handle other backslash escapes. */
433       {
434         int i, j;
435         for (i = 0; buf[i]; i++)
436           if (buf[i] == '\\')
437             {
438               switch (buf[i+1])
439                 {
440                 case 'n': buf[i] = '\n'; break;
441                 case 'r': buf[i] = '\r'; break;
442                 case 't': buf[i] = '\t'; break;
443                 default:  buf[i] = buf[i+1]; break;
444                 }
445               for (j = i+2; buf[j]; j++)
446                 buf[j-1] = buf[j];
447               buf[j-1] = 0;
448             }
449       }
450
451       key = strip(buf);
452
453       if (*key == '#' || *key == '!' || *key == ';' ||
454           *key == '\n' || *key == 0)
455         continue;
456
457       value = strchr (key, ':');
458       if (!value)
459         {
460           fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
461                   name, line, key);
462           continue;
463         }
464       else
465         {
466           *value++ = 0;
467           value = strip(value);
468         }
469
470       if (!p->db) abort();
471       handle_entry (&p->db, key, value, name, line);
472     }
473   fclose (in);
474   free(buf);
475
476   p->init_file_date = write_date;
477   return 0;
478 }
479
480
481 Bool
482 init_file_changed_p (saver_preferences *p)
483 {
484   const char *name = init_file_name();
485   struct stat st;
486
487   if (!name) return False;
488
489   if (stat(name, &st) != 0)
490     return False;
491
492   if (p->init_file_date == st.st_mtime)
493     return False;
494
495   return True;
496 }
497
498 \f
499 /* Writing
500  */
501
502 static int
503 tab_to (FILE *out, int from, int to)
504 {
505   int tab_width = 8;
506   int to_mod = (to / tab_width) * tab_width;
507   while (from < to_mod)
508     {
509       fprintf(out, "\t");
510       from = (((from / tab_width) + 1) * tab_width);
511     }
512   while (from < to)
513     {
514       fprintf(out, " ");
515       from++;
516     }
517   return from;
518 }
519
520 static char *
521 stab_to (char *out, int from, int to)
522 {
523   int tab_width = 8;
524   int to_mod = (to / tab_width) * tab_width;
525   while (from < to_mod)
526     {
527       *out++ = '\t';
528       from = (((from / tab_width) + 1) * tab_width);
529     }
530   while (from < to)
531     {
532       *out++ = ' ';
533       from++;
534     }
535   return out;
536 }
537
538 static int
539 string_columns (const char *string, int length, int start)
540 {
541   int tab_width = 8;
542   int col = start;
543   const char *end = string + length;
544   while (string < end)
545     {
546       if (*string == '\n')
547         col = 0;
548       else if (*string == '\t')
549         col = (((col / tab_width) + 1) * tab_width);
550       else
551         col++;
552       string++;
553     }
554   return col;
555 }
556
557
558 static void
559 write_entry (FILE *out, const char *key, const char *value)
560 {
561   char *v = strdup(value ? value : "");
562   char *v2 = v;
563   char *nl = 0;
564   int col;
565   Bool programs_p = (!strcmp(key, "programs"));
566   int tab = (programs_p ? 32 : 16);
567   Bool first = True;
568
569   fprintf(out, "%s:", key);
570   col = strlen(key) + 1;
571
572   if (strlen(key) > 14)
573     col = tab_to (out, col, 20);
574
575   while (1)
576     {
577       if (!programs_p)
578         v2 = strip(v2);
579       nl = strchr(v2, '\n');
580       if (nl)
581         *nl = 0;
582
583       if (first && programs_p)
584         {
585           col = tab_to (out, col, 77);
586           fprintf (out, " \\\n");
587           col = 0;
588         }
589
590       if (first)
591         first = False;
592       else
593         {
594           col = tab_to (out, col, 75);
595           fprintf (out, " \\n\\\n");
596           col = 0;
597         }
598
599       if (!programs_p)
600         col = tab_to (out, col, tab);
601
602       if (programs_p &&
603           string_columns(v2, strlen (v2), col) + col > 75)
604         {
605           int L = strlen (v2);
606           int start = 0;
607           int end = start;
608           while (start < L)
609             {
610               while (v2[end] == ' ' || v2[end] == '\t')
611                 end++;
612               while (v2[end] != ' ' && v2[end] != '\t' &&
613                      v2[end] != '\n' && v2[end] != 0)
614                 end++;
615               if (string_columns (v2 + start, (end - start), col) >= 74)
616                 {
617                   col = tab_to (out, col, 75);
618                   fprintf(out, "   \\\n");
619                   col = tab_to (out, 0, tab + 2);
620                   while (v2[start] == ' ' || v2[start] == '\t')
621                     start++;
622                 }
623
624               col = string_columns (v2 + start, (end - start), col);
625               while (start < end)
626                 fputc(v2[start++], out);
627             }
628         }
629       else
630         {
631           fprintf (out, "%s", v2);
632           col += string_columns(v2, strlen (v2), col);
633         }
634
635       if (nl)
636         v2 = nl + 1;
637       else
638         break;
639     }
640
641   fprintf(out, "\n");
642   free(v);
643 }
644
645 int
646 write_init_file (Display *dpy,
647                  saver_preferences *p, const char *version_string,
648                  Bool verbose_p)
649 {
650   int status = -1;
651   const char *name = init_file_name();
652   const char *tmp_name = init_file_tmp_name();
653   char *n2 = chase_symlinks (name);
654   struct stat st;
655   int i, j;
656
657   /* Kludge, since these aren't in the saver_preferences struct as strings...
658    */
659   char *visual_name;
660   char *programs;
661   Bool overlay_stderr_p;
662   char *stderr_font;
663   FILE *out;
664
665   if (!name) goto END;
666
667   if (n2) name = n2;
668
669   /* Throttle the various timeouts to reasonable values before writing
670      the file to disk. */
671   stop_the_insanity (p);
672
673
674   if (verbose_p)
675     fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
676
677   unlink (tmp_name);
678   out = fopen(tmp_name, "w");
679   if (!out)
680     {
681       char *buf = (char *) malloc(1024 + strlen(name));
682       sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
683       perror(buf);
684       free(buf);
685       goto END;
686     }
687
688   /* Give the new .xscreensaver file the same permissions as the old one;
689      except ensure that it is readable and writable by owner, and not
690      executable.  Extra hack: if we're running as root, make the file
691      be world-readable (so that the daemon, running as "nobody", will
692      still be able to read it.)
693    */
694   if (stat(name, &st) == 0)
695     {
696       mode_t mode = st.st_mode;
697       mode |= S_IRUSR | S_IWUSR;                /* read/write by user */
698       mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);   /* executable by none */
699
700       if (getuid() == (uid_t) 0)                /* read by group/other */
701         mode |= S_IRGRP | S_IROTH;
702
703       if (fchmod (fileno(out), mode) != 0)
704         {
705           char *buf = (char *) malloc(1024 + strlen(name));
706           sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
707                    tmp_name, (unsigned int) mode);
708           perror(buf);
709           free(buf);
710           goto END;
711         }
712     }
713
714   /* Kludge, since these aren't in the saver_preferences struct... */
715   visual_name = get_string_resource (dpy, "visualID", "VisualID");
716   programs = 0;
717   overlay_stderr_p = get_boolean_resource (dpy, "overlayStderr", "Boolean");
718   stderr_font = get_string_resource (dpy, "font", "Font");
719
720   i = 0;
721   {
722     char *ss;
723     char **hack_strings = (char **)
724       calloc (p->screenhacks_count, sizeof(char *));
725
726     for (j = 0; j < p->screenhacks_count; j++)
727       {
728         hack_strings[j] = format_hack (dpy, p->screenhacks[j], True);
729         i += strlen (hack_strings[j]);
730         i += 2;
731       }
732
733     ss = programs = (char *) malloc(i + 10);
734     *ss = 0;
735     for (j = 0; j < p->screenhacks_count; j++)
736       {
737         strcat (ss, hack_strings[j]);
738         free (hack_strings[j]);
739         ss += strlen(ss);
740         *ss++ = '\n';
741         *ss = 0;
742       }
743     free (hack_strings);
744   }
745
746   {
747     struct passwd *pw = getpwuid (getuid ());
748     char *whoami = (pw && pw->pw_name && *pw->pw_name
749                     ? pw->pw_name
750                     : "<unknown>");
751     time_t now = time ((time_t *) 0);
752     char *timestr = (char *) ctime (&now);
753     char *nl = (char *) strchr (timestr, '\n');
754     if (nl) *nl = 0;
755     fprintf (out,
756              "# %s Preferences File\n"
757              "# Written by %s %s for %s on %s.\n"
758              "# https://www.jwz.org/xscreensaver/\n"
759              "\n",
760              progclass, progname, version_string, whoami, timestr);
761   }
762
763   for (j = 0; prefs[j]; j++)
764     {
765       char buf[255];
766       const char *pr = prefs[j];
767       enum pref_type { pref_str, pref_int, pref_bool, pref_byte, pref_time
768       } type = pref_str;
769       const char *s = 0;
770       int i = 0;
771       Bool b = False;
772       Time t = 0;
773
774       if (pr && !*pr)
775         {
776           fprintf(out, "\n");
777           continue;
778         }
779
780 # undef CHECK
781 # define CHECK(X) else if (!strcmp(pr, X))
782       if (!pr || !*pr)          ;
783       CHECK("timeout")          type = pref_time, t = p->timeout;
784       CHECK("cycle")            type = pref_time, t = p->cycle;
785       CHECK("lock")             type = pref_bool, b = p->lock_p;
786       CHECK("lockVTs")          continue;  /* don't save, unused */
787       CHECK("lockTimeout")      type = pref_time, t = p->lock_timeout;
788       CHECK("passwdTimeout")    type = pref_time, t = p->passwd_timeout;
789       CHECK("visualID")         type = pref_str,  s =    visual_name;
790       CHECK("installColormap")  type = pref_bool, b = p->install_cmap_p;
791       CHECK("verbose")          type = pref_bool, b = p->verbose_p;
792       CHECK("timestamp")        type = pref_bool, b = p->timestamp_p;
793       CHECK("splash")           type = pref_bool, b = p->splash_p;
794       CHECK("splashDuration")   type = pref_time, t = p->splash_duration;
795 # ifdef QUAD_MODE
796       CHECK("quad")             type = pref_bool, b = p->quad_p;
797 # else  /* !QUAD_MODE */
798       CHECK("quad")             continue;  /* don't save */
799 # endif /* !QUAD_MODE */
800       CHECK("demoCommand")      type = pref_str,  s = p->demo_command;
801       CHECK("prefsCommand")     type = pref_str,  s = p->prefs_command;
802 /*    CHECK("helpURL")          type = pref_str,  s = p->help_url; */
803       CHECK("helpURL")          continue;  /* don't save */
804 /*    CHECK("loadURL")          type = pref_str,  s = p->load_url_command; */
805       CHECK("loadURL")          continue;  /* don't save */
806 /*    CHECK("newLoginCommand")  type = pref_str,  s = p->new_login_command; */
807       CHECK("newLoginCommand")  continue;  /* don't save */
808       CHECK("nice")             type = pref_int,  i = p->nice_inferior;
809       CHECK("memoryLimit")      type = pref_byte, i = p->inferior_memory_limit;
810       CHECK("fade")             type = pref_bool, b = p->fade_p;
811       CHECK("unfade")           type = pref_bool, b = p->unfade_p;
812       CHECK("fadeSeconds")      type = pref_time, t = p->fade_seconds;
813       CHECK("fadeTicks")        type = pref_int,  i = p->fade_ticks;
814       CHECK("captureStderr")    type = pref_bool, b = p->capture_stderr_p;
815       CHECK("captureStdout")    continue;  /* don't save */
816       CHECK("logFile")          continue;  /* don't save */
817       CHECK("ignoreUninstalledPrograms")
818                                 type = pref_bool, b = p->ignore_uninstalled_p;
819
820       CHECK("font")             type = pref_str,  s =    stderr_font;
821
822       CHECK("dpmsEnabled")      type = pref_bool, b = p->dpms_enabled_p;
823       CHECK("dpmsQuickOff")     type = pref_bool, b = p->dpms_quickoff_p;
824       CHECK("dpmsStandby")      type = pref_time, t = p->dpms_standby;
825       CHECK("dpmsSuspend")      type = pref_time, t = p->dpms_suspend;
826       CHECK("dpmsOff")          type = pref_time, t = p->dpms_off;
827
828       CHECK("grabDesktopImages") type =pref_bool, b = p->grab_desktop_p;
829       CHECK("grabVideoFrames")   type =pref_bool, b = p->grab_video_p;
830       CHECK("chooseRandomImages")type =pref_bool, b = p->random_image_p;
831       CHECK("imageDirectory")    type =pref_str,  s = p->image_directory;
832
833       CHECK("mode")             type = pref_str,
834                                 s = (p->mode == ONE_HACK ? "one" :
835                                      p->mode == BLANK_ONLY ? "blank" :
836                                      p->mode == DONT_BLANK ? "off" :
837                                      p->mode == RANDOM_HACKS_SAME
838                                      ? "random-same"
839                                      : "random");
840       CHECK("selected")         type = pref_int,  i = p->selected_hack;
841
842       CHECK("textMode")         type = pref_str,
843                                 s = (p->tmode == TEXT_URL     ? "url" :
844                                      p->tmode == TEXT_LITERAL ? "literal" :
845                                      p->tmode == TEXT_FILE    ? "file" :
846                                      p->tmode == TEXT_PROGRAM ? "program" :
847                                      "date");
848       CHECK("textLiteral")      type = pref_str,  s = p->text_literal;
849       CHECK("textFile")         type = pref_str,  s = p->text_file;
850       CHECK("textProgram")      type = pref_str,  s = p->text_program;
851       CHECK("textURL")          type = pref_str,  s = p->text_url;
852
853       CHECK("programs")         type = pref_str,  s =    programs;
854       CHECK("pointerPollTime")  type = pref_time, t = p->pointer_timeout;
855       CHECK("pointerHysteresis")type = pref_int,  i = p->pointer_hysteresis;
856       CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
857       CHECK("initialDelay")     type = pref_time, t = p->initial_delay;
858       CHECK("sgiSaverExtension") continue;  /* don't save */
859       CHECK("mitSaverExtension") continue;  /* don't save */
860       CHECK("xidleExtension")    continue;  /* don't save */
861       CHECK("procInterrupts")   type = pref_bool, b = p->use_proc_interrupts;
862       CHECK("xinputExtensionDev") type = pref_bool, b = p->use_xinput_extension;
863       CHECK("GetViewPortIsFullOfLies")  type = pref_bool,
864                                         b = p->getviewport_full_of_lies_p;
865       CHECK("overlayStderr")    type = pref_bool, b = overlay_stderr_p;
866       CHECK("overlayTextBackground") continue;  /* don't save */
867       CHECK("overlayTextForeground") continue;  /* don't save */
868       CHECK("bourneShell")           continue;  /* don't save */
869       CHECK("authWarningSlack") type = pref_int, i = p->auth_warning_slack;
870       else                      abort();
871 # undef CHECK
872
873       switch (type)
874         {
875         case pref_str:
876           break;
877         case pref_int:
878           sprintf(buf, "%d", i);
879           s = buf;
880           break;
881         case pref_bool:
882           s = b ? "True" : "False";
883           break;
884         case pref_time:
885           {
886             unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
887             if (sec >= 60)
888               {
889                 min += (sec / 60);
890                 sec %= 60;
891               }
892             if (min >= 60)
893               {
894                 hour += (min / 60);
895                 min %= 60;
896               }
897             sprintf (buf, "%u:%02u:%02u", hour, min, sec);
898             s = buf;
899           }
900           break;
901         case pref_byte:
902           {
903             if      (i >= (1<<30) && i == ((i >> 30) << 30))
904               sprintf(buf, "%dG", i >> 30);
905             else if (i >= (1<<20) && i == ((i >> 20) << 20))
906               sprintf(buf, "%dM", i >> 20);
907             else if (i >= (1<<10) && i == ((i >> 10) << 10))
908               sprintf(buf, "%dK", i >> 10);
909             else
910               sprintf(buf, "%d", i);
911             s = buf;
912           }
913           break;
914         default:
915           abort();
916           break;
917         }
918
919       if (pr && (!strcmp(pr, "mode") || !strcmp(pr, "textMode")))
920         fprintf(out, "\n");
921
922       write_entry (out, pr, s);
923     }
924
925   fprintf(out, "\n");
926
927   if (visual_name) free(visual_name);
928   if (stderr_font) free(stderr_font);
929   if (programs) free(programs);
930
931   if (fclose(out) == 0)
932     {
933       time_t write_date = 0;
934
935       if (stat(tmp_name, &st) == 0)
936         {
937           write_date = st.st_mtime;
938         }
939       else
940         {
941           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
942           sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
943           perror(buf);
944           unlink (tmp_name);
945           free(buf);
946           goto END;
947         }
948
949       if (rename (tmp_name, name) != 0)
950         {
951           char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
952           sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
953                   blurb(), tmp_name, name);
954           perror(buf);
955           unlink (tmp_name);
956           free(buf);
957           goto END;
958         }
959       else
960         {
961           p->init_file_date = write_date;
962
963           /* Since the .xscreensaver file is used for IPC, let's try and make
964              sure that the bits actually land on the disk right away. */
965           sync ();
966
967           status = 0;    /* wrote and renamed successfully! */
968         }
969     }
970   else
971     {
972       char *buf = (char *) malloc(1024 + strlen(name));
973       sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
974       perror(buf);
975       free(buf);
976       unlink (tmp_name);
977       goto END;
978     }
979
980  END:
981   if (n2) free (n2);
982   return status;
983 }
984
985 \f
986 /* Parsing the resource database
987  */
988
989 void
990 free_screenhack (screenhack *hack)
991 {
992   if (hack->visual) free (hack->visual);
993   if (hack->name) free (hack->name);
994   free (hack->command);
995   memset (hack, 0, sizeof(*hack));
996   free (hack);
997 }
998
999 static void
1000 free_screenhack_list (screenhack **list, int count)
1001 {
1002   int i;
1003   if (!list) return;
1004   for (i = 0; i < count; i++)
1005     if (list[i])
1006       free_screenhack (list[i]);
1007   free (list);
1008 }
1009
1010
1011
1012 /* Populate `saver_preferences' with the contents of the resource database.
1013    Note that this may be called multiple times -- it is re-run each time
1014    the ~/.xscreensaver file is reloaded.
1015
1016    This function can be very noisy, since it issues resource syntax errors
1017    and so on.
1018  */
1019 void
1020 load_init_file (Display *dpy, saver_preferences *p)
1021 {
1022   static Bool first_time = True;
1023   
1024   screenhack **system_default_screenhacks = 0;
1025   int system_default_screenhack_count = 0;
1026
1027   if (first_time)
1028     {
1029       /* Get the programs resource before the .xscreensaver file has been
1030          parsed and merged into the resource database for the first time:
1031          this is the value of *programs from the app-defaults file.
1032          Then clear it out so that it will be parsed again later, after
1033          the init file has been read.
1034        */
1035       get_screenhacks (dpy, p);
1036       system_default_screenhacks = p->screenhacks;
1037       system_default_screenhack_count = p->screenhacks_count;
1038       p->screenhacks = 0;
1039       p->screenhacks_count = 0;
1040     }
1041
1042   if (parse_init_file (p) != 0)         /* file might have gone away */
1043     if (!first_time) return;
1044
1045   first_time = False;
1046
1047   p->xsync_p        = get_boolean_resource (dpy, "synchronous", "Synchronous");
1048   p->verbose_p      = get_boolean_resource (dpy, "verbose", "Boolean");
1049   p->timestamp_p    = get_boolean_resource (dpy, "timestamp", "Boolean");
1050   p->lock_p         = get_boolean_resource (dpy, "lock", "Boolean");
1051   p->fade_p         = get_boolean_resource (dpy, "fade", "Boolean");
1052   p->unfade_p       = get_boolean_resource (dpy, "unfade", "Boolean");
1053   p->fade_seconds   = 1000 * get_seconds_resource (dpy, "fadeSeconds", "Time");
1054   p->fade_ticks     = get_integer_resource (dpy, "fadeTicks", "Integer");
1055   p->install_cmap_p = get_boolean_resource (dpy, "installColormap", "Boolean");
1056   p->nice_inferior  = get_integer_resource (dpy, "nice", "Nice");
1057   p->inferior_memory_limit = get_byte_resource (dpy, "memoryLimit",
1058                                                 "MemoryLimit");
1059   p->splash_p       = get_boolean_resource (dpy, "splash", "Boolean");
1060 # ifdef QUAD_MODE
1061   p->quad_p         = get_boolean_resource (dpy, "quad", "Boolean");
1062 # endif
1063   p->capture_stderr_p = get_boolean_resource (dpy, "captureStderr", "Boolean");
1064   p->ignore_uninstalled_p = get_boolean_resource (dpy, 
1065                                                   "ignoreUninstalledPrograms",
1066                                                   "Boolean");
1067
1068   p->initial_delay   = 1000 * get_seconds_resource (dpy, "initialDelay", "Time");
1069   p->splash_duration = 1000 * get_seconds_resource (dpy, "splashDuration", "Time");
1070   p->timeout         = 1000 * get_minutes_resource (dpy, "timeout", "Time");
1071   p->lock_timeout    = 1000 * get_minutes_resource (dpy, "lockTimeout", "Time");
1072   p->cycle           = 1000 * get_minutes_resource (dpy, "cycle", "Time");
1073   p->passwd_timeout  = 1000 * get_seconds_resource (dpy, "passwdTimeout", "Time");
1074   p->pointer_timeout = 1000 * get_seconds_resource (dpy, "pointerPollTime", "Time");
1075   p->pointer_hysteresis = get_integer_resource (dpy, "pointerHysteresis","Integer");
1076   p->notice_events_timeout = 1000*get_seconds_resource(dpy,
1077                                                        "windowCreationTimeout",
1078                                                        "Time");
1079
1080   p->dpms_enabled_p  = get_boolean_resource (dpy, "dpmsEnabled", "Boolean");
1081   p->dpms_quickoff_p = get_boolean_resource (dpy, "dpmsQuickOff", "Boolean");
1082   p->dpms_standby    = 1000 * get_minutes_resource (dpy, "dpmsStandby", "Time");
1083   p->dpms_suspend    = 1000 * get_minutes_resource (dpy, "dpmsSuspend", "Time");
1084   p->dpms_off        = 1000 * get_minutes_resource (dpy, "dpmsOff",     "Time");
1085
1086   p->grab_desktop_p  = get_boolean_resource (dpy, "grabDesktopImages",  "Boolean");
1087   p->grab_video_p    = get_boolean_resource (dpy, "grabVideoFrames",    "Boolean");
1088   p->random_image_p  = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
1089   p->image_directory = get_string_resource  (dpy,
1090                                              "imageDirectory",
1091                                              "ImageDirectory");
1092
1093   p->text_literal = get_string_resource (dpy, "textLiteral", "TextLiteral");
1094   p->text_file    = get_string_resource (dpy, "textFile",    "TextFile");
1095   p->text_program = get_string_resource (dpy, "textProgram", "TextProgram");
1096   p->text_url     = get_string_resource (dpy, "textURL",     "TextURL");
1097
1098   p->shell = get_string_resource (dpy, "bourneShell", "BourneShell");
1099
1100   p->demo_command = get_string_resource(dpy, "demoCommand", "URL");
1101   p->prefs_command = get_string_resource(dpy, "prefsCommand", "URL");
1102   p->help_url = get_string_resource(dpy, "helpURL", "URL");
1103   p->load_url_command = get_string_resource(dpy, "loadURL", "LoadURL");
1104   p->new_login_command = get_string_resource(dpy,
1105                                              "newLoginCommand",
1106                                              "NewLoginCommand");
1107   p->auth_warning_slack = get_integer_resource(dpy, "authWarningSlack",
1108                                                "Integer");
1109
1110   /* If "*splash" is unset, default to true. */
1111   {
1112     char *s = get_string_resource (dpy, "splash", "Boolean");
1113     if (s)
1114       free (s);
1115     else
1116       p->splash_p = True;
1117   }
1118
1119   /* If "*grabDesktopImages" is unset, default to true. */
1120   {
1121     char *s = get_string_resource (dpy, "grabDesktopImages", "Boolean");
1122     if (s)
1123       free (s);
1124     else
1125       p->grab_desktop_p = True;
1126   }
1127
1128   p->use_xidle_extension = get_boolean_resource (dpy, "xidleExtension","Boolean");
1129 #if 0 /* obsolete. */
1130   p->use_sgi_saver_extension = get_boolean_resource (dpy,
1131                                                      "sgiSaverExtension",
1132                                                      "Boolean");
1133 #endif
1134 #ifdef HAVE_XINPUT
1135   p->use_xinput_extension = get_boolean_resource (dpy, "xinputExtensionDev",
1136                                                   "Boolean");
1137 #endif
1138 #if 0 /* broken and evil. */
1139   p->use_mit_saver_extension = get_boolean_resource (dpy, 
1140                                                      "mitSaverExtension",
1141                                                      "Boolean");
1142 #endif
1143
1144   p->use_proc_interrupts = get_boolean_resource (dpy,
1145                                                  "procInterrupts", "Boolean");
1146
1147   p->getviewport_full_of_lies_p =
1148     get_boolean_resource (dpy, "GetViewPortIsFullOfLies", "Boolean");
1149
1150   get_screenhacks (dpy, p);             /* Parse the "programs" resource. */
1151
1152   {
1153     char *s = get_string_resource (dpy, "selected", "Integer");
1154     if (!s || !*s)
1155       p->selected_hack = -1;
1156     else
1157       p->selected_hack = get_integer_resource (dpy, "selected", "Integer");
1158     if (s) free (s);
1159     if (p->selected_hack < 0 || p->selected_hack >= p->screenhacks_count)
1160       p->selected_hack = -1;
1161   }
1162
1163   {
1164     char *s = get_string_resource (dpy, "mode", "Mode");
1165     if      (s && !strcasecmp (s, "one"))         p->mode = ONE_HACK;
1166     else if (s && !strcasecmp (s, "blank"))       p->mode = BLANK_ONLY;
1167     else if (s && !strcasecmp (s, "off"))         p->mode = DONT_BLANK;
1168     else if (s && !strcasecmp (s, "random-same")) p->mode = RANDOM_HACKS_SAME;
1169     else                                          p->mode = RANDOM_HACKS;
1170     if (s) free (s);
1171   }
1172
1173   {
1174     char *s = get_string_resource (dpy, "textMode", "TextMode");
1175     if      (s && !strcasecmp (s, "url"))         p->tmode = TEXT_URL;
1176     else if (s && !strcasecmp (s, "literal"))     p->tmode = TEXT_LITERAL;
1177     else if (s && !strcasecmp (s, "file"))        p->tmode = TEXT_FILE;
1178     else if (s && !strcasecmp (s, "program"))     p->tmode = TEXT_PROGRAM;
1179     else                                          p->tmode = TEXT_DATE;
1180     if (s) free (s);
1181   }
1182
1183   if (system_default_screenhack_count)  /* note: first_time is also true */
1184     {
1185       merge_system_screenhacks (dpy, p, system_default_screenhacks,
1186                                 system_default_screenhack_count);
1187       free_screenhack_list (system_default_screenhacks,
1188                             system_default_screenhack_count);
1189       system_default_screenhacks = 0;
1190       system_default_screenhack_count = 0;
1191     }
1192
1193   if (p->debug_p)
1194     {
1195       p->xsync_p = True;
1196       p->verbose_p = True;
1197       p->timestamp_p = True;
1198       p->initial_delay = 0;
1199     }
1200
1201   /* Throttle the various timeouts to reasonable values after reading the
1202      disk file. */
1203   stop_the_insanity (p);
1204 }
1205
1206
1207 /* If there are any hacks in the system-wide defaults that are not in
1208    the ~/.xscreensaver file, add the new ones to the end of the list.
1209    This does *not* actually save the file.
1210  */
1211 static void
1212 merge_system_screenhacks (Display *dpy, saver_preferences *p,
1213                           screenhack **system_list, int system_count)
1214 {
1215   /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
1216      so fuck it. */
1217
1218   int made_space = 0;
1219   int i;
1220   for (i = 0; i < system_count; i++)
1221     {
1222       int j;
1223       Bool matched_p = False;
1224
1225       for (j = 0; j < p->screenhacks_count; j++)
1226         {
1227           char *name;
1228           if (!system_list[i]->name)
1229             system_list[i]->name = make_hack_name (dpy, 
1230                                                    system_list[i]->command);
1231
1232           name = p->screenhacks[j]->name;
1233           if (!name)
1234             name = make_hack_name (dpy, p->screenhacks[j]->command);
1235
1236           matched_p = !strcasecmp (name, system_list[i]->name);
1237
1238           if (name != p->screenhacks[j]->name)
1239             free (name);
1240
1241           if (matched_p)
1242             break;
1243         }
1244
1245       if (!matched_p)
1246         {
1247           /* We have an entry in the system-wide list that is not in the
1248              user's .xscreensaver file.  Add it to the end.
1249              Note that p->screenhacks is a single malloc block, not a
1250              linked list, so we have to realloc it.
1251            */
1252           screenhack *oh = system_list[i];
1253           screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
1254
1255           if (made_space == 0)
1256             {
1257               made_space = 10;
1258               p->screenhacks = (screenhack **)
1259                 realloc (p->screenhacks,
1260                          (p->screenhacks_count + made_space + 1)
1261                          * sizeof(screenhack));
1262               if (!p->screenhacks) abort();
1263             }
1264
1265           nh->enabled_p = oh->enabled_p;
1266           nh->visual    = oh->visual  ? strdup(oh->visual)  : 0;
1267           nh->name      = oh->name    ? strdup(oh->name)    : 0;
1268           nh->command   = oh->command ? strdup(oh->command) : 0;
1269
1270           p->screenhacks[p->screenhacks_count++] = nh;
1271           p->screenhacks[p->screenhacks_count] = 0;
1272           made_space--;
1273
1274 #if 0
1275           fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
1276                    (nh->name ? nh->name : make_hack_name (dpy, nh->command)));
1277 #endif
1278         }
1279     }
1280 }
1281
1282
1283 \f
1284 /* Parsing the programs resource.
1285  */
1286
1287 screenhack *
1288 parse_screenhack (const char *line)
1289 {
1290   screenhack *h = (screenhack *) calloc (1, sizeof(*h));
1291   const char *s;
1292
1293   h->enabled_p = True;
1294
1295   while (isspace(*line)) line++;                /* skip whitespace */
1296   if (*line == '-')                             /* handle "-" */
1297     {
1298       h->enabled_p = False;
1299       line++;
1300       while (isspace(*line)) line++;            /* skip whitespace */
1301     }
1302
1303   s = line;                                     /* handle "visual:" */
1304   while (*line && *line != ':' && *line != '"' && !isspace(*line))
1305     line++;
1306   if (*line != ':')
1307     line = s;
1308   else
1309     {
1310       h->visual = (char *) malloc (line-s+1);
1311       strncpy (h->visual, s, line-s);
1312       h->visual[line-s] = 0;
1313       if (*line == ':') line++;                 /* skip ":" */
1314       while (isspace(*line)) line++;            /* skip whitespace */
1315     }
1316
1317   if (*line == '"')                             /* handle "name" */
1318     {
1319       line++;
1320       s = line;
1321       while (*line && *line != '"')
1322         line++;
1323       h->name = (char *) malloc (line-s+1);
1324       strncpy (h->name, s, line-s);
1325       h->name[line-s] = 0;
1326       if (*line == '"') line++;                 /* skip "\"" */
1327       while (isspace(*line)) line++;            /* skip whitespace */
1328     }
1329
1330   h->command = format_command (line, False);    /* handle command */
1331   return h;
1332 }
1333
1334
1335 static char *
1336 format_command (const char *cmd, Bool wrap_p)
1337 {
1338   int tab = 30;
1339   int col = tab;
1340   char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
1341   const char *in = cmd;
1342   char *out = cmd2;
1343   while (*in)
1344     {
1345       /* shrink all whitespace to one space, for the benefit of the "demo"
1346          mode display.  We only do this when we can easily tell that the
1347          whitespace is not significant (no shell metachars).
1348        */
1349       switch (*in)
1350         {
1351         case '\'': case '"': case '`': case '\\':
1352           /* Metachars are scary.  Copy the rest of the line unchanged. */
1353           while (*in)
1354             *out++ = *in++, col++;
1355           break;
1356
1357         case ' ': case '\t':
1358           /* Squeeze all other whitespace down to one space. */
1359           while (*in == ' ' || *in == '\t')
1360             in++;
1361           *out++ = ' ', col++;
1362           break;
1363
1364         default:
1365           /* Copy other chars unchanged. */
1366           *out++ = *in++, col++;
1367           break;
1368         }
1369     }
1370
1371   *out = 0;
1372
1373   /* Strip trailing whitespace */
1374   while (out > cmd2 && isspace (out[-1]))
1375     *(--out) = 0;
1376
1377   return cmd2;
1378 }
1379
1380
1381 /* Returns a new string describing the shell command.
1382    This may be just the name of the program, capitalized.
1383    It also may be something from the resource database (gotten
1384    by looking for "hacks.XYZ.name", where XYZ is the program.)
1385  */
1386 char *
1387 make_hack_name (Display *dpy, const char *shell_command)
1388 {
1389   char *s = strdup (shell_command);
1390   char *s2;
1391   char res_name[255];
1392
1393   for (s2 = s; *s2; s2++)       /* truncate at first whitespace */
1394     if (isspace (*s2))
1395       {
1396         *s2 = 0;
1397         break;
1398       }
1399
1400   s2 = strrchr (s, '/');        /* if pathname, take last component */
1401   if (s2)
1402     {
1403       s2 = strdup (s2+1);
1404       free (s);
1405       s = s2;
1406     }
1407
1408   if (strlen (s) > 50)          /* 51 is hereby defined as "unreasonable" */
1409     s[50] = 0;
1410
1411   sprintf (res_name, "hacks.%s.name", s);               /* resource? */
1412   s2 = get_string_resource (dpy, res_name, res_name);
1413   if (s2)
1414     {
1415       free (s);
1416       return s2;
1417     }
1418
1419   for (s2 = s; *s2; s2++)       /* if it has any capitals, return it */
1420     if (*s2 >= 'A' && *s2 <= 'Z')
1421       return s;
1422
1423   if (s[0] >= 'a' && s[0] <= 'z')                       /* else cap it */
1424     s[0] -= 'a'-'A';
1425   if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z')        /* (magic leading X) */
1426     s[1] -= 'a'-'A';
1427   if (s[0] == 'G' && s[1] == 'l' && 
1428       s[2] >= 'a' && s[2] <= 'z')                      /* (magic leading GL) */
1429     s[1] -= 'a'-'A',
1430     s[2] -= 'a'-'A';
1431   return s;
1432 }
1433
1434
1435 char *
1436 format_hack (Display *dpy, screenhack *hack, Bool wrap_p)
1437 {
1438   int tab = 32;
1439   int size;
1440   char *h2, *out, *s;
1441   int col = 0;
1442
1443   char *def_name = make_hack_name (dpy, hack->command);
1444
1445   /* Don't ever write out a name for a hack if it's the same as the default.
1446    */
1447   if (hack->name && !strcmp (hack->name, def_name))
1448     {
1449       free (hack->name);
1450       hack->name = 0;
1451     }
1452   free (def_name);
1453
1454   size = (2 * (strlen(hack->command) +
1455                (hack->visual ? strlen(hack->visual) : 0) +
1456                (hack->name ? strlen(hack->name) : 0) +
1457                tab));
1458   h2 = (char *) malloc (size);
1459   out = h2;
1460
1461   if (!hack->enabled_p) *out++ = '-';           /* write disabled flag */
1462
1463   if (hack->visual && *hack->visual)            /* write visual name */
1464     {
1465       if (hack->enabled_p) *out++ = ' ';
1466       *out++ = ' ';
1467       strcpy (out, hack->visual);
1468       out += strlen (hack->visual);
1469       *out++ = ':';
1470       *out++ = ' ';
1471     }
1472
1473   *out = 0;
1474   col = string_columns (h2, strlen (h2), 0);
1475
1476   if (hack->name && *hack->name)                /* write pretty name */
1477     {
1478       int L = (strlen (hack->name) + 2);
1479       if (L + col < tab)
1480         out = stab_to (out, col, tab - L - 2);
1481       else
1482         *out++ = ' ';
1483       *out++ = '"';
1484       strcpy (out, hack->name);
1485       out += strlen (hack->name);
1486       *out++ = '"';
1487       *out = 0;
1488
1489       col = string_columns (h2, strlen (h2), 0);
1490       if (wrap_p && col >= tab)
1491         out = stab_to (out, col, 77);
1492       else
1493         *out++ = ' ';
1494
1495       if (out >= h2+size) abort();
1496     }
1497
1498   *out = 0;
1499   col = string_columns (h2, strlen (h2), 0);
1500   out = stab_to (out, col, tab);                /* indent */
1501
1502   if (out >= h2+size) abort();
1503   s = format_command (hack->command, wrap_p);
1504   strcpy (out, s);
1505   out += strlen (s);
1506   free (s);
1507   *out = 0;
1508
1509   return h2;
1510 }
1511
1512
1513 static void
1514 get_screenhacks (Display *dpy, saver_preferences *p)
1515 {
1516   int i, j;
1517   int start = 0;
1518   int end = 0;
1519   int size;
1520   char *d;
1521
1522   d = get_string_resource (dpy, "monoPrograms", "MonoPrograms");
1523   if (d && !*d) { free(d); d = 0; }
1524   if (!d)
1525     d = get_string_resource (dpy, "colorPrograms", "ColorPrograms");
1526   if (d && !*d) { free(d); d = 0; }
1527
1528   if (d)
1529     {
1530       fprintf (stderr,
1531        "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1532         see the manual for details.\n", blurb());
1533       free(d);
1534     }
1535
1536   d = get_string_resource (dpy, "programs", "Programs");
1537
1538   free_screenhack_list (p->screenhacks, p->screenhacks_count);
1539   p->screenhacks = 0;
1540   p->screenhacks_count = 0;
1541
1542   if (!d || !*d)
1543     return;
1544
1545   size = strlen (d);
1546
1547
1548   /* Count up the number of newlines (which will be equal to or larger than
1549      one less than the number of hacks.)
1550    */
1551   for (i = j = 0; d[i]; i++)
1552     if (d[i] == '\n')
1553       j++;
1554   j++;
1555
1556   p->screenhacks = (screenhack **) calloc (j + 1, sizeof (screenhack *));
1557
1558   /* Iterate over the lines in `d' (the string with newlines)
1559      and make new strings to stuff into the `screenhacks' array.
1560    */
1561   p->screenhacks_count = 0;
1562   while (start < size)
1563     {
1564       /* skip forward over whitespace. */
1565       while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1566         start++;
1567
1568       /* skip forward to newline or end of string. */
1569       end = start;
1570       while (d[end] != 0 && d[end] != '\n')
1571         end++;
1572
1573       /* null terminate. */
1574       d[end] = 0;
1575
1576       p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1577       if (p->screenhacks_count >= i)
1578         abort();
1579
1580       start = end+1;
1581     }
1582
1583   free (d);
1584
1585   if (p->screenhacks_count == 0)
1586     {
1587       free (p->screenhacks);
1588       p->screenhacks = 0;
1589     }
1590 }
1591
1592
1593 /* Make sure all the values in the preferences struct are sane.
1594  */
1595 static void
1596 stop_the_insanity (saver_preferences *p)
1597 {
1598   if (p->passwd_timeout <= 0) p->passwd_timeout = 30000;         /* 30 secs */
1599   if (p->timeout < 15000) p->timeout = 15000;                    /* 15 secs */
1600   if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000;         /*  2 secs */
1601   if (p->pointer_timeout <= 0) p->pointer_timeout = 5000;        /*  5 secs */
1602   if (p->notice_events_timeout <= 0)
1603     p->notice_events_timeout = 10000;                            /* 10 secs */
1604   if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
1605     p->fade_p = False;
1606   if (! p->fade_p) p->unfade_p = False;
1607
1608   /* The DPMS settings may have the value 0.
1609      But if they are negative, or are a range less than 10 seconds,
1610      reset them to sensible defaults.  (Since that must be a mistake.)
1611    */
1612   if (p->dpms_standby != 0 &&
1613       p->dpms_standby < 10 * 1000)
1614     p->dpms_standby =  2 * 60 * 60 * 1000;                       /* 2 hours */
1615   if (p->dpms_suspend != 0 &&
1616       p->dpms_suspend < 10 * 1000)
1617     p->dpms_suspend =  2 * 60 * 60 * 1000;                       /* 2 hours */
1618   if (p->dpms_off != 0 &&
1619       p->dpms_off < 10 * 1000)
1620     p->dpms_off      = 4 * 60 * 60 * 1000;                       /* 4 hours */
1621
1622   /* suspend may not be greater than off, unless off is 0.
1623      standby may not be greater than suspend, unless suspend is 0.
1624    */
1625   if (p->dpms_off != 0 &&
1626       p->dpms_suspend > p->dpms_off)
1627     p->dpms_suspend = p->dpms_off;
1628   if (p->dpms_suspend != 0 &&
1629       p->dpms_standby > p->dpms_suspend)
1630     p->dpms_standby = p->dpms_suspend;
1631
1632   /* These fixes above ignores the case
1633      suspend = 0 and standby > off ...
1634    */
1635   if (p->dpms_off != 0 &&
1636       p->dpms_standby > p->dpms_off)
1637     p->dpms_standby = p->dpms_off;
1638
1639
1640   if (p->dpms_standby == 0 &&      /* if *all* are 0, then DPMS is disabled */
1641       p->dpms_suspend == 0 &&
1642       p->dpms_off     == 0 &&
1643       !(p->dpms_quickoff_p)           /* ... but we want to do DPMS quick off */
1644       )
1645     p->dpms_enabled_p = False;
1646
1647
1648   /* Set watchdog timeout to about half of the cycle timeout, but
1649      don't let it be faster than 1/2 minute or slower than 1 minute.
1650    */
1651   p->watchdog_timeout = p->cycle * 0.6;
1652   if (p->watchdog_timeout < 27000) p->watchdog_timeout = 27000;   /* 27 secs */
1653   if (p->watchdog_timeout > 57000) p->watchdog_timeout = 57000;   /* 57 secs */
1654
1655   if (p->pointer_hysteresis < 0)   p->pointer_hysteresis = 0;
1656   if (p->pointer_hysteresis > 100) p->pointer_hysteresis = 100;
1657
1658   if (p->auth_warning_slack < 0)   p->auth_warning_slack = 0;
1659   if (p->auth_warning_slack > 300) p->auth_warning_slack = 300;
1660 }
1661
1662
1663 Bool
1664 decrepit_p (void)
1665 {
1666   /* If you are in here because you're planning on disabling this warning
1667      before redistributing my software, please don't.
1668
1669      I sincerely request that you do one of the following:
1670
1671          1: leave this code intact and this warning in place, -OR-
1672
1673          2: Remove xscreensaver from your distribution.
1674
1675      I would seriously prefer that you not distribute my software at all
1676      than that you distribute one version and then never update it for
1677      years.
1678
1679      I am *constantly* getting email from users reporting bugs that have
1680      been fixed for literally years who have no idea that the software
1681      they are running is years out of date.  Yes, it would be great if we
1682      lived in the ideal world where people checked that they were running
1683      the latest release before they report a bug, but we don't.  To most
1684      people, "running the latest release" is synonymous with "running the
1685      latest release that my distro packages for me."
1686
1687      When they even bother to tell me what version they're running, I
1688      say, "That version is three years old!", and they say "But this is
1689      the latest version my distro ships".  Then I say, "your distro
1690      sucks", and they say "but I don't know how to compile from source,
1691      herp derp I eat paste", and *everybody* goes away unhappy.
1692
1693      It wastes an enormous amount of my time, but worse than that, it
1694      does a grave disservice to the users, who are stuck experiencing
1695      bugs that are already fixed!  These users think they are running the
1696      latest release, and they are not.  They would like to be running the
1697      actual latest release, but they don't know how, because their distro
1698      makes that very difficult for them.  It's terrible for everyone, and
1699      kind of makes me regret ever having released this software in the
1700      first place.
1701
1702      So seriously. I ask that if you're planning on disabling this
1703      obsolescence warning, that you instead just remove xscreensaver from
1704      your distro entirely.  Everybody will be happier that way.  Check
1705      out gnome-screensaver instead, I understand it's really nice.
1706
1707      Of course, my license allows you to ignore me and do whatever the
1708      fuck you want, but as the author, I hope you will have the common
1709      courtesy of complying with my request.
1710
1711      Thank you!
1712
1713      jwz, 2014, 2016.
1714
1715      PS: In particular, since Debian refuses to upgrade software on any
1716      kind of rational timeline, I have asked that they stop shipping
1717      xscreensaver at all.  They have refused.  Instead of upgrading the
1718      software, they simply patched out this warning.
1719
1720      If you want to witness the sad state of the open source peanut
1721      gallery, look no farther than the comments on my blog:
1722      http://jwz.org/b/yiYo
1723
1724      Many of these people fall back on their go-to argument of, "If it is
1725      legal, it must be right."  If you believe in that rhetorical device
1726      then you are a terrible person, and possibly a sociopath.
1727
1728      There are also the armchair lawyers who say "Well, instead of
1729      *asking* people to do the right thing out of common courtesy, you
1730      should just change your license to prohibit them from acting
1731      amorally."  Again, this is the answer of a sociopath, but that aside,
1732      if you devote even a second's thought to this you will realize that
1733      the end result of this would be for distros like Debian to just keep
1734      shipping the last version with the old license and then never
1735      upgrading it again -- which would be the worst possible outcome for
1736      everyone involved, most especially the users.
1737   */
1738
1739   time_t now = time ((time_t *) 0);                             /*       */
1740   struct tm *tm = localtime (&now);                             /*   d   */
1741   const char *s = screensaver_id;                               /*   o   */
1742   char mon[4], year[5];                                         /*   n   */
1743   int m, y, months;                                             /*   '   */
1744   s = strchr (s, ' '); if (!s) abort(); s++;                    /*   t   */
1745   s = strchr (s, '('); if (!s) abort(); s++;                    /*       */
1746   s = strchr (s, '-'); if (!s) abort(); s++;                    /*   d   */
1747   strncpy (mon, s, 3);                                          /*   o   */
1748   mon[3] = 0;                                                   /*       */
1749   s = strchr (s, '-'); if (!s) abort(); s++;                    /*   i   */
1750   strncpy (year, s, 4);                                         /*   t   */
1751   year[4] = 0;                                                  /*   ,   */
1752   y = atoi (year);                                              /*       */
1753   if      (!strcmp(mon, "Jan")) m = 0;                          /*   s   */
1754   else if (!strcmp(mon, "Feb")) m = 1;                          /*   t   */
1755   else if (!strcmp(mon, "Mar")) m = 2;                          /*   o   */
1756   else if (!strcmp(mon, "Apr")) m = 3;                          /*   p   */
1757   else if (!strcmp(mon, "May")) m = 4;                          /*   ,   */
1758   else if (!strcmp(mon, "Jun")) m = 5;                          /*       */
1759   else if (!strcmp(mon, "Jul")) m = 6;                          /*   s   */
1760   else if (!strcmp(mon, "Aug")) m = 7;                          /*   t   */
1761   else if (!strcmp(mon, "Sep")) m = 8;                          /*   a   */
1762   else if (!strcmp(mon, "Oct")) m = 9;                          /*   a   */
1763   else if (!strcmp(mon, "Nov")) m = 10;                         /*   a   */
1764   else if (!strcmp(mon, "Dec")) m = 11;                         /*   h   */
1765   else abort();                                                 /*   h   */
1766   months = ((((tm->tm_year + 1900) * 12) + tm->tm_mon) -        /*   h   */
1767             (y * 12 + m));                                      /*   p   */
1768                                                                 /*   .   */
1769   return (months >= 17);                                        /*       */
1770 }