diff options
-rw-r--r-- | include/pathnames.h | 4 | ||||
-rw-r--r-- | lib/colors.c | 299 |
2 files changed, 270 insertions, 33 deletions
diff --git a/include/pathnames.h b/include/pathnames.h index 94cc412a7..b6efdb7dc 100644 --- a/include/pathnames.h +++ b/include/pathnames.h @@ -50,8 +50,8 @@ #define _PATH_SECURE "/etc/securesingle" #define _PATH_USERTTY "/etc/usertty" -#define _PATH_TERMCOLORS_DIR "/etc/terminal-colors.d/" -#define _PATH_TERMCOLORS_DISABLE _PATH_TERMCOLORS_DIR "disable" +#define _PATH_TERMCOLORS_DIRNAME "terminal-colors.d" +#define _PATH_TERMCOLORS_DIR "/etc/" _PATH_TERMCOLORS_DIRNAME /* used in login-utils/shutdown.c */ diff --git a/lib/colors.c b/lib/colors.c index 195027376..4ad2bca53 100644 --- a/lib/colors.c +++ b/lib/colors.c @@ -6,66 +6,290 @@ */ #include <assert.h> #include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> #include "c.h" #include "colors.h" #include "xalloc.h" #include "pathnames.h" -static int ul_color_term_ok; +enum { + UL_COLORFILE_DISABLE, /* .disable */ + UL_COLORFILE_ENABLE, /* .enable */ + UL_COLORFILE_SCHEME, /* .scheme */ + + __UL_COLORFILE_COUNT +}; + +struct ul_color_ctl { + const char *utilname; /* util name */ + const char *termname; /* terminal name ($TERM) */ + + char *scheme; /* path to scheme */ + + int mode; /* UL_COLORMODE_* */ + int use_colors; /* based on mode and scores[] */ + int scores[__UL_COLORFILE_COUNT]; +}; + +static struct ul_color_ctl ul_colors; + + +static void colors_reset(struct ul_color_ctl *cc) +{ + if (!cc) + return; + + free(cc->scheme); + + cc->scheme = NULL; + cc->utilname = NULL; + cc->termname = NULL; + cc->mode = UL_COLORMODE_UNDEF; + + memset(cc->scores, 0, sizeof(cc->scores)); +} + +#ifdef TEST_PROGRAM +static void colors_debug(struct ul_color_ctl *cc) +{ + int i; + + if (!cc) + return; + + printf("Colors:\n"); + printf("\tutilname = '%s'\n", cc->utilname); + printf("\ttermname = '%s'\n", cc->termname); + printf("\tscheme = '%s'\n", cc->scheme); + printf("\tmode = %s\n", + cc->mode == UL_COLORMODE_UNDEF ? "undefined" : + cc->mode == UL_COLORMODE_AUTO ? "auto" : + cc->mode == UL_COLORMODE_NEVER ? "never" : + cc->mode == UL_COLORMODE_ALWAYS ? "always" : "???"); + + for (i = 0; i < ARRAY_SIZE(cc->scores); i++) + printf("\tscore %s = %d\n", + i == UL_COLORFILE_DISABLE ? "disable" : + i == UL_COLORFILE_ENABLE ? "enable" : + i == UL_COLORFILE_SCHEME ? "scheme" : "???", + cc->scores[i]); +} + +#endif + +static int filename_to_tokens(const char *str, + const char **name, size_t *namesz, + const char **term, size_t *termsz, + int *filetype) +{ + const char *type_start, *term_start, *p; + + if (!str || !*str || *str == '.' || strlen(str) > PATH_MAX) + return -EINVAL; + + /* parse .type */ + p = strrchr(str, '.'); + type_start = p ? p + 1 : str; + + if (strcmp(type_start, "disable") == 0) + *filetype = UL_COLORFILE_DISABLE; + else if (strcmp(type_start, "enable") == 0) + *filetype = UL_COLORFILE_ENABLE; + else if (strcmp(type_start, "scheme") == 0) + *filetype = UL_COLORFILE_SCHEME; + else + return 1; /* unknown type */ + + if (type_start == str) + return 0; /* "type" only */ + + /* parse @termname */ + p = strchr(str, '@'); + term_start = p ? p + 1 : NULL; + if (term_start) { + *term = term_start; + *termsz = type_start - term_start - 1; + if (term_start - 1 == str) + return 0; /* "@termname.type" */ + } + + /* parse utilname */ + p = term_start ? term_start : type_start; + *name = str; + *namesz = p - str - 1; + + return 0; +} + + +/* + * Checks for: + * + * filename score + * --------------------------------- + * type 1 + * @termname.type 10 + 1 + * utilname.type 20 + 1 + * utilname@termname.type 20 + 10 + 1 + */ +static int colors_readdir(struct ul_color_ctl *cc, const char *dirname) +{ + DIR *dir; + int rc = 0; + struct dirent *d; + char scheme[PATH_MAX] = { '\0' }; + size_t namesz, termsz; + + if (!dirname || !cc || !cc->utilname || !*cc->utilname) + return -EINVAL; + dir = opendir(dirname); + if (!dir) + return -errno; + + namesz = strlen(cc->utilname); + termsz = cc->termname ? strlen(cc->termname) : 0; + + while ((d = readdir(dir))) { + int type, score = 1; + const char *tk_name = NULL, *tk_term = NULL; + size_t tk_namesz = 0, tk_termsz = 0; + + if (*d->d_name == '.') + continue; +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK && + d->d_type != DT_REG) + continue; +#endif + if (filename_to_tokens(d->d_name, + &tk_name, &tk_namesz, + &tk_term, &tk_termsz, &type) != 0) + continue; + + /* count teoretical score before we check names to avoid + * unnecessary strcmp() */ + if (tk_name) + score += 20; + if (tk_term) + score += 10; + /* + fprintf(stderr, "%20s score: %2d [cur max: %2d]\n", + d->d_name, score, cc->scores[type]); + */ + if (score < cc->scores[type]) + continue; + + /* filter out by names */ + if (tk_namesz != namesz + || strncmp(tk_name, cc->utilname, namesz) != 0) + continue; + if (tk_termsz != termsz + || termsz == 0 + || strncmp(tk_term, cc->termname, termsz) != 0) + continue; + + cc->scores[type] = score; + if (type == UL_COLORFILE_SCHEME) + strncpy(scheme, d->d_name, sizeof(scheme)); + } + + if (*scheme) { + scheme[sizeof(scheme) - 1] = '\0'; + if (asprintf(&cc->scheme, "%s/%s", dirname, scheme) <= 0) + rc = -ENOMEM; + } + + closedir(dir); + return rc; +} + +static void colors_deinit(void) +{ + colors_reset(&ul_colors); +} + +static char *colors_get_homedir(char *buf, size_t bufsz) +{ + char *p = getenv("XDG_CONFIG_HOME"); + + if (p) { + snprintf(buf, bufsz, "%s/" _PATH_TERMCOLORS_DIRNAME, p); + return buf; + } + + p = getenv("HOME"); + if (p) { + snprintf(buf, bufsz, "%s/.config/" _PATH_TERMCOLORS_DIRNAME, p); + return buf; + } + + return NULL; +} int colors_init(int mode, const char *name) { int atty = -1; + struct ul_color_ctl *cc = &ul_colors; + + cc->utilname = name; + cc->mode = mode; if (mode == UL_COLORMODE_UNDEF && (atty = isatty(STDOUT_FILENO))) { - char path[PATH_MAX]; + int rc = -ENOENT; + char *dirname, buf[PATH_MAX]; - snprintf(path, sizeof(path), "%s%s%s", - _PATH_TERMCOLORS_DIR, name, ".enable"); + cc->termname = getenv("TERM"); - if (access(path, F_OK) == 0) - mode = UL_COLORMODE_AUTO; - else { - snprintf(path, sizeof(path), "%s%s%s", - _PATH_TERMCOLORS_DIR, name, ".disable"); + dirname = colors_get_homedir(buf, sizeof(buf)); + if (dirname) + rc = colors_readdir(cc, dirname); /* ~/.config */ + if (rc == -EPERM || rc == -EACCES || rc == -ENOENT) + rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR); /* /etc */ - if (access(_PATH_TERMCOLORS_DISABLE, F_OK) == 0 || - access(path, F_OK) == 0) - mode = UL_COLORMODE_NEVER; + if (rc) + cc->mode = UL_COLORMODE_AUTO; + else { + /* evaluate scores */ + if (cc->scores[UL_COLORFILE_DISABLE] > + cc->scores[UL_COLORFILE_ENABLE]) + cc->mode = UL_COLORMODE_NEVER; else - mode = UL_COLORMODE_AUTO; + cc->mode = UL_COLORMODE_AUTO; + + atexit(colors_deinit); } } - switch (mode) { + switch (cc->mode) { case UL_COLORMODE_AUTO: - ul_color_term_ok = atty == -1 ? isatty(STDOUT_FILENO) : atty; + cc->use_colors = atty == -1 ? isatty(STDOUT_FILENO) : atty; break; case UL_COLORMODE_ALWAYS: - ul_color_term_ok = 1; + cc->use_colors = 1; break; case UL_COLORMODE_NEVER: default: - ul_color_term_ok = 0; + cc->use_colors = 0; } - return ul_color_term_ok; + return cc->use_colors; } int colors_wanted(void) { - return ul_color_term_ok; + return ul_colors.use_colors; } void color_fenable(const char *color_scheme, FILE *f) { - if (ul_color_term_ok && color_scheme) + if (ul_colors.use_colors && color_scheme) fputs(color_scheme, f); } void color_fdisable(FILE *f) { - if (ul_color_term_ok) + if (ul_colors.use_colors) fputs(UL_COLOR_RESET, f); } @@ -150,31 +374,44 @@ const char *colorscheme_from_string(const char *str) int main(int argc, char *argv[]) { static const struct option longopts[] = { - { "colors", optional_argument, 0, 'c' }, - { "scheme", required_argument, 0, 's' }, + { "mode", optional_argument, 0, 'm' }, + { "color", required_argument, 0, 'c' }, + { "read-dir", required_argument, 0, 'd' }, { NULL, 0, 0, 0 } }; - int c, mode = UL_COLORMODE_NEVER; /* default */ - const char *scheme = "red"; + int c, mode = UL_COLORMODE_UNDEF; /* default */ + const char *color = "red", *dir = NULL; - while ((c = getopt_long(argc, argv, "c::s:", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "m::c:d:", longopts, NULL)) != -1) { switch (c) { - case 'c': - mode = UL_COLORMODE_AUTO; + case 'm': if (optarg) mode = colormode_or_err(optarg, "unsupported color mode"); break; - case 's': - scheme = optarg; + case 'c': + color = optarg; + break; + case 'd': + dir = optarg; break; } } + if (dir) { + struct ul_color_ctl cc = { .utilname = "foo", + .termname = "footerm" }; + + colors_readdir(&cc, dir); + colors_debug(&cc); + colors_reset(&cc); + } + colors_init(mode, program_invocation_short_name); - color_enable(colorscheme_from_string(scheme)); + color_enable(colorscheme_from_string(color)); printf("Hello World!"); color_disable(); + return EXIT_SUCCESS; } #endif |