Skip to content

Commit

Permalink
Restore cursor shape on exit.
Browse files Browse the repository at this point in the history
Fixed: #792
  • Loading branch information
ArthurSonzogni committed Dec 16, 2023
1 parent bfadcb7 commit 809dc0c
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 5 deletions.
3 changes: 3 additions & 0 deletions examples/component/print_key_press.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions include/ftxui/component/event.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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_; }
Expand All @@ -92,6 +98,7 @@ struct Event {
Character,
Mouse,
CursorReporting,
DeviceControlString,
};
Type type_ = Type::Unknown;

Expand Down
3 changes: 3 additions & 0 deletions include/ftxui/component/screen_interactive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
9 changes: 9 additions & 0 deletions src/ftxui/component/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
26 changes: 24 additions & 2 deletions src/ftxui/component/screen_interactive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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<decltype(arg)>;

// clang-format off
// Handle Event.
if constexpr (std::is_same_v<T, Event>) {
if (arg.is_cursor_reporting()) {
Expand All @@ -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_;
Expand Down
10 changes: 9 additions & 1 deletion src/ftxui/component/terminal_input_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -305,7 +313,7 @@ TerminalInputParser::Output TerminalInputParser::ParseDCS() {
continue;
}

return SPECIAL;
return DEVICE_CONTROL_STRING;
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/ftxui/component/terminal_input_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ class TerminalInputParser {
UNCOMPLETED,
DROP,
CHARACTER,
SPECIAL,
DEVICE_CONTROL_STRING,
MOUSE,
CURSOR_REPORTING,
SPECIAL,
};

struct CursorReporting {
Expand All @@ -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<int> arguments);
Expand Down
26 changes: 25 additions & 1 deletion src/ftxui/component/terminal_input_parser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,5 +446,29 @@ TEST(Event, Special) {
}
}

TEST(Event, DeviceControlString) {
auto event_receiver = MakeReceiver<Task>();
{
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<Event>(received).is_device_control_string());
EXPECT_EQ("\x1BP1$r1 q\x1B\\",
std::get<Event>(received).device_control_string());
EXPECT_FALSE(event_receiver->Receive(&received));
}

} // namespace ftxui
// NOLINTEND
// NOLINTEND

0 comments on commit 809dc0c

Please sign in to comment.