1 /* dotfile.c --- management of the ~/.xscreensaver file.
2 * xscreensaver, Copyright (c) 1998 Jamie Zawinski <jwz@jwz.org>
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
30 #include <X11/Xresource.h>
39 /* This file doesn't need the Xt headers, so stub these types out... */
41 #define XtAppContext void*
42 #define XtIntervalId void*
43 #define XtPointer void*
47 /* Just in case there's something pathological about stat.h... */
49 # define S_IRUSR 00400
52 # define S_IWUSR 00200
55 # define S_IXUSR 00100
58 # define S_IXGRP 00010
61 # define S_IXOTH 00001
66 #include "resources.h"
69 extern char *progname;
70 extern char *progclass;
71 extern const char *blurb (void);
75 static void get_screenhacks (saver_preferences *p);
76 static char *format_command (const char *cmd, Bool wrap_p);
80 chase_symlinks (const char *file)
86 if (realpath (file, buf))
89 sprintf (buf, "%s: realpath", blurb());
92 # endif /* HAVE_REALPATH */
100 static char *file = 0;
104 struct passwd *p = getpwuid (getuid ());
106 if (!p || !p->pw_name || !*p->pw_name)
108 fprintf (stderr, "%s: couldn't get user info of uid %d\n",
112 else if (!p->pw_dir || !*p->pw_dir)
114 fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
115 blurb(), (p->pw_name ? p->pw_name : "???"));
120 const char *home = p->pw_dir;
121 const char *name = ".xscreensaver";
122 file = (char *) malloc(strlen(home) + strlen(name) + 2);
124 if (!*home || home[strlen(home)-1] != '/')
138 init_file_tmp_name (void)
140 static char *file = 0;
143 const char *name = init_file_name();
144 const char *suffix = ".tmp";
146 char *n2 = chase_symlinks (name);
153 file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
155 strcat(file, suffix);
168 static const char * const prefs[] = {
179 "splash", /* not saved -- same as "splashDuration: 0" */
191 "captureStdout", /* not saved -- obsolete */
197 "windowCreationTimeout",
204 "overlayTextBackground", /* not saved -- X resources only */
205 "overlayTextForeground", /* not saved -- X resources only */
206 "bourneShell", /* not saved -- X resources only */
214 while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
216 for (s2 = s; *s2; s2++)
218 for (s2--; s2 >= s; s2--)
219 if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n')
231 handle_entry (XrmDatabase *db, const char *key, const char *value,
232 const char *filename, int line)
235 for (i = 0; prefs[i]; i++)
236 if (*prefs[i] && !strcasecmp(key, prefs[i]))
238 char *val = strdup(value);
239 char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
240 strcpy(spec, progclass);
242 strcat(spec, prefs[i]);
244 XrmPutStringResource (db, spec, val);
251 fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
252 blurb(), filename, line, key);
258 parse_init_file (saver_preferences *p)
260 time_t write_date = 0;
261 const char *name = init_file_name();
270 if (stat(name, &st) != 0)
272 p->init_file_date = 0;
276 in = fopen(name, "r");
279 char *buf = (char *) malloc(1024 + strlen(name));
280 sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
286 if (fstat (fileno(in), &st) == 0)
288 write_date = st.st_mtime;
292 char *buf = (char *) malloc(1024 + strlen(name));
293 sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
299 buf = (char *) malloc(buf_size);
301 while (fgets (buf, buf_size-1, in))
308 (buf[L-1] != '\n' || /* whole line didn't fit in buffer */
309 buf[L-2] == '\\')) /* or line ended with backslash */
311 if (buf[L-2] == '\\') /* backslash-newline gets swallowed */
317 buf = (char *) realloc(buf, buf_size);
321 if (!fgets (buf + L, buf_size-L-1, in))
326 /* Now handle other backslash escapes. */
329 for (i = 0; buf[i]; i++)
334 case 'n': buf[i] = '\n'; break;
335 case 'r': buf[i] = '\r'; break;
336 case 't': buf[i] = '\t'; break;
337 default: buf[i] = buf[i+1]; break;
339 for (j = i+2; buf[j]; j++)
347 if (*key == '#' || *key == '!' || *key == ';' ||
348 *key == '\n' || *key == 0)
351 value = strchr (key, ':');
354 fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
361 value = strip(value);
365 handle_entry (&p->db, key, value, name, line);
370 p->init_file_date = write_date;
376 init_file_changed_p (saver_preferences *p)
378 const char *name = init_file_name();
381 if (!name) return False;
383 if (stat(name, &st) != 0)
386 if (p->init_file_date == st.st_mtime)
397 tab_to (FILE *out, int from, int to)
400 int to_mod = (to / tab_width) * tab_width;
401 while (from < to_mod)
404 from = (((from / tab_width) + 1) * tab_width);
415 stab_to (char *out, int from, int to)
418 int to_mod = (to / tab_width) * tab_width;
419 while (from < to_mod)
422 from = (((from / tab_width) + 1) * tab_width);
433 string_columns (const char *string, int length, int start)
437 const char *end = string + length;
442 else if (*string == '\t')
443 col = (((col / tab_width) + 1) * tab_width);
453 write_entry (FILE *out, const char *key, const char *value)
455 char *v = strdup(value ? value : "");
459 Bool programs_p = (!strcmp(key, "programs"));
460 int tab = (programs_p ? 32 : 16);
463 fprintf(out, "%s:", key);
464 col = strlen(key) + 1;
470 nl = strchr(v2, '\n');
474 if (first && programs_p)
476 col = tab_to (out, col, 77);
477 fprintf (out, " \\\n");
485 col = tab_to (out, col, 75);
486 fprintf (out, " \\n\\\n");
491 col = tab_to (out, col, tab);
494 string_columns(v2, strlen (v2), col) + col > 75)
501 while (v2[end] == ' ' || v2[end] == '\t')
503 while (v2[end] != ' ' && v2[end] != '\t' &&
504 v2[end] != '\n' && v2[end] != 0)
506 if (string_columns (v2 + start, (end - start), col) >= 74)
508 col = tab_to (out, col, 75);
509 fprintf(out, " \\\n");
510 col = tab_to (out, 0, tab + 2);
511 while (v2[start] == ' ' || v2[start] == '\t')
515 col = string_columns (v2 + start, (end - start), col);
517 fputc(v2[start++], out);
522 fprintf (out, "%s", v2);
523 col += string_columns(v2, strlen (v2), col);
537 write_init_file (saver_preferences *p, const char *version_string,
541 const char *name = init_file_name();
542 const char *tmp_name = init_file_tmp_name();
543 char *n2 = chase_symlinks (name);
547 /* Kludge, since these aren't in the saver_preferences struct as strings...
551 Bool capture_stderr_p;
552 Bool overlay_stderr_p;
561 fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
564 out = fopen(tmp_name, "w");
567 char *buf = (char *) malloc(1024 + strlen(name));
568 sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
574 /* Give the new .xscreensaver file the same permissions as the old one;
575 except ensure that it is readable and writable by owner, and not
576 executable. Extra hack: if we're running as root, make the file
577 be world-readable (so that the daemon, running as "nobody", will
578 still be able to read it.)
580 if (stat(name, &st) == 0)
582 mode_t mode = st.st_mode;
583 mode |= S_IRUSR | S_IWUSR; /* read/write by user */
584 mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); /* executable by none */
586 if (getuid() == (uid_t) 0) /* read by group/other */
587 mode |= S_IRGRP | S_IROTH;
589 if (fchmod (fileno(out), mode) != 0)
591 char *buf = (char *) malloc(1024 + strlen(name));
592 sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
593 tmp_name, (unsigned int) mode);
600 /* Kludge, since these aren't in the saver_preferences struct... */
601 visual_name = get_string_resource ("visualID", "VisualID");
603 capture_stderr_p = get_boolean_resource ("captureStderr", "Boolean");
604 overlay_stderr_p = get_boolean_resource ("overlayStderr", "Boolean");
605 stderr_font = get_string_resource ("font", "Font");
610 char **hack_strings = (char **)
611 calloc (p->screenhacks_count, sizeof(char *));
613 for (j = 0; j < p->screenhacks_count; j++)
615 hack_strings[j] = format_hack (p->screenhacks[j], True);
616 i += strlen (hack_strings[j]);
620 ss = programs = (char *) malloc(i + 10);
622 for (j = 0; j < p->screenhacks_count; j++)
624 strcat (ss, hack_strings[j]);
625 free (hack_strings[j]);
633 struct passwd *pw = getpwuid (getuid ());
634 char *whoami = (pw && pw->pw_name && *pw->pw_name
637 time_t now = time ((time_t *) 0);
638 char *timestr = (char *) ctime (&now);
639 char *nl = (char *) strchr (timestr, '\n');
642 "# %s Preferences File\n"
643 "# Written by %s %s for %s on %s.\n"
644 "# http://www.jwz.org/xscreensaver/\n"
646 progclass, progname, version_string, whoami, timestr);
649 for (j = 0; prefs[j]; j++)
652 const char *pr = prefs[j];
653 enum pref_type { pref_str, pref_int, pref_bool, pref_time
667 # define CHECK(X) else if (!strcmp(pr, X))
669 CHECK("timeout") type = pref_time, t = p->timeout;
670 CHECK("cycle") type = pref_time, t = p->cycle;
671 CHECK("lock") type = pref_bool, b = p->lock_p;
672 # if 0 /* #### not ready yet */
673 CHECK("lockVTs") type = pref_bool, b = p->lock_vt_p;
675 CHECK("lockVTs") continue; /* don't save */
677 CHECK("lockTimeout") type = pref_time, t = p->lock_timeout;
678 CHECK("passwdTimeout") type = pref_time, t = p->passwd_timeout;
679 CHECK("visualID") type = pref_str, s = visual_name;
680 CHECK("installColormap") type = pref_bool, b = p->install_cmap_p;
681 CHECK("verbose") type = pref_bool, b = p->verbose_p;
682 CHECK("timestamp") type = pref_bool, b = p->timestamp_p;
683 CHECK("splash") continue; /* don't save */
684 CHECK("splashDuration") type = pref_time, t = p->splash_duration;
685 CHECK("demoCommand") type = pref_str, s = p->demo_command;
686 CHECK("prefsCommand") type = pref_str, s = p->prefs_command;
687 CHECK("helpURL") type = pref_str, s = p->help_url;
688 CHECK("loadURL") type = pref_str, s = p->load_url_command;
689 CHECK("nice") type = pref_int, i = p->nice_inferior;
690 CHECK("fade") type = pref_bool, b = p->fade_p;
691 CHECK("unfade") type = pref_bool, b = p->unfade_p;
692 CHECK("fadeSeconds") type = pref_time, t = p->fade_seconds;
693 CHECK("fadeTicks") type = pref_int, i = p->fade_ticks;
694 CHECK("captureStderr") type = pref_bool, b = capture_stderr_p;
695 CHECK("captureStdout") continue; /* don't save */
696 CHECK("font") type = pref_str, s = stderr_font;
697 CHECK("programs") type = pref_str, s = programs;
698 CHECK("pointerPollTime") type = pref_time, t = p->pointer_timeout;
699 CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
700 CHECK("initialDelay") type = pref_time, t = p->initial_delay;
701 CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension;
702 CHECK("mitSaverExtension")type = pref_bool, b=p->use_mit_saver_extension;
703 CHECK("xidleExtension") type = pref_bool, b = p->use_xidle_extension;
704 CHECK("procInterrupts") type = pref_bool, b = p->use_proc_interrupts;
705 CHECK("overlayStderr") type = pref_bool, b = overlay_stderr_p;
706 CHECK("overlayTextBackground") continue; /* don't save */
707 CHECK("overlayTextForeground") continue; /* don't save */
708 CHECK("bourneShell") continue;
717 sprintf(buf, "%d", i);
721 s = b ? "True" : "False";
725 unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
736 sprintf (buf, "%u:%02u:%02u", hour, min, sec);
744 write_entry (out, pr, s);
749 if (visual_name) free(visual_name);
750 if (stderr_font) free(stderr_font);
751 if (programs) free(programs);
753 if (fclose(out) == 0)
755 time_t write_date = 0;
757 if (stat(tmp_name, &st) == 0)
759 write_date = st.st_mtime;
763 char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
764 sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
771 if (rename (tmp_name, name) != 0)
773 char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
774 sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
775 blurb(), tmp_name, name);
783 p->init_file_date = write_date;
785 /* Since the .xscreensaver file is used for IPC, let's try and make
786 sure that the bits actually land on the disk right away. */
789 status = 0; /* wrote and renamed successfully! */
794 char *buf = (char *) malloc(1024 + strlen(name));
795 sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
808 /* Parsing the resource database
812 /* Populate `saver_preferences' with the contents of the resource database.
813 Note that this may be called multiple times -- it is re-run each time
814 the ~/.xscreensaver file is reloaded.
816 This function can be very noisy, since it issues resource syntax errors
820 load_init_file (saver_preferences *p)
822 static Bool first_time = True;
824 if (parse_init_file (p) != 0) /* file might have gone away */
825 if (!first_time) return;
829 p->xsync_p = get_boolean_resource ("synchronous", "Synchronous");
830 p->verbose_p = get_boolean_resource ("verbose", "Boolean");
831 p->timestamp_p = get_boolean_resource ("timestamp", "Boolean");
832 p->lock_p = get_boolean_resource ("lock", "Boolean");
833 p->lock_vt_p = get_boolean_resource ("lockVTs", "Boolean");
834 p->fade_p = get_boolean_resource ("fade", "Boolean");
835 p->unfade_p = get_boolean_resource ("unfade", "Boolean");
836 p->fade_seconds = 1000 * get_seconds_resource ("fadeSeconds", "Time");
837 p->fade_ticks = get_integer_resource ("fadeTicks", "Integer");
838 p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
839 p->nice_inferior = get_integer_resource ("nice", "Nice");
841 p->initial_delay = 1000 * get_seconds_resource ("initialDelay", "Time");
842 p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
843 p->timeout = 1000 * get_minutes_resource ("timeout", "Time");
844 p->lock_timeout = 1000 * get_minutes_resource ("lockTimeout", "Time");
845 p->cycle = 1000 * get_minutes_resource ("cycle", "Time");
846 p->passwd_timeout = 1000 * get_seconds_resource ("passwdTimeout", "Time");
847 p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
848 p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
850 p->shell = get_string_resource ("bourneShell", "BourneShell");
852 p->demo_command = get_string_resource("demoCommand", "URL");
853 p->prefs_command = get_string_resource("prefsCommand", "URL");
854 p->help_url = get_string_resource("helpURL", "URL");
855 p->load_url_command = get_string_resource("loadURL", "LoadURL");
859 if ((s = get_string_resource ("splash", "Boolean")))
860 if (!get_boolean_resource("splash", "Boolean"))
861 p->splash_duration = 0;
865 p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
866 p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
868 p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
870 p->use_proc_interrupts = get_boolean_resource ("procInterrupts", "Boolean");
872 /* Throttle the various timeouts to reasonable values.
874 if (p->passwd_timeout <= 0) p->passwd_timeout = 30000; /* 30 secs */
875 if (p->timeout < 10000) p->timeout = 10000; /* 10 secs */
876 if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000; /* 2 secs */
877 if (p->pointer_timeout <= 0) p->pointer_timeout = 5000; /* 5 secs */
878 if (p->notice_events_timeout <= 0)
879 p->notice_events_timeout = 10000; /* 10 secs */
880 if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
882 if (! p->fade_p) p->unfade_p = False;
884 p->watchdog_timeout = p->cycle * 0.6;
885 if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000; /* 30 secs */
886 if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /* 1 hr */
894 p->timestamp_p = True;
895 p->initial_delay = 0;
900 /* Parsing the programs resource.
904 parse_screenhack (const char *line)
906 screenhack *h = (screenhack *) calloc (1, sizeof(*h));
911 while (isspace(*line)) line++; /* skip whitespace */
912 if (*line == '-') /* handle "-" */
914 h->enabled_p = False;
916 while (isspace(*line)) line++; /* skip whitespace */
919 s = line; /* handle "visual:" */
920 while (*line && *line != ':' && *line != '"' && !isspace(*line))
926 h->visual = (char *) malloc (line-s+1);
927 strncpy (h->visual, s, line-s);
928 h->visual[line-s] = 0;
929 if (*line == ':') line++; /* skip ":" */
930 while (isspace(*line)) line++; /* skip whitespace */
933 if (*line == '"') /* handle "name" */
937 while (*line && *line != '"')
939 h->name = (char *) malloc (line-s+1);
940 strncpy (h->name, s, line-s);
942 if (*line == '"') line++; /* skip "\"" */
943 while (isspace(*line)) line++; /* skip whitespace */
946 h->command = format_command (line, False); /* handle command */
952 free_screenhack (screenhack *hack)
954 if (hack->visual) free (hack->visual);
955 if (hack->name) free (hack->name);
956 free (hack->command);
957 memset (hack, 0, sizeof(*hack));
963 format_command (const char *cmd, Bool wrap_p)
967 char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
968 const char *in = cmd;
972 /* shrink all whitespace to one space, for the benefit of the "demo"
973 mode display. We only do this when we can easily tell that the
974 whitespace is not significant (no shell metachars).
978 case '\'': case '"': case '`': case '\\':
979 /* Metachars are scary. Copy the rest of the line unchanged. */
981 *out++ = *in++, col++;
985 /* Squeeze all other whitespace down to one space. */
986 while (*in == ' ' || *in == '\t')
992 /* Copy other chars unchanged. */
993 *out++ = *in++, col++;
1000 /* Strip trailing whitespace */
1001 while (out > cmd2 && isspace (out[-1]))
1009 format_hack (screenhack *hack, Bool wrap_p)
1012 int size = (2 * (strlen(hack->command) +
1013 (hack->visual ? strlen(hack->visual) : 0) +
1014 (hack->name ? strlen(hack->name) : 0) +
1016 char *h2 = (char *) malloc (size);
1021 if (!hack->enabled_p) *out++ = '-'; /* write disabled flag */
1023 if (hack->visual && *hack->visual) /* write visual name */
1025 if (hack->enabled_p) *out++ = ' ';
1027 strcpy (out, hack->visual);
1028 out += strlen (hack->visual);
1034 col = string_columns (h2, strlen (h2), 0);
1036 if (hack->name && *hack->name) /* write pretty name */
1038 int L = (strlen (hack->name) + 2);
1040 out = stab_to (out, col, tab - L - 2);
1044 strcpy (out, hack->name);
1045 out += strlen (hack->name);
1049 col = string_columns (h2, strlen (h2), 0);
1050 if (wrap_p && col >= tab)
1052 out = stab_to (out, col, 77);
1053 *out += strlen(out);
1058 if (out >= h2+size) abort();
1062 col = string_columns (h2, strlen (h2), 0);
1063 out = stab_to (out, col, tab); /* indent */
1065 if (out >= h2+size) abort();
1066 s = format_command (hack->command, wrap_p);
1077 get_screenhacks (saver_preferences *p)
1085 d = get_string_resource ("monoPrograms", "MonoPrograms");
1086 if (d && !*d) { free(d); d = 0; }
1088 d = get_string_resource ("colorPrograms", "ColorPrograms");
1089 if (d && !*d) { free(d); d = 0; }
1094 "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1095 see the manual for details.\n", blurb());
1099 d = get_string_resource ("programs", "Programs");
1103 for (i = 0; i < p->screenhacks_count; i++)
1104 if (p->screenhacks[i])
1105 free_screenhack (p->screenhacks[i]);
1106 free(p->screenhacks);
1112 p->screenhacks_count = 0;
1120 /* Count up the number of newlines (which will be equal to or larger than
1121 the number of hacks.)
1124 for (i = 0; d[i]; i++)
1129 p->screenhacks = (screenhack **) calloc (sizeof (screenhack *), i+1);
1131 /* Iterate over the lines in `d' (the string with newlines)
1132 and make new strings to stuff into the `screenhacks' array.
1134 p->screenhacks_count = 0;
1135 while (start < size)
1137 /* skip forward over whitespace. */
1138 while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1141 /* skip forward to newline or end of string. */
1143 while (d[end] != 0 && d[end] != '\n')
1146 /* null terminate. */
1149 p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1150 if (p->screenhacks_count >= i)
1156 if (p->screenhacks_count == 0)
1158 free (p->screenhacks);