Line data Source code
1 : /*! 2 : * \file esys/repo/tui/is_tty.cpp 3 : * \brief 4 : * 5 : * \cond 6 : * __legal_b__ 7 : * 8 : * Copyright (c) 2026 Michel Gillet 9 : * Distributed under the MIT License. 10 : * (See accompanying file LICENSE.txt or 11 : * copy at https://opensource.org/licenses/MIT) 12 : * 13 : * __legal_e__ 14 : * \endcond 15 : * 16 : */ 17 : 18 : #include "esys/repo/esysrepo_prec.h" 19 : #include "esys/repo/tui/is_tty.h" 20 : 21 : #ifdef _WIN32 22 : #include <io.h> 23 : #include <stdio.h> 24 : #include <windows.h> 25 : #else 26 : #include <sys/ioctl.h> 27 : #include <unistd.h> 28 : #include <stdio.h> 29 : #endif 30 : 31 : namespace esys::repo::tui 32 : { 33 : 34 : namespace 35 : { 36 : 37 : #ifdef _WIN32 38 : bool g_console_prepared = false; 39 : UINT g_prev_output_cp = 0; 40 : UINT g_prev_input_cp = 0; 41 : DWORD g_prev_out_mode = 0; 42 : DWORD g_prev_in_mode = 0; 43 : bool g_have_out_mode = false; 44 : bool g_have_in_mode = false; 45 : #endif 46 : 47 : } // namespace 48 : 49 0 : bool is_tty() 50 : { 51 : #ifdef _WIN32 52 : // FTXUI needs a real Win32 console for both stdout and stdin (VT + ReadConsoleInput). 53 : // Git Bash / mintty / pipes often fail GetConsoleMode even when isatty is true. 54 : const HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 55 : const HANDLE in = GetStdHandle(STD_INPUT_HANDLE); 56 : if (out == nullptr || out == INVALID_HANDLE_VALUE) return false; 57 : if (in == nullptr || in == INVALID_HANDLE_VALUE) return false; 58 : 59 : DWORD out_mode = 0; 60 : DWORD in_mode = 0; 61 : if (GetConsoleMode(out, &out_mode) == 0) return false; 62 : if (GetConsoleMode(in, &in_mode) == 0) return false; 63 : 64 : if (_isatty(_fileno(stdout)) == 0) return false; 65 : if (_isatty(_fileno(stdin)) == 0) return false; 66 : return true; 67 : #else 68 0 : return ::isatty(::fileno(stdout)) != 0 && ::isatty(::fileno(stdin)) != 0; 69 : #endif 70 : } 71 : 72 0 : bool prepare_console_for_tui() 73 : { 74 : #ifdef _WIN32 75 : const HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 76 : const HANDLE in = GetStdHandle(STD_INPUT_HANDLE); 77 : if (out == nullptr || out == INVALID_HANDLE_VALUE) return false; 78 : if (in == nullptr || in == INVALID_HANDLE_VALUE) return false; 79 : 80 : // Save previous settings once so restore_console_after_tui can undo. 81 : if (!g_console_prepared) 82 : { 83 : g_prev_output_cp = GetConsoleOutputCP(); 84 : g_prev_input_cp = GetConsoleCP(); 85 : g_have_out_mode = GetConsoleMode(out, &g_prev_out_mode) != 0; 86 : g_have_in_mode = GetConsoleMode(in, &g_prev_in_mode) != 0; 87 : g_console_prepared = true; 88 : } 89 : 90 : // Re-apply every call: ScreenInteractive / other writers may reset modes. 91 : SetConsoleOutputCP(CP_UTF8); 92 : SetConsoleCP(CP_UTF8); 93 : 94 : DWORD out_mode = 0; 95 : if (GetConsoleMode(out, &out_mode) != 0) 96 : { 97 : out_mode |= ENABLE_PROCESSED_OUTPUT; 98 : out_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 99 : out_mode |= DISABLE_NEWLINE_AUTO_RETURN; 100 : SetConsoleMode(out, out_mode); 101 : } 102 : 103 : DWORD in_mode = 0; 104 : if (GetConsoleMode(in, &in_mode) != 0) 105 : { 106 : in_mode |= ENABLE_EXTENDED_FLAGS; 107 : in_mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; 108 : in_mode |= ENABLE_PROCESSED_INPUT; 109 : SetConsoleMode(in, in_mode); 110 : } 111 : 112 : return true; 113 : #else 114 0 : return true; 115 : #endif 116 : } 117 : 118 0 : void restore_console_after_tui() 119 : { 120 : #ifdef _WIN32 121 : if (!g_console_prepared) return; 122 : 123 : const HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 124 : const HANDLE in = GetStdHandle(STD_INPUT_HANDLE); 125 : if (out != nullptr && out != INVALID_HANDLE_VALUE && g_have_out_mode) 126 : SetConsoleMode(out, g_prev_out_mode); 127 : if (in != nullptr && in != INVALID_HANDLE_VALUE && g_have_in_mode) 128 : SetConsoleMode(in, g_prev_in_mode); 129 : 130 : if (g_prev_output_cp != 0) SetConsoleOutputCP(g_prev_output_cp); 131 : if (g_prev_input_cp != 0) SetConsoleCP(g_prev_input_cp); 132 : 133 : g_console_prepared = false; 134 : g_have_out_mode = false; 135 : g_have_in_mode = false; 136 : #endif 137 0 : } 138 : 139 0 : bool query_console_size(int &cols, int &rows) 140 : { 141 0 : cols = 0; 142 0 : rows = 0; 143 : #ifdef _WIN32 144 : const HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 145 : if (out == nullptr || out == INVALID_HANDLE_VALUE) return false; 146 : 147 : CONSOLE_SCREEN_BUFFER_INFO info; 148 : if (GetConsoleScreenBufferInfo(out, &info) == 0) return false; 149 : 150 : cols = static_cast<int>(info.srWindow.Right - info.srWindow.Left + 1); 151 : rows = static_cast<int>(info.srWindow.Bottom - info.srWindow.Top + 1); 152 : return cols > 0 && rows > 0; 153 : #else 154 0 : winsize ws{}; 155 0 : if (::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != 0) return false; 156 0 : cols = static_cast<int>(ws.ws_col); 157 0 : rows = static_cast<int>(ws.ws_row); 158 0 : return cols > 0 && rows > 0; 159 : #endif 160 : } 161 : 162 : } // namespace esys::repo::tui