1 /** Terminal screen drawing routines.
13 #include "config/options.h"
14 #include "intl/charsets.h"
15 #include "main/module.h"
16 #include "osdep/ascii.h"
17 #include "osdep/osdep.h"
18 #include "terminal/color.h"
19 #include "terminal/draw.h"
20 #include "terminal/hardio.h"
21 #include "terminal/kbd.h"
22 #include "terminal/screen.h"
23 #include "terminal/terminal.h"
24 #include "util/conv.h"
25 #include "util/error.h"
26 #include "util/memory.h"
27 #include "util/string.h"
30 /* TODO: We must use termcap/terminfo if available! --pasky
31 * Please mention ELinks bug 96 in commit logs. --KON */
33 /** Mapping from (enum ::border_char - 0xB0) to ASCII characters. */
34 const unsigned char frame_dumb
[48] = " ||||++||++++++--|-+||++--|-+----++++++++ ";
36 /** Mapping from (enum ::border_char - 0xB0) to VT100 line-drawing
38 static const unsigned char frame_vt100
[48] = "aaaxuuukkuxkjjjkmvwtqnttmlvwtqnvvwwmmllnnjla ";
40 /** Mapping from (enum ::border_char - 0xB0) to VT100 line-drawing
41 * characters encoded in CP437.
42 * When UTF-8 I/O is enabled, ELinks uses this array instead of
43 * ::frame_vt100[], and converts the characters from CP437 to UTF-8. */
44 static const unsigned char frame_vt100_u
[48] = {
45 177, 177, 177, 179, 180, 180, 180, 191,
46 191, 180, 179, 191, 217, 217, 217, 191,
47 192, 193, 194, 195, 196, 197, 195, 195,
48 192, 218, 193, 194, 195, 196, 197, 193,
49 193, 194, 194, 192, 192, 218, 218, 197,
50 197, 217, 218, 177, 32, 32, 32, 32
53 /** Mapping from (enum ::border_char - 0xB0) to obsolete FreeBSD ACS
56 * This is for FreeBSD fonts that place graphics characters in the
57 * 0x80...0x9F range, which ISO 8859 does not use. The characters are
58 * supposed to be sorted according to the codes used in VT100 or in
59 * the terminfo "acsc" capability:
62 * 0x80 U+2588 '0' ACS_BLOCK
63 * 0x81 U+25C6 '`' ACS_DIAMOND
64 * 0x82 U+2592 'a' ACS_CKBOARD
69 * 0x87 U+00B0 'f' ACS_DEGREE
70 * 0x88 U+00B1 'g' ACS_PLMINUS
73 * 0x8B U+2518 'j' ACS_LRCORNER
74 * 0x8C U+2510 'k' ACS_URCORNER
75 * 0x8D U+250C 'l' ACS_ULCORNER
76 * 0x8E U+2514 'm' ACS_LLCORNER
77 * 0x8F U+253C 'n' ACS_PLUS
78 * 0x90 U+23BA 'o' ACS_S1
79 * 0x91 U+23BB 'p' ACS_S3
80 * 0x92 U+2500 'q' ACS_HLINE
81 * 0x93 U+23BC 'r' ACS_S7
82 * 0x94 U+23BD 's' ACS_S9
83 * 0x95 U+251C 't' ACS_LTEE
84 * 0x96 U+2524 'u' ACS_RTEE
85 * 0x97 U+2534 'v' ACS_BTEE
86 * 0x98 U+252C 'w' ACS_TTEE
87 * 0x99 U+2502 'x' ACS_VLINE
88 * 0x9A U+2264 'y' ACS_LEQUAL
89 * 0x9B U+2265 'z' ACS_GEQUAL
90 * 0x9C U+03C0 '{' ACS_PI
91 * 0x9D U+2260 '|' ACS_NEQUAL
92 * 0x9E U+00A3 '}' ACS_STERLING
93 * 0x9F U+00B7 '~' ACS_BULLET
96 * (Ncurses 5.5 defines ACS_BOARD using 'h' and ACS_LANTERN using 'i',
97 * but those are not the characters meant above.)
99 * In FreeBSD CVS, src/share/syscons/fonts/iso-8x16.fnt revision 1.1
100 * includes these characters, except it has a space at 0x80. In
101 * revision 1.2 however, all the characters not defined by ISO 8859-1
102 * have been blanked out. This change was made on 2001-11-22 and
103 * included in FreeBSD 4.6.0, which was then released in June 2002.
104 * Yet, support for these characters was added to Links on 2003-11-18
105 * and to ELinks on 2003-12-07, so perhaps we should keep it for now. */
106 static const unsigned char frame_freebsd
[48] = {
107 130, 138, 128, 153, 150, 150, 150, 140,
108 140, 150, 153, 140, 139, 139, 139, 140,
109 142, 151, 152, 149, 146, 143, 149, 149,
110 142, 141, 151, 152, 149, 146, 143, 151,
111 151, 152, 152, 142, 142, 141, 141, 143,
112 143, 139, 141, 128, 128, 128, 128, 128,
115 /** Mapping from (enum ::border_char - 0xB0) to obsolete FreeBSD ACS
116 * graphics encoded in CP437.
117 * When UTF-8 I/O is enabled, ELinks uses this array instead of
118 * ::frame_freebsd[], and converts the characters from CP437 to UTF-8.
120 * Derived from ::frame_freebsd[] by converting the characters to
121 * Unicode and back to CP437. frame_freebsd[1] = 138 = 0x8a = U+240B
122 * SYMBOL FOR VERTICAL TABULATION does not exist in CP437, so we
123 * substitute U+2592 MEDIUM SHADE. */
124 static const unsigned char frame_freebsd_u
[48] = {
125 177, 177, 219, 179, 180, 180, 180, 191,
126 191, 180, 179, 191, 217, 217, 217, 191,
127 192, 193, 194, 195, 196, 197, 195, 195,
128 192, 218, 193, 194, 195, 196, 197, 193,
129 193, 194, 194, 192, 192, 218, 218, 197,
130 197, 217, 218, 219, 219, 219, 219, 219,
133 /** Mapping from (enum ::border_char - 0xB0) to KOI8-R. */
134 static const unsigned char frame_koi
[48] = {
135 144, 145, 146, 129, 135, 178, 180, 167,
136 166, 181, 161, 168, 174, 173, 172, 131,
137 132, 137, 136, 134, 128, 138, 175, 176,
138 171, 165, 187, 184, 177, 160, 190, 185,
139 186, 182, 183, 170, 169, 162, 164, 189,
140 188, 133, 130, 141, 140, 142, 143, 139,
143 /** Mapping from (enum ::border_char - 0xB0) to CP850 or CP852. Most of
144 * this table is just 0xB0 + @<index in table>, because these codepages
145 * are quite similar to CP437. However, they lack some line-drawing
146 * characters, so we must use others instead. */
147 static const unsigned char frame_restrict
[48] = {
148 176, 177, 178, 179, 180, 179, 186, 186,
149 205, 185, 186, 187, 188, 186, 205, 191,
150 192, 193, 194, 195, 196, 197, 179, 186,
151 200, 201, 202, 203, 204, 205, 206, 205,
152 196, 205, 196, 186, 205, 205, 186, 186,
153 179, 217, 218, 219, 220, 221, 222, 223,
156 #define TERM_STRING(str) INIT_STRING(str, sizeof(str) - 1)
158 /** Like add_string_to_string() but has fewer checks to slow it down. */
159 #define add_term_string(str, tstr) \
160 add_bytes_to_string(str, (tstr).source, (tstr).length)
162 /** Frame begin/end sequences that switch fonts with ECMA-48 SGR.
163 * ECMA-48: CSI Ps... 06/13 = SGR - SELECT GRAPHIC RENDITION
164 * - Ps = 10 = primary (default) font
165 * - Ps = 11 = first alternative font */
166 static const struct string m11_hack_frame_seqs
[] = {
167 /* end border: */ TERM_STRING("\033[10m"),
168 /* begin border: */ TERM_STRING("\033[11m"),
171 /** Frame begin/end sequences for VT100. */
172 static const struct string vt100_frame_seqs
[] = {
173 /* end border: */ TERM_STRING("\x0f"),
174 /* begin border: */ TERM_STRING("\x0e"),
177 /** Italic begin/end sequences using ECMA-48 SGR.
178 * ECMA-48: CSI Ps... 06/13 = SGR - SELECT GRAPHIC RENDITION
179 * - Ps = 3 = italicized
180 * - Ps = 20 = Fraktur (Gothic)
181 * - Ps = 23 = not italicized, not fraktur */
182 static const struct string italic_seqs
[] = {
183 /* end italics: */ TERM_STRING("\033[23m"),
184 /* begin italics: */ TERM_STRING("\033[3m"),
187 /** Underline begin/end sequences using ECMA-48 SGR.
188 * ECMA-48: CSI Ps... 06/13 = SGR - SELECT GRAPHIC RENDITION
189 * - Ps = 4 = singly underlined
190 * - Ps = 21 = doubly underlined
191 * - Ps = 24 = not underlined (neither singly nor doubly) */
192 static const struct string underline_seqs
[] = {
193 /* end underline: */ TERM_STRING("\033[24m"),
194 /* begin underline: */ TERM_STRING("\033[4m"),
197 /* elinks --dump has a separate implementation of color-changing
198 * sequences and does not use these. */
199 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
200 static const struct string color256_seqs
[] = {
201 /* foreground: */ TERM_STRING("\033[0;38;5;%dm"),
202 /* background: */ TERM_STRING("\033[48;5;%dm"),
205 static const struct string fbterm_color256_seqs
[] = {
206 /* foreground: */ TERM_STRING("\033[m\033[1;%d}"),
207 /* background: */ TERM_STRING("\033[2;%d}"),
211 /** Used in @c add_char*() and @c redraw_screen() to reduce the logic.
212 * It is updated from terminal._template_.* using option.change_hook.
214 * @todo TODO: termcap/terminfo can maybe gradually be introduced via
215 * this structure. We'll see. --jonas */
216 struct screen_driver
{
217 LIST_HEAD(struct screen_driver
);
219 /** The terminal._template_.type. Together with the #name member they
220 * uniquely identify the screen_driver. */
221 enum term_mode_type type
;
223 /** set_screen_driver_opt() sets these. */
224 struct screen_driver_opt
{
225 /** Charsets when doing UTF-8 I/O.
226 * [0] is the common charset and [1] is the frame charset.
227 * Test whether to use UTF-8 I/O using the use_utf8_io() macro. */
230 /** The frame translation table. May be NULL. */
231 const unsigned char *frame
;
233 /** The frame mode setup and teardown sequences. May be NULL. */
234 const struct string
*frame_seqs
;
236 /** The italic mode setup and teardown sequences. May be NULL. */
237 const struct string
*italic
;
239 /** The underline mode setup and teardown sequences. May be NULL. */
240 const struct string
*underline
;
242 /** The color mode */
243 enum color_mode color_mode
;
245 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
246 const struct string
*color256_seqs
;
248 /** These are directly derived from the terminal options. */
249 unsigned int transparent
:1;
252 /* Whether the charset of the terminal is UTF-8. This
253 * is the same as is_cp_utf8(charsets[0]), except the
254 * latter might crash if UTF-8 I/O is disabled. */
255 unsigned int utf8_cp
:1;
256 #endif /* CONFIG_UTF8 */
258 #ifdef CONFIG_COMBINE
259 /* Whether the terminal supports combining characters. */
260 unsigned int combine
:1;
261 #endif /* CONFIG_COMBINE */
264 /* The terminal._template_ name. */
265 unsigned char name
[1]; /* XXX: Keep last! */
268 /** Default options for ::TERM_DUMB. */
269 static const struct screen_driver_opt dumb_screen_driver_opt
= {
270 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
271 /* frame: */ frame_dumb
,
272 /* frame_seqs: */ NULL
,
273 /* italic: */ italic_seqs
,
274 /* underline: */ underline_seqs
,
275 /* color_mode: */ COLOR_MODE_16
,
276 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
277 /* color256_seqs: */ color256_seqs
,
279 /* transparent: */ 1,
282 #endif /* CONFIG_UTF8 */
283 #ifdef CONFIG_COMBINE
285 #endif /* CONFIG_COMBINE */
288 /** Default options for ::TERM_VT100. */
289 static const struct screen_driver_opt vt100_screen_driver_opt
= {
290 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
291 /* frame: */ frame_vt100
,
292 /* frame_seqs: */ vt100_frame_seqs
,
293 /* italic: */ italic_seqs
,
294 /* underline: */ underline_seqs
,
295 /* color_mode: */ COLOR_MODE_16
,
296 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
297 /* color256_seqs: */ color256_seqs
,
299 /* transparent: */ 1,
302 #endif /* CONFIG_UTF8 */
303 #ifdef CONFIG_COMBINE
305 #endif /* CONFIG_COMBINE */
308 /** Default options for ::TERM_LINUX. */
309 static const struct screen_driver_opt linux_screen_driver_opt
= {
310 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
311 /* frame: */ NULL
, /* No restrict_852 */
312 /* frame_seqs: */ NULL
, /* No m11_hack */
313 /* italic: */ italic_seqs
,
314 /* underline: */ underline_seqs
,
315 /* color_mode: */ COLOR_MODE_16
,
316 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
317 /* color256_seqs: */ color256_seqs
,
319 /* transparent: */ 1,
322 #endif /* CONFIG_UTF8 */
323 #ifdef CONFIG_COMBINE
325 #endif /* CONFIG_COMBINE */
328 /** Default options for ::TERM_KOI8. */
329 static const struct screen_driver_opt koi8_screen_driver_opt
= {
330 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
331 /* frame: */ frame_koi
,
332 /* frame_seqs: */ NULL
,
333 /* italic: */ italic_seqs
,
334 /* underline: */ underline_seqs
,
335 /* color_mode: */ COLOR_MODE_16
,
336 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
337 /* color256_seqs: */ color256_seqs
,
339 /* transparent: */ 1,
342 #endif /* CONFIG_UTF8 */
343 #ifdef CONFIG_COMBINE
345 #endif /* CONFIG_COMBINE */
348 /** Default options for ::TERM_FREEBSD. */
349 static const struct screen_driver_opt freebsd_screen_driver_opt
= {
350 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
351 /* frame: */ frame_freebsd
,
352 /* frame_seqs: */ NULL
, /* No m11_hack */
353 /* italic: */ italic_seqs
,
354 /* underline: */ underline_seqs
,
355 /* color_mode: */ COLOR_MODE_16
,
356 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
357 /* color256_seqs: */ color256_seqs
,
359 /* transparent: */ 1,
362 #endif /* CONFIG_UTF8 */
363 #ifdef CONFIG_COMBINE
365 #endif /* CONFIG_COMBINE */
368 /** Default options for ::TERM_FBTERM. */
369 static const struct screen_driver_opt fbterm_screen_driver_opt
= {
370 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
372 /* frame_seqs: */ NULL
, /* No m11_hack */
373 /* italic: */ italic_seqs
,
374 /* underline: */ underline_seqs
,
375 /* color_mode: */ COLOR_MODE_16
,
376 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
377 /* color256_seqs: */ fbterm_color256_seqs
,
379 /* transparent: */ 1,
382 #endif /* CONFIG_UTF8 */
383 #ifdef CONFIG_COMBINE
385 #endif /* CONFIG_COMBINE */
388 /** Default options for all the different types of terminals.
389 * XXX: Keep in sync with enum term_mode_type. */
390 static const struct screen_driver_opt
*const screen_driver_opts
[] = {
391 /* TERM_DUMB: */ &dumb_screen_driver_opt
,
392 /* TERM_VT100: */ &vt100_screen_driver_opt
,
393 /* TERM_LINUX: */ &linux_screen_driver_opt
,
394 /* TERM_KOI8: */ &koi8_screen_driver_opt
,
395 /* TERM_FREEBSD: */ &freebsd_screen_driver_opt
,
396 /* TERM_FBTERM: */ &fbterm_screen_driver_opt
,
399 #define use_utf8_io(driver) ((driver)->opt.charsets[0] != -1)
401 static INIT_LIST_OF(struct screen_driver
, active_screen_drivers
);
403 /** Set screen_driver.opt according to screen_driver.type and @a term_spec.
404 * Other members of @a *driver need not have been initialized.
406 * If you modify anything here, check whether option descriptions
407 * should be updated. */
409 set_screen_driver_opt(struct screen_driver
*driver
, struct option
*term_spec
)
411 const int cp
= get_opt_codepage_tree(term_spec
, "charset", NULL
);
412 int utf8_io
= get_opt_bool_tree(term_spec
, "utf_8_io", NULL
);
414 /* Copy all the original options from constants, so that this
415 * function need not carefully restore options one by one. */
416 copy_struct(&driver
->opt
, screen_driver_opts
[driver
->type
]);
418 #ifdef CONFIG_COMBINE
419 driver
->opt
.combine
= get_opt_bool_tree(term_spec
, "combine", NULL
);
420 #endif /* CONFIG_COMBINE */
422 /* Force UTF-8 I/O if the UTF-8 charset is selected. Various
423 * places assume that the terminal's charset is unibyte if
424 * UTF-8 I/O is disabled. (bug 827) */
425 if (is_cp_utf8(cp
)) {
426 driver
->opt
.utf8_cp
= 1;
429 driver
->opt
.utf8_cp
= 0;
431 #endif /* CONFIG_UTF8 */
433 driver
->opt
.color_mode
= get_opt_int_tree(term_spec
, "colors", NULL
);
434 driver
->opt
.transparent
= get_opt_bool_tree(term_spec
, "transparency",
437 if (get_opt_bool_tree(term_spec
, "italic", NULL
)) {
438 driver
->opt
.italic
= italic_seqs
;
440 driver
->opt
.italic
= NULL
;
443 if (get_opt_bool_tree(term_spec
, "underline", NULL
)) {
444 driver
->opt
.underline
= underline_seqs
;
446 driver
->opt
.underline
= NULL
;
450 driver
->opt
.charsets
[0] = cp
;
452 /* When we're using UTF-8 I/O, we never need to switch
453 * charsets or fonts for drawing frames, because the
454 * characters encoded in UTF-8 are already unambiguous. */
455 driver
->opt
.frame_seqs
= NULL
;
457 if (driver
->type
== TERM_LINUX
|| driver
->type
== TERM_FBTERM
) {
458 if (get_opt_bool_tree(term_spec
, "restrict_852", NULL
))
459 driver
->opt
.frame
= frame_restrict
;
460 driver
->opt
.charsets
[1] = get_cp_index("cp437");
462 } else if (driver
->type
== TERM_FREEBSD
) {
463 driver
->opt
.frame
= frame_freebsd_u
;
464 driver
->opt
.charsets
[1] = get_cp_index("cp437");
466 } else if (driver
->type
== TERM_VT100
) {
467 driver
->opt
.frame
= frame_vt100_u
;
468 driver
->opt
.charsets
[1] = get_cp_index("cp437");
470 } else if (driver
->type
== TERM_KOI8
) {
471 driver
->opt
.charsets
[1] = get_cp_index("koi8-r");
475 /* Don't let driver->opt.charsets[1] become
476 * UTF-8, because it is passed to cp2u(),
477 * which supports only unibyte characters. */
478 if (driver
->opt
.utf8_cp
)
479 driver
->opt
.charsets
[1] = get_cp_index("US-ASCII");
481 #endif /* CONFIG_UTF8 */
482 driver
->opt
.charsets
[1] = driver
->opt
.charsets
[0];
485 } else { /* !utf8_io */
486 driver
->opt
.charsets
[0] = -1;
488 if (driver
->type
== TERM_LINUX
) {
489 if (get_opt_bool_tree(term_spec
, "restrict_852", NULL
))
490 driver
->opt
.frame
= frame_restrict
;
492 if (get_opt_bool_tree(term_spec
, "m11_hack", NULL
))
493 driver
->opt
.frame_seqs
= m11_hack_frame_seqs
;
495 } else if (driver
->type
== TERM_FREEBSD
) {
496 if (get_opt_bool_tree(term_spec
, "m11_hack", NULL
))
497 driver
->opt
.frame_seqs
= m11_hack_frame_seqs
;
499 } else if (driver
->type
== TERM_VT100
) {
500 driver
->opt
.frame
= frame_vt100
;
506 screen_driver_change_hook(struct session
*ses
, struct option
*term_spec
,
507 struct option
*changed
)
509 enum term_mode_type type
= get_opt_int_tree(term_spec
, "type", NULL
);
510 struct screen_driver
*driver
;
511 unsigned char *name
= term_spec
->name
;
513 foreach (driver
, active_screen_drivers
)
514 if (driver
->type
== type
&& !strcmp(driver
->name
, name
)) {
515 set_screen_driver_opt(driver
, term_spec
);
522 static inline struct screen_driver
*
523 add_screen_driver(enum term_mode_type type
, struct terminal
*term
, int env_len
)
525 struct screen_driver
*driver
;
527 /* One byte is reserved for name in struct screen_driver. */
528 driver
= mem_alloc(sizeof(*driver
) + env_len
);
529 if (!driver
) return NULL
;
531 /* These four operations fully initialize *driver. */
532 add_to_list(active_screen_drivers
, driver
);
534 set_screen_driver_opt(driver
, term
->spec
);
535 memcpy(driver
->name
, term
->spec
->name
, env_len
+ 1);
537 term
->spec
->change_hook
= screen_driver_change_hook
;
540 term
->utf8_cp
= driver
->opt
.utf8_cp
;
541 term
->utf8_io
= use_utf8_io(driver
);
542 #endif /* CONFIG_UTF8 */
547 static inline struct screen_driver
*
548 get_screen_driver(struct terminal
*term
)
550 enum term_mode_type type
= get_opt_int_tree(term
->spec
, "type", NULL
);
551 unsigned char *name
= term
->spec
->name
;
552 int len
= strlen(name
);
553 struct screen_driver
*driver
;
555 foreach (driver
, active_screen_drivers
) {
556 if (driver
->type
!= type
) continue;
557 if (strcmp(driver
->name
, name
)) continue;
559 /* Some simple probably useless MRU ;) */
560 move_to_top_of_list(active_screen_drivers
, driver
);
563 term
->utf8_cp
= driver
->opt
.utf8_cp
;
564 term
->utf8_io
= use_utf8_io(driver
);
565 #endif /* CONFIG_UTF8 */
569 return add_screen_driver(type
, term
, len
);
572 /** Release private screen drawing utilities. */
574 done_screen_drivers(struct module
*xxx
)
576 free_list(active_screen_drivers
);
580 /** Adds the term code for positioning the cursor at @a x and @a y to
581 * @a string. The template term code is: "\033[<y>;<x>H" */
582 static inline struct string
*
583 add_cursor_move_to_string(struct string
*screen
, int y
, int x
)
585 #define CURSOR_NUM_LEN 10 /* 10 chars for @y and @x numbers should be more than enough. */
586 unsigned char code
[4 + 2 * CURSOR_NUM_LEN
+ 1];
587 unsigned int length
= 2;
592 if (ulongcat(code
, &length
, y
, CURSOR_NUM_LEN
, 0) < 0)
595 code
[length
++] = ';';
597 if (ulongcat(code
, &length
, x
, CURSOR_NUM_LEN
, 0) < 0)
600 code
[length
++] = 'H';
602 return add_bytes_to_string(screen
, code
, length
);
603 #undef CURSOR_NUM_LEN
606 struct screen_state
{
607 unsigned char border
;
608 unsigned char italic
;
609 unsigned char underline
;
612 /** Following should match the screen_char.color field. */
613 unsigned char color
[SCREEN_COLOR_SIZE
];
616 #if defined(CONFIG_TRUE_COLOR)
617 #define INIT_SCREEN_STATE { 0xFF, 0xFF, 0xFF, 0xFF, 0, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} }
618 #elif defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
619 #define INIT_SCREEN_STATE { 0xFF, 0xFF, 0xFF, 0xFF, 0, { 0xFF, 0xFF } }
621 #define INIT_SCREEN_STATE { 0xFF, 0xFF, 0xFF, 0xFF, 0, { 0xFF } }
624 #ifdef CONFIG_TRUE_COLOR
626 compare_color_true(unsigned char *a
, unsigned char *b
)
628 return !memcmp(a
, b
, 6);
632 compare_bg_color_true(unsigned char *a
, unsigned char *b
)
634 return (a
[3] == b
[3] && a
[4] == b
[4] && a
[5] == b
[5]);
638 compare_fg_color_true(unsigned char *a
, unsigned char *b
)
640 return (a
[0] == b
[0] && a
[1] == b
[1] && a
[2] == b
[2]);
644 copy_color_true(unsigned char *a
, unsigned char *b
)
650 background_is_black(unsigned char *a
)
652 static unsigned char b
[6] = {0, 0, 0, 0, 0, 0};
654 return compare_bg_color_true(a
, b
);
658 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
660 compare_color_256(unsigned char *a
, unsigned char *b
)
662 return (a
[0] == b
[0] && a
[1] == b
[1]);
666 compare_bg_color_256(unsigned char *a
, unsigned char *b
)
668 return (a
[1] == b
[1]);
672 compare_fg_color_256(unsigned char *a
, unsigned char *b
)
674 return (a
[0] == b
[0]);
678 copy_color_256(unsigned char *a
, unsigned char *b
)
686 compare_color_16(unsigned char *a
, unsigned char *b
)
688 return (a
[0] == b
[0]);
692 compare_bg_color_16(unsigned char *a
, unsigned char *b
)
694 return (TERM_COLOR_BACKGROUND_16(a
) == TERM_COLOR_BACKGROUND_16(b
));
698 compare_fg_color_16(unsigned char *a
, unsigned char *b
)
700 return (TERM_COLOR_FOREGROUND_16(a
) == TERM_COLOR_FOREGROUND_16(b
));
704 copy_color_16(unsigned char *a
, unsigned char *b
)
711 add_char_data(struct string
*screen
, struct screen_driver
*driver
,
712 unicode_val_T data
, unsigned char border
)
713 #else /* !CONFIG_UTF8 */
715 add_char_data(struct string
*screen
, struct screen_driver
*driver
,
716 unsigned char data
, unsigned char border
)
717 #endif /* !CONFIG_UTF8 */
719 /* charset use_utf8_io border data add_to_string
720 * ------- ----------- ------ ---------------- ----------------
721 * unibyte 0 0 terminal unibyte terminal unibyte
722 * unibyte 0 1 enum border_char border unibyte
723 * unibyte 1 0 terminal unibyte UTF-8
724 * unibyte 1 1 enum border_char UTF-8
725 * UTF-8 1 0 UTF-32 (*) UTF-8
726 * UTF-8 1 1 enum border_char UTF-8
728 * (*) For "UTF-32" above, data can also be UCS_NO_CHAR,
729 * in which case this function must not alter *screen.
732 if (border
&& driver
->opt
.frame
&& data
>= 176 && data
< 224)
733 data
= driver
->opt
.frame
[data
- 176];
736 if (driver
->opt
.utf8_cp
) {
738 data
= cp2u(driver
->opt
.charsets
[1],
739 (unsigned char) data
);
741 if (data
== UCS_NO_CHAR
)
743 #ifdef CONFIG_COMBINE
744 if (data
>= UCS_BEGIN_COMBINED
&& data
<= last_combined
) {
745 unicode_val_T
*text
= combined
[data
- UCS_BEGIN_COMBINED
];
747 if (driver
->opt
.combine
) {
749 while (*text
!= UCS_END_COMBINED
) {
750 add_to_string(screen
, encode_utf8(*text
));
759 #endif /* CONFIG_COMBINE */
760 if (!isscreensafe_ucs(data
))
762 add_to_string(screen
, encode_utf8(data
));
764 #endif /* CONFIG_UTF8 */
765 if (use_utf8_io(driver
)) {
766 int charset
= driver
->opt
.charsets
[!!border
];
768 if (border
|| isscreensafe(data
))
769 add_to_string(screen
, cp2utf8(charset
, data
));
770 else /* UCS_SPACE <= 0x7F and so fits in one UTF-8 byte */
771 add_char_to_string(screen
, UCS_SPACE
);
773 if (border
|| isscreensafe(data
))
774 add_char_to_string(screen
, (unsigned char)data
);
776 add_char_to_string(screen
, ' ');
780 /** Time critical section. */
782 add_char16(struct string
*screen
, struct screen_driver
*driver
,
783 struct screen_char
*ch
, struct screen_state
*state
)
785 unsigned char border
= (ch
->attr
& SCREEN_ATTR_FRAME
);
786 unsigned char italic
= (ch
->attr
& SCREEN_ATTR_ITALIC
);
787 unsigned char underline
= (ch
->attr
& SCREEN_ATTR_UNDERLINE
);
788 unsigned char bold
= (ch
->attr
& SCREEN_ATTR_BOLD
);
792 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
793 #endif /* CONFIG_UTF8 */
794 border
!= state
->border
&& driver
->opt
.frame_seqs
796 state
->border
= border
;
797 add_term_string(screen
, driver
->opt
.frame_seqs
[!!border
]);
802 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
803 #endif /* CONFIG_UTF8 */
804 italic
!= state
->italic
&& driver
->opt
.italic
806 state
->italic
= italic
;
807 add_term_string(screen
, driver
->opt
.italic
[!!italic
]);
812 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
813 #endif /* CONFIG_UTF8 */
814 underline
!= state
->underline
&& driver
->opt
.underline
816 state
->underline
= underline
;
817 add_term_string(screen
, driver
->opt
.underline
[!!underline
]);
822 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
823 #endif /* CONFIG_UTF8 */
828 add_bytes_to_string(screen
, "\033[1m", 4);
830 /* Force repainting of the other attributes. */
831 state
->color
[0] = ch
->c
.color
[0] + 1;
837 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
838 #endif /* CONFIG_UTF8 */
839 !compare_color_16(ch
->c
.color
, state
->color
)
841 copy_color_16(state
->color
, ch
->c
.color
);
843 add_bytes_to_string(screen
, "\033[0", 3);
845 /* @set_screen_driver_opt has set @driver->opt.color_mode
846 * according to terminal-type-specific options.
847 * The caller of @add_char16 has already partially
848 * checked it, but there are still these possibilities:
849 * - COLOR_MODE_MONO. Then don't show colors, but
850 * perhaps use the standout attribute.
851 * - COLOR_MODE_16. Use 16 colors.
852 * - An unsupported color mode. Use 16 colors. */
853 if (driver
->opt
.color_mode
!= COLOR_MODE_MONO
) {
854 unsigned char code
[6] = ";30;40";
855 unsigned char bgcolor
= TERM_COLOR_BACKGROUND_16(ch
->c
.color
);
857 code
[2] += TERM_COLOR_FOREGROUND_16(ch
->c
.color
);
859 if (!driver
->opt
.transparent
|| bgcolor
!= 0) {
861 add_bytes_to_string(screen
, code
, 6);
863 add_bytes_to_string(screen
, code
, 3);
866 } else if (ch
->attr
& SCREEN_ATTR_STANDOUT
) {
867 /* Flip the fore- and background colors for highlighing
869 add_bytes_to_string(screen
, ";7", 2);
872 if (italic
&& driver
->opt
.italic
) {
873 add_bytes_to_string(screen
, ";3", 2);
876 if (underline
&& driver
->opt
.underline
) {
877 add_bytes_to_string(screen
, ";4", 2);
880 /* Check if the char should be rendered bold. */
882 add_bytes_to_string(screen
, ";1", 2);
885 add_bytes_to_string(screen
, "m", 1);
888 add_char_data(screen
, driver
, ch
->data
, border
);
891 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
893 add_char_color(struct string
*screen
, const struct string
*seq
, unsigned char color
)
895 unsigned char color_buf
[3];
896 unsigned char *color_pos
= color_buf
;
900 check_string_magic(seq
);
901 for (; seq
->source
[seq_pos
] != '%'; seq_pos
++) ;
903 add_bytes_to_string(screen
, seq
->source
, seq_pos
);
925 color2
= (color
% 10);
927 color_buf
[1] = '0' + color
;
931 color_buf
[2] = '0' + color
;
933 add_bytes_to_string(screen
, color_pos
, color_len
);
935 seq_pos
+= 2; /* Skip "%d" */
936 add_bytes_to_string(screen
, &seq
->source
[seq_pos
], seq
->length
- seq_pos
);
939 #define add_background_color(str, seq, chr) add_char_color(str, &(seq)[1], (chr)->c.color[1])
940 #define add_foreground_color(str, seq, chr) add_char_color(str, &(seq)[0], (chr)->c.color[0])
942 /** Time critical section. */
944 add_char256(struct string
*screen
, struct screen_driver
*driver
,
945 struct screen_char
*ch
, struct screen_state
*state
)
947 unsigned char attr_delta
= (ch
->attr
^ state
->attr
);
951 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
952 #endif /* CONFIG_UTF8 */
955 if ((attr_delta
& SCREEN_ATTR_FRAME
) && driver
->opt
.frame_seqs
) {
956 state
->border
= !!(ch
->attr
& SCREEN_ATTR_FRAME
);
957 add_term_string(screen
, driver
->opt
.frame_seqs
[state
->border
]);
960 if ((attr_delta
& SCREEN_ATTR_ITALIC
) && driver
->opt
.italic
) {
961 state
->italic
= !!(ch
->attr
& SCREEN_ATTR_ITALIC
);
962 add_term_string(screen
, driver
->opt
.italic
[state
->italic
]);
965 if ((attr_delta
& SCREEN_ATTR_UNDERLINE
) && driver
->opt
.underline
) {
966 state
->underline
= !!(ch
->attr
& SCREEN_ATTR_UNDERLINE
);
967 add_term_string(screen
, driver
->opt
.underline
[state
->underline
]);
970 if (attr_delta
& SCREEN_ATTR_BOLD
) {
971 if (ch
->attr
& SCREEN_ATTR_BOLD
) {
972 add_bytes_to_string(screen
, "\033[1m", 4);
974 /* Force repainting of the other attributes. */
975 state
->color
[0] = ch
->c
.color
[0] + 1;
979 state
->attr
= ch
->attr
;
984 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
985 #endif /* CONFIG_UTF8 */
986 !compare_color_256(ch
->c
.color
, state
->color
)
988 copy_color_256(state
->color
, ch
->c
.color
);
990 add_foreground_color(screen
, driver
->opt
.color256_seqs
, ch
);
991 if (!driver
->opt
.transparent
|| ch
->c
.color
[1] != 0) {
992 add_background_color(screen
, driver
->opt
.color256_seqs
, ch
);
995 if (ch
->attr
& SCREEN_ATTR_BOLD
)
996 add_bytes_to_string(screen
, "\033[1m", 4);
998 if (ch
->attr
& SCREEN_ATTR_ITALIC
&& driver
->opt
.italic
) {
999 state
->italic
= !!(ch
->attr
& SCREEN_ATTR_ITALIC
);
1000 add_term_string(screen
, driver
->opt
.italic
[state
->italic
]);
1003 if (ch
->attr
& SCREEN_ATTR_UNDERLINE
&& driver
->opt
.underline
) {
1004 state
->underline
= !!(ch
->attr
& SCREEN_ATTR_UNDERLINE
);
1005 add_term_string(screen
, driver
->opt
.underline
[state
->underline
]);
1009 add_char_data(screen
, driver
, ch
->data
, ch
->attr
& SCREEN_ATTR_FRAME
);
1013 #ifdef CONFIG_TRUE_COLOR
1014 static const struct string color_true_seqs
[] = {
1015 /* foreground: */ TERM_STRING("\033[0;38;2"),
1016 /* background: */ TERM_STRING("\033[48;2"),
1018 #define add_true_background_color(str, seq, chr) add_char_true_color(str, &(seq)[1], &(chr)->c.color[3])
1019 #define add_true_foreground_color(str, seq, chr) add_char_true_color(str, &(seq)[0], &(chr)->c.color[0])
1021 add_char_true_color(struct string
*screen
, const struct string
*seq
, unsigned char *colors
)
1023 unsigned char color_buf
[3];
1026 check_string_magic(seq
);
1027 add_string_to_string(screen
, seq
);
1028 for (i
= 0; i
< 3; i
++) {
1029 unsigned char *color_pos
= color_buf
;
1031 unsigned char color
= colors
[i
];
1033 add_char_to_string(screen
, ';');
1055 color2
= (color
% 10);
1057 color_buf
[1] = '0' + color
;
1060 color_buf
[2] = '0' + color
;
1062 add_bytes_to_string(screen
, color_pos
, color_len
);
1064 add_char_to_string(screen
, 'm');
1067 /** Time critical section. */
1069 add_char_true(struct string
*screen
, struct screen_driver
*driver
,
1070 struct screen_char
*ch
, struct screen_state
*state
)
1072 unsigned char attr_delta
= (ch
->attr
^ state
->attr
);
1076 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
1077 #endif /* CONFIG_UTF8 */
1080 if ((attr_delta
& SCREEN_ATTR_FRAME
) && driver
->opt
.frame_seqs
) {
1081 state
->border
= !!(ch
->attr
& SCREEN_ATTR_FRAME
);
1082 add_term_string(screen
, driver
->opt
.frame_seqs
[state
->border
]);
1085 if ((attr_delta
& SCREEN_ATTR_ITALIC
) && driver
->opt
.italic
) {
1086 state
->italic
= !!(ch
->attr
& SCREEN_ATTR_ITALIC
);
1087 add_term_string(screen
, driver
->opt
.italic
[state
->italic
]);
1090 if ((attr_delta
& SCREEN_ATTR_UNDERLINE
) && driver
->opt
.underline
) {
1091 state
->underline
= !!(ch
->attr
& SCREEN_ATTR_UNDERLINE
);
1092 add_term_string(screen
, driver
->opt
.underline
[state
->underline
]);
1095 if (attr_delta
& SCREEN_ATTR_BOLD
) {
1096 if (ch
->attr
& SCREEN_ATTR_BOLD
) {
1097 add_bytes_to_string(screen
, "\033[1m", 4);
1099 /* Force repainting of the other attributes. */
1100 state
->color
[0] = ch
->c
.color
[0] + 1;
1104 state
->attr
= ch
->attr
;
1109 !(driver
->opt
.utf8_cp
&& ch
->data
== UCS_NO_CHAR
) &&
1110 #endif /* CONFIG_UTF8 */
1111 !compare_color_true(ch
->c
.color
, state
->color
)
1113 copy_color_true(state
->color
, ch
->c
.color
);
1115 add_true_foreground_color(screen
, color_true_seqs
, ch
);
1116 if (!driver
->opt
.transparent
|| !background_is_black(ch
->c
.color
)) {
1117 add_true_background_color(screen
, color_true_seqs
, ch
);
1120 if (ch
->attr
& SCREEN_ATTR_BOLD
)
1121 add_bytes_to_string(screen
, "\033[1m", 4);
1123 if (ch
->attr
& SCREEN_ATTR_ITALIC
&& driver
->opt
.italic
) {
1124 state
->italic
= !!(ch
->attr
& SCREEN_ATTR_ITALIC
);
1125 add_term_string(screen
, driver
->opt
.italic
[state
->italic
]);
1128 if (ch
->attr
& SCREEN_ATTR_UNDERLINE
&& driver
->opt
.underline
) {
1129 state
->underline
= !!(ch
->attr
& SCREEN_ATTR_UNDERLINE
);
1130 add_term_string(screen
, driver
->opt
.underline
[state
->underline
]);
1134 add_char_data(screen
, driver
, ch
->data
, ch
->attr
& SCREEN_ATTR_FRAME
);
1138 #define add_chars(image_, term_, driver_, state_, ADD_CHAR, compare_bg_color, compare_fg_color) \
1140 struct terminal_screen *screen = (term_)->screen; \
1141 int y = screen->dirty_from; \
1142 int ypos = y * (term_)->width; \
1144 int xmax = (term_)->width - 1; \
1145 int ymax = (term_)->height - 1; \
1146 struct screen_char *current = &screen->last_image[ypos]; \
1147 struct screen_char *pos = &screen->image[ypos]; \
1148 struct screen_char *prev_pos = NULL; /* Warning prevention. */ \
1150 int_upper_bound(&screen->dirty_to, ymax); \
1152 for (; y <= screen->dirty_to; y++) { \
1153 int is_last_line = (y == ymax); \
1156 for (; x <= xmax; x++, current++, pos++) { \
1157 /* Workaround for terminals without
1158 * "eat_newline_glitch (xn)", e.g., the cons25 family
1159 * of terminals and cygwin terminal.
1160 * It prevents display distortion, but char at bottom
1161 * right of terminal will not be drawn.
1162 * A better fix would be to correctly detects
1163 * terminal type, and/or add a terminal option for
1164 * this purpose. */ \
1166 if (is_last_line && x == xmax) \
1169 if (compare_bg_color(pos->c.color, current->c.color)) { \
1170 /* No update for exact match. */ \
1171 if (compare_fg_color(pos->c.color, current->c.color)\
1172 && pos->data == current->data \
1173 && pos->attr == current->attr) \
1176 /* Else if the color match and the data is
1178 if (pos->data <= ' ' && current->data <= ' ' \
1179 && pos->attr == current->attr) \
1183 /* Move the cursor when @prev_pos is more than 10 chars
1185 if (prev_y != y || prev_pos + 10 <= pos) { \
1186 add_cursor_move_to_string(image_, y + 1, x + 1);\
1191 for (; prev_pos <= pos ; prev_pos++) \
1192 ADD_CHAR(image_, driver_, prev_pos, state_); \
1197 /*! Updating of the terminal screen is done by checking what needs to
1198 * be updated using the last screen. */
1200 redraw_screen(struct terminal
*term
)
1202 struct screen_driver
*driver
;
1203 struct string image
;
1204 struct screen_state state
= INIT_SCREEN_STATE
;
1205 struct terminal_screen
*screen
= term
->screen
;
1207 if (!screen
|| screen
->dirty_from
> screen
->dirty_to
) return;
1208 if (term
->master
&& is_blocked()) return;
1210 driver
= get_screen_driver(term
);
1211 if (!driver
) return;
1213 if (!init_string(&image
)) return;
1215 switch (driver
->opt
.color_mode
) {
1217 /* If the desired color mode was not compiled in,
1219 case COLOR_MODE_MONO
:
1221 add_chars(&image
, term
, driver
, &state
, add_char16
, compare_bg_color_16
, compare_fg_color_16
);
1223 #ifdef CONFIG_88_COLORS
1225 add_chars(&image
, term
, driver
, &state
, add_char256
, compare_bg_color_256
, compare_fg_color_256
);
1228 #ifdef CONFIG_256_COLORS
1229 case COLOR_MODE_256
:
1230 add_chars(&image
, term
, driver
, &state
, add_char256
, compare_bg_color_256
, compare_fg_color_256
);
1233 #ifdef CONFIG_TRUE_COLOR
1234 case COLOR_MODE_TRUE_COLOR
:
1235 add_chars(&image
, term
, driver
, &state
, add_char_true
, compare_bg_color_true
, compare_fg_color_true
);
1239 case COLOR_MODE_DUMP
:
1240 INTERNAL("Invalid color mode (%d).", driver
->opt
.color_mode
);
1245 if (driver
->opt
.color_mode
!= COLOR_MODE_MONO
)
1246 add_bytes_to_string(&image
, "\033[37;40m", 8);
1248 add_bytes_to_string(&image
, "\033[0m", 4);
1250 /* If we ended in border state end the frame mode. */
1251 if (state
.border
&& driver
->opt
.frame_seqs
)
1252 add_term_string(&image
, driver
->opt
.frame_seqs
[0]);
1256 /* Even if nothing was redrawn, we possibly still need to move
1259 || screen
->cx
!= screen
->lcx
1260 || screen
->cy
!= screen
->lcy
) {
1261 screen
->lcx
= screen
->cx
;
1262 screen
->lcy
= screen
->cy
;
1264 add_cursor_move_to_string(&image
, screen
->cy
+ 1,
1269 if (term
->master
) want_draw();
1270 hard_write(term
->fdout
, image
.source
, image
.length
);
1271 if (term
->master
) done_draw();
1274 done_string(&image
);
1276 copy_screen_chars(screen
->last_image
, screen
->image
, term
->width
* term
->height
);
1277 screen
->dirty_from
= term
->height
;
1278 screen
->dirty_to
= 0;
1282 erase_screen(struct terminal
*term
)
1285 if (is_blocked()) return;
1289 hard_write(term
->fdout
, "\033[2J\033[1;1H", 10);
1290 if (term
->master
) done_draw();
1294 beep_terminal(struct terminal
*term
)
1296 #ifdef CONFIG_OS_WIN32
1297 MessageBeep(MB_ICONEXCLAMATION
);
1299 hard_write(term
->fdout
, "\a", 1);
1303 struct terminal_screen
*
1306 struct terminal_screen
*screen
;
1308 screen
= mem_calloc(1, sizeof(*screen
));
1309 if (!screen
) return NULL
;
1317 /*! The two images are allocated in one chunk.
1318 * @todo TODO: It seems allocation failure here is fatal.
1319 * We should do something! */
1321 resize_screen(struct terminal
*term
, int width
, int height
)
1323 struct terminal_screen
*screen
;
1324 struct screen_char
*image
;
1327 assert(term
&& term
->screen
);
1329 screen
= term
->screen
;
1332 assert(height
>= 0);
1334 size
= width
* height
;
1335 if (size
<= 0) return;
1337 bsize
= size
* sizeof(*image
);
1339 image
= mem_realloc(screen
->image
, bsize
* 2);
1342 screen
->image
= image
;
1343 screen
->last_image
= image
+ size
;
1345 memset(screen
->image
, 0, bsize
);
1346 memset(screen
->last_image
, 0xFF, bsize
);
1348 term
->width
= width
;
1349 term
->height
= height
;
1350 set_screen_dirty(screen
, 0, height
);
1354 done_screen(struct terminal_screen
*screen
)
1356 mem_free_if(screen
->image
);
1360 struct module terminal_screen_module
= struct_module(
1361 /* Because this module is a submodule of terminal_module,
1362 * which is listed in main_modules rather than in builtin_modules,
1363 * its name does not appear in the user interface and
1364 * so need not be translatable. */
1365 /* name: */ "Terminal Screen",
1366 /* options: */ NULL
,
1368 /* submodules: */ NULL
,
1371 /* done: */ done_screen_drivers