diff --git a/terminal/TerminalWidget.cpp b/terminal/TerminalWidget.cpp index 6587fa0a..fcbe3687 100644 --- a/terminal/TerminalWidget.cpp +++ b/terminal/TerminalWidget.cpp @@ -1,9 +1,11 @@ #include "TerminalWidget.h" #include #include +#include #include #include #include +#include #include #include @@ -42,44 +44,164 @@ Result TerminalWidget::init(char* const* args) signal(SIGCHLD, sigchld_handler); - int infds[2]; - int outfds[2]; + int fd = posix_openpt(O_RDWR | O_CLOEXEC); + if (fd < 0) return err(errno); - int result = pipe(infds); - if (result < 0) return err(errno); - - result = pipe(outfds); - if (result < 0) return err(errno); + grantpt(fd); + unlockpt(fd); pid_t child = TRY(os::Process::fork()); if (child == 0) { - dup2(infds[0], STDIN_FILENO); - dup2(outfds[1], STDOUT_FILENO); - dup2(outfds[1], STDERR_FILENO); + int ptsfd = open(ptsname(fd), O_RDWR); - close(infds[0]); - close(infds[1]); - close(outfds[0]); - close(outfds[1]); + dup2(ptsfd, STDIN_FILENO); + dup2(ptsfd, STDOUT_FILENO); + dup2(ptsfd, STDERR_FILENO); + + setpgid(0, 0); + tcsetpgrp(ptsfd, getpid()); + + close(ptsfd); execv(args[0], args); _exit(127); } - close(infds[0]); - close(outfds[1]); + m_pty = fd; - m_write_fd = infds[1]; - m_read_fd = outfds[0]; - - fcntl(m_read_fd, F_SETFL, O_NONBLOCK); + fcntl(fd, F_SETFL, O_NONBLOCK); m_child_pid = child; return {}; } +Result TerminalWidget::handle_key_event(const ui::KeyEventRequest& request) +{ + if (!request.pressed) return ui::EventResult::DidNotHandle; + + query_termios(); + + bool is_special_character { false }; + + if (/*is_canonical()*/ true) + { + if (request.letter == m_settings.c_cc[VERASE]) + { + auto maybe_char = m_line_buffer.try_pop(); + if (maybe_char.has_value() && maybe_char.value()) + { + if ((m_settings.c_lflag & ECHO) && (m_settings.c_lflag & ECHOE)) + { + put_code_point(L'\b'); + if (_iscntrl(maybe_char.value())) put_code_point(L'\b'); + if (maybe_char.value() == '\t') + { + put_code_point(L'\b'); + put_code_point(L'\b'); + } + } + ui::App::the().main_window()->draw(); + return ui::EventResult::DidHandle; + } + + if ((m_settings.c_lflag & ECHOE)) return ui::EventResult::DidHandle; + else + is_special_character = true; + } + + if (request.letter == m_settings.c_cc[VEOF]) + { + write(m_pty, m_line_buffer.data(), m_line_buffer.size()); + m_line_buffer.clear(); + + // FIXME: tell the kernel that process may read without blocking. + is_special_character = true; + } + + if (m_settings.c_lflag & ISIG) + { + if (request.letter == m_settings.c_cc[VINTR]) + { + if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear(); + + // FIXME: Send SIGINT. + /*if (m_foreground_process_group.has_value()) + { + Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) { + thread->send_signal(SIGINT); + return true; + }); + }*/ + is_special_character = true; + } + + if (request.letter == m_settings.c_cc[VQUIT]) + { + if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear(); + + // FIXME: Send SIGINT. + /*if (m_foreground_process_group.has_value()) + { + Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) { + thread->send_signal(SIGQUIT); + return true; + }); + }*/ + is_special_character = true; + } + } + } + + if (!is_special_character) + { + if (/*is_canonical()*/ true) + { + m_line_buffer.try_append((u8)request.letter); + + if (request.letter == '\n') + { + write(m_pty, m_line_buffer.data(), m_line_buffer.size()); + m_line_buffer.clear(); + } + } + else + write(m_pty, &request.letter, 1); + } + + if (!(m_settings.c_lflag & ECHO)) return ui::EventResult::DidHandle; + + if (_iscntrl(request.letter)) + { + if (m_settings.c_lflag & ECHOCTL) + { + bool should_echo = true; + if (request.letter == '\n' || request.letter == '\t' || request.letter == '\0' || + request.letter == m_settings.c_cc[VEOF]) + should_echo = false; + + if (should_echo) + { + char caret_notation[3] = { '^', '\0', '\0' }; + if (request.letter == 0x7f) caret_notation[1] = '?'; + else + caret_notation[1] = request.letter + 0x40; + + for (int i = 0; i < 2; i++) { putchar(caret_notation[i]); } + } + else + putchar(request.letter); + } + } + else + putchar(request.letter); + + ui::App::the().main_window()->draw(); + + return ui::EventResult::DidHandle; +} + Result TerminalWidget::draw(ui::Canvas& canvas) { canvas.fill((u32*)m_terminal_canvas.ptr, m_terminal_canvas.stride); @@ -89,14 +211,14 @@ Result TerminalWidget::draw(ui::Canvas& canvas) Result TerminalWidget::process() { char buffer[BUFSIZ]; - ssize_t nread = read(m_read_fd, buffer, BUFSIZ); + ssize_t nread = read(m_pty, buffer, BUFSIZ); if (nread < 0) { if (errno == EAGAIN) return {}; return err(errno); } - os::eprintln("terminal: read %zd characters, processing...", nread); + query_termios(); for (ssize_t i = 0; i < nread; i++) TRY(putchar(buffer[i])); @@ -105,6 +227,11 @@ Result TerminalWidget::process() return {}; } +void TerminalWidget::query_termios() +{ + tcgetattr(m_pty, &m_settings); +} + void TerminalWidget::draw_glyph(wchar_t c, int x, int y) { auto subcanvas = m_terminal_canvas.subcanvas({ x, y, m_font->width(), m_font->height() }); @@ -114,7 +241,7 @@ void TerminalWidget::draw_glyph(wchar_t c, int x, int y) void TerminalWidget::erase_current_line() { - m_terminal_canvas.subcanvas({ 0, m_y_position, m_rect.width, m_font->height() }); + m_terminal_canvas.subcanvas({ 0, m_y_position, m_rect.width, m_font->height() }).fill(ui::BLACK); } void TerminalWidget::scroll() @@ -143,7 +270,7 @@ void TerminalWidget::next_char() void TerminalWidget::prev_char() { - m_y_position -= m_font->width(); + m_x_position -= m_font->width(); } void TerminalWidget::erase_current_char() diff --git a/terminal/TerminalWidget.h b/terminal/TerminalWidget.h index aaf26c5b..0c18bd74 100644 --- a/terminal/TerminalWidget.h +++ b/terminal/TerminalWidget.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -11,6 +12,8 @@ class TerminalWidget : public ui::Widget public: Result init(char* const* args); + Result handle_key_event(const ui::KeyEventRequest& request) override; + Result draw(ui::Canvas& canvas) override; Result process(); @@ -20,10 +23,11 @@ class TerminalWidget : public ui::Widget private: ui::Canvas m_terminal_canvas; Vector m_line_buffer; - int m_write_fd; - int m_read_fd; + int m_pty; pid_t m_child_pid; + struct termios m_settings; + SharedPtr m_font; SharedPtr m_bold_font; @@ -41,6 +45,8 @@ class TerminalWidget : public ui::Widget ui::Color m_foreground_color { ui::WHITE }; ui::Color m_background_color { ui::BLACK }; + void query_termios(); + Utf8StateDecoder m_decoder; Option m_escape_parser; diff --git a/terminal/main.cpp b/terminal/main.cpp index 20986d35..96e1d698 100644 --- a/terminal/main.cpp +++ b/terminal/main.cpp @@ -1,6 +1,7 @@ #include "TerminalWidget.h" #include #include +#include Result luna_main(int argc, char** argv) { @@ -8,7 +9,7 @@ Result luna_main(int argc, char** argv) TRY(app.init(argc, argv)); app.set_nonblocking(); - auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 500, 300 })); + auto* window = TRY(ui::Window::create(ui::Rect { 150, 150, 640, 400 })); app.set_main_window(window); window->set_background(ui::BLACK); window->set_title("Terminal"); @@ -16,12 +17,16 @@ Result luna_main(int argc, char** argv) TerminalWidget terminal; window->set_main_widget(terminal); - char* args[] = { "/bin/sh", "-i", nullptr }; + char* args[] = { "/bin/sh", nullptr }; TRY(terminal.init(args)); window->draw(); - while (app.process_events()) TRY(terminal.process()); + while (app.process_events()) + { + TRY(terminal.process()); + usleep(10000); + } terminal.quit();