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, maybe be override 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 */
427 assert ((out
- *seq
) <= len
);
435 * Adds one color sequence to array with color scheme.
436 * When returning success (0) this function takes ownership of
437 * @seq and @name, which have to be allocated strings.
439 static int colors_add_scheme(struct ul_color_ctl
*cc
,
443 struct ul_color_scheme
*cs
= NULL
;
447 if (!cc
|| !name
|| !*name
|| !seq0
|| !*seq0
)
450 DBG(SCHEME
, ul_debug("add '%s'", name
));
452 rc
= cn_sequence(seq0
, &seq
);
458 /* convert logical name (e.g. "red") to real ESC code */
460 const char *s
= color_sequence_from_colorname(seq
);
464 DBG(SCHEME
, ul_debug("unknown logical name: %s", seq
));
476 /* enlarge the array */
477 if (cc
->nschemes
== cc
->schemes_sz
) {
478 void *tmp
= realloc(cc
->schemes
, (cc
->nschemes
+ 10)
479 * sizeof(struct ul_color_scheme
));
483 cc
->schemes_sz
= cc
->nschemes
+ 10;
487 cs
= &cc
->schemes
[cc
->nschemes
];
489 cs
->name
= strdup(name
);
499 cs
->seq
= cs
->name
= NULL
;
506 * Deallocates all regards to color schemes
508 static void colors_free_schemes(struct ul_color_ctl
*cc
)
512 DBG(SCHEME
, ul_debug("free scheme"));
514 for (i
= 0; i
< cc
->nschemes
; i
++) {
515 free(cc
->schemes
[i
].name
);
516 free(cc
->schemes
[i
].seq
);
526 * The scheme configuration has to be sorted for bsearch
528 static void colors_sort_schemes(struct ul_color_ctl
*cc
)
533 DBG(SCHEME
, ul_debug("sort scheme"));
535 qsort(cc
->schemes
, cc
->nschemes
,
536 sizeof(struct ul_color_scheme
), cmp_scheme_name
);
540 * Returns just one color scheme
542 static struct ul_color_scheme
*colors_get_scheme(struct ul_color_ctl
*cc
,
545 struct ul_color_scheme key
= { .name
= (char *) name
}, *res
;
547 if (!cc
|| !name
|| !*name
)
550 if (!cc
->cs_configured
) {
551 int rc
= colors_read_schemes(cc
);
558 DBG(SCHEME
, ul_debug("search '%s'", name
));
560 res
= bsearch(&key
, cc
->schemes
, cc
->nschemes
,
561 sizeof(struct ul_color_scheme
),
564 return res
&& res
->seq
? res
: NULL
;
568 * Parses filenames in terminal-colors.d
570 static int colors_read_configuration(struct ul_color_ctl
*cc
)
573 char *dirname
, buf
[PATH_MAX
];
575 cc
->termname
= getenv("TERM");
577 dirname
= colors_get_homedir(buf
, sizeof(buf
));
579 rc
= colors_readdir(cc
, dirname
); /* ~/.config */
580 if (rc
== -EPERM
|| rc
== -EACCES
|| rc
== -ENOENT
)
581 rc
= colors_readdir(cc
, _PATH_TERMCOLORS_DIR
); /* /etc */
588 * Reads terminal-colors.d/ scheme file into array schemes
590 static int colors_read_schemes(struct ul_color_ctl
*cc
)
598 rc
= colors_read_configuration(cc
);
600 cc
->cs_configured
= 1;
602 if (rc
|| !cc
->sfile
)
605 DBG(SCHEME
, ul_debug("reading file '%s'", cc
->sfile
));
607 f
= fopen(cc
->sfile
, "r");
613 while (fgets(buf
, sizeof(buf
), f
)) {
614 char *p
= strchr(buf
, '\n');
618 p
= strchr(buf
, '\0');
625 p
= (char *) skip_blank(buf
);
626 if (*p
== '\0' || *p
== '#')
629 rc
= sscanf(p
, "%128[^ ] %128[^\n ]", cn
, seq
);
630 if (rc
== 2 && *cn
&& *seq
) {
631 rc
= colors_add_scheme(cc
, cn
, seq
); /* set rc=0 on success */
641 colors_sort_schemes(cc
);
647 static void termcolors_init_debug(void)
649 __UL_INIT_DEBUG_FROM_ENV(termcolors
, TERMCOLORS_DEBUG_
, 0, TERMINAL_COLORS_DEBUG
);
652 static int colors_terminal_is_ready(void)
656 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
660 if (setupterm(NULL
, STDOUT_FILENO
, &ret
) != 0 || ret
!= 1)
662 ncolors
= tigetnum("colors");
668 DBG(CONF
, ul_debug("terminal is ready (supports %d colors)", ncolors
));
672 DBG(CONF
, ul_debug("terminal is NOT ready"));
678 * @mode: UL_COLORMODE_*
679 * @name: util argv[0]
681 * Initialize private color control struct and initialize the colors
682 * status. The color schemes are parsed on demand by colors_get_scheme().
684 * Returns: >0 on success.
686 int colors_init(int mode
, const char *name
)
689 struct ul_color_ctl
*cc
= &ul_colors
;
693 termcolors_init_debug();
695 if (mode
!= UL_COLORMODE_ALWAYS
&& !isatty(STDOUT_FILENO
))
696 cc
->mode
= UL_COLORMODE_NEVER
;
700 if (cc
->mode
== UL_COLORMODE_UNDEF
701 && (ready
= colors_terminal_is_ready())) {
702 int rc
= colors_read_configuration(cc
);
704 cc
->mode
= UL_COLORMODE_DEFAULT
;
707 /* evaluate scores */
708 if (cc
->scores
[UL_COLORFILE_DISABLE
] >
709 cc
->scores
[UL_COLORFILE_ENABLE
])
710 cc
->mode
= UL_COLORMODE_NEVER
;
712 cc
->mode
= UL_COLORMODE_DEFAULT
;
714 atexit(colors_deinit
);
719 case UL_COLORMODE_AUTO
:
720 cc
->has_colors
= ready
== -1 ? colors_terminal_is_ready() : ready
;
722 case UL_COLORMODE_ALWAYS
:
725 case UL_COLORMODE_NEVER
:
730 ON_DBG(CONF
, colors_debug(cc
));
732 return cc
->has_colors
;
736 * Temporary disable colors (this setting is independent on terminal-colors.d/)
738 void colors_off(void)
740 ul_colors
.disabled
= 1;
748 ul_colors
.disabled
= 0;
752 * Is terminal-colors.d/ configured to use colors?
754 int colors_wanted(void)
756 return ul_colors
.has_colors
;
762 int colors_mode(void)
764 return ul_colors
.mode
;
770 void color_fenable(const char *seq
, FILE *f
)
772 if (!ul_colors
.disabled
&& ul_colors
.has_colors
&& seq
)
777 * Returns escape sequence by logical @name, if undefined then returns @dflt.
779 const char *color_scheme_get_sequence(const char *name
, const char *dflt
)
781 struct ul_color_scheme
*cs
;
783 if (ul_colors
.disabled
|| !ul_colors
.has_colors
)
786 cs
= colors_get_scheme(&ul_colors
, name
);
787 return cs
&& cs
->seq
? cs
->seq
: dflt
;
791 * Enable color by logical @name, if undefined enable @dflt.
793 void color_scheme_fenable(const char *name
, const char *dflt
, FILE *f
)
795 const char *seq
= color_scheme_get_sequence(name
, dflt
);
799 color_fenable(seq
, f
);
804 * Disable previously enabled color
806 void color_fdisable(FILE *f
)
808 if (!ul_colors
.disabled
&& ul_colors
.has_colors
)
809 fputs(UL_COLOR_RESET
, f
);
813 * Parses @str to return UL_COLORMODE_*
815 int colormode_from_string(const char *str
)
818 static const char *modes
[] = {
819 [UL_COLORMODE_AUTO
] = "auto",
820 [UL_COLORMODE_NEVER
] = "never",
821 [UL_COLORMODE_ALWAYS
] = "always",
822 [UL_COLORMODE_UNDEF
] = ""
828 assert(ARRAY_SIZE(modes
) == __UL_NCOLORMODES
);
830 for (i
= 0; i
< ARRAY_SIZE(modes
); i
++) {
831 if (strcasecmp(str
, modes
[i
]) == 0)
839 * Parses @str and exit(EXIT_FAILURE) on error
841 int colormode_or_err(const char *str
, const char *errmsg
)
843 const char *p
= str
&& *str
== '=' ? str
+ 1 : str
;
846 colormode
= colormode_from_string(p
);
848 errx(EXIT_FAILURE
, "%s: '%s'", errmsg
, p
);
853 #ifdef TEST_PROGRAM_COLORS
855 int main(int argc
, char *argv
[])
857 static const struct option longopts
[] = {
858 { "mode", required_argument
, NULL
, 'm' },
859 { "color", required_argument
, NULL
, 'c' },
860 { "color-scheme", required_argument
, NULL
, 'C' },
861 { "name", required_argument
, NULL
, 'n' },
864 int c
, mode
= UL_COLORMODE_UNDEF
; /* default */
865 const char *color
= "red", *name
= NULL
, *color_scheme
= NULL
;
866 const char *seq
= NULL
;
868 while ((c
= getopt_long(argc
, argv
, "C:c:m:n:", longopts
, NULL
)) != -1) {
874 color_scheme
= optarg
;
877 mode
= colormode_or_err(optarg
, "unsupported color mode");
883 fprintf(stderr
, "usage: %s [options]\n"
884 " -m, --mode <auto|never|always> default is undefined\n"
885 " -c, --color <red|blue|...> color for the test message\n"
886 " -C, --color-scheme <name> color for the test message\n"
887 " -n, --name <utilname> util name\n",
888 program_invocation_short_name
);
893 colors_init(mode
, name
? name
: program_invocation_short_name
);
895 seq
= color_sequence_from_colorname(color
);
898 color_scheme_enable(color_scheme
, seq
);
901 printf("Hello World!");
907 #endif /* TEST_PROGRAM_COLORS */