vtgl

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

Commit: 5891ce7bae709d3026df0a9f89c57798e1dd8478
Parent: 7d1c35b39e63aeb8dfea640aa6724de442955fba
Author: Randy Palamar
Date:   Wed,  3 Jul 2024 21:52:38 -0600

handle most escapes produced by less

Diffstat:
Mbuild.sh | 2+-
Mmain.c | 1+
Mterminal.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mutil.h | 7+++++++
Mvtgl.c | 45+++++++++++++++++++++++++++++++++++++++++----
5 files changed, 126 insertions(+), 16 deletions(-)

diff --git a/build.sh b/build.sh @@ -6,7 +6,7 @@ ldflags="" ldflags="$ldflags -lfreetype $(pkg-config --static --libs glfw3 gl)" # Hot Reloading/Debugging -cflags="$cflags -D_DEBUG -Wno-unused-function" +cflags="$cflags -D_DEBUG -Wno-unused-function -Wno-undefined-internal" libcflags="$cflags -fPIC -Wno-unused-function" libldflags="$ldflags -shared" diff --git a/main.c b/main.c @@ -55,6 +55,7 @@ do_debug(GLCtx *gl) nanosleep(&sleep_time, &sleep_time); load_library(libname); init_callbacks(gl); + fputs("Reloaded Main Program\n", stderr); } } diff --git a/terminal.c b/terminal.c @@ -126,6 +126,8 @@ dump_csi(CSI *csi) fputs(" } }\n", stderr); } + +/* ED/DECSED: Erase in Display */ static void erase_in_display(Term *t, CSI *csi) { @@ -142,10 +144,50 @@ erase_in_display(Term *t, CSI *csi) case 3: /* Erase Saved Lines (xterm) */ /* NOTE: ignored; we don't save lines in the way xterm does */ break; - default: ASSERT(0); break; + default: ASSERT(0); } } +/* EL/DECSEL: Erase in Line */ +static void +erase_in_line(Term *t, CSI *csi) +{ + Cursor *c = &t->cursor; + switch (csi->argv[0]) { + case 0: /* Erase to Right */ + push_empty_cell_rect(t, c->row, c->row, c->col, t->size.w); + break; + case 1: /* Erase to Left */ + push_empty_cell_rect(t, c->row, c->row, 0, c->col); + break; + case 2: /* Erase All */ + push_empty_cell_rect(t, c->row, c->row, 0, t->size.w); + break; + default: ASSERT(0); + } +} + +/* SM/DECSET: Set Mode & RM/DECRST Reset Mode */ +static void +set_mode(Term *t, CSI *csi, b32 set) +{ + #define PRIV(a) ((1 << 30) | (a)) + for (i32 i = 0; i < csi->argc; i++) { + i32 arg = (csi->argv[i]) | ((csi->priv & 1) << 30); + switch (arg) { + case PRIV(1): /* DECCKM: use application cursor keys */ + if (set) t->gl.mode |= WIN_MODE_APPCURSOR; + else t->gl.mode &= ~WIN_MODE_APPCURSOR; + break; + case PRIV(1049): /* TODO: save cursor and switch to alt screen */ + break; + default: + fputs("set_mode: unhandled mode: ", stderr); + dump_csi(csi); + } + } + #undef PRIV +} /* SGR: Select Graphic Rendition */ static void @@ -154,15 +196,22 @@ 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; + 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; + case 22: cs->attr &= ~(ATTR_BOLD|ATTR_FAINT); break; + case 23: cs->attr &= ~ATTR_ITALIC; break; + case 24: cs->attr &= ~ATTR_UNDERLINED; break; + case 25: cs->attr &= ~ATTR_BLINK; break; + case 27: cs->attr &= ~ATTR_INVERSE; break; + case 28: cs->attr &= ~ATTR_INVISIBLE; break; + case 29: cs->attr &= ~ATTR_STRUCK; break; default: fprintf(stderr, "unhandled colour arg: %d\n", csi->argv[i]); dump_csi(csi); @@ -170,6 +219,18 @@ set_colours(Term *t, CSI *csi) } } +static void +window_manipulation(Term *t, CSI *csi) +{ + switch (csi->argv[0]) { + case 22: /* TODO: save title */ break; + case 23: /* TODO: restore title */ break; + default: + fprintf(stderr, "unhandled xtwinops: %d\n", csi->argv[0]); + dump_csi(csi); + } +} + static CSI parse_csi(s8 *r) { @@ -212,9 +273,13 @@ handle_csi(Term *t, s8 *raw) CSI csi = parse_csi(raw); switch (csi.mode) { - case 'm': set_colours(t, &csi); break; case 'H': cursor_move_to(t, csi.argv[0], csi.argv[1]); break; case 'J': erase_in_display(t, &csi); break; + case 'K': erase_in_line(t, &csi); break; + case 'h': set_mode(t, &csi, 1); break; + case 'l': set_mode(t, &csi, 0); break; + case 'm': set_colours(t, &csi); break; + case 't': window_manipulation(t, &csi); break; default: fputs("unknown csi: ", stderr); dump_csi(&csi); diff --git a/util.h b/util.h @@ -151,6 +151,10 @@ enum gl_flags { UPDATE_POST_UNIFORMS = 1 << 30, }; +enum win_mode { + WIN_MODE_APPCURSOR = 1 << 0, +}; + enum shader_stages { SHADER_RENDER, SHADER_POST, @@ -180,6 +184,7 @@ typedef struct { #undef X u32 flags; + u32 mode; u32 glyph_tex; u32 glyph_cache_len; @@ -225,6 +230,8 @@ typedef struct { FT_Library ftlib; } Term; +static void push_empty_cell_rect(Term *, u32 minrow, u32 maxrow, u32 mincol, u32 maxcol); + #include "config.h" #include "font.c" #include "terminal.c" diff --git a/vtgl.c b/vtgl.c @@ -83,15 +83,21 @@ update_uniforms(Term *t, enum shader_stages stage) switch (stage) { case SHADER_RENDER: - for (u32 i = 0; i < ARRAY_COUNT(t->gl.render.uniforms); i++) + for (u32 i = 0; i < ARRAY_COUNT(t->gl.render.uniforms); i++) { t->gl.render.uniforms[i] = glGetUniformLocation(t->gl.programs[stage], render_uniform_names[i]); + //fprintf(stderr, "uniform (RENDER): %s; id %d\n", + // render_uniform_names[i], t->gl.render.uniforms[i]); + } t->gl.flags &= ~UPDATE_RENDER_UNIFORMS; break; case SHADER_POST: - for (u32 i = 0; i < ARRAY_COUNT(t->gl.post.uniforms); i++) + for (u32 i = 0; i < ARRAY_COUNT(t->gl.post.uniforms); i++) { t->gl.post.uniforms[i] = glGetUniformLocation(t->gl.programs[stage], post_uniform_names[i]); + //fprintf(stderr, "uniform (POST): %s; id %d\n", + // post_uniform_names[i], t->gl.post.uniforms[i]); + } t->gl.flags &= ~UPDATE_POST_UNIFORMS; break; case SHADER_LAST: ASSERT(0); break; @@ -139,6 +145,21 @@ push_char(GLCtx *gl, v2 vertscale, v2 vertoff, v2 texscale, uv2 colours, i32 cha } static void +push_empty_cell_rect(Term *t, u32 minrow, u32 maxrow, u32 mincol, u32 maxcol) +{ + ASSERT(minrow <= maxrow && mincol <= maxcol); + ASSERT(maxrow <= t->size.h && maxcol <= t->size.w); + + v2 cs = get_cell_size(t); + v2 size = {.x = (maxcol - mincol + 1) * cs.w, .y = (maxrow - minrow + 1) * cs.h}; + v2 pos = {.x = mincol * cs.w, .y = t->gl.window_size.h - cs.h * (minrow + 1)}; + + /* TODO: global colour table */ + Colour colour = {.r = 20, .g = 20, .b = 20, .a = 255}; + push_char(&t->gl, size, pos, (v2){0}, (uv2){.y = colour.rgba}, 0); +} + +static void draw_text(GLCtx *gl, s8 text, v2 position, Colour colour, b32 monospaced) { f32 single_space_width = gl->glyph_cache[0].size.w; @@ -232,6 +253,7 @@ push_line(Term *t, Line *line, v2 start_pos) 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); @@ -311,6 +333,15 @@ key_callback(GLFWwindow *win, i32 key, i32 sc, i32 act, i32 mods) { Term *t = glfwGetWindowUserPointer(win); + if (mods & GLFW_MOD_CONTROL && key < 0x80) { + if (act == GLFW_RELEASE) + return; + if (BETWEEN(key, 0x40, 0x5F)) { + os_child_put_char(t->child, key - 0x40); + return; + } + } + switch (ENCODE_KEY(act, 0, key)) { case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_ESCAPE): case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_ESCAPE): @@ -356,9 +387,15 @@ do_terminal(Term *t, Arena a) if (t->gl.flags & UPDATE_POST_UNIFORMS) update_uniforms(t, SHADER_POST); + /* TODO: don't let the input splitting cause draw calls */ + glUseProgram(t->gl.programs[SHADER_RENDER]); + if (os_child_data_available(t->child)) { - if (os_child_exited(t->child)) + if (os_child_exited(t->child)) { + /* TODO: is there a reason to not immediately exit? */ glfwSetWindowShouldClose(t->gl.window, GL_TRUE); + return; + } t->unprocessed_bytes += os_read_from_child(t->child, &t->log); s8 raw = { .len = t->unprocessed_bytes, @@ -372,7 +409,7 @@ do_terminal(Term *t, Arena a) v2 ws = t->gl.window_size; /* NOTE: reset the camera/viewport */ - glUseProgram(t->gl.programs[SHADER_RENDER]); + //glUseProgram(t->gl.programs[SHADER_RENDER]); glUniform1i(t->gl.render.texslot, 0); glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb); clear_colour();