Compare commits

..

7 Commits

Author SHA1 Message Date
Bananymous 9f4cb5c4dd Kernel: Make pseudo terminals not overwrite old data
If pseudo terminal buffer was filled, old implementation would overwrite
old data. This is bad if producer is capable of producing more data than
consumer can handle.
2025-04-23 22:03:53 +03:00
Bananymous 73f9de6635 Terminal: Add support for ANSI SGR 8bit and 24bit 2025-04-23 22:03:53 +03:00
Bananymous 12b9c82086 Terminal: Don't wrap cursor right after x == cols
This prevents scrolling when bottom right scroll is written to
2025-04-23 22:03:53 +03:00
Bananymous a8edb8870e Kernel: Add support for 8bit and 24bit ANSI SGR 2025-04-23 22:03:53 +03:00
Bananymous dabc3c6cc4 Kernel: Don't wrap cursor immediatly at cols()
This prevents unwanted scrolling when writing to bottom right cell
2025-04-23 22:03:53 +03:00
Bananymous cc7c1ad30d Kernel: Add per terminal palette for TerminalDriver
This allows better color approximation on text mode and in future will
allow user to possibly change the palette
2025-04-23 18:44:37 +03:00
Bananymous 4d840a8d9a BAN: Make Array more constexpr 2025-04-23 18:40:20 +03:00
18 changed files with 350 additions and 190 deletions

View File

@ -18,78 +18,78 @@ namespace BAN
using const_iterator = ConstIteratorSimple<T, Array>;
public:
Array() = default;
Array(const T&);
constexpr Array() = default;
constexpr Array(const T&);
iterator begin() { return iterator(m_data); }
iterator end() { return iterator(m_data + size()); }
const_iterator begin() const { return const_iterator(m_data); }
const_iterator end() const { return const_iterator(m_data + size()); }
const T& operator[](size_type) const;
T& operator[](size_type);
constexpr const T& operator[](size_type) const;
constexpr T& operator[](size_type);
const T& back() const;
T& back();
const T& front() const;
T& front();
constexpr const T& back() const;
constexpr T& back();
constexpr const T& front() const;
constexpr T& front();
Span<T> span() { return Span(m_data, size()); }
const Span<T> span() const { return Span(m_data, size()); }
constexpr size_type size() const;
const T* data() const { return m_data; }
T* data() { return m_data; }
constexpr const T* data() const { return m_data; }
constexpr T* data() { return m_data; }
private:
T m_data[S] {};
};
template<typename T, size_t S>
Array<T, S>::Array(const T& value)
constexpr Array<T, S>::Array(const T& value)
{
for (size_type i = 0; i < S; i++)
m_data[i] = value;
}
template<typename T, size_t S>
const T& Array<T, S>::operator[](size_type index) const
constexpr const T& Array<T, S>::operator[](size_type index) const
{
ASSERT(index < S);
return m_data[index];
}
template<typename T, size_t S>
T& Array<T, S>::operator[](size_type index)
constexpr T& Array<T, S>::operator[](size_type index)
{
ASSERT(index < S);
return m_data[index];
}
template<typename T, size_t S>
const T& Array<T, S>::back() const
constexpr const T& Array<T, S>::back() const
{
ASSERT(S != 0);
return m_data[S - 1];
}
template<typename T, size_t S>
T& Array<T, S>::back()
constexpr T& Array<T, S>::back()
{
ASSERT(S != 0);
return m_data[S - 1];
}
template<typename T, size_t S>
const T& Array<T, S>::front() const
constexpr const T& Array<T, S>::front() const
{
ASSERT(S != 0);
return m_data[0];
}
template<typename T, size_t S>
T& Array<T, S>::front()
constexpr T& Array<T, S>::front()
{
ASSERT(S != 0);
return m_data[0];

View File

@ -27,9 +27,10 @@ namespace Kernel
const LibFont::Font& font() const override { return m_font; };
private:
FramebufferTerminalDriver(BAN::RefPtr<FramebufferDevice> framebuffer_device)
: m_framebuffer_device(framebuffer_device)
{ }
FramebufferTerminalDriver(BAN::RefPtr<FramebufferDevice> framebuffer_device, const Palette& palette)
: TerminalDriver(palette)
, m_framebuffer_device(framebuffer_device)
{}
void read_cursor();
void show_cursor(bool use_data);
@ -42,7 +43,7 @@ namespace Kernel
uint32_t m_cursor_y { 0 };
bool m_cursor_shown { true };
BAN::Vector<uint32_t> m_cursor_data;
static constexpr Color m_cursor_color = TerminalColor::BRIGHT_WHITE;
static constexpr Color m_cursor_color = TerminalColor::WHITE;
};
}

View File

@ -20,7 +20,7 @@ namespace Kernel
BAN::ErrorOr<BAN::String> ptsname();
void putchar(uint8_t ch);
bool putchar(uint8_t ch);
protected:
BAN::ErrorOr<size_t> read_impl(off_t, BAN::ByteSpan) override;
@ -61,7 +61,7 @@ namespace Kernel
void clear() override;
protected:
void putchar_impl(uint8_t ch) override;
bool putchar_impl(uint8_t ch) override;
BAN::ErrorOr<long> ioctl_impl(int, void*) override;

View File

@ -53,7 +53,7 @@ namespace Kernel
protected:
virtual BAN::StringView name() const override { return m_name; }
virtual void putchar_impl(uint8_t) override;
virtual bool putchar_impl(uint8_t) override;
private:
SerialTTY(Serial);

View File

@ -58,14 +58,14 @@ namespace Kernel
protected:
TTY(mode_t mode, uid_t uid, gid_t gid);
virtual void putchar_impl(uint8_t ch) = 0;
virtual bool putchar_impl(uint8_t ch) = 0;
virtual void update_cursor() {}
virtual BAN::ErrorOr<size_t> read_impl(off_t, BAN::ByteSpan) override;
virtual BAN::ErrorOr<size_t> write_impl(off_t, BAN::ConstByteSpan) override;
private:
void putchar(uint8_t ch);
bool putchar(uint8_t ch);
void do_backspace();
protected:

View File

@ -12,20 +12,28 @@ namespace Kernel
public:
struct Color
{
constexpr Color()
: rgb(0)
{ }
constexpr Color(uint32_t rgb)
: rgb(rgb)
{ }
constexpr Color(uint8_t r, uint8_t g, uint8_t b)
: rgb(((uint32_t)r << 16) | ((uint32_t)g << 8) | b)
{ }
uint8_t red() const { return (rgb >> 0x10) & 0xFF; }
uint8_t green() const { return (rgb >> 0x08) & 0xFF; }
uint8_t blue() const { return (rgb >> 0x00) & 0xFF; }
constexpr uint8_t red() const { return (rgb >> 0x10) & 0xFF; }
constexpr uint8_t green() const { return (rgb >> 0x08) & 0xFF; }
constexpr uint8_t blue() const { return (rgb >> 0x00) & 0xFF; }
uint32_t rgb;
};
using Palette = BAN::Array<Color, 16>;
public:
static BAN::ErrorOr<void> initialize_from_boot_info();
TerminalDriver(const Palette& palette)
: m_palette(palette)
{}
virtual ~TerminalDriver() {}
virtual uint32_t width() const = 0;
virtual uint32_t height() const = 0;
@ -40,29 +48,19 @@ namespace Kernel
virtual bool has_font() const { return false; }
virtual BAN::ErrorOr<void> set_font(LibFont::Font&&) { return BAN::Error::from_errno(EINVAL); }
virtual const LibFont::Font& font() const { ASSERT_NOT_REACHED(); }
const Palette& palette() const { return m_palette; }
protected:
Palette m_palette;
};
extern BAN::RefPtr<TerminalDriver> g_terminal_driver;
namespace TerminalColor
{
static constexpr TerminalDriver::Color BLACK = 0x000000;
static constexpr TerminalDriver::Color RED = 0xFF0000;
static constexpr TerminalDriver::Color GREEN = 0x00FF00;
static constexpr TerminalDriver::Color YELLOW = 0xFFFF00;
static constexpr TerminalDriver::Color BLUE = 0x0000FF;
static constexpr TerminalDriver::Color MAGENTA = 0xFF00FF;
static constexpr TerminalDriver::Color CYAN = 0x00FFFF;
static constexpr TerminalDriver::Color WHITE = 0xBFBFBF;
static constexpr TerminalDriver::Color BRIGHT_BLACK = 0x3F3F3F;
static constexpr TerminalDriver::Color BRIGHT_RED = 0xFF7F7F;
static constexpr TerminalDriver::Color BRIGHT_GREEN = 0x7FFF7F;
static constexpr TerminalDriver::Color BRIGHT_YELLOW = 0xFFFF7F;
static constexpr TerminalDriver::Color BRIGHT_BLUE = 0x7F7FFF;
static constexpr TerminalDriver::Color BRIGHT_MAGENTA = 0xFF7FFF;
static constexpr TerminalDriver::Color BRIGHT_CYAN = 0x7FFFFF;
static constexpr TerminalDriver::Color BRIGHT_WHITE = 0xFFFFFF;
constexpr TerminalDriver::Color BLACK = 0x000000;
constexpr TerminalDriver::Color WHITE = 0xFFFFFF;
}
}

View File

@ -21,8 +21,9 @@ namespace Kernel
void set_cursor_position(uint32_t, uint32_t) override;
private:
TextModeTerminalDriver(paddr_t paddr, uint32_t width, uint32_t height, uint32_t pitch)
: m_paddr(paddr)
TextModeTerminalDriver(paddr_t paddr, uint32_t width, uint32_t height, uint32_t pitch, const Palette& palette)
: TerminalDriver(palette)
, m_paddr(paddr)
, m_width(width)
, m_height(height)
, m_pitch(pitch)
@ -36,7 +37,7 @@ namespace Kernel
const uint32_t m_height;
const uint32_t m_pitch;
vaddr_t m_vaddr { 0 };
static constexpr Color s_cursor_color = TerminalColor::BRIGHT_WHITE;
static constexpr Color s_cursor_color = TerminalColor::WHITE;
};
}

View File

@ -11,6 +11,9 @@ namespace Kernel
class VirtualTTY : public TTY
{
public:
using Palette = TerminalDriver::Palette;
public:
static BAN::ErrorOr<BAN::RefPtr<VirtualTTY>> create(BAN::RefPtr<TerminalDriver>);
@ -23,7 +26,7 @@ namespace Kernel
protected:
virtual BAN::StringView name() const override { return m_name; }
virtual void putchar_impl(uint8_t ch) override;
virtual bool putchar_impl(uint8_t ch) override;
void update_cursor() override;
private:
@ -35,6 +38,7 @@ namespace Kernel
void putcodepoint(uint32_t codepoint);
void putchar_at(uint32_t codepoint, uint32_t x, uint32_t y);
void render_from_buffer(uint32_t x, uint32_t y);
void scroll_if_needed();
private:
enum class State
@ -47,11 +51,15 @@ namespace Kernel
struct AnsiState
{
int32_t nums[2] { -1, -1 };
int32_t index { 0 };
static constexpr size_t max_nums = 5;
int32_t nums[max_nums] { -1, -1, -1, -1, -1 };
size_t index { 0 };
bool question { false };
};
BAN::Optional<TerminalDriver::Color> get_8bit_color();
BAN::Optional<TerminalDriver::Color> get_24bit_color();
struct UTF8State
{
uint32_t codepoint { 0 };
@ -60,14 +68,16 @@ namespace Kernel
struct Cell
{
TerminalDriver::Color foreground { TerminalColor::BRIGHT_WHITE };
TerminalDriver::Color background { TerminalColor::BLACK };
TerminalDriver::Color foreground;
TerminalDriver::Color background;
uint32_t codepoint { ' ' };
};
private:
BAN::String m_name;
BAN::RefPtr<TerminalDriver> m_terminal_driver;
State m_state { State::Normal };
AnsiState m_ansi_state { };
UTF8State m_utf8_state { };
@ -84,11 +94,11 @@ namespace Kernel
uint32_t m_column { 0 };
Cell* m_buffer { nullptr };
TerminalDriver::Color m_foreground { TerminalColor::BRIGHT_WHITE };
TerminalDriver::Color m_background { TerminalColor::BLACK };
bool m_colors_inverted { false };
const Palette& m_palette;
BAN::RefPtr<TerminalDriver> m_terminal_driver;
TerminalDriver::Color m_foreground;
TerminalDriver::Color m_background;
bool m_colors_inverted { false };
};
}

View File

@ -104,7 +104,7 @@ namespace Debug
{
if (!isprint(ch))
ch = '?';
g_terminal_driver->putchar_at(ch, col, row, TerminalColor::BRIGHT_WHITE, TerminalColor::BLACK);
g_terminal_driver->putchar_at(ch, col, row, TerminalColor::WHITE, TerminalColor::BLACK);
col++;
if (col >= g_terminal_driver->width())
@ -121,9 +121,9 @@ namespace Debug
{
for (uint32_t i = col; i < g_terminal_driver->width(); i++)
{
g_terminal_driver->putchar_at(' ', i, row, TerminalColor::BRIGHT_WHITE, TerminalColor::BLACK);
g_terminal_driver->putchar_at(' ', i, row, TerminalColor::WHITE, TerminalColor::BLACK);
if (row + 1 < g_terminal_driver->height())
g_terminal_driver->putchar_at(' ', i, row + 1, TerminalColor::BRIGHT_WHITE, TerminalColor::BLACK);
g_terminal_driver->putchar_at(' ', i, row + 1, TerminalColor::WHITE, TerminalColor::BLACK);
}
}
}

View File

@ -360,7 +360,7 @@ namespace Kernel
[&x, y](char ch)
{
if (x < g_terminal_driver->width() && y < g_terminal_driver->height())
g_terminal_driver->putchar_at(ch, x++, y, TerminalColor::BRIGHT_WHITE, TerminalColor::BLACK);
g_terminal_driver->putchar_at(ch, x++, y, TerminalColor::WHITE, TerminalColor::BLACK);
};
BAN::Formatter::print(proc_putc, "CPU { 2}: { 3}.{3}%", current_id(), load_x1000 / 1000, load_x1000 % 1000);

View File

@ -3,15 +3,38 @@
namespace Kernel
{
static consteval TerminalDriver::Palette default_palette()
{
TerminalDriver::Palette palette;
palette[ 0] = 0x000000;
palette[ 1] = 0xFF0000;
palette[ 2] = 0x00FF00;
palette[ 3] = 0xFFFF00;
palette[ 4] = 0x0000FF;
palette[ 5] = 0xFF00FF;
palette[ 6] = 0x00FFFF;
palette[ 7] = 0xBFBFBF;
palette[ 8] = 0x3F3F3F;
palette[ 9] = 0xFF7F7F;
palette[10] = 0x7FFF7F;
palette[11] = 0xFFFF7F;
palette[12] = 0x7F7FFF;
palette[13] = 0xFF7FFF;
palette[14] = 0x7FFFFF;
palette[15] = 0xFFFFFF;
return palette;
}
BAN::ErrorOr<BAN::RefPtr<FramebufferTerminalDriver>> FramebufferTerminalDriver::create(BAN::RefPtr<FramebufferDevice> framebuffer_device)
{
auto* driver_ptr = new FramebufferTerminalDriver(framebuffer_device);
auto* driver_ptr = new FramebufferTerminalDriver(framebuffer_device, default_palette());
if (driver_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
auto driver = BAN::RefPtr<FramebufferTerminalDriver>::adopt(driver_ptr);
TRY(driver->set_font(BAN::move(TRY(LibFont::Font::prefs()))));
driver->set_cursor_position(0, 0);
driver->clear(TerminalColor::BLACK);
driver->clear(driver->m_palette[0]);
return driver;
}
@ -60,6 +83,9 @@ namespace Kernel
for (uint32_t x = 0; x < m_framebuffer_device->width(); x++)
m_framebuffer_device->set_pixel(x, y, color.rgb);
m_framebuffer_device->sync_pixels_full();
if (m_cursor_shown)
show_cursor(false);
}
void FramebufferTerminalDriver::read_cursor()
@ -78,6 +104,11 @@ namespace Kernel
void FramebufferTerminalDriver::show_cursor(bool use_data)
{
// NOTE: cursor is allowed to be on width as scrolling only
// happens after character gets printed to next line
if (m_cursor_x == width())
return;
if (!use_data)
read_cursor();

View File

@ -85,16 +85,19 @@ namespace Kernel
return BAN::Error::from_errno(ENODEV);
}
void PseudoTerminalMaster::putchar(uint8_t ch)
bool PseudoTerminalMaster::putchar(uint8_t ch)
{
SpinLockGuard _(m_buffer_lock);
reinterpret_cast<uint8_t*>(m_buffer->vaddr())[(m_buffer_tail + m_buffer_size) % m_buffer->size()] = ch;
if (m_buffer_size >= m_buffer->size())
return false;
if (m_buffer_size < m_buffer->size())
reinterpret_cast<uint8_t*>(m_buffer->vaddr())[(m_buffer_tail + m_buffer_size) % m_buffer->size()] = ch;
m_buffer_size++;
else
m_buffer_tail = (m_buffer_tail + 1) % m_buffer->size();
m_buffer_blocker.unblock();
return true;
}
BAN::ErrorOr<size_t> PseudoTerminalMaster::read_impl(off_t, BAN::ByteSpan buffer)
@ -166,10 +169,11 @@ namespace Kernel
(void)write_impl(0, BAN::ConstByteSpan::from(message));
}
void PseudoTerminalSlave::putchar_impl(uint8_t ch)
bool PseudoTerminalSlave::putchar_impl(uint8_t ch)
{
if (auto master = m_master.lock())
master->putchar(ch);
return master->putchar(ch);
return false;
}
BAN::ErrorOr<long> PseudoTerminalSlave::ioctl_impl(int request, void* argument)

View File

@ -258,9 +258,10 @@ namespace Kernel
return m_serial.height();
}
void SerialTTY::putchar_impl(uint8_t ch)
bool SerialTTY::putchar_impl(uint8_t ch)
{
m_serial.putchar(ch);
return true;
}
}

View File

@ -327,11 +327,12 @@ namespace Kernel
}
}
void TTY::putchar(uint8_t ch)
bool TTY::putchar(uint8_t ch)
{
SpinLockGuard _(m_write_lock);
if (m_tty_ctrl.draw_graphics)
putchar_impl(ch);
return putchar_impl(ch);
return true;
}
BAN::ErrorOr<size_t> TTY::read_impl(off_t, BAN::ByteSpan buffer)
@ -365,10 +366,12 @@ namespace Kernel
BAN::ErrorOr<size_t> TTY::write_impl(off_t, BAN::ConstByteSpan buffer)
{
SpinLockGuard _(m_write_lock);
for (size_t i = 0; i < buffer.size(); i++)
putchar(buffer[i]);
size_t written = 0;
for (; written < buffer.size(); written++)
if (!putchar(buffer[written]))
break;
update_cursor();
return buffer.size();
return written;
}
void TTY::putchar_current(uint8_t ch)

View File

@ -7,36 +7,39 @@
namespace Kernel
{
static constexpr TerminalDriver::Color s_palette[] {
TerminalColor::BLACK,
TerminalColor::BLUE,
TerminalColor::GREEN,
TerminalColor::CYAN,
TerminalColor::RED,
TerminalColor::MAGENTA,
TerminalColor::YELLOW,
TerminalColor::WHITE,
TerminalColor::BRIGHT_BLACK,
TerminalColor::BRIGHT_BLUE,
TerminalColor::BRIGHT_GREEN,
TerminalColor::BRIGHT_CYAN,
TerminalColor::BRIGHT_RED,
TerminalColor::BRIGHT_MAGENTA,
TerminalColor::BRIGHT_YELLOW,
TerminalColor::BRIGHT_WHITE,
};
static consteval TerminalDriver::Palette default_palette()
{
TerminalDriver::Palette palette;
palette[ 0] = 0x000000;
palette[ 1] = 0x0000AA;
palette[ 2] = 0x00AA00;
palette[ 3] = 0x00AAAA;
palette[ 4] = 0xAA0000;
palette[ 5] = 0xAA00AA;
palette[ 6] = 0xAA5500;
palette[ 7] = 0xAAAAAA;
palette[ 8] = 0x555555;
palette[ 9] = 0x5555FF;
palette[10] = 0x55FF55;
palette[11] = 0x55FFFF;
palette[12] = 0xFF5555;
palette[13] = 0xFF55FF;
palette[14] = 0xFFFF55;
palette[15] = 0xFFFFFF;
return palette;
}
static constexpr uint8_t color_to_text_mode_color(TerminalDriver::Color color)
{
uint32_t min_diff = BAN::numeric_limits<uint32_t>::max();
uint8_t closest = 0;
static_assert(sizeof(s_palette) / sizeof(*s_palette) == 16);
static_assert(default_palette().size() == 16);
for (size_t i = 0; i < 16; i++)
{
const auto rdiff = color.red() - s_palette[i].red();
const auto gdiff = color.green() - s_palette[i].green();
const auto bdiff = color.blue() - s_palette[i].blue();
const auto rdiff = color.red() - default_palette()[i].red();
const auto gdiff = color.green() - default_palette()[i].green();
const auto bdiff = color.blue() - default_palette()[i].blue();
const uint32_t diff = rdiff*rdiff + gdiff*gdiff + bdiff*bdiff;
if (diff >= min_diff)
continue;
@ -56,7 +59,8 @@ namespace Kernel
g_boot_info.framebuffer.address,
g_boot_info.framebuffer.width,
g_boot_info.framebuffer.height,
g_boot_info.framebuffer.pitch
g_boot_info.framebuffer.pitch,
default_palette()
);
if (driver_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
@ -83,7 +87,7 @@ namespace Kernel
m_vaddr = vaddr + (m_paddr % PAGE_SIZE);
set_cursor_position(0, 0);
clear(TerminalColor::BLACK);
clear(m_palette[0]);
return {};
}
@ -114,7 +118,7 @@ namespace Kernel
{
for (uint32_t y = 0; y < m_height; y++)
for (uint32_t x = 0; x < m_width; x++)
putchar_at(' ', x, y, TerminalColor::BRIGHT_WHITE, color);
putchar_at(' ', x, y, color, color);
}
void TextModeTerminalDriver::set_cursor_shown(bool shown)
@ -136,6 +140,10 @@ namespace Kernel
void TextModeTerminalDriver::set_cursor_position(uint32_t x, uint32_t y)
{
// NOTE: cursor is allowed to be on width as scrolling only
// happens after character gets printed to next line
if (x == m_width)
return;
const uint16_t pos = y * m_width + x;
IO::outb(0x3D4, 0x0F);
IO::outb(0x3D5, pos & 0xFF);

View File

@ -39,6 +39,9 @@ namespace Kernel
: TTY(0600, 0, 0)
, m_name(MUST(BAN::String::formatted("tty{}", s_next_tty_number++)))
, m_terminal_driver(driver)
, m_palette(driver->palette())
, m_foreground(m_palette[15])
, m_background(m_palette[0])
{
m_width = m_terminal_driver->width();
m_height = m_terminal_driver->height();
@ -95,10 +98,11 @@ namespace Kernel
void VirtualTTY::reset_ansi()
{
ASSERT(m_write_lock.current_processor_has_lock());
m_ansi_state.index = 0;
m_ansi_state.nums[0] = -1;
m_ansi_state.nums[1] = -1;
m_ansi_state.question = false;
m_ansi_state = {
.nums = { -1, -1, -1, -1, -1 },
.index = 0,
.question = false,
};
m_state = State::Normal;
}
@ -108,64 +112,76 @@ namespace Kernel
switch (ch)
{
case 0:
m_foreground = TerminalColor::BRIGHT_WHITE;
m_background = TerminalColor::BLACK;
m_foreground = m_palette[15];
m_background = m_palette[0];
m_colors_inverted = false;
break;
case 7: m_colors_inverted = true; break;
case 27: m_colors_inverted = false; break;
case 30: m_foreground = TerminalColor::BLACK; break;
case 31: m_foreground = TerminalColor::RED; break;
case 32: m_foreground = TerminalColor::GREEN; break;
case 33: m_foreground = TerminalColor::YELLOW; break;
case 34: m_foreground = TerminalColor::BLUE; break;
case 35: m_foreground = TerminalColor::MAGENTA; break;
case 36: m_foreground = TerminalColor::CYAN; break;
case 37: m_foreground = TerminalColor::WHITE; break;
case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37:
m_foreground = m_palette[ch - 30];
break;
case 40: m_background = TerminalColor::BLACK; break;
case 41: m_background = TerminalColor::RED; break;
case 42: m_background = TerminalColor::GREEN; break;
case 43: m_background = TerminalColor::YELLOW; break;
case 44: m_background = TerminalColor::BLUE; break;
case 45: m_background = TerminalColor::MAGENTA; break;
case 46: m_background = TerminalColor::CYAN; break;
case 47: m_background = TerminalColor::WHITE; break;
case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47:
m_background = m_palette[ch - 40];
break;
case 90: m_foreground = TerminalColor::BRIGHT_BLACK; break;
case 91: m_foreground = TerminalColor::BRIGHT_RED; break;
case 92: m_foreground = TerminalColor::BRIGHT_GREEN; break;
case 93: m_foreground = TerminalColor::BRIGHT_YELLOW; break;
case 94: m_foreground = TerminalColor::BRIGHT_BLUE; break;
case 95: m_foreground = TerminalColor::BRIGHT_MAGENTA; break;
case 96: m_foreground = TerminalColor::BRIGHT_CYAN; break;
case 97: m_foreground = TerminalColor::BRIGHT_WHITE; break;
case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97:
m_foreground = m_palette[ch - 90 + 8];
break;
case 100: m_background = TerminalColor::BRIGHT_BLACK; break;
case 101: m_background = TerminalColor::BRIGHT_RED; break;
case 102: m_background = TerminalColor::BRIGHT_GREEN; break;
case 103: m_background = TerminalColor::BRIGHT_YELLOW; break;
case 104: m_background = TerminalColor::BRIGHT_BLUE; break;
case 105: m_background = TerminalColor::BRIGHT_MAGENTA; break;
case 106: m_background = TerminalColor::BRIGHT_CYAN; break;
case 107: m_background = TerminalColor::BRIGHT_WHITE; break;
case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107:
m_background = m_palette[ch - 100 + 8];
break;
}
}
BAN::Optional<TerminalDriver::Color> VirtualTTY::get_8bit_color()
{
ASSERT(m_ansi_state.nums[1] == 5);
if (m_ansi_state.nums[2] < 1)
return {};
const uint8_t code = BAN::Math::min(m_ansi_state.nums[2], 256) - 1;
if (code < 16)
return m_palette[code];
if (code < 232)
{
const uint8_t r = (code - 16) / 36 % 6 * 40 + 55;
const uint8_t g = (code - 16) / 6 % 6 * 40 + 55;
const uint8_t b = (code - 16) / 1 % 6 * 40 + 55;
return TerminalDriver::Color(r, g, b);
}
const uint8_t gray = (code - 232) * 10 + 8;
return TerminalDriver::Color(gray, gray, gray);
}
BAN::Optional<TerminalDriver::Color> VirtualTTY::get_24bit_color()
{
ASSERT(m_ansi_state.nums[1] == 2);
if (m_ansi_state.nums[2] < 1) return {};
if (m_ansi_state.nums[3] < 1) return {};
if (m_ansi_state.nums[4] < 1) return {};
const uint8_t r = BAN::Math::min(m_ansi_state.nums[2], 256) - 1;
const uint8_t g = BAN::Math::min(m_ansi_state.nums[3], 256) - 1;
const uint8_t b = BAN::Math::min(m_ansi_state.nums[4], 256) - 1;
return TerminalDriver::Color(r, g, b);
}
void VirtualTTY::handle_ansi_csi(uint8_t ch)
{
constexpr size_t max_ansi_args = sizeof(m_ansi_state.nums) / sizeof(*m_ansi_state.nums);
ASSERT(m_write_lock.current_processor_has_lock());
switch (ch)
{
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
if ((size_t)m_ansi_state.index >= max_ansi_args)
dwarnln("Only {} arguments supported with ANSI codes", max_ansi_args);
if (m_ansi_state.index >= m_ansi_state.max_nums)
dwarnln("Only {} arguments supported with ANSI codes", m_ansi_state.max_nums);
else
{
int32_t& val = m_ansi_state.nums[m_ansi_state.index];
@ -174,7 +190,7 @@ namespace Kernel
return;
}
case ';':
m_ansi_state.index = BAN::Math::min<size_t>(m_ansi_state.index + 1, max_ansi_args);
m_ansi_state.index = BAN::Math::min<size_t>(m_ansi_state.index + 1, m_ansi_state.max_nums);
return;
case 'A': // Cursor Up
if (m_ansi_state.nums[0] == -1)
@ -306,7 +322,22 @@ namespace Kernel
dprintln_if(DEBUG_VTTY, "Unsupported ANSI CSI character f");
return;
case 'm':
for (int i = 0; i <= m_ansi_state.index && i < static_cast<int>(max_ansi_args); i++)
if (m_ansi_state.nums[0] == 38 || m_ansi_state.nums[0] == 48)
{
if (m_ansi_state.nums[1] != 5 && m_ansi_state.nums[1] != 2)
{
reset_ansi();
dprintln_if(DEBUG_VTTY, "Unsupported ANSI SGR {}", m_ansi_state.nums[1]);
return;
}
const auto color = (m_ansi_state.nums[1] == 5)
? get_8bit_color()
: get_24bit_color();
if (color.has_value())
(m_ansi_state.nums[0] == 38 ? m_foreground : m_background) = *color;
return reset_ansi();
}
for (size_t i = 0; i <= m_ansi_state.index && i < m_ansi_state.max_nums; i++)
handle_ansi_csi_color(BAN::Math::max(m_ansi_state.nums[i], 0));
return reset_ansi();
case 's':
@ -401,6 +432,29 @@ namespace Kernel
m_terminal_driver->putchar_at(codepoint, x, y, cell.foreground, cell.background);
}
void VirtualTTY::scroll_if_needed()
{
while (m_row >= m_height)
{
memmove(m_buffer, m_buffer + m_width, m_width * (m_height - 1) * sizeof(Cell));
// Clear last line in buffer
for (uint32_t x = 0; x < m_width; x++)
m_buffer[(m_height - 1) * m_width + x] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' };
if (!m_terminal_driver->scroll(m_background))
{
// No fast scrolling, render the whole buffer to the screen
for (uint32_t y = 0; y < m_height; y++)
for (uint32_t x = 0; x < m_width; x++)
render_from_buffer(x, y);
}
m_column = 0;
m_row--;
}
}
void VirtualTTY::putcodepoint(uint32_t codepoint)
{
ASSERT(m_write_lock.current_processor_has_lock());
@ -432,40 +486,22 @@ namespace Kernel
m_state = State::WaitingAnsiEscape;
break;
default:
if (m_column >= m_width)
{
m_column = 0;
m_row++;
}
scroll_if_needed();
putchar_at(codepoint, m_column, m_row);
m_last_graphic_char = codepoint;
m_column++;
break;
}
if (m_column >= m_width)
{
m_column = 0;
m_row++;
scroll_if_needed();
}
while (m_row >= m_height)
{
memmove(m_buffer, m_buffer + m_width, m_width * (m_height - 1) * sizeof(Cell));
// Clear last line in buffer
for (uint32_t x = 0; x < m_width; x++)
m_buffer[(m_height - 1) * m_width + x] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' };
if (!m_terminal_driver->scroll(m_background))
{
// No fast scrolling, render the whole buffer to the screen
for (uint32_t y = 0; y < m_height; y++)
for (uint32_t x = 0; x < m_width; x++)
render_from_buffer(x, y);
}
m_column = 0;
m_row--;
}
}
void VirtualTTY::putchar_impl(uint8_t ch)
bool VirtualTTY::putchar_impl(uint8_t ch)
{
ASSERT(m_write_lock.current_processor_has_lock());
@ -495,10 +531,10 @@ namespace Kernel
{
reset_ansi();
dprintln_if(DEBUG_VTTY, "invalid utf8");
return;
return true;
}
m_state = State::WaitingUTF8;
return;
return true;
case State::WaitingAnsiEscape:
if (ch == CSI)
m_state = State::WaitingAnsiCSI;
@ -507,21 +543,21 @@ namespace Kernel
reset_ansi();
dprintln_if(DEBUG_VTTY, "unsupported byte after ansi escape {2H}", (uint8_t)ch);
}
return;
return true;
case State::WaitingAnsiCSI:
handle_ansi_csi(ch);
return;
return true;
case State::WaitingUTF8:
if ((ch & 0xC0) != 0x80)
{
m_state = State::Normal;
dprintln_if(DEBUG_VTTY, "invalid utf8");
return;
return true;
}
m_utf8_state.codepoint = (m_utf8_state.codepoint << 6) | (ch & 0x3F);
m_utf8_state.bytes_missing--;
if (m_utf8_state.bytes_missing)
return;
return true;
m_state = State::Normal;
codepoint = m_utf8_state.codepoint;
break;
@ -530,6 +566,7 @@ namespace Kernel
}
putcodepoint(codepoint);
return true;
}
void VirtualTTY::update_cursor()

View File

@ -185,6 +185,8 @@ void Terminal::run()
void Terminal::hide_cursor()
{
if (m_cursor.x == cols())
return;
const uint32_t cursor_base_x = m_cursor.x * m_font.width();
const uint32_t cursor_base_y = m_cursor.y * m_font.height();
for (uint32_t y = 0; y < m_font.height(); y++)
@ -195,6 +197,8 @@ void Terminal::hide_cursor()
void Terminal::show_cursor()
{
if (m_cursor.x == cols())
return;
const uint32_t cursor_base_x = m_cursor.x * m_font.width();
const uint32_t cursor_base_y = m_cursor.y * m_font.height();
for (uint32_t y = 0; y < m_font.height(); y++)
@ -321,6 +325,42 @@ void Terminal::handle_sgr(int32_t value)
}
}
BAN::Optional<uint32_t> Terminal::get_8bit_color()
{
ASSERT(m_csi_info.fields[1] == 5);
if (m_csi_info.fields[2] < 1)
return {};
const uint8_t code = BAN::Math::min(m_csi_info.fields[2], 256) - 1;
if (code < 8)
return s_colors_dark[code];
if (code < 16)
return s_colors_bright[code - 8];
if (code < 232)
{
const uint8_t r = (code - 16) / 36 % 6 * 40 + 55;
const uint8_t g = (code - 16) / 6 % 6 * 40 + 55;
const uint8_t b = (code - 16) / 1 % 6 * 40 + 55;
return b | (g << 8) | (r << 16) | (0xCC << 24);
}
const uint8_t gray = (code - 232) * 10 + 8;
return gray | (gray << 8) | (gray << 16) | (0xCC << 24);
}
BAN::Optional<uint32_t> Terminal::get_24bit_color()
{
ASSERT(m_csi_info.fields[1] == 2);
if (m_csi_info.fields[2] < 1) return {};
if (m_csi_info.fields[3] < 1) return {};
if (m_csi_info.fields[4] < 1) return {};
const uint8_t r = BAN::Math::min(m_csi_info.fields[2], 256) - 1;
const uint8_t g = BAN::Math::min(m_csi_info.fields[3], 256) - 1;
const uint8_t b = BAN::Math::min(m_csi_info.fields[4], 256) - 1;
return b | (g << 8) | (r << 16) | (0xCC << 24);
}
Rectangle Terminal::handle_csi(char ch)
{
if (ch == ';')
@ -504,6 +544,20 @@ Rectangle Terminal::handle_csi(char ch)
m_cursor.y = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, rows()) - 1;
break;
case 'm':
if (m_csi_info.fields[0] == 38 || m_csi_info.fields[0] == 48)
{
if (m_csi_info.fields[1] != 5 && m_csi_info.fields[1] != 2)
{
dprintln("unsupported ANSI SGR {}", m_csi_info.fields[1]);
break;
}
const auto color = (m_csi_info.fields[1] == 5)
? get_8bit_color()
: get_24bit_color();
if (color.has_value())
(m_csi_info.fields[0] == 38 ? m_fg_color : m_bg_color) = *color;
break;
}
for (size_t i = 0; i <= m_csi_info.index && i < m_csi_info.max_fields; i++)
handle_sgr(m_csi_info.fields[i]);
break;
@ -563,6 +617,20 @@ Rectangle Terminal::putcodepoint(uint32_t codepoint)
break;
default:
{
if (m_cursor.x >= cols())
{
m_cursor.x = 0;
m_cursor.y++;
}
if (m_cursor.y >= rows())
{
const uint32_t scroll = m_cursor.y - rows() + 1;
m_cursor.y -= scroll;
m_window->shift_vertical(-scroll * (int32_t)m_font.height(), m_bg_color);
should_invalidate = { 0, 0, m_window->width(), m_window->height() };
}
const uint32_t cell_w = m_font.width();
const uint32_t cell_h = m_font.height();
const uint32_t cell_x = m_cursor.x * cell_w;
@ -580,12 +648,6 @@ Rectangle Terminal::putcodepoint(uint32_t codepoint)
}
}
if (m_cursor.x >= cols())
{
m_cursor.x = 0;
m_cursor.y++;
}
if (m_cursor.y >= rows())
{
const uint32_t scroll = m_cursor.y - rows() + 1;
@ -608,10 +670,11 @@ Rectangle Terminal::putchar(uint8_t ch)
return {};
}
m_state = State::CSI;
m_csi_info.index = 0;
m_csi_info.fields[0] = -1;
m_csi_info.fields[1] = -1;
m_csi_info.question = false;
m_csi_info = {
.fields = { -1, -1, -1, -1, -1 },
.index = 0,
.question = false,
};
return {};
}

View File

@ -41,6 +41,9 @@ private:
Rectangle putchar(uint8_t ch);
bool read_shell();
BAN::Optional<uint32_t> get_8bit_color();
BAN::Optional<uint32_t> get_24bit_color();
void hide_cursor();
void show_cursor();
@ -70,7 +73,7 @@ private:
struct CSIInfo
{
static constexpr size_t max_fields = 2;
static constexpr size_t max_fields = 5;
int32_t fields[max_fields];
size_t index;
bool question;