/* Output stream for attributed text, producing ANSI escape sequences. Copyright (C) 2006-2008, 2015-2016 Free Software Foundation, Inc. Written by Bruno Haible , 2006. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include /* Specification. */ #include "term-ostream.h" #include #include #include #include #include #include #include "error.h" #include "fatal-signal.h" #include "full-write.h" #include "terminfo.h" #include "xalloc.h" #include "xsize.h" #include "gettext.h" #define _(str) gettext (str) #if HAVE_TPARAM /* GNU termcap's tparam() function requires a buffer argument. Make it so large that there is no risk that tparam() needs to call malloc(). */ static char tparambuf[100]; /* Define tparm in terms of tparam. In the scope of this file, it is called with at most one argument after the string. */ # define tparm(str, arg1) \ tparam (str, tparambuf, sizeof (tparambuf), arg1) #endif #define SIZEOF(a) (sizeof(a) / sizeof(a[0])) /* =========================== Color primitives =========================== */ /* A color in RGB format. */ typedef struct { unsigned int red : 8; /* range 0..255 */ unsigned int green : 8; /* range 0..255 */ unsigned int blue : 8; /* range 0..255 */ } rgb_t; /* A color in HSV (a.k.a. HSB) format. */ typedef struct { float hue; /* normalized to interval [0,6) */ float saturation; /* normalized to interval [0,1] */ float brightness; /* a.k.a. value, normalized to interval [0,1] */ } hsv_t; /* Conversion of a color in RGB to HSV format. */ static void rgb_to_hsv (rgb_t c, hsv_t *result) { unsigned int r = c.red; unsigned int g = c.green; unsigned int b = c.blue; if (r > g) { if (b > r) { /* b > r > g, so max = b, min = g */ result->hue = 4.0f + (float) (r - g) / (float) (b - g); result->saturation = 1.0f - (float) g / (float) b; result->brightness = (float) b / 255.0f; } else if (b <= g) { /* r > g >= b, so max = r, min = b */ result->hue = 0.0f + (float) (g - b) / (float) (r - b); result->saturation = 1.0f - (float) b / (float) r; result->brightness = (float) r / 255.0f; } else { /* r >= b > g, so max = r, min = g */ result->hue = 6.0f - (float) (b - g) / (float) (r - g); result->saturation = 1.0f - (float) g / (float) r; result->brightness = (float) r / 255.0f; } } else { if (b > g) { /* b > g >= r, so max = b, min = r */ result->hue = 4.0f - (float) (g - r) / (float) (b - r); result->saturation = 1.0f - (float) r / (float) b; result->brightness = (float) b / 255.0f; } else if (b < r) { /* g >= r > b, so max = g, min = b */ result->hue = 2.0f - (float) (r - b) / (float) (g - b); result->saturation = 1.0f - (float) b / (float) g; result->brightness = (float) g / 255.0f; } else if (g > r) { /* g >= b >= r, g > r, so max = g, min = r */ result->hue = 2.0f + (float) (b - r) / (float) (g - r); result->saturation = 1.0f - (float) r / (float) g; result->brightness = (float) g / 255.0f; } else { /* r = g = b. A grey color. */ result->hue = 0; /* arbitrary */ result->saturation = 0; result->brightness = (float) r / 255.0f; } } } /* Square of distance of two colors. */ static float color_distance (const hsv_t *color1, const hsv_t *color2) { #if 0 /* Formula taken from "John Smith: Color Similarity", http://www.ctr.columbia.edu/~jrsmith/html/pubs/acmmm96/node8.html. */ float angle1 = color1->hue * 1.04719755f; /* normalize to [0,2π] */ float angle2 = color2->hue * 1.04719755f; /* normalize to [0,2π] */ float delta_x = color1->saturation * cosf (angle1) - color2->saturation * cosf (angle2); float delta_y = color1->saturation * sinf (angle1) - color2->saturation * sinf (angle2); float delta_v = color1->brightness - color2->brightness; return delta_x * delta_x + delta_y * delta_y + delta_v * delta_v; #else /* Formula that considers hue differences with more weight than saturation or brightness differences, like the human eye does. */ float delta_hue = (color1->hue >= color2->hue ? (color1->hue - color2->hue >= 3.0f ? 6.0f + color2->hue - color1->hue : color1->hue - color2->hue) : (color2->hue - color1->hue >= 3.0f ? 6.0f + color1->hue - color2->hue : color2->hue - color1->hue)); float min_saturation = (color1->saturation < color2->saturation ? color1->saturation : color2->saturation); float delta_saturation = color1->saturation - color2->saturation; float delta_brightness = color1->brightness - color2->brightness; return delta_hue * delta_hue * min_saturation + delta_saturation * delta_saturation * 0.2f + delta_brightness * delta_brightness * 0.8f; #endif } /* Return the index of the color in a color table that is nearest to a given color. */ static unsigned int nearest_color (rgb_t given, const rgb_t *table, unsigned int table_size) { hsv_t given_hsv; unsigned int best_index; float best_distance; unsigned int i; assert (table_size > 0); rgb_to_hsv (given, &given_hsv); best_index = 0; best_distance = 1000000.0f; for (i = 0; i < table_size; i++) { hsv_t i_hsv; rgb_to_hsv (table[i], &i_hsv); /* Avoid converting a color to grey, or fading out a color too much. */ if (i_hsv.saturation > given_hsv.saturation * 0.5f) { float distance = color_distance (&given_hsv, &i_hsv); if (distance < best_distance) { best_index = i; best_distance = distance; } } } #if 0 /* Debugging code */ hsv_t best_hsv; rgb_to_hsv (table[best_index], &best_hsv); fprintf (stderr, "nearest: (%d,%d,%d) = (%f,%f,%f)\n -> (%f,%f,%f) = (%d,%d,%d)\n", given.red, given.green, given.blue, (double)given_hsv.hue, (double)given_hsv.saturation, (double)given_hsv.brightness, (double)best_hsv.hue, (double)best_hsv.saturation, (double)best_hsv.brightness, table[best_index].red, table[best_index].green, table[best_index].blue); #endif return best_index; } /* The luminance of a color. This is the brightness of the color, as it appears to the human eye. This must be used in color to grey conversion. */ static float color_luminance (int r, int g, int b) { /* Use the luminance model used by NTSC and JPEG. Taken from http://www.fho-emden.de/~hoffmann/gray10012001.pdf . No need to care about rounding errors leading to luminance > 1; this cannot happen. */ return (0.299f * r + 0.587f * g + 0.114f * b) / 255.0f; } /* ============================= Color models ============================= */ /* The color model used by the terminal. */ typedef enum { cm_monochrome, /* No colors. */ cm_common8, /* Usual terminal with at least 8 colors. */ cm_xterm8, /* TERM=xterm, with 8 colors. */ cm_xterm16, /* TERM=xterm-16color, with 16 colors. */ cm_xterm88, /* TERM=xterm-88color, with 88 colors. */ cm_xterm256 /* TERM=xterm-256color, with 256 colors. */ } colormodel_t; /* ----------------------- cm_monochrome color model ----------------------- */ /* A non-default color index doesn't exist in this color model. */ static inline term_color_t rgb_to_color_monochrome () { return COLOR_DEFAULT; } /* ------------------------ cm_common8 color model ------------------------ */ /* A non-default color index is in the range 0..7. RGB components COLOR_BLACK 000 COLOR_BLUE 001 COLOR_GREEN 010 COLOR_CYAN 011 COLOR_RED 100 COLOR_MAGENTA 101 COLOR_YELLOW 110 COLOR_WHITE 111 */ static const rgb_t colors_of_common8[8] = { /* R G B grey index */ { 0, 0, 0 }, /* 0.000 0 */ { 0, 0, 255 }, { 0, 255, 0 }, { 0, 255, 255 }, { 255, 0, 0 }, { 255, 0, 255 }, { 255, 255, 0 }, { 255, 255, 255 } /* 1.000 7 */ }; static inline term_color_t rgb_to_color_common8 (int r, int g, int b) { rgb_t color; hsv_t hsv; color.red = r; color.green = g; color.blue = b; rgb_to_hsv (color, &hsv); if (hsv.saturation < 0.065f) { /* Greyscale approximation. */ float luminance = color_luminance (r, g, b); if (luminance < 0.500f) return 0; else return 7; } else /* Color approximation. */ return nearest_color (color, colors_of_common8, 8); } /* Convert a cm_common8 color in RGB encoding to BGR encoding. See the ncurses terminfo(5) manual page, section "Color Handling", for an explanation why this is needed. */ static inline int color_bgr (term_color_t color) { return ((color & 4) >> 2) | (color & 2) | ((color & 1) << 2); } /* ------------------------- cm_xterm8 color model ------------------------- */ /* A non-default color index is in the range 0..7. BGR components COLOR_BLACK 000 COLOR_RED 001 COLOR_GREEN 010 COLOR_YELLOW 011 COLOR_BLUE 100 COLOR_MAGENTA 101 COLOR_CYAN 110 COLOR_WHITE 111 */ static const rgb_t colors_of_xterm8[8] = { /* The real xterm's colors are dimmed; assume full-brightness instead. */ /* R G B grey index */ { 0, 0, 0 }, /* 0.000 0 */ { 255, 0, 0 }, { 0, 255, 0 }, { 255, 255, 0 }, { 0, 0, 255 }, { 255, 0, 255 }, { 0, 255, 255 }, { 255, 255, 255 } /* 1.000 7 */ }; static inline term_color_t rgb_to_color_xterm8 (int r, int g, int b) { rgb_t color; hsv_t hsv; color.red = r; color.green = g; color.blue = b; rgb_to_hsv (color, &hsv); if (hsv.saturation < 0.065f) { /* Greyscale approximation. */ float luminance = color_luminance (r, g, b); if (luminance < 0.500f) return 0; else return 7; } else /* Color approximation. */ return nearest_color (color, colors_of_xterm8, 8); } /* ------------------------ cm_xterm16 color model ------------------------ */ /* A non-default color index is in the range 0..15. The RGB values come from xterm's XTerm-col.ad. */ static const rgb_t colors_of_xterm16[16] = { /* R G B grey index */ { 0, 0, 0 }, /* 0.000 0 */ { 205, 0, 0 }, { 0, 205, 0 }, { 205, 205, 0 }, { 0, 0, 205 }, { 205, 0, 205 }, { 0, 205, 205 }, { 229, 229, 229 }, /* 0.898 7 */ { 77, 77, 77 }, /* 0.302 8 */ { 255, 0, 0 }, { 0, 255, 0 }, { 255, 255, 0 }, { 0, 0, 255 }, { 255, 0, 255 }, { 0, 255, 255 }, { 255, 255, 255 } /* 1.000 15 */ }; static inline term_color_t rgb_to_color_xterm16 (int r, int g, int b) { rgb_t color; hsv_t hsv; color.red = r; color.green = g; color.blue = b; rgb_to_hsv (color, &hsv); if (hsv.saturation < 0.065f) { /* Greyscale approximation. */ float luminance = color_luminance (r, g, b); if (luminance < 0.151f) return 0; else if (luminance < 0.600f) return 8; else if (luminance < 0.949f) return 7; else return 15; } else /* Color approximation. */ return nearest_color (color, colors_of_xterm16, 16); } /* ------------------------ cm_xterm88 color model ------------------------ */ /* A non-default color index is in the range 0..87. Colors 0..15 are the same as in the cm_xterm16 color model. Colors 16..87 are defined in xterm's 88colres.h. */ static const rgb_t colors_of_xterm88[88] = { /* R G B grey index */ { 0, 0, 0 }, /* 0.000 0 */ { 205, 0, 0 }, { 0, 205, 0 }, { 205, 205, 0 }, { 0, 0, 205 }, { 205, 0, 205 }, { 0, 205, 205 }, { 229, 229, 229 }, /* 0.898 7 */ { 77, 77, 77 }, /* 0.302 8 */ { 255, 0, 0 }, { 0, 255, 0 }, { 255, 255, 0 }, { 0, 0, 255 }, { 255, 0, 255 }, { 0, 255, 255 }, { 255, 255, 255 }, /* 1.000 15 */ { 0, 0, 0 }, /* 0.000 16 */ { 0, 0, 139 }, { 0, 0, 205 }, { 0, 0, 255 }, { 0, 139, 0 }, { 0, 139, 139 }, { 0, 139, 205 }, { 0, 139, 255 }, { 0, 205, 0 }, { 0, 205, 139 }, { 0, 205, 205 }, { 0, 205, 255 }, { 0, 255, 0 }, { 0, 255, 139 }, { 0, 255, 205 }, { 0, 255, 255 }, { 139, 0, 0 }, { 139, 0, 139 }, { 139, 0, 205 }, { 139, 0, 255 }, { 139, 139, 0 }, { 139, 139, 139 }, /* 0.545 37 */ { 139, 139, 205 }, { 139, 139, 255 }, { 139, 205, 0 }, { 139, 205, 139 }, { 139, 205, 205 }, { 139, 205, 255 }, { 139, 255, 0 }, { 139, 255, 139 }, { 139, 255, 205 }, { 139, 255, 255 }, { 205, 0, 0 }, { 205, 0, 139 }, { 205, 0, 205 }, { 205, 0, 255 }, { 205, 139, 0 }, { 205, 139, 139 }, { 205, 139, 205 }, { 205, 139, 255 }, { 205, 205, 0 }, { 205, 205, 139 }, { 205, 205, 205 }, /* 0.804 58 */ { 205, 205, 255 }, { 205, 255, 0 }, { 205, 255, 139 }, { 205, 255, 205 }, { 205, 255, 255 }, { 255, 0, 0 }, { 255, 0, 139 }, { 255, 0, 205 }, { 255, 0, 255 }, { 255, 139, 0 }, { 255, 139, 139 }, { 255, 139, 205 }, { 255, 139, 255 }, { 255, 205, 0 }, { 255, 205, 139 }, { 255, 205, 205 }, { 255, 205, 255 }, { 255, 255, 0 }, { 255, 255, 139 }, { 255, 255, 205 }, { 255, 255, 255 }, /* 1.000 79 */ { 46, 46, 46 }, /* 0.180 80 */ { 92, 92, 92 }, /* 0.361 81 */ { 115, 115, 115 }, /* 0.451 82 */ { 139, 139, 139 }, /* 0.545 83 */ { 162, 162, 162 }, /* 0.635 84 */ { 185, 185, 185 }, /* 0.725 85 */ { 208, 208, 208 }, /* 0.816 86 */ { 231, 231, 231 } /* 0.906 87 */ }; static inline term_color_t rgb_to_color_xterm88 (int r, int g, int b) { rgb_t color; hsv_t hsv; color.red = r; color.green = g; color.blue = b; rgb_to_hsv (color, &hsv); if (hsv.saturation < 0.065f) { /* Greyscale approximation. */ float luminance = color_luminance (r, g, b); if (luminance < 0.090f) return 0; else if (luminance < 0.241f) return 80; else if (luminance < 0.331f) return 8; else if (luminance < 0.406f) return 81; else if (luminance < 0.498f) return 82; else if (luminance < 0.585f) return 37; else if (luminance < 0.680f) return 84; else if (luminance < 0.764f) return 85; else if (luminance < 0.810f) return 58; else if (luminance < 0.857f) return 86; else if (luminance < 0.902f) return 7; else if (luminance < 0.953f) return 87; else return 15; } else /* Color approximation. */ return nearest_color (color, colors_of_xterm88, 88); } /* ------------------------ cm_xterm256 color model ------------------------ */ /* A non-default color index is in the range 0..255. Colors 0..15 are the same as in the cm_xterm16 color model. Colors 16..255 are defined in xterm's 256colres.h. */ static const rgb_t colors_of_xterm256[256] = { /* R G B grey index */ { 0, 0, 0 }, /* 0.000 0 */ { 205, 0, 0 }, { 0, 205, 0 }, { 205, 205, 0 }, { 0, 0, 205 }, { 205, 0, 205 }, { 0, 205, 205 }, { 229, 229, 229 }, /* 0.898 7 */ { 77, 77, 77 }, /* 0.302 8 */ { 255, 0, 0 }, { 0, 255, 0 }, { 255, 255, 0 }, { 0, 0, 255 }, { 255, 0, 255 }, { 0, 255, 255 }, { 255, 255, 255 }, /* 1.000 15 */ { 0, 0, 0 }, /* 0.000 16 */ { 0, 0, 42 }, { 0, 0, 85 }, { 0, 0, 127 }, { 0, 0, 170 }, { 0, 0, 212 }, { 0, 42, 0 }, { 0, 42, 42 }, { 0, 42, 85 }, { 0, 42, 127 }, { 0, 42, 170 }, { 0, 42, 212 }, { 0, 85, 0 }, { 0, 85, 42 }, { 0, 85, 85 }, { 0, 85, 127 }, { 0, 85, 170 }, { 0, 85, 212 }, { 0, 127, 0 }, { 0, 127, 42 }, { 0, 127, 85 }, { 0, 127, 127 }, { 0, 127, 170 }, { 0, 127, 212 }, { 0, 170, 0 }, { 0, 170, 42 }, { 0, 170, 85 }, { 0, 170, 127 }, { 0, 170, 170 }, { 0, 170, 212 }, { 0, 212, 0 }, { 0, 212, 42 }, { 0, 212, 85 }, { 0, 212, 127 }, { 0, 212, 170 }, { 0, 212, 212 }, { 42, 0, 0 }, { 42, 0, 42 }, { 42, 0, 85 }, { 42, 0, 127 }, { 42, 0, 170 }, { 42, 0, 212 }, { 42, 42, 0 }, { 42, 42, 42 }, /* 0.165 59 */ { 42, 42, 85 }, { 42, 42, 127 }, { 42, 42, 170 }, { 42, 42, 212 }, { 42, 85, 0 }, { 42, 85, 42 }, { 42, 85, 85 }, { 42, 85, 127 }, { 42, 85, 170 }, { 42, 85, 212 }, { 42, 127, 0 }, { 42, 127, 42 }, { 42, 127, 85 }, { 42, 127, 127 }, { 42, 127, 170 }, { 42, 127, 212 }, { 42, 170, 0 }, { 42, 170, 42 }, { 42, 170, 85 }, { 42, 170, 127 }, { 42, 170, 170 }, { 42, 170, 212 }, { 42, 212, 0 }, { 42, 212, 42 }, { 42, 212, 85 }, { 42, 212, 127 }, { 42, 212, 170 }, { 42, 212, 212 }, { 85, 0, 0 }, { 85, 0, 42 }, { 85, 0, 85 }, { 85, 0, 127 }, { 85, 0, 170 }, { 85, 0, 212 }, { 85, 42, 0 }, { 85, 42, 42 }, { 85, 42, 85 }, { 85, 42, 127 }, { 85, 42, 170 }, { 85, 42, 212 }, { 85, 85, 0 }, { 85, 85, 42 }, { 85, 85, 85 }, /* 0.333 102 */ { 85, 85, 127 }, { 85, 85, 170 }, { 85, 85, 212 }, { 85, 127, 0 }, { 85, 127, 42 }, { 85, 127, 85 }, { 85, 127, 127 }, { 85, 127, 170 }, { 85, 127, 212 }, { 85, 170, 0 }, { 85, 170, 42 }, { 85, 170, 85 }, { 85, 170, 127 }, { 85, 170, 170 }, { 85, 170, 212 }, { 85, 212, 0 }, { 85, 212, 42 }, { 85, 212, 85 }, { 85, 212, 127 }, { 85, 212, 170 }, { 85, 212, 212 }, { 127, 0, 0 }, { 127, 0, 42 }, { 127, 0, 85 }, { 127, 0, 127 }, { 127, 0, 170 }, { 127, 0, 212 }, { 127, 42, 0 }, { 127, 42, 42 }, { 127, 42, 85 }, { 127, 42, 127 }, { 127, 42, 170 }, { 127, 42, 212 }, { 127, 85, 0 }, { 127, 85, 42 }, { 127, 85, 85 }, { 127, 85, 127 }, { 127, 85, 170 }, { 127, 85, 212 }, { 127, 127, 0 }, { 127, 127, 42 }, { 127, 127, 85 }, { 127, 127, 127 }, /* 0.498 145 */ { 127, 127, 170 }, { 127, 127, 212 }, { 127, 170, 0 }, { 127, 170, 42 }, { 127, 170, 85 }, { 127, 170, 127 }, { 127, 170, 170 }, { 127, 170, 212 }, { 127, 212, 0 }, { 127, 212, 42 }, { 127, 212, 85 }, { 127, 212, 127 }, { 127, 212, 170 }, { 127, 212, 212 }, { 170, 0, 0 }, { 170, 0, 42 }, { 170, 0, 85 }, { 170, 0, 127 }, { 170, 0, 170 }, { 170, 0, 212 }, { 170, 42, 0 }, { 170, 42, 42 }, { 170, 42, 85 }, { 170, 42, 127 }, { 170, 42, 170 }, { 170, 42, 212 }, { 170, 85, 0 }, { 170, 85, 42 }, { 170, 85, 85 }, { 170, 85, 127 }, { 170, 85, 170 }, { 170, 85, 212 }, { 170, 127, 0 }, { 170, 127, 42 }, { 170, 127, 85 }, { 170, 127, 127 }, { 170, 127, 170 }, { 170, 127, 212 }, { 170, 170, 0 }, { 170, 170, 42 }, { 170, 170, 85 }, { 170, 170, 127 }, { 170, 170, 170 }, /* 0.667 188 */ { 170, 170, 212 }, { 170, 212, 0 }, { 170, 212, 42 }, { 170, 212, 85 }, { 170, 212, 127 }, { 170, 212, 170 }, { 170, 212, 212 }, { 212, 0, 0 }, { 212, 0, 42 }, { 212, 0, 85 }, { 212, 0, 127 }, { 212, 0, 170 }, { 212, 0, 212 }, { 212, 42, 0 }, { 212, 42, 42 }, { 212, 42, 85 }, { 212, 42, 127 }, { 212, 42, 170 }, { 212, 42, 212 }, { 212, 85, 0 }, { 212, 85, 42 }, { 212, 85, 85 }, { 212, 85, 127 }, { 212, 85, 170 }, { 212, 85, 212 }, { 212, 127, 0 }, { 212, 127, 42 }, { 212, 127, 85 }, { 212, 127, 127 }, { 212, 127, 170 }, { 212, 127, 212 }, { 212, 170, 0 }, { 212, 170, 42 }, { 212, 170, 85 }, { 212, 170, 127 }, { 212, 170, 170 }, { 212, 170, 212 }, { 212, 212, 0 }, { 212, 212, 42 }, { 212, 212, 85 }, { 212, 212, 127 }, { 212, 212, 170 }, { 212, 212, 212 }, /* 0.831 231 */ { 8, 8, 8 }, /* 0.031 232 */ { 18, 18, 18 }, /* 0.071 233 */ { 28, 28, 28 }, /* 0.110 234 */ { 38, 38, 38 }, /* 0.149 235 */ { 48, 48, 48 }, /* 0.188 236 */ { 58, 58, 58 }, /* 0.227 237 */ { 68, 68, 68 }, /* 0.267 238 */ { 78, 78, 78 }, /* 0.306 239 */ { 88, 88, 88 }, /* 0.345 240 */ { 98, 98, 98 }, /* 0.384 241 */ { 108, 108, 108 }, /* 0.424 242 */ { 118, 118, 118 }, /* 0.463 243 */ { 128, 128, 128 }, /* 0.502 244 */ { 138, 138, 138 }, /* 0.541 245 */ { 148, 148, 148 }, /* 0.580 246 */ { 158, 158, 158 }, /* 0.620 247 */ { 168, 168, 168 }, /* 0.659 248 */ { 178, 178, 178 }, /* 0.698 249 */ { 188, 188, 188 }, /* 0.737 250 */ { 198, 198, 198 }, /* 0.776 251 */ { 208, 208, 208 }, /* 0.816 252 */ { 218, 218, 218 }, /* 0.855 253 */ { 228, 228, 228 }, /* 0.894 254 */ { 238, 238, 238 } /* 0.933 255 */ }; static inline term_color_t rgb_to_color_xterm256 (int r, int g, int b) { rgb_t color; hsv_t hsv; color.red = r; color.green = g; color.blue = b; rgb_to_hsv (color, &hsv); if (hsv.saturation < 0.065f) { /* Greyscale approximation. */ float luminance = color_luminance (r, g, b); if (luminance < 0.015f) return 0; else if (luminance < 0.051f) return 232; else if (luminance < 0.090f) return 233; else if (luminance < 0.129f) return 234; else if (luminance < 0.157f) return 235; else if (luminance < 0.177f) return 59; else if (luminance < 0.207f) return 236; else if (luminance < 0.247f) return 237; else if (luminance < 0.284f) return 238; else if (luminance < 0.304f) return 8; else if (luminance < 0.319f) return 239; else if (luminance < 0.339f) return 102; else if (luminance < 0.364f) return 240; else if (luminance < 0.404f) return 241; else if (luminance < 0.443f) return 242; else if (luminance < 0.480f) return 243; else if (luminance < 0.500f) return 145; else if (luminance < 0.521f) return 244; else if (luminance < 0.560f) return 245; else if (luminance < 0.600f) return 246; else if (luminance < 0.639f) return 247; else if (luminance < 0.663f) return 248; else if (luminance < 0.682f) return 188; else if (luminance < 0.717f) return 249; else if (luminance < 0.756f) return 250; else if (luminance < 0.796f) return 251; else if (luminance < 0.823f) return 252; else if (luminance < 0.843f) return 231; else if (luminance < 0.874f) return 253; else if (luminance < 0.896f) return 254; else if (luminance < 0.915f) return 7; else if (luminance < 0.966f) return 255; else return 15; } else /* Color approximation. */ return nearest_color (color, colors_of_xterm256, 256); } /* ============================= attributes_t ============================= */ /* ANSI C and ISO C99 6.7.2.1.(4) forbid use of bit fields for types other than 'int' or 'unsigned int'. On the other hand, C++ forbids conversion between enum types and integer types without an explicit cast. */ #ifdef __cplusplus # define BITFIELD_TYPE(orig_type,integer_type) orig_type #else # define BITFIELD_TYPE(orig_type,integer_type) integer_type #endif /* Attributes that can be set on a character. */ typedef struct { BITFIELD_TYPE(term_color_t, signed int) color : 9; BITFIELD_TYPE(term_color_t, signed int) bgcolor : 9; BITFIELD_TYPE(term_weight_t, unsigned int) weight : 1; BITFIELD_TYPE(term_posture_t, unsigned int) posture : 1; BITFIELD_TYPE(term_underline_t, unsigned int) underline : 1; } attributes_t; /* ============================ term_ostream_t ============================ */ struct term_ostream : struct ostream { fields: /* The file descriptor used for output. Note that ncurses termcap emulation uses the baud rate information from file descriptor 1 (stdout) if it is a tty, or from file descriptor 2 (stderr) otherwise. */ int fd; char *filename; /* Values from the terminal type's terminfo/termcap description. See terminfo(5) for details. */ /* terminfo termcap */ int max_colors; /* colors Co */ int no_color_video; /* ncv NC */ char *set_a_foreground; /* setaf AF */ char *set_foreground; /* setf Sf */ char *set_a_background; /* setab AB */ char *set_background; /* setb Sb */ char *orig_pair; /* op op */ char *enter_bold_mode; /* bold md */ char *enter_italics_mode; /* sitm ZH */ char *exit_italics_mode; /* ritm ZR */ char *enter_underline_mode; /* smul us */ char *exit_underline_mode; /* rmul ue */ char *exit_attribute_mode; /* sgr0 me */ /* Inferred values. */ bool supports_foreground; bool supports_background; colormodel_t colormodel; bool supports_weight; bool supports_posture; bool supports_underline; /* Variable state. */ char *buffer; /* Buffer for the current line. */ attributes_t *attrbuffer; /* Buffer for the simplified attributes; same length as buffer. */ size_t buflen; /* Number of bytes stored so far. */ size_t allocated; /* Allocated size of the buffer. */ attributes_t curr_attr; /* Current attributes. */ attributes_t simp_attr; /* Simplified current attributes. */ }; /* Simplify attributes, according to the terminal's capabilities. */ static attributes_t simplify_attributes (term_ostream_t stream, attributes_t attr) { if ((attr.color != COLOR_DEFAULT || attr.bgcolor != COLOR_DEFAULT) && stream->no_color_video > 0) { /* When colors and attributes can not be represented simultaneously, we give preference to the color. */ if (stream->no_color_video & 2) /* Colors conflict with underlining. */ attr.underline = UNDERLINE_OFF; if (stream->no_color_video & 32) /* Colors conflict with bold weight. */ attr.weight = WEIGHT_NORMAL; } if (!stream->supports_foreground) attr.color = COLOR_DEFAULT; if (!stream->supports_background) attr.bgcolor = COLOR_DEFAULT; if (!stream->supports_weight) attr.weight = WEIGHT_DEFAULT; if (!stream->supports_posture) attr.posture = POSTURE_DEFAULT; if (!stream->supports_underline) attr.underline = UNDERLINE_DEFAULT; return attr; } /* While a line is being output, we need to be careful to restore the terminal's settings in case of a fatal signal or an exit() call. */ /* File descriptor to which out_char shall output escape sequences. */ static int out_fd = -1; /* Filename of out_fd. */ static const char *out_filename; /* Output a single char to out_fd. Ignore errors. */ static int out_char_unchecked (int c) { char bytes[1]; bytes[0] = (char)c; full_write (out_fd, bytes, 1); return 0; } /* State that informs the exit handler what to do. */ static const char *restore_colors; static const char *restore_weight; static const char *restore_posture; static const char *restore_underline; /* The exit handler. */ static void restore (void) { /* Only do something while some output was interrupted. */ if (out_fd >= 0) { if (restore_colors != NULL) tputs (restore_colors, 1, out_char_unchecked); if (restore_weight != NULL) tputs (restore_weight, 1, out_char_unchecked); if (restore_posture != NULL) tputs (restore_posture, 1, out_char_unchecked); if (restore_underline != NULL) tputs (restore_underline, 1, out_char_unchecked); } } /* The list of signals whose default behaviour is to stop the program. */ static int stopping_signals[] = { #ifdef SIGTSTP SIGTSTP, #endif #ifdef SIGTTIN SIGTTIN, #endif #ifdef SIGTTOU SIGTTOU, #endif 0 }; #define num_stopping_signals (SIZEOF (stopping_signals) - 1) static sigset_t stopping_signal_set; static void init_stopping_signal_set () { static bool stopping_signal_set_initialized = false; if (!stopping_signal_set_initialized) { size_t i; sigemptyset (&stopping_signal_set); for (i = 0; i < num_stopping_signals; i++) sigaddset (&stopping_signal_set, stopping_signals[i]); stopping_signal_set_initialized = true; } } /* Temporarily delay the stopping signals. */ static inline void block_stopping_signals () { init_stopping_signal_set (); sigprocmask (SIG_BLOCK, &stopping_signal_set, NULL); } /* Stop delaying the stopping signals. */ static inline void unblock_stopping_signals () { init_stopping_signal_set (); sigprocmask (SIG_UNBLOCK, &stopping_signal_set, NULL); } /* Compare two sets of attributes for equality. */ static inline bool equal_attributes (attributes_t attr1, attributes_t attr2) { return (attr1.color == attr2.color && attr1.bgcolor == attr2.bgcolor && attr1.weight == attr2.weight && attr1.posture == attr2.posture && attr1.underline == attr2.underline); } /* Signal error after full_write failed. */ static void out_error () { error (EXIT_FAILURE, errno, _("error writing to %s"), out_filename); } /* Output a single char to out_fd. */ static int out_char (int c) { char bytes[1]; bytes[0] = (char)c; /* We have to write directly to the file descriptor, not to a buffer with the same destination, because of the padding and sleeping that tputs() does. */ if (full_write (out_fd, bytes, 1) < 1) out_error (); return 0; } /* Output escape sequences to switch from OLD_ATTR to NEW_ATTR. */ static void out_attr_change (term_ostream_t stream, attributes_t old_attr, attributes_t new_attr) { bool cleared_attributes; /* We don't know the default colors of the terminal. The only way to switch back to a default color is to use stream->orig_pair. */ if ((new_attr.color == COLOR_DEFAULT && old_attr.color != COLOR_DEFAULT) || (new_attr.bgcolor == COLOR_DEFAULT && old_attr.bgcolor != COLOR_DEFAULT)) { assert (stream->supports_foreground || stream->supports_background); tputs (stream->orig_pair, 1, out_char); old_attr.color = COLOR_DEFAULT; old_attr.bgcolor = COLOR_DEFAULT; } /* To turn off WEIGHT_BOLD, the only way is to output the exit_attribute_mode sequence. (With xterm, you can also do it with "Esc [ 0 m", but this escape sequence is not contained in the terminfo description.) It may also clear the colors; this is the case e.g. when TERM="xterm" or TERM="ansi". To turn off UNDERLINE_ON, we can use the exit_underline_mode or the exit_attribute_mode sequence. In the latter case, it will not only turn off UNDERLINE_ON, but also the other attributes, and possibly also the colors. To turn off POSTURE_ITALIC, we can use the exit_italics_mode or the exit_attribute_mode sequence. Again, in the latter case, it will not only turn off POSTURE_ITALIC, but also the other attributes, and possibly also the colors. There is no point in setting an attribute just before emitting an escape sequence that may again turn off the attribute. Therefore we proceed in two steps: First, clear the attributes that need to be cleared; then - taking into account that this may have cleared all attributes and all colors - set the colors and the attributes. The variable 'cleared_attributes' tells whether an escape sequence has been output that may have cleared all attributes and all color settings. */ cleared_attributes = false; if (old_attr.posture != POSTURE_NORMAL && new_attr.posture == POSTURE_NORMAL && stream->exit_italics_mode != NULL) { tputs (stream->exit_italics_mode, 1, out_char); old_attr.posture = POSTURE_NORMAL; cleared_attributes = true; } if (old_attr.underline != UNDERLINE_OFF && new_attr.underline == UNDERLINE_OFF && stream->exit_underline_mode != NULL) { tputs (stream->exit_underline_mode, 1, out_char); old_attr.underline = UNDERLINE_OFF; cleared_attributes = true; } if ((old_attr.weight != WEIGHT_NORMAL && new_attr.weight == WEIGHT_NORMAL) || (old_attr.posture != POSTURE_NORMAL && new_attr.posture == POSTURE_NORMAL /* implies stream->exit_italics_mode == NULL */) || (old_attr.underline != UNDERLINE_OFF && new_attr.underline == UNDERLINE_OFF /* implies stream->exit_underline_mode == NULL */)) { tputs (stream->exit_attribute_mode, 1, out_char); /* We don't know exactly what effects exit_attribute_mode has, but this is the minimum effect: */ old_attr.weight = WEIGHT_NORMAL; if (stream->exit_italics_mode == NULL) old_attr.posture = POSTURE_NORMAL; if (stream->exit_underline_mode == NULL) old_attr.underline = UNDERLINE_OFF; cleared_attributes = true; } /* Turn on the colors. */ if (new_attr.color != old_attr.color || (cleared_attributes && new_attr.color != COLOR_DEFAULT)) { assert (stream->supports_foreground); assert (new_attr.color != COLOR_DEFAULT); switch (stream->colormodel) { case cm_common8: assert (new_attr.color >= 0 && new_attr.color < 8); if (stream->set_a_foreground != NULL) tputs (tparm (stream->set_a_foreground, color_bgr (new_attr.color)), 1, out_char); else tputs (tparm (stream->set_foreground, new_attr.color), 1, out_char); break; /* When we are dealing with an xterm, there is no need to go through tputs() because we know there is no padding and sleeping. */ case cm_xterm8: assert (new_attr.color >= 0 && new_attr.color < 8); { char bytes[5]; bytes[0] = 0x1B; bytes[1] = '['; bytes[2] = '3'; bytes[3] = '0' + new_attr.color; bytes[4] = 'm'; if (full_write (out_fd, bytes, 5) < 5) out_error (); } break; case cm_xterm16: assert (new_attr.color >= 0 && new_attr.color < 16); { char bytes[5]; bytes[0] = 0x1B; bytes[1] = '['; if (new_attr.color < 8) { bytes[2] = '3'; bytes[3] = '0' + new_attr.color; } else { bytes[2] = '9'; bytes[3] = '0' + (new_attr.color - 8); } bytes[4] = 'm'; if (full_write (out_fd, bytes, 5) < 5) out_error (); } break; case cm_xterm88: assert (new_attr.color >= 0 && new_attr.color < 88); { char bytes[10]; char *p; bytes[0] = 0x1B; bytes[1] = '['; bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';'; bytes[5] = '5'; bytes[6] = ';'; p = bytes + 7; if (new_attr.color >= 10) *p++ = '0' + (new_attr.color / 10); *p++ = '0' + (new_attr.color % 10); *p++ = 'm'; if (full_write (out_fd, bytes, p - bytes) < p - bytes) out_error (); } break; case cm_xterm256: assert (new_attr.color >= 0 && new_attr.color < 256); { char bytes[11]; char *p; bytes[0] = 0x1B; bytes[1] = '['; bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';'; bytes[5] = '5'; bytes[6] = ';'; p = bytes + 7; if (new_attr.color >= 100) *p++ = '0' + (new_attr.color / 100); if (new_attr.color >= 10) *p++ = '0' + ((new_attr.color % 100) / 10); *p++ = '0' + (new_attr.color % 10); *p++ = 'm'; if (full_write (out_fd, bytes, p - bytes) < p - bytes) out_error (); } break; default: abort (); } } if (new_attr.bgcolor != old_attr.bgcolor || (cleared_attributes && new_attr.bgcolor != COLOR_DEFAULT)) { assert (stream->supports_background); assert (new_attr.bgcolor != COLOR_DEFAULT); switch (stream->colormodel) { case cm_common8: assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 8); if (stream->set_a_background != NULL) tputs (tparm (stream->set_a_background, color_bgr (new_attr.bgcolor)), 1, out_char); else tputs (tparm (stream->set_background, new_attr.bgcolor), 1, out_char); break; /* When we are dealing with an xterm, there is no need to go through tputs() because we know there is no padding and sleeping. */ case cm_xterm8: assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 8); { char bytes[5]; bytes[0] = 0x1B; bytes[1] = '['; bytes[2] = '4'; bytes[3] = '0' + new_attr.bgcolor; bytes[4] = 'm'; if (full_write (out_fd, bytes, 5) < 5) out_error (); } break; case cm_xterm16: assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 16); { char bytes[6]; bytes[0] = 0x1B; bytes[1] = '['; if (new_attr.bgcolor < 8) { bytes[2] = '4'; bytes[3] = '0' + new_attr.bgcolor; bytes[4] = 'm'; if (full_write (out_fd, bytes, 5) < 5) out_error (); } else { bytes[2] = '1'; bytes[3] = '0'; bytes[4] = '0' + (new_attr.bgcolor - 8); bytes[5] = 'm'; if (full_write (out_fd, bytes, 6) < 6) out_error (); } } break; case cm_xterm88: assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 88); { char bytes[10]; char *p; bytes[0] = 0x1B; bytes[1] = '['; bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';'; bytes[5] = '5'; bytes[6] = ';'; p = bytes + 7; if (new_attr.bgcolor >= 10) *p++ = '0' + (new_attr.bgcolor / 10); *p++ = '0' + (new_attr.bgcolor % 10); *p++ = 'm'; if (full_write (out_fd, bytes, p - bytes) < p - bytes) out_error (); } break; case cm_xterm256: assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 256); { char bytes[11]; char *p; bytes[0] = 0x1B; bytes[1] = '['; bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';'; bytes[5] = '5'; bytes[6] = ';'; p = bytes + 7; if (new_attr.bgcolor >= 100) *p++ = '0' + (new_attr.bgcolor / 100); if (new_attr.bgcolor >= 10) *p++ = '0' + ((new_attr.bgcolor % 100) / 10); *p++ = '0' + (new_attr.bgcolor % 10); *p++ = 'm'; if (full_write (out_fd, bytes, p - bytes) < p - bytes) out_error (); } break; default: abort (); } } if (new_attr.weight != old_attr.weight || (cleared_attributes && new_attr.weight != WEIGHT_DEFAULT)) { assert (stream->supports_weight); assert (new_attr.weight != WEIGHT_DEFAULT); /* This implies: */ assert (new_attr.weight == WEIGHT_BOLD); tputs (stream->enter_bold_mode, 1, out_char); } if (new_attr.posture != old_attr.posture || (cleared_attributes && new_attr.posture != POSTURE_DEFAULT)) { assert (stream->supports_posture); assert (new_attr.posture != POSTURE_DEFAULT); /* This implies: */ assert (new_attr.posture == POSTURE_ITALIC); tputs (stream->enter_italics_mode, 1, out_char); } if (new_attr.underline != old_attr.underline || (cleared_attributes && new_attr.underline != UNDERLINE_DEFAULT)) { assert (stream->supports_underline); assert (new_attr.underline != UNDERLINE_DEFAULT); /* This implies: */ assert (new_attr.underline == UNDERLINE_ON); tputs (stream->enter_underline_mode, 1, out_char); } } /* Output the buffered line atomically. The terminal is assumed to have the default state (regarding colors and attributes) before this call. It is left in default state after this call (regardless of stream->curr_attr). */ static void output_buffer (term_ostream_t stream) { attributes_t default_attr; attributes_t attr; const char *cp; const attributes_t *ap; size_t len; size_t n; default_attr.color = COLOR_DEFAULT; default_attr.bgcolor = COLOR_DEFAULT; default_attr.weight = WEIGHT_DEFAULT; default_attr.posture = POSTURE_DEFAULT; default_attr.underline = UNDERLINE_DEFAULT; attr = default_attr; cp = stream->buffer; ap = stream->attrbuffer; len = stream->buflen; /* See how much we can output without blocking signals. */ for (n = 0; n < len && equal_attributes (ap[n], attr); n++) ; if (n > 0) { if (full_write (stream->fd, cp, n) < n) error (EXIT_FAILURE, errno, _("error writing to %s"), stream->filename); cp += n; ap += n; len -= n; } if (len > 0) { /* Block fatal signals, so that a SIGINT or similar doesn't interrupt us without the possibility of restoring the terminal's state. */ block_fatal_signals (); /* Likewise for SIGTSTP etc. */ block_stopping_signals (); /* Enable the exit handler for restoring the terminal's state. */ restore_colors = (stream->supports_foreground || stream->supports_background ? stream->orig_pair : NULL); restore_weight = (stream->supports_weight ? stream->exit_attribute_mode : NULL); restore_posture = (stream->supports_posture ? (stream->exit_italics_mode != NULL ? stream->exit_italics_mode : stream->exit_attribute_mode) : NULL); restore_underline = (stream->supports_underline ? (stream->exit_underline_mode != NULL ? stream->exit_underline_mode : stream->exit_attribute_mode) : NULL); out_fd = stream->fd; out_filename = stream->filename; while (len > 0) { /* Activate the attributes in *ap. */ out_attr_change (stream, attr, *ap); attr = *ap; /* See how many characters we can output without further attribute changes. */ for (n = 1; n < len && equal_attributes (ap[n], attr); n++) ; if (full_write (stream->fd, cp, n) < n) error (EXIT_FAILURE, errno, _("error writing to %s"), stream->filename); cp += n; ap += n; len -= n; } /* Switch back to the default attributes. */ out_attr_change (stream, attr, default_attr); /* Disable the exit handler. */ out_fd = -1; out_filename = NULL; /* Unblock fatal and stopping signals. */ unblock_stopping_signals (); unblock_fatal_signals (); } stream->buflen = 0; } /* Implementation of ostream_t methods. */ static term_color_t term_ostream::rgb_to_color (term_ostream_t stream, int red, int green, int blue) { switch (stream->colormodel) { case cm_monochrome: return rgb_to_color_monochrome (); case cm_common8: return rgb_to_color_common8 (red, green, blue); case cm_xterm8: return rgb_to_color_xterm8 (red, green, blue); case cm_xterm16: return rgb_to_color_xterm16 (red, green, blue); case cm_xterm88: return rgb_to_color_xterm88 (red, green, blue); case cm_xterm256: return rgb_to_color_xterm256 (red, green, blue); default: abort (); } } static void term_ostream::write_mem (term_ostream_t stream, const void *data, size_t len) { const char *cp = (const char *) data; while (len > 0) { /* Look for the next newline. */ const char *newline = (const char *) memchr (cp, '\n', len); size_t n = (newline != NULL ? newline - cp : len); /* Copy n bytes into the buffer. */ if (n > stream->allocated - stream->buflen) { size_t new_allocated = xmax (xsum (stream->buflen, n), xsum (stream->allocated, stream->allocated)); if (size_overflow_p (new_allocated)) error (EXIT_FAILURE, 0, _("%s: too much output, buffer size overflow"), "term_ostream"); stream->buffer = (char *) xrealloc (stream->buffer, new_allocated); stream->attrbuffer = (attributes_t *) xrealloc (stream->attrbuffer, new_allocated * sizeof (attributes_t)); stream->allocated = new_allocated; } memcpy (stream->buffer + stream->buflen, cp, n); { attributes_t attr = stream->simp_attr; attributes_t *ap = stream->attrbuffer + stream->buflen; attributes_t *ap_end = ap + n; for (; ap < ap_end; ap++) *ap = attr; } stream->buflen += n; if (newline != NULL) { output_buffer (stream); if (full_write (stream->fd, "\n", 1) < 1) error (EXIT_FAILURE, errno, _("error writing to %s"), stream->filename); cp += n + 1; /* cp = newline + 1; */ len -= n + 1; } else break; } } static void term_ostream::flush (term_ostream_t stream) { output_buffer (stream); } static void term_ostream::free (term_ostream_t stream) { term_ostream_flush (stream); free (stream->filename); if (stream->set_a_foreground != NULL) free (stream->set_a_foreground); if (stream->set_foreground != NULL) free (stream->set_foreground); if (stream->set_a_background != NULL) free (stream->set_a_background); if (stream->set_background != NULL) free (stream->set_background); if (stream->orig_pair != NULL) free (stream->orig_pair); if (stream->enter_bold_mode != NULL) free (stream->enter_bold_mode); if (stream->enter_italics_mode != NULL) free (stream->enter_italics_mode); if (stream->exit_italics_mode != NULL) free (stream->exit_italics_mode); if (stream->enter_underline_mode != NULL) free (stream->enter_underline_mode); if (stream->exit_underline_mode != NULL) free (stream->exit_underline_mode); if (stream->exit_attribute_mode != NULL) free (stream->exit_attribute_mode); free (stream->buffer); free (stream); } /* Implementation of term_ostream_t methods. */ static term_color_t term_ostream::get_color (term_ostream_t stream) { return stream->curr_attr.color; } static void term_ostream::set_color (term_ostream_t stream, term_color_t color) { stream->curr_attr.color = color; stream->simp_attr = simplify_attributes (stream, stream->curr_attr); } static term_color_t term_ostream::get_bgcolor (term_ostream_t stream) { return stream->curr_attr.bgcolor; } static void term_ostream::set_bgcolor (term_ostream_t stream, term_color_t color) { stream->curr_attr.bgcolor = color; stream->simp_attr = simplify_attributes (stream, stream->curr_attr); } static term_weight_t term_ostream::get_weight (term_ostream_t stream) { return stream->curr_attr.weight; } static void term_ostream::set_weight (term_ostream_t stream, term_weight_t weight) { stream->curr_attr.weight = weight; stream->simp_attr = simplify_attributes (stream, stream->curr_attr); } static term_posture_t term_ostream::get_posture (term_ostream_t stream) { return stream->curr_attr.posture; } static void term_ostream::set_posture (term_ostream_t stream, term_posture_t posture) { stream->curr_attr.posture = posture; stream->simp_attr = simplify_attributes (stream, stream->curr_attr); } static term_underline_t term_ostream::get_underline (term_ostream_t stream) { return stream->curr_attr.underline; } static void term_ostream::set_underline (term_ostream_t stream, term_underline_t underline) { stream->curr_attr.underline = underline; stream->simp_attr = simplify_attributes (stream, stream->curr_attr); } /* Constructor. */ static inline char * xstrdup0 (const char *str) { if (str == NULL) return NULL; #if HAVE_TERMINFO if (str == (const char *)(-1)) return NULL; #endif return xstrdup (str); } term_ostream_t term_ostream_create (int fd, const char *filename) { term_ostream_t stream = XMALLOC (struct term_ostream_representation); const char *term; stream->base.vtable = &term_ostream_vtable; stream->fd = fd; stream->filename = xstrdup (filename); /* Defaults. */ stream->max_colors = -1; stream->no_color_video = -1; stream->set_a_foreground = NULL; stream->set_foreground = NULL; stream->set_a_background = NULL; stream->set_background = NULL; stream->orig_pair = NULL; stream->enter_bold_mode = NULL; stream->enter_italics_mode = NULL; stream->exit_italics_mode = NULL; stream->enter_underline_mode = NULL; stream->exit_underline_mode = NULL; stream->exit_attribute_mode = NULL; /* Retrieve the terminal type. */ term = getenv ("TERM"); if (term != NULL && term[0] != '\0') { /* When the terminfo function are available, we prefer them over the termcap functions because 1. they don't risk a buffer overflow, 2. on OSF/1, for TERM=xterm, the tiget* functions provide access to the number of colors and the color escape sequences, whereas the tget* functions don't provide them. */ #if HAVE_TERMINFO int err = 1; if (setupterm (term, fd, &err) || err == 1) { /* Retrieve particular values depending on the terminal type. */ stream->max_colors = tigetnum ("colors"); stream->no_color_video = tigetnum ("ncv"); stream->set_a_foreground = xstrdup0 (tigetstr ("setaf")); stream->set_foreground = xstrdup0 (tigetstr ("setf")); stream->set_a_background = xstrdup0 (tigetstr ("setab")); stream->set_background = xstrdup0 (tigetstr ("setb")); stream->orig_pair = xstrdup0 (tigetstr ("op")); stream->enter_bold_mode = xstrdup0 (tigetstr ("bold")); stream->enter_italics_mode = xstrdup0 (tigetstr ("sitm")); stream->exit_italics_mode = xstrdup0 (tigetstr ("ritm")); stream->enter_underline_mode = xstrdup0 (tigetstr ("smul")); stream->exit_underline_mode = xstrdup0 (tigetstr ("rmul")); stream->exit_attribute_mode = xstrdup0 (tigetstr ("sgr0")); } #elif HAVE_TERMCAP struct { char buf[1024]; char canary[4]; } termcapbuf; int retval; /* Call tgetent, being defensive against buffer overflow. */ memcpy (termcapbuf.canary, "CnRy", 4); retval = tgetent (termcapbuf.buf, term); if (memcmp (termcapbuf.canary, "CnRy", 4) != 0) /* Buffer overflow! */ abort (); if (retval > 0) { struct { char buf[1024]; char canary[4]; } termentrybuf; char *termentryptr; /* Prepare for calling tgetstr, being defensive against buffer overflow. ncurses' tgetstr() supports a second argument NULL, but NetBSD's tgetstr() doesn't. */ memcpy (termentrybuf.canary, "CnRz", 4); #define TEBP ((termentryptr = termentrybuf.buf), &termentryptr) /* Retrieve particular values depending on the terminal type. */ stream->max_colors = tgetnum ("Co"); stream->no_color_video = tgetnum ("NC"); stream->set_a_foreground = xstrdup0 (tgetstr ("AF", TEBP)); stream->set_foreground = xstrdup0 (tgetstr ("Sf", TEBP)); stream->set_a_background = xstrdup0 (tgetstr ("AB", TEBP)); stream->set_background = xstrdup0 (tgetstr ("Sb", TEBP)); stream->orig_pair = xstrdup0 (tgetstr ("op", TEBP)); stream->enter_bold_mode = xstrdup0 (tgetstr ("md", TEBP)); stream->enter_italics_mode = xstrdup0 (tgetstr ("ZH", TEBP)); stream->exit_italics_mode = xstrdup0 (tgetstr ("ZR", TEBP)); stream->enter_underline_mode = xstrdup0 (tgetstr ("us", TEBP)); stream->exit_underline_mode = xstrdup0 (tgetstr ("ue", TEBP)); stream->exit_attribute_mode = xstrdup0 (tgetstr ("me", TEBP)); # ifdef __BEOS__ /* The BeOS termcap entry for "beterm" is broken: For "AF" and "AB" it contains balues in terminfo syntax but the system's tparam() function understands only the termcap syntax. */ if (stream->set_a_foreground != NULL && strcmp (stream->set_a_foreground, "\033[3%p1%dm") == 0) { free (stream->set_a_foreground); stream->set_a_foreground = xstrdup ("\033[3%dm"); } if (stream->set_a_background != NULL && strcmp (stream->set_a_background, "\033[4%p1%dm") == 0) { free (stream->set_a_background); stream->set_a_background = xstrdup ("\033[4%dm"); } # endif /* The termcap entry for cygwin is broken: It has no "ncv" value, but bold and underline are actually rendered through colors. */ if (strcmp (term, "cygwin") == 0) stream->no_color_video |= 2 | 32; /* Done with tgetstr. Detect possible buffer overflow. */ #undef TEBP if (memcmp (termentrybuf.canary, "CnRz", 4) != 0) /* Buffer overflow! */ abort (); } #else /* Fallback code for platforms with neither the terminfo nor the termcap functions, such as mingw. Assume the ANSI escape sequences. Extracted through "TERM=ansi infocmp", replacing \E with \033. */ stream->max_colors = 8; stream->no_color_video = 3; stream->set_a_foreground = xstrdup ("\033[3%p1%dm"); stream->set_a_background = xstrdup ("\033[4%p1%dm"); stream->orig_pair = xstrdup ("\033[39;49m"); stream->enter_bold_mode = xstrdup ("\033[1m"); stream->enter_underline_mode = xstrdup ("\033[4m"); stream->exit_underline_mode = xstrdup ("\033[m"); stream->exit_attribute_mode = xstrdup ("\033[0;10m"); #endif /* AIX 4.3.2, IRIX 6.5, HP-UX 11, Solaris 7..10 all lack the description of color capabilities of "xterm" and "xterms" in their terminfo database. But it is important to have color in xterm. So we provide the color capabilities here. */ if (stream->max_colors <= 1 && (strcmp (term, "xterm") == 0 || strcmp (term, "xterms") == 0)) { stream->max_colors = 8; stream->set_a_foreground = xstrdup ("\033[3%p1%dm"); stream->set_a_background = xstrdup ("\033[4%p1%dm"); stream->orig_pair = xstrdup ("\033[39;49m"); } } /* Infer the capabilities. */ stream->supports_foreground = (stream->max_colors >= 8 && (stream->set_a_foreground != NULL || stream->set_foreground != NULL) && stream->orig_pair != NULL); stream->supports_background = (stream->max_colors >= 8 && (stream->set_a_background != NULL || stream->set_background != NULL) && stream->orig_pair != NULL); stream->colormodel = (stream->supports_foreground || stream->supports_background ? (term != NULL && (/* Recognize xterm-16color, xterm-88color, xterm-256color. */ (strlen (term) >= 5 && memcmp (term, "xterm", 5) == 0) || /* Recognize rxvt-16color. */ (strlen (term) >= 4 && memcmp (term, "rxvt", 7) == 0) || /* Recognize konsole-16color. */ (strlen (term) >= 7 && memcmp (term, "konsole", 7) == 0)) ? (stream->max_colors == 256 ? cm_xterm256 : stream->max_colors == 88 ? cm_xterm88 : stream->max_colors == 16 ? cm_xterm16 : cm_xterm8) : cm_common8) : cm_monochrome); stream->supports_weight = (stream->enter_bold_mode != NULL && stream->exit_attribute_mode != NULL); stream->supports_posture = (stream->enter_italics_mode != NULL && (stream->exit_italics_mode != NULL || stream->exit_attribute_mode != NULL)); stream->supports_underline = (stream->enter_underline_mode != NULL && (stream->exit_underline_mode != NULL || stream->exit_attribute_mode != NULL)); /* Initialize the buffer. */ stream->allocated = 120; stream->buffer = XNMALLOC (stream->allocated, char); stream->attrbuffer = XNMALLOC (stream->allocated, attributes_t); stream->buflen = 0; /* Initialize the current attributes. */ stream->curr_attr.color = COLOR_DEFAULT; stream->curr_attr.bgcolor = COLOR_DEFAULT; stream->curr_attr.weight = WEIGHT_DEFAULT; stream->curr_attr.posture = POSTURE_DEFAULT; stream->curr_attr.underline = UNDERLINE_DEFAULT; stream->simp_attr = simplify_attributes (stream, stream->curr_attr); /* Register an exit handler. */ { static bool registered = false; if (!registered) { atexit (restore); registered = true; } } return stream; }