diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c4f1624..8e14fe28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ unreleased (development) #### Component - Add the `collapsible` component. +- Add the `ScreenInteractive::WithRestoredIO`. This decorates a callback. This + runs it with the terminal hooks temporarilly uninstalled. This is useful if + you want to execute command using directly stdin/stdout/sterr. ### Bug diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 848c55a5..6267d729 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -31,3 +31,4 @@ example(slider_rgb) example(tab_horizontal) example(tab_vertical) example(toggle) +example(with_restored_io) diff --git a/examples/component/with_restored_io.cpp b/examples/component/with_restored_io.cpp new file mode 100644 index 00000000..938059bf --- /dev/null +++ b/examples/component/with_restored_io.cpp @@ -0,0 +1,55 @@ +#include "ftxui/component/component.hpp" // for Menu +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive + +int main() { + using namespace ftxui; + + auto screen = ScreenInteractive::Fullscreen(); + + // When pressing this button, "screen.WithRestoredIO" will execute the + // temporarily uninstall the terminal hook and execute the provided callback + // function. This allow running the application in a non-interactive mode. + auto btn_run = Button("Execute with restored IO", screen.WithRestoredIO([] { + std::system("bash"); + std::cout << "This is a child program using stdin/stdout." << std::endl; + for (int i = 0; i < 10; ++i) { + std::cout << "Please enter 10 strings (" << i << "/10)" << std::flush; + std::string input; + std::getline(std::cin, input); + } + std::system("bash"); + })); + + auto btn_quit = Button("Quit", screen.ExitLoopClosure()); + + auto layout = Container::Horizontal({ + btn_run, + btn_quit, + }); + + auto renderer = Renderer(layout, [&] { + auto explanation = paragraph( + "After clicking this button, the ScreenInteractive will be " + "suspended and access to stdin/stdout will temporarilly be " + "restore for running a function."); + auto element = vbox({ + explanation | borderEmpty, + hbox({ + btn_run->Render(), + filler(), + btn_quit->Render(), + }), + }); + + element = element | borderEmpty | border | size(WIDTH, LESS_THAN, 80) | + size(HEIGHT, LESS_THAN, 20) | center; + return element; + }); + + screen.Loop(renderer); + return EXIT_SUCCESS; +} + +// Copyright 2022 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 6d3d6056..0451b21d 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -20,20 +20,28 @@ using Component = std::shared_ptr; class ScreenInteractive : public Screen { public: + using Callback = std::function; + static ScreenInteractive FixedSize(int dimx, int dimy); static ScreenInteractive Fullscreen(); static ScreenInteractive FitComponent(); static ScreenInteractive TerminalOutput(); void Loop(Component); - std::function ExitLoopClosure(); + Callback ExitLoopClosure(); void PostEvent(Event event); CapturedMouse CaptureMouse(); + // Decorate a function. The outputted one will execute similarly to the + // inputted one, but with the currently active screen terminal hooks + // temporarily uninstalled. + Callback WithRestoredIO(Callback); + private: void Install(); void Uninstall(); + void Main(Component component); ScreenInteractive* suspended_screen_ = nullptr; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 439b0824..f0436908 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -200,7 +200,7 @@ const std::string DeviceStatusReport(DSRMode ps) { } using SignalHandler = void(int); -std::stack> on_exit_functions; +std::stack on_exit_functions; void OnExit(int signal) { (void)signal; while (!on_exit_functions.empty()) { @@ -211,10 +211,10 @@ void OnExit(int signal) { auto install_signal_handler = [](int sig, SignalHandler handler) { auto old_signal_handler = std::signal(sig, handler); - on_exit_functions.push([&]() { std::signal(sig, old_signal_handler); }); + on_exit_functions.push([&] { std::signal(sig, old_signal_handler); }); }; -std::function on_resize = [] {}; +ScreenInteractive::Callback on_resize = [] {}; void OnResize(int /* signal */) { on_resize(); } @@ -230,6 +230,8 @@ class CapturedMouseImpl : public CapturedMouseInterface { } // namespace +ScreenInteractive* g_active_screen = nullptr; + ScreenInteractive::ScreenInteractive(int dimx, int dimy, Dimension dimension, @@ -275,7 +277,6 @@ CapturedMouse ScreenInteractive::CaptureMouse() { } void ScreenInteractive::Loop(Component component) { - static ScreenInteractive* g_active_screen = nullptr; // Suspend previously active screen: if (g_active_screen) { @@ -311,7 +312,22 @@ void ScreenInteractive::Loop(Component component) { } } +/// @brief Decorate a function. It executes the same way, but with the currently +/// active screen terminal hooks temporarilly uninstalled during its execution. +/// @param fn The function to decorate. +ScreenInteractive::Callback ScreenInteractive::WithRestoredIO(Callback fn) { + return [this, fn] { + Uninstall(); + fn(); + Install(); + }; +} + void ScreenInteractive::Install() { + // After uninstalling the new configuration, flush it to the terminal to + // ensure it is fully applied: + on_exit_functions.push([] { Flush(); }); + on_exit_functions.push([this] { ExitLoopClosure()(); }); // Install signal handlers to restore the terminal state on exit. The default @@ -370,12 +386,6 @@ void ScreenInteractive::Install() { install_signal_handler(SIGWINCH, OnResize); #endif - // Commit state: - auto flush = [&] { - Flush(); - on_exit_functions.push([] { Flush(); }); - }; - auto enable = [&](std::vector parameters) { std::cout << Set(parameters); on_exit_functions.push([=] { std::cout << Reset(parameters); }); @@ -404,7 +414,9 @@ void ScreenInteractive::Install() { DECMode::kMouseSgrExtMode, }); - flush(); + // After installing the new configuration, flush it to the terminal to ensure + // it is fully applied: + Flush(); quit_ = false; event_listener_ = @@ -526,8 +538,8 @@ void ScreenInteractive::Draw(Component component) { } } -std::function ScreenInteractive::ExitLoopClosure() { - return [this]() { +ScreenInteractive::Callback ScreenInteractive::ExitLoopClosure() { + return [this] { quit_ = true; event_sender_.reset(); };