From 886cb478120666d1cf0e9ecc8ac86db3a483cc10 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Sat, 27 Apr 2024 15:01:03 +0200 Subject: [PATCH] Support for CTRL Z --- CHANGELOG.md | 4 ++ .../ftxui/component/screen_interactive.hpp | 12 ++++ src/ftxui/component/screen_interactive.cpp | 29 ++++++-- .../component/screen_interactive_test.cpp | 67 +++++++++++++++++++ src/ftxui/component/terminal_input_parser.cpp | 13 +--- 5 files changed, 108 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4be1b2b8..9eb964f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ current (development) ### Component - Feature: Add support for raw input. Allowing more keys to be detected. +- Feature: Add `ScreenInteractive::ForceHandleCtrlC(false)` to allow component + to fully override the default `Ctrl+C` handler. +- Feature: Add `ScreenInteractive::ForceHandleCtrlZ(false)` to allow component + to fully override the default `Ctrl+Z` handler. - Feature: Add `Mouse::WeelLeft` and `Mouse::WeelRight` events on supported terminals. - Feature: Add `Event::DebugString()`. diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index cb4c189c6..e13119796 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -59,6 +59,15 @@ class ScreenInteractive : public Screen { // temporarily uninstalled. Closure WithRestoredIO(Closure); + // FTXUI implements handlers for Ctrl-C and Ctrl-Z. By default, these handlers + // are executed, even if the component catches the event. This avoid users + // handling every event to be trapped in the application. However, in some + // cases, the application may want to handle these events itself. In this case, + // the application can force FTXUI to not handle these events by calling the + // following functions with force=true. + void ForceHandleCtrlC(bool force); + void ForceHandleCtrlZ(bool force); + private: void ExitNow(); @@ -114,6 +123,9 @@ class ScreenInteractive : public Screen { bool frame_valid_ = false; + bool force_handle_ctrl_c_ = true; + bool force_handle_ctrl_z_ = true; + // 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 d4a00557b..320d67875 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -559,6 +559,19 @@ Closure ScreenInteractive::WithRestoredIO(Closure fn) { // NOLINT }; } + +/// @brief Force FTXUI to handle or not handle Ctrl-C, even if the component +/// catches the Event::CtrlC. +void ScreenInteractive::ForceHandleCtrlC(bool force) { + force_handle_ctrl_c_ = force; +} + +/// @brief Force FTXUI to handle or not handle Ctrl-Z, even if the component +/// catches the Event::CtrlZ. +void ScreenInteractive::ForceHandleCtrlZ(bool force) { + force_handle_ctrl_z_ = force; +} + /// @brief Return the currently active screen, or null if none. // static ScreenInteractive* ScreenInteractive::Active() { @@ -657,7 +670,7 @@ void ScreenInteractive::Install() { // - C-C => INTR // - C-d => QUIT terminal.c_lflag &= ~IEXTEN; // Disable extended input processing - terminal.c_cflag |= (CS8); // 8 bits per byte + terminal.c_cflag |= CS8; // 8 bits per byte terminal.c_cc[VMIN] = 0; // Minimum number of characters for non-canonical // read. @@ -765,14 +778,16 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { const bool handled = component->OnEvent(arg); - if (arg == Event::CtrlC) { - Exit(); - } - - if (arg == Event::CtrlZ) { - // How to handle SIGTSTP manually? + if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) { + RecordSignal(SIGABRT); } +#if !defined(_WIN32) + if (arg == Event::CtrlZ && (!handled || force_handle_ctrl_z_)) { + RecordSignal(SIGTSTP); + } +#endif + frame_valid_ = false; return; } diff --git a/src/ftxui/component/screen_interactive_test.cpp b/src/ftxui/component/screen_interactive_test.cpp index 3a80c1370..73562fe1b 100644 --- a/src/ftxui/component/screen_interactive_test.cpp +++ b/src/ftxui/component/screen_interactive_test.cpp @@ -64,4 +64,71 @@ TEST(ScreenInteractive, PostTaskToNonActive) { screen.Post([] {}); } +TEST(ScreenInteractive, CtrlC) { + auto screen = ScreenInteractive::FitComponent(); + bool called = false; + auto component = Renderer([&] { + if (!called) { + called = true; + screen.PostEvent(Event::CtrlC); + } + return text(""); + }); + screen.Loop(component); +} + +TEST(ScreenInteractive, CtrlC_Forced) { + auto screen = ScreenInteractive::FitComponent(); + screen.ForceHandleCtrlC(true); + auto component = Renderer([&] { + screen.PostEvent(Event::CtrlC); + return text(""); + }); + + int ctrl_c_count = 0; + component |= CatchEvent([&](Event event) { + if (event != Event::CtrlC) { + return false; + } + + ++ctrl_c_count; + + if (ctrl_c_count == 100) { + return false; + } + + return true; + }); + screen.Loop(component); + + ASSERT_LE(ctrl_c_count, 50); +} + +TEST(ScreenInteractive, CtrlC_NotForced) { + auto screen = ScreenInteractive::FitComponent(); + screen.ForceHandleCtrlC(false); + auto component = Renderer([&] { + screen.PostEvent(Event::CtrlC); + return text(""); + }); + + int ctrl_c_count = 0; + component |= CatchEvent([&](Event event) { + if (event != Event::CtrlC) { + return false; + } + + ++ctrl_c_count; + + if (ctrl_c_count == 100) { + return false; + } + + return true; + }); + screen.Loop(component); + + ASSERT_GE(ctrl_c_count, 50); +} + } // namespace ftxui diff --git a/src/ftxui/component/terminal_input_parser.cpp b/src/ftxui/component/terminal_input_parser.cpp index 316fec7fc..d93ba49a8 100644 --- a/src/ftxui/component/terminal_input_parser.cpp +++ b/src/ftxui/component/terminal_input_parser.cpp @@ -169,16 +169,9 @@ TerminalInputParser::Output TerminalInputParser::Parse() { if (!Eat()) { return UNCOMPLETED; } - - switch (Current()) { - case 24: // CAN NOLINT - case 26: // SUB NOLINT - return DROP; - - case '\x1B': - return ParseESC(); - default: - break; + + if (Current() == '\x1B') { + return ParseESC(); } if (Current() < 32) { // C0 NOLINT