]>
Commit | Line | Data |
---|---|---|
dfa68ad1 OO |
1 | /* |
2 | * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com> | |
7a4704d8 | 3 | * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com> |
dfa68ad1 OO |
4 | * |
5 | * This file may be distributed under the terms of the | |
6 | * GNU Lesser General Public License. | |
7 | */ | |
a10c0434 | 8 | #include <assert.h> |
d0c9ddc3 | 9 | #include <sys/stat.h> |
570b3210 KZ |
10 | #include <sys/types.h> |
11 | #include <dirent.h> | |
7a4704d8 | 12 | #include <ctype.h> |
23d47267 KZ |
13 | |
14 | #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) | |
3947ca4c KZ |
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) | |
20 | # include <ncurses.h> | |
21 | # endif | |
22 | # if defined(HAVE_NCURSESW_TERM_H) | |
23 | # include <ncursesw/term.h> | |
23d47267 KZ |
24 | # elif defined(HAVE_NCURSES_TERM_H) |
25 | # include <ncurses/term.h> | |
3947ca4c KZ |
26 | # elif defined(HAVE_TERM_H) |
27 | # include <term.h> | |
23d47267 | 28 | # endif |
4310faf9 | 29 | #endif |
dfa68ad1 | 30 | |
51dfd171 | 31 | #include "c.h" |
dfa68ad1 | 32 | #include "colors.h" |
d0c9ddc3 | 33 | #include "pathnames.h" |
7a4704d8 | 34 | #include "strutils.h" |
dfa68ad1 | 35 | |
b73cc390 KZ |
36 | #include "debug.h" |
37 | ||
81f55ab9 | 38 | /* |
218b1dd6 | 39 | * Default behavior, may be overridden by terminal-colors.d/{enable,disable}. |
81f55ab9 KZ |
40 | */ |
41 | #ifdef USE_COLORS_BY_DEFAULT | |
42 | # define UL_COLORMODE_DEFAULT UL_COLORMODE_AUTO /* check isatty() */ | |
43 | #else | |
44 | # define UL_COLORMODE_DEFAULT UL_COLORMODE_NEVER /* no colors by default */ | |
45 | #endif | |
46 | ||
b73cc390 KZ |
47 | /* |
48 | * terminal-colors.d debug stuff | |
49 | */ | |
2ba641e5 | 50 | static UL_DEBUG_DEFINE_MASK(termcolors); |
819d9a29 | 51 | UL_DEBUG_DEFINE_MASKNAMES(termcolors) = UL_DEBUG_EMPTY_MASKNAMES; |
b73cc390 KZ |
52 | |
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 | |
57 | ||
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) | |
60 | ||
e66a6627 KZ |
61 | /* |
62 | * terminal-colors.d file types | |
63 | */ | |
570b3210 KZ |
64 | enum { |
65 | UL_COLORFILE_DISABLE, /* .disable */ | |
66 | UL_COLORFILE_ENABLE, /* .enable */ | |
67 | UL_COLORFILE_SCHEME, /* .scheme */ | |
68 | ||
69 | __UL_COLORFILE_COUNT | |
70 | }; | |
71 | ||
7a4704d8 KZ |
72 | struct ul_color_scheme { |
73 | char *name; | |
74 | char *seq; | |
75 | }; | |
76 | ||
e66a6627 KZ |
77 | /* |
78 | * Global colors control struct | |
79 | * | |
80 | * The terminal-colors.d/ evaluation is based on "scores": | |
81 | * | |
82 | * filename score | |
83 | * --------------------------------------- | |
84 | * type 1 | |
85 | * @termname.type 10 + 1 | |
86 | * utilname.type 20 + 1 | |
87 | * utilname@termname.type 20 + 10 + 1 | |
88 | * | |
89 | * the match with higher score wins. The score is per type. | |
90 | */ | |
570b3210 KZ |
91 | struct ul_color_ctl { |
92 | const char *utilname; /* util name */ | |
93 | const char *termname; /* terminal name ($TERM) */ | |
94 | ||
7a4704d8 KZ |
95 | char *sfile; /* path to scheme */ |
96 | ||
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 */ | |
570b3210 KZ |
100 | |
101 | int mode; /* UL_COLORMODE_* */ | |
e66a6627 KZ |
102 | unsigned int has_colors : 1, /* based on mode and scores[] */ |
103 | disabled : 1, /* disable colors */ | |
7a4704d8 | 104 | cs_configured : 1, /* color schemes read */ |
e66a6627 KZ |
105 | configured : 1; /* terminal-colors.d parsed */ |
106 | ||
107 | int scores[__UL_COLORFILE_COUNT]; /* the best match */ | |
570b3210 KZ |
108 | }; |
109 | ||
b8731ebc KZ |
110 | /* |
111 | * Control struct, globally shared. | |
112 | */ | |
570b3210 KZ |
113 | static struct ul_color_ctl ul_colors; |
114 | ||
7a4704d8 KZ |
115 | static void colors_free_schemes(struct ul_color_ctl *cc); |
116 | static int colors_read_schemes(struct ul_color_ctl *cc); | |
117 | ||
118 | /* | |
119 | * qsort/bsearch buddy | |
120 | */ | |
121 | static int cmp_scheme_name(const void *a0, const void *b0) | |
122 | { | |
892fc7d9 KZ |
123 | const struct ul_color_scheme *a = (const struct ul_color_scheme *) a0, |
124 | *b = (const struct ul_color_scheme *) b0; | |
7a4704d8 KZ |
125 | return strcmp(a->name, b->name); |
126 | } | |
127 | ||
e66a6627 | 128 | /* |
7a4704d8 | 129 | * Resets control struct (note that we don't allocate the struct) |
e66a6627 | 130 | */ |
570b3210 KZ |
131 | static void colors_reset(struct ul_color_ctl *cc) |
132 | { | |
133 | if (!cc) | |
134 | return; | |
135 | ||
7a4704d8 KZ |
136 | colors_free_schemes(cc); |
137 | ||
138 | free(cc->sfile); | |
570b3210 | 139 | |
7a4704d8 | 140 | cc->sfile = NULL; |
570b3210 KZ |
141 | cc->utilname = NULL; |
142 | cc->termname = NULL; | |
143 | cc->mode = UL_COLORMODE_UNDEF; | |
144 | ||
145 | memset(cc->scores, 0, sizeof(cc->scores)); | |
146 | } | |
147 | ||
570b3210 KZ |
148 | static void colors_debug(struct ul_color_ctl *cc) |
149 | { | |
b73cc390 | 150 | size_t i; |
570b3210 KZ |
151 | |
152 | if (!cc) | |
153 | return; | |
154 | ||
155 | printf("Colors:\n"); | |
156 | printf("\tutilname = '%s'\n", cc->utilname); | |
157 | printf("\ttermname = '%s'\n", cc->termname); | |
7a4704d8 | 158 | printf("\tscheme file = '%s'\n", cc->sfile); |
570b3210 KZ |
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" : "???"); | |
7a4704d8 KZ |
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); | |
168 | ||
169 | fputc('\n', stdout); | |
570b3210 KZ |
170 | |
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" : "???", | |
176 | cc->scores[i]); | |
7a4704d8 KZ |
177 | |
178 | fputc('\n', stdout); | |
179 | ||
180 | for (i = 0; i < cc->nschemes; i++) { | |
b73cc390 | 181 | printf("\tscheme #%02zu ", i); |
7a4704d8 KZ |
182 | color_scheme_enable(cc->schemes[i].name, NULL); |
183 | fputs(cc->schemes[i].name, stdout); | |
184 | color_disable(); | |
185 | fputc('\n', stdout); | |
186 | } | |
187 | fputc('\n', stdout); | |
570b3210 | 188 | } |
570b3210 | 189 | |
e66a6627 KZ |
190 | /* |
191 | * Parses [[<utilname>][@<termname>].]<type> | |
192 | */ | |
570b3210 KZ |
193 | static int filename_to_tokens(const char *str, |
194 | const char **name, size_t *namesz, | |
195 | const char **term, size_t *termsz, | |
196 | int *filetype) | |
197 | { | |
198 | const char *type_start, *term_start, *p; | |
199 | ||
200 | if (!str || !*str || *str == '.' || strlen(str) > PATH_MAX) | |
201 | return -EINVAL; | |
202 | ||
203 | /* parse .type */ | |
204 | p = strrchr(str, '.'); | |
205 | type_start = p ? p + 1 : str; | |
206 | ||
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; | |
b73cc390 KZ |
213 | else { |
214 | DBG(CONF, ul_debug("unknown type '%s'", type_start)); | |
570b3210 | 215 | return 1; /* unknown type */ |
b73cc390 | 216 | } |
570b3210 KZ |
217 | |
218 | if (type_start == str) | |
219 | return 0; /* "type" only */ | |
220 | ||
221 | /* parse @termname */ | |
222 | p = strchr(str, '@'); | |
223 | term_start = p ? p + 1 : NULL; | |
224 | if (term_start) { | |
225 | *term = term_start; | |
226 | *termsz = type_start - term_start - 1; | |
227 | if (term_start - 1 == str) | |
228 | return 0; /* "@termname.type" */ | |
229 | } | |
230 | ||
231 | /* parse utilname */ | |
232 | p = term_start ? term_start : type_start; | |
233 | *name = str; | |
234 | *namesz = p - str - 1; | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
570b3210 | 239 | /* |
e66a6627 KZ |
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. | |
570b3210 KZ |
243 | */ |
244 | static int colors_readdir(struct ul_color_ctl *cc, const char *dirname) | |
245 | { | |
246 | DIR *dir; | |
247 | int rc = 0; | |
248 | struct dirent *d; | |
7a4704d8 | 249 | char sfile[PATH_MAX] = { '\0' }; |
570b3210 KZ |
250 | size_t namesz, termsz; |
251 | ||
252 | if (!dirname || !cc || !cc->utilname || !*cc->utilname) | |
253 | return -EINVAL; | |
b73cc390 KZ |
254 | |
255 | DBG(CONF, ul_debug("reading dir: '%s'", dirname)); | |
256 | ||
570b3210 KZ |
257 | dir = opendir(dirname); |
258 | if (!dir) | |
259 | return -errno; | |
260 | ||
261 | namesz = strlen(cc->utilname); | |
262 | termsz = cc->termname ? strlen(cc->termname) : 0; | |
263 | ||
264 | while ((d = readdir(dir))) { | |
265 | int type, score = 1; | |
266 | const char *tk_name = NULL, *tk_term = NULL; | |
267 | size_t tk_namesz = 0, tk_termsz = 0; | |
268 | ||
269 | if (*d->d_name == '.') | |
270 | continue; | |
271 | #ifdef _DIRENT_HAVE_D_TYPE | |
272 | if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK && | |
273 | d->d_type != DT_REG) | |
274 | continue; | |
275 | #endif | |
276 | if (filename_to_tokens(d->d_name, | |
277 | &tk_name, &tk_namesz, | |
278 | &tk_term, &tk_termsz, &type) != 0) | |
279 | continue; | |
280 | ||
9e930041 | 281 | /* count theoretical score before we check names to avoid |
570b3210 KZ |
282 | * unnecessary strcmp() */ |
283 | if (tk_name) | |
284 | score += 20; | |
285 | if (tk_term) | |
286 | score += 10; | |
7a4704d8 | 287 | |
b73cc390 KZ |
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], | |
291 | tk_namesz, tk_name, | |
292 | tk_termsz, tk_term)); | |
293 | ||
7a4704d8 | 294 | |
570b3210 KZ |
295 | if (score < cc->scores[type]) |
296 | continue; | |
297 | ||
298 | /* filter out by names */ | |
eb728f96 KZ |
299 | if (tk_namesz && (tk_namesz != namesz || |
300 | strncmp(tk_name, cc->utilname, namesz) != 0)) | |
570b3210 | 301 | continue; |
7a4704d8 KZ |
302 | |
303 | if (tk_termsz && (termsz == 0 || tk_termsz != termsz || | |
304 | strncmp(tk_term, cc->termname, termsz) != 0)) | |
570b3210 KZ |
305 | continue; |
306 | ||
b73cc390 KZ |
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)); | |
570b3210 KZ |
312 | cc->scores[type] = score; |
313 | if (type == UL_COLORFILE_SCHEME) | |
7a4704d8 | 314 | strncpy(sfile, d->d_name, sizeof(sfile)); |
570b3210 KZ |
315 | } |
316 | ||
7a4704d8 KZ |
317 | if (*sfile) { |
318 | sfile[sizeof(sfile) - 1] = '\0'; | |
319 | if (asprintf(&cc->sfile, "%s/%s", dirname, sfile) <= 0) | |
570b3210 KZ |
320 | rc = -ENOMEM; |
321 | } | |
322 | ||
323 | closedir(dir); | |
324 | return rc; | |
325 | } | |
326 | ||
e66a6627 | 327 | /* atexit() wrapper */ |
570b3210 KZ |
328 | static void colors_deinit(void) |
329 | { | |
330 | colors_reset(&ul_colors); | |
331 | } | |
332 | ||
e66a6627 KZ |
333 | /* |
334 | * Returns path to $XDG_CONFIG_HOME/terminal-colors.d | |
335 | */ | |
570b3210 KZ |
336 | static char *colors_get_homedir(char *buf, size_t bufsz) |
337 | { | |
338 | char *p = getenv("XDG_CONFIG_HOME"); | |
339 | ||
340 | if (p) { | |
341 | snprintf(buf, bufsz, "%s/" _PATH_TERMCOLORS_DIRNAME, p); | |
342 | return buf; | |
343 | } | |
344 | ||
345 | p = getenv("HOME"); | |
346 | if (p) { | |
347 | snprintf(buf, bufsz, "%s/.config/" _PATH_TERMCOLORS_DIRNAME, p); | |
348 | return buf; | |
349 | } | |
350 | ||
351 | return NULL; | |
352 | } | |
dfa68ad1 | 353 | |
7a4704d8 KZ |
354 | /* canonicalize sequence */ |
355 | static int cn_sequence(const char *str, char **seq) | |
356 | { | |
357 | char *in, *out; | |
ab709377 | 358 | int len; |
7a4704d8 KZ |
359 | |
360 | if (!str) | |
361 | return -EINVAL; | |
362 | ||
363 | *seq = NULL; | |
364 | ||
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); | |
369 | ||
370 | return *seq ? 0 : -ENOMEM; | |
371 | } | |
372 | ||
373 | /* convert xx;yy sequences to "\033[xx;yy" */ | |
ab709377 | 374 | if ((len = asprintf(seq, "\033[%sm", str)) < 1) |
7a4704d8 KZ |
375 | return -ENOMEM; |
376 | ||
377 | for (in = *seq, out = *seq; in && *in; in++) { | |
378 | if (*in != '\\') { | |
379 | *out++ = *in; | |
380 | continue; | |
381 | } | |
382 | switch(*(in + 1)) { | |
383 | case 'a': | |
384 | *out++ = '\a'; /* Bell */ | |
385 | break; | |
386 | case 'b': | |
387 | *out++ = '\b'; /* Backspace */ | |
388 | break; | |
389 | case 'e': | |
390 | *out++ = '\033'; /* Escape */ | |
391 | break; | |
392 | case 'f': | |
393 | *out++ = '\f'; /* Form Feed */ | |
394 | break; | |
395 | case 'n': | |
396 | *out++ = '\n'; /* Newline */ | |
397 | break; | |
398 | case 'r': | |
399 | *out++ = '\r'; /* Carriage Return */ | |
400 | break; | |
401 | case 't': | |
402 | *out++ = '\t'; /* Tab */ | |
403 | break; | |
404 | case 'v': | |
405 | *out++ = '\v'; /* Vertical Tab */ | |
406 | break; | |
407 | case '\\': | |
408 | *out++ = '\\'; /* Backslash */ | |
409 | break; | |
410 | case '_': | |
411 | *out++ = ' '; /* Space */ | |
412 | break; | |
413 | case '#': | |
414 | *out++ = '#'; /* Hash mark */ | |
415 | break; | |
416 | case '?': | |
9e930041 | 417 | *out++ = '?'; /* Question mark */ |
7a4704d8 KZ |
418 | break; |
419 | default: | |
420 | *out++ = *in; | |
421 | *out++ = *(in + 1); | |
422 | break; | |
423 | } | |
424 | in++; | |
425 | } | |
ab709377 | 426 | |
dcf6f5b3 KZ |
427 | if (out) { |
428 | assert ((out - *seq) <= len); | |
429 | *out = '\0'; | |
430 | } | |
7a4704d8 KZ |
431 | |
432 | return 0; | |
433 | } | |
434 | ||
435 | ||
436 | /* | |
a72de3cf AH |
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. | |
7a4704d8 KZ |
440 | */ |
441 | static int colors_add_scheme(struct ul_color_ctl *cc, | |
442 | char *name, | |
443 | char *seq0) | |
444 | { | |
6508db29 KZ |
445 | struct ul_color_scheme *cs = NULL; |
446 | char *seq = NULL; | |
7a4704d8 KZ |
447 | int rc; |
448 | ||
449 | if (!cc || !name || !*name || !seq0 || !*seq0) | |
450 | return -EINVAL; | |
451 | ||
b73cc390 KZ |
452 | DBG(SCHEME, ul_debug("add '%s'", name)); |
453 | ||
7a4704d8 KZ |
454 | rc = cn_sequence(seq0, &seq); |
455 | if (rc) | |
456 | return rc; | |
7a4704d8 | 457 | |
6508db29 KZ |
458 | rc = -ENOMEM; |
459 | ||
7a4704d8 KZ |
460 | /* convert logical name (e.g. "red") to real ESC code */ |
461 | if (isalpha(*seq)) { | |
462 | const char *s = color_sequence_from_colorname(seq); | |
463 | char *p; | |
464 | ||
b73cc390 KZ |
465 | if (!s) { |
466 | DBG(SCHEME, ul_debug("unknown logical name: %s", seq)); | |
6508db29 KZ |
467 | rc = -EINVAL; |
468 | goto err; | |
b73cc390 | 469 | } |
6508db29 | 470 | |
7a4704d8 KZ |
471 | p = strdup(s); |
472 | if (!p) | |
6508db29 | 473 | goto err; |
7a4704d8 KZ |
474 | free(seq); |
475 | seq = p; | |
476 | } | |
477 | ||
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)); | |
482 | if (!tmp) | |
6508db29 | 483 | goto err; |
7a4704d8 KZ |
484 | cc->schemes = tmp; |
485 | cc->schemes_sz = cc->nschemes + 10; | |
486 | } | |
487 | ||
488 | /* add a new item */ | |
6508db29 | 489 | cs = &cc->schemes[cc->nschemes]; |
7a4704d8 | 490 | cs->seq = seq; |
6508db29 KZ |
491 | cs->name = strdup(name); |
492 | if (!cs->name) | |
493 | goto err; | |
7a4704d8 | 494 | |
6508db29 | 495 | cc->nschemes++; |
7a4704d8 | 496 | return 0; |
6508db29 KZ |
497 | err: |
498 | if (cs) { | |
499 | free(cs->seq); | |
500 | free(cs->name); | |
501 | cs->seq = cs->name = NULL; | |
502 | } else | |
503 | free(seq); | |
504 | return rc; | |
7a4704d8 KZ |
505 | } |
506 | ||
507 | /* | |
508 | * Deallocates all regards to color schemes | |
509 | */ | |
510 | static void colors_free_schemes(struct ul_color_ctl *cc) | |
511 | { | |
512 | size_t i; | |
513 | ||
b73cc390 KZ |
514 | DBG(SCHEME, ul_debug("free scheme")); |
515 | ||
7a4704d8 KZ |
516 | for (i = 0; i < cc->nschemes; i++) { |
517 | free(cc->schemes[i].name); | |
518 | free(cc->schemes[i].seq); | |
519 | } | |
520 | ||
521 | free(cc->schemes); | |
522 | cc->schemes = NULL; | |
523 | cc->nschemes = 0; | |
524 | cc->schemes_sz = 0; | |
525 | } | |
526 | ||
527 | /* | |
528 | * The scheme configuration has to be sorted for bsearch | |
529 | */ | |
530 | static void colors_sort_schemes(struct ul_color_ctl *cc) | |
531 | { | |
532 | if (!cc->nschemes) | |
533 | return; | |
534 | ||
b73cc390 KZ |
535 | DBG(SCHEME, ul_debug("sort scheme")); |
536 | ||
7a4704d8 KZ |
537 | qsort(cc->schemes, cc->nschemes, |
538 | sizeof(struct ul_color_scheme), cmp_scheme_name); | |
539 | } | |
540 | ||
541 | /* | |
542 | * Returns just one color scheme | |
543 | */ | |
544 | static struct ul_color_scheme *colors_get_scheme(struct ul_color_ctl *cc, | |
545 | const char *name) | |
546 | { | |
547 | struct ul_color_scheme key = { .name = (char *) name}, *res; | |
548 | ||
549 | if (!cc || !name || !*name) | |
550 | return NULL; | |
551 | ||
552 | if (!cc->cs_configured) { | |
553 | int rc = colors_read_schemes(cc); | |
554 | if (rc) | |
555 | return NULL; | |
556 | } | |
557 | if (!cc->nschemes) | |
558 | return NULL; | |
559 | ||
b73cc390 KZ |
560 | DBG(SCHEME, ul_debug("search '%s'", name)); |
561 | ||
7a4704d8 KZ |
562 | res = bsearch(&key, cc->schemes, cc->nschemes, |
563 | sizeof(struct ul_color_scheme), | |
564 | cmp_scheme_name); | |
565 | ||
566 | return res && res->seq ? res : NULL; | |
567 | } | |
568 | ||
569 | /* | |
570 | * Parses filenames in terminal-colors.d | |
571 | */ | |
e66a6627 KZ |
572 | static int colors_read_configuration(struct ul_color_ctl *cc) |
573 | { | |
574 | int rc = -ENOENT; | |
575 | char *dirname, buf[PATH_MAX]; | |
576 | ||
577 | cc->termname = getenv("TERM"); | |
578 | ||
579 | dirname = colors_get_homedir(buf, sizeof(buf)); | |
580 | if (dirname) | |
581 | rc = colors_readdir(cc, dirname); /* ~/.config */ | |
582 | if (rc == -EPERM || rc == -EACCES || rc == -ENOENT) | |
583 | rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR); /* /etc */ | |
584 | ||
585 | cc->configured = 1; | |
586 | return rc; | |
587 | } | |
588 | ||
589 | /* | |
7a4704d8 KZ |
590 | * Reads terminal-colors.d/ scheme file into array schemes |
591 | */ | |
592 | static int colors_read_schemes(struct ul_color_ctl *cc) | |
593 | { | |
594 | int rc = 0; | |
595 | FILE *f = NULL; | |
6508db29 KZ |
596 | char buf[BUFSIZ], |
597 | cn[129], seq[129]; | |
7a4704d8 KZ |
598 | |
599 | if (!cc->configured) | |
600 | rc = colors_read_configuration(cc); | |
601 | ||
602 | cc->cs_configured = 1; | |
603 | ||
604 | if (rc || !cc->sfile) | |
605 | goto done; | |
606 | ||
b73cc390 KZ |
607 | DBG(SCHEME, ul_debug("reading file '%s'", cc->sfile)); |
608 | ||
7a4704d8 KZ |
609 | f = fopen(cc->sfile, "r"); |
610 | if (!f) { | |
611 | rc = -errno; | |
612 | goto done; | |
613 | } | |
614 | ||
615 | while (fgets(buf, sizeof(buf), f)) { | |
7a4704d8 KZ |
616 | char *p = strchr(buf, '\n'); |
617 | ||
618 | if (!p) { | |
619 | if (feof(f)) | |
620 | p = strchr(buf, '\0'); | |
621 | else { | |
622 | rc = -errno; | |
623 | goto done; | |
624 | } | |
625 | } | |
626 | *p = '\0'; | |
627 | p = (char *) skip_blank(buf); | |
628 | if (*p == '\0' || *p == '#') | |
629 | continue; | |
630 | ||
6508db29 KZ |
631 | rc = sscanf(p, "%128[^ ] %128[^\n ]", cn, seq); |
632 | if (rc == 2 && *cn && *seq) { | |
7a4704d8 | 633 | rc = colors_add_scheme(cc, cn, seq); /* set rc=0 on success */ |
6508db29 KZ |
634 | if (rc) |
635 | goto done; | |
7a4704d8 | 636 | } |
7a4704d8 | 637 | } |
6508db29 | 638 | rc = 0; |
7a4704d8 KZ |
639 | |
640 | done: | |
641 | if (f) | |
642 | fclose(f); | |
643 | colors_sort_schemes(cc); | |
644 | ||
645 | return rc; | |
646 | } | |
647 | ||
b73cc390 KZ |
648 | |
649 | static void termcolors_init_debug(void) | |
650 | { | |
a15dca2f | 651 | __UL_INIT_DEBUG_FROM_ENV(termcolors, TERMCOLORS_DEBUG_, 0, TERMINAL_COLORS_DEBUG); |
b73cc390 KZ |
652 | } |
653 | ||
4310faf9 KZ |
654 | static int colors_terminal_is_ready(void) |
655 | { | |
656 | int ncolors = -1; | |
657 | ||
23d47267 | 658 | #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) |
4310faf9 KZ |
659 | { |
660 | int ret; | |
661 | ||
9d6852d3 KZ |
662 | if (setupterm(NULL, STDOUT_FILENO, &ret) == 0 && ret == 1) |
663 | ncolors = tigetnum("colors"); | |
4310faf9 KZ |
664 | } |
665 | #endif | |
cc7ffe12 | 666 | if (1 < ncolors) { |
23d47267 KZ |
667 | DBG(CONF, ul_debug("terminal is ready (supports %d colors)", ncolors)); |
668 | return 1; | |
669 | } | |
9d6852d3 KZ |
670 | |
671 | DBG(CONF, ul_debug("terminal is NOT ready (no colors)")); | |
4310faf9 KZ |
672 | return 0; |
673 | } | |
674 | ||
b8731ebc KZ |
675 | /** |
676 | * colors_init: | |
677 | * @mode: UL_COLORMODE_* | |
678 | * @name: util argv[0] | |
679 | * | |
7a4704d8 KZ |
680 | * Initialize private color control struct and initialize the colors |
681 | * status. The color schemes are parsed on demand by colors_get_scheme(). | |
682 | * | |
b8731ebc | 683 | * Returns: >0 on success. |
e66a6627 | 684 | */ |
d0c9ddc3 | 685 | int colors_init(int mode, const char *name) |
dfa68ad1 | 686 | { |
4310faf9 | 687 | int ready = -1; |
570b3210 KZ |
688 | struct ul_color_ctl *cc = &ul_colors; |
689 | ||
690 | cc->utilname = name; | |
c1a54286 | 691 | |
b73cc390 KZ |
692 | termcolors_init_debug(); |
693 | ||
5db59623 KZ |
694 | if (mode != UL_COLORMODE_ALWAYS && !isatty(STDOUT_FILENO)) |
695 | cc->mode = UL_COLORMODE_NEVER; | |
696 | else | |
697 | cc->mode = mode; | |
698 | ||
699 | if (cc->mode == UL_COLORMODE_UNDEF | |
700 | && (ready = colors_terminal_is_ready())) { | |
e66a6627 | 701 | int rc = colors_read_configuration(cc); |
570b3210 | 702 | if (rc) |
81f55ab9 | 703 | cc->mode = UL_COLORMODE_DEFAULT; |
570b3210 | 704 | else { |
b73cc390 | 705 | |
570b3210 KZ |
706 | /* evaluate scores */ |
707 | if (cc->scores[UL_COLORFILE_DISABLE] > | |
708 | cc->scores[UL_COLORFILE_ENABLE]) | |
709 | cc->mode = UL_COLORMODE_NEVER; | |
329b0ee7 | 710 | else |
81f55ab9 | 711 | cc->mode = UL_COLORMODE_DEFAULT; |
570b3210 KZ |
712 | |
713 | atexit(colors_deinit); | |
d0c9ddc3 | 714 | } |
329b0ee7 KZ |
715 | } |
716 | ||
570b3210 | 717 | switch (cc->mode) { |
a10c0434 | 718 | case UL_COLORMODE_AUTO: |
4310faf9 | 719 | cc->has_colors = ready == -1 ? colors_terminal_is_ready() : ready; |
a10c0434 KZ |
720 | break; |
721 | case UL_COLORMODE_ALWAYS: | |
e66a6627 | 722 | cc->has_colors = 1; |
a10c0434 KZ |
723 | break; |
724 | case UL_COLORMODE_NEVER: | |
725 | default: | |
e66a6627 | 726 | cc->has_colors = 0; |
a10c0434 | 727 | } |
b73cc390 KZ |
728 | |
729 | ON_DBG(CONF, colors_debug(cc)); | |
730 | ||
e66a6627 KZ |
731 | return cc->has_colors; |
732 | } | |
733 | ||
7a4704d8 KZ |
734 | /* |
735 | * Temporary disable colors (this setting is independent on terminal-colors.d/) | |
736 | */ | |
e66a6627 KZ |
737 | void colors_off(void) |
738 | { | |
739 | ul_colors.disabled = 1; | |
740 | } | |
741 | ||
7a4704d8 KZ |
742 | /* |
743 | * Enable colors | |
744 | */ | |
e66a6627 KZ |
745 | void colors_on(void) |
746 | { | |
747 | ul_colors.disabled = 0; | |
dfa68ad1 OO |
748 | } |
749 | ||
7a4704d8 KZ |
750 | /* |
751 | * Is terminal-colors.d/ configured to use colors? | |
752 | */ | |
80a1712f KZ |
753 | int colors_wanted(void) |
754 | { | |
7a4704d8 KZ |
755 | return ul_colors.has_colors; |
756 | } | |
757 | ||
5db59623 KZ |
758 | /* |
759 | * Returns mode | |
760 | */ | |
761 | int colors_mode(void) | |
762 | { | |
763 | return ul_colors.mode; | |
764 | } | |
765 | ||
7a4704d8 KZ |
766 | /* |
767 | * Enable @seq color | |
768 | */ | |
769 | void color_fenable(const char *seq, FILE *f) | |
770 | { | |
771 | if (!ul_colors.disabled && ul_colors.has_colors && seq) | |
772 | fputs(seq, f); | |
80a1712f KZ |
773 | } |
774 | ||
7a4704d8 | 775 | /* |
b8731ebc | 776 | * Returns escape sequence by logical @name, if undefined then returns @dflt. |
7a4704d8 | 777 | */ |
b8731ebc | 778 | const char *color_scheme_get_sequence(const char *name, const char *dflt) |
dfa68ad1 | 779 | { |
7a4704d8 KZ |
780 | struct ul_color_scheme *cs; |
781 | ||
782 | if (ul_colors.disabled || !ul_colors.has_colors) | |
b8731ebc | 783 | return NULL; |
7a4704d8 KZ |
784 | |
785 | cs = colors_get_scheme(&ul_colors, name); | |
b8731ebc KZ |
786 | return cs && cs->seq ? cs->seq : dflt; |
787 | } | |
788 | ||
789 | /* | |
790 | * Enable color by logical @name, if undefined enable @dflt. | |
791 | */ | |
792 | void color_scheme_fenable(const char *name, const char *dflt, FILE *f) | |
793 | { | |
794 | const char *seq = color_scheme_get_sequence(name, dflt); | |
795 | ||
796 | if (!seq) | |
797 | return; | |
798 | color_fenable(seq, f); | |
dfa68ad1 OO |
799 | } |
800 | ||
b8731ebc | 801 | |
7a4704d8 KZ |
802 | /* |
803 | * Disable previously enabled color | |
804 | */ | |
80a1712f | 805 | void color_fdisable(FILE *f) |
dfa68ad1 | 806 | { |
e66a6627 | 807 | if (!ul_colors.disabled && ul_colors.has_colors) |
80a1712f | 808 | fputs(UL_COLOR_RESET, f); |
dfa68ad1 | 809 | } |
a10c0434 | 810 | |
7a4704d8 KZ |
811 | /* |
812 | * Parses @str to return UL_COLORMODE_* | |
813 | */ | |
a10c0434 KZ |
814 | int colormode_from_string(const char *str) |
815 | { | |
816 | size_t i; | |
817 | static const char *modes[] = { | |
818 | [UL_COLORMODE_AUTO] = "auto", | |
819 | [UL_COLORMODE_NEVER] = "never", | |
d0c9ddc3 OO |
820 | [UL_COLORMODE_ALWAYS] = "always", |
821 | [UL_COLORMODE_UNDEF] = "" | |
a10c0434 KZ |
822 | }; |
823 | ||
824 | if (!str || !*str) | |
825 | return -EINVAL; | |
826 | ||
827 | assert(ARRAY_SIZE(modes) == __UL_NCOLORMODES); | |
828 | ||
829 | for (i = 0; i < ARRAY_SIZE(modes); i++) { | |
830 | if (strcasecmp(str, modes[i]) == 0) | |
831 | return i; | |
832 | } | |
833 | ||
834 | return -EINVAL; | |
835 | } | |
836 | ||
7a4704d8 KZ |
837 | /* |
838 | * Parses @str and exit(EXIT_FAILURE) on error | |
839 | */ | |
b7faf991 KZ |
840 | int colormode_or_err(const char *str, const char *errmsg) |
841 | { | |
842 | const char *p = str && *str == '=' ? str + 1 : str; | |
843 | int colormode; | |
844 | ||
845 | colormode = colormode_from_string(p); | |
846 | if (colormode < 0) | |
847 | errx(EXIT_FAILURE, "%s: '%s'", errmsg, p); | |
848 | ||
849 | return colormode; | |
850 | } | |
851 | ||
e8f7acb0 | 852 | #ifdef TEST_PROGRAM_COLORS |
a10c0434 | 853 | # include <getopt.h> |
a10c0434 KZ |
854 | int main(int argc, char *argv[]) |
855 | { | |
856 | static const struct option longopts[] = { | |
71f08e97 SK |
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' }, | |
861 | { NULL, 0, NULL, 0 } | |
a10c0434 | 862 | }; |
570b3210 | 863 | int c, mode = UL_COLORMODE_UNDEF; /* default */ |
7a4704d8 KZ |
864 | const char *color = "red", *name = NULL, *color_scheme = NULL; |
865 | const char *seq = NULL; | |
a10c0434 | 866 | |
7a4704d8 | 867 | while ((c = getopt_long(argc, argv, "C:c:m:n:", longopts, NULL)) != -1) { |
a10c0434 | 868 | switch (c) { |
570b3210 KZ |
869 | case 'c': |
870 | color = optarg; | |
871 | break; | |
7a4704d8 KZ |
872 | case 'C': |
873 | color_scheme = optarg; | |
874 | break; | |
875 | case 'm': | |
876 | mode = colormode_or_err(optarg, "unsupported color mode"); | |
877 | break; | |
878 | case 'n': | |
879 | name = optarg; | |
68f7b572 | 880 | break; |
7a4704d8 KZ |
881 | default: |
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); | |
888 | return EXIT_FAILURE; | |
a10c0434 KZ |
889 | } |
890 | } | |
891 | ||
7a4704d8 | 892 | colors_init(mode, name ? name : program_invocation_short_name); |
570b3210 | 893 | |
7a4704d8 | 894 | seq = color_sequence_from_colorname(color); |
d0c9ddc3 | 895 | |
7a4704d8 KZ |
896 | if (color_scheme) |
897 | color_scheme_enable(color_scheme, seq); | |
898 | else | |
899 | color_enable(seq); | |
a10c0434 KZ |
900 | printf("Hello World!"); |
901 | color_disable(); | |
7a4704d8 KZ |
902 | fputc('\n', stdout); |
903 | ||
a10c0434 KZ |
904 | return EXIT_SUCCESS; |
905 | } | |
e8f7acb0 | 906 | #endif /* TEST_PROGRAM_COLORS */ |
a10c0434 | 907 |