1 /* Output stream for attributed text, producing ANSI escape sequences.
2    Copyright (C) 2006-2008, 2017, 2019-2020 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2006.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 #include <config.h>
19 
20 /* Specification.  */
21 #include "term-ostream.h"
22 
23 #include <assert.h>
24 #include <errno.h>
25 #include <stdbool.h>
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <sys/time.h>
29 #include <string.h>
30 #include <unistd.h>
31 #if HAVE_TCDRAIN
32 # include <termios.h>
33 #endif
34 #if defined _WIN32 || defined __CYGWIN__ /* Windows */
35 # define HAVE_WINDOWS_CONSOLES 1
36 # include <windows.h>
37 #endif
38 
39 #include "error.h"
40 #include "full-write.h"
41 #include "get_ppid_of.h"
42 #include "get_progname_of.h"
43 #include "terminfo.h"
44 #include "xalloc.h"
45 #include "xgethostname.h"
46 #include "xsize.h"
47 #if HAVE_WINDOWS_CONSOLES
48 /* Get _get_osfhandle().  */
49 # if defined _WIN32 && ! defined __CYGWIN__
50 #  include "msvc-nothrow.h"
51 # else
52 #  include <io.h>
53 # endif
54 #endif
55 #include "gettext.h"
56 
57 #define _(str) gettext (str)
58 
59 #if HAVE_TPARAM
60 /* GNU termcap's tparam() function requires a buffer argument.  Make it so
61    large that there is no risk that tparam() needs to call malloc().  */
62 static char tparambuf[100];
63 /* Define tparm in terms of tparam.  In the scope of this file, it is called
64    with at most one argument after the string.  */
65 # define tparm(str, arg1) \
66   tparam (str, tparambuf, sizeof (tparambuf), arg1)
67 #endif
68 
69 
70 /* =========================== Color primitives =========================== */
71 
72 /* A color in RGB format.  */
73 typedef struct
74 {
75   unsigned int red   : 8; /* range 0..255 */
76   unsigned int green : 8; /* range 0..255 */
77   unsigned int blue  : 8; /* range 0..255 */
78 } rgb_t;
79 
80 /* A color in HSV (a.k.a. HSB) format.  */
81 typedef struct
82 {
83   float hue;        /* normalized to interval [0,6) */
84   float saturation; /* normalized to interval [0,1] */
85   float brightness; /* a.k.a. value, normalized to interval [0,1] */
86 } hsv_t;
87 
88 /* Conversion of a color in RGB to HSV format.  */
89 static void
rgb_to_hsv(rgb_t c,hsv_t * result)90 rgb_to_hsv (rgb_t c, hsv_t *result)
91 {
92   unsigned int r = c.red;
93   unsigned int g = c.green;
94   unsigned int b = c.blue;
95 
96   if (r > g)
97     {
98       if (b > r)
99         {
100           /* b > r > g, so max = b, min = g */
101           result->hue = 4.0f + (float) (r - g) / (float) (b - g);
102           result->saturation = 1.0f - (float) g / (float) b;
103           result->brightness = (float) b / 255.0f;
104         }
105       else if (b <= g)
106         {
107           /* r > g >= b, so max = r, min = b */
108           result->hue = 0.0f + (float) (g - b) / (float) (r - b);
109           result->saturation = 1.0f - (float) b / (float) r;
110           result->brightness = (float) r / 255.0f;
111         }
112       else
113         {
114           /* r >= b > g, so max = r, min = g */
115           result->hue = 6.0f - (float) (b - g) / (float) (r - g);
116           result->saturation = 1.0f - (float) g / (float) r;
117           result->brightness = (float) r / 255.0f;
118         }
119     }
120   else
121     {
122       if (b > g)
123         {
124           /* b > g >= r, so max = b, min = r */
125           result->hue = 4.0f - (float) (g - r) / (float) (b - r);
126           result->saturation = 1.0f - (float) r / (float) b;
127           result->brightness = (float) b / 255.0f;
128         }
129       else if (b < r)
130         {
131           /* g >= r > b, so max = g, min = b */
132           result->hue = 2.0f - (float) (r - b) / (float) (g - b);
133           result->saturation = 1.0f - (float) b / (float) g;
134           result->brightness = (float) g / 255.0f;
135         }
136       else if (g > r)
137         {
138           /* g >= b >= r, g > r, so max = g, min = r */
139           result->hue = 2.0f + (float) (b - r) / (float) (g - r);
140           result->saturation = 1.0f - (float) r / (float) g;
141           result->brightness = (float) g / 255.0f;
142         }
143       else
144         {
145           /* r = g = b.  A grey color.  */
146           result->hue = 0; /* arbitrary */
147           result->saturation = 0;
148           result->brightness = (float) r / 255.0f;
149         }
150     }
151 }
152 
153 /* Square of distance of two colors.  */
154 static float
color_distance(const hsv_t * color1,const hsv_t * color2)155 color_distance (const hsv_t *color1, const hsv_t *color2)
156 {
157 #if 0
158   /* Formula taken from "John Smith: Color Similarity",
159        http://www.ctr.columbia.edu/~jrsmith/html/pubs/acmmm96/node8.html.  */
160   float angle1 = color1->hue * 1.04719755f; /* normalize to [0,2π] */
161   float angle2 = color2->hue * 1.04719755f; /* normalize to [0,2π] */
162   float delta_x = color1->saturation * cosf (angle1)
163                   - color2->saturation * cosf (angle2);
164   float delta_y = color1->saturation * sinf (angle1)
165                   - color2->saturation * sinf (angle2);
166   float delta_v = color1->brightness
167                   - color2->brightness;
168 
169   return delta_x * delta_x + delta_y * delta_y + delta_v * delta_v;
170 #else
171   /* Formula that considers hue differences with more weight than saturation
172      or brightness differences, like the human eye does.  */
173   float delta_hue =
174     (color1->hue >= color2->hue
175      ? (color1->hue - color2->hue >= 3.0f
176         ? 6.0f + color2->hue - color1->hue
177         : color1->hue - color2->hue)
178      : (color2->hue - color1->hue >= 3.0f
179         ? 6.0f + color1->hue - color2->hue
180         : color2->hue - color1->hue));
181   float min_saturation =
182     (color1->saturation < color2->saturation
183      ? color1->saturation
184      : color2->saturation);
185   float delta_saturation = color1->saturation - color2->saturation;
186   float delta_brightness = color1->brightness - color2->brightness;
187 
188   return delta_hue * delta_hue * min_saturation
189          + delta_saturation * delta_saturation * 0.2f
190          + delta_brightness * delta_brightness * 0.8f;
191 #endif
192 }
193 
194 /* Return the index of the color in a color table that is nearest to a given
195    color.  */
196 static unsigned int
nearest_color(rgb_t given,const rgb_t * table,unsigned int table_size)197 nearest_color (rgb_t given, const rgb_t *table, unsigned int table_size)
198 {
199   hsv_t given_hsv;
200   unsigned int best_index;
201   float best_distance;
202   unsigned int i;
203 
204   assert (table_size > 0);
205 
206   rgb_to_hsv (given, &given_hsv);
207 
208   best_index = 0;
209   best_distance = 1000000.0f;
210   for (i = 0; i < table_size; i++)
211     {
212       hsv_t i_hsv;
213 
214       rgb_to_hsv (table[i], &i_hsv);
215 
216       /* Avoid converting a color to grey, or fading out a color too much.  */
217       if (i_hsv.saturation > given_hsv.saturation * 0.5f)
218         {
219           float distance = color_distance (&given_hsv, &i_hsv);
220           if (distance < best_distance)
221             {
222               best_index = i;
223               best_distance = distance;
224             }
225         }
226     }
227 
228 #if 0 /* Debugging code */
229   hsv_t best_hsv;
230   rgb_to_hsv (table[best_index], &best_hsv);
231   fprintf (stderr, "nearest: (%d,%d,%d) = (%f,%f,%f)\n    -> (%f,%f,%f) = (%d,%d,%d)\n",
232                    given.red, given.green, given.blue,
233                    (double)given_hsv.hue, (double)given_hsv.saturation, (double)given_hsv.brightness,
234                    (double)best_hsv.hue, (double)best_hsv.saturation, (double)best_hsv.brightness,
235                    table[best_index].red, table[best_index].green, table[best_index].blue);
236 #endif
237 
238   return best_index;
239 }
240 
241 /* The luminance of a color.  This is the brightness of the color, as it
242    appears to the human eye.  This must be used in color to grey conversion.  */
243 static float
color_luminance(int r,int g,int b)244 color_luminance (int r, int g, int b)
245 {
246   /* Use the luminance model used by NTSC and JPEG.
247      Taken from http://www.fho-emden.de/~hoffmann/gray10012001.pdf .
248      No need to care about rounding errors leading to luminance > 1;
249      this cannot happen.  */
250   return (0.299f * r + 0.587f * g + 0.114f * b) / 255.0f;
251 }
252 
253 
254 /* ============================= Color models ============================= */
255 
256 /* The color model used by the terminal.  */
257 typedef enum
258 {
259   cm_monochrome,        /* No colors.  */
260   cm_common8,           /* Usual terminal with at least 8 colors.  */
261   cm_xterm8,            /* TERM=xterm, with 8 colors.  */
262   cm_xterm16,           /* TERM=xterm-16color, with 16 colors.  */
263   cm_xterm88,           /* TERM=xterm-88color, with 88 colors.  */
264   cm_xterm256,          /* TERM=xterm-256color, with 256 colors.  */
265   cm_xtermrgb           /* TERM=xterm-direct, with 256*256*256 colors.  */
266 } colormodel_t;
267 
268 /* ----------------------- cm_monochrome color model ----------------------- */
269 
270 /* A non-default color index doesn't exist in this color model.  */
271 static inline term_color_t
rgb_to_color_monochrome(void)272 rgb_to_color_monochrome (void)
273 {
274   return COLOR_DEFAULT;
275 }
276 
277 /* ------------------------ cm_common8 color model ------------------------ */
278 
279 /* A non-default color index is in the range 0..7.
280                        RGB components
281    COLOR_BLACK         000
282    COLOR_BLUE          001
283    COLOR_GREEN         010
284    COLOR_CYAN          011
285    COLOR_RED           100
286    COLOR_MAGENTA       101
287    COLOR_YELLOW        110
288    COLOR_WHITE         111 */
289 static const rgb_t colors_of_common8[8] =
290 {
291   /* R    G    B        grey  index */
292   {   0,   0,   0 }, /* 0.000   0 */
293   {   0,   0, 255 },
294   {   0, 255,   0 },
295   {   0, 255, 255 },
296   { 255,   0,   0 },
297   { 255,   0, 255 },
298   { 255, 255,   0 },
299   { 255, 255, 255 }  /* 1.000   7 */
300 };
301 
302 static inline term_color_t
rgb_to_color_common8(int r,int g,int b)303 rgb_to_color_common8 (int r, int g, int b)
304 {
305   rgb_t color;
306   hsv_t hsv;
307 
308   color.red = r; color.green = g; color.blue = b;
309   rgb_to_hsv (color, &hsv);
310 
311   if (hsv.saturation < 0.065f)
312     {
313       /* Greyscale approximation.  */
314       float luminance = color_luminance (r, g, b);
315       if (luminance < 0.500f)
316         return 0;
317       else
318         return 7;
319     }
320   else
321     /* Color approximation.  */
322     return nearest_color (color, colors_of_common8, 8);
323 }
324 
325 /* Convert a cm_common8 color in RGB encoding to BGR encoding.
326    See the ncurses terminfo(5) manual page, section "Color Handling", for an
327    explanation why this is needed.  */
328 static _GL_ASYNC_SAFE inline int
color_bgr(term_color_t color)329 color_bgr (term_color_t color)
330 {
331   return ((color & 4) >> 2) | (color & 2) | ((color & 1) << 2);
332 }
333 
334 /* ------------------------- cm_xterm8 color model ------------------------- */
335 
336 /* A non-default color index is in the range 0..7.
337                        BGR components
338    COLOR_BLACK         000
339    COLOR_RED           001
340    COLOR_GREEN         010
341    COLOR_YELLOW        011
342    COLOR_BLUE          100
343    COLOR_MAGENTA       101
344    COLOR_CYAN          110
345    COLOR_WHITE         111 */
346 static const rgb_t colors_of_xterm8[8] =
347 {
348   /* The real xterm's colors are dimmed; assume full-brightness instead.  */
349   /* R    G    B        grey  index */
350   {   0,   0,   0 }, /* 0.000   0 */
351   { 255,   0,   0 },
352   {   0, 255,   0 },
353   { 255, 255,   0 },
354   {   0,   0, 255 },
355   { 255,   0, 255 },
356   {   0, 255, 255 },
357   { 255, 255, 255 }  /* 1.000   7 */
358 };
359 
360 static inline term_color_t
rgb_to_color_xterm8(int r,int g,int b)361 rgb_to_color_xterm8 (int r, int g, int b)
362 {
363   rgb_t color;
364   hsv_t hsv;
365 
366   color.red = r; color.green = g; color.blue = b;
367   rgb_to_hsv (color, &hsv);
368 
369   if (hsv.saturation < 0.065f)
370     {
371       /* Greyscale approximation.  */
372       float luminance = color_luminance (r, g, b);
373       if (luminance < 0.500f)
374         return 0;
375       else
376         return 7;
377     }
378   else
379     /* Color approximation.  */
380     return nearest_color (color, colors_of_xterm8, 8);
381 }
382 
383 /* ------------------------ cm_xterm16 color model ------------------------ */
384 
385 /* A non-default color index is in the range 0..15.
386    The RGB values come from xterm's XTerm-col.ad.  */
387 static const rgb_t colors_of_xterm16[16] =
388 {
389   /* R    G    B        grey  index */
390   {   0,   0,   0 }, /* 0.000   0 */
391   { 205,   0,   0 },
392   {   0, 205,   0 },
393   { 205, 205,   0 },
394   {   0,   0, 205 },
395   { 205,   0, 205 },
396   {   0, 205, 205 },
397   { 229, 229, 229 }, /* 0.898   7 */
398   {  77,  77,  77 }, /* 0.302   8 */
399   { 255,   0,   0 },
400   {   0, 255,   0 },
401   { 255, 255,   0 },
402   {   0,   0, 255 },
403   { 255,   0, 255 },
404   {   0, 255, 255 },
405   { 255, 255, 255 }  /* 1.000  15 */
406 };
407 
408 static inline term_color_t
rgb_to_color_xterm16(int r,int g,int b)409 rgb_to_color_xterm16 (int r, int g, int b)
410 {
411   rgb_t color;
412   hsv_t hsv;
413 
414   color.red = r; color.green = g; color.blue = b;
415   rgb_to_hsv (color, &hsv);
416 
417   if (hsv.saturation < 0.065f)
418     {
419       /* Greyscale approximation.  */
420       float luminance = color_luminance (r, g, b);
421       if (luminance < 0.151f)
422         return 0;
423       else if (luminance < 0.600f)
424         return 8;
425       else if (luminance < 0.949f)
426         return 7;
427       else
428         return 15;
429     }
430   else
431     /* Color approximation.  */
432     return nearest_color (color, colors_of_xterm16, 16);
433 }
434 
435 /* ------------------------ cm_xterm88 color model ------------------------ */
436 
437 /* A non-default color index is in the range 0..87.
438    Colors 0..15 are the same as in the cm_xterm16 color model.
439    Colors 16..87 are defined in xterm's 88colres.h.  */
440 
441 static const rgb_t colors_of_xterm88[88] =
442 {
443   /* R    G    B        grey  index */
444   {   0,   0,   0 }, /* 0.000   0 */
445   { 205,   0,   0 },
446   {   0, 205,   0 },
447   { 205, 205,   0 },
448   {   0,   0, 205 },
449   { 205,   0, 205 },
450   {   0, 205, 205 },
451   { 229, 229, 229 }, /* 0.898   7 */
452   {  77,  77,  77 }, /* 0.302   8 */
453   { 255,   0,   0 },
454   {   0, 255,   0 },
455   { 255, 255,   0 },
456   {   0,   0, 255 },
457   { 255,   0, 255 },
458   {   0, 255, 255 },
459   { 255, 255, 255 }, /* 1.000  15 */
460   {   0,   0,   0 }, /* 0.000  16 */
461   {   0,   0, 139 },
462   {   0,   0, 205 },
463   {   0,   0, 255 },
464   {   0, 139,   0 },
465   {   0, 139, 139 },
466   {   0, 139, 205 },
467   {   0, 139, 255 },
468   {   0, 205,   0 },
469   {   0, 205, 139 },
470   {   0, 205, 205 },
471   {   0, 205, 255 },
472   {   0, 255,   0 },
473   {   0, 255, 139 },
474   {   0, 255, 205 },
475   {   0, 255, 255 },
476   { 139,   0,   0 },
477   { 139,   0, 139 },
478   { 139,   0, 205 },
479   { 139,   0, 255 },
480   { 139, 139,   0 },
481   { 139, 139, 139 }, /* 0.545  37 */
482   { 139, 139, 205 },
483   { 139, 139, 255 },
484   { 139, 205,   0 },
485   { 139, 205, 139 },
486   { 139, 205, 205 },
487   { 139, 205, 255 },
488   { 139, 255,   0 },
489   { 139, 255, 139 },
490   { 139, 255, 205 },
491   { 139, 255, 255 },
492   { 205,   0,   0 },
493   { 205,   0, 139 },
494   { 205,   0, 205 },
495   { 205,   0, 255 },
496   { 205, 139,   0 },
497   { 205, 139, 139 },
498   { 205, 139, 205 },
499   { 205, 139, 255 },
500   { 205, 205,   0 },
501   { 205, 205, 139 },
502   { 205, 205, 205 }, /* 0.804  58 */
503   { 205, 205, 255 },
504   { 205, 255,   0 },
505   { 205, 255, 139 },
506   { 205, 255, 205 },
507   { 205, 255, 255 },
508   { 255,   0,   0 },
509   { 255,   0, 139 },
510   { 255,   0, 205 },
511   { 255,   0, 255 },
512   { 255, 139,   0 },
513   { 255, 139, 139 },
514   { 255, 139, 205 },
515   { 255, 139, 255 },
516   { 255, 205,   0 },
517   { 255, 205, 139 },
518   { 255, 205, 205 },
519   { 255, 205, 255 },
520   { 255, 255,   0 },
521   { 255, 255, 139 },
522   { 255, 255, 205 },
523   { 255, 255, 255 }, /* 1.000  79 */
524   {  46,  46,  46 }, /* 0.180  80 */
525   {  92,  92,  92 }, /* 0.361  81 */
526   { 115, 115, 115 }, /* 0.451  82 */
527   { 139, 139, 139 }, /* 0.545  83 */
528   { 162, 162, 162 }, /* 0.635  84 */
529   { 185, 185, 185 }, /* 0.725  85 */
530   { 208, 208, 208 }, /* 0.816  86 */
531   { 231, 231, 231 }  /* 0.906  87 */
532 };
533 
534 static inline term_color_t
rgb_to_color_xterm88(int r,int g,int b)535 rgb_to_color_xterm88 (int r, int g, int b)
536 {
537   rgb_t color;
538   hsv_t hsv;
539 
540   color.red = r; color.green = g; color.blue = b;
541   rgb_to_hsv (color, &hsv);
542 
543   if (hsv.saturation < 0.065f)
544     {
545       /* Greyscale approximation.  */
546       float luminance = color_luminance (r, g, b);
547       if (luminance < 0.090f)
548         return 0;
549       else if (luminance < 0.241f)
550         return 80;
551       else if (luminance < 0.331f)
552         return 8;
553       else if (luminance < 0.406f)
554         return 81;
555       else if (luminance < 0.498f)
556         return 82;
557       else if (luminance < 0.585f)
558         return 37;
559       else if (luminance < 0.680f)
560         return 84;
561       else if (luminance < 0.764f)
562         return 85;
563       else if (luminance < 0.810f)
564         return 58;
565       else if (luminance < 0.857f)
566         return 86;
567       else if (luminance < 0.902f)
568         return 7;
569       else if (luminance < 0.953f)
570         return 87;
571       else
572         return 15;
573     }
574   else
575     /* Color approximation.  */
576     return nearest_color (color, colors_of_xterm88, 88);
577 }
578 
579 /* ------------------------ cm_xterm256 color model ------------------------ */
580 
581 /* A non-default color index is in the range 0..255.
582    Colors 0..15 are the same as in the cm_xterm16 color model.
583    Colors 16..255 are defined in xterm's 256colres.h.  */
584 
585 static const rgb_t colors_of_xterm256[256] =
586 {
587   /* R    G    B        grey  index */
588   {   0,   0,   0 }, /* 0.000   0 */
589   { 205,   0,   0 },
590   {   0, 205,   0 },
591   { 205, 205,   0 },
592   {   0,   0, 205 },
593   { 205,   0, 205 },
594   {   0, 205, 205 },
595   { 229, 229, 229 }, /* 0.898   7 */
596   {  77,  77,  77 }, /* 0.302   8 */
597   { 255,   0,   0 },
598   {   0, 255,   0 },
599   { 255, 255,   0 },
600   {   0,   0, 255 },
601   { 255,   0, 255 },
602   {   0, 255, 255 },
603   { 255, 255, 255 }, /* 1.000  15 */
604   {   0,   0,   0 }, /* 0.000  16 */
605   {   0,   0,  42 },
606   {   0,   0,  85 },
607   {   0,   0, 127 },
608   {   0,   0, 170 },
609   {   0,   0, 212 },
610   {   0,  42,   0 },
611   {   0,  42,  42 },
612   {   0,  42,  85 },
613   {   0,  42, 127 },
614   {   0,  42, 170 },
615   {   0,  42, 212 },
616   {   0,  85,   0 },
617   {   0,  85,  42 },
618   {   0,  85,  85 },
619   {   0,  85, 127 },
620   {   0,  85, 170 },
621   {   0,  85, 212 },
622   {   0, 127,   0 },
623   {   0, 127,  42 },
624   {   0, 127,  85 },
625   {   0, 127, 127 },
626   {   0, 127, 170 },
627   {   0, 127, 212 },
628   {   0, 170,   0 },
629   {   0, 170,  42 },
630   {   0, 170,  85 },
631   {   0, 170, 127 },
632   {   0, 170, 170 },
633   {   0, 170, 212 },
634   {   0, 212,   0 },
635   {   0, 212,  42 },
636   {   0, 212,  85 },
637   {   0, 212, 127 },
638   {   0, 212, 170 },
639   {   0, 212, 212 },
640   {  42,   0,   0 },
641   {  42,   0,  42 },
642   {  42,   0,  85 },
643   {  42,   0, 127 },
644   {  42,   0, 170 },
645   {  42,   0, 212 },
646   {  42,  42,   0 },
647   {  42,  42,  42 }, /* 0.165  59 */
648   {  42,  42,  85 },
649   {  42,  42, 127 },
650   {  42,  42, 170 },
651   {  42,  42, 212 },
652   {  42,  85,   0 },
653   {  42,  85,  42 },
654   {  42,  85,  85 },
655   {  42,  85, 127 },
656   {  42,  85, 170 },
657   {  42,  85, 212 },
658   {  42, 127,   0 },
659   {  42, 127,  42 },
660   {  42, 127,  85 },
661   {  42, 127, 127 },
662   {  42, 127, 170 },
663   {  42, 127, 212 },
664   {  42, 170,   0 },
665   {  42, 170,  42 },
666   {  42, 170,  85 },
667   {  42, 170, 127 },
668   {  42, 170, 170 },
669   {  42, 170, 212 },
670   {  42, 212,   0 },
671   {  42, 212,  42 },
672   {  42, 212,  85 },
673   {  42, 212, 127 },
674   {  42, 212, 170 },
675   {  42, 212, 212 },
676   {  85,   0,   0 },
677   {  85,   0,  42 },
678   {  85,   0,  85 },
679   {  85,   0, 127 },
680   {  85,   0, 170 },
681   {  85,   0, 212 },
682   {  85,  42,   0 },
683   {  85,  42,  42 },
684   {  85,  42,  85 },
685   {  85,  42, 127 },
686   {  85,  42, 170 },
687   {  85,  42, 212 },
688   {  85,  85,   0 },
689   {  85,  85,  42 },
690   {  85,  85,  85 }, /* 0.333 102 */
691   {  85,  85, 127 },
692   {  85,  85, 170 },
693   {  85,  85, 212 },
694   {  85, 127,   0 },
695   {  85, 127,  42 },
696   {  85, 127,  85 },
697   {  85, 127, 127 },
698   {  85, 127, 170 },
699   {  85, 127, 212 },
700   {  85, 170,   0 },
701   {  85, 170,  42 },
702   {  85, 170,  85 },
703   {  85, 170, 127 },
704   {  85, 170, 170 },
705   {  85, 170, 212 },
706   {  85, 212,   0 },
707   {  85, 212,  42 },
708   {  85, 212,  85 },
709   {  85, 212, 127 },
710   {  85, 212, 170 },
711   {  85, 212, 212 },
712   { 127,   0,   0 },
713   { 127,   0,  42 },
714   { 127,   0,  85 },
715   { 127,   0, 127 },
716   { 127,   0, 170 },
717   { 127,   0, 212 },
718   { 127,  42,   0 },
719   { 127,  42,  42 },
720   { 127,  42,  85 },
721   { 127,  42, 127 },
722   { 127,  42, 170 },
723   { 127,  42, 212 },
724   { 127,  85,   0 },
725   { 127,  85,  42 },
726   { 127,  85,  85 },
727   { 127,  85, 127 },
728   { 127,  85, 170 },
729   { 127,  85, 212 },
730   { 127, 127,   0 },
731   { 127, 127,  42 },
732   { 127, 127,  85 },
733   { 127, 127, 127 }, /* 0.498 145 */
734   { 127, 127, 170 },
735   { 127, 127, 212 },
736   { 127, 170,   0 },
737   { 127, 170,  42 },
738   { 127, 170,  85 },
739   { 127, 170, 127 },
740   { 127, 170, 170 },
741   { 127, 170, 212 },
742   { 127, 212,   0 },
743   { 127, 212,  42 },
744   { 127, 212,  85 },
745   { 127, 212, 127 },
746   { 127, 212, 170 },
747   { 127, 212, 212 },
748   { 170,   0,   0 },
749   { 170,   0,  42 },
750   { 170,   0,  85 },
751   { 170,   0, 127 },
752   { 170,   0, 170 },
753   { 170,   0, 212 },
754   { 170,  42,   0 },
755   { 170,  42,  42 },
756   { 170,  42,  85 },
757   { 170,  42, 127 },
758   { 170,  42, 170 },
759   { 170,  42, 212 },
760   { 170,  85,   0 },
761   { 170,  85,  42 },
762   { 170,  85,  85 },
763   { 170,  85, 127 },
764   { 170,  85, 170 },
765   { 170,  85, 212 },
766   { 170, 127,   0 },
767   { 170, 127,  42 },
768   { 170, 127,  85 },
769   { 170, 127, 127 },
770   { 170, 127, 170 },
771   { 170, 127, 212 },
772   { 170, 170,   0 },
773   { 170, 170,  42 },
774   { 170, 170,  85 },
775   { 170, 170, 127 },
776   { 170, 170, 170 }, /* 0.667 188 */
777   { 170, 170, 212 },
778   { 170, 212,   0 },
779   { 170, 212,  42 },
780   { 170, 212,  85 },
781   { 170, 212, 127 },
782   { 170, 212, 170 },
783   { 170, 212, 212 },
784   { 212,   0,   0 },
785   { 212,   0,  42 },
786   { 212,   0,  85 },
787   { 212,   0, 127 },
788   { 212,   0, 170 },
789   { 212,   0, 212 },
790   { 212,  42,   0 },
791   { 212,  42,  42 },
792   { 212,  42,  85 },
793   { 212,  42, 127 },
794   { 212,  42, 170 },
795   { 212,  42, 212 },
796   { 212,  85,   0 },
797   { 212,  85,  42 },
798   { 212,  85,  85 },
799   { 212,  85, 127 },
800   { 212,  85, 170 },
801   { 212,  85, 212 },
802   { 212, 127,   0 },
803   { 212, 127,  42 },
804   { 212, 127,  85 },
805   { 212, 127, 127 },
806   { 212, 127, 170 },
807   { 212, 127, 212 },
808   { 212, 170,   0 },
809   { 212, 170,  42 },
810   { 212, 170,  85 },
811   { 212, 170, 127 },
812   { 212, 170, 170 },
813   { 212, 170, 212 },
814   { 212, 212,   0 },
815   { 212, 212,  42 },
816   { 212, 212,  85 },
817   { 212, 212, 127 },
818   { 212, 212, 170 },
819   { 212, 212, 212 }, /* 0.831 231 */
820   {   8,   8,   8 }, /* 0.031 232 */
821   {  18,  18,  18 }, /* 0.071 233 */
822   {  28,  28,  28 }, /* 0.110 234 */
823   {  38,  38,  38 }, /* 0.149 235 */
824   {  48,  48,  48 }, /* 0.188 236 */
825   {  58,  58,  58 }, /* 0.227 237 */
826   {  68,  68,  68 }, /* 0.267 238 */
827   {  78,  78,  78 }, /* 0.306 239 */
828   {  88,  88,  88 }, /* 0.345 240 */
829   {  98,  98,  98 }, /* 0.384 241 */
830   { 108, 108, 108 }, /* 0.424 242 */
831   { 118, 118, 118 }, /* 0.463 243 */
832   { 128, 128, 128 }, /* 0.502 244 */
833   { 138, 138, 138 }, /* 0.541 245 */
834   { 148, 148, 148 }, /* 0.580 246 */
835   { 158, 158, 158 }, /* 0.620 247 */
836   { 168, 168, 168 }, /* 0.659 248 */
837   { 178, 178, 178 }, /* 0.698 249 */
838   { 188, 188, 188 }, /* 0.737 250 */
839   { 198, 198, 198 }, /* 0.776 251 */
840   { 208, 208, 208 }, /* 0.816 252 */
841   { 218, 218, 218 }, /* 0.855 253 */
842   { 228, 228, 228 }, /* 0.894 254 */
843   { 238, 238, 238 }  /* 0.933 255 */
844 };
845 
846 static inline term_color_t
rgb_to_color_xterm256(int r,int g,int b)847 rgb_to_color_xterm256 (int r, int g, int b)
848 {
849   rgb_t color;
850   hsv_t hsv;
851 
852   color.red = r; color.green = g; color.blue = b;
853   rgb_to_hsv (color, &hsv);
854 
855   if (hsv.saturation < 0.065f)
856     {
857       /* Greyscale approximation.  */
858       float luminance = color_luminance (r, g, b);
859       if (luminance < 0.015f)
860         return 0;
861       else if (luminance < 0.051f)
862         return 232;
863       else if (luminance < 0.090f)
864         return 233;
865       else if (luminance < 0.129f)
866         return 234;
867       else if (luminance < 0.157f)
868         return 235;
869       else if (luminance < 0.177f)
870         return 59;
871       else if (luminance < 0.207f)
872         return 236;
873       else if (luminance < 0.247f)
874         return 237;
875       else if (luminance < 0.284f)
876         return 238;
877       else if (luminance < 0.304f)
878         return 8;
879       else if (luminance < 0.319f)
880         return 239;
881       else if (luminance < 0.339f)
882         return 102;
883       else if (luminance < 0.364f)
884         return 240;
885       else if (luminance < 0.404f)
886         return 241;
887       else if (luminance < 0.443f)
888         return 242;
889       else if (luminance < 0.480f)
890         return 243;
891       else if (luminance < 0.500f)
892         return 145;
893       else if (luminance < 0.521f)
894         return 244;
895       else if (luminance < 0.560f)
896         return 245;
897       else if (luminance < 0.600f)
898         return 246;
899       else if (luminance < 0.639f)
900         return 247;
901       else if (luminance < 0.663f)
902         return 248;
903       else if (luminance < 0.682f)
904         return 188;
905       else if (luminance < 0.717f)
906         return 249;
907       else if (luminance < 0.756f)
908         return 250;
909       else if (luminance < 0.796f)
910         return 251;
911       else if (luminance < 0.823f)
912         return 252;
913       else if (luminance < 0.843f)
914         return 231;
915       else if (luminance < 0.874f)
916         return 253;
917       else if (luminance < 0.896f)
918         return 254;
919       else if (luminance < 0.915f)
920         return 7;
921       else if (luminance < 0.966f)
922         return 255;
923       else
924         return 15;
925     }
926   else
927     /* Color approximation.  */
928     return nearest_color (color, colors_of_xterm256, 256);
929 }
930 
931 /* ------------------------ cm_xtermrgb color model ------------------------ */
932 
933 /* We represent a color as an RGB triplet: (r << 16) | (g << 8) | (b << 0),
934    where r, g, b are in the range [0..255].  */
935 
936 static inline term_color_t
rgb_to_color_xtermrgb(int r,int g,int b)937 rgb_to_color_xtermrgb (int r, int g, int b)
938 {
939   return (r << 16) | (g << 8) | (b << 0);
940 }
941 
942 
943 /* ============================== hyperlink_t ============================== */
944 
945 /* A hyperlink is a heap-allocated structure that can be assigned to a run
946    of characters.  */
947 typedef struct
948 {
949   /* URL.
950      Should better be <= 2083 bytes long (because of Microsoft Internet
951      Explorer).  */
952   char *ref;
953   /* Id.
954      Used when the same hyperlink persists across newlines.
955      Should better be <= 256 bytes long (because of VTE and iTerm2).  */
956   char *id;
957   /* Same as id, if non-NULL.  Or some generated id.  */
958   char *real_id;
959 } hyperlink_t;
960 
961 static inline void
free_hyperlink(hyperlink_t * hyperlink)962 free_hyperlink (hyperlink_t *hyperlink)
963 {
964   free (hyperlink->ref);
965   free (hyperlink->real_id);
966   free (hyperlink);
967 }
968 
969 
970 /* ============================= attributes_t ============================= */
971 
972 /* ANSI C and ISO C99 6.7.2.1.(4) forbid use of bit fields for types other
973    than 'int' or 'unsigned int'.
974    On the other hand, C++ forbids conversion between enum types and integer
975    types without an explicit cast.  */
976 #ifdef __cplusplus
977 # define BITFIELD_TYPE(orig_type,integer_type) orig_type
978 #else
979 # define BITFIELD_TYPE(orig_type,integer_type) integer_type
980 #endif
981 
982 /* Attributes that can be set on a character.  */
983 typedef struct
984 {
985   BITFIELD_TYPE(term_color_t,     signed int)   color     : 25;
986   BITFIELD_TYPE(term_color_t,     signed int)   bgcolor   : 25;
987   BITFIELD_TYPE(term_weight_t,    unsigned int) weight    : 1;
988   BITFIELD_TYPE(term_posture_t,   unsigned int) posture   : 1;
989   BITFIELD_TYPE(term_underline_t, unsigned int) underline : 1;
990   /* Hyperlink, or NULL for none.  */
991   hyperlink_t *hyperlink;
992 } attributes_t;
993 
994 /* Compare two sets of attributes for equality.  */
995 static inline bool
equal_attributes(attributes_t attr1,attributes_t attr2)996 equal_attributes (attributes_t attr1, attributes_t attr2)
997 {
998   return (attr1.color == attr2.color
999           && attr1.bgcolor == attr2.bgcolor
1000           && attr1.weight == attr2.weight
1001           && attr1.posture == attr2.posture
1002           && attr1.underline == attr2.underline
1003           && attr1.hyperlink == attr2.hyperlink);
1004 }
1005 
1006 
1007 /* ============================ EINTR handling ============================ */
1008 
1009 /* EINTR handling for tcdrain().
1010    This function can return -1/EINTR even when we don't have any
1011    signal handlers set up, namely when we get interrupted via SIGSTOP.  */
1012 
1013 #if HAVE_TCDRAIN
1014 
1015 static inline int
nonintr_tcdrain(int fd)1016 nonintr_tcdrain (int fd)
1017 {
1018   int retval;
1019 
1020   do
1021     retval = tcdrain (fd);
1022   while (retval < 0 && errno == EINTR);
1023 
1024   return retval;
1025 }
1026 
1027 #endif
1028 
1029 
1030 /* ============================ term_ostream_t ============================ */
1031 
1032 struct term_ostream : struct ostream
1033 {
1034 fields:
1035   /* The file descriptor used for output.  Note that ncurses termcap emulation
1036      uses the baud rate information from file descriptor 1 (stdout) if it is
1037      a tty, or from file descriptor 2 (stderr) otherwise.  */
1038   int volatile fd;
1039   #if HAVE_WINDOWS_CONSOLES
1040   HANDLE volatile handle;
1041   bool volatile is_windows_console;
1042   #endif
1043   char *filename;
1044   /* Values from the terminal type's terminfo/termcap description.
1045      See terminfo(5) for details.  */
1046                                          /* terminfo  termcap */
1047   int max_colors;                        /* colors    Co */
1048   int no_color_video;                    /* ncv       NC */
1049   char * volatile set_a_foreground;      /* setaf     AF */
1050   char * volatile set_foreground;        /* setf      Sf */
1051   char * volatile set_a_background;      /* setab     AB */
1052   char * volatile set_background;        /* setb      Sb */
1053   char *orig_pair;                       /* op        op */
1054   char * volatile enter_bold_mode;       /* bold      md */
1055   char * volatile enter_italics_mode;    /* sitm      ZH */
1056   char *exit_italics_mode;               /* ritm      ZR */
1057   char * volatile enter_underline_mode;  /* smul      us */
1058   char *exit_underline_mode;             /* rmul      ue */
1059   char *exit_attribute_mode;             /* sgr0      me */
1060   /* Inferred values.  */
1061   bool volatile supports_foreground;
1062   bool volatile supports_background;
1063   colormodel_t volatile colormodel;
1064   bool volatile supports_weight;
1065   bool volatile supports_posture;
1066   bool volatile supports_underline;
1067   bool volatile supports_hyperlink;
1068   /* Inferred values for the exit handler and the signal handlers.  */
1069   const char * volatile restore_colors;
1070   const char * volatile restore_weight;
1071   const char * volatile restore_posture;
1072   const char * volatile restore_underline;
1073   const char * volatile restore_hyperlink;
1074   /* Signal handling and tty control.  */
1075   struct term_style_control_data control_data;
1076   /* State for producing hyperlink ids.  */
1077   uint32_t hostname_hash;
1078   uint64_t start_time;
1079   uint32_t id_serial;
1080   /* Set of hyperlink_t that are currently in use.  */
1081   hyperlink_t **hyperlinks_array;
1082   size_t hyperlinks_count;
1083   size_t hyperlinks_allocated;
1084   /* Variable state, representing past output.  */
1085   #if HAVE_WINDOWS_CONSOLES
1086   WORD volatile default_console_attributes;
1087   WORD volatile current_console_attributes;
1088   #endif
1089   attributes_t default_attr;         /* Default simplified attributes of the
1090                                         terminal.  */
1091   attributes_t volatile active_attr; /* Simplified attributes that we have set
1092                                         on the terminal.  */
1093   term_color_t volatile active_attr_color;   /* Same as active_attr.color,
1094                                                 atomically accessible.  */
1095   term_color_t volatile active_attr_bgcolor; /* Same as active_attr.bgcolor,
1096                                                 atomically accessible.  */
1097   hyperlink_t *volatile active_attr_hyperlink; /* Same as active_attr.hyperlink,
1098                                                   atomically accessible.  */
1099   /* Variable state, representing future output.  */
1100   char *buffer;                      /* Buffer for the current line.  */
1101   attributes_t *attrbuffer;          /* Buffer for the simplified attributes;
1102                                         same length as buffer.  */
1103   size_t buflen;                     /* Number of bytes stored so far.  */
1104   size_t allocated;                  /* Allocated size of the buffer.  */
1105   attributes_t curr_attr;            /* Current attributes.  */
1106   attributes_t simp_attr;            /* Simplified current attributes.  */
1107 };
1108 
1109 static struct term_style_control_data *
get_control_data(term_ostream_t stream)1110 get_control_data (term_ostream_t stream)
1111 {
1112   return &stream->control_data;
1113 }
1114 
1115 /* Simplify attributes, according to the terminal's capabilities.  */
1116 static attributes_t
simplify_attributes(term_ostream_t stream,attributes_t attr)1117 simplify_attributes (term_ostream_t stream, attributes_t attr)
1118 {
1119   if ((attr.color != COLOR_DEFAULT || attr.bgcolor != COLOR_DEFAULT)
1120       && stream->no_color_video > 0)
1121     {
1122       /* When colors and attributes can not be represented simultaneously,
1123          we give preference to the color.  */
1124       if (stream->no_color_video & 2)
1125         /* Colors conflict with underlining.  */
1126         attr.underline = UNDERLINE_OFF;
1127       if (stream->no_color_video & 32)
1128         /* Colors conflict with bold weight.  */
1129         attr.weight = WEIGHT_NORMAL;
1130     }
1131   if (!stream->supports_foreground)
1132     attr.color = COLOR_DEFAULT;
1133   if (!stream->supports_background)
1134     attr.bgcolor = COLOR_DEFAULT;
1135   if (!stream->supports_weight)
1136     attr.weight = WEIGHT_DEFAULT;
1137   if (!stream->supports_posture)
1138     attr.posture = POSTURE_DEFAULT;
1139   if (!stream->supports_underline)
1140     attr.underline = UNDERLINE_DEFAULT;
1141   if (!stream->supports_hyperlink)
1142     attr.hyperlink = NULL;
1143   return attr;
1144 }
1145 
1146 /* Generate an id for a hyperlink.  */
1147 static char *
generate_hyperlink_id(term_ostream_t stream)1148 generate_hyperlink_id (term_ostream_t stream)
1149 {
1150   /* A UUID would be optimal, but is overkill here.  An id of 128 bits
1151      (32 hexadecimal digits) should be sufficient.  */
1152   static const char hexdigits[16] =
1153     {
1154       '0', '1', '2', '3', '4', '5', '6', '7',
1155       '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
1156     };
1157   char *id = (char *) xmalloc (128 / 4 + 1);
1158   uint32_t words[4] =
1159     {
1160       stream->hostname_hash,
1161       (uint32_t) (stream->start_time >> 32),
1162       (uint32_t) stream->start_time,
1163       stream->id_serial
1164     };
1165   char *p = id;
1166   unsigned int i;
1167   for (i = 0; i < 4; i++)
1168     {
1169       uint32_t word = words[i];
1170       unsigned int j;
1171       for (j = 0; j < 32 / 4; j++)
1172         *p++ = hexdigits[(word >> (32 - 4 * (j + 1))) & 0x0f];
1173     }
1174   *p = '\0';
1175   stream->id_serial++;
1176   return id;
1177 }
1178 
1179 /* Stream that contains information about how the various out_* functions shall
1180    do output.  */
1181 static term_ostream_t volatile out_stream;
1182 
1183 /* File descriptor to which out_char and out_char_unchecked shall output escape
1184    sequences.
1185    Same as (out_stream != NULL ? out_stream->fd : -1).  */
1186 static int volatile out_fd = -1;
1187 
1188 /* Signal error after full_write failed.  */
1189 static void
out_error(void)1190 out_error (void)
1191 {
1192   error (EXIT_FAILURE, errno, _("error writing to %s"), out_stream->filename);
1193 }
1194 
1195 /* Output a single char to out_fd.  */
1196 static int
out_char(int c)1197 out_char (int c)
1198 {
1199   char bytes[1];
1200 
1201   bytes[0] = (char)c;
1202   /* We have to write directly to the file descriptor, not to a buffer with
1203      the same destination, because of the padding and sleeping that tputs()
1204      does.  */
1205   if (full_write (out_fd, bytes, 1) < 1)
1206     out_error ();
1207   return 0;
1208 }
1209 
1210 /* Output a single char to out_fd.  Ignore errors.  */
1211 static _GL_ASYNC_SAFE int
out_char_unchecked(int c)1212 out_char_unchecked (int c)
1213 {
1214   char bytes[1];
1215 
1216   bytes[0] = (char)c;
1217   full_write (out_fd, bytes, 1);
1218   return 0;
1219 }
1220 
1221 /* Output escape sequences to switch the foreground color to NEW_COLOR.  */
1222 static _GL_ASYNC_SAFE void
out_color_change(term_ostream_t stream,term_color_t new_color,bool async_safe)1223 out_color_change (term_ostream_t stream, term_color_t new_color,
1224                   bool async_safe)
1225 {
1226   assert (stream->supports_foreground);
1227   assert (new_color != COLOR_DEFAULT);
1228   switch (stream->colormodel)
1229     {
1230     case cm_common8:
1231       assert (new_color >= 0 && new_color < 8);
1232       #if HAVE_WINDOWS_CONSOLES
1233       if (stream->is_windows_console)
1234         {
1235           /* SetConsoleTextAttribute
1236              <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1237              <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers>  */
1238           /* Assign to stream->current_console_attributes *before* calling
1239              SetConsoleTextAttribute, otherwise async_set_attributes_from_default
1240              will not do its job correctly.  */
1241           stream->current_console_attributes =
1242             (stream->current_console_attributes & ~(7 << 0))
1243             | (new_color << 0);
1244           SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1245         }
1246       else
1247       #endif
1248         {
1249           if (stream->set_a_foreground != NULL)
1250             tputs (tparm (stream->set_a_foreground, color_bgr (new_color)),
1251                    1, async_safe ? out_char_unchecked : out_char);
1252           else
1253             tputs (tparm (stream->set_foreground, new_color),
1254                    1, async_safe ? out_char_unchecked : out_char);
1255         }
1256       break;
1257     /* When we are dealing with an xterm, there is no need to go through
1258        tputs() because we know there is no padding and sleeping.  */
1259     case cm_xterm8:
1260       assert (new_color >= 0 && new_color < 8);
1261       {
1262         char bytes[5];
1263         bytes[0] = 0x1B; bytes[1] = '[';
1264         bytes[2] = '3'; bytes[3] = '0' + new_color;
1265         bytes[4] = 'm';
1266         if (full_write (out_fd, bytes, 5) < 5)
1267           if (!async_safe)
1268             out_error ();
1269       }
1270       break;
1271     case cm_xterm16:
1272       assert (new_color >= 0 && new_color < 16);
1273       {
1274         char bytes[5];
1275         bytes[0] = 0x1B; bytes[1] = '[';
1276         if (new_color < 8)
1277           {
1278             bytes[2] = '3'; bytes[3] = '0' + new_color;
1279           }
1280         else
1281           {
1282             bytes[2] = '9'; bytes[3] = '0' + (new_color - 8);
1283           }
1284         bytes[4] = 'm';
1285         if (full_write (out_fd, bytes, 5) < 5)
1286           if (!async_safe)
1287             out_error ();
1288       }
1289       break;
1290     case cm_xterm88:
1291       assert (new_color >= 0 && new_color < 88);
1292       {
1293         char bytes[10];
1294         char *p;
1295         bytes[0] = 0x1B; bytes[1] = '[';
1296         bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
1297         bytes[5] = '5'; bytes[6] = ';';
1298         p = bytes + 7;
1299         if (new_color >= 10)
1300           *p++ = '0' + (new_color / 10);
1301         *p++ = '0' + (new_color % 10);
1302         *p++ = 'm';
1303         if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1304           if (!async_safe)
1305             out_error ();
1306       }
1307       break;
1308     case cm_xterm256:
1309       assert (new_color >= 0 && new_color < 256);
1310       {
1311         char bytes[11];
1312         char *p;
1313         bytes[0] = 0x1B; bytes[1] = '[';
1314         bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
1315         bytes[5] = '5'; bytes[6] = ';';
1316         p = bytes + 7;
1317         if (new_color >= 100)
1318           *p++ = '0' + (new_color / 100);
1319         if (new_color >= 10)
1320           *p++ = '0' + ((new_color % 100) / 10);
1321         *p++ = '0' + (new_color % 10);
1322         *p++ = 'm';
1323         if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1324           if (!async_safe)
1325             out_error ();
1326       }
1327       break;
1328     case cm_xtermrgb:
1329       assert (new_color >= 0 && new_color < 0x1000000);
1330       {
1331         char bytes[19];
1332         char *p;
1333         unsigned int r = (new_color >> 16) & 0xff;
1334         unsigned int g = (new_color >> 8) & 0xff;
1335         unsigned int b = new_color & 0xff;
1336         bytes[0] = 0x1B; bytes[1] = '[';
1337         bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
1338         bytes[5] = '2'; bytes[6] = ';';
1339         p = bytes + 7;
1340         if (r >= 100)
1341           *p++ = '0' + (r / 100);
1342         if (r >= 10)
1343           *p++ = '0' + ((r % 100) / 10);
1344         *p++ = '0' + (r % 10);
1345         *p++ = ';';
1346         if (g >= 100)
1347           *p++ = '0' + (g / 100);
1348         if (g >= 10)
1349           *p++ = '0' + ((g % 100) / 10);
1350         *p++ = '0' + (g % 10);
1351         *p++ = ';';
1352         if (b >= 100)
1353           *p++ = '0' + (b / 100);
1354         if (b >= 10)
1355           *p++ = '0' + ((b % 100) / 10);
1356         *p++ = '0' + (b % 10);
1357         *p++ = 'm';
1358         if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1359           if (!async_safe)
1360             out_error ();
1361       }
1362       break;
1363     default:
1364       abort ();
1365     }
1366 }
1367 
1368 /* Output escape sequences to switch the background color to NEW_BGCOLOR.  */
1369 static _GL_ASYNC_SAFE void
out_bgcolor_change(term_ostream_t stream,term_color_t new_bgcolor,bool async_safe)1370 out_bgcolor_change (term_ostream_t stream, term_color_t new_bgcolor,
1371                     bool async_safe)
1372 {
1373   assert (stream->supports_background);
1374   assert (new_bgcolor != COLOR_DEFAULT);
1375   switch (stream->colormodel)
1376     {
1377     case cm_common8:
1378       assert (new_bgcolor >= 0 && new_bgcolor < 8);
1379       #if HAVE_WINDOWS_CONSOLES
1380       if (stream->is_windows_console)
1381         {
1382           /* SetConsoleTextAttribute
1383              <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1384              <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers>  */
1385           /* Assign to stream->current_console_attributes *before* calling
1386              SetConsoleTextAttribute, otherwise async_set_attributes_from_default
1387              will not do its job correctly.  */
1388           stream->current_console_attributes =
1389             (stream->current_console_attributes & ~(7 << 4))
1390             | (new_bgcolor << 4);
1391           SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1392         }
1393       else
1394       #endif
1395         {
1396           if (stream->set_a_background != NULL)
1397             tputs (tparm (stream->set_a_background, color_bgr (new_bgcolor)),
1398                    1, async_safe ? out_char_unchecked : out_char);
1399           else
1400             tputs (tparm (stream->set_background, new_bgcolor),
1401                    1, async_safe ? out_char_unchecked : out_char);
1402         }
1403       break;
1404     /* When we are dealing with an xterm, there is no need to go through
1405        tputs() because we know there is no padding and sleeping.  */
1406     case cm_xterm8:
1407       assert (new_bgcolor >= 0 && new_bgcolor < 8);
1408       {
1409         char bytes[5];
1410         bytes[0] = 0x1B; bytes[1] = '[';
1411         bytes[2] = '4'; bytes[3] = '0' + new_bgcolor;
1412         bytes[4] = 'm';
1413         if (full_write (out_fd, bytes, 5) < 5)
1414           if (!async_safe)
1415             out_error ();
1416       }
1417       break;
1418     case cm_xterm16:
1419       assert (new_bgcolor >= 0 && new_bgcolor < 16);
1420       {
1421         char bytes[6];
1422         bytes[0] = 0x1B; bytes[1] = '[';
1423         if (new_bgcolor < 8)
1424           {
1425             bytes[2] = '4'; bytes[3] = '0' + new_bgcolor;
1426             bytes[4] = 'm';
1427             if (full_write (out_fd, bytes, 5) < 5)
1428               if (!async_safe)
1429                 out_error ();
1430           }
1431         else
1432           {
1433             bytes[2] = '1'; bytes[3] = '0';
1434             bytes[4] = '0' + (new_bgcolor - 8); bytes[5] = 'm';
1435             if (full_write (out_fd, bytes, 6) < 6)
1436               if (!async_safe)
1437                 out_error ();
1438           }
1439       }
1440       break;
1441     case cm_xterm88:
1442       assert (new_bgcolor >= 0 && new_bgcolor < 88);
1443       {
1444         char bytes[10];
1445         char *p;
1446         bytes[0] = 0x1B; bytes[1] = '[';
1447         bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
1448         bytes[5] = '5'; bytes[6] = ';';
1449         p = bytes + 7;
1450         if (new_bgcolor >= 10)
1451           *p++ = '0' + (new_bgcolor / 10);
1452         *p++ = '0' + (new_bgcolor % 10);
1453         *p++ = 'm';
1454         if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1455           if (!async_safe)
1456             out_error ();
1457       }
1458       break;
1459     case cm_xterm256:
1460       assert (new_bgcolor >= 0 && new_bgcolor < 256);
1461       {
1462         char bytes[11];
1463         char *p;
1464         bytes[0] = 0x1B; bytes[1] = '[';
1465         bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
1466         bytes[5] = '5'; bytes[6] = ';';
1467         p = bytes + 7;
1468         if (new_bgcolor >= 100)
1469           *p++ = '0' + (new_bgcolor / 100);
1470         if (new_bgcolor >= 10)
1471           *p++ = '0' + ((new_bgcolor % 100) / 10);
1472         *p++ = '0' + (new_bgcolor % 10);
1473         *p++ = 'm';
1474         if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1475           if (!async_safe)
1476             out_error ();
1477       }
1478       break;
1479     case cm_xtermrgb:
1480       assert (new_bgcolor >= 0 && new_bgcolor < 0x1000000);
1481       {
1482         char bytes[19];
1483         char *p;
1484         unsigned int r = (new_bgcolor >> 16) & 0xff;
1485         unsigned int g = (new_bgcolor >> 8) & 0xff;
1486         unsigned int b = new_bgcolor & 0xff;
1487         bytes[0] = 0x1B; bytes[1] = '[';
1488         bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
1489         bytes[5] = '2'; bytes[6] = ';';
1490         p = bytes + 7;
1491         if (r >= 100)
1492           *p++ = '0' + (r / 100);
1493         if (r >= 10)
1494           *p++ = '0' + ((r % 100) / 10);
1495         *p++ = '0' + (r % 10);
1496         *p++ = ';';
1497         if (g >= 100)
1498           *p++ = '0' + (g / 100);
1499         if (g >= 10)
1500           *p++ = '0' + ((g % 100) / 10);
1501         *p++ = '0' + (g % 10);
1502         *p++ = ';';
1503         if (b >= 100)
1504           *p++ = '0' + (b / 100);
1505         if (b >= 10)
1506           *p++ = '0' + ((b % 100) / 10);
1507         *p++ = '0' + (b % 10);
1508         *p++ = 'm';
1509         if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1510           if (!async_safe)
1511             out_error ();
1512       }
1513       break;
1514     default:
1515       abort ();
1516     }
1517 }
1518 
1519 /* Output escape sequences to switch the weight to NEW_WEIGHT.  */
1520 static _GL_ASYNC_SAFE void
out_weight_change(term_ostream_t stream,term_weight_t new_weight,bool async_safe)1521 out_weight_change (term_ostream_t stream, term_weight_t new_weight,
1522                    bool async_safe)
1523 {
1524   assert (stream->supports_weight);
1525   assert (new_weight != WEIGHT_DEFAULT);
1526   /* This implies:  */
1527   assert (new_weight == WEIGHT_BOLD);
1528   tputs (stream->enter_bold_mode,
1529          1, async_safe ? out_char_unchecked : out_char);
1530 }
1531 
1532 /* Output escape sequences to switch the posture to NEW_POSTURE.  */
1533 static _GL_ASYNC_SAFE void
out_posture_change(term_ostream_t stream,term_posture_t new_posture,bool async_safe)1534 out_posture_change (term_ostream_t stream, term_posture_t new_posture,
1535                     bool async_safe)
1536 {
1537   assert (stream->supports_posture);
1538   assert (new_posture != POSTURE_DEFAULT);
1539   /* This implies:  */
1540   assert (new_posture == POSTURE_ITALIC);
1541   tputs (stream->enter_italics_mode,
1542          1, async_safe ? out_char_unchecked : out_char);
1543 }
1544 
1545 /* Output escape sequences to switch the underline to NEW_UNDERLINE.  */
1546 static _GL_ASYNC_SAFE void
out_underline_change(term_ostream_t stream,term_underline_t new_underline,bool async_safe)1547 out_underline_change (term_ostream_t stream, term_underline_t new_underline,
1548                       bool async_safe)
1549 {
1550   assert (stream->supports_underline);
1551   assert (new_underline != UNDERLINE_DEFAULT);
1552   /* This implies:  */
1553   assert (new_underline == UNDERLINE_ON);
1554   #if HAVE_WINDOWS_CONSOLES
1555   if (stream->is_windows_console)
1556     {
1557       /* SetConsoleTextAttribute
1558          <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1559          <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers>  */
1560       /* Assign to stream->current_console_attributes *before* calling
1561          SetConsoleTextAttribute, otherwise async_set_attributes_from_default
1562          will not do its job correctly.  */
1563       stream->current_console_attributes =
1564         stream->current_console_attributes | COMMON_LVB_UNDERSCORE;
1565       SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1566     }
1567   else
1568   #endif
1569     {
1570       tputs (stream->enter_underline_mode,
1571              1, async_safe ? out_char_unchecked : out_char);
1572     }
1573 }
1574 
1575 /* Output escape seqeuences to switch the hyperlink to NEW_HYPERLINK.  */
1576 static _GL_ASYNC_SAFE void
out_hyperlink_change(term_ostream_t stream,hyperlink_t * new_hyperlink,bool async_safe)1577 out_hyperlink_change (term_ostream_t stream, hyperlink_t *new_hyperlink,
1578                       bool async_safe)
1579 {
1580   int (*out_ch) (int) = (async_safe ? out_char_unchecked : out_char);
1581   assert (stream->supports_hyperlink);
1582   if (new_hyperlink != NULL)
1583     {
1584       assert (new_hyperlink->real_id != NULL);
1585       tputs ("\033]8;id=",           1, out_ch);
1586       tputs (new_hyperlink->real_id, 1, out_ch);
1587       tputs (";",                    1, out_ch);
1588       tputs (new_hyperlink->ref,     1, out_ch);
1589       tputs ("\033\\",               1, out_ch);
1590     }
1591   else
1592     tputs ("\033]8;;\033\\", 1, out_ch);
1593 }
1594 
1595 /* Output escape sequences to switch from STREAM->ACTIVE_ATTR to NEW_ATTR,
1596    and update STREAM->ACTIVE_ATTR.  */
1597 static void
out_attr_change(term_ostream_t stream,attributes_t new_attr)1598 out_attr_change (term_ostream_t stream, attributes_t new_attr)
1599 {
1600   attributes_t old_attr = stream->active_attr;
1601 
1602   /* Keep track of the active attributes.  Do this *before* emitting the
1603      escape sequences, otherwise async_set_attributes_from_default will not
1604      do its job correctly.  */
1605   stream->active_attr = new_attr;
1606   stream->active_attr_color = new_attr.color;
1607   stream->active_attr_bgcolor = new_attr.bgcolor;
1608   stream->active_attr_hyperlink = new_attr.hyperlink;
1609 
1610   #if HAVE_WINDOWS_CONSOLES
1611   if (stream->is_windows_console)
1612     {
1613       /* SetConsoleTextAttribute
1614          <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1615          <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers>  */
1616       /* Assign to stream->current_console_attributes *before* calling
1617          SetConsoleTextAttribute, otherwise async_set_attributes_from_default
1618          will not do its job correctly.  */
1619       stream->current_console_attributes =
1620         (stream->current_console_attributes
1621          & ~((7 << 0) | (7 << 4) | COMMON_LVB_UNDERSCORE))
1622         | (new_attr.color == COLOR_DEFAULT
1623            ? stream->default_console_attributes & (7 << 0)
1624            : (new_attr.color << 0))
1625         | (new_attr.bgcolor == COLOR_DEFAULT
1626            ? stream->default_console_attributes & (7 << 4)
1627            : (new_attr.bgcolor << 4))
1628         | (new_attr.underline ? COMMON_LVB_UNDERSCORE : 0);
1629       SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1630     }
1631   else
1632   #endif
1633     {
1634       bool cleared_attributes;
1635 
1636       /* For out_char to work.  */
1637       out_stream = stream;
1638       out_fd = stream->fd;
1639 
1640       /* We don't know the default colors of the terminal.  The only way to
1641          switch back to a default color is to use stream->orig_pair.  */
1642       if ((new_attr.color == COLOR_DEFAULT && old_attr.color != COLOR_DEFAULT)
1643           || (new_attr.bgcolor == COLOR_DEFAULT && old_attr.bgcolor != COLOR_DEFAULT))
1644         {
1645           assert (stream->supports_foreground || stream->supports_background);
1646           tputs (stream->orig_pair, 1, out_char);
1647           old_attr.color = COLOR_DEFAULT;
1648           old_attr.bgcolor = COLOR_DEFAULT;
1649         }
1650 
1651       /* To turn off WEIGHT_BOLD, the only way is to output the
1652          exit_attribute_mode sequence.  (With xterm, you can also do it with
1653          "Esc [ 0 m", but this escape sequence is not contained in the terminfo
1654          description.)  It may also clear the colors; this is the case e.g. when
1655          TERM="xterm" or TERM="ansi".
1656          To turn off UNDERLINE_ON, we can use the exit_underline_mode or the
1657          exit_attribute_mode sequence.  In the latter case, it will not only
1658          turn off UNDERLINE_ON, but also the other attributes, and possibly also
1659          the colors.
1660          To turn off POSTURE_ITALIC, we can use the exit_italics_mode or the
1661          exit_attribute_mode sequence.  Again, in the latter case, it will not
1662          only turn off POSTURE_ITALIC, but also the other attributes, and
1663          possibly also the colors.
1664          There is no point in setting an attribute just before emitting an
1665          escape sequence that may again turn off the attribute.  Therefore we
1666          proceed in two steps: First, clear the attributes that need to be
1667          cleared; then - taking into account that this may have cleared all
1668          attributes and all colors - set the colors and the attributes.
1669          The variable 'cleared_attributes' tells whether an escape sequence
1670          has been output that may have cleared all attributes and all color
1671          settings.  */
1672       cleared_attributes = false;
1673       if (old_attr.posture != POSTURE_NORMAL
1674           && new_attr.posture == POSTURE_NORMAL
1675           && stream->exit_italics_mode != NULL)
1676         {
1677           tputs (stream->exit_italics_mode, 1, out_char);
1678           old_attr.posture = POSTURE_NORMAL;
1679           cleared_attributes = true;
1680         }
1681       if (old_attr.underline != UNDERLINE_OFF
1682           && new_attr.underline == UNDERLINE_OFF
1683           && stream->exit_underline_mode != NULL)
1684         {
1685           tputs (stream->exit_underline_mode, 1, out_char);
1686           old_attr.underline = UNDERLINE_OFF;
1687           cleared_attributes = true;
1688         }
1689       if ((old_attr.weight != WEIGHT_NORMAL
1690            && new_attr.weight == WEIGHT_NORMAL)
1691           || (old_attr.posture != POSTURE_NORMAL
1692               && new_attr.posture == POSTURE_NORMAL
1693               /* implies stream->exit_italics_mode == NULL */)
1694           || (old_attr.underline != UNDERLINE_OFF
1695               && new_attr.underline == UNDERLINE_OFF
1696               /* implies stream->exit_underline_mode == NULL */))
1697         {
1698           tputs (stream->exit_attribute_mode, 1, out_char);
1699           /* We don't know exactly what effects exit_attribute_mode has, but
1700              this is the minimum effect:  */
1701           old_attr.weight = WEIGHT_NORMAL;
1702           if (stream->exit_italics_mode == NULL)
1703             old_attr.posture = POSTURE_NORMAL;
1704           if (stream->exit_underline_mode == NULL)
1705             old_attr.underline = UNDERLINE_OFF;
1706           cleared_attributes = true;
1707         }
1708 
1709       /* Turn on the colors.  */
1710       if (new_attr.color != old_attr.color
1711           || (cleared_attributes && new_attr.color != COLOR_DEFAULT))
1712         {
1713           out_color_change (stream, new_attr.color, false);
1714         }
1715       if (new_attr.bgcolor != old_attr.bgcolor
1716           || (cleared_attributes && new_attr.bgcolor != COLOR_DEFAULT))
1717         {
1718           out_bgcolor_change (stream, new_attr.bgcolor, false);
1719         }
1720       if (new_attr.weight != old_attr.weight
1721           || (cleared_attributes && new_attr.weight != WEIGHT_DEFAULT))
1722         {
1723           out_weight_change (stream, new_attr.weight, false);
1724         }
1725       if (new_attr.posture != old_attr.posture
1726           || (cleared_attributes && new_attr.posture != POSTURE_DEFAULT))
1727         {
1728           out_posture_change (stream, new_attr.posture, false);
1729         }
1730       if (new_attr.underline != old_attr.underline
1731           || (cleared_attributes && new_attr.underline != UNDERLINE_DEFAULT))
1732         {
1733           out_underline_change (stream, new_attr.underline, false);
1734         }
1735       if (new_attr.hyperlink != old_attr.hyperlink)
1736         {
1737           out_hyperlink_change (stream, new_attr.hyperlink, false);
1738         }
1739     }
1740 }
1741 
1742 static void
restore(term_ostream_t stream)1743 restore (term_ostream_t stream)
1744 {
1745   #if HAVE_WINDOWS_CONSOLES
1746   if (stream->is_windows_console)
1747     {
1748       /* SetConsoleTextAttribute
1749          <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1750          <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers>  */
1751       SetConsoleTextAttribute (stream->handle, stream->default_console_attributes);
1752     }
1753   else
1754   #endif
1755     {
1756       /* For out_char_unchecked to work.  */
1757       out_stream = stream;
1758       out_fd = stream->fd;
1759 
1760       if (stream->restore_colors != NULL)
1761         tputs (stream->restore_colors, 1, out_char_unchecked);
1762       if (stream->restore_weight != NULL)
1763         tputs (stream->restore_weight, 1, out_char_unchecked);
1764       if (stream->restore_posture != NULL)
1765         tputs (stream->restore_posture, 1, out_char_unchecked);
1766       if (stream->restore_underline != NULL)
1767         tputs (stream->restore_underline, 1, out_char_unchecked);
1768       if (stream->restore_hyperlink != NULL)
1769         tputs (stream->restore_hyperlink, 1, out_char_unchecked);
1770     }
1771 }
1772 
1773 static _GL_ASYNC_SAFE void
async_restore(term_ostream_t stream)1774 async_restore (term_ostream_t stream)
1775 {
1776   #if HAVE_WINDOWS_CONSOLES
1777   if (stream->is_windows_console)
1778     {
1779       /* SetConsoleTextAttribute
1780          <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1781          <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers>  */
1782       SetConsoleTextAttribute (stream->handle, stream->default_console_attributes);
1783     }
1784   else
1785   #endif
1786     {
1787       /* For out_char_unchecked to work.  */
1788       out_stream = stream;
1789       out_fd = stream->fd;
1790 
1791       if (stream->restore_colors != NULL)
1792         tputs (stream->restore_colors, 1, out_char_unchecked);
1793       if (stream->restore_weight != NULL)
1794         tputs (stream->restore_weight, 1, out_char_unchecked);
1795       if (stream->restore_posture != NULL)
1796         tputs (stream->restore_posture, 1, out_char_unchecked);
1797       if (stream->restore_underline != NULL)
1798         tputs (stream->restore_underline, 1, out_char_unchecked);
1799       if (stream->restore_hyperlink != NULL)
1800         tputs (stream->restore_hyperlink, 1, out_char_unchecked);
1801     }
1802 }
1803 
1804 static _GL_ASYNC_SAFE void
async_set_attributes_from_default(term_ostream_t stream)1805 async_set_attributes_from_default (term_ostream_t stream)
1806 {
1807   #if HAVE_WINDOWS_CONSOLES
1808   if (stream->is_windows_console)
1809     {
1810       /* SetConsoleTextAttribute
1811          <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1812          <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers>  */
1813       SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1814     }
1815   else
1816   #endif
1817     {
1818       attributes_t new_attr = stream->active_attr;
1819       /* Since stream->active_attr is not guaranteed to be loaded atomically,
1820          new_attr.color and new_attr.bgcolor may have invalid values.
1821          Use the atomically loadable values instead.  */
1822       new_attr.color = stream->active_attr_color;
1823       new_attr.bgcolor = stream->active_attr_bgcolor;
1824       new_attr.hyperlink = stream->active_attr_hyperlink;
1825 
1826       /* For out_char_unchecked to work.  */
1827       out_stream = stream;
1828       out_fd = stream->fd;
1829 
1830       if (new_attr.color != COLOR_DEFAULT)
1831         out_color_change (stream, new_attr.color, true);
1832       if (new_attr.bgcolor != COLOR_DEFAULT)
1833         out_bgcolor_change (stream, new_attr.bgcolor, true);
1834       if (new_attr.weight != WEIGHT_DEFAULT)
1835         out_weight_change (stream, new_attr.weight, true);
1836       if (new_attr.posture != POSTURE_DEFAULT)
1837         out_posture_change (stream, new_attr.posture, true);
1838       if (new_attr.underline != UNDERLINE_DEFAULT)
1839         out_underline_change (stream, new_attr.underline, true);
1840       if (new_attr.hyperlink != NULL)
1841         out_hyperlink_change (stream, new_attr.hyperlink, true);
1842     }
1843 }
1844 
1845 static const struct term_style_controller controller =
1846 {
1847   get_control_data,
1848   restore,
1849   async_restore,
1850   async_set_attributes_from_default
1851 };
1852 
1853 /* Activate the default attributes.  */
1854 static void
activate_default_attr(term_ostream_t stream)1855 activate_default_attr (term_ostream_t stream)
1856 {
1857   /* Switch back to the default attributes.  */
1858   out_attr_change (stream, stream->default_attr);
1859 
1860   deactivate_term_non_default_mode (&controller, stream);
1861 }
1862 
1863 /* Output the buffered line atomically.
1864    The terminal is left in the the state (regarding colors and attributes)
1865    represented by the simplified attributes goal_attr.  */
1866 static void
output_buffer(term_ostream_t stream,attributes_t goal_attr)1867 output_buffer (term_ostream_t stream, attributes_t goal_attr)
1868 {
1869   const char *cp;
1870   const attributes_t *ap;
1871   size_t len;
1872   size_t n;
1873 
1874   cp = stream->buffer;
1875   ap = stream->attrbuffer;
1876   len = stream->buflen;
1877 
1878   /* See how much we can output without blocking signals.  */
1879   for (n = 0; n < len && equal_attributes (ap[n], stream->active_attr); n++)
1880     ;
1881   if (n > 0)
1882     {
1883       if (full_write (stream->fd, cp, n) < n)
1884         {
1885           int error_code = errno;
1886           /* Do output to stderr only after we have switched back to the
1887              default attributes.  Otherwise this output may come out with
1888              the wrong text attributes.  */
1889           if (!equal_attributes (stream->active_attr, stream->default_attr))
1890             activate_default_attr (stream);
1891           error (EXIT_FAILURE, error_code, _("error writing to %s"),
1892                  stream->filename);
1893         }
1894       cp += n;
1895       ap += n;
1896       len -= n;
1897     }
1898   if (len > 0)
1899     {
1900       if (!equal_attributes (*ap, stream->default_attr))
1901         activate_term_non_default_mode (&controller, stream);
1902 
1903       do
1904         {
1905           /* Activate the attributes in *ap.  */
1906           out_attr_change (stream, *ap);
1907           /* See how many characters we can output without further attribute
1908              changes.  */
1909           for (n = 1; n < len && equal_attributes (ap[n], stream->active_attr); n++)
1910             ;
1911           if (full_write (stream->fd, cp, n) < n)
1912             {
1913               int error_code = errno;
1914               /* Do output to stderr only after we have switched back to the
1915                  default attributes.  Otherwise this output may come out with
1916                  the wrong text attributes.  */
1917               if (!equal_attributes (stream->active_attr, stream->default_attr))
1918                 activate_default_attr (stream);
1919               error (EXIT_FAILURE, error_code, _("error writing to %s"),
1920                      stream->filename);
1921             }
1922           cp += n;
1923           ap += n;
1924           len -= n;
1925         }
1926       while (len > 0);
1927     }
1928   stream->buflen = 0;
1929 
1930   /* Before changing to goal_attr, we may need to enable the non-default
1931      attributes mode.  */
1932   if (!equal_attributes (goal_attr, stream->default_attr))
1933     activate_term_non_default_mode (&controller, stream);
1934   /* Change to goal_attr.  */
1935   if (!equal_attributes (goal_attr, stream->active_attr))
1936     out_attr_change (stream, goal_attr);
1937   /* When we can deactivate the non-default attributes mode, do so.  */
1938   if (equal_attributes (goal_attr, stream->default_attr))
1939     deactivate_term_non_default_mode (&controller, stream);
1940 
1941   /* Free the hyperlink_t objects that are no longer referenced by the
1942      stream->attrbuffer.  */
1943   {
1944     size_t count = stream->hyperlinks_count;
1945     size_t j = 0;
1946     size_t i;
1947     for (i = 0; i < count; i++)
1948       {
1949         /* Here 0 <= j <= i.  */
1950         hyperlink_t *hyperlink = stream->hyperlinks_array[i];
1951         /* stream->default_attr.hyperlink is always == NULL.
1952            stream->simp_attr.hyperlink is either == NULL
1953                                               or == stream->curr_attr.hyperlink.
1954            We can therefore ignore both.  */
1955         if (hyperlink == stream->curr_attr.hyperlink
1956             || hyperlink == stream->active_attr.hyperlink)
1957           {
1958             /* The hyperlink is still in use.  */
1959             stream->hyperlinks_array[j] = hyperlink;
1960             j++;
1961           }
1962         else
1963           {
1964             /* The hyperlink is not in use any more.  */
1965             free_hyperlink (hyperlink);
1966           }
1967       }
1968     stream->hyperlinks_count = j;
1969   }
1970 }
1971 
1972 /* Implementation of ostream_t methods.  */
1973 
1974 static term_color_t
rgb_to_color(term_ostream_t stream,int red,int green,int blue)1975 term_ostream::rgb_to_color (term_ostream_t stream, int red, int green, int blue)
1976 {
1977   switch (stream->colormodel)
1978     {
1979     case cm_monochrome:
1980       return rgb_to_color_monochrome ();
1981     case cm_common8:
1982       return rgb_to_color_common8 (red, green, blue);
1983     case cm_xterm8:
1984       return rgb_to_color_xterm8 (red, green, blue);
1985     case cm_xterm16:
1986       return rgb_to_color_xterm16 (red, green, blue);
1987     case cm_xterm88:
1988       return rgb_to_color_xterm88 (red, green, blue);
1989     case cm_xterm256:
1990       return rgb_to_color_xterm256 (red, green, blue);
1991     case cm_xtermrgb:
1992       return rgb_to_color_xtermrgb (red, green, blue);
1993     default:
1994       abort ();
1995     }
1996 }
1997 
1998 static void
write_mem(term_ostream_t stream,const void * data,size_t len)1999 term_ostream::write_mem (term_ostream_t stream, const void *data, size_t len)
2000 {
2001   const char *cp = (const char *) data;
2002   while (len > 0)
2003     {
2004       /* Look for the next newline.  */
2005       const char *newline = (const char *) memchr (cp, '\n', len);
2006       size_t n = (newline != NULL ? newline - cp : len);
2007 
2008       /* Copy n bytes into the buffer.  */
2009       if (n > stream->allocated - stream->buflen)
2010         {
2011           size_t new_allocated =
2012             xmax (xsum (stream->buflen, n),
2013                   xsum (stream->allocated, stream->allocated));
2014           if (size_overflow_p (new_allocated))
2015             error (EXIT_FAILURE, 0,
2016                    _("%s: too much output, buffer size overflow"),
2017                    "term_ostream");
2018           stream->buffer = (char *) xrealloc (stream->buffer, new_allocated);
2019           stream->attrbuffer =
2020             (attributes_t *)
2021             xrealloc (stream->attrbuffer,
2022                       new_allocated * sizeof (attributes_t));
2023           stream->allocated = new_allocated;
2024         }
2025       memcpy (stream->buffer + stream->buflen, cp, n);
2026       {
2027         attributes_t attr = stream->simp_attr;
2028         attributes_t *ap = stream->attrbuffer + stream->buflen;
2029         attributes_t *ap_end = ap + n;
2030         for (; ap < ap_end; ap++)
2031           *ap = attr;
2032       }
2033       stream->buflen += n;
2034 
2035       if (newline != NULL)
2036         {
2037           output_buffer (stream, stream->default_attr);
2038           if (full_write (stream->fd, "\n", 1) < 1)
2039             error (EXIT_FAILURE, errno, _("error writing to %s"),
2040                    stream->filename);
2041           cp += n + 1; /* cp = newline + 1; */
2042           len -= n + 1;
2043         }
2044       else
2045         break;
2046     }
2047 }
2048 
2049 static void
flush(term_ostream_t stream,ostream_flush_scope_t scope)2050 term_ostream::flush (term_ostream_t stream, ostream_flush_scope_t scope)
2051 {
2052   output_buffer (stream, stream->default_attr);
2053   if (scope == FLUSH_ALL)
2054     {
2055       #if HAVE_WINDOWS_CONSOLES
2056       if (!stream->is_windows_console)
2057       #endif
2058         {
2059           /* For streams connected to a disk file:  */
2060           fsync (stream->fd);
2061           #if HAVE_TCDRAIN
2062           /* For streams connected to a terminal:  */
2063           nonintr_tcdrain (stream->fd);
2064           #endif
2065         }
2066     }
2067 }
2068 
2069 static void
free(term_ostream_t stream)2070 term_ostream::free (term_ostream_t stream)
2071 {
2072   term_ostream_flush (stream, FLUSH_THIS_STREAM);
2073 
2074   deactivate_term_style_controller (&controller, stream);
2075 
2076   free (stream->filename);
2077   if (stream->set_a_foreground != NULL)
2078     free (stream->set_a_foreground);
2079   if (stream->set_foreground != NULL)
2080     free (stream->set_foreground);
2081   if (stream->set_a_background != NULL)
2082     free (stream->set_a_background);
2083   if (stream->set_background != NULL)
2084     free (stream->set_background);
2085   if (stream->orig_pair != NULL)
2086     free (stream->orig_pair);
2087   if (stream->enter_bold_mode != NULL)
2088     free (stream->enter_bold_mode);
2089   if (stream->enter_italics_mode != NULL)
2090     free (stream->enter_italics_mode);
2091   if (stream->exit_italics_mode != NULL)
2092     free (stream->exit_italics_mode);
2093   if (stream->enter_underline_mode != NULL)
2094     free (stream->enter_underline_mode);
2095   if (stream->exit_underline_mode != NULL)
2096     free (stream->exit_underline_mode);
2097   if (stream->exit_attribute_mode != NULL)
2098     free (stream->exit_attribute_mode);
2099   if (stream->hyperlinks_array != NULL)
2100     {
2101       size_t count = stream->hyperlinks_count;
2102       size_t i;
2103       for (i = 0; i < count; i++)
2104         free_hyperlink (stream->hyperlinks_array[i]);
2105       free (stream->hyperlinks_array);
2106     }
2107   free (stream->buffer);
2108   free (stream->attrbuffer);
2109   free (stream);
2110 }
2111 
2112 /* Implementation of term_ostream_t methods.  */
2113 
2114 static term_color_t
get_color(term_ostream_t stream)2115 term_ostream::get_color (term_ostream_t stream)
2116 {
2117   return stream->curr_attr.color;
2118 }
2119 
2120 static void
set_color(term_ostream_t stream,term_color_t color)2121 term_ostream::set_color (term_ostream_t stream, term_color_t color)
2122 {
2123   stream->curr_attr.color = color;
2124   stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2125 }
2126 
2127 static term_color_t
get_bgcolor(term_ostream_t stream)2128 term_ostream::get_bgcolor (term_ostream_t stream)
2129 {
2130   return stream->curr_attr.bgcolor;
2131 }
2132 
2133 static void
set_bgcolor(term_ostream_t stream,term_color_t color)2134 term_ostream::set_bgcolor (term_ostream_t stream, term_color_t color)
2135 {
2136   stream->curr_attr.bgcolor = color;
2137   stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2138 }
2139 
2140 static term_weight_t
get_weight(term_ostream_t stream)2141 term_ostream::get_weight (term_ostream_t stream)
2142 {
2143   return stream->curr_attr.weight;
2144 }
2145 
2146 static void
set_weight(term_ostream_t stream,term_weight_t weight)2147 term_ostream::set_weight (term_ostream_t stream, term_weight_t weight)
2148 {
2149   stream->curr_attr.weight = weight;
2150   stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2151 }
2152 
2153 static term_posture_t
get_posture(term_ostream_t stream)2154 term_ostream::get_posture (term_ostream_t stream)
2155 {
2156   return stream->curr_attr.posture;
2157 }
2158 
2159 static void
set_posture(term_ostream_t stream,term_posture_t posture)2160 term_ostream::set_posture (term_ostream_t stream, term_posture_t posture)
2161 {
2162   stream->curr_attr.posture = posture;
2163   stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2164 }
2165 
2166 static term_underline_t
get_underline(term_ostream_t stream)2167 term_ostream::get_underline (term_ostream_t stream)
2168 {
2169   return stream->curr_attr.underline;
2170 }
2171 
2172 static void
set_underline(term_ostream_t stream,term_underline_t underline)2173 term_ostream::set_underline (term_ostream_t stream, term_underline_t underline)
2174 {
2175   stream->curr_attr.underline = underline;
2176   stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2177 }
2178 
2179 static const char *
get_hyperlink_ref(term_ostream_t stream)2180 term_ostream::get_hyperlink_ref (term_ostream_t stream)
2181 {
2182   hyperlink_t *hyperlink = stream->curr_attr.hyperlink;
2183   return (hyperlink != NULL ? hyperlink->ref : NULL);
2184 }
2185 
2186 static const char *
get_hyperlink_id(term_ostream_t stream)2187 term_ostream::get_hyperlink_id (term_ostream_t stream)
2188 {
2189   hyperlink_t *hyperlink = stream->curr_attr.hyperlink;
2190   return (hyperlink != NULL ? hyperlink->id : NULL);
2191 }
2192 
2193 static void
set_hyperlink(term_ostream_t stream,const char * ref,const char * id)2194 term_ostream::set_hyperlink (term_ostream_t stream,
2195                              const char *ref, const char *id)
2196 {
2197   if (ref == NULL)
2198     stream->curr_attr.hyperlink = NULL;
2199   else
2200     {
2201       /* Create a new hyperlink_t object.  */
2202       hyperlink_t *hyperlink = XMALLOC (hyperlink_t);
2203 
2204       hyperlink->ref = xstrdup (ref);
2205       if (id != NULL)
2206         {
2207           hyperlink->id = xstrdup (id);
2208           hyperlink->real_id = hyperlink->id;
2209         }
2210       else
2211         {
2212           hyperlink->id = NULL;
2213           if (stream->supports_hyperlink)
2214             {
2215               /* Generate an id always, since we don't know at this point
2216                  whether the hyperlink will span multiple lines.  */
2217               hyperlink->real_id = generate_hyperlink_id (stream);
2218             }
2219           else
2220             hyperlink->real_id = NULL;
2221         }
2222 
2223       /* Store it.  */
2224       if (stream->hyperlinks_count == stream->hyperlinks_allocated)
2225         {
2226           stream->hyperlinks_allocated = 2 * stream->hyperlinks_allocated + 10;
2227           stream->hyperlinks_array =
2228             (hyperlink_t **)
2229             xrealloc (stream->hyperlinks_array,
2230                       stream->hyperlinks_allocated * sizeof (hyperlink_t *));
2231         }
2232       stream->hyperlinks_array[stream->hyperlinks_count++] = hyperlink;
2233 
2234       /* Install it.  */
2235       stream->curr_attr.hyperlink = hyperlink;
2236     }
2237   stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2238 }
2239 
2240 static void
flush_to_current_style(term_ostream_t stream)2241 term_ostream::flush_to_current_style (term_ostream_t stream)
2242 {
2243   output_buffer (stream, stream->simp_attr);
2244 }
2245 
2246 /* Constructor.  */
2247 
2248 static inline char *
xstrdup0(const char * str)2249 xstrdup0 (const char *str)
2250 {
2251   if (str == NULL)
2252     return NULL;
2253 #if HAVE_TERMINFO
2254   if (str == (const char *)(-1))
2255     return NULL;
2256 #endif
2257   return xstrdup (str);
2258 }
2259 
2260 /* Returns the base name of the terminal emulator program, possibly truncated,
2261    as a freshly allocated string, or NULL if it cannot be determined.
2262    Note: This function is a hack.  It does not work across ssh, and it may fail
2263    in some local situations as well.  */
2264 static inline char *
get_terminal_emulator_progname(void)2265 get_terminal_emulator_progname (void)
2266 {
2267   #if HAVE_GETSID
2268   /* Get the process id of the session leader.
2269      When running in a terminal emulator, it's the shell process that was
2270      spawned by the terminal emulator.  When running in a console, it's the
2271      'login' process.
2272      On some operating systems (Linux, *BSD, AIX), the same result could also
2273      be obtained through
2274        pid_t p;
2275        if (ioctl (1, TIOCGSID, &p) >= 0) ...
2276    */
2277   pid_t session_leader_pid = getsid (0);
2278   if (session_leader_pid != (pid_t)(-1))
2279     {
2280       /* Get the process id of the terminal emulator.
2281          When running in a console, it's the process id of the 'init'
2282          process.  */
2283       pid_t terminal_emulator_pid = get_ppid_of (session_leader_pid);
2284       if (terminal_emulator_pid != 0)
2285         {
2286           /* Retrieve the base name of the program name of this process.  */
2287           return get_progname_of (terminal_emulator_pid);
2288         }
2289     }
2290   #endif
2291   return NULL;
2292 }
2293 
2294 /* Returns true if we should enable hyperlinks.
2295    term is the value of the TERM environment variable.  */
2296 static inline bool
should_enable_hyperlinks(const char * term)2297 should_enable_hyperlinks (const char *term)
2298 {
2299   if (getenv ("NO_TERM_HYPERLINKS") != NULL)
2300     /* The user has disabled hyperlinks.  */
2301     return false;
2302 
2303   /* Dispatch based on $TERM.  */
2304   if (term != NULL)
2305     {
2306       /* rxvt-based terminal emulators:
2307            Program           | TERM         | Supports hyperlinks?
2308            ------------------+--------------+-------------------------------------
2309            rxvt 2.7.10       | rxvt         | hangs after "cat hyperlink-demo.txt"
2310            mrxvt 0.5.3       | rxvt         | no
2311            rxvt-unicode 9.22 | rxvt-unicode | no
2312        */
2313       if (strcmp (term, "rxvt") == 0)
2314         return false;
2315 
2316       /* Emacs-based terminal emulators:
2317            Program             | TERM        | Supports hyperlinks?
2318            --------------------+-------------+---------------------
2319            emacs-terminal 26.1 | eterm-color | produces garbage
2320        */
2321       if (strncmp (term, "eterm", 5) == 0)
2322         return false;
2323 
2324       /* xterm-compatible terminal emulators:
2325            Program          | TERM           | Supports hyperlinks?
2326            -----------------+----------------+---------------------------
2327            guake 0.8.8      | xterm          | produces garbage
2328            lilyterm 0.9.9.2 | xterm          | produces garbage
2329            lterm 1.5.1      | xterm          | produces garbage
2330            lxterminal 0.3.2 | xterm          | produces garbage
2331            termit 2.9.6     | xterm          | produces garbage
2332            konsole 18.12.3  | xterm-256color | produces extra backslashes
2333            yakuake 3.0.5    | xterm-256color | produces extra backslashes
2334            other            |                | yes or no, no severe bugs
2335 
2336          TODO: Revisit this table periodically.
2337        */
2338       if (strncmp (term, "xterm", 5) == 0)
2339         {
2340           char *progname = get_terminal_emulator_progname ();
2341           if (progname != NULL)
2342             {
2343               bool known_buggy =
2344                 strncmp (progname, "python", 6) == 0 /* guake */
2345                 || strcmp (progname, "lilyterm") == 0
2346                 || strcmp (progname, "lterm") == 0
2347                 || strcmp (progname, "lxterminal") == 0
2348                 || strcmp (progname, "termit") == 0
2349                 || strcmp (progname, "konsole") == 0
2350                 || strcmp (progname, "yakuake") == 0;
2351               free (progname);
2352               /* Enable hyperlinks except for programs that are known buggy.  */
2353               return !known_buggy;
2354             }
2355         }
2356     }
2357 
2358   /* In case of doubt, enable hyperlinks.  So this code does not need to change
2359      as more and more terminal emulators support hyperlinks.
2360      If there are adverse effects, the user can disable hyperlinks by setting
2361      NO_TERM_HYPERLINKS.  */
2362   return true;
2363 }
2364 
2365 term_ostream_t
term_ostream_create(int fd,const char * filename,ttyctl_t tty_control)2366 term_ostream_create (int fd, const char *filename, ttyctl_t tty_control)
2367 {
2368   term_ostream_t stream = XMALLOC (struct term_ostream_representation);
2369 
2370   stream->base.vtable = &term_ostream_vtable;
2371   stream->fd = fd;
2372   #if HAVE_WINDOWS_CONSOLES
2373   stream->handle = (HANDLE) _get_osfhandle (fd);
2374   {
2375     DWORD mode;
2376 
2377     if (stream->handle != INVALID_HANDLE_VALUE
2378         /* GetConsoleMode
2379            <https://docs.microsoft.com/en-us/windows/console/getconsolemode>  */
2380         && GetConsoleMode (stream->handle, &mode) != 0)
2381       {
2382         CONSOLE_SCREEN_BUFFER_INFO info;
2383         BOOL ok;
2384 
2385         /* GetConsoleScreenBufferInfo
2386            <https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo>
2387            <https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str>  */
2388         ok = GetConsoleScreenBufferInfo (stream->handle, &info);
2389         if (!ok)
2390           {
2391             /* GetConsoleScreenBufferInfo
2392                  - fails when the handle is == GetStdHandle (STD_INPUT_HANDLE)
2393                  - but succeeds when it is == GetStdHandle (STD_OUTPUT_HANDLE)
2394                    or == GetStdHandle (STD_ERROR_HANDLE).
2395                Native Windows programs use GetStdHandle (STD_OUTPUT_HANDLE) for
2396                fd 1, as expected.
2397                But Cygwin uses GetStdHandle (STD_INPUT_HANDLE) for all of fd 0,
2398                1, 2.  So, we have to use either GetStdHandle (STD_OUTPUT_HANDLE)
2399                or GetStdHandle (STD_ERROR_HANDLE) in order to be able to use
2400                GetConsoleScreenBufferInfo.  */
2401             if (fd == 1 || fd == 2)
2402               {
2403                 HANDLE handle =
2404                   GetStdHandle (fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
2405                 ok = GetConsoleScreenBufferInfo (handle, &info);
2406                 if (ok)
2407                   stream->handle = handle;
2408               }
2409           }
2410         if (ok)
2411           {
2412             stream->is_windows_console = true;
2413             stream->default_console_attributes = info.wAttributes;
2414             stream->current_console_attributes = stream->default_console_attributes;
2415           }
2416         else
2417           /* It's a console, but we cannot use GetConsoleScreenBufferInfo.  */
2418           stream->is_windows_console = false;
2419       }
2420     else
2421       stream->is_windows_console = false;
2422   }
2423   #endif
2424   stream->filename = xstrdup (filename);
2425 
2426   /* Defaults.  */
2427   stream->max_colors = -1;
2428   stream->no_color_video = -1;
2429   stream->set_a_foreground = NULL;
2430   stream->set_foreground = NULL;
2431   stream->set_a_background = NULL;
2432   stream->set_background = NULL;
2433   stream->orig_pair = NULL;
2434   stream->enter_bold_mode = NULL;
2435   stream->enter_italics_mode = NULL;
2436   stream->exit_italics_mode = NULL;
2437   stream->enter_underline_mode = NULL;
2438   stream->exit_underline_mode = NULL;
2439   stream->exit_attribute_mode = NULL;
2440 
2441   #if HAVE_WINDOWS_CONSOLES
2442   if (stream->is_windows_console)
2443     {
2444       /* For Windows consoles, two approaches are possible:
2445          (A) Use SetConsoleMode
2446              <https://docs.microsoft.com/en-us/windows/console/setconsolemode>
2447              to enable the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag, and then
2448              emit escape sequences, as documented in
2449              <https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences>.
2450          (B) Use SetConsoleTextAttribute
2451              <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
2452              to change the text attributes.
2453          Approach (A) has two drawbacks:
2454            * It produces colors that ignore the console's configuration: it
2455              assumes the default configuration (light grey foreground).  Thus
2456              when you ask for cyan, you will always get some blue color, never
2457              real cyan.  Whereas approach (B) produces colors that respect the
2458              "Screen Text" and "Screen Background" settings in the console's
2459              configuration.
2460            * When the program terminates abnormally, we would leave the console
2461              with ENABLE_VIRTUAL_TERMINAL_PROCESSING enabled, which can be
2462              dangerous.
2463          Therefore we use approach (B).  */
2464       stream->max_colors = 8;
2465       stream->no_color_video = 1 | 4;
2466       stream->supports_foreground = true;
2467       stream->supports_background = true;
2468       stream->colormodel = cm_common8;
2469       /* The Windows consoles have high and low intensity, but the default is
2470          high intensity.  If we wanted to support WEIGHT_BOLD, we would have to
2471          use low-intensity rendering for normal output, which would look ugly
2472          compared to the output by other programs.  We could support WEIGHT_DIM,
2473          but this is not part of our enum term_weight_t.  */
2474       stream->supports_weight = false;
2475       stream->supports_posture = false;
2476       stream->supports_underline = true;
2477       stream->supports_hyperlink = false;
2478       stream->restore_colors = NULL;
2479       stream->restore_weight = NULL;
2480       stream->restore_posture = NULL;
2481       stream->restore_underline = NULL;
2482       stream->restore_hyperlink = NULL;
2483     }
2484   else
2485   #endif
2486     {
2487       const char *term;
2488 
2489       /* Retrieve the terminal type.  */
2490       term = getenv ("TERM");
2491       if (term != NULL && term[0] != '\0')
2492         {
2493           /* When the terminfo function are available, we prefer them over the
2494              termcap functions because
2495                1. they don't risk a buffer overflow,
2496                2. on OSF/1, for TERM=xterm, the tiget* functions provide access
2497                   to the number of colors and the color escape sequences,
2498                   whereas the tget* functions don't provide them.  */
2499           #if HAVE_TERMINFO
2500           int err = 1;
2501 
2502           if (setupterm (term, fd, &err) || err == 1)
2503             {
2504               /* Retrieve particular values depending on the terminal type.  */
2505               stream->max_colors = tigetnum ("colors");
2506               stream->no_color_video = tigetnum ("ncv");
2507               stream->set_a_foreground = xstrdup0 (tigetstr ("setaf"));
2508               stream->set_foreground = xstrdup0 (tigetstr ("setf"));
2509               stream->set_a_background = xstrdup0 (tigetstr ("setab"));
2510               stream->set_background = xstrdup0 (tigetstr ("setb"));
2511               stream->orig_pair = xstrdup0 (tigetstr ("op"));
2512               stream->enter_bold_mode = xstrdup0 (tigetstr ("bold"));
2513               stream->enter_italics_mode = xstrdup0 (tigetstr ("sitm"));
2514               stream->exit_italics_mode = xstrdup0 (tigetstr ("ritm"));
2515               stream->enter_underline_mode = xstrdup0 (tigetstr ("smul"));
2516               stream->exit_underline_mode = xstrdup0 (tigetstr ("rmul"));
2517               stream->exit_attribute_mode = xstrdup0 (tigetstr ("sgr0"));
2518             }
2519           #elif HAVE_TERMCAP
2520           struct { char buf[1024]; char canary[4]; } termcapbuf;
2521           int retval;
2522 
2523           /* Call tgetent, being defensive against buffer overflow.  */
2524           memcpy (termcapbuf.canary, "CnRy", 4);
2525           retval = tgetent (termcapbuf.buf, term);
2526           if (memcmp (termcapbuf.canary, "CnRy", 4) != 0)
2527             /* Buffer overflow!  */
2528             abort ();
2529 
2530           if (retval > 0)
2531             {
2532               struct { char buf[1024]; char canary[4]; } termentrybuf;
2533               char *termentryptr;
2534 
2535               /* Prepare for calling tgetstr, being defensive against buffer
2536                  overflow.  ncurses' tgetstr() supports a second argument NULL,
2537                  but NetBSD's tgetstr() doesn't.  */
2538               memcpy (termentrybuf.canary, "CnRz", 4);
2539               #define TEBP ((termentryptr = termentrybuf.buf), &termentryptr)
2540 
2541               /* Retrieve particular values depending on the terminal type.  */
2542               stream->max_colors = tgetnum ("Co");
2543               stream->no_color_video = tgetnum ("NC");
2544               stream->set_a_foreground = xstrdup0 (tgetstr ("AF", TEBP));
2545               stream->set_foreground = xstrdup0 (tgetstr ("Sf", TEBP));
2546               stream->set_a_background = xstrdup0 (tgetstr ("AB", TEBP));
2547               stream->set_background = xstrdup0 (tgetstr ("Sb", TEBP));
2548               stream->orig_pair = xstrdup0 (tgetstr ("op", TEBP));
2549               stream->enter_bold_mode = xstrdup0 (tgetstr ("md", TEBP));
2550               stream->enter_italics_mode = xstrdup0 (tgetstr ("ZH", TEBP));
2551               stream->exit_italics_mode = xstrdup0 (tgetstr ("ZR", TEBP));
2552               stream->enter_underline_mode = xstrdup0 (tgetstr ("us", TEBP));
2553               stream->exit_underline_mode = xstrdup0 (tgetstr ("ue", TEBP));
2554               stream->exit_attribute_mode = xstrdup0 (tgetstr ("me", TEBP));
2555 
2556               #ifdef __BEOS__
2557               /* The BeOS termcap entry for "beterm" is broken: For "AF" and
2558                  "AB" it contains balues in terminfo syntax but the system's
2559                  tparam() function understands only the termcap syntax.  */
2560               if (stream->set_a_foreground != NULL
2561                   && strcmp (stream->set_a_foreground, "\033[3%p1%dm") == 0)
2562                 {
2563                   free (stream->set_a_foreground);
2564                   stream->set_a_foreground = xstrdup ("\033[3%dm");
2565                 }
2566               if (stream->set_a_background != NULL
2567                   && strcmp (stream->set_a_background, "\033[4%p1%dm") == 0)
2568                 {
2569                   free (stream->set_a_background);
2570                   stream->set_a_background = xstrdup ("\033[4%dm");
2571                 }
2572               #endif
2573 
2574               /* The termcap entry for cygwin is broken: It has no "ncv" value,
2575                  but bold and underline are actually rendered through colors.  */
2576               if (strcmp (term, "cygwin") == 0)
2577                 stream->no_color_video |= 2 | 32;
2578 
2579               /* Done with tgetstr.  Detect possible buffer overflow.  */
2580               #undef TEBP
2581               if (memcmp (termentrybuf.canary, "CnRz", 4) != 0)
2582                 /* Buffer overflow!  */
2583                 abort ();
2584             }
2585           #else
2586           /* Fallback code for platforms with neither the terminfo nor the
2587              termcap functions, such as mingw.
2588              Assume the ANSI escape sequences.  Extracted through
2589              "TERM=ansi infocmp", replacing \E with \033.  */
2590           stream->max_colors = 8;
2591           stream->no_color_video = 3;
2592           stream->set_a_foreground = xstrdup ("\033[3%p1%dm");
2593           stream->set_a_background = xstrdup ("\033[4%p1%dm");
2594           stream->orig_pair = xstrdup ("\033[39;49m");
2595           stream->enter_bold_mode = xstrdup ("\033[1m");
2596           stream->enter_underline_mode = xstrdup ("\033[4m");
2597           stream->exit_underline_mode = xstrdup ("\033[m");
2598           stream->exit_attribute_mode = xstrdup ("\033[0;10m");
2599           #endif
2600 
2601           /* AIX 4.3.2, IRIX 6.5, HP-UX 11, Solaris 7..10 all lack the
2602              description of color capabilities of "xterm" and "xterms"
2603              in their terminfo database.  But it is important to have
2604              color in xterm.  So we provide the color capabilities here.  */
2605           if (stream->max_colors <= 1
2606               && (strcmp (term, "xterm") == 0 || strcmp (term, "xterms") == 0))
2607             {
2608               stream->max_colors = 8;
2609               stream->set_a_foreground = xstrdup ("\033[3%p1%dm");
2610               stream->set_a_background = xstrdup ("\033[4%p1%dm");
2611               stream->orig_pair = xstrdup ("\033[39;49m");
2612             }
2613         }
2614 
2615       /* Infer the capabilities.  */
2616       stream->supports_foreground =
2617         (stream->max_colors >= 8
2618          && (stream->set_a_foreground != NULL || stream->set_foreground != NULL)
2619          && stream->orig_pair != NULL);
2620       stream->supports_background =
2621         (stream->max_colors >= 8
2622          && (stream->set_a_background != NULL || stream->set_background != NULL)
2623          && stream->orig_pair != NULL);
2624       stream->colormodel =
2625         (stream->supports_foreground || stream->supports_background
2626          ? (term != NULL
2627             && (/* Recognize xterm-16color, xterm-88color, xterm-256color.  */
2628                 (strlen (term) >= 5 && memcmp (term, "xterm", 5) == 0)
2629                 || /* Recognize *-16color.  */
2630                    (strlen (term) > 8
2631                     && strcmp (term + strlen (term) - 8, "-16color") == 0)
2632                 || /* Recognize *-256color.  */
2633                    (strlen (term) > 9
2634                     && strcmp (term + strlen (term) - 9, "-256color") == 0)
2635                 || /* Recognize *-direct.  */
2636                    (strlen (term) > 8
2637                     && strcmp (term + strlen (term) - 8, "-direct") == 0))
2638             ? (stream->max_colors >= 0x7fff ? cm_xtermrgb :
2639                stream->max_colors == 256 ? cm_xterm256 :
2640                stream->max_colors == 88 ? cm_xterm88 :
2641                stream->max_colors == 16 ? cm_xterm16 :
2642                cm_xterm8)
2643             : cm_common8)
2644          : cm_monochrome);
2645       stream->supports_weight =
2646         (stream->enter_bold_mode != NULL
2647          && stream->exit_attribute_mode != NULL);
2648       stream->supports_posture =
2649         (stream->enter_italics_mode != NULL
2650          && (stream->exit_italics_mode != NULL
2651              || stream->exit_attribute_mode != NULL));
2652       stream->supports_underline =
2653         (stream->enter_underline_mode != NULL
2654          && (stream->exit_underline_mode != NULL
2655              || stream->exit_attribute_mode != NULL));
2656       /* TODO: Use a terminfo capability, once ncurses implements it.  */
2657       stream->supports_hyperlink = should_enable_hyperlinks (term);
2658 
2659       /* Infer the restore strings.  */
2660       stream->restore_colors =
2661         (stream->supports_foreground || stream->supports_background
2662          ? stream->orig_pair
2663          : NULL);
2664       stream->restore_weight =
2665         (stream->supports_weight ? stream->exit_attribute_mode : NULL);
2666       stream->restore_posture =
2667         (stream->supports_posture
2668          ? (stream->exit_italics_mode != NULL
2669             ? stream->exit_italics_mode
2670             : stream->exit_attribute_mode)
2671          : NULL);
2672       stream->restore_underline =
2673         (stream->supports_underline
2674          ? (stream->exit_underline_mode != NULL
2675             ? stream->exit_underline_mode
2676             : stream->exit_attribute_mode)
2677          : NULL);
2678       stream->restore_hyperlink =
2679         (stream->supports_hyperlink
2680          ? "\033]8;;\033\\"
2681          : NULL);
2682     }
2683 
2684   /* Initialize the hyperlink id generator.  */
2685   if (stream->supports_hyperlink)
2686     {
2687       char *hostname = xgethostname ();
2688       { /* Compute a hash code, like in gnulib/lib/hash-pjw.c.  */
2689         uint32_t h = 0;
2690         const char *p;
2691         for (p = hostname; *p; p++)
2692           h = (unsigned char) *p + ((h << 9) | (h >> (32 - 9)));
2693         stream->hostname_hash = h;
2694       }
2695       free (hostname);
2696 
2697       {
2698         struct timeval tv;
2699         gettimeofday (&tv, NULL);
2700         stream->start_time =
2701           (uint64_t) tv.tv_sec * (uint64_t) 1000000 + (uint64_t) tv.tv_usec;
2702       }
2703 
2704       stream->id_serial = 0;
2705     }
2706 
2707   /* Initialize the set of hyperlink_t.  */
2708   stream->hyperlinks_array = NULL;
2709   stream->hyperlinks_count = 0;
2710   stream->hyperlinks_allocated = 0;
2711 
2712   /* Initialize the buffer.  */
2713   stream->allocated = 120;
2714   stream->buffer = XNMALLOC (stream->allocated, char);
2715   stream->attrbuffer = XNMALLOC (stream->allocated, attributes_t);
2716   stream->buflen = 0;
2717 
2718   /* Initialize the current attributes.  */
2719   {
2720     attributes_t assumed_default;
2721     attributes_t simplified_default;
2722 
2723     assumed_default.color = COLOR_DEFAULT;
2724     assumed_default.bgcolor = COLOR_DEFAULT;
2725     assumed_default.weight = WEIGHT_DEFAULT;
2726     assumed_default.posture = POSTURE_DEFAULT;
2727     assumed_default.underline = UNDERLINE_DEFAULT;
2728     assumed_default.hyperlink = NULL;
2729 
2730     simplified_default = simplify_attributes (stream, assumed_default);
2731 
2732     stream->default_attr = simplified_default;
2733     stream->active_attr = simplified_default;
2734     stream->curr_attr = assumed_default;
2735     stream->simp_attr = simplified_default;
2736   }
2737 
2738   /* Prepare tty control.  */
2739   activate_term_style_controller (&controller, stream, fd, tty_control);
2740 
2741   return stream;
2742 }
2743