Introduce WithRestoredIO (#307)

This function allow running a callback with the terminal hooks
temporarily uninstalled.

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
Vladislav Nepogodin 2022-01-19 16:38:39 +04:00 committed by GitHub
parent cd82fccde7
commit b4a655ec65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 14 deletions

View File

@ -24,6 +24,9 @@ unreleased (development)
#### Component #### Component
- Add the `collapsible` 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 ### Bug

View File

@ -31,3 +31,4 @@ example(slider_rgb)
example(tab_horizontal) example(tab_horizontal)
example(tab_vertical) example(tab_vertical)
example(toggle) example(toggle)
example(with_restored_io)

View File

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

View File

@ -20,20 +20,28 @@ using Component = std::shared_ptr<ComponentBase>;
class ScreenInteractive : public Screen { class ScreenInteractive : public Screen {
public: public:
using Callback = std::function<void()>;
static ScreenInteractive FixedSize(int dimx, int dimy); static ScreenInteractive FixedSize(int dimx, int dimy);
static ScreenInteractive Fullscreen(); static ScreenInteractive Fullscreen();
static ScreenInteractive FitComponent(); static ScreenInteractive FitComponent();
static ScreenInteractive TerminalOutput(); static ScreenInteractive TerminalOutput();
void Loop(Component); void Loop(Component);
std::function<void()> ExitLoopClosure(); Callback ExitLoopClosure();
void PostEvent(Event event); void PostEvent(Event event);
CapturedMouse CaptureMouse(); 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: private:
void Install(); void Install();
void Uninstall(); void Uninstall();
void Main(Component component); void Main(Component component);
ScreenInteractive* suspended_screen_ = nullptr; ScreenInteractive* suspended_screen_ = nullptr;

View File

@ -200,7 +200,7 @@ const std::string DeviceStatusReport(DSRMode ps) {
} }
using SignalHandler = void(int); using SignalHandler = void(int);
std::stack<std::function<void()>> on_exit_functions; std::stack<ScreenInteractive::Callback> on_exit_functions;
void OnExit(int signal) { void OnExit(int signal) {
(void)signal; (void)signal;
while (!on_exit_functions.empty()) { while (!on_exit_functions.empty()) {
@ -211,10 +211,10 @@ void OnExit(int signal) {
auto install_signal_handler = [](int sig, SignalHandler handler) { auto install_signal_handler = [](int sig, SignalHandler handler) {
auto old_signal_handler = std::signal(sig, 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<void()> on_resize = [] {}; ScreenInteractive::Callback on_resize = [] {};
void OnResize(int /* signal */) { void OnResize(int /* signal */) {
on_resize(); on_resize();
} }
@ -230,6 +230,8 @@ class CapturedMouseImpl : public CapturedMouseInterface {
} // namespace } // namespace
ScreenInteractive* g_active_screen = nullptr;
ScreenInteractive::ScreenInteractive(int dimx, ScreenInteractive::ScreenInteractive(int dimx,
int dimy, int dimy,
Dimension dimension, Dimension dimension,
@ -275,7 +277,6 @@ CapturedMouse ScreenInteractive::CaptureMouse() {
} }
void ScreenInteractive::Loop(Component component) { void ScreenInteractive::Loop(Component component) {
static ScreenInteractive* g_active_screen = nullptr;
// Suspend previously active screen: // Suspend previously active screen:
if (g_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() { 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()(); }); on_exit_functions.push([this] { ExitLoopClosure()(); });
// Install signal handlers to restore the terminal state on exit. The default // Install signal handlers to restore the terminal state on exit. The default
@ -370,12 +386,6 @@ void ScreenInteractive::Install() {
install_signal_handler(SIGWINCH, OnResize); install_signal_handler(SIGWINCH, OnResize);
#endif #endif
// Commit state:
auto flush = [&] {
Flush();
on_exit_functions.push([] { Flush(); });
};
auto enable = [&](std::vector<DECMode> parameters) { auto enable = [&](std::vector<DECMode> parameters) {
std::cout << Set(parameters); std::cout << Set(parameters);
on_exit_functions.push([=] { std::cout << Reset(parameters); }); on_exit_functions.push([=] { std::cout << Reset(parameters); });
@ -404,7 +414,9 @@ void ScreenInteractive::Install() {
DECMode::kMouseSgrExtMode, DECMode::kMouseSgrExtMode,
}); });
flush(); // After installing the new configuration, flush it to the terminal to ensure
// it is fully applied:
Flush();
quit_ = false; quit_ = false;
event_listener_ = event_listener_ =
@ -526,8 +538,8 @@ void ScreenInteractive::Draw(Component component) {
} }
} }
std::function<void()> ScreenInteractive::ExitLoopClosure() { ScreenInteractive::Callback ScreenInteractive::ExitLoopClosure() {
return [this]() { return [this] {
quit_ = true; quit_ = true;
event_sender_.reset(); event_sender_.reset();
}; };