2 * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
3 * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
5 * This file may be distributed under the terms of the
6 * GNU Lesser General Public License.
10 #include <sys/types.h>
14 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
15 # if defined(HAVE_NCURSESW_NCURSES_H)
16 # include <ncursesw/ncurses.h>
17 # elif defined(HAVE_NCURSES_NCURSES_H)
18 # include <ncurses/ncurses.h>
19 # elif defined(HAVE_NCURSES_H)
22 # if defined(HAVE_NCURSESW_TERM_H)
23 # include <ncursesw/term.h>
24 # elif defined(HAVE_NCURSES_TERM_H)
25 # include <ncurses/term.h>
26 # elif defined(HAVE_TERM_H)
33 #include "pathnames.h"
39 * Default behavior, may be overriden by terminal-colors.d/{enable,disable}.
41 #ifdef USE_COLORS_BY_DEFAULT
42 # define UL_COLORMODE_DEFAULT UL_COLORMODE_AUTO /* check isatty() */
44 # define UL_COLORMODE_DEFAULT UL_COLORMODE_NEVER /* no colors by default */
48 * terminal-colors.d debug stuff
50 static UL_DEBUG_DEFINE_MASK(termcolors
);
51 UL_DEBUG_DEFINE_MASKNAMES(termcolors
) = UL_DEBUG_EMPTY_MASKNAMES
;
53 #define TERMCOLORS_DEBUG_INIT (1 << 1)
54 #define TERMCOLORS_DEBUG_CONF (1 << 2)
55 #define TERMCOLORS_DEBUG_SCHEME (1 << 3)
56 #define TERMCOLORS_DEBUG_ALL 0xFFFF
58 #define DBG(m, x) __UL_DBG(termcolors, TERMCOLORS_DEBUG_, m, x)
59 #define ON_DBG(m, x) __UL_DBG_CALL(termcolors, TERMCOLORS_DEBUG_, m, x)
62 * terminal-colors.d file types
65 UL_COLORFILE_DISABLE
, /* .disable */
66 UL_COLORFILE_ENABLE
, /* .enable */
67 UL_COLORFILE_SCHEME
, /* .scheme */
72 struct ul_color_scheme
{
78 * Global colors control struct
80 * The terminal-colors.d/ evaluation is based on "scores":
83 * ---------------------------------------
85 * @termname.type 10 + 1
86 * utilname.type 20 + 1
87 * utilname@termname.type 20 + 10 + 1
89 * the match with higher score wins. The score is per type.
92 const char *utilname
; /* util name */
93 const char *termname
; /* terminal name ($TERM) */
95 char *sfile
; /* path to scheme */
97 struct ul_color_scheme
*schemes
; /* array with color schemes */
98 size_t nschemes
; /* number of the items */
99 size_t schemes_sz
; /* number of the allocated items */
101 int mode
; /* UL_COLORMODE_* */
102 unsigned int has_colors
: 1, /* based on mode and scores[] */
103 disabled
: 1, /* disable colors */
104 cs_configured
: 1, /* color schemes read */
105 configured
: 1; /* terminal-colors.d parsed */
107 int scores
[__UL_COLORFILE_COUNT
]; /* the best match */
111 * Control struct, globally shared.
113 static struct ul_color_ctl ul_colors
;
115 static void colors_free_schemes(struct ul_color_ctl
*cc
);
116 static int colors_read_schemes(struct ul_color_ctl
*cc
);
119 * qsort/bsearch buddy
121 static int cmp_scheme_name(const void *a0
, const void *b0
)
123 const struct ul_color_scheme
*a
= (const struct ul_color_scheme
*) a0
,
124 *b
= (const struct ul_color_scheme
*) b0
;
125 return strcmp(a
->name
, b
->name
);
129 * Resets control struct (note that we don't allocate the struct)
131 static void colors_reset(struct ul_color_ctl
*cc
)
136 colors_free_schemes(cc
);
143 cc
->mode
= UL_COLORMODE_UNDEF
;
145 memset(cc
->scores
, 0, sizeof(cc
->scores
));
148 static void colors_debug(struct ul_color_ctl
*cc
)
156 printf("\tutilname = '%s'\n", cc
->utilname
);
157 printf("\ttermname = '%s'\n", cc
->termname
);
158 printf("\tscheme file = '%s'\n", cc
->sfile
);
159 printf("\tmode = %s\n",
160 cc
->mode
== UL_COLORMODE_UNDEF
? "undefined" :
161 cc
->mode
== UL_COLORMODE_AUTO
? "auto" :
162 cc
->mode
== UL_COLORMODE_NEVER
? "never" :
163 cc
->mode
== UL_COLORMODE_ALWAYS
? "always" : "???");
164 printf("\thas_colors = %d\n", cc
->has_colors
);
165 printf("\tdisabled = %d\n", cc
->disabled
);
166 printf("\tconfigured = %d\n", cc
->configured
);
167 printf("\tcs configured = %d\n", cc
->cs_configured
);
171 for (i
= 0; i
< ARRAY_SIZE(cc
->scores
); i
++)
172 printf("\tscore %s = %d\n",
173 i
== UL_COLORFILE_DISABLE
? "disable" :
174 i
== UL_COLORFILE_ENABLE
? "enable" :
175 i
== UL_COLORFILE_SCHEME
? "scheme" : "???",
180 for (i
= 0; i
< cc
->nschemes
; i
++) {
181 printf("\tscheme #%02zu ", i
);
182 color_scheme_enable(cc
->schemes
[i
].name
, NULL
);
183 fputs(cc
->schemes
[i
].name
, stdout
);
191 * Parses [[<utilname>][@<termname>].]<type>
193 static int filename_to_tokens(const char *str
,
194 const char **name
, size_t *namesz
,
195 const char **term
, size_t *termsz
,
198 const char *type_start
, *term_start
, *p
;
200 if (!str
|| !*str
|| *str
== '.' || strlen(str
) > PATH_MAX
)
204 p
= strrchr(str
, '.');
205 type_start
= p
? p
+ 1 : str
;
207 if (strcmp(type_start
, "disable") == 0)
208 *filetype
= UL_COLORFILE_DISABLE
;
209 else if (strcmp(type_start
, "enable") == 0)
210 *filetype
= UL_COLORFILE_ENABLE
;
211 else if (strcmp(type_start
, "scheme") == 0)
212 *filetype
= UL_COLORFILE_SCHEME
;
214 DBG(CONF
, ul_debug("unknown type '%s'", type_start
));
215 return 1; /* unknown type */
218 if (type_start
== str
)
219 return 0; /* "type" only */
221 /* parse @termname */
222 p
= strchr(str
, '@');
223 term_start
= p
? p
+ 1 : NULL
;
226 *termsz
= type_start
- term_start
- 1;
227 if (term_start
- 1 == str
)
228 return 0; /* "@termname.type" */
232 p
= term_start
? term_start
: type_start
;
234 *namesz
= p
- str
- 1;
240 * Scans @dirname and select the best matches for UL_COLORFILE_* types.
241 * The result is stored to cc->scores. The path to the best "scheme"
242 * file is stored to cc->scheme.
244 static int colors_readdir(struct ul_color_ctl
*cc
, const char *dirname
)
249 char sfile
[PATH_MAX
] = { '\0' };
250 size_t namesz
, termsz
;
252 if (!dirname
|| !cc
|| !cc
->utilname
|| !*cc
->utilname
)
255 DBG(CONF
, ul_debug("reading dir: '%s'", dirname
));
257 dir
= opendir(dirname
);
261 namesz
= strlen(cc
->utilname
);
262 termsz
= cc
->termname
? strlen(cc
->termname
) : 0;
264 while ((d
= readdir(dir
))) {
266 const char *tk_name
= NULL
, *tk_term
= NULL
;
267 size_t tk_namesz
= 0, tk_termsz
= 0;
269 if (*d
->d_name
== '.')
271 #ifdef _DIRENT_HAVE_D_TYPE
272 if (d
->d_type
!= DT_UNKNOWN
&& d
->d_type
!= DT_LNK
&&
276 if (filename_to_tokens(d
->d_name
,
277 &tk_name
, &tk_namesz
,
278 &tk_term
, &tk_termsz
, &type
) != 0)
281 /* count theoretical score before we check names to avoid
282 * unnecessary strcmp() */
288 DBG(CONF
, ul_debug("item '%s': score=%d "
289 "[cur: %d, name(%zu): %s, term(%zu): %s]",
290 d
->d_name
, score
, cc
->scores
[type
],
292 tk_termsz
, tk_term
));
295 if (score
< cc
->scores
[type
])
298 /* filter out by names */
299 if (tk_namesz
&& (tk_namesz
!= namesz
||
300 strncmp(tk_name
, cc
->utilname
, namesz
) != 0))
303 if (tk_termsz
&& (termsz
== 0 || tk_termsz
!= termsz
||
304 strncmp(tk_term
, cc
->termname
, termsz
) != 0))
307 DBG(CONF
, ul_debug("setting '%s' from %d -to-> %d",
308 type
== UL_COLORFILE_SCHEME
? "scheme" :
309 type
== UL_COLORFILE_DISABLE
? "disable" :
310 type
== UL_COLORFILE_ENABLE
? "enable" : "???",
311 cc
->scores
[type
], score
));
312 cc
->scores
[type
] = score
;
313 if (type
== UL_COLORFILE_SCHEME
)
314 strncpy(sfile
, d
->d_name
, sizeof(sfile
));
318 sfile
[sizeof(sfile
) - 1] = '\0';
319 if (asprintf(&cc
->sfile
, "%s/%s", dirname
, sfile
) <= 0)
327 /* atexit() wrapper */
328 static void colors_deinit(void)
330 colors_reset(&ul_colors
);
334 * Returns path to $XDG_CONFIG_HOME/terminal-colors.d
336 static char *colors_get_homedir(char *buf
, size_t bufsz
)
338 char *p
= getenv("XDG_CONFIG_HOME");
341 snprintf(buf
, bufsz
, "%s/" _PATH_TERMCOLORS_DIRNAME
, p
);
347 snprintf(buf
, bufsz
, "%s/.config/" _PATH_TERMCOLORS_DIRNAME
, p
);
354 /* canonicalize sequence */
355 static int cn_sequence(const char *str
, char **seq
)
365 /* convert logical names like "red" to the real sequence */
366 if (*str
!= '\\' && isalpha(*str
)) {
367 const char *s
= color_sequence_from_colorname(str
);
368 *seq
= strdup(s
? s
: str
);
370 return *seq
? 0 : -ENOMEM
;
373 /* convert xx;yy sequences to "\033[xx;yy" */
374 if ((len
= asprintf(seq
, "\033[%sm", str
)) < 1)
377 for (in
= *seq
, out
= *seq
; in
&& *in
; in
++) {
384 *out
++ = '\a'; /* Bell */
387 *out
++ = '\b'; /* Backspace */
390 *out
++ = '\033'; /* Escape */
393 *out
++ = '\f'; /* Form Feed */
396 *out
++ = '\n'; /* Newline */
399 *out
++ = '\r'; /* Carriage Return */
402 *out
++ = '\t'; /* Tab */
405 *out
++ = '\v'; /* Vertical Tab */
408 *out
++ = '\\'; /* Backslash */
411 *out
++ = ' '; /* Space */
414 *out
++ = '#'; /* Hash mark */
417 *out
++ = '?'; /* Question mark */
428 assert ((out
- *seq
) <= len
);
437 * Adds one color sequence to array with color scheme.
438 * When returning success (0) this function takes ownership of
439 * @seq and @name, which have to be allocated strings.
441 static int colors_add_scheme(struct ul_color_ctl
*cc
,
445 struct ul_color_scheme
*cs
= NULL
;
449 if (!cc
|| !name
|| !*name
|| !seq0
|| !*seq0
)
452 DBG(SCHEME
, ul_debug("add '%s'", name
));
454 rc
= cn_sequence(seq0
, &seq
);
460 /* convert logical name (e.g. "red") to real ESC code */
462 const char *s
= color_sequence_from_colorname(seq
);
466 DBG(SCHEME
, ul_debug("unknown logical name: %s", seq
));
478 /* enlarge the array */
479 if (cc
->nschemes
== cc
->schemes_sz
) {
480 void *tmp
= realloc(cc
->schemes
, (cc
->nschemes
+ 10)
481 * sizeof(struct ul_color_scheme
));
485 cc
->schemes_sz
= cc
->nschemes
+ 10;
489 cs
= &cc
->schemes
[cc
->nschemes
];
491 cs
->name
= strdup(name
);
501 cs
->seq
= cs
->name
= NULL
;
508 * Deallocates all regards to color schemes
510 static void colors_free_schemes(struct ul_color_ctl
*cc
)
514 DBG(SCHEME
, ul_debug("free scheme"));
516 for (i
= 0; i
< cc
->nschemes
; i
++) {
517 free(cc
->schemes
[i
].name
);
518 free(cc
->schemes
[i
].seq
);
528 * The scheme configuration has to be sorted for bsearch
530 static void colors_sort_schemes(struct ul_color_ctl
*cc
)
535 DBG(SCHEME
, ul_debug("sort scheme"));
537 qsort(cc
->schemes
, cc
->nschemes
,
538 sizeof(struct ul_color_scheme
), cmp_scheme_name
);
542 * Returns just one color scheme
544 static struct ul_color_scheme
*colors_get_scheme(struct ul_color_ctl
*cc
,
547 struct ul_color_scheme key
= { .name
= (char *) name
}, *res
;
549 if (!cc
|| !name
|| !*name
)
552 if (!cc
->cs_configured
) {
553 int rc
= colors_read_schemes(cc
);
560 DBG(SCHEME
, ul_debug("search '%s'", name
));
562 res
= bsearch(&key
, cc
->schemes
, cc
->nschemes
,
563 sizeof(struct ul_color_scheme
),
566 return res
&& res
->seq
? res
: NULL
;
570 * Parses filenames in terminal-colors.d
572 static int colors_read_configuration(struct ul_color_ctl
*cc
)
575 char *dirname
, buf
[PATH_MAX
];
577 cc
->termname
= getenv("TERM");
579 dirname
= colors_get_homedir(buf
, sizeof(buf
));
581 rc
= colors_readdir(cc
, dirname
); /* ~/.config */
582 if (rc
== -EPERM
|| rc
== -EACCES
|| rc
== -ENOENT
)
583 rc
= colors_readdir(cc
, _PATH_TERMCOLORS_DIR
); /* /etc */
590 * Reads terminal-colors.d/ scheme file into array schemes
592 static int colors_read_schemes(struct ul_color_ctl
*cc
)
600 rc
= colors_read_configuration(cc
);
602 cc
->cs_configured
= 1;
604 if (rc
|| !cc
->sfile
)
607 DBG(SCHEME
, ul_debug("reading file '%s'", cc
->sfile
));
609 f
= fopen(cc
->sfile
, "r");
615 while (fgets(buf
, sizeof(buf
), f
)) {
616 char *p
= strchr(buf
, '\n');
620 p
= strchr(buf
, '\0');
627 p
= (char *) skip_blank(buf
);
628 if (*p
== '\0' || *p
== '#')
631 rc
= sscanf(p
, "%128[^ ] %128[^\n ]", cn
, seq
);
632 if (rc
== 2 && *cn
&& *seq
) {
633 rc
= colors_add_scheme(cc
, cn
, seq
); /* set rc=0 on success */
643 colors_sort_schemes(cc
);
649 static void termcolors_init_debug(void)
651 __UL_INIT_DEBUG_FROM_ENV(termcolors
, TERMCOLORS_DEBUG_
, 0, TERMINAL_COLORS_DEBUG
);
654 static int colors_terminal_is_ready(void)
658 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
662 if (setupterm(NULL
, STDOUT_FILENO
, &ret
) == 0 && ret
== 1)
663 ncolors
= tigetnum("colors");
667 DBG(CONF
, ul_debug("terminal is ready (supports %d colors)", ncolors
));
671 DBG(CONF
, ul_debug("terminal is NOT ready (no colors)"));
677 * @mode: UL_COLORMODE_*
678 * @name: util argv[0]
680 * Initialize private color control struct and initialize the colors
681 * status. The color schemes are parsed on demand by colors_get_scheme().
683 * Returns: >0 on success.
685 int colors_init(int mode
, const char *name
)
688 struct ul_color_ctl
*cc
= &ul_colors
;
692 termcolors_init_debug();
694 if (mode
!= UL_COLORMODE_ALWAYS
&& !isatty(STDOUT_FILENO
))
695 cc
->mode
= UL_COLORMODE_NEVER
;
699 if (cc
->mode
== UL_COLORMODE_UNDEF
700 && (ready
= colors_terminal_is_ready())) {
701 int rc
= colors_read_configuration(cc
);
703 cc
->mode
= UL_COLORMODE_DEFAULT
;
706 /* evaluate scores */
707 if (cc
->scores
[UL_COLORFILE_DISABLE
] >
708 cc
->scores
[UL_COLORFILE_ENABLE
])
709 cc
->mode
= UL_COLORMODE_NEVER
;
711 cc
->mode
= UL_COLORMODE_DEFAULT
;
713 atexit(colors_deinit
);
718 case UL_COLORMODE_AUTO
:
719 cc
->has_colors
= ready
== -1 ? colors_terminal_is_ready() : ready
;
721 case UL_COLORMODE_ALWAYS
:
724 case UL_COLORMODE_NEVER
:
729 ON_DBG(CONF
, colors_debug(cc
));
731 return cc
->has_colors
;
735 * Temporary disable colors (this setting is independent on terminal-colors.d/)
737 void colors_off(void)
739 ul_colors
.disabled
= 1;
747 ul_colors
.disabled
= 0;
751 * Is terminal-colors.d/ configured to use colors?
753 int colors_wanted(void)
755 return ul_colors
.has_colors
;
761 int colors_mode(void)
763 return ul_colors
.mode
;
769 void color_fenable(const char *seq
, FILE *f
)
771 if (!ul_colors
.disabled
&& ul_colors
.has_colors
&& seq
)
776 * Returns escape sequence by logical @name, if undefined then returns @dflt.
778 const char *color_scheme_get_sequence(const char *name
, const char *dflt
)
780 struct ul_color_scheme
*cs
;
782 if (ul_colors
.disabled
|| !ul_colors
.has_colors
)
785 cs
= colors_get_scheme(&ul_colors
, name
);
786 return cs
&& cs
->seq
? cs
->seq
: dflt
;
790 * Enable color by logical @name, if undefined enable @dflt.
792 void color_scheme_fenable(const char *name
, const char *dflt
, FILE *f
)
794 const char *seq
= color_scheme_get_sequence(name
, dflt
);
798 color_fenable(seq
, f
);
803 * Disable previously enabled color
805 void color_fdisable(FILE *f
)
807 if (!ul_colors
.disabled
&& ul_colors
.has_colors
)
808 fputs(UL_COLOR_RESET
, f
);
812 * Parses @str to return UL_COLORMODE_*
814 int colormode_from_string(const char *str
)
817 static const char *modes
[] = {
818 [UL_COLORMODE_AUTO
] = "auto",
819 [UL_COLORMODE_NEVER
] = "never",
820 [UL_COLORMODE_ALWAYS
] = "always",
821 [UL_COLORMODE_UNDEF
] = ""
827 assert(ARRAY_SIZE(modes
) == __UL_NCOLORMODES
);
829 for (i
= 0; i
< ARRAY_SIZE(modes
); i
++) {
830 if (strcasecmp(str
, modes
[i
]) == 0)
838 * Parses @str and exit(EXIT_FAILURE) on error
840 int colormode_or_err(const char *str
, const char *errmsg
)
842 const char *p
= str
&& *str
== '=' ? str
+ 1 : str
;
845 colormode
= colormode_from_string(p
);
847 errx(EXIT_FAILURE
, "%s: '%s'", errmsg
, p
);
852 #ifdef TEST_PROGRAM_COLORS
854 int main(int argc
, char *argv
[])
856 static const struct option longopts
[] = {
857 { "mode", required_argument
, NULL
, 'm' },
858 { "color", required_argument
, NULL
, 'c' },
859 { "color-scheme", required_argument
, NULL
, 'C' },
860 { "name", required_argument
, NULL
, 'n' },
863 int c
, mode
= UL_COLORMODE_UNDEF
; /* default */
864 const char *color
= "red", *name
= NULL
, *color_scheme
= NULL
;
865 const char *seq
= NULL
;
867 while ((c
= getopt_long(argc
, argv
, "C:c:m:n:", longopts
, NULL
)) != -1) {
873 color_scheme
= optarg
;
876 mode
= colormode_or_err(optarg
, "unsupported color mode");
882 fprintf(stderr
, "usage: %s [options]\n"
883 " -m, --mode <auto|never|always> default is undefined\n"
884 " -c, --color <red|blue|...> color for the test message\n"
885 " -C, --color-scheme <name> color for the test message\n"
886 " -n, --name <utilname> util name\n",
887 program_invocation_short_name
);
892 colors_init(mode
, name
? name
: program_invocation_short_name
);
894 seq
= color_sequence_from_colorname(color
);
897 color_scheme_enable(color_scheme
, seq
);
900 printf("Hello World!");
906 #endif /* TEST_PROGRAM_COLORS */