module; #include #include #include #include #include #include export module ttwhy.terminal:scoped_attributes; namespace ttwhy::terminal { auto static read_attributes(int file_descriptor) -> termios { auto active_attributes = termios{}; if (::tcgetattr(file_descriptor, &active_attributes)) { throw std::system_error(errno, std::system_category(), "failed to read termios attributes"); } return active_attributes; } auto static get_line_discipline_flag(int file_descriptor, int flag) -> bool { auto active_attributes = read_attributes(file_descriptor); return active_attributes.c_lflag & flag; } auto static set_line_discipline_flag(int file_descriptor, int flag, bool enabled) -> void { auto active_attributes = read_attributes(file_descriptor); if ((active_attributes.c_lflag & flag) == enabled) { return; } if (enabled) { active_attributes.c_lflag = active_attributes.c_lflag | flag; } else { active_attributes.c_lflag = active_attributes.c_lflag & ~flag; } if (::tcsetattr(file_descriptor, TCSANOW, &active_attributes)) { throw std::system_error(errno, std::system_category(), "failed to write termios attributes"); } } //! RAII wrapper storing the current terminal attributes and restoring them on destruction. export struct scoped_attributes { //! Construct and capture the current attributes of a terminal file descriptor. //! //! @param file_descriptor The file descriptor of the terminal. explicit scoped_attributes(int file_descriptor) : m_file_descriptor{-1} , m_attributes{} { if (isatty(file_descriptor)) { m_file_descriptor = dup(file_descriptor); m_attributes = read_attributes(m_file_descriptor); } } scoped_attributes(scoped_attributes const &) = delete; scoped_attributes(scoped_attributes && other) : m_file_descriptor{std::exchange(other.m_file_descriptor, -1)} , m_attributes{std::move(other.m_attributes)} {} ~scoped_attributes() { if (m_file_descriptor > 0) { ::tcsetattr(m_file_descriptor, TCSANOW, &m_attributes); ::close(m_file_descriptor); } } auto operator=(scoped_attributes const &) -> scoped_attributes & = delete; auto operator=(scoped_attributes && other) -> scoped_attributes & { std::ranges::swap(m_file_descriptor, other.m_file_descriptor); std::ranges::swap(m_attributes, other.m_attributes); return *this; } //! Check if canonical mode is currently active. //! //! @return True if ICANON is set, false otherwise. [[nodiscard]] auto canonical_mode() const noexcept -> bool { return get_line_discipline_flag(m_file_descriptor, ICANON); } //! Set whether canonical mode is active or not on an lvalue reference. //! //! @param enabled True to enable canonical mode, false to disable. //! @return Reference to this object for chaining. auto canonical_mode(bool enabled) & -> scoped_attributes & { set_line_discipline_flag(m_file_descriptor, ICANON, enabled); return *this; } //! Set whether canonical mode is active or not on an rvalue. //! //! @param enabled True to enable canonical mode, false to disable. //! @return The modified scoped_attributes object by value. auto canonical_mode(bool enabled) && -> scoped_attributes { set_line_discipline_flag(m_file_descriptor, ICANON, enabled); return std::move(*this); } //! Check if echo is currently active. //! //! @return True if ECHO is set, false otherwise. [[nodiscard]] auto echo() const noexcept -> bool { return get_line_discipline_flag(m_file_descriptor, ECHO); } //! Set whether echo is active or not on an lvalue reference. //! //! @param enabled True to enable echo, false to disable. //! @return Reference to this object for chaining. auto echo(bool enabled) & -> scoped_attributes & { set_line_discipline_flag(m_file_descriptor, ECHO, enabled); return *this; } //! Set whether echo is active or not on an rvalue. //! //! @param enabled True to enable echo, false to disable. //! @return The modified scoped_attributes object by value. auto echo(bool enabled) && -> scoped_attributes { set_line_discipline_flag(m_file_descriptor, ECHO, enabled); return std::move(*this); } //! Capture the currently active terminal mode and mark the attributes as suspended. //! //! Must be called before the terminal mode is changed while the process is being stopped. auto suspend() & -> scoped_attributes & { if (m_file_descriptor > 0) { m_suspended = true; m_suspended_canonical = canonical_mode(); m_suspended_echo = echo(); } return *this; } //! Restore the terminal mode captured before suspension. auto resume() & -> scoped_attributes & { if (m_suspended) { canonical_mode(m_suspended_canonical); echo(m_suspended_echo); m_suspended = false; } return *this; } private: int m_file_descriptor; termios m_attributes; bool m_suspended{false}; bool m_suspended_canonical{false}; bool m_suspended_echo{false}; }; } // namespace ttwhy::terminal