15 #include <initializer_list>
22 #include <type_traits>
39 #define DEFINE_CONSOLEV2_PROPERTIES
40 #define WIN32_LEAN_AND_MEAN
46 #error Must be compiled in UNICODE mode
49 #include <sys/select.h>
55 #if defined(__clang__) && defined(__APPLE__)
56 #define quick_exit(a) exit(a)
65 screen->RequestAnimationFrame();
72 ScreenInteractive* g_active_screen =
nullptr;
76 std::cout <<
'\0' << std::flush;
79 constexpr
int timeout_milliseconds = 20;
80 [[maybe_unused]] constexpr
int timeout_microseconds =
81 timeout_milliseconds * 1000;
84 void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
85 auto console = GetStdHandle(STD_INPUT_HANDLE);
86 auto parser = TerminalInputParser(out->Clone());
90 auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
91 if (wait_result == WAIT_TIMEOUT) {
92 parser.Timeout(timeout_milliseconds);
96 DWORD number_of_events = 0;
97 if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
99 if (number_of_events <= 0)
102 std::vector<INPUT_RECORD> records{number_of_events};
103 DWORD number_of_events_read = 0;
104 ReadConsoleInput(console, records.data(), (DWORD)records.size(),
105 &number_of_events_read);
106 records.resize(number_of_events_read);
108 for (
const auto& r : records) {
109 switch (r.EventType) {
111 auto key_event = r.Event.KeyEvent;
113 if (key_event.bKeyDown == FALSE)
115 std::wstring wstring;
116 wstring += key_event.uChar.UnicodeChar;
121 case WINDOW_BUFFER_SIZE_EVENT:
134 #elif defined(__EMSCRIPTEN__)
135 #include <emscripten.h>
138 void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
139 auto parser = TerminalInputParser(std::move(out));
143 while (read(STDIN_FILENO, &c, 1), c)
153 void ftxui_on_resize(
int columns,
int rows) {
158 std::raise(SIGWINCH);
164 int CheckStdinReady(
int usec_timeout) {
165 timeval tv = {0, usec_timeout};
168 FD_SET(STDIN_FILENO, &fds);
169 select(STDIN_FILENO + 1, &fds,
nullptr,
nullptr, &tv);
170 return FD_ISSET(STDIN_FILENO, &fds);
174 void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
175 auto parser = TerminalInputParser(std::move(out));
178 if (!CheckStdinReady(timeout_microseconds)) {
179 parser.Timeout(timeout_milliseconds);
183 const size_t buffer_size = 100;
184 std::array<char, buffer_size> buffer;
185 size_t l = read(fileno(stdin), buffer.data(), buffer_size);
186 for (
size_t i = 0; i < l; ++i) {
187 parser.Add(buffer[i]);
193 std::stack<Closure> on_exit_functions;
195 while (!on_exit_functions.empty()) {
196 on_exit_functions.top()();
197 on_exit_functions.pop();
201 std::atomic<int> g_signal_exit_count = 0;
203 std::atomic<int> g_signal_stop_count = 0;
204 std::atomic<int> g_signal_resize_count = 0;
208 void RecordSignal(
int signal) {
216 g_signal_exit_count++;
221 g_signal_stop_count++;
225 g_signal_resize_count++;
234 void ExecuteSignalHandlers() {
235 int signal_exit_count = g_signal_exit_count.exchange(0);
236 while (signal_exit_count--) {
241 int signal_stop_count = g_signal_stop_count.exchange(0);
242 while (signal_stop_count--) {
246 int signal_resize_count = g_signal_resize_count.exchange(0);
247 while (signal_resize_count--) {
253 void InstallSignalHandler(
int sig) {
254 auto old_signal_handler = std::signal(sig, RecordSignal);
255 on_exit_functions.emplace(
256 [=] { std::ignore = std::signal(sig, old_signal_handler); });
260 const std::string CSI =
"\x1b[";
263 const std::string DCS =
"\x1bP";
265 const std::string ST =
"\x1b\\";
269 const std::string DECRQSS_DECSCUSR = DCS +
"$q q" + ST;
272 enum class DECMode : std::uint16_t {
278 kMouseVt200Highlight = 1001,
280 kMouseBtnEventMouse = 1002,
281 kMouseAnyEvent = 1003,
284 kMouseSgrExtMode = 1006,
285 kMouseUrxvtMode = 1015,
286 kMouseSgrPixelsMode = 1016,
287 kAlternateScreen = 1049,
291 enum class DSRMode : std::uint8_t {
295 std::string Serialize(
const std::vector<DECMode>& parameters) {
298 for (
const DECMode parameter : parameters) {
309 std::string Set(
const std::vector<DECMode>& parameters) {
310 return CSI +
"?" + Serialize(parameters) +
"h";
314 std::string Reset(
const std::vector<DECMode>& parameters) {
315 return CSI +
"?" + Serialize(parameters) +
"l";
319 std::string DeviceStatusReport(DSRMode ps) {
323 class CapturedMouseImpl :
public CapturedMouseInterface {
325 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
326 : callback_(std::move(callback)) {}
327 ~CapturedMouseImpl()
override { callback_(); }
328 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
329 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
330 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
331 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
334 std::function<void(
void)> callback_;
337 void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
339 const auto time_delta = std::chrono::milliseconds(15);
341 out->Send(AnimationTask());
342 std::this_thread::sleep_for(time_delta);
348 ScreenInteractive::ScreenInteractive(
int dimx,
351 bool use_alternative_screen)
352 : Screen(dimx, dimy),
353 dimension_(dimension),
354 use_alternative_screen_(use_alternative_screen) {
355 task_receiver_ = MakeReceiver<Task>();
386 Dimension::Fullscreen,
399 Dimension::Fullscreen,
409 Dimension::TerminalOutput,
419 Dimension::FitComponent,
441 track_mouse_ = enable;
454 task_sender_->Send(std::move(task));
467 if (animation_requested_) {
470 animation_requested_ =
true;
471 auto now = animation::Clock::now();
472 const auto time_histeresis = std::chrono::milliseconds(33);
473 if (now - previous_animation_time_ >= time_histeresis) {
474 previous_animation_time_ = now;
483 if (mouse_captured) {
486 mouse_captured =
true;
487 return std::make_unique<CapturedMouseImpl>(
488 [
this] { mouse_captured =
false; });
495 class Loop loop(this, std::move(component));
501 bool ScreenInteractive::HasQuitted() {
506 void ScreenInteractive::PreMain() {
508 if (g_active_screen) {
509 std::swap(suspended_screen_, g_active_screen);
511 suspended_screen_->ResetCursorPosition();
513 suspended_screen_->
dimx_ = 0;
514 suspended_screen_->
dimy_ = 0;
517 suspended_screen_->Uninstall();
521 g_active_screen =
this;
522 g_active_screen->Install();
524 previous_animation_time_ = animation::Clock::now();
528 void ScreenInteractive::PostMain() {
530 ResetCursorPosition();
532 g_active_screen =
nullptr;
535 if (suspended_screen_) {
541 std::swap(g_active_screen, suspended_screen_);
542 g_active_screen->Install();
549 if (!use_alternative_screen_) {
551 std::cout << std::flush;
570 force_handle_ctrl_c_ = force;
576 force_handle_ctrl_z_ = force;
582 return g_active_screen;
586 void ScreenInteractive::Install() {
587 frame_valid_ =
false;
598 on_exit_functions.emplace([] { Flush(); });
604 std::cout << DECRQSS_DECSCUSR;
605 on_exit_functions.emplace([
this] {
606 std::cout <<
"\033[?25h";
607 std::cout <<
"\033[" +
std::to_string(cursor_reset_shape_) +
" q";
612 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
613 InstallSignalHandler(signal);
619 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
620 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
624 GetConsoleMode(stdout_handle, &out_mode);
625 GetConsoleMode(stdin_handle, &in_mode);
626 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
627 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
630 const int enable_virtual_terminal_processing = 0x0004;
631 const int disable_newline_auto_return = 0x0008;
632 out_mode |= enable_virtual_terminal_processing;
633 out_mode |= disable_newline_auto_return;
636 const int enable_line_input = 0x0002;
637 const int enable_echo_input = 0x0004;
638 const int enable_virtual_terminal_input = 0x0200;
639 const int enable_window_input = 0x0008;
640 in_mode &= ~enable_echo_input;
641 in_mode &= ~enable_line_input;
642 in_mode |= enable_virtual_terminal_input;
643 in_mode |= enable_window_input;
645 SetConsoleMode(stdin_handle, in_mode);
646 SetConsoleMode(stdout_handle, out_mode);
648 for (
const int signal : {SIGWINCH, SIGTSTP}) {
649 InstallSignalHandler(signal);
652 struct termios terminal;
653 tcgetattr(STDIN_FILENO, &terminal);
654 on_exit_functions.emplace(
655 [=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
658 terminal.c_iflag &= ~IGNBRK;
659 terminal.c_iflag &= ~BRKINT;
661 terminal.c_iflag &= ~PARMRK;
662 terminal.c_iflag &= ~ISTRIP;
663 terminal.c_iflag &= ~INLCR;
664 terminal.c_iflag &= ~IGNCR;
665 terminal.c_iflag &= ~ICRNL;
666 terminal.c_iflag &= ~IXON;
668 terminal.c_lflag &= ~ECHO;
669 terminal.c_lflag &= ~ECHONL;
670 terminal.c_lflag &= ~ICANON;
671 terminal.c_lflag &= ~ISIG;
676 terminal.c_lflag &= ~IEXTEN;
677 terminal.c_cflag |= CS8;
679 terminal.c_cc[VMIN] = 0;
681 terminal.c_cc[VTIME] = 0;
683 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
687 auto enable = [&](
const std::vector<DECMode>& parameters) {
688 std::cout << Set(parameters);
689 on_exit_functions.emplace([=] { std::cout << Reset(parameters); });
692 auto disable = [&](
const std::vector<DECMode>& parameters) {
693 std::cout << Reset(parameters);
694 on_exit_functions.emplace([=] { std::cout << Set(parameters); });
697 if (use_alternative_screen_) {
699 DECMode::kAlternateScreen,
709 enable({DECMode::kMouseVt200});
710 enable({DECMode::kMouseAnyEvent});
711 enable({DECMode::kMouseUrxvtMode});
712 enable({DECMode::kMouseSgrExtMode});
720 task_sender_ = task_receiver_->MakeSender();
722 std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
723 animation_listener_ =
724 std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
728 void ScreenInteractive::Uninstall() {
730 event_listener_.join();
731 animation_listener_.join();
737 void ScreenInteractive::RunOnceBlocking(
Component component) {
738 ExecuteSignalHandlers();
740 if (task_receiver_->Receive(&task)) {
741 HandleTask(component, task);
747 void ScreenInteractive::RunOnce(
Component component) {
749 while (task_receiver_->ReceiveNonBlocking(&task)) {
750 HandleTask(component, task);
751 ExecuteSignalHandlers();
753 Draw(std::move(component));
758 void ScreenInteractive::HandleTask(
Component component,
Task& task) {
761 using T = std::decay_t<decltype(arg)>;
765 if constexpr (std::is_same_v<T, Event>) {
766 if (arg.is_cursor_position()) {
767 cursor_x_ = arg.cursor_x();
768 cursor_y_ = arg.cursor_y();
772 if (arg.is_cursor_shape()) {
773 cursor_reset_shape_= arg.cursor_shape();
777 if (arg.is_mouse()) {
778 arg.mouse().x -= cursor_x_;
779 arg.mouse().y -= cursor_y_;
784 const bool handled = component->OnEvent(arg);
786 if (arg ==
Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
787 RecordSignal(SIGABRT);
791 if (arg ==
Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
792 RecordSignal(SIGTSTP);
796 frame_valid_ =
false;
801 if constexpr (std::is_same_v<T, Closure>) {
807 if constexpr (std::is_same_v<T, AnimationTask>) {
808 if (!animation_requested_) {
812 animation_requested_ =
false;
815 previous_animation_time_ = now;
817 animation::Params params(delta);
818 component->OnAnimation(params);
819 frame_valid_ =
false;
829 void ScreenInteractive::Draw(
Component component) {
833 auto document = component->Render();
837 document->ComputeRequirement();
838 switch (dimension_) {
843 case Dimension::TerminalOutput:
844 dimx = terminal.dimx;
845 dimy = document->requirement().min_y;
847 case Dimension::Fullscreen:
848 dimx = terminal.dimx;
849 dimy = terminal.dimy;
851 case Dimension::FitComponent:
852 dimx = std::min(document->requirement().min_x, terminal.dimx);
853 dimy = std::min(document->requirement().min_y, terminal.dimy);
858 ResetCursorPosition();
865 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
873 #if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
882 if (!use_alternative_screen_ && (i % 150 == 0)) {
883 std::cout << DeviceStatusReport(DSRMode::kCursor);
888 if (!use_alternative_screen_ &&
889 (previous_frame_resized_ || i % 40 == 0)) {
890 std::cout << DeviceStatusReport(DSRMode::kCursor);
893 previous_frame_resized_ = resized;
902 set_cursor_position.clear();
903 reset_cursor_position.clear();
916 set_cursor_position +=
"\033[?25l";
918 set_cursor_position +=
"\033[?25h";
919 set_cursor_position +=
924 std::cout <<
ToString() << set_cursor_position;
931 void ScreenInteractive::ResetCursorPosition() {
932 std::cout << reset_cursor_position;
933 reset_cursor_position =
"";
939 return [
this] {
Exit(); };
945 Post([
this] { ExitNow(); });
949 void ScreenInteractive::ExitNow() {
951 task_sender_.reset();
955 void ScreenInteractive::Signal(
int signal) {
956 if (signal == SIGABRT) {
963 if (signal == SIGTSTP) {
965 ResetCursorPosition();
971 std::ignore = std::raise(SIGTSTP);
977 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.
void ForceHandleCtrlC(bool force)
Force FTXUI to handle or not handle Ctrl-C, even if the component catches the Event::CtrlC.
void ForceHandleCtrlZ(bool force)
Force FTXUI to handle or not handle Ctrl-Z, even if the component catches the Event::CtrlZ.
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.