terminal: Use pseudoterminals and add keyboard support

This commit is contained in:
apio 2023-09-16 11:48:11 +02:00
parent 75ea81bfbc
commit 9b1e19ef72
Signed by: apio
GPG Key ID: B8A7D06E42258954
3 changed files with 167 additions and 29 deletions

View File

@ -1,9 +1,11 @@
#include "TerminalWidget.h"
#include <ctype.h>
#include <errno.h>
#include <luna/CType.h>
#include <os/File.h>
#include <os/Process.h>
#include <signal.h>
#include <stdlib.h>
#include <ui/App.h>
#include <unistd.h>
@ -42,44 +44,164 @@ Result<void> 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<ui::EventResult> 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<void> TerminalWidget::draw(ui::Canvas& canvas)
{
canvas.fill((u32*)m_terminal_canvas.ptr, m_terminal_canvas.stride);
@ -89,14 +211,14 @@ Result<void> TerminalWidget::draw(ui::Canvas& canvas)
Result<void> 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<void> 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()

View File

@ -3,6 +3,7 @@
#include <luna/Utf8.h>
#include <luna/Vector.h>
#include <stdio.h>
#include <termios.h>
#include <ui/Font.h>
#include <ui/Widget.h>
@ -11,6 +12,8 @@ class TerminalWidget : public ui::Widget
public:
Result<void> init(char* const* args);
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(ui::Canvas& canvas) override;
Result<void> process();
@ -20,10 +23,11 @@ class TerminalWidget : public ui::Widget
private:
ui::Canvas m_terminal_canvas;
Vector<u8> m_line_buffer;
int m_write_fd;
int m_read_fd;
int m_pty;
pid_t m_child_pid;
struct termios m_settings;
SharedPtr<ui::Font> m_font;
SharedPtr<ui::Font> 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<EscapeSequenceParser> m_escape_parser;

View File

@ -1,6 +1,7 @@
#include "TerminalWidget.h"
#include <os/ArgumentParser.h>
#include <ui/App.h>
#include <unistd.h>
Result<int> luna_main(int argc, char** argv)
{
@ -8,7 +9,7 @@ Result<int> 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<int> 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();