15#include <initializer_list>
45#define DEFINE_CONSOLEV2_PROPERTIES
46#define WIN32_LEAN_AND_MEAN
61#if defined(__EMSCRIPTEN__)
62#include <emscripten.h>
76 auto* screen = App::Active();
78 screen->RequestAnimationFrame();
83#if defined(__EMSCRIPTEN__)
86void ftxui_on_resize(
int columns,
int rows) {
99 class PreviousFrame :
public Screen {
101 PreviousFrame() : Screen(0, 0) {}
103 void Swap(std::vector<Cell>& cells,
104 std::vector<std::string>& hyperlinks,
107 std::swap(cells_, cells);
108 std::swap(hyperlinks_, hyperlinks);
116 App* suspended_screen_ =
nullptr;
118 const bool use_alternative_screen_;
120 bool track_mouse_ =
true;
122 std::string set_cursor_position_;
123 std::string reset_cursor_position_;
125 std::atomic<bool> quit_{
false};
126 bool installed_ =
false;
127 bool animation_requested_ =
false;
133 std::uint64_t frame_count_ = 0;
134 bool mouse_captured =
false;
135 bool previous_frame_resized_ =
false;
137 bool frame_valid_ =
false;
141 PreviousFrame previous_frame_;
144 std::vector<Element> print_above_;
146 bool force_handle_ctrl_c_ =
true;
147 bool force_handle_ctrl_z_ =
true;
150 int cursor_reset_shape_ = 0;
153 bool handle_piped_input_ =
true;
154 bool is_stdin_a_tty_ =
false;
155 bool is_stdout_a_tty_ =
false;
159 std::string terminal_name_ =
"unknown";
160 int terminal_version_ = 0;
162 std::string terminal_emulator_name_ =
"unknown";
163 std::string terminal_emulator_version_ =
"unknown";
165 std::vector<int> terminal_capabilities_;
169 struct SelectionData {
175 bool operator==(
const SelectionData& other)
const {
176 if (empty && other.empty) {
179 if (empty || other.empty) {
182 return start_x == other.start_x && start_y == other.start_y &&
183 end_x == other.end_x && end_y == other.end_y;
185 bool operator!=(
const SelectionData& other)
const {
186 return !(*
this == other);
189 SelectionData selection_data_;
190 SelectionData selection_data_previous_;
191 std::unique_ptr<Selection> selection_;
192 std::function<void()> selection_on_change_;
197 TerminalInputParser terminal_input_parser;
198 task::TaskRunner task_runner;
199 std::chrono::time_point<std::chrono::steady_clock> last_char_time =
200 std::chrono::steady_clock::now();
201 std::string output_buffer;
203 class ThrottledRequest {
205 ThrottledRequest(App::Internal* internal, std::function<
void()> send)
206 : internal_(internal), send_(std::move(send)) {}
208 void Request(
bool force =
false) {
209 if (!internal_->is_stdin_a_tty_) {
224 const auto now = std::chrono::steady_clock::now();
225 if (now - last_request_time_ < std::chrono::milliseconds(500)) {
237 void OnReply() { pending_request_ =
false; }
239 bool HasPending()
const {
240 if (!pending_request_) {
243 const auto now = std::chrono::steady_clock::now();
244 return now - last_sent_time_ < std::chrono::seconds(5);
249 last_sent_time_ = std::chrono::steady_clock::now();
250 last_request_time_ = last_sent_time_;
251 pending_request_ =
true;
255 App::Internal* internal_;
256 std::function<void()> send_;
257 bool pending_request_ =
false;
258 std::chrono::steady_clock::time_point last_request_time_ =
259 std::chrono::steady_clock::now() - std::chrono::hours(1);
260 std::chrono::steady_clock::time_point last_sent_time_ =
261 std::chrono::steady_clock::now() - std::chrono::hours(1);
264 ThrottledRequest cursor_position_request;
266 MultiReceiverBuffer<Event> event_buffer;
267 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> main_loop_receiver;
269 Internal(App* app,
AppDimension dimension,
bool use_alternative_screen);
277 void RunOnce(
const Component& component);
278 void RunOnceBlocking(
Component component);
280 bool HandleSelection(
bool handled, Event event);
282 std::string ResetCursorPosition();
283 void RequestCursorPosition(
bool force =
false);
284 void TerminalSend(std::string_view);
285 void TerminalFlush();
286 void InstallPipedInputHandling();
287 void InstallTerminalInfo();
288 void Signal(
int signal);
289 size_t FetchTerminalEvents();
290 void PostAnimationTask();
295App* g_active_screen =
nullptr;
297std::stack<Closure> on_exit_functions;
300 while (!on_exit_functions.empty()) {
301 on_exit_functions.top()();
302 on_exit_functions.pop();
307const std::string CSI =
"\x1b[";
310const std::string DCS =
"\x1bP";
313const std::string ST =
"\x1b\\";
317const std::string DECRQSS_DECSCUSR = DCS +
"$q q" + ST;
321bool SupportsDECRQSS() {
323 if (std::string_view(
util::GetEnv(
"TERM_PROGRAM")) ==
"Apple_Terminal") {
334enum class DECMode : std::uint16_t {
340 kMouseVt200Highlight = 1001,
342 kMouseBtnEventMouse = 1002,
343 kMouseAnyEvent = 1003,
346 kMouseSgrExtMode = 1006,
347 kMouseUrxvtMode = 1015,
348 kMouseSgrPixelsMode = 1016,
349 kAlternateScreen = 1049,
350 kSynchronizedOutput = 2026,
354enum class DSRMode : std::uint8_t {
358std::string Serialize(
const std::vector<DECMode>& parameters) {
361 for (
const DECMode parameter : parameters) {
365 out += std::to_string(
int(parameter));
372std::string Set(
const std::vector<DECMode>& parameters) {
373 return CSI +
"?" + Serialize(parameters) +
"h";
377std::string Reset(
const std::vector<DECMode>& parameters) {
378 return CSI +
"?" + Serialize(parameters) +
"l";
382std::string DeviceStatusReport(DSRMode ps) {
383 return CSI + std::to_string(
int(ps)) +
"n";
386class CapturedMouseImpl :
public CapturedMouseInterface {
388 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
389 : callback_(std::move(callback)) {}
390 ~CapturedMouseImpl()
override { callback_(); }
391 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
392 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
393 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
394 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
397 std::function<void(
void)> callback_;
401std::atomic<int> g_signal_exit_count = 0;
402std::atomic<int> g_signal_stop_count = 0;
403std::atomic<int> g_signal_resize_count = 0;
405std::atomic<int> g_signal_exit_count = 0;
410std::atomic<bool> g_terminal_is_raw{
false};
414std::atomic<int> g_last_signal{0};
417using SignalHandler = void (*)(int);
419std::map<int, SignalHandler> g_old_signal_handlers;
422DWORD g_original_stdout_mode = 0;
423DWORD g_original_stdin_mode = 0;
424bool g_has_original_console_mode =
false;
427std::map<int, struct sigaction> g_old_sigactions;
430struct termios g_original_termios;
431bool g_has_original_termios =
false;
437void RestoreSignalHandlerAndRaise(
int signal) {
439 auto it = g_old_signal_handlers.find(signal);
440 auto old_handler = (it != g_old_signal_handlers.end()) ? it->second : SIG_DFL;
441 std::signal(signal, old_handler);
443 auto it = g_old_sigactions.find(signal);
444 if (it != g_old_sigactions.end()) {
445 sigaction(signal, &it->second,
nullptr);
448 sa.sa_handler = SIG_DFL;
449 sigemptyset(&sa.sa_mask);
451 sigaction(signal, &sa,
nullptr);
459void RestoreTerminalEmergency() {
460 if (!g_terminal_is_raw.exchange(
false)) {
464 if (g_has_original_console_mode) {
465 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
466 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
467 SetConsoleMode(stdout_handle, g_original_stdout_mode);
468 SetConsoleMode(stdin_handle, g_original_stdin_mode);
471 if (g_has_original_termios && g_tty_fd >= 0) {
472 const char restore_seq[] =
481 std::ignore = write(STDOUT_FILENO, restore_seq,
sizeof(restore_seq) - 1);
482 tcsetattr(g_tty_fd, TCSANOW, &g_original_termios);
488void RecordSignal(
int signal) {
505 RestoreTerminalEmergency();
506 RestoreSignalHandlerAndRaise(signal);
520 g_last_signal.store(signal);
521 g_signal_exit_count++;
527 g_signal_stop_count++;
532 g_signal_resize_count++;
541void ExecuteSignalHandlers() {
542 if (g_last_signal.load() != 0) {
543 App::Private::Signal(*g_active_screen, SIGABRT);
546 int signal_exit_count = g_signal_exit_count.exchange(0);
547 while (signal_exit_count--) {
548 App::Private::Signal(*g_active_screen, SIGABRT);
552 int signal_stop_count = g_signal_stop_count.exchange(0);
553 while (signal_stop_count--) {
554 App::Private::Signal(*g_active_screen, SIGTSTP);
557 int signal_resize_count = g_signal_resize_count.exchange(0);
558 while (signal_resize_count--) {
559 App::Private::Signal(*g_active_screen, SIGWINCH);
564void InstallSignalHandler(
int sig) {
566 auto old_signal_handler = std::signal(sig, RecordSignal);
567 g_old_signal_handlers[sig] = old_signal_handler;
568 on_exit_functions.emplace(
569 [=] { std::ignore = std::signal(sig, old_signal_handler); });
572 sa.sa_handler = RecordSignal;
573 sigemptyset(&sa.sa_mask);
574 sa.sa_flags = SA_RESTART;
575 struct sigaction old_sa;
576 sigaction(sig, &sa, &old_sa);
577 g_old_sigactions[sig] = old_sa;
578 on_exit_functions.emplace([=] { sigaction(sig, &old_sa,
nullptr); });
584App::Internal::Internal(App* app,
586 bool use_alternative_screen)
588 dimension_(dimension),
589 use_alternative_screen_(use_alternative_screen),
590 terminal_input_parser([&](Event event) {
591 event_buffer.Push(std::move(event));
593 cursor_position_request(
this, [
this] {
594 TerminalSend(DeviceStatusReport(DSRMode::kCursor));
596 main_loop_receiver = event_buffer.CreateReceiver();
599void App::Internal::ExitNow() {
603void App::Internal::Install() {
604 frame_valid_ =
false;
605 previous_frame_ = PreviousFrame();
614 InstallPipedInputHandling();
618 on_exit_functions.emplace([
this] { TerminalFlush(); });
622 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
623 InstallSignalHandler(signal);
629 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
630 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
634 GetConsoleMode(stdout_handle, &out_mode);
635 GetConsoleMode(stdin_handle, &in_mode);
636 g_original_stdout_mode = out_mode;
637 g_original_stdin_mode = in_mode;
638 g_has_original_console_mode =
true;
639 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
640 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
643 const int enable_virtual_terminal_processing = 0x0004;
644 const int disable_newline_auto_return = 0x0008;
645 out_mode |= enable_virtual_terminal_processing;
646 out_mode |= disable_newline_auto_return;
649 const int enable_line_input = 0x0002;
650 const int enable_echo_input = 0x0004;
651 const int enable_virtual_terminal_input = 0x0200;
652 const int enable_window_input = 0x0008;
653 in_mode &= ~enable_echo_input;
654 in_mode &= ~enable_line_input;
655 in_mode |= enable_virtual_terminal_input;
656 in_mode |= enable_window_input;
658 SetConsoleMode(stdin_handle, in_mode);
659 SetConsoleMode(stdout_handle, out_mode);
661 for (
const int signal :
662 {SIGWINCH, SIGTSTP, SIGBUS, SIGSYS, SIGQUIT, SIGHUP}) {
663 InstallSignalHandler(signal);
666 struct termios terminal;
667 tcgetattr(tty_fd_, &terminal);
668 g_original_termios = terminal;
670 g_has_original_termios =
true;
671 on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
672 tcsetattr(tty_fd_, TCSANOW, &terminal);
676 terminal.c_iflag &= ~IGNBRK;
677 terminal.c_iflag &= ~BRKINT;
679 terminal.c_iflag &= ~PARMRK;
680 terminal.c_iflag &= ~ISTRIP;
681 terminal.c_iflag &= ~INLCR;
682 terminal.c_iflag &= ~IGNCR;
683 terminal.c_iflag &= ~ICRNL;
684 terminal.c_iflag &= ~IXON;
686 terminal.c_lflag &= ~ECHO;
687 terminal.c_lflag &= ~ECHONL;
688 terminal.c_lflag &= ~ICANON;
689 terminal.c_lflag &= ~ISIG;
694 terminal.c_lflag &= ~IEXTEN;
695 terminal.c_cflag |= CS8;
697 terminal.c_cc[VMIN] = 0;
699 terminal.c_cc[VTIME] = 0;
701 tcsetattr(tty_fd_, TCSANOW, &terminal);
705 auto enable = [&](
const std::vector<DECMode>& parameters) {
706 TerminalSend(Set(parameters));
707 on_exit_functions.emplace(
708 [
this, parameters] { TerminalSend(Reset(parameters)); });
711 auto disable = [&](
const std::vector<DECMode>& parameters) {
712 TerminalSend(Reset(parameters));
713 on_exit_functions.emplace(
714 [
this, parameters] { TerminalSend(Set(parameters)); });
717 if (use_alternative_screen_) {
719 DECMode::kAlternateScreen,
728 enable({DECMode::kMouseVt200});
729 enable({DECMode::kMouseAnyEvent});
730 enable({DECMode::kMouseUrxvtMode});
731 enable({DECMode::kMouseSgrExtMode});
738 InstallTerminalInfo();
745 g_terminal_is_raw =
true;
748void App::Internal::Uninstall() {
749 g_terminal_is_raw =
false;
753 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
754 auto closing_receiver =
755 event_buffer.CreateReceiverAt(main_loop_receiver->index());
756 auto start = std::chrono::steady_clock::now();
757 while (cursor_position_request.HasPending()) {
758 FetchTerminalEvents();
760 while (closing_receiver->Has()) {
761 const auto event = closing_receiver->Pop();
762 if (event.is_cursor_position()) {
763 cursor_x_ =
event.cursor_x();
764 cursor_y_ =
event.cursor_y();
765 cursor_position_request.OnReply();
769 task_runner.RunUntilIdle();
771 if (std::chrono::steady_clock::now() - start >
772 std::chrono::milliseconds(400)) {
775 std::this_thread::sleep_for(std::chrono::milliseconds(10));
782void App::Internal::PreMain() {
784 if (g_active_screen) {
785 std::swap(suspended_screen_, g_active_screen);
788 if (suspended_screen_->internal_->frame_count_ != 0) {
789 suspended_screen_->internal_->TerminalSend(
790 suspended_screen_->internal_->ResetCursorPosition());
791 suspended_screen_->ResetPosition(
792 suspended_screen_->internal_->output_buffer,
794 suspended_screen_->dimx_ = 0;
795 suspended_screen_->dimy_ = 0;
799 suspended_screen_->internal_->Uninstall();
807 g_active_screen = public_;
808 g_active_screen->internal_->Install();
810 previous_animation_time_ = animation::Clock::now();
813void App::Internal::PostMain() {
815 TerminalSend(ResetCursorPosition());
817 g_active_screen =
nullptr;
820 if (suspended_screen_) {
822 if (frame_count_ != 0) {
823 public_->ResetPosition(output_buffer,
true);
828 std::swap(g_active_screen, suspended_screen_);
829 g_active_screen->internal_->Install();
836 if (!use_alternative_screen_) {
839 std::cout << std::flush;
842 int sig = g_last_signal.exchange(0);
844 RestoreSignalHandlerAndRaise(sig);
848bool App::Internal::HasQuitted() {
852void App::Internal::RunOnce(
const Component& component) {
853 const AutoReset set_component(&component_, component);
854 ExecuteSignalHandlers();
855 FetchTerminalEvents();
857 while (!quit_ && main_loop_receiver->Has()) {
858 public_->Post(main_loop_receiver->Pop());
862 const size_t executed_task = task_runner.ExecutedTasks();
863 task_runner.RunUntilIdle();
865 if (executed_task == task_runner.ExecutedTasks()) {
869 ExecuteSignalHandlers();
872 if (selection_data_previous_ != selection_data_) {
873 selection_data_previous_ = selection_data_;
874 if (selection_on_change_) {
875 selection_on_change_();
876 public_->Post(Event::Custom);
881void App::Internal::RunOnceBlocking(
Component component) {
883 const auto time_per_frame = std::chrono::microseconds(16666);
885 auto time = std::chrono::steady_clock::now();
886 const size_t executed_task = task_runner.ExecutedTasks();
889 while (executed_task == task_runner.ExecutedTasks() && !HasQuitted()) {
892 const auto now = std::chrono::steady_clock::now();
893 const auto delta = now - time;
896 if (delta < time_per_frame) {
897 const auto sleep_duration = time_per_frame - delta;
898 std::this_thread::sleep_for(sleep_duration);
903void App::Internal::HandleTask(
Component component,
Task& task) {
906 using T = std::decay_t<
decltype(arg)>;
910 if constexpr (std::is_same_v<T, Event>) {
912 if (arg.is_cursor_position()) {
913 cursor_x_ = arg.cursor_x();
914 cursor_y_ = arg.cursor_y();
915 cursor_position_request.OnReply();
919 if (arg.is_cursor_shape()) {
920 cursor_reset_shape_ = arg.cursor_shape();
924 if (arg.IsTerminalCapabilities()) {
925 terminal_capabilities_ = arg.TerminalCapabilities();
929 if (arg.IsTerminalNameVersion()) {
930 terminal_name_ = arg.TerminalName();
931 terminal_version_ = arg.TerminalVersion();
935 if (arg.IsTerminalEmulator()) {
936 terminal_emulator_name_ = arg.TerminalEmulatorName();
937 terminal_emulator_version_ = arg.TerminalEmulatorVersion();
941 if (arg.is_mouse()) {
942 arg.mouse().x -= cursor_x_;
943 arg.mouse().y -= cursor_y_;
946 arg.screen_ = public_;
948 bool handled = component->OnEvent(arg);
949 handled = HandleSelection(handled, arg);
951 if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
952 RecordSignal(SIGINT);
956 if (arg == Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
957 RecordSignal(SIGTSTP);
961 frame_valid_ =
false;
966 if constexpr (std::is_same_v<T, Closure>) {
972 if constexpr (std::is_same_v<T, AnimationTask>) {
973 if (!animation_requested_) {
977 animation_requested_ =
false;
980 previous_animation_time_ = now;
982 animation::Params params(delta);
983 component->OnAnimation(params);
984 frame_valid_ =
false;
992bool App::Internal::HandleSelection(
bool handled, Event event) {
994 selection_pending_ =
nullptr;
995 selection_data_.empty =
true;
996 selection_ =
nullptr;
1000 if (!event.is_mouse()) {
1004 auto& mouse =
event.mouse();
1005 if (mouse.button != Mouse::Left) {
1009 if (mouse.motion == Mouse::Pressed) {
1010 selection_pending_ = public_->CaptureMouse();
1011 selection_data_.start_x = mouse.x;
1012 selection_data_.start_y = mouse.y;
1013 selection_data_.end_x = mouse.x;
1014 selection_data_.end_y = mouse.y;
1018 if (!selection_pending_) {
1022 if (mouse.motion == Mouse::Moved) {
1023 if ((mouse.x != selection_data_.end_x) ||
1024 (mouse.y != selection_data_.end_y)) {
1025 selection_data_.end_x = mouse.x;
1026 selection_data_.end_y = mouse.y;
1027 selection_data_.empty =
false;
1033 if (mouse.motion == Mouse::Released) {
1034 selection_pending_ =
nullptr;
1035 selection_data_.end_x = mouse.x;
1036 selection_data_.end_y = mouse.y;
1037 selection_data_.empty =
false;
1044void App::Internal::Draw(
Component component) {
1048 auto document = component->Render();
1052 document->ComputeRequirement();
1053 switch (dimension_) {
1055 dimx = public_->dimx_;
1056 dimy = public_->dimy_;
1059 dimx = terminal.dimx;
1060 dimy =
util::clamp(document->requirement().min_y, 0, terminal.dimy);
1063 dimx = terminal.dimx;
1064 dimy = terminal.dimy;
1067 dimx =
util::clamp(document->requirement().min_x, 0, terminal.dimx);
1068 dimy =
util::clamp(document->requirement().min_y, 0, terminal.dimy);
1074 TerminalSend(Set({DECMode::kSynchronizedOutput}));
1077 TerminalSend(
"\033[?25l");
1079 const bool resized =
1080 frame_count_ == 0 || (dimx != public_->dimx_) || (dimy != public_->dimy_);
1082 const bool update_in_place = !resized && print_above_.empty();
1083 TerminalSend(ResetCursorPosition());
1085 if (frame_count_ != 0) {
1090 public_->ResetPosition(output_buffer, resized || !print_above_.empty());
1094 if ((dimx < public_->dimx_) && !use_alternative_screen_) {
1095 TerminalSend(
"\033[J");
1096 TerminalSend(
"\033[H");
1102 for (
auto&
element : print_above_) {
1104 Dimension::Fit(
element,
true).dimy;
1105 Screen screen(terminal.dimx, height);
1107 screen.ToString(output_buffer);
1108 TerminalSend(
"\r\n");
1110 print_above_.clear();
1114 public_->dimx_ = dimx;
1115 public_->dimy_ = dimy;
1116 public_->cells_ = std::vector<Cell>(
static_cast<size_t>(dimx) *
1117 static_cast<size_t>(dimy));
1118 Cursor cursor = public_->cursor_;
1119 cursor.x = dimx - 1;
1120 cursor.y = dimy - 1;
1121 public_->SetCursor(cursor);
1127 if (!use_alternative_screen_ && is_stdout_a_tty_) {
1128 RequestCursorPosition(previous_frame_resized_);
1130 previous_frame_resized_ = resized;
1132 selection_ = selection_data_.empty
1133 ? std::make_unique<Selection>()
1134 : std::make_unique<Selection>(
1135 selection_data_.start_x, selection_data_.start_y,
1136 selection_data_.end_x, selection_data_.end_y);
1137 Render(*public_, document.get(), *selection_);
1141 const int dx = public_->dimx_ - 1 - public_->cursor_.x +
1142 int(public_->dimx_ != terminal.dimx);
1143 const int dy = public_->dimy_ - 1 - public_->cursor_.y;
1145 set_cursor_position_.clear();
1146 reset_cursor_position_.clear();
1149 set_cursor_position_ +=
"\x1B[" + std::to_string(dy) +
"A";
1150 reset_cursor_position_ +=
"\x1B[" + std::to_string(dy) +
"B";
1154 set_cursor_position_ +=
"\x1B[" + std::to_string(dx) +
"D";
1155 reset_cursor_position_ +=
"\x1B[" + std::to_string(dx) +
"C";
1158 if (public_->cursor_.shape != Screen::Cursor::Hidden) {
1159 set_cursor_position_ +=
"\033[?25h";
1160 set_cursor_position_ +=
1161 "\033[" + std::to_string(
int(public_->cursor_.shape)) +
" q";
1165 if (update_in_place) {
1166 public_->ToString(output_buffer, previous_frame_);
1168 public_->ToString(output_buffer);
1170 TerminalSend(set_cursor_position_);
1171 TerminalSend(Reset({DECMode::kSynchronizedOutput}));
1174 previous_frame_.Swap(public_->cells_, public_->hyperlinks_, public_->dimx_,
1176 public_->cells_.resize(
static_cast<size_t>(public_->dimx_) *
1177 static_cast<size_t>(public_->dimy_));
1179 frame_valid_ =
true;
1183std::string App::Internal::ResetCursorPosition() {
1184 std::string result = std::move(reset_cursor_position_);
1185 reset_cursor_position_ =
"";
1189void App::Internal::RequestCursorPosition(
bool force) {
1190 cursor_position_request.Request(force);
1193void App::Internal::TerminalSend(std::string_view s) {
1197void App::Internal::TerminalFlush() {
1199 output_buffer +=
'\0';
1200 std::cout << output_buffer << std::flush;
1201 output_buffer.clear();
1204void App::Internal::InstallPipedInputHandling() {
1205 is_stdin_a_tty_ =
false;
1206 is_stdout_a_tty_ =
false;
1207#if defined(__EMSCRIPTEN__)
1208 is_stdin_a_tty_ =
true;
1209 is_stdout_a_tty_ =
true;
1210#elif defined(_WIN32)
1211 is_stdin_a_tty_ = _isatty(_fileno(stdin));
1212 is_stdout_a_tty_ = _isatty(_fileno(stdout));
1214 tty_fd_ = STDIN_FILENO;
1215 is_stdout_a_tty_ = isatty(STDOUT_FILENO);
1219 if (!handle_piped_input_) {
1220 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1221 }
else if (isatty(STDIN_FILENO)) {
1222 is_stdin_a_tty_ =
true;
1225 tty_fd_ = open(
"/dev/tty", O_RDONLY);
1228 tty_fd_ = STDIN_FILENO;
1229 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1231 is_stdin_a_tty_ =
true;
1233 on_exit_functions.emplace([
this] {
1242void App::Internal::InstallTerminalInfo() {
1245 if (is_stdout_a_tty_) {
1246 if (SupportsDECRQSS()) {
1247 TerminalSend(DECRQSS_DECSCUSR);
1249 TerminalSend(
"\033[>q");
1250 TerminalSend(
"\033[>c");
1251 TerminalSend(
"\033[c");
1256 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
1260 auto setup_receiver = event_buffer.CreateReceiver();
1261 auto start = std::chrono::steady_clock::now();
1262 bool terminal_capabilities_received =
false;
1265 FetchTerminalEvents();
1266 while (setup_receiver->Has()) {
1267 const auto event = setup_receiver->Pop();
1268 if (event.is_cursor_shape()) {
1269 cursor_reset_shape_ =
event.cursor_shape();
1272 if (event.IsTerminalCapabilities()) {
1273 terminal_capabilities_ =
event.TerminalCapabilities();
1274 terminal_capabilities_received =
true;
1277 if (event.IsTerminalNameVersion()) {
1278 terminal_name_ =
event.TerminalName();
1279 terminal_version_ =
event.TerminalVersion();
1282 if (event.IsTerminalEmulator()) {
1283 terminal_emulator_name_ =
event.TerminalEmulatorName();
1284 terminal_emulator_version_ =
event.TerminalEmulatorVersion();
1291 if (terminal_capabilities_received) {
1295 if (std::chrono::steady_clock::now() - start >
1296 std::chrono::milliseconds(500)) {
1299 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1308 util::GetEnv(
"TERM_PROGRAM"), terminal_name_, terminal_emulator_name_,
1309 terminal_capabilities_);
1311 quirks.SetColorSupport(color_support);
1313 const bool is_modern_emulator = (terminal_emulator_name_ !=
"unknown");
1314 const bool is_vt220_plus =
1315 (terminal_name_ !=
"vt100" && terminal_name_ !=
"unknown");
1316 bool reports_utf8 =
false;
1317 for (
const int x : terminal_capabilities_) {
1319 reports_utf8 =
true;
1330 bool modern = is_modern_emulator || is_vt220_plus || reports_utf8;
1332 quirks.SetBlockCharacters(
true);
1333 quirks.SetCursorHiding(
true);
1334 quirks.SetComponentAscii(
false);
1339 on_exit_functions.emplace([
this] {
1340 TerminalSend(
"\033[?25h");
1341 if (is_stdout_a_tty_) {
1342 TerminalSend(
"\033[" + std::to_string(cursor_reset_shape_) +
" q");
1347void App::Internal::Signal(
int signal) {
1348 if (signal == SIGABRT) {
1355 if (signal == SIGTSTP) {
1357 TerminalSend(ResetCursorPosition());
1358 public_->ResetPosition(output_buffer,
true);
1362 (void)std::raise(SIGTSTP);
1368 if (signal == SIGWINCH) {
1369 public_->Post(Event::Special({0}));
1375size_t App::Internal::FetchTerminalEvents() {
1377 auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
1379 auto console = GetStdHandle(STD_INPUT_HANDLE);
1380 DWORD number_of_events = 0;
1381 if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
1382 return std::vector<INPUT_RECORD>();
1384 if (number_of_events <= 0) {
1386 return std::vector<INPUT_RECORD>();
1389 std::vector<INPUT_RECORD> records(number_of_events);
1390 DWORD number_of_events_read = 0;
1391 if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
1392 &number_of_events_read)) {
1393 return std::vector<INPUT_RECORD>();
1395 records.resize(number_of_events_read);
1399 auto records = get_input_records();
1400 if (records.size() == 0) {
1401 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1402 const size_t timeout_microseconds =
1403 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1404 terminal_input_parser.Timeout(timeout_microseconds);
1407 last_char_time = std::chrono::steady_clock::now();
1412 std::wstring wstring;
1413 for (
const auto& r : records) {
1414 switch (r.EventType) {
1416 auto key_event = r.Event.KeyEvent;
1418 if (key_event.bKeyDown == FALSE) {
1421 const wchar_t wc = key_event.uChar.UnicodeChar;
1423 if (wc >= 0xd800 && wc <= 0xdbff) {
1428 terminal_input_parser.Add(it);
1432 case WINDOW_BUFFER_SIZE_EVENT:
1433 public_->Post(Event::Special({0}));
1442 return records.size();
1443#elif defined(__EMSCRIPTEN__)
1446 std::array<char, 4096> out{};
1447 const ssize_t l = read(STDIN_FILENO, out.data(), out.size());
1449 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1450 const size_t timeout_microseconds =
1451 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1452 terminal_input_parser.Timeout(timeout_microseconds);
1455 last_char_time = std::chrono::steady_clock::now();
1458 for (ssize_t i = 0; i < l; ++i) {
1459 terminal_input_parser.Add(out.at(
static_cast<size_t>(i)));
1463 struct pollfd pfd = {tty_fd_, POLLIN, 0};
1464 const int poll_result = poll(&pfd, 1, 0);
1465 if (poll_result <= 0) {
1466 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1467 const size_t timeout_ms =
1468 std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
1469 terminal_input_parser.Timeout(
static_cast<int>(timeout_ms));
1472 last_char_time = std::chrono::steady_clock::now();
1477 constexpr size_t kMaxBytesPerFetch = 64 * 1024;
1478 std::array<char, 4096> out{};
1480 while (total < kMaxBytesPerFetch) {
1481 const ssize_t l = read(tty_fd_, out.data(), out.size());
1487 for (ssize_t i = 0; i < l; ++i) {
1488 terminal_input_parser.Add(out.at(
static_cast<size_t>(i)));
1490 total +=
static_cast<size_t>(l);
1493 if (poll(&pfd, 1, 0) <= 0) {
1501void App::Internal::PostAnimationTask() {
1502 public_->Post(AnimationTask());
1506 task_runner.PostDelayedTask([
this] { PostAnimationTask(); },
1507 std::chrono::milliseconds(15));
1510App::App(std::unique_ptr<Internal> internal,
int dimx,
int dimy)
1511 : Screen(dimx, dimy), internal_(std::move(internal)) {
1512 internal_->public_ =
this;
1515App::App(App&& other) noexcept : Screen(std::move(other)) {
1516 internal_ = std::move(other.internal_);
1518 internal_->public_ =
this;
1522App& App::operator=(App&& other)
noexcept {
1523 Screen::operator=(std::move(other));
1524 internal_ = std::move(other.internal_);
1526 internal_->public_ =
this;
1531App::~App() =
default;
1534App App::FixedSize(
int dimx,
int dimy) {
1537 return App(std::move(internal), dimx, dimy);
1541App App::Fullscreen() {
1542 return FullscreenAlternateScreen();
1546App App::FullscreenPrimaryScreen() {
1550 return App(std::move(internal), terminal.dimx, terminal.dimy);
1554App App::FullscreenAlternateScreen() {
1558 return App(std::move(internal), terminal.dimx, terminal.dimy);
1562App App::FitComponent() {
1566 return App(std::move(internal), terminal.dimx, terminal.dimy);
1570App App::TerminalOutput() {
1574 return App(std::move(internal), terminal.dimx, terminal.dimy);
1577void App::TrackMouse(
bool enable) {
1578 internal_->track_mouse_ = enable;
1581void App::HandlePipedInput(
bool enable) {
1582 internal_->handle_piped_input_ = enable;
1587 return g_active_screen;
1591 class Loop loop(this, std::move(component));
1596 Post([
this] { internal_->ExitNow(); });
1599Closure App::ExitLoopClosure() {
1600 return [
this] { Exit(); };
1605 internal_->print_above_.push_back(
element);
1606 internal_->frame_valid_ =
false;
1610void App::Post(
Task task) {
1611 internal_->task_runner.PostTask([
this, task = std::move(task)]()
mutable {
1612 if (internal_->component_) {
1613 internal_->HandleTask(internal_->component_, task);
1618 if (std::holds_alternative<Closure>(task)) {
1619 std::get<Closure>(task)();
1624void App::PostEvent(Event event) {
1627 Post(
Task(std::move(event)));
1631void App::PostEventOrExecute(
Closure closure) {
1635 if (
auto* app = App::Active()) {
1636 app->Post(std::move(closure));
1642void App::RequestAnimationFrame() {
1643 if (internal_->animation_requested_) {
1646 internal_->animation_requested_ =
true;
1647 auto now = animation::Clock::now();
1648 const auto time_histeresis = std::chrono::milliseconds(33);
1649 if (now - internal_->previous_animation_time_ >= time_histeresis) {
1650 internal_->previous_animation_time_ = now;
1655 if (internal_->mouse_captured) {
1658 internal_->mouse_captured =
true;
1659 return std::make_unique<CapturedMouseImpl>(
1660 [
this] { internal_->mouse_captured =
false; });
1665 internal_->Uninstall();
1667 internal_->Install();
1671void App::ForceHandleCtrlC(
bool force) {
1672 internal_->force_handle_ctrl_c_ = force;
1675void App::ForceHandleCtrlZ(
bool force) {
1676 internal_->force_handle_ctrl_z_ = force;
1679std::string App::GetSelection() {
1680 if (!internal_->selection_) {
1683 return internal_->selection_->GetParts();
1686void App::SelectionChange(std::function<
void()> callback) {
1687 internal_->selection_on_change_ = std::move(callback);
1690const std::string& App::TerminalName()
const {
1691 return internal_->terminal_name_;
1694int App::TerminalVersion()
const {
1695 return internal_->terminal_version_;
1698const std::string& App::TerminalEmulatorName()
const {
1699 return internal_->terminal_emulator_name_;
1702const std::string& App::TerminalEmulatorVersion()
const {
1703 return internal_->terminal_emulator_version_;
1706const std::vector<int>& App::TerminalCapabilities()
const {
1707 return internal_->terminal_capabilities_;
1710std::vector<std::string> App::TerminalCapabilityNames()
const {
1711 return Event::TerminalCapabilities(
"", internal_->terminal_capabilities_)
1712 .TerminalCapabilityNames();
1717void App::ExitNow() {
1718 internal_->ExitNow();
1720void App::Install() {
1721 internal_->Install();
1723void App::Uninstall() {
1724 internal_->Uninstall();
1726void App::PreMain() {
1727 internal_->PreMain();
1729void App::PostMain() {
1730 internal_->PostMain();
1732bool App::HasQuitted() {
1733 return internal_->HasQuitted();
1735void App::RunOnce(
const Component& component) {
1736 internal_->RunOnce(component);
1738void App::RunOnceBlocking(
Component component) {
1739 internal_->RunOnceBlocking(component);
1742 internal_->HandleTask(component, task);
1744bool App::HandleSelection(
bool handled, Event event) {
1745 return internal_->HandleSelection(handled, event);
1748 internal_->Draw(component);
1750std::string App::ResetCursorPosition() {
1751 return internal_->ResetCursorPosition();
1753void App::RequestCursorPosition(
bool force) {
1754 internal_->RequestCursorPosition(force);
1756void App::TerminalSend(std::string_view s) {
1757 internal_->TerminalSend(s);
1759void App::TerminalFlush() {
1760 internal_->TerminalFlush();
1762void App::InstallPipedInputHandling() {
1763 internal_->InstallPipedInputHandling();
1765void App::InstallTerminalInfo() {
1766 internal_->InstallTerminalInfo();
1768void App::Signal(
int signal) {
1769 internal_->Signal(signal);
1771size_t App::FetchTerminalEvents() {
1772 return internal_->FetchTerminalEvents();
1774void App::PostAnimationTask() {
1775 internal_->PostAnimationTask();
1778Loop::Loop(App* screen,
Component component)
1779 : screen_(screen), component_(std::move(component)) {
1784 screen_->PostMain();
1787bool Loop::HasQuitted() {
1788 return screen_->HasQuitted();
1791void Loop::RunOnce() {
1792 screen_->RunOnce(component_);
1795void Loop::RunOnceBlocking() {
1796 screen_->RunOnceBlocking(component_);
1800 while (!HasQuitted()) {
Quirks GetQuirks()
Get the terminal quirks.
Dimensions Size()
Get the terminal size.
void SetQuirks(const Quirks &quirks)
Override terminal quirks.
The FTXUI ftxui::animation:: namespace.
void SetFallbackSize(const Dimensions &fallbackSize)
Override terminal size in case auto-detection fails.
Color ComputeColorSupport(std::string_view term, std::string_view colorterm, std::string_view term_program, std::string_view terminal_name, std::string_view terminal_emulator_name, const std::vector< int > &capabilities)
Compute the color support based on environment variables and terminal identification.
std::chrono::duration< float > Duration
std::chrono::time_point< Clock > TimePoint
void RequestAnimationFrame()
const char * GetEnv(const char *name)
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
The FTXUI ftxui:: namespace.
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::shared_ptr< Node > Element
std::string to_string(std::wstring_view s)
Convert a std::wstring into a UTF8 std::string.
std::variant< Event, Closure, AnimationTask > Task
void Render(Screen &screen, Node *node, Selection &selection)
std::function< void()> Closure
std::shared_ptr< ComponentBase > Component