7 Commits

Author SHA1 Message Date
ArthurSonzogni
e85d655b4d Fix stdin references. 2025-08-17 20:21:20 +02:00
ArthurSonzogni
4b0d0984a5 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().
2025-08-17 19:48:54 +02:00
ArthurSonzogni
b0ba518d85 docs: Update CHANGELOG.md for POSIX Piped Input Handling
Added an entry for the POSIX Piped Input Handling feature, including
details about its functionality, default state, and how to disable it.
Also attributed @HarryPehkonen for their contribution in PR #1094.
2025-08-17 19:32:28 +02:00
ArthurSonzogni
be39aed592 Tweak implementation and documentation. 2025-08-17 19:24:26 +02:00
ArthurSonzogni
587e7620f1 Update doc 2025-08-17 19:24:26 +02:00
Harri Pehkonen
def5c6d50c Add opt-in piped input support for POSIX systems
Enables applications to read piped data while maintaining interactive
keyboard input by redirecting stdin to /dev/tty when explicitly enabled.
2025-08-17 19:24:26 +02:00
ArthurSonzogni
0dde21f09e Fix Bazel build. 2025-08-17 19:23:53 +02:00
4 changed files with 35 additions and 35 deletions

View File

@@ -169,13 +169,15 @@ ftxui_cc_library(
"src/ftxui/component/util.cpp",
"src/ftxui/component/window.cpp",
# Private header from ftxui:dom.
"src/ftxui/dom/node_decorator.hpp",
# Private header from ftxui:screen.
"src/ftxui/screen/string_internal.hpp",
"src/ftxui/screen/util.hpp",
# Private header.
"include/ftxui/util/warn_windows_macro.hpp",
],
hdrs = [
"include/ftxui/component/animation.hpp",

View File

@@ -27,6 +27,12 @@ Next
- Remove dependency on 'pthread'.
### Component
- Feature: POSIX Piped Input Handling.
- Allows FTXUI applications to read data from stdin (when piped) while still receiving keyboard input from the terminal.
- Enabled by default.
- Can be disabled using `ScreenInteractive::HandlePipedInput(false)`.
- Only available on Linux and macOS.
Thanks @HarryPehkonen for PR #1094.
- Fix ScreenInteractive::FixedSize screen stomps on the preceding terminal
output. Thanks @zozowell in #1064.

View File

@@ -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;

View File

@@ -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
@@ -539,6 +539,8 @@ void ScreenInteractive::Install() {
// https://github.com/ArthurSonzogni/FTXUI/issues/846
Flush();
InstallPipedInputHandling();
// After uninstalling the new configuration, flush it to the terminal to
// ensure it is fully applied:
on_exit_functions.emplace([] { Flush(); });
@@ -604,9 +606,10 @@ void ScreenInteractive::Install() {
}
struct termios terminal; // NOLINT
tcgetattr(STDIN_FILENO, &terminal);
on_exit_functions.emplace(
[=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
tcgetattr(tty_fd_, &terminal);
on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
tcsetattr(tty_fd_, TCSANOW, &terminal);
});
// Enabling raw terminal input mode
terminal.c_iflag &= ~IGNBRK; // Disable ignoring break condition
@@ -634,7 +637,7 @@ void ScreenInteractive::Install() {
// read.
terminal.c_cc[VTIME] = 0; // Timeout in deciseconds for non-canonical read.
tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
tcsetattr(tty_fd_, TCSANOW, &terminal);
#endif
@@ -670,20 +673,13 @@ void ScreenInteractive::Install() {
// ensure it is fully applied:
Flush();
// Redirect the true terminal to stdin, so that we can read keyboard input
// directly from stdin, even if the input is piped from a file or another
// process.
//
// TODO: Instead of redirecting stdin, we could define the file descriptor to
// read from, and use it in the TerminalInputParser.
InstallPipedInputHandling();
quit_ = false;
PostAnimationTask();
}
void ScreenInteractive::InstallPipedInputHandling() {
tty_fd_ = fileno(stdin); // NOLINT
#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 +688,23 @@ void ScreenInteractive::InstallPipedInputHandling() {
return;
}
// If stdin is a terminal, we don't need to redirect it.
if (isatty(STDIN_FILENO)) {
// If stdin is a terminal, we don't need to open /dev/tty.
if (isatty(fileno(stdin))) {
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_ = fileno(stdin); // 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 +1142,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 +1154,7 @@ void ScreenInteractive::FetchTerminalEvents() {
// Read chars from the terminal.
std::array<char, 128> 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) {