1 /* Terminal screen drawing routines. */
12 #include "config/options.h"
13 #include "intl/charsets.h"
14 #include "osdep/ascii.h"
15 #include "osdep/osdep.h"
16 #include "terminal/color.h"
17 #include "terminal/draw.h"
18 #include "terminal/hardio.h"
19 #include "terminal/kbd.h"
20 #include "terminal/screen.h"
21 #include "terminal/terminal.h"
22 #include "util/conv.h"
23 #include "util/error.h"
24 #include "util/memory.h"
25 #include "util/string.h"
28 /* TODO: We must use termcap/terminfo if available! --pasky */
30 unsigned char frame_dumb
[48] = " ||||++||++++++--|-+||++--|-+----++++++++ ";
31 static unsigned char frame_vt100
[48] = "aaaxuuukkuxkjjjkmvwtqnttmlvwtqnvvwwmmllnnjla ";
34 static unsigned char frame_vt100_u
[48] = {
35 177, 177, 177, 179, 180, 180, 180, 191,
36 191, 180, 179, 191, 217, 217, 217, 191,
37 192, 193, 194, 195, 196, 197, 195, 195,
38 192, 218, 193, 194, 195, 196, 197, 193,
39 193, 194, 194, 192, 192, 218, 218, 197,
40 197, 217, 218, 177, 32, 32, 32, 32
43 static unsigned char frame_freebsd
[48] = {
44 130, 138, 128, 153, 150, 150, 150, 140,
45 140, 150, 153, 140, 139, 139, 139, 140,
46 142, 151, 152, 149, 146, 143, 149, 149,
47 142, 141, 151, 152, 149, 146, 143, 151,
48 151, 152, 152, 142, 142, 141, 141, 143,
49 143, 139, 141, 128, 128, 128, 128, 128,
52 static unsigned char frame_koi
[48] = {
53 144, 145, 146, 129, 135, 178, 180, 167,
54 166, 181, 161, 168, 174, 173, 172, 131,
55 132, 137, 136, 134, 128, 138, 175, 176,
56 171, 165, 187, 184, 177, 160, 190, 185,
57 186, 182, 183, 170, 169, 162, 164, 189,
58 188, 133, 130, 141, 140, 142, 143, 139,
61 /* Most of this table is just 176 + <index in table>. */
62 static unsigned char frame_restrict
[48] = {
63 176, 177, 178, 179, 180, 179, 186, 186,
64 205, 185, 186, 187, 188, 186, 205, 191,
65 192, 193, 194, 195, 196, 197, 179, 186,
66 200, 201, 202, 203, 204, 205, 206, 205,
67 196, 205, 196, 186, 205, 205, 186, 186,
68 179, 217, 218, 219, 220, 221, 222, 223,
71 #define TERM_STRING(str) INIT_STRING(str, sizeof(str) - 1)
73 #define add_term_string(str, tstr) \
74 add_bytes_to_string(str, (tstr).source, (tstr).length)
76 static struct string m11_hack_frame_seqs
[] = {
77 /* end border: */ TERM_STRING("\033[10m"),
78 /* begin border: */ TERM_STRING("\033[11m"),
81 static struct string vt100_frame_seqs
[] = {
82 /* end border: */ TERM_STRING("\x0f"),
83 /* begin border: */ TERM_STRING("\x0e"),
86 static struct string underline_seqs
[] = {
87 /* begin underline: */ TERM_STRING("\033[24m"),
88 /* end underline: */ TERM_STRING("\033[4m"),
91 /* Used in {add_char*()} and {redraw_screen()} to reduce the logic. It is
92 * updated from terminal._template_.* using option change_hooks. */
93 /* TODO: termcap/terminfo can maybe gradually be introduced via this
94 * structure. We'll see. --jonas */
95 struct screen_driver
{
96 LIST_HEAD(struct screen_driver
);
98 /* The terminal._template_.type. Together with the @name member the
99 * uniquely identify the screen_driver. */
100 enum term_mode_type type
;
102 /* Charsets when doing UTF8 I/O. */
103 /* [0] is the common charset and [1] is the frame charset.
104 * Test wether to use UTF8 I/O using the use_utf8_io() macro. */
107 /* The frame translation table. May be NULL. */
108 unsigned char *frame
;
110 /* The frame mode setup and teardown sequences. May be NULL. */
111 struct string
*frame_seqs
;
113 /* The underline mode setup and teardown sequences. May be NULL. */
114 struct string
*underline
;
117 enum color_mode color_mode
;
119 /* These are directly derived from the terminal options. */
120 unsigned int transparent
:1;
122 /* The terminal._template_ name. */
123 unsigned char name
[1]; /* XXX: Keep last! */
126 static struct screen_driver dumb_screen_driver
= {
128 /* type: */ TERM_DUMB
,
129 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
130 /* frame: */ frame_dumb
,
131 /* frame_seqs: */ NULL
,
132 /* underline: */ underline_seqs
,
133 /* color_mode: */ COLOR_MODE_16
,
134 /* transparent: */ 1,
137 static struct screen_driver vt100_screen_driver
= {
139 /* type: */ TERM_VT100
,
140 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
141 /* frame: */ frame_vt100
,
142 /* frame_seqs: */ vt100_frame_seqs
,
143 /* underline: */ underline_seqs
,
144 /* color_mode: */ COLOR_MODE_16
,
145 /* transparent: */ 1,
148 static struct screen_driver linux_screen_driver
= {
150 /* type: */ TERM_LINUX
,
151 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
152 /* frame: */ NULL
, /* No restrict_852 */
153 /* frame_seqs: */ NULL
, /* No m11_hack */
154 /* underline: */ underline_seqs
,
155 /* color_mode: */ COLOR_MODE_16
,
156 /* transparent: */ 1,
159 static struct screen_driver koi8_screen_driver
= {
161 /* type: */ TERM_KOI8
,
162 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
163 /* frame: */ frame_koi
,
164 /* frame_seqs: */ NULL
,
165 /* underline: */ underline_seqs
,
166 /* color_mode: */ COLOR_MODE_16
,
167 /* transparent: */ 1,
170 static struct screen_driver freebsd_screen_driver
= {
172 /* type: */ TERM_FREEBSD
,
173 /* charsets: */ { -1, -1 }, /* No UTF8 I/O */
174 /* frame: */ frame_freebsd
,
175 /* frame_seqs: */ NULL
, /* No m11_hack */
176 /* underline: */ underline_seqs
,
177 /* color_mode: */ COLOR_MODE_16
,
178 /* transparent: */ 1,
181 /* XXX: Keep in sync with enum term_mode_type. */
182 static struct screen_driver
*screen_drivers
[] = {
183 /* TERM_DUMB: */ &dumb_screen_driver
,
184 /* TERM_VT100: */ &vt100_screen_driver
,
185 /* TERM_LINUX: */ &linux_screen_driver
,
186 /* TERM_KOI8: */ &koi8_screen_driver
,
187 /* TERM_FREEBSD: */ &freebsd_screen_driver
,
191 static INIT_LIST_HEAD(active_screen_drivers
);
194 update_screen_driver(struct screen_driver
*driver
, struct option
*term_spec
)
196 int utf8_io
= get_opt_bool_tree(term_spec
, "utf_8_io");
198 driver
->color_mode
= get_opt_int_tree(term_spec
, "colors");
199 driver
->transparent
= get_opt_bool_tree(term_spec
, "transparency");
201 if (get_opt_bool_tree(term_spec
, "underline")) {
202 driver
->underline
= underline_seqs
;
204 driver
->underline
= NULL
;
208 driver
->charsets
[0] = get_opt_codepage_tree(term_spec
, "charset");
209 if (driver
->type
== TERM_LINUX
) {
210 if (get_opt_bool_tree(term_spec
, "restrict_852"))
211 driver
->frame
= frame_restrict
;
213 driver
->charsets
[1] = get_cp_index("cp437");
215 } else if (driver
->type
== TERM_FREEBSD
) {
216 driver
->charsets
[1] = get_cp_index("cp437");
218 } else if (driver
->type
== TERM_VT100
) {
219 driver
->frame
= frame_vt100_u
;
220 driver
->charsets
[1] = get_cp_index("cp437");
222 } else if (driver
->type
== TERM_KOI8
) {
223 driver
->charsets
[1] = get_cp_index("koi8-r");
226 driver
->charsets
[1] = driver
->charsets
[0];
230 driver
->charsets
[0] = -1;
231 if (driver
->type
== TERM_LINUX
) {
232 if (get_opt_bool_tree(term_spec
, "restrict_852"))
233 driver
->frame
= frame_restrict
;
235 if (get_opt_bool_tree(term_spec
, "m11_hack"))
236 driver
->frame_seqs
= m11_hack_frame_seqs
;
238 } else if (driver
->type
== TERM_FREEBSD
) {
239 if (get_opt_bool_tree(term_spec
, "m11_hack"))
240 driver
->frame_seqs
= m11_hack_frame_seqs
;
242 } else if (driver
->type
== TERM_VT100
) {
243 driver
->frame
= frame_vt100
;
249 screen_driver_change_hook(struct session
*ses
, struct option
*term_spec
,
250 struct option
*changed
)
252 enum term_mode_type type
= get_opt_int_tree(term_spec
, "type");
253 struct screen_driver
*driver
;
254 unsigned char *name
= term_spec
->name
;
256 foreach (driver
, active_screen_drivers
)
257 if (driver
->type
== type
&& !strcmp(driver
->name
, name
)) {
258 update_screen_driver(driver
, term_spec
);
265 static inline struct screen_driver
*
266 add_screen_driver(enum term_mode_type type
, struct terminal
*term
, int env_len
)
268 struct screen_driver
*driver
;
270 /* One byte is reserved for name in struct screen_driver. */
271 driver
= mem_alloc(sizeof(*driver
) + env_len
);
272 if (!driver
) return NULL
;
274 memcpy(driver
, screen_drivers
[type
], sizeof(*driver
) - 1);
275 memcpy(driver
->name
, term
->spec
->name
, env_len
+ 1);
277 add_to_list(active_screen_drivers
, driver
);
279 update_screen_driver(driver
, term
->spec
);
281 term
->spec
->change_hook
= screen_driver_change_hook
;
286 static inline struct screen_driver
*
287 get_screen_driver(struct terminal
*term
)
289 enum term_mode_type type
= get_opt_int_tree(term
->spec
, "type");
290 unsigned char *name
= term
->spec
->name
;
291 int len
= strlen(name
);
292 struct screen_driver
*driver
;
294 foreach (driver
, active_screen_drivers
) {
295 if (driver
->type
!= type
) continue;
296 if (strcmp(driver
->name
, name
)) continue;
298 /* Some simple probably useless MRU ;) */
299 move_to_top_of_list(active_screen_drivers
, driver
);
304 return add_screen_driver(type
, term
, len
);
308 done_screen_drivers(void)
310 free_list(active_screen_drivers
);
314 /* Adds the term code for positioning the cursor at @x and @y to @string.
315 * The template term code is: "\033[<@y>;<@x>H" */
316 static inline struct string
*
317 add_cursor_move_to_string(struct string
*screen
, int y
, int x
)
319 #define CURSOR_NUM_LEN 10 /* 10 chars for @y and @x numbers should be more than enough. */
320 unsigned char code
[4 + 2 * CURSOR_NUM_LEN
+ 1];
321 unsigned int length
= 2;
326 if (ulongcat(code
, &length
, y
, CURSOR_NUM_LEN
, 0) < 0)
329 code
[length
++] = ';';
331 if (ulongcat(code
, &length
, x
, CURSOR_NUM_LEN
, 0) < 0)
334 code
[length
++] = 'H';
336 return add_bytes_to_string(screen
, code
, length
);
337 #undef CURSOR_NUM_LEN
340 struct screen_state
{
341 unsigned char border
;
342 unsigned char underline
;
345 /* Following should match struct screen_char color field. */
346 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
347 unsigned char color
[2];
349 unsigned char color
[1];
353 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
354 #define compare_color(a, b) ((a)[0] == (b)[0] && (a)[1] == (b)[1])
355 #define copy_color(a, b) do { (a)[0] = (b)[0]; (a)[1] = (b)[1]; } while (0)
356 #define INIT_SCREEN_STATE { 0xFF, 0xFF, 0xFF, 0, { 0xFF, 0xFF } }
358 #define compare_color(a, b) ((a)[0] == (b)[0])
359 #define copy_color(a, b) do { (a)[0] = (b)[0]; } while (0)
360 #define INIT_SCREEN_STATE { 0xFF, 0xFF, 0xFF, 0, { 0xFF } }
363 #define compare_bg_color(a, b) (TERM_COLOR_BACKGROUND(a) == TERM_COLOR_BACKGROUND(b))
364 #define compare_fg_color(a, b) (TERM_COLOR_FOREGROUND(a) == TERM_COLOR_FOREGROUND(b))
366 #define use_utf8_io(driver) ((driver)->charsets[0] != -1)
369 add_char_data(struct string
*screen
, struct screen_driver
*driver
,
370 unsigned char data
, unsigned char border
)
372 if (!isscreensafe(data
)) {
373 add_char_to_string(screen
, ' ');
377 if (border
&& driver
->frame
&& data
>= 176 && data
< 224)
378 data
= driver
->frame
[data
- 176];
380 if (use_utf8_io(driver
)) {
381 int charset
= driver
->charsets
[!!border
];
383 add_to_string(screen
, cp2utf_8(charset
, data
));
387 add_char_to_string(screen
, data
);
390 /* Time critical section. */
392 add_char16(struct string
*screen
, struct screen_driver
*driver
,
393 struct screen_char
*ch
, struct screen_state
*state
)
395 unsigned char border
= (ch
->attr
& SCREEN_ATTR_FRAME
);
396 unsigned char underline
= (ch
->attr
& SCREEN_ATTR_UNDERLINE
);
397 unsigned char bold
= (ch
->attr
& SCREEN_ATTR_BOLD
);
399 if (border
!= state
->border
&& driver
->frame_seqs
) {
400 state
->border
= border
;
401 add_term_string(screen
, driver
->frame_seqs
[!!border
]);
404 if (underline
!= state
->underline
&& driver
->underline
) {
405 state
->underline
= underline
;
406 add_term_string(screen
, driver
->underline
[!!underline
]);
409 if (bold
!= state
->bold
) {
412 add_bytes_to_string(screen
, "\033[1m", 4);
414 /* Force repainting of the other attributes. */
415 state
->color
[0] = ch
->color
[0] + 1;
419 if (!compare_color(ch
->color
, state
->color
)) {
420 copy_color(state
->color
, ch
->color
);
422 add_bytes_to_string(screen
, "\033[0", 3);
424 if (driver
->color_mode
== COLOR_MODE_16
) {
425 unsigned char code
[6] = ";30;40";
426 unsigned char bgcolor
= TERM_COLOR_BACKGROUND(ch
->color
);
428 code
[2] += TERM_COLOR_FOREGROUND(ch
->color
);
430 if (!driver
->transparent
|| bgcolor
!= 0) {
432 add_bytes_to_string(screen
, code
, 6);
434 add_bytes_to_string(screen
, code
, 3);
437 } else if (ch
->attr
& SCREEN_ATTR_STANDOUT
) {
438 /* Flip the fore- and background colors for highlighing
440 add_bytes_to_string(screen
, ";7", 2);
443 if (underline
&& driver
->underline
) {
444 add_bytes_to_string(screen
, ";4", 2);
447 /* Check if the char should be rendered bold. */
449 add_bytes_to_string(screen
, ";1", 2);
452 add_bytes_to_string(screen
, "m", 1);
455 add_char_data(screen
, driver
, ch
->data
, border
);
458 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
459 static struct string color256_seqs
[] = {
460 /* foreground: */ TERM_STRING("\033[0;38;5;%dm"),
461 /* background: */ TERM_STRING("\033[48;5;%dm"),
465 add_char_color(struct string
*screen
, struct string
*seq
, unsigned char color
)
467 unsigned char color_buf
[3];
468 unsigned char *color_pos
= color_buf
;
472 check_string_magic(seq
);
473 for (; seq
->source
[seq_pos
] != '%'; seq_pos
++) ;
475 add_bytes_to_string(screen
, seq
->source
, seq_pos
);
497 color2
= (color
% 10);
499 color_buf
[1] = '0' + color
;
503 color_buf
[2] = '0' + color
;
505 add_bytes_to_string(screen
, color_pos
, color_len
);
507 seq_pos
+= 2; /* Skip "%d" */
508 add_bytes_to_string(screen
, &seq
->source
[seq_pos
], seq
->length
- seq_pos
);
511 #define add_background_color(str, seq, chr) add_char_color(str, &(seq)[1], (chr)->color[1])
512 #define add_foreground_color(str, seq, chr) add_char_color(str, &(seq)[0], (chr)->color[0])
514 /* Time critical section. */
516 add_char256(struct string
*screen
, struct screen_driver
*driver
,
517 struct screen_char
*ch
, struct screen_state
*state
)
519 unsigned char attr_delta
= (ch
->attr
^ state
->attr
);
522 if ((attr_delta
& SCREEN_ATTR_FRAME
) && driver
->frame_seqs
) {
523 state
->border
= !!(ch
->attr
& SCREEN_ATTR_FRAME
);
524 add_term_string(screen
, driver
->frame_seqs
[state
->border
]);
527 if ((attr_delta
& SCREEN_ATTR_UNDERLINE
) && driver
->underline
) {
528 state
->underline
= !!(ch
->attr
& SCREEN_ATTR_UNDERLINE
);
529 add_term_string(screen
, driver
->underline
[state
->underline
]);
532 if (attr_delta
& SCREEN_ATTR_BOLD
) {
533 if (ch
->attr
& SCREEN_ATTR_BOLD
) {
534 add_bytes_to_string(screen
, "\033[1m", 4);
536 /* Force repainting of the other attributes. */
537 state
->color
[0] = ch
->color
[0] + 1;
541 state
->attr
= ch
->attr
;
544 if (!compare_color(ch
->color
, state
->color
)) {
545 copy_color(state
->color
, ch
->color
);
547 add_foreground_color(screen
, color256_seqs
, ch
);
548 if (!driver
->transparent
|| ch
->color
[1] != 0) {
549 add_background_color(screen
, color256_seqs
, ch
);
552 if (ch
->attr
& SCREEN_ATTR_BOLD
)
553 add_bytes_to_string(screen
, "\033[1m", 4);
555 if (ch
->attr
& SCREEN_ATTR_UNDERLINE
&& driver
->underline
) {
556 state
->underline
= !!(ch
->attr
& SCREEN_ATTR_UNDERLINE
);
557 add_term_string(screen
, driver
->underline
[state
->underline
]);
561 add_char_data(screen
, driver
, ch
->data
, ch
->attr
& SCREEN_ATTR_FRAME
);
565 #define add_chars(image_, term_, driver_, state_, ADD_CHAR) \
567 struct terminal_screen *screen = (term_)->screen; \
568 int y = screen->dirty_from; \
569 int ypos = y * (term_)->width; \
571 int xmax = (term_)->width - 1; \
572 int ymax = (term_)->height - 1; \
573 struct screen_char *current = &screen->last_image[ypos]; \
574 struct screen_char *pos = &screen->image[ypos]; \
575 struct screen_char *prev_pos = NULL; /* Warning prevention. */ \
577 int_upper_bound(&screen->dirty_to, ymax); \
579 for (; y <= screen->dirty_to; y++) { \
580 int is_last_line = (y == ymax); \
583 for (; x <= xmax; x++, current++, pos++) { \
584 /* Workaround for terminals without
585 * "eat_newline_glitch (xn)", e.g., the cons25 family
586 * of terminals and cygwin terminal.
587 * It prevents display distortion, but char at bottom
588 * right of terminal will not be drawn.
589 * A better fix would be to correctly detects
590 * terminal type, and/or add a terminal option for
593 if (is_last_line && x == xmax) \
596 if (compare_bg_color(pos->color, current->color)) { \
597 /* No update for exact match. */ \
598 if (compare_fg_color(pos->color, current->color)\
599 && pos->data == current->data \
600 && pos->attr == current->attr) \
603 /* Else if the color match and the data is
605 if (pos->data <= ' ' && current->data <= ' ' \
606 && pos->attr == current->attr) \
610 /* Move the cursor when @prev_pos is more than 10 chars
612 if (prev_y != y || prev_pos + 10 <= pos) { \
613 add_cursor_move_to_string(image_, y + 1, x + 1);\
618 for (; prev_pos <= pos ; prev_pos++) \
619 ADD_CHAR(image_, driver_, prev_pos, state_); \
624 /* Updating of the terminal screen is done by checking what needs to be updated
625 * using the last screen. */
627 redraw_screen(struct terminal
*term
)
629 struct screen_driver
*driver
;
631 struct screen_state state
= INIT_SCREEN_STATE
;
632 struct terminal_screen
*screen
= term
->screen
;
634 if (!screen
|| screen
->dirty_from
> screen
->dirty_to
) return;
635 if (term
->master
&& is_blocked()) return;
637 driver
= get_screen_driver(term
);
640 if (!init_string(&image
)) return;
642 switch (driver
->color_mode
) {
643 case COLOR_MODE_MONO
:
645 add_chars(&image
, term
, driver
, &state
, add_char16
);
647 #ifdef CONFIG_88_COLORS
649 add_chars(&image
, term
, driver
, &state
, add_char256
);
652 #ifdef CONFIG_256_COLORS
654 add_chars(&image
, term
, driver
, &state
, add_char256
);
658 case COLOR_MODE_DUMP
:
660 INTERNAL("Invalid color mode (%d).", driver
->color_mode
);
665 if (driver
->color_mode
)
666 add_bytes_to_string(&image
, "\033[37;40m", 8);
668 add_bytes_to_string(&image
, "\033[0m", 4);
670 /* If we ended in border state end the frame mode. */
671 if (state
.border
&& driver
->frame_seqs
)
672 add_term_string(&image
, driver
->frame_seqs
[0]);
676 /* Even if nothing was redrawn, we possibly still need to move
679 || screen
->cx
!= screen
->lcx
680 || screen
->cy
!= screen
->lcy
) {
681 screen
->lcx
= screen
->cx
;
682 screen
->lcy
= screen
->cy
;
684 add_cursor_move_to_string(&image
, screen
->cy
+ 1,
689 if (term
->master
) want_draw();
690 hard_write(term
->fdout
, image
.source
, image
.length
);
691 if (term
->master
) done_draw();
696 copy_screen_chars(screen
->last_image
, screen
->image
, term
->width
* term
->height
);
697 screen
->dirty_from
= term
->height
;
698 screen
->dirty_to
= 0;
702 erase_screen(struct terminal
*term
)
705 if (is_blocked()) return;
709 hard_write(term
->fdout
, "\033[2J\033[1;1H", 10);
710 if (term
->master
) done_draw();
714 beep_terminal(struct terminal
*term
)
717 MessageBeep(MB_ICONEXCLAMATION
);
719 hard_write(term
->fdout
, "\a", 1);
723 struct terminal_screen
*
726 struct terminal_screen
*screen
;
728 screen
= mem_calloc(1, sizeof(*screen
));
729 if (!screen
) return NULL
;
737 /* The two images are allocated in one chunk. */
738 /* TODO: It seems allocation failure here is fatal. We should do something! */
740 resize_screen(struct terminal
*term
, int width
, int height
)
742 struct terminal_screen
*screen
;
743 struct screen_char
*image
;
746 assert(term
&& term
->screen
);
748 screen
= term
->screen
;
753 size
= width
* height
;
754 if (size
<= 0) return;
756 bsize
= size
* sizeof(*image
);
758 image
= mem_realloc(screen
->image
, bsize
* 2);
761 screen
->image
= image
;
762 screen
->last_image
= image
+ size
;
764 memset(screen
->image
, 0, bsize
);
765 memset(screen
->last_image
, 0xFF, bsize
);
768 term
->height
= height
;
769 set_screen_dirty(screen
, 0, height
);
773 done_screen(struct terminal_screen
*screen
)
775 mem_free_if(screen
->image
);