423 lines
9.7 KiB
C
Executable File
423 lines
9.7 KiB
C
Executable File
#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]);
|
|
}
|
|
}
|