Skip to content

Commit

Permalink
Restore cursor shape on exit. (#793)
Browse files Browse the repository at this point in the history
Fixed: #792
  • Loading branch information
ArthurSonzogni authored Dec 17, 2023
1 parent bfadcb7 commit 348c385
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 41 deletions.
1 change: 1 addition & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Checks: "*,
-android-*,
-bugprone-easily-swappable-parameters,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-pro-type-union-access,
-fuchsia-*,
-google-*,
-hicpp-signed-bitwise,
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ current (development)
alternate screen.
- Bugfix: `Input` `onchange` was not called on backspace or delete key.
Fixed by @chrysante in chrysante in PR #776.
- Bugfix: Propertly restore cursor shape on exit. See #792.
### Dom
- Feature: Add `hscroll_indicator`. It display an horizontal indicator
Expand Down
21 changes: 14 additions & 7 deletions include/ftxui/component/event.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ struct Event {
static Event Character(wchar_t);
static Event Special(std::string);
static Event Mouse(std::string, Mouse mouse);
static Event CursorReporting(std::string, int x, int y);
static Event CursorPosition(std::string, int x, int y); // Internal
static Event CursorShape(std::string, int shape); // Internal

// --- Arrow ---
static const Event ArrowLeft;
Expand Down Expand Up @@ -66,20 +67,24 @@ struct Event {
static const Event Custom;

//--- Method section ---------------------------------------------------------
bool operator==(const Event& other) const { return input_ == other.input_; }
bool operator!=(const Event& other) const { return !operator==(other); }

const std::string& input() const { return input_; }

bool is_character() const { return type_ == Type::Character; }
std::string character() const { return input_; }

bool is_mouse() const { return type_ == Type::Mouse; }
struct Mouse& mouse() { return data_.mouse; }

bool is_cursor_reporting() const { return type_ == Type::CursorReporting; }
// --- Internal Method section -----------------------------------------------
bool is_cursor_position() const { return type_ == Type::CursorPosition; }
int cursor_x() const { return data_.cursor.x; }
int cursor_y() const { return data_.cursor.y; }

const std::string& input() const { return input_; }

bool operator==(const Event& other) const { return input_ == other.input_; }
bool operator!=(const Event& other) const { return !operator==(other); }
bool is_cursor_shape() const { return type_ == Type::CursorShape; }
int cursor_shape() const { return data_.cursor_shape; }

//--- State section ----------------------------------------------------------
ScreenInteractive* screen_ = nullptr;
Expand All @@ -91,7 +96,8 @@ struct Event {
Unknown,
Character,
Mouse,
CursorReporting,
CursorPosition,
CursorShape,
};
Type type_ = Type::Unknown;

Expand All @@ -103,6 +109,7 @@ struct Event {
union {
struct Mouse mouse;
struct Cursor cursor;
int cursor_shape;
} data_ = {};

std::string input_;
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.
int cursor_reset_shape_ = 1;

Mouse latest_mouse_event_;
friend class Loop;

Expand Down
14 changes: 12 additions & 2 deletions src/ftxui/component/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ 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::CursorShape(std::string input, int shape) {
Event event;
event.input_ = std::move(input);
event.type_ = Type::CursorShape;
event.data_.cursor_shape = shape; // NOLINT
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 All @@ -61,10 +71,10 @@ Event Event::Special(std::string input) {

/// @internal
// static
Event Event::CursorReporting(std::string input, int x, int y) {
Event Event::CursorPosition(std::string input, int x, int y) {
Event event;
event.input_ = std::move(input);
event.type_ = Type::CursorReporting;
event.type_ = Type::CursorPosition;
event.data_.cursor = {x, y}; // NOLINT
return event;
}
Expand Down
37 changes: 28 additions & 9 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 @@ -566,6 +576,14 @@ void ScreenInteractive::Install() {

on_exit_functions.push([this] { ExitLoopClosure()(); });

// Request the terminal to report the current cursor shape. We will restore it
// on exit.
std::cout << DECRQSS_DECSCUSR;
on_exit_functions.push([=] {
std::cout << "\033[?25h"; // Enable cursor.
std::cout << "\033[" + std::to_string(cursor_reset_shape_) + " q";
});

// Install signal handlers to restore the terminal state on exit. The default
// signal handlers are restored on exit.
for (const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
Expand Down Expand Up @@ -640,11 +658,6 @@ void ScreenInteractive::Install() {
});
}

on_exit_functions.push([=] {
std::cout << "\033[?25h"; // Enable cursor.
std::cout << "\033[?1 q"; // Cursor block blinking.
});

disable({
// DECMode::kCursor,
DECMode::kLineWrap,
Expand Down Expand Up @@ -700,18 +713,24 @@ 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)>;
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()) {
if (arg.is_cursor_position()) {
cursor_x_ = arg.cursor_x();
cursor_y_ = arg.cursor_y();
return;
}

if (arg.is_cursor_shape()) {
cursor_reset_shape_= arg.cursor_shape();
return;
}

if (arg.is_mouse()) {
arg.mouse().x -= cursor_x_;
arg.mouse().y -= cursor_y_;
Expand Down
30 changes: 23 additions & 7 deletions src/ftxui/component/terminal_input_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,15 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
pending_.clear();
return;

case CURSOR_REPORTING:
out_->Send(Event::CursorReporting(std::move(pending_), // NOLINT
output.cursor.x, // NOLINT
output.cursor.y)); // NOLINT
case CURSOR_POSITION:
out_->Send(Event::CursorPosition(std::move(pending_), // NOLINT
output.cursor.x, // NOLINT
output.cursor.y)); // NOLINT
pending_.clear();
return;

case CURSOR_SHAPE:
out_->Send(Event::CursorShape(std::move(pending_), output.cursor_shape));
pending_.clear();
return;
}
Expand Down Expand Up @@ -286,6 +291,7 @@ TerminalInputParser::Output TerminalInputParser::ParseESC() {
}
}

// ESC P ... ESC BACKSLASH
TerminalInputParser::Output TerminalInputParser::ParseDCS() {
// Parse until the string terminator ST.
while (true) {
Expand All @@ -305,6 +311,16 @@ TerminalInputParser::Output TerminalInputParser::ParseDCS() {
continue;
}

if (pending_.size() == 10 && //
pending_[2] == '1' && //
pending_[3] == '$' && //
pending_[4] == 'r' && //
true) {
Output output(CURSOR_SHAPE);
output.cursor_shape = pending_[5] - '0';
return output;
}

return SPECIAL;
}
}
Expand Down Expand Up @@ -351,7 +367,7 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
case 'm':
return ParseMouse(altered, false, std::move(arguments));
case 'R':
return ParseCursorReporting(std::move(arguments));
return ParseCursorPosition(std::move(arguments));
default:
return SPECIAL;
}
Expand Down Expand Up @@ -405,12 +421,12 @@ TerminalInputParser::Output TerminalInputParser::ParseMouse( // NOLINT
}

// NOLINTNEXTLINE
TerminalInputParser::Output TerminalInputParser::ParseCursorReporting(
TerminalInputParser::Output TerminalInputParser::ParseCursorPosition(
std::vector<int> arguments) {
if (arguments.size() != 2) {
return SPECIAL;
}
Output output(CURSOR_REPORTING);
Output output(CURSOR_POSITION);
output.cursor.y = arguments[0]; // NOLINT
output.cursor.x = arguments[1]; // NOLINT
return output;
Expand Down
12 changes: 7 additions & 5 deletions src/ftxui/component/terminal_input_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ class TerminalInputParser {
UNCOMPLETED,
DROP,
CHARACTER,
SPECIAL,
MOUSE,
CURSOR_REPORTING,
CURSOR_POSITION,
CURSOR_SHAPE,
SPECIAL,
};

struct CursorReporting {
struct CursorPosition {
int x;
int y;
};
Expand All @@ -45,7 +46,8 @@ class TerminalInputParser {
Type type;
union {
Mouse mouse;
CursorReporting cursor;
CursorPosition cursor;
int cursor_shape;
};

Output(Type t) : type(t) {}
Expand All @@ -59,7 +61,7 @@ class TerminalInputParser {
Output ParseCSI();
Output ParseOSC();
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
Output ParseCursorReporting(std::vector<int> arguments);
Output ParseCursorPosition(std::vector<int> arguments);

Sender<Task> out_;
int position_ = -1;
Expand Down
27 changes: 25 additions & 2 deletions src/ftxui/component/terminal_input_parser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ TEST(Event, MouseReporting) {

Task received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(std::get<Event>(received).is_cursor_reporting());
EXPECT_TRUE(std::get<Event>(received).is_cursor_position());
EXPECT_EQ(42, std::get<Event>(received).cursor_x());
EXPECT_EQ(12, std::get<Event>(received).cursor_y());
EXPECT_FALSE(event_receiver->Receive(&received));
Expand Down Expand Up @@ -446,5 +446,28 @@ 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_cursor_shape());
EXPECT_EQ(1, std::get<Event>(received).cursor_shape());
EXPECT_FALSE(event_receiver->Receive(&received));
}

} // namespace ftxui
// NOLINTEND
// NOLINTEND
Loading

0 comments on commit 348c385

Please sign in to comment.