libluna+kernel: Basic ANSI escape sequences

This commit is contained in:
apio 2023-09-02 19:35:42 +02:00
parent e76a91d5d0
commit c524dc8d58
Signed by: apio
GPG Key ID: B8A7D06E42258954
5 changed files with 450 additions and 2 deletions

View File

@ -3,6 +3,7 @@
#include "video/Framebuffer.h"
#include <luna/CString.h>
#include <luna/CType.h>
#include <luna/EscapeSequence.h>
#include <luna/Format.h>
#include <luna/Result.h>
#include <luna/ScopeGuard.h>
@ -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<EscapeSequenceParser> 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<u8>(c));
if (rc.has_error())
{
escape_sequence_parser = Option<EscapeSequenceParser> {};
return false;
}
if (!rc.value()) return true;
if (!escape_sequence_parser->valid())
{
escape_sequence_parser = Option<EscapeSequenceParser> {};
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<EscapeSequenceParser> {};
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);

View File

@ -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

View File

@ -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 <luna/Vector.h>
enum class EscapeCode
{
SaveCursor,
RestoreCursor,
CursorUp,
CursorDown,
CursorForward,
CursorBack,
CursorNextLine,
CursorPreviousLine,
CursorHorizontalAbsolute,
SetCursorPosition,
SelectGraphicRendition,
};
class EscapeSequenceParser
{
public:
EscapeSequenceParser(u8 begin);
Result<bool> advance(u8 byte);
bool valid() const
{
return m_valid;
}
const Vector<int>& parameters() const
{
return m_parameters;
}
EscapeCode code() const
{
return m_escape_code;
}
private:
enum class SequenceType
{
ESC,
CSI,
DCS,
OSC,
};
Vector<u8> m_parameter;
Vector<int> m_parameters;
SequenceType m_sequence_type;
bool m_parsing_parameter { false };
bool m_valid { false };
EscapeCode m_escape_code;
};

View File

@ -0,0 +1,146 @@
/**
* @file EscapeSequence.cpp
* @author apio (cloudapio.eu)
* @brief ANSI escape sequence parsing.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <luna/CType.h>
#include <luna/Check.h>
#include <luna/EscapeSequence.h>
#include <luna/NumberParsing.h>
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<bool> 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<int>(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();
}
}

View File

@ -133,7 +133,8 @@ Result<int> 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();