terminal: Avoid doing too many redraws + support non-canonical mode
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
apio 2023-09-20 22:01:26 +02:00
parent 9636b5d8da
commit b4a9ea3857
Signed by: apio
GPG Key ID: B8A7D06E42258954
2 changed files with 58 additions and 33 deletions

View File

@ -90,17 +90,23 @@ Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventReque
{
// Avoid handling "key released" events
if (!request.pressed) return ui::EventResult::DidNotHandle;
// Non-printable key or key that has no special character (unlike Tab or Enter). We exit early to avoid inserting an
// 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()*/ true)
if (is_canonical())
{
if (request.letter == m_settings.c_cc[VERASE])
if (key == m_settings.c_cc[VERASE])
{
auto maybe_char = m_line_buffer.try_pop();
if (maybe_char.has_value() && maybe_char.value())
@ -114,17 +120,17 @@ Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventReque
put_code_point(L'\b');
put_code_point(L'\b');
}
}
ui::App::the().main_window()->draw();
return ui::EventResult::DidHandle;
}
return ui::EventResult::DidNotHandle;
}
if ((m_settings.c_lflag & ECHOE)) return ui::EventResult::DidHandle;
if ((m_settings.c_lflag & ECHOE)) return ui::EventResult::DidNotHandle;
else
is_special_character = true;
}
if (request.letter == m_settings.c_cc[VEOF])
if (key == m_settings.c_cc[VEOF])
{
write(m_pty, m_line_buffer.data(), m_line_buffer.size());
m_line_buffer.clear();
@ -135,7 +141,7 @@ Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventReque
if (m_settings.c_lflag & ISIG)
{
if (request.letter == m_settings.c_cc[VINTR])
if (key == m_settings.c_cc[VINTR])
{
if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear();
@ -145,7 +151,7 @@ Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventReque
is_special_character = true;
}
if (request.letter == m_settings.c_cc[VQUIT])
if (key == m_settings.c_cc[VQUIT])
{
if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear();
@ -159,48 +165,44 @@ Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventReque
if (!is_special_character)
{
if (/*is_canonical()*/ true)
if (is_canonical())
{
m_line_buffer.try_append((u8)request.letter);
m_line_buffer.try_append((u8)key);
if (request.letter == '\n')
if (key == '\n')
{
write(m_pty, m_line_buffer.data(), m_line_buffer.size());
m_line_buffer.clear();
}
}
else
write(m_pty, &request.letter, 1);
write(m_pty, &key, 1);
}
if (!(m_settings.c_lflag & ECHO)) return ui::EventResult::DidHandle;
if (!(m_settings.c_lflag & ECHO)) return ui::EventResult::DidNotHandle;
if (_iscntrl(request.letter))
if (_iscntrl(key))
{
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 (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 (request.letter == 0x7f) caret_notation[1] = '?';
if (key == 0x7f) caret_notation[1] = '?';
else
caret_notation[1] = request.letter + 0x40;
caret_notation[1] = key + 0x40;
for (int i = 0; i < 2; i++) { putchar(caret_notation[i]); }
}
else
putchar(request.letter);
putchar(key);
}
}
else
putchar(request.letter);
ui::App::the().main_window()->draw();
putchar(key);
return ui::EventResult::DidHandle;
}
@ -225,9 +227,15 @@ Result<bool> TerminalWidget::process()
bool should_update_cursor = tick_cursor();
for (ssize_t i = 0; i < nread; i++) TRY(putchar(buffer[i]));
ssize_t drawn = 0;
if (should_update_cursor || nread > 0) ui::App::the().main_window()->draw();
for (ssize_t i = 0; i < nread; i++)
{
bool did_draw = TRY(putchar(buffer[i]));
if (did_draw) drawn++;
}
if (should_update_cursor || drawn > 0) ui::App::the().main_window()->draw();
return nread == 0;
}
@ -260,6 +268,11 @@ 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() });
@ -516,26 +529,28 @@ bool TerminalWidget::handle_escape_sequence(wchar_t c)
return true;
}
Result<void> TerminalWidget::putchar(char c)
Result<bool> TerminalWidget::putchar(char c)
{
auto guard = make_scope_guard([this] { m_decoder.reset(); });
bool is_ready = TRY(m_decoder.feed(c));
if (is_ready) put_code_point(TRY(m_decoder.extract()));
bool result = false;
if (is_ready) result = put_code_point(TRY(m_decoder.extract()));
guard.deactivate();
return {};
return result;
}
void TerminalWidget::put_code_point(wchar_t c)
bool TerminalWidget::put_code_point(wchar_t c)
{
if (c > (wchar_t)255) c = (wchar_t)256;
if (m_escape_parser.has_value())
{
if (handle_escape_sequence(c)) return;
if (handle_escape_sequence(c)) return false;
}
// Erase the current cursor.
@ -543,6 +558,8 @@ void TerminalWidget::put_code_point(wchar_t c)
bool should_draw_cursor = m_cursor_enabled;
bool did_draw = false;
switch (c)
{
case L'\n': {
@ -552,6 +569,7 @@ void TerminalWidget::put_code_point(wchar_t c)
}
case L'\t': {
for (int i = 0; i < 4; i++) { put_code_point(L' '); }
did_draw = true;
break;
}
case L'\r': m_x_position = 0; break;
@ -560,6 +578,7 @@ void TerminalWidget::put_code_point(wchar_t c)
{
prev_char();
erase_current_char();
did_draw = true;
}
break;
case L'\x1b':
@ -568,9 +587,10 @@ void TerminalWidget::put_code_point(wchar_t c)
case L'\x9d':
m_escape_parser = EscapeSequenceParser { (u8)c };
should_draw_cursor = false;
did_draw = true;
break;
default: {
if (iscntrl(c)) return;
if (iscntrl(c)) return false;
draw_glyph(c, m_x_position, m_y_position);
next_char();
if (at_end_of_screen())
@ -588,6 +608,8 @@ void TerminalWidget::put_code_point(wchar_t c)
m_cursor_activated = true;
draw_cursor();
}
return did_draw;
}
void TerminalWidget::quit()

View File

@ -54,6 +54,9 @@ class TerminalWidget : public ui::Widget
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();
@ -65,6 +68,6 @@ class TerminalWidget : public ui::Widget
void draw_cursor();
bool at_end_of_screen();
bool handle_escape_sequence(wchar_t c);
Result<void> putchar(char c);
void put_code_point(wchar_t c);
Result<bool> putchar(char c);
bool put_code_point(wchar_t c);
};