1 /* Output stream for attributed text, producing ANSI escape sequences.
2 Copyright (C) 2006-2008, 2017, 2019-2020, 2022-2023 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 ttyctl_t tty_control;
1045 /* Values from the terminal type's terminfo/termcap description.
1046 See terminfo(5) for details. */
1047 /* terminfo termcap */
1048 int max_colors; /* colors Co */
1049 int no_color_video; /* ncv NC */
1050 char * volatile set_a_foreground; /* setaf AF */
1051 char * volatile set_foreground; /* setf Sf */
1052 char * volatile set_a_background; /* setab AB */
1053 char * volatile set_background; /* setb Sb */
1054 char *orig_pair; /* op op */
1055 char * volatile enter_bold_mode; /* bold md */
1056 char * volatile enter_italics_mode; /* sitm ZH */
1057 char *exit_italics_mode; /* ritm ZR */
1058 char * volatile enter_underline_mode; /* smul us */
1059 char *exit_underline_mode; /* rmul ue */
1060 char *exit_attribute_mode; /* sgr0 me */
1061 /* Inferred values. */
1062 bool volatile supports_foreground;
1063 bool volatile supports_background;
1064 colormodel_t volatile colormodel;
1065 bool volatile supports_weight;
1066 bool volatile supports_posture;
1067 bool volatile supports_underline;
1068 bool volatile supports_hyperlink;
1069 /* Inferred values for the exit handler and the signal handlers. */
1070 const char * volatile restore_colors;
1071 const char * volatile restore_weight;
1072 const char * volatile restore_posture;
1073 const char * volatile restore_underline;
1074 const char * volatile restore_hyperlink;
1075 /* Signal handling and tty control. */
1076 struct term_style_control_data control_data;
1077 /* State for producing hyperlink ids. */
1078 uint32_t hostname_hash;
1079 uint64_t start_time;
1080 uint32_t id_serial;
1081 /* Set of hyperlink_t that are currently in use. */
1082 hyperlink_t **hyperlinks_array;
1083 size_t hyperlinks_count;
1084 size_t hyperlinks_allocated;
1085 /* Variable state, representing past output. */
1086 #if HAVE_WINDOWS_CONSOLES
1087 WORD volatile default_console_attributes;
1088 WORD volatile current_console_attributes;
1089 #endif
1090 attributes_t default_attr; /* Default simplified attributes of the
1091 terminal. */
1092 attributes_t volatile active_attr; /* Simplified attributes that we have set
1093 on the terminal. */
1094 term_color_t volatile active_attr_color; /* Same as active_attr.color,
1095 atomically accessible. */
1096 term_color_t volatile active_attr_bgcolor; /* Same as active_attr.bgcolor,
1097 atomically accessible. */
1098 hyperlink_t *volatile active_attr_hyperlink; /* Same as active_attr.hyperlink,
1099 atomically accessible. */
1100 /* Variable state, representing future output. */
1101 char *buffer; /* Buffer for the current line. */
1102 attributes_t *attrbuffer; /* Buffer for the simplified attributes;
1103 same length as buffer. */
1104 size_t buflen; /* Number of bytes stored so far. */
1105 size_t allocated; /* Allocated size of the buffer. */
1106 attributes_t curr_attr; /* Current attributes. */
1107 attributes_t simp_attr; /* Simplified current attributes. */
1108 };
1109
1110 static struct term_style_control_data *
1111 get_control_data (term_ostream_t stream)
1112 {
1113 return &stream->control_data;
1114 }
1115
1116 /* Simplify attributes, according to the terminal's capabilities. */
1117 static attributes_t
1118 simplify_attributes (term_ostream_t stream, attributes_t attr)
1119 {
1120 if ((attr.color != COLOR_DEFAULT || attr.bgcolor != COLOR_DEFAULT)
1121 && stream->no_color_video > 0)
1122 {
1123 /* When colors and attributes can not be represented simultaneously,
1124 we give preference to the color. */
1125 if (stream->no_color_video & 2)
1126 /* Colors conflict with underlining. */
1127 attr.underline = UNDERLINE_OFF;
1128 if (stream->no_color_video & 32)
1129 /* Colors conflict with bold weight. */
1130 attr.weight = WEIGHT_NORMAL;
1131 }
1132 if (!stream->supports_foreground)
1133 attr.color = COLOR_DEFAULT;
1134 if (!stream->supports_background)
1135 attr.bgcolor = COLOR_DEFAULT;
1136 if (!stream->supports_weight)
1137 attr.weight = WEIGHT_DEFAULT;
1138 if (!stream->supports_posture)
1139 attr.posture = POSTURE_DEFAULT;
1140 if (!stream->supports_underline)
1141 attr.underline = UNDERLINE_DEFAULT;
1142 if (!stream->supports_hyperlink)
1143 attr.hyperlink = NULL;
1144 return attr;
1145 }
1146
1147 /* Generate an id for a hyperlink. */
1148 static char *
1149 generate_hyperlink_id (term_ostream_t stream)
1150 {
1151 /* A UUID would be optimal, but is overkill here. An id of 128 bits
1152 (32 hexadecimal digits) should be sufficient. */
1153 static const char hexdigits[16] =
1154 {
1155 '0', '1', '2', '3', '4', '5', '6', '7',
1156 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
1157 };
1158 char *id = (char *) xmalloc (128 / 4 + 1);
1159 uint32_t words[4] =
1160 {
1161 stream->hostname_hash,
1162 (uint32_t) (stream->start_time >> 32),
1163 (uint32_t) stream->start_time,
1164 stream->id_serial
1165 };
1166 char *p = id;
1167 unsigned int i;
1168 for (i = 0; i < 4; i++)
1169 {
1170 uint32_t word = words[i];
1171 unsigned int j;
1172 for (j = 0; j < 32 / 4; j++)
1173 *p++ = hexdigits[(word >> (32 - 4 * (j + 1))) & 0x0f];
1174 }
1175 *p = '\0';
1176 stream->id_serial++;
1177 return id;
1178 }
1179
1180 /* Stream that contains information about how the various out_* functions shall
1181 do output. */
1182 static term_ostream_t volatile out_stream;
1183
1184 /* File descriptor to which out_char and out_char_unchecked shall output escape
1185 sequences.
1186 Same as (out_stream != NULL ? out_stream->fd : -1). */
1187 static int volatile out_fd = -1;
1188
1189 /* Signal error after full_write failed. */
1190 static void
1191 out_error (void)
1192 {
1193 error (EXIT_FAILURE, errno, _("error writing to %s"), out_stream->filename);
1194 }
1195
1196 /* Output a single char to out_fd. */
1197 static int
1198 out_char (int c)
1199 {
1200 char bytes[1];
1201
1202 bytes[0] = (char)c;
1203 /* We have to write directly to the file descriptor, not to a buffer with
1204 the same destination, because of the padding and sleeping that tputs()
1205 does. */
1206 if (full_write (out_fd, bytes, 1) < 1)
1207 out_error ();
1208 return 0;
1209 }
1210
1211 /* Output a single char to out_fd. Ignore errors. */
1212 static _GL_ASYNC_SAFE int
1213 out_char_unchecked (int c)
1214 {
1215 char bytes[1];
1216
1217 bytes[0] = (char)c;
1218 full_write (out_fd, bytes, 1);
1219 return 0;
1220 }
1221
1222 /* Output escape sequences to switch the foreground color to NEW_COLOR. */
1223 static _GL_ASYNC_SAFE void
1224 out_color_change (term_ostream_t stream, term_color_t new_color,
1225 bool async_safe)
1226 {
1227 assert (stream->supports_foreground);
1228 assert (new_color != COLOR_DEFAULT);
1229 switch (stream->colormodel)
1230 {
1231 case cm_common8:
1232 assert (new_color >= 0 && new_color < 8);
1233 #if HAVE_WINDOWS_CONSOLES
1234 if (stream->is_windows_console)
1235 {
1236 /* SetConsoleTextAttribute
1237 <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1238 <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */
1239 /* Assign to stream->current_console_attributes *before* calling
1240 SetConsoleTextAttribute, otherwise async_set_attributes_from_default
1241 will not do its job correctly. */
1242 stream->current_console_attributes =
1243 (stream->current_console_attributes & ~(7 << 0))
1244 | (new_color << 0);
1245 SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1246 }
1247 else
1248 #endif
1249 {
1250 if (stream->set_a_foreground != NULL)
1251 tputs (tparm (stream->set_a_foreground, color_bgr (new_color)),
1252 1, async_safe ? out_char_unchecked : out_char);
1253 else
1254 tputs (tparm (stream->set_foreground, new_color),
1255 1, async_safe ? out_char_unchecked : out_char);
1256 }
1257 break;
1258 /* When we are dealing with an xterm, there is no need to go through
1259 tputs() because we know there is no padding and sleeping. */
1260 case cm_xterm8:
1261 assert (new_color >= 0 && new_color < 8);
1262 {
1263 char bytes[5];
1264 bytes[0] = 0x1B; bytes[1] = '[';
1265 bytes[2] = '3'; bytes[3] = '0' + new_color;
1266 bytes[4] = 'm';
1267 if (full_write (out_fd, bytes, 5) < 5)
1268 if (!async_safe)
1269 out_error ();
1270 }
1271 break;
1272 case cm_xterm16:
1273 assert (new_color >= 0 && new_color < 16);
1274 {
1275 char bytes[5];
1276 bytes[0] = 0x1B; bytes[1] = '[';
1277 if (new_color < 8)
1278 {
1279 bytes[2] = '3'; bytes[3] = '0' + new_color;
1280 }
1281 else
1282 {
1283 bytes[2] = '9'; bytes[3] = '0' + (new_color - 8);
1284 }
1285 bytes[4] = 'm';
1286 if (full_write (out_fd, bytes, 5) < 5)
1287 if (!async_safe)
1288 out_error ();
1289 }
1290 break;
1291 case cm_xterm88:
1292 assert (new_color >= 0 && new_color < 88);
1293 {
1294 char bytes[10];
1295 char *p;
1296 bytes[0] = 0x1B; bytes[1] = '[';
1297 bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
1298 bytes[5] = '5'; bytes[6] = ';';
1299 p = bytes + 7;
1300 if (new_color >= 10)
1301 *p++ = '0' + (new_color / 10);
1302 *p++ = '0' + (new_color % 10);
1303 *p++ = 'm';
1304 if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1305 if (!async_safe)
1306 out_error ();
1307 }
1308 break;
1309 case cm_xterm256:
1310 assert (new_color >= 0 && new_color < 256);
1311 {
1312 char bytes[11];
1313 char *p;
1314 bytes[0] = 0x1B; bytes[1] = '[';
1315 bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
1316 bytes[5] = '5'; bytes[6] = ';';
1317 p = bytes + 7;
1318 if (new_color >= 100)
1319 *p++ = '0' + (new_color / 100);
1320 if (new_color >= 10)
1321 *p++ = '0' + ((new_color % 100) / 10);
1322 *p++ = '0' + (new_color % 10);
1323 *p++ = 'm';
1324 if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1325 if (!async_safe)
1326 out_error ();
1327 }
1328 break;
1329 case cm_xtermrgb:
1330 assert (new_color >= 0 && new_color < 0x1000000);
1331 {
1332 char bytes[19];
1333 char *p;
1334 unsigned int r = (new_color >> 16) & 0xff;
1335 unsigned int g = (new_color >> 8) & 0xff;
1336 unsigned int b = new_color & 0xff;
1337 bytes[0] = 0x1B; bytes[1] = '[';
1338 bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';';
1339 bytes[5] = '2'; bytes[6] = ';';
1340 p = bytes + 7;
1341 if (r >= 100)
1342 *p++ = '0' + (r / 100);
1343 if (r >= 10)
1344 *p++ = '0' + ((r % 100) / 10);
1345 *p++ = '0' + (r % 10);
1346 *p++ = ';';
1347 if (g >= 100)
1348 *p++ = '0' + (g / 100);
1349 if (g >= 10)
1350 *p++ = '0' + ((g % 100) / 10);
1351 *p++ = '0' + (g % 10);
1352 *p++ = ';';
1353 if (b >= 100)
1354 *p++ = '0' + (b / 100);
1355 if (b >= 10)
1356 *p++ = '0' + ((b % 100) / 10);
1357 *p++ = '0' + (b % 10);
1358 *p++ = 'm';
1359 if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1360 if (!async_safe)
1361 out_error ();
1362 }
1363 break;
1364 default:
1365 abort ();
1366 }
1367 }
1368
1369 /* Output escape sequences to switch the background color to NEW_BGCOLOR. */
1370 static _GL_ASYNC_SAFE void
1371 out_bgcolor_change (term_ostream_t stream, term_color_t new_bgcolor,
1372 bool async_safe)
1373 {
1374 assert (stream->supports_background);
1375 assert (new_bgcolor != COLOR_DEFAULT);
1376 switch (stream->colormodel)
1377 {
1378 case cm_common8:
1379 assert (new_bgcolor >= 0 && new_bgcolor < 8);
1380 #if HAVE_WINDOWS_CONSOLES
1381 if (stream->is_windows_console)
1382 {
1383 /* SetConsoleTextAttribute
1384 <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1385 <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */
1386 /* Assign to stream->current_console_attributes *before* calling
1387 SetConsoleTextAttribute, otherwise async_set_attributes_from_default
1388 will not do its job correctly. */
1389 stream->current_console_attributes =
1390 (stream->current_console_attributes & ~(7 << 4))
1391 | (new_bgcolor << 4);
1392 SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1393 }
1394 else
1395 #endif
1396 {
1397 if (stream->set_a_background != NULL)
1398 tputs (tparm (stream->set_a_background, color_bgr (new_bgcolor)),
1399 1, async_safe ? out_char_unchecked : out_char);
1400 else
1401 tputs (tparm (stream->set_background, new_bgcolor),
1402 1, async_safe ? out_char_unchecked : out_char);
1403 }
1404 break;
1405 /* When we are dealing with an xterm, there is no need to go through
1406 tputs() because we know there is no padding and sleeping. */
1407 case cm_xterm8:
1408 assert (new_bgcolor >= 0 && new_bgcolor < 8);
1409 {
1410 char bytes[5];
1411 bytes[0] = 0x1B; bytes[1] = '[';
1412 bytes[2] = '4'; bytes[3] = '0' + new_bgcolor;
1413 bytes[4] = 'm';
1414 if (full_write (out_fd, bytes, 5) < 5)
1415 if (!async_safe)
1416 out_error ();
1417 }
1418 break;
1419 case cm_xterm16:
1420 assert (new_bgcolor >= 0 && new_bgcolor < 16);
1421 {
1422 char bytes[6];
1423 bytes[0] = 0x1B; bytes[1] = '[';
1424 if (new_bgcolor < 8)
1425 {
1426 bytes[2] = '4'; bytes[3] = '0' + new_bgcolor;
1427 bytes[4] = 'm';
1428 if (full_write (out_fd, bytes, 5) < 5)
1429 if (!async_safe)
1430 out_error ();
1431 }
1432 else
1433 {
1434 bytes[2] = '1'; bytes[3] = '0';
1435 bytes[4] = '0' + (new_bgcolor - 8); bytes[5] = 'm';
1436 if (full_write (out_fd, bytes, 6) < 6)
1437 if (!async_safe)
1438 out_error ();
1439 }
1440 }
1441 break;
1442 case cm_xterm88:
1443 assert (new_bgcolor >= 0 && new_bgcolor < 88);
1444 {
1445 char bytes[10];
1446 char *p;
1447 bytes[0] = 0x1B; bytes[1] = '[';
1448 bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
1449 bytes[5] = '5'; bytes[6] = ';';
1450 p = bytes + 7;
1451 if (new_bgcolor >= 10)
1452 *p++ = '0' + (new_bgcolor / 10);
1453 *p++ = '0' + (new_bgcolor % 10);
1454 *p++ = 'm';
1455 if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1456 if (!async_safe)
1457 out_error ();
1458 }
1459 break;
1460 case cm_xterm256:
1461 assert (new_bgcolor >= 0 && new_bgcolor < 256);
1462 {
1463 char bytes[11];
1464 char *p;
1465 bytes[0] = 0x1B; bytes[1] = '[';
1466 bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
1467 bytes[5] = '5'; bytes[6] = ';';
1468 p = bytes + 7;
1469 if (new_bgcolor >= 100)
1470 *p++ = '0' + (new_bgcolor / 100);
1471 if (new_bgcolor >= 10)
1472 *p++ = '0' + ((new_bgcolor % 100) / 10);
1473 *p++ = '0' + (new_bgcolor % 10);
1474 *p++ = 'm';
1475 if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1476 if (!async_safe)
1477 out_error ();
1478 }
1479 break;
1480 case cm_xtermrgb:
1481 assert (new_bgcolor >= 0 && new_bgcolor < 0x1000000);
1482 {
1483 char bytes[19];
1484 char *p;
1485 unsigned int r = (new_bgcolor >> 16) & 0xff;
1486 unsigned int g = (new_bgcolor >> 8) & 0xff;
1487 unsigned int b = new_bgcolor & 0xff;
1488 bytes[0] = 0x1B; bytes[1] = '[';
1489 bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';';
1490 bytes[5] = '2'; bytes[6] = ';';
1491 p = bytes + 7;
1492 if (r >= 100)
1493 *p++ = '0' + (r / 100);
1494 if (r >= 10)
1495 *p++ = '0' + ((r % 100) / 10);
1496 *p++ = '0' + (r % 10);
1497 *p++ = ';';
1498 if (g >= 100)
1499 *p++ = '0' + (g / 100);
1500 if (g >= 10)
1501 *p++ = '0' + ((g % 100) / 10);
1502 *p++ = '0' + (g % 10);
1503 *p++ = ';';
1504 if (b >= 100)
1505 *p++ = '0' + (b / 100);
1506 if (b >= 10)
1507 *p++ = '0' + ((b % 100) / 10);
1508 *p++ = '0' + (b % 10);
1509 *p++ = 'm';
1510 if (full_write (out_fd, bytes, p - bytes) < p - bytes)
1511 if (!async_safe)
1512 out_error ();
1513 }
1514 break;
1515 default:
1516 abort ();
1517 }
1518 }
1519
1520 /* Output escape sequences to switch the weight to NEW_WEIGHT. */
1521 static _GL_ASYNC_SAFE void
1522 out_weight_change (term_ostream_t stream, term_weight_t new_weight,
1523 bool async_safe)
1524 {
1525 assert (stream->supports_weight);
1526 assert (new_weight != WEIGHT_DEFAULT);
1527 /* This implies: */
1528 assert (new_weight == WEIGHT_BOLD);
1529 tputs (stream->enter_bold_mode,
1530 1, async_safe ? out_char_unchecked : out_char);
1531 }
1532
1533 /* Output escape sequences to switch the posture to NEW_POSTURE. */
1534 static _GL_ASYNC_SAFE void
1535 out_posture_change (term_ostream_t stream, term_posture_t new_posture,
1536 bool async_safe)
1537 {
1538 assert (stream->supports_posture);
1539 assert (new_posture != POSTURE_DEFAULT);
1540 /* This implies: */
1541 assert (new_posture == POSTURE_ITALIC);
1542 tputs (stream->enter_italics_mode,
1543 1, async_safe ? out_char_unchecked : out_char);
1544 }
1545
1546 /* Output escape sequences to switch the underline to NEW_UNDERLINE. */
1547 static _GL_ASYNC_SAFE void
1548 out_underline_change (term_ostream_t stream, term_underline_t new_underline,
1549 bool async_safe)
1550 {
1551 assert (stream->supports_underline);
1552 assert (new_underline != UNDERLINE_DEFAULT);
1553 /* This implies: */
1554 assert (new_underline == UNDERLINE_ON);
1555 #if HAVE_WINDOWS_CONSOLES
1556 if (stream->is_windows_console)
1557 {
1558 /* SetConsoleTextAttribute
1559 <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1560 <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */
1561 /* Assign to stream->current_console_attributes *before* calling
1562 SetConsoleTextAttribute, otherwise async_set_attributes_from_default
1563 will not do its job correctly. */
1564 stream->current_console_attributes =
1565 stream->current_console_attributes | COMMON_LVB_UNDERSCORE;
1566 SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1567 }
1568 else
1569 #endif
1570 {
1571 tputs (stream->enter_underline_mode,
1572 1, async_safe ? out_char_unchecked : out_char);
1573 }
1574 }
1575
1576 /* Output escape seqeuences to switch the hyperlink to NEW_HYPERLINK. */
1577 static _GL_ASYNC_SAFE void
1578 out_hyperlink_change (term_ostream_t stream, hyperlink_t *new_hyperlink,
1579 bool async_safe)
1580 {
1581 int (*out_ch) (int) = (async_safe ? out_char_unchecked : out_char);
1582 assert (stream->supports_hyperlink);
1583 if (new_hyperlink != NULL)
1584 {
1585 assert (new_hyperlink->real_id != NULL);
1586 tputs ("\033]8;id=", 1, out_ch);
1587 tputs (new_hyperlink->real_id, 1, out_ch);
1588 tputs (";", 1, out_ch);
1589 tputs (new_hyperlink->ref, 1, out_ch);
1590 tputs ("\033\\", 1, out_ch);
1591 }
1592 else
1593 tputs ("\033]8;;\033\\", 1, out_ch);
1594 }
1595
1596 /* Output escape sequences to switch from STREAM->ACTIVE_ATTR to NEW_ATTR,
1597 and update STREAM->ACTIVE_ATTR. */
1598 static void
1599 out_attr_change (term_ostream_t stream, attributes_t new_attr)
1600 {
1601 attributes_t old_attr = stream->active_attr;
1602
1603 /* Keep track of the active attributes. Do this *before* emitting the
1604 escape sequences, otherwise async_set_attributes_from_default will not
1605 do its job correctly. */
1606 stream->active_attr = new_attr;
1607 stream->active_attr_color = new_attr.color;
1608 stream->active_attr_bgcolor = new_attr.bgcolor;
1609 stream->active_attr_hyperlink = new_attr.hyperlink;
1610
1611 #if HAVE_WINDOWS_CONSOLES
1612 if (stream->is_windows_console)
1613 {
1614 /* SetConsoleTextAttribute
1615 <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1616 <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */
1617 /* Assign to stream->current_console_attributes *before* calling
1618 SetConsoleTextAttribute, otherwise async_set_attributes_from_default
1619 will not do its job correctly. */
1620 stream->current_console_attributes =
1621 (stream->current_console_attributes
1622 & ~((7 << 0) | (7 << 4) | COMMON_LVB_UNDERSCORE))
1623 | (new_attr.color == COLOR_DEFAULT
1624 ? stream->default_console_attributes & (7 << 0)
1625 : (new_attr.color << 0))
1626 | (new_attr.bgcolor == COLOR_DEFAULT
1627 ? stream->default_console_attributes & (7 << 4)
1628 : (new_attr.bgcolor << 4))
1629 | (new_attr.underline ? COMMON_LVB_UNDERSCORE : 0);
1630 SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1631 }
1632 else
1633 #endif
1634 {
1635 bool cleared_attributes;
1636
1637 /* For out_char to work. */
1638 out_stream = stream;
1639 out_fd = stream->fd;
1640
1641 /* We don't know the default colors of the terminal. The only way to
1642 switch back to a default color is to use stream->orig_pair. */
1643 if ((new_attr.color == COLOR_DEFAULT && old_attr.color != COLOR_DEFAULT)
1644 || (new_attr.bgcolor == COLOR_DEFAULT && old_attr.bgcolor != COLOR_DEFAULT))
1645 {
1646 assert (stream->supports_foreground || stream->supports_background);
1647 tputs (stream->orig_pair, 1, out_char);
1648 old_attr.color = COLOR_DEFAULT;
1649 old_attr.bgcolor = COLOR_DEFAULT;
1650 }
1651
1652 /* To turn off WEIGHT_BOLD, the only way is to output the
1653 exit_attribute_mode sequence. (With xterm, you can also do it with
1654 "Esc [ 0 m", but this escape sequence is not contained in the terminfo
1655 description.) It may also clear the colors; this is the case e.g. when
1656 TERM="xterm" or TERM="ansi".
1657 To turn off UNDERLINE_ON, we can use the exit_underline_mode or the
1658 exit_attribute_mode sequence. In the latter case, it will not only
1659 turn off UNDERLINE_ON, but also the other attributes, and possibly also
1660 the colors.
1661 To turn off POSTURE_ITALIC, we can use the exit_italics_mode or the
1662 exit_attribute_mode sequence. Again, in the latter case, it will not
1663 only turn off POSTURE_ITALIC, but also the other attributes, and
1664 possibly also the colors.
1665 There is no point in setting an attribute just before emitting an
1666 escape sequence that may again turn off the attribute. Therefore we
1667 proceed in two steps: First, clear the attributes that need to be
1668 cleared; then - taking into account that this may have cleared all
1669 attributes and all colors - set the colors and the attributes.
1670 The variable 'cleared_attributes' tells whether an escape sequence
1671 has been output that may have cleared all attributes and all color
1672 settings. */
1673 cleared_attributes = false;
1674 if (old_attr.posture != POSTURE_NORMAL
1675 && new_attr.posture == POSTURE_NORMAL
1676 && stream->exit_italics_mode != NULL)
1677 {
1678 tputs (stream->exit_italics_mode, 1, out_char);
1679 old_attr.posture = POSTURE_NORMAL;
1680 cleared_attributes = true;
1681 }
1682 if (old_attr.underline != UNDERLINE_OFF
1683 && new_attr.underline == UNDERLINE_OFF
1684 && stream->exit_underline_mode != NULL)
1685 {
1686 tputs (stream->exit_underline_mode, 1, out_char);
1687 old_attr.underline = UNDERLINE_OFF;
1688 cleared_attributes = true;
1689 }
1690 if ((old_attr.weight != WEIGHT_NORMAL
1691 && new_attr.weight == WEIGHT_NORMAL)
1692 || (old_attr.posture != POSTURE_NORMAL
1693 && new_attr.posture == POSTURE_NORMAL
1694 /* implies stream->exit_italics_mode == NULL */)
1695 || (old_attr.underline != UNDERLINE_OFF
1696 && new_attr.underline == UNDERLINE_OFF
1697 /* implies stream->exit_underline_mode == NULL */))
1698 {
1699 tputs (stream->exit_attribute_mode, 1, out_char);
1700 /* We don't know exactly what effects exit_attribute_mode has, but
1701 this is the minimum effect: */
1702 old_attr.weight = WEIGHT_NORMAL;
1703 if (stream->exit_italics_mode == NULL)
1704 old_attr.posture = POSTURE_NORMAL;
1705 if (stream->exit_underline_mode == NULL)
1706 old_attr.underline = UNDERLINE_OFF;
1707 cleared_attributes = true;
1708 }
1709
1710 /* Turn on the colors. */
1711 if (new_attr.color != old_attr.color
1712 || (cleared_attributes && new_attr.color != COLOR_DEFAULT))
1713 {
1714 out_color_change (stream, new_attr.color, false);
1715 }
1716 if (new_attr.bgcolor != old_attr.bgcolor
1717 || (cleared_attributes && new_attr.bgcolor != COLOR_DEFAULT))
1718 {
1719 out_bgcolor_change (stream, new_attr.bgcolor, false);
1720 }
1721 if (new_attr.weight != old_attr.weight
1722 || (cleared_attributes && new_attr.weight != WEIGHT_DEFAULT))
1723 {
1724 out_weight_change (stream, new_attr.weight, false);
1725 }
1726 if (new_attr.posture != old_attr.posture
1727 || (cleared_attributes && new_attr.posture != POSTURE_DEFAULT))
1728 {
1729 out_posture_change (stream, new_attr.posture, false);
1730 }
1731 if (new_attr.underline != old_attr.underline
1732 || (cleared_attributes && new_attr.underline != UNDERLINE_DEFAULT))
1733 {
1734 out_underline_change (stream, new_attr.underline, false);
1735 }
1736 if (new_attr.hyperlink != old_attr.hyperlink)
1737 {
1738 out_hyperlink_change (stream, new_attr.hyperlink, false);
1739 }
1740 }
1741 }
1742
1743 static void
1744 restore (term_ostream_t stream)
1745 {
1746 #if HAVE_WINDOWS_CONSOLES
1747 if (stream->is_windows_console)
1748 {
1749 /* SetConsoleTextAttribute
1750 <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1751 <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */
1752 SetConsoleTextAttribute (stream->handle, stream->default_console_attributes);
1753 }
1754 else
1755 #endif
1756 {
1757 /* For out_char_unchecked to work. */
1758 out_stream = stream;
1759 out_fd = stream->fd;
1760
1761 if (stream->restore_colors != NULL)
1762 tputs (stream->restore_colors, 1, out_char_unchecked);
1763 if (stream->restore_weight != NULL)
1764 tputs (stream->restore_weight, 1, out_char_unchecked);
1765 if (stream->restore_posture != NULL)
1766 tputs (stream->restore_posture, 1, out_char_unchecked);
1767 if (stream->restore_underline != NULL)
1768 tputs (stream->restore_underline, 1, out_char_unchecked);
1769 if (stream->restore_hyperlink != NULL)
1770 tputs (stream->restore_hyperlink, 1, out_char_unchecked);
1771 }
1772 }
1773
1774 static _GL_ASYNC_SAFE void
1775 async_restore (term_ostream_t stream)
1776 {
1777 #if HAVE_WINDOWS_CONSOLES
1778 if (stream->is_windows_console)
1779 {
1780 /* SetConsoleTextAttribute
1781 <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1782 <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */
1783 SetConsoleTextAttribute (stream->handle, stream->default_console_attributes);
1784 }
1785 else
1786 #endif
1787 {
1788 /* For out_char_unchecked to work. */
1789 out_stream = stream;
1790 out_fd = stream->fd;
1791
1792 if (stream->restore_colors != NULL)
1793 tputs (stream->restore_colors, 1, out_char_unchecked);
1794 if (stream->restore_weight != NULL)
1795 tputs (stream->restore_weight, 1, out_char_unchecked);
1796 if (stream->restore_posture != NULL)
1797 tputs (stream->restore_posture, 1, out_char_unchecked);
1798 if (stream->restore_underline != NULL)
1799 tputs (stream->restore_underline, 1, out_char_unchecked);
1800 if (stream->restore_hyperlink != NULL)
1801 tputs (stream->restore_hyperlink, 1, out_char_unchecked);
1802 }
1803 }
1804
1805 static _GL_ASYNC_SAFE void
1806 async_set_attributes_from_default (term_ostream_t stream)
1807 {
1808 #if HAVE_WINDOWS_CONSOLES
1809 if (stream->is_windows_console)
1810 {
1811 /* SetConsoleTextAttribute
1812 <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
1813 <https://docs.microsoft.com/en-us/windows/console/console-screen-buffers> */
1814 SetConsoleTextAttribute (stream->handle, stream->current_console_attributes);
1815 }
1816 else
1817 #endif
1818 {
1819 attributes_t new_attr = stream->active_attr;
1820 /* Since stream->active_attr is not guaranteed to be loaded atomically,
1821 new_attr.color and new_attr.bgcolor may have invalid values.
1822 Use the atomically loadable values instead. */
1823 new_attr.color = stream->active_attr_color;
1824 new_attr.bgcolor = stream->active_attr_bgcolor;
1825 new_attr.hyperlink = stream->active_attr_hyperlink;
1826
1827 /* For out_char_unchecked to work. */
1828 out_stream = stream;
1829 out_fd = stream->fd;
1830
1831 if (new_attr.color != COLOR_DEFAULT)
1832 out_color_change (stream, new_attr.color, true);
1833 if (new_attr.bgcolor != COLOR_DEFAULT)
1834 out_bgcolor_change (stream, new_attr.bgcolor, true);
1835 if (new_attr.weight != WEIGHT_DEFAULT)
1836 out_weight_change (stream, new_attr.weight, true);
1837 if (new_attr.posture != POSTURE_DEFAULT)
1838 out_posture_change (stream, new_attr.posture, true);
1839 if (new_attr.underline != UNDERLINE_DEFAULT)
1840 out_underline_change (stream, new_attr.underline, true);
1841 if (new_attr.hyperlink != NULL)
1842 out_hyperlink_change (stream, new_attr.hyperlink, true);
1843 }
1844 }
1845
1846 static const struct term_style_controller controller =
1847 {
1848 get_control_data,
1849 restore,
1850 async_restore,
1851 async_set_attributes_from_default
1852 };
1853
1854 /* Activate the default attributes. */
1855 static void
1856 activate_default_attr (term_ostream_t stream)
1857 {
1858 /* Switch back to the default attributes. */
1859 out_attr_change (stream, stream->default_attr);
1860
1861 deactivate_term_non_default_mode (&controller, stream);
1862 }
1863
1864 /* Output the buffered line atomically.
1865 The terminal is left in the the state (regarding colors and attributes)
1866 represented by the simplified attributes goal_attr. */
1867 static void
1868 output_buffer (term_ostream_t stream, attributes_t goal_attr)
1869 {
1870 const char *cp;
1871 const attributes_t *ap;
1872 size_t len;
1873 size_t n;
1874
1875 cp = stream->buffer;
1876 ap = stream->attrbuffer;
1877 len = stream->buflen;
1878
1879 /* See how much we can output without blocking signals. */
1880 for (n = 0; n < len && equal_attributes (ap[n], stream->active_attr); n++)
1881 ;
1882 if (n > 0)
1883 {
1884 if (full_write (stream->fd, cp, n) < n)
1885 {
1886 int error_code = errno;
1887 /* Do output to stderr only after we have switched back to the
1888 default attributes. Otherwise this output may come out with
1889 the wrong text attributes. */
1890 if (!equal_attributes (stream->active_attr, stream->default_attr))
1891 activate_default_attr (stream);
1892 error (EXIT_FAILURE, error_code, _("error writing to %s"),
1893 stream->filename);
1894 }
1895 cp += n;
1896 ap += n;
1897 len -= n;
1898 }
1899 if (len > 0)
1900 {
1901 if (!equal_attributes (*ap, stream->default_attr))
1902 activate_term_non_default_mode (&controller, stream);
1903
1904 do
1905 {
1906 /* Activate the attributes in *ap. */
1907 out_attr_change (stream, *ap);
1908 /* See how many characters we can output without further attribute
1909 changes. */
1910 for (n = 1; n < len && equal_attributes (ap[n], stream->active_attr); n++)
1911 ;
1912 if (full_write (stream->fd, cp, n) < n)
1913 {
1914 int error_code = errno;
1915 /* Do output to stderr only after we have switched back to the
1916 default attributes. Otherwise this output may come out with
1917 the wrong text attributes. */
1918 if (!equal_attributes (stream->active_attr, stream->default_attr))
1919 activate_default_attr (stream);
1920 error (EXIT_FAILURE, error_code, _("error writing to %s"),
1921 stream->filename);
1922 }
1923 cp += n;
1924 ap += n;
1925 len -= n;
1926 }
1927 while (len > 0);
1928 }
1929 stream->buflen = 0;
1930
1931 /* Before changing to goal_attr, we may need to enable the non-default
1932 attributes mode. */
1933 if (!equal_attributes (goal_attr, stream->default_attr))
1934 activate_term_non_default_mode (&controller, stream);
1935 /* Change to goal_attr. */
1936 if (!equal_attributes (goal_attr, stream->active_attr))
1937 out_attr_change (stream, goal_attr);
1938 /* When we can deactivate the non-default attributes mode, do so. */
1939 if (equal_attributes (goal_attr, stream->default_attr))
1940 deactivate_term_non_default_mode (&controller, stream);
1941
1942 /* Free the hyperlink_t objects that are no longer referenced by the
1943 stream->attrbuffer. */
1944 {
1945 size_t count = stream->hyperlinks_count;
1946 size_t j = 0;
1947 size_t i;
1948 for (i = 0; i < count; i++)
1949 {
1950 /* Here 0 <= j <= i. */
1951 hyperlink_t *hyperlink = stream->hyperlinks_array[i];
1952 /* stream->default_attr.hyperlink is always == NULL.
1953 stream->simp_attr.hyperlink is either == NULL
1954 or == stream->curr_attr.hyperlink.
1955 We can therefore ignore both. */
1956 if (hyperlink == stream->curr_attr.hyperlink
1957 || hyperlink == stream->active_attr.hyperlink)
1958 {
1959 /* The hyperlink is still in use. */
1960 stream->hyperlinks_array[j] = hyperlink;
1961 j++;
1962 }
1963 else
1964 {
1965 /* The hyperlink is not in use any more. */
1966 free_hyperlink (hyperlink);
1967 }
1968 }
1969 stream->hyperlinks_count = j;
1970 }
1971 }
1972
1973 /* Implementation of ostream_t methods. */
1974
1975 static term_color_t
1976 term_ostream::rgb_to_color (term_ostream_t stream, int red, int green, int blue)
1977 {
1978 switch (stream->colormodel)
1979 {
1980 case cm_monochrome:
1981 return rgb_to_color_monochrome ();
1982 case cm_common8:
1983 return rgb_to_color_common8 (red, green, blue);
1984 case cm_xterm8:
1985 return rgb_to_color_xterm8 (red, green, blue);
1986 case cm_xterm16:
1987 return rgb_to_color_xterm16 (red, green, blue);
1988 case cm_xterm88:
1989 return rgb_to_color_xterm88 (red, green, blue);
1990 case cm_xterm256:
1991 return rgb_to_color_xterm256 (red, green, blue);
1992 case cm_xtermrgb:
1993 return rgb_to_color_xtermrgb (red, green, blue);
1994 default:
1995 abort ();
1996 }
1997 }
1998
1999 static void
2000 term_ostream::write_mem (term_ostream_t stream, const void *data, size_t len)
2001 {
2002 const char *cp = (const char *) data;
2003 while (len > 0)
2004 {
2005 /* Look for the next newline. */
2006 const char *newline = (const char *) memchr (cp, '\n', len);
2007 size_t n = (newline != NULL ? newline - cp : len);
2008
2009 /* Copy n bytes into the buffer. */
2010 if (n > stream->allocated - stream->buflen)
2011 {
2012 size_t new_allocated =
2013 xmax (xsum (stream->buflen, n),
2014 xsum (stream->allocated, stream->allocated));
2015 if (size_overflow_p (new_allocated))
2016 error (EXIT_FAILURE, 0,
2017 _("%s: too much output, buffer size overflow"),
2018 "term_ostream");
2019 stream->buffer = (char *) xrealloc (stream->buffer, new_allocated);
2020 stream->attrbuffer =
2021 (attributes_t *)
2022 xrealloc (stream->attrbuffer,
2023 new_allocated * sizeof (attributes_t));
2024 stream->allocated = new_allocated;
2025 }
2026 memcpy (stream->buffer + stream->buflen, cp, n);
2027 {
2028 attributes_t attr = stream->simp_attr;
2029 attributes_t *ap = stream->attrbuffer + stream->buflen;
2030 attributes_t *ap_end = ap + n;
2031 for (; ap < ap_end; ap++)
2032 *ap = attr;
2033 }
2034 stream->buflen += n;
2035
2036 if (newline != NULL)
2037 {
2038 output_buffer (stream, stream->default_attr);
2039 if (full_write (stream->fd, "\n", 1) < 1)
2040 error (EXIT_FAILURE, errno, _("error writing to %s"),
2041 stream->filename);
2042 cp += n + 1; /* cp = newline + 1; */
2043 len -= n + 1;
2044 }
2045 else
2046 break;
2047 }
2048 }
2049
2050 static void
2051 term_ostream::flush (term_ostream_t stream, ostream_flush_scope_t scope)
2052 {
2053 output_buffer (stream, stream->default_attr);
2054 if (scope == FLUSH_ALL)
2055 {
2056 #if HAVE_WINDOWS_CONSOLES
2057 if (!stream->is_windows_console)
2058 #endif
2059 {
2060 /* For streams connected to a disk file: */
2061 fsync (stream->fd);
2062 #if HAVE_TCDRAIN
2063 /* For streams connected to a terminal: */
2064 nonintr_tcdrain (stream->fd);
2065 #endif
2066 }
2067 }
2068 }
2069
2070 static void
2071 term_ostream::free (term_ostream_t stream)
2072 {
2073 term_ostream_flush (stream, FLUSH_THIS_STREAM);
2074
2075 deactivate_term_style_controller (&controller, stream);
2076
2077 free (stream->filename);
2078 if (stream->set_a_foreground != NULL)
2079 free (stream->set_a_foreground);
2080 if (stream->set_foreground != NULL)
2081 free (stream->set_foreground);
2082 if (stream->set_a_background != NULL)
2083 free (stream->set_a_background);
2084 if (stream->set_background != NULL)
2085 free (stream->set_background);
2086 if (stream->orig_pair != NULL)
2087 free (stream->orig_pair);
2088 if (stream->enter_bold_mode != NULL)
2089 free (stream->enter_bold_mode);
2090 if (stream->enter_italics_mode != NULL)
2091 free (stream->enter_italics_mode);
2092 if (stream->exit_italics_mode != NULL)
2093 free (stream->exit_italics_mode);
2094 if (stream->enter_underline_mode != NULL)
2095 free (stream->enter_underline_mode);
2096 if (stream->exit_underline_mode != NULL)
2097 free (stream->exit_underline_mode);
2098 if (stream->exit_attribute_mode != NULL)
2099 free (stream->exit_attribute_mode);
2100 if (stream->hyperlinks_array != NULL)
2101 {
2102 size_t count = stream->hyperlinks_count;
2103 size_t i;
2104 for (i = 0; i < count; i++)
2105 free_hyperlink (stream->hyperlinks_array[i]);
2106 free (stream->hyperlinks_array);
2107 }
2108 free (stream->buffer);
2109 free (stream->attrbuffer);
2110 free (stream);
2111 }
2112
2113 /* Implementation of term_ostream_t methods. */
2114
2115 static term_color_t
2116 term_ostream::get_color (term_ostream_t stream)
2117 {
2118 return stream->curr_attr.color;
2119 }
2120
2121 static void
2122 term_ostream::set_color (term_ostream_t stream, term_color_t color)
2123 {
2124 stream->curr_attr.color = color;
2125 stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2126 }
2127
2128 static term_color_t
2129 term_ostream::get_bgcolor (term_ostream_t stream)
2130 {
2131 return stream->curr_attr.bgcolor;
2132 }
2133
2134 static void
2135 term_ostream::set_bgcolor (term_ostream_t stream, term_color_t color)
2136 {
2137 stream->curr_attr.bgcolor = color;
2138 stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2139 }
2140
2141 static term_weight_t
2142 term_ostream::get_weight (term_ostream_t stream)
2143 {
2144 return stream->curr_attr.weight;
2145 }
2146
2147 static void
2148 term_ostream::set_weight (term_ostream_t stream, term_weight_t weight)
2149 {
2150 stream->curr_attr.weight = weight;
2151 stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2152 }
2153
2154 static term_posture_t
2155 term_ostream::get_posture (term_ostream_t stream)
2156 {
2157 return stream->curr_attr.posture;
2158 }
2159
2160 static void
2161 term_ostream::set_posture (term_ostream_t stream, term_posture_t posture)
2162 {
2163 stream->curr_attr.posture = posture;
2164 stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2165 }
2166
2167 static term_underline_t
2168 term_ostream::get_underline (term_ostream_t stream)
2169 {
2170 return stream->curr_attr.underline;
2171 }
2172
2173 static void
2174 term_ostream::set_underline (term_ostream_t stream, term_underline_t underline)
2175 {
2176 stream->curr_attr.underline = underline;
2177 stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2178 }
2179
2180 static const char *
2181 term_ostream::get_hyperlink_ref (term_ostream_t stream)
2182 {
2183 hyperlink_t *hyperlink = stream->curr_attr.hyperlink;
2184 return (hyperlink != NULL ? hyperlink->ref : NULL);
2185 }
2186
2187 static const char *
2188 term_ostream::get_hyperlink_id (term_ostream_t stream)
2189 {
2190 hyperlink_t *hyperlink = stream->curr_attr.hyperlink;
2191 return (hyperlink != NULL ? hyperlink->id : NULL);
2192 }
2193
2194 static void
2195 term_ostream::set_hyperlink (term_ostream_t stream,
2196 const char *ref, const char *id)
2197 {
2198 if (ref == NULL)
2199 stream->curr_attr.hyperlink = NULL;
2200 else
2201 {
2202 /* Create a new hyperlink_t object. */
2203 hyperlink_t *hyperlink = XMALLOC (hyperlink_t);
2204
2205 hyperlink->ref = xstrdup (ref);
2206 if (id != NULL)
2207 {
2208 hyperlink->id = xstrdup (id);
2209 hyperlink->real_id = hyperlink->id;
2210 }
2211 else
2212 {
2213 hyperlink->id = NULL;
2214 if (stream->supports_hyperlink)
2215 {
2216 /* Generate an id always, since we don't know at this point
2217 whether the hyperlink will span multiple lines. */
2218 hyperlink->real_id = generate_hyperlink_id (stream);
2219 }
2220 else
2221 hyperlink->real_id = NULL;
2222 }
2223
2224 /* Store it. */
2225 if (stream->hyperlinks_count == stream->hyperlinks_allocated)
2226 {
2227 stream->hyperlinks_allocated = 2 * stream->hyperlinks_allocated + 10;
2228 stream->hyperlinks_array =
2229 (hyperlink_t **)
2230 xrealloc (stream->hyperlinks_array,
2231 stream->hyperlinks_allocated * sizeof (hyperlink_t *));
2232 }
2233 stream->hyperlinks_array[stream->hyperlinks_count++] = hyperlink;
2234
2235 /* Install it. */
2236 stream->curr_attr.hyperlink = hyperlink;
2237 }
2238 stream->simp_attr = simplify_attributes (stream, stream->curr_attr);
2239 }
2240
2241 static void
2242 term_ostream::flush_to_current_style (term_ostream_t stream)
2243 {
2244 output_buffer (stream, stream->simp_attr);
2245 }
2246
2247 /* Constructor. */
2248
2249 static inline char *
2250 xstrdup0 (const char *str)
2251 {
2252 if (str == NULL)
2253 return NULL;
2254 #if HAVE_TERMINFO
2255 if (str == (const char *)(-1))
2256 return NULL;
2257 #endif
2258 return xstrdup (str);
2259 }
2260
2261 /* Returns the base name of the terminal emulator program, possibly truncated,
2262 as a freshly allocated string, or NULL if it cannot be determined.
2263 Note: This function is a hack. It does not work across ssh, and it may fail
2264 in some local situations as well. */
2265 static inline char *
2266 get_terminal_emulator_progname (void)
2267 {
2268 #if HAVE_GETSID
2269 /* Get the process id of the session leader.
2270 When running in a terminal emulator, it's the shell process that was
2271 spawned by the terminal emulator. When running in a console, it's the
2272 'login' process.
2273 On some operating systems (Linux, *BSD, AIX), the same result could also
2274 be obtained through
2275 pid_t p;
2276 if (ioctl (1, TIOCGSID, &p) >= 0) ...
2277 */
2278 pid_t session_leader_pid = getsid (0);
2279 if (session_leader_pid != (pid_t)(-1))
2280 {
2281 /* Get the process id of the terminal emulator.
2282 When running in a console, it's the process id of the 'init'
2283 process. */
2284 pid_t terminal_emulator_pid = get_ppid_of (session_leader_pid);
2285 if (terminal_emulator_pid != 0)
2286 {
2287 /* Retrieve the base name of the program name of this process. */
2288 return get_progname_of (terminal_emulator_pid);
2289 }
2290 }
2291 #endif
2292 return NULL;
2293 }
2294
2295 /* Returns true if we should enable hyperlinks.
2296 term is the value of the TERM environment variable. */
2297 static inline bool
2298 should_enable_hyperlinks (const char *term)
2299 {
2300 if (getenv ("NO_TERM_HYPERLINKS") != NULL)
2301 /* The user has disabled hyperlinks. */
2302 return false;
2303
2304 /* Dispatch based on $TERM. */
2305 if (term != NULL)
2306 {
2307 /* rxvt-based terminal emulators:
2308 Program | TERM | Supports hyperlinks?
2309 ------------------+--------------+-------------------------------------
2310 rxvt 2.7.10 | rxvt | hangs after "cat hyperlink-demo.txt"
2311 mrxvt 0.5.3 | rxvt | no
2312 rxvt-unicode 9.22 | rxvt-unicode | no
2313 */
2314 if (strcmp (term, "rxvt") == 0)
2315 return false;
2316
2317 /* Emacs-based terminal emulators:
2318 Program | TERM | Supports hyperlinks?
2319 --------------------+-------------+---------------------
2320 emacs-terminal 26.1 | eterm-color | produces garbage
2321 */
2322 if (strncmp (term, "eterm", 5) == 0)
2323 return false;
2324
2325 /* xterm-compatible terminal emulators:
2326 Program | TERM | Supports hyperlinks?
2327 -----------------+----------------+---------------------------
2328 guake 0.8.8 | xterm | produces garbage
2329 lilyterm 0.9.9.2 | xterm | produces garbage
2330 lterm 1.5.1 | xterm | produces garbage
2331 lxterminal 0.3.2 | xterm | produces garbage
2332 termit 2.9.6 | xterm | produces garbage
2333 konsole 18.12.3 | xterm-256color | produces extra backslashes
2334 yakuake 3.0.5 | xterm-256color | produces extra backslashes
2335 other | | yes or no, no severe bugs
2336
2337 TODO: Revisit this table periodically.
2338 */
2339 if (strncmp (term, "xterm", 5) == 0)
2340 {
2341 char *progname = get_terminal_emulator_progname ();
2342 if (progname != NULL)
2343 {
2344 bool known_buggy =
2345 strncmp (progname, "python", 6) == 0 /* guake */
2346 || strcmp (progname, "lilyterm") == 0
2347 || strcmp (progname, "lterm") == 0
2348 || strcmp (progname, "lxterminal") == 0
2349 || strcmp (progname, "termit") == 0
2350 || strcmp (progname, "konsole") == 0
2351 || strcmp (progname, "yakuake") == 0;
2352 free (progname);
2353 /* Enable hyperlinks except for programs that are known buggy. */
2354 return !known_buggy;
2355 }
2356 }
2357
2358 /* Solaris console.
2359 Program | TERM | Supports hyperlinks?
2360 -----------------------------------+-----------+---------------------------
2361 Solaris kernel's terminal emulator | sun-color | produces garbage
2362 SPARC PROM's terminal emulator | sun | ?
2363 */
2364 if (strcmp (term, "sun") == 0 || strcmp (term, "sun-color") == 0)
2365 return false;
2366 }
2367
2368 /* In case of doubt, enable hyperlinks. So this code does not need to change
2369 as more and more terminal emulators support hyperlinks.
2370 If there are adverse effects, the user can disable hyperlinks by setting
2371 NO_TERM_HYPERLINKS. */
2372 return true;
2373 }
2374
2375 term_ostream_t
2376 term_ostream_create (int fd, const char *filename, ttyctl_t tty_control)
2377 {
2378 term_ostream_t stream = XMALLOC (struct term_ostream_representation);
2379
2380 stream->base.vtable = &term_ostream_vtable;
2381 stream->fd = fd;
2382 #if HAVE_WINDOWS_CONSOLES
2383 stream->handle = (HANDLE) _get_osfhandle (fd);
2384 {
2385 DWORD mode;
2386
2387 if (stream->handle != INVALID_HANDLE_VALUE
2388 /* GetConsoleMode
2389 <https://docs.microsoft.com/en-us/windows/console/getconsolemode> */
2390 && GetConsoleMode (stream->handle, &mode) != 0)
2391 {
2392 CONSOLE_SCREEN_BUFFER_INFO info;
2393 BOOL ok;
2394
2395 /* GetConsoleScreenBufferInfo
2396 <https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo>
2397 <https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str> */
2398 ok = GetConsoleScreenBufferInfo (stream->handle, &info);
2399 if (!ok)
2400 {
2401 /* GetConsoleScreenBufferInfo
2402 - fails when the handle is == GetStdHandle (STD_INPUT_HANDLE)
2403 - but succeeds when it is == GetStdHandle (STD_OUTPUT_HANDLE)
2404 or == GetStdHandle (STD_ERROR_HANDLE).
2405 Native Windows programs use GetStdHandle (STD_OUTPUT_HANDLE) for
2406 fd 1, as expected.
2407 But Cygwin uses GetStdHandle (STD_INPUT_HANDLE) for all of fd 0,
2408 1, 2. So, we have to use either GetStdHandle (STD_OUTPUT_HANDLE)
2409 or GetStdHandle (STD_ERROR_HANDLE) in order to be able to use
2410 GetConsoleScreenBufferInfo. */
2411 if (fd == 1 || fd == 2)
2412 {
2413 HANDLE handle =
2414 GetStdHandle (fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
2415 ok = GetConsoleScreenBufferInfo (handle, &info);
2416 if (ok)
2417 stream->handle = handle;
2418 }
2419 }
2420 if (ok)
2421 {
2422 stream->is_windows_console = true;
2423 stream->default_console_attributes = info.wAttributes;
2424 stream->current_console_attributes = stream->default_console_attributes;
2425 }
2426 else
2427 /* It's a console, but we cannot use GetConsoleScreenBufferInfo. */
2428 stream->is_windows_console = false;
2429 }
2430 else
2431 stream->is_windows_console = false;
2432 }
2433 #endif
2434 stream->filename = xstrdup (filename);
2435 stream->tty_control = tty_control;
2436
2437 /* Defaults. */
2438 stream->max_colors = -1;
2439 stream->no_color_video = -1;
2440 stream->set_a_foreground = NULL;
2441 stream->set_foreground = NULL;
2442 stream->set_a_background = NULL;
2443 stream->set_background = NULL;
2444 stream->orig_pair = NULL;
2445 stream->enter_bold_mode = NULL;
2446 stream->enter_italics_mode = NULL;
2447 stream->exit_italics_mode = NULL;
2448 stream->enter_underline_mode = NULL;
2449 stream->exit_underline_mode = NULL;
2450 stream->exit_attribute_mode = NULL;
2451
2452 #if HAVE_WINDOWS_CONSOLES
2453 if (stream->is_windows_console)
2454 {
2455 /* For Windows consoles, two approaches are possible:
2456 (A) Use SetConsoleMode
2457 <https://docs.microsoft.com/en-us/windows/console/setconsolemode>
2458 to enable the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag, and then
2459 emit escape sequences, as documented in
2460 <https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences>.
2461 (B) Use SetConsoleTextAttribute
2462 <https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute>
2463 to change the text attributes.
2464 Approach (A) has two drawbacks:
2465 * It produces colors that ignore the console's configuration: it
2466 assumes the default configuration (light grey foreground). Thus
2467 when you ask for cyan, you will always get some blue color, never
2468 real cyan. Whereas approach (B) produces colors that respect the
2469 "Screen Text" and "Screen Background" settings in the console's
2470 configuration.
2471 * When the program terminates abnormally, we would leave the console
2472 with ENABLE_VIRTUAL_TERMINAL_PROCESSING enabled, which can be
2473 dangerous.
2474 Therefore we use approach (B). */
2475 stream->max_colors = 8;
2476 stream->no_color_video = 1 | 4;
2477 stream->supports_foreground = true;
2478 stream->supports_background = true;
2479 stream->colormodel = cm_common8;
2480 /* The Windows consoles have high and low intensity, but the default is
2481 high intensity. If we wanted to support WEIGHT_BOLD, we would have to
2482 use low-intensity rendering for normal output, which would look ugly
2483 compared to the output by other programs. We could support WEIGHT_DIM,
2484 but this is not part of our enum term_weight_t. */
2485 stream->supports_weight = false;
2486 stream->supports_posture = false;
2487 stream->supports_underline = true;
2488 stream->supports_hyperlink = false;
2489 stream->restore_colors = NULL;
2490 stream->restore_weight = NULL;
2491 stream->restore_posture = NULL;
2492 stream->restore_underline = NULL;
2493 stream->restore_hyperlink = NULL;
2494 }
2495 else
2496 #endif
2497 {
2498 const char *term;
2499
2500 /* Retrieve the terminal type. */
2501 term = getenv ("TERM");
2502 if (term != NULL && term[0] != '\0')
2503 {
2504 /* When the terminfo function are available, we prefer them over the
2505 termcap functions because
2506 1. they don't risk a buffer overflow,
2507 2. on OSF/1, for TERM=xterm, the tiget* functions provide access
2508 to the number of colors and the color escape sequences,
2509 whereas the tget* functions don't provide them. */
2510 #if HAVE_TERMINFO
2511 int err = 1;
2512
2513 if (setupterm (term, fd, &err) == 0 || err == 1)
2514 {
2515 /* Retrieve particular values depending on the terminal type. */
2516 stream->max_colors = tigetnum ("colors");
2517 stream->no_color_video = tigetnum ("ncv");
2518 stream->set_a_foreground = xstrdup0 (tigetstr ("setaf"));
2519 stream->set_foreground = xstrdup0 (tigetstr ("setf"));
2520 stream->set_a_background = xstrdup0 (tigetstr ("setab"));
2521 stream->set_background = xstrdup0 (tigetstr ("setb"));
2522 stream->orig_pair = xstrdup0 (tigetstr ("op"));
2523 stream->enter_bold_mode = xstrdup0 (tigetstr ("bold"));
2524 stream->enter_italics_mode = xstrdup0 (tigetstr ("sitm"));
2525 stream->exit_italics_mode = xstrdup0 (tigetstr ("ritm"));
2526 stream->enter_underline_mode = xstrdup0 (tigetstr ("smul"));
2527 stream->exit_underline_mode = xstrdup0 (tigetstr ("rmul"));
2528 stream->exit_attribute_mode = xstrdup0 (tigetstr ("sgr0"));
2529 }
2530 #elif HAVE_TERMCAP
2531 /* The buffer size needed for termcap was 1024 bytes in the past, but
2532 nowadays the largest termcap description (bq300-8-pc-w-rv) is 1507
2533 bytes long. <https://tldp.org/LDP/lpg/node91.html> suggests a
2534 buffer size of 2048 bytes. */
2535 struct { char buf[2048]; char canary[4]; } termcapbuf;
2536 int retval;
2537
2538 /* Call tgetent, being defensive against buffer overflow. */
2539 memcpy (termcapbuf.canary, "CnRy", 4);
2540 retval = tgetent (termcapbuf.buf, term);
2541 if (memcmp (termcapbuf.canary, "CnRy", 4) != 0)
2542 /* Buffer overflow! */
2543 abort ();
2544
2545 if (retval > 0)
2546 {
2547 /* The buffer size needed for a termcap entry was 1024 bytes in
2548 the past, but nowadays the largest one (in bq300-8-pc-w-rv)
2549 is 1034 bytes long. */
2550 struct { char buf[2048]; char canary[4]; } termentrybuf;
2551 char *termentryptr;
2552
2553 /* Prepare for calling tgetstr, being defensive against buffer
2554 overflow. ncurses' tgetstr() supports a second argument NULL,
2555 but NetBSD's tgetstr() doesn't. */
2556 memcpy (termentrybuf.canary, "CnRz", 4);
2557 #define TEBP ((termentryptr = termentrybuf.buf), &termentryptr)
2558
2559 /* Retrieve particular values depending on the terminal type. */
2560 stream->max_colors = tgetnum ("Co");
2561 stream->no_color_video = tgetnum ("NC");
2562 stream->set_a_foreground = xstrdup0 (tgetstr ("AF", TEBP));
2563 stream->set_foreground = xstrdup0 (tgetstr ("Sf", TEBP));
2564 stream->set_a_background = xstrdup0 (tgetstr ("AB", TEBP));
2565 stream->set_background = xstrdup0 (tgetstr ("Sb", TEBP));
2566 stream->orig_pair = xstrdup0 (tgetstr ("op", TEBP));
2567 stream->enter_bold_mode = xstrdup0 (tgetstr ("md", TEBP));
2568 stream->enter_italics_mode = xstrdup0 (tgetstr ("ZH", TEBP));
2569 stream->exit_italics_mode = xstrdup0 (tgetstr ("ZR", TEBP));
2570 stream->enter_underline_mode = xstrdup0 (tgetstr ("us", TEBP));
2571 stream->exit_underline_mode = xstrdup0 (tgetstr ("ue", TEBP));
2572 stream->exit_attribute_mode = xstrdup0 (tgetstr ("me", TEBP));
2573
2574 #ifdef __BEOS__
2575 /* The BeOS termcap entry for "beterm" is broken: For "AF" and
2576 "AB" it contains values in terminfo syntax but the system's
2577 tparam() function understands only the termcap syntax. */
2578 if (stream->set_a_foreground != NULL
2579 && strcmp (stream->set_a_foreground, "\033[3%p1%dm") == 0)
2580 {
2581 free (stream->set_a_foreground);
2582 stream->set_a_foreground = xstrdup ("\033[3%dm");
2583 }
2584 if (stream->set_a_background != NULL
2585 && strcmp (stream->set_a_background, "\033[4%p1%dm") == 0)
2586 {
2587 free (stream->set_a_background);
2588 stream->set_a_background = xstrdup ("\033[4%dm");
2589 }
2590 #endif
2591
2592 /* The termcap entry for cygwin is broken: It has no "ncv" value,
2593 but bold and underline are actually rendered through colors. */
2594 if (strcmp (term, "cygwin") == 0)
2595 stream->no_color_video |= 2 | 32;
2596
2597 /* Done with tgetstr. Detect possible buffer overflow. */
2598 #undef TEBP
2599 if (memcmp (termentrybuf.canary, "CnRz", 4) != 0)
2600 /* Buffer overflow! */
2601 abort ();
2602 }
2603 #else
2604 /* Fallback code for platforms with neither the terminfo nor the
2605 termcap functions, such as mingw.
2606 Assume the ANSI escape sequences. Extracted through
2607 "TERM=ansi infocmp", replacing \E with \033. */
2608 stream->max_colors = 8;
2609 stream->no_color_video = 3;
2610 stream->set_a_foreground = xstrdup ("\033[3%p1%dm");
2611 stream->set_a_background = xstrdup ("\033[4%p1%dm");
2612 stream->orig_pair = xstrdup ("\033[39;49m");
2613 stream->enter_bold_mode = xstrdup ("\033[1m");
2614 stream->enter_underline_mode = xstrdup ("\033[4m");
2615 stream->exit_underline_mode = xstrdup ("\033[m");
2616 stream->exit_attribute_mode = xstrdup ("\033[0;10m");
2617 #endif
2618
2619 /* AIX 4.3.2, IRIX 6.5, HP-UX 11, Solaris 7..10 all lack the
2620 description of color capabilities of "xterm" and "xterms"
2621 in their terminfo database. But it is important to have
2622 color in xterm. So we provide the color capabilities here. */
2623 if (stream->max_colors <= 1
2624 && (strcmp (term, "xterm") == 0 || strcmp (term, "xterms") == 0))
2625 {
2626 stream->max_colors = 8;
2627 stream->set_a_foreground = xstrdup ("\033[3%p1%dm");
2628 stream->set_a_background = xstrdup ("\033[4%p1%dm");
2629 stream->orig_pair = xstrdup ("\033[39;49m");
2630 }
2631 }
2632
2633 /* Infer the capabilities. */
2634 stream->supports_foreground =
2635 (stream->max_colors >= 8
2636 && (stream->set_a_foreground != NULL || stream->set_foreground != NULL)
2637 && stream->orig_pair != NULL);
2638 stream->supports_background =
2639 (stream->max_colors >= 8
2640 && (stream->set_a_background != NULL || stream->set_background != NULL)
2641 && stream->orig_pair != NULL);
2642 stream->colormodel =
2643 (stream->supports_foreground || stream->supports_background
2644 ? (term != NULL
2645 && (/* Recognize xterm-16color, xterm-88color, xterm-256color. */
2646 (strlen (term) >= 5 && memcmp (term, "xterm", 5) == 0)
2647 || /* Recognize *-16color. */
2648 (strlen (term) > 8
2649 && strcmp (term + strlen (term) - 8, "-16color") == 0)
2650 || /* Recognize *-256color. */
2651 (strlen (term) > 9
2652 && strcmp (term + strlen (term) - 9, "-256color") == 0)
2653 || /* Recognize *-direct. */
2654 (strlen (term) > 8
2655 && strcmp (term + strlen (term) - 8, "-direct") == 0))
2656 ? (stream->max_colors >= 0x7fff ? cm_xtermrgb :
2657 stream->max_colors == 256 ? cm_xterm256 :
2658 stream->max_colors == 88 ? cm_xterm88 :
2659 stream->max_colors == 16 ? cm_xterm16 :
2660 cm_xterm8)
2661 : cm_common8)
2662 : cm_monochrome);
2663 stream->supports_weight =
2664 (stream->enter_bold_mode != NULL
2665 && stream->exit_attribute_mode != NULL);
2666 stream->supports_posture =
2667 (stream->enter_italics_mode != NULL
2668 && (stream->exit_italics_mode != NULL
2669 || stream->exit_attribute_mode != NULL));
2670 stream->supports_underline =
2671 (stream->enter_underline_mode != NULL
2672 && (stream->exit_underline_mode != NULL
2673 || stream->exit_attribute_mode != NULL));
2674 /* TODO: Use a terminfo capability, once ncurses implements it. */
2675 stream->supports_hyperlink = should_enable_hyperlinks (term);
2676
2677 /* Infer the restore strings. */
2678 stream->restore_colors =
2679 (stream->supports_foreground || stream->supports_background
2680 ? stream->orig_pair
2681 : NULL);
2682 stream->restore_weight =
2683 (stream->supports_weight ? stream->exit_attribute_mode : NULL);
2684 stream->restore_posture =
2685 (stream->supports_posture
2686 ? (stream->exit_italics_mode != NULL
2687 ? stream->exit_italics_mode
2688 : stream->exit_attribute_mode)
2689 : NULL);
2690 stream->restore_underline =
2691 (stream->supports_underline
2692 ? (stream->exit_underline_mode != NULL
2693 ? stream->exit_underline_mode
2694 : stream->exit_attribute_mode)
2695 : NULL);
2696 stream->restore_hyperlink =
2697 (stream->supports_hyperlink
2698 ? "\033]8;;\033\\"
2699 : NULL);
2700 }
2701
2702 /* Initialize the hyperlink id generator. */
2703 if (stream->supports_hyperlink)
2704 {
2705 char *hostname = xgethostname ();
2706 { /* Compute a hash code, like in gnulib/lib/hash-pjw.c. */
2707 uint32_t h = 0;
2708 if (hostname != NULL)
2709 {
2710 const char *p;
2711 for (p = hostname; *p; p++)
2712 h = (unsigned char) *p + ((h << 9) | (h >> (32 - 9)));
2713 }
2714 stream->hostname_hash = h;
2715 }
2716 free (hostname);
2717
2718 {
2719 struct timeval tv;
2720 gettimeofday (&tv, NULL);
2721 stream->start_time =
2722 (uint64_t) tv.tv_sec * (uint64_t) 1000000 + (uint64_t) tv.tv_usec;
2723 }
2724
2725 stream->id_serial = 0;
2726 }
2727
2728 /* Initialize the set of hyperlink_t. */
2729 stream->hyperlinks_array = NULL;
2730 stream->hyperlinks_count = 0;
2731 stream->hyperlinks_allocated = 0;
2732
2733 /* Initialize the buffer. */
2734 stream->allocated = 120;
2735 stream->buffer = XNMALLOC (stream->allocated, char);
2736 stream->attrbuffer = XNMALLOC (stream->allocated, attributes_t);
2737 stream->buflen = 0;
2738
2739 /* Initialize the current attributes. */
2740 {
2741 attributes_t assumed_default;
2742 attributes_t simplified_default;
2743
2744 assumed_default.color = COLOR_DEFAULT;
2745 assumed_default.bgcolor = COLOR_DEFAULT;
2746 assumed_default.weight = WEIGHT_DEFAULT;
2747 assumed_default.posture = POSTURE_DEFAULT;
2748 assumed_default.underline = UNDERLINE_DEFAULT;
2749 assumed_default.hyperlink = NULL;
2750
2751 simplified_default = simplify_attributes (stream, assumed_default);
2752
2753 stream->default_attr = simplified_default;
2754 stream->active_attr = simplified_default;
2755 stream->curr_attr = assumed_default;
2756 stream->simp_attr = simplified_default;
2757 }
2758
2759 /* Prepare tty control. */
2760 activate_term_style_controller (&controller, stream, fd, tty_control);
2761
2762 return stream;
2763 }
2764
2765 /* Accessors. */
2766
2767 static int
2768 term_ostream::get_descriptor (term_ostream_t stream)
2769 {
2770 return stream->fd;
2771 }
2772
2773 static const char *
2774 term_ostream::get_filename (term_ostream_t stream)
2775 {
2776 return stream->filename;
2777 }
2778
2779 static ttyctl_t
2780 term_ostream::get_tty_control (term_ostream_t stream)
2781 {
2782 return stream->tty_control;
2783 }
2784
2785 static ttyctl_t
2786 term_ostream::get_effective_tty_control (term_ostream_t stream)
2787 {
2788 return stream->control_data.tty_control;
2789 }
2790
2791 /* Instanceof test. */
2792
2793 bool
2794 is_instance_of_term_ostream (ostream_t stream)
2795 {
2796 return IS_INSTANCE (stream, ostream, term_ostream);
2797 }