diff --git a/.gitignore b/.gitignore index 40a2b7c4..70d197c7 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ out/ !doc/**/*.html !doc/**/*.xml !doc/**/*.md +!doc/*.md # examples directory: !examples/**/*.cpp diff --git a/README.md b/README.md index fdfdd28c..25684055 100644 --- a/README.md +++ b/README.md @@ -378,21 +378,7 @@ Several games using the FTXUI have been made during the Game Jam: - [smoothlife](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/smoothlife.md) - [Consu](https://github.com/cpp-best-practices/game_jam/blob/main/Jam1_April_2022/consu.md) -## Advanced Usage -### Piped Input Support - -If your application reads from stdin (piped data) and also needs interactive keyboard input: - -```cpp -auto screen = ScreenInteractive::Fullscreen(); -screen.HandlePipedInput(true); // Enable before Loop() -screen.Loop(component); -``` - -This allows commands like `cat data.txt | your_app` to work with full keyboard interaction. - -**Note:** This feature is only available on POSIX systems (Linux/macOS). On Windows, the method call is a no-op. ## Build using CMake diff --git a/doc/posix_pipe.md b/doc/posix_pipe.md new file mode 100644 index 00000000..b5ad8f0f --- /dev/null +++ b/doc/posix_pipe.md @@ -0,0 +1,55 @@ +# POSIX Piped Input in FTXUI + +## What is a POSIX Pipe? + +A POSIX pipe is a way for two separate programs to communicate. One program sends its output directly as input to another program. Think of it like a one-way tube for data. + +**Example:** + +Imagine you want to list files and then filter them interactively. + +- `ls`: Lists files. +- `interactive_grep`: An FTXUI application that filters text and lets you type. + +You can connect them with a pipe (`|`): + +```bash +ls -l | interactive_grep +``` + +Here's what happens: +1. `ls -l` lists files with details. +2. The `|` sends this list directly to `interactive_grep`. +3. `interactive_grep` receives the list and displays it. Because it's an FTXUI app, you can then type to filter the list, even though it received initial data from `ls`. + +## How FTXUI Handles Piped Input + +Now that you understand what a POSIX pipe is, let's look at how FTXUI uses them. + +FTXUI lets your application read data from other programs (like from a pipe) while still allowing you to use your keyboard for interaction. This is useful for interactive command-line tools that process data. + +Normally, FTXUI applications receive all input from `stdin`. However, when FTXUI detects that `stdin` is connected to the output of a pipe (meaning data is being piped into your application), it automatically switches to reading interactive keyboard input from `/dev/tty`. This ensures that your application can still receive user input even while processing piped data. + +This feature is **turned on by default**. + +If your FTXUI application needs to read piped data and also respond to keyboard input, you typically don't need to do anything special: + +```cpp +auto screen = ScreenInteractive::Fullscreen(); +// screen.HandlePipedInput(true); // This is enabled by default +screen.Loop(component); +``` + +**Note:** This feature works only on Linux and macOS. It does nothing on Windows. + +## Turning Off Piped Input + +If you don't need this feature, or if it conflicts with your custom input handling, you can turn it off. + +To disable it, call `HandlePipedInput(false)` before starting your application's main loop: + +```cpp +auto screen = ScreenInteractive::Fullscreen(); +screen.HandlePipedInput(false); // Turn off piped input handling +screen.Loop(component); +``` \ No newline at end of file diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 0291e76e..e723caff 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -144,7 +144,7 @@ class ScreenInteractive : public Screen { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) // Piped input handling state (POSIX only) - bool handle_piped_input_ = false; + bool handle_piped_input_ = true; bool stdin_was_redirected_ = false; int original_stdin_fd_ = -1; #endif diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index fad54113..8836b6e4 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -376,9 +376,9 @@ void ScreenInteractive::TrackMouse(bool enable) { /// When enabled, FTXUI will detect piped input and redirect stdin to /dev/tty /// for keyboard input, allowing applications to read piped data while still /// receiving interactive keyboard events. -/// @param enable Whether to enable piped input handling +/// @param enable Whether to enable piped input handling. Default is true. /// @note This must be called before Loop(). -/// @note This feature is disabled by default for backward compatibility. +/// @note This feature is enabled by default. /// @note This feature is only available on POSIX systems (Linux/macOS). #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) void ScreenInteractive::HandlePipedInput(bool enable) { diff --git a/src/ftxui/component/screen_interactive_piped_input_test.cpp b/src/ftxui/component/screen_interactive_piped_input_test.cpp index e953bbd3..643f8bb8 100644 --- a/src/ftxui/component/screen_interactive_piped_input_test.cpp +++ b/src/ftxui/component/screen_interactive_piped_input_test.cpp @@ -63,20 +63,24 @@ class PipedInputTest : public ::testing::Test { bool piped_stdin_setup_ = false; }; -TEST_F(PipedInputTest, DefaultBehaviorNoChange) { - // Test that HandlePipedInput is disabled by default +TEST_F(PipedInputTest, DefaultBehaviorEnabled) { + // Test that HandlePipedInput is enabled by default + if (!IsTtyAvailable()) { + GTEST_SKIP() << "/dev/tty not available in this environment"; + } + auto screen = ScreenInteractive::TerminalOutput(); auto component = Renderer([] { return text("test"); }); SetupPipedStdin(); WriteToPipedStdin("test data\n"); - // Install should not redirect stdin since HandlePipedInput not called + // Install should redirect stdin since HandlePipedInput is on by default screen.Install(); - - // Stdin should still be the pipe (isatty should return false) - EXPECT_FALSE(isatty(STDIN_FILENO)); - + + // Stdin should be the tty + EXPECT_TRUE(isatty(STDIN_FILENO)); + screen.Uninstall(); } @@ -97,7 +101,7 @@ TEST_F(PipedInputTest, ExplicitlyDisabled) { screen.Uninstall(); } -TEST_F(PipedInputTest, PipedInputDetectionAndRedirection) { +TEST_F(PipedInputTest, ExplicitlyEnabled) { if (!IsTtyAvailable()) { GTEST_SKIP() << "/dev/tty not available in this environment"; } @@ -127,7 +131,6 @@ TEST_F(PipedInputTest, PipedInputDetectionAndRedirection) { TEST_F(PipedInputTest, NormalStdinUnchanged) { // Test that normal stdin (not piped) is not affected auto screen = ScreenInteractive::TerminalOutput(); - screen.HandlePipedInput(true); auto component = Renderer([] { return text("test"); }); // Don't setup piped stdin - use normal stdin @@ -150,7 +153,6 @@ TEST_F(PipedInputTest, MultipleInstallUninstallCycles) { } auto screen = ScreenInteractive::TerminalOutput(); - screen.HandlePipedInput(true); auto component = Renderer([] { return text("test"); }); SetupPipedStdin(); @@ -192,7 +194,6 @@ TEST_F(PipedInputTest, HandlePipedInputMethodBehavior) { // This test simulates environments like containers where /dev/tty might not exist TEST_F(PipedInputTest, GracefulFallbackWhenTtyUnavailable) { auto screen = ScreenInteractive::TerminalOutput(); - screen.HandlePipedInput(true); auto component = Renderer([] { return text("test"); }); SetupPipedStdin();