2 * SPDX-License-Identifier: LGPL-2.1-or-later
4 * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
5 * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
7 * This file may be distributed under the terms of the
8 * GNU Lesser General Public License.
12 #include <sys/types.h>
16 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
17 # if defined(HAVE_NCURSESW_NCURSES_H)
18 # include <ncursesw/ncurses.h>
19 # elif defined(HAVE_NCURSES_NCURSES_H)
20 # include <ncurses/ncurses.h>
21 # elif defined(HAVE_NCURSES_H)
24 # if defined(HAVE_NCURSESW_TERM_H)
25 # include <ncursesw/term.h>
26 # elif defined(HAVE_NCURSES_TERM_H)
27 # include <ncurses/term.h>
28 # elif defined(HAVE_TERM_H)
35 #include "pathnames.h"
41 * Default behavior, may be overridden by terminal-colors.d/{enable,disable}.
43 #ifdef USE_COLORS_BY_DEFAULT
44 # define UL_COLORMODE_DEFAULT UL_COLORMODE_AUTO /* check isatty() */
46 # define UL_COLORMODE_DEFAULT UL_COLORMODE_NEVER /* no colors by default */
50 * terminal-colors.d debug stuff
52 static UL_DEBUG_DEFINE_MASK(termcolors
);
53 UL_DEBUG_DEFINE_MASKNAMES(termcolors
) = UL_DEBUG_EMPTY_MASKNAMES
;
55 #define TERMCOLORS_DEBUG_INIT (1 << 1)
56 #define TERMCOLORS_DEBUG_CONF (1 << 2)
57 #define TERMCOLORS_DEBUG_SCHEME (1 << 3)
58 #define TERMCOLORS_DEBUG_ALL 0xFFFF
60 #define DBG(m, x) __UL_DBG(termcolors, TERMCOLORS_DEBUG_, m, x)
61 #define ON_DBG(m, x) __UL_DBG_CALL(termcolors, TERMCOLORS_DEBUG_, m, x)
64 * terminal-colors.d file types
67 UL_COLORFILE_DISABLE
, /* .disable */
68 UL_COLORFILE_ENABLE
, /* .enable */
69 UL_COLORFILE_SCHEME
, /* .scheme */
74 struct ul_color_scheme
{
80 * Global colors control struct
82 * The terminal-colors.d/ evaluation is based on "scores":
85 * ---------------------------------------
87 * @termname.type 10 + 1
88 * utilname.type 20 + 1
89 * utilname@termname.type 20 + 10 + 1
91 * the match with higher score wins. The score is per type.
94 const char *utilname
; /* util name */
95 const char *termname
; /* terminal name ($TERM) */
97 char *sfile
; /* path to scheme */
99 struct ul_color_scheme
*schemes
; /* array with color schemes */
100 size_t nschemes
; /* number of the items */
101 size_t schemes_sz
; /* number of the allocated items */
103 int mode
; /* UL_COLORMODE_* */
104 unsigned int has_colors
: 1, /* based on mode and scores[] */
105 disabled
: 1, /* disable colors */
106 cs_configured
: 1, /* color schemes read */
107 configured
: 1; /* terminal-colors.d parsed */
109 int scores
[__UL_COLORFILE_COUNT
]; /* the best match */
113 * Control struct, globally shared.
115 static struct ul_color_ctl ul_colors
;
117 static void colors_free_schemes(struct ul_color_ctl
*cc
);
118 static int colors_read_schemes(struct ul_color_ctl
*cc
);
121 * qsort/bsearch buddy
123 static int cmp_scheme_name(const void *a0
, const void *b0
)
125 const struct ul_color_scheme
*a
= (const struct ul_color_scheme
*) a0
,
126 *b
= (const struct ul_color_scheme
*) b0
;
127 return strcmp(a
->name
, b
->name
);
131 * Resets control struct (note that we don't allocate the struct)
133 static void colors_reset(struct ul_color_ctl
*cc
)
138 colors_free_schemes(cc
);
145 cc
->mode
= UL_COLORMODE_UNDEF
;
147 memset(cc
->scores
, 0, sizeof(cc
->scores
));
150 static void colors_debug(struct ul_color_ctl
*cc
)
158 printf("\tutilname = '%s'\n", cc
->utilname
);
159 printf("\ttermname = '%s'\n", cc
->termname
);
160 printf("\tscheme file = '%s'\n", cc
->sfile
);
161 printf("\tmode = %s\n",
162 cc
->mode
== UL_COLORMODE_UNDEF
? "undefined" :
163 cc
->mode
== UL_COLORMODE_AUTO
? "auto" :
164 cc
->mode
== UL_COLORMODE_NEVER
? "never" :
165 cc
->mode
== UL_COLORMODE_ALWAYS
? "always" : "???");
166 printf("\thas_colors = %d\n", cc
->has_colors
);
167 printf("\tdisabled = %d\n", cc
->disabled
);
168 printf("\tconfigured = %d\n", cc
->configured
);
169 printf("\tcs configured = %d\n", cc
->cs_configured
);
173 for (i
= 0; i
< ARRAY_SIZE(cc
->scores
); i
++)
174 printf("\tscore %s = %d\n",
175 i
== UL_COLORFILE_DISABLE
? "disable" :
176 i
== UL_COLORFILE_ENABLE
? "enable" :
177 i
== UL_COLORFILE_SCHEME
? "scheme" : "???",
182 for (i
= 0; i
< cc
->nschemes
; i
++) {
183 printf("\tscheme #%02zu ", i
);
184 color_scheme_enable(cc
->schemes
[i
].name
, NULL
);
185 fputs(cc
->schemes
[i
].name
, stdout
);
193 * Parses [[<utilname>][@<termname>].]<type>
195 static int filename_to_tokens(const char *str
,
196 const char **name
, size_t *namesz
,
197 const char **term
, size_t *termsz
,
200 const char *type_start
, *term_start
, *p
;
202 if (!str
|| !*str
|| *str
== '.' || strlen(str
) > PATH_MAX
)
206 p
= strrchr(str
, '.');
207 type_start
= p
? p
+ 1 : str
;
209 if (strcmp(type_start
, "disable") == 0)
210 *filetype
= UL_COLORFILE_DISABLE
;
211 else if (strcmp(type_start
, "enable") == 0)
212 *filetype
= UL_COLORFILE_ENABLE
;
213 else if (strcmp(type_start
, "scheme") == 0)
214 *filetype
= UL_COLORFILE_SCHEME
;
216 DBG(CONF
, ul_debug("unknown type '%s'", type_start
));
217 return 1; /* unknown type */
220 if (type_start
== str
)
221 return 0; /* "type" only */
223 /* parse @termname */
224 p
= strchr(str
, '@');
225 term_start
= p
? p
+ 1 : NULL
;
228 *termsz
= type_start
- term_start
- 1;
229 if (term_start
- 1 == str
)
230 return 0; /* "@termname.type" */
234 p
= term_start
? term_start
: type_start
;
236 *namesz
= p
- str
- 1;
242 * Scans @dirname and select the best matches for UL_COLORFILE_* types.
243 * The result is stored to cc->scores. The path to the best "scheme"
244 * file is stored to cc->scheme.
246 static int colors_readdir(struct ul_color_ctl
*cc
, const char *dirname
)
251 char sfile
[PATH_MAX
] = { '\0' };
252 size_t namesz
, termsz
;
254 if (!dirname
|| !cc
|| !cc
->utilname
|| !*cc
->utilname
)
257 DBG(CONF
, ul_debug("reading dir: '%s'", dirname
));
259 dir
= opendir(dirname
);
263 namesz
= strlen(cc
->utilname
);
264 termsz
= cc
->termname
? strlen(cc
->termname
) : 0;
266 while ((d
= readdir(dir
))) {
268 const char *tk_name
= NULL
, *tk_term
= NULL
;
269 size_t tk_namesz
= 0, tk_termsz
= 0;
271 if (*d
->d_name
== '.')
273 #ifdef _DIRENT_HAVE_D_TYPE
274 if (d
->d_type
!= DT_UNKNOWN
&& d
->d_type
!= DT_LNK
&&
278 if (filename_to_tokens(d
->d_name
,
279 &tk_name
, &tk_namesz
,
280 &tk_term
, &tk_termsz
, &type
) != 0)
283 /* count theoretical score before we check names to avoid
284 * unnecessary strcmp() */
290 DBG(CONF
, ul_debug("item '%s': score=%d "
291 "[cur: %d, name(%zu): %s, term(%zu): %s]",
292 d
->d_name
, score
, cc
->scores
[type
],
294 tk_termsz
, tk_term
));
297 if (score
< cc
->scores
[type
])
300 /* filter out by names */
301 if (tk_namesz
&& (tk_namesz
!= namesz
||
302 strncmp(tk_name
, cc
->utilname
, namesz
) != 0))
305 if (tk_termsz
&& (termsz
== 0 || tk_termsz
!= termsz
||
306 strncmp(tk_term
, cc
->termname
, termsz
) != 0))
309 DBG(CONF
, ul_debug("setting '%s' from %d -to-> %d",
310 type
== UL_COLORFILE_SCHEME
? "scheme" :
311 type
== UL_COLORFILE_DISABLE
? "disable" :
312 type
== UL_COLORFILE_ENABLE
? "enable" : "???",
313 cc
->scores
[type
], score
));
314 cc
->scores
[type
] = score
;
315 if (type
== UL_COLORFILE_SCHEME
)
316 strncpy(sfile
, d
->d_name
, sizeof(sfile
));
320 sfile
[sizeof(sfile
) - 1] = '\0';
321 if (asprintf(&cc
->sfile
, "%s/%s", dirname
, sfile
) <= 0)
331 /* atexit() wrapper */
332 static void colors_deinit(void)
334 colors_reset(&ul_colors
);
338 * Returns path to $XDG_CONFIG_HOME/terminal-colors.d
340 static char *colors_get_homedir(char *buf
, size_t bufsz
)
342 char *p
= getenv("XDG_CONFIG_HOME");
345 snprintf(buf
, bufsz
, "%s/" _PATH_TERMCOLORS_DIRNAME
, p
);
351 snprintf(buf
, bufsz
, "%s/.config/" _PATH_TERMCOLORS_DIRNAME
, p
);
359 * Adds one color sequence to array with color scheme.
360 * When returning success (0) this function takes ownership of
361 * @seq and @name, which have to be allocated strings.
363 static int colors_add_scheme(struct ul_color_ctl
*cc
,
367 struct ul_color_scheme
*cs
= NULL
;
371 if (!cc
|| !name
|| !*name
|| !seq0
|| !*seq0
)
374 DBG(SCHEME
, ul_debug("add '%s'", name
));
376 seq
= color_get_sequence(seq0
);
381 /* enlarge the array */
382 if (cc
->nschemes
== cc
->schemes_sz
) {
383 void *tmp
= realloc(cc
->schemes
, (cc
->nschemes
+ 10)
384 * sizeof(struct ul_color_scheme
));
388 cc
->schemes_sz
= cc
->nschemes
+ 10;
392 cs
= &cc
->schemes
[cc
->nschemes
];
394 cs
->name
= strdup(name
);
404 cs
->seq
= cs
->name
= NULL
;
411 * Deallocates all regards to color schemes
413 static void colors_free_schemes(struct ul_color_ctl
*cc
)
417 DBG(SCHEME
, ul_debug("free scheme"));
419 for (i
= 0; i
< cc
->nschemes
; i
++) {
420 free(cc
->schemes
[i
].name
);
421 free(cc
->schemes
[i
].seq
);
431 * The scheme configuration has to be sorted for bsearch
433 static void colors_sort_schemes(struct ul_color_ctl
*cc
)
438 DBG(SCHEME
, ul_debug("sort scheme"));
440 qsort(cc
->schemes
, cc
->nschemes
,
441 sizeof(struct ul_color_scheme
), cmp_scheme_name
);
445 * Returns just one color scheme
447 static struct ul_color_scheme
*colors_get_scheme(struct ul_color_ctl
*cc
,
450 struct ul_color_scheme key
= { .name
= (char *) name
}, *res
;
452 if (!cc
|| !name
|| !*name
)
455 if (!cc
->cs_configured
) {
456 int rc
= colors_read_schemes(cc
);
463 DBG(SCHEME
, ul_debug("search '%s'", name
));
465 res
= bsearch(&key
, cc
->schemes
, cc
->nschemes
,
466 sizeof(struct ul_color_scheme
),
469 return res
&& res
->seq
? res
: NULL
;
473 * Parses filenames in terminal-colors.d
475 static int colors_read_configuration(struct ul_color_ctl
*cc
)
478 char *dirname
, buf
[PATH_MAX
];
480 cc
->termname
= getenv("TERM");
482 dirname
= colors_get_homedir(buf
, sizeof(buf
));
484 rc
= colors_readdir(cc
, dirname
); /* ~/.config */
485 if (rc
== -EPERM
|| rc
== -EACCES
|| rc
== -ENOENT
)
486 rc
= colors_readdir(cc
, _PATH_TERMCOLORS_DIR
); /* /etc */
493 * Reads terminal-colors.d/ scheme file into array schemes
495 static int colors_read_schemes(struct ul_color_ctl
*cc
)
503 rc
= colors_read_configuration(cc
);
505 cc
->cs_configured
= 1;
507 if (rc
|| !cc
->sfile
)
510 DBG(SCHEME
, ul_debug("reading file '%s'", cc
->sfile
));
512 f
= fopen(cc
->sfile
, "r");
518 while (fgets(buf
, sizeof(buf
), f
)) {
519 char *p
= strchr(buf
, '\n');
523 p
= strchr(buf
, '\0');
530 p
= (char *) skip_blank(buf
);
531 if (*p
== '\0' || *p
== '#')
534 rc
= sscanf(p
, "%128[^ ] %128[^\n ]", cn
, seq
);
535 if (rc
== 2 && *cn
&& *seq
) {
536 rc
= colors_add_scheme(cc
, cn
, seq
); /* set rc=0 on success */
546 colors_sort_schemes(cc
);
552 static void termcolors_init_debug(void)
554 __UL_INIT_DEBUG_FROM_ENV(termcolors
, TERMCOLORS_DEBUG_
, 0, TERMINAL_COLORS_DEBUG
);
557 static int colors_terminal_is_ready(void)
561 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
565 if (setupterm(NULL
, STDOUT_FILENO
, &ret
) == 0 && ret
== 1)
566 ncolors
= tigetnum("colors");
570 DBG(CONF
, ul_debug("terminal is ready (supports %d colors)", ncolors
));
574 DBG(CONF
, ul_debug("terminal is NOT ready (no colors)"));
580 * @mode: UL_COLORMODE_*
581 * @name: util argv[0]
583 * Initialize private color control struct and initialize the colors
584 * status. The color schemes are parsed on demand by colors_get_scheme().
586 * Returns: >0 on success.
588 int colors_init(int mode
, const char *name
)
591 struct ul_color_ctl
*cc
= &ul_colors
;
595 termcolors_init_debug();
597 if (mode
!= UL_COLORMODE_ALWAYS
&& !isatty(STDOUT_FILENO
))
598 cc
->mode
= UL_COLORMODE_NEVER
;
602 if (cc
->mode
== UL_COLORMODE_UNDEF
603 && (ready
= colors_terminal_is_ready())) {
604 int rc
= colors_read_configuration(cc
);
606 cc
->mode
= UL_COLORMODE_DEFAULT
;
609 /* evaluate scores */
610 if (cc
->scores
[UL_COLORFILE_DISABLE
] >
611 cc
->scores
[UL_COLORFILE_ENABLE
])
612 cc
->mode
= UL_COLORMODE_NEVER
;
614 cc
->mode
= UL_COLORMODE_DEFAULT
;
616 atexit(colors_deinit
);
621 case UL_COLORMODE_AUTO
:
622 cc
->has_colors
= ready
== -1 ? colors_terminal_is_ready() : ready
;
624 case UL_COLORMODE_ALWAYS
:
627 case UL_COLORMODE_NEVER
:
632 ON_DBG(CONF
, colors_debug(cc
));
634 return cc
->has_colors
;
638 * Temporary disable colors (this setting is independent on terminal-colors.d/)
640 void colors_off(void)
642 ul_colors
.disabled
= 1;
650 ul_colors
.disabled
= 0;
654 * Is terminal-colors.d/ configured to use colors?
656 int colors_wanted(void)
658 return ul_colors
.has_colors
;
664 int colors_mode(void)
666 return ul_colors
.mode
;
672 void color_fenable(const char *seq
, FILE *f
)
674 if (!ul_colors
.disabled
&& ul_colors
.has_colors
&& seq
)
679 * Returns escape sequence by logical @name, if undefined then returns @dflt.
681 const char *color_scheme_get_sequence(const char *name
, const char *dflt
)
683 struct ul_color_scheme
*cs
;
685 if (ul_colors
.disabled
|| !ul_colors
.has_colors
)
688 cs
= colors_get_scheme(&ul_colors
, name
);
689 return cs
&& cs
->seq
? cs
->seq
: dflt
;
693 * Enable color by logical @name, if undefined enable @dflt.
695 void color_scheme_fenable(const char *name
, const char *dflt
, FILE *f
)
697 const char *seq
= color_scheme_get_sequence(name
, dflt
);
701 color_fenable(seq
, f
);
706 * Disable previously enabled color
708 void color_fdisable(FILE *f
)
710 if (!ul_colors
.disabled
&& ul_colors
.has_colors
)
711 fputs(UL_COLOR_RESET
, f
);
717 const char *color_get_disable_sequence(void)
719 if (!ul_colors
.disabled
&& ul_colors
.has_colors
)
720 return UL_COLOR_RESET
;
726 * Parses @str to return UL_COLORMODE_*
728 int colormode_from_string(const char *str
)
731 static const char *modes
[] = {
732 [UL_COLORMODE_AUTO
] = "auto",
733 [UL_COLORMODE_NEVER
] = "never",
734 [UL_COLORMODE_ALWAYS
] = "always",
735 [UL_COLORMODE_UNDEF
] = ""
741 assert(ARRAY_SIZE(modes
) == __UL_NCOLORMODES
);
743 for (i
= 0; i
< ARRAY_SIZE(modes
); i
++) {
744 if (strcasecmp(str
, modes
[i
]) == 0)
752 * Parses @str and exit(EXIT_FAILURE) on error
754 int colormode_or_err(const char *str
, const char *errmsg
)
756 const char *p
= str
&& *str
== '=' ? str
+ 1 : str
;
759 colormode
= colormode_from_string(p
);
761 errx(EXIT_FAILURE
, "%s: '%s'", errmsg
, p
);
766 #ifdef TEST_PROGRAM_COLORS
768 int main(int argc
, char *argv
[])
770 static const struct option longopts
[] = {
771 { "mode", required_argument
, NULL
, 'm' },
772 { "color", required_argument
, NULL
, 'c' },
773 { "color-scheme", required_argument
, NULL
, 'C' },
774 { "name", required_argument
, NULL
, 'n' },
777 int c
, mode
= UL_COLORMODE_UNDEF
; /* default */
778 const char *color
= "red", *name
= NULL
, *color_scheme
= NULL
;
781 while ((c
= getopt_long(argc
, argv
, "C:c:m:n:", longopts
, NULL
)) != -1) {
787 color_scheme
= optarg
;
790 mode
= colormode_or_err(optarg
, "unsupported color mode");
796 fprintf(stderr
, "usage: %s [options]\n"
797 " -m, --mode <auto|never|always> default is undefined\n"
798 " -c, --color <red|blue|...> color for the test message\n"
799 " -C, --color-scheme <name> color for the test message\n"
800 " -n, --name <utilname> util name\n",
801 program_invocation_short_name
);
806 colors_init(mode
, name
? name
: program_invocation_short_name
);
808 if (color_is_sequence(color
))
811 seq
= color_get_sequence(color
);
814 color_scheme_enable(color_scheme
, seq
);
817 printf("Hello World!");
825 #endif /* TEST_PROGRAM_COLORS */