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 */
98 i_am_a_nobody (uid_t uid)
102 p = getpwnam ("nobody");
103 if (! p) p = getpwnam ("noaccess");
104 if (! p) p = getpwnam ("daemon");
106 if (! p) /* There is no nobody? */
109 return (uid == p->pw_uid);
114 init_file_name (void)
116 static char *file = 0;
120 uid_t uid = getuid ();
121 struct passwd *p = getpwuid (uid);
123 if (i_am_a_nobody (uid))
124 /* If we're running as nobody, then use root's .xscreensaver file
125 (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely
126 to be different -- if we didn't do this, then xscreensaver-demo
127 would appear to have no effect when the luser is running as root.)
133 if (!p || !p->pw_name || !*p->pw_name)
135 fprintf (stderr, "%s: couldn't get user info of uid %d\n",
139 else if (!p->pw_dir || !*p->pw_dir)
141 fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
142 blurb(), (p->pw_name ? p->pw_name : "???"));
147 const char *home = p->pw_dir;
148 const char *name = ".xscreensaver";
149 file = (char *) malloc(strlen(home) + strlen(name) + 2);
151 if (!*home || home[strlen(home)-1] != '/')
165 init_file_tmp_name (void)
167 static char *file = 0;
170 const char *name = init_file_name();
171 const char *suffix = ".tmp";
173 char *n2 = chase_symlinks (name);
180 file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
182 strcat(file, suffix);
195 static const char * const prefs[] = {
206 "splash", /* not saved -- same as "splashDuration: 0" */
218 "captureStdout", /* not saved -- obsolete */
224 "windowCreationTimeout",
231 "overlayTextBackground", /* not saved -- X resources only */
232 "overlayTextForeground", /* not saved -- X resources only */
233 "bourneShell", /* not saved -- X resources only */
241 while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
243 for (s2 = s; *s2; s2++)
245 for (s2--; s2 >= s; s2--)
246 if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n')
258 handle_entry (XrmDatabase *db, const char *key, const char *value,
259 const char *filename, int line)
262 for (i = 0; prefs[i]; i++)
263 if (*prefs[i] && !strcasecmp(key, prefs[i]))
265 char *val = strdup(value);
266 char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
267 strcpy(spec, progclass);
269 strcat(spec, prefs[i]);
271 XrmPutStringResource (db, spec, val);
278 fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
279 blurb(), filename, line, key);
285 parse_init_file (saver_preferences *p)
287 time_t write_date = 0;
288 const char *name = init_file_name();
297 if (stat(name, &st) != 0)
299 p->init_file_date = 0;
303 in = fopen(name, "r");
306 char *buf = (char *) malloc(1024 + strlen(name));
307 sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
313 if (fstat (fileno(in), &st) == 0)
315 write_date = st.st_mtime;
319 char *buf = (char *) malloc(1024 + strlen(name));
320 sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
326 buf = (char *) malloc(buf_size);
328 while (fgets (buf, buf_size-1, in))
335 (buf[L-1] != '\n' || /* whole line didn't fit in buffer */
336 buf[L-2] == '\\')) /* or line ended with backslash */
338 if (buf[L-2] == '\\') /* backslash-newline gets swallowed */
344 buf = (char *) realloc(buf, buf_size);
348 if (!fgets (buf + L, buf_size-L-1, in))
353 /* Now handle other backslash escapes. */
356 for (i = 0; buf[i]; i++)
361 case 'n': buf[i] = '\n'; break;
362 case 'r': buf[i] = '\r'; break;
363 case 't': buf[i] = '\t'; break;
364 default: buf[i] = buf[i+1]; break;
366 for (j = i+2; buf[j]; j++)
374 if (*key == '#' || *key == '!' || *key == ';' ||
375 *key == '\n' || *key == 0)
378 value = strchr (key, ':');
381 fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
388 value = strip(value);
392 handle_entry (&p->db, key, value, name, line);
397 p->init_file_date = write_date;
403 init_file_changed_p (saver_preferences *p)
405 const char *name = init_file_name();
408 if (!name) return False;
410 if (stat(name, &st) != 0)
413 if (p->init_file_date == st.st_mtime)
424 tab_to (FILE *out, int from, int to)
427 int to_mod = (to / tab_width) * tab_width;
428 while (from < to_mod)
431 from = (((from / tab_width) + 1) * tab_width);
442 stab_to (char *out, int from, int to)
445 int to_mod = (to / tab_width) * tab_width;
446 while (from < to_mod)
449 from = (((from / tab_width) + 1) * tab_width);
460 string_columns (const char *string, int length, int start)
464 const char *end = string + length;
469 else if (*string == '\t')
470 col = (((col / tab_width) + 1) * tab_width);
480 write_entry (FILE *out, const char *key, const char *value)
482 char *v = strdup(value ? value : "");
486 Bool programs_p = (!strcmp(key, "programs"));
487 int tab = (programs_p ? 32 : 16);
490 fprintf(out, "%s:", key);
491 col = strlen(key) + 1;
497 nl = strchr(v2, '\n');
501 if (first && programs_p)
503 col = tab_to (out, col, 77);
504 fprintf (out, " \\\n");
512 col = tab_to (out, col, 75);
513 fprintf (out, " \\n\\\n");
518 col = tab_to (out, col, tab);
521 string_columns(v2, strlen (v2), col) + col > 75)
528 while (v2[end] == ' ' || v2[end] == '\t')
530 while (v2[end] != ' ' && v2[end] != '\t' &&
531 v2[end] != '\n' && v2[end] != 0)
533 if (string_columns (v2 + start, (end - start), col) >= 74)
535 col = tab_to (out, col, 75);
536 fprintf(out, " \\\n");
537 col = tab_to (out, 0, tab + 2);
538 while (v2[start] == ' ' || v2[start] == '\t')
542 col = string_columns (v2 + start, (end - start), col);
544 fputc(v2[start++], out);
549 fprintf (out, "%s", v2);
550 col += string_columns(v2, strlen (v2), col);
564 write_init_file (saver_preferences *p, const char *version_string,
568 const char *name = init_file_name();
569 const char *tmp_name = init_file_tmp_name();
570 char *n2 = chase_symlinks (name);
574 /* Kludge, since these aren't in the saver_preferences struct as strings...
578 Bool capture_stderr_p;
579 Bool overlay_stderr_p;
588 fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
591 out = fopen(tmp_name, "w");
594 char *buf = (char *) malloc(1024 + strlen(name));
595 sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
601 /* Give the new .xscreensaver file the same permissions as the old one;
602 except ensure that it is readable and writable by owner, and not
603 executable. Extra hack: if we're running as root, make the file
604 be world-readable (so that the daemon, running as "nobody", will
605 still be able to read it.)
607 if (stat(name, &st) == 0)
609 mode_t mode = st.st_mode;
610 mode |= S_IRUSR | S_IWUSR; /* read/write by user */
611 mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); /* executable by none */
613 if (getuid() == (uid_t) 0) /* read by group/other */
614 mode |= S_IRGRP | S_IROTH;
616 if (fchmod (fileno(out), mode) != 0)
618 char *buf = (char *) malloc(1024 + strlen(name));
619 sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
620 tmp_name, (unsigned int) mode);
627 /* Kludge, since these aren't in the saver_preferences struct... */
628 visual_name = get_string_resource ("visualID", "VisualID");
630 capture_stderr_p = get_boolean_resource ("captureStderr", "Boolean");
631 overlay_stderr_p = get_boolean_resource ("overlayStderr", "Boolean");
632 stderr_font = get_string_resource ("font", "Font");
637 char **hack_strings = (char **)
638 calloc (p->screenhacks_count, sizeof(char *));
640 for (j = 0; j < p->screenhacks_count; j++)
642 hack_strings[j] = format_hack (p->screenhacks[j], True);
643 i += strlen (hack_strings[j]);
647 ss = programs = (char *) malloc(i + 10);
649 for (j = 0; j < p->screenhacks_count; j++)
651 strcat (ss, hack_strings[j]);
652 free (hack_strings[j]);
660 struct passwd *pw = getpwuid (getuid ());
661 char *whoami = (pw && pw->pw_name && *pw->pw_name
664 time_t now = time ((time_t *) 0);
665 char *timestr = (char *) ctime (&now);
666 char *nl = (char *) strchr (timestr, '\n');
669 "# %s Preferences File\n"
670 "# Written by %s %s for %s on %s.\n"
671 "# http://www.jwz.org/xscreensaver/\n"
673 progclass, progname, version_string, whoami, timestr);
676 for (j = 0; prefs[j]; j++)
679 const char *pr = prefs[j];
680 enum pref_type { pref_str, pref_int, pref_bool, pref_time
694 # define CHECK(X) else if (!strcmp(pr, X))
696 CHECK("timeout") type = pref_time, t = p->timeout;
697 CHECK("cycle") type = pref_time, t = p->cycle;
698 CHECK("lock") type = pref_bool, b = p->lock_p;
699 # if 0 /* #### not ready yet */
700 CHECK("lockVTs") type = pref_bool, b = p->lock_vt_p;
702 CHECK("lockVTs") continue; /* don't save */
704 CHECK("lockTimeout") type = pref_time, t = p->lock_timeout;
705 CHECK("passwdTimeout") type = pref_time, t = p->passwd_timeout;
706 CHECK("visualID") type = pref_str, s = visual_name;
707 CHECK("installColormap") type = pref_bool, b = p->install_cmap_p;
708 CHECK("verbose") type = pref_bool, b = p->verbose_p;
709 CHECK("timestamp") type = pref_bool, b = p->timestamp_p;
710 CHECK("splash") continue; /* don't save */
711 CHECK("splashDuration") type = pref_time, t = p->splash_duration;
712 CHECK("demoCommand") type = pref_str, s = p->demo_command;
713 CHECK("prefsCommand") type = pref_str, s = p->prefs_command;
714 CHECK("helpURL") type = pref_str, s = p->help_url;
715 CHECK("loadURL") type = pref_str, s = p->load_url_command;
716 CHECK("nice") type = pref_int, i = p->nice_inferior;
717 CHECK("fade") type = pref_bool, b = p->fade_p;
718 CHECK("unfade") type = pref_bool, b = p->unfade_p;
719 CHECK("fadeSeconds") type = pref_time, t = p->fade_seconds;
720 CHECK("fadeTicks") type = pref_int, i = p->fade_ticks;
721 CHECK("captureStderr") type = pref_bool, b = capture_stderr_p;
722 CHECK("captureStdout") continue; /* don't save */
723 CHECK("font") type = pref_str, s = stderr_font;
724 CHECK("programs") type = pref_str, s = programs;
725 CHECK("pointerPollTime") type = pref_time, t = p->pointer_timeout;
726 CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
727 CHECK("initialDelay") type = pref_time, t = p->initial_delay;
728 CHECK("sgiSaverExtension")type = pref_bool, b=p->use_sgi_saver_extension;
729 CHECK("mitSaverExtension")type = pref_bool, b=p->use_mit_saver_extension;
730 CHECK("xidleExtension") type = pref_bool, b = p->use_xidle_extension;
731 CHECK("procInterrupts") type = pref_bool, b = p->use_proc_interrupts;
732 CHECK("overlayStderr") type = pref_bool, b = overlay_stderr_p;
733 CHECK("overlayTextBackground") continue; /* don't save */
734 CHECK("overlayTextForeground") continue; /* don't save */
735 CHECK("bourneShell") continue;
744 sprintf(buf, "%d", i);
748 s = b ? "True" : "False";
752 unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
763 sprintf (buf, "%u:%02u:%02u", hour, min, sec);
771 write_entry (out, pr, s);
776 if (visual_name) free(visual_name);
777 if (stderr_font) free(stderr_font);
778 if (programs) free(programs);
780 if (fclose(out) == 0)
782 time_t write_date = 0;
784 if (stat(tmp_name, &st) == 0)
786 write_date = st.st_mtime;
790 char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
791 sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
798 if (rename (tmp_name, name) != 0)
800 char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
801 sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
802 blurb(), tmp_name, name);
810 p->init_file_date = write_date;
812 /* Since the .xscreensaver file is used for IPC, let's try and make
813 sure that the bits actually land on the disk right away. */
816 status = 0; /* wrote and renamed successfully! */
821 char *buf = (char *) malloc(1024 + strlen(name));
822 sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
835 /* Parsing the resource database
839 /* Populate `saver_preferences' with the contents of the resource database.
840 Note that this may be called multiple times -- it is re-run each time
841 the ~/.xscreensaver file is reloaded.
843 This function can be very noisy, since it issues resource syntax errors
847 load_init_file (saver_preferences *p)
849 static Bool first_time = True;
851 if (parse_init_file (p) != 0) /* file might have gone away */
852 if (!first_time) return;
856 p->xsync_p = get_boolean_resource ("synchronous", "Synchronous");
857 p->verbose_p = get_boolean_resource ("verbose", "Boolean");
858 p->timestamp_p = get_boolean_resource ("timestamp", "Boolean");
859 p->lock_p = get_boolean_resource ("lock", "Boolean");
860 p->lock_vt_p = get_boolean_resource ("lockVTs", "Boolean");
861 p->fade_p = get_boolean_resource ("fade", "Boolean");
862 p->unfade_p = get_boolean_resource ("unfade", "Boolean");
863 p->fade_seconds = 1000 * get_seconds_resource ("fadeSeconds", "Time");
864 p->fade_ticks = get_integer_resource ("fadeTicks", "Integer");
865 p->install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
866 p->nice_inferior = get_integer_resource ("nice", "Nice");
868 p->initial_delay = 1000 * get_seconds_resource ("initialDelay", "Time");
869 p->splash_duration = 1000 * get_seconds_resource ("splashDuration", "Time");
870 p->timeout = 1000 * get_minutes_resource ("timeout", "Time");
871 p->lock_timeout = 1000 * get_minutes_resource ("lockTimeout", "Time");
872 p->cycle = 1000 * get_minutes_resource ("cycle", "Time");
873 p->passwd_timeout = 1000 * get_seconds_resource ("passwdTimeout", "Time");
874 p->pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
875 p->notice_events_timeout = 1000*get_seconds_resource("windowCreationTimeout",
877 p->shell = get_string_resource ("bourneShell", "BourneShell");
879 p->demo_command = get_string_resource("demoCommand", "URL");
880 p->prefs_command = get_string_resource("prefsCommand", "URL");
881 p->help_url = get_string_resource("helpURL", "URL");
882 p->load_url_command = get_string_resource("loadURL", "LoadURL");
886 if ((s = get_string_resource ("splash", "Boolean")))
887 if (!get_boolean_resource("splash", "Boolean"))
888 p->splash_duration = 0;
892 p->use_xidle_extension = get_boolean_resource ("xidleExtension","Boolean");
893 p->use_mit_saver_extension = get_boolean_resource ("mitSaverExtension",
895 p->use_sgi_saver_extension = get_boolean_resource ("sgiSaverExtension",
897 p->use_proc_interrupts = get_boolean_resource ("procInterrupts", "Boolean");
899 /* Throttle the various timeouts to reasonable values.
901 if (p->passwd_timeout <= 0) p->passwd_timeout = 30000; /* 30 secs */
902 if (p->timeout < 10000) p->timeout = 10000; /* 10 secs */
903 if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000; /* 2 secs */
904 if (p->pointer_timeout <= 0) p->pointer_timeout = 5000; /* 5 secs */
905 if (p->notice_events_timeout <= 0)
906 p->notice_events_timeout = 10000; /* 10 secs */
907 if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
909 if (! p->fade_p) p->unfade_p = False;
911 p->watchdog_timeout = p->cycle * 0.6;
912 if (p->watchdog_timeout < 30000) p->watchdog_timeout = 30000; /* 30 secs */
913 if (p->watchdog_timeout > 3600000) p->watchdog_timeout = 3600000; /* 1 hr */
921 p->timestamp_p = True;
922 p->initial_delay = 0;
927 /* Parsing the programs resource.
931 parse_screenhack (const char *line)
933 screenhack *h = (screenhack *) calloc (1, sizeof(*h));
938 while (isspace(*line)) line++; /* skip whitespace */
939 if (*line == '-') /* handle "-" */
941 h->enabled_p = False;
943 while (isspace(*line)) line++; /* skip whitespace */
946 s = line; /* handle "visual:" */
947 while (*line && *line != ':' && *line != '"' && !isspace(*line))
953 h->visual = (char *) malloc (line-s+1);
954 strncpy (h->visual, s, line-s);
955 h->visual[line-s] = 0;
956 if (*line == ':') line++; /* skip ":" */
957 while (isspace(*line)) line++; /* skip whitespace */
960 if (*line == '"') /* handle "name" */
964 while (*line && *line != '"')
966 h->name = (char *) malloc (line-s+1);
967 strncpy (h->name, s, line-s);
969 if (*line == '"') line++; /* skip "\"" */
970 while (isspace(*line)) line++; /* skip whitespace */
973 h->command = format_command (line, False); /* handle command */
979 free_screenhack (screenhack *hack)
981 if (hack->visual) free (hack->visual);
982 if (hack->name) free (hack->name);
983 free (hack->command);
984 memset (hack, 0, sizeof(*hack));
990 format_command (const char *cmd, Bool wrap_p)
994 char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
995 const char *in = cmd;
999 /* shrink all whitespace to one space, for the benefit of the "demo"
1000 mode display. We only do this when we can easily tell that the
1001 whitespace is not significant (no shell metachars).
1005 case '\'': case '"': case '`': case '\\':
1006 /* Metachars are scary. Copy the rest of the line unchanged. */
1008 *out++ = *in++, col++;
1011 case ' ': case '\t':
1012 /* Squeeze all other whitespace down to one space. */
1013 while (*in == ' ' || *in == '\t')
1015 *out++ = ' ', col++;
1019 /* Copy other chars unchanged. */
1020 *out++ = *in++, col++;
1027 /* Strip trailing whitespace */
1028 while (out > cmd2 && isspace (out[-1]))
1036 format_hack (screenhack *hack, Bool wrap_p)
1039 int size = (2 * (strlen(hack->command) +
1040 (hack->visual ? strlen(hack->visual) : 0) +
1041 (hack->name ? strlen(hack->name) : 0) +
1043 char *h2 = (char *) malloc (size);
1048 if (!hack->enabled_p) *out++ = '-'; /* write disabled flag */
1050 if (hack->visual && *hack->visual) /* write visual name */
1052 if (hack->enabled_p) *out++ = ' ';
1054 strcpy (out, hack->visual);
1055 out += strlen (hack->visual);
1061 col = string_columns (h2, strlen (h2), 0);
1063 if (hack->name && *hack->name) /* write pretty name */
1065 int L = (strlen (hack->name) + 2);
1067 out = stab_to (out, col, tab - L - 2);
1071 strcpy (out, hack->name);
1072 out += strlen (hack->name);
1076 col = string_columns (h2, strlen (h2), 0);
1077 if (wrap_p && col >= tab)
1079 out = stab_to (out, col, 77);
1080 *out += strlen(out);
1085 if (out >= h2+size) abort();
1089 col = string_columns (h2, strlen (h2), 0);
1090 out = stab_to (out, col, tab); /* indent */
1092 if (out >= h2+size) abort();
1093 s = format_command (hack->command, wrap_p);
1104 get_screenhacks (saver_preferences *p)
1112 d = get_string_resource ("monoPrograms", "MonoPrograms");
1113 if (d && !*d) { free(d); d = 0; }
1115 d = get_string_resource ("colorPrograms", "ColorPrograms");
1116 if (d && !*d) { free(d); d = 0; }
1121 "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
1122 see the manual for details.\n", blurb());
1126 d = get_string_resource ("programs", "Programs");
1130 for (i = 0; i < p->screenhacks_count; i++)
1131 if (p->screenhacks[i])
1132 free_screenhack (p->screenhacks[i]);
1133 free(p->screenhacks);
1139 p->screenhacks_count = 0;
1147 /* Count up the number of newlines (which will be equal to or larger than
1148 the number of hacks.)
1151 for (i = 0; d[i]; i++)
1156 p->screenhacks = (screenhack **) calloc (sizeof (screenhack *), i+1);
1158 /* Iterate over the lines in `d' (the string with newlines)
1159 and make new strings to stuff into the `screenhacks' array.
1161 p->screenhacks_count = 0;
1162 while (start < size)
1164 /* skip forward over whitespace. */
1165 while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
1168 /* skip forward to newline or end of string. */
1170 while (d[end] != 0 && d[end] != '\n')
1173 /* null terminate. */
1176 p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
1177 if (p->screenhacks_count >= i)
1183 if (p->screenhacks_count == 0)
1185 free (p->screenhacks);