From a1100624bfd68b67a02881ceb7c1932b11aca596 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Wed, 5 Apr 2023 00:56:09 +0300 Subject: [PATCH] Kernel: Start work on making tty a file TTY is now a file that you can read from/write to. I still have to port shell to use this new interface --- kernel/CMakeLists.txt | 4 +- kernel/include/kernel/FS/Inode.h | 4 +- kernel/include/kernel/Process.h | 1 + kernel/include/kernel/Shell.h | 6 +- kernel/include/kernel/TTY.h | 57 -- kernel/include/kernel/Terminal/TTY.h | 113 ++++ .../kernel/{ => Terminal}/TerminalDriver.h | 0 .../{ => Terminal}/VesaTerminalDriver.h | 2 +- kernel/include/kernel/Terminal/termios.h | 12 + kernel/include/kernel/kprint.h | 6 +- kernel/kernel/Debug.cpp | 6 +- kernel/kernel/Process.cpp | 20 + kernel/kernel/Shell.cpp | 34 +- kernel/kernel/TTY.cpp | 392 -------------- kernel/kernel/Terminal/TTY.cpp | 512 ++++++++++++++++++ .../{ => Terminal}/VesaTerminalDriver.cpp | 2 +- kernel/kernel/kernel.cpp | 38 +- 17 files changed, 718 insertions(+), 491 deletions(-) delete mode 100644 kernel/include/kernel/TTY.h create mode 100644 kernel/include/kernel/Terminal/TTY.h rename kernel/include/kernel/{ => Terminal}/TerminalDriver.h (100%) rename kernel/include/kernel/{ => Terminal}/VesaTerminalDriver.h (95%) create mode 100644 kernel/include/kernel/Terminal/termios.h delete mode 100644 kernel/kernel/TTY.cpp create mode 100644 kernel/kernel/Terminal/TTY.cpp rename kernel/kernel/{ => Terminal}/VesaTerminalDriver.cpp (98%) diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index bdf1d65ecc..eb3ea45924 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -43,8 +43,8 @@ set(KERNEL_SOURCES kernel/Storage/StorageDevice.cpp kernel/Syscall.cpp kernel/Thread.cpp - kernel/TTY.cpp - kernel/VesaTerminalDriver.cpp + kernel/Terminal/TTY.cpp + kernel/Terminal/VesaTerminalDriver.cpp userspace/userspace.cpp icxxabi.cpp ) diff --git a/kernel/include/kernel/FS/Inode.h b/kernel/include/kernel/FS/Inode.h index a91721f83f..1ecffd0e05 100644 --- a/kernel/include/kernel/FS/Inode.h +++ b/kernel/include/kernel/FS/Inode.h @@ -81,8 +81,8 @@ namespace Kernel virtual BAN::ErrorOr> read_directory_inode(BAN::StringView) { if (!mode().ifdir()) return BAN::Error::from_errno(ENOTDIR); ASSERT_NOT_REACHED(); } virtual BAN::ErrorOr> read_directory_entries(size_t) { if (!mode().ifdir()) return BAN::Error::from_errno(ENOTDIR); ASSERT_NOT_REACHED(); } - virtual BAN::ErrorOr read(size_t, void*, size_t) { if (mode().ifdir()) return BAN::Error::from_errno(EISDIR); ASSERT_NOT_REACHED(); } - virtual BAN::ErrorOr write(size_t, void*, size_t) { if (mode().ifdir()) return BAN::Error::from_errno(EISDIR); ASSERT_NOT_REACHED(); } + virtual BAN::ErrorOr read(size_t, void*, size_t) { if (mode().ifdir()) return BAN::Error::from_errno(EISDIR); ASSERT_NOT_REACHED(); } + virtual BAN::ErrorOr write(size_t, const void*, size_t) { if (mode().ifdir()) return BAN::Error::from_errno(EISDIR); ASSERT_NOT_REACHED(); } virtual BAN::ErrorOr create_file(BAN::StringView, mode_t) { if (!mode().ifdir()) return BAN::Error::from_errno(ENOTDIR); ASSERT_NOT_REACHED(); } }; diff --git a/kernel/include/kernel/Process.h b/kernel/include/kernel/Process.h index 7b8988efad..3227da2215 100644 --- a/kernel/include/kernel/Process.h +++ b/kernel/include/kernel/Process.h @@ -32,6 +32,7 @@ namespace Kernel BAN::ErrorOr open(BAN::StringView, int); BAN::ErrorOr close(int); BAN::ErrorOr read(int, void*, size_t); + BAN::ErrorOr write(int, const void*, size_t); BAN::ErrorOr creat(BAN::StringView, mode_t); BAN::ErrorOr fstat(int, stat*); diff --git a/kernel/include/kernel/Shell.h b/kernel/include/kernel/Shell.h index b7741f6cc2..614e49a496 100644 --- a/kernel/include/kernel/Shell.h +++ b/kernel/include/kernel/Shell.h @@ -3,7 +3,6 @@ #include #include #include -#include namespace Kernel { @@ -11,7 +10,7 @@ namespace Kernel class Shell { public: - Shell(TTY*); + Shell(); Shell(const Shell&) = delete; BAN::ErrorOr set_prompt(BAN::StringView); void run(); @@ -23,8 +22,7 @@ namespace Kernel void key_event_callback(Input::KeyEvent); BAN::ErrorOr update_prompt(); - private: - TTY* m_tty; + private: BAN::Vector m_old_buffer; BAN::Vector m_buffer; BAN::String m_prompt_string; diff --git a/kernel/include/kernel/TTY.h b/kernel/include/kernel/TTY.h deleted file mode 100644 index 09175e8426..0000000000 --- a/kernel/include/kernel/TTY.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -#include - -class TTY -{ -public: - TTY(TerminalDriver*); - void clear(); - void putchar(char ch); - void write(const char* data, size_t size); - void write_string(const char* data); - void set_cursor_position(uint32_t x, uint32_t y); - void set_font(const Kernel::Font&); - - uint32_t height() const { return m_height; } - uint32_t width() const { return m_width; } - - void render_from_buffer(uint32_t x, uint32_t y); - - // for kprint - static void putchar_current(char ch); - static bool is_initialized(); - -private: - void reset_ansi_escape(); - void handle_ansi_sgr(); - void handle_ansi_escape(uint16_t ch); - void putchar_at(uint16_t ch, uint32_t x, uint32_t y); - -private: - struct Cell - { - TerminalDriver::Color foreground = TerminalColor::BRIGHT_WHITE; - TerminalDriver::Color background = TerminalColor::BLACK; - uint16_t character = ' '; - }; - - struct AnsiState - { - uint8_t mode = '\0'; - int32_t index = 0; - int32_t nums[2] = { -1, -1 }; - }; - - uint32_t m_width { 0 }; - uint32_t m_height { 0 }; - uint32_t m_row { 0 }; - uint32_t m_column { 0 }; - TerminalDriver::Color m_foreground { TerminalColor::BRIGHT_WHITE }; - TerminalDriver::Color m_background { TerminalColor::BLACK }; - Cell* m_buffer { nullptr }; - AnsiState m_ansi_state; - TerminalDriver* m_terminal_driver { nullptr }; - Kernel::SpinLock m_lock; -}; diff --git a/kernel/include/kernel/Terminal/TTY.h b/kernel/include/kernel/Terminal/TTY.h new file mode 100644 index 0000000000..f2e197115d --- /dev/null +++ b/kernel/include/kernel/Terminal/TTY.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +namespace Kernel +{ + + class TTY : public CharacterDevice + { + public: + TTY(TerminalDriver*); + void clear(); + void putchar(uint8_t ch); + void set_cursor_position(uint32_t x, uint32_t y); + void set_font(const Kernel::Font&); + + uint32_t height() const { return m_height; } + uint32_t width() const { return m_width; } + + // for kprint + static void putchar_current(uint8_t ch); + static bool is_initialized(); + + virtual BAN::ErrorOr read(size_t, void*, size_t) override; + virtual BAN::ErrorOr write(size_t, const void*, size_t) override; + + private: + void reset_ansi(); + void handle_ansi_csi(uint8_t ch); + void handle_ansi_csi_color(); + void putchar_at(uint32_t codepoint, uint32_t x, uint32_t y); + void render_from_buffer(uint32_t x, uint32_t y); + + void on_key(Input::KeyEvent); + + private: + enum class State + { + Normal, + WaitingAnsiEscape, + WaitingAnsiCSI, + WaitingUTF8, + }; + + struct AnsiState + { + int32_t nums[2] { -1, -1 }; + int32_t index { 0 }; + }; + + struct UTF8State + { + uint32_t codepoint { 0 }; + uint8_t bytes_missing { 0 }; + }; + + struct Cell + { + TerminalDriver::Color foreground { TerminalColor::BRIGHT_WHITE }; + TerminalDriver::Color background { TerminalColor::BLACK }; + uint32_t codepoint { ' ' }; + }; + + private: + Kernel::SpinLock m_lock; + + State m_state { State::Normal }; + AnsiState m_ansi_state { }; + UTF8State m_utf8_state { }; + + uint32_t m_width { 0 }; + uint32_t m_height { 0 }; + + uint32_t m_row { 0 }; + uint32_t m_column { 0 }; + Cell* m_buffer { nullptr }; + + TerminalDriver::Color m_foreground { TerminalColor::BRIGHT_WHITE }; + TerminalDriver::Color m_background { TerminalColor::BLACK }; + + termios m_termios; + + struct Buffer + { + BAN::Array buffer; + size_t bytes { 0 }; + bool flush { false }; + Semaphore semaphore; + }; + Buffer m_output; + + TerminalDriver* m_terminal_driver { nullptr }; + + public: + virtual Mode mode() const override { return { Mode::IFCHR | Mode::IRUSR }; } + virtual uid_t uid() const override { return 0; } + virtual gid_t gid() const override { return 0; } + virtual dev_t rdev() const override { return m_rdev; } + virtual BAN::StringView name() const { return m_name; } + + private: + const dev_t m_rdev; + const BAN::String m_name; + }; + +} diff --git a/kernel/include/kernel/TerminalDriver.h b/kernel/include/kernel/Terminal/TerminalDriver.h similarity index 100% rename from kernel/include/kernel/TerminalDriver.h rename to kernel/include/kernel/Terminal/TerminalDriver.h diff --git a/kernel/include/kernel/VesaTerminalDriver.h b/kernel/include/kernel/Terminal/VesaTerminalDriver.h similarity index 95% rename from kernel/include/kernel/VesaTerminalDriver.h rename to kernel/include/kernel/Terminal/VesaTerminalDriver.h index a1bdf9a9d8..c330d8e00b 100644 --- a/kernel/include/kernel/VesaTerminalDriver.h +++ b/kernel/include/kernel/Terminal/VesaTerminalDriver.h @@ -1,6 +1,6 @@ #pragma once -#include +#include class VesaTerminalDriver final : public TerminalDriver { diff --git a/kernel/include/kernel/Terminal/termios.h b/kernel/include/kernel/Terminal/termios.h new file mode 100644 index 0000000000..bb51fd75df --- /dev/null +++ b/kernel/include/kernel/Terminal/termios.h @@ -0,0 +1,12 @@ +#pragma once + +namespace Kernel +{ + + struct termios + { + bool canonical { true }; + bool echo { true }; + }; + +} \ No newline at end of file diff --git a/kernel/include/kernel/kprint.h b/kernel/include/kernel/kprint.h index 45a5b3a1db..4c3746f9d3 100644 --- a/kernel/include/kernel/kprint.h +++ b/kernel/include/kernel/kprint.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include -#define kprint(...) BAN::Formatter::print(TTY::putchar_current, __VA_ARGS__) -#define kprintln(...) BAN::Formatter::println(TTY::putchar_current, __VA_ARGS__) +#define kprint(...) BAN::Formatter::print(Kernel::TTY::putchar_current, __VA_ARGS__) +#define kprintln(...) BAN::Formatter::println(Kernel::TTY::putchar_current, __VA_ARGS__) diff --git a/kernel/kernel/Debug.cpp b/kernel/kernel/Debug.cpp index 6d6793f6b7..d8f1d6a4a7 100644 --- a/kernel/kernel/Debug.cpp +++ b/kernel/kernel/Debug.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include namespace Debug { @@ -40,8 +40,8 @@ namespace Debug { if (Serial::is_initialized()) return Serial::putchar(ch); - if (TTY::is_initialized()) - return TTY::putchar_current(ch); + if (Kernel::TTY::is_initialized()) + return Kernel::TTY::putchar_current(ch); } } \ No newline at end of file diff --git a/kernel/kernel/Process.cpp b/kernel/kernel/Process.cpp index 9325768a59..d3058aaf6a 100644 --- a/kernel/kernel/Process.cpp +++ b/kernel/kernel/Process.cpp @@ -90,6 +90,26 @@ namespace Kernel return n_read; } + BAN::ErrorOr Process::write(int fd, const void* buffer, size_t count) + { + m_lock.lock(); + TRY(validate_fd(fd)); + auto open_fd_copy = open_file_description(fd); + m_lock.unlock(); + + if (!(open_fd_copy.flags & O_RDONLY)) + return BAN::Error::from_errno(EBADF); + size_t n_written = TRY(open_fd_copy.inode->write(open_fd_copy.offset, buffer, count)); + open_fd_copy.offset += n_written; + + m_lock.lock(); + MUST(validate_fd(fd)); + open_file_description(fd) = open_fd_copy; + m_lock.unlock(); + + return n_written; + } + BAN::ErrorOr Process::creat(BAN::StringView path, mode_t mode) { auto absolute_path = TRY(absolute_path_of(path)); diff --git a/kernel/kernel/Shell.cpp b/kernel/kernel/Shell.cpp index 0995e11cdb..3e75360843 100644 --- a/kernel/kernel/Shell.cpp +++ b/kernel/kernel/Shell.cpp @@ -1,12 +1,12 @@ -#include #include #include #include #include #include +#include #include -#include #include +#include #include #include #include @@ -14,8 +14,8 @@ #include #include -#define TTY_PRINT(...) BAN::Formatter::print([this](char c) { m_tty->putchar(c); }, __VA_ARGS__) -#define TTY_PRINTLN(...) BAN::Formatter::println([this](char c) { m_tty->putchar(c); }, __VA_ARGS__) +#define TTY_PRINT(...) +#define TTY_PRINTLN(...) namespace Kernel { @@ -43,8 +43,7 @@ namespace Kernel return (const char*)buffer; }; - Shell::Shell(TTY* tty) - : m_tty(tty) + Shell::Shell() { MUST(set_prompt(s_default_prompt)); MUST(m_buffer.push_back(""sv)); @@ -100,14 +99,17 @@ namespace Kernel void Shell::run() { - int fd = MUST(Process::current()->open("/dev/input0"sv, O_RDONLY)); + int fd = MUST(Process::current()->open("/dev/tty1"sv, O_RDONLY)); TTY_PRINT("{}", m_prompt); for (;;) { - Input::KeyEvent event; - MUST(Process::current()->read(fd, &event, sizeof(event))); - key_event_callback(event); + uint8_t buffer[128]; + size_t n_read = MUST(Process::current()->read(fd, buffer, sizeof(buffer))); + dprintln("{}", BAN::StringView((const char*)buffer, n_read)); + //Input::KeyEvent event; + //MUST(Process::current()->read(fd, &event, sizeof(event))); + //key_event_callback(event); } } @@ -267,8 +269,9 @@ argument_done: { if (arguments.size() != 1) return BAN::Error::from_c_string("'clear' does not support command line arguments"); - m_tty->clear(); - m_tty->set_cursor_position(0, 0); + //m_tty->clear(); + //m_tty->set_cursor_position(0, 0); + TTY_PRINT("\e[2J\e[1;1H"); // clear and reset cursor } else if (arguments.front() == "time") { @@ -301,7 +304,8 @@ argument_done: PIT::sleep(5000); if (auto res = shell->process_command(args); res.is_error()) - BAN::Formatter::println([&](char c) { shell->m_tty->putchar(c); }, "{}", res.error()); + dprintln("{}", res.error()); + //BAN::Formatter::println([&](char c) { shell->m_tty->putchar(c); }, "{}", res.error()); }; SpinLock spinlock; @@ -521,7 +525,7 @@ argument_done: return BAN::Error::from_c_string("usage: 'loadfont font_path'"); auto font = TRY(Font::load(arguments[1])); - m_tty->set_font(font); + //m_tty->set_font(font); } else { @@ -676,7 +680,7 @@ argument_done: } } - TTY_PRINT("\e[{}G", (m_prompt_length + m_cursor_pos.col) % m_tty->width() + 1); + //TTY_PRINT("\e[{}G", (m_prompt_length + m_cursor_pos.col) % m_tty->width() + 1); } } \ No newline at end of file diff --git a/kernel/kernel/TTY.cpp b/kernel/kernel/TTY.cpp deleted file mode 100644 index 41eaf069ee..0000000000 --- a/kernel/kernel/TTY.cpp +++ /dev/null @@ -1,392 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -#define BEL 0x07 -#define BS 0x08 -#define HT 0x09 -#define LF 0x0A -#define FF 0x0C -#define CR 0x0D -#define ESC 0x1B - -#define CSI '[' - -template inline constexpr T max(T a, T b) { return a > b ? a : b; } -template inline constexpr T min(T a, T b) { return a < b ? a : b; } -template inline constexpr T clamp(T x, T a, T b) { return x < a ? a : x > b ? b : x; } - -static TTY* s_tty = nullptr; - -TTY::TTY(TerminalDriver* driver) - : m_terminal_driver(driver) -{ - m_width = m_terminal_driver->width(); - m_height = m_terminal_driver->height(); - - m_buffer = new Cell[m_width * m_height]; - - if (s_tty == nullptr) - s_tty = this; -} - -void TTY::clear() -{ - for (uint32_t i = 0; i < m_width * m_height; i++) - m_buffer[i] = { .foreground = m_foreground, .background = m_background, .character = ' ' }; - m_terminal_driver->clear(m_background); -} - -void TTY::set_cursor_position(uint32_t x, uint32_t y) -{ - 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); // Hacky way to clear previous cursor in graphics mode :D - m_terminal_driver->set_cursor_position(x, y); - last_x = m_column = x; - last_y = m_row = y; -} - -void TTY::set_font(const Kernel::Font& font) -{ - m_terminal_driver->set_font(font); - - uint32_t new_width = m_terminal_driver->width(); - uint32_t new_height = m_terminal_driver->height(); - - if (m_width != new_width || m_height != new_height) - { - Cell* new_buffer = new Cell[new_width * new_height]; - ASSERT(new_buffer); - - for (uint32_t i = 0; i < new_width * m_height; i++) - new_buffer[i] = { .foreground = m_foreground, .background = m_background, .character = ' ' }; - - for (uint32_t y = 0; y < BAN::Math::min(m_height, new_height); y++) - for (uint32_t x = 0; x < BAN::Math::min(m_width, new_width); x++) - new_buffer[y * new_width + x] = m_buffer[y * m_width + x]; - - delete[] m_buffer; - m_buffer = new_buffer; - m_width = new_width; - m_height = new_height; - } - - for (uint32_t y = 0; y < m_height; y++) - for (uint32_t x = 0; x < m_width; x++) - render_from_buffer(x, y); -} - -static uint16_t handle_unicode(uint8_t ch) -{ - static uint8_t unicode_left = 0; - static uint16_t codepoint = 0; - - if (unicode_left) - { - if ((ch >> 6) == 0b10) - { - codepoint = (codepoint << 6) | ch; - unicode_left--; - if (unicode_left > 0) - return 0xFFFF; - return codepoint; - } - else - { - // invalid utf-8 - unicode_left = 0; - return 0x00; - } - } - else - { - if ((ch >> 3) == 0b11110) - { - unicode_left = 3; - codepoint = ch & 0b00000111; - return 0xFFFF; - } - if ((ch >> 4) == 0b1110) - { - unicode_left = 2; - codepoint = ch & 0b00001111; - return 0xFFFF; - } - if ((ch >> 5) == 0b110) - { - unicode_left = 1; - codepoint = ch & 0b00011111; - return 0xFFFF; - } - } - - return ch & 0x7F; -} - -void TTY::reset_ansi_escape() -{ - m_ansi_state.mode = '\0'; - m_ansi_state.index = 0; - m_ansi_state.nums[0] = -1; - m_ansi_state.nums[1] = -1; -} - -void TTY::handle_ansi_sgr() -{ - switch (m_ansi_state.nums[0]) - { - case -1: - case 0: - m_foreground = TerminalColor::BRIGHT_WHITE; - m_background = TerminalColor::BLACK; - break; - - case 30: m_foreground = TerminalColor::BRIGHT_BLACK; break; - case 31: m_foreground = TerminalColor::BRIGHT_RED; break; - case 32: m_foreground = TerminalColor::BRIGHT_GREEN; break; - case 33: m_foreground = TerminalColor::BRIGHT_YELLOW; break; - case 34: m_foreground = TerminalColor::BRIGHT_BLUE; break; - case 35: m_foreground = TerminalColor::BRIGHT_MAGENTA; break; - case 36: m_foreground = TerminalColor::BRIGHT_CYAN; break; - case 37: m_foreground = TerminalColor::BRIGHT_WHITE; break; - - case 40: m_background = TerminalColor::BRIGHT_BLACK; break; - case 41: m_background = TerminalColor::BRIGHT_RED; break; - case 42: m_background = TerminalColor::BRIGHT_GREEN; break; - case 43: m_background = TerminalColor::BRIGHT_YELLOW; break; - case 44: m_background = TerminalColor::BRIGHT_BLUE; break; - case 45: m_background = TerminalColor::BRIGHT_MAGENTA; break; - case 46: m_background = TerminalColor::BRIGHT_CYAN; break; - case 47: m_background = TerminalColor::BRIGHT_WHITE; break; - } -} - -void TTY::handle_ansi_escape(uint16_t ch) -{ - switch (m_ansi_state.mode) - { - case '\1': - { - if (ch == CSI) - { - m_ansi_state.mode = CSI; - return; - } - return reset_ansi_escape(); - } - - case CSI: - { - switch (ch) - { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - { - int32_t& val = m_ansi_state.nums[m_ansi_state.index]; - val = (val == -1) ? (ch - '0') : (val * 10 + ch - '0'); - return; - } - case ';': - m_ansi_state.index++; - return; - case 'A': // Cursor Up - if (m_ansi_state.nums[0] == -1) - m_ansi_state.nums[0] = 1; - m_row = max(m_row - m_ansi_state.nums[0], 0); - return reset_ansi_escape(); - case 'B': // Curson Down - if (m_ansi_state.nums[0] == -1) - m_ansi_state.nums[0] = 1; - m_row = min(m_row + m_ansi_state.nums[0], m_height - 1); - return reset_ansi_escape(); - case 'C': // Cursor Forward - if (m_ansi_state.nums[0] == -1) - m_ansi_state.nums[0] = 1; - m_column = min(m_column + m_ansi_state.nums[0], m_width - 1); - return reset_ansi_escape(); - case 'D': // Cursor Back - if (m_ansi_state.nums[0] == -1) - m_ansi_state.nums[0] = 1; - m_column = max(m_column - m_ansi_state.nums[0], 0); - return reset_ansi_escape(); - case 'E': // Cursor Next Line - if (m_ansi_state.nums[0] == -1) - m_ansi_state.nums[0] = 1; - m_row = min(m_row + m_ansi_state.nums[0], m_height - 1); - m_column = 0; - return reset_ansi_escape(); - case 'F': // Cursor Previous Line - if (m_ansi_state.nums[0] == -1) - m_ansi_state.nums[0] = 1; - m_row = max(m_row - m_ansi_state.nums[0], 0); - m_column = 0; - return reset_ansi_escape(); - case 'G': // Cursor Horizontal Absolute - if (m_ansi_state.nums[0] == -1) - m_ansi_state.nums[0] = 1; - m_column = clamp(m_ansi_state.nums[0] - 1, 0, m_width - 1); - return reset_ansi_escape(); - case 'H': // Cursor Position - if (m_ansi_state.nums[0] == -1) - m_ansi_state.nums[0] = 1; - if (m_ansi_state.nums[1] == -1) - m_ansi_state.nums[1] = 1; - m_row = clamp(m_ansi_state.nums[0] - 1, 0, m_height - 1); - m_column = clamp(m_ansi_state.nums[1] - 1, 0, m_width - 1); - return reset_ansi_escape(); - case 'J': // Erase in Display - dprintln("Unsupported ANSI CSI character J"); - return reset_ansi_escape(); - case 'K': // Erase in Line - if (m_ansi_state.nums[0] == -1 || m_ansi_state.nums[0] == 0) - for (uint32_t i = m_column; i < m_width; i++) - putchar_at(' ', i, m_row); - else - dprintln("Unsupported ANSI CSI character K"); - return reset_ansi_escape(); - case 'S': // Scroll Up - dprintln("Unsupported ANSI CSI character S"); - return reset_ansi_escape(); - case 'T': // Scroll Down - dprintln("Unsupported ANSI CSI character T"); - return reset_ansi_escape(); - case 'f': // Horizontal Vertical Position - dprintln("Unsupported ANSI CSI character f"); - return reset_ansi_escape(); - case 'm': - handle_ansi_sgr(); - return reset_ansi_escape(); - default: - dprintln("Unsupported ANSI CSI character {}", ch); - return reset_ansi_escape(); - } - } - - default: - dprintln("Unsupported ANSI mode"); - return reset_ansi_escape(); - } -} - -void TTY::render_from_buffer(uint32_t x, uint32_t y) -{ - ASSERT(x < m_width && y < m_height); - const auto& cell = m_buffer[y * m_width + x]; - m_terminal_driver->putchar_at(cell.character, x, y, cell.foreground, cell.background); -} - -void TTY::putchar_at(uint16_t ch, uint32_t x, uint32_t y) -{ - ASSERT(x < m_width && y < m_height); - auto& cell = m_buffer[y * m_width + x]; - cell.character = ch; - cell.foreground = m_foreground; - cell.background = m_background; - m_terminal_driver->putchar_at(ch, x, y, m_foreground, m_background); -} - -void TTY::putchar(char ch) -{ - Kernel::LockGuard guard(m_lock); - - uint16_t cp = handle_unicode(ch); - if (cp == 0xFFFF) - return; - - if (m_ansi_state.mode != 0) - { - handle_ansi_escape(cp); - set_cursor_position(m_column, m_row); - return; - } - - // https://en.wikipedia.org/wiki/ANSI_escape_code - switch (cp) - { - case BEL: // TODO - break; - case BS: - if (m_column > 0) - m_column--; - break; - case HT: - m_column++; - while (m_column % 8) - m_column++; - break; - case LF: - m_column = 0; - m_row++; - break; - case FF: - m_row++; - break; - case CR: - m_column = 0; - break; - case ESC: - m_ansi_state.mode = '\1'; - break; - default: - putchar_at(cp, m_column, m_row); - m_column++; - break; - } - - if (m_column >= m_width) - { - m_column = 0; - m_row++; - } - - 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, .character = ' ' }; - - // 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--; - } - - set_cursor_position(m_column, m_row); -} - -void TTY::write(const char* data, size_t size) -{ - for (size_t i = 0; i < size; i++) - putchar(data[i]); -} - -void TTY::write_string(const char* data) -{ - while (*data) - { - putchar(*data); - data++; - } -} - -void TTY::putchar_current(char ch) -{ - ASSERT(s_tty); - s_tty->putchar(ch); -} - -bool TTY::is_initialized() -{ - return s_tty != nullptr; -} diff --git a/kernel/kernel/Terminal/TTY.cpp b/kernel/kernel/Terminal/TTY.cpp new file mode 100644 index 0000000000..db41bf2421 --- /dev/null +++ b/kernel/kernel/Terminal/TTY.cpp @@ -0,0 +1,512 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BEL 0x07 +#define BS 0x08 +#define HT 0x09 +#define LF 0x0A +#define FF 0x0C +#define CR 0x0D +#define ESC 0x1B + +#define CSI '[' + +namespace Kernel +{ + + static dev_t next_tty_rdev() + { + static dev_t major = DeviceManager::get().get_next_rdev(); + static dev_t minor = 1; + return makedev(major, minor++); + } + + static TTY* s_tty = nullptr; + + TTY::TTY(TerminalDriver* driver) + : m_terminal_driver(driver) + , m_rdev(next_tty_rdev()) + , m_name(BAN::String::formatted("tty{}", minor(m_rdev))) + { + m_width = m_terminal_driver->width(); + m_height = m_terminal_driver->height(); + + m_buffer = new Cell[m_width * m_height]; + + if (s_tty == nullptr) + s_tty = this; + + MUST(Process::create_kernel( + [](void* tty_) + { + TTY* tty = (TTY*)tty_; + int fd = MUST(Process::current()->open("/dev/input0"sv, O_RDONLY)); + while (true) + { + Input::KeyEvent event; + MUST(Process::current()->read(fd, &event, sizeof(event))); + tty->on_key(event); + } + }, this) + ); + } + + void TTY::on_key(Input::KeyEvent event) + { + ASSERT(!m_lock.is_locked()); + LockGuard _(m_lock); + + if (event.released()) + return; + + const char* ansi = Input::key_event_to_utf8(event); + bool flush = false; + + if (event.ctrl()) + { + ansi = nullptr; + switch (event.key) + { + case Input::Key::D: + flush = true; + break; + default: + break; + } + } + else + { + switch (event.key) + { + case Input::Key::Enter: + case Input::Key::NumpadEnter: + flush = true; + ansi = "\n"; + break; + case Input::Key::Backspace: + if (m_output.bytes > 0) + { + // Multibyte UTF8 + if ((m_output.buffer[m_output.bytes - 1] & 0xC0) == 0x80) + { + // NOTE: this should be valid UTF8 since keyboard input already 'validates' it + while ((m_output.buffer[m_output.bytes - 1] & 0xC0) == 0x80) + { + ASSERT(m_output.bytes > 0); + m_output.bytes--; + } + ansi = "\b \b"; + } + // Control sequence + else if (m_output.bytes >= 2 && m_output.buffer[m_output.bytes - 2] == '\e') + { + m_output.bytes -= 2; + ansi = "\b\b \b\b"; + } + // Ascii + else + { + m_output.bytes--; + ansi = "\b \b"; + } + } + break; + default: + break; + } + } + + if (!m_termios.canonical) + flush = true; + + if (ansi != nullptr) + { + for (size_t i = 0; ansi[i]; i++) + { + if (m_termios.echo) + putchar(ansi[i]); + if (m_output.bytes == m_output.buffer.size()) + { + dprintln("TTY buffer full"); + continue; + } + m_output.buffer[m_output.bytes++] = ansi[i]; + } + } + + if (flush) + { + m_output.flush = true; + m_output.semaphore.unblock(); + } + } + + void TTY::clear() + { + for (uint32_t i = 0; i < m_width * m_height; i++) + m_buffer[i] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' }; + m_terminal_driver->clear(m_background); + } + + void TTY::set_cursor_position(uint32_t x, uint32_t y) + { + 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); // Hacky way to clear previous cursor in graphics mode :D + m_terminal_driver->set_cursor_position(x, y); + last_x = m_column = x; + last_y = m_row = y; + } + + void TTY::set_font(const Kernel::Font& font) + { + m_terminal_driver->set_font(font); + + uint32_t new_width = m_terminal_driver->width(); + uint32_t new_height = m_terminal_driver->height(); + + if (m_width != new_width || m_height != new_height) + { + Cell* new_buffer = new Cell[new_width * new_height]; + ASSERT(new_buffer); + + for (uint32_t i = 0; i < new_width * m_height; i++) + new_buffer[i] = { .foreground = m_foreground, .background = m_background, .codepoint = ' ' }; + + for (uint32_t y = 0; y < BAN::Math::min(m_height, new_height); y++) + for (uint32_t x = 0; x < BAN::Math::min(m_width, new_width); x++) + new_buffer[y * new_width + x] = m_buffer[y * m_width + x]; + + delete[] m_buffer; + m_buffer = new_buffer; + m_width = new_width; + m_height = new_height; + } + + for (uint32_t y = 0; y < m_height; y++) + for (uint32_t x = 0; x < m_width; x++) + render_from_buffer(x, y); + } + + void TTY::reset_ansi() + { + m_ansi_state.index = 0; + m_ansi_state.nums[0] = -1; + m_ansi_state.nums[1] = -1; + m_state = State::Normal; + } + + void TTY::handle_ansi_csi_color() + { + switch (m_ansi_state.nums[0]) + { + case -1: + case 0: + m_foreground = TerminalColor::BRIGHT_WHITE; + m_background = TerminalColor::BLACK; + break; + + case 30: m_foreground = TerminalColor::BRIGHT_BLACK; break; + case 31: m_foreground = TerminalColor::BRIGHT_RED; break; + case 32: m_foreground = TerminalColor::BRIGHT_GREEN; break; + case 33: m_foreground = TerminalColor::BRIGHT_YELLOW; break; + case 34: m_foreground = TerminalColor::BRIGHT_BLUE; break; + case 35: m_foreground = TerminalColor::BRIGHT_MAGENTA; break; + case 36: m_foreground = TerminalColor::BRIGHT_CYAN; break; + case 37: m_foreground = TerminalColor::BRIGHT_WHITE; break; + + case 40: m_background = TerminalColor::BRIGHT_BLACK; break; + case 41: m_background = TerminalColor::BRIGHT_RED; break; + case 42: m_background = TerminalColor::BRIGHT_GREEN; break; + case 43: m_background = TerminalColor::BRIGHT_YELLOW; break; + case 44: m_background = TerminalColor::BRIGHT_BLUE; break; + case 45: m_background = TerminalColor::BRIGHT_MAGENTA; break; + case 46: m_background = TerminalColor::BRIGHT_CYAN; break; + case 47: m_background = TerminalColor::BRIGHT_WHITE; break; + } + } + + void TTY::handle_ansi_csi(uint8_t ch) + { + uint32_t old_column = m_column; + uint32_t old_row = m_row; + + switch (ch) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + int32_t& val = m_ansi_state.nums[m_ansi_state.index]; + val = (val == -1) ? (ch - '0') : (val * 10 + ch - '0'); + return; + } + case ';': + m_ansi_state.index++; + return; + case 'A': // Cursor Up + if (m_ansi_state.nums[0] == -1) + m_ansi_state.nums[0] = 1; + m_row = BAN::Math::max(m_row - m_ansi_state.nums[0], 0); + return reset_ansi(); + case 'B': // Curson Down + if (m_ansi_state.nums[0] == -1) + m_ansi_state.nums[0] = 1; + m_row = BAN::Math::min(m_row + m_ansi_state.nums[0], m_height - 1); + return reset_ansi(); + case 'C': // Cursor Forward + if (m_ansi_state.nums[0] == -1) + m_ansi_state.nums[0] = 1; + m_column = BAN::Math::min(m_column + m_ansi_state.nums[0], m_width - 1); + return reset_ansi(); + case 'D': // Cursor Back + if (m_ansi_state.nums[0] == -1) + m_ansi_state.nums[0] = 1; + m_column = BAN::Math::max(m_column - m_ansi_state.nums[0], 0); + return reset_ansi(); + case 'E': // Cursor Next Line + if (m_ansi_state.nums[0] == -1) + m_ansi_state.nums[0] = 1; + m_row = BAN::Math::min(m_row + m_ansi_state.nums[0], m_height - 1); + m_column = 0; + return reset_ansi(); + case 'F': // Cursor Previous Line + if (m_ansi_state.nums[0] == -1) + m_ansi_state.nums[0] = 1; + m_row = BAN::Math::max(m_row - m_ansi_state.nums[0], 0); + m_column = 0; + return reset_ansi(); + case 'G': // Cursor Horizontal Absolute + if (m_ansi_state.nums[0] == -1) + m_ansi_state.nums[0] = 1; + m_column = BAN::Math::clamp(m_ansi_state.nums[0] - 1, 0, m_width - 1); + return reset_ansi(); + case 'H': // Cursor Position + if (m_ansi_state.nums[0] == -1) + m_ansi_state.nums[0] = 1; + if (m_ansi_state.nums[1] == -1) + m_ansi_state.nums[1] = 1; + m_row = BAN::Math::clamp(m_ansi_state.nums[0] - 1, 0, m_height - 1); + m_column = BAN::Math::clamp(m_ansi_state.nums[1] - 1, 0, m_width - 1); + return reset_ansi(); + case 'J': // Erase in Display + dprintln("Unsupported ANSI CSI character J"); + return reset_ansi(); + case 'K': // Erase in Line + if (m_ansi_state.nums[0] == -1 || m_ansi_state.nums[0] == 0) + for (uint32_t i = m_column; i < m_width; i++) + putchar_at(' ', i, m_row); + else + dprintln("Unsupported ANSI CSI character K"); + return reset_ansi(); + case 'S': // Scroll Up + dprintln("Unsupported ANSI CSI character S"); + return reset_ansi(); + case 'T': // Scroll Down + dprintln("Unsupported ANSI CSI character T"); + return reset_ansi(); + case 'f': // Horizontal Vertical Position + dprintln("Unsupported ANSI CSI character f"); + return reset_ansi(); + case 'm': + handle_ansi_csi_color(); + return reset_ansi(); + default: + dprintln("Unsupported ANSI CSI character {}", ch); + return reset_ansi(); + } + + if (old_column != m_column || old_row != m_row) + set_cursor_position(m_column, m_row); + } + + void TTY::render_from_buffer(uint32_t x, uint32_t y) + { + ASSERT(x < m_width && y < m_height); + const auto& cell = m_buffer[y * m_width + x]; + m_terminal_driver->putchar_at(cell.codepoint, x, y, cell.foreground, cell.background); + } + + void TTY::putchar_at(uint32_t codepoint, uint32_t x, uint32_t y) + { + ASSERT(x < m_width && y < m_height); + auto& cell = m_buffer[y * m_width + x]; + cell.codepoint = codepoint; + cell.foreground = m_foreground; + cell.background = m_background; + m_terminal_driver->putchar_at(codepoint, x, y, m_foreground, m_background); + } + + void TTY::putchar(uint8_t ch) + { + ASSERT(m_lock.is_locked()); + + 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 + { + dprintln("invalid utf8"); + } + m_state = State::WaitingUTF8; + return; + case State::WaitingAnsiEscape: + if (ch == CSI) + m_state = State::WaitingAnsiCSI; + else + { + dprintln("unsupported byte after ansi escape {2H}", (uint8_t)ch); + reset_ansi(); + } + return; + case State::WaitingAnsiCSI: + handle_ansi_csi(ch); + return; + case State::WaitingUTF8: + if ((ch & 0xC0) != 0x80) + { + dprintln("invalid utf8"); + m_state = State::Normal; + 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(); + } + + switch (codepoint) + { + case BEL: // TODO + break; + case BS: + if (m_column > 0) + m_column--; + break; + case HT: + m_column++; + while (m_column % 8) + m_column++; + break; + case LF: + m_column = 0; + m_row++; + break; + case FF: + m_row++; + break; + case CR: + m_column = 0; + break; + case ESC: + m_state = State::WaitingAnsiEscape; + break;; + default: + putchar_at(codepoint, m_column, m_row); + m_column++; + break; + } + + if (m_column >= m_width) + { + m_column = 0; + m_row++; + } + + 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 = ' ' }; + + // 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--; + } + + set_cursor_position(m_column, m_row); + } + + BAN::ErrorOr TTY::read(size_t, void* buffer, size_t count) + { + m_lock.lock(); + while (!m_output.flush) + { + m_lock.unlock(); + m_output.semaphore.block(); + m_lock.lock(); + } + + size_t to_copy = BAN::Math::min(count, m_output.bytes); + memcpy(buffer, m_output.buffer.data(), to_copy); + + memmove(m_output.buffer.data(), m_output.buffer.data() + to_copy, m_output.bytes - to_copy); + m_output.bytes -= to_copy; + + if (m_output.bytes == 0) + m_output.flush = false; + + m_lock.unlock(); + + return to_copy; + } + + BAN::ErrorOr TTY::write(size_t, const void* buffer, size_t count) + { + LockGuard _(m_lock); + for (size_t i = 0; i < count; i++) + putchar(((uint8_t*)buffer)[i]); + return count; + } + + void TTY::putchar_current(uint8_t ch) + { + ASSERT(s_tty); + LockGuard _(s_tty->m_lock); + s_tty->putchar(ch); + } + + bool TTY::is_initialized() + { + return s_tty != nullptr; + } + +} diff --git a/kernel/kernel/VesaTerminalDriver.cpp b/kernel/kernel/Terminal/VesaTerminalDriver.cpp similarity index 98% rename from kernel/kernel/VesaTerminalDriver.cpp rename to kernel/kernel/Terminal/VesaTerminalDriver.cpp index a599dd5078..532f1b9b89 100644 --- a/kernel/kernel/VesaTerminalDriver.cpp +++ b/kernel/kernel/Terminal/VesaTerminalDriver.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include VesaTerminalDriver* VesaTerminalDriver::create() { diff --git a/kernel/kernel/kernel.cpp b/kernel/kernel/kernel.cpp index f30e03a4d0..0fde0d7f41 100644 --- a/kernel/kernel/kernel.cpp +++ b/kernel/kernel/kernel.cpp @@ -19,8 +19,8 @@ #include #include #include -#include -#include +#include +#include extern "C" const char g_kernel_cmdline[]; @@ -142,8 +142,6 @@ extern "C" void kernel_main() TerminalDriver* terminal_driver = VesaTerminalDriver::create(); ASSERT(terminal_driver); dprintln("VESA initialized"); - TTY* tty1 = new TTY(terminal_driver); - ASSERT(tty1); MUST(ACPI::initialize()); dprintln("ACPI initialized"); @@ -200,19 +198,17 @@ extern "C" void kernel_main() } )))); #else - MUST(scheduler.add_thread(MUST(Thread::create(init2, tty1)))); + MUST(scheduler.add_thread(MUST(Thread::create(init2, terminal_driver)))); #endif scheduler.start(); ASSERT(false); } -static void init2(void* tty1_ptr) +static void init2(void* terminal_driver) { using namespace Kernel; using namespace Kernel::Input; - TTY* tty1 = (TTY*)tty1_ptr; - DeviceManager::initialize(); MUST(VirtualFileSystem::initialize(cmdline.root)); @@ -220,12 +216,32 @@ static void init2(void* tty1_ptr) if (auto res = PS2Controller::initialize(); res.is_error()) dprintln("{}", res.error()); + TTY* tty1 = new TTY((TerminalDriver*)terminal_driver); + ASSERT(tty1); + DeviceManager::get().add_device(tty1); + MUST(Process::create_kernel( - [](void* tty1) + [](void*) { - Shell* shell = new Shell((TTY*)tty1); + int fd = MUST(Process::current()->open("/dev/tty1", 1)); + while (true) + { + char buffer[1024]; + int n_read = MUST(Process::current()->read(fd, buffer, sizeof(buffer))); + MUST(Process::current()->write(fd, buffer, n_read)); + dprintln("{} bytes", n_read); + } + }, nullptr + )); + + return; + + MUST(Process::create_kernel( + [](void*) + { + Shell* shell = new Shell(); ASSERT(shell); shell->run(); - }, tty1 + }, nullptr )); } \ No newline at end of file