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_term_backend_init ui_vt100_init
43 #define ui_term_backend_blit ui_vt100_blit
44 #define ui_term_backend_clear ui_vt100_clear
45 #define ui_term_backend_colors ui_vt100_colors
46 #define ui_term_backend_resize ui_vt100_resize
47 #define ui_term_backend_save ui_vt100_save
48 #define ui_term_backend_restore ui_vt100_restore
49 #define ui_term_backend_suspend ui_vt100_suspend
50 #define ui_term_backend_resume ui_vt100_resume
51 #define ui_term_backend_new ui_vt100_new
52 #define ui_term_backend_free ui_vt100_free
54 #define CELL_COLOR_BLACK (CellColor){ .index = 0 }
55 #define CELL_COLOR_RED (CellColor){ .index = 1 }
56 #define CELL_COLOR_GREEN (CellColor){ .index = 2 }
57 #define CELL_COLOR_YELLOW (CellColor){ .index = 3 }
58 #define CELL_COLOR_BLUE (CellColor){ .index = 4 }
59 #define CELL_COLOR_MAGENTA (CellColor){ .index = 5 }
60 #define CELL_COLOR_CYAN (CellColor){ .index = 6 }
61 #define CELL_COLOR_WHITE (CellColor){ .index = 7 }
62 #define CELL_COLOR_DEFAULT (CellColor){ .index = 9 }
64 #define CELL_ATTR_NORMAL 0
65 #define CELL_ATTR_UNDERLINE (1 << 0)
66 #define CELL_ATTR_REVERSE (1 << 1)
67 #define CELL_ATTR_BLINK (1 << 2)
68 #define CELL_ATTR_BOLD (1 << 3)
69 #define CELL_ATTR_ITALIC (1 << 4)
76 static CellColor
color_rgb(UiTerm
*ui
, uint8_t r
, uint8_t g
, uint8_t b
) {
77 return (CellColor
){ .r
= r
, .g
= g
, .b
= b
, .index
= (uint8_t)-1 };
80 static CellColor
color_terminal(UiTerm
*ui
, uint8_t index
) {
81 return (CellColor
){ .r
= 0, .g
= 0, .b
= 0, .index
= index
};
85 static void output(const char *data
, size_t len
) {
86 fwrite(data
, len
, 1, stderr
);
90 static void output_literal(const char *data
) {
91 output(data
, strlen(data
));
94 static void screen_alternate(bool alternate
) {
95 output_literal(alternate
? "\x1b[?1049h" : "\x1b[0m" "\x1b[?1049l");
98 static void cursor_visible(bool visible
) {
99 output_literal(visible
? "\x1b[?25h" : "\x1b[?25l");
102 static void ui_vt100_blit(UiTerm
*tui
) {
103 Buffer
*buf
= &((UiVt100
*)tui
)->buf
;
105 CellAttr attr
= CELL_ATTR_NORMAL
;
106 CellColor fg
= CELL_COLOR_DEFAULT
, bg
= CELL_COLOR_DEFAULT
;
107 int w
= tui
->width
, h
= tui
->height
;
108 Cell
*cell
= tui
->cells
;
109 /* erase screen, reposition cursor, reset attributes */
110 buffer_append0(buf
, "\x1b[2J" "\x1b[H" "\x1b[0m");
111 for (int y
= 0; y
< h
; y
++) {
112 for (int x
= 0; x
< w
; x
++) {
113 CellStyle
*style
= &cell
->style
;
114 if (style
->attr
!= attr
) {
120 { CELL_ATTR_BOLD
, "1", "22" },
121 { CELL_ATTR_ITALIC
, "3", "23" },
122 { CELL_ATTR_UNDERLINE
, "4", "24" },
123 { CELL_ATTR_BLINK
, "5", "25" },
124 { CELL_ATTR_REVERSE
, "7", "27" },
127 for (size_t i
= 0; i
< LENGTH(cell_attrs
); i
++) {
128 CellAttr a
= cell_attrs
[i
].attr
;
129 if ((style
->attr
& a
) == (attr
& a
))
131 buffer_appendf(buf
, "\x1b[%sm",
140 if (!cell_color_equal(fg
, style
->fg
)) {
142 if (fg
.index
!= (uint8_t)-1) {
143 buffer_appendf(buf
, "\x1b[%dm", 30 + fg
.index
);
145 buffer_appendf(buf
, "\x1b[38;2;%d;%d;%dm",
150 if (!cell_color_equal(bg
, style
->bg
)) {
152 if (bg
.index
!= (uint8_t)-1) {
153 buffer_appendf(buf
, "\x1b[%dm", 40 + bg
.index
);
155 buffer_appendf(buf
, "\x1b[48;2;%d;%d;%dm",
160 buffer_append0(buf
, cell
->data
);
164 output(buffer_content(buf
), buffer_length0(buf
));
167 static void ui_vt100_clear(UiTerm
*tui
) { }
169 static bool ui_vt100_resize(UiTerm
*tui
, int width
, int height
) {
173 static void ui_vt100_save(UiTerm
*tui
) {
174 cursor_visible(true);
177 static void ui_vt100_restore(UiTerm
*tui
) {
178 cursor_visible(false);
181 static int ui_vt100_colors(Ui
*ui
) {
182 char *term
= getenv("TERM");
183 return (term
&& strstr(term
, "-256color")) ? 256 : 16;
186 static void ui_vt100_suspend(UiTerm
*term
) {
187 cursor_visible(true);
188 screen_alternate(false);
191 static void ui_vt100_resume(UiTerm
*term
) {
192 screen_alternate(true);
193 cursor_visible(false);
196 static bool ui_vt100_init(UiTerm
*tui
, char *term
) {
197 ui_vt100_resume(tui
);
201 static UiTerm
*ui_vt100_new(void) {
202 UiVt100
*vtui
= calloc(1, sizeof *vtui
);
205 buffer_init(&vtui
->buf
);
206 return (UiTerm
*)vtui
;
209 static void ui_vt100_free(UiTerm
*tui
) {
210 UiVt100
*vtui
= (UiVt100
*)tui
;
211 ui_vt100_suspend(tui
);
212 buffer_release(&vtui
->buf
);