15#include <initializer_list>
40#define DEFINE_CONSOLEV2_PROPERTIES
41#define WIN32_LEAN_AND_MEAN
47#error Must be compiled in UNICODE mode
50#include <sys/select.h>
56#if defined(__clang__) && defined(__APPLE__)
57#define quick_exit(a) exit(a)
66 screen->RequestAnimationFrame();
73ScreenInteractive* g_active_screen =
nullptr;
77 std::cout <<
'\0' << std::flush;
80constexpr int timeout_milliseconds = 20;
81[[maybe_unused]]
constexpr int timeout_microseconds =
82 timeout_milliseconds * 1000;
85void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
86 auto console = GetStdHandle(STD_INPUT_HANDLE);
87 auto parser = TerminalInputParser(out->Clone());
91 auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
92 if (wait_result == WAIT_TIMEOUT) {
93 parser.Timeout(timeout_milliseconds);
97 DWORD number_of_events = 0;
98 if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
100 if (number_of_events <= 0)
103 std::vector<INPUT_RECORD> records{number_of_events};
104 DWORD number_of_events_read = 0;
105 ReadConsoleInput(console, records.data(), (DWORD)records.size(),
106 &number_of_events_read);
107 records.resize(number_of_events_read);
109 for (
const auto& r : records) {
110 switch (r.EventType) {
112 auto key_event = r.Event.KeyEvent;
114 if (key_event.bKeyDown == FALSE)
116 std::wstring wstring;
117 wstring += key_event.uChar.UnicodeChar;
122 case WINDOW_BUFFER_SIZE_EVENT:
135#elif defined(__EMSCRIPTEN__)
136#include <emscripten.h>
139void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
140 auto parser = TerminalInputParser(std::move(out));
144 while (read(STDIN_FILENO, &c, 1), c)
154void ftxui_on_resize(
int columns,
int rows) {
159 std::raise(SIGWINCH);
165int CheckStdinReady(
int usec_timeout) {
166 timeval tv = {0, usec_timeout};
169 FD_SET(STDIN_FILENO, &fds);
170 select(STDIN_FILENO + 1, &fds,
nullptr,
nullptr, &tv);
171 return FD_ISSET(STDIN_FILENO, &fds);
175void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
176 auto parser = TerminalInputParser(std::move(out));
179 if (!CheckStdinReady(timeout_microseconds)) {
180 parser.Timeout(timeout_milliseconds);
184 const size_t buffer_size = 100;
185 std::array<char, buffer_size> buffer;
186 size_t l = read(fileno(stdin), buffer.data(), buffer_size);
187 for (
size_t i = 0; i < l; ++i) {
188 parser.Add(buffer[i]);
194std::stack<Closure> on_exit_functions;
196 while (!on_exit_functions.empty()) {
197 on_exit_functions.top()();
198 on_exit_functions.pop();
202std::atomic<int> g_signal_exit_count = 0;
204std::atomic<int> g_signal_stop_count = 0;
205std::atomic<int> g_signal_resize_count = 0;
209void RecordSignal(
int signal) {
217 g_signal_exit_count++;
222 g_signal_stop_count++;
226 g_signal_resize_count++;
235void ExecuteSignalHandlers() {
236 int signal_exit_count = g_signal_exit_count.exchange(0);
237 while (signal_exit_count--) {
242 int signal_stop_count = g_signal_stop_count.exchange(0);
243 while (signal_stop_count--) {
247 int signal_resize_count = g_signal_resize_count.exchange(0);
248 while (signal_resize_count--) {
254void InstallSignalHandler(
int sig) {
255 auto old_signal_handler = std::signal(sig, RecordSignal);
256 on_exit_functions.emplace(
257 [=] { std::ignore = std::signal(sig, old_signal_handler); });
261const std::string CSI =
"\x1b[";
264const std::string DCS =
"\x1bP";
266const std::string ST =
"\x1b\\";
270const std::string DECRQSS_DECSCUSR = DCS +
"$q q" + ST;
273enum class DECMode : std::uint16_t {
279 kMouseVt200Highlight = 1001,
281 kMouseBtnEventMouse = 1002,
282 kMouseAnyEvent = 1003,
285 kMouseSgrExtMode = 1006,
286 kMouseUrxvtMode = 1015,
287 kMouseSgrPixelsMode = 1016,
288 kAlternateScreen = 1049,
292enum class DSRMode : std::uint8_t {
296std::string Serialize(
const std::vector<DECMode>& parameters) {
299 for (
const DECMode parameter : parameters) {
303 out += std::to_string(
int(parameter));
310std::string Set(
const std::vector<DECMode>& parameters) {
311 return CSI +
"?" + Serialize(parameters) +
"h";
315std::string Reset(
const std::vector<DECMode>& parameters) {
316 return CSI +
"?" + Serialize(parameters) +
"l";
320std::string DeviceStatusReport(DSRMode ps) {
321 return CSI + std::to_string(
int(ps)) +
"n";
324class CapturedMouseImpl :
public CapturedMouseInterface {
326 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
327 : callback_(std::move(callback)) {}
328 ~CapturedMouseImpl()
override { callback_(); }
329 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
330 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
331 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
332 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
335 std::function<void(
void)> callback_;
338void AnimationListener(std::atomic<bool>* quit,
Sender<Task> out) {
340 const auto time_delta = std::chrono::milliseconds(15);
342 out->Send(AnimationTask());
343 std::this_thread::sleep_for(time_delta);
349ScreenInteractive::ScreenInteractive(
int dimx,
352 bool use_alternative_screen)
353 : Screen(dimx, dimy),
354 dimension_(dimension),
355 use_alternative_screen_(use_alternative_screen) {
385 Dimension::Fullscreen,
397 Dimension::Fullscreen,
407 Dimension::TerminalOutput,
417 Dimension::FitComponent,
438 track_mouse_ = enable;
450 task_sender_->Send(std::move(task));
462 if (animation_requested_) {
465 animation_requested_ =
true;
466 auto now = animation::Clock::now();
467 const auto time_histeresis = std::chrono::milliseconds(33);
468 if (now - previous_animation_time_ >= time_histeresis) {
469 previous_animation_time_ = now;
477 if (mouse_captured) {
480 mouse_captured =
true;
481 return std::make_unique<CapturedMouseImpl>(
482 [
this] { mouse_captured =
false; });
488 class Loop loop(this, std::move(component));
493bool ScreenInteractive::HasQuitted() {
498void ScreenInteractive::PreMain() {
500 if (g_active_screen) {
501 std::swap(suspended_screen_, g_active_screen);
503 suspended_screen_->ResetCursorPosition();
505 suspended_screen_->
dimx_ = 0;
506 suspended_screen_->
dimy_ = 0;
509 suspended_screen_->Uninstall();
513 g_active_screen =
this;
514 g_active_screen->Install();
516 previous_animation_time_ = animation::Clock::now();
520void ScreenInteractive::PostMain() {
522 ResetCursorPosition();
524 g_active_screen =
nullptr;
527 if (suspended_screen_) {
533 std::swap(g_active_screen, suspended_screen_);
534 g_active_screen->Install();
541 if (!use_alternative_screen_) {
543 std::cout << std::flush;
562 force_handle_ctrl_c_ = force;
568 force_handle_ctrl_z_ = force;
576 return selection_->GetParts();
580 selection_on_change_ = std::move(callback);
586 return g_active_screen;
590void ScreenInteractive::Install() {
591 frame_valid_ =
false;
602 on_exit_functions.emplace([] { Flush(); });
608 std::cout << DECRQSS_DECSCUSR;
609 on_exit_functions.emplace([
this] {
610 std::cout <<
"\033[?25h";
611 std::cout <<
"\033[" + std::to_string(cursor_reset_shape_) +
" q";
616 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
617 InstallSignalHandler(signal);
623 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
624 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
628 GetConsoleMode(stdout_handle, &out_mode);
629 GetConsoleMode(stdin_handle, &in_mode);
630 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
631 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
634 const int enable_virtual_terminal_processing = 0x0004;
635 const int disable_newline_auto_return = 0x0008;
636 out_mode |= enable_virtual_terminal_processing;
637 out_mode |= disable_newline_auto_return;
640 const int enable_line_input = 0x0002;
641 const int enable_echo_input = 0x0004;
642 const int enable_virtual_terminal_input = 0x0200;
643 const int enable_window_input = 0x0008;
644 in_mode &= ~enable_echo_input;
645 in_mode &= ~enable_line_input;
646 in_mode |= enable_virtual_terminal_input;
647 in_mode |= enable_window_input;
649 SetConsoleMode(stdin_handle, in_mode);
650 SetConsoleMode(stdout_handle, out_mode);
652 for (
const int signal : {SIGWINCH, SIGTSTP}) {
653 InstallSignalHandler(signal);
656 struct termios terminal;
657 tcgetattr(STDIN_FILENO, &terminal);
658 on_exit_functions.emplace(
659 [=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
662 terminal.c_iflag &= ~IGNBRK;
663 terminal.c_iflag &= ~BRKINT;
665 terminal.c_iflag &= ~PARMRK;
666 terminal.c_iflag &= ~ISTRIP;
667 terminal.c_iflag &= ~INLCR;
668 terminal.c_iflag &= ~IGNCR;
669 terminal.c_iflag &= ~ICRNL;
670 terminal.c_iflag &= ~IXON;
672 terminal.c_lflag &= ~ECHO;
673 terminal.c_lflag &= ~ECHONL;
674 terminal.c_lflag &= ~ICANON;
675 terminal.c_lflag &= ~ISIG;
680 terminal.c_lflag &= ~IEXTEN;
681 terminal.c_cflag |= CS8;
683 terminal.c_cc[VMIN] = 0;
685 terminal.c_cc[VTIME] = 0;
687 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
691 auto enable = [&](
const std::vector<DECMode>& parameters) {
692 std::cout << Set(parameters);
693 on_exit_functions.emplace([=] { std::cout << Reset(parameters); });
696 auto disable = [&](
const std::vector<DECMode>& parameters) {
697 std::cout << Reset(parameters);
698 on_exit_functions.emplace([=] { std::cout << Set(parameters); });
701 if (use_alternative_screen_) {
703 DECMode::kAlternateScreen,
713 enable({DECMode::kMouseVt200});
714 enable({DECMode::kMouseAnyEvent});
715 enable({DECMode::kMouseUrxvtMode});
716 enable({DECMode::kMouseSgrExtMode});
724 task_sender_ = task_receiver_->MakeSender();
726 std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
727 animation_listener_ =
728 std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
732void ScreenInteractive::Uninstall() {
734 event_listener_.join();
735 animation_listener_.join();
741void ScreenInteractive::RunOnceBlocking(
Component component) {
742 ExecuteSignalHandlers();
744 if (task_receiver_->Receive(&task)) {
745 HandleTask(component, task);
751void ScreenInteractive::RunOnce(
Component component) {
753 while (task_receiver_->ReceiveNonBlocking(&task)) {
754 HandleTask(component, task);
755 ExecuteSignalHandlers();
757 Draw(std::move(component));
759 if (selection_data_previous_ != selection_data_) {
760 selection_data_previous_ = selection_data_;
761 if (selection_on_change_) {
762 selection_on_change_();
770void ScreenInteractive::HandleTask(
Component component,
Task& task) {
773 using T = std::decay_t<
decltype(arg)>;
777 if constexpr (std::is_same_v<T, Event>) {
778 if (arg.is_cursor_position()) {
779 cursor_x_ = arg.cursor_x();
780 cursor_y_ = arg.cursor_y();
784 if (arg.is_cursor_shape()) {
785 cursor_reset_shape_= arg.cursor_shape();
789 if (arg.is_mouse()) {
790 arg.mouse().x -= cursor_x_;
791 arg.mouse().y -= cursor_y_;
796 bool handled = component->OnEvent(arg);
798 handled = HandleSelection(handled, arg);
800 if (arg ==
Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
801 RecordSignal(SIGABRT);
805 if (arg ==
Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
806 RecordSignal(SIGTSTP);
810 frame_valid_ =
false;
815 if constexpr (std::is_same_v<T, Closure>) {
821 if constexpr (std::is_same_v<T, AnimationTask>) {
822 if (!animation_requested_) {
826 animation_requested_ =
false;
829 previous_animation_time_ = now;
831 animation::Params params(delta);
832 component->OnAnimation(params);
833 frame_valid_ =
false;
842bool ScreenInteractive::HandleSelection(
bool handled, Event event) {
844 selection_pending_ =
nullptr;
845 selection_data_.empty =
true;
846 selection_ =
nullptr;
850 if (!event.is_mouse()) {
854 auto& mouse =
event.mouse();
861 selection_data_.start_x = mouse.x;
862 selection_data_.start_y = mouse.y;
863 selection_data_.end_x = mouse.x;
864 selection_data_.end_y = mouse.y;
868 if (!selection_pending_) {
873 if ((mouse.x != selection_data_.end_x) ||
874 (mouse.y != selection_data_.end_y)) {
875 selection_data_.end_x = mouse.x;
876 selection_data_.end_y = mouse.y;
877 selection_data_.empty =
false;
884 selection_pending_ =
nullptr;
885 selection_data_.end_x = mouse.x;
886 selection_data_.end_y = mouse.y;
887 selection_data_.empty =
false;
896void ScreenInteractive::Draw(
Component component) {
900 auto document = component->Render();
904 document->ComputeRequirement();
905 switch (dimension_) {
906 case Dimension::Fixed:
910 case Dimension::TerminalOutput:
911 dimx = terminal.dimx;
914 case Dimension::Fullscreen:
915 dimx = terminal.dimx;
916 dimy = terminal.dimy;
918 case Dimension::FitComponent:
925 ResetCursorPosition();
930 if ((
dimx <
dimx_) && !use_alternative_screen_) {
931 std::cout <<
"\033[J";
932 std::cout <<
"\033[H";
939 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
947#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
956 if (!use_alternative_screen_ && (i % 150 == 0)) {
957 std::cout << DeviceStatusReport(DSRMode::kCursor);
962 if (!use_alternative_screen_ &&
963 (previous_frame_resized_ || i % 40 == 0)) {
964 std::cout << DeviceStatusReport(DSRMode::kCursor);
967 previous_frame_resized_ = resized;
969 selection_ = selection_data_.empty
970 ? std::make_unique<Selection>()
971 : std::make_unique<Selection>(
972 selection_data_.start_x, selection_data_.start_y,
973 selection_data_.end_x, selection_data_.end_y);
974 Render(*
this, document.get(), *selection_);
981 set_cursor_position.clear();
982 reset_cursor_position.clear();
985 set_cursor_position +=
"\x1B[" + std::to_string(dy) +
"A";
986 reset_cursor_position +=
"\x1B[" + std::to_string(dy) +
"B";
990 set_cursor_position +=
"\x1B[" + std::to_string(dx) +
"D";
991 reset_cursor_position +=
"\x1B[" + std::to_string(dx) +
"C";
995 set_cursor_position +=
"\033[?25l";
997 set_cursor_position +=
"\033[?25h";
998 set_cursor_position +=
1003 std::cout <<
ToString() << set_cursor_position;
1006 frame_valid_ =
true;
1010void ScreenInteractive::ResetCursorPosition() {
1011 std::cout << reset_cursor_position;
1012 reset_cursor_position =
"";
1017 return [
this] {
Exit(); };
1022 Post([
this] { ExitNow(); });
1026void ScreenInteractive::ExitNow() {
1028 task_sender_.reset();
1032void ScreenInteractive::Signal(
int signal) {
1033 if (signal == SIGABRT) {
1040 if (signal == SIGTSTP) {
1042 ResetCursorPosition();
1048 std::ignore = std::raise(SIGTSTP);
1054 if (signal == SIGWINCH) {
1061bool ScreenInteractive::SelectionData::operator==(
1062 const ScreenInteractive::SelectionData& other)
const {
1063 if (empty && other.empty) {
1066 if (empty || other.empty) {
1069 return start_x == other.start_x && start_y == other.start_y &&
1070 end_x == other.end_x && end_y == other.end_y;
1073bool ScreenInteractive::SelectionData::operator!=(
1074 const ScreenInteractive::SelectionData& other)
const {
1075 return !(*
this == other);
static void Signal(ScreenInteractive &s, int signal)
static ScreenInteractive TerminalOutput()
bool HasQuitted()
Whether the loop has quitted.
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 const Event Custom
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.
std::string GetSelection()
Returns the content of the current selection.
static ScreenInteractive FullscreenAlternateScreen()
void TrackMouse(bool enable=true)
Set whether mouse is tracked and events reported. called outside of the main loop....
void SelectionChange(std::function< void()> callback)
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...
static Event Special(std::string)
An custom event whose meaning is defined by the user of the library.
Loop is a class that manages the event loop for a component.
ScreenInteractive is a Screen that can handle events, run a main loop, and manage components.
void RequestAnimationFrame()
RequestAnimationFrame is a function that requests a new frame to be drawn in the next animation cycle...
Represent an event. It can be key press event, a terminal resize, or more ...
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
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_
Dimensions Size()
Get the terminal size.
void SetFallbackSize(const Dimensions &fallbackSize)
Override terminal size in case auto-detection fails.
std::chrono::duration< float > Duration
std::chrono::time_point< Clock > TimePoint
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
The FTXUI ftxui:: namespace.
std::unique_ptr< CapturedMouseInterface > CapturedMouse
Receiver< T > MakeReceiver()
std::string to_string(const std::wstring &s)
Convert a std::wstring into a UTF8 std::string.
std::unique_ptr< SenderImpl< T > > Sender
Element select(Element e)
Set the child to be the one focused among its siblings.
std::variant< Event, Closure, AnimationTask > Task
std::function< void()> Closure
std::shared_ptr< ComponentBase > Component