From 861c2e9326e710103d3515c4893736d1f075e2d4 Mon Sep 17 00:00:00 2001 From: Aintmee <85761348+Aintdev@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:33:56 +0200 Subject: [PATCH 1/4] feat(config): add RAII guard for raw mode --- Sample/App/Source/Main.cpp | 5 ++++- Source/ctui_config.cpp | 43 ++----------------------------------- Source/ctui_config.h | 3 --- Source/ctui_raw_mode.cpp | Bin 0 -> 2746 bytes Source/ctui_raw_mode.h | Bin 0 -> 1202 bytes 5 files changed, 6 insertions(+), 45 deletions(-) create mode 100644 Source/ctui_raw_mode.cpp create mode 100644 Source/ctui_raw_mode.h 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_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 0000000000000000000000000000000000000000..bc2c1701c4dcbeaadd2653574dda48e74ce886a8 GIT binary patch literal 2746 zcmb`JZ%-3J5XPU^CVq!YNeCte6nrCM4AKf1prkEgqRFKx6q}Tup@(QxKDzpw-EOXT zy_OSAv%T)#?94OI{Oj*uOB!gTo`$;Cna&ldrqAp@X_ZyeLp^Y-Os=PtTLZ|V*xHid*I$mTxWp{lnt9n-+w{pG9E=z97hX#?~)@v90 zm~YFA2K;6D5$Ol`@^TIBkTuPmTwu4%)3(dUsu3;2yh!^jkHsk5#bi-_5tl_-%f$LyQ%*q|oc?tFJrd-rG1W3wFf92T(#7MF;}5SjDR zT2JY57cOF5YCWa4Lf^2gi1h}v)+>&{Kd&Bwr7~YqbYIa|2h{BttiA`&i&1$j{GC;h z#(2_ab-`@iz+z#Uy$`q6*P5)5!#-;Rw$N|Fb6ThM-=fZay?5F?qH@oAj@1-2Rha95 zu{}X~I^oiz2pBC=*1ZjnSuC@y)io`n4+$o!lbvQL<6 z5hoS}>$KK!!+yYx2pwOMw`>|SSIeyXOtTs1<7vOf`i3Tp%?+n1^Em6s39;#8)90W0 z8Qr1ReDG6eOIt|S^ifZkh8y~zS9;3tXL^o}>~vjkr*?bX$Y;^d87^BOx^cYOe~J9; zzNJ#j-~Ny3{dc{w>NE)X6LLE|t69tX$b5VE?=9AM{-r`@F$Hgn)e^i8&n#D~< literal 0 HcmV?d00001 diff --git a/Source/ctui_raw_mode.h b/Source/ctui_raw_mode.h new file mode 100644 index 0000000000000000000000000000000000000000..7d69525425e0a71410f333a1d1c92b80ea485ae7 GIT binary patch literal 1202 zcmcIj%SyvQ6g}Gr{=-0^#EpXP)CxjG1+}1p%P{t#18pKrLKjj$T|IX)Nt(zM7fP6+ z$((!7<4(UmL%d-LjTbDqD`ZG9#uU5Qu`7hZ3=@8t@if5`u5pJW95$zdpQE%Gg;T6p zk@79$uN?b$#hJrSiOXGY>D#X?c($Yt!`g1;Pi)C*LsUVgE{AZ7=Gs9@Uki55=~P>9 zihDuD#$w*>9#|KesGL6UiCp29I>h-Uv6{*z4p~b^T#nK`VOEvv zmgjfX6>q;jq~mt#qNO`!xl?^T6|+=s&NATqN<6ch;jE6E6nwLau(y%bhj*qeu1?F} zaZIhLP0N2r2IN&3a>?D9bBW_NO<(r`D>8bN|A5>SNo9zLUjL6X@}@GtU;FU;-p;gk J*{$uwzX6cZx=#QA literal 0 HcmV?d00001 From 415fcd919f056163c71f5c3fa0352d63cdf029db Mon Sep 17 00:00:00 2001 From: Aintmee <85761348+Aintdev@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:47:11 +0200 Subject: [PATCH 2/4] fix(rawmodeguard): change file encoding to utf8 --- Source/ctui_raw_mode.cpp | Bin 2746 -> 1322 bytes Source/ctui_raw_mode.h | Bin 1202 -> 567 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Source/ctui_raw_mode.cpp b/Source/ctui_raw_mode.cpp index bc2c1701c4dcbeaadd2653574dda48e74ce886a8..59d9d0b25afd4ab4e9817b17f5a3a2596eeb4466 100644 GIT binary patch literal 1322 zcmah|U2obj6n)RHxB{sHsiIxk%Rdbnw03V;7 z7ML(SvO{d!=3l3)6P~c=I_7JjE0^@|bW9b}T*gTTLzr?I$au!GIOUxnoKTlMEJHdQ zk;&ZY4r&GBD4Qz0WmqezYOwcMWVuMW0@*2MUc(g?hh&pvEG;6MqcN(X3@5*?!%$gR z4Vc+=-NEZdBsOY z8!nlY2jvBq(68>?A)cwdNl5a;E)hAPQB5ir_;}EZXu3|>2DHHLctkv+R7E+lmGYu` zio~Q!bbolnpdzEZ5mq=3u-^*tVFOJji<9+bJvTQHdK1rvg?Pk^gDwOqV8_tYYg7zmr_%y$TcxW!uQlw(~z(L{Ev_YR8JsnxSaj0n}3?lojspq4n#w-~ajf z!|lgUTm5T_8{v0PA= Yv+V|X($)Z~9qrVP%=Kt>&(_)c3wLgdZU6uP literal 2746 zcmb`JZ%-3J5XPU^CVq!YNeCte6nrCM4AKf1prkEgqRFKx6q}Tup@(QxKDzpw-EOXT zy_OSAv%T)#?94OI{Oj*uOB!gTo`$;Cna&ldrqAp@X_ZyeLp^Y-Os=PtTLZ|V*xHid*I$mTxWp{lnt9n-+w{pG9E=z97hX#?~)@v90 zm~YFA2K;6D5$Ol`@^TIBkTuPmTwu4%)3(dUsu3;2yh!^jkHsk5#bi-_5tl_-%f$LyQ%*q|oc?tFJrd-rG1W3wFf92T(#7MF;}5SjDR zT2JY57cOF5YCWa4Lf^2gi1h}v)+>&{Kd&Bwr7~YqbYIa|2h{BttiA`&i&1$j{GC;h z#(2_ab-`@iz+z#Uy$`q6*P5)5!#-;Rw$N|Fb6ThM-=fZay?5F?qH@oAj@1-2Rha95 zu{}X~I^oiz2pBC=*1ZjnSuC@y)io`n4+$o!lbvQL<6 z5hoS}>$KK!!+yYx2pwOMw`>|SSIeyXOtTs1<7vOf`i3Tp%?+n1^Em6s39;#8)90W0 z8Qr1ReDG6eOIt|S^ifZkh8y~zS9;3tXL^o}>~vjkr*?bX$Y;^d87^BOx^cYOe~J9; zzNJ#j-~Ny3{dc{w>NE)X6LLE|t69tX$b5VE?=9AM{-r`@F$Hgn)e^i8&n#D~< diff --git a/Source/ctui_raw_mode.h b/Source/ctui_raw_mode.h index 7d69525425e0a71410f333a1d1c92b80ea485ae7..1abe5d1221f11ca0f52e8d753829fce34d17afbd 100644 GIT binary patch literal 567 zcmb7AO-sW-5Iyhwih)2$FBbG>tRO^G&=#uDV_0`5bz!p`_JbUx{&qJ_(6or?ZTHRl zm^WGL_^#n-jUa$b)skwgwhya|^Yk-9M@e|qDI;y?&)%*fBkgH23gjAPhtU^58s&o= z=timCOAcbKs#*ovz2& z{mrU;yu~VIDN`iv1h6p_kf3tA-jrC`(Q@EcP0H*AKTMNw91m3)8!LXB6zK|wTC9z) zT8Yc#G%b?Pd+(1^=#(X_@!{wKvpFtNlBRIjZfYD#X?c($Yt!`g1;Pi)C*LsUVgE{AZ7=Gs9@Uki55=~P>9 zihDuD#$w*>9#|KesGL6UiCp29I>h-Uv6{*z4p~b^T#nK`VOEvv zmgjfX6>q;jq~mt#qNO`!xl?^T6|+=s&NATqN<6ch;jE6E6nwLau(y%bhj*qeu1?F} zaZIhLP0N2r2IN&3a>?D9bBW_NO<(r`D>8bN|A5>SNo9zLUjL6X@}@GtU;FU;-p;gk J*{$uwzX6cZx=#QA From 44484e3863e981a5e319ab99a6a90951fbdf7646 Mon Sep 17 00:00:00 2001 From: Aintmee <85761348+Aintdev@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:21:53 +0200 Subject: [PATCH 3/4] fix(rawmodeguard): check console-mode retrieval and fix VT-processing restore order Throw std::system_error when GetConsoleMode/tcgetattr fail (redirected or invalid stdin/stdout) instead of restoring uninitialized state later. Also write the alt-screen exit sequence before restoring the original console mode, so it's still interpreted if VT processing wasn't enabled originally. --- Source/ctui_raw_mode.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Source/ctui_raw_mode.cpp b/Source/ctui_raw_mode.cpp index 59d9d0b..a473308 100644 --- a/Source/ctui_raw_mode.cpp +++ b/Source/ctui_raw_mode.cpp @@ -10,20 +10,26 @@ namespace ctui _stdin = GetStdHandle(STD_INPUT_HANDLE); _stdout = GetStdHandle(STD_OUTPUT_HANDLE); - GetConsoleMode(_stdin, &_original_stdin_mode); - GetConsoleMode(_stdout, &_original_stdout_mode); + // 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 - tcgetattr(STDIN_FILENO, &_original); + // 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); @@ -33,18 +39,23 @@ namespace ctui #endif // TODO: Move code to main loop as soon as it exists. - std::ios::sync_with_stdio(false); + 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 - std::cout << "\033[?1049l"; // TODO: move to main loop } } From 72e03058269556e6a9ab14899d23e5bc56ef7fe2 Mon Sep 17 00:00:00 2001 From: Aintmee <85761348+Aintdev@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:24:10 +0200 Subject: [PATCH 4/4] feat(raw_mode_guard): added to umbrella header --- Source/ctui.h | 1 + 1 file changed, 1 insertion(+) 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