From d59b77e6a04e380ea37e2e0fc23545b18b928152 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Sun, 17 Aug 2025 19:40:20 +0200 Subject: [PATCH] feat: Improve POSIX piped input handling Refactored the POSIX piped input handling to avoid redirecting stdin. Instead, it now directly opens and reads from /dev/tty for keyboard input when stdin is piped, allowing applications to process piped data while still receiving interactive keyboard events. This addresses the TODO comment in ScreenInteractive::InstallPipedInputHandling(). --- .../ftxui/component/screen_interactive.hpp | 2 + src/ftxui/component/screen_interactive.cpp | 37 ++++++++----------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index b3d1f5156..aff53bc31 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -147,6 +147,8 @@ class ScreenInteractive : public Screen { // Piped input handling state (POSIX only) bool handle_piped_input_ = true; + // File descriptor for /dev/tty, used for piped input handling. + int tty_fd_ = -1; // The style of the cursor to restore on exit. int cursor_reset_shape_ = 1; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 41b7028a3..3bca110d4 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -112,13 +112,13 @@ void ftxui_on_resize(int columns, int rows) { #else // POSIX (Linux & Mac) -int CheckStdinReady() { +int CheckStdinReady(int fd) { timeval tv = {0, 0}; // NOLINT fd_set fds; FD_ZERO(&fds); // NOLINT - FD_SET(STDIN_FILENO, &fds); // NOLINT - select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv); // NOLINT - return FD_ISSET(STDIN_FILENO, &fds); // NOLINT + FD_SET(fd, &fds); // NOLINT + select(fd + 1, &fds, nullptr, nullptr, &tv); // NOLINT + return FD_ISSET(fd, &fds); // NOLINT } #endif @@ -684,6 +684,7 @@ void ScreenInteractive::Install() { } void ScreenInteractive::InstallPipedInputHandling() { + tty_fd_ = STDIN_FILENO; // Default to stdin. #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) // Handle piped input redirection if explicitly enabled by the application. // This allows applications to read data from stdin while still receiving @@ -692,29 +693,23 @@ void ScreenInteractive::InstallPipedInputHandling() { return; } - // If stdin is a terminal, we don't need to redirect it. + // If stdin is a terminal, we don't need to open /dev/tty. if (isatty(STDIN_FILENO)) { return; } - // Save the current stdin so we can restore it later. - int original_fd = dup(STDIN_FILENO); - if (original_fd < 0) { - return; - } - - // Redirect stdin to the controlling terminal for keyboard input. - if (std::freopen("/dev/tty", "r", stdin) == nullptr) { + // Open /dev/tty for keyboard input. + tty_fd_ = open("/dev/tty", O_RDONLY); + if (tty_fd_ < 0) { // Failed to open /dev/tty (containers, headless systems, etc.) - // Clean up and continue without redirection - close(original_fd); + tty_fd_ = STDIN_FILENO; // Fallback to stdin. return; } - // Restore the original stdin file descriptor on exit. - on_exit_functions.emplace([=] { - dup2(original_fd, STDIN_FILENO); - close(original_fd); + // Close the /dev/tty file descriptor on exit. + on_exit_functions.emplace([this] { + close(tty_fd_); + tty_fd_ = -1; }); #endif } @@ -1152,7 +1147,7 @@ void ScreenInteractive::FetchTerminalEvents() { internal_->terminal_input_parser.Add(out[i]); } #else // POSIX (Linux & Mac) - if (!CheckStdinReady()) { + if (!CheckStdinReady(tty_fd_)) { const auto timeout = std::chrono::steady_clock::now() - internal_->last_char_time; const size_t timeout_ms = @@ -1164,7 +1159,7 @@ void ScreenInteractive::FetchTerminalEvents() { // Read chars from the terminal. std::array out{}; - size_t l = read(fileno(stdin), out.data(), out.size()); + size_t l = read(tty_fd_, out.data(), out.size()); // Convert the chars to events. for (size_t i = 0; i < l; ++i) {