280 lines
9.1 KiB
C++
280 lines
9.1 KiB
C++
/*
|
|
* Terminal.cpp
|
|
* Terminal implementation
|
|
* Copyright (c) 2025 Daniel Hammer
|
|
*/
|
|
|
|
#include "Terminal.hpp"
|
|
|
|
#define FLANTERM_IN_FLANTERM
|
|
#include "../Libraries/flanterm/src/flanterm_backends/fb.h"
|
|
#include "../Libraries/flanterm/src/flanterm.h"
|
|
|
|
#include "../Libraries/String.hpp"
|
|
#include "../Libraries/Memory.hpp"
|
|
#include <CppLib/CString.hpp>
|
|
#include <CppLib/Spinlock.hpp>
|
|
|
|
namespace Kt {
|
|
flanterm_context *ctx;
|
|
std::size_t g_terminal_width = 0;
|
|
|
|
// Kernel log depth counter and suppression flag
|
|
uint32_t g_kernelLogDepth = 0;
|
|
bool g_suppressKernelLog = false;
|
|
|
|
// Protects flanterm writes from concurrent CPU access.
|
|
// Mutex (not Spinlock) so interrupts stay enabled -- prevents dropped
|
|
// PS/2 mouse/keyboard bytes during log output.
|
|
kcp::Mutex g_termLock;
|
|
|
|
// 64KB ring buffer for kernel log messages
|
|
static constexpr uint64_t KLOG_BUF_SIZE = 65536;
|
|
static char g_klogBuf[KLOG_BUF_SIZE];
|
|
static uint64_t g_klogHead = 0; // next write position
|
|
static uint64_t g_klogCount = 0; // total chars stored (capped at KLOG_BUF_SIZE)
|
|
|
|
// ANSI escape sequence filter state
|
|
static bool g_ansiEscape = false;
|
|
|
|
static void RingBufferAppend(char c) {
|
|
// Strip ANSI escape sequences
|
|
if (c == '\033') {
|
|
g_ansiEscape = true;
|
|
return;
|
|
}
|
|
if (g_ansiEscape) {
|
|
if (c == 'm') {
|
|
g_ansiEscape = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
g_klogBuf[g_klogHead] = c;
|
|
g_klogHead = (g_klogHead + 1) % KLOG_BUF_SIZE;
|
|
if (g_klogCount < KLOG_BUF_SIZE) {
|
|
g_klogCount++;
|
|
}
|
|
}
|
|
|
|
// Maximum grid cells allocated at init (scale 1,1). Used to validate
|
|
// that a requested scale does not exceed the original buffer capacity.
|
|
static std::size_t g_max_grid_cells = 0;
|
|
|
|
// Custom plot_char that works for any font_scale_x/y >= 1.
|
|
// This is the same algorithm as flanterm's plot_char_scaled_uncanvas
|
|
// but lives outside fb.c so we can install it after rescaling.
|
|
static void plot_char_universal(struct flanterm_context *_ctx,
|
|
struct flanterm_fb_char *c,
|
|
size_t x, size_t y) {
|
|
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)_ctx;
|
|
|
|
if (x >= _ctx->cols || y >= _ctx->rows) {
|
|
return;
|
|
}
|
|
|
|
uint32_t default_bg = fbctx->default_bg;
|
|
uint32_t bg = c->bg == 0xffffffff ? default_bg : c->bg;
|
|
uint32_t fg = c->fg == 0xffffffff ? fbctx->default_fg : c->fg;
|
|
|
|
x = fbctx->offset_x + x * fbctx->glyph_width;
|
|
y = fbctx->offset_y + y * fbctx->glyph_height;
|
|
|
|
bool *glyph = &fbctx->font_bool[c->c * fbctx->font_height * fbctx->font_width];
|
|
|
|
// Only ROTATE_0 is used in MontaukOS
|
|
volatile uint32_t *dest = fbctx->framebuffer + x + y * (fbctx->pitch / 4);
|
|
size_t stride = fbctx->pitch / 4;
|
|
|
|
for (size_t gy = 0; gy < fbctx->glyph_height; gy++) {
|
|
size_t fy = gy / fbctx->font_scale_y;
|
|
volatile uint32_t *fb_line = dest;
|
|
bool *glyph_pointer = glyph + (fy * fbctx->font_width);
|
|
for (size_t fx = 0; fx < fbctx->font_width; fx++) {
|
|
for (size_t i = 0; i < fbctx->font_scale_x; i++) {
|
|
*fb_line = *glyph_pointer ? fg : bg;
|
|
fb_line++;
|
|
}
|
|
glyph_pointer++;
|
|
}
|
|
dest += stride;
|
|
}
|
|
}
|
|
|
|
void Rescale(std::size_t scale_x, std::size_t scale_y) {
|
|
if (scale_x == 0) scale_x = 1;
|
|
if (scale_y == 0) scale_y = 1;
|
|
|
|
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
|
|
|
|
// Calculate new dimensions
|
|
size_t new_glyph_w = fbctx->font_width * scale_x;
|
|
size_t new_glyph_h = fbctx->font_height * scale_y;
|
|
size_t new_cols = fbctx->width / new_glyph_w;
|
|
size_t new_rows = fbctx->height / new_glyph_h;
|
|
|
|
if (new_cols == 0 || new_rows == 0) return;
|
|
|
|
// Ensure the new grid fits within original buffer allocation
|
|
if (new_cols * new_rows > g_max_grid_cells) return;
|
|
|
|
// Update scale and glyph dimensions
|
|
fbctx->font_scale_x = scale_x;
|
|
fbctx->font_scale_y = scale_y;
|
|
fbctx->glyph_width = new_glyph_w;
|
|
fbctx->glyph_height = new_glyph_h;
|
|
|
|
// Update terminal grid dimensions
|
|
ctx->cols = new_cols;
|
|
ctx->rows = new_rows;
|
|
|
|
// Center the text area
|
|
fbctx->offset_x = (fbctx->width % new_glyph_w) / 2;
|
|
fbctx->offset_y = (fbctx->height % new_glyph_h) / 2;
|
|
|
|
// Install our universal plot_char
|
|
fbctx->plot_char = plot_char_universal;
|
|
|
|
// Reinitialize grid data (reuse existing buffers)
|
|
for (size_t i = 0; i < new_rows * new_cols; i++) {
|
|
fbctx->grid[i].c = ' ';
|
|
fbctx->grid[i].fg = fbctx->text_fg;
|
|
fbctx->grid[i].bg = fbctx->text_bg;
|
|
}
|
|
|
|
fbctx->queue_i = 0;
|
|
memset(fbctx->queue, 0, new_rows * new_cols * sizeof(struct flanterm_fb_queue_item));
|
|
memset(fbctx->map, 0, new_rows * new_cols * sizeof(struct flanterm_fb_queue_item *));
|
|
|
|
// Clear the framebuffer
|
|
for (size_t y = 0; y < fbctx->height; y++) {
|
|
volatile uint32_t *row = fbctx->framebuffer + y * (fbctx->pitch / 4);
|
|
for (size_t x = 0; x < fbctx->width; x++) {
|
|
row[x] = fbctx->default_bg;
|
|
}
|
|
}
|
|
|
|
// Reset terminal state and refresh
|
|
flanterm_context_reinit(ctx);
|
|
flanterm_full_refresh(ctx);
|
|
}
|
|
|
|
std::size_t GetFontScaleX() {
|
|
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
|
|
return fbctx->font_scale_x;
|
|
}
|
|
|
|
std::size_t GetFontScaleY() {
|
|
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
|
|
return fbctx->font_scale_y;
|
|
}
|
|
|
|
void Initialize(std::uint32_t *framebuffer, std::size_t width, std::size_t height, std::size_t pitch,
|
|
std::uint8_t red_mask_size, std::uint8_t red_mask_shift,
|
|
std::uint8_t green_mask_size, std::uint8_t green_mask_shift,
|
|
std::uint8_t blue_mask_size, std::uint8_t blue_mask_shift
|
|
)
|
|
{
|
|
ctx = flanterm_fb_init(
|
|
NULL,
|
|
NULL,
|
|
framebuffer,
|
|
width, height, pitch,
|
|
red_mask_size, red_mask_shift,
|
|
green_mask_size, green_mask_shift,
|
|
blue_mask_size, blue_mask_shift,
|
|
NULL,
|
|
NULL, NULL,
|
|
NULL, NULL,
|
|
NULL, NULL,
|
|
NULL, 0, 0, 1,
|
|
1, 1,
|
|
0,
|
|
FLANTERM_FB_ROTATE_0
|
|
);
|
|
|
|
g_terminal_width = width;
|
|
|
|
// Store max grid cells for rescale buffer bounds checking
|
|
g_max_grid_cells = ctx->cols * ctx->rows;
|
|
|
|
// Install our universal plot_char so rescaling works at any scale
|
|
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
|
|
fbctx->plot_char = plot_char_universal;
|
|
}
|
|
|
|
void Putchar(char c) {
|
|
if (g_kernelLogDepth > 0) {
|
|
if (c == '\n') {
|
|
RingBufferAppend('\r');
|
|
RingBufferAppend('\n');
|
|
} else {
|
|
RingBufferAppend(c);
|
|
}
|
|
|
|
if (g_suppressKernelLog) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Once a graphical app takes over, suppress ALL flanterm writes
|
|
// (SYS_PRINT from user processes, etc.) to avoid painting text
|
|
// over the GUI framebuffer.
|
|
if (g_suppressKernelLog && g_kernelLogDepth == 0) {
|
|
return;
|
|
}
|
|
|
|
if (c == '\n') {
|
|
flanterm_write(ctx, "\r\n", 2);
|
|
return;
|
|
}
|
|
flanterm_write(ctx, &c, 1);
|
|
}
|
|
|
|
void Print(const char *text) {
|
|
for (size_t i = 0; text[i] != '\0'; i++) {
|
|
Putchar(text[i]);
|
|
}
|
|
}
|
|
|
|
void SuppressKernelLog() {
|
|
g_suppressKernelLog = true;
|
|
}
|
|
|
|
void EnablePanicOutput() {
|
|
// Once the graphical desktop starts, ordinary console writes are
|
|
// suppressed to avoid painting over it. A kernel panic is different:
|
|
// leaving suppression enabled makes the halted system look like a
|
|
// frozen desktop. Do not acquire g_termLock here; panic may have
|
|
// interrupted its owner on this or another CPU.
|
|
g_suppressKernelLog = false;
|
|
}
|
|
|
|
int64_t ReadKernelLogBuffer(char* buf, uint64_t size) {
|
|
if (buf == nullptr || size == 0) return 0;
|
|
|
|
uint64_t toRead = g_klogCount;
|
|
if (toRead > size) toRead = size;
|
|
|
|
// Start position: oldest character in the ring buffer
|
|
uint64_t start;
|
|
if (g_klogCount < KLOG_BUF_SIZE) {
|
|
start = 0;
|
|
} else {
|
|
start = g_klogHead; // head points to the oldest entry when full
|
|
}
|
|
|
|
// Copy from oldest to newest, skipping entries if buffer too small
|
|
uint64_t skipCount = g_klogCount - toRead;
|
|
uint64_t readPos = (start + skipCount) % KLOG_BUF_SIZE;
|
|
|
|
for (uint64_t i = 0; i < toRead; i++) {
|
|
buf[i] = g_klogBuf[readPos];
|
|
readPos = (readPos + 1) % KLOG_BUF_SIZE;
|
|
}
|
|
|
|
return (int64_t)toRead;
|
|
}
|
|
|
|
};
|