| /* Output stream for attributed text, producing ANSI escape sequences. |
| Copyright (C) 2006-2008, 2017, 2019-2020 Free Software Foundation, Inc. |
| Written by Bruno Haible <bruno@clisp.org>, 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 <https://www.gnu.org/licenses/>. */ |
| |
| #include <config.h> |
| |
| /* Specification. */ |
| #include "term-ostream.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <sys/time.h> |
| #include <string.h> |
| #include <unistd.h> |
| #if HAVE_TCDRAIN |
| # include <termios.h> |
| #endif |
| #if defined _WIN32 || defined __CYGWIN__ /* Windows */ |
| # define HAVE_WINDOWS_CONSOLES 1 |
| # include <windows.h> |
| #endif |
| |
| #include "error.h" |
| #include "full-write.h" |
| #include "get_ppid_of.h" |
| #include "get_progname_of.h" |
| #include "terminfo.h" |
| #include "xalloc.h" |
| #include "xgethostname.h" |
| #include "xsize.h" |
| #if HAVE_WINDOWS_CONSOLES |
| /* Get _get_osfhandle(). */ |
| # if defined _WIN32 && ! defined __CYGWIN__ |
| # include "msvc-nothrow.h" |
| # else |
| # include <io.h> |
| # endif |
| #endif |
| #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 |
| |
| |
| /* =========================== 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. */ |
| cm_xtermrgb /* TERM=xterm-direct, with 256*256*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 (void) |
| { |
| 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 _GL_ASYNC_SAFE 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); |
| } |
| |
| /* ------------------------ cm_xtermrgb color model ------------------------ */ |
| |
| /* We represent a color as an RGB triplet: (r << 16) | (g << 8) | (b << 0), |
| where r, g, b are in the range [0..255]. */ |
| |
| static inline term_color_t |
| rgb_to_color_xtermrgb (int r, int g, int b) |
| { |
| return (r << 16) | (g << 8) | (b << 0); |
| } |
| |
| |
| /* ============================== hyperlink_t ============================== */ |
| |
| /* A hyperlink is a heap-allocated structure that can be assigned to a run |
| of characters. */ |
| typedef struct |
| { |
| /* URL. |
| Should better be <= 2083 bytes long (because of Microsoft Internet |
| Explorer). */ |
| char *ref; |
| /* Id. |
| Used when the same hyperlink persists across newlines. |
| Should better be <= 256 bytes long (because of VTE and iTerm2). */ |
| char *id; |
| /* Same as id, if non-NULL. Or some generated id. */ |
| char *real_id; |
| } hyperlink_t; |
| |
| static inline void |
| free_hyperlink (hyperlink_t *hyperlink) |
| { |
| free (hyperlink->ref); |
| free (hyperlink->real_id); |
| free (hyperlink); |
| } |
| |
| |
| /* ============================= 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 : 25; |
| BITFIELD_TYPE(term_color_t, signed int) bgcolor : 25; |
| 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; |
| /* Hyperlink, or NULL for none. */ |
| hyperlink_t *hyperlink; |
| } attributes_t; |
| |
| /* 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 |
| && attr1.hyperlink == attr2.hyperlink); |
| } |
| |
| |
| /* ============================ EINTR handling ============================ */ |
| |
| /* EINTR handling for tcdrain(). |
| This function can return -1/EINTR even when we don't have any |
| signal handlers set up, namely when we get interrupted via SIGSTOP. */ |
| |
| #if HAVE_TCDRAIN |
| |
| static inline int |
| nonintr_tcdrain (int fd) |
| { |
| int retval; |
| |
| do |
| retval = tcdrain (fd); |
| while (retval < 0 && errno == EINTR); |
| |
| return retval; |
| } |
| |
| #endif |
| |
| |
| /* ============================ 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 volatile fd; |
| #if HAVE_WINDOWS_CONSOLES |
| HANDLE volatile handle; |
| bool volatile is_windows_console; |
| #endif |
| char *filename; |
| ttyctl_t tty_control; |
| /* 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 * volatile set_a_foreground; /* setaf AF */ |
| char * volatile set_foreground; /* setf Sf */ |
| char * volatile set_a_background; /* setab AB */ |
| char * volatile set_background; /* setb Sb */ |
| char *orig_pair; /* op op */ |
| char * volatile enter_bold_mode; /* bold md */ |
| char * volatile enter_italics_mode; /* sitm ZH */ |
| char *exit_italics_mode; /* ritm ZR */ |
| char * volatile enter_underline_mode; /* smul us */ |
| char *exit_underline_mode; /* rmul ue */ |
| char *exit_attribute_mode; /* sgr0 me */ |
| /* Inferred values. */ |
| bool volatile supports_foreground; |
| bool volatile supports_background; |
| colormodel_t volatile colormodel; |
| bool volatile supports_weight; |
| bool volatile supports_posture; |
| bool volatile supports_underline; |
| bool volatile supports_hyperlink; |
| /* Inferred values for the exit handler and the signal handlers. */ |
| const char * volatile restore_colors; |
| const char * volatile restore_weight; |
| const char * volatile restore_posture; |
| const char * volatile restore_underline; |
| const char * volatile restore_hyperlink; |
| /* Signal handling and tty control. */ |
| struct term_style_control_data control_data; |
| /* State for producing hyperlink ids. */ |
| uint32_t hostname_hash; |
| uint64_t start_time; |
| uint32_t id_serial; |
| /* Set of hyperlink_t that are currently in use. */ |
| hyperlink_t **hyperlinks_array; |
| size_t hyperlinks_count; |
| size_t hyperlinks_allocated; |
| /* Variable state, representing past output. */ |
| #if HAVE_WINDOWS_CONSOLES |
| WORD volatile default_console_attributes; |
| WORD volatile current_console_attributes; |
| #endif |
| attributes_t default_attr; /* Default simplified attributes of the |
| terminal. */ |
| attributes_t volatile active_attr; /* Simplified attributes that we have set |
| on the terminal. */ |
| term_color_t volatile active_attr_color; /* Same as active_attr.color, |
| atomically accessible. */ |
| term_color_t volatile active_attr_bgcolor; /* Same as active_attr.bgcolor, |
| atomically accessible. */ |
| hyperlink_t *volatile active_attr_hyperlink; /* Same as active_attr.hyperlink, |
| atomically accessible. */ |
| /* Variable state, representing future output. */ |
| 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. */ |
| }; |
| |
| static struct term_style_control_data * |
| get_control_data (term_ostream_t stream) |
| { |
| return &stream->control_data; |
| } |
| |
| /* 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; |
| if (!stream->supports_hyperlink) |
| attr.hyperlink = NULL; |
| return attr; |
| } |
| |
| /* Generate an id for a hyperlink. */ |
| static char * |
| generate_hyperlink_id (term_ostream_t stream) |
| { |
| /* A UUID would be optimal, but is overkill here. An id of 128 bits |
| (32 hexadecimal digits) should be sufficient. */ |
| static const char hexdigits[16] = |
| { |
| '0', '1', '2', '3', '4', '5', '6', '7', |
| '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' |
| }; |
| char *id = (char *) xmalloc (128 / 4 + 1); |
| uint32_t words[4] = |
| { |
| stream->hostname_hash, |
| (uint32_t) (stream->start_time >> 32), |
| (uint32_t) stream->start_time, |
| stream->id_serial |
| }; |
| char *p = id; |
| unsigned int i; |
| for (i = 0; i < 4; i++) |
| { |
| uint32_t word = words[i]; |
| unsigned int j; |
| for (j = 0; j < 32 / 4; j++) |
| *p++ = hexdigits[(word >> (32 - 4 * (j + 1))) & 0x0f]; |
| } |
| *p = '\0'; |
| stream->id_serial++; |
| return id; |
| } |
| |
| /* Stream that contains information about how the various out_* functions shall |
| do output. */ |
| static term_ostream_t volatile out_stream; |
| |
| /* File descriptor to which out_char and out_char_unchecked shall output escape |
| sequences. |
| Same as (out_stream != NULL ? out_stream->fd : -1). */ |
| static int volatile out_fd = -1; |
| |
| /* Signal error after full_write failed. */ |
| static void |
| out_error (void) |
| { |
| error (EXIT_FAILURE, errno, _("error writing to %s"), out_stream->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 a single char to out_fd. Ignore errors. */ |
| static _GL_ASYNC_SAFE int |
| out_char_unchecked (int c) |
| { |
| char bytes[1]; |
| |
| bytes[0] = (char)c; |
| full_write (out_fd, bytes, 1); |
| return 0; |
| } |
| |
| /* Output escape sequences to switch the foreground color to NEW_COLOR. */ |
| static _GL_ASYNC_SAFE void |
| out_color_change (term_ostream_t stream, term_color_t new_color, |
| bool async_safe) |
| { |
| assert (stream->supports_foreground); |
| assert (new_color != COLOR_DEFAULT); |
| switch (stream->colormodel) |
| { |
| case cm_common8: |
| assert (new_color >= 0 && new_color < 8); |
| #if HAVE_WINDOWS_CONSOLES |
| if (stream->is_windows_console) |
| { |
| /* SetConsoleTextAttribute |
| <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute> |
| <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */ |
| /* Assign to stream->current_console_attributes *before* calling |
| SetConsoleTextAttribute, otherwise async_set_attributes_from_default |
| will not do its job correctly. */ |
| stream->current_console_attributes = |
| (stream->current_console_attributes & ~(7 << 0)) |
| | (new_color << 0); |
| SetConsoleTextAttribute (stream->handle, stream->current_console_attributes); |
| } |
| else |
| #endif |
| { |
| if (stream->set_a_foreground != NULL) |
| tputs (tparm (stream->set_a_foreground, color_bgr (new_color)), |
| 1, async_safe ? out_char_unchecked : out_char); |
| else |
| tputs (tparm (stream->set_foreground, new_color), |
| 1, async_safe ? out_char_unchecked : 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_color >= 0 && new_color < 8); |
| { |
| char bytes[5]; |
| bytes[0] = 0x1B; bytes[1] = '['; |
| bytes[2] = '3'; bytes[3] = '0' + new_color; |
| bytes[4] = 'm'; |
| if (full_write (out_fd, bytes, 5) < 5) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| case cm_xterm16: |
| assert (new_color >= 0 && new_color < 16); |
| { |
| char bytes[5]; |
| bytes[0] = 0x1B; bytes[1] = '['; |
| if (new_color < 8) |
| { |
| bytes[2] = '3'; bytes[3] = '0' + new_color; |
| } |
| else |
| { |
| bytes[2] = '9'; bytes[3] = '0' + (new_color - 8); |
| } |
| bytes[4] = 'm'; |
| if (full_write (out_fd, bytes, 5) < 5) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| case cm_xterm88: |
| assert (new_color >= 0 && new_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_color >= 10) |
| *p++ = '0' + (new_color / 10); |
| *p++ = '0' + (new_color % 10); |
| *p++ = 'm'; |
| if (full_write (out_fd, bytes, p - bytes) < p - bytes) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| case cm_xterm256: |
| assert (new_color >= 0 && new_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_color >= 100) |
| *p++ = '0' + (new_color / 100); |
| if (new_color >= 10) |
| *p++ = '0' + ((new_color % 100) / 10); |
| *p++ = '0' + (new_color % 10); |
| *p++ = 'm'; |
| if (full_write (out_fd, bytes, p - bytes) < p - bytes) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| case cm_xtermrgb: |
| assert (new_color >= 0 && new_color < 0x1000000); |
| { |
| char bytes[19]; |
| char *p; |
| unsigned int r = (new_color >> 16) & 0xff; |
| unsigned int g = (new_color >> 8) & 0xff; |
| unsigned int b = new_color & 0xff; |
| bytes[0] = 0x1B; bytes[1] = '['; |
| bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';'; |
| bytes[5] = '2'; bytes[6] = ';'; |
| p = bytes + 7; |
| if (r >= 100) |
| *p++ = '0' + (r / 100); |
| if (r >= 10) |
| *p++ = '0' + ((r % 100) / 10); |
| *p++ = '0' + (r % 10); |
| *p++ = ';'; |
| if (g >= 100) |
| *p++ = '0' + (g / 100); |
| if (g >= 10) |
| *p++ = '0' + ((g % 100) / 10); |
| *p++ = '0' + (g % 10); |
| *p++ = ';'; |
| if (b >= 100) |
| *p++ = '0' + (b / 100); |
| if (b >= 10) |
| *p++ = '0' + ((b % 100) / 10); |
| *p++ = '0' + (b % 10); |
| *p++ = 'm'; |
| if (full_write (out_fd, bytes, p - bytes) < p - bytes) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| default: |
| abort (); |
| } |
| } |
| |
| /* Output escape sequences to switch the background color to NEW_BGCOLOR. */ |
| static _GL_ASYNC_SAFE void |
| out_bgcolor_change (term_ostream_t stream, term_color_t new_bgcolor, |
| bool async_safe) |
| { |
| assert (stream->supports_background); |
| assert (new_bgcolor != COLOR_DEFAULT); |
| switch (stream->colormodel) |
| { |
| case cm_common8: |
| assert (new_bgcolor >= 0 && new_bgcolor < 8); |
| #if HAVE_WINDOWS_CONSOLES |
| if (stream->is_windows_console) |
| { |
| /* SetConsoleTextAttribute |
| <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute> |
| <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */ |
| /* Assign to stream->current_console_attributes *before* calling |
| SetConsoleTextAttribute, otherwise async_set_attributes_from_default |
| will not do its job correctly. */ |
| stream->current_console_attributes = |
| (stream->current_console_attributes & ~(7 << 4)) |
| | (new_bgcolor << 4); |
| SetConsoleTextAttribute (stream->handle, stream->current_console_attributes); |
| } |
| else |
| #endif |
| { |
| if (stream->set_a_background != NULL) |
| tputs (tparm (stream->set_a_background, color_bgr (new_bgcolor)), |
| 1, async_safe ? out_char_unchecked : out_char); |
| else |
| tputs (tparm (stream->set_background, new_bgcolor), |
| 1, async_safe ? out_char_unchecked : 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_bgcolor >= 0 && new_bgcolor < 8); |
| { |
| char bytes[5]; |
| bytes[0] = 0x1B; bytes[1] = '['; |
| bytes[2] = '4'; bytes[3] = '0' + new_bgcolor; |
| bytes[4] = 'm'; |
| if (full_write (out_fd, bytes, 5) < 5) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| case cm_xterm16: |
| assert (new_bgcolor >= 0 && new_bgcolor < 16); |
| { |
| char bytes[6]; |
| bytes[0] = 0x1B; bytes[1] = '['; |
| if (new_bgcolor < 8) |
| { |
| bytes[2] = '4'; bytes[3] = '0' + new_bgcolor; |
| bytes[4] = 'm'; |
| if (full_write (out_fd, bytes, 5) < 5) |
| if (!async_safe) |
| out_error (); |
| } |
| else |
| { |
| bytes[2] = '1'; bytes[3] = '0'; |
| bytes[4] = '0' + (new_bgcolor - 8); bytes[5] = 'm'; |
| if (full_write (out_fd, bytes, 6) < 6) |
| if (!async_safe) |
| out_error (); |
| } |
| } |
| break; |
| case cm_xterm88: |
| assert (new_bgcolor >= 0 && new_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_bgcolor >= 10) |
| *p++ = '0' + (new_bgcolor / 10); |
| *p++ = '0' + (new_bgcolor % 10); |
| *p++ = 'm'; |
| if (full_write (out_fd, bytes, p - bytes) < p - bytes) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| case cm_xterm256: |
| assert (new_bgcolor >= 0 && new_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_bgcolor >= 100) |
| *p++ = '0' + (new_bgcolor / 100); |
| if (new_bgcolor >= 10) |
| *p++ = '0' + ((new_bgcolor % 100) / 10); |
| *p++ = '0' + (new_bgcolor % 10); |
| *p++ = 'm'; |
| if (full_write (out_fd, bytes, p - bytes) < p - bytes) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| case cm_xtermrgb: |
| assert (new_bgcolor >= 0 && new_bgcolor < 0x1000000); |
| { |
| char bytes[19]; |
| char *p; |
| unsigned int r = (new_bgcolor >> 16) & 0xff; |
| unsigned int g = (new_bgcolor >> 8) & 0xff; |
| unsigned int b = new_bgcolor & 0xff; |
| bytes[0] = 0x1B; bytes[1] = '['; |
| bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';'; |
| bytes[5] = '2'; bytes[6] = ';'; |
| p = bytes + 7; |
| if (r >= 100) |
| *p++ = '0' + (r / 100); |
| if (r >= 10) |
| *p++ = '0' + ((r % 100) / 10); |
| *p++ = '0' + (r % 10); |
| *p++ = ';'; |
| if (g >= 100) |
| *p++ = '0' + (g / 100); |
| if (g >= 10) |
| *p++ = '0' + ((g % 100) / 10); |
| *p++ = '0' + (g % 10); |
| *p++ = ';'; |
| if (b >= 100) |
| *p++ = '0' + (b / 100); |
| if (b >= 10) |
| *p++ = '0' + ((b % 100) / 10); |
| *p++ = '0' + (b % 10); |
| *p++ = 'm'; |
| if (full_write (out_fd, bytes, p - bytes) < p - bytes) |
| if (!async_safe) |
| out_error (); |
| } |
| break; |
| default: |
| abort (); |
| } |
| } |
| |
| /* Output escape sequences to switch the weight to NEW_WEIGHT. */ |
| static _GL_ASYNC_SAFE void |
| out_weight_change (term_ostream_t stream, term_weight_t new_weight, |
| bool async_safe) |
| { |
| assert (stream->supports_weight); |
| assert (new_weight != WEIGHT_DEFAULT); |
| /* This implies: */ |
| assert (new_weight == WEIGHT_BOLD); |
| tputs (stream->enter_bold_mode, |
| 1, async_safe ? out_char_unchecked : out_char); |
| } |
| |
| /* Output escape sequences to switch the posture to NEW_POSTURE. */ |
| static _GL_ASYNC_SAFE void |
| out_posture_change (term_ostream_t stream, term_posture_t new_posture, |
| bool async_safe) |
| { |
| assert (stream->supports_posture); |
| assert (new_posture != POSTURE_DEFAULT); |
| /* This implies: */ |
| assert (new_posture == POSTURE_ITALIC); |
| tputs (stream->enter_italics_mode, |
| 1, async_safe ? out_char_unchecked : out_char); |
| } |
| |
| /* Output escape sequences to switch the underline to NEW_UNDERLINE. */ |
| static _GL_ASYNC_SAFE void |
| out_underline_change (term_ostream_t stream, term_underline_t new_underline, |
| bool async_safe) |
| { |
| assert (stream->supports_underline); |
| assert (new_underline != UNDERLINE_DEFAULT); |
| /* This implies: */ |
| assert (new_underline == UNDERLINE_ON); |
| #if HAVE_WINDOWS_CONSOLES |
| if (stream->is_windows_console) |
| { |
| /* SetConsoleTextAttribute |
| <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute> |
| <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */ |
| /* Assign to stream->current_console_attributes *before* calling |
| SetConsoleTextAttribute, otherwise async_set_attributes_from_default |
| will not do its job correctly. */ |
| stream->current_console_attributes = |
| stream->current_console_attributes | COMMON_LVB_UNDERSCORE; |
| SetConsoleTextAttribute (stream->handle, stream->current_console_attributes); |
| } |
| else |
| #endif |
| { |
| tputs (stream->enter_underline_mode, |
| 1, async_safe ? out_char_unchecked : out_char); |
| } |
| } |
| |
| /* Output escape seqeuences to switch the hyperlink to NEW_HYPERLINK. */ |
| static _GL_ASYNC_SAFE void |
| out_hyperlink_change (term_ostream_t stream, hyperlink_t *new_hyperlink, |
| bool async_safe) |
| { |
| int (*out_ch) (int) = (async_safe ? out_char_unchecked : out_char); |
| assert (stream->supports_hyperlink); |
| if (new_hyperlink != NULL) |
| { |
| assert (new_hyperlink->real_id != NULL); |
| tputs ("\033]8;id=", 1, out_ch); |
| tputs (new_hyperlink->real_id, 1, out_ch); |
| tputs (";", 1, out_ch); |
| tputs (new_hyperlink->ref, 1, out_ch); |
| tputs ("\033\\", 1, out_ch); |
| } |
| else |
| tputs ("\033]8;;\033\\", 1, out_ch); |
| } |
| |
| /* Output escape sequences to switch from STREAM->ACTIVE_ATTR to NEW_ATTR, |
| and update STREAM->ACTIVE_ATTR. */ |
| static void |
| out_attr_change (term_ostream_t stream, attributes_t new_attr) |
| { |
| attributes_t old_attr = stream->active_attr; |
| |
| /* Keep track of the active attributes. Do this *before* emitting the |
| escape sequences, otherwise async_set_attributes_from_default will not |
| do its job correctly. */ |
| stream->active_attr = new_attr; |
| stream->active_attr_color = new_attr.color; |
| stream->active_attr_bgcolor = new_attr.bgcolor; |
| stream->active_attr_hyperlink = new_attr.hyperlink; |
| |
| #if HAVE_WINDOWS_CONSOLES |
| if (stream->is_windows_console) |
| { |
| /* SetConsoleTextAttribute |
| <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute> |
| <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */ |
| /* Assign to stream->current_console_attributes *before* calling |
| SetConsoleTextAttribute, otherwise async_set_attributes_from_default |
| will not do its job correctly. */ |
| stream->current_console_attributes = |
| (stream->current_console_attributes |
| & ~((7 << 0) | (7 << 4) | COMMON_LVB_UNDERSCORE)) |
| | (new_attr.color == COLOR_DEFAULT |
| ? stream->default_console_attributes & (7 << 0) |
| : (new_attr.color << 0)) |
| | (new_attr.bgcolor == COLOR_DEFAULT |
| ? stream->default_console_attributes & (7 << 4) |
| : (new_attr.bgcolor << 4)) |
| | (new_attr.underline ? COMMON_LVB_UNDERSCORE : 0); |
| SetConsoleTextAttribute (stream->handle, stream->current_console_attributes); |
| } |
| else |
| #endif |
| { |
| bool cleared_attributes; |
| |
| /* For out_char to work. */ |
| out_stream = stream; |
| out_fd = stream->fd; |
| |
| /* 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)) |
| { |
| out_color_change (stream, new_attr.color, false); |
| } |
| if (new_attr.bgcolor != old_attr.bgcolor |
| || (cleared_attributes && new_attr.bgcolor != COLOR_DEFAULT)) |
| { |
| out_bgcolor_change (stream, new_attr.bgcolor, false); |
| } |
| if (new_attr.weight != old_attr.weight |
| || (cleared_attributes && new_attr.weight != WEIGHT_DEFAULT)) |
| { |
| out_weight_change (stream, new_attr.weight, false); |
| } |
| if (new_attr.posture != old_attr.posture |
| || (cleared_attributes && new_attr.posture != POSTURE_DEFAULT)) |
| { |
| out_posture_change (stream, new_attr.posture, false); |
| } |
| if (new_attr.underline != old_attr.underline |
| || (cleared_attributes && new_attr.underline != UNDERLINE_DEFAULT)) |
| { |
| out_underline_change (stream, new_attr.underline, false); |
| } |
| if (new_attr.hyperlink != old_attr.hyperlink) |
| { |
| out_hyperlink_change (stream, new_attr.hyperlink, false); |
| } |
| } |
| } |
| |
| static void |
| restore (term_ostream_t stream) |
| { |
| #if HAVE_WINDOWS_CONSOLES |
| if (stream->is_windows_console) |
| { |
| /* SetConsoleTextAttribute |
| <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute> |
| <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */ |
| SetConsoleTextAttribute (stream->handle, stream->default_console_attributes); |
| } |
| else |
| #endif |
| { |
| /* For out_char_unchecked to work. */ |
| out_stream = stream; |
| out_fd = stream->fd; |
| |
| if (stream->restore_colors != NULL) |
| tputs (stream->restore_colors, 1, out_char_unchecked); |
| if (stream->restore_weight != NULL) |
| tputs (stream->restore_weight, 1, out_char_unchecked); |
| if (stream->restore_posture != NULL) |
| tputs (stream->restore_posture, 1, out_char_unchecked); |
| if (stream->restore_underline != NULL) |
| tputs (stream->restore_underline, 1, out_char_unchecked); |
| if (stream->restore_hyperlink != NULL) |
| tputs (stream->restore_hyperlink, 1, out_char_unchecked); |
| } |
| } |
| |
| static _GL_ASYNC_SAFE void |
| async_restore (term_ostream_t stream) |
| { |
| #if HAVE_WINDOWS_CONSOLES |
| if (stream->is_windows_console) |
| { |
| /* SetConsoleTextAttribute |
| <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute> |
| <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */ |
| SetConsoleTextAttribute (stream->handle, stream->default_console_attributes); |
| } |
| else |
| #endif |
| { |
| /* For out_char_unchecked to work. */ |
| out_stream = stream; |
| out_fd = stream->fd; |
| |
| if (stream->restore_colors != NULL) |
| tputs (stream->restore_colors, 1, out_char_unchecked); |
| if (stream->restore_weight != NULL) |
| tputs (stream->restore_weight, 1, out_char_unchecked); |
| if (stream->restore_posture != NULL) |
| tputs (stream->restore_posture, 1, out_char_unchecked); |
| if (stream->restore_underline != NULL) |
| tputs (stream->restore_underline, 1, out_char_unchecked); |
| if (stream->restore_hyperlink != NULL) |
| tputs (stream->restore_hyperlink, 1, out_char_unchecked); |
| } |
| } |
| |
| static _GL_ASYNC_SAFE void |
| async_set_attributes_from_default (term_ostream_t stream) |
| { |
| #if HAVE_WINDOWS_CONSOLES |
| if (stream->is_windows_console) |
| { |
| /* SetConsoleTextAttribute |
| <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute> |
| <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */ |
| SetConsoleTextAttribute (stream->handle, stream->current_console_attributes); |
| } |
| else |
| #endif |
| { |
| attributes_t new_attr = stream->active_attr; |
| /* Since stream->active_attr is not guaranteed to be loaded atomically, |
| new_attr.color and new_attr.bgcolor may have invalid values. |
| Use the atomically loadable values instead. */ |
| new_attr.color = stream->active_attr_color; |
| new_attr.bgcolor = stream->active_attr_bgcolor; |
| new_attr.hyperlink = stream->active_attr_hyperlink; |
| |
| /* For out_char_unchecked to work. */ |
| out_stream = stream; |
| out_fd = stream->fd; |
| |
| if (new_attr.color != COLOR_DEFAULT) |
| out_color_change (stream, new_attr.color, true); |
| if (new_attr.bgcolor != COLOR_DEFAULT) |
| out_bgcolor_change (stream, new_attr.bgcolor, true); |
| if (new_attr.weight != WEIGHT_DEFAULT) |
| out_weight_change (stream, new_attr.weight, true); |
| if (new_attr.posture != POSTURE_DEFAULT) |
| out_posture_change (stream, new_attr.posture, true); |
| if (new_attr.underline != UNDERLINE_DEFAULT) |
| out_underline_change (stream, new_attr.underline, true); |
| if (new_attr.hyperlink != NULL) |
| out_hyperlink_change (stream, new_attr.hyperlink, true); |
| } |
| } |
| |
| static const struct term_style_controller controller = |
| { |
| get_control_data, |
| restore, |
| async_restore, |
| async_set_attributes_from_default |
| }; |
| |
| /* Activate the default attributes. */ |
| static void |
| activate_default_attr (term_ostream_t stream) |
| { |
| /* Switch back to the default attributes. */ |
| out_attr_change (stream, stream->default_attr); |
| |
| deactivate_term_non_default_mode (&controller, stream); |
| } |
| |
| /* Output the buffered line atomically. |
| The terminal is left in the the state (regarding colors and attributes) |
| represented by the simplified attributes goal_attr. */ |
| static void |
| output_buffer (term_ostream_t stream, attributes_t goal_attr) |
| { |
| const char *cp; |
| const attributes_t *ap; |
| size_t len; |
| size_t n; |
| |
| 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], stream->active_attr); n++) |
| ; |
| if (n > 0) |
| { |
| if (full_write (stream->fd, cp, n) < n) |
| { |
| int error_code = errno; |
| /* Do output to stderr only after we have switched back to the |
| default attributes. Otherwise this output may come out with |
| the wrong text attributes. */ |
| if (!equal_attributes (stream->active_attr, stream->default_attr)) |
| activate_default_attr (stream); |
| error (EXIT_FAILURE, error_code, _("error writing to %s"), |
| stream->filename); |
| } |
| cp += n; |
| ap += n; |
| len -= n; |
| } |
| if (len > 0) |
| { |
| if (!equal_attributes (*ap, stream->default_attr)) |
| activate_term_non_default_mode (&controller, stream); |
| |
| do |
| { |
| /* Activate the attributes in *ap. */ |
| out_attr_change (stream, *ap); |
| /* See how many characters we can output without further attribute |
| changes. */ |
| for (n = 1; n < len && equal_attributes (ap[n], stream->active_attr); n++) |
| ; |
| if (full_write (stream->fd, cp, n) < n) |
| { |
| int error_code = errno; |
| /* Do output to stderr only after we have switched back to the |
| default attributes. Otherwise this output may come out with |
| the wrong text attributes. */ |
| if (!equal_attributes (stream->active_attr, stream->default_attr)) |
| activate_default_attr (stream); |
| error (EXIT_FAILURE, error_code, _("error writing to %s"), |
| stream->filename); |
| } |
| cp += n; |
| ap += n; |
| len -= n; |
| } |
| while (len > 0); |
| } |
| stream->buflen = 0; |
| |
| /* Before changing to goal_attr, we may need to enable the non-default |
| attributes mode. */ |
| if (!equal_attributes (goal_attr, stream->default_attr)) |
| activate_term_non_default_mode (&controller, stream); |
| /* Change to goal_attr. */ |
| if (!equal_attributes (goal_attr, stream->active_attr)) |
| out_attr_change (stream, goal_attr); |
| /* When we can deactivate the non-default attributes mode, do so. */ |
| if (equal_attributes (goal_attr, stream->default_attr)) |
| deactivate_term_non_default_mode (&controller, stream); |
| |
| /* Free the hyperlink_t objects that are no longer referenced by the |
| stream->attrbuffer. */ |
| { |
| size_t count = stream->hyperlinks_count; |
| size_t j = 0; |
| size_t i; |
| for (i = 0; i < count; i++) |
| { |
| /* Here 0 <= j <= i. */ |
| hyperlink_t *hyperlink = stream->hyperlinks_array[i]; |
| /* stream->default_attr.hyperlink is always == NULL. |
| stream->simp_attr.hyperlink is either == NULL |
| or == stream->curr_attr.hyperlink. |
| We can therefore ignore both. */ |
| if (hyperlink == stream->curr_attr.hyperlink |
| || hyperlink == stream->active_attr.hyperlink) |
| { |
| /* The hyperlink is still in use. */ |
| stream->hyperlinks_array[j] = hyperlink; |
| j++; |
| } |
| else |
| { |
| /* The hyperlink is not in use any more. */ |
| free_hyperlink (hyperlink); |
| } |
| } |
| stream->hyperlinks_count = j; |
| } |
| } |
| |
| /* 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); |
| case cm_xtermrgb: |
| return rgb_to_color_xtermrgb (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, stream->default_attr); |
| 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, ostream_flush_scope_t scope) |
| { |
| output_buffer (stream, stream->default_attr); |
| if (scope == FLUSH_ALL) |
| { |
| #if HAVE_WINDOWS_CONSOLES |
| if (!stream->is_windows_console) |
| #endif |
| { |
| /* For streams connected to a disk file: */ |
| fsync (stream->fd); |
| #if HAVE_TCDRAIN |
| /* For streams connected to a terminal: */ |
| nonintr_tcdrain (stream->fd); |
| #endif |
| } |
| } |
| } |
| |
| static void |
| term_ostream::free (term_ostream_t stream) |
| { |
| term_ostream_flush (stream, FLUSH_THIS_STREAM); |
| |
| deactivate_term_style_controller (&controller, 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); |
| if (stream->hyperlinks_array != NULL) |
| { |
| size_t count = stream->hyperlinks_count; |
| size_t i; |
| for (i = 0; i < count; i++) |
| free_hyperlink (stream->hyperlinks_array[i]); |
| free (stream->hyperlinks_array); |
| } |
| free (stream->buffer); |
| free (stream->attrbuffer); |
| 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); |
| } |
| |
| static const char * |
| term_ostream::get_hyperlink_ref (term_ostream_t stream) |
| { |
| hyperlink_t *hyperlink = stream->curr_attr.hyperlink; |
| return (hyperlink != NULL ? hyperlink->ref : NULL); |
| } |
| |
| static const char * |
| term_ostream::get_hyperlink_id (term_ostream_t stream) |
| { |
| hyperlink_t *hyperlink = stream->curr_attr.hyperlink; |
| return (hyperlink != NULL ? hyperlink->id : NULL); |
| } |
| |
| static void |
| term_ostream::set_hyperlink (term_ostream_t stream, |
| const char *ref, const char *id) |
| { |
| if (ref == NULL) |
| stream->curr_attr.hyperlink = NULL; |
| else |
| { |
| /* Create a new hyperlink_t object. */ |
| hyperlink_t *hyperlink = XMALLOC (hyperlink_t); |
| |
| hyperlink->ref = xstrdup (ref); |
| if (id != NULL) |
| { |
| hyperlink->id = xstrdup (id); |
| hyperlink->real_id = hyperlink->id; |
| } |
| else |
| { |
| hyperlink->id = NULL; |
| if (stream->supports_hyperlink) |
| { |
| /* Generate an id always, since we don't know at this point |
| whether the hyperlink will span multiple lines. */ |
| hyperlink->real_id = generate_hyperlink_id (stream); |
| } |
| else |
| hyperlink->real_id = NULL; |
| } |
| |
| /* Store it. */ |
| if (stream->hyperlinks_count == stream->hyperlinks_allocated) |
| { |
| stream->hyperlinks_allocated = 2 * stream->hyperlinks_allocated + 10; |
| stream->hyperlinks_array = |
| (hyperlink_t **) |
| xrealloc (stream->hyperlinks_array, |
| stream->hyperlinks_allocated * sizeof (hyperlink_t *)); |
| } |
| stream->hyperlinks_array[stream->hyperlinks_count++] = hyperlink; |
| |
| /* Install it. */ |
| stream->curr_attr.hyperlink = hyperlink; |
| } |
| stream->simp_attr = simplify_attributes (stream, stream->curr_attr); |
| } |
| |
| static void |
| term_ostream::flush_to_current_style (term_ostream_t stream) |
| { |
| output_buffer (stream, stream->simp_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); |
| } |
| |
| /* Returns the base name of the terminal emulator program, possibly truncated, |
| as a freshly allocated string, or NULL if it cannot be determined. |
| Note: This function is a hack. It does not work across ssh, and it may fail |
| in some local situations as well. */ |
| static inline char * |
| get_terminal_emulator_progname (void) |
| { |
| #if HAVE_GETSID |
| /* Get the process id of the session leader. |
| When running in a terminal emulator, it's the shell process that was |
| spawned by the terminal emulator. When running in a console, it's the |
| 'login' process. |
| On some operating systems (Linux, *BSD, AIX), the same result could also |
| be obtained through |
| pid_t p; |
| if (ioctl (1, TIOCGSID, &p) >= 0) ... |
| */ |
| pid_t session_leader_pid = getsid (0); |
| if (session_leader_pid != (pid_t)(-1)) |
| { |
| /* Get the process id of the terminal emulator. |
| When running in a console, it's the process id of the 'init' |
| process. */ |
| pid_t terminal_emulator_pid = get_ppid_of (session_leader_pid); |
| if (terminal_emulator_pid != 0) |
| { |
| /* Retrieve the base name of the program name of this process. */ |
| return get_progname_of (terminal_emulator_pid); |
| } |
| } |
| #endif |
| return NULL; |
| } |
| |
| /* Returns true if we should enable hyperlinks. |
| term is the value of the TERM environment variable. */ |
| static inline bool |
| should_enable_hyperlinks (const char *term) |
| { |
| if (getenv ("NO_TERM_HYPERLINKS") != NULL) |
| /* The user has disabled hyperlinks. */ |
| return false; |
| |
| /* Dispatch based on $TERM. */ |
| if (term != NULL) |
| { |
| /* rxvt-based terminal emulators: |
| Program | TERM | Supports hyperlinks? |
| ------------------+--------------+------------------------------------- |
| rxvt 2.7.10 | rxvt | hangs after "cat hyperlink-demo.txt" |
| mrxvt 0.5.3 | rxvt | no |
| rxvt-unicode 9.22 | rxvt-unicode | no |
| */ |
| if (strcmp (term, "rxvt") == 0) |
| return false; |
| |
| /* Emacs-based terminal emulators: |
| Program | TERM | Supports hyperlinks? |
| --------------------+-------------+--------------------- |
| emacs-terminal 26.1 | eterm-color | produces garbage |
| */ |
| if (strncmp (term, "eterm", 5) == 0) |
| return false; |
| |
| /* xterm-compatible terminal emulators: |
| Program | TERM | Supports hyperlinks? |
| -----------------+----------------+--------------------------- |
| guake 0.8.8 | xterm | produces garbage |
| lilyterm 0.9.9.2 | xterm | produces garbage |
| lterm 1.5.1 | xterm | produces garbage |
| lxterminal 0.3.2 | xterm | produces garbage |
| termit 2.9.6 | xterm | produces garbage |
| konsole 18.12.3 | xterm-256color | produces extra backslashes |
| yakuake 3.0.5 | xterm-256color | produces extra backslashes |
| other | | yes or no, no severe bugs |
| |
| TODO: Revisit this table periodically. |
| */ |
| if (strncmp (term, "xterm", 5) == 0) |
| { |
| char *progname = get_terminal_emulator_progname (); |
| if (progname != NULL) |
| { |
| bool known_buggy = |
| strncmp (progname, "python", 6) == 0 /* guake */ |
| || strcmp (progname, "lilyterm") == 0 |
| || strcmp (progname, "lterm") == 0 |
| || strcmp (progname, "lxterminal") == 0 |
| || strcmp (progname, "termit") == 0 |
| || strcmp (progname, "konsole") == 0 |
| || strcmp (progname, "yakuake") == 0; |
| free (progname); |
| /* Enable hyperlinks except for programs that are known buggy. */ |
| return !known_buggy; |
| } |
| } |
| } |
| |
| /* In case of doubt, enable hyperlinks. So this code does not need to change |
| as more and more terminal emulators support hyperlinks. |
| If there are adverse effects, the user can disable hyperlinks by setting |
| NO_TERM_HYPERLINKS. */ |
| return true; |
| } |
| |
| term_ostream_t |
| term_ostream_create (int fd, const char *filename, ttyctl_t tty_control) |
| { |
| term_ostream_t stream = XMALLOC (struct term_ostream_representation); |
| |
| stream->base.vtable = &term_ostream_vtable; |
| stream->fd = fd; |
| #if HAVE_WINDOWS_CONSOLES |
| stream->handle = (HANDLE) _get_osfhandle (fd); |
| { |
| DWORD mode; |
| |
| if (stream->handle != INVALID_HANDLE_VALUE |
| /* GetConsoleMode |
| <https://docs.microsoft.com/en-us/windows/console/getconsolemode> */ |
| && GetConsoleMode (stream->handle, &mode) != 0) |
| { |
| CONSOLE_SCREEN_BUFFER_INFO info; |
| BOOL ok; |
| |
| /* GetConsoleScreenBufferInfo |
| <https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo> |
| <https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str> */ |
| ok = GetConsoleScreenBufferInfo (stream->handle, &info); |
| if (!ok) |
| { |
| /* GetConsoleScreenBufferInfo |
| - fails when the handle is == GetStdHandle (STD_INPUT_HANDLE) |
| - but succeeds when it is == GetStdHandle (STD_OUTPUT_HANDLE) |
| or == GetStdHandle (STD_ERROR_HANDLE). |
| Native Windows programs use GetStdHandle (STD_OUTPUT_HANDLE) for |
| fd 1, as expected. |
| But Cygwin uses GetStdHandle (STD_INPUT_HANDLE) for all of fd 0, |
| 1, 2. So, we have to use either GetStdHandle (STD_OUTPUT_HANDLE) |
| or GetStdHandle (STD_ERROR_HANDLE) in order to be able to use |
| GetConsoleScreenBufferInfo. */ |
| if (fd == 1 || fd == 2) |
| { |
| HANDLE handle = |
| GetStdHandle (fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); |
| ok = GetConsoleScreenBufferInfo (handle, &info); |
| if (ok) |
| stream->handle = handle; |
| } |
| } |
| if (ok) |
| { |
| stream->is_windows_console = true; |
| stream->default_console_attributes = info.wAttributes; |
| stream->current_console_attributes = stream->default_console_attributes; |
| } |
| else |
| /* It's a console, but we cannot use GetConsoleScreenBufferInfo. */ |
| stream->is_windows_console = false; |
| } |
| else |
| stream->is_windows_console = false; |
| } |
| #endif |
| stream->filename = xstrdup (filename); |
| stream->tty_control = tty_control; |
| |
| /* 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; |
| |
| #if HAVE_WINDOWS_CONSOLES |
| if (stream->is_windows_console) |
| { |
| /* For Windows consoles, two approaches are possible: |
| (A) Use SetConsoleMode |
| <https://docs.microsoft.com/en-us/windows/console/setconsolemode> |
| to enable the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag, and then |
| emit escape sequences, as documented in |
| <https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences>. |
| (B) Use SetConsoleTextAttribute |
| <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute> |
| to change the text attributes. |
| Approach (A) has two drawbacks: |
| * It produces colors that ignore the console's configuration: it |
| assumes the default configuration (light grey foreground). Thus |
| when you ask for cyan, you will always get some blue color, never |
| real cyan. Whereas approach (B) produces colors that respect the |
| "Screen Text" and "Screen Background" settings in the console's |
| configuration. |
| * When the program terminates abnormally, we would leave the console |
| with ENABLE_VIRTUAL_TERMINAL_PROCESSING enabled, which can be |
| dangerous. |
| Therefore we use approach (B). */ |
| stream->max_colors = 8; |
| stream->no_color_video = 1 | 4; |
| stream->supports_foreground = true; |
| stream->supports_background = true; |
| stream->colormodel = cm_common8; |
| /* The Windows consoles have high and low intensity, but the default is |
| high intensity. If we wanted to support WEIGHT_BOLD, we would have to |
| use low-intensity rendering for normal output, which would look ugly |
| compared to the output by other programs. We could support WEIGHT_DIM, |
| but this is not part of our enum term_weight_t. */ |
| stream->supports_weight = false; |
| stream->supports_posture = false; |
| stream->supports_underline = true; |
| stream->supports_hyperlink = false; |
| stream->restore_colors = NULL; |
| stream->restore_weight = NULL; |
| stream->restore_posture = NULL; |
| stream->restore_underline = NULL; |
| stream->restore_hyperlink = NULL; |
| } |
| else |
| #endif |
| { |
| const char *term; |
| |
| /* 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 *-16color. */ |
| (strlen (term) > 8 |
| && strcmp (term + strlen (term) - 8, "-16color") == 0) |
| || /* Recognize *-256color. */ |
| (strlen (term) > 9 |
| && strcmp (term + strlen (term) - 9, "-256color") == 0) |
| || /* Recognize *-direct. */ |
| (strlen (term) > 8 |
| && strcmp (term + strlen (term) - 8, "-direct") == 0)) |
| ? (stream->max_colors >= 0x7fff ? cm_xtermrgb : |
| 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)); |
| /* TODO: Use a terminfo capability, once ncurses implements it. */ |
| stream->supports_hyperlink = should_enable_hyperlinks (term); |
| |
| /* Infer the restore strings. */ |
| stream->restore_colors = |
| (stream->supports_foreground || stream->supports_background |
| ? stream->orig_pair |
| : NULL); |
| stream->restore_weight = |
| (stream->supports_weight ? stream->exit_attribute_mode : NULL); |
| stream->restore_posture = |
| (stream->supports_posture |
| ? (stream->exit_italics_mode != NULL |
| ? stream->exit_italics_mode |
| : stream->exit_attribute_mode) |
| : NULL); |
| stream->restore_underline = |
| (stream->supports_underline |
| ? (stream->exit_underline_mode != NULL |
| ? stream->exit_underline_mode |
| : stream->exit_attribute_mode) |
| : NULL); |
| stream->restore_hyperlink = |
| (stream->supports_hyperlink |
| ? "\033]8;;\033\\" |
| : NULL); |
| } |
| |
| /* Initialize the hyperlink id generator. */ |
| if (stream->supports_hyperlink) |
| { |
| char *hostname = xgethostname (); |
| { /* Compute a hash code, like in gnulib/lib/hash-pjw.c. */ |
| uint32_t h = 0; |
| const char *p; |
| for (p = hostname; *p; p++) |
| h = (unsigned char) *p + ((h << 9) | (h >> (32 - 9))); |
| stream->hostname_hash = h; |
| } |
| free (hostname); |
| |
| { |
| struct timeval tv; |
| gettimeofday (&tv, NULL); |
| stream->start_time = |
| (uint64_t) tv.tv_sec * (uint64_t) 1000000 + (uint64_t) tv.tv_usec; |
| } |
| |
| stream->id_serial = 0; |
| } |
| |
| /* Initialize the set of hyperlink_t. */ |
| stream->hyperlinks_array = NULL; |
| stream->hyperlinks_count = 0; |
| stream->hyperlinks_allocated = 0; |
| |
| /* 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. */ |
| { |
| attributes_t assumed_default; |
| attributes_t simplified_default; |
| |
| assumed_default.color = COLOR_DEFAULT; |
| assumed_default.bgcolor = COLOR_DEFAULT; |
| assumed_default.weight = WEIGHT_DEFAULT; |
| assumed_default.posture = POSTURE_DEFAULT; |
| assumed_default.underline = UNDERLINE_DEFAULT; |
| assumed_default.hyperlink = NULL; |
| |
| simplified_default = simplify_attributes (stream, assumed_default); |
| |
| stream->default_attr = simplified_default; |
| stream->active_attr = simplified_default; |
| stream->curr_attr = assumed_default; |
| stream->simp_attr = simplified_default; |
| } |
| |
| /* Prepare tty control. */ |
| activate_term_style_controller (&controller, stream, fd, tty_control); |
| |
| return stream; |
| } |
| |
| /* Accessors. */ |
| |
| static int |
| term_ostream::get_descriptor (term_ostream_t stream) |
| { |
| return stream->fd; |
| } |
| |
| static const char * |
| term_ostream::get_filename (term_ostream_t stream) |
| { |
| return stream->filename; |
| } |
| |
| static ttyctl_t |
| term_ostream::get_tty_control (term_ostream_t stream) |
| { |
| return stream->tty_control; |
| } |
| |
| static ttyctl_t |
| term_ostream::get_effective_tty_control (term_ostream_t stream) |
| { |
| return stream->control_data.tty_control; |
| } |
| |
| /* Instanceof test. */ |
| |
| bool |
| is_instance_of_term_ostream (ostream_t stream) |
| { |
| return IS_INSTANCE (stream, ostream, term_ostream); |
| } |