vtgl

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

Commit: 9af05d8c979276cb33541f8425f8754bb98f7966
Parent: bdb41e690d9a63bf913dac552fdc7bc9d0dcd6ef
Author: Randy Palamar
Date:   Sun, 17 Nov 2024 13:27:37 -0700

support saving/restoring the private mode settings

who knows if this useful in practice but it is trivial to support

Diffstat:
Mdebug.c | 4++--
Mterminal.c | 99++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mutil.h | 60++++++++++++++++++++++++++++++++++++------------------------
Mvtgl.c | 34+++++++++++++++++-----------------
4 files changed, 110 insertions(+), 87 deletions(-)

diff --git a/debug.c b/debug.c @@ -27,8 +27,8 @@ dump_lines_to_file(Term *t) stream_push_s8(&out, s8("Term Info:")); stream_push_s8(&out, s8("\n Size: ")); stream_push_iv2(&out, tsize); stream_push_s8(&out, s8("\n Window Size: ")); stream_push_iv2(&out, wsize); - stream_push_s8(&out, s8("\n Mode: 0x")); stream_push_hex_u64(&out, t->mode); - stream_push_s8(&out, s8("\n Window Mode: 0x")); stream_push_hex_u64(&out, t->gl.mode); + stream_push_s8(&out, s8("\n Mode: 0x")); stream_push_hex_u64(&out, t->mode.term); + stream_push_s8(&out, s8("\n Window Mode: 0x")); stream_push_hex_u64(&out, t->mode.win); stream_push_s8(&out, s8("\n Scroll Region:")); stream_push_s8(&out, s8("\n Top: ")); stream_push_u64(&out, t->top); stream_push_s8(&out, s8("\n Bottom: ")); stream_push_u64(&out, t->bot); diff --git a/terminal.c b/terminal.c @@ -278,8 +278,8 @@ end: static void swap_screen(Term *t) { - t->mode ^= TM_ALTSCREEN; - t->view_idx = !!(t->mode & TM_ALTSCREEN); + t->mode.term ^= TM_ALTSCREEN; + t->view_idx = !!(t->mode.term & TM_ALTSCREEN); } static void @@ -390,7 +390,7 @@ term_tab_col(Term *t, u32 col, b32 set) static void term_reset(Term *t) { - i32 mode = t->mode & TM_ALTSCREEN; + i32 mode = t->mode.term & TM_ALTSCREEN; t->cursor.state = CURSOR_NORMAL; for (u32 i = 0; i < ARRAY_COUNT(t->saved_cursors); i++) { cursor_reset(t); @@ -410,7 +410,7 @@ term_reset(Term *t) t->top = 0; t->bot = t->size.h - 1; /* TODO: why is term_reset() being called when we are in the altscreen */ - t->mode = mode|TM_AUTO_WRAP|TM_UTF8; + t->mode.term = mode|TM_AUTO_WRAP|TM_UTF8; } static void @@ -544,7 +544,7 @@ clear_term_tab(Term *t, i32 arg) /* SM/DECSET: Set Mode & RM/DECRST Reset Mode */ static void -set_mode(Term *t, CSI *csi, b32 set, b32 simulate) +set_mode(Term *t, CSI *csi, b32 set, ModeState src, ModeState *dest) { BEGIN_TIMED_BLOCK(); i32 alt = t->view_idx; @@ -555,25 +555,24 @@ set_mode(Term *t, CSI *csi, b32 set, b32 simulate) i32 arg = (csi->argv[i]) | ((csi->priv & 1) << 30); switch (arg) { case 4: /* IRM: Insert/Replace Mode */ - if (set) t->mode |= TM_REPLACE; - else t->mode &= ~TM_REPLACE; + dest->term &= ~TM_REPLACE; + if (set) dest->term |= (src.term & TM_REPLACE); break; case 20: /* LNM: Linefeed Assumes Carriage Return */ - if (set) t->mode |= TM_CRLF; - else t->mode &= ~TM_CRLF; + dest->term &= ~TM_CRLF; + if (set) dest->term |= (src.term & TM_CRLF); break; case PRIV(1): /* DECCKM: use application cursor keys */ - if (set) t->gl.mode |= WIN_MODE_APPCURSOR; - else t->gl.mode &= ~WIN_MODE_APPCURSOR; + dest->win &= ~WM_APPCURSOR; + if (set) dest->win |= (src.win & WM_APPCURSOR); break; case PRIV(5): /* DECSCNM: reverse/normal video mode */ - if (set) t->gl.mode |= WIN_MODE_REVERSE; - else t->gl.mode &= ~WIN_MODE_REVERSE; + dest->win &= ~WM_REVERSE; + if (set) dest->win |= (src.win & WM_REVERSE); break; case PRIV(1000): /* xterm: report mouse button presses */ - t->gl.mode &= ~WIN_MODE_MOUSE_MASK; - if (set) t->gl.mode |= WIN_MODE_MOUSE_BTN; - else t->gl.mode &= ~WIN_MODE_MOUSE_BTN; + dest->win &= ~WM_MOUSE_MASK; + if (set) dest->win |= (src.win & WM_MOUSE_BTN); break; case PRIV(6): /* DECOM: Cursor Origin Mode */ if (set) t->cursor.state |= CURSOR_ORIGIN; @@ -581,8 +580,8 @@ set_mode(Term *t, CSI *csi, b32 set, b32 simulate) cursor_move_abs_to(t, 0, 0); break; case PRIV(7): /* DECAWM: Auto-Wrap Mode */ - if (set) t->mode |= TM_AUTO_WRAP; - else t->mode &= ~TM_AUTO_WRAP; + dest->term &= ~TM_AUTO_WRAP; + if (set) dest->term |= (src.term & TM_AUTO_WRAP); break; case PRIV(3): /* DECCOLM: 132/80 Column Mode */ case PRIV(4): /* DECSCLM: Fast/Slow Scroll */ @@ -594,18 +593,18 @@ set_mode(Term *t, CSI *csi, b32 set, b32 simulate) /* IGNORED */ break; case PRIV(25): /* DECTCEM: Show/Hide Cursor */ - if (!set) t->gl.mode |= WIN_MODE_HIDECURSOR; - else t->gl.mode &= ~WIN_MODE_HIDECURSOR; + dest->win &= ~WM_HIDECURSOR; + if (!set) dest->win |= (src.win & WM_HIDECURSOR); break; case PRIV(1034): /* xterm: enable 8-bit input mode */ - if (set) t->gl.mode |= WIN_MODE_8BIT; - else t->gl.mode &= ~WIN_MODE_8BIT; + dest->win &= ~WM_8BIT; + if (set) dest->win |= (src.win & WM_8BIT); break; case PRIV(1049): /* xterm: swap cursor then swap screen */ cursor_alt(t, set); case PRIV(47): /* xterm: swap screen buffer */ case PRIV(1047): /* xterm: swap screen buffer */ - if (alt && !simulate) fb_clear_region(t, 0, t->size.h, 0, t->size.w); + if (alt) fb_clear_region(t, 0, t->size.h, 0, t->size.w); if (set ^ alt) swap_screen(t); if (csi->argv[i] != 1049) break; /* FALLTHROUGH */ @@ -613,8 +612,8 @@ set_mode(Term *t, CSI *csi, b32 set, b32 simulate) cursor_alt(t, set); break; case PRIV(2004): /* xterm: bracketed paste mode */ - if (set) t->gl.mode |= WIN_MODE_BRACKPASTE; - else t->gl.mode &= ~WIN_MODE_BRACKPASTE; + dest->win &= ~WM_BRACKPASTE; + if (set) dest->win |= (src.win & WM_BRACKPASTE); break; case PRIV(2026): /* synchronized render (eg. wezterm) */ /* IGNORED: we aren't writing some slow garbage so we won't let some @@ -899,14 +898,26 @@ handle_csi(Term *t, CSI *csi) case 'e': cursor_step_raw(t, ORONE(csi->argv[0]), 1, 0); break; case 'f': cursor_move_abs_to(t, csi->argv[0] - 1, csi->argv[1] - 1); break; case 'g': clear_term_tab(t, csi->argv[0]); break; - case 'h': set_mode(t, csi, 1, 0); break; - case 'l': set_mode(t, csi, 0, 0); break; + case 'h': set_mode(t, csi, 1, MODE_STATE_ALL_MASK, &t->mode); break; + case 'l': set_mode(t, csi, 0, MODE_STATE_ALL_MASK, &t->mode); break; case 'm': set_colours(t, csi); break; case 'r': - if (csi->priv) - goto unknown; - set_scrolling_region(t, ORONE(csi->argv[0]) - 1, ORONE(csi->argv[1]) - 1); - cursor_move_abs_to(t, 0, 0); + if (csi->priv) { + /* NOTE: XTRESTORE: restore the value of a private mode */ + set_mode(t, csi, 1, t->saved_mode, &t->mode); + } else { + set_scrolling_region(t, ORONE(csi->argv[0]) - 1, ORONE(csi->argv[1]) - 1); + cursor_move_abs_to(t, 0, 0); + } + break; + case 's': + if (csi->priv) { + /* NOTE: XTSAVE: save the value of a private mode */ + set_mode(t, csi, 1, t->mode, &t->saved_mode); + } else { + /* NOTE: SCOSC/ANSI.SYS: save the cursor */ + cursor_alt(t, 1); + } break; case 't': window_manipulation(t, csi); break; case '!': @@ -1053,8 +1064,8 @@ handle_escape(Term *t, s8 *raw, Arena a) result = 1; } else { switch (get_ascii(raw)) { - case 'G': t->mode |= TM_UTF8; break; - case '@': t->mode &= ~TM_UTF8; break; + case 'G': t->mode.term |= TM_UTF8; break; + case '@': t->mode.term &= ~TM_UTF8; break; } } break; @@ -1133,10 +1144,10 @@ push_control(Term *t, s8 *line, u32 cp, Arena a) if (!line->len) result = 1; else result = handle_escape(t, line, a); break; - case '\r': cursor_move_to(t, t->cursor.pos.y, 0); break; - case '\n': push_newline(t, t->mode & TM_CRLF); break; - case '\t': push_tab(t, 1); break; - case '\a': /* TODO: ding ding? */ break; + case '\r': cursor_move_to(t, t->cursor.pos.y, 0); break; + case '\n': push_newline(t, t->mode.term & TM_CRLF); break; + case '\t': push_tab(t, 1); break; + case '\a': /* TODO: ding ding? */ break; case '\b': cursor_move_to(t, t->cursor.pos.y, t->cursor.pos.x - 1); break; @@ -1163,7 +1174,7 @@ push_normal_cp(Term *t, TermView *tv, u32 cp) { BEGIN_TIMED_BLOCK(); - if (t->mode & TM_AUTO_WRAP && t->cursor.state & CURSOR_WRAP_NEXT) + if (t->mode.term & TM_AUTO_WRAP && t->cursor.state & CURSOR_WRAP_NEXT) push_newline(t, 1); u32 width = 1; @@ -1205,7 +1216,7 @@ push_normal_cp(Term *t, TermView *tv, u32 cp) if (t->cursor.pos.x + width > t->size.w) { /* NOTE: make space for character if mode enabled else * clobber whatever was on the end of the line */ - if (t->mode & TM_AUTO_WRAP) + if (t->mode.term & TM_AUTO_WRAP) push_newline(t, 1); else cursor_move_to(t, t->cursor.pos.y, t->size.w - width); @@ -1253,7 +1264,7 @@ push_line(Term *t, Line *line, Arena a) ASSERT(cp != (u32)-1); if (ISCONTROL(cp)) { - if (!(t->mode & TM_UTF8) || !ISCONTROLC1(cp)) + if (!(t->mode.term & TM_UTF8) || !ISCONTROLC1(cp)) push_control(t, &l, cp, a); continue; } else if (t->escape & EM_CSI) { @@ -1317,7 +1328,7 @@ handle_input(Term *t, Arena a, s8 raw) u32 cp = peek(raw, 0); /* TODO: this could be a performance issue; may need seperate code path for * terminal when not in UTF8 mode */ - if (cp > 0x7F && (t->mode & TM_UTF8)) { + if (cp > 0x7F && (t->mode.term & TM_UTF8)) { cp = get_utf8(&raw); tv->lines.buf[tv->lines.widx].has_unicode = 1; if (cp == (u32)-1) { @@ -1332,7 +1343,7 @@ handle_input(Term *t, Arena a, s8 raw) ASSERT(cp != (u32)-1); if (ISCONTROL(cp)) { - if (!(t->mode & TM_UTF8) || !ISCONTROLC1(cp)) { + if (!(t->mode.term & TM_UTF8) || !ISCONTROLC1(cp)) { i32 old_curs_y = t->cursor.pos.y; if (push_control(t, &raw, cp, a)) { raw.len = start_len; @@ -1346,10 +1357,10 @@ handle_input(Term *t, Arena a, s8 raw) t->csi.raw.len++; if (BETWEEN(cp, '@', '~')) { i32 old_curs_y = t->cursor.pos.y; - i32 mode = t->mode & TM_ALTSCREEN; + i32 mode = t->mode.term & TM_ALTSCREEN; handle_csi(t, &t->csi); t->escape &= ~EM_CSI; - if ((t->mode & TM_ALTSCREEN) != mode) { + if ((t->mode.term & TM_ALTSCREEN) != mode) { u8 *old = raw.data - t->csi.raw.len - 2; ASSERT(*old == 0x1B); feed_line(&tv->lines, old, t->cursor.style); diff --git a/util.h b/util.h @@ -241,6 +241,37 @@ typedef struct { size last_line_idx; } TermView; +enum terminal_mode { + TM_ALTSCREEN = 1 << 0, + TM_REPLACE = 1 << 1, + TM_AUTO_WRAP = 1 << 2, + TM_CRLF = 1 << 3, + TM_UTF8 = 1 << 4, + + TM_ALL_MASK = TM_UTF8|(TM_UTF8 - 1), +}; + +enum window_mode { + WM_APPCURSOR = 1 << 0, + WM_HIDECURSOR = 1 << 1, + WM_BRACKPASTE = 1 << 2, + WM_8BIT = 1 << 3, + WM_REVERSE = 1 << 4, + WM_MOUSE_X10 = 1 << 5, + WM_MOUSE_BTN = 1 << 6, + WM_MOUSE_TRK = 1 << 7, + + WM_MOUSE_MASK = WM_MOUSE_X10|WM_MOUSE_BTN|WM_MOUSE_TRK, + + WM_ALL_MASK = WM_MOUSE_TRK|(WM_MOUSE_TRK - 1), +}; + +#define MODE_STATE_ALL_MASK (ModeState){.term = TM_ALL_MASK, .win = WM_ALL_MASK} +typedef struct { + enum terminal_mode term; + enum window_mode win; +} ModeState; + #define SHADER_PMAT_LOC 0 #define GL_POST_UNIFORMS \ @@ -256,19 +287,6 @@ enum gl_flags { DRAW_DEBUG_OVERLAY = 1 << 30, }; -enum win_mode { - WIN_MODE_APPCURSOR = 1 << 0, - WIN_MODE_HIDECURSOR = 1 << 1, - WIN_MODE_BRACKPASTE = 1 << 2, - WIN_MODE_8BIT = 1 << 3, - WIN_MODE_REVERSE = 1 << 4, - WIN_MODE_MOUSE_X10 = 1 << 5, - WIN_MODE_MOUSE_BTN = 1 << 6, - WIN_MODE_MOUSE_TRK = 1 << 7, - - WIN_MODE_MOUSE_MASK = WIN_MODE_MOUSE_X10|WIN_MODE_MOUSE_BTN|WIN_MODE_MOUSE_TRK, -}; - enum shader_stages { SHADER_RENDER, SHADER_RECTS, @@ -320,7 +338,6 @@ typedef struct { u32 glyph_bitmap_tex; u32 flags; - u32 mode; } GLCtx; typedef struct { @@ -500,14 +517,6 @@ enum escape_mode { EM_STR = 1 << 1, }; -enum terminal_mode { - TM_ALTSCREEN = 1 << 0, - TM_REPLACE = 1 << 1, - TM_AUTO_WRAP = 1 << 2, - TM_CRLF = 1 << 3, - TM_UTF8 = 1 << 4, -}; - enum charsets { CS_USA, CS_GRAPHIC0, @@ -530,6 +539,10 @@ typedef struct Term { InteractionState interaction; Selection selection; + + ModeState mode; + ModeState saved_mode; + Cursor cursor; Cursor saved_cursors[2]; TermView views[2]; @@ -546,8 +559,7 @@ typedef struct Term { CSI csi; - enum escape_mode escape; - enum terminal_mode mode; + enum escape_mode escape; /* NOTE: this means we limit ourselves to 32 * 32 cells (1024). Even on a 4k sceen * this would mean that cells are ~4px wide which is unreadable */ diff --git a/vtgl.c b/vtgl.c @@ -574,20 +574,20 @@ render_framebuffer(Term *t, RenderCell *render_buf) } /* NOTE: draw cursor */ - if (!(t->gl.mode & WIN_MODE_HIDECURSOR) && t->scroll_offset == 0) { + if (!(t->mode.win & WM_HIDECURSOR) && t->scroll_offset == 0) { iv2 curs = t->cursor.pos; Cell *c = &tv->fb.rows[curs.y][curs.x]; RenderCell *rc = render_buf + curs.y * t->size.w + curs.x; ASSERT(!(c->bg & ATTR_WDUMMY)); rc->fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); - if ((t->mode & TM_ALTSCREEN) == 0) + if ((t->mode.term & TM_ALTSCREEN) == 0) rc->fg |= SHADER_PACK_ATTR(ATTR_BLINK); if (c->bg & ATTR_WIDE) { for (u32 j = 1; c[j].bg & ATTR_WDUMMY; j++) { rc[j].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); - if ((t->mode & TM_ALTSCREEN) == 0) + if ((t->mode.term & TM_ALTSCREEN) == 0) rc[j].fg |= SHADER_PACK_ATTR(ATTR_BLINK); } } @@ -727,7 +727,7 @@ KEYBIND_FN(copy) KEYBIND_FN(paste) { Stream buf = arena_stream(t->arena_for_frame); - b32 bracketed = t->gl.mode & WIN_MODE_BRACKPASTE; + b32 bracketed = t->mode.win & WM_BRACKPASTE; if (bracketed) stream_push_s8(&buf, s8("\033[200~")); b32 success = platform->get_clipboard(&buf, a.i); @@ -741,7 +741,7 @@ KEYBIND_FN(paste) KEYBIND_FN(scroll) { - if (t->mode & TM_ALTSCREEN) + if (t->mode.term & TM_ALTSCREEN) return 0; TermView *tv = t->views + t->view_idx; @@ -780,10 +780,10 @@ report_mouse(Term *t, TerminalInput *input, b32 pressed) else if (input->mouse_scroll.y < 0) value = 64 + (5 - 4); else return; - if ((t->gl.mode & WIN_MODE_MOUSE_X10) && !pressed) + if ((t->mode.win & WM_MOUSE_X10) && !pressed) return; - if (!(t->gl.mode & WIN_MODE_MOUSE_X10)) { + if (!(t->mode.win & WM_MOUSE_X10)) { value += ((input->modifiers & MOD_SHIFT) ? 4 : 0) + ((input->modifiers & MOD_ALT) ? 8 : 0) + ((input->modifiers & MOD_CONTROL) ? 16 : 0); @@ -803,7 +803,7 @@ report_mouse(Term *t, TerminalInput *input, b32 pressed) static void begin_terminal_interaction(Term *t, TerminalInput *input, u32 click_count) { - if (t->gl.mode & WIN_MODE_MOUSE_MASK) { + if (t->mode.win & WM_MOUSE_MASK) { report_mouse(t, input, 1); } else { if (pressed_last_frame(input->keys + MOUSE_LEFT)) @@ -816,7 +816,7 @@ terminal_interaction(Term *t, PlatformAPI *platform, TerminalInput *input, u32 c { b32 should_end_interaction = 0; - if (t->gl.mode & WIN_MODE_MOUSE_MASK) { + if (t->mode.win & WM_MOUSE_MASK) { should_end_interaction = released_last_frame(input->keys + MOUSE_LEFT) || released_last_frame(input->keys + MOUSE_MIDDLE) || released_last_frame(input->keys + MOUSE_RIGHT); @@ -827,7 +827,7 @@ terminal_interaction(Term *t, PlatformAPI *platform, TerminalInput *input, u32 c b32 shift_down = input->modifiers & MOD_SHIFT; if (input->mouse_scroll.y) { - if (t->mode & TM_ALTSCREEN) { + if (t->mode.term & TM_ALTSCREEN) { iptr child = t->child; if (input->mouse_scroll.y > 0) { if (shift_down) platform->write(child, s8("\x1B[5;2~"), 0); @@ -851,7 +851,7 @@ terminal_interaction(Term *t, PlatformAPI *platform, TerminalInput *input, u32 c static void end_terminal_interaction(Term *t, TerminalInput *input) { - if (t->gl.mode & WIN_MODE_MOUSE_MASK) { + if (t->mode.win & WM_MOUSE_MASK) { b32 end_from_release = released_last_frame(input->keys + MOUSE_LEFT) || released_last_frame(input->keys + MOUSE_MIDDLE) || released_last_frame(input->keys + MOUSE_RIGHT); @@ -897,7 +897,7 @@ DEBUG_EXPORT VTGL_HANDLE_KEYS_FN(vtgl_handle_keys) /* NOTE: send control sequences */ if (modifiers & MOD_CONTROL && action != ACT_RELEASE) { /* TODO: this is wrong. look up where 8-bit modifiers should be sent */ - if (0 && t->gl.mode & WIN_MODE_8BIT) { + if (0 && t->mode.win & WM_8BIT) { if (key < 0x7F) { platform->write(child, utf8_encode(key | 0x80), 0); return; @@ -928,28 +928,28 @@ DEBUG_EXPORT VTGL_HANDLE_KEYS_FN(vtgl_handle_keys) break; case ENCODE_KEY(ACT_PRESS, 0, KEY_UP): case ENCODE_KEY(ACT_REPEAT, 0, KEY_UP): - if (t->gl.mode & WIN_MODE_APPCURSOR) + if (t->mode.win & WM_APPCURSOR) platform->write(child, s8("\x1BOA"), 0); else platform->write(child, s8("\x1B[A"), 0); break; case ENCODE_KEY(ACT_PRESS, 0, KEY_DOWN): case ENCODE_KEY(ACT_REPEAT, 0, KEY_DOWN): - if (t->gl.mode & WIN_MODE_APPCURSOR) + if (t->mode.win & WM_APPCURSOR) platform->write(child, s8("\x1BOB"), 0); else platform->write(child, s8("\x1B[B"), 0); break; case ENCODE_KEY(ACT_PRESS, 0, KEY_RIGHT): case ENCODE_KEY(ACT_REPEAT, 0, KEY_RIGHT): - if (t->gl.mode & WIN_MODE_APPCURSOR) + if (t->mode.win & WM_APPCURSOR) platform->write(child, s8("\x1BOC"), 0); else platform->write(child, s8("\x1B[C"), 0); break; case ENCODE_KEY(ACT_PRESS, 0, KEY_LEFT): case ENCODE_KEY(ACT_REPEAT, 0, KEY_LEFT): - if (t->gl.mode & WIN_MODE_APPCURSOR) + if (t->mode.win & WM_APPCURSOR) platform->write(child, s8("\x1BOD"), 0); else platform->write(child, s8("\x1B[D"), 0); @@ -1324,7 +1324,7 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) sp->underline_min = (0.89 * t->fa.info.h); sp->underline_max = (0.96 * t->fa.info.h); - sp->reverse_video_mask = REVERSE_VIDEO_MASK * !!(t->gl.mode & WIN_MODE_REVERSE); + sp->reverse_video_mask = REVERSE_VIDEO_MASK * !!(t->mode.win & WM_REVERSE); glBindBuffer(GL_UNIFORM_BUFFER, t->gl.render_shader_ubo); glBindBufferBase(GL_UNIFORM_BUFFER, 0, t->gl.render_shader_ubo);