os/vga_drv/vtconsole.c
2021-02-28 13:46:33 -06:00

423 lines
9.7 KiB
C

#include "vtconsole.h"
#include <stdlib.h>
#include <ctype.h>
#define min(a, b) ((a) < (b) ? (a) : (b))
/* --- Constructor/Destructor ----------------------------------------------- */
vtconsole_t *vtconsole(int width, int height, vtc_paint_handler_t on_paint, vtc_cursor_handler_t on_move)
{
vtconsole_t *vtc = malloc(sizeof(vtconsole_t));
vtc->width = width;
vtc->height = height;
vtc->ansiparser = (vtansi_parser_t){VTSTATE_ESC, {{0, 0}}, 0};
vtc->attr = VTC_DEFAULT_ATTR;
vtc->buffer = malloc(width * height * sizeof(vtcell_t));
vtc->cursor = (vtcursor_t){0, 0};
vtc->on_paint = on_paint;
vtc->on_move = on_move;
vtconsole_clear(vtc, 0, 0, width, height - 1);
return vtc;
}
void vtconsole_delete(vtconsole_t *vtc)
{
free(vtc->buffer);
free(vtc);
}
/* --- Internal methodes ---------------------------------------------------- */
void vtconsole_clear(vtconsole_t *vtc, int fromx, int fromy, int tox, int toy)
{
for (int i = fromx + fromy * vtc->width; i < tox + toy * vtc->width; i++)
{
vtcell_t *cell = &vtc->buffer[i];
cell->attr = VTC_DEFAULT_ATTR;
cell->c = ' ';
if (vtc->on_paint)
{
vtc->on_paint(vtc, cell, i % vtc->width, i / vtc->width);
}
}
}
void vtconsole_scroll(vtconsole_t *vtc, int lines)
{
if (lines == 0) return;
lines = lines > vtc->height ? vtc->height : lines;
// Scroll the screen by number of $lines.
for (int i = 0; i < ((vtc->width * vtc->height) - (vtc->width * lines)); i++)
{
vtc->buffer[i] = vtc->buffer[i + (vtc->width * lines)];
if (vtc->on_paint)
{
vtc->on_paint(vtc, &vtc->buffer[i], i % vtc->width, i / vtc->width);
}
}
// Clear the last $lines.
for (int i = ((vtc->width * vtc->height) - (vtc->width * lines)); i < vtc->width * vtc->height; i++)
{
vtcell_t *cell = &vtc->buffer[i];
cell->attr = VTC_DEFAULT_ATTR;
cell->c = ' ';
if (vtc->on_paint)
{
vtc->on_paint(vtc, &vtc->buffer[i], i % vtc->width, i / vtc->width);
}
}
// Move the cursor up $lines
if (vtc->cursor.y > 0)
{
vtc->cursor.y -= lines;
if (vtc->cursor.y < 0) vtc->cursor.y = 0;
if (vtc->on_move)
{
vtc->on_move(vtc, &vtc->cursor);
}
}
}
// Append a new line
void vtconsole_newline(vtconsole_t *vtc)
{
vtc->cursor.x = 0;
vtc->cursor.y++;
if (vtc->cursor.y == vtc->height)
{
vtconsole_scroll(vtc, 1);
}
if (vtc->on_move)
{
vtc->on_move(vtc, &vtc->cursor);
}
}
// Append character to the console buffer.
void vtconsole_append(vtconsole_t *vtc, char c)
{
if (c == '\n')
{
vtconsole_newline(vtc);
}
else if (c == '\r')
{
vtc->cursor.x = 0;
if (vtc->on_move)
{
vtc->on_move(vtc, &vtc->cursor);
}
}
else if (c == '\t')
{
int n = 8 - (vtc->cursor.x % 8);
for (int i = 0; i < n; i++)
{
vtconsole_append(vtc, ' ');
}
}
else if (c == '\b')
{
if (vtc->cursor.x > 0)
{
vtc->cursor.x--;
}
else
{
vtc->cursor.y--;
vtc->cursor.x = vtc->width - 1;
}
if (vtc->on_move)
{
vtc->on_move(vtc, &vtc->cursor);
}
}
else
{
if (vtc->cursor.x >= vtc->width)
vtconsole_newline(vtc);
vtcell_t *cell = &vtc->buffer[vtc->cursor.x + vtc->cursor.y * vtc->width];
cell->c = c;
cell->attr = vtc->attr;
if (vtc->on_paint)
{
vtc->on_paint(vtc, cell, vtc->cursor.x, vtc->cursor.y);
}
vtc->cursor.x++;
if (vtc->on_move)
{
vtc->on_move(vtc, &vtc->cursor);
}
}
}
// Moves the cursor to row n, column m. The values are 1-based,
void vtconsole_csi_cup(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
{
if (count == 1 && stack[0].empty)
{
vtc->cursor.x = 0;
vtc->cursor.y = 0;
}
else if (count == 2)
{
if (stack[0].empty)
{
vtc->cursor.y = 0;
}
else
{
vtc->cursor.y = min(stack[0].value - 1, vtc->height - 1);
}
if (stack[1].empty)
{
vtc->cursor.y = 0;
}
else
{
vtc->cursor.x = min(stack[1].value - 1, vtc->width - 1);
}
}
if (vtc->on_move)
{
vtc->on_move(vtc, &vtc->cursor);
}
}
// Clears part of the screen.
void vtconsole_csi_ed(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
{
(void)(count);
vtcursor_t cursor = vtc->cursor;
if (stack[0].empty)
{
vtconsole_clear(vtc, cursor.x, cursor.y, vtc->width, vtc->height - 1);
}
else
{
int attr = stack[0].value;
if (attr == 0)
vtconsole_clear(vtc, cursor.x, cursor.y, vtc->width, vtc->height - 1);
else if (attr == 1)
vtconsole_clear(vtc, 0, 0, cursor.x, cursor.y);
else if (attr == 2)
vtconsole_clear(vtc, 0, 0, vtc->width, vtc->height - 1);
}
}
// Erases part of the line.
void vtconsole_csi_el(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
{
(void)(count);
vtcursor_t cursor = vtc->cursor;
if (stack[0].empty)
{
vtconsole_clear(vtc, cursor.x, cursor.y, vtc->width, cursor.y);
}
else
{
int attr = stack[0].value;
if (attr == 0)
vtconsole_clear(vtc, cursor.x, cursor.y, vtc->width, cursor.y);
else if (attr == 1)
vtconsole_clear(vtc, 0, cursor.y, cursor.x, cursor.y);
else if (attr == 2)
vtconsole_clear(vtc, 0, cursor.y, vtc->width, cursor.y);
}
}
// Sets the appearance of the following characters
void vtconsole_csi_sgr(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
{
for (int i = 0; i < count; i++)
{
if (stack[i].empty || stack[i].value == 0)
{
vtc->attr = VTC_DEFAULT_ATTR;
}
else
{
int attr = stack[i].value;
if (attr == 1) // Increased intensity
{
vtc->attr.bright = true;
}
else if (attr >= 30 && attr <= 37) // Set foreground color
{
vtc->attr.fg = attr - 30;
}
else if (attr >= 40 && attr <= 47) // Set background color
{
vtc->attr.bg = attr - 40;
}
}
}
}
// Process ANSI escape sequences and append character to the console buffer.
void vtconsole_process(vtconsole_t *vtc, char c)
{
vtansi_parser_t *parser = &vtc->ansiparser;
switch (parser->state)
{
case VTSTATE_ESC:
if (c == '\033')
{
parser->state = VTSTATE_BRACKET;
parser->index = 0;
parser->stack[parser->index].value = 0;
parser->stack[parser->index].empty = true;
}
else
{
parser->state = VTSTATE_ESC;
vtconsole_append(vtc, c);
}
break;
case VTSTATE_BRACKET:
if (c == '[')
{
parser->state = VTSTATE_ATTR;
}
else
{
parser->state = VTSTATE_ESC;
vtconsole_append(vtc, c);
}
break;
case VTSTATE_ATTR:
if (isdigit(c))
{
parser->stack[parser->index].value *= 10;
parser->stack[parser->index].value += (c - '0');
parser->stack[parser->index].empty = false;
}
else
{
if ((parser->index) < VTC_ANSI_PARSER_STACK_SIZE)
{
parser->index++;
}
parser->stack[parser->index].value = 0;
parser->stack[parser->index].empty = true;
parser->state = VTSTATE_ENDVAL;
}
break;
default:
break;
}
if (parser->state == VTSTATE_ENDVAL)
{
if (c == ';')
{
parser->state = VTSTATE_ATTR;
}
else
{
switch (c)
{
case 'A':
/* Cursor up P1 rows */
break;
case 'B':
/* Cursor down P1 rows */
break;
case 'C':
/* Cursor right P1 columns */
break;
case 'D':
/* Cursor left P1 columns */
break;
case 'E':
/* Cursor to first column of line P1 rows down from current */
break;
case 'F':
/* Cursor to first column of line P1 rows up from current */
break;
case 'G':
/* Cursor to column P1 */
break;
case 'd':
/* Cursor left P1 columns */
break;
case 'H':
vtconsole_csi_cup(vtc, parser->stack, parser->index);
break;
case 'J':
vtconsole_csi_ed(vtc, parser->stack, parser->index);
break;
case 'K':
vtconsole_csi_el(vtc, parser->stack, parser->index);
break;
case 'm':
vtconsole_csi_sgr(vtc, parser->stack, parser->index);
break;
}
parser->state = VTSTATE_ESC;
}
}
}
/* --- Methodes ------------------------------------------------------------- */
void vtconsole_putchar(vtconsole_t *vtc, char c)
{
vtconsole_process(vtc, c);
}
void vtconsole_write(vtconsole_t *vtc, const char *buffer, unsigned int size)
{
for (unsigned int i = 0; i < size; i++)
{
vtconsole_process(vtc, buffer[i]);
}
}