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); });
257const std::string CSI =
"\x1b[";
260const std::string DCS =
"\x1bP";
262const std::string ST =
"\x1b\\";
266const 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,
292std::string Serialize(
const std::vector<DECMode>& parameters) {
295 for (
const DECMode parameter : parameters) {
306std::string Set(
const std::vector<DECMode>& parameters) {
307 return CSI +
"?" + Serialize(parameters) +
"h";
311std::string Reset(
const std::vector<DECMode>& parameters) {
312 return CSI +
"?" + Serialize(parameters) +
"l";
316std::string DeviceStatusReport(DSRMode ps) {
320class 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_;
334void 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);
345ScreenInteractive::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));
498bool ScreenInteractive::HasQuitted() {
503void 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();
525void 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;
570void ScreenInteractive::Install() {
571 frame_valid_ =
false;
575 on_exit_functions.push([] { Flush(); });
581 std::cout << DECRQSS_DECSCUSR;
582 on_exit_functions.push([=] {
583 std::cout <<
"\033[?25h";
584 std::cout <<
"\033[" +
std::to_string(cursor_reset_shape_) +
" q";
589 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
590 InstallSignalHandler(signal);
596 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
597 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
601 GetConsoleMode(stdout_handle, &out_mode);
602 GetConsoleMode(stdin_handle, &in_mode);
603 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
604 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
607 const int enable_virtual_terminal_processing = 0x0004;
608 const int disable_newline_auto_return = 0x0008;
609 out_mode |= enable_virtual_terminal_processing;
610 out_mode |= disable_newline_auto_return;
613 const int enable_line_input = 0x0002;
614 const int enable_echo_input = 0x0004;
615 const int enable_virtual_terminal_input = 0x0200;
616 const int enable_window_input = 0x0008;
617 in_mode &= ~enable_echo_input;
618 in_mode &= ~enable_line_input;
619 in_mode |= enable_virtual_terminal_input;
620 in_mode |= enable_window_input;
622 SetConsoleMode(stdin_handle, in_mode);
623 SetConsoleMode(stdout_handle, out_mode);
625 for (
const int signal : {SIGWINCH, SIGTSTP}) {
626 InstallSignalHandler(signal);
629 struct termios terminal;
630 tcgetattr(STDIN_FILENO, &terminal);
631 on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
633 terminal.c_lflag &= ~ICANON;
634 terminal.c_lflag &= ~ECHO;
635 terminal.c_cc[VMIN] = 0;
636 terminal.c_cc[VTIME] = 0;
641 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
645 auto enable = [&](
const std::vector<DECMode>& parameters) {
646 std::cout << Set(parameters);
647 on_exit_functions.push([=] { std::cout << Reset(parameters); });
650 auto disable = [&](
const std::vector<DECMode>& parameters) {
651 std::cout << Reset(parameters);
652 on_exit_functions.push([=] { std::cout << Set(parameters); });
655 if (use_alternative_screen_) {
657 DECMode::kAlternateScreen,
667 enable({DECMode::kMouseVt200});
668 enable({DECMode::kMouseAnyEvent});
669 enable({DECMode::kMouseUrxvtMode});
670 enable({DECMode::kMouseSgrExtMode});
678 task_sender_ = task_receiver_->MakeSender();
680 std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
681 animation_listener_ =
682 std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
686void ScreenInteractive::Uninstall() {
688 event_listener_.join();
689 animation_listener_.join();
695void ScreenInteractive::RunOnceBlocking(
Component component) {
696 ExecuteSignalHandlers();
698 if (task_receiver_->Receive(&task)) {
699 HandleTask(component, task);
705void ScreenInteractive::RunOnce(
Component component) {
707 while (task_receiver_->ReceiveNonBlocking(&task)) {
708 HandleTask(component, task);
709 ExecuteSignalHandlers();
711 Draw(std::move(component));
715void ScreenInteractive::HandleTask(
Component component,
Task& task) {
718 using T = std::decay_t<
decltype(arg)>;
722 if constexpr (std::is_same_v<T, Event>) {
723 if (arg.is_cursor_position()) {
724 cursor_x_ = arg.cursor_x();
725 cursor_y_ = arg.cursor_y();
729 if (arg.is_cursor_shape()) {
730 cursor_reset_shape_= arg.cursor_shape();
734 if (arg.is_mouse()) {
735 arg.mouse().x -= cursor_x_;
736 arg.mouse().y -= cursor_y_;
737 arg.mouse().previous = &latest_mouse_event_;
741 component->OnEvent(arg);
742 frame_valid_ =
false;
744 if (arg.is_mouse()) {
745 latest_mouse_event_ = arg.mouse();
746 latest_mouse_event_.
previous =
nullptr;
752 if constexpr (std::is_same_v<T, Closure>) {
758 if constexpr (std::is_same_v<T, AnimationTask>) {
759 if (!animation_requested_) {
763 animation_requested_ =
false;
766 previous_animation_time_ = now;
768 animation::Params params(delta);
769 component->OnAnimation(params);
770 frame_valid_ =
false;
780void ScreenInteractive::Draw(
Component component) {
784 auto document = component->Render();
788 document->ComputeRequirement();
789 switch (dimension_) {
794 case Dimension::TerminalOutput:
795 dimx = terminal.dimx;
796 dimy = document->requirement().min_y;
798 case Dimension::Fullscreen:
799 dimx = terminal.dimx;
800 dimy = terminal.dimy;
802 case Dimension::FitComponent:
803 dimx = std::min(document->requirement().min_x, terminal.dimx);
804 dimy = std::min(document->requirement().min_y, terminal.dimy);
809 ResetCursorPosition();
816 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
824#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
833 if (!use_alternative_screen_ && (i % 150 == 0)) {
834 std::cout << DeviceStatusReport(DSRMode::kCursor);
839 if (!use_alternative_screen_ &&
840 (previous_frame_resized_ || i % 40 == 0)) {
841 std::cout << DeviceStatusReport(DSRMode::kCursor);
844 previous_frame_resized_ = resized;
859 set_cursor_position +=
"\033[?25l";
861 set_cursor_position +=
"\033[?25h";
862 set_cursor_position +=
867 std::cout <<
ToString() << set_cursor_position;
874void ScreenInteractive::ResetCursorPosition() {
875 std::cout << reset_cursor_position;
876 reset_cursor_position =
"";
882 return [
this] {
Exit(); };
888 Post([
this] { ExitNow(); });
892void ScreenInteractive::ExitNow() {
894 task_sender_.reset();
898void ScreenInteractive::Signal(
int signal) {
899 if (signal == SIGABRT) {
906 if (signal == SIGTSTP) {
908 ResetCursorPosition();
914 std::ignore = std::raise(SIGTSTP);
920 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 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.
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
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.