mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-19 01:38:08 +08:00
Support SIGTSTP and task posting. (#331)
- Add support for SIGTSTP: https://github.com/ArthurSonzogni/FTXUI/issues/330 This - Add support for task posting. This allows folks to defer function execution, and execute it directly below the main loop. The task are executed in a FIFO order.
This commit is contained in:
@@ -151,16 +151,16 @@ extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
|
||||
auto screen =
|
||||
Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height));
|
||||
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
parser.Add(data[i]);
|
||||
}
|
||||
|
||||
Event event;
|
||||
Task event;
|
||||
while (event_receiver->Receive(&event)) {
|
||||
component->OnEvent(event);
|
||||
component->OnEvent(std::get<Event>(event));
|
||||
auto document = component->Render();
|
||||
Render(screen, document);
|
||||
}
|
||||
|
@@ -1,18 +1,20 @@
|
||||
#include <stdio.h> // for fileno, stdin
|
||||
#include <algorithm> // for copy, max, min
|
||||
#include <csignal> // for signal, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
|
||||
#include <cstdlib> // for NULL
|
||||
#include <csignal> // for signal, raise, SIGTSTP, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
|
||||
#include <cstdlib> // for NULL
|
||||
#include <functional> // for function
|
||||
#include <initializer_list> // for initializer_list
|
||||
#include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush
|
||||
#include <stack> // for stack
|
||||
#include <thread> // for thread
|
||||
#include <utility> // for swap, move
|
||||
#include <vector> // for vector
|
||||
#include <type_traits> // for decay_t
|
||||
#include <utility> // for swap, move
|
||||
#include <variant> // for visit
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse
|
||||
#include "ftxui/component/receiver.hpp" // for ReceiverImpl, MakeReceiver, Sender, SenderImpl, Receiver
|
||||
#include "ftxui/component/screen_interactive.hpp"
|
||||
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
|
||||
@@ -32,8 +34,8 @@
|
||||
#endif
|
||||
#else
|
||||
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set
|
||||
#include <termios.h> // for tcsetattr, tcgetattr, cc_t
|
||||
#include <unistd.h> // for STDIN_FILENO, read
|
||||
#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
|
||||
#include <unistd.h> // for STDIN_FILENO, read
|
||||
#endif
|
||||
|
||||
// Quick exit is missing in standard CLang headers
|
||||
@@ -45,6 +47,8 @@ namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
ScreenInteractive* g_active_screen = nullptr;
|
||||
|
||||
void Flush() {
|
||||
// Emscripten doesn't implement flush. We interpret zero as flush.
|
||||
std::cout << '\0' << std::flush;
|
||||
@@ -54,7 +58,7 @@ constexpr int timeout_milliseconds = 20;
|
||||
constexpr int timeout_microseconds = timeout_milliseconds * 1000;
|
||||
#if defined(_WIN32)
|
||||
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
auto console = GetStdHandle(STD_INPUT_HANDLE);
|
||||
auto parser = TerminalInputParser(out->Clone());
|
||||
while (!*quit) {
|
||||
@@ -104,7 +108,7 @@ void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
#include <emscripten.h>
|
||||
|
||||
// Read char from the terminal.
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
(void)timeout_microseconds;
|
||||
auto parser = TerminalInputParser(std::move(out));
|
||||
|
||||
@@ -131,7 +135,7 @@ int CheckStdinReady(int usec_timeout) {
|
||||
}
|
||||
|
||||
// Read char from the terminal.
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
|
||||
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
|
||||
const int buffer_size = 100;
|
||||
|
||||
auto parser = TerminalInputParser(std::move(out));
|
||||
@@ -200,7 +204,7 @@ const std::string DeviceStatusReport(DSRMode ps) {
|
||||
}
|
||||
|
||||
using SignalHandler = void(int);
|
||||
std::stack<ScreenInteractive::Callback> on_exit_functions;
|
||||
std::stack<Closure> on_exit_functions;
|
||||
void OnExit(int signal) {
|
||||
(void)signal;
|
||||
while (!on_exit_functions.empty()) {
|
||||
@@ -211,14 +215,18 @@ void OnExit(int signal) {
|
||||
|
||||
auto install_signal_handler = [](int sig, SignalHandler handler) {
|
||||
auto old_signal_handler = std::signal(sig, handler);
|
||||
on_exit_functions.push([&] { std::signal(sig, old_signal_handler); });
|
||||
on_exit_functions.push([=] { std::signal(sig, old_signal_handler); });
|
||||
};
|
||||
|
||||
ScreenInteractive::Callback on_resize = [] {};
|
||||
Closure on_resize = [] {};
|
||||
void OnResize(int /* signal */) {
|
||||
on_resize();
|
||||
}
|
||||
|
||||
void OnSigStop(int /*signal*/) {
|
||||
ScreenInteractive::Private::SigStop(*g_active_screen);
|
||||
}
|
||||
|
||||
class CapturedMouseImpl : public CapturedMouseInterface {
|
||||
public:
|
||||
CapturedMouseImpl(std::function<void(void)> callback) : callback_(callback) {}
|
||||
@@ -230,8 +238,6 @@ class CapturedMouseImpl : public CapturedMouseInterface {
|
||||
|
||||
} // namespace
|
||||
|
||||
ScreenInteractive* g_active_screen = nullptr;
|
||||
|
||||
ScreenInteractive::ScreenInteractive(int dimx,
|
||||
int dimy,
|
||||
Dimension dimension,
|
||||
@@ -239,8 +245,7 @@ ScreenInteractive::ScreenInteractive(int dimx,
|
||||
: Screen(dimx, dimy),
|
||||
dimension_(dimension),
|
||||
use_alternative_screen_(use_alternative_screen) {
|
||||
event_receiver_ = MakeReceiver<Event>();
|
||||
event_sender_ = event_receiver_->MakeSender();
|
||||
task_receiver_ = MakeReceiver<Task>();
|
||||
}
|
||||
|
||||
// static
|
||||
@@ -263,9 +268,12 @@ ScreenInteractive ScreenInteractive::FitComponent() {
|
||||
return ScreenInteractive(0, 0, Dimension::FitComponent, false);
|
||||
}
|
||||
|
||||
void ScreenInteractive::PostEvent(Event event) {
|
||||
void ScreenInteractive::Post(Task task) {
|
||||
if (!quit_)
|
||||
event_sender_->Send(event);
|
||||
task_sender_->Send(task);
|
||||
}
|
||||
void ScreenInteractive::PostEvent(Event event) {
|
||||
Post(event);
|
||||
}
|
||||
|
||||
CapturedMouse ScreenInteractive::CaptureMouse() {
|
||||
@@ -314,7 +322,7 @@ void ScreenInteractive::Loop(Component component) {
|
||||
/// @brief Decorate a function. It executes the same way, but with the currently
|
||||
/// active screen terminal hooks temporarilly uninstalled during its execution.
|
||||
/// @param fn The function to decorate.
|
||||
ScreenInteractive::Callback ScreenInteractive::WithRestoredIO(Callback fn) {
|
||||
Closure ScreenInteractive::WithRestoredIO(Closure fn) {
|
||||
return [this, fn] {
|
||||
Uninstall();
|
||||
fn();
|
||||
@@ -381,8 +389,11 @@ void ScreenInteractive::Install() {
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
|
||||
|
||||
// Handle resize.
|
||||
on_resize = [&] { event_sender_->Send(Event::Special({0})); };
|
||||
on_resize = [&] { task_sender_->Send(Event::Special({0})); };
|
||||
install_signal_handler(SIGWINCH, OnResize);
|
||||
|
||||
// Handle SIGTSTP/SIGCONT.
|
||||
install_signal_handler(SIGTSTP, OnSigStop);
|
||||
#endif
|
||||
|
||||
auto enable = [&](std::vector<DECMode> parameters) {
|
||||
@@ -418,8 +429,9 @@ void ScreenInteractive::Install() {
|
||||
Flush();
|
||||
|
||||
quit_ = false;
|
||||
task_sender_ = task_receiver_->MakeSender();
|
||||
event_listener_ =
|
||||
std::thread(&EventListener, &quit_, event_receiver_->MakeSender());
|
||||
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
|
||||
}
|
||||
|
||||
void ScreenInteractive::Uninstall() {
|
||||
@@ -431,30 +443,43 @@ void ScreenInteractive::Uninstall() {
|
||||
|
||||
void ScreenInteractive::Main(Component component) {
|
||||
while (!quit_) {
|
||||
if (!event_receiver_->HasPending()) {
|
||||
if (!task_receiver_->HasPending()) {
|
||||
Draw(component);
|
||||
std::cout << ToString() << set_cursor_position;
|
||||
Flush();
|
||||
Clear();
|
||||
}
|
||||
|
||||
Event event;
|
||||
if (!event_receiver_->Receive(&event))
|
||||
Task task;
|
||||
if (!task_receiver_->Receive(&task))
|
||||
break;
|
||||
|
||||
if (event.is_cursor_reporting()) {
|
||||
cursor_x_ = event.cursor_x();
|
||||
cursor_y_ = event.cursor_y();
|
||||
continue;
|
||||
}
|
||||
std::visit(
|
||||
[&](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
|
||||
if (event.is_mouse()) {
|
||||
event.mouse().x -= cursor_x_;
|
||||
event.mouse().y -= cursor_y_;
|
||||
}
|
||||
// Handle Event.
|
||||
if constexpr (std::is_same_v<T, Event>) {
|
||||
if (arg.is_cursor_reporting()) {
|
||||
cursor_x_ = arg.cursor_x();
|
||||
cursor_y_ = arg.cursor_y();
|
||||
return;
|
||||
}
|
||||
|
||||
event.screen_ = this;
|
||||
component->OnEvent(event);
|
||||
if (arg.is_mouse()) {
|
||||
arg.mouse().x -= cursor_x_;
|
||||
arg.mouse().y -= cursor_y_;
|
||||
}
|
||||
|
||||
arg.screen_ = this;
|
||||
component->OnEvent(arg);
|
||||
}
|
||||
|
||||
// Handle callback
|
||||
if constexpr (std::is_same_v<T, Closure>)
|
||||
arg();
|
||||
},
|
||||
task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,13 +562,31 @@ void ScreenInteractive::Draw(Component component) {
|
||||
}
|
||||
}
|
||||
|
||||
ScreenInteractive::Callback ScreenInteractive::ExitLoopClosure() {
|
||||
Closure ScreenInteractive::ExitLoopClosure() {
|
||||
return [this] {
|
||||
quit_ = true;
|
||||
event_sender_.reset();
|
||||
task_sender_.reset();
|
||||
};
|
||||
}
|
||||
|
||||
void ScreenInteractive::SigStop() {
|
||||
#if defined(_WIN32)
|
||||
// Windows do no support SIGTSTP.
|
||||
#else
|
||||
Post([&] {
|
||||
Uninstall();
|
||||
std::cout << reset_cursor_position;
|
||||
reset_cursor_position = "";
|
||||
std::cout << ResetPosition(/*clear=*/true);
|
||||
dimx_ = 0;
|
||||
dimy_ = 0;
|
||||
Flush();
|
||||
std::raise(SIGTSTP);
|
||||
Install();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ftxui.
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
|
@@ -1,15 +1,15 @@
|
||||
#include "ftxui/component/terminal_input_parser.hpp"
|
||||
|
||||
#include <algorithm> // for max
|
||||
#include <cstdint>
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <memory> // for unique_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/event.hpp" // for Event
|
||||
#include "ftxui/component/task.hpp" // for Task
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
TerminalInputParser::TerminalInputParser(Sender<Event> out)
|
||||
TerminalInputParser::TerminalInputParser(Sender<Task> out)
|
||||
: out_(std::move(out)) {}
|
||||
|
||||
void TerminalInputParser::Timeout(int time) {
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#include "ftxui/component/event.hpp" // for Event (ptr only)
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse
|
||||
#include "ftxui/component/receiver.hpp" // for Sender
|
||||
#include "ftxui/component/task.hpp" // for Task
|
||||
|
||||
namespace ftxui {
|
||||
struct Event;
|
||||
@@ -15,7 +16,7 @@ struct Event;
|
||||
// Parse a sequence of |char| accross |time|. Produces |Event|.
|
||||
class TerminalInputParser {
|
||||
public:
|
||||
TerminalInputParser(Sender<Event> out);
|
||||
TerminalInputParser(Sender<Task> out);
|
||||
void Timeout(int time);
|
||||
void Add(char c);
|
||||
|
||||
@@ -57,7 +58,7 @@ class TerminalInputParser {
|
||||
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
|
||||
Output ParseCursorReporting(std::vector<int> arguments);
|
||||
|
||||
Sender<Event> out_;
|
||||
Sender<Task> out_;
|
||||
int position_ = -1;
|
||||
int timeout_ = 0;
|
||||
std::string pending_;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver, TestFactoryImpl
|
||||
#include <algorithm> // for max
|
||||
#include <memory> // for unique_ptr, allocator
|
||||
#include <variant> // for get
|
||||
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Escape
|
||||
#include "ftxui/component/receiver.hpp" // for MakeReceiver, ReceiverImpl
|
||||
@@ -18,61 +19,61 @@ TEST(Event, Character) {
|
||||
for (char c = 'A'; c <= 'Z'; ++c)
|
||||
basic_char.push_back(c);
|
||||
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (char c : basic_char)
|
||||
parser.Add(c);
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
for (char c : basic_char) {
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_character());
|
||||
EXPECT_EQ(c, received.character()[0]);
|
||||
EXPECT_TRUE(std::get<Event>(received).is_character());
|
||||
EXPECT_EQ(c, std::get<Event>(received).character()[0]);
|
||||
}
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, EscapeKeyWithoutWaiting) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, EscapeKeyNotEnoughWait) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
parser.Timeout(49);
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, EscapeKeyEnoughWait) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
parser.Timeout(50);
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_EQ(received, Event::Escape);
|
||||
EXPECT_EQ(std::get<Event>(received), Event::Escape);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, MouseLeftClick) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
@@ -88,17 +89,17 @@ TEST(Event, MouseLeftClick) {
|
||||
parser.Add('M');
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_mouse());
|
||||
EXPECT_EQ(Mouse::Left, received.mouse().button);
|
||||
EXPECT_EQ(12, received.mouse().x);
|
||||
EXPECT_EQ(42, received.mouse().y);
|
||||
EXPECT_TRUE(std::get<Event>(received).is_mouse());
|
||||
EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button);
|
||||
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
|
||||
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, MouseMiddleClick) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
@@ -114,17 +115,17 @@ TEST(Event, MouseMiddleClick) {
|
||||
parser.Add('M');
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_mouse());
|
||||
EXPECT_EQ(Mouse::Middle, received.mouse().button);
|
||||
EXPECT_EQ(12, received.mouse().x);
|
||||
EXPECT_EQ(42, received.mouse().y);
|
||||
EXPECT_TRUE(std::get<Event>(received).is_mouse());
|
||||
EXPECT_EQ(Mouse::Middle, std::get<Event>(received).mouse().button);
|
||||
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
|
||||
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, MouseRightClick) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
@@ -140,12 +141,12 @@ TEST(Event, MouseRightClick) {
|
||||
parser.Add('M');
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_mouse());
|
||||
EXPECT_EQ(Mouse::Right, received.mouse().button);
|
||||
EXPECT_EQ(12, received.mouse().x);
|
||||
EXPECT_EQ(42, received.mouse().y);
|
||||
EXPECT_TRUE(std::get<Event>(received).is_mouse());
|
||||
EXPECT_EQ(Mouse::Right, std::get<Event>(received).mouse().button);
|
||||
EXPECT_EQ(12, std::get<Event>(received).mouse().x);
|
||||
EXPECT_EQ(42, std::get<Event>(received).mouse().y);
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
@@ -216,16 +217,16 @@ TEST(Event, UTF8) {
|
||||
|
||||
};
|
||||
for (auto test : kTestCase) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (auto input : test.input)
|
||||
parser.Add(input);
|
||||
}
|
||||
Event received;
|
||||
Task received;
|
||||
if (test.valid) {
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_character());
|
||||
EXPECT_TRUE(std::get<Event>(received).is_character());
|
||||
}
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
@@ -5,14 +5,14 @@
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const char* data, size_t size) {
|
||||
using namespace ftxui;
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
auto event_receiver = MakeReceiver<Task>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
parser.Add(data[i]);
|
||||
}
|
||||
|
||||
Event received;
|
||||
Task received;
|
||||
while (event_receiver->Receive(&received))
|
||||
;
|
||||
return 0; // Non-zero return values are reserved for future use.
|
||||
|
Reference in New Issue
Block a user