#include "video/TextConsole.h" #include "boot/bootboot.h" #include "video/Framebuffer.h" #include #include #include #include #include #include #include #include extern const BOOTBOOT bootboot; #include "video/BuiltinFont.h" // Default text color. static constexpr u32 WHITE = 0xffffffff; // xterm color palette. static constexpr u32 BLACK = 0xff000000; static constexpr u32 RED = 0xffcd0000; static constexpr u32 GREEN = 0xff00cd00; static constexpr u32 YELLOW = 0xffcdcd00; static constexpr u32 BLUE = 0xff0000ee; static constexpr u32 MAGENTA = 0xffcd00cd; static constexpr u32 CYAN = 0xff00cdcd; static constexpr u32 GRAY = 0xffe5e5e5; static constexpr u32 BRIGHT_BLACK = 0xff7f7f7f; static constexpr u32 BRIGHT_RED = 0xffff0000; static constexpr u32 BRIGHT_GREEN = 0xff00ff00; static constexpr u32 BRIGHT_YELLOW = 0xffffff00; static constexpr u32 BRIGHT_BLUE = 0xff5c5cff; static constexpr u32 BRIGHT_MAGENTA = 0xffff00ff; static constexpr u32 BRIGHT_CYAN = 0xff00ffff; static constexpr u32 BRIGHT_GRAY = 0xffffffff; static u32 g_background_color = BLACK; static u32 g_foreground_color = WHITE; static constexpr u32 FONT_HEIGHT = 16; static constexpr u32 FONT_WIDTH = 8; static bool bold = false; static u32 g_x_position = 0; static u32 g_y_position = 0; static constexpr int CURSOR_TIMEOUT = 500; static int current_cursor_timeout = CURSOR_TIMEOUT; static bool cursor_activated = true; static bool cursor_enabled = true; static Utf8StateDecoder utf8_decoder; static Option escape_sequence_parser; static void putwchar_at(wchar_t c, u32 x, u32 y) { const u8* glyph = &font[c * 16]; for (u32 i = 0; i < FONT_HEIGHT; i++) { for (u32 j = 0; j < FONT_WIDTH; j++) { volatile u8 mask = *glyph; if (mask & (0b10000000 >> j)) Framebuffer::pixel(x + j, y + i, g_foreground_color); else Framebuffer::pixel(x + j, y + i, g_background_color); } glyph++; } } static void erase_current_line() { Framebuffer::rect(0, g_y_position, Framebuffer::width(), FONT_HEIGHT, BLACK); } static void scroll() { memcpy(Framebuffer::ptr(), Framebuffer::ptr() + (Framebuffer::scanline() * FONT_HEIGHT), Framebuffer::size() - (Framebuffer::scanline() * FONT_HEIGHT)); g_y_position -= FONT_HEIGHT; erase_current_line(); } static bool should_scroll() { return g_y_position >= Framebuffer::height(); } static void next_line() { g_x_position = 0; g_y_position += FONT_HEIGHT; } static void next_char() { g_x_position += FONT_WIDTH; } static void prev_char() { g_x_position -= FONT_WIDTH; } static void erase_current_char() { Framebuffer::rect(g_x_position, g_y_position, FONT_WIDTH, FONT_HEIGHT, BLACK); } static void draw_cursor() { Framebuffer::rect(g_x_position, g_y_position, FONT_WIDTH, FONT_HEIGHT, WHITE); } static bool at_end_of_screen() { return (g_x_position + FONT_WIDTH) > Framebuffer::width(); } static bool handle_escape_sequence(wchar_t c) { auto rc = escape_sequence_parser->advance(static_cast(c)); if (rc.has_error()) { escape_sequence_parser = Option {}; return false; } if (!rc.value()) return true; if (!escape_sequence_parser->valid()) { escape_sequence_parser = Option {}; return false; } const auto& params = escape_sequence_parser->parameters(); switch (escape_sequence_parser->code()) { case EscapeCode::CursorUp: { int lines = params.size() ? params[0] : 1; int pixels = lines * FONT_HEIGHT; if ((u32)pixels > g_y_position) g_y_position = 0; else g_y_position -= pixels; }; break; case EscapeCode::CursorDown: { int lines = params.size() ? params[0] : 1; int pixels = lines * FONT_HEIGHT; if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT; else g_y_position += pixels; }; break; case EscapeCode::CursorBack: { int chars = params.size() ? params[0] : 1; int pixels = chars * FONT_WIDTH; if ((u32)pixels > g_x_position) g_x_position = 0; else g_x_position -= pixels; }; break; case EscapeCode::CursorForward: { int chars = params.size() ? params[0] : 1; int pixels = chars * FONT_WIDTH; if (pixels + g_x_position >= Framebuffer::width()) g_x_position = Framebuffer::width() - FONT_WIDTH; else g_x_position += pixels; }; break; case EscapeCode::CursorNextLine: { int lines = params.size() ? params[0] : 1; int pixels = lines * FONT_HEIGHT; if ((u32)pixels > g_y_position) g_y_position = 0; else g_y_position -= pixels; g_x_position = 0; }; break; case EscapeCode::CursorPreviousLine: { int lines = params.size() ? params[0] : 1; int pixels = lines * FONT_HEIGHT; if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT; else g_y_position += pixels; g_x_position = 0; }; break; case EscapeCode::CursorHorizontalAbsolute: { int line = (params.size() ? params[0] : 1) - 1; if (line < 0) break; u32 position = line * FONT_HEIGHT; if (position >= Framebuffer::height()) position = Framebuffer::height() - FONT_HEIGHT; g_y_position = position; }; break; case EscapeCode::SetCursorPosition: { int x = (params.size() ? params[0] : 1) - 1; int y = (params.size() > 1 ? params[1] : 1) - 1; if (x < 0 || y < 0) break; u32 x_position = x * FONT_WIDTH; if (x_position >= Framebuffer::width()) x_position = Framebuffer::width() - FONT_HEIGHT; g_x_position = x_position; u32 y_position = y * FONT_HEIGHT; if (y_position >= Framebuffer::height()) y_position = Framebuffer::height() - FONT_HEIGHT; g_y_position = y_position; }; break; case EscapeCode::SelectGraphicRendition: { if (!params.size()) { g_foreground_color = WHITE; g_background_color = BLACK; bold = false; break; } for (usize i = 0; i < params.size(); i++) { int arg = params[i]; switch (arg) { case 0: { g_foreground_color = BLACK; g_background_color = WHITE; bold = false; break; } case 1: { bold = true; break; } case 22: { bold = false; break; } case 30: { g_foreground_color = bold ? BRIGHT_BLACK : BLACK; break; } case 31: { g_foreground_color = bold ? BRIGHT_RED : RED; break; } case 32: { g_foreground_color = bold ? BRIGHT_GREEN : GREEN; break; } case 33: { g_foreground_color = bold ? BRIGHT_YELLOW : YELLOW; break; } case 34: { g_foreground_color = bold ? BRIGHT_BLUE : BLUE; break; } case 35: { g_foreground_color = bold ? BRIGHT_MAGENTA : MAGENTA; break; } case 36: { g_foreground_color = bold ? BRIGHT_CYAN : CYAN; break; } case 37: { g_foreground_color = bold ? BRIGHT_GRAY : GRAY; break; } case 39: { g_foreground_color = WHITE; break; } case 40: { g_background_color = bold ? BRIGHT_BLACK : BLACK; break; } case 41: { g_background_color = bold ? BRIGHT_RED : RED; break; } case 42: { g_background_color = bold ? BRIGHT_GREEN : GREEN; break; } case 43: { g_background_color = bold ? BRIGHT_YELLOW : YELLOW; break; } case 44: { g_background_color = bold ? BRIGHT_BLUE : BLUE; break; } case 45: { g_background_color = bold ? BRIGHT_MAGENTA : MAGENTA; break; } case 46: { g_background_color = bold ? BRIGHT_CYAN : CYAN; break; } case 47: { g_background_color = bold ? BRIGHT_GRAY : GRAY; break; } case 49: { g_background_color = BLACK; break; } default: break; } } } break; default: break; } escape_sequence_parser = Option {}; return true; } namespace TextConsole { void putwchar(wchar_t c) { // Unprintable (not in the built-in font) characters get represented as a box if (c > (wchar_t)255) c = (wchar_t)256; if (escape_sequence_parser.has_value()) { if (handle_escape_sequence(c)) return; } // Erase the current cursor. if (cursor_enabled) erase_current_char(); bool should_draw_cursor = cursor_enabled; switch (c) { case L'\n': { next_line(); if (should_scroll()) scroll(); break; } case L'\t': { wprint(L" "); break; } case L'\r': g_x_position = 0; break; case L'\b': if (g_x_position != 0) { prev_char(); erase_current_char(); } break; case L'\x1b': case L'\x9b': case L'\x90': case L'\x9d': escape_sequence_parser = EscapeSequenceParser { (u8)c }; should_draw_cursor = false; break; default: { if (_iscntrl(c)) return; putwchar_at(c, g_x_position, g_y_position); next_char(); if (at_end_of_screen()) { next_line(); if (should_scroll()) scroll(); } break; } } if (should_draw_cursor) { current_cursor_timeout = CURSOR_TIMEOUT; cursor_activated = true; draw_cursor(); } } void tick_cursor() { if (!cursor_enabled) return; current_cursor_timeout--; if (current_cursor_timeout == 0) { current_cursor_timeout = CURSOR_TIMEOUT; cursor_activated = !cursor_activated; if (cursor_activated) draw_cursor(); else erase_current_char(); } } void disable_cursor() { cursor_enabled = false; } void enable_cursor() { cursor_enabled = true; cursor_activated = true; current_cursor_timeout = CURSOR_TIMEOUT; draw_cursor(); } Result putchar(char c) { auto guard = make_scope_guard([] { utf8_decoder.reset(); }); bool is_ready = TRY(utf8_decoder.feed(c)); if (is_ready) putwchar(TRY(utf8_decoder.extract())); guard.deactivate(); return {}; } void set_foreground(u32 color) { g_foreground_color = color; } void set_background(u32 color) { g_background_color = color; } u32 foreground() { return g_foreground_color; } u32 background() { return g_background_color; } void move_to(u32 x, u32 y) { g_x_position = x; g_y_position = y; } void clear() { move_to(0, 0); Framebuffer::rect(0, 0, Framebuffer::width(), Framebuffer::height(), BLACK); } Result print(const char* str) { while (*str) TRY(putchar(*str++)); return {}; } Result write(const char* str, usize len) { while (len--) TRY(putchar(*str++)); return {}; } void wprint(const wchar_t* str) { while (*str) putwchar(*str++); } Result println(const char* str) { TRY(print(str)); putwchar(L'\n'); return {}; } void wprintln(const wchar_t* str) { wprint(str); putwchar(L'\n'); } Result printf(const char* format, ...) { va_list ap; va_start(ap, format); const usize rc = TRY(cstyle_format( format, [](char c, void*) -> Result { return putchar(c); }, nullptr, ap)); va_end(ap); return rc; } u16 rows() { return (u16)Framebuffer::height() / (u16)FONT_HEIGHT; } u16 cols() { return (u16)Framebuffer::width() / (u16)FONT_WIDTH; } }