#include #include #include #include "term.h" #define MAX_PARAMS 2 static char *colors[8] = { "000000", "ff0000", "00ff00", "ffff00", "0000ff", "ff00ff", "00ffff", "ffffff", }; void term_init(struct term *t, int num_rows, int num_cols, int pango) { t->state = TERM_STATE_GROUND; t->cursor.x = 0; t->cursor.y = 0; t->num_rows = num_rows; t->num_cols = num_cols; t->rows = calloc(t->num_rows, sizeof(struct row)); t->param_idx = 0; t->params[0] = 0; t->params[1] = 0; t->pango = pango; t->sgra.normal = 1; for (int i = 0; i < t->num_rows; i++) { t->rows[i].cells = calloc(t->num_cols, sizeof(struct cell)); for (int j = 0; j < t->num_cols; j++) { struct cell *cur_cell = &t->rows[i].cells[j]; cur_cell->c = ' '; cur_cell->sgra.normal = 1; } } } static void term_cursor_move_up(struct term *t, int off) { t->cursor.y -= off; if (t->cursor.y < 0) { t->cursor.y = 0; } } static void term_cursor_move_left(struct term *t, int off) { t->cursor.x -= off; if (t->cursor.x < 0) { t->cursor.x = 0; } } static void term_cursor_move_down(struct term *t, int off) { t->cursor.y += off; if (t->cursor.y > t->num_rows - 1) { t->cursor.y = t->num_rows - 1; } } static void term_cursor_move_right(struct term *t, int off) { t->cursor.x += off; if (t->cursor.x > t->num_cols - 1) { t->cursor.x = t->num_cols - 1; } } static void term_line_feed(struct term *t) { if (t->cursor.y == t->num_rows - 1) { for (int i = 1; i < t->num_rows; i++) { memcpy(t->rows[i-1].cells, t->rows[i].cells, t->num_cols * sizeof(struct cell)); } for (int i = 0; i < t->num_cols; i++) { t->rows[t->cursor.y].cells[i].c = ' '; } } else { t->cursor.y++; } } static void term_print(struct term *t, char c) { struct row *cur_row = &t->rows[t->cursor.y]; struct cell *cur_cell = &cur_row->cells[t->cursor.x]; cur_cell->c = c; cur_cell->sgra = t->sgra; if (t->cursor.x >= t->num_cols - 1) { term_line_feed(t); t->cursor.x = 0; } else { t->cursor.x++; } } static void term_clear(struct term *t) { t->param_idx = 0; t->params[0] = 0; t->params[1] = 0; // TODO: also clear: // private flag? // intermediate characters? // final character? } static void term_add_param_digit(struct term *t, char c) { t->params[t->param_idx] *= 10; t->params[t->param_idx] += c - '0'; } static void term_next_param(struct term *t) { if (t->param_idx > TERM_MAX_PARAMS - 1) return; t->params[++t->param_idx] = 0; } static void term_param(struct term *t, char c) { switch (c) { case 0x30 ... 0x39: term_add_param_digit(t, c); break; case 0x3b: term_next_param(t); break; } } static void term_execute(struct term *t, char c) { switch (c) { case '\b': term_cursor_move_left(t, 1); break; case '\f': case '\n': case '\v': term_line_feed(t); break; case '\r': t->cursor.x = 0; break; case '\t': term_print(t, ' '); term_print(t, ' '); term_print(t, ' '); term_print(t, ' '); break; } } static void term_collect(struct term *t, char c) { // not implemented } static void term_csi_erase_in_display(struct term *t) { // inclusive int start_x; int start_y; // exclusive int end_x; int end_y; switch (t->params[0]) { case 0: start_x = t->cursor.x; start_y = t->cursor.y; end_x = t->num_cols; end_y = t->num_rows; break; case 1: start_x = 0; start_y = 0; end_x = t->cursor.x + 1; end_y = t->cursor.y + 1; break; // We don't have a scrollback buffer, so 2 and 3 are equivalent case 2: case 3: start_x = 0; start_y = 0; end_x = t->num_cols; end_y = t->num_rows; break; default: return; } for (int i = start_y; i < end_y; i++) { for (int j = start_x; j < end_x; j++) { struct cell *cur_cell = &t->rows[i].cells[j]; cur_cell->c = ' '; cur_cell->sgra.normal = 1; } } } static void term_csi_erase_in_line(struct term *t) { int start_x; // inclusive int end_x; // exclusive switch (t->params[0]) { case 0: start_x = t->cursor.x; end_x = t->num_cols; break; case 1: start_x = 0; end_x = t->cursor.x + 1; break; case 2: start_x = 0; end_x = t->num_cols; break; } for (int j = start_x; j < end_x; j++) { struct cell *cur_cell = &t->rows[t->cursor.y].cells[j]; cur_cell->c = ' '; cur_cell->sgra.normal = 1; } } static void term_csi_sgr(struct term *t) { for (int i = 0; i < t->param_idx + 1; i++) { int p = t->params[i]; if (0 == p) { t->sgra.normal = 1; } else { t->sgra.normal = 0; switch (p) { case 1: t->sgra.bold = 1; break; case 30 ... 37: t->sgra.fg = p - 30; break; case 40 ... 47: t->sgra.bg = p - 40; break; } } } } static void term_csi_dispatch(struct term *t, char c) { int p0 = t->params[0]; int p1 = t->params[1]; switch (c) { case 'A': term_cursor_move_up(t, p0 == 0 ? 1 : p0); break; case 'B': term_cursor_move_down(t, p0 == 0 ? 1 : p0); break; case 'C': term_cursor_move_right(t, p0 == 0 ? 1 : p0); break; case 'D': term_cursor_move_left(t, p0 == 0 ? 1 : p0); break; case 'E': t->cursor.x = 0; term_cursor_move_down(t, p0 == 0 ? 1 : p0); break; case 'F': t->cursor.x = 0; term_cursor_move_up(t, p0 == 0 ? 1 : p0); break; case 'G': t->cursor.x = p0; break; case 'H': t->cursor.y = p0 == 0 ? 0 : p0 - 1; t->cursor.x = p1 == 0 ? 0 : p1 - 1; break; case 'J': term_csi_erase_in_display(t); break; case 'K': term_csi_erase_in_line(t); break; case 'm': term_csi_sgr(t); break; } } static void term_esc_dispatch(struct term *t, char c) { switch (c) { case 'D': term_line_feed(t); break; case 'E': t->cursor.x = 0; term_line_feed(t); break; } } void term_putc(struct term *t, char c) { // Loosely based on a very (very) limited subset of the state machine at https://vt100.net/emu/dec_ansi_parser // Expect most things to be unsupported, irrelevant or straight out broken ^__- switch (t->state) { case TERM_STATE_GROUND: // events switch (c) { case 0x00 ... 0x17: case 0x19: case 0x1c ... 0x1f: term_execute(t, c); break; case 0x1b: t->state = TERM_STATE_ESC; break; case 0x20 ... 0x7f: term_print(t, c); break; } break; case TERM_STATE_ESC: // entry term_clear(t); // events switch (c) { case 0x00 ... 0x17: case 0x19: case 0x1c ... 0x1f: term_execute(t, c); break; case 0x20 ... 0x2f: term_collect(t, c); t->state = TERM_STATE_ESC_INTERMEDIATE; break; case 0x30 ... 0x4f: case 0x51 ... 0x57: case 0x59: case 0x5a: case 0x5c: case 0x60 ... 0x7e: term_esc_dispatch(t, c); t->state = TERM_STATE_GROUND; break; case 0x5b: t->state = TERM_STATE_CSI_ENTRY; break; } break; case TERM_STATE_ESC_INTERMEDIATE: switch (c) { case 0x00 ... 0x17: case 0x19: case 0x1c ... 0x1f: term_execute(t, c); break; case 0x1b: t->state = TERM_STATE_ESC; break; case 0x20 ... 0x2f: term_collect(t, c); break; case 0x30 ... 0x7e: term_esc_dispatch(t, c); t->state = TERM_STATE_GROUND; break; } break; case TERM_STATE_CSI_ENTRY: // entry term_clear(t); // events switch (c) { case 0x00 ... 0x17: case 0x19: case 0x1c ... 0x1f: term_execute(t, c); break; case 0x1b: t->state = TERM_STATE_ESC; break; case 0x20 ... 0x2f: term_collect(t, c); t->state = TERM_STATE_CSI_INTERMEDIATE; break; case 0x3a: t->state = TERM_STATE_CSI_IGNORE; break; case 0x3c ... 0x3f: term_collect(t, c); break; case 0x30 ... 0x39: case 0x3b: term_param(t, c); t->state = TERM_STATE_CSI_PARAM; break; case 0x40 ... 0x7e: term_csi_dispatch(t, c); t->state = TERM_STATE_GROUND; break; } break; case TERM_STATE_CSI_PARAM: // events switch (c) { case 0x00 ... 0x17: case 0x19: case 0x1c ... 0x1f: term_execute(t, c); break; case 0x1b: t->state = TERM_STATE_ESC; break; case 0x20 ... 0x2f: term_collect(t, c); t->state = TERM_STATE_CSI_INTERMEDIATE; break; case 0x30 ... 0x39: case 0x3b: term_param(t, c); break; case 0x3a: case 0x3c ... 0x3f: t->state = TERM_STATE_CSI_IGNORE; break; case 0x40 ... 0x7e: term_csi_dispatch(t, c); t->state = TERM_STATE_GROUND; break; } break; case TERM_STATE_CSI_INTERMEDIATE: // events switch (c) { case 0x00 ... 0x17: case 0x19: case 0x1c ... 0x1f: term_execute(t, c); break; case 0x1b: t->state = TERM_STATE_ESC; break; case 0x20 ... 0x2f: term_collect(t, c); break; case 0x30 ... 0x3f: t->state = TERM_STATE_CSI_IGNORE; break; case 0x40 ... 0x7e: term_csi_dispatch(t, c); t->state = TERM_STATE_GROUND; break; } break; case TERM_STATE_CSI_IGNORE: // events switch (c) { case 0x00 ... 0x17: case 0x19: case 0x1c ... 0x1f: term_execute(t, c); break; case 0x1b: t->state = TERM_STATE_ESC; break; case 0x40 ... 0x7e: t->state = TERM_STATE_GROUND; break; } break; } } void term_put_string(struct term *t, char *str) { int len = strlen(str); for (int i = 0; i < len; i++) { term_putc(t, str[i]); } } static char *xml_entity(char *str, char c) { switch (c) { case '<': strncpy(str, "<", 9); break; case '>': strncpy(str, ">", 9); break; default: str[0] = c; str[1] = '\0'; break; } return str; } char *term_to_string(struct term *t) { // TODO: Allocate once // TODO: don't allocate too much if pango is not used. int str_size = t->num_rows * t->num_cols * 128 + t->num_rows; char *ret = calloc(str_size, sizeof(char)); char *s = ret; char cell_str[9]; for (int i = 0; i < t->num_rows; i++) { for (int j = 0; j < t->num_cols; j++) { struct cell cur_cell = t->rows[i].cells[j]; if (t->pango && 0 == cur_cell.sgra.normal) { // TODO: use snprintf... s += sprintf(s, "%s", cur_cell.sgra.bold ? "bold" : "normal", colors[cur_cell.sgra.fg], colors[cur_cell.sgra.bg], xml_entity(cell_str, cur_cell.c)); } else { *s++ = cur_cell.c; } } if (i == t->num_rows - 1) { *s++ = '\0'; } else { *s++ = '\n'; } } return ret; } void term_free(struct term *t) { if (t->rows != NULL) { for(int i = 0; i < t->num_rows; i++) { if (t->rows[i].cells != NULL) { free(t->rows[i].cells); } } free(t->rows); } }