kernel+terminal: Move pseudoterminal input processing to kernel-space
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
441e04076a
commit
ffdcc843eb
@ -5,6 +5,7 @@
|
||||
#include "fs/devpts/FileSystem.h"
|
||||
#include "memory/MemoryManager.h"
|
||||
#include "thread/Scheduler.h"
|
||||
#include <luna/CType.h>
|
||||
|
||||
Result<SharedPtr<VFS::Inode>> MasterPTY::create_pair(int index)
|
||||
{
|
||||
@ -57,6 +58,130 @@ Result<void> MasterPTY::handle_background_process_group(bool can_succeed, int si
|
||||
return err(EIO);
|
||||
}
|
||||
|
||||
bool MasterPTY::is_canonical()
|
||||
{
|
||||
return m_settings.c_lflag & ICANON;
|
||||
}
|
||||
|
||||
Result<void> MasterPTY::handle_input(u8 key)
|
||||
{
|
||||
bool is_special_character { false };
|
||||
|
||||
if (is_canonical())
|
||||
{
|
||||
if (key == m_settings.c_cc[VERASE])
|
||||
{
|
||||
auto maybe_char = m_current_line_buffer.try_pop();
|
||||
if (maybe_char.has_value() && maybe_char.value())
|
||||
{
|
||||
if ((m_settings.c_lflag & ECHO) && (m_settings.c_lflag & ECHOE))
|
||||
{
|
||||
u8 backspace = (u8)'\b';
|
||||
m_buffer.append_data(&backspace, 1);
|
||||
if (_iscntrl(maybe_char.value())) m_buffer.append_data(&backspace, 1);
|
||||
if (maybe_char.value() == '\t')
|
||||
{
|
||||
m_buffer.append_data(&backspace, 1);
|
||||
m_buffer.append_data(&backspace, 1);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if ((m_settings.c_lflag & ECHOE)) return {};
|
||||
else
|
||||
is_special_character = true;
|
||||
}
|
||||
|
||||
if (key == m_settings.c_cc[VEOF])
|
||||
{
|
||||
const auto size = m_current_line_buffer.size();
|
||||
auto buffer = Buffer { m_current_line_buffer.release_data(), size };
|
||||
TRY(m_lines.try_append(move(buffer)));
|
||||
|
||||
is_special_character = true;
|
||||
}
|
||||
|
||||
if (m_settings.c_lflag & ISIG)
|
||||
{
|
||||
if (key == m_settings.c_cc[VINTR])
|
||||
{
|
||||
if (!(m_settings.c_lflag & NOFLSH)) m_current_line_buffer.clear();
|
||||
|
||||
if (m_foreground_process_group.has_value())
|
||||
Scheduler::for_each_in_process_group(*m_foreground_process_group, [](Thread* thread) {
|
||||
thread->send_signal(SIGINT);
|
||||
return true;
|
||||
});
|
||||
|
||||
is_special_character = true;
|
||||
}
|
||||
|
||||
if (key == m_settings.c_cc[VQUIT])
|
||||
{
|
||||
if (!(m_settings.c_lflag & NOFLSH)) m_current_line_buffer.clear();
|
||||
|
||||
if (m_foreground_process_group.has_value())
|
||||
Scheduler::for_each_in_process_group(*m_foreground_process_group, [](Thread* thread) {
|
||||
thread->send_signal(SIGQUIT);
|
||||
return true;
|
||||
});
|
||||
|
||||
is_special_character = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_special_character)
|
||||
{
|
||||
if (is_canonical())
|
||||
{
|
||||
const usize max_line_size = key == '\n' ? 4096 : 4095;
|
||||
if (m_current_line_buffer.size() < max_line_size) TRY(m_current_line_buffer.try_append(key));
|
||||
|
||||
if (key == '\n')
|
||||
{
|
||||
const auto size = m_current_line_buffer.size();
|
||||
auto buffer = Buffer { m_current_line_buffer.release_data(), size };
|
||||
TRY(m_lines.try_append(move(buffer)));
|
||||
}
|
||||
}
|
||||
else
|
||||
TRY(m_slave->m_buffer.append_data(&key, 1));
|
||||
}
|
||||
|
||||
if (!(m_settings.c_lflag & ECHO)) return {};
|
||||
|
||||
if (_iscntrl(key))
|
||||
{
|
||||
if (m_settings.c_lflag & ECHOCTL)
|
||||
{
|
||||
bool should_echo = true;
|
||||
if (key == '\n' || key == '\t' || key == '\0' || key == m_settings.c_cc[VEOF]) should_echo = false;
|
||||
|
||||
if (should_echo)
|
||||
{
|
||||
u8 caret_notation[2] = {
|
||||
'^',
|
||||
'\0',
|
||||
};
|
||||
if (key == 0x7f) caret_notation[1] = '?';
|
||||
else
|
||||
caret_notation[1] = key + 0x40;
|
||||
|
||||
m_buffer.append_data(caret_notation, 2);
|
||||
}
|
||||
else
|
||||
m_buffer.append_data(&key, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
m_buffer.append_data(&key, 1);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<usize> MasterPTY::read(u8* buf, usize, usize length) const
|
||||
{
|
||||
length = m_buffer.dequeue_data(buf, length);
|
||||
@ -66,7 +191,7 @@ Result<usize> MasterPTY::read(u8* buf, usize, usize length) const
|
||||
|
||||
Result<usize> MasterPTY::write(const u8* buf, usize, usize length)
|
||||
{
|
||||
TRY(m_slave->m_buffer.append_data(buf, length));
|
||||
for (usize i = 0; i < length; i++) { TRY(handle_input(buf[i])); }
|
||||
|
||||
return length;
|
||||
}
|
||||
|
@ -67,7 +67,15 @@ class MasterPTY : public VFS::DeviceInode
|
||||
mutable Option<pid_t> m_foreground_process_group;
|
||||
struct winsize m_window;
|
||||
|
||||
typedef Vector<u8> Line;
|
||||
|
||||
Vector<Buffer> m_lines;
|
||||
Line m_current_line_buffer;
|
||||
|
||||
Result<void> handle_background_process_group(bool can_succeed, int signo) const;
|
||||
Result<void> handle_input(u8 key);
|
||||
|
||||
bool is_canonical();
|
||||
|
||||
int m_index;
|
||||
|
||||
|
@ -10,7 +10,15 @@ Result<usize> SlavePTY::read(u8* buf, usize, usize length) const
|
||||
|
||||
TRY(m_master->handle_background_process_group(false, SIGTTIN));
|
||||
|
||||
length = m_buffer.dequeue_data(buf, length);
|
||||
if (!m_master->is_canonical()) length = m_buffer.dequeue_data(buf, length);
|
||||
else
|
||||
{
|
||||
if (m_master->m_lines.size() == 0) { return 0; }
|
||||
|
||||
auto& line = m_master->m_lines[0];
|
||||
length = line.dequeue_data(buf, length);
|
||||
if (line.size() == 0) m_master->m_lines.remove_at(0);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
@ -26,6 +34,13 @@ Result<usize> SlavePTY::write(const u8* buf, usize, usize length)
|
||||
return length;
|
||||
}
|
||||
|
||||
bool SlavePTY::will_block_if_read() const
|
||||
{
|
||||
if (!m_master->is_canonical()) return m_buffer.is_empty();
|
||||
else
|
||||
return !m_master->m_lines.size();
|
||||
}
|
||||
|
||||
Result<u64> SlavePTY::ioctl(int request, void* arg)
|
||||
{
|
||||
auto* current = Scheduler::current();
|
||||
|
@ -42,10 +42,7 @@ class SlavePTY : public VFS::DeviceInode
|
||||
return err(EINVAL);
|
||||
}
|
||||
|
||||
bool will_block_if_read() const override
|
||||
{
|
||||
return m_buffer.is_empty();
|
||||
}
|
||||
bool will_block_if_read() const override;
|
||||
|
||||
void did_link() override
|
||||
{
|
||||
|
@ -20,6 +20,16 @@ class Buffer
|
||||
Buffer();
|
||||
~Buffer();
|
||||
|
||||
/**
|
||||
* @brief Create a new Buffer object, adopting existing data.
|
||||
*
|
||||
* The data provided will be owned by the buffer.
|
||||
*
|
||||
* @param data The data to use.
|
||||
* @param size The amount of bytes in the data.
|
||||
*/
|
||||
Buffer(u8* data, usize size);
|
||||
|
||||
Buffer(Buffer&& other);
|
||||
Buffer(const Buffer& other) = delete; // For now.
|
||||
|
||||
@ -161,8 +171,6 @@ class Buffer
|
||||
}
|
||||
|
||||
private:
|
||||
Buffer(u8* data, usize size);
|
||||
|
||||
u8* m_data { nullptr };
|
||||
usize m_size { 0 };
|
||||
};
|
||||
|
@ -95,115 +95,7 @@ Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventReque
|
||||
// invalid zero byte into the terminal input (this would also happen on Shift or Ctrl keypresses).
|
||||
if (request.letter == '\0') return ui::EventResult::DidNotHandle;
|
||||
|
||||
return handle_input(request.letter);
|
||||
}
|
||||
|
||||
Result<ui::EventResult> TerminalWidget::handle_input(char key)
|
||||
{
|
||||
query_termios();
|
||||
|
||||
bool is_special_character { false };
|
||||
|
||||
if (is_canonical())
|
||||
{
|
||||
if (key == 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');
|
||||
}
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
return ui::EventResult::DidNotHandle;
|
||||
}
|
||||
|
||||
if ((m_settings.c_lflag & ECHOE)) return ui::EventResult::DidNotHandle;
|
||||
else
|
||||
is_special_character = true;
|
||||
}
|
||||
|
||||
if (key == 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 (key == m_settings.c_cc[VINTR])
|
||||
{
|
||||
if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear();
|
||||
|
||||
pid_t group = tcgetpgrp(m_pty);
|
||||
TRY(os::Process::kill(-group, SIGINT));
|
||||
|
||||
is_special_character = true;
|
||||
}
|
||||
|
||||
if (key == m_settings.c_cc[VQUIT])
|
||||
{
|
||||
if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear();
|
||||
|
||||
pid_t group = tcgetpgrp(m_pty);
|
||||
TRY(os::Process::kill(-group, SIGQUIT));
|
||||
|
||||
is_special_character = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_special_character)
|
||||
{
|
||||
if (is_canonical())
|
||||
{
|
||||
m_line_buffer.try_append((u8)key);
|
||||
|
||||
if (key == '\n')
|
||||
{
|
||||
write(m_pty, m_line_buffer.data(), m_line_buffer.size());
|
||||
m_line_buffer.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
write(m_pty, &key, 1);
|
||||
}
|
||||
|
||||
if (!(m_settings.c_lflag & ECHO)) return ui::EventResult::DidNotHandle;
|
||||
|
||||
if (_iscntrl(key))
|
||||
{
|
||||
if (m_settings.c_lflag & ECHOCTL)
|
||||
{
|
||||
bool should_echo = true;
|
||||
if (key == '\n' || key == '\t' || key == '\0' || key == m_settings.c_cc[VEOF]) should_echo = false;
|
||||
|
||||
if (should_echo)
|
||||
{
|
||||
char caret_notation[3] = { '^', '\0', '\0' };
|
||||
if (key == 0x7f) caret_notation[1] = '?';
|
||||
else
|
||||
caret_notation[1] = key + 0x40;
|
||||
|
||||
for (int i = 0; i < 2; i++) { putchar(caret_notation[i]); }
|
||||
}
|
||||
else
|
||||
putchar(key);
|
||||
}
|
||||
}
|
||||
else
|
||||
putchar(key);
|
||||
|
||||
write(m_pty, &request.letter, 1);
|
||||
return ui::EventResult::DidHandle;
|
||||
}
|
||||
|
||||
@ -223,8 +115,6 @@ Result<bool> TerminalWidget::process()
|
||||
return err(errno);
|
||||
}
|
||||
|
||||
query_termios();
|
||||
|
||||
bool should_update_cursor = tick_cursor();
|
||||
|
||||
ssize_t drawn = 0;
|
||||
@ -263,16 +153,6 @@ bool TerminalWidget::tick_cursor()
|
||||
return false;
|
||||
}
|
||||
|
||||
void TerminalWidget::query_termios()
|
||||
{
|
||||
tcgetattr(m_pty, &m_settings);
|
||||
}
|
||||
|
||||
bool TerminalWidget::is_canonical()
|
||||
{
|
||||
return m_settings.c_lflag & ICANON;
|
||||
}
|
||||
|
||||
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() });
|
||||
|
@ -47,16 +47,11 @@ class TerminalWidget : public ui::Widget
|
||||
ui::Color m_foreground_color { ui::WHITE };
|
||||
ui::Color m_background_color { ui::BLACK };
|
||||
|
||||
void query_termios();
|
||||
|
||||
bool tick_cursor();
|
||||
|
||||
Utf8StateDecoder m_decoder;
|
||||
Option<EscapeSequenceParser> m_escape_parser;
|
||||
|
||||
Result<ui::EventResult> handle_input(char key);
|
||||
|
||||
bool is_canonical();
|
||||
void draw_glyph(wchar_t c, int x, int y);
|
||||
void erase_current_line();
|
||||
void scroll();
|
||||
|
Loading…
Reference in New Issue
Block a user