448 lines
13 KiB
C
448 lines
13 KiB
C
#include "sys/char/input.h"
|
|
#include "sys/char/tty.h"
|
|
#include "sys/char/chr.h"
|
|
#include "sys/display.h"
|
|
#include "sys/console.h"
|
|
#include "sys/assert.h"
|
|
#include "sys/string.h"
|
|
#include "sys/debug.h"
|
|
#include "sys/heap.h"
|
|
|
|
#define ATTR_DEFAULT 0x1700
|
|
|
|
#define ESC_ESC 1
|
|
#define ESC_CSI 2
|
|
|
|
#define ATTR_BOLD 1
|
|
|
|
static void console_flush(struct console *con, struct console_buffer *buf);
|
|
|
|
static LIST_HEAD(g_consoles);
|
|
|
|
struct console_buffer {
|
|
// Current attribute
|
|
uint16_t attr;
|
|
// Extended attributes
|
|
uint16_t xattrs;
|
|
// Escape code processing
|
|
uint32_t esc_argv[8];
|
|
char esc_letter;
|
|
size_t esc_argc;
|
|
int esc_mode;
|
|
// Cursor
|
|
uint16_t y, x;
|
|
uint16_t saved_y, saved_x;
|
|
// Blink
|
|
uint16_t last_blink_y, last_blink_x;
|
|
uint16_t data[0];
|
|
};
|
|
|
|
#define ECON_BUFSIZ 32768
|
|
static int have_early;
|
|
static union {
|
|
struct console_buffer buf;
|
|
char _buf[sizeof(struct console_buffer) + ECON_BUFSIZ];
|
|
} early_buffer;
|
|
static struct console early;
|
|
|
|
static struct console_buffer *console_buffer_create(uint16_t rows, uint16_t cols) {
|
|
struct console_buffer *buf = kmalloc(sizeof(struct console_buffer) + rows * cols * sizeof(uint16_t));
|
|
_assert(buf);
|
|
memsetw(buf->data, ATTR_DEFAULT, rows * cols);
|
|
buf->y = 0;
|
|
buf->x = 0;
|
|
buf->saved_y = 0;
|
|
buf->saved_x = 0;
|
|
buf->last_blink_x = 0;
|
|
buf->last_blink_y = 0;
|
|
buf->attr = ATTR_DEFAULT;
|
|
buf->xattrs = 0;
|
|
buf->esc_mode = 0;
|
|
return buf;
|
|
}
|
|
|
|
void console_attach(struct console *con, struct chrdev *tty) {
|
|
struct console_buffer *tty_buffer = console_buffer_create(con->width_chars, con->height_chars);
|
|
_assert(tty_buffer);
|
|
|
|
struct tty_data *data = tty->dev_data;
|
|
_assert(data);
|
|
|
|
// Make sure the TTY hasn't already been enslaved to a console
|
|
_assert(!data->buffer);
|
|
_assert(!data->master);
|
|
data->master = con;
|
|
data->buffer = tty_buffer;
|
|
|
|
list_add(&data->list, &con->slaves);
|
|
|
|
// Make current active TTY if none yet selected
|
|
if (!con->tty_active) {
|
|
con->tty_active = tty;
|
|
con->buf_active = tty_buffer;
|
|
|
|
// Clear new default console
|
|
console_flush(con, tty_buffer);
|
|
}
|
|
}
|
|
|
|
static void console_flush(struct console *con, struct console_buffer *buf) {
|
|
for (uint16_t row = 0; row < con->height_chars; ++row) {
|
|
for (uint16_t col = 0; col < con->width_chars; ++col) {
|
|
uint16_t ch = buf->data[row * con->width_chars + col];
|
|
display_setc(con->display, row, col, ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void console_scroll_check(struct console *con, struct console_buffer *buf) {
|
|
if (buf->y >= con->height_chars) {
|
|
buf->y = con->height_chars - 1;
|
|
|
|
memcpy(buf->data, &buf->data[con->width_chars], (con->height_chars - 1) * con->width_chars * 2);
|
|
memsetw(&buf->data[(con->height_chars - 1) * con->width_chars], ATTR_DEFAULT, con->width_chars);
|
|
|
|
console_flush(con, buf);
|
|
}
|
|
}
|
|
|
|
static uint8_t color_map[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
|
|
// I guess I could've implemented VT100 escape handling better, but
|
|
// this is all I invented so far
|
|
static void process_csi(struct console *con, struct console_buffer *buf) {
|
|
switch (buf->esc_letter) {
|
|
case 'm':
|
|
for (size_t i = 0; i < buf->esc_argc; ++i) {
|
|
uint32_t v = buf->esc_argv[i];
|
|
switch (v / 10) {
|
|
case 0:
|
|
switch (v % 10) {
|
|
case 0:
|
|
// Reset
|
|
buf->attr = ATTR_DEFAULT;
|
|
buf->xattrs = 0;
|
|
break;
|
|
case 1:
|
|
// Bright
|
|
buf->xattrs |= ATTR_BOLD;
|
|
break;
|
|
case 2:
|
|
// Dim
|
|
buf->xattrs &= ~ATTR_BOLD;
|
|
break;
|
|
case 7:
|
|
// Reverse
|
|
buf->attr >>= 4;
|
|
buf->attr |= (buf->attr & 0xF0) << 8;
|
|
buf->attr &= 0xFF00;
|
|
break;
|
|
}
|
|
break;
|
|
case 3:
|
|
// Foreground
|
|
buf->attr &= ~0x0F00;
|
|
buf->attr |= (uint16_t) color_map[v % 10] << 8;
|
|
break;
|
|
case 4:
|
|
// Background
|
|
buf->attr &= ~0xF000;
|
|
buf->attr |= (uint16_t) color_map[v % 10] << 12;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 's':
|
|
buf->saved_x = buf->x;
|
|
buf->saved_y = buf->y;
|
|
break;
|
|
case 'u':
|
|
buf->x = buf->saved_x;
|
|
buf->y = buf->saved_y;
|
|
break;
|
|
case 'J':
|
|
switch (buf->esc_argv[0]) {
|
|
case 0:
|
|
// Erase lines down
|
|
memsetw(buf->data, buf->attr, con->width_chars * buf->y);
|
|
break;
|
|
case 1:
|
|
// Erase lines up
|
|
memsetw(&buf->data[buf->y * con->width_chars],
|
|
buf->attr,
|
|
con->width_chars * (con->height_chars - buf->y));
|
|
break;
|
|
case 2:
|
|
// Erase all
|
|
memsetw(buf->data, buf->attr, con->width_chars * con->height_chars);
|
|
console_flush(con, buf);
|
|
break;
|
|
}
|
|
break;
|
|
case 'f':
|
|
// Set cursor position
|
|
buf->y = (buf->esc_argv[0] - 1) % con->height_chars;
|
|
buf->x = (buf->esc_argv[1] - 1) % (con->width_chars - 1);
|
|
break;
|
|
case 'A':
|
|
// Cursor up
|
|
if (!buf->esc_argv[0]) {
|
|
buf->esc_argv[0] = 1;
|
|
}
|
|
|
|
if (buf->esc_argv[0] >= buf->y) {
|
|
buf->y = 0;
|
|
} else {
|
|
buf->y -= buf->esc_argv[0];
|
|
}
|
|
break;
|
|
case 'B':
|
|
// Cursor down
|
|
if (!buf->esc_argv[0]) {
|
|
buf->esc_argv[0] = 1;
|
|
}
|
|
|
|
if (buf->esc_argv[0] + buf->y >= (uint32_t) con->height_chars) {
|
|
buf->y = con->height_chars - 1;
|
|
} else {
|
|
buf->y += buf->esc_argv[0];
|
|
}
|
|
break;
|
|
case 'C':
|
|
// Forward
|
|
if (!buf->esc_argv[0]) {
|
|
buf->esc_argv[0] = 1;
|
|
}
|
|
|
|
if (buf->esc_argv[0] + buf->x >= con->width_chars) {
|
|
buf->x = con->width_chars - 1;
|
|
} else {
|
|
buf->x += buf->esc_argv[0];
|
|
}
|
|
|
|
break;
|
|
case 'D':
|
|
// Backward
|
|
if (!buf->esc_argv[0]) {
|
|
buf->esc_argv[0] = 1;
|
|
}
|
|
|
|
if (buf->esc_argv[0] >= buf->x) {
|
|
buf->x = 0;
|
|
} else {
|
|
buf->x -= buf->esc_argv[0];
|
|
}
|
|
|
|
break;
|
|
case 'K':
|
|
// Erase end of line
|
|
memsetw(&buf->data[buf->y * con->width_chars + buf->x], buf->attr, con->width_chars - buf->x);
|
|
// TODO: allow flushing a single line
|
|
console_flush(con, buf);
|
|
break;
|
|
default:
|
|
kdebug("\033[31mUnknown CSI sequence: %c\033[0m\n", buf->esc_letter);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void _console_putc(struct console *con, struct console_buffer *buf, int c) {
|
|
int act = buf == con->buf_active;
|
|
|
|
switch (buf->esc_mode) {
|
|
case ESC_CSI:
|
|
if (c >= '0' && c <= '9') {
|
|
buf->esc_argv[buf->esc_argc] *= 10;
|
|
buf->esc_argv[buf->esc_argc] += c - '0';
|
|
} else if (c == ';') {
|
|
buf->esc_argv[++buf->esc_argc] = 0;
|
|
} else {
|
|
++buf->esc_argc;
|
|
buf->esc_letter = c;
|
|
process_csi(con, buf);
|
|
buf->esc_mode = 0;
|
|
}
|
|
break;
|
|
case ESC_ESC:
|
|
if (c == '[') {
|
|
buf->esc_mode = ESC_CSI;
|
|
break;
|
|
} else {
|
|
buf->esc_mode = 0;
|
|
}
|
|
__attribute__((fallthrough));
|
|
case 0:
|
|
if (c == '\033') {
|
|
buf->esc_mode = ESC_ESC;
|
|
buf->esc_argv[0] = 0;
|
|
buf->esc_argc = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
switch (c) {
|
|
case '\n':
|
|
++buf->y;
|
|
// TODO: separate \r and \n?
|
|
buf->x = 0;
|
|
console_scroll_check(con, buf);
|
|
break;
|
|
case '\r':
|
|
buf->x = 0;
|
|
break;
|
|
case '\t':
|
|
buf->x += (4 - buf->x % 4);
|
|
if (buf->x >= con->width_chars) {
|
|
++buf->y;
|
|
buf->x = 0;
|
|
console_scroll_check(con, buf);
|
|
}
|
|
break;
|
|
default:
|
|
if (c >= ' ') {
|
|
buf->data[buf->y * con->width_chars + buf->x] = c | buf->attr;
|
|
if (act) {
|
|
uint16_t a = buf->attr;
|
|
if (buf->xattrs & ATTR_BOLD) {
|
|
a |= 0x8000;
|
|
}
|
|
display_setc(con->display, buf->y, buf->x, c | a);
|
|
}
|
|
|
|
++buf->x;
|
|
if (buf->x >= con->width_chars) {
|
|
++buf->y;
|
|
buf->x = 0;
|
|
console_scroll_check(con, buf);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void console_update_cursor(void) {
|
|
struct console *con;
|
|
static int prev_blink = 0;
|
|
|
|
list_for_each_entry(con, &g_consoles, list) {
|
|
if (con->display && con->buf_active) {
|
|
struct display *d = con->display;
|
|
struct console_buffer *buf = con->buf_active;
|
|
|
|
if (d->flags & DISP_GRAPHIC) {
|
|
if (g_display_blink_state != prev_blink) {
|
|
uint16_t c = buf->data[buf->y * con->width_chars + buf->x];
|
|
if (g_display_blink_state) {
|
|
// Swap attributes
|
|
c = ((c >> 4) & 0xF00) |
|
|
((c & 0xF00) << 4) |
|
|
(c & 0xFF);
|
|
}
|
|
display_setc(d, buf->y, buf->x, c);
|
|
if (buf->last_blink_x != buf->x || buf->last_blink_y != buf->y) {
|
|
// Also redraw character at last blink position
|
|
display_setc(d,
|
|
buf->last_blink_y,
|
|
buf->last_blink_x,
|
|
buf->data[buf->last_blink_y * con->width_chars +
|
|
buf->last_blink_x]);
|
|
buf->last_blink_x = buf->x;
|
|
buf->last_blink_y = buf->y;
|
|
}
|
|
}
|
|
} else {
|
|
_assert(d->cursor);
|
|
d->cursor(buf->y, buf->x);
|
|
}
|
|
}
|
|
}
|
|
|
|
prev_blink = g_display_blink_state;
|
|
}
|
|
|
|
void console_putc(struct console *con, struct chrdev *tty, int c) {
|
|
struct tty_data *data = tty->dev_data;
|
|
_assert(data);
|
|
|
|
struct console_buffer *buf = data->buffer;
|
|
_assert(buf);
|
|
|
|
_console_putc(con, buf, c);
|
|
}
|
|
|
|
void console_type(struct console *con, int c) {
|
|
if (con->tty_active) {
|
|
tty_data_write(con->tty_active, c);
|
|
}
|
|
}
|
|
|
|
void console_default_putc(int c) {
|
|
if (list_empty(&g_consoles)) {
|
|
if (have_early) {
|
|
struct console *con = &early;
|
|
_assert(con->buf_active);
|
|
_console_putc(con, con->buf_active, c);
|
|
}
|
|
return;
|
|
}
|
|
struct console *con = list_entry(g_consoles.next, struct console, list);
|
|
if (con->buf_active) {
|
|
_console_putc(con, con->buf_active, c);
|
|
}
|
|
}
|
|
|
|
void console_init_early(struct display *output) {
|
|
// TODO: allow selecting fonts per console/display
|
|
early.tty_active = NULL;
|
|
early.buf_active = &early_buffer.buf;
|
|
early.display = output;
|
|
early.width_chars = output->width_pixels / 8;
|
|
early.height_chars = output->height_pixels / 16;
|
|
|
|
if (early.width_chars * early.height_chars * 2 > ECON_BUFSIZ) {
|
|
panic("Failed to fit early console\n");
|
|
}
|
|
|
|
struct console_buffer *buf = &early_buffer.buf;
|
|
memsetw(buf->data, ATTR_DEFAULT, early.width_chars * early.height_chars);
|
|
buf->y = 0;
|
|
buf->x = 0;
|
|
buf->saved_y = 0;
|
|
buf->saved_x = 0;
|
|
buf->last_blink_x = 0;
|
|
buf->last_blink_y = 0;
|
|
buf->attr = ATTR_DEFAULT;
|
|
buf->xattrs = 0;
|
|
buf->esc_mode = 0;
|
|
|
|
have_early = 1;
|
|
console_flush(&early, buf);
|
|
}
|
|
|
|
void console_init_default(void) {
|
|
// Initialize default display+keyboard pair
|
|
struct console *con = kmalloc(sizeof(struct console));
|
|
_assert(con);
|
|
con->tty_active = NULL;
|
|
con->buf_active = NULL;
|
|
list_head_init(&con->slaves);
|
|
list_head_init(&con->list);
|
|
|
|
struct display *display = display_get_default();
|
|
_assert(display);
|
|
con->display = display;
|
|
|
|
// Extract initial display mode
|
|
con->width_chars = display->width_chars;
|
|
con->height_chars = display->height_chars;
|
|
kdebug("Initializing default console: %ux%u\n", con->width_chars, con->height_chars);
|
|
|
|
if (!g_keyboard_console) {
|
|
g_keyboard_console = con;
|
|
}
|
|
|
|
// Create a single TTY device
|
|
_assert(tty_create(con) == 0);
|
|
list_add(&con->list, &g_consoles);
|
|
}
|