From c524dc8d588453a67d9733384d9cdcdf370689ee Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 2 Sep 2023 19:35:42 +0200 Subject: [PATCH] libluna+kernel: Basic ANSI escape sequences --- kernel/src/video/TextConsole.cpp | 235 +++++++++++++++++++++++++- libluna/CMakeLists.txt | 1 + libluna/include/luna/EscapeSequence.h | 67 ++++++++ libluna/src/EscapeSequence.cpp | 146 ++++++++++++++++ shell/main.cpp | 3 +- 5 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 libluna/include/luna/EscapeSequence.h create mode 100644 libluna/src/EscapeSequence.cpp diff --git a/kernel/src/video/TextConsole.cpp b/kernel/src/video/TextConsole.cpp index 92d271e2..19bca1ce 100644 --- a/kernel/src/video/TextConsole.cpp +++ b/kernel/src/video/TextConsole.cpp @@ -3,6 +3,7 @@ #include "video/Framebuffer.h" #include #include +#include #include #include #include @@ -13,20 +14,43 @@ extern const BOOTBOOT bootboot; #include "video/BuiltinFont.h" -static constexpr u32 BLACK = 0xff000000; +// 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 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]; @@ -87,6 +111,206 @@ 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) @@ -94,6 +318,11 @@ namespace TextConsole // 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; + } + switch (c) { case L'\n': { @@ -113,6 +342,10 @@ namespace TextConsole erase_current_char(); } break; + case L'\x1b': + case L'\x9b': + case L'\x90': + case L'\x9d': escape_sequence_parser = EscapeSequenceParser { (u8)c }; break; default: { if (_iscntrl(c)) return; putwchar_at(c, g_x_position, g_y_position); diff --git a/libluna/CMakeLists.txt b/libluna/CMakeLists.txt index 31ddb6fa..405d746d 100644 --- a/libluna/CMakeLists.txt +++ b/libluna/CMakeLists.txt @@ -5,6 +5,7 @@ file(GLOB HEADERS include/luna/*.h) set(FREESTANDING_SOURCES ${HEADERS} src/CRC32.cpp + src/EscapeSequence.cpp src/Format.cpp src/Sort.cpp src/NumberParsing.cpp diff --git a/libluna/include/luna/EscapeSequence.h b/libluna/include/luna/EscapeSequence.h new file mode 100644 index 00000000..179d47c9 --- /dev/null +++ b/libluna/include/luna/EscapeSequence.h @@ -0,0 +1,67 @@ +/** + * @file EscapeSequence.h + * @author apio (cloudapio.eu) + * @brief ANSI escape sequence parsing. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include + +enum class EscapeCode +{ + SaveCursor, + RestoreCursor, + CursorUp, + CursorDown, + CursorForward, + CursorBack, + CursorNextLine, + CursorPreviousLine, + CursorHorizontalAbsolute, + SetCursorPosition, + SelectGraphicRendition, +}; + +class EscapeSequenceParser +{ + public: + EscapeSequenceParser(u8 begin); + + Result advance(u8 byte); + + bool valid() const + { + return m_valid; + } + + const Vector& parameters() const + { + return m_parameters; + } + + EscapeCode code() const + { + return m_escape_code; + } + + private: + enum class SequenceType + { + ESC, + CSI, + DCS, + OSC, + }; + + Vector m_parameter; + Vector m_parameters; + + SequenceType m_sequence_type; + bool m_parsing_parameter { false }; + bool m_valid { false }; + + EscapeCode m_escape_code; +}; diff --git a/libluna/src/EscapeSequence.cpp b/libluna/src/EscapeSequence.cpp new file mode 100644 index 00000000..71e4d3fc --- /dev/null +++ b/libluna/src/EscapeSequence.cpp @@ -0,0 +1,146 @@ +/** + * @file EscapeSequence.cpp + * @author apio (cloudapio.eu) + * @brief ANSI escape sequence parsing. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include +#include +#include + +EscapeSequenceParser::EscapeSequenceParser(u8 begin) +{ + switch (begin) + { + case 0x1b: m_sequence_type = SequenceType::ESC; break; + case 0x9b: m_sequence_type = SequenceType::CSI; break; + case 0x90: m_sequence_type = SequenceType::DCS; break; + case 0x9d: m_sequence_type = SequenceType::OSC; break; + default: fail("Unrecognized escape sequence type"); + } +} + +Result EscapeSequenceParser::advance(u8 byte) +{ + switch (m_sequence_type) + { + case SequenceType::ESC: { + switch (byte) + { + case '[': { + m_sequence_type = SequenceType::CSI; + return false; + }; + case 'P': { + m_sequence_type = SequenceType::DCS; + return false; + }; + case ']': { + m_sequence_type = SequenceType::OSC; + return false; + }; + case '7': { + m_escape_code = EscapeCode::SaveCursor; + m_valid = true; + return true; + }; + case '8': { + m_escape_code = EscapeCode::RestoreCursor; + m_valid = true; + return true; + }; + default: { + m_valid = false; + return true; + } + } + }; + break; + case SequenceType::CSI: { + if (_isdigit(byte)) + { + m_parsing_parameter = true; + TRY(m_parameter.try_append(byte)); + return false; + } + + if (!m_parsing_parameter && byte == ';') + { + TRY(m_parameters.try_append(0)); + return false; + } + + if (m_parsing_parameter) + { + TRY(m_parameter.try_append(0)); + int value = static_cast(parse_unsigned_integer((const char*)m_parameter.data(), nullptr, 10)); + m_parameter.clear(); + TRY(m_parameters.try_append(value)); + } + + switch (byte) + { + case 'A': { + m_escape_code = EscapeCode::CursorUp; + m_valid = true; + return true; + }; + case 'B': { + m_escape_code = EscapeCode::CursorDown; + m_valid = true; + return true; + }; + case 'C': { + m_escape_code = EscapeCode::CursorForward; + m_valid = true; + return true; + }; + case 'D': { + m_escape_code = EscapeCode::CursorBack; + m_valid = true; + return true; + }; + case 'E': { + m_escape_code = EscapeCode::CursorNextLine; + m_valid = true; + return true; + }; + case 'F': { + m_escape_code = EscapeCode::CursorPreviousLine; + m_valid = true; + return true; + }; + case 'G': { + m_escape_code = EscapeCode::CursorHorizontalAbsolute; + m_valid = true; + return true; + }; + case 'H': { + m_escape_code = EscapeCode::SetCursorPosition; + m_valid = true; + return true; + }; + case 'm': { + m_escape_code = EscapeCode::SelectGraphicRendition; + m_valid = true; + return true; + }; + case ';': { + return false; + }; + default: { + m_valid = false; + return true; + } + } + }; + break; + case SequenceType::DCS: todo(); + case SequenceType::OSC: todo(); + default: todo(); + } +} diff --git a/shell/main.cpp b/shell/main.cpp index 91944445..70c68a2b 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -133,7 +133,8 @@ Result luna_main(int argc, char** argv) if (interactive) { auto cwd = TRY(os::FileSystem::working_directory()); - os::print("%s@%s:%s%c ", username, hostname, cwd.chars(), prompt_end); + os::print("\x1b[%dm%s\x1b[m@\x1b[36m%s\x1b[m:\x1b[1;34m%s\x1b[m%c ", getuid() == 0 ? 31 : 35, username, + hostname, cwd.chars(), prompt_end); } auto maybe_cmd = input_file->read_line();