Compare commits

...

13 Commits

Author SHA1 Message Date
Bananymous 7c6966a9c4 Kernel: Add support for text mode terminal
This probably won't be used at all but it was so simple and made me do
really nice refactorings so i decided to add it :)
2025-04-18 02:45:06 +03:00
Bananymous 40d1d20cd6 Kernel: Move cursor handling from TTY -> TerminalDriver 2025-04-18 02:43:41 +03:00
Bananymous c0942d78cb Kernel: Fix TTY ANSI ? handling 2025-04-18 02:42:49 +03:00
Bananymous cef8779bf7 Kernel: Improve error handling when setting TTY font 2025-04-18 02:42:24 +03:00
Bananymous d6667844de Kernel: Fix fcntl F_SETFL and masking 2025-04-18 02:37:44 +03:00
Bananymous 4cdf218145 Kernel: Don't allow opening file with path "" 2025-04-18 02:37:16 +03:00
Bananymous 994713d04c Kernel: Implement FramebufferDevice::get_pixel() 2025-04-18 02:35:28 +03:00
Bananymous 554b13ac50 Kernel: Restructure terminal initialization
This is still very ugly and will be rewritten in the future :D
2025-04-18 01:19:59 +03:00
Bananymous 439fb57d88 Kernel: Fix ANSI CSI @ and b for VirtualTTY 2025-04-17 23:24:17 +03:00
Bananymous 4409d0f03f Terminal: Implement ANSI CSI A, B, b, d, @ 2025-04-17 23:16:22 +03:00
Bananymous ebd00b1eb2 Terminal: Implement ANSI SGR 10, 39, 49 2025-04-17 23:15:44 +03:00
Bananymous 3ca0ef1583 LibGUI: Add copy_rect API
This allows moving parts of window around
2025-04-17 23:14:46 +03:00
Bananymous 88abbd90dc LibC: Fix strncat
strncat was using strncpy internally which nullpadded dest until n bytes
were written.

also there was no terminating null byte added if src was shorter than n
bytes
2025-04-17 23:12:40 +03:00
27 changed files with 625 additions and 219 deletions

View File

@ -89,6 +89,8 @@ set(KERNEL_SOURCES
kernel/Terminal/FramebufferTerminal.cpp
kernel/Terminal/PseudoTerminal.cpp
kernel/Terminal/Serial.cpp
kernel/Terminal/TerminalDriver.cpp
kernel/Terminal/TextModeTerminal.cpp
kernel/Terminal/TTY.cpp
kernel/Terminal/VirtualTTY.cpp
kernel/Thread.cpp

View File

@ -3,7 +3,8 @@
#include <stdint.h>
#define BANAN_BOOTLOADER_MAGIC 0xD3C60CFF
#define BANAN_BOOTLOADER_FB_RGB 1
#define BANAN_BOOTLOADER_FB_RGB 1
#define BANAN_BOOTLOADER_FB_TEXT 2
struct BananBootFramebufferInfo
{

View File

@ -15,6 +15,7 @@ namespace Kernel
None,
Unknown,
RGB,
Text,
};
paddr_t address;

View File

@ -15,6 +15,7 @@ namespace Kernel
uint32_t width() const { return m_width; }
uint32_t height() const { return m_height; }
uint32_t get_pixel(uint32_t x, uint32_t y) const;
void set_pixel(uint32_t x, uint32_t y, uint32_t rgb);
// positive rows -> empty pixels on bottom

View File

@ -9,25 +9,40 @@ namespace Kernel
class FramebufferTerminalDriver final : public TerminalDriver
{
public:
static FramebufferTerminalDriver* create(BAN::RefPtr<FramebufferDevice>);
static BAN::ErrorOr<BAN::RefPtr<FramebufferTerminalDriver>> create(BAN::RefPtr<FramebufferDevice>);
virtual uint32_t width() const override { return m_framebuffer_device->width() / font().width(); }
virtual uint32_t height() const override { return m_framebuffer_device->height() / font().height(); }
uint32_t width() const override { return m_framebuffer_device->width() / m_font.width(); }
uint32_t height() const override { return m_framebuffer_device->height() / m_font.height(); }
virtual void putchar_at(uint16_t, uint32_t, uint32_t, Color, Color) override;
virtual bool scroll(Color) override;
virtual void clear(Color) override;
void putchar_at(uint16_t, uint32_t, uint32_t, Color, Color) override;
bool scroll(Color) override;
void clear(Color) override;
virtual void set_cursor_position(uint32_t, uint32_t) override;
void set_cursor_shown(bool) override;
void set_cursor_position(uint32_t, uint32_t) override;
bool has_font() const override { return true; }
BAN::ErrorOr<void> set_font(LibFont::Font&& font) override;
const LibFont::Font& font() const override { return m_font; };
private:
FramebufferTerminalDriver(BAN::RefPtr<FramebufferDevice> framebuffer_device)
: m_framebuffer_device(framebuffer_device)
{ }
void read_cursor();
void show_cursor(bool use_data);
private:
BAN::RefPtr<FramebufferDevice> m_framebuffer_device;
static constexpr Color s_cursor_color = TerminalColor::BRIGHT_WHITE;
LibFont::Font m_font;
uint32_t m_cursor_x { 0 };
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;
};
}

View File

@ -15,7 +15,7 @@ namespace Kernel
class TTY : public CharacterDevice
{
public:
virtual void set_font(const LibFont::Font&) {};
virtual BAN::ErrorOr<void> set_font(LibFont::Font&&) { return BAN::Error::from_errno(EINVAL); }
void set_foreground_pgrp(pid_t pgrp) { m_foreground_pgrp = pgrp; }
pid_t foreground_pgrp() const { return m_foreground_pgrp; }

View File

@ -1,13 +1,13 @@
#pragma once
#include <LibFont/Font.h>
#include <BAN/RefPtr.h>
#include <stdint.h>
#include <LibFont/Font.h>
namespace Kernel
{
class TerminalDriver
class TerminalDriver : public BAN::RefCounted<TerminalDriver>
{
public:
struct Color
@ -25,7 +25,7 @@ namespace Kernel
};
public:
TerminalDriver() : m_font(MUST(LibFont::Font::prefs())) {}
static BAN::ErrorOr<void> initialize_from_boot_info();
virtual ~TerminalDriver() {}
virtual uint32_t width() const = 0;
virtual uint32_t height() const = 0;
@ -34,15 +34,16 @@ namespace Kernel
virtual bool scroll(Color) { return false; }
virtual void clear(Color) = 0;
virtual void set_cursor_shown(bool) = 0;
virtual void set_cursor_position(uint32_t, uint32_t) = 0;
void set_font(const LibFont::Font& font) { m_font = font; };
const LibFont::Font& font() const { return m_font; }
private:
LibFont::Font m_font;
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(); }
};
extern BAN::RefPtr<TerminalDriver> g_terminal_driver;
namespace TerminalColor
{
static constexpr TerminalDriver::Color BLACK = 0x000000;

View File

@ -0,0 +1,42 @@
#pragma once
#include <kernel/Terminal/TerminalDriver.h>
namespace Kernel
{
class TextModeTerminalDriver final : public TerminalDriver
{
public:
static BAN::ErrorOr<BAN::RefPtr<TextModeTerminalDriver>> create_from_boot_info();
~TextModeTerminalDriver();
uint32_t width() const override { return m_width; }
uint32_t height() const override { return m_height; }
void putchar_at(uint16_t, uint32_t, uint32_t, Color, Color) override;
void clear(Color) override;
void set_cursor_shown(bool) override;
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)
, m_width(width)
, m_height(height)
, m_pitch(pitch)
{}
BAN::ErrorOr<void> initialize();
private:
const paddr_t m_paddr;
const uint32_t m_width;
const uint32_t m_height;
const uint32_t m_pitch;
vaddr_t m_vaddr { 0 };
static constexpr Color s_cursor_color = TerminalColor::BRIGHT_WHITE;
};
}

View File

@ -12,9 +12,9 @@ namespace Kernel
class VirtualTTY : public TTY
{
public:
static BAN::ErrorOr<BAN::RefPtr<VirtualTTY>> create(TerminalDriver*);
static BAN::ErrorOr<BAN::RefPtr<VirtualTTY>> create(BAN::RefPtr<TerminalDriver>);
virtual void set_font(const LibFont::Font&) override;
virtual BAN::ErrorOr<void> set_font(LibFont::Font&&) override;
virtual uint32_t height() const override { return m_height; }
virtual uint32_t width() const override { return m_width; }
@ -26,14 +26,14 @@ namespace Kernel
virtual void putchar_impl(uint8_t ch) override;
private:
VirtualTTY(TerminalDriver*);
VirtualTTY(BAN::RefPtr<TerminalDriver>);
void reset_ansi();
void handle_ansi_csi(uint8_t ch);
void handle_ansi_csi_color(uint8_t ch);
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 set_cursor_position(uint32_t x, uint32_t y);
private:
enum class State
@ -81,9 +81,8 @@ namespace Kernel
uint32_t m_row { 0 };
uint32_t m_column { 0 };
Cell* m_buffer { nullptr };
bool m_show_cursor { true };
TerminalDriver* m_terminal_driver { nullptr };
BAN::RefPtr<TerminalDriver> m_terminal_driver;
};
}

View File

@ -11,7 +11,8 @@
#define MULTIBOOT2_TAG_OLD_RSDP 14
#define MULTIBOOT2_TAG_NEW_RSDP 15
#define MULTIBOOT2_FRAMEBUFFER_TYPE_RGB 1
#define MULTIBOOT2_FRAMEBUFFER_TYPE_RGB 1
#define MULTIBOOT2_FRAMEBUFFER_TYPE_TEXT 2
#define MULTIBOOT2_MAGIC 0x36d76289

View File

@ -41,6 +41,8 @@ namespace Kernel
g_boot_info.framebuffer.bpp = framebuffer_tag.framebuffer_bpp;
if (framebuffer_tag.framebuffer_type == MULTIBOOT2_FRAMEBUFFER_TYPE_RGB)
g_boot_info.framebuffer.type = FramebufferInfo::Type::RGB;
else if (framebuffer_tag.framebuffer_type == MULTIBOOT2_FRAMEBUFFER_TYPE_TEXT)
g_boot_info.framebuffer.type = FramebufferInfo::Type::Text;
else
g_boot_info.framebuffer.type = FramebufferInfo::Type::Unknown;
}
@ -107,6 +109,8 @@ namespace Kernel
g_boot_info.framebuffer.bpp = framebuffer.bpp;
if (framebuffer.type == BANAN_BOOTLOADER_FB_RGB)
g_boot_info.framebuffer.type = FramebufferInfo::Type::RGB;
else if (framebuffer.type == BANAN_BOOTLOADER_FB_TEXT)
g_boot_info.framebuffer.type = FramebufferInfo::Type::Text;
else
g_boot_info.framebuffer.type = FramebufferInfo::Type::Unknown;

View File

@ -9,7 +9,6 @@
#include <ctype.h>
bool g_disable_debug = false;
extern Kernel::TerminalDriver* g_terminal_driver;
namespace Debug
{

View File

@ -3,14 +3,12 @@
#include <kernel/Device/FramebufferDevice.h>
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/Memory/Heap.h>
#include <kernel/Terminal/TerminalDriver.h>
#include <kernel/Terminal/FramebufferTerminal.h>
#include <sys/framebuffer.h>
#include <sys/mman.h>
#include <sys/sysmacros.h>
extern Kernel::TerminalDriver* g_terminal_driver;
namespace Kernel
{
@ -22,8 +20,7 @@ namespace Kernel
BAN::ErrorOr<BAN::RefPtr<FramebufferDevice>> FramebufferDevice::create_from_boot_framebuffer()
{
if (g_boot_info.framebuffer.type != FramebufferInfo::Type::RGB)
return BAN::Error::from_errno(ENODEV);
ASSERT(g_boot_info.framebuffer.type == FramebufferInfo::Type::RGB);
if (g_boot_info.framebuffer.bpp != 24 && g_boot_info.framebuffer.bpp != 32)
return BAN::Error::from_errno(ENOTSUP);
auto* device_ptr = new FramebufferDevice(
@ -39,6 +36,7 @@ namespace Kernel
return BAN::Error::from_errno(ENOMEM);
auto device = BAN::RefPtr<FramebufferDevice>::adopt(device_ptr);
TRY(device->initialize());
DevFileSystem::get().add_device(device);
return device;
}
@ -127,6 +125,15 @@ namespace Kernel
return bytes_to_copy;
}
uint32_t FramebufferDevice::get_pixel(uint32_t x, uint32_t y) const
{
ASSERT(x < m_width && y < m_height);
const auto* video_buffer_u8 = reinterpret_cast<const uint8_t*>(m_video_buffer->vaddr());
return (video_buffer_u8[(y * m_width + x) * (BANAN_FB_BPP / 8) + 0] << 0)
| (video_buffer_u8[(y * m_width + x) * (BANAN_FB_BPP / 8) + 1] << 8)
| (video_buffer_u8[(y * m_width + x) * (BANAN_FB_BPP / 8) + 2] << 16);
}
void FramebufferDevice::set_pixel(uint32_t x, uint32_t y, uint32_t rgb)
{
if (x >= m_width || y >= m_height)
@ -315,6 +322,8 @@ namespace Kernel
const uint32_t fb_width = m_framebuffer->width();
// If we are here (in FramebufferMemoryRegion), our terminal driver is FramebufferTerminalDriver
ASSERT(g_terminal_driver->has_font());
const auto& font = g_terminal_driver->font();
const uint32_t x = first_pixel % fb_width;

View File

@ -210,7 +210,7 @@ namespace Kernel
return m_open_files[fd].status_flags();
case F_SETFL:
extra &= O_APPEND | O_DSYNC | O_NONBLOCK | O_RSYNC | O_SYNC;
m_open_files[fd].status_flags() &= ~O_ACCMODE;
m_open_files[fd].status_flags() &= O_ACCMODE;
m_open_files[fd].status_flags() |= extra;
return 0;
default:

View File

@ -445,6 +445,9 @@ namespace Kernel
{
ASSERT(m_process_lock.is_locked());
if (path[0] == '\0')
return BAN::Error::from_errno(ENOENT);
auto relative_parent = TRY(find_relative_parent(fd, path));
VirtualFileSystem::File parent;

View File

@ -5,8 +5,6 @@
#include <kernel/Thread.h>
#include <kernel/Timer/Timer.h>
extern Kernel::TerminalDriver* g_terminal_driver;
namespace Kernel
{

View File

@ -3,11 +3,13 @@
namespace Kernel
{
FramebufferTerminalDriver* FramebufferTerminalDriver::create(BAN::RefPtr<FramebufferDevice> framebuffer_device)
BAN::ErrorOr<BAN::RefPtr<FramebufferTerminalDriver>> FramebufferTerminalDriver::create(BAN::RefPtr<FramebufferDevice> framebuffer_device)
{
auto* driver = new FramebufferTerminalDriver(framebuffer_device);
if (driver == nullptr)
return nullptr;
auto* driver_ptr = new FramebufferTerminalDriver(framebuffer_device);
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);
return driver;
@ -15,51 +17,129 @@ namespace Kernel
void FramebufferTerminalDriver::putchar_at(uint16_t ch, uint32_t x, uint32_t y, Color fg, Color bg)
{
const uint8_t* glyph = font().has_glyph(ch) ? font().glyph(ch) : font().glyph('?');
const uint8_t* glyph = m_font.has_glyph(ch) ? m_font.glyph(ch) : m_font.glyph('?');
x *= font().width();
y *= font().height();
x *= m_font.width();
y *= m_font.height();
for (uint32_t dy = 0; dy < font().height() && y + dy < m_framebuffer_device->height(); dy++)
for (uint32_t dy = 0; dy < m_font.height() && y + dy < m_framebuffer_device->height(); dy++)
{
for (uint32_t dx = 0; dx < font().width() && x + dx < m_framebuffer_device->width(); dx++)
for (uint32_t dx = 0; dx < m_font.width() && x + dx < m_framebuffer_device->width(); dx++)
{
const uint8_t bitmask = 1 << (font().width() - dx - 1);
const auto color = glyph[dy * font().pitch()] & bitmask ? fg : bg;
const uint8_t bitmask = 1 << (m_font.width() - dx - 1);
const auto color = glyph[dy * m_font.pitch()] & bitmask ? fg : bg;
m_framebuffer_device->set_pixel(x + dx, y + dy, color.rgb);
}
}
m_framebuffer_device->sync_pixels_rectangle(x, y, font().width(), font().height());
m_framebuffer_device->sync_pixels_rectangle(x, y, m_font.width(), m_font.height());
if (x == m_cursor_x && y == m_cursor_y)
{
read_cursor();
show_cursor(false);
}
}
bool FramebufferTerminalDriver::scroll(Color color)
{
m_framebuffer_device->scroll(font().height(), color.rgb);
m_framebuffer_device->scroll(m_font.height(), color.rgb);
m_framebuffer_device->sync_pixels_full();
return true;
}
void FramebufferTerminalDriver::clear(Color color)
{
for (auto& pixel : m_cursor_data)
pixel = color.rgb;
for (uint32_t y = 0; y < m_framebuffer_device->height(); y++)
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();
}
void FramebufferTerminalDriver::set_cursor_position(uint32_t x, uint32_t y)
void FramebufferTerminalDriver::read_cursor()
{
const uint32_t cursor_h = font().height() / 8;
const uint32_t cursor_top = font().height() * 13 / 16;
const uint32_t cursor_h = m_font.height() / 8;
const uint32_t cursor_top = m_font.height() * 13 / 16;
x *= font().width();
y *= font().height();
const uint32_t x = m_cursor_x * m_font.width();
const uint32_t y = m_cursor_y * m_font.height();
for (uint32_t dy = 0; dy < cursor_h; dy++)
for (uint32_t dx = 0; dx < font().width(); dx++)
m_framebuffer_device->set_pixel(x + dx, y + cursor_top + dy, s_cursor_color.rgb);
m_framebuffer_device->sync_pixels_rectangle(x, y + cursor_top, font().width(), cursor_h);
for (uint32_t dx = 0; dx < m_font.width(); dx++)
m_cursor_data[dy * m_font.width() + dx] = m_framebuffer_device->get_pixel(x + dx, y + cursor_top + dy);
}
void FramebufferTerminalDriver::show_cursor(bool use_data)
{
const auto get_color =
[this, use_data](uint32_t x, uint32_t y) -> uint32_t
{
if (!use_data)
return m_cursor_color.rgb;
return m_cursor_data[y * m_font.width() + x];
};
const uint32_t cursor_h = m_font.height() / 8;
const uint32_t cursor_w = m_font.width();
const uint32_t cursor_top = m_font.height() * 13 / 16;
const uint32_t x = m_cursor_x * m_font.width();
const uint32_t y = m_cursor_y * m_font.height();
for (uint32_t dy = 0; dy < cursor_h; dy++)
for (uint32_t dx = 0; dx < cursor_w; dx++)
m_framebuffer_device->set_pixel(x + dx, y + cursor_top + dy, get_color(dx, dy));
m_framebuffer_device->sync_pixels_rectangle(x, y + cursor_top, cursor_w, cursor_h);
}
void FramebufferTerminalDriver::set_cursor_shown(bool shown)
{
if (m_cursor_shown == shown)
return;
m_cursor_shown = shown;
if (m_cursor_shown)
{
read_cursor();
show_cursor(false);
}
else
{
show_cursor(true);
}
}
void FramebufferTerminalDriver::set_cursor_position(uint32_t x, uint32_t y)
{
if (!m_cursor_shown)
{
m_cursor_x = x;
m_cursor_y = y;
return;
}
show_cursor(true);
m_cursor_x = x;
m_cursor_y = y;
read_cursor();
show_cursor(false);
}
BAN::ErrorOr<void> FramebufferTerminalDriver::set_font(LibFont::Font&& font)
{
const uint32_t cursor_h = font.height() / 8;
const uint32_t cursor_w = font.width();
TRY(m_cursor_data.resize(cursor_h * cursor_w));
for (auto& val : m_cursor_data)
val = TerminalColor::BLACK.rgb;
m_font = BAN::move(font);
m_cursor_x = BAN::Math::clamp<uint32_t>(m_cursor_x, 0, width() - 1);
m_cursor_y = BAN::Math::clamp<uint32_t>(m_cursor_y, 0, height() - 1);
return {};
}
}

View File

@ -188,7 +188,7 @@ namespace Kernel
{
auto absolute_path = TRY(Process::current().absolute_path_of(BAN::StringView(reinterpret_cast<const char*>(argument))));
auto new_font = TRY(LibFont::Font::load(absolute_path));
set_font(new_font);
TRY(set_font(BAN::move(new_font)));
return 0;
}
case TIOCGWINSZ:

View File

@ -0,0 +1,29 @@
#include <kernel/BootInfo.h>
#include <kernel/Terminal/FramebufferTerminal.h>
#include <kernel/Terminal/TextModeTerminal.h>
namespace Kernel
{
BAN::RefPtr<TerminalDriver> g_terminal_driver;
BAN::ErrorOr<void> TerminalDriver::initialize_from_boot_info()
{
switch (g_boot_info.framebuffer.type)
{
case FramebufferInfo::Type::None:
case FramebufferInfo::Type::Unknown:
return BAN::Error::from_errno(ENODEV);
case FramebufferInfo::Type::RGB:
g_terminal_driver = TRY(FramebufferTerminalDriver::create(
TRY(FramebufferDevice::create_from_boot_framebuffer())
));
break;
case FramebufferInfo::Type::Text:
g_terminal_driver = TRY(TextModeTerminalDriver::create_from_boot_info());
break;
}
return {};
}
}

View File

@ -0,0 +1,146 @@
#include <kernel/BootInfo.h>
#include <kernel/IO.h>
#include <kernel/Memory/PageTable.h>
#include <kernel/MMIO.h>
#include <kernel/Terminal/TextModeTerminal.h>
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 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);
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 uint32_t diff = rdiff*rdiff + gdiff*gdiff + bdiff*bdiff;
if (diff >= min_diff)
continue;
min_diff = diff;
closest = i;
}
return closest;
}
BAN::ErrorOr<BAN::RefPtr<TextModeTerminalDriver>> TextModeTerminalDriver::create_from_boot_info()
{
ASSERT(g_boot_info.framebuffer.type == FramebufferInfo::Type::Text);
if (g_boot_info.framebuffer.bpp != 16)
return BAN::Error::from_errno(ENOTSUP);
auto* driver_ptr = new TextModeTerminalDriver(
g_boot_info.framebuffer.address,
g_boot_info.framebuffer.width,
g_boot_info.framebuffer.height,
g_boot_info.framebuffer.pitch
);
if (driver_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
auto driver = BAN::RefPtr<TextModeTerminalDriver>::adopt(driver_ptr);
TRY(driver->initialize());
return driver;
}
BAN::ErrorOr<void> TextModeTerminalDriver::initialize()
{
const size_t page_count = range_page_count(m_paddr, m_height * m_pitch);
const vaddr_t vaddr = PageTable::kernel().reserve_free_contiguous_pages(page_count, KERNEL_OFFSET);
if (vaddr == 0)
return BAN::Error::from_errno(ENOMEM);
PageTable::kernel().map_range_at(
m_paddr & PAGE_ADDR_MASK,
vaddr,
page_count * PAGE_SIZE,
PageTable::Flags::ReadWrite | PageTable::Flags::Present,
PageTable::MemoryType::WriteCombining
);
m_vaddr = vaddr + (m_paddr % PAGE_SIZE);
set_cursor_position(0, 0);
clear(TerminalColor::BLACK);
return {};
}
TextModeTerminalDriver::~TextModeTerminalDriver()
{
if (m_vaddr == 0)
return;
const size_t page_count = range_page_count(m_paddr, m_height * m_pitch);
PageTable::kernel().unmap_range(m_vaddr & PAGE_ADDR_MASK, page_count * PAGE_SIZE);
}
void TextModeTerminalDriver::putchar_at(uint16_t ch, uint32_t x, uint32_t y, Color fg, Color bg)
{
if (x >= m_width || y >= m_height)
return;
if (ch >= 0x100)
ch = '?';
const uint8_t color =
(color_to_text_mode_color(bg) << 4) |
(color_to_text_mode_color(fg) << 0);
MMIO::write16(m_vaddr + y * m_pitch + 2 * x, ch | (color << 8));
}
void TextModeTerminalDriver::clear(Color color)
{
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);
}
void TextModeTerminalDriver::set_cursor_shown(bool shown)
{
if (shown)
{
IO::outb(0x3D4, 0x0A);
IO::outb(0x3D5, (IO::inb(0x3D5) & 0xC0) | 14);
IO::outb(0x3D4, 0x0B);
IO::outb(0x3D5, (IO::inb(0x3D5) & 0xE0) | 15);
}
else
{
IO::outb(0x3D4, 0x0A);
IO::outb(0x3D5, 0x20);
}
}
void TextModeTerminalDriver::set_cursor_position(uint32_t x, uint32_t y)
{
const uint16_t pos = y * m_width + x;
IO::outb(0x3D4, 0x0F);
IO::outb(0x3D5, pos & 0xFF);
IO::outb(0x3D4, 0x0E);
IO::outb(0x3D5, pos >> 8);
}
}

View File

@ -25,7 +25,7 @@ namespace Kernel
static BAN::Atomic<uint32_t> s_next_tty_number = 0;
BAN::ErrorOr<BAN::RefPtr<VirtualTTY>> VirtualTTY::create(TerminalDriver* driver)
BAN::ErrorOr<BAN::RefPtr<VirtualTTY>> VirtualTTY::create(BAN::RefPtr<TerminalDriver> driver)
{
auto* tty_ptr = new VirtualTTY(driver);
ASSERT(tty_ptr);
@ -35,7 +35,7 @@ namespace Kernel
return tty;
}
VirtualTTY::VirtualTTY(TerminalDriver* driver)
VirtualTTY::VirtualTTY(BAN::RefPtr<TerminalDriver> driver)
: TTY(0600, 0, 0)
, m_name(MUST(BAN::String::formatted("tty{}", s_next_tty_number++)))
, m_terminal_driver(driver)
@ -55,11 +55,14 @@ namespace Kernel
m_terminal_driver->clear(m_background);
}
void VirtualTTY::set_font(const LibFont::Font& font)
BAN::ErrorOr<void> VirtualTTY::set_font(LibFont::Font&& font)
{
if (!m_terminal_driver->has_font())
return BAN::Error::from_errno(EINVAL);
SpinLockGuard _(m_write_lock);
m_terminal_driver->set_font(font);
TRY(m_terminal_driver->set_font(BAN::move(font)));
uint32_t new_width = m_terminal_driver->width();
uint32_t new_height = m_terminal_driver->height();
@ -85,19 +88,8 @@ namespace Kernel
for (uint32_t y = 0; y < m_height; y++)
for (uint32_t x = 0; x < m_width; x++)
render_from_buffer(x, y);
}
void VirtualTTY::set_cursor_position(uint32_t x, uint32_t y)
{
ASSERT(m_write_lock.current_processor_has_lock());
static uint32_t last_x = -1;
static uint32_t last_y = -1;
if (last_x != uint32_t(-1) && last_y != uint32_t(-1))
render_from_buffer(last_x, last_y);
if (m_show_cursor)
m_terminal_driver->set_cursor_position(x, y);
last_x = x;
last_y = y;
return {};
}
void VirtualTTY::reset_ansi()
@ -329,23 +321,24 @@ namespace Kernel
case '@':
if (m_ansi_state.nums[0] == -1)
m_ansi_state.nums[0] = 1;
reset_ansi();
m_ansi_state.nums[0] = BAN::Math::min<uint32_t>(m_ansi_state.nums[0], m_width - m_column);
memmove(
&m_buffer[m_row * m_width + m_column],
&m_buffer[m_row * m_width + m_column + m_ansi_state.nums[0]],
m_width - m_column - m_ansi_state.nums[0]
);
for (int i = 0; i < m_ansi_state.nums[0]; i++)
putchar_impl(' ');
return;
putchar_at(' ', m_column + i, m_row);
for (uint32_t x = m_column + m_ansi_state.nums[0]; x < m_width; x++)
render_from_buffer(x, m_row);
return reset_ansi();
case 'b':
if (m_ansi_state.nums[0] == -1)
m_ansi_state.nums[0] = 1;
reset_ansi();
if (m_last_graphic_char)
{
char buffer[5] {};
BAN::UTF8::from_codepoints(&m_last_graphic_char, 1, buffer);
for (int i = 0; i < m_ansi_state.nums[0]; i++)
for (int j = 0; buffer[j]; j++)
putchar_impl(buffer[j]);
}
return;
putcodepoint(m_last_graphic_char);
return reset_ansi();
case 'd':
if (m_ansi_state.nums[0] == -1)
m_ansi_state.nums[0] = 1;
@ -355,7 +348,7 @@ namespace Kernel
if (m_ansi_state.index == 0 || m_ansi_state.nums[0] == -1)
{
m_ansi_state.question = true;
return reset_ansi();
return;
}
reset_ansi();
dprintln_if(DEBUG_VTTY, "invalid ANSI CSI ?");
@ -364,7 +357,7 @@ namespace Kernel
case 'l':
if (m_ansi_state.question && m_ansi_state.nums[0] == 25)
{
m_show_cursor = (ch == 'h');
m_terminal_driver->set_cursor_shown(ch == 'h');
return reset_ansi();
}
reset_ansi();
@ -396,75 +389,10 @@ namespace Kernel
m_terminal_driver->putchar_at(codepoint, x, y, m_foreground, m_background);
}
void VirtualTTY::putchar_impl(uint8_t ch)
void VirtualTTY::putcodepoint(uint32_t codepoint)
{
ASSERT(m_write_lock.current_processor_has_lock());
uint32_t codepoint = ch;
switch (m_state)
{
case State::Normal:
if ((ch & 0x80) == 0)
break;
if ((ch & 0xE0) == 0xC0)
{
m_utf8_state.codepoint = ch & 0x1F;
m_utf8_state.bytes_missing = 1;
}
else if ((ch & 0xF0) == 0xE0)
{
m_utf8_state.codepoint = ch & 0x0F;
m_utf8_state.bytes_missing = 2;
}
else if ((ch & 0xF8) == 0xF0)
{
m_utf8_state.codepoint = ch & 0x07;
m_utf8_state.bytes_missing = 3;
}
else
{
reset_ansi();
dprintln_if(DEBUG_VTTY, "invalid utf8");
return;
}
m_state = State::WaitingUTF8;
return;
case State::WaitingAnsiEscape:
if (ch == CSI)
m_state = State::WaitingAnsiCSI;
else
{
reset_ansi();
dprintln_if(DEBUG_VTTY, "unsupported byte after ansi escape {2H}", (uint8_t)ch);
}
return;
case State::WaitingAnsiCSI:
handle_ansi_csi(ch);
set_cursor_position(m_column, m_row);
return;
case State::WaitingUTF8:
if ((ch & 0xC0) != 0x80)
{
m_state = State::Normal;
dprintln_if(DEBUG_VTTY, "invalid utf8");
return;
}
m_utf8_state.codepoint = (m_utf8_state.codepoint << 6) | (ch & 0x3F);
m_utf8_state.bytes_missing--;
if (m_utf8_state.bytes_missing)
return;
m_state = State::Normal;
codepoint = m_utf8_state.codepoint;
break;
default:
ASSERT_NOT_REACHED();
}
bool old_show_cursor = m_show_cursor;
m_show_cursor = false;
set_cursor_position(m_column, m_row);
switch (codepoint)
{
case BEL: // TODO
@ -523,9 +451,75 @@ namespace Kernel
m_column = 0;
m_row--;
}
}
m_show_cursor = old_show_cursor;
set_cursor_position(m_column, m_row);
void VirtualTTY::putchar_impl(uint8_t ch)
{
ASSERT(m_write_lock.current_processor_has_lock());
uint32_t codepoint = ch;
switch (m_state)
{
case State::Normal:
if ((ch & 0x80) == 0)
break;
if ((ch & 0xE0) == 0xC0)
{
m_utf8_state.codepoint = ch & 0x1F;
m_utf8_state.bytes_missing = 1;
}
else if ((ch & 0xF0) == 0xE0)
{
m_utf8_state.codepoint = ch & 0x0F;
m_utf8_state.bytes_missing = 2;
}
else if ((ch & 0xF8) == 0xF0)
{
m_utf8_state.codepoint = ch & 0x07;
m_utf8_state.bytes_missing = 3;
}
else
{
reset_ansi();
dprintln_if(DEBUG_VTTY, "invalid utf8");
return;
}
m_state = State::WaitingUTF8;
return;
case State::WaitingAnsiEscape:
if (ch == CSI)
m_state = State::WaitingAnsiCSI;
else
{
reset_ansi();
dprintln_if(DEBUG_VTTY, "unsupported byte after ansi escape {2H}", (uint8_t)ch);
}
return;
case State::WaitingAnsiCSI:
handle_ansi_csi(ch);
return;
case State::WaitingUTF8:
if ((ch & 0xC0) != 0x80)
{
m_state = State::Normal;
dprintln_if(DEBUG_VTTY, "invalid utf8");
return;
}
m_utf8_state.codepoint = (m_utf8_state.codepoint << 6) | (ch & 0x3F);
m_utf8_state.bytes_missing--;
if (m_utf8_state.bytes_missing)
return;
m_state = State::Normal;
codepoint = m_utf8_state.codepoint;
break;
default:
ASSERT_NOT_REACHED();
}
putcodepoint(codepoint);
m_terminal_driver->set_cursor_position(m_column, m_row);
}
}

View File

@ -107,8 +107,6 @@ static void parse_command_line()
}
}
Kernel::TerminalDriver* g_terminal_driver = nullptr;
static void init2(void*);
extern "C" void kernel_main(uint32_t boot_magic, uint32_t boot_info)
@ -163,14 +161,10 @@ extern "C" void kernel_main(uint32_t boot_magic, uint32_t boot_info)
DevFileSystem::initialize();
dprintln("devfs initialized");
auto framebuffer_device = FramebufferDevice::create_from_boot_framebuffer();
if (!framebuffer_device.is_error())
{
DevFileSystem::get().add_device(framebuffer_device.value());
g_terminal_driver = FramebufferTerminalDriver::create(framebuffer_device.value());
}
if (g_terminal_driver)
dprintln("Framebuffer terminal initialized");
if (auto ret = TerminalDriver::initialize_from_boot_info(); ret.is_error())
dprintln("failed to initialize terminal driver: {}", ret.error());
else
dprintln("terminal driver initialized");
if (!cmdline.disable_smp)
InterruptController::get().initialize_multiprocessor();

View File

@ -147,7 +147,10 @@ char* strcat(char* __restrict__ dest, const char* __restrict__ src)
char* strncat(char* __restrict__ dest, const char* __restrict__ src, size_t n)
{
strncpy(dest + strlen(dest), src, n);
dest += strlen(dest);
while (*src && n--)
*dest++ = *src++;
*dest = '\0';
return dest;
}

View File

@ -211,6 +211,27 @@ namespace LibGUI
set_pixel(x, dst_y + fill_y_off + i, fill_color);
}
void Window::copy_rect(int32_t dst_x, int32_t dst_y, int32_t src_x, int32_t src_y, uint32_t width, uint32_t height, uint32_t fill_color)
{
fill_rect(dst_x, dst_y, width, height, fill_color);
if (!clamp_to_framebuffer(dst_x, dst_y, width, height))
return;
if (!clamp_to_framebuffer(src_x, src_y, width, height))
return;
const bool copy_dir = dst_y < src_y;
for (uint32_t i = 0; i < height; i++)
{
const uint32_t y_off = copy_dir ? i : height - i - 1;
memmove(
&m_framebuffer[(dst_y + y_off) * this->width()],
&m_framebuffer[(src_y + y_off) * this->width()],
width * 4
);
}
}
bool Window::clamp_to_framebuffer(int32_t& signed_x, int32_t& signed_y, uint32_t& width, uint32_t& height) const
{
int32_t min_x = BAN::Math::max<int32_t>(signed_x, 0);

View File

@ -59,6 +59,10 @@ namespace LibGUI
// fill_color is used when copying data outside of window bounds
void copy_horizontal_slice(int32_t dst_y, int32_t src_y, uint32_t amount, uint32_t fill_color);
// copy rect (src_x, src_y, width, height) to (dst_x, dst_y, width, height)
// fill_color is used when copying data outside of window bounds
void copy_rect(int32_t dst_x, int32_t dst_y, int32_t src_x, int32_t src_y, uint32_t width, uint32_t height, uint32_t fill_color);
void invalidate(int32_t x, int32_t y, uint32_t width, uint32_t height);
void invalidate() { return invalidate(0, 0, width(), height()); }

View File

@ -32,6 +32,9 @@ static constexpr uint32_t s_colors_bright[] {
0xCC'FFFFFF,
};
static constexpr auto s_default_bg_color = s_colors_dark[0];
static constexpr auto s_default_fg_color = s_colors_bright[7];
void Terminal::start_shell()
{
int pts_master = posix_openpt(O_RDWR | O_NOCTTY);
@ -108,8 +111,8 @@ void Terminal::run()
signal(SIGCHLD, [](int) { s_shell_exited = true; });
start_shell();
m_bg_color = s_colors_dark[0];
m_fg_color = s_colors_bright[7];
m_bg_color = s_default_bg_color;
m_fg_color = s_default_fg_color;
auto attributes = LibGUI::Window::default_attributes;
attributes.alpha_channel = true;
@ -278,8 +281,8 @@ void Terminal::handle_sgr()
switch (m_csi_info.fields[0])
{
case -1: case 0:
m_bg_color = s_colors_dark[0];
m_fg_color = s_colors_bright[7];
m_bg_color = s_default_bg_color;
m_fg_color = s_default_fg_color;
break;
case 1:
// FIXME: bold
@ -287,12 +290,21 @@ void Terminal::handle_sgr()
case 7:
BAN::swap(m_fg_color, m_bg_color);
break;
case 10:
// default font
break;
case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37:
m_fg_color = s_colors_dark[m_csi_info.fields[0] - 30];
break;
case 39:
m_fg_color = s_default_fg_color;
break;
case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47:
m_bg_color = s_colors_dark[m_csi_info.fields[0] - 40];
break;
case 49:
m_bg_color = s_default_bg_color;
break;
case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97:
m_fg_color = s_colors_bright[m_csi_info.fields[0] - 90];
break;
@ -332,15 +344,25 @@ Rectangle Terminal::handle_csi(char ch)
Rectangle should_invalidate;
switch (ch)
{
case 'A':
if (m_csi_info.fields[0] == -1)
m_csi_info.fields[0] = 1;
m_cursor.y = BAN::Math::max<int32_t>(m_cursor.y - m_csi_info.fields[0], 0);
break;
case 'B':
if (m_csi_info.fields[0] == -1)
m_csi_info.fields[0] = 1;
m_cursor.y = BAN::Math::min<int32_t>(m_cursor.y + m_csi_info.fields[0], rows() - 1);
break;
case 'C':
if (m_csi_info.fields[0] == -1)
m_csi_info.fields[0] = 1;
m_cursor.x = BAN::Math::clamp<int32_t>(m_cursor.x + m_csi_info.fields[0], 0, cols() - 1);
m_cursor.x = BAN::Math::min<int32_t>(m_cursor.x + m_csi_info.fields[0], cols() - 1);
break;
case 'D':
if (m_csi_info.fields[0] == -1)
m_csi_info.fields[0] = 1;
m_cursor.x = BAN::Math::clamp<int32_t>((int32_t)m_cursor.x - m_csi_info.fields[0], 0, cols() - 1);
m_cursor.x = BAN::Math::max<int32_t>(m_cursor.x - m_csi_info.fields[0], 0);
break;
case 'G':
m_cursor.x = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, cols()) - 1;
@ -449,6 +471,34 @@ Rectangle Terminal::handle_csi(char ch)
break;
}
case '@':
{
const uint32_t count = (m_csi_info.fields[0] == -1) ? 1 : m_csi_info.fields[0];
const uint32_t dst_x = (m_cursor.x + count) * m_font.width();
const uint32_t src_x = m_cursor.x * m_font.width();
const uint32_t y = m_cursor.y * m_font.height();
m_window->copy_rect(dst_x, y, src_x, y, m_window->width() - dst_x, m_font.height(), m_bg_color);
m_window->fill_rect(src_x, y, count * m_font.width(), m_font.height(), m_bg_color);
should_invalidate = {
src_x,
y,
m_window->width() - src_x,
m_font.height()
};
break;
}
case 'b':
if (m_csi_info.fields[0] == -1)
m_csi_info.fields[0] = 1;
if (m_last_graphic_char)
for (int32_t i = 0; i < m_csi_info.fields[0]; i++)
should_invalidate = should_invalidate.get_bounding_box(putcodepoint(m_last_graphic_char));
break;
case 'd':
m_cursor.y = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, rows()) - 1;
break;
case 'm':
handle_sgr();
break;
@ -476,6 +526,59 @@ Rectangle Terminal::handle_csi(char ch)
return should_invalidate;
}
Rectangle Terminal::putcodepoint(uint32_t codepoint)
{
Rectangle should_invalidate;
switch (codepoint)
{
case '\e':
m_state = State::ESC;
break;
case '\n':
m_cursor.x = 0;
m_cursor.y++;
break;
case '\r':
m_cursor.x = 0;
break;
case '\b':
if (m_cursor.x > 0)
m_cursor.x--;
break;
default:
{
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;
const uint32_t cell_y = m_cursor.y * cell_h;
m_window->fill_rect(cell_x, cell_y, cell_w, cell_h, m_bg_color);
m_window->draw_character(codepoint, m_font, cell_x, cell_y, m_fg_color);
m_last_graphic_char = codepoint;
should_invalidate = { cell_x, cell_y, cell_w, cell_h };
m_cursor.x++;
break;
}
}
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() };
}
return should_invalidate;
}
Rectangle Terminal::putchar(uint8_t ch)
{
if (m_state == State::ESC)
@ -538,54 +641,7 @@ Rectangle Terminal::putchar(uint8_t ch)
return {};
}
Rectangle should_invalidate;
switch (codepoint)
{
case '\e':
m_state = State::ESC;
break;
case '\n':
m_cursor.x = 0;
m_cursor.y++;
break;
case '\r':
m_cursor.x = 0;
break;
case '\b':
if (m_cursor.x > 0)
m_cursor.x--;
break;
default:
{
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;
const uint32_t cell_y = m_cursor.y * cell_h;
m_window->fill_rect(cell_x, cell_y, cell_w, cell_h, m_bg_color);
m_window->draw_character(codepoint, m_font, cell_x, cell_y, m_fg_color);
should_invalidate = { cell_x, cell_y, cell_w, cell_h };
m_cursor.x++;
break;
}
}
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() };
}
return should_invalidate;
return putcodepoint(codepoint);
}
void Terminal::on_key_event(LibGUI::EventPacket::KeyEvent::event_t event)

View File

@ -37,6 +37,7 @@ public:
private:
void handle_sgr();
Rectangle handle_csi(char ch);
Rectangle putcodepoint(uint32_t codepoint);
Rectangle putchar(uint8_t ch);
bool read_shell();
@ -91,6 +92,8 @@ private:
uint8_t m_utf8_index { 0 };
uint8_t m_utf8_bytes[4] { };
uint32_t m_last_graphic_char { 0 };
Cursor m_saved_cursor { 0, 0 };
uint32_t m_fg_color { 0 };
uint32_t m_bg_color { 0 };