Python: Give goto_url_hook only one argument, like follow_url_hook.
[elinks.git] / src / terminal / screen.c
blob77b8fad5be685cc86ac5e2d7d8cb26f19de2ab81
1 /* Terminal screen drawing routines. */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
10 #include "elinks.h"
12 #include "config/options.h"
13 #include "intl/charsets.h"
14 #include "main/module.h"
15 #include "osdep/ascii.h"
16 #include "osdep/osdep.h"
17 #include "terminal/color.h"
18 #include "terminal/draw.h"
19 #include "terminal/hardio.h"
20 #include "terminal/kbd.h"
21 #include "terminal/screen.h"
22 #include "terminal/terminal.h"
23 #include "util/conv.h"
24 #include "util/error.h"
25 #include "util/memory.h"
26 #include "util/string.h"
29 /* TODO: We must use termcap/terminfo if available! --pasky */
31 unsigned char frame_dumb[48] = " ||||++||++++++--|-+||++--|-+----++++++++ ";
32 static unsigned char frame_vt100[48] = "aaaxuuukkuxkjjjkmvwtqnttmlvwtqnvvwwmmllnnjla ";
34 #ifndef CONFIG_UTF8
35 /* For UTF8 I/O */
36 static unsigned char frame_vt100_u[48] = {
37 177, 177, 177, 179, 180, 180, 180, 191,
38 191, 180, 179, 191, 217, 217, 217, 191,
39 192, 193, 194, 195, 196, 197, 195, 195,
40 192, 218, 193, 194, 195, 196, 197, 193,
41 193, 194, 194, 192, 192, 218, 218, 197,
42 197, 217, 218, 177, 32, 32, 32, 32
44 #endif /* CONFIG_UTF8 */
46 static unsigned char frame_freebsd[48] = {
47 130, 138, 128, 153, 150, 150, 150, 140,
48 140, 150, 153, 140, 139, 139, 139, 140,
49 142, 151, 152, 149, 146, 143, 149, 149,
50 142, 141, 151, 152, 149, 146, 143, 151,
51 151, 152, 152, 142, 142, 141, 141, 143,
52 143, 139, 141, 128, 128, 128, 128, 128,
55 static unsigned char frame_koi[48] = {
56 144, 145, 146, 129, 135, 178, 180, 167,
57 166, 181, 161, 168, 174, 173, 172, 131,
58 132, 137, 136, 134, 128, 138, 175, 176,
59 171, 165, 187, 184, 177, 160, 190, 185,
60 186, 182, 183, 170, 169, 162, 164, 189,
61 188, 133, 130, 141, 140, 142, 143, 139,
64 /* Most of this table is just 176 + <index in table>. */
65 static unsigned char frame_restrict[48] = {
66 176, 177, 178, 179, 180, 179, 186, 186,
67 205, 185, 186, 187, 188, 186, 205, 191,
68 192, 193, 194, 195, 196, 197, 179, 186,
69 200, 201, 202, 203, 204, 205, 206, 205,
70 196, 205, 196, 186, 205, 205, 186, 186,
71 179, 217, 218, 219, 220, 221, 222, 223,
74 #define TERM_STRING(str) INIT_STRING(str, sizeof(str) - 1)
76 #define add_term_string(str, tstr) \
77 add_bytes_to_string(str, (tstr).source, (tstr).length)
79 static struct string m11_hack_frame_seqs[] = {
80 /* end border: */ TERM_STRING("\033[10m"),
81 /* begin border: */ TERM_STRING("\033[11m"),
84 #ifdef CONFIG_UTF8
85 static struct string utf8_linux_frame_seqs[] = {
86 /* end border: */ TERM_STRING("\033[10m\033%G"),
87 /* begin border: */ TERM_STRING("\033%@\033[11m"),
89 #endif /* CONFIG_UTF8 */
91 static struct string vt100_frame_seqs[] = {
92 /* end border: */ TERM_STRING("\x0f"),
93 /* begin border: */ TERM_STRING("\x0e"),
96 static struct string underline_seqs[] = {
97 /* begin underline: */ TERM_STRING("\033[24m"),
98 /* end underline: */ TERM_STRING("\033[4m"),
101 /* Used in {add_char*()} and {redraw_screen()} to reduce the logic. It is
102 * updated from terminal._template_.* using option change_hooks. */
103 /* TODO: termcap/terminfo can maybe gradually be introduced via this
104 * structure. We'll see. --jonas */
105 struct screen_driver {
106 LIST_HEAD(struct screen_driver);
108 /* The terminal._template_.type. Together with the @name member the
109 * uniquely identify the screen_driver. */
110 enum term_mode_type type;
112 #ifndef CONFIG_UTF8
113 /* Charsets when doing UTF8 I/O. */
114 /* [0] is the common charset and [1] is the frame charset.
115 * Test wether to use UTF8 I/O using the use_utf8_io() macro. */
116 int charsets[2];
117 #endif /* CONFIG_UTF8 */
119 /* The frame translation table. May be NULL. */
120 unsigned char *frame;
122 /* The frame mode setup and teardown sequences. May be NULL. */
123 struct string *frame_seqs;
125 /* The underline mode setup and teardown sequences. May be NULL. */
126 struct string *underline;
128 /* The color mode */
129 enum color_mode color_mode;
131 /* These are directly derived from the terminal options. */
132 unsigned int transparent:1;
134 #ifdef CONFIG_UTF8
135 /* UTF-8 I/O. Forced on if the UTF-8 charset is selected. (bug 827) */
136 unsigned int utf8:1;
137 #endif /* CONFIG_UTF8 */
139 /* The terminal._template_ name. */
140 unsigned char name[1]; /* XXX: Keep last! */
143 static struct screen_driver dumb_screen_driver = {
144 NULL_LIST_HEAD,
145 /* type: */ TERM_DUMB,
146 #ifndef CONFIG_UTF8
147 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
148 #endif /* CONFIG_UTF8 */
149 /* frame: */ frame_dumb,
150 /* frame_seqs: */ NULL,
151 /* underline: */ underline_seqs,
152 /* color_mode: */ COLOR_MODE_16,
153 /* transparent: */ 1,
154 #ifdef CONFIG_UTF8
155 /* utf-8: */ 0,
156 #endif /* CONFIG_UTF8 */
159 static struct screen_driver vt100_screen_driver = {
160 NULL_LIST_HEAD,
161 /* type: */ TERM_VT100,
162 #ifndef CONFIG_UTF8
163 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
164 #endif /* CONFIG_UTF8 */
165 /* frame: */ frame_vt100,
166 /* frame_seqs: */ vt100_frame_seqs,
167 /* underline: */ underline_seqs,
168 /* color_mode: */ COLOR_MODE_16,
169 /* transparent: */ 1,
170 #ifdef CONFIG_UTF8
171 /* utf-8: */ 0,
172 #endif /* CONFIG_UTF8 */
175 static struct screen_driver linux_screen_driver = {
176 NULL_LIST_HEAD,
177 /* type: */ TERM_LINUX,
178 #ifndef CONFIG_UTF8
179 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
180 #endif /* CONFIG_UTF8 */
181 /* frame: */ NULL, /* No restrict_852 */
182 /* frame_seqs: */ NULL, /* No m11_hack */
183 /* underline: */ underline_seqs,
184 /* color_mode: */ COLOR_MODE_16,
185 /* transparent: */ 1,
186 #ifdef CONFIG_UTF8
187 /* utf-8: */ 0,
188 #endif /* CONFIG_UTF8 */
191 static struct screen_driver koi8_screen_driver = {
192 NULL_LIST_HEAD,
193 /* type: */ TERM_KOI8,
194 #ifndef CONFIG_UTF8
195 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
196 #endif /* CONFIG_UTF8 */
197 /* frame: */ frame_koi,
198 /* frame_seqs: */ NULL,
199 /* underline: */ underline_seqs,
200 /* color_mode: */ COLOR_MODE_16,
201 /* transparent: */ 1,
202 #ifdef CONFIG_UTF8
203 /* utf-8: */ 0,
204 #endif /* CONFIG_UTF8 */
207 static struct screen_driver freebsd_screen_driver = {
208 NULL_LIST_HEAD,
209 /* type: */ TERM_FREEBSD,
210 #ifndef CONFIG_UTF8
211 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
212 #endif /* CONFIG_UTF8 */
213 /* frame: */ frame_freebsd,
214 /* frame_seqs: */ NULL, /* No m11_hack */
215 /* underline: */ underline_seqs,
216 /* color_mode: */ COLOR_MODE_16,
217 /* transparent: */ 1,
218 #ifdef CONFIG_UTF8
219 /* utf-8: */ 0,
220 #endif /* CONFIG_UTF8 */
223 /* XXX: Keep in sync with enum term_mode_type. */
224 static struct screen_driver *screen_drivers[] = {
225 /* TERM_DUMB: */ &dumb_screen_driver,
226 /* TERM_VT100: */ &vt100_screen_driver,
227 /* TERM_LINUX: */ &linux_screen_driver,
228 /* TERM_KOI8: */ &koi8_screen_driver,
229 /* TERM_FREEBSD: */ &freebsd_screen_driver,
232 #ifdef CONFIG_UTF8
233 #define use_utf8_io(driver) ((driver)->utf8)
234 #else
235 #define use_utf8_io(driver) ((driver)->charsets[0] != -1)
236 #endif /* CONFIG_UTF8 */
238 static INIT_LIST_HEAD(active_screen_drivers);
240 static void
241 update_screen_driver(struct screen_driver *driver, struct option *term_spec)
243 #ifdef CONFIG_UTF8
244 /* Force UTF-8 I/O if the UTF-8 charset is selected. Various
245 * places assume that the terminal's charset is unibyte if
246 * UTF-8 I/O is disabled. (bug 827) */
247 driver->utf8 = get_opt_bool_tree(term_spec, "utf_8_io")
248 || is_cp_utf8(get_opt_codepage_tree(term_spec, "charset"));
249 #else
250 int utf8_io = get_opt_bool_tree(term_spec, "utf_8_io");
251 #endif /* CONFIG_UTF8 */
253 driver->color_mode = get_opt_int_tree(term_spec, "colors");
254 driver->transparent = get_opt_bool_tree(term_spec, "transparency");
256 if (get_opt_bool_tree(term_spec, "underline")) {
257 driver->underline = underline_seqs;
258 } else {
259 driver->underline = NULL;
262 #ifdef CONFIG_UTF8
263 if (driver->type == TERM_LINUX) {
264 if (get_opt_bool_tree(term_spec, "restrict_852"))
265 driver->frame = frame_restrict;
267 if (get_opt_bool_tree(term_spec, "m11_hack"))
268 driver->frame_seqs = m11_hack_frame_seqs;
270 if (driver->utf8)
271 driver->frame_seqs = utf8_linux_frame_seqs;
273 } else if (driver->type == TERM_FREEBSD) {
274 if (get_opt_bool_tree(term_spec, "m11_hack"))
275 driver->frame_seqs = m11_hack_frame_seqs;
277 } else if (driver->type == TERM_VT100) {
278 driver->frame = frame_vt100;
280 #else
281 if (utf8_io) {
282 driver->charsets[0] = get_opt_codepage_tree(term_spec, "charset");
283 if (driver->type == TERM_LINUX) {
284 if (get_opt_bool_tree(term_spec, "restrict_852"))
285 driver->frame = frame_restrict;
287 driver->charsets[1] = get_cp_index("cp437");
289 } else if (driver->type == TERM_FREEBSD) {
290 driver->charsets[1] = get_cp_index("cp437");
292 } else if (driver->type == TERM_VT100) {
293 driver->frame = frame_vt100_u;
294 driver->charsets[1] = get_cp_index("cp437");
296 } else if (driver->type == TERM_KOI8) {
297 driver->charsets[1] = get_cp_index("koi8-r");
299 } else {
300 driver->charsets[1] = driver->charsets[0];
303 } else {
304 driver->charsets[0] = -1;
305 if (driver->type == TERM_LINUX) {
306 if (get_opt_bool_tree(term_spec, "restrict_852"))
307 driver->frame = frame_restrict;
309 if (get_opt_bool_tree(term_spec, "m11_hack"))
310 driver->frame_seqs = m11_hack_frame_seqs;
312 } else if (driver->type == TERM_FREEBSD) {
313 if (get_opt_bool_tree(term_spec, "m11_hack"))
314 driver->frame_seqs = m11_hack_frame_seqs;
315 } else if (driver->type == TERM_VT100) {
316 driver->frame = frame_vt100;
319 #endif /* CONFIG_UTF8 */
322 static int
323 screen_driver_change_hook(struct session *ses, struct option *term_spec,
324 struct option *changed)
326 enum term_mode_type type = get_opt_int_tree(term_spec, "type");
327 struct screen_driver *driver;
328 unsigned char *name = term_spec->name;
329 int len = strlen(name);
331 foreach (driver, active_screen_drivers)
332 if (driver->type == type && !memcmp(driver->name, name, len)) {
333 update_screen_driver(driver, term_spec);
334 break;
337 return 0;
340 static inline struct screen_driver *
341 add_screen_driver(enum term_mode_type type, struct terminal *term, int env_len)
343 struct screen_driver *driver;
345 /* One byte is reserved for name in struct screen_driver. */
346 driver = mem_alloc(sizeof(*driver) + env_len);
347 if (!driver) return NULL;
349 memcpy(driver, screen_drivers[type], sizeof(*driver) - 1);
350 memcpy(driver->name, term->spec->name, env_len + 1);
352 add_to_list(active_screen_drivers, driver);
354 update_screen_driver(driver, term->spec);
356 term->spec->change_hook = screen_driver_change_hook;
358 #ifdef CONFIG_UTF8
359 term->utf8 = use_utf8_io(driver);
360 #endif /* CONFIG_UTF8 */
362 return driver;
365 static inline struct screen_driver *
366 get_screen_driver(struct terminal *term)
368 enum term_mode_type type = get_opt_int_tree(term->spec, "type");
369 unsigned char *name = term->spec->name;
370 int len = strlen(name);
371 struct screen_driver *driver;
373 foreach (driver, active_screen_drivers) {
374 if (driver->type != type) continue;
375 if (memcmp(driver->name, name, len + 1)) continue;
377 /* Some simple probably useless MRU ;) */
378 move_to_top_of_list(active_screen_drivers, driver);
380 #ifdef CONFIG_UTF8
381 term->utf8 = use_utf8_io(driver);
382 #endif /* CONFIG_UTF8 */
383 return driver;
386 return add_screen_driver(type, term, len);
389 /* Release private screen drawing utilities. */
390 void
391 done_screen_drivers(struct module *xxx)
393 free_list(active_screen_drivers);
397 /* Adds the term code for positioning the cursor at @x and @y to @string.
398 * The template term code is: "\033[<@y>;<@x>H" */
399 static inline struct string *
400 add_cursor_move_to_string(struct string *screen, int y, int x)
402 #define CURSOR_NUM_LEN 10 /* 10 chars for @y and @x numbers should be more than enough. */
403 unsigned char code[4 + 2 * CURSOR_NUM_LEN + 1];
404 unsigned int length = 2;
406 code[0] = '\033';
407 code[1] = '[';
409 if (ulongcat(code, &length, y, CURSOR_NUM_LEN, 0) < 0)
410 return screen;
412 code[length++] = ';';
414 if (ulongcat(code, &length, x, CURSOR_NUM_LEN, 0) < 0)
415 return screen;
417 code[length++] = 'H';
419 return add_bytes_to_string(screen, code, length);
420 #undef CURSOR_NUM_LEN
423 struct screen_state {
424 unsigned char border;
425 unsigned char underline;
426 unsigned char bold;
427 unsigned char attr;
428 /* Following should match struct screen_char color field. */
429 unsigned char color[SCREEN_COLOR_SIZE];
432 #if defined(CONFIG_TRUE_COLOR)
433 #define INIT_SCREEN_STATE { 0xFF, 0xFF, 0xFF, 0, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} }
434 #elif defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
435 #define INIT_SCREEN_STATE { 0xFF, 0xFF, 0xFF, 0, { 0xFF, 0xFF } }
436 #else
437 #define INIT_SCREEN_STATE { 0xFF, 0xFF, 0xFF, 0, { 0xFF } }
438 #endif
440 #ifdef CONFIG_TRUE_COLOR
441 static inline int
442 compare_color_true(unsigned char *a, unsigned char *b)
444 return !memcmp(a, b, 6);
447 static inline int
448 compare_bg_color_true(unsigned char *a, unsigned char *b)
450 return (a[3] == b[3] && a[4] == b[4] && a[5] == b[5]);
453 static inline int
454 compare_fg_color_true(unsigned char *a, unsigned char *b)
456 return (a[0] == b[0] && a[1] == b[1] && a[2] == b[2]);
459 static inline void
460 copy_color_true(unsigned char *a, unsigned char *b)
462 memcpy(a, b, 6);
465 static inline int
466 background_is_black(unsigned char *a)
468 static unsigned char b[6] = {0, 0, 0, 0, 0, 0};
470 return compare_bg_color_true(a, b);
472 #endif
474 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
475 static inline int
476 compare_color_256(unsigned char *a, unsigned char *b)
478 return (a[0] == b[0] && a[1] == b[1]);
481 static inline int
482 compare_bg_color_256(unsigned char *a, unsigned char *b)
484 return (a[1] == b[1]);
487 static inline int
488 compare_fg_color_256(unsigned char *a, unsigned char *b)
490 return (a[0] == b[0]);
493 static inline void
494 copy_color_256(unsigned char *a, unsigned char *b)
496 a[0] = b[0];
497 a[1] = b[1];
499 #endif
501 static inline int
502 compare_color_16(unsigned char *a, unsigned char *b)
504 return (a[0] == b[0]);
507 static inline int
508 compare_bg_color_16(unsigned char *a, unsigned char *b)
510 return (TERM_COLOR_BACKGROUND_16(a) == TERM_COLOR_BACKGROUND_16(b));
513 static inline int
514 compare_fg_color_16(unsigned char *a, unsigned char *b)
516 return (TERM_COLOR_FOREGROUND_16(a) == TERM_COLOR_FOREGROUND_16(b));
519 static inline void
520 copy_color_16(unsigned char *a, unsigned char *b)
522 a[0] = b[0];
525 #ifdef CONFIG_UTF8
526 static inline void
527 add_char_data(struct string *screen, struct screen_driver *driver,
528 unicode_val_T data, unsigned char border)
529 #else
530 static inline void
531 add_char_data(struct string *screen, struct screen_driver *driver,
532 unsigned char data, unsigned char border)
533 #endif /* CONFIG_UTF8 */
535 if (!isscreensafe(data)) {
536 add_char_to_string(screen, ' ');
537 return;
540 if (border && driver->frame && data >= 176 && data < 224)
541 data = driver->frame[data - 176];
543 if (use_utf8_io(driver)) {
544 #ifdef CONFIG_UTF8
545 if (border)
546 add_char_to_string(screen, (unsigned char)data);
547 else
548 if (data != UCS_NO_CHAR)
549 add_to_string(screen, encode_utf8(data));
550 #else
551 int charset = driver->charsets[!!border];
553 add_to_string(screen, cp2utf8(charset, data));
554 #endif /* CONFIG_UTF8 */
555 return;
558 add_char_to_string(screen, (unsigned char)data);
561 /* Time critical section. */
562 static inline void
563 add_char16(struct string *screen, struct screen_driver *driver,
564 struct screen_char *ch, struct screen_state *state)
566 unsigned char border = (ch->attr & SCREEN_ATTR_FRAME);
567 unsigned char underline = (ch->attr & SCREEN_ATTR_UNDERLINE);
568 unsigned char bold = (ch->attr & SCREEN_ATTR_BOLD);
570 if (
571 #ifdef CONFIG_UTF8
572 (!use_utf8_io(driver) || ch->data != UCS_NO_CHAR) &&
573 #endif /* CONFIG_UTF8 */
574 border != state->border && driver->frame_seqs
576 state->border = border;
577 add_term_string(screen, driver->frame_seqs[!!border]);
580 if (
581 #ifdef CONFIG_UTF8
582 (!use_utf8_io(driver) || ch->data != UCS_NO_CHAR) &&
583 #endif /* CONFIG_UTF8 */
584 underline != state->underline && driver->underline
586 state->underline = underline;
587 add_term_string(screen, driver->underline[!!underline]);
590 if (
591 #ifdef CONFIG_UTF8
592 (!use_utf8_io(driver) || ch->data != UCS_NO_CHAR) &&
593 #endif /* CONFIG_UTF8 */
594 bold != state->bold
596 state->bold = bold;
597 if (bold) {
598 add_bytes_to_string(screen, "\033[1m", 4);
599 } else {
600 /* Force repainting of the other attributes. */
601 state->color[0] = ch->color[0] + 1;
605 if (
606 #ifdef CONFIG_UTF8
607 (!use_utf8_io(driver) || ch->data != UCS_NO_CHAR) &&
608 #endif /* CONFIG_UTF8 */
609 !compare_color_16(ch->color, state->color)
611 copy_color_16(state->color, ch->color);
613 add_bytes_to_string(screen, "\033[0", 3);
615 /* @update_screen_driver has set @driver->color_mode
616 * according to terminal-type-specific options.
617 * The caller of @add_char16 has already partially
618 * checked it, but there are still these possibilities:
619 * - COLOR_MODE_MONO. Then don't show colors, but
620 * perhaps use the standout attribute.
621 * - COLOR_MODE_16. Use 16 colors.
622 * - An unsupported color mode. Use 16 colors. */
623 if (driver->color_mode != COLOR_MODE_MONO) {
624 unsigned char code[6] = ";30;40";
625 unsigned char bgcolor = TERM_COLOR_BACKGROUND_16(ch->color);
627 code[2] += TERM_COLOR_FOREGROUND_16(ch->color);
629 if (!driver->transparent || bgcolor != 0) {
630 code[5] += bgcolor;
631 add_bytes_to_string(screen, code, 6);
632 } else {
633 add_bytes_to_string(screen, code, 3);
636 } else if (ch->attr & SCREEN_ATTR_STANDOUT) {
637 /* Flip the fore- and background colors for highlighing
638 * purposes. */
639 add_bytes_to_string(screen, ";7", 2);
642 if (underline && driver->underline) {
643 add_bytes_to_string(screen, ";4", 2);
646 /* Check if the char should be rendered bold. */
647 if (bold) {
648 add_bytes_to_string(screen, ";1", 2);
651 add_bytes_to_string(screen, "m", 1);
654 add_char_data(screen, driver, ch->data, border);
657 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
658 static struct string color256_seqs[] = {
659 /* foreground: */ TERM_STRING("\033[0;38;5;%dm"),
660 /* background: */ TERM_STRING("\033[48;5;%dm"),
663 static inline void
664 add_char_color(struct string *screen, struct string *seq, unsigned char color)
666 unsigned char color_buf[3];
667 unsigned char *color_pos = color_buf;
668 int seq_pos = 0;
669 int color_len = 1;
671 check_string_magic(seq);
672 for (; seq->source[seq_pos] != '%'; seq_pos++) ;
674 add_bytes_to_string(screen, seq->source, seq_pos);
676 if (color < 10) {
677 color_pos += 2;
678 } else {
679 int color2;
681 ++color_len;
682 if (color < 100) {
683 ++color_pos;
684 } else {
685 ++color_len;
687 if (color < 200) {
688 color_buf[0] = '1';
689 color -= 100;
690 } else {
691 color_buf[0] = '2';
692 color -= 200;
696 color2 = (color % 10);
697 color /= 10;
698 color_buf[1] = '0' + color;
699 color = color2;
702 color_buf[2] = '0' + color;
704 add_bytes_to_string(screen, color_pos, color_len);
706 seq_pos += 2; /* Skip "%d" */
707 add_bytes_to_string(screen, &seq->source[seq_pos], seq->length - seq_pos);
710 #define add_background_color(str, seq, chr) add_char_color(str, &(seq)[1], (chr)->color[1])
711 #define add_foreground_color(str, seq, chr) add_char_color(str, &(seq)[0], (chr)->color[0])
713 /* Time critical section. */
714 static inline void
715 add_char256(struct string *screen, struct screen_driver *driver,
716 struct screen_char *ch, struct screen_state *state)
718 unsigned char attr_delta = (ch->attr ^ state->attr);
720 if (
721 #ifdef CONFIG_UTF8
722 (!use_utf8_io(driver) || ch->data != UCS_NO_CHAR) &&
723 #endif /* CONFIG_UTF8 */
724 attr_delta
726 if ((attr_delta & SCREEN_ATTR_FRAME) && driver->frame_seqs) {
727 state->border = !!(ch->attr & SCREEN_ATTR_FRAME);
728 add_term_string(screen, driver->frame_seqs[state->border]);
731 if ((attr_delta & SCREEN_ATTR_UNDERLINE) && driver->underline) {
732 state->underline = !!(ch->attr & SCREEN_ATTR_UNDERLINE);
733 add_term_string(screen, driver->underline[state->underline]);
736 if (attr_delta & SCREEN_ATTR_BOLD) {
737 if (ch->attr & SCREEN_ATTR_BOLD) {
738 add_bytes_to_string(screen, "\033[1m", 4);
739 } else {
740 /* Force repainting of the other attributes. */
741 state->color[0] = ch->color[0] + 1;
745 state->attr = ch->attr;
748 if (
749 #ifdef CONFIG_UTF8
750 (!use_utf8_io(driver) || ch->data != UCS_NO_CHAR) &&
751 #endif /* CONFIG_UTF8 */
752 !compare_color_256(ch->color, state->color)
754 copy_color_256(state->color, ch->color);
756 add_foreground_color(screen, color256_seqs, ch);
757 if (!driver->transparent || ch->color[1] != 0) {
758 add_background_color(screen, color256_seqs, ch);
761 if (ch->attr & SCREEN_ATTR_BOLD)
762 add_bytes_to_string(screen, "\033[1m", 4);
764 if (ch->attr & SCREEN_ATTR_UNDERLINE && driver->underline) {
765 state->underline = !!(ch->attr & SCREEN_ATTR_UNDERLINE);
766 add_term_string(screen, driver->underline[state->underline]);
770 add_char_data(screen, driver, ch->data, ch->attr & SCREEN_ATTR_FRAME);
772 #endif
774 #ifdef CONFIG_TRUE_COLOR
775 static struct string color_true_seqs[] = {
776 /* foreground: */ TERM_STRING("\033[0;38;2"),
777 /* background: */ TERM_STRING("\033[48;2"),
779 #define add_true_background_color(str, seq, chr) add_char_true_color(str, &(seq)[1], &(chr)->color[3])
780 #define add_true_foreground_color(str, seq, chr) add_char_true_color(str, &(seq)[0], &(chr)->color[0])
781 static inline void
782 add_char_true_color(struct string *screen, struct string *seq, unsigned char *colors)
784 unsigned char color_buf[3];
785 int i;
787 check_string_magic(seq);
788 add_string_to_string(screen, seq);
789 for (i = 0; i < 3; i++) {
790 unsigned char *color_pos = color_buf;
791 int color_len = 1;
792 unsigned char color = colors[i];
794 add_char_to_string(screen, ';');
796 if (color < 10) {
797 color_pos += 2;
798 } else {
799 int color2;
801 ++color_len;
802 if (color < 100) {
803 ++color_pos;
804 } else {
805 ++color_len;
807 if (color < 200) {
808 color_buf[0] = '1';
809 color -= 100;
810 } else {
811 color_buf[0] = '2';
812 color -= 200;
816 color2 = (color % 10);
817 color /= 10;
818 color_buf[1] = '0' + color;
819 color = color2;
821 color_buf[2] = '0' + color;
823 add_bytes_to_string(screen, color_pos, color_len);
825 add_char_to_string(screen, 'm');
828 /* Time critical section. */
829 static inline void
830 add_char_true(struct string *screen, struct screen_driver *driver,
831 struct screen_char *ch, struct screen_state *state)
833 unsigned char attr_delta = (ch->attr ^ state->attr);
835 if (
836 #ifdef CONFIG_UTF8
837 (!use_utf8_io(driver) || ch->data != UCS_NO_CHAR) &&
838 #endif /* CONFIG_UTF8 */
839 attr_delta
841 if ((attr_delta & SCREEN_ATTR_FRAME) && driver->frame_seqs) {
842 state->border = !!(ch->attr & SCREEN_ATTR_FRAME);
843 add_term_string(screen, driver->frame_seqs[state->border]);
846 if ((attr_delta & SCREEN_ATTR_UNDERLINE) && driver->underline) {
847 state->underline = !!(ch->attr & SCREEN_ATTR_UNDERLINE);
848 add_term_string(screen, driver->underline[state->underline]);
851 if (attr_delta & SCREEN_ATTR_BOLD) {
852 if (ch->attr & SCREEN_ATTR_BOLD) {
853 add_bytes_to_string(screen, "\033[1m", 4);
854 } else {
855 /* Force repainting of the other attributes. */
856 state->color[0] = ch->color[0] + 1;
860 state->attr = ch->attr;
863 if (
864 #ifdef CONFIG_UTF8
865 (!use_utf8_io(driver) || ch->data != UCS_NO_CHAR) &&
866 #endif /* CONFIG_UTF8 */
867 !compare_color_true(ch->color, state->color)
869 copy_color_true(state->color, ch->color);
871 add_true_foreground_color(screen, color_true_seqs, ch);
872 if (!driver->transparent || !background_is_black(ch->color)) {
873 add_true_background_color(screen, color_true_seqs, ch);
876 if (ch->attr & SCREEN_ATTR_BOLD)
877 add_bytes_to_string(screen, "\033[1m", 4);
879 if (ch->attr & SCREEN_ATTR_UNDERLINE && driver->underline) {
880 state->underline = !!(ch->attr & SCREEN_ATTR_UNDERLINE);
881 add_term_string(screen, driver->underline[state->underline]);
885 add_char_data(screen, driver, ch->data, ch->attr & SCREEN_ATTR_FRAME);
887 #endif
889 #define add_chars(image_, term_, driver_, state_, ADD_CHAR, compare_bg_color, compare_fg_color) \
891 struct terminal_screen *screen = (term_)->screen; \
892 int y = screen->dirty_from; \
893 int ypos = y * (term_)->width; \
894 int prev_y = -1; \
895 int xmax = (term_)->width - 1; \
896 int ymax = (term_)->height - 1; \
897 struct screen_char *current = &screen->last_image[ypos]; \
898 struct screen_char *pos = &screen->image[ypos]; \
899 struct screen_char *prev_pos = NULL; /* Warning prevention. */ \
901 int_upper_bound(&screen->dirty_to, ymax); \
903 for (; y <= screen->dirty_to; y++) { \
904 int is_last_line = (y == ymax); \
905 int x = 0; \
907 for (; x <= xmax; x++, current++, pos++) { \
908 /* Workaround for terminals without
909 * "eat_newline_glitch (xn)", e.g., the cons25 family
910 * of terminals and cygwin terminal.
911 * It prevents display distortion, but char at bottom
912 * right of terminal will not be drawn.
913 * A better fix would be to correctly detects
914 * terminal type, and/or add a terminal option for
915 * this purpose. */ \
917 if (is_last_line && x == xmax) \
918 break; \
920 if (compare_bg_color(pos->color, current->color)) { \
921 /* No update for exact match. */ \
922 if (compare_fg_color(pos->color, current->color)\
923 && pos->data == current->data \
924 && pos->attr == current->attr) \
925 continue; \
927 /* Else if the color match and the data is
928 * ``space''. */ \
929 if (pos->data <= ' ' && current->data <= ' ' \
930 && pos->attr == current->attr) \
931 continue; \
934 /* Move the cursor when @prev_pos is more than 10 chars
935 * away. */ \
936 if (prev_y != y || prev_pos + 10 <= pos) { \
937 add_cursor_move_to_string(image_, y + 1, x + 1);\
938 prev_pos = pos; \
939 prev_y = y; \
942 for (; prev_pos <= pos ; prev_pos++) \
943 ADD_CHAR(image_, driver_, prev_pos, state_); \
948 /* Updating of the terminal screen is done by checking what needs to be updated
949 * using the last screen. */
950 void
951 redraw_screen(struct terminal *term)
953 struct screen_driver *driver;
954 struct string image;
955 struct screen_state state = INIT_SCREEN_STATE;
956 struct terminal_screen *screen = term->screen;
958 if (!screen || screen->dirty_from > screen->dirty_to) return;
959 if (term->master && is_blocked()) return;
961 driver = get_screen_driver(term);
962 if (!driver) return;
964 if (!init_string(&image)) return;
966 switch (driver->color_mode) {
967 default:
968 /* If the desired color mode was not compiled in,
969 * use 16 colors. */
970 case COLOR_MODE_MONO:
971 case COLOR_MODE_16:
972 add_chars(&image, term, driver, &state, add_char16, compare_bg_color_16, compare_fg_color_16);
973 break;
974 #ifdef CONFIG_88_COLORS
975 case COLOR_MODE_88:
976 add_chars(&image, term, driver, &state, add_char256, compare_bg_color_256, compare_fg_color_256);
977 break;
978 #endif
979 #ifdef CONFIG_256_COLORS
980 case COLOR_MODE_256:
981 add_chars(&image, term, driver, &state, add_char256, compare_bg_color_256, compare_fg_color_256);
982 break;
983 #endif
984 #ifdef CONFIG_TRUE_COLOR
985 case COLOR_MODE_TRUE_COLOR:
986 add_chars(&image, term, driver, &state, add_char_true, compare_bg_color_true, compare_fg_color_true);
987 break;
988 #endif
989 case COLOR_MODES:
990 case COLOR_MODE_DUMP:
991 INTERNAL("Invalid color mode (%d).", driver->color_mode);
992 return;
995 if (image.length) {
996 if (driver->color_mode != COLOR_MODE_MONO)
997 add_bytes_to_string(&image, "\033[37;40m", 8);
999 add_bytes_to_string(&image, "\033[0m", 4);
1001 /* If we ended in border state end the frame mode. */
1002 if (state.border && driver->frame_seqs)
1003 add_term_string(&image, driver->frame_seqs[0]);
1007 /* Even if nothing was redrawn, we possibly still need to move
1008 * cursor. */
1009 if (image.length
1010 || screen->cx != screen->lcx
1011 || screen->cy != screen->lcy) {
1012 screen->lcx = screen->cx;
1013 screen->lcy = screen->cy;
1015 add_cursor_move_to_string(&image, screen->cy + 1,
1016 screen->cx + 1);
1019 if (image.length) {
1020 if (term->master) want_draw();
1021 hard_write(term->fdout, image.source, image.length);
1022 if (term->master) done_draw();
1025 done_string(&image);
1027 copy_screen_chars(screen->last_image, screen->image, term->width * term->height);
1028 screen->dirty_from = term->height;
1029 screen->dirty_to = 0;
1032 void
1033 erase_screen(struct terminal *term)
1035 if (term->master) {
1036 if (is_blocked()) return;
1037 want_draw();
1040 hard_write(term->fdout, "\033[2J\033[1;1H", 10);
1041 if (term->master) done_draw();
1044 void
1045 beep_terminal(struct terminal *term)
1047 #ifdef CONFIG_OS_WIN32
1048 MessageBeep(MB_ICONEXCLAMATION);
1049 #else
1050 hard_write(term->fdout, "\a", 1);
1051 #endif
1054 struct terminal_screen *
1055 init_screen(void)
1057 struct terminal_screen *screen;
1059 screen = mem_calloc(1, sizeof(*screen));
1060 if (!screen) return NULL;
1062 screen->lcx = -1;
1063 screen->lcy = -1;
1065 return screen;
1068 /* The two images are allocated in one chunk. */
1069 /* TODO: It seems allocation failure here is fatal. We should do something! */
1070 void
1071 resize_screen(struct terminal *term, int width, int height)
1073 struct terminal_screen *screen;
1074 struct screen_char *image;
1075 size_t size, bsize;
1077 assert(term && term->screen);
1079 screen = term->screen;
1081 assert(width >= 0);
1082 assert(height >= 0);
1084 size = width * height;
1085 if (size <= 0) return;
1087 bsize = size * sizeof(*image);
1089 image = mem_realloc(screen->image, bsize * 2);
1090 if (!image) return;
1092 screen->image = image;
1093 screen->last_image = image + size;
1095 memset(screen->image, 0, bsize);
1096 memset(screen->last_image, 0xFF, bsize);
1098 term->width = width;
1099 term->height = height;
1100 set_screen_dirty(screen, 0, height);
1103 void
1104 done_screen(struct terminal_screen *screen)
1106 mem_free_if(screen->image);
1107 mem_free(screen);
1110 struct module terminal_screen_module = struct_module(
1111 /* name: */ "Terminal Screen",
1112 /* options: */ NULL,
1113 /* hooks: */ NULL,
1114 /* submodules: */ NULL,
1115 /* data: */ NULL,
1116 /* init: */ NULL,
1117 /* done: */ done_screen_drivers