From d02d46d27982a8e351736067ab9787f87052b989 Mon Sep 17 00:00:00 2001 From: HampusM Date: Wed, 29 Jun 2022 20:17:23 +0200 Subject: refactor: add termios abstraction --- src/CMakeLists.txt | 3 +- src/engine/graphics/scene.cpp | 35 +++++------- src/engine/graphics/scene.hpp | 4 +- src/engine/io/terminal.cpp | 54 ++++++++++++++++++ src/engine/io/terminal.hpp | 130 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 201 insertions(+), 25 deletions(-) create mode 100644 src/engine/io/terminal.cpp create mode 100644 src/engine/io/terminal.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a29cc35..2a98dd7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,8 @@ file(GLOB SOURCES engine/graphics/string_matrix.cpp engine/graphics/component_renderer.cpp engine/user/input.cpp - engine/user/cursor.cpp) + engine/user/cursor.cpp + engine/io/terminal.cpp) add_executable(${PROJECT_NAME} ${SOURCES}) diff --git a/src/engine/graphics/scene.cpp b/src/engine/graphics/scene.cpp index 7c23c7d..8ec8ddf 100644 --- a/src/engine/graphics/scene.cpp +++ b/src/engine/graphics/scene.cpp @@ -1,13 +1,13 @@ #include "scene.hpp" +#include #include +#include #include -#include #include -#include -#include #include "engine/escape.hpp" +#include "engine/io/terminal.hpp" class IComponent; @@ -19,7 +19,7 @@ Scene::Scene(const IMatrixFactory &matrix_factory) noexcept void Scene::enter() noexcept { - if (_is_shown || _original_termios != nullptr) + if (_is_shown) { return; } @@ -28,30 +28,22 @@ void Scene::enter() noexcept fmt::print(ENABLE_ALT_BUFFER, fmt::arg("esc", ESC)); std::cout.flush(); - // Create a backup of the current terminal state - _original_termios = std::make_shared(); - tcgetattr(STDIN_FILENO, _original_termios.get()); + _original_terminal_state = get_terminal_state(STDIN_FILENO); - auto new_termios = termios(*_original_termios); + auto new_terminal_state = TerminalState(_original_terminal_state); - // Set the local mode flags of the new termios structure - // - // The following flags are disabled: - // ECHO - Echoing input characters - // ICANON - Canonical mode (line by line input) - // ISIG - Generate the corresponding signals for the characters - // INTR, QUIT, SUSP and DSUSP - new_termios.c_lflag &= static_cast(~(ECHO | ICANON | ISIG)); + new_terminal_state.set_local_mode_flag(TerminalLocalModeFlag::echo, false); + new_terminal_state.set_local_mode_flag(TerminalLocalModeFlag::icanon, false); + new_terminal_state.set_local_mode_flag(TerminalLocalModeFlag::isig, false); - // Set a new terminal state - tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios); + set_terminal_state(STDIN_FILENO, new_terminal_state); _is_shown = true; } void Scene::leave() noexcept { - if (!_is_shown || _original_termios == nullptr) + if (!_is_shown) { return; } @@ -60,10 +52,9 @@ void Scene::leave() noexcept fmt::print(DISABLE_ALT_BUFFER, fmt::arg("esc", ESC)); std::cout.flush(); - // Restore the original terminal state - tcsetattr(STDIN_FILENO, TCSAFLUSH, _original_termios.get()); + set_terminal_state(STDIN_FILENO, _original_terminal_state); - _original_termios = nullptr; + _original_terminal_state = {}; _is_shown = false; } diff --git a/src/engine/graphics/scene.hpp b/src/engine/graphics/scene.hpp index 5b52027..da3d990 100644 --- a/src/engine/graphics/scene.hpp +++ b/src/engine/graphics/scene.hpp @@ -3,13 +3,13 @@ #include #include #include -#include #include #include #include #include "engine/data/bounds.hpp" #include "engine/data/vector2.hpp" +#include "engine/io/terminal.hpp" #include "interfaces/component.hpp" #include "interfaces/matrix.hpp" #include "interfaces/scene.hpp" @@ -47,7 +47,7 @@ private: std::shared_ptr> _matrix; bool _is_shown; - std::shared_ptr _original_termios = nullptr; + TerminalState _original_terminal_state{}; std::vector, Vector2>> _components; }; diff --git a/src/engine/io/terminal.cpp b/src/engine/io/terminal.cpp new file mode 100644 index 0000000..e6d7d3a --- /dev/null +++ b/src/engine/io/terminal.cpp @@ -0,0 +1,54 @@ +#include "terminal.hpp" + +TerminalState::TerminalState(termios term_attrs) noexcept : _term_attrs(term_attrs) {} + +void TerminalState::set_input_mode_flag(TerminalInputModeFlag flag, bool is_on) noexcept +{ + auto flag_num = static_cast(flag); + + _term_attrs.c_iflag &= is_on ? flag_num : ~flag_num; +} + +void TerminalState::set_output_mode_flag(TerminalOutputModeFlag flag, bool is_on) noexcept +{ + auto flag_num = static_cast(flag); + + _term_attrs.c_oflag &= is_on ? flag_num : ~flag_num; +} + +void TerminalState::set_control_mode_flag( + TerminalControlModeFlag flag, + bool is_on) noexcept +{ + auto flag_num = static_cast(flag); + + _term_attrs.c_cflag &= is_on ? flag_num : ~flag_num; +} + +void TerminalState::set_local_mode_flag(TerminalLocalModeFlag flag, bool is_on) noexcept +{ + auto flag_num = static_cast(flag); + + _term_attrs.c_lflag &= is_on ? flag_num : ~flag_num; +} + +auto TerminalState::get_attributes() noexcept -> termios +{ + return _term_attrs; +} + +auto get_terminal_state(int term_fd) noexcept -> TerminalState +{ + termios term_attrs{}; + + tcgetattr(term_fd, &term_attrs); + + return TerminalState(term_attrs); +} + +void set_terminal_state(int term_fd, TerminalState state) noexcept +{ + auto state_attrs = state.get_attributes(); + + tcsetattr(term_fd, TCSAFLUSH, &state_attrs); +} diff --git a/src/engine/io/terminal.hpp b/src/engine/io/terminal.hpp new file mode 100644 index 0000000..27455bb --- /dev/null +++ b/src/engine/io/terminal.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include + +enum class TerminalControlModeFlag +{ + csize = 0000060, + cs5 = 0000000, + cs6 = 0000020, + cs7 = 0000040, + cs8 = 0000060, + cstopb = 0000100, + cread = 0000200, + parenb = 0000400, + parodd = 0001000, + hupcl = 0002000, + clocal = 0004000, +}; + +enum class TerminalInputModeFlag +{ + ignbrk = 0000001, // Ignore break condition + brkint = 0000002, // Signal interrupt on break + ignpar = 0000004, // Ignore characters with parity errors + parmrk = 0000010, // Mark parity and framing errors + inpck = 0000020, // Enable input parity check + istrip = 0000040, // Strip = 8,th bit off characters + inlcr = 0000100, // Map NL to CR on input + igncr = 0000200, // Ignore CR + icrnl = 0000400, // Map CR to NL on input + iuclc = 0001000, // Map uppercase characters to lowercase on input (not in POSIX) + ixon = 0002000, // Enable start/stop output control + ixany = 0004000, // Enable any character to restart output + ixoff = 0010000, // Enable start/stop input control + imaxbel = 0020000, // Ring bell when input queue is full (not in POSIX) + iutf8 = 0040000, // Input is UTF8 (not in POSIX) +}; + +enum class TerminalOutputModeFlag +{ + opost = 0000001, // Post-process output + olcuc = 0000002, // Map lowercase characters to uppercase on output (not in POSIX) + onlcr = 0000004, // Map NL to CR-NL on output + ocrnl = 0000010, // Map CR to NL on output + onocr = 0000020, // No CR output at column = 0 + onlret = 0000040, // NL performs CR function + ofill = 0000100, // Use fill characters for delay + ofdel = 0000200, // Fill is DEL + nldly = 0000400, // Select newline delays: + nl0 = 0000000, // Newline type = 0 + nl1 = 0000400, // Newline type = 1 + crdly = 0003000, // Select carriage-return delays: + cr0 = 0000000, // Carriage-return delay type = 0 + cr1 = 0001000, // Carriage-return delay type = 1 + cr2 = 0002000, // Carriage-return delay type = 2 + cr3 = 0003000, // Carriage-return delay type = 3 + tabdly = 0014000, // Select horizontal-tab delays: + tab0 = 0000000, // Horizontal-tab delay type = 0 + tab1 = 0004000, // Horizontal-tab delay type = 1 + tab2 = 0010000, // Horizontal-tab delay type = 2 + tab3 = 0014000, // Expand tabs to spaces + bsdly = 0020000, // Select backspace delays: + bs0 = 0000000, // Backspace-delay type = 0 + bs1 = 0020000, // Backspace-delay type = 1 + ffdly = 0100000, // Select form-feed delays: + ff0 = 0000000, // Form-feed delay type = 0 + ff1 = 0100000, // Form-feed delay type = 1 + vtdly = 0040000, // Select vertical-tab delays: + vt0 = 0000000, // Vertical-tab delay type = 0 + vt1 = 0040000, // Vertical-tab delay type = 1 + xtabs = 0014000, +}; + +enum class TerminalLocalModeFlag +{ + isig = 0000001, // When any of the characters INTR, QUIT, SUSP, or DSUSP are received, + // generate the corresponding signal + icanon = 0000002, // Canonical input + xcase = 0000004, + echo = 0000010, // Echo input characters + echoe = 0000020, // Echo erase character as error-correcting backspace + echok = 0000040, // Echo KILL + echonl = 0000100, // Echo NL + noflsh = 0000200, // Disable flush after interrupt or quit + tostop = 0000400, // Send SIGTTOU for background output + echoctl = 0001000, /* If ECHO is also set, terminal special characters + other than TAB, NL, START, and STOP are echoed as + ^X, where X is the character with ASCII code = 0,x40 + greater than the special character + (not in POSIX) */ + echoprt = 0002000, /* If ICANON and ECHO are also set, characters are + printed as they are being erased + (not in POSIX) */ + echoke = 0004000, /* If ICANON is also set, KILL is echoed by erasing + each character on the line, as specified by ECHOE + and ECHOPRT (not in POSIX) */ + flusho = 0010000, /* Output is being flushed This flag is toggled by + typing the DISCARD character (not in POSIX) */ + pendin = 0040000, /* All characters in the input queue are reprinted + when the next character is read + (not in POSIX) */ + iexten = 0100000, // Enable implementation-defined input processing + extproc = 0200000, +}; + +class TerminalState +{ +public: + TerminalState() = default; + + explicit TerminalState(termios term_attrs) noexcept; + + void set_input_mode_flag(TerminalInputModeFlag flag, bool is_on) noexcept; + + void set_output_mode_flag(TerminalOutputModeFlag flag, bool is_on) noexcept; + + void set_control_mode_flag(TerminalControlModeFlag flag, bool is_on) noexcept; + + void set_local_mode_flag(TerminalLocalModeFlag flag, bool is_on) noexcept; + + auto get_attributes() noexcept -> termios; + +private: + termios _term_attrs; +}; + +auto get_terminal_state(int term_fd) noexcept -> TerminalState; + +void set_terminal_state(int term_fd, TerminalState state) noexcept; + -- cgit v1.2.3-18-g5258