vt100: do not crash if termkey is not yet initialized
[vis.git] / ui-terminal-vt100.c
blob8ba8a1a59daf272a16dcde1bf5d654e68c660a7b
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)
20 * - CSI 0 m Normal
21 * - CSI 1 m Bold
22 * - CSI 3 m Italicized
23 * - CSI 4 m Underlined
24 * - CSI 5 m Blink
25 * - CSI 7 m Inverse
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.
39 #include <stdio.h>
40 #include "buffer.h"
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)
73 typedef struct {
74 UiTerm uiterm;
75 Buffer buf;
76 } UiVt100;
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);
89 fflush(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;
106 buffer_clear(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 {
119 CellAttr attr;
120 char on[4], off[4];
121 } cell_attrs[] = {
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))
132 continue;
133 buffer_appendf(buf, "\x1b[%sm",
134 style->attr & a ?
135 cell_attrs[i].on :
136 cell_attrs[i].off);
139 attr = style->attr;
142 if (!cell_color_equal(fg, style->fg)) {
143 fg = style->fg;
144 if (fg.index != (uint8_t)-1) {
145 buffer_appendf(buf, "\x1b[%dm", 30 + fg.index);
146 } else {
147 buffer_appendf(buf, "\x1b[38;2;%d;%d;%dm",
148 fg.r, fg.g, fg.b);
152 if (!cell_color_equal(bg, style->bg)) {
153 bg = style->bg;
154 if (bg.index != (uint8_t)-1) {
155 buffer_appendf(buf, "\x1b[%dm", 40 + bg.index);
156 } else {
157 buffer_appendf(buf, "\x1b[48;2;%d;%d;%dm",
158 bg.r, bg.g, bg.b);
162 buffer_append0(buf, cell->data);
163 cell++;
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) {
172 return true;
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);
203 return true;
206 static UiTerm *ui_vt100_new(void) {
207 UiVt100 *vtui = calloc(1, sizeof *vtui);
208 if (!vtui)
209 return NULL;
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;