666 lines
16 KiB
C
666 lines
16 KiB
C
/*
|
|
* $Id: rc.c,v 1.60 2020/11/25 00:06:40 tom Exp $
|
|
*
|
|
* rc.c -- routines for processing the configuration file
|
|
*
|
|
* Copyright 2000-2019,2020 Thomas E. Dickey
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License, version 2.1
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program; if not, write to
|
|
* Free Software Foundation, Inc.
|
|
* 51 Franklin St., Fifth Floor
|
|
* Boston, MA 02110, USA.
|
|
*
|
|
* An earlier version of this program lists as authors
|
|
* Savio Lam (lam836@cs.cuhk.hk)
|
|
*/
|
|
|
|
#include <dialog.h>
|
|
|
|
#include <dlg_keys.h>
|
|
|
|
#ifdef HAVE_COLOR
|
|
#include <dlg_colors.h>
|
|
#include <dlg_internals.h>
|
|
|
|
#define L_PAREN '('
|
|
#define R_PAREN ')'
|
|
|
|
#define MIN_TOKEN 3
|
|
#ifdef HAVE_RC_FILE2
|
|
#define MAX_TOKEN 5
|
|
#else
|
|
#define MAX_TOKEN MIN_TOKEN
|
|
#endif
|
|
|
|
#define UNKNOWN_COLOR -2
|
|
|
|
/*
|
|
* For matching color names with color values
|
|
*/
|
|
static const color_names_st color_names[] =
|
|
{
|
|
#ifdef HAVE_USE_DEFAULT_COLORS
|
|
{"DEFAULT", -1},
|
|
#endif
|
|
{"BLACK", COLOR_BLACK},
|
|
{"RED", COLOR_RED},
|
|
{"GREEN", COLOR_GREEN},
|
|
{"YELLOW", COLOR_YELLOW},
|
|
{"BLUE", COLOR_BLUE},
|
|
{"MAGENTA", COLOR_MAGENTA},
|
|
{"CYAN", COLOR_CYAN},
|
|
{"WHITE", COLOR_WHITE},
|
|
}; /* color names */
|
|
#define COLOR_COUNT TableSize(color_names)
|
|
#endif /* HAVE_COLOR */
|
|
|
|
#define GLOBALRC "/etc/dialogrc"
|
|
#define DIALOGRC ".dialogrc"
|
|
|
|
/* Types of values */
|
|
#define VAL_INT 0
|
|
#define VAL_STR 1
|
|
#define VAL_BOOL 2
|
|
|
|
/* Type of line in configuration file */
|
|
typedef enum {
|
|
LINE_ERROR = -1,
|
|
LINE_EQUALS,
|
|
LINE_EMPTY
|
|
} PARSE_LINE;
|
|
|
|
/* number of configuration variables */
|
|
#define VAR_COUNT TableSize(vars)
|
|
|
|
/* check if character is string quoting characters */
|
|
#define isquote(c) ((c) == '"' || (c) == '\'')
|
|
|
|
/* get last character of string */
|
|
#define lastch(str) str[strlen(str)-1]
|
|
|
|
/*
|
|
* Configuration variables
|
|
*/
|
|
typedef struct {
|
|
const char *name; /* name of configuration variable as in DIALOGRC */
|
|
void *var; /* address of actual variable to change */
|
|
int type; /* type of value */
|
|
const char *comment; /* comment to put in "rc" file */
|
|
} vars_st;
|
|
|
|
/*
|
|
* This table should contain only references to dialog_state, since dialog_vars
|
|
* is reset specially in dialog.c before each widget.
|
|
*/
|
|
static const vars_st vars[] =
|
|
{
|
|
{"aspect",
|
|
&dialog_state.aspect_ratio,
|
|
VAL_INT,
|
|
"Set aspect-ration."},
|
|
|
|
{"separate_widget",
|
|
&dialog_state.separate_str,
|
|
VAL_STR,
|
|
"Set separator (for multiple widgets output)."},
|
|
|
|
{"tab_len",
|
|
&dialog_state.tab_len,
|
|
VAL_INT,
|
|
"Set tab-length (for textbox tab-conversion)."},
|
|
|
|
{"visit_items",
|
|
&dialog_state.visit_items,
|
|
VAL_BOOL,
|
|
"Make tab-traversal for checklist, etc., include the list."},
|
|
|
|
#ifdef HAVE_COLOR
|
|
{"use_shadow",
|
|
&dialog_state.use_shadow,
|
|
VAL_BOOL,
|
|
"Shadow dialog boxes? This also turns on color."},
|
|
|
|
{"use_colors",
|
|
&dialog_state.use_colors,
|
|
VAL_BOOL,
|
|
"Turn color support ON or OFF"},
|
|
#endif /* HAVE_COLOR */
|
|
}; /* vars */
|
|
|
|
static int
|
|
skip_whitespace(char *str, int n)
|
|
{
|
|
while (isblank(UCH(str[n])) && str[n] != '\0')
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
skip_keyword(char *str, int n)
|
|
{
|
|
while (isalnum(UCH(str[n])) && str[n] != '\0')
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
static void
|
|
trim_token(char **tok)
|
|
{
|
|
char *tmp = *tok + skip_whitespace(*tok, 0);
|
|
|
|
*tok = tmp;
|
|
|
|
while (*tmp != '\0' && !isblank(UCH(*tmp)))
|
|
tmp++;
|
|
|
|
*tmp = '\0';
|
|
}
|
|
|
|
static int
|
|
from_boolean(const char *str)
|
|
{
|
|
int code = -1;
|
|
|
|
if (str != NULL && *str != '\0') {
|
|
if (!dlg_strcmp(str, "ON")) {
|
|
code = 1;
|
|
} else if (!dlg_strcmp(str, "OFF")) {
|
|
code = 0;
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int
|
|
from_color_name(const char *str)
|
|
{
|
|
int code = UNKNOWN_COLOR;
|
|
|
|
if (str != NULL && *str != '\0') {
|
|
size_t i;
|
|
|
|
for (i = 0; i < COLOR_COUNT; ++i) {
|
|
if (!dlg_strcmp(str, color_names[i].name)) {
|
|
code = color_names[i].value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
static int
|
|
find_vars(char *name)
|
|
{
|
|
int result = -1;
|
|
unsigned i;
|
|
|
|
for (i = 0; i < VAR_COUNT; i++) {
|
|
if (dlg_strcmp(vars[i].name, name) == 0) {
|
|
result = (int) i;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#ifdef HAVE_COLOR
|
|
static int
|
|
find_color(char *name)
|
|
{
|
|
int result = -1;
|
|
int i;
|
|
int limit = dlg_color_count();
|
|
|
|
for (i = 0; i < limit; i++) {
|
|
if (dlg_strcmp(dlg_color_table[i].name, name) == 0) {
|
|
result = i;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static const char *
|
|
to_color_name(int code)
|
|
{
|
|
const char *result = "?";
|
|
size_t n;
|
|
for (n = 0; n < TableSize(color_names); ++n) {
|
|
if (code == color_names[n].value) {
|
|
result = color_names[n].name;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static const char *
|
|
to_boolean(int code)
|
|
{
|
|
return code ? "ON" : "OFF";
|
|
}
|
|
|
|
/*
|
|
* Extract the foreground, background and highlight values from an attribute
|
|
* represented as a string in one of these forms:
|
|
*
|
|
* "(foreground,background,highlight,underline,reverse)"
|
|
* "(foreground,background,highlight,underline)"
|
|
* "(foreground,background,highlight)"
|
|
* "xxxx_color"
|
|
*/
|
|
static int
|
|
str_to_attr(char *str, DIALOG_COLORS * result)
|
|
{
|
|
char *tokens[MAX_TOKEN + 1];
|
|
char tempstr[MAX_LEN + 1];
|
|
size_t have;
|
|
size_t i = 0;
|
|
size_t tok_count = 0;
|
|
|
|
memset(result, 0, sizeof(*result));
|
|
result->fg = -1;
|
|
result->bg = -1;
|
|
result->hilite = -1;
|
|
|
|
if (str[0] != L_PAREN || lastch(str) != R_PAREN) {
|
|
int ret;
|
|
|
|
if ((ret = find_color(str)) >= 0) {
|
|
*result = dlg_color_table[ret];
|
|
return 0;
|
|
}
|
|
/* invalid representation */
|
|
return -1;
|
|
}
|
|
|
|
/* remove the parenthesis */
|
|
have = strlen(str);
|
|
if (have > MAX_LEN) {
|
|
have = MAX_LEN - 1;
|
|
} else {
|
|
have -= 2;
|
|
}
|
|
memcpy(tempstr, str + 1, have);
|
|
tempstr[have] = '\0';
|
|
|
|
/* parse comma-separated tokens, allow up to
|
|
* one more than max tokens to detect extras */
|
|
while (tok_count < TableSize(tokens)) {
|
|
|
|
tokens[tok_count++] = &tempstr[i];
|
|
|
|
while (tempstr[i] != '\0' && tempstr[i] != ',')
|
|
i++;
|
|
|
|
if (tempstr[i] == '\0')
|
|
break;
|
|
|
|
tempstr[i++] = '\0';
|
|
}
|
|
|
|
if (tok_count < MIN_TOKEN || tok_count > MAX_TOKEN) {
|
|
/* invalid representation */
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < tok_count; ++i)
|
|
trim_token(&tokens[i]);
|
|
|
|
/* validate */
|
|
if (UNKNOWN_COLOR == (result->fg = from_color_name(tokens[0]))
|
|
|| UNKNOWN_COLOR == (result->bg = from_color_name(tokens[1]))
|
|
|| UNKNOWN_COLOR == (result->hilite = from_boolean(tokens[2]))
|
|
#ifdef HAVE_RC_FILE2
|
|
|| (tok_count >= 4 && (result->ul = from_boolean(tokens[3])) == -1)
|
|
|| (tok_count >= 5 && (result->rv = from_boolean(tokens[4])) == -1)
|
|
#endif /* HAVE_RC_FILE2 */
|
|
) {
|
|
/* invalid representation */
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* HAVE_COLOR */
|
|
|
|
/*
|
|
* Check if the line begins with a special keyword; if so, return true while
|
|
* pointing params to its parameters.
|
|
*/
|
|
static int
|
|
begins_with(char *line, const char *keyword, char **params)
|
|
{
|
|
int i = skip_whitespace(line, 0);
|
|
int j = skip_keyword(line, i);
|
|
|
|
if ((j - i) == (int) strlen(keyword)) {
|
|
char save = line[j];
|
|
line[j] = 0;
|
|
if (!dlg_strcmp(keyword, line + i)) {
|
|
*params = line + skip_whitespace(line, j + 1);
|
|
return 1;
|
|
}
|
|
line[j] = save;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Parse a line in the configuration file
|
|
*
|
|
* Each line is of the form: "variable = value". On exit, 'var' will contain
|
|
* the variable name, and 'value' will contain the value string.
|
|
*
|
|
* Return values:
|
|
*
|
|
* LINE_EMPTY - line is blank or comment
|
|
* LINE_EQUALS - line contains "variable = value"
|
|
* LINE_ERROR - syntax error in line
|
|
*/
|
|
static PARSE_LINE
|
|
parse_line(char *line, char **var, char **value)
|
|
{
|
|
int i = 0;
|
|
|
|
/* ignore white space at beginning of line */
|
|
i = skip_whitespace(line, i);
|
|
|
|
if (line[i] == '\0') /* line is blank */
|
|
return LINE_EMPTY;
|
|
else if (line[i] == '#') /* line is comment */
|
|
return LINE_EMPTY;
|
|
else if (line[i] == '=') /* variable names cannot start with a '=' */
|
|
return LINE_ERROR;
|
|
|
|
/* set 'var' to variable name */
|
|
*var = line + i++; /* skip to next character */
|
|
|
|
/* find end of variable name */
|
|
while (!isblank(UCH(line[i])) && line[i] != '=' && line[i] != '\0')
|
|
i++;
|
|
|
|
if (line[i] == '\0') /* syntax error */
|
|
return LINE_ERROR;
|
|
else if (line[i] == '=')
|
|
line[i++] = '\0';
|
|
else {
|
|
line[i++] = '\0';
|
|
|
|
/* skip white space before '=' */
|
|
i = skip_whitespace(line, i);
|
|
|
|
if (line[i] != '=') /* syntax error */
|
|
return LINE_ERROR;
|
|
else
|
|
i++; /* skip the '=' */
|
|
}
|
|
|
|
/* skip white space after '=' */
|
|
i = skip_whitespace(line, i);
|
|
|
|
if (line[i] == '\0')
|
|
return LINE_ERROR;
|
|
else
|
|
*value = line + i; /* set 'value' to value string */
|
|
|
|
/* trim trailing white space from 'value' */
|
|
i = (int) strlen(*value) - 1;
|
|
while (isblank(UCH((*value)[i])) && i > 0)
|
|
i--;
|
|
(*value)[i + 1] = '\0';
|
|
|
|
return LINE_EQUALS; /* no syntax error in line */
|
|
}
|
|
|
|
/*
|
|
* Create the configuration file
|
|
*/
|
|
void
|
|
dlg_create_rc(const char *filename)
|
|
{
|
|
unsigned i;
|
|
FILE *rc_file;
|
|
|
|
if ((rc_file = fopen(filename, "wt")) == NULL)
|
|
dlg_exiterr("Error opening file for writing in dlg_create_rc().");
|
|
|
|
fprintf(rc_file, "#\n\
|
|
# Run-time configuration file for dialog\n\
|
|
#\n\
|
|
# Automatically generated by \"dialog --create-rc <file>\"\n\
|
|
#\n\
|
|
#\n\
|
|
# Types of values:\n\
|
|
#\n\
|
|
# Number - <number>\n\
|
|
# String - \"string\"\n\
|
|
# Boolean - <ON|OFF>\n"
|
|
#ifdef HAVE_COLOR
|
|
#ifdef HAVE_RC_FILE2
|
|
"\
|
|
# Attribute - (foreground,background,highlight?,underline?,reverse?)\n"
|
|
#else /* HAVE_RC_FILE2 */
|
|
"\
|
|
# Attribute - (foreground,background,highlight?)\n"
|
|
#endif /* HAVE_RC_FILE2 */
|
|
#endif /* HAVE_COLOR */
|
|
);
|
|
|
|
/* Print an entry for each configuration variable */
|
|
for (i = 0; i < VAR_COUNT; i++) {
|
|
fprintf(rc_file, "\n# %s\n", vars[i].comment);
|
|
switch (vars[i].type) {
|
|
case VAL_INT:
|
|
fprintf(rc_file, "%s = %d\n", vars[i].name,
|
|
*((int *) vars[i].var));
|
|
break;
|
|
case VAL_STR:
|
|
fprintf(rc_file, "%s = \"%s\"\n", vars[i].name,
|
|
(char *) vars[i].var);
|
|
break;
|
|
case VAL_BOOL:
|
|
fprintf(rc_file, "%s = %s\n", vars[i].name,
|
|
*((bool *) vars[i].var) ? "ON" : "OFF");
|
|
break;
|
|
}
|
|
}
|
|
#ifdef HAVE_COLOR
|
|
for (i = 0; i < (unsigned) dlg_color_count(); ++i) {
|
|
unsigned j;
|
|
bool repeat = FALSE;
|
|
|
|
fprintf(rc_file, "\n# %s\n", dlg_color_table[i].comment);
|
|
for (j = 0; j != i; ++j) {
|
|
if (dlg_color_table[i].fg == dlg_color_table[j].fg
|
|
&& dlg_color_table[i].bg == dlg_color_table[j].bg
|
|
&& dlg_color_table[i].hilite == dlg_color_table[j].hilite) {
|
|
fprintf(rc_file, "%s = %s\n",
|
|
dlg_color_table[i].name,
|
|
dlg_color_table[j].name);
|
|
repeat = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!repeat) {
|
|
fprintf(rc_file, "%s = %c", dlg_color_table[i].name, L_PAREN);
|
|
fprintf(rc_file, "%s", to_color_name(dlg_color_table[i].fg));
|
|
fprintf(rc_file, ",%s", to_color_name(dlg_color_table[i].bg));
|
|
fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].hilite));
|
|
#ifdef HAVE_RC_FILE2
|
|
if (dlg_color_table[i].ul || dlg_color_table[i].rv)
|
|
fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].ul));
|
|
if (dlg_color_table[i].rv)
|
|
fprintf(rc_file, ",%s", to_boolean(dlg_color_table[i].rv));
|
|
#endif /* HAVE_RC_FILE2 */
|
|
fprintf(rc_file, "%c\n", R_PAREN);
|
|
}
|
|
}
|
|
#endif /* HAVE_COLOR */
|
|
dlg_dump_keys(rc_file);
|
|
|
|
(void) fclose(rc_file);
|
|
}
|
|
|
|
static void
|
|
report_error(const char *filename, int line_no, const char *msg)
|
|
{
|
|
fprintf(stderr, "%s:%d: %s\n", filename, line_no, msg);
|
|
dlg_trace_msg("%s:%d: %s\n", filename, line_no, msg);
|
|
}
|
|
|
|
/*
|
|
* Parse the configuration file and set up variables
|
|
*/
|
|
int
|
|
dlg_parse_rc(void)
|
|
{
|
|
int i;
|
|
int l = 1;
|
|
PARSE_LINE parse;
|
|
char str[MAX_LEN + 1];
|
|
char *var;
|
|
char *value;
|
|
char *filename;
|
|
int result = 0;
|
|
FILE *rc_file = 0;
|
|
char *params;
|
|
|
|
/*
|
|
* At startup, dialog determines the settings to use as follows:
|
|
*
|
|
* a) if the environment variable $DIALOGRC is set, its value determines
|
|
* the name of the configuration file.
|
|
*
|
|
* b) if the file in (a) can't be found, use the file $HOME/.dialogrc
|
|
* as the configuration file.
|
|
*
|
|
* c) if the file in (b) can't be found, try using the GLOBALRC file.
|
|
* Usually this will be /etc/dialogrc.
|
|
*
|
|
* d) if the file in (c) cannot be found, use the compiled-in defaults.
|
|
*/
|
|
|
|
/* try step (a) */
|
|
if ((filename = dlg_getenv_str("DIALOGRC")) != NULL)
|
|
rc_file = fopen(filename, "rt");
|
|
|
|
if (rc_file == NULL) { /* step (a) failed? */
|
|
/* try step (b) */
|
|
if ((filename = dlg_getenv_str("HOME")) != NULL
|
|
&& strlen(filename) < MAX_LEN - (sizeof(DIALOGRC) + 3)) {
|
|
if (filename[0] == '\0' || lastch(filename) == '/')
|
|
sprintf(str, "%s%s", filename, DIALOGRC);
|
|
else
|
|
sprintf(str, "%s/%s", filename, DIALOGRC);
|
|
rc_file = fopen(filename = str, "rt");
|
|
}
|
|
}
|
|
|
|
if (rc_file == NULL) { /* step (b) failed? */
|
|
/* try step (c) */
|
|
strcpy(str, GLOBALRC);
|
|
if ((rc_file = fopen(filename = str, "rt")) == NULL)
|
|
return 0; /* step (c) failed, use default values */
|
|
}
|
|
|
|
DLG_TRACE(("# opened rc file \"%s\"\n", filename));
|
|
/* Scan each line and set variables */
|
|
while ((result == 0) && (fgets(str, MAX_LEN, rc_file) != NULL)) {
|
|
DLG_TRACE(("#\t%s", str));
|
|
if (*str == '\0' || lastch(str) != '\n') {
|
|
/* ignore rest of file if line too long */
|
|
report_error(filename, l, "line too long");
|
|
result = -1; /* parse aborted */
|
|
break;
|
|
}
|
|
|
|
lastch(str) = '\0';
|
|
if (begins_with(str, "bindkey", ¶ms)) {
|
|
if (!dlg_parse_bindkey(params)) {
|
|
report_error(filename, l, "invalid bindkey");
|
|
result = -1;
|
|
}
|
|
continue;
|
|
}
|
|
parse = parse_line(str, &var, &value); /* parse current line */
|
|
|
|
switch (parse) {
|
|
case LINE_EMPTY: /* ignore blank lines and comments */
|
|
break;
|
|
case LINE_EQUALS:
|
|
/* search table for matching config variable name */
|
|
if ((i = find_vars(var)) >= 0) {
|
|
switch (vars[i].type) {
|
|
case VAL_INT:
|
|
*((int *) vars[i].var) = atoi(value);
|
|
break;
|
|
case VAL_STR:
|
|
if (!isquote(value[0]) || !isquote(lastch(value))
|
|
|| strlen(value) < 2) {
|
|
report_error(filename, l, "expected string value");
|
|
result = -1; /* parse aborted */
|
|
} else {
|
|
/* remove the (") quotes */
|
|
value++;
|
|
lastch(value) = '\0';
|
|
strcpy((char *) vars[i].var, value);
|
|
}
|
|
break;
|
|
case VAL_BOOL:
|
|
if (!dlg_strcmp(value, "ON"))
|
|
*((bool *) vars[i].var) = TRUE;
|
|
else if (!dlg_strcmp(value, "OFF"))
|
|
*((bool *) vars[i].var) = FALSE;
|
|
else {
|
|
report_error(filename, l, "expected boolean value");
|
|
result = -1; /* parse aborted */
|
|
}
|
|
break;
|
|
}
|
|
#ifdef HAVE_COLOR
|
|
} else if ((i = find_color(var)) >= 0) {
|
|
DIALOG_COLORS temp;
|
|
if (str_to_attr(value, &temp) == -1) {
|
|
report_error(filename, l, "expected attribute value");
|
|
result = -1; /* parse aborted */
|
|
} else {
|
|
dlg_color_table[i].fg = temp.fg;
|
|
dlg_color_table[i].bg = temp.bg;
|
|
dlg_color_table[i].hilite = temp.hilite;
|
|
#ifdef HAVE_RC_FILE2
|
|
dlg_color_table[i].ul = temp.ul;
|
|
dlg_color_table[i].rv = temp.rv;
|
|
#endif /* HAVE_RC_FILE2 */
|
|
}
|
|
} else {
|
|
#endif /* HAVE_COLOR */
|
|
report_error(filename, l, "unknown variable");
|
|
result = -1; /* parse aborted */
|
|
}
|
|
break;
|
|
case LINE_ERROR:
|
|
report_error(filename, l, "syntax error");
|
|
result = -1; /* parse aborted */
|
|
break;
|
|
}
|
|
l++; /* next line */
|
|
}
|
|
|
|
(void) fclose(rc_file);
|
|
return result;
|
|
}
|