diff --git a/Sample/App/Source/Main.cpp b/Sample/App/Source/Main.cpp index cf74613..8a27e78 100644 --- a/Sample/App/Source/Main.cpp +++ b/Sample/App/Source/Main.cpp @@ -1,5 +1,7 @@ #include +#include "ctui_raw_mode.h" + #ifdef _WIN32 #include #endif @@ -13,7 +15,8 @@ int main() { SetConsoleCP(CP_UTF8); #endif - enable_raw_mode(); + RawModeGuard rmg; + screen->config(); auto root = screen->make_child( diff --git a/Source/ctui.h b/Source/ctui.h index 68a9499..46eefe2 100644 --- a/Source/ctui.h +++ b/Source/ctui.h @@ -9,3 +9,4 @@ #include "Widgets/Container/ctui_container_vstack.h" #include "Widgets/Container/ctui_container_screen.h" #include "ctui_utf8.h" +#include "ctui_raw_mode.h" \ No newline at end of file diff --git a/Source/ctui_config.cpp b/Source/ctui_config.cpp index 850f008..8c9c253 100644 --- a/Source/ctui_config.cpp +++ b/Source/ctui_config.cpp @@ -7,51 +7,12 @@ #include #include #else -#include #include #include -#include #endif -namespace ctui { - bool raw_mode = false; - - - void enable_raw_mode() { -#ifdef _WIN32 - HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); - DWORD mode; - GetConsoleMode(hStdin, &mode); - SetConsoleMode(hStdin, mode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)); - HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); - GetConsoleMode(hOut, &mode); - SetConsoleMode(hOut, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); -#else - termios t; - tcgetattr(STDIN_FILENO, &t); - t.c_lflag &= ~(ICANON | ECHO | IXON); - tcsetattr(STDIN_FILENO, TCSANOW, &t); -#endif - std::ios::sync_with_stdio(false); - raw_mode = true; - std::cout << "\033[2J\033[H"; - } - - void disable_raw_mode() { -#ifdef _WIN32 - HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); - DWORD mode; - GetConsoleMode(hStdin, &mode); - SetConsoleMode(hStdin, mode | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); -#else - termios t; - tcgetattr(STDIN_FILENO, &t); - t.c_lflag |= (ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &t); -#endif - raw_mode = false; - } - +namespace ctui +{ std::pair get_win_size() { #ifdef _WIN32 CONSOLE_SCREEN_BUFFER_INFO csbi; diff --git a/Source/ctui_config.h b/Source/ctui_config.h index b37abc7..1ac9dba 100644 --- a/Source/ctui_config.h +++ b/Source/ctui_config.h @@ -2,8 +2,5 @@ #include namespace ctui { - extern bool raw_mode; - void enable_raw_mode(); - void disable_raw_mode(); std::pair get_win_size(); } \ No newline at end of file diff --git a/Source/ctui_raw_mode.cpp b/Source/ctui_raw_mode.cpp new file mode 100644 index 0000000..a473308 --- /dev/null +++ b/Source/ctui_raw_mode.cpp @@ -0,0 +1,61 @@ +#include "pch.h" + +#include "ctui_raw_mode.h" + +namespace ctui +{ + RawModeGuard::RawModeGuard() + { +#ifdef _WIN32 + _stdin = GetStdHandle(STD_INPUT_HANDLE); + _stdout = GetStdHandle(STD_OUTPUT_HANDLE); + + // GetConsoleMode fails if the handle is invalid or redirected (e.g. stdin/stdout + // piped to a file). Without this check, _original_*_mode stays uninitialized and + // we'd later restore garbage in the destructor. + if (!GetConsoleMode(_stdin, &_original_stdin_mode)) + throw std::system_error(GetLastError(), std::system_category(), "GetConsoleMode (stdin) failed"); + if (!GetConsoleMode(_stdout, &_original_stdout_mode)) + throw std::system_error(GetLastError(), std::system_category(), "GetConsoleMode (stdout) failed"); + + DWORD stdin_mode = _original_stdin_mode; + stdin_mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); + SetConsoleMode(_stdin, stdin_mode); + + DWORD stdout_mode = _original_stdout_mode; + stdout_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(_stdout, stdout_mode); +#else + // Same reasoning as GetConsoleMode above: tcgetattr fails for non-TTY stdin + // (redirected/piped), and leaves _original untouched on failure. + if (tcgetattr(STDIN_FILENO, &_original) != 0) + throw std::system_error(errno, std::generic_category(), "tcgetattr failed"); + + termios raw = _original; + raw.c_lflag &= ~(ICANON | ECHO); + raw.c_iflag &= ~IXON; + + tcsetattr(STDIN_FILENO, TCSANOW, &raw); +#endif + + // TODO: Move code to main loop as soon as it exists. + std::ios::sync_with_stdio(false); + std::cout << "\033[?1049h\033[2J\033[H"; + } + + RawModeGuard::~RawModeGuard() + { + // Must run *before* restoring the console mode below: if the original stdout + // mode didn't have ENABLE_VIRTUAL_TERMINAL_PROCESSING (the common case), the + // escape sequence would no longer be interpreted once we restore it, and the + // terminal would be stuck in the alternate screen buffer. + std::cout << "\033[?1049l"; // TODO: move to main loop + +#ifdef _WIN32 + SetConsoleMode(_stdin, _original_stdin_mode); + SetConsoleMode(_stdout, _original_stdout_mode); +#else + tcsetattr(STDIN_FILENO, TCSANOW, &_original); +#endif + } +} diff --git a/Source/ctui_raw_mode.h b/Source/ctui_raw_mode.h new file mode 100644 index 0000000..1abe5d1 --- /dev/null +++ b/Source/ctui_raw_mode.h @@ -0,0 +1,33 @@ +#pragma once + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +namespace ctui +{ + class RawModeGuard + { + // Original Terminal State +#ifdef _WIN32 + HANDLE _stdin; + HANDLE _stdout; + + DWORD _original_stdin_mode; + DWORD _original_stdout_mode; +#else + termios _original; +#endif + public: + RawModeGuard(); + ~RawModeGuard(); + + RawModeGuard(const RawModeGuard&) = delete; + RawModeGuard& operator=(const RawModeGuard&) = delete; + RawModeGuard(RawModeGuard&&) = delete; + RawModeGuard& operator=(RawModeGuard&&) = delete; + }; +}