12#include <initializer_list>
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();
68ScreenInteractive* g_active_screen =
nullptr;
72 std::cout <<
'\0' << std::flush;
75constexpr int timeout_milliseconds = 20;
76[[maybe_unused]]
constexpr int timeout_microseconds =
77 timeout_milliseconds * 1000;
80void 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>
134void 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)
150void ftxui_on_resize(
int columns,
int rows) {
155 std::raise(SIGWINCH);
161int 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);
171void 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]);
190std::stack<Closure> on_exit_functions;
192 while (!on_exit_functions.empty()) {
193 on_exit_functions.top()();
194 on_exit_functions.pop();
198std::atomic<int> g_signal_exit_count = 0;
200std::atomic<int> g_signal_stop_count = 0;
201std::atomic<int> g_signal_resize_count = 0;
205void RecordSignal(
int signal) {
213 g_signal_exit_count++;
218 g_signal_stop_count++;
222 g_signal_resize_count++;
231void 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--) {
250void 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); });
256const std::string CSI =
"\x1b[";
265 kMouseVt200Highlight = 1001,
267 kMouseBtnEventMouse = 1002,
268 kMouseAnyEvent = 1003,
271 kMouseSgrExtMode = 1006,
272 kMouseUrxvtMode = 1015,
273 kMouseSgrPixelsMode = 1016,
274 kAlternateScreen = 1049,
282std::string Serialize(
const std::vector<DECMode>& parameters) {
285 for (
const DECMode parameter : parameters) {
289 out += std::to_string(
int(parameter));
296std::string Set(
const std::vector<DECMode>& parameters) {
297 return CSI +
"?" + Serialize(parameters) +
"h";
301std::string Reset(
const std::vector<DECMode>& parameters) {
302 return CSI +
"?" + Serialize(parameters) +
"l";
306std::string DeviceStatusReport(DSRMode ps) {
307 return CSI + std::to_string(
int(ps)) +
"n";
310class CapturedMouseImpl :
public CapturedMouseInterface {
312 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
313 : callback_(std::move(callback)) {}
314 ~CapturedMouseImpl()
override { callback_(); }
315 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
316 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
317 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
318 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
321 std::function<void(
void)> callback_;
324void AnimationListener(std::atomic<bool>* quit,
Sender<Task> out) {
326 const auto time_delta = std::chrono::milliseconds(15);
328 out->Send(AnimationTask());
329 std::this_thread::sleep_for(time_delta);
335ScreenInteractive::ScreenInteractive(
int dimx,
338 bool use_alternative_screen)
339 : Screen(dimx, dimy),
340 dimension_(dimension),
341 use_alternative_screen_(use_alternative_screen) {
360 Dimension::Fullscreen,
370 Dimension::TerminalOutput,
380 Dimension::FitComponent,
402 track_mouse_ = enable;
415 task_sender_->Send(std::move(task));
428 if (animation_requested_) {
431 animation_requested_ =
true;
432 auto now = animation::Clock::now();
433 const auto time_histeresis = std::chrono::milliseconds(33);
434 if (now - previous_animation_time_ >= time_histeresis) {
435 previous_animation_time_ = now;
444 if (mouse_captured) {
447 mouse_captured =
true;
448 return std::make_unique<CapturedMouseImpl>(
449 [
this] { mouse_captured =
false; });
456 class Loop loop(this, std::move(component));
462bool ScreenInteractive::HasQuitted() {
467void ScreenInteractive::PreMain() {
469 if (g_active_screen) {
470 std::swap(suspended_screen_, g_active_screen);
472 suspended_screen_->ResetCursorPosition();
474 suspended_screen_->
dimx_ = 0;
475 suspended_screen_->
dimy_ = 0;
478 suspended_screen_->Uninstall();
482 g_active_screen =
this;
483 g_active_screen->Install();
485 previous_animation_time_ = animation::Clock::now();
489void ScreenInteractive::PostMain() {
491 ResetCursorPosition();
493 g_active_screen =
nullptr;
496 if (suspended_screen_) {
502 std::swap(g_active_screen, suspended_screen_);
503 g_active_screen->Install();
510 if (!use_alternative_screen_) {
511 std::cout << std::endl;
530 return g_active_screen;
534void ScreenInteractive::Install() {
535 frame_valid_ =
false;
539 on_exit_functions.push([] { Flush(); });
545 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
546 InstallSignalHandler(signal);
552 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
553 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
557 GetConsoleMode(stdout_handle, &out_mode);
558 GetConsoleMode(stdin_handle, &in_mode);
559 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
560 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
563 const int enable_virtual_terminal_processing = 0x0004;
564 const int disable_newline_auto_return = 0x0008;
565 out_mode |= enable_virtual_terminal_processing;
566 out_mode |= disable_newline_auto_return;
569 const int enable_line_input = 0x0002;
570 const int enable_echo_input = 0x0004;
571 const int enable_virtual_terminal_input = 0x0200;
572 const int enable_window_input = 0x0008;
573 in_mode &= ~enable_echo_input;
574 in_mode &= ~enable_line_input;
575 in_mode |= enable_virtual_terminal_input;
576 in_mode |= enable_window_input;
578 SetConsoleMode(stdin_handle, in_mode);
579 SetConsoleMode(stdout_handle, out_mode);
581 for (
const int signal : {SIGWINCH, SIGTSTP}) {
582 InstallSignalHandler(signal);
585 struct termios terminal;
586 tcgetattr(STDIN_FILENO, &terminal);
587 on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
589 terminal.c_lflag &= ~ICANON;
590 terminal.c_lflag &= ~ECHO;
591 terminal.c_cc[VMIN] = 0;
592 terminal.c_cc[VTIME] = 0;
597 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
601 auto enable = [&](
const std::vector<DECMode>& parameters) {
602 std::cout << Set(parameters);
603 on_exit_functions.push([=] { std::cout << Reset(parameters); });
606 auto disable = [&](
const std::vector<DECMode>& parameters) {
607 std::cout << Reset(parameters);
608 on_exit_functions.push([=] { std::cout << Set(parameters); });
611 if (use_alternative_screen_) {
613 DECMode::kAlternateScreen,
617 on_exit_functions.push([=] {
618 std::cout <<
"\033[?25h";
619 std::cout <<
"\033[?1 q";
628 enable({DECMode::kMouseVt200});
629 enable({DECMode::kMouseAnyEvent});
630 enable({DECMode::kMouseUrxvtMode});
631 enable({DECMode::kMouseSgrExtMode});
639 task_sender_ = task_receiver_->MakeSender();
641 std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
642 animation_listener_ =
643 std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
647void ScreenInteractive::Uninstall() {
649 event_listener_.join();
650 animation_listener_.join();
656void ScreenInteractive::RunOnceBlocking(
Component component) {
657 ExecuteSignalHandlers();
659 if (task_receiver_->Receive(&task)) {
660 HandleTask(component, task);
666void ScreenInteractive::RunOnce(
Component component) {
668 while (task_receiver_->ReceiveNonBlocking(&task)) {
669 HandleTask(component, task);
670 ExecuteSignalHandlers();
672 Draw(std::move(component));
676void ScreenInteractive::HandleTask(
Component component,
Task& task) {
678 std::visit([&](
auto&& arg) {
679 using T = std::decay_t<
decltype(arg)>;
682 if constexpr (std::is_same_v<T, Event>) {
683 if (arg.is_cursor_reporting()) {
684 cursor_x_ = arg.cursor_x();
685 cursor_y_ = arg.cursor_y();
689 if (arg.is_mouse()) {
690 arg.mouse().x -= cursor_x_;
691 arg.mouse().y -= cursor_y_;
695 component->OnEvent(arg);
696 frame_valid_ =
false;
701 if constexpr (std::is_same_v<T, Closure>) {
707 if constexpr (std::is_same_v<T, AnimationTask>) {
708 if (!animation_requested_) {
712 animation_requested_ =
false;
715 previous_animation_time_ = now;
717 animation::Params params(delta);
718 component->OnAnimation(params);
719 frame_valid_ =
false;
729void ScreenInteractive::Draw(
Component component) {
733 auto document = component->Render();
737 document->ComputeRequirement();
738 switch (dimension_) {
739 case Dimension::Fixed:
743 case Dimension::TerminalOutput:
744 dimx = terminal.dimx;
745 dimy = document->requirement().min_y;
747 case Dimension::Fullscreen:
748 dimx = terminal.dimx;
749 dimy = terminal.dimy;
751 case Dimension::FitComponent:
752 dimx = std::min(document->requirement().min_x, terminal.dimx);
753 dimy = std::min(document->requirement().min_y, terminal.dimy);
758 ResetCursorPosition();
765 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
773#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
782 if (!use_alternative_screen_ && (i % 150 == 0)) {
783 std::cout << DeviceStatusReport(DSRMode::kCursor);
788 if (!use_alternative_screen_ &&
789 (previous_frame_resized_ || i % 40 == 0)) {
790 std::cout << DeviceStatusReport(DSRMode::kCursor);
793 previous_frame_resized_ = resized;
802 set_cursor_position =
"\x1B[" + std::to_string(dy) +
"A" +
803 "\x1B[" + std::to_string(dx) +
"D";
804 reset_cursor_position =
"\x1B[" + std::to_string(dy) +
"B" +
805 "\x1B[" + std::to_string(dx) +
"C";
808 set_cursor_position +=
"\033[?25l";
810 set_cursor_position +=
"\033[?25h";
811 set_cursor_position +=
816 std::cout <<
ToString() << set_cursor_position;
823void ScreenInteractive::ResetCursorPosition() {
824 std::cout << reset_cursor_position;
825 reset_cursor_position =
"";
831 return [
this] {
Exit(); };
837 Post([
this] { ExitNow(); });
841void ScreenInteractive::ExitNow() {
843 task_sender_.reset();
847void ScreenInteractive::Signal(
int signal) {
848 if (signal == SIGABRT) {
855 if (signal == SIGTSTP) {
857 ResetCursorPosition();
863 std::ignore = std::raise(SIGTSTP);
869 if (signal == SIGWINCH) {
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 * 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.
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.
std::vector< std::vector< Pixel > > pixels_
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
Receiver< T > MakeReceiver()
std::string to_string(const std::wstring &s)
Convert a UTF8 std::string into a std::wstring.
std::unique_ptr< SenderImpl< T > > Sender
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
std::shared_ptr< ComponentBase > Component
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.