#include #include #include #include #include #include #include #include #include #include #include extern void reset_term(void); bool early_term = false; void term_deinit(void) { switch (term_backend) { case VBE: gterm_deinit(); } term_notready(); } void term_vbe(size_t width, size_t height) { term_notready(); if (quiet || allocations_disallowed) { return; } if (!gterm_init(&term_rows, &term_cols, width, height)) { #if bios == 1 // Failed to set VBE properly, default to text mode term_textmode(); #endif return; } term_reinit(); raw_putchar = gterm_putchar; clear = gterm_clear; enable_cursor = gterm_enable_cursor; disable_cursor = gterm_disable_cursor; set_cursor_pos = gterm_set_cursor_pos; get_cursor_pos = gterm_get_cursor_pos; set_text_fg = gterm_set_text_fg; set_text_bg = gterm_set_text_bg; set_text_fg_bright = gterm_set_text_fg_bright; set_text_bg_bright = gterm_set_text_bg_bright; set_text_fg_default = gterm_set_text_fg_default; set_text_bg_default = gterm_set_text_bg_default; scroll_disable = gterm_scroll_disable; scroll_enable = gterm_scroll_enable; term_move_character = gterm_move_character; term_scroll = gterm_scroll; term_revscroll = gterm_revscroll; term_swap_palette = gterm_swap_palette; term_save_state = gterm_save_state; term_restore_state = gterm_restore_state; term_double_buffer_flush = gterm_double_buffer_flush; term_context_size = gterm_context_size; term_context_save = gterm_context_save; term_context_restore = gterm_context_restore; term_full_refresh = gterm_full_refresh; term_backend = VBE; } // Tries to implement this standard for terminfo // https://man7.org/linux/man-pages/man4/console_codes.4.html no_unwind int current_video_mode = -1; int term_backend = NOT_READY; size_t term_rows, term_cols; bool term_runtime = false; static void notready_raw_putchar(uint8_t c) { (void)c; } static void notready_clear(bool move) { (void)move; } static void notready_void(void) {} static void notready_set_cursor_pos(size_t x, size_t y) { (void)x; (void)y; } static void notready_get_cursor_pos(size_t *x, size_t *y) { *x = 0; *y = 0; } static void notready_size_t(size_t n) { (void)n; } static bool notready_disable(void) { return false; } static void notready_move_character(size_t a, size_t b, size_t c, size_t d) { (void)a; (void)b; (void)c; (void)d; } static uint64_t notready_context_size(void) { return 0; } static void notready_uint64_t(uint64_t n) { (void)n; } void term_notready(void) { term_backend = NOT_READY; raw_putchar = notready_raw_putchar; clear = notready_clear; enable_cursor = notready_void; disable_cursor = notready_disable; set_cursor_pos = notready_set_cursor_pos; get_cursor_pos = notready_get_cursor_pos; set_text_fg = notready_size_t; set_text_bg = notready_size_t; set_text_fg_bright = notready_size_t; set_text_bg_bright = notready_size_t; set_text_fg_default = notready_void; set_text_bg_default = notready_void; scroll_disable = notready_disable; scroll_enable = notready_void; term_move_character = notready_move_character; term_scroll = notready_void; term_revscroll = notready_void; term_swap_palette = notready_void; term_save_state = notready_void; term_restore_state = notready_void; term_double_buffer_flush = notready_void; term_context_size = notready_context_size; term_context_save = notready_uint64_t; term_context_restore = notready_uint64_t; term_full_refresh = notready_void; term_rows = 100; term_cols = 100; } #if bios == 1 void fallback_raw_putchar(uint8_t c) { struct rm_regs r = {0}; r.eax = 0x0e00 | c; rm_int(0x10, &r, &r); } void fallback_clear(bool move) { (void)move; struct rm_regs r = {0}; rm_int(0x11, &r, &r); switch ((r.eax >> 4) & 3) { case 0: r.eax = 3; break; case 1: r.eax = 1; break; case 2: r.eax = 3; break; case 3: r.eax = 7; break; } rm_int(0x10, &r, &r); } void fallback_set_cursor_pos(size_t x, size_t y) { struct rm_regs r = {0}; r.eax = 0x0200; r.ebx = 0; r.edx = (y << 8) + x; rm_int(0x10, &r, &r); } void fallback_get_cursor_pos(size_t *x, size_t *y) { struct rm_regs r = {0}; r.eax = 0x0300; r.ebx = 0; rm_int(0x10, &r, &r); *x = r.edx & 0xff; *y = r.edx >> 8; } #elif uefi == 1 static int cursor_x = 0, cursor_y = 0; void fallback_raw_putchar(uint8_t c) { CHAR16 string[2]; string[0] = c; string[1] = 0; gST->ConOut->OutputString(gST->ConOut, string); switch (c) { case 0x08: if (cursor_x > 0) cursor_x--; break; case 0x0A: cursor_x = 0; break; case 0x0D: if (cursor_y < 24) cursor_y++; break; default: if (++cursor_x > 80) { cursor_x = 0; if (cursor_y < 24) cursor_y++; } } } void fallback_clear(bool move) { (void)move; gST->ConOut->ClearScreen(gST->ConOut); cursor_x = cursor_y = 0; } void fallback_set_cursor_pos(size_t x, size_t y) { if (x >= 80 || y >= 25) return; gST->ConOut->SetCursorPosition(gST->ConOut, x, y); cursor_x = x; cursor_y = y; } void fallback_get_cursor_pos(size_t *x, size_t *y) { *x = cursor_x; *y = cursor_y; } #endif void term_fallback(void) { #if uefi == 1 if (!efi_boot_services_exited) { gST->ConOut->Reset(gST->ConOut, false); gST->ConOut->SetMode(gST->ConOut, 0); cursor_x = cursor_y = 0; #elif bios == 1 fallback_clear(true); #endif term_notready(); raw_putchar = fallback_raw_putchar; clear = fallback_clear; set_cursor_pos = fallback_set_cursor_pos; get_cursor_pos = fallback_get_cursor_pos; term_backend = FALLBACK; #if uefi == 1 } #endif } void (*raw_putchar)(uint8_t c); void (*clear)(bool move); void (*enable_cursor)(void); bool (*disable_cursor)(void); void (*set_cursor_pos)(size_t x, size_t y); void (*get_cursor_pos)(size_t *x, size_t *y); void (*set_text_fg)(size_t fg); void (*set_text_bg)(size_t bg); void (*set_text_fg_bright)(size_t fg); void (*set_text_bg_bright)(size_t bg); void (*set_text_fg_default)(void); void (*set_text_bg_default)(void); bool (*scroll_disable)(void); void (*scroll_enable)(void); void (*term_move_character)(size_t new_x, size_t new_y, size_t old_x, size_t old_y); void (*term_scroll)(void); void (*term_revscroll)(void); void (*term_swap_palette)(void); void (*term_save_state)(void); void (*term_restore_state)(void); void (*term_double_buffer_flush)(void); uint64_t (*term_context_size)(void); void (*term_context_save)(uint64_t ptr); void (*term_context_restore)(uint64_t ptr); void (*term_full_refresh)(void); void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t) = NULL; struct term_context term_context; #define escape_offset term_context.escape_offset #define control_sequence term_context.control_sequence #define csi term_context.csi #define escape term_context.escape #define rrr term_context.rrr #define discard_next term_context.discard_next #define bold term_context.bold #define reverse_video term_context.reverse_video #define dec_private term_context.dec_private #define esc_values term_context.esc_values #define esc_values_i term_context.esc_values_i #define saved_cursor_x term_context.saved_cursor_x #define saved_cursor_y term_context.saved_cursor_y #define current_primary term_context.current_primary #define insert_mode term_context.insert_mode #define scroll_top_margin term_context.scroll_top_margin #define scroll_bottom_margin term_context.scroll_bottom_margin #define current_charset term_context.current_charset #define charsets term_context.charsets #define g_select term_context.g_select #define saved_state_bold term_context.saved_state_bold #define saved_state_reverse_video term_context.saved_state_reverse_video #define saved_state_current_charset term_context.saved_state_current_charset #define saved_state_current_primary term_context.saved_state_current_primary #define CHARSET_DEFAULT 0 #define CHARSET_DEC_SPECIAL 1 void term_reinit(void) { escape_offset = 0; control_sequence = false; csi = false; escape = false; rrr = false; discard_next = false; bold = false; reverse_video = false; dec_private = false; esc_values_i = 0; saved_cursor_x = 0; saved_cursor_y = 0; current_primary = (size_t)-1; insert_mode = false; scroll_top_margin = 0; scroll_bottom_margin = term_rows; current_charset = 0; g_select = 0; charsets[0] = CHARSET_DEFAULT; charsets[1] = CHARSET_DEC_SPECIAL; term_autoflush = true; } #if bios == 1 void term_textmode(void) { term_notready(); if (quiet || allocations_disallowed) { return; } init_vga_textmode(&term_rows, &term_cols, true); term_reinit(); raw_putchar = text_putchar; clear = text_clear; enable_cursor = text_enable_cursor; disable_cursor = text_disable_cursor; set_cursor_pos = text_set_cursor_pos; get_cursor_pos = text_get_cursor_pos; set_text_fg = text_set_text_fg; set_text_bg = text_set_text_bg; set_text_fg_bright = text_set_text_fg_bright; set_text_bg_bright = text_set_text_bg_bright; set_text_fg_default = text_set_text_fg_default; set_text_bg_default = text_set_text_bg_default; scroll_disable = text_scroll_disable; scroll_enable = text_scroll_enable; term_move_character = text_move_character; term_scroll = text_scroll; term_revscroll = text_revscroll; term_swap_palette = text_swap_palette; term_save_state = text_save_state; term_restore_state = text_restore_state; term_double_buffer_flush = text_double_buffer_flush; term_context_size = text_context_size; term_context_save = text_context_save; term_context_restore = text_context_restore; term_full_refresh = text_full_refresh; term_backend = TEXTMODE; } #endif static uint64_t context_size(void) { uint64_t ret = 0; ret += sizeof(struct term_context); ret += term_context_size(); return ret; } static void context_save(uint64_t ptr) { memcpy32to64(ptr, (uint64_t)(uintptr_t)&term_context, sizeof(struct term_context)); ptr += sizeof(struct term_context); term_context_save(ptr); } static void context_restore(uint64_t ptr) { memcpy32to64((uint64_t)(uintptr_t)&term_context, ptr, sizeof(struct term_context)); ptr += sizeof(struct term_context); term_context_restore(ptr); } #if defined (__i386__) #define TERM_XFER_CHUNK 8192 static uint8_t xfer_buf[TERM_XFER_CHUNK]; #endif bool term_autoflush = true; void term_write(uint64_t buf, uint64_t count) { if (term_backend == NOT_READY) return; switch (count) { case TERM_CTX_SIZE: { uint64_t ret = context_size(); memcpy32to64(buf, (uint64_t)(uintptr_t)&ret, sizeof(uint64_t)); return; } case TERM_CTX_SAVE: { context_save(buf); return; } case TERM_CTX_RESTORE: { context_restore(buf); return; } case TERM_FULL_REFRESH: { term_full_refresh(); return; } } bool native = false; #if defined (__x86_64__) native = true; #endif if (!term_runtime || native) { const char *s = (const char *)(uintptr_t)buf; for (size_t i = 0; i < count; i++) term_putchar(s[i]); } else { #if defined (__i386__) while (count != 0) { uint64_t chunk; if (count > TERM_XFER_CHUNK) { chunk = TERM_XFER_CHUNK; } else { chunk = count; } memcpy32to64((uint64_t)(uintptr_t)xfer_buf, buf, chunk); for (size_t i = 0; i < chunk; i++) term_putchar(xfer_buf[i]); count -= chunk; buf += chunk; } #endif } if (term_autoflush) { term_double_buffer_flush(); } } static void sgr(void) { size_t i = 0; if (!esc_values_i) goto def; for (; i < esc_values_i; i++) { size_t offset; if (esc_values[i] == 0) { def: if (reverse_video) { reverse_video = false; term_swap_palette(); } bold = false; current_primary = (size_t)-1; set_text_bg_default(); set_text_fg_default(); continue; } else if (esc_values[i] == 1) { bold = true; if (current_primary != (size_t)-1) { if (!reverse_video) { set_text_fg_bright(current_primary); } else { set_text_bg_bright(current_primary); } } continue; } else if (esc_values[i] == 22) { bold = false; if (current_primary != (size_t)-1) { if (!reverse_video) { set_text_fg(current_primary); } else { set_text_bg(current_primary); } } continue; } else if (esc_values[i] >= 30 && esc_values[i] <= 37) { offset = 30; current_primary = esc_values[i] - offset; if (reverse_video) { goto set_bg; } set_fg: if (bold && !reverse_video) { set_text_fg_bright(esc_values[i] - offset); } else { set_text_fg(esc_values[i] - offset); } continue; } else if (esc_values[i] >= 40 && esc_values[i] <= 47) { offset = 40; if (reverse_video) { goto set_fg; } set_bg: if (bold && reverse_video) { set_text_bg_bright(esc_values[i] - offset); } else { set_text_bg(esc_values[i] - offset); } continue; } else if (esc_values[i] >= 90 && esc_values[i] <= 97) { offset = 90; current_primary = esc_values[i] - offset; if (reverse_video) { goto set_bg_bright; } set_fg_bright: set_text_fg_bright(esc_values[i] - offset); continue; } else if (esc_values[i] >= 100 && esc_values[i] <= 107) { offset = 100; if (reverse_video) { goto set_fg_bright; } set_bg_bright: set_text_bg_bright(esc_values[i] - offset); continue; } else if (esc_values[i] == 39) { current_primary = (size_t)-1; if (reverse_video) { term_swap_palette(); } set_text_fg_default(); if (reverse_video) { term_swap_palette(); } continue; } else if (esc_values[i] == 49) { if (reverse_video) { term_swap_palette(); } set_text_bg_default(); if (reverse_video) { term_swap_palette(); } continue; } else if (esc_values[i] == 7) { if (!reverse_video) { reverse_video = true; term_swap_palette(); } continue; } else if (esc_values[i] == 27) { if (reverse_video) { reverse_video = false; term_swap_palette(); } continue; } } } static void dec_private_parse(uint8_t c) { dec_private = false; if (esc_values_i == 0) { return; } bool set; switch (c) { case 'h': set = true; break; case 'l': set = false; break; default: return; } switch (esc_values[0]) { case 25: { if (set) { enable_cursor(); } else { disable_cursor(); } return; } } if (term_callback != NULL) { term_callback(TERM_CB_DEC, esc_values_i, (uintptr_t)esc_values, c); } } static void linux_private_parse(void) { if (esc_values_i == 0) { return; } if (term_callback != NULL) { term_callback(TERM_CB_LINUX, esc_values_i, (uintptr_t)esc_values, 0); } } static void mode_toggle(uint8_t c) { if (esc_values_i == 0) { return; } bool set; switch (c) { case 'h': set = true; break; case 'l': set = false; break; default: return; } switch (esc_values[0]) { case 4: insert_mode = set; return; } if (term_callback != NULL) { term_callback(TERM_CB_MODE, esc_values_i, (uintptr_t)esc_values, c); } } static void control_sequence_parse(uint8_t c) { if (escape_offset == 2) { switch (c) { case '[': discard_next = true; goto cleanup; case '?': dec_private = true; return; } } if (c >= '0' && c <= '9') { if (esc_values_i == MAX_ESC_VALUES) { return; } rrr = true; esc_values[esc_values_i] *= 10; esc_values[esc_values_i] += c - '0'; return; } if (rrr == true) { esc_values_i++; rrr = false; if (c == ';') return; } else if (c == ';') { if (esc_values_i == MAX_ESC_VALUES) { return; } esc_values[esc_values_i] = 0; esc_values_i++; return; } size_t esc_default; switch (c) { case 'J': case 'K': case 'q': esc_default = 0; break; default: esc_default = 1; break; } for (size_t i = esc_values_i; i < MAX_ESC_VALUES; i++) { esc_values[i] = esc_default; } if (dec_private == true) { dec_private_parse(c); goto cleanup; } bool r = scroll_disable(); size_t x, y; get_cursor_pos(&x, &y); switch (c) { case 'F': x = 0; // FALLTHRU case 'A': { if (esc_values[0] > y) esc_values[0] = y; size_t orig_y = y; size_t dest_y = y - esc_values[0]; bool will_be_in_scroll_region = false; if ((scroll_top_margin >= dest_y && scroll_top_margin <= orig_y) || (scroll_bottom_margin >= dest_y && scroll_bottom_margin <= orig_y)) { will_be_in_scroll_region = true; } if (will_be_in_scroll_region && dest_y < scroll_top_margin) { dest_y = scroll_top_margin; } set_cursor_pos(x, dest_y); break; } case 'E': x = 0; // FALLTHRU case 'e': case 'B': { if (y + esc_values[0] > term_rows - 1) esc_values[0] = (term_rows - 1) - y; size_t orig_y = y; size_t dest_y = y + esc_values[0]; bool will_be_in_scroll_region = false; if ((scroll_top_margin >= orig_y && scroll_top_margin <= dest_y) || (scroll_bottom_margin >= orig_y && scroll_bottom_margin <= dest_y)) { will_be_in_scroll_region = true; } if (will_be_in_scroll_region && dest_y >= scroll_bottom_margin) { dest_y = scroll_bottom_margin - 1; } set_cursor_pos(x, dest_y); break; } case 'a': case 'C': if (x + esc_values[0] > term_cols - 1) esc_values[0] = (term_cols - 1) - x; set_cursor_pos(x + esc_values[0], y); break; case 'D': if (esc_values[0] > x) esc_values[0] = x; set_cursor_pos(x - esc_values[0], y); break; case 'c': if (term_callback != NULL) { term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0); } break; case 'd': esc_values[0] -= 1; if (esc_values[0] >= term_rows) esc_values[0] = term_rows - 1; set_cursor_pos(x, esc_values[0]); break; case 'G': case '`': esc_values[0] -= 1; if (esc_values[0] >= term_cols) esc_values[0] = term_cols - 1; set_cursor_pos(esc_values[0], y); break; case 'H': case 'f': esc_values[0] -= 1; esc_values[1] -= 1; if (esc_values[1] >= term_cols) esc_values[1] = term_cols - 1; if (esc_values[0] >= term_rows) esc_values[0] = term_rows - 1; set_cursor_pos(esc_values[1], esc_values[0]); break; case 'n': switch (esc_values[0]) { case 5: if (term_callback != NULL) { term_callback(TERM_CB_STATUS_REPORT, 0, 0, 0); } break; case 6: if (term_callback != NULL) { term_callback(TERM_CB_POS_REPORT, x + 1, y + 1, 0); } break; } break; case 'q': if (term_callback != NULL) { term_callback(TERM_CB_KBD_LEDS, esc_values[0], 0, 0); } break; case 'J': switch (esc_values[0]) { case 0: { size_t rows_remaining = term_rows - (y + 1); size_t cols_diff = term_cols - (x + 1); size_t to_clear = rows_remaining * term_cols + cols_diff; for (size_t i = 0; i < to_clear; i++) { raw_putchar(' '); } set_cursor_pos(x, y); break; } case 1: { set_cursor_pos(0, 0); bool b = false; for (size_t yc = 0; yc < term_rows; yc++) { for (size_t xc = 0; xc < term_cols; xc++) { raw_putchar(' '); if (xc == x && yc == y) { set_cursor_pos(x, y); b = true; break; } } if (b == true) break; } break; } case 2: case 3: clear(false); break; } break; case '@': for (size_t i = term_cols - 1; ; i--) { term_move_character(i + esc_values[0], y, i, y); set_cursor_pos(i, y); raw_putchar(' '); if (i == x) { break; } } set_cursor_pos(x, y); break; case 'P': for (size_t i = x + esc_values[0]; i < term_cols; i++) term_move_character(i - esc_values[0], y, i, y); set_cursor_pos(term_cols - esc_values[0], y); // FALLTHRU case 'X': for (size_t i = 0; i < esc_values[0]; i++) raw_putchar(' '); set_cursor_pos(x, y); break; case 'm': sgr(); break; case 's': get_cursor_pos(&saved_cursor_x, &saved_cursor_y); break; case 'u': set_cursor_pos(saved_cursor_x, saved_cursor_y); break; case 'K': switch (esc_values[0]) { case 0: { for (size_t i = x; i < term_cols; i++) raw_putchar(' '); set_cursor_pos(x, y); break; } case 1: { set_cursor_pos(0, y); for (size_t i = 0; i < x; i++) raw_putchar(' '); break; } case 2: { set_cursor_pos(0, y); for (size_t i = 0; i < term_cols; i++) raw_putchar(' '); set_cursor_pos(x, y); break; } } break; case 'r': scroll_top_margin = 0; scroll_bottom_margin = term_rows; if (esc_values_i > 0) { scroll_top_margin = esc_values[0] - 1; } if (esc_values_i > 1) { scroll_bottom_margin = esc_values[1]; } if (scroll_top_margin >= term_rows || scroll_bottom_margin > term_rows || scroll_top_margin >= (scroll_bottom_margin - 1)) { scroll_top_margin = 0; scroll_bottom_margin = term_rows; } set_cursor_pos(0, 0); break; case 'l': case 'h': mode_toggle(c); break; case ']': linux_private_parse(); break; } if (r) scroll_enable(); cleanup: control_sequence = false; escape = false; } static void restore_state(void) { bold = saved_state_bold; reverse_video = saved_state_reverse_video; current_charset = saved_state_current_charset; current_primary = saved_state_current_primary; term_restore_state(); } static void save_state(void) { term_save_state(); saved_state_bold = bold; saved_state_reverse_video = reverse_video; saved_state_current_charset = current_charset; saved_state_current_primary = current_primary; } static void escape_parse(uint8_t c) { escape_offset++; if (control_sequence == true) { control_sequence_parse(c); return; } if (csi == true) { csi = false; goto is_csi; } size_t x, y; get_cursor_pos(&x, &y); switch (c) { case '[': is_csi: for (size_t i = 0; i < MAX_ESC_VALUES; i++) esc_values[i] = 0; esc_values_i = 0; rrr = false; control_sequence = true; return; case '7': save_state(); break; case '8': restore_state(); break; case 'c': term_reinit(); clear(true); break; case 'D': if (y == scroll_bottom_margin - 1) { term_scroll(); set_cursor_pos(x, y); } else { set_cursor_pos(x, y + 1); } break; case 'E': if (y == scroll_bottom_margin - 1) { term_scroll(); set_cursor_pos(0, y); } else { set_cursor_pos(0, y + 1); } break; case 'M': // "Reverse linefeed" if (y == scroll_top_margin) { term_revscroll(); set_cursor_pos(0, y); } else { set_cursor_pos(0, y - 1); } break; case 'Z': if (term_callback != NULL) { term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0); } break; case '(': case ')': g_select = c - '\''; break; case '\e': if (term_runtime == false) { raw_putchar(c); } break; } escape = false; } static uint8_t dec_special_to_cp437(uint8_t c) { switch (c) { case '`': return 0x04; case '0': return 0xdb; case '-': return 0x18; case ',': return 0x1b; case '.': return 0x19; case 'a': return 0xb1; case 'f': return 0xf8; case 'g': return 0xf1; case 'h': return 0xb0; case 'j': return 0xd9; case 'k': return 0xbf; case 'l': return 0xda; case 'm': return 0xc0; case 'n': return 0xc5; case 'q': return 0xc4; case 's': return 0x5f; case 't': return 0xc3; case 'u': return 0xb4; case 'v': return 0xc1; case 'w': return 0xc2; case 'x': return 0xb3; case 'y': return 0xf3; case 'z': return 0xf2; case '~': return 0xfa; case '_': return 0xff; case '+': return 0x1a; case '{': return 0xe3; case '}': return 0x9c; } return c; } void term_putchar(uint8_t c) { if (discard_next || (term_runtime == true && (c == 0x18 || c == 0x1a))) { discard_next = false; escape = false; csi = false; control_sequence = false; g_select = 0; return; } if (escape == true) { escape_parse(c); return; } if (g_select) { g_select--; switch (c) { case 'B': charsets[g_select] = CHARSET_DEFAULT; break; case '0': charsets[g_select] = CHARSET_DEC_SPECIAL; break; } g_select = 0; return; } size_t x, y; get_cursor_pos(&x, &y); switch (c) { case 0x00: case 0x7f: return; case 0x9b: csi = true; // FALLTHRU case '\e': escape_offset = 0; escape = true; return; case '\t': if ((x / TERM_TABSIZE + 1) >= term_cols) { set_cursor_pos(term_cols - 1, y); return; } set_cursor_pos((x / TERM_TABSIZE + 1) * TERM_TABSIZE, y); return; case 0x0b: case 0x0c: case '\n': if (y == scroll_bottom_margin - 1) { term_scroll(); set_cursor_pos(0, y); } else { set_cursor_pos(0, y + 1); } return; case '\b': set_cursor_pos(x - 1, y); return; case '\r': set_cursor_pos(0, y); return; case '\a': // The bell is handled by the kernel if (term_callback != NULL) { term_callback(TERM_CB_BELL, 0, 0, 0); } return; case 14: // Move to G1 set current_charset = 1; return; case 15: // Move to G0 set current_charset = 0; return; } if (insert_mode == true) { for (size_t i = term_cols - 1; ; i--) { term_move_character(i + 1, y, i, y); if (i == x) { break; } } } // Translate character set switch (charsets[current_charset]) { case CHARSET_DEFAULT: break; case CHARSET_DEC_SPECIAL: c = dec_special_to_cp437(c); } raw_putchar(c); }