summaryrefslogblamecommitdiffstats
path: root/lib/colors.c
blob: 91677ba9be4d682d984d46a2c52bde4c89c3904c (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                        
                                                 



                                                      
                   
                     

                      
 
              
                   

                      
 


                               







                                                      













                                                           






                                                                   




                                                                            



                                     


                                                                










































                                                                            


                                            











































                                                                              
  


                                                                       







































































                                                                         
                      




                                 


                                                     
















                                                                                
 




















                                                                               
                                           
 
                      



                                             

                                                                           
                                                       






                                                              
                            


                                                             
                 

         
                           
                               
                                                                           

                                 
                                   


                                
                                   
         










                               

 

                       
                                                           


                                                     
 
                                                                        
                                       

 
                            
 
                                                        
                                         
 






                                                

                                                 














                                                      











                                                           







































                                                                     
 

                    


                                                 


                                                          

                                 

                                                             
 
                                                                                
                            
                         

                                                                                          
                              




                                       
                              


                 








                                                                   
                                                         
 
                                                     

                               
 



                            
/*
 * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
 * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
 *
 * This file may be distributed under the terms of the
 * GNU Lesser General Public License.
 */
#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"

/*
 * terminal-colors.d file types
 */
enum {
	UL_COLORFILE_DISABLE,		/* .disable */
	UL_COLORFILE_ENABLE,		/* .enable */
	UL_COLORFILE_SCHEME,		/* .scheme */

	__UL_COLORFILE_COUNT
};

/*
 * Global colors control struct
 *
 * The terminal-colors.d/ evaluation is based on "scores":
 *
 *  filename                    score
 *  ---------------------------------------
 *  type			1
 *  @termname.type		10 + 1
 *  utilname.type		20 + 1
 *  utilname@termname.type	20 + 10 + 1
 *
 * the match with higher score wins. The score is per type.
 */
struct ul_color_ctl {
	const char	*utilname;	/* util name */
	const char	*termname;	/* terminal name ($TERM) */

	char		*scheme;	/* path to scheme */

	int		mode;		/* UL_COLORMODE_* */
	unsigned int	has_colors : 1,	/* based on mode and scores[] */
			disabled   : 1, /* disable colors */
			configured : 1; /* terminal-colors.d parsed */

	int		scores[__UL_COLORFILE_COUNT];	/* the best match */
};

static struct ul_color_ctl ul_colors;

/*
 * Reset control struct (note that we don't allocate the struct)
 */
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

/*
 * Parses [[<utilname>][@<termname>].]<type>
 */
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;
}

/*
 * Scans @dirname and select the best matches for UL_COLORFILE_* types.
 * The result is stored to cc->scores. The path to the best "scheme"
 * file is stored to cc->scheme.
 */
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;
}

/* atexit() wrapper */
static void colors_deinit(void)
{
	colors_reset(&ul_colors);
}

/*
 * Returns path to $XDG_CONFIG_HOME/terminal-colors.d
 */
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;
}

static int colors_read_configuration(struct ul_color_ctl *cc)
{
	int rc = -ENOENT;
	char *dirname, buf[PATH_MAX];

	cc->termname = getenv("TERM");

	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 */

	cc->configured = 1;
	return rc;
}

/*
 * Initialize private color control struct, @mode is UL_COLORMODE_*
 * and @name is util argv[0]
 */
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))) {
		int rc = colors_read_configuration(cc);
		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
				cc->mode = UL_COLORMODE_AUTO;

			atexit(colors_deinit);
		}
	}

	switch (cc->mode) {
	case UL_COLORMODE_AUTO:
		cc->has_colors = atty == -1 ? isatty(STDOUT_FILENO) : atty;
		break;
	case UL_COLORMODE_ALWAYS:
		cc->has_colors = 1;
		break;
	case UL_COLORMODE_NEVER:
	default:
		cc->has_colors = 0;
	}
	return cc->has_colors;
}

void colors_off(void)
{
	ul_colors.disabled = 1;
}

void colors_on(void)
{
	ul_colors.disabled = 0;
}

int colors_wanted(void)
{
	return !ul_colors.disabled && ul_colors.has_colors;
}

void color_fenable(const char *color_scheme, FILE *f)
{
	if (!ul_colors.disabled && ul_colors.has_colors && color_scheme)
		fputs(color_scheme, f);
}

void color_fdisable(FILE *f)
{
	if (!ul_colors.disabled && ul_colors.has_colors)
		fputs(UL_COLOR_RESET, f);
}

int colormode_from_string(const char *str)
{
	size_t i;
	static const char *modes[] = {
		[UL_COLORMODE_AUTO]   = "auto",
		[UL_COLORMODE_NEVER]  = "never",
		[UL_COLORMODE_ALWAYS] = "always",
		[UL_COLORMODE_UNDEF] = ""
	};

	if (!str || !*str)
		return -EINVAL;

	assert(ARRAY_SIZE(modes) == __UL_NCOLORMODES);

	for (i = 0; i < ARRAY_SIZE(modes); i++) {
		if (strcasecmp(str, modes[i]) == 0)
			return i;
	}

	return -EINVAL;
}

int colormode_or_err(const char *str, const char *errmsg)
{
	const char *p = str && *str == '=' ? str + 1 : str;
	int colormode;

	colormode = colormode_from_string(p);
	if (colormode < 0)
		errx(EXIT_FAILURE, "%s: '%s'", errmsg, p);

	return colormode;
}

struct colorscheme {
	const char *name, *scheme;
};

static int cmp_colorscheme_name(const void *a0, const void *b0)
{
	struct colorscheme *a = (struct colorscheme *) a0,
			   *b = (struct colorscheme *) b0;
	return strcmp(a->name, b->name);
}

const char *colorscheme_from_string(const char *str)
{
	static const struct colorscheme basic_schemes[] = {
		{ "black",	UL_COLOR_BLACK           },
		{ "blue",	UL_COLOR_BLUE            },
		{ "brown",	UL_COLOR_BROWN           },
		{ "cyan",	UL_COLOR_CYAN            },
		{ "darkgray",	UL_COLOR_DARK_GRAY       },
		{ "gray",	UL_COLOR_GRAY            },
		{ "green",	UL_COLOR_GREEN           },
		{ "lightblue",	UL_COLOR_BOLD_BLUE       },
		{ "lightcyan",	UL_COLOR_BOLD_CYAN       },
		{ "lightgray,",	UL_COLOR_GRAY            },
		{ "lightgreen", UL_COLOR_BOLD_GREEN      },
		{ "lightmagenta", UL_COLOR_BOLD_MAGENTA  },
		{ "lightred",	UL_COLOR_BOLD_RED        },
		{ "magenta",	UL_COLOR_MAGENTA         },
		{ "red",	UL_COLOR_RED             },
		{ "yellow",	UL_COLOR_BOLD_YELLOW     },
	};
	struct colorscheme key = { .name = str }, *res;
	if (!str)
		return NULL;

	res = bsearch(&key, basic_schemes, ARRAY_SIZE(basic_schemes),
				sizeof(struct colorscheme),
				cmp_colorscheme_name);
	return res ? res->scheme : NULL;
}

#ifdef TEST_PROGRAM
# include <getopt.h>
int main(int argc, char *argv[])
{
	static const struct option longopts[] = {
		{ "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_UNDEF;	/* default */
	const char *color = "red", *dir = NULL;

	while ((c = getopt_long(argc, argv, "m::c:d:", longopts, NULL)) != -1) {
		switch (c) {
		case 'm':
			if (optarg)
				mode = colormode_or_err(optarg, "unsupported color mode");
			break;
		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(color));
	printf("Hello World!");
	color_disable();

	return EXIT_SUCCESS;
}
#endif