From 809dc0c7db45909d0a17e1a2c7acc30480c5bb1a Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Sat, 16 Dec 2023 11:35:39 +0100 Subject: [PATCH] Restore cursor shape on exit. Fixed: https://github.com/ArthurSonzogni/FTXUI/issues/792 --- examples/component/print_key_press.cpp | 3 +++ include/ftxui/component/event.hpp | 7 +++++ .../ftxui/component/screen_interactive.hpp | 3 +++ src/ftxui/component/event.cpp | 9 +++++++ src/ftxui/component/screen_interactive.cpp | 26 +++++++++++++++++-- src/ftxui/component/terminal_input_parser.cpp | 10 ++++++- src/ftxui/component/terminal_input_parser.hpp | 4 ++- .../component/terminal_input_parser_test.cpp | 26 ++++++++++++++++++- 8 files changed, 83 insertions(+), 5 deletions(-) diff --git a/examples/component/print_key_press.cpp b/examples/component/print_key_press.cpp index c48127197..bf7baa10c 100644 --- a/examples/component/print_key_press.cpp +++ b/examples/component/print_key_press.cpp @@ -66,6 +66,9 @@ std::string Stringify(Event event) { out += "(" + // std::to_string(event.mouse().x) + "," + std::to_string(event.mouse().y) + ")"; + } else if (event.is_device_control_string()) { + out += + "Event::DeviceControlString(\"" + event.device_control_string() + "\")"; } else if (event == Event::ArrowLeft) { out += "Event::ArrowLeft"; } else if (event == Event::ArrowRight) { diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index b48162259..f5887d113 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -34,6 +34,7 @@ struct Event { static Event Special(std::string); static Event Mouse(std::string, Mouse mouse); static Event CursorReporting(std::string, int x, int y); + static Event DeviceControlString(std::string); // --- Arrow --- static const Event ArrowLeft; @@ -76,6 +77,11 @@ struct Event { int cursor_x() const { return data_.cursor.x; } int cursor_y() const { return data_.cursor.y; } + bool is_device_control_string() const { + return type_ == Type::DeviceControlString; + } + const std::string& device_control_string() const { return input_; } + const std::string& input() const { return input_; } bool operator==(const Event& other) const { return input_ == other.input_; } @@ -92,6 +98,7 @@ struct Event { Character, Mouse, CursorReporting, + DeviceControlString, }; Type type_ = Type::Unknown; diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 7629f031b..909761cbc 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -114,6 +114,9 @@ class ScreenInteractive : public Screen { bool frame_valid_ = false; + // The style of the cursor to restore on exit. + std::string cursor_blinking_reset_ = "1 q"; + Mouse latest_mouse_event_; friend class Loop; diff --git a/src/ftxui/component/event.cpp b/src/ftxui/component/event.cpp index 72a57f135..7a571505e 100644 --- a/src/ftxui/component/event.cpp +++ b/src/ftxui/component/event.cpp @@ -49,6 +49,15 @@ Event Event::Mouse(std::string input, struct Mouse mouse) { return event; } +/// @brief An event corresponding to a terminal DCS (Device Control String). +// static +Event Event::DeviceControlString(std::string input) { + Event event; + event.input_ = std::move(input); + event.type_ = Type::DeviceControlString; + return event; +} + /// @brief An custom event whose meaning is defined by the user of the library. /// @param input An arbitrary sequence of character defined by the developer. /// @ingroup component. diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index a8156b889..d4b02566f 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -253,7 +253,17 @@ void InstallSignalHandler(int sig) { [=] { std::ignore = std::signal(sig, old_signal_handler); }); } +// CSI: Control Sequence Introducer const std::string CSI = "\x1b["; // NOLINT + // +// DCS: Device Control String +const std::string DCS = "\x1bP"; // NOLINT +// ST: String Terminator +const std::string ST = "\x1b\\"; // NOLINT + +// DECRQSS: Request Status String +// DECSCUSR: Set Cursor Style +const std::string DECRQSS_DECSCUSR = DCS + "$q q" + ST; // NOLINT // DEC: Digital Equipment Corporation enum class DECMode { @@ -640,9 +650,12 @@ void ScreenInteractive::Install() { }); } + // Request the terminal to report the current cursor blinking effect. We will + // restore it on exit. + std::cout << DECRQSS_DECSCUSR; on_exit_functions.push([=] { std::cout << "\033[?25h"; // Enable cursor. - std::cout << "\033[?1 q"; // Cursor block blinking. + std::cout << "\033[?" + cursor_blinking_reset_; }); disable({ @@ -700,10 +713,10 @@ void ScreenInteractive::RunOnce(Component component) { // private void ScreenInteractive::HandleTask(Component component, Task& task) { - // clang-format off std::visit([&](auto&& arg) { using T = std::decay_t; + // clang-format off // Handle Event. if constexpr (std::is_same_v) { if (arg.is_cursor_reporting()) { @@ -712,6 +725,15 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { return; } + if (arg.is_device_control_string()) { + if (arg.device_control_string().size() == 10 && + arg.device_control_string()[6] == ' ' && + arg.device_control_string()[7] == 'q') { + cursor_blinking_reset_ = arg.device_control_string().substr(5, 3); + } + return; + } + if (arg.is_mouse()) { arg.mouse().x -= cursor_x_; arg.mouse().y -= cursor_y_; diff --git a/src/ftxui/component/terminal_input_parser.cpp b/src/ftxui/component/terminal_input_parser.cpp index 3ba0e69d5..569f66a94 100644 --- a/src/ftxui/component/terminal_input_parser.cpp +++ b/src/ftxui/component/terminal_input_parser.cpp @@ -135,6 +135,14 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) { pending_.clear(); return; + case DEVICE_CONTROL_STRING: + // Valid requests + if (pending_[2] == '1' && pending_.size() >= 7) { + out_->Send(Event::DeviceControlString(std::move(pending_))); + } + pending_.clear(); + return; + case SPECIAL: { auto it = g_uniformize.find(pending_); if (it != g_uniformize.end()) { @@ -305,7 +313,7 @@ TerminalInputParser::Output TerminalInputParser::ParseDCS() { continue; } - return SPECIAL; + return DEVICE_CONTROL_STRING; } } diff --git a/src/ftxui/component/terminal_input_parser.hpp b/src/ftxui/component/terminal_input_parser.hpp index 5a808c56d..ba31a1613 100644 --- a/src/ftxui/component/terminal_input_parser.hpp +++ b/src/ftxui/component/terminal_input_parser.hpp @@ -31,9 +31,10 @@ class TerminalInputParser { UNCOMPLETED, DROP, CHARACTER, - SPECIAL, + DEVICE_CONTROL_STRING, MOUSE, CURSOR_REPORTING, + SPECIAL, }; struct CursorReporting { @@ -56,6 +57,7 @@ class TerminalInputParser { Output ParseUTF8(); Output ParseESC(); Output ParseDCS(); + Output ParseDECRQSS(); Output ParseCSI(); Output ParseOSC(); Output ParseMouse(bool altered, bool pressed, std::vector arguments); diff --git a/src/ftxui/component/terminal_input_parser_test.cpp b/src/ftxui/component/terminal_input_parser_test.cpp index 971c08674..bc480e833 100644 --- a/src/ftxui/component/terminal_input_parser_test.cpp +++ b/src/ftxui/component/terminal_input_parser_test.cpp @@ -446,5 +446,29 @@ TEST(Event, Special) { } } +TEST(Event, DeviceControlString) { + auto event_receiver = MakeReceiver(); + { + auto parser = TerminalInputParser(event_receiver->MakeSender()); + parser.Add(27); // ESC + parser.Add(80); // P + parser.Add(49); // 1 + parser.Add(36); // $ + parser.Add(114); // r + parser.Add(49); // 1 + parser.Add(32); // SP + parser.Add(113); // q + parser.Add(27); // ESC + parser.Add(92); // (backslash) + } + + Task received; + EXPECT_TRUE(event_receiver->Receive(&received)); + EXPECT_TRUE(std::get(received).is_device_control_string()); + EXPECT_EQ("\x1BP1$r1 q\x1B\\", + std::get(received).device_control_string()); + EXPECT_FALSE(event_receiver->Receive(&received)); +} + } // namespace ftxui -// NOLINTEND + // NOLINTEND