From b4a9ea3857801a8182689632ea191c81fb0be53a Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 20 Sep 2023 22:01:26 +0200 Subject: [PATCH] terminal: Avoid doing too many redraws + support non-canonical mode --- terminal/TerminalWidget.cpp | 84 +++++++++++++++++++++++-------------- terminal/TerminalWidget.h | 7 +++- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/terminal/TerminalWidget.cpp b/terminal/TerminalWidget.cpp index a55274f9..52bc152a 100644 --- a/terminal/TerminalWidget.cpp +++ b/terminal/TerminalWidget.cpp @@ -90,17 +90,23 @@ Result 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 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 TerminalWidget::handle_key_event(const ui::KeyEventReque put_code_point(L'\b'); put_code_point(L'\b'); } + return ui::EventResult::DidHandle; } - 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 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 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 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 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 TerminalWidget::putchar(char c) +Result 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() diff --git a/terminal/TerminalWidget.h b/terminal/TerminalWidget.h index 694761d8..dcf18dad 100644 --- a/terminal/TerminalWidget.h +++ b/terminal/TerminalWidget.h @@ -54,6 +54,9 @@ class TerminalWidget : public ui::Widget Utf8StateDecoder m_decoder; Option m_escape_parser; + Result 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 putchar(char c); - void put_code_point(wchar_t c); + Result putchar(char c); + bool put_code_point(wchar_t c); };