mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-22 02:34:21 +08:00
Restore cursor shape on exit. (#793)
Fixed: https://github.com/ArthurSonzogni/FTXUI/issues/792
This commit is contained in:
parent
bfadcb7165
commit
348c3853d4
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
@ -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;
|
||||
@ -91,7 +96,8 @@ struct Event {
|
||||
Unknown,
|
||||
Character,
|
||||
Mouse,
|
||||
CursorReporting,
|
||||
CursorPosition,
|
||||
CursorShape,
|
||||
};
|
||||
Type type_ = Type::Unknown;
|
||||
|
||||
@ -103,6 +109,7 @@ struct Event {
|
||||
union {
|
||||
struct Mouse mouse;
|
||||
struct Cursor cursor;
|
||||
int cursor_shape;
|
||||
} data_ = {};
|
||||
|
||||
std::string input_;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -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}) {
|
||||
@ -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,
|
||||
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
@ -286,6 +291,7 @@ TerminalInputParser::Output TerminalInputParser::ParseESC() {
|
||||
}
|
||||
}
|
||||
|
||||
// ESC P ... ESC BACKSLASH
|
||||
TerminalInputParser::Output TerminalInputParser::ParseDCS() {
|
||||
// Parse until the string terminator ST.
|
||||
while (true) {
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
@ -45,7 +46,8 @@ class TerminalInputParser {
|
||||
Type type;
|
||||
union {
|
||||
Mouse mouse;
|
||||
CursorReporting cursor;
|
||||
CursorPosition cursor;
|
||||
int cursor_shape;
|
||||
};
|
||||
|
||||
Output(Type t) : type(t) {}
|
||||
@ -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;
|
||||
|
@ -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));
|
||||
@ -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
|
||||
|
@ -8,8 +8,8 @@
|
||||
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Element, operator|=, text, vbox, Elements, border, focus, frame, vscroll_indicator
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Red
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
#include "ftxui/screen/color.hpp" // for Color, Color::Red
|
||||
|
||||
// NOLINTBEGIN
|
||||
namespace ftxui {
|
||||
@ -129,7 +129,6 @@ TEST(ScrollIndicator, BasicVertical) {
|
||||
}
|
||||
|
||||
TEST(ScrollIndicator, VerticalColorable) {
|
||||
|
||||
// The list we generate looks like this
|
||||
// "╭────╮\r\n"
|
||||
// "│0 ┃│\r\n"
|
||||
@ -147,7 +146,6 @@ TEST(ScrollIndicator, VerticalColorable) {
|
||||
}
|
||||
|
||||
TEST(ScrollIndicator, VerticalBackgroundColorable) {
|
||||
|
||||
// The list we generate looks like this
|
||||
// "╭────╮\r\n"
|
||||
// "│0 ┃│\r\n"
|
||||
@ -165,7 +163,6 @@ TEST(ScrollIndicator, VerticalBackgroundColorable) {
|
||||
}
|
||||
|
||||
TEST(ScrollIndicator, VerticalFullColorable) {
|
||||
|
||||
// The list we generate looks like this
|
||||
// "╭────╮\r\n"
|
||||
// "│0 ┃│\r\n"
|
||||
@ -174,7 +171,8 @@ TEST(ScrollIndicator, VerticalFullColorable) {
|
||||
// "│3 │\r\n"
|
||||
// "╰────╯"
|
||||
|
||||
auto element = MakeVerticalList(0, 10) | color(Color::Red) | bgcolor(Color::Red);
|
||||
auto element =
|
||||
MakeVerticalList(0, 10) | color(Color::Red) | bgcolor(Color::Red);
|
||||
Screen screen(6, 6);
|
||||
Render(screen, element);
|
||||
|
||||
@ -233,7 +231,6 @@ TEST(ScrollIndicator, BasicHorizontal) {
|
||||
}
|
||||
|
||||
TEST(ScrollIndicator, HorizontalColorable) {
|
||||
|
||||
// The list we generate looks like this
|
||||
// "╭────╮\r\n"
|
||||
// "│5678│\r\n"
|
||||
@ -249,7 +246,6 @@ TEST(ScrollIndicator, HorizontalColorable) {
|
||||
}
|
||||
|
||||
TEST(ScrollIndicator, HorizontalBackgroundColorable) {
|
||||
|
||||
// The list we generate looks like this
|
||||
// "╭────╮\r\n"
|
||||
// "│5678│\r\n"
|
||||
@ -265,14 +261,14 @@ TEST(ScrollIndicator, HorizontalBackgroundColorable) {
|
||||
}
|
||||
|
||||
TEST(ScrollIndicator, HorizontalFullColorable) {
|
||||
|
||||
// The list we generate looks like this
|
||||
// "╭────╮\r\n"
|
||||
// "│5678│\r\n"
|
||||
// "│ ──│\r\n"
|
||||
// "╰────╯"
|
||||
|
||||
auto element = MakeHorizontalList(6, 10) | color(Color::Red) | bgcolor(Color::Red);
|
||||
auto element =
|
||||
MakeHorizontalList(6, 10) | color(Color::Red) | bgcolor(Color::Red);
|
||||
Screen screen(6, 4);
|
||||
Render(screen, element);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user