From e1350f2e892d619b12f72934347fbf32fa80882b Mon Sep 17 00:00:00 2001 From: rolesen Date: Sun, 24 Jan 2021 23:28:20 +0100 Subject: [PATCH] Using alternative buffer (becomes the default) and handling user interrupt signal (usually 'ctrl-c' - also enabled as default) to be able to clean up the terminal on user interrupt. Consider if kill signal should also be handled. This is implemented via a monitoring thread that then calls std::exit, as it is very limited what is allowed to do be done in a signal handler. I am proposing this as part of the library so users of the library won't have to bother with these issues. --- include/rxterm/terminal.hpp | 223 +++++++++++++++++++++++++++--------- 1 file changed, 167 insertions(+), 56 deletions(-) diff --git a/include/rxterm/terminal.hpp b/include/rxterm/terminal.hpp index 3bc55f0..0f9af4d 100644 --- a/include/rxterm/terminal.hpp +++ b/include/rxterm/terminal.hpp @@ -1,11 +1,12 @@ #ifndef RXTERM_TERMINAL_HPP #define RXTERM_TERMINAL_HPP -#include #include +#include #include #ifdef _WIN32 +#include #include #ifndef NOMINMAX #define NOMINMAX @@ -16,86 +17,196 @@ #include #endif -#include #include +#include -namespace rxterm { +namespace rxterm +{ -struct VirtualTerminal { - std::string buffer; - static unsigned width() { - unsigned w; + volatile std::sig_atomic_t& gSignalStatus() + { + static volatile std::sig_atomic_t instance = 0; + return instance; + } + + struct VirtualTerminal + { + std::string buffer; + static unsigned width() + { + unsigned w; #ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO info; - w = (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)) - ? info.srWindow.Right - info.srWindow.Left + 1 - : 0; + CONSOLE_SCREEN_BUFFER_INFO info; + w = (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)) + ? info.srWindow.Right - info.srWindow.Left + 1 + : 0; #else - winsize ws{}; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); - w = ws.ws_col; + winsize ws{}; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); + w = ws.ws_col; #endif - return w; - } - static unsigned height() { - unsigned h; + return w; + } + static unsigned height() + { + unsigned h; #ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO info; - h = (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)) - ? info.srWindow.Bottom - info.srWindow.Top + 1 - : 0; + CONSOLE_SCREEN_BUFFER_INFO info; + h = (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)) + ? info.srWindow.Bottom - info.srWindow.Top + 1 + : 0; #else winsize ws{}; ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); h = ws.ws_row; #endif - return h; - } + return h; + } - std::string computeTransition(std::string const& next) const { - if(buffer == next) return ""; - unsigned const n = std::count(buffer.begin(), buffer.end(), '\n'); - return clearLines(n) + "\033[0m" + next; - } + std::string computeTransition(std::string const& next) const + { + if (buffer == next) + return ""; + unsigned const n = std::count(buffer.begin(), buffer.end(), '\n'); + return clearLines(n) + "\033[0m" + next; + } - static std::string hide() { return "\033[0;8m"; } + static std::string hide() + { + return "\033[0;8m"; + } - VirtualTerminal flip(std::string const& next) const { - auto const transition = computeTransition(next); - if(transition.empty()) return *this; - std::cout << transition << hide(); - std::flush(std::cout); - return VirtualTerminal{next}; - } + VirtualTerminal flip(std::string const& next) const + { + auto const transition = computeTransition(next); + if (transition.empty()) + return *this; + std::cout << transition << hide(); + std::flush(std::cout); + return VirtualTerminal{ next }; + } + + struct InitSignalHandlerThread + { + static void globalInit() + { + static InitSignalHandlerThread _; + } + + private: + InitSignalHandlerThread() + { + + std::signal(SIGINT, [](int signal) { gSignalStatus() = signal; }); + + std::thread signal_handler_thread([] { + using namespace std::chrono_literals; + for (;;) + { + if (gSignalStatus() != 0) + { + + std::exit(gSignalStatus()); + } + std::this_thread::sleep_for(100ms); + } + }); + signal_handler_thread.detach(); + } + }; + + struct InitAlternateBuffer + { + static void globalInit() + { + static InitAlternateBuffer _; + } + + private: + InitAlternateBuffer() + { + // Enter the alternate buffer + std::cout << "\x1b[?1049h"; + } + ~InitAlternateBuffer() + { + // Exit the alternate buffer + std::cout << "\x1b?1049l"; + // Returns all attributes to the default state prior to modification + std::cout << "\033[0;m"; + } + }; #ifdef _WIN32 - static std::once_flag initInstanceFlag; - static DWORD initWindowsTerminal() { #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 #endif - // Set output mode to handle virtual terminal sequences - HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut == INVALID_HANDLE_VALUE) return GetLastError(); - DWORD dwMode = 0; - if (!GetConsoleMode(hOut, &dwMode)) return GetLastError(); - dwMode |= DWORD(ENABLE_VIRTUAL_TERMINAL_PROCESSING); - if (!SetConsoleMode(hOut, dwMode)) return GetLastError(); - return ERROR_SUCCESS; - } + struct InitVirtualConsoleMode + { + static void globalInit() + { + static InitVirtualConsoleMode _; + } - VirtualTerminal() { - std::call_once(initInstanceFlag, VirtualTerminal::initWindowsTerminal); - } - VirtualTerminal(const VirtualTerminal&) = default; - VirtualTerminal& operator=(const VirtualTerminal&) = default; - explicit VirtualTerminal(std::string buf) : buffer{std::move(buf)} {}; + private: + static void setVirtualTerminalProcessingMode(bool value) + { + // Set output mode to handle virtual terminal sequences + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE) + throw std::system_error( + std::error_code(GetLastError(), std::system_category())); + DWORD dwMode = 0; + if (!GetConsoleMode(hOut, &dwMode)) + throw std::system_error( + std::error_code(GetLastError(), std::system_category())); + if (value) + { + dwMode |= DWORD(ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + else + { + dwMode &= ~DWORD(ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + if (!SetConsoleMode(hOut, dwMode)) + throw std::system_error( + std::error_code(GetLastError(), std::system_category())); + } + + InitVirtualConsoleMode() + { + setVirtualTerminalProcessingMode(true); + } + ~InitVirtualConsoleMode() + { + setVirtualTerminalProcessingMode(false); + } + }; #endif -}; + VirtualTerminal(bool installInterruptSignalHandlerThread = true, + bool useAlternateBuffer = true) + { #ifdef _WIN32 -std::once_flag VirtualTerminal::initInstanceFlag; + InitVirtualConsoleMode::globalInit(); #endif + + if (useAlternateBuffer) + { + InitAlternateBuffer::globalInit(); + } + if (installInterruptSignalHandlerThread) + { + InitSignalHandlerThread::globalInit(); + } + } + + VirtualTerminal(const VirtualTerminal&) = default; + VirtualTerminal& operator=(const VirtualTerminal&) = default; + explicit VirtualTerminal(std::string buf) : buffer{ std::move(buf) } {}; + }; + } #endif +