vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: afeabe75acbd1a6d5284e085b051930d9fc129f5
Parent: cfe9a297ff93b377d8a78f04f36587d2367d15d2
Author: Randy Palamar
Date:   Sun, 23 Jun 2024 17:41:12 -0600

start parsing colour escapes

Diffstat:
Mmain.c | 7+++++--
Mterminal.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mutil.h | 14+++++++++++++-
Mvtgl.c | 57++++++++++++++++++++++++++++++++++++++++++++++-----------
4 files changed, 207 insertions(+), 17 deletions(-)

diff --git a/main.c b/main.c @@ -233,7 +233,7 @@ check_shaders(GLCtx *gl, Arena a) } static void -line_buf_alloc(LineBuf *lb, Arena *a, u8 *start_position, size capacity) +line_buf_alloc(LineBuf *lb, Arena *a, u8 *start_position, CursorState state, size capacity) { lb->cap = capacity; lb->filled = 0; @@ -241,6 +241,7 @@ line_buf_alloc(LineBuf *lb, Arena *a, u8 *start_position, size capacity) lb->buf = alloc(a, Line, capacity); lb->buf[0].start = start_position; lb->buf[0].end = start_position; + lb->buf[0].cursor_state = state; } i32 @@ -255,8 +256,10 @@ main(void) init_window(&term); init_fonts(&term, font_paths, ARRAY_COUNT(font_paths), 48, &memory); + cursor_reset(&term); + os_alloc_ring_buffer(&term.log, BACKLOG_SIZE); - line_buf_alloc(&term.log_lines, &memory, term.log.buf, BACKLOG_LINES); + line_buf_alloc(&term.log_lines, &memory, term.log.buf, term.cursor.state, BACKLOG_LINES); term.child = os_fork_child("/bin/sh"); diff --git a/terminal.c b/terminal.c @@ -1,5 +1,15 @@ #include <immintrin.h> +#define ESC_ARG_SIZ 6 + +typedef struct { + s8 raw; + i32 argv[ESC_ARG_SIZ]; + i32 argc; + u8 mode; + u8 priv; +} CSI; + static const u8 utf8overhangmask[32] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, @@ -7,6 +17,17 @@ static const u8 utf8overhangmask[32] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +static v2 +get_cell_size(Term *t) +{ + /* TODO: Make padding configurable */ + v2 result = { + .w = t->fa.size.w + 0, + .h = t->fa.size.h + 8, + }; + return result; +} + static s8 consume(s8 raw, size count) { @@ -51,16 +72,135 @@ feed_line(LineBuf *lb, u8 *position, CursorState cursor_state) lb->buf[lb->widx].cursor_state = cursor_state; } +static void +cursor_reset(Term *t) +{ + t->cursor.state.fg = (Colour){.rgba = 0x1e9e33ff}; + t->cursor.state.bg = (Colour){.r = 20, .g = 20, .b = 20, .a = 0xff}; + t->cursor.state.attr = ATTR_NULL; +} + +static void +dump_csi(CSI *csi) +{ + fputs("raw: ESC[", stderr); + for (size i = 0; i < csi->raw.len; i++) { + u8 c = csi->raw.data[i]; + if (ISPRINT(c)) + fputc(c, stderr); + else if (c == '\n') + fputs("(\\n)", stderr); + else if (c == '\r') + fputs("(\\r)", stderr); + else + fprintf(stderr, "(0x%02X)", c); + } + fprintf(stderr, "\n\tparsed = { .priv = %d, .mode = ", csi->priv); + if (ISPRINT(csi->mode)) + fputc(csi->mode, stderr); + else + fprintf(stderr, "(0x%02X)", csi->mode); + fprintf(stderr, ", .argc = %d, .argv = {", csi->argc); + for (i32 i = 0; i < csi->argc; i++) + fprintf(stderr, " %d", csi->argv[i]); + fputs(" } }\n", stderr); +} + +/* SGR: Select Graphic Rendition */ +static void +set_colours(Term *t, CSI *csi) +{ + CursorState *cs = &t->cursor.state; + for (i32 i = 0; i < csi->argc; i++) { + switch (csi->argv[i]) { + case 0: cursor_reset(t); break; + case 1: cs->attr |= ATTR_BOLD; break; + case 2: cs->attr |= ATTR_FAINT; break; + case 3: cs->attr |= ATTR_ITALIC; break; + case 4: cs->attr |= ATTR_UNDERLINED; break; + case 5: cs->attr |= ATTR_BLINK; break; + case 7: cs->attr |= ATTR_INVERSE; break; + case 8: cs->attr |= ATTR_INVISIBLE; break; + case 9: cs->attr |= ATTR_STRUCK; break; + default: + fprintf(stderr, "unhandled colour arg: %d\n", csi->argv[i]); + dump_csi(csi); + } + } +} + +static CSI +parse_csi(s8 *r) +{ + CSI csi = {0}; + csi.raw.data = r->data; + + if (peek(*r, 0) == '?') { + csi.priv = 1; + csi.raw.len++; + get_ascii(r); + } + + while (r->len) { + u32 cp = get_ascii(r); + csi.raw.len++; + if (BETWEEN(cp, '0', '9')) { + /* 123 */ + csi.argv[csi.argc] *= 10; + csi.argv[csi.argc] += cp - '0'; + continue; + } + csi.argc++; + if (cp != ';' || csi.argc == ESC_ARG_SIZ) { + if (cp == ';') { + csi.mode = get_ascii(r); + csi.raw.len++; + } else { + csi.mode = cp; + } + return csi; + } + } + /* TODO: error case: needs more chars! */ + return csi; +} + +static void +handle_csi(Term *t, s8 *raw) +{ + CSI csi = parse_csi(raw); + + switch (csi.mode) { + case 'm': set_colours(t, &csi); break; + default: + fputs("unknown csi: ", stderr); + dump_csi(&csi); + } +} + enum handle_escape_return { HESC_NORMAL_RETURN, HESC_NEEDS_MORE_BYTES, HESC_CURSOR_MOVED, }; static enum handle_escape_return -handle_escape(s8 *raw) +handle_escape(Term *t, s8 *raw) { enum handle_escape_return result = HESC_NORMAL_RETURN; - + u32 cp = get_ascii(raw); + switch(cp) { + case '[': handle_csi(t, raw); break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + case '%': /* utf-8 mode */ + get_ascii(raw); + break; + default: + //fprintf(stderr, "unknown escape sequence: ESC %c (0x%02x)\n", cp, cp); + break; + } return result; } @@ -106,7 +246,7 @@ split_raw_input_to_lines(Term *t, s8 raw) if (peek(raw, 0) == 0x1B) { s8 old = raw; raw = consume(raw, 1); - switch (handle_escape(&raw)) { + switch (handle_escape(t, &raw)) { case HESC_NEEDS_MORE_BYTES: t->unprocessed_bytes = old.len; return parsed_lines; diff --git a/util.h b/util.h @@ -25,6 +25,8 @@ #define MAX(a, b) ((a) >= (b) ? (a) : (b)) #define CLAMP(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)) +#define ISPRINT(c) BETWEEN((c), ' ', '~') + typedef float f32; typedef double f64; typedef uint8_t u8; @@ -47,6 +49,7 @@ typedef union { typedef union { struct { f32 x, y; }; + struct { f32 w, h; }; f32 E[2]; } v2; @@ -70,7 +73,15 @@ typedef struct { size len; u8 *data; } s8; #define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s} enum CellAttr { - ATTR_NULL = 0, + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_ITALIC = 1 << 1, + ATTR_FAINT = 1 << 2, + ATTR_UNDERLINED = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_INVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, }; typedef struct { @@ -115,6 +126,7 @@ typedef struct { enum gl_flags { NEEDS_RESIZE = 1 << 0, + NEEDS_BLIT = 1 << 1, UPDATE_UNIFORMS = 1 << 30, }; diff --git a/vtgl.c b/vtgl.c @@ -141,16 +141,17 @@ draw_rectangle(GLCtx *gl, Rect r, Colour colour) } static void -draw_cell(GLCtx *gl, u32 cp, Rect r, u32 bg, u32 fg) +push_cell(Term *t, u32 cp, Rect r, u32 bg, u32 fg) { + GLCtx *gl = &t->gl; Glyph g; i32 depth_idx = get_gpu_glyph_index(gl, cp, &g); v2 texscale[2]; texscale[0] = (v2){0}; texscale[1] = (v2){ - .x = g.size.w / 128.0f, - .y = g.size.h / 128.0f, + .x = g.size.w / MAX_FONT_SIZE, + .y = g.size.h / MAX_FONT_SIZE, }; @@ -164,8 +165,8 @@ draw_cell(GLCtx *gl, u32 cp, Rect r, u32 bg, u32 fg) vertoff[0] = r.pos; v2 texture_position; - texture_position.x = (r.size.x - g.size.w) * 0.5 + r.pos.x;// + g.delta.x; - texture_position.y = (r.size.y - g.size.h) * 0.5 + r.pos.y;// + g.delta.y + t->fa.deltay; + texture_position.x = r.pos.x + g.delta.x; + texture_position.y = r.pos.y + g.delta.y + t->fa.deltay; vertscale[1] = (v2){.x = g.size.w, .y = g.size.h}; vertoff[1] = texture_position; @@ -194,19 +195,53 @@ line_to_s8(Line *l) } static void +push_line(Term *t, Line *line, v2 start_pos) +{ + s8 l = line_to_s8(line); + + t->cursor.state = line->cursor_state; + + v2 cs = get_cell_size(t); + Rect cr = {.pos = start_pos, .size = cs}; + while (l.len) { + /* TODO: handle unicode case */ + u32 cp = get_ascii(&l); + switch (cp) { + case 0x1B: handle_escape(t, &l); break; + default: + /* TODO properly make characters are printable */ + CLAMP(cp, ' ', '~'); + push_cell(t, cp, cr, t->cursor.state.bg.rgba, t->cursor.state.fg.rgba); + cr.pos.w += cs.w; + /* TODO: properly advance cursor */ + t->cursor.col++; + } + } + /* TODO: properly advance cursor */ + t->cursor.row++; +} + +static void blit_lines(Term *t) { - size line_count = 12; + size line_count = 16; CLAMP(line_count, 0, t->log_lines.filled); - + v2 cs = get_cell_size(t); /* TODO: handle case where widx has wrapped around */ ASSERT(t->log_lines.widx >= line_count); size line_off = t->log_lines.widx - line_count; - v2 pos = {.y = t->gl.window_size.h - t->fa.size.h - 8}; + + /* TODO: properly set cursor */ + t->cursor.row = 0; + for (size i = 0; i <= line_count; i++) { - s8 line = line_to_s8(t->log_lines.buf + line_off + i); - draw_text(&t->gl, line, pos, (Colour){.rgba = 0x1e9e33ff}, 1); - pos.y -= (t->fa.size.h + 8); + /* TODO: properly set cursor */ + t->cursor.col = 0; + v2 pos = { + .x = t->cursor.col * cs.w, + .y = t->gl.window_size.h - (t->cursor.row + 1) * cs.h + }; + push_line(t, t->log_lines.buf + line_off + i, pos); } }