12 #include <initializer_list>
17 #include <type_traits>
35 #define DEFINE_CONSOLEV2_PROPERTIES
36 #define WIN32_LEAN_AND_MEAN
42 #error Must be compiled in UNICODE mode
45 #include <sys/select.h>
51 #if defined(__clang__) && defined(__APPLE__)
52 #define quick_exit(a) exit(a)
61 screen->RequestAnimationFrame();
68 ScreenInteractive* g_active_screen =
nullptr;
72 std::cout <<
'\0' << std::flush;
75 constexpr
int timeout_milliseconds = 20;
76 [[maybe_unused]] constexpr
int timeout_microseconds =
77 timeout_milliseconds * 1000;
80 void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
81 auto console = GetStdHandle(STD_INPUT_HANDLE);
82 auto parser = TerminalInputParser(out->Clone());
86 auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
87 if (wait_result == WAIT_TIMEOUT) {
88 parser.Timeout(timeout_milliseconds);
92 DWORD number_of_events = 0;
93 if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
95 if (number_of_events <= 0)
98 std::vector<INPUT_RECORD> records{number_of_events};
99 DWORD number_of_events_read = 0;
100 ReadConsoleInput(console, records.data(), (DWORD)records.size(),
101 &number_of_events_read);
102 records.resize(number_of_events_read);
104 for (
const auto& r : records) {
105 switch (r.EventType) {
107 auto key_event = r.Event.KeyEvent;
109 if (key_event.bKeyDown == FALSE)
111 std::wstring wstring;
112 wstring += key_event.uChar.UnicodeChar;
117 case WINDOW_BUFFER_SIZE_EVENT:
130 #elif defined(__EMSCRIPTEN__)
131 #include <emscripten.h>
134 void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
135 (void)timeout_microseconds;
136 auto parser = TerminalInputParser(std::move(out));
140 while (read(STDIN_FILENO, &c, 1), c)
150 void ftxui_on_resize(
int columns,
int rows) {
155 std::raise(SIGWINCH);
161 int CheckStdinReady(
int usec_timeout) {
162 timeval tv = {0, usec_timeout};
165 FD_SET(STDIN_FILENO, &fds);
166 select(STDIN_FILENO + 1, &fds,
nullptr,
nullptr, &tv);
167 return FD_ISSET(STDIN_FILENO, &fds);
171 void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
172 auto parser = TerminalInputParser(std::move(out));
175 if (!CheckStdinReady(timeout_microseconds)) {
176 parser.Timeout(timeout_milliseconds);
180 const size_t buffer_size = 100;
181 std::array<char, buffer_size> buffer;
182 size_t l = read(fileno(stdin), buffer.data(), buffer_size);
183 for (
size_t i = 0; i < l; ++i) {
184 parser.Add(buffer[i]);
190 std::stack<Closure> on_exit_functions;
192 while (!on_exit_functions.empty()) {
193 on_exit_functions.top()();
194 on_exit_functions.pop();
198 std::atomic<int> g_signal_exit_count = 0;
200 std::atomic<int> g_signal_stop_count = 0;
201 std::atomic<int> g_signal_resize_count = 0;
205 void RecordSignal(
int signal) {
213 g_signal_exit_count++;
218 g_signal_stop_count++;
222 g_signal_resize_count++;
231 void ExecuteSignalHandlers() {
232 int signal_exit_count = g_signal_exit_count.exchange(0);
233 while (signal_exit_count--) {
238 int signal_stop_count = g_signal_stop_count.exchange(0);
239 while (signal_stop_count--) {
243 int signal_resize_count = g_signal_resize_count.exchange(0);
244 while (signal_resize_count--) {
250 void InstallSignalHandler(
int sig) {
251 auto old_signal_handler = std::signal(sig, RecordSignal);
252 on_exit_functions.push(
253 [=] { std::ignore = std::signal(sig, old_signal_handler); });
257 const std::string CSI =
"\x1b[";
260 const std::string DCS =
"\x1bP";
262 const std::string ST =
"\x1b\\";
266 const std::string DECRQSS_DECSCUSR = DCS +
"$q q" + ST;
275 kMouseVt200Highlight = 1001,
277 kMouseBtnEventMouse = 1002,
278 kMouseAnyEvent = 1003,
281 kMouseSgrExtMode = 1006,
282 kMouseUrxvtMode = 1015,
283 kMouseSgrPixelsMode = 1016,
284 kAlternateScreen = 1049,
292 std::string Serialize(
const std::vector<DECMode>& parameters) {
295 for (
const DECMode parameter : parameters) {
306 std::string Set(
const std::vector<DECMode>& parameters) {
307 return CSI +
"?" + Serialize(parameters) +
"h";
311 std::string Reset(
const std::vector<DECMode>& parameters) {
312 return CSI +
"?" + Serialize(parameters) +
"l";
316 std::string DeviceStatusReport(DSRMode ps) {
320 class CapturedMouseImpl :
public CapturedMouseInterface {
322 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
323 : callback_(std::move(callback)) {}
324 ~CapturedMouseImpl()
override { callback_(); }
325 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
326 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
327 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
328 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
331 std::function<void(
void)> callback_;
334 void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
336 const auto time_delta = std::chrono::milliseconds(15);
338 out->Send(AnimationTask());
339 std::this_thread::sleep_for(time_delta);
345 ScreenInteractive::ScreenInteractive(
int dimx,
348 bool use_alternative_screen)
349 : Screen(dimx, dimy),
350 dimension_(dimension),
351 use_alternative_screen_(use_alternative_screen) {
352 task_receiver_ = MakeReceiver<Task>();
383 Dimension::Fullscreen,
396 Dimension::Fullscreen,
406 Dimension::TerminalOutput,
416 Dimension::FitComponent,
438 track_mouse_ = enable;
451 task_sender_->Send(std::move(task));
464 if (animation_requested_) {
467 animation_requested_ =
true;
468 auto now = animation::Clock::now();
469 const auto time_histeresis = std::chrono::milliseconds(33);
470 if (now - previous_animation_time_ >= time_histeresis) {
471 previous_animation_time_ = now;
480 if (mouse_captured) {
483 mouse_captured =
true;
484 return std::make_unique<CapturedMouseImpl>(
485 [
this] { mouse_captured =
false; });
492 class Loop loop(this, std::move(component));
498 bool ScreenInteractive::HasQuitted() {
503 void ScreenInteractive::PreMain() {
505 if (g_active_screen) {
506 std::swap(suspended_screen_, g_active_screen);
508 suspended_screen_->ResetCursorPosition();
510 suspended_screen_->
dimx_ = 0;
511 suspended_screen_->
dimy_ = 0;
514 suspended_screen_->Uninstall();
518 g_active_screen =
this;
519 g_active_screen->Install();
521 previous_animation_time_ = animation::Clock::now();
525 void ScreenInteractive::PostMain() {
527 ResetCursorPosition();
529 g_active_screen =
nullptr;
532 if (suspended_screen_) {
538 std::swap(g_active_screen, suspended_screen_);
539 g_active_screen->Install();
546 if (!use_alternative_screen_) {
547 std::cout << std::endl;
566 return g_active_screen;
570 void ScreenInteractive::Install() {
572 frame_valid_ =
false;
583 on_exit_functions.push([] { Flush(); });
589 std::cout << DECRQSS_DECSCUSR;
590 on_exit_functions.push([=] {
591 std::cout <<
"\033[?25h";
592 std::cout <<
"\033[" +
std::to_string(cursor_reset_shape_) +
" q";
597 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
598 InstallSignalHandler(signal);
604 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
605 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
609 GetConsoleMode(stdout_handle, &out_mode);
610 GetConsoleMode(stdin_handle, &in_mode);
611 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
612 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
615 const int enable_virtual_terminal_processing = 0x0004;
616 const int disable_newline_auto_return = 0x0008;
617 out_mode |= enable_virtual_terminal_processing;
618 out_mode |= disable_newline_auto_return;
621 const int enable_line_input = 0x0002;
622 const int enable_echo_input = 0x0004;
623 const int enable_virtual_terminal_input = 0x0200;
624 const int enable_window_input = 0x0008;
625 in_mode &= ~enable_echo_input;
626 in_mode &= ~enable_line_input;
627 in_mode |= enable_virtual_terminal_input;
628 in_mode |= enable_window_input;
630 SetConsoleMode(stdin_handle, in_mode);
631 SetConsoleMode(stdout_handle, out_mode);
633 for (
const int signal : {SIGWINCH, SIGTSTP}) {
634 InstallSignalHandler(signal);
637 struct termios terminal;
638 tcgetattr(STDIN_FILENO, &terminal);
639 on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
641 terminal.c_lflag &= ~ICANON;
642 terminal.c_lflag &= ~ECHO;
643 terminal.c_cc[VMIN] = 0;
644 terminal.c_cc[VTIME] = 0;
649 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
653 auto enable = [&](
const std::vector<DECMode>& parameters) {
654 std::cout << Set(parameters);
655 on_exit_functions.push([=] { std::cout << Reset(parameters); });
658 auto disable = [&](
const std::vector<DECMode>& parameters) {
659 std::cout << Reset(parameters);
660 on_exit_functions.push([=] { std::cout << Set(parameters); });
663 if (use_alternative_screen_) {
665 DECMode::kAlternateScreen,
675 enable({DECMode::kMouseVt200});
676 enable({DECMode::kMouseAnyEvent});
677 enable({DECMode::kMouseUrxvtMode});
678 enable({DECMode::kMouseSgrExtMode});
686 task_sender_ = task_receiver_->MakeSender();
688 std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
689 animation_listener_ =
690 std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
694 void ScreenInteractive::Uninstall() {
696 event_listener_.join();
697 animation_listener_.join();
703 void ScreenInteractive::RunOnceBlocking(
Component component) {
704 ExecuteSignalHandlers();
706 if (task_receiver_->Receive(&task)) {
707 HandleTask(component, task);
713 void ScreenInteractive::RunOnce(
Component component) {
715 while (task_receiver_->ReceiveNonBlocking(&task)) {
716 HandleTask(component, task);
717 ExecuteSignalHandlers();
719 Draw(std::move(component));
723 void ScreenInteractive::HandleTask(
Component component,
Task& task) {
726 using T = std::decay_t<decltype(arg)>;
730 if constexpr (std::is_same_v<T, Event>) {
731 if (arg.is_cursor_position()) {
732 cursor_x_ = arg.cursor_x();
733 cursor_y_ = arg.cursor_y();
737 if (arg.is_cursor_shape()) {
738 cursor_reset_shape_= arg.cursor_shape();
742 if (arg.is_mouse()) {
743 arg.mouse().x -= cursor_x_;
744 arg.mouse().y -= cursor_y_;
748 component->OnEvent(arg);
749 frame_valid_ =
false;
754 if constexpr (std::is_same_v<T, Closure>) {
760 if constexpr (std::is_same_v<T, AnimationTask>) {
761 if (!animation_requested_) {
765 animation_requested_ =
false;
768 previous_animation_time_ = now;
770 animation::Params params(delta);
771 component->OnAnimation(params);
772 frame_valid_ =
false;
782 void ScreenInteractive::Draw(
Component component) {
786 auto document = component->Render();
790 document->ComputeRequirement();
791 switch (dimension_) {
796 case Dimension::TerminalOutput:
797 dimx = terminal.dimx;
798 dimy = document->requirement().min_y;
800 case Dimension::Fullscreen:
801 dimx = terminal.dimx;
802 dimy = terminal.dimy;
804 case Dimension::FitComponent:
805 dimx = std::min(document->requirement().min_x, terminal.dimx);
806 dimy = std::min(document->requirement().min_y, terminal.dimy);
811 ResetCursorPosition();
818 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
826 #if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
835 if (!use_alternative_screen_ && (i % 150 == 0)) {
836 std::cout << DeviceStatusReport(DSRMode::kCursor);
841 if (!use_alternative_screen_ &&
842 (previous_frame_resized_ || i % 40 == 0)) {
843 std::cout << DeviceStatusReport(DSRMode::kCursor);
846 previous_frame_resized_ = resized;
855 set_cursor_position.clear();
856 reset_cursor_position.clear();
869 set_cursor_position +=
"\033[?25l";
871 set_cursor_position +=
"\033[?25h";
872 set_cursor_position +=
877 std::cout <<
ToString() << set_cursor_position;
884 void ScreenInteractive::ResetCursorPosition() {
885 std::cout << reset_cursor_position;
886 reset_cursor_position =
"";
892 return [
this] {
Exit(); };
898 Post([
this] { ExitNow(); });
902 void ScreenInteractive::ExitNow() {
904 task_sender_.reset();
908 void ScreenInteractive::Signal(
int signal) {
909 if (signal == SIGABRT) {
916 if (signal == SIGTSTP) {
918 ResetCursorPosition();
924 std::ignore = std::raise(SIGTSTP);
930 if (signal == SIGWINCH) {
std::vector< std::vector< Pixel > > pixels_
bool HasQuitted()
Whether the loop has quitted.
static void Signal(ScreenInteractive &s, int signal)
static ScreenInteractive TerminalOutput()
void Exit()
Exit the main loop.
static ScreenInteractive FixedSize(int dimx, int dimy)
void PostEvent(Event event)
Add an event to the main loop. It will be executed later, after every other scheduled events.
void Post(Task task)
Add a task to the main loop. It will be executed later, after every other scheduled tasks.
static ScreenInteractive FitComponent()
static ScreenInteractive Fullscreen()
static ScreenInteractive FullscreenPrimaryScreen()
static ScreenInteractive * Active()
Return the currently active screen, or null if none.
CapturedMouse CaptureMouse()
Try to get the unique lock about behing able to capture the mouse.
static ScreenInteractive FullscreenAlternateScreen()
void TrackMouse(bool enable=true)
Set whether mouse is tracked and events reported. called outside of the main loop....
void RequestAnimationFrame()
Add a task to draw the screen one more time, until all the animations are done.
Closure ExitLoopClosure()
Return a function to exit the main loop.
Closure WithRestoredIO(Closure)
Decorate a function. It executes the same way, but with the currently active screen terminal hooks te...
std::string ToString() const
std::string ResetPosition(bool clear=false) const
Return a string to be printed in order to reset the cursor position to the beginning of the screen.
void Clear()
Clear all the pixel from the screen.
void SetFallbackSize(const Dimensions &fallbackSize)
Override terminal size in case auto-detection fails.
Dimensions Size()
Get the terminal size.
std::chrono::duration< float > Duration
std::chrono::time_point< Clock > TimePoint
void RequestAnimationFrame()
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::shared_ptr< ComponentBase > Component
std::string to_string(const std::wstring &s)
Convert a UTF8 std::string into a std::wstring.
Element select(Element)
Set the child to be the one selected among its siblings.
std::variant< Event, Closure, AnimationTask > Task
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
std::function< void()> Closure
Represent an event. It can be key press event, a terminal resize, or more ...
static Event Special(std::string)
An custom event whose meaning is defined by the user of the library.