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