1 /* This file is included from ui-terminal.c
3 * The goal is *not* to reimplement curses. Instead we aim to provide the
4 * simplest possible drawing backend for VT-100 compatible terminals.
5 * This is useful for debugging and fuzzing purposes as well as for environments
6 * with no curses support.
8 * Currently no attempt is made to optimize terminal output. The amount of
9 * flickering will depend on the smartness of your terminal emulator.
11 * The following terminal escape sequences are used:
13 * - CSI ? 1049 h Save cursor and use Alternate Screen Buffer (DECSET)
14 * - CSI ? 1049 l Use Normal Screen Buffer and restore cursor (DECRST)
15 * - CSI ? 25 l Hide Cursor (DECTCEM)
16 * - CSI ? 25 h Show Cursor (DECTCEM)
17 * - CSI 2 J Erase in Display (ED)
18 * - CSI row ; column H Cursor Position (CUP)
19 * - CSI ... m Character Attributes (SGR)
22 * - CSI 3 m Italicized
23 * - CSI 4 m Underlined
26 * - CSI 22 m Normal (not bold)
27 * - CSI 23 m Not italicized
28 * - CSI 24 m Not underlined
29 * - CSI 25 m Not blinking
30 * - CSI 27 m Not inverse
31 * - CSI 30-37,39 Set foreground color
32 * - CSI 38 ; 2 ; R ; G ; B m Set RGB foreground color
33 * - CSI 40-47,49 Set background color
34 * - CSI 48 ; 2 ; R ; G ; B m Set RGB background color
36 * See http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
37 * for further information.
42 #define UI_TERMKEY_FLAGS TERMKEY_FLAG_UTF8
44 #define ui_term_backend_init ui_vt100_init
45 #define ui_term_backend_blit ui_vt100_blit
46 #define ui_term_backend_clear ui_vt100_clear
47 #define ui_term_backend_colors ui_vt100_colors
48 #define ui_term_backend_resize ui_vt100_resize
49 #define ui_term_backend_save ui_vt100_save
50 #define ui_term_backend_restore ui_vt100_restore
51 #define ui_term_backend_suspend ui_vt100_suspend
52 #define ui_term_backend_resume ui_vt100_resume
53 #define ui_term_backend_new ui_vt100_new
54 #define ui_term_backend_free ui_vt100_free
56 #define CELL_COLOR_BLACK { .index = 0 }
57 #define CELL_COLOR_RED { .index = 1 }
58 #define CELL_COLOR_GREEN { .index = 2 }
59 #define CELL_COLOR_YELLOW { .index = 3 }
60 #define CELL_COLOR_BLUE { .index = 4 }
61 #define CELL_COLOR_MAGENTA { .index = 5 }
62 #define CELL_COLOR_CYAN { .index = 6 }
63 #define CELL_COLOR_WHITE { .index = 7 }
64 #define CELL_COLOR_DEFAULT { .index = 9 }
66 #define CELL_ATTR_NORMAL 0
67 #define CELL_ATTR_UNDERLINE (1 << 0)
68 #define CELL_ATTR_REVERSE (1 << 1)
69 #define CELL_ATTR_BLINK (1 << 2)
70 #define CELL_ATTR_BOLD (1 << 3)
71 #define CELL_ATTR_ITALIC (1 << 4)
78 static CellColor
color_rgb(UiTerm
*ui
, uint8_t r
, uint8_t g
, uint8_t b
) {
79 return (CellColor
){ .r
= r
, .g
= g
, .b
= b
, .index
= (uint8_t)-1 };
82 static CellColor
color_terminal(UiTerm
*ui
, uint8_t index
) {
83 return (CellColor
){ .r
= 0, .g
= 0, .b
= 0, .index
= index
};
87 static void output(const char *data
, size_t len
) {
88 fwrite(data
, len
, 1, stderr
);
92 static void output_literal(const char *data
) {
93 output(data
, strlen(data
));
96 static void screen_alternate(bool alternate
) {
97 output_literal(alternate
? "\x1b[?1049h" : "\x1b[0m" "\x1b[?1049l" "\x1b[0m" );
100 static void cursor_visible(bool visible
) {
101 output_literal(visible
? "\x1b[?25h" : "\x1b[?25l");
104 static void ui_vt100_blit(UiTerm
*tui
) {
105 Buffer
*buf
= &((UiVt100
*)tui
)->buf
;
107 CellAttr attr
= CELL_ATTR_NORMAL
;
108 CellColor fg
= CELL_COLOR_DEFAULT
, bg
= CELL_COLOR_DEFAULT
;
109 int w
= tui
->width
, h
= tui
->height
;
110 Cell
*cell
= tui
->cells
;
111 /* reposition cursor, erase screen, reset attributes */
112 buffer_append0(buf
, "\x1b[H" "\x1b[J" "\x1b[0m");
113 for (int y
= 0; y
< h
; y
++) {
114 for (int x
= 0; x
< w
; x
++) {
115 CellStyle
*style
= &cell
->style
;
116 if (style
->attr
!= attr
) {
118 static const struct {
122 { CELL_ATTR_BOLD
, "1", "22" },
123 { CELL_ATTR_ITALIC
, "3", "23" },
124 { CELL_ATTR_UNDERLINE
, "4", "24" },
125 { CELL_ATTR_BLINK
, "5", "25" },
126 { CELL_ATTR_REVERSE
, "7", "27" },
129 for (size_t i
= 0; i
< LENGTH(cell_attrs
); i
++) {
130 CellAttr a
= cell_attrs
[i
].attr
;
131 if ((style
->attr
& a
) == (attr
& a
))
133 buffer_appendf(buf
, "\x1b[%sm",
142 if (!cell_color_equal(fg
, style
->fg
)) {
144 if (fg
.index
!= (uint8_t)-1) {
145 buffer_appendf(buf
, "\x1b[%dm", 30 + fg
.index
);
147 buffer_appendf(buf
, "\x1b[38;2;%d;%d;%dm",
152 if (!cell_color_equal(bg
, style
->bg
)) {
154 if (bg
.index
!= (uint8_t)-1) {
155 buffer_appendf(buf
, "\x1b[%dm", 40 + bg
.index
);
157 buffer_appendf(buf
, "\x1b[48;2;%d;%d;%dm",
162 buffer_append0(buf
, cell
->data
);
166 output(buffer_content(buf
), buffer_length0(buf
));
169 static void ui_vt100_clear(UiTerm
*tui
) { }
171 static bool ui_vt100_resize(UiTerm
*tui
, int width
, int height
) {
175 static void ui_vt100_save(UiTerm
*tui
) {
176 cursor_visible(true);
179 static void ui_vt100_restore(UiTerm
*tui
) {
180 cursor_visible(false);
183 static int ui_vt100_colors(Ui
*ui
) {
184 char *term
= getenv("TERM");
185 return (term
&& strstr(term
, "-256color")) ? 256 : 16;
188 static void ui_vt100_suspend(UiTerm
*tui
) {
189 if (!tui
->termkey
) return;
190 termkey_stop(tui
->termkey
);
191 cursor_visible(true);
192 screen_alternate(false);
195 static void ui_vt100_resume(UiTerm
*tui
) {
196 screen_alternate(true);
197 cursor_visible(false);
198 termkey_start(tui
->termkey
);
201 static bool ui_vt100_init(UiTerm
*tui
, char *term
) {
202 ui_vt100_resume(tui
);
206 static UiTerm
*ui_vt100_new(void) {
207 UiVt100
*vtui
= calloc(1, sizeof *vtui
);
210 buffer_init(&vtui
->buf
);
211 return (UiTerm
*)vtui
;
214 static void ui_vt100_free(UiTerm
*tui
) {
215 UiVt100
*vtui
= (UiVt100
*)tui
;
216 ui_vt100_suspend(tui
);
217 buffer_release(&vtui
->buf
);
220 bool is_default_color(CellColor c
) {
221 return c
.index
== ((CellColor
) CELL_COLOR_DEFAULT
).index
;