toaruos/lib/termemu.c

592 lines
15 KiB
C

/* vim: tabstop=4 shiftwidth=4 noexpandtab
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2014-2018 K. Lange
*
* Portable library for terminal emulation.
*/
#ifdef _KERNEL_
# include <kernel/system.h>
# include <kernel/types.h>
# include <kernel/logging.h>
static void _spin_lock(volatile int * foo) { return; }
static void _spin_unlock(volatile int * foo) { return; }
# define rgba(r,g,b,a) (((uint32_t)a * 0x1000000) + ((uint32_t)r * 0x10000) + ((uint32_t)g * 0x100) + ((uint32_t)b * 0x1))
# define rgb(r,g,b) rgba(r,g,b,0xFF)
# define atof(i) (0.0f)
#include <toaru/termemu.h>
#else
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <toaru/graphics.h>
#include <toaru/termemu.h>
#include <toaru/spinlock.h>
#define _spin_lock spin_lock
#define _spin_unlock spin_unlock
#endif
#define MAX_ARGS 1024
static wchar_t box_chars[] = L"▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥";
/* Returns the lower of two shorts */
static uint16_t min(uint16_t a, uint16_t b) {
return (a < b) ? a : b;
}
/* Returns the higher of two shorts */
static uint16_t max(uint16_t a, uint16_t b) {
return (a > b) ? a : b;
}
/* Write the contents of the buffer, as they were all non-escaped data. */
static void ansi_dump_buffer(term_state_t * s) {
for (int i = 0; i < s->buflen; ++i) {
s->callbacks->writer(s->buffer[i]);
}
}
/* Add to the internal buffer for the ANSI parser */
static void ansi_buf_add(term_state_t * s, char c) {
if (s->buflen >= TERM_BUF_LEN-1) return;
s->buffer[s->buflen] = c;
s->buflen++;
s->buffer[s->buflen] = '\0';
}
static int to_eight(uint32_t codepoint, char * out) {
memset(out, 0x00, 7);
if (codepoint < 0x0080) {
out[0] = (char)codepoint;
} else if (codepoint < 0x0800) {
out[0] = 0xC0 | (codepoint >> 6);
out[1] = 0x80 | (codepoint & 0x3F);
} else if (codepoint < 0x10000) {
out[0] = 0xE0 | (codepoint >> 12);
out[1] = 0x80 | ((codepoint >> 6) & 0x3F);
out[2] = 0x80 | (codepoint & 0x3F);
} else if (codepoint < 0x200000) {
out[0] = 0xF0 | (codepoint >> 18);
out[1] = 0x80 | ((codepoint >> 12) & 0x3F);
out[2] = 0x80 | ((codepoint >> 6) & 0x3F);
out[3] = 0x80 | ((codepoint) & 0x3F);
} else if (codepoint < 0x4000000) {
out[0] = 0xF8 | (codepoint >> 24);
out[1] = 0x80 | (codepoint >> 18);
out[2] = 0x80 | ((codepoint >> 12) & 0x3F);
out[3] = 0x80 | ((codepoint >> 6) & 0x3F);
out[4] = 0x80 | ((codepoint) & 0x3F);
} else {
out[0] = 0xF8 | (codepoint >> 30);
out[1] = 0x80 | ((codepoint >> 24) & 0x3F);
out[2] = 0x80 | ((codepoint >> 18) & 0x3F);
out[3] = 0x80 | ((codepoint >> 12) & 0x3F);
out[4] = 0x80 | ((codepoint >> 6) & 0x3F);
out[5] = 0x80 | ((codepoint) & 0x3F);
}
return strlen(out);
}
static void _ansi_put(term_state_t * s, char c) {
term_callbacks_t * callbacks = s->callbacks;
switch (s->escape) {
case 0:
/* We are not escaped, check for escape character */
if (c == ANSI_ESCAPE) {
/*
* Enable escape mode, setup a buffer,
* fill the buffer, get out of here.
*/
s->escape = 1;
s->buflen = 0;
ansi_buf_add(s, c);
return;
} else if (c == 0) {
return;
} else {
if (s->box && c >= 'a' && c <= 'z') {
char buf[7];
char *w = (char *)&buf;
to_eight(box_chars[c-'a'], w);
while (*w) {
callbacks->writer(*w);
w++;
}
} else {
callbacks->writer(c);
}
}
break;
case 1:
/* We're ready for [ */
if (c == ANSI_BRACKET) {
s->escape = 2;
ansi_buf_add(s, c);
} else if (c == ANSI_BRACKET_RIGHT) {
s->escape = 3;
ansi_buf_add(s, c);
} else if (c == ANSI_OPEN_PAREN) {
s->escape = 4;
ansi_buf_add(s, c);
} else if (c == 'T') {
s->escape = 5;
ansi_buf_add(s, c);
} else {
/* This isn't a bracket, we're not actually escaped!
* Get out of here! */
ansi_dump_buffer(s);
callbacks->writer(c);
s->escape = 0;
s->buflen = 0;
return;
}
break;
case 2:
if (c >= ANSI_LOW && c <= ANSI_HIGH) {
/* Woah, woah, let's see here. */
char * pch; /* tokenizer pointer */
char * save; /* strtok_r pointer */
char * argv[MAX_ARGS]; /* escape arguments */
/* Get rid of the front of the buffer */
strtok_r(s->buffer,"[",&save);
pch = strtok_r(NULL,";",&save);
/* argc = Number of arguments, obviously */
int argc = 0;
while (pch != NULL) {
argv[argc] = (char *)pch;
++argc;
if (argc > MAX_ARGS)
break;
pch = strtok_r(NULL,";",&save);
}
/* Alright, let's do this */
switch (c) {
case ANSI_EXT_IOCTL:
{
if (argc > 0) {
int arg = atoi(argv[0]);
switch (arg) {
case 1:
callbacks->redraw_cursor();
break;
default:
break;
}
}
}
break;
case ANSI_SCP:
{
s->save_x = callbacks->get_csr_x();
s->save_y = callbacks->get_csr_y();
}
break;
case ANSI_RCP:
{
callbacks->set_csr(s->save_x, s->save_y);
}
break;
case ANSI_SGR:
/* Set Graphics Rendition */
if (argc == 0) {
/* Default = 0 */
argv[0] = "0";
argc = 1;
}
for (int i = 0; i < argc; ++i) {
int arg = atoi(argv[i]);
if (arg >= 100 && arg < 110) {
/* Bright background */
s->bg = 8 + (arg - 100);
s->flags |= ANSI_SPECBG;
} else if (arg >= 90 && arg < 100) {
/* Bright foreground */
s->fg = 8 + (arg - 90);
} else if (arg >= 40 && arg < 49) {
/* Set background */
s->bg = arg - 40;
s->flags |= ANSI_SPECBG;
} else if (arg == 49) {
s->bg = TERM_DEFAULT_BG;
s->flags &= ~ANSI_SPECBG;
} else if (arg >= 30 && arg < 39) {
/* Set Foreground */
s->fg = arg - 30;
} else if (arg == 39) {
/* Default Foreground */
s->fg = 7;
} else if (arg == 24) {
/* Underline off */
s->flags &= ~ANSI_UNDERLINE;
} else if (arg == 23) {
/* Oblique off */
s->flags &= ~ANSI_ITALIC;
} else if (arg == 21 || arg == 22) {
/* Bold off */
s->flags &= ~ANSI_BOLD;
} else if (arg == 9) {
/* X-OUT */
s->flags |= ANSI_CROSS;
} else if (arg == 7) {
/* INVERT: Swap foreground / background */
uint32_t temp = s->fg;
s->fg = s->bg;
s->bg = temp;
} else if (arg == 6) {
/* proprietary RGBA color support */
if (i == 0) { break; }
if (i < argc) {
int r = atoi(argv[i+1]);
int g = atoi(argv[i+2]);
int b = atoi(argv[i+3]);
int a = atoi(argv[i+4]);
if (a == 0) a = 1; /* Override a = 0 */
uint32_t c = rgba(r,g,b,a);
if (atoi(argv[i-1]) == 48) {
s->bg = c;
s->flags |= ANSI_SPECBG;
} else if (atoi(argv[i-1]) == 38) {
s->fg = c;
}
i += 4;
}
} else if (arg == 5) {
/* Supposed to be blink; instead, support X-term 256 colors */
if (i == 0) { break; }
if (i < argc) {
if (atoi(argv[i-1]) == 48) {
/* Background to i+1 */
s->bg = atoi(argv[i+1]);
s->flags |= ANSI_SPECBG;
} else if (atoi(argv[i-1]) == 38) {
/* Foreground to i+1 */
s->fg = atoi(argv[i+1]);
}
++i;
}
} else if (arg == 4) {
/* UNDERLINE */
s->flags |= ANSI_UNDERLINE;
} else if (arg == 3) {
/* ITALIC: Oblique */
s->flags |= ANSI_ITALIC;
} else if (arg == 2) {
/* Konsole RGB color support */
if (i == 0) { break; }
if (i < argc - 2) {
int r = atoi(argv[i+1]);
int g = atoi(argv[i+2]);
int b = atoi(argv[i+3]);
uint32_t c = rgb(r,g,b);
if (atoi(argv[i-1]) == 48) {
/* Background to i+1 */
s->bg = c;
s->flags |= ANSI_SPECBG;
} else if (atoi(argv[i-1]) == 38) {
/* Foreground to i+1 */
s->fg = c;
}
i += 3;
}
} else if (arg == 1) {
/* BOLD/BRIGHT: Brighten the output color */
s->flags |= ANSI_BOLD;
} else if (arg == 0) {
/* Reset everything */
s->fg = TERM_DEFAULT_FG;
s->bg = TERM_DEFAULT_BG;
s->flags = TERM_DEFAULT_FLAGS;
}
}
break;
case ANSI_SHOW:
if (argc > 0) {
if (!strcmp(argv[0], "?1049")) {
callbacks->cls(2);
callbacks->set_csr(0,0);
} else if (!strcmp(argv[0], "?1000")) {
s->mouse_on = 1;
} else if (!strcmp(argv[0], "?1002")) {
s->mouse_on = 2;
} else if (!strcmp(argv[0], "?25")) {
callbacks->set_csr_on(1);
}
}
break;
case ANSI_HIDE:
if (argc > 0) {
if (!strcmp(argv[0], "?1049")) {
/* TODO: Unimplemented */
} else if (!strcmp(argv[0], "?1000")) {
s->mouse_on = 0;
} else if (!strcmp(argv[0], "?1002")) {
s->mouse_on = 0;
} else if (!strcmp(argv[0], "?25")) {
callbacks->set_csr_on(0);
}
}
break;
case ANSI_CUF:
{
int i = 1;
if (argc) {
i = atoi(argv[0]);
}
callbacks->set_csr(min(callbacks->get_csr_x() + i, s->width - 1), callbacks->get_csr_y());
}
break;
case ANSI_CUU:
{
int i = 1;
if (argc) {
i = atoi(argv[0]);
}
callbacks->set_csr(callbacks->get_csr_x(), max(callbacks->get_csr_y() - i, 0));
}
break;
case ANSI_CUD:
{
int i = 1;
if (argc) {
i = atoi(argv[0]);
}
callbacks->set_csr(callbacks->get_csr_x(), min(callbacks->get_csr_y() + i, s->height - 1));
}
break;
case ANSI_CUB:
{
int i = 1;
if (argc) {
i = atoi(argv[0]);
}
callbacks->set_csr(max(callbacks->get_csr_x() - i,0), callbacks->get_csr_y());
}
break;
case ANSI_CHA:
if (argc < 1) {
callbacks->set_csr(0,callbacks->get_csr_y());
} else {
callbacks->set_csr(min(max(atoi(argv[0]), 1), s->width) - 1, callbacks->get_csr_y());
}
break;
case ANSI_CUP:
if (argc < 2) {
callbacks->set_csr(0,0);
} else {
callbacks->set_csr(min(max(atoi(argv[1]), 1), s->width) - 1, min(max(atoi(argv[0]), 1), s->height) - 1);
}
break;
case ANSI_ED:
if (argc < 1) {
callbacks->cls(0);
} else {
callbacks->cls(atoi(argv[0]));
}
break;
case ANSI_EL:
{
int what = 0, x = 0, y = 0;
if (argc >= 1) {
what = atoi(argv[0]);
}
if (what == 0) {
x = callbacks->get_csr_x();
y = s->width;
} else if (what == 1) {
x = 0;
y = callbacks->get_csr_x();
} else if (what == 2) {
x = 0;
y = s->width;
}
for (int i = x; i < y; ++i) {
callbacks->set_cell(i, callbacks->get_csr_y(), ' ');
}
}
break;
case ANSI_DSR:
{
char out[24];
sprintf(out, "\033[%d;%dR", callbacks->get_csr_y() + 1, callbacks->get_csr_x() + 1);
callbacks->input_buffer_stuff(out);
}
break;
case ANSI_SU:
{
int how_many = 1;
if (argc > 0) {
how_many = atoi(argv[0]);
}
callbacks->scroll(how_many);
}
break;
case ANSI_SD:
{
int how_many = 1;
if (argc > 0) {
how_many = atoi(argv[0]);
}
callbacks->scroll(-how_many);
}
break;
case 'X':
{
int how_many = 1;
if (argc > 0) {
how_many = atoi(argv[0]);
}
for (int i = 0; i < how_many; ++i) {
callbacks->writer(' ');
}
}
break;
case 'd':
if (argc < 1) {
callbacks->set_csr(callbacks->get_csr_x(), 0);
} else {
callbacks->set_csr(callbacks->get_csr_x(), atoi(argv[0]) - 1);
}
break;
default:
/* Meh */
break;
}
/* Set the states */
if (s->flags & ANSI_BOLD && s->fg < 9) {
callbacks->set_color(s->fg % 8 + 8, s->bg);
} else {
callbacks->set_color(s->fg, s->bg);
}
/* Clear out the buffer */
s->buflen = 0;
s->escape = 0;
return;
} else {
/* Still escaped */
ansi_buf_add(s, c);
}
break;
case 3:
if (c == '\007') {
/* Tokenize on semicolons, like we always do */
char * pch; /* tokenizer pointer */
char * save; /* strtok_r pointer */
char * argv[MAX_ARGS]; /* escape arguments */
/* Get rid of the front of the buffer */
strtok_r(s->buffer,"]",&save);
pch = strtok_r(NULL,";",&save);
/* argc = Number of arguments, obviously */
int argc = 0;
while (pch != NULL) {
argv[argc] = (char *)pch;
++argc;
if (argc > MAX_ARGS) break;
pch = strtok_r(NULL,";",&save);
}
/* Start testing the first argument for what command to use */
if (argv[0]) {
if (!strcmp(argv[0], "1")) {
if (argc > 1) {
callbacks->set_title(argv[1]);
}
} /* Currently, no other options */
}
/* Clear out the buffer */
s->buflen = 0;
s->escape = 0;
return;
} else {
/* Still escaped */
if (c == '\n' || s->buflen == 255) {
ansi_dump_buffer(s);
callbacks->writer(c);
s->buflen = 0;
s->escape = 0;
return;
}
ansi_buf_add(s, c);
}
break;
case 4:
if (c == '0') {
s->box = 1;
} else if (c == 'B') {
s->box = 0;
} else {
ansi_dump_buffer(s);
callbacks->writer(c);
}
s->escape = 0;
s->buflen = 0;
break;
case 5:
if (c == 'q') {
char out[24];
sprintf(out, "\033T%d;%dq", callbacks->get_cell_width(), callbacks->get_cell_height());
callbacks->input_buffer_stuff(out);
s->escape = 0;
s->buflen = 0;
} else if (c == 's') {
s->img_collected = 0;
s->escape = 6;
s->img_size = sizeof(uint32_t) * callbacks->get_cell_width() * callbacks->get_cell_height();
if (!s->img_data) {
s->img_data = malloc(s->img_size);
}
memset(s->img_data, 0x00, s->img_size);
} else {
ansi_dump_buffer(s);
callbacks->writer(c);
s->escape = 0;
s->buflen = 0;
}
break;
case 6:
s->img_data[s->img_collected++] = c;
if (s->img_collected == s->img_size) {
callbacks->set_cell_contents(callbacks->get_csr_x(), callbacks->get_csr_y(), s->img_data);
callbacks->set_csr(min(callbacks->get_csr_x() + 1, s->width - 1), callbacks->get_csr_y());
s->escape = 0;
s->buflen = 0;
}
break;
}
}
void ansi_put(term_state_t * s, char c) {
_spin_lock(&s->lock);
_ansi_put(s, c);
_spin_unlock(&s->lock);
}
term_state_t * ansi_init(term_state_t * s, int w, int y, term_callbacks_t * callbacks_in) {
if (!s) {
s = malloc(sizeof(term_state_t));
}
memset(s, 0x00, sizeof(term_state_t));
/* Terminal Defaults */
s->fg = TERM_DEFAULT_FG; /* Light grey */
s->bg = TERM_DEFAULT_BG; /* Black */
s->flags = TERM_DEFAULT_FLAGS; /* Nothing fancy*/
s->width = w;
s->height = y;
s->box = 0;
s->callbacks = callbacks_in;
s->callbacks->set_color(s->fg, s->bg);
s->mouse_on = 0;
return s;
}