Fast and reasonably complete (framebuffer) terminal emulator (Zig fork)

Initial commit

mintsuki 0d2f55ba

Changed files
+1607
backends
+567
backends/framebuffer.c
···
+
#include <stdint.h>
+
#include <stddef.h>
+
+
#include "../term.h"
+
#include "framebuffer.h"
+
+
void *memset(void *, int, size_t);
+
void *memcpy(void *, const void *, size_t);
+
+
static void fbterm_swap_palette(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
uint32_t tmp = ctx->text_bg;
+
ctx->text_bg = ctx->text_fg;
+
ctx->text_fg = tmp;
+
}
+
+
static void plot_char(struct term_context *_ctx, struct fbterm_char *c, size_t x, size_t y) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
if (x >= _ctx->cols || y >= _ctx->rows) {
+
return;
+
}
+
+
x = ctx->offset_x + x * ctx->glyph_width;
+
y = ctx->offset_y + y * ctx->glyph_height;
+
+
bool *glyph = &ctx->font_bool[c->c * ctx->font_height * ctx->font_width];
+
// naming: fx,fy for font coordinates, gx,gy for glyph coordinates
+
for (size_t gy = 0; gy < ctx->glyph_height; gy++) {
+
uint8_t fy = gy / ctx->font_scale_y;
+
volatile uint32_t *fb_line = ctx->framebuffer + x + (y + gy) * (ctx->pitch / 4);
+
uint32_t *canvas_line = ctx->canvas + x + (y + gy) * ctx->width;
+
for (size_t fx = 0; fx < ctx->font_width; fx++) {
+
bool draw = glyph[fy * ctx->font_width + fx];
+
for (size_t i = 0; i < ctx->font_scale_x; i++) {
+
size_t gx = ctx->font_scale_x * fx + i;
+
uint32_t bg = c->bg == 0xffffffff ? canvas_line[gx] : c->bg;
+
uint32_t fg = c->fg == 0xffffffff ? canvas_line[gx] : c->fg;
+
fb_line[gx] = draw ? fg : bg;
+
}
+
}
+
}
+
}
+
+
static void plot_char_fast(struct term_context *_ctx, struct fbterm_char *old, struct fbterm_char *c, size_t x, size_t y) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
if (x >= _ctx->cols || y >= _ctx->rows) {
+
return;
+
}
+
+
x = ctx->offset_x + x * ctx->glyph_width;
+
y = ctx->offset_y + y * ctx->glyph_height;
+
+
bool *new_glyph = &ctx->font_bool[c->c * ctx->font_height * ctx->font_width];
+
bool *old_glyph = &ctx->font_bool[old->c * ctx->font_height * ctx->font_width];
+
for (size_t gy = 0; gy < ctx->glyph_height; gy++) {
+
uint8_t fy = gy / ctx->font_scale_y;
+
volatile uint32_t *fb_line = ctx->framebuffer + x + (y + gy) * (ctx->pitch / 4);
+
uint32_t *canvas_line = ctx->canvas + x + (y + gy) * ctx->width;
+
for (size_t fx = 0; fx < ctx->font_width; fx++) {
+
bool old_draw = old_glyph[fy * ctx->font_width + fx];
+
bool new_draw = new_glyph[fy * ctx->font_width + fx];
+
if (old_draw == new_draw)
+
continue;
+
for (size_t i = 0; i < ctx->font_scale_x; i++) {
+
size_t gx = ctx->font_scale_x * fx + i;
+
uint32_t bg = c->bg == 0xffffffff ? canvas_line[gx] : c->bg;
+
uint32_t fg = c->fg == 0xffffffff ? canvas_line[gx] : c->fg;
+
fb_line[gx] = new_draw ? fg : bg;
+
}
+
}
+
}
+
}
+
+
static inline bool compare_char(struct fbterm_char *a, struct fbterm_char *b) {
+
return !(a->c != b->c || a->bg != b->bg || a->fg != b->fg);
+
}
+
+
static void push_to_queue(struct term_context *_ctx, struct fbterm_char *c, size_t x, size_t y) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
if (x >= _ctx->cols || y >= _ctx->rows) {
+
return;
+
}
+
+
size_t i = y * _ctx->cols + x;
+
+
struct fbterm_queue_item *q = ctx->map[i];
+
+
if (q == NULL) {
+
if (compare_char(&ctx->grid[i], c)) {
+
return;
+
}
+
q = &ctx->queue[ctx->queue_i++];
+
q->x = x;
+
q->y = y;
+
ctx->map[i] = q;
+
}
+
+
q->c = *c;
+
}
+
+
static void fbterm_revscroll(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
for (size_t i = (_ctx->scroll_bottom_margin - 1) * _ctx->cols - 1; ; i--) {
+
struct fbterm_char *c;
+
struct fbterm_queue_item *q = ctx->map[i];
+
if (q != NULL) {
+
c = &q->c;
+
} else {
+
c = &ctx->grid[i];
+
}
+
push_to_queue(_ctx, c, (i + _ctx->cols) % _ctx->cols, (i + _ctx->cols) / _ctx->cols);
+
if (i == _ctx->scroll_top_margin * _ctx->cols) {
+
break;
+
}
+
}
+
+
// Clear the first line of the screen.
+
struct fbterm_char empty;
+
empty.c = ' ';
+
empty.fg = ctx->text_fg;
+
empty.bg = ctx->text_bg;
+
for (size_t i = _ctx->scroll_top_margin * _ctx->cols;
+
i < (_ctx->scroll_top_margin + 1) * _ctx->cols; i++) {
+
push_to_queue(_ctx, &empty, i % _ctx->cols, i / _ctx->cols);
+
}
+
}
+
+
static void fbterm_scroll(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
for (size_t i = (_ctx->scroll_top_margin + 1) * _ctx->cols;
+
i < _ctx->scroll_bottom_margin * _ctx->cols; i++) {
+
struct fbterm_char *c;
+
struct fbterm_queue_item *q = ctx->map[i];
+
if (q != NULL) {
+
c = &q->c;
+
} else {
+
c = &ctx->grid[i];
+
}
+
push_to_queue(_ctx, c, (i - _ctx->cols) % _ctx->cols, (i - _ctx->cols) / _ctx->cols);
+
}
+
+
// Clear the last line of the screen.
+
struct fbterm_char empty;
+
empty.c = ' ';
+
empty.fg = ctx->text_fg;
+
empty.bg = ctx->text_bg;
+
for (size_t i = (_ctx->scroll_bottom_margin - 1) * _ctx->cols;
+
i < _ctx->scroll_bottom_margin * _ctx->cols; i++) {
+
push_to_queue(_ctx, &empty, i % _ctx->cols, i / _ctx->cols);
+
}
+
}
+
+
static void fbterm_clear(struct term_context *_ctx, bool move) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
struct fbterm_char empty;
+
empty.c = ' ';
+
empty.fg = ctx->text_fg;
+
empty.bg = ctx->text_bg;
+
for (size_t i = 0; i < _ctx->rows * _ctx->cols; i++) {
+
push_to_queue(_ctx, &empty, i % _ctx->cols, i / _ctx->cols);
+
}
+
+
if (move) {
+
ctx->cursor_x = 0;
+
ctx->cursor_y = 0;
+
}
+
}
+
+
static void fbterm_enable_cursor(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->cursor_status = true;
+
}
+
+
static bool fbterm_disable_cursor(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
bool ret = ctx->cursor_status;
+
ctx->cursor_status = false;
+
return ret;
+
}
+
+
static void fbterm_set_cursor_pos(struct term_context *_ctx, size_t x, size_t y) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
if (x >= _ctx->cols) {
+
if ((int)x < 0) {
+
x = 0;
+
} else {
+
x = _ctx->cols - 1;
+
}
+
}
+
if (y >= _ctx->rows) {
+
if ((int)y < 0) {
+
y = 0;
+
} else {
+
y = _ctx->rows - 1;
+
}
+
}
+
ctx->cursor_x = x;
+
ctx->cursor_y = y;
+
}
+
+
static void fbterm_get_cursor_pos(struct term_context *_ctx, size_t *x, size_t *y) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
*x = ctx->cursor_x;
+
*y = ctx->cursor_y;
+
}
+
+
static void fbterm_move_character(struct term_context *_ctx, size_t new_x, size_t new_y, size_t old_x, size_t old_y) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
if (old_x >= _ctx->cols || old_y >= _ctx->rows
+
|| new_x >= _ctx->cols || new_y >= _ctx->rows) {
+
return;
+
}
+
+
size_t i = old_x + old_y * _ctx->cols;
+
+
struct fbterm_char *c;
+
struct fbterm_queue_item *q = ctx->map[i];
+
if (q != NULL) {
+
c = &q->c;
+
} else {
+
c = &ctx->grid[i];
+
}
+
+
push_to_queue(_ctx, c, new_x, new_y);
+
}
+
+
static void fbterm_set_text_fg(struct term_context *_ctx, size_t fg) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->text_fg = ctx->ansi_colours[fg];
+
}
+
+
static void fbterm_set_text_bg(struct term_context *_ctx, size_t bg) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->text_bg = ctx->ansi_colours[bg];
+
}
+
+
static void fbterm_set_text_fg_bright(struct term_context *_ctx, size_t fg) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->text_fg = ctx->ansi_bright_colours[fg];
+
}
+
+
static void fbterm_set_text_bg_bright(struct term_context *_ctx, size_t bg) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->text_bg = ctx->ansi_bright_colours[bg];
+
}
+
+
static void fbterm_set_text_fg_rgb(struct term_context *_ctx, uint32_t fg) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->text_fg = fg;
+
}
+
+
static void fbterm_set_text_bg_rgb(struct term_context *_ctx, uint32_t bg) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->text_bg = bg;
+
}
+
+
static void fbterm_set_text_fg_default(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->text_fg = ctx->default_fg;
+
}
+
+
static void fbterm_set_text_bg_default(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
ctx->text_bg = 0xffffffff;
+
}
+
+
static void draw_cursor(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
size_t i = ctx->cursor_x + ctx->cursor_y * _ctx->cols;
+
struct fbterm_char c;
+
struct fbterm_queue_item *q = ctx->map[i];
+
if (q != NULL) {
+
c = q->c;
+
} else {
+
c = ctx->grid[i];
+
}
+
uint32_t tmp = c.fg;
+
c.fg = c.bg;
+
c.bg = tmp;
+
plot_char(_ctx, &c, ctx->cursor_x, ctx->cursor_y);
+
if (q != NULL) {
+
ctx->grid[i] = q->c;
+
ctx->map[i] = NULL;
+
}
+
}
+
+
static void fbterm_double_buffer_flush(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
if (ctx->cursor_status) {
+
draw_cursor(_ctx);
+
}
+
+
for (size_t i = 0; i < ctx->queue_i; i++) {
+
struct fbterm_queue_item *q = &ctx->queue[i];
+
size_t offset = q->y * _ctx->cols + q->x;
+
if (ctx->map[offset] == NULL) {
+
continue;
+
}
+
struct fbterm_char *old = &ctx->grid[offset];
+
if (q->c.bg == old->bg && q->c.fg == old->fg) {
+
plot_char_fast(_ctx, old, &q->c, q->x, q->y);
+
} else {
+
plot_char(_ctx, &q->c, q->x, q->y);
+
}
+
ctx->grid[offset] = q->c;
+
ctx->map[offset] = NULL;
+
}
+
+
if ((ctx->old_cursor_x != ctx->cursor_x || ctx->old_cursor_y != ctx->cursor_y) || ctx->cursor_status == false) {
+
plot_char(_ctx, &ctx->grid[ctx->old_cursor_x + ctx->old_cursor_y * _ctx->cols], ctx->old_cursor_x, ctx->old_cursor_y);
+
}
+
+
ctx->old_cursor_x = ctx->cursor_x;
+
ctx->old_cursor_y = ctx->cursor_y;
+
+
ctx->queue_i = 0;
+
}
+
+
static void fbterm_raw_putchar(struct term_context *_ctx, uint8_t c) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
struct fbterm_char ch;
+
ch.c = c;
+
ch.fg = ctx->text_fg;
+
ch.bg = ctx->text_bg;
+
push_to_queue(_ctx, &ch, ctx->cursor_x++, ctx->cursor_y);
+
if (ctx->cursor_x == _ctx->cols && (ctx->cursor_y < _ctx->scroll_bottom_margin - 1 || _ctx->scroll_enabled)) {
+
ctx->cursor_x = 0;
+
ctx->cursor_y++;
+
}
+
if (ctx->cursor_y == _ctx->scroll_bottom_margin) {
+
ctx->cursor_y--;
+
fbterm_scroll(_ctx);
+
}
+
}
+
+
static void fbterm_full_refresh(struct term_context *_ctx) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
for (size_t y = 0; y < ctx->height; y++) {
+
for (size_t x = 0; x < ctx->width; x++) {
+
ctx->framebuffer[y * (ctx->pitch / sizeof(uint32_t)) + x] = ctx->canvas[y * ctx->width + x];
+
}
+
}
+
+
for (size_t i = 0; i < (size_t)_ctx->rows * _ctx->cols; i++) {
+
size_t x = i % _ctx->cols;
+
size_t y = i / _ctx->cols;
+
+
plot_char(_ctx, &ctx->grid[i], x, y);
+
}
+
+
if (ctx->cursor_status) {
+
draw_cursor(_ctx);
+
}
+
}
+
+
static void fbterm_deinit(struct term_context *_ctx, void (*_free)(void *, size_t)) {
+
struct fbterm_context *ctx = (void *)_ctx;
+
+
_free(ctx->font_bits, ctx->font_bits_size);
+
_free(ctx->font_bool, ctx->font_bool_size);
+
_free(ctx->grid, ctx->grid_size);
+
_free(ctx->queue, ctx->queue_size);
+
_free(ctx->map, ctx->map_size);
+
_free(ctx->canvas, ctx->canvas_size);
+
}
+
+
struct term_context *fbterm_init(
+
void *(*_malloc)(size_t),
+
uint32_t *framebuffer, size_t width, size_t height, size_t pitch,
+
uint32_t *canvas,
+
uint32_t *ansi_colours, uint32_t *ansi_bright_colours,
+
uint32_t *default_bg, uint32_t *default_fg,
+
void *font, size_t font_width, size_t font_height, size_t font_spacing,
+
size_t font_scale_x, size_t font_scale_y,
+
size_t margin
+
) {
+
struct fbterm_context *ctx = _malloc(sizeof(struct fbterm_context));
+
+
struct term_context *_ctx = (void *)ctx;
+
+
memset(ctx, 0, sizeof(struct fbterm_context));
+
+
ctx->cursor_status = true;
+
+
if (ansi_colours != NULL) {
+
memcpy(ctx->ansi_colours, ansi_colours, sizeof(ctx->ansi_colours));
+
} else {
+
ctx->ansi_colours[0] = 0x00000000; // black
+
ctx->ansi_colours[1] = 0x00aa0000; // red
+
ctx->ansi_colours[2] = 0x0000aa00; // green
+
ctx->ansi_colours[3] = 0x00aa5500; // brown
+
ctx->ansi_colours[4] = 0x000000aa; // blue
+
ctx->ansi_colours[5] = 0x00aa00aa; // magenta
+
ctx->ansi_colours[6] = 0x0000aaaa; // cyan
+
ctx->ansi_colours[7] = 0x00aaaaaa; // grey
+
}
+
+
if (ansi_bright_colours != NULL) {
+
memcpy(ctx->ansi_bright_colours, ansi_bright_colours, sizeof(ctx->ansi_bright_colours));
+
} else {
+
ctx->ansi_bright_colours[0] = 0x00555555; // black
+
ctx->ansi_bright_colours[1] = 0x00ff5555; // red
+
ctx->ansi_bright_colours[2] = 0x0055ff55; // green
+
ctx->ansi_bright_colours[3] = 0x00ffff55; // brown
+
ctx->ansi_bright_colours[4] = 0x005555ff; // blue
+
ctx->ansi_bright_colours[5] = 0x00ff55ff; // magenta
+
ctx->ansi_bright_colours[6] = 0x0055ffff; // cyan
+
ctx->ansi_bright_colours[7] = 0x00ffffff; // grey
+
}
+
+
if (default_bg != NULL) {
+
ctx->default_bg = *default_bg;
+
} else {
+
ctx->default_bg = 0x00000000; // background (black)
+
}
+
+
if (default_fg != NULL) {
+
ctx->default_fg = *default_fg;
+
} else {
+
ctx->default_fg = 0x00aaaaaa; // foreground (grey)
+
}
+
+
ctx->text_fg = ctx->default_fg;
+
ctx->text_bg = 0xffffffff;
+
+
ctx->framebuffer = (void *)framebuffer;
+
ctx->width = width;
+
ctx->height = height;
+
ctx->pitch = pitch;
+
+
#define FONT_BYTES ((font_width * font_height * FBTERM_FONT_GLYPHS) / 8)
+
+
if (font != NULL) {
+
ctx->font_width = font_width;
+
ctx->font_height = font_height;
+
ctx->font_bits = _malloc(FONT_BYTES);
+
memcpy(ctx->font_bits, font, FONT_BYTES);
+
} else {
+
ctx->font_width = font_width = 8;
+
ctx->font_height = font_height = 16;
+
font_spacing = 1;
+
ctx->font_bits = _malloc(FONT_BYTES);
+
// XXX memcpy(ctx->font_bits, builtin_font, FONT_BYTES);
+
}
+
+
ctx->font_bits_size = FONT_BYTES;
+
+
#undef FONT_BYTES
+
+
ctx->font_width += font_spacing;
+
+
ctx->font_bool_size = FBTERM_FONT_GLYPHS * font_height * ctx->font_width * sizeof(bool);
+
ctx->font_bool = _malloc(ctx->font_bool_size);
+
+
for (size_t i = 0; i < FBTERM_FONT_GLYPHS; i++) {
+
uint8_t *glyph = &ctx->font_bits[i * font_height];
+
+
for (size_t y = 0; y < font_height; y++) {
+
// NOTE: the characters in VGA fonts are always one byte wide.
+
// 9 dot wide fonts have 8 dots and one empty column, except
+
// characters 0xC0-0xDF replicate column 9.
+
for (size_t x = 0; x < 8; x++) {
+
size_t offset = i * font_height * ctx->font_width + y * ctx->font_width + x;
+
+
if ((glyph[y] & (0x80 >> x))) {
+
ctx->font_bool[offset] = true;
+
} else {
+
ctx->font_bool[offset] = false;
+
}
+
}
+
// fill columns above 8 like VGA Line Graphics Mode does
+
for (size_t x = 8; x < ctx->font_width; x++) {
+
size_t offset = i * font_height * ctx->font_width + y * ctx->font_width + x;
+
+
if (i >= 0xc0 && i <= 0xdf) {
+
ctx->font_bool[offset] = (glyph[y] & 1);
+
} else {
+
ctx->font_bool[offset] = false;
+
}
+
}
+
}
+
}
+
+
ctx->font_scale_x = font_scale_x;
+
ctx->font_scale_y = font_scale_y;
+
+
ctx->glyph_width = ctx->font_width * font_scale_x;
+
ctx->glyph_height = font_height * font_scale_y;
+
+
_ctx->cols = (ctx->width - margin * 2) / ctx->glyph_width;
+
_ctx->rows = (ctx->height - margin * 2) / ctx->glyph_height;
+
+
ctx->offset_x = margin + ((ctx->width - margin * 2) % ctx->glyph_width) / 2;
+
ctx->offset_y = margin + ((ctx->height - margin * 2) % ctx->glyph_height) / 2;
+
+
ctx->grid_size = _ctx->rows * _ctx->cols * sizeof(struct fbterm_char);
+
ctx->grid = _malloc(ctx->grid_size);
+
+
ctx->queue_size = _ctx->rows * _ctx->cols * sizeof(struct fbterm_queue_item);
+
ctx->queue = _malloc(ctx->queue_size);
+
ctx->queue_i = 0;
+
+
ctx->map_size = _ctx->rows * _ctx->cols * sizeof(struct fbterm_queue_item *);
+
ctx->map = _malloc(ctx->map_size);
+
+
ctx->canvas_size = ctx->width * ctx->height * sizeof(uint32_t);
+
ctx->canvas = _malloc(ctx->canvas_size);
+
if (canvas != NULL) {
+
memcpy(ctx->canvas, canvas, ctx->canvas_size);
+
} else {
+
for (size_t i = 0; i < ctx->width * ctx->height; i++) {
+
ctx->canvas[i] = ctx->default_bg;
+
}
+
}
+
+
_ctx->raw_putchar = fbterm_raw_putchar;
+
_ctx->clear = fbterm_clear;
+
_ctx->enable_cursor = fbterm_enable_cursor;
+
_ctx->disable_cursor = fbterm_disable_cursor;
+
_ctx->set_cursor_pos = fbterm_set_cursor_pos;
+
_ctx->get_cursor_pos = fbterm_get_cursor_pos;
+
_ctx->set_text_fg = fbterm_set_text_fg;
+
_ctx->set_text_bg = fbterm_set_text_bg;
+
_ctx->set_text_fg_bright = fbterm_set_text_fg_bright;
+
_ctx->set_text_bg_bright = fbterm_set_text_bg_bright;
+
_ctx->set_text_fg_rgb = fbterm_set_text_fg_rgb;
+
_ctx->set_text_bg_rgb = fbterm_set_text_bg_rgb;
+
_ctx->set_text_fg_default = fbterm_set_text_fg_default;
+
_ctx->set_text_bg_default = fbterm_set_text_bg_default;
+
_ctx->move_character = fbterm_move_character;
+
_ctx->scroll = fbterm_scroll;
+
_ctx->revscroll = fbterm_revscroll;
+
_ctx->swap_palette = fbterm_swap_palette;
+
_ctx->double_buffer_flush = fbterm_double_buffer_flush;
+
_ctx->full_refresh = fbterm_full_refresh;
+
_ctx->deinit = fbterm_deinit;
+
+
term_context_reinit(_ctx);
+
+
fbterm_clear(_ctx, true);
+
fbterm_full_refresh(_ctx);
+
+
return _ctx;
+
}
+91
backends/framebuffer.h
···
+
#ifndef _TERM_FRAMEBUFFER_H
+
#define _TERM_FRAMEBUFFER_H
+
+
#include <stdint.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
+
#include "../term.h"
+
+
#define FBTERM_FONT_GLYPHS 256
+
+
struct fbterm_char {
+
uint32_t c;
+
uint32_t fg;
+
uint32_t bg;
+
};
+
+
struct fbterm_queue_item {
+
size_t x, y;
+
struct fbterm_char c;
+
};
+
+
struct fbterm_context {
+
struct term_context term;
+
+
size_t font_width;
+
size_t font_height;
+
size_t glyph_width;
+
size_t glyph_height;
+
+
size_t font_scale_x;
+
size_t font_scale_y;
+
+
size_t offset_x, offset_y;
+
+
volatile uint32_t *framebuffer;
+
size_t pitch;
+
size_t width;
+
size_t height;
+
size_t bpp;
+
+
size_t font_bits_size;
+
uint8_t *font_bits;
+
size_t font_bool_size;
+
bool *font_bool;
+
+
uint32_t ansi_colours[8];
+
uint32_t ansi_bright_colours[8];
+
uint32_t default_fg, default_bg;
+
+
size_t canvas_size;
+
uint32_t *canvas;
+
+
size_t grid_size;
+
size_t queue_size;
+
size_t map_size;
+
+
struct fbterm_char *grid;
+
+
struct fbterm_queue_item *queue;
+
size_t queue_i;
+
+
struct fbterm_queue_item **map;
+
+
uint32_t text_fg;
+
uint32_t text_bg;
+
bool cursor_status;
+
size_t cursor_x;
+
size_t cursor_y;
+
+
uint32_t saved_state_text_fg;
+
uint32_t saved_state_text_bg;
+
size_t saved_state_cursor_x;
+
size_t saved_state_cursor_y;
+
+
size_t old_cursor_x;
+
size_t old_cursor_y;
+
};
+
+
struct term_context *fbterm_init(
+
void *(*_malloc)(size_t),
+
uint32_t *framebuffer, size_t width, size_t height, size_t pitch,
+
uint32_t *canvas,
+
uint32_t *ansi_colours, uint32_t *ansi_bright_colours,
+
uint32_t *default_bg, uint32_t *default_fg,
+
void *font, size_t font_width, size_t font_height, size_t font_spacing,
+
size_t font_scale_x, size_t font_scale_y,
+
size_t margin
+
);
+
+
#endif
+864
term.c
···
+
#include <stdint.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
+
#include "term.h"
+
+
static const uint32_t col256[] = {
+
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
+
0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
+
0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
+
0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
+
0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
+
0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
+
0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
+
0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
+
0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
+
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
+
0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
+
0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
+
0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
+
0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
+
0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
+
0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
+
0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
+
0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
+
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
+
0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
+
0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
+
0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
+
0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
+
0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
+
0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
+
0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
+
0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
+
0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
+
0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
+
0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
+
};
+
+
// Tries to implement this standard for terminfo
+
// https://man7.org/linux/man-pages/man4/console_codes.4.html
+
+
#define CHARSET_DEFAULT 0
+
#define CHARSET_DEC_SPECIAL 1
+
+
void term_context_reinit(struct term_context *ctx) {
+
ctx->scroll_enabled = true;
+
ctx->tab_size = 8;
+
ctx->escape_offset = 0;
+
ctx->control_sequence = false;
+
ctx->csi = false;
+
ctx->escape = false;
+
ctx->rrr = false;
+
ctx->discard_next = false;
+
ctx->bold = false;
+
ctx->reverse_video = false;
+
ctx->dec_private = false;
+
ctx->esc_values_i = 0;
+
ctx->saved_cursor_x = 0;
+
ctx->saved_cursor_y = 0;
+
ctx->current_primary = (size_t)-1;
+
ctx->insert_mode = false;
+
ctx->scroll_top_margin = 0;
+
ctx->scroll_bottom_margin = ctx->rows;
+
ctx->current_charset = 0;
+
ctx->g_select = 0;
+
ctx->charsets[0] = CHARSET_DEFAULT;
+
ctx->charsets[1] = CHARSET_DEC_SPECIAL;
+
ctx->autoflush = true;
+
}
+
+
static void term_putchar(struct term_context *ctx, uint8_t c);
+
+
void term_write(struct term_context *ctx, const char *buf, size_t count) {
+
for (size_t i = 0; i < count; i++) {
+
term_putchar(ctx, buf[i]);
+
}
+
+
if (ctx->autoflush) {
+
ctx->double_buffer_flush(ctx);
+
}
+
}
+
+
static void sgr(struct term_context *ctx) {
+
size_t i = 0;
+
+
if (!ctx->esc_values_i)
+
goto def;
+
+
for (; i < ctx->esc_values_i; i++) {
+
size_t offset;
+
+
if (ctx->esc_values[i] == 0) {
+
def:
+
if (ctx->reverse_video) {
+
ctx->reverse_video = false;
+
ctx->swap_palette(ctx);
+
}
+
ctx->bold = false;
+
ctx->current_primary = (size_t)-1;
+
ctx->set_text_bg_default(ctx);
+
ctx->set_text_fg_default(ctx);
+
continue;
+
}
+
+
else if (ctx->esc_values[i] == 1) {
+
ctx->bold = true;
+
if (ctx->current_primary != (size_t)-1) {
+
if (!ctx->reverse_video) {
+
ctx->set_text_fg_bright(ctx, ctx->current_primary);
+
} else {
+
ctx->set_text_bg_bright(ctx, ctx->current_primary);
+
}
+
}
+
continue;
+
}
+
+
else if (ctx->esc_values[i] == 22) {
+
ctx->bold = false;
+
if (ctx->current_primary != (size_t)-1) {
+
if (!ctx->reverse_video) {
+
ctx->set_text_fg(ctx, ctx->current_primary);
+
} else {
+
ctx->set_text_bg(ctx, ctx->current_primary);
+
}
+
}
+
continue;
+
}
+
+
else if (ctx->esc_values[i] >= 30 && ctx->esc_values[i] <= 37) {
+
offset = 30;
+
ctx->current_primary = ctx->esc_values[i] - offset;
+
+
if (ctx->reverse_video) {
+
goto set_bg;
+
}
+
+
set_fg:
+
if (ctx->bold && !ctx->reverse_video) {
+
ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
+
} else {
+
ctx->set_text_fg(ctx, ctx->esc_values[i] - offset);
+
}
+
continue;
+
}
+
+
else if (ctx->esc_values[i] >= 40 && ctx->esc_values[i] <= 47) {
+
offset = 40;
+
if (ctx->reverse_video) {
+
goto set_fg;
+
}
+
+
set_bg:
+
if (ctx->bold && ctx->reverse_video) {
+
ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
+
} else {
+
ctx->set_text_bg(ctx, ctx->esc_values[i] - offset);
+
}
+
continue;
+
}
+
+
else if (ctx->esc_values[i] >= 90 && ctx->esc_values[i] <= 97) {
+
offset = 90;
+
ctx->current_primary = ctx->esc_values[i] - offset;
+
+
if (ctx->reverse_video) {
+
goto set_bg_bright;
+
}
+
+
set_fg_bright:
+
ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
+
continue;
+
}
+
+
else if (ctx->esc_values[i] >= 100 && ctx->esc_values[i] <= 107) {
+
offset = 100;
+
if (ctx->reverse_video) {
+
goto set_fg_bright;
+
}
+
+
set_bg_bright:
+
ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
+
continue;
+
}
+
+
else if (ctx->esc_values[i] == 39) {
+
ctx->current_primary = (size_t)-1;
+
+
if (ctx->reverse_video) {
+
ctx->swap_palette(ctx);
+
}
+
+
ctx->set_text_fg_default(ctx);
+
+
if (ctx->reverse_video) {
+
ctx->swap_palette(ctx);
+
}
+
+
continue;
+
}
+
+
else if (ctx->esc_values[i] == 49) {
+
if (ctx->reverse_video) {
+
ctx->swap_palette(ctx);
+
}
+
+
ctx->set_text_bg_default(ctx);
+
+
if (ctx->reverse_video) {
+
ctx->swap_palette(ctx);
+
}
+
+
continue;
+
}
+
+
else if (ctx->esc_values[i] == 7) {
+
if (!ctx->reverse_video) {
+
ctx->reverse_video = true;
+
ctx->swap_palette(ctx);
+
}
+
continue;
+
}
+
+
else if (ctx->esc_values[i] == 27) {
+
if (ctx->reverse_video) {
+
ctx->reverse_video = false;
+
ctx->swap_palette(ctx);
+
}
+
continue;
+
}
+
+
// 256/RGB
+
else if (ctx->esc_values[i] == 38 || ctx->esc_values[i] == 48) {
+
bool fg = ctx->esc_values[i] == 38;
+
+
i++;
+
if (i >= ctx->esc_values_i) {
+
break;
+
}
+
+
switch (ctx->esc_values[i]) {
+
case 2: { // RGB
+
if (i + 3 >= ctx->esc_values_i) {
+
goto out;
+
}
+
+
uint32_t rgb_value = 0;
+
+
rgb_value |= ctx->esc_values[i + 1] << 16;
+
rgb_value |= ctx->esc_values[i + 2] << 8;
+
rgb_value |= ctx->esc_values[i + 3];
+
+
i += 3;
+
+
(fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
+
+
break;
+
}
+
case 5: { // 256 colors
+
if (i + 1 >= ctx->esc_values_i) {
+
goto out;
+
}
+
+
uint32_t col = ctx->esc_values[i + 1];
+
+
i++;
+
+
if (col < 8) {
+
(fg ? ctx->set_text_fg : ctx->set_text_bg)(ctx, col);
+
} else if (col < 16) {
+
(fg ? ctx->set_text_fg_bright : ctx->set_text_bg_bright)(ctx, col - 8);
+
} else {
+
uint32_t rgb_value = col256[col - 16];
+
(fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
+
}
+
+
break;
+
}
+
default: continue;
+
}
+
}
+
}
+
+
out:;
+
}
+
+
static void dec_private_parse(struct term_context *ctx, uint8_t c) {
+
ctx->dec_private = false;
+
+
if (ctx->esc_values_i == 0) {
+
return;
+
}
+
+
bool set;
+
+
switch (c) {
+
case 'h':
+
set = true; break;
+
case 'l':
+
set = false; break;
+
default:
+
return;
+
}
+
+
switch (ctx->esc_values[0]) {
+
case 25: {
+
if (set) {
+
ctx->enable_cursor(ctx);
+
} else {
+
ctx->disable_cursor(ctx);
+
}
+
return;
+
}
+
}
+
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_DEC, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
+
}
+
}
+
+
static void linux_private_parse(struct term_context *ctx) {
+
if (ctx->esc_values_i == 0) {
+
return;
+
}
+
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_LINUX, ctx->esc_values_i, (uintptr_t)ctx->esc_values, 0);
+
}
+
}
+
+
static void mode_toggle(struct term_context *ctx, uint8_t c) {
+
if (ctx->esc_values_i == 0) {
+
return;
+
}
+
+
bool set;
+
+
switch (c) {
+
case 'h':
+
set = true; break;
+
case 'l':
+
set = false; break;
+
default:
+
return;
+
}
+
+
switch (ctx->esc_values[0]) {
+
case 4:
+
ctx->insert_mode = set; return;
+
}
+
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_MODE, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
+
}
+
}
+
+
static void control_sequence_parse(struct term_context *ctx, uint8_t c) {
+
if (ctx->escape_offset == 2) {
+
switch (c) {
+
case '[':
+
ctx->discard_next = true;
+
goto cleanup;
+
case '?':
+
ctx->dec_private = true;
+
return;
+
}
+
}
+
+
if (c >= '0' && c <= '9') {
+
if (ctx->esc_values_i == TERM_MAX_ESC_VALUES) {
+
return;
+
}
+
ctx->rrr = true;
+
ctx->esc_values[ctx->esc_values_i] *= 10;
+
ctx->esc_values[ctx->esc_values_i] += c - '0';
+
return;
+
}
+
+
if (ctx->rrr == true) {
+
ctx->esc_values_i++;
+
ctx->rrr = false;
+
if (c == ';')
+
return;
+
} else if (c == ';') {
+
if (ctx->esc_values_i == TERM_MAX_ESC_VALUES) {
+
return;
+
}
+
ctx->esc_values[ctx->esc_values_i] = 0;
+
ctx->esc_values_i++;
+
return;
+
}
+
+
size_t esc_default;
+
switch (c) {
+
case 'J': case 'K': case 'q':
+
esc_default = 0; break;
+
default:
+
esc_default = 1; break;
+
}
+
+
for (size_t i = ctx->esc_values_i; i < TERM_MAX_ESC_VALUES; i++) {
+
ctx->esc_values[i] = esc_default;
+
}
+
+
if (ctx->dec_private == true) {
+
dec_private_parse(ctx, c);
+
goto cleanup;
+
}
+
+
bool r = ctx->scroll_enabled;
+
ctx->scroll_enabled = false;
+
size_t x, y;
+
ctx->get_cursor_pos(ctx, &x, &y);
+
+
switch (c) {
+
case 'F':
+
x = 0;
+
// FALLTHRU
+
case 'A': {
+
if (ctx->esc_values[0] > y)
+
ctx->esc_values[0] = y;
+
size_t orig_y = y;
+
size_t dest_y = y - ctx->esc_values[0];
+
bool will_be_in_scroll_region = false;
+
if ((ctx->scroll_top_margin >= dest_y && ctx->scroll_top_margin <= orig_y)
+
|| (ctx->scroll_bottom_margin >= dest_y && ctx->scroll_bottom_margin <= orig_y)) {
+
will_be_in_scroll_region = true;
+
}
+
if (will_be_in_scroll_region && dest_y < ctx->scroll_top_margin) {
+
dest_y = ctx->scroll_top_margin;
+
}
+
ctx->set_cursor_pos(ctx, x, dest_y);
+
break;
+
}
+
case 'E':
+
x = 0;
+
// FALLTHRU
+
case 'e':
+
case 'B': {
+
if (y + ctx->esc_values[0] > ctx->rows - 1)
+
ctx->esc_values[0] = (ctx->rows - 1) - y;
+
size_t orig_y = y;
+
size_t dest_y = y + ctx->esc_values[0];
+
bool will_be_in_scroll_region = false;
+
if ((ctx->scroll_top_margin >= orig_y && ctx->scroll_top_margin <= dest_y)
+
|| (ctx->scroll_bottom_margin >= orig_y && ctx->scroll_bottom_margin <= dest_y)) {
+
will_be_in_scroll_region = true;
+
}
+
if (will_be_in_scroll_region && dest_y >= ctx->scroll_bottom_margin) {
+
dest_y = ctx->scroll_bottom_margin - 1;
+
}
+
ctx->set_cursor_pos(ctx, x, dest_y);
+
break;
+
}
+
case 'a':
+
case 'C':
+
if (x + ctx->esc_values[0] > ctx->cols - 1)
+
ctx->esc_values[0] = (ctx->cols - 1) - x;
+
ctx->set_cursor_pos(ctx, x + ctx->esc_values[0], y);
+
break;
+
case 'D':
+
if (ctx->esc_values[0] > x)
+
ctx->esc_values[0] = x;
+
ctx->set_cursor_pos(ctx, x - ctx->esc_values[0], y);
+
break;
+
case 'c':
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_PRIVATE_ID, 0, 0, 0);
+
}
+
break;
+
case 'd':
+
ctx->esc_values[0] -= 1;
+
if (ctx->esc_values[0] >= ctx->rows)
+
ctx->esc_values[0] = ctx->rows - 1;
+
ctx->set_cursor_pos(ctx, x, ctx->esc_values[0]);
+
break;
+
case 'G':
+
case '`':
+
ctx->esc_values[0] -= 1;
+
if (ctx->esc_values[0] >= ctx->cols)
+
ctx->esc_values[0] = ctx->cols - 1;
+
ctx->set_cursor_pos(ctx, ctx->esc_values[0], y);
+
break;
+
case 'H':
+
case 'f':
+
ctx->esc_values[0] -= 1;
+
ctx->esc_values[1] -= 1;
+
if (ctx->esc_values[1] >= ctx->cols)
+
ctx->esc_values[1] = ctx->cols - 1;
+
if (ctx->esc_values[0] >= ctx->rows)
+
ctx->esc_values[0] = ctx->rows - 1;
+
ctx->set_cursor_pos(ctx, ctx->esc_values[1], ctx->esc_values[0]);
+
break;
+
case 'n':
+
switch (ctx->esc_values[0]) {
+
case 5:
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_STATUS_REPORT, 0, 0, 0);
+
}
+
break;
+
case 6:
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_POS_REPORT, x + 1, y + 1, 0);
+
}
+
break;
+
}
+
break;
+
case 'q':
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_KBD_LEDS, ctx->esc_values[0], 0, 0);
+
}
+
break;
+
case 'J':
+
switch (ctx->esc_values[0]) {
+
case 0: {
+
size_t rows_remaining = ctx->rows - (y + 1);
+
size_t cols_diff = ctx->cols - (x + 1);
+
size_t to_clear = rows_remaining * ctx->cols + cols_diff;
+
for (size_t i = 0; i < to_clear; i++) {
+
ctx->raw_putchar(ctx, ' ');
+
}
+
ctx->set_cursor_pos(ctx, x, y);
+
break;
+
}
+
case 1: {
+
ctx->set_cursor_pos(ctx, 0, 0);
+
bool b = false;
+
for (size_t yc = 0; yc < ctx->rows; yc++) {
+
for (size_t xc = 0; xc < ctx->cols; xc++) {
+
ctx->raw_putchar(ctx, ' ');
+
if (xc == x && yc == y) {
+
ctx->set_cursor_pos(ctx, x, y);
+
b = true;
+
break;
+
}
+
}
+
if (b == true)
+
break;
+
}
+
break;
+
}
+
case 2:
+
case 3:
+
ctx->clear(ctx, false);
+
break;
+
}
+
break;
+
case '@':
+
for (size_t i = ctx->cols - 1; ; i--) {
+
ctx->move_character(ctx, i + ctx->esc_values[0], y, i, y);
+
ctx->set_cursor_pos(ctx, i, y);
+
ctx->raw_putchar(ctx, ' ');
+
if (i == x) {
+
break;
+
}
+
}
+
ctx->set_cursor_pos(ctx, x, y);
+
break;
+
case 'P':
+
for (size_t i = x + ctx->esc_values[0]; i < ctx->cols; i++)
+
ctx->move_character(ctx, i - ctx->esc_values[0], y, i, y);
+
ctx->set_cursor_pos(ctx, ctx->cols - ctx->esc_values[0], y);
+
// FALLTHRU
+
case 'X':
+
for (size_t i = 0; i < ctx->esc_values[0]; i++)
+
ctx->raw_putchar(ctx, ' ');
+
ctx->set_cursor_pos(ctx, x, y);
+
break;
+
case 'm':
+
sgr(ctx);
+
break;
+
case 's':
+
ctx->get_cursor_pos(ctx, &ctx->saved_cursor_x, &ctx->saved_cursor_y);
+
break;
+
case 'u':
+
ctx->set_cursor_pos(ctx, ctx->saved_cursor_x, ctx->saved_cursor_y);
+
break;
+
case 'K':
+
switch (ctx->esc_values[0]) {
+
case 0: {
+
for (size_t i = x; i < ctx->cols; i++)
+
ctx->raw_putchar(ctx, ' ');
+
ctx->set_cursor_pos(ctx, x, y);
+
break;
+
}
+
case 1: {
+
ctx->set_cursor_pos(ctx, 0, y);
+
for (size_t i = 0; i < x; i++)
+
ctx->raw_putchar(ctx, ' ');
+
break;
+
}
+
case 2: {
+
ctx->set_cursor_pos(ctx, 0, y);
+
for (size_t i = 0; i < ctx->cols; i++)
+
ctx->raw_putchar(ctx, ' ');
+
ctx->set_cursor_pos(ctx, x, y);
+
break;
+
}
+
}
+
break;
+
case 'r':
+
ctx->scroll_top_margin = 0;
+
ctx->scroll_bottom_margin = ctx->rows;
+
if (ctx->esc_values_i > 0) {
+
ctx->scroll_top_margin = ctx->esc_values[0] - 1;
+
}
+
if (ctx->esc_values_i > 1) {
+
ctx->scroll_bottom_margin = ctx->esc_values[1];
+
}
+
if (ctx->scroll_top_margin >= ctx->rows
+
|| ctx->scroll_bottom_margin > ctx->rows
+
|| ctx->scroll_top_margin >= (ctx->scroll_bottom_margin - 1)) {
+
ctx->scroll_top_margin = 0;
+
ctx->scroll_bottom_margin = ctx->rows;
+
}
+
ctx->set_cursor_pos(ctx, 0, 0);
+
break;
+
case 'l':
+
case 'h':
+
mode_toggle(ctx, c);
+
break;
+
case ']':
+
linux_private_parse(ctx);
+
break;
+
}
+
+
ctx->scroll_enabled = r;
+
+
cleanup:
+
ctx->control_sequence = false;
+
ctx->escape = false;
+
}
+
+
static void restore_state(struct term_context *ctx) {
+
ctx->bold = ctx->saved_state_bold;
+
ctx->reverse_video = ctx->saved_state_reverse_video;
+
ctx->current_charset = ctx->saved_state_current_charset;
+
ctx->current_primary = ctx->saved_state_current_primary;
+
+
ctx->restore_state(ctx);
+
}
+
+
static void save_state(struct term_context *ctx) {
+
ctx->save_state(ctx);
+
+
ctx->saved_state_bold = ctx->bold;
+
ctx->saved_state_reverse_video = ctx->reverse_video;
+
ctx->saved_state_current_charset = ctx->current_charset;
+
ctx->saved_state_current_primary = ctx->current_primary;
+
}
+
+
static void escape_parse(struct term_context *ctx, uint8_t c) {
+
ctx->escape_offset++;
+
+
if (ctx->control_sequence == true) {
+
control_sequence_parse(ctx, c);
+
return;
+
}
+
+
if (ctx->csi == true) {
+
ctx->csi = false;
+
goto is_csi;
+
}
+
+
size_t x, y;
+
ctx->get_cursor_pos(ctx, &x, &y);
+
+
switch (c) {
+
case '[':
+
is_csi:
+
for (size_t i = 0; i < TERM_MAX_ESC_VALUES; i++)
+
ctx->esc_values[i] = 0;
+
ctx->esc_values_i = 0;
+
ctx->rrr = false;
+
ctx->control_sequence = true;
+
return;
+
case '7':
+
save_state(ctx);
+
break;
+
case '8':
+
restore_state(ctx);
+
break;
+
case 'c':
+
term_context_reinit(ctx);
+
ctx->clear(ctx, true);
+
break;
+
case 'D':
+
if (y == ctx->scroll_bottom_margin - 1) {
+
ctx->scroll(ctx);
+
ctx->set_cursor_pos(ctx, x, y);
+
} else {
+
ctx->set_cursor_pos(ctx, x, y + 1);
+
}
+
break;
+
case 'E':
+
if (y == ctx->scroll_bottom_margin - 1) {
+
ctx->scroll(ctx);
+
ctx->set_cursor_pos(ctx, 0, y);
+
} else {
+
ctx->set_cursor_pos(ctx, 0, y + 1);
+
}
+
break;
+
case 'M':
+
// "Reverse linefeed"
+
if (y == ctx->scroll_top_margin) {
+
ctx->revscroll(ctx);
+
ctx->set_cursor_pos(ctx, 0, y);
+
} else {
+
ctx->set_cursor_pos(ctx, 0, y - 1);
+
}
+
break;
+
case 'Z':
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_PRIVATE_ID, 0, 0, 0);
+
}
+
break;
+
case '(':
+
case ')':
+
ctx->g_select = c - '\'';
+
break;
+
case '\e':
+
if (ctx->in_bootloader == true) {
+
ctx->raw_putchar(ctx, c);
+
}
+
break;
+
}
+
+
ctx->escape = false;
+
}
+
+
static uint8_t dec_special_to_cp437(uint8_t c) {
+
switch (c) {
+
case '`': return 0x04;
+
case '0': return 0xdb;
+
case '-': return 0x18;
+
case ',': return 0x1b;
+
case '.': return 0x19;
+
case 'a': return 0xb1;
+
case 'f': return 0xf8;
+
case 'g': return 0xf1;
+
case 'h': return 0xb0;
+
case 'j': return 0xd9;
+
case 'k': return 0xbf;
+
case 'l': return 0xda;
+
case 'm': return 0xc0;
+
case 'n': return 0xc5;
+
case 'q': return 0xc4;
+
case 's': return 0x5f;
+
case 't': return 0xc3;
+
case 'u': return 0xb4;
+
case 'v': return 0xc1;
+
case 'w': return 0xc2;
+
case 'x': return 0xb3;
+
case 'y': return 0xf3;
+
case 'z': return 0xf2;
+
case '~': return 0xfa;
+
case '_': return 0xff;
+
case '+': return 0x1a;
+
case '{': return 0xe3;
+
case '}': return 0x9c;
+
}
+
+
return c;
+
}
+
+
static void term_putchar(struct term_context *ctx, uint8_t c) {
+
if (ctx->discard_next || (ctx->in_bootloader == false && (c == 0x18 || c == 0x1a))) {
+
ctx->discard_next = false;
+
ctx->escape = false;
+
ctx->csi = false;
+
ctx->control_sequence = false;
+
ctx->g_select = 0;
+
return;
+
}
+
+
if (ctx->escape == true) {
+
escape_parse(ctx, c);
+
return;
+
}
+
+
if (ctx->g_select) {
+
ctx->g_select--;
+
switch (c) {
+
case 'B':
+
ctx->charsets[ctx->g_select] = CHARSET_DEFAULT; break;
+
case '0':
+
ctx->charsets[ctx->g_select] = CHARSET_DEC_SPECIAL; break;
+
}
+
ctx->g_select = 0;
+
return;
+
}
+
+
size_t x, y;
+
ctx->get_cursor_pos(ctx, &x, &y);
+
+
switch (c) {
+
case 0x00:
+
case 0x7f:
+
return;
+
case 0x9b:
+
ctx->csi = true;
+
// FALLTHRU
+
case '\e':
+
ctx->escape_offset = 0;
+
ctx->escape = true;
+
return;
+
case '\t':
+
if ((x / ctx->tab_size + 1) >= ctx->cols) {
+
ctx->set_cursor_pos(ctx, ctx->cols - 1, y);
+
return;
+
}
+
ctx->set_cursor_pos(ctx, (x / ctx->tab_size + 1) * ctx->tab_size, y);
+
return;
+
case 0x0b:
+
case 0x0c:
+
case '\n':
+
if (y == ctx->scroll_bottom_margin - 1) {
+
ctx->scroll(ctx);
+
ctx->set_cursor_pos(ctx, 0, y);
+
} else {
+
ctx->set_cursor_pos(ctx, 0, y + 1);
+
}
+
return;
+
case '\b':
+
ctx->set_cursor_pos(ctx, x - 1, y);
+
return;
+
case '\r':
+
ctx->set_cursor_pos(ctx, 0, y);
+
return;
+
case '\a':
+
// The bell is handled by the kernel
+
if (ctx->callback != NULL) {
+
ctx->callback(ctx, TERM_CB_BELL, 0, 0, 0);
+
}
+
return;
+
case 14:
+
// Move to G1 set
+
ctx->current_charset = 1;
+
return;
+
case 15:
+
// Move to G0 set
+
ctx->current_charset = 0;
+
return;
+
}
+
+
if (ctx->insert_mode == true) {
+
for (size_t i = ctx->cols - 1; ; i--) {
+
ctx->move_character(ctx, i + 1, y, i, y);
+
if (i == x) {
+
break;
+
}
+
}
+
}
+
+
// Translate character set
+
switch (ctx->charsets[ctx->current_charset]) {
+
case CHARSET_DEFAULT:
+
break;
+
case CHARSET_DEC_SPECIAL:
+
c = dec_special_to_cp437(c);
+
}
+
+
ctx->raw_putchar(ctx, c);
+
}
+85
term.h
···
+
#ifndef _TERM_H
+
#define _TERM_H
+
+
#include <stddef.h>
+
#include <stdint.h>
+
#include <stdbool.h>
+
+
#define TERM_MAX_ESC_VALUES 16
+
+
#define TERM_CB_DEC 10
+
#define TERM_CB_BELL 20
+
#define TERM_CB_PRIVATE_ID 30
+
#define TERM_CB_STATUS_REPORT 40
+
#define TERM_CB_POS_REPORT 50
+
#define TERM_CB_KBD_LEDS 60
+
#define TERM_CB_MODE 70
+
#define TERM_CB_LINUX 80
+
+
struct term_context {
+
size_t rows, cols;
+
+
size_t tab_size;
+
+
bool autoflush;
+
+
bool in_bootloader;
+
+
bool scroll_enabled;
+
bool control_sequence;
+
bool csi;
+
bool escape;
+
bool rrr;
+
bool discard_next;
+
bool bold;
+
bool reverse_video;
+
bool dec_private;
+
bool insert_mode;
+
uint8_t g_select;
+
uint8_t charsets[2];
+
size_t current_charset;
+
size_t escape_offset;
+
size_t esc_values_i;
+
size_t saved_cursor_x;
+
size_t saved_cursor_y;
+
size_t current_primary;
+
size_t scroll_top_margin;
+
size_t scroll_bottom_margin;
+
uint32_t esc_values[TERM_MAX_ESC_VALUES];
+
+
bool saved_state_bold;
+
bool saved_state_reverse_video;
+
size_t saved_state_current_charset;
+
size_t saved_state_current_primary;
+
+
void (*raw_putchar)(struct term_context *, uint8_t c);
+
void (*clear)(struct term_context *, bool move);
+
void (*enable_cursor)(struct term_context *);
+
bool (*disable_cursor)(struct term_context *);
+
void (*set_cursor_pos)(struct term_context *, size_t x, size_t y);
+
void (*get_cursor_pos)(struct term_context *, size_t *x, size_t *y);
+
void (*set_text_fg)(struct term_context *, size_t fg);
+
void (*set_text_bg)(struct term_context *, size_t bg);
+
void (*set_text_fg_bright)(struct term_context *, size_t fg);
+
void (*set_text_bg_bright)(struct term_context *, size_t bg);
+
void (*set_text_fg_rgb)(struct term_context *, uint32_t fg);
+
void (*set_text_bg_rgb)(struct term_context *, uint32_t bg);
+
void (*set_text_fg_default)(struct term_context *);
+
void (*set_text_bg_default)(struct term_context *);
+
void (*move_character)(struct term_context *, size_t new_x, size_t new_y, size_t old_x, size_t old_y);
+
void (*scroll)(struct term_context *);
+
void (*revscroll)(struct term_context *);
+
void (*swap_palette)(struct term_context *);
+
void (*save_state)(struct term_context *);
+
void (*restore_state)(struct term_context *);
+
void (*double_buffer_flush)(struct term_context *);
+
void (*full_refresh)(struct term_context *);
+
void (*deinit)(struct term_context *, void (*)(void *, size_t));
+
+
void (*callback)(struct term_context *, uint64_t, uint64_t, uint64_t, uint64_t);
+
};
+
+
void term_context_reinit(struct term_context *ctx);
+
void term_write(struct term_context *ctx, const char *buf, size_t count);
+
+
#endif