rulimine/stage23/lib/term.s2.c
2021-11-21 01:18:39 +01:00

987 lines
26 KiB
C

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <lib/term.h>
#include <lib/real.h>
#include <lib/image.h>
#include <lib/blib.h>
#include <drivers/vga_textmode.h>
#include <lib/print.h>
// Tries to implement this standard for terminfo
// https://man7.org/linux/man-pages/man4/console_codes.4.html
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_swap_palette = 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;
}
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_swap_palette)(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 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) {
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_swap_palette = text_swap_palette;
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 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 '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"
set_cursor_pos(x, 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);
}