15#include <initializer_list>
44#define DEFINE_CONSOLEV2_PROPERTIES
45#define WIN32_LEAN_AND_MEAN
60#if defined(__EMSCRIPTEN__)
61#include <emscripten.h>
75 auto* screen = App::Active();
77 screen->RequestAnimationFrame();
82#if defined(__EMSCRIPTEN__)
85void ftxui_on_resize(
int columns,
int rows) {
98 App* suspended_screen_ =
nullptr;
100 const bool use_alternative_screen_;
102 bool track_mouse_ =
true;
104 std::string set_cursor_position_;
105 std::string reset_cursor_position_;
107 std::atomic<bool> quit_{
false};
108 bool installed_ =
false;
109 bool animation_requested_ =
false;
115 std::uint64_t frame_count_ = 0;
116 bool mouse_captured =
false;
117 bool previous_frame_resized_ =
false;
119 bool frame_valid_ =
false;
121 bool force_handle_ctrl_c_ =
true;
122 bool force_handle_ctrl_z_ =
true;
124 int cursor_reset_shape_ = 1;
127 bool handle_piped_input_ =
true;
128 bool is_stdin_a_tty_ =
false;
129 bool is_stdout_a_tty_ =
false;
134 std::string terminal_name_ =
"unknown";
135 int terminal_version_ = 0;
137 std::string terminal_emulator_name_ =
"unknown";
138 std::string terminal_emulator_version_ =
"unknown";
140 std::vector<int> terminal_capabilities_;
144 struct SelectionData {
150 bool operator==(
const SelectionData& other)
const {
151 if (empty && other.empty) {
154 if (empty || other.empty) {
157 return start_x == other.start_x && start_y == other.start_y &&
158 end_x == other.end_x && end_y == other.end_y;
160 bool operator!=(
const SelectionData& other)
const {
161 return !(*
this == other);
164 SelectionData selection_data_;
165 SelectionData selection_data_previous_;
166 std::unique_ptr<Selection> selection_;
167 std::function<void()> selection_on_change_;
172 TerminalInputParser terminal_input_parser;
173 task::TaskRunner task_runner;
174 std::chrono::time_point<std::chrono::steady_clock> last_char_time =
175 std::chrono::steady_clock::now();
176 std::string output_buffer;
178 class ThrottledRequest {
180 ThrottledRequest(App::Internal* internal, std::function<
void()> send)
181 : internal_(internal), send_(std::move(send)) {}
183 void Request(
bool force =
false) {
184 if (!internal_->is_stdin_a_tty_) {
199 const auto now = std::chrono::steady_clock::now();
200 if (now - last_request_time_ < std::chrono::milliseconds(500)) {
213 void OnReply() { pending_request_ =
false; }
215 bool HasPending()
const {
216 if (!pending_request_) {
219 const auto now = std::chrono::steady_clock::now();
220 return now - last_sent_time_ < std::chrono::seconds(5);
225 last_sent_time_ = std::chrono::steady_clock::now();
226 last_request_time_ = last_sent_time_;
227 pending_request_ =
true;
231 App::Internal* internal_;
232 std::function<void()> send_;
233 bool pending_request_ =
false;
234 std::chrono::steady_clock::time_point last_request_time_ =
235 std::chrono::steady_clock::now() - std::chrono::hours(1);
236 std::chrono::steady_clock::time_point last_sent_time_ =
237 std::chrono::steady_clock::now() - std::chrono::hours(1);
240 ThrottledRequest cursor_position_request;
242 MultiReceiverBuffer<Event> event_buffer;
243 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> main_loop_receiver;
245 Internal(App* app,
AppDimension dimension,
bool use_alternative_screen);
253 void RunOnce(
const Component& component);
254 void RunOnceBlocking(
Component component);
256 bool HandleSelection(
bool handled, Event event);
258 std::string ResetCursorPosition();
259 void RequestCursorPosition(
bool force =
false);
260 void TerminalSend(std::string_view);
261 void TerminalFlush();
262 void InstallPipedInputHandling();
263 void InstallTerminalInfo();
264 void Signal(
int signal);
265 size_t FetchTerminalEvents();
266 void PostAnimationTask();
271App* g_active_screen =
nullptr;
273std::stack<Closure> on_exit_functions;
276 while (!on_exit_functions.empty()) {
277 on_exit_functions.top()();
278 on_exit_functions.pop();
283const std::string CSI =
"\x1b[";
286const std::string DCS =
"\x1bP";
289const std::string ST =
"\x1b\\";
293const std::string DECRQSS_DECSCUSR = DCS +
"$q q" + ST;
296enum class DECMode : std::uint16_t {
302 kMouseVt200Highlight = 1001,
304 kMouseBtnEventMouse = 1002,
305 kMouseAnyEvent = 1003,
308 kMouseSgrExtMode = 1006,
309 kMouseUrxvtMode = 1015,
310 kMouseSgrPixelsMode = 1016,
311 kAlternateScreen = 1049,
315enum class DSRMode : std::uint8_t {
319std::string Serialize(
const std::vector<DECMode>& parameters) {
322 for (
const DECMode parameter : parameters) {
326 out += std::to_string(
int(parameter));
333std::string Set(
const std::vector<DECMode>& parameters) {
334 return CSI +
"?" + Serialize(parameters) +
"h";
338std::string Reset(
const std::vector<DECMode>& parameters) {
339 return CSI +
"?" + Serialize(parameters) +
"l";
343std::string DeviceStatusReport(DSRMode ps) {
344 return CSI + std::to_string(
int(ps)) +
"n";
347class CapturedMouseImpl :
public CapturedMouseInterface {
349 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
350 : callback_(std::move(callback)) {}
351 ~CapturedMouseImpl()
override { callback_(); }
352 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
353 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
354 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
355 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
358 std::function<void(
void)> callback_;
362std::atomic<int> g_signal_exit_count = 0;
363std::atomic<int> g_signal_stop_count = 0;
364std::atomic<int> g_signal_resize_count = 0;
366std::atomic<int> g_signal_exit_count = 0;
372std::atomic<bool> g_terminal_is_raw{
false};
376std::atomic<int> g_last_signal{0};
379using SignalHandler = void (*)(int);
382std::map<int, SignalHandler> g_old_signal_handlers;
385DWORD g_original_stdout_mode = 0;
386DWORD g_original_stdin_mode = 0;
387bool g_has_original_console_mode =
false;
391std::map<int, struct sigaction> g_old_sigactions;
395struct termios g_original_termios;
396bool g_has_original_termios =
false;
403void RestoreSignalHandlerAndRaise(
int signal) {
405 auto it = g_old_signal_handlers.find(signal);
406 auto old_handler = (it != g_old_signal_handlers.end()) ? it->second : SIG_DFL;
407 std::signal(signal, old_handler);
409 auto it = g_old_sigactions.find(signal);
410 if (it != g_old_sigactions.end()) {
411 sigaction(signal, &it->second,
nullptr);
414 sa.sa_handler = SIG_DFL;
415 sigemptyset(&sa.sa_mask);
417 sigaction(signal, &sa,
nullptr);
425void RestoreTerminalEmergency() {
426 if (!g_terminal_is_raw.exchange(
false)) {
430 if (g_has_original_console_mode) {
431 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
432 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
433 SetConsoleMode(stdout_handle, g_original_stdout_mode);
434 SetConsoleMode(stdin_handle, g_original_stdin_mode);
437 if (g_has_original_termios && g_tty_fd >= 0) {
438 const char restore_seq[] =
447 std::ignore = write(STDOUT_FILENO, restore_seq,
sizeof(restore_seq) - 1);
448 tcsetattr(g_tty_fd, TCSANOW, &g_original_termios);
454void RecordSignal(
int signal) {
471 RestoreTerminalEmergency();
472 RestoreSignalHandlerAndRaise(signal);
487 g_last_signal.store(signal);
488 g_signal_exit_count++;
494 g_signal_stop_count++;
499 g_signal_resize_count++;
508void ExecuteSignalHandlers() {
509 if (g_last_signal.load() != 0) {
510 App::Private::Signal(*g_active_screen, SIGABRT);
513 int signal_exit_count = g_signal_exit_count.exchange(0);
514 while (signal_exit_count--) {
515 App::Private::Signal(*g_active_screen, SIGABRT);
519 int signal_stop_count = g_signal_stop_count.exchange(0);
520 while (signal_stop_count--) {
521 App::Private::Signal(*g_active_screen, SIGTSTP);
524 int signal_resize_count = g_signal_resize_count.exchange(0);
525 while (signal_resize_count--) {
526 App::Private::Signal(*g_active_screen, SIGWINCH);
531void InstallSignalHandler(
int sig) {
533 auto old_signal_handler = std::signal(sig, RecordSignal);
534 g_old_signal_handlers[sig] = old_signal_handler;
535 on_exit_functions.emplace(
536 [=] { std::ignore = std::signal(sig, old_signal_handler); });
539 sa.sa_handler = RecordSignal;
540 sigemptyset(&sa.sa_mask);
541 sa.sa_flags = SA_RESTART;
542 struct sigaction old_sa;
543 sigaction(sig, &sa, &old_sa);
544 g_old_sigactions[sig] = old_sa;
545 on_exit_functions.emplace([=] { sigaction(sig, &old_sa,
nullptr); });
551App::Internal::Internal(App* app,
553 bool use_alternative_screen)
555 dimension_(dimension),
556 use_alternative_screen_(use_alternative_screen),
557 terminal_input_parser([&](Event event) {
558 event_buffer.Push(std::move(event));
560 cursor_position_request(
this, [
this] {
561 TerminalSend(DeviceStatusReport(DSRMode::kCursor));
563 main_loop_receiver = event_buffer.CreateReceiver();
566void App::Internal::ExitNow() {
570void App::Internal::Install() {
571 frame_valid_ =
false;
581 InstallPipedInputHandling();
585 on_exit_functions.emplace([
this] { TerminalFlush(); });
590 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
591 InstallSignalHandler(signal);
597 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
598 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
602 GetConsoleMode(stdout_handle, &out_mode);
603 GetConsoleMode(stdin_handle, &in_mode);
604 g_original_stdout_mode = out_mode;
605 g_original_stdin_mode = in_mode;
606 g_has_original_console_mode =
true;
607 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
608 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
611 const int enable_virtual_terminal_processing = 0x0004;
612 const int disable_newline_auto_return = 0x0008;
613 out_mode |= enable_virtual_terminal_processing;
614 out_mode |= disable_newline_auto_return;
617 const int enable_line_input = 0x0002;
618 const int enable_echo_input = 0x0004;
619 const int enable_virtual_terminal_input = 0x0200;
620 const int enable_window_input = 0x0008;
621 in_mode &= ~enable_echo_input;
622 in_mode &= ~enable_line_input;
623 in_mode |= enable_virtual_terminal_input;
624 in_mode |= enable_window_input;
626 SetConsoleMode(stdin_handle, in_mode);
627 SetConsoleMode(stdout_handle, out_mode);
629 for (
const int signal :
630 {SIGWINCH, SIGTSTP, SIGBUS, SIGSYS, SIGQUIT, SIGHUP}) {
631 InstallSignalHandler(signal);
634 struct termios terminal;
635 tcgetattr(tty_fd_, &terminal);
636 g_original_termios = terminal;
638 g_has_original_termios =
true;
639 on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
640 tcsetattr(tty_fd_, TCSANOW, &terminal);
644 terminal.c_iflag &= ~IGNBRK;
645 terminal.c_iflag &= ~BRKINT;
647 terminal.c_iflag &= ~PARMRK;
648 terminal.c_iflag &= ~ISTRIP;
650 terminal.c_iflag &= ~INLCR;
651 terminal.c_iflag &= ~IGNCR;
652 terminal.c_iflag &= ~ICRNL;
653 terminal.c_iflag &= ~IXON;
656 terminal.c_lflag &= ~ECHO;
657 terminal.c_lflag &= ~ECHONL;
658 terminal.c_lflag &= ~ICANON;
659 terminal.c_lflag &= ~ISIG;
665 terminal.c_lflag &= ~IEXTEN;
666 terminal.c_cflag |= CS8;
668 terminal.c_cc[VMIN] = 0;
670 terminal.c_cc[VTIME] = 0;
673 tcsetattr(tty_fd_, TCSANOW, &terminal);
677 auto enable = [&](
const std::vector<DECMode>& parameters) {
678 TerminalSend(Set(parameters));
679 on_exit_functions.emplace(
680 [
this, parameters] { TerminalSend(Reset(parameters)); });
683 auto disable = [&](
const std::vector<DECMode>& parameters) {
684 TerminalSend(Reset(parameters));
685 on_exit_functions.emplace(
686 [
this, parameters] { TerminalSend(Set(parameters)); });
689 if (use_alternative_screen_) {
691 DECMode::kAlternateScreen,
700 enable({DECMode::kMouseVt200});
701 enable({DECMode::kMouseAnyEvent});
702 enable({DECMode::kMouseUrxvtMode});
703 enable({DECMode::kMouseSgrExtMode});
710 InstallTerminalInfo();
717 g_terminal_is_raw =
true;
720void App::Internal::Uninstall() {
721 g_terminal_is_raw =
false;
725 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
726 auto closing_receiver =
727 event_buffer.CreateReceiverAt(main_loop_receiver->index());
728 auto start = std::chrono::steady_clock::now();
729 while (cursor_position_request.HasPending()) {
730 FetchTerminalEvents();
732 while (closing_receiver->Has()) {
733 const auto event = closing_receiver->Pop();
734 if (event.is_cursor_position()) {
735 cursor_x_ =
event.cursor_x();
736 cursor_y_ =
event.cursor_y();
737 cursor_position_request.OnReply();
741 task_runner.RunUntilIdle();
743 if (std::chrono::steady_clock::now() - start >
744 std::chrono::milliseconds(400)) {
747 std::this_thread::sleep_for(std::chrono::milliseconds(10));
754void App::Internal::PreMain() {
756 if (g_active_screen) {
757 std::swap(suspended_screen_, g_active_screen);
759 suspended_screen_->internal_->TerminalSend(
760 suspended_screen_->internal_->ResetCursorPosition());
761 suspended_screen_->ResetPosition(
762 suspended_screen_->internal_->output_buffer,
764 suspended_screen_->dimx_ = 0;
765 suspended_screen_->dimy_ = 0;
769 suspended_screen_->internal_->Uninstall();
773 g_active_screen = public_;
774 g_active_screen->internal_->Install();
776 previous_animation_time_ = animation::Clock::now();
779void App::Internal::PostMain() {
781 TerminalSend(ResetCursorPosition());
783 g_active_screen =
nullptr;
786 if (suspended_screen_) {
788 public_->ResetPosition(output_buffer,
true);
792 std::swap(g_active_screen, suspended_screen_);
793 g_active_screen->internal_->Install();
800 if (!use_alternative_screen_) {
803 std::cout << std::flush;
806 int sig = g_last_signal.exchange(0);
808 RestoreSignalHandlerAndRaise(sig);
812bool App::Internal::HasQuitted() {
816void App::Internal::RunOnce(
const Component& component) {
817 const AutoReset set_component(&component_, component);
818 ExecuteSignalHandlers();
819 FetchTerminalEvents();
821 while (!quit_ && main_loop_receiver->Has()) {
822 public_->Post(main_loop_receiver->Pop());
826 const size_t executed_task = task_runner.ExecutedTasks();
827 task_runner.RunUntilIdle();
830 if (executed_task == task_runner.ExecutedTasks()) {
834 ExecuteSignalHandlers();
837 if (selection_data_previous_ != selection_data_) {
838 selection_data_previous_ = selection_data_;
839 if (selection_on_change_) {
840 selection_on_change_();
841 public_->Post(Event::Custom);
846void App::Internal::RunOnceBlocking(
Component component) {
848 const auto time_per_frame = std::chrono::microseconds(16666);
850 auto time = std::chrono::steady_clock::now();
851 const size_t executed_task = task_runner.ExecutedTasks();
854 while (executed_task == task_runner.ExecutedTasks() && !HasQuitted()) {
857 const auto now = std::chrono::steady_clock::now();
858 const auto delta = now - time;
861 if (delta < time_per_frame) {
862 const auto sleep_duration = time_per_frame - delta;
863 std::this_thread::sleep_for(sleep_duration);
868void App::Internal::HandleTask(
Component component,
Task& task) {
871 using T = std::decay_t<
decltype(arg)>;
875 if constexpr (std::is_same_v<T, Event>) {
877 if (arg.is_cursor_position()) {
878 cursor_x_ = arg.cursor_x();
879 cursor_y_ = arg.cursor_y();
880 cursor_position_request.OnReply();
884 if (arg.is_cursor_shape()) {
885 cursor_reset_shape_ = arg.cursor_shape();
889 if (arg.IsTerminalCapabilities()) {
890 terminal_capabilities_ = arg.TerminalCapabilities();
894 if (arg.IsTerminalNameVersion()) {
895 terminal_name_ = arg.TerminalName();
896 terminal_version_ = arg.TerminalVersion();
900 if (arg.IsTerminalEmulator()) {
901 terminal_emulator_name_ = arg.TerminalEmulatorName();
902 terminal_emulator_version_ = arg.TerminalEmulatorVersion();
906 if (arg.is_mouse()) {
907 arg.mouse().x -= cursor_x_;
908 arg.mouse().y -= cursor_y_;
911 arg.screen_ = public_;
913 bool handled = component->OnEvent(arg);
914 handled = HandleSelection(handled, arg);
916 if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
917 RecordSignal(SIGINT);
921 if (arg == Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
922 RecordSignal(SIGTSTP);
926 frame_valid_ =
false;
931 if constexpr (std::is_same_v<T, Closure>) {
937 if constexpr (std::is_same_v<T, AnimationTask>) {
938 if (!animation_requested_) {
942 animation_requested_ =
false;
945 previous_animation_time_ = now;
947 animation::Params params(delta);
948 component->OnAnimation(params);
949 frame_valid_ =
false;
957bool App::Internal::HandleSelection(
bool handled, Event event) {
959 selection_pending_ =
nullptr;
960 selection_data_.empty =
true;
961 selection_ =
nullptr;
965 if (!event.is_mouse()) {
969 auto& mouse =
event.mouse();
970 if (mouse.button != Mouse::Left) {
974 if (mouse.motion == Mouse::Pressed) {
975 selection_pending_ = public_->CaptureMouse();
976 selection_data_.start_x = mouse.x;
977 selection_data_.start_y = mouse.y;
978 selection_data_.end_x = mouse.x;
979 selection_data_.end_y = mouse.y;
983 if (!selection_pending_) {
987 if (mouse.motion == Mouse::Moved) {
988 if ((mouse.x != selection_data_.end_x) ||
989 (mouse.y != selection_data_.end_y)) {
990 selection_data_.end_x = mouse.x;
991 selection_data_.end_y = mouse.y;
992 selection_data_.empty =
false;
998 if (mouse.motion == Mouse::Released) {
999 selection_pending_ =
nullptr;
1000 selection_data_.end_x = mouse.x;
1001 selection_data_.end_y = mouse.y;
1002 selection_data_.empty =
false;
1009void App::Internal::Draw(
Component component) {
1013 auto document = component->Render();
1017 document->ComputeRequirement();
1018 switch (dimension_) {
1020 dimx = public_->dimx_;
1021 dimy = public_->dimy_;
1024 dimx = terminal.dimx;
1025 dimy =
util::clamp(document->requirement().min_y, 0, terminal.dimy);
1028 dimx = terminal.dimx;
1029 dimy = terminal.dimy;
1032 dimx =
util::clamp(document->requirement().min_x, 0, terminal.dimx);
1033 dimy =
util::clamp(document->requirement().min_y, 0, terminal.dimy);
1038 TerminalSend(
"\033[?25l");
1040 const bool resized =
1041 frame_count_ == 0 || (dimx != public_->dimx_) || (dimy != public_->dimy_);
1042 TerminalSend(ResetCursorPosition());
1044 if (frame_count_ != 0) {
1047 public_->ResetPosition(output_buffer, resized);
1052 if ((dimx < public_->dimx_) && !use_alternative_screen_) {
1053 TerminalSend(
"\033[J");
1054 TerminalSend(
"\033[H");
1060 public_->dimx_ = dimx;
1061 public_->dimy_ = dimy;
1062 public_->cells_ = std::vector<Cell>(
static_cast<size_t>(dimx) *
1063 static_cast<size_t>(dimy));
1064 Cursor cursor = public_->cursor_;
1065 cursor.x = dimx - 1;
1066 cursor.y = dimy - 1;
1067 public_->SetCursor(cursor);
1074 if (!use_alternative_screen_ && is_stdout_a_tty_) {
1075 RequestCursorPosition(previous_frame_resized_);
1077 previous_frame_resized_ = resized;
1079 selection_ = selection_data_.empty
1080 ? std::make_unique<Selection>()
1081 : std::make_unique<Selection>(
1082 selection_data_.start_x, selection_data_.start_y,
1083 selection_data_.end_x, selection_data_.end_y);
1084 Render(*public_, document.get(), *selection_);
1089 const int dx = public_->dimx_ - 1 - public_->cursor_.x +
1090 int(public_->dimx_ != terminal.dimx);
1091 const int dy = public_->dimy_ - 1 - public_->cursor_.y;
1093 set_cursor_position_.clear();
1094 reset_cursor_position_.clear();
1097 set_cursor_position_ +=
"\x1B[" + std::to_string(dy) +
"A";
1098 reset_cursor_position_ +=
"\x1B[" + std::to_string(dy) +
"B";
1102 set_cursor_position_ +=
"\x1B[" + std::to_string(dx) +
"D";
1103 reset_cursor_position_ +=
"\x1B[" + std::to_string(dx) +
"C";
1106 if (public_->cursor_.shape != Screen::Cursor::Hidden) {
1107 set_cursor_position_ +=
"\033[?25h";
1108 set_cursor_position_ +=
1109 "\033[" + std::to_string(
int(public_->cursor_.shape)) +
" q";
1113 public_->ToString(output_buffer);
1114 TerminalSend(set_cursor_position_);
1118 frame_valid_ =
true;
1122std::string App::Internal::ResetCursorPosition() {
1123 std::string result = std::move(reset_cursor_position_);
1124 reset_cursor_position_ =
"";
1128void App::Internal::RequestCursorPosition(
bool force) {
1129 cursor_position_request.Request(force);
1132void App::Internal::TerminalSend(std::string_view s) {
1136void App::Internal::TerminalFlush() {
1138 output_buffer +=
'\0';
1139 std::cout << output_buffer << std::flush;
1140 output_buffer.clear();
1143void App::Internal::InstallPipedInputHandling() {
1144 is_stdin_a_tty_ =
false;
1145 is_stdout_a_tty_ =
false;
1146#if defined(__EMSCRIPTEN__)
1147 is_stdin_a_tty_ =
true;
1148 is_stdout_a_tty_ =
true;
1149#elif defined(_WIN32)
1150 is_stdin_a_tty_ = _isatty(_fileno(stdin));
1151 is_stdout_a_tty_ = _isatty(_fileno(stdout));
1153 tty_fd_ = STDIN_FILENO;
1154 is_stdout_a_tty_ = isatty(STDOUT_FILENO);
1159 if (!handle_piped_input_) {
1160 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1161 }
else if (isatty(STDIN_FILENO)) {
1162 is_stdin_a_tty_ =
true;
1165 tty_fd_ = open(
"/dev/tty", O_RDONLY);
1169 tty_fd_ = STDIN_FILENO;
1170 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1172 is_stdin_a_tty_ =
true;
1174 on_exit_functions.emplace([
this] {
1183void App::Internal::InstallTerminalInfo() {
1186 if (is_stdout_a_tty_) {
1187 TerminalSend(DECRQSS_DECSCUSR);
1188 TerminalSend(
"\033[>q");
1189 TerminalSend(
"\033[>c");
1190 TerminalSend(
"\033[c");
1196 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
1200 auto setup_receiver = event_buffer.CreateReceiver();
1201 auto start = std::chrono::steady_clock::now();
1202 bool terminal_capabilities_received =
false;
1206 FetchTerminalEvents();
1207 while (setup_receiver->Has()) {
1208 const auto event = setup_receiver->Pop();
1209 if (event.is_cursor_shape()) {
1210 cursor_reset_shape_ =
event.cursor_shape();
1213 if (event.IsTerminalCapabilities()) {
1214 terminal_capabilities_ =
event.TerminalCapabilities();
1215 terminal_capabilities_received =
true;
1218 if (event.IsTerminalNameVersion()) {
1219 terminal_name_ =
event.TerminalName();
1220 terminal_version_ =
event.TerminalVersion();
1223 if (event.IsTerminalEmulator()) {
1224 terminal_emulator_name_ =
event.TerminalEmulatorName();
1225 terminal_emulator_version_ =
event.TerminalEmulatorVersion();
1233 if (terminal_capabilities_received) {
1237 if (std::chrono::steady_clock::now() - start >
1238 std::chrono::milliseconds(500)) {
1241 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1251 util::GetEnv(
"TERM_PROGRAM"), terminal_name_, terminal_emulator_name_,
1252 terminal_capabilities_);
1254 quirks.SetColorSupport(color_support);
1256 const bool is_modern_emulator = (terminal_emulator_name_ !=
"unknown");
1257 const bool is_vt220_plus =
1258 (terminal_name_ !=
"vt100" && terminal_name_ !=
"unknown");
1259 bool reports_utf8 =
false;
1260 for (
const int x : terminal_capabilities_) {
1262 reports_utf8 =
true;
1274 bool modern = is_modern_emulator || is_vt220_plus || reports_utf8;
1276 quirks.SetBlockCharacters(
true);
1277 quirks.SetCursorHiding(
true);
1278 quirks.SetComponentAscii(
false);
1283 on_exit_functions.emplace([
this] {
1284 TerminalSend(
"\033[?25h");
1285 if (is_stdout_a_tty_) {
1286 TerminalSend(
"\033[" + std::to_string(cursor_reset_shape_) +
" q");
1291void App::Internal::Signal(
int signal) {
1292 if (signal == SIGABRT) {
1299 if (signal == SIGTSTP) {
1301 TerminalSend(ResetCursorPosition());
1302 public_->ResetPosition(output_buffer,
true);
1306 (void)std::raise(SIGTSTP);
1312 if (signal == SIGWINCH) {
1313 public_->Post(Event::Special({0}));
1319size_t App::Internal::FetchTerminalEvents() {
1321 auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
1323 auto console = GetStdHandle(STD_INPUT_HANDLE);
1324 DWORD number_of_events = 0;
1325 if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
1326 return std::vector<INPUT_RECORD>();
1328 if (number_of_events <= 0) {
1330 return std::vector<INPUT_RECORD>();
1333 std::vector<INPUT_RECORD> records(number_of_events);
1334 DWORD number_of_events_read = 0;
1335 if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
1336 &number_of_events_read)) {
1337 return std::vector<INPUT_RECORD>();
1339 records.resize(number_of_events_read);
1343 auto records = get_input_records();
1344 if (records.size() == 0) {
1345 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1346 const size_t timeout_microseconds =
1347 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1348 terminal_input_parser.Timeout(timeout_microseconds);
1351 last_char_time = std::chrono::steady_clock::now();
1356 std::wstring wstring;
1357 for (
const auto& r : records) {
1358 switch (r.EventType) {
1360 auto key_event = r.Event.KeyEvent;
1362 if (key_event.bKeyDown == FALSE) {
1365 const wchar_t wc = key_event.uChar.UnicodeChar;
1367 if (wc >= 0xd800 && wc <= 0xdbff) {
1372 terminal_input_parser.Add(it);
1376 case WINDOW_BUFFER_SIZE_EVENT:
1377 public_->Post(Event::Special({0}));
1386 return records.size();
1387#elif defined(__EMSCRIPTEN__)
1390 std::array<char, 4096> out{};
1391 const ssize_t l = read(STDIN_FILENO, out.data(), out.size());
1393 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1394 const size_t timeout_microseconds =
1395 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1396 terminal_input_parser.Timeout(timeout_microseconds);
1399 last_char_time = std::chrono::steady_clock::now();
1402 for (ssize_t i = 0; i < l; ++i) {
1403 terminal_input_parser.Add(out.at(
static_cast<size_t>(i)));
1407 struct pollfd pfd = {tty_fd_, POLLIN, 0};
1408 const int poll_result = poll(&pfd, 1, 0);
1409 if (poll_result <= 0) {
1410 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1411 const size_t timeout_ms =
1412 std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
1413 terminal_input_parser.Timeout(
static_cast<int>(timeout_ms));
1416 last_char_time = std::chrono::steady_clock::now();
1422 constexpr size_t kMaxBytesPerFetch = 64 * 1024;
1423 std::array<char, 4096> out{};
1425 while (total < kMaxBytesPerFetch) {
1426 const ssize_t l = read(tty_fd_, out.data(), out.size());
1432 for (ssize_t i = 0; i < l; ++i) {
1433 terminal_input_parser.Add(out.at(
static_cast<size_t>(i)));
1435 total +=
static_cast<size_t>(l);
1438 if (poll(&pfd, 1, 0) <= 0) {
1446void App::Internal::PostAnimationTask() {
1447 public_->Post(AnimationTask());
1451 task_runner.PostDelayedTask([
this] { PostAnimationTask(); },
1452 std::chrono::milliseconds(15));
1455App::App(std::unique_ptr<Internal> internal,
int dimx,
int dimy)
1456 : Screen(dimx, dimy), internal_(std::move(internal)) {
1457 internal_->public_ =
this;
1460App::App(App&& other) noexcept : Screen(std::move(other)) {
1461 internal_ = std::move(other.internal_);
1463 internal_->public_ =
this;
1467App& App::operator=(App&& other)
noexcept {
1468 Screen::operator=(std::move(other));
1469 internal_ = std::move(other.internal_);
1471 internal_->public_ =
this;
1476App::~App() =
default;
1479App App::FixedSize(
int dimx,
int dimy) {
1482 return App(std::move(internal), dimx, dimy);
1486App App::Fullscreen() {
1487 return FullscreenAlternateScreen();
1491App App::FullscreenPrimaryScreen() {
1495 return App(std::move(internal), terminal.dimx, terminal.dimy);
1499App App::FullscreenAlternateScreen() {
1503 return App(std::move(internal), terminal.dimx, terminal.dimy);
1507App App::FitComponent() {
1511 return App(std::move(internal), terminal.dimx, terminal.dimy);
1515App App::TerminalOutput() {
1519 return App(std::move(internal), terminal.dimx, terminal.dimy);
1522void App::TrackMouse(
bool enable) {
1523 internal_->track_mouse_ = enable;
1526void App::HandlePipedInput(
bool enable) {
1527 internal_->handle_piped_input_ = enable;
1532 return g_active_screen;
1536 class Loop loop(this, std::move(component));
1541 Post([
this] { internal_->ExitNow(); });
1544Closure App::ExitLoopClosure() {
1545 return [
this] { Exit(); };
1548void App::Post(
Task task) {
1549 internal_->task_runner.PostTask([
this, task = std::move(task)]()
mutable {
1550 if (internal_->component_) {
1551 internal_->HandleTask(internal_->component_, task);
1556 if (std::holds_alternative<Closure>(task)) {
1557 std::get<Closure>(task)();
1562void App::PostEvent(Event event) {
1566 Post(
Task(std::move(event)));
1570void App::PostEventOrExecute(
Closure closure) {
1574 if (
auto* app = App::Active()) {
1575 app->Post(std::move(closure));
1581void App::RequestAnimationFrame() {
1582 if (internal_->animation_requested_) {
1585 internal_->animation_requested_ =
true;
1586 auto now = animation::Clock::now();
1587 const auto time_histeresis = std::chrono::milliseconds(33);
1588 if (now - internal_->previous_animation_time_ >= time_histeresis) {
1589 internal_->previous_animation_time_ = now;
1594 if (internal_->mouse_captured) {
1597 internal_->mouse_captured =
true;
1598 return std::make_unique<CapturedMouseImpl>(
1599 [
this] { internal_->mouse_captured =
false; });
1604 internal_->Uninstall();
1606 internal_->Install();
1610void App::ForceHandleCtrlC(
bool force) {
1611 internal_->force_handle_ctrl_c_ = force;
1614void App::ForceHandleCtrlZ(
bool force) {
1615 internal_->force_handle_ctrl_z_ = force;
1618std::string App::GetSelection() {
1619 if (!internal_->selection_) {
1622 return internal_->selection_->GetParts();
1625void App::SelectionChange(std::function<
void()> callback) {
1626 internal_->selection_on_change_ = std::move(callback);
1629const std::string& App::TerminalName()
const {
1630 return internal_->terminal_name_;
1633int App::TerminalVersion()
const {
1634 return internal_->terminal_version_;
1637const std::string& App::TerminalEmulatorName()
const {
1638 return internal_->terminal_emulator_name_;
1641const std::string& App::TerminalEmulatorVersion()
const {
1642 return internal_->terminal_emulator_version_;
1645const std::vector<int>& App::TerminalCapabilities()
const {
1646 return internal_->terminal_capabilities_;
1649std::vector<std::string> App::TerminalCapabilityNames()
const {
1650 return Event::TerminalCapabilities(
"", internal_->terminal_capabilities_)
1651 .TerminalCapabilityNames();
1656void App::ExitNow() {
1657 internal_->ExitNow();
1659void App::Install() {
1660 internal_->Install();
1662void App::Uninstall() {
1663 internal_->Uninstall();
1665void App::PreMain() {
1666 internal_->PreMain();
1668void App::PostMain() {
1669 internal_->PostMain();
1671bool App::HasQuitted() {
1672 return internal_->HasQuitted();
1674void App::RunOnce(
const Component& component) {
1675 internal_->RunOnce(component);
1677void App::RunOnceBlocking(
Component component) {
1678 internal_->RunOnceBlocking(component);
1681 internal_->HandleTask(component, task);
1683bool App::HandleSelection(
bool handled, Event event) {
1684 return internal_->HandleSelection(handled, event);
1687 internal_->Draw(component);
1689std::string App::ResetCursorPosition() {
1690 return internal_->ResetCursorPosition();
1692void App::RequestCursorPosition(
bool force) {
1693 internal_->RequestCursorPosition(force);
1695void App::TerminalSend(std::string_view s) {
1696 internal_->TerminalSend(s);
1698void App::TerminalFlush() {
1699 internal_->TerminalFlush();
1701void App::InstallPipedInputHandling() {
1702 internal_->InstallPipedInputHandling();
1704void App::InstallTerminalInfo() {
1705 internal_->InstallTerminalInfo();
1707void App::Signal(
int signal) {
1708 internal_->Signal(signal);
1710size_t App::FetchTerminalEvents() {
1711 return internal_->FetchTerminalEvents();
1713void App::PostAnimationTask() {
1714 internal_->PostAnimationTask();
1717Loop::Loop(App* screen,
Component component)
1718 : screen_(screen), component_(std::move(component)) {
1723 screen_->PostMain();
1726bool Loop::HasQuitted() {
1727 return screen_->HasQuitted();
1730void Loop::RunOnce() {
1731 screen_->RunOnce(component_);
1734void Loop::RunOnceBlocking() {
1735 screen_->RunOnceBlocking(component_);
1739 while (!HasQuitted()) {
Quirks GetQuirks()
Récupère les particularités du terminal.
Dimensions Size()
Obtenir la taille du terminal.
void SetQuirks(const Quirks &quirks)
Redéfinit les particularités du terminal.
L'espace de noms FTXUI ftxui::animation::
void SetFallbackSize(const Dimensions &fallbackSize)
Outrepasser la taille du terminal en cas d'échec de la détection automatique.
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)
Calcule la prise en charge des couleurs en fonction des variables d'environnement et de l'identificat...
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)
L'espace de noms FTXUI ftxui::
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::string to_string(std::wstring_view s)
Convertit un std::wstring en std::string UTF8.
std::variant< Event, Closure, AnimationTask > Task
void Render(Screen &screen, Node *node, Selection &selection)
std::function< void()> Closure
std::shared_ptr< ComponentBase > Component