terminal: Use pseudoterminals and add keyboard support
This commit is contained in:
parent
75ea81bfbc
commit
9b1e19ef72
@ -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()
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user