15#include <initializer_list>
43#define DEFINE_CONSOLEV2_PROPERTIES
44#define WIN32_LEAN_AND_MEAN
59#if defined(__EMSCRIPTEN__)
60#include <emscripten.h>
74 auto* screen = App::Active();
76 screen->RequestAnimationFrame();
81#if defined(__EMSCRIPTEN__)
84void ftxui_on_resize(
int columns,
int rows) {
97 App* suspended_screen_ =
nullptr;
99 const bool use_alternative_screen_;
101 bool track_mouse_ =
true;
103 std::string set_cursor_position_;
104 std::string reset_cursor_position_;
106 std::atomic<bool> quit_{
false};
107 bool installed_ =
false;
108 bool animation_requested_ =
false;
114 std::uint64_t frame_count_ = 0;
115 bool mouse_captured =
false;
116 bool previous_frame_resized_ =
false;
118 bool frame_valid_ =
false;
120 bool force_handle_ctrl_c_ =
true;
121 bool force_handle_ctrl_z_ =
true;
123 int cursor_reset_shape_ = 1;
126 bool handle_piped_input_ =
true;
127 bool is_stdin_a_tty_ =
false;
128 bool is_stdout_a_tty_ =
false;
132 std::string terminal_name_ =
"unknown";
133 int terminal_version_ = 0;
135 std::string terminal_emulator_name_ =
"unknown";
136 std::string terminal_emulator_version_ =
"unknown";
138 std::vector<int> terminal_capabilities_;
142 struct SelectionData {
148 bool operator==(
const SelectionData& other)
const {
149 if (empty && other.empty) {
152 if (empty || other.empty) {
155 return start_x == other.start_x && start_y == other.start_y &&
156 end_x == other.end_x && end_y == other.end_y;
158 bool operator!=(
const SelectionData& other)
const {
159 return !(*
this == other);
162 SelectionData selection_data_;
163 SelectionData selection_data_previous_;
164 std::unique_ptr<Selection> selection_;
165 std::function<void()> selection_on_change_;
170 TerminalInputParser terminal_input_parser;
171 task::TaskRunner task_runner;
172 std::chrono::time_point<std::chrono::steady_clock> last_char_time =
173 std::chrono::steady_clock::now();
174 std::string output_buffer;
176 class ThrottledRequest {
178 ThrottledRequest(App::Internal* internal, std::function<
void()> send)
179 : internal_(internal), send_(std::move(send)) {}
181 void Request(
bool force =
false) {
182 if (!internal_->is_stdin_a_tty_) {
197 const auto now = std::chrono::steady_clock::now();
198 const auto delta = now - last_request_time_;
199 const auto delay = std::chrono::milliseconds(500) - delta;
201 if (delay <= std::chrono::milliseconds(0)) {
206 request_queued_ =
true;
209 request_queued_ =
false;
215 void OnReply() { pending_request_ =
false; }
217 bool HasPending()
const {
218 if (pending_request_) {
219 const auto now = std::chrono::steady_clock::now();
220 if (now - last_sent_time_ < std::chrono::seconds(5)) {
224 return request_queued_;
229 last_sent_time_ = std::chrono::steady_clock::now();
230 pending_request_ =
true;
234 App::Internal* internal_;
235 std::function<void()> send_;
236 bool pending_request_ =
false;
237 std::chrono::steady_clock::time_point last_request_time_ =
238 std::chrono::steady_clock::now() - std::chrono::hours(1);
239 std::chrono::steady_clock::time_point last_sent_time_ =
240 std::chrono::steady_clock::now() - std::chrono::hours(1);
241 bool request_queued_ =
false;
244 ThrottledRequest cursor_position_request;
246 MultiReceiverBuffer<Event> event_buffer;
247 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> setup_receiver;
248 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> main_loop_receiver;
250 Internal(App* app,
AppDimension dimension,
bool use_alternative_screen);
258 void RunOnce(
const Component& component);
259 void RunOnceBlocking(
Component component);
261 bool HandleSelection(
bool handled, Event event);
263 std::string ResetCursorPosition();
264 void RequestCursorPosition(
bool force =
false);
265 void TerminalSend(std::string_view);
266 void TerminalFlush();
267 void InstallPipedInputHandling();
268 void InstallTerminalInfo();
269 void Signal(
int signal);
270 size_t FetchTerminalEvents();
271 void PostAnimationTask();
276App* g_active_screen =
nullptr;
278std::stack<Closure> on_exit_functions;
281 while (!on_exit_functions.empty()) {
282 on_exit_functions.top()();
283 on_exit_functions.pop();
288const std::string CSI =
"\x1b[";
291const std::string DCS =
"\x1bP";
294const std::string ST =
"\x1b\\";
298const std::string DECRQSS_DECSCUSR = DCS +
"$q q" + ST;
301enum class DECMode : std::uint16_t {
307 kMouseVt200Highlight = 1001,
309 kMouseBtnEventMouse = 1002,
310 kMouseAnyEvent = 1003,
313 kMouseSgrExtMode = 1006,
314 kMouseUrxvtMode = 1015,
315 kMouseSgrPixelsMode = 1016,
316 kAlternateScreen = 1049,
320enum class DSRMode : std::uint8_t {
324std::string Serialize(
const std::vector<DECMode>& parameters) {
327 for (
const DECMode parameter : parameters) {
331 out += std::to_string(
int(parameter));
338std::string Set(
const std::vector<DECMode>& parameters) {
339 return CSI +
"?" + Serialize(parameters) +
"h";
343std::string Reset(
const std::vector<DECMode>& parameters) {
344 return CSI +
"?" + Serialize(parameters) +
"l";
348std::string DeviceStatusReport(DSRMode ps) {
349 return CSI + std::to_string(
int(ps)) +
"n";
352class CapturedMouseImpl :
public CapturedMouseInterface {
354 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
355 : callback_(std::move(callback)) {}
356 ~CapturedMouseImpl()
override { callback_(); }
357 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
358 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
359 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
360 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
363 std::function<void(
void)> callback_;
367std::atomic<int> g_signal_exit_count = 0;
368std::atomic<int> g_signal_stop_count = 0;
369std::atomic<int> g_signal_resize_count = 0;
371std::atomic<int> g_signal_exit_count = 0;
375void RecordSignal(
int signal) {
383 g_signal_exit_count++;
388 g_signal_stop_count++;
392 g_signal_resize_count++;
401void ExecuteSignalHandlers() {
402 int signal_exit_count = g_signal_exit_count.exchange(0);
403 while (signal_exit_count--) {
404 App::Private::Signal(*g_active_screen, SIGABRT);
408 int signal_stop_count = g_signal_stop_count.exchange(0);
409 while (signal_stop_count--) {
410 App::Private::Signal(*g_active_screen, SIGTSTP);
413 int signal_resize_count = g_signal_resize_count.exchange(0);
414 while (signal_resize_count--) {
415 App::Private::Signal(*g_active_screen, SIGWINCH);
420void InstallSignalHandler(
int sig) {
421 auto old_signal_handler = std::signal(sig, RecordSignal);
422 on_exit_functions.emplace(
423 [=] { std::ignore = std::signal(sig, old_signal_handler); });
428App::Internal::Internal(App* app,
430 bool use_alternative_screen)
432 dimension_(dimension),
433 use_alternative_screen_(use_alternative_screen),
434 terminal_input_parser([&](Event event) {
435 event_buffer.Push(std::move(event));
436 public_->RequestAnimationFrame();
438 cursor_position_request(
this, [
this] {
439 TerminalSend(DeviceStatusReport(DSRMode::kCursor));
441 setup_receiver = event_buffer.CreateReceiver();
442 main_loop_receiver = event_buffer.CreateReceiver();
445void App::Internal::ExitNow() {
449void App::Internal::Install() {
450 frame_valid_ =
false;
459 InstallPipedInputHandling();
463 on_exit_functions.emplace([
this] { TerminalFlush(); });
467 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
468 InstallSignalHandler(signal);
474 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
475 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
479 GetConsoleMode(stdout_handle, &out_mode);
480 GetConsoleMode(stdin_handle, &in_mode);
481 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
482 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
485 const int enable_virtual_terminal_processing = 0x0004;
486 const int disable_newline_auto_return = 0x0008;
487 out_mode |= enable_virtual_terminal_processing;
488 out_mode |= disable_newline_auto_return;
491 const int enable_line_input = 0x0002;
492 const int enable_echo_input = 0x0004;
493 const int enable_virtual_terminal_input = 0x0200;
494 const int enable_window_input = 0x0008;
495 in_mode &= ~enable_echo_input;
496 in_mode &= ~enable_line_input;
497 in_mode |= enable_virtual_terminal_input;
498 in_mode |= enable_window_input;
500 SetConsoleMode(stdin_handle, in_mode);
501 SetConsoleMode(stdout_handle, out_mode);
503 for (
const int signal : {SIGWINCH, SIGTSTP}) {
504 InstallSignalHandler(signal);
507 struct termios terminal;
508 tcgetattr(tty_fd_, &terminal);
509 on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
510 tcsetattr(tty_fd_, TCSANOW, &terminal);
514 terminal.c_iflag &= ~IGNBRK;
515 terminal.c_iflag &= ~BRKINT;
517 terminal.c_iflag &= ~PARMRK;
518 terminal.c_iflag &= ~ISTRIP;
519 terminal.c_iflag &= ~INLCR;
520 terminal.c_iflag &= ~IGNCR;
521 terminal.c_iflag &= ~ICRNL;
522 terminal.c_iflag &= ~IXON;
524 terminal.c_lflag &= ~ECHO;
525 terminal.c_lflag &= ~ECHONL;
526 terminal.c_lflag &= ~ICANON;
527 terminal.c_lflag &= ~ISIG;
532 terminal.c_lflag &= ~IEXTEN;
533 terminal.c_cflag |= CS8;
535 terminal.c_cc[VMIN] = 0;
537 terminal.c_cc[VTIME] = 0;
539 tcsetattr(tty_fd_, TCSANOW, &terminal);
543 auto enable = [&](
const std::vector<DECMode>& parameters) {
544 TerminalSend(Set(parameters));
545 on_exit_functions.emplace(
546 [
this, parameters] { TerminalSend(Reset(parameters)); });
549 auto disable = [&](
const std::vector<DECMode>& parameters) {
550 TerminalSend(Reset(parameters));
551 on_exit_functions.emplace(
552 [
this, parameters] { TerminalSend(Set(parameters)); });
555 if (use_alternative_screen_) {
557 DECMode::kAlternateScreen,
566 enable({DECMode::kMouseVt200});
567 enable({DECMode::kMouseAnyEvent});
568 enable({DECMode::kMouseUrxvtMode});
569 enable({DECMode::kMouseSgrExtMode});
576 InstallTerminalInfo();
585void App::Internal::Uninstall() {
589 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
590 auto closing_receiver =
591 event_buffer.CreateReceiverAt(main_loop_receiver->index());
592 auto start = std::chrono::steady_clock::now();
593 while (cursor_position_request.HasPending()) {
594 FetchTerminalEvents();
596 while (closing_receiver->Has()) {
597 const auto event = closing_receiver->Pop();
598 if (event.is_cursor_position()) {
599 cursor_x_ =
event.cursor_x();
600 cursor_y_ =
event.cursor_y();
601 cursor_position_request.OnReply();
605 task_runner.RunUntilIdle();
607 if (std::chrono::steady_clock::now() - start >
608 std::chrono::milliseconds(400)) {
611 std::this_thread::sleep_for(std::chrono::milliseconds(10));
618void App::Internal::PreMain() {
620 if (g_active_screen) {
621 std::swap(suspended_screen_, g_active_screen);
623 suspended_screen_->internal_->TerminalSend(
624 suspended_screen_->internal_->ResetCursorPosition());
625 suspended_screen_->ResetPosition(
626 suspended_screen_->internal_->output_buffer,
628 suspended_screen_->dimx_ = 0;
629 suspended_screen_->dimy_ = 0;
632 suspended_screen_->internal_->Uninstall();
636 g_active_screen = public_;
637 g_active_screen->internal_->Install();
639 previous_animation_time_ = animation::Clock::now();
642void App::Internal::PostMain() {
644 TerminalSend(ResetCursorPosition());
646 g_active_screen =
nullptr;
649 if (suspended_screen_) {
651 public_->ResetPosition(output_buffer,
true);
655 std::swap(g_active_screen, suspended_screen_);
656 g_active_screen->internal_->Install();
663 if (!use_alternative_screen_) {
666 std::cout << std::flush;
670bool App::Internal::HasQuitted() {
674void App::Internal::RunOnce(
const Component& component) {
675 const AutoReset set_component(&component_, component);
676 ExecuteSignalHandlers();
677 FetchTerminalEvents();
679 while (!quit_ && main_loop_receiver->Has()) {
680 public_->Post(main_loop_receiver->Pop());
684 const size_t executed_task = task_runner.ExecutedTasks();
685 task_runner.RunUntilIdle();
687 if (executed_task == task_runner.ExecutedTasks()) {
691 ExecuteSignalHandlers();
694 if (selection_data_previous_ != selection_data_) {
695 selection_data_previous_ = selection_data_;
696 if (selection_on_change_) {
697 selection_on_change_();
698 public_->Post(Event::Custom);
703void App::Internal::RunOnceBlocking(
Component component) {
705 const auto time_per_frame = std::chrono::microseconds(16666);
707 auto time = std::chrono::steady_clock::now();
708 const size_t executed_task = task_runner.ExecutedTasks();
711 while (executed_task == task_runner.ExecutedTasks() && !HasQuitted()) {
714 const auto now = std::chrono::steady_clock::now();
715 const auto delta = now - time;
718 if (delta < time_per_frame) {
719 const auto sleep_duration = time_per_frame - delta;
720 std::this_thread::sleep_for(sleep_duration);
725void App::Internal::HandleTask(
Component component,
Task& task) {
728 using T = std::decay_t<
decltype(arg)>;
732 if constexpr (std::is_same_v<T, Event>) {
734 if (arg.is_cursor_position()) {
735 cursor_x_ = arg.cursor_x();
736 cursor_y_ = arg.cursor_y();
737 cursor_position_request.OnReply();
741 if (arg.is_cursor_shape()) {
742 cursor_reset_shape_ = arg.cursor_shape();
746 if (arg.IsTerminalCapabilities()) {
747 terminal_capabilities_ = arg.TerminalCapabilities();
751 if (arg.IsTerminalNameVersion()) {
752 terminal_name_ = arg.TerminalName();
753 terminal_version_ = arg.TerminalVersion();
757 if (arg.IsTerminalEmulator()) {
758 terminal_emulator_name_ = arg.TerminalEmulatorName();
759 terminal_emulator_version_ = arg.TerminalEmulatorVersion();
763 if (arg.is_mouse()) {
764 arg.mouse().x -= cursor_x_;
765 arg.mouse().y -= cursor_y_;
768 arg.screen_ = public_;
770 bool handled = component->OnEvent(arg);
771 handled = HandleSelection(handled, arg);
773 if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
774 RecordSignal(SIGABRT);
778 if (arg == Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
779 RecordSignal(SIGTSTP);
783 frame_valid_ =
false;
788 if constexpr (std::is_same_v<T, Closure>) {
794 if constexpr (std::is_same_v<T, AnimationTask>) {
795 if (!animation_requested_) {
799 animation_requested_ =
false;
802 previous_animation_time_ = now;
804 animation::Params params(delta);
805 component->OnAnimation(params);
806 frame_valid_ =
false;
814bool App::Internal::HandleSelection(
bool handled, Event event) {
816 selection_pending_ =
nullptr;
817 selection_data_.empty =
true;
818 selection_ =
nullptr;
822 if (!event.is_mouse()) {
826 auto& mouse =
event.mouse();
827 if (mouse.button != Mouse::Left) {
831 if (mouse.motion == Mouse::Pressed) {
832 selection_pending_ = public_->CaptureMouse();
833 selection_data_.start_x = mouse.x;
834 selection_data_.start_y = mouse.y;
835 selection_data_.end_x = mouse.x;
836 selection_data_.end_y = mouse.y;
840 if (!selection_pending_) {
844 if (mouse.motion == Mouse::Moved) {
845 if ((mouse.x != selection_data_.end_x) ||
846 (mouse.y != selection_data_.end_y)) {
847 selection_data_.end_x = mouse.x;
848 selection_data_.end_y = mouse.y;
849 selection_data_.empty =
false;
855 if (mouse.motion == Mouse::Released) {
856 selection_pending_ =
nullptr;
857 selection_data_.end_x = mouse.x;
858 selection_data_.end_y = mouse.y;
859 selection_data_.empty =
false;
866void App::Internal::Draw(
Component component) {
870 auto document = component->Render();
874 document->ComputeRequirement();
875 switch (dimension_) {
877 dimx = public_->dimx_;
878 dimy = public_->dimy_;
881 dimx = terminal.dimx;
882 dimy =
util::clamp(document->requirement().min_y, 0, terminal.dimy);
885 dimx = terminal.dimx;
886 dimy = terminal.dimy;
889 dimx =
util::clamp(document->requirement().min_x, 0, terminal.dimx);
890 dimy =
util::clamp(document->requirement().min_y, 0, terminal.dimy);
895 TerminalSend(
"\033[?25l");
898 frame_count_ == 0 || (dimx != public_->dimx_) || (dimy != public_->dimy_);
899 TerminalSend(ResetCursorPosition());
901 if (frame_count_ != 0) {
904 public_->ResetPosition(output_buffer, resized);
908 if ((dimx < public_->dimx_) && !use_alternative_screen_) {
909 TerminalSend(
"\033[J");
910 TerminalSend(
"\033[H");
916 public_->dimx_ = dimx;
917 public_->dimy_ = dimy;
919 std::vector<Cell>(
static_cast<size_t>(dimx) *
static_cast<size_t>(dimy));
920 Cursor cursor = public_->cursor_;
923 public_->SetCursor(cursor);
929 if (!use_alternative_screen_ && is_stdout_a_tty_) {
930 RequestCursorPosition(previous_frame_resized_);
932 previous_frame_resized_ = resized;
934 selection_ = selection_data_.empty
935 ? std::make_unique<Selection>()
936 : std::make_unique<Selection>(
937 selection_data_.start_x, selection_data_.start_y,
938 selection_data_.end_x, selection_data_.end_y);
939 Render(*public_, document.get(), *selection_);
943 const int dx = public_->dimx_ - 1 - public_->cursor_.x +
944 int(public_->dimx_ != terminal.dimx);
945 const int dy = public_->dimy_ - 1 - public_->cursor_.y;
947 set_cursor_position_.clear();
948 reset_cursor_position_.clear();
951 set_cursor_position_ +=
"\x1B[" + std::to_string(dy) +
"A";
952 reset_cursor_position_ +=
"\x1B[" + std::to_string(dy) +
"B";
956 set_cursor_position_ +=
"\x1B[" + std::to_string(dx) +
"D";
957 reset_cursor_position_ +=
"\x1B[" + std::to_string(dx) +
"C";
960 if (public_->cursor_.shape != Screen::Cursor::Hidden) {
961 set_cursor_position_ +=
"\033[?25h";
962 set_cursor_position_ +=
963 "\033[" + std::to_string(
int(public_->cursor_.shape)) +
" q";
967 public_->ToString(output_buffer);
968 TerminalSend(set_cursor_position_);
976std::string App::Internal::ResetCursorPosition() {
977 std::string result = std::move(reset_cursor_position_);
978 reset_cursor_position_ =
"";
982void App::Internal::RequestCursorPosition(
bool force) {
983 cursor_position_request.Request(force);
986void App::Internal::TerminalSend(std::string_view s) {
990void App::Internal::TerminalFlush() {
992 output_buffer +=
'\0';
993 std::cout << output_buffer << std::flush;
994 output_buffer.clear();
997void App::Internal::InstallPipedInputHandling() {
998 is_stdin_a_tty_ =
false;
999 is_stdout_a_tty_ =
false;
1000#if defined(__EMSCRIPTEN__)
1001 is_stdin_a_tty_ =
true;
1002 is_stdout_a_tty_ =
true;
1003#elif defined(_WIN32)
1004 is_stdin_a_tty_ = _isatty(_fileno(stdin));
1005 is_stdout_a_tty_ = _isatty(_fileno(stdout));
1007 tty_fd_ = STDIN_FILENO;
1008 is_stdout_a_tty_ = isatty(STDOUT_FILENO);
1012 if (!handle_piped_input_) {
1013 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1014 }
else if (isatty(STDIN_FILENO)) {
1015 is_stdin_a_tty_ =
true;
1018 tty_fd_ = open(
"/dev/tty", O_RDONLY);
1021 tty_fd_ = STDIN_FILENO;
1022 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1024 is_stdin_a_tty_ =
true;
1026 on_exit_functions.emplace([
this] {
1035void App::Internal::InstallTerminalInfo() {
1038 if (is_stdout_a_tty_) {
1039 TerminalSend(DECRQSS_DECSCUSR);
1040 TerminalSend(
"\033[>q");
1041 TerminalSend(
"\033[>c");
1042 TerminalSend(
"\033[c");
1047 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
1048 auto start = std::chrono::steady_clock::now();
1049 bool terminal_capabilities_received =
false;
1052 FetchTerminalEvents();
1053 while (setup_receiver->Has()) {
1054 const auto event = setup_receiver->Pop();
1055 if (event.is_cursor_shape()) {
1056 cursor_reset_shape_ =
event.cursor_shape();
1059 if (event.IsTerminalCapabilities()) {
1060 terminal_capabilities_ =
event.TerminalCapabilities();
1061 terminal_capabilities_received =
true;
1064 if (event.IsTerminalNameVersion()) {
1065 terminal_name_ =
event.TerminalName();
1066 terminal_version_ =
event.TerminalVersion();
1069 if (event.IsTerminalEmulator()) {
1070 terminal_emulator_name_ =
event.TerminalEmulatorName();
1071 terminal_emulator_version_ =
event.TerminalEmulatorVersion();
1078 if (terminal_capabilities_received) {
1082 if (std::chrono::steady_clock::now() - start >
1083 std::chrono::milliseconds(500)) {
1086 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1093 auto safe_getenv = [](
const char* name) -> std::string_view {
1094 const char*
value = std::getenv(name);
1099 safe_getenv(
"TERM"), safe_getenv(
"COLORTERM"),
1100 safe_getenv(
"TERM_PROGRAM"), terminal_name_, terminal_emulator_name_,
1101 terminal_capabilities_);
1103 quirks.SetColorSupport(color_support);
1105 const bool is_modern_emulator = (terminal_emulator_name_ !=
"unknown");
1106 const bool is_vt220_plus =
1107 (terminal_name_ !=
"vt100" && terminal_name_ !=
"unknown");
1108 bool reports_utf8 =
false;
1109 for (
const int x : terminal_capabilities_) {
1111 reports_utf8 =
true;
1122 bool modern = is_modern_emulator || is_vt220_plus || reports_utf8;
1124 quirks.SetBlockCharacters(
true);
1125 quirks.SetCursorHiding(
true);
1126 quirks.SetComponentAscii(
false);
1131 on_exit_functions.emplace([
this] {
1132 TerminalSend(
"\033[?25h");
1133 if (is_stdout_a_tty_) {
1134 TerminalSend(
"\033[" + std::to_string(cursor_reset_shape_) +
" q");
1139void App::Internal::Signal(
int signal) {
1140 if (signal == SIGABRT) {
1147 if (signal == SIGTSTP) {
1149 TerminalSend(ResetCursorPosition());
1150 public_->ResetPosition(output_buffer,
true);
1154 (void)std::raise(SIGTSTP);
1160 if (signal == SIGWINCH) {
1161 public_->Post(Event::Special({0}));
1167size_t App::Internal::FetchTerminalEvents() {
1169 auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
1171 auto console = GetStdHandle(STD_INPUT_HANDLE);
1172 DWORD number_of_events = 0;
1173 if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
1174 return std::vector<INPUT_RECORD>();
1176 if (number_of_events <= 0) {
1178 return std::vector<INPUT_RECORD>();
1181 std::vector<INPUT_RECORD> records(number_of_events);
1182 DWORD number_of_events_read = 0;
1183 if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
1184 &number_of_events_read)) {
1185 return std::vector<INPUT_RECORD>();
1187 records.resize(number_of_events_read);
1191 auto records = get_input_records();
1192 if (records.size() == 0) {
1193 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1194 const size_t timeout_microseconds =
1195 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1196 terminal_input_parser.Timeout(timeout_microseconds);
1199 last_char_time = std::chrono::steady_clock::now();
1204 std::wstring wstring;
1205 for (
const auto& r : records) {
1206 switch (r.EventType) {
1208 auto key_event = r.Event.KeyEvent;
1210 if (key_event.bKeyDown == FALSE) {
1213 const wchar_t wc = key_event.uChar.UnicodeChar;
1215 if (wc >= 0xd800 && wc <= 0xdbff) {
1220 terminal_input_parser.Add(it);
1224 case WINDOW_BUFFER_SIZE_EVENT:
1225 public_->Post(Event::Special({0}));
1234 return records.size();
1235#elif defined(__EMSCRIPTEN__)
1238 std::array<char, 128> out{};
1239 const ssize_t l = read(STDIN_FILENO, out.data(), out.size());
1241 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1242 const size_t timeout_microseconds =
1243 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1244 terminal_input_parser.Timeout(timeout_microseconds);
1247 last_char_time = std::chrono::steady_clock::now();
1250 for (ssize_t i = 0; i < l; ++i) {
1251 terminal_input_parser.Add(out.at(
static_cast<size_t>(i)));
1255 struct pollfd pfd = {tty_fd_, POLLIN, 0};
1256 const int poll_result = poll(&pfd, 1, 0);
1257 if (poll_result <= 0) {
1258 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1259 const size_t timeout_ms =
1260 std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
1261 terminal_input_parser.Timeout(
static_cast<int>(timeout_ms));
1264 last_char_time = std::chrono::steady_clock::now();
1267 std::array<char, 128> out{};
1268 const ssize_t l = read(tty_fd_, out.data(), out.size());
1274 for (ssize_t i = 0; i < l; ++i) {
1275 terminal_input_parser.Add(out.at(
static_cast<size_t>(i)));
1281void App::Internal::PostAnimationTask() {
1282 public_->Post(AnimationTask());
1286 task_runner.PostDelayedTask([
this] { PostAnimationTask(); },
1287 std::chrono::milliseconds(15));
1290App::App(std::unique_ptr<Internal> internal,
int dimx,
int dimy)
1291 : Screen(dimx, dimy), internal_(std::move(internal)) {
1292 internal_->public_ =
this;
1295App::App(App&& other) noexcept : Screen(std::move(other)) {
1296 internal_ = std::move(other.internal_);
1298 internal_->public_ =
this;
1302App& App::operator=(App&& other)
noexcept {
1303 Screen::operator=(std::move(other));
1304 internal_ = std::move(other.internal_);
1306 internal_->public_ =
this;
1311App::~App() =
default;
1314App App::FixedSize(
int dimx,
int dimy) {
1317 return App(std::move(internal), dimx, dimy);
1321App App::Fullscreen() {
1322 return FullscreenAlternateScreen();
1326App App::FullscreenPrimaryScreen() {
1330 return App(std::move(internal), terminal.dimx, terminal.dimy);
1334App App::FullscreenAlternateScreen() {
1338 return App(std::move(internal), terminal.dimx, terminal.dimy);
1342App App::FitComponent() {
1346 return App(std::move(internal), terminal.dimx, terminal.dimy);
1350App App::TerminalOutput() {
1354 return App(std::move(internal), terminal.dimx, terminal.dimy);
1357void App::TrackMouse(
bool enable) {
1358 internal_->track_mouse_ = enable;
1361void App::HandlePipedInput(
bool enable) {
1362 internal_->handle_piped_input_ = enable;
1367 return g_active_screen;
1371 class Loop loop(this, std::move(component));
1376 Post([
this] { internal_->ExitNow(); });
1379Closure App::ExitLoopClosure() {
1380 return [
this] { Exit(); };
1383void App::Post(
Task task) {
1384 internal_->task_runner.PostTask([
this, task = std::move(task)]()
mutable {
1385 if (internal_->component_) {
1386 internal_->HandleTask(internal_->component_, task);
1391 if (std::holds_alternative<Closure>(task)) {
1392 std::get<Closure>(task)();
1397void App::PostEvent(Event event) {
1398 internal_->event_buffer.Push(std::move(event));
1399 RequestAnimationFrame();
1403void App::PostEventOrExecute(
Closure closure) {
1407 if (
auto* app = App::Active()) {
1408 app->Post(std::move(closure));
1414void App::RequestAnimationFrame() {
1415 if (internal_->animation_requested_) {
1418 internal_->animation_requested_ =
true;
1419 auto now = animation::Clock::now();
1420 const auto time_histeresis = std::chrono::milliseconds(33);
1421 if (now - internal_->previous_animation_time_ >= time_histeresis) {
1422 internal_->previous_animation_time_ = now;
1427 if (internal_->mouse_captured) {
1430 internal_->mouse_captured =
true;
1431 return std::make_unique<CapturedMouseImpl>(
1432 [
this] { internal_->mouse_captured =
false; });
1437 internal_->Uninstall();
1439 internal_->Install();
1443void App::ForceHandleCtrlC(
bool force) {
1444 internal_->force_handle_ctrl_c_ = force;
1447void App::ForceHandleCtrlZ(
bool force) {
1448 internal_->force_handle_ctrl_z_ = force;
1451std::string App::GetSelection() {
1452 if (!internal_->selection_) {
1455 return internal_->selection_->GetParts();
1458void App::SelectionChange(std::function<
void()> callback) {
1459 internal_->selection_on_change_ = std::move(callback);
1462const std::string& App::TerminalName()
const {
1463 return internal_->terminal_name_;
1466int App::TerminalVersion()
const {
1467 return internal_->terminal_version_;
1470const std::string& App::TerminalEmulatorName()
const {
1471 return internal_->terminal_emulator_name_;
1474const std::string& App::TerminalEmulatorVersion()
const {
1475 return internal_->terminal_emulator_version_;
1478const std::vector<int>& App::TerminalCapabilities()
const {
1479 return internal_->terminal_capabilities_;
1482std::vector<std::string> App::TerminalCapabilityNames()
const {
1483 return Event::TerminalCapabilities(
"", internal_->terminal_capabilities_)
1484 .TerminalCapabilityNames();
1489void App::ExitNow() {
1490 internal_->ExitNow();
1492void App::Install() {
1493 internal_->Install();
1495void App::Uninstall() {
1496 internal_->Uninstall();
1498void App::PreMain() {
1499 internal_->PreMain();
1501void App::PostMain() {
1502 internal_->PostMain();
1504bool App::HasQuitted() {
1505 return internal_->HasQuitted();
1507void App::RunOnce(
const Component& component) {
1508 internal_->RunOnce(component);
1510void App::RunOnceBlocking(
Component component) {
1511 internal_->RunOnceBlocking(component);
1514 internal_->HandleTask(component, task);
1516bool App::HandleSelection(
bool handled, Event event) {
1517 return internal_->HandleSelection(handled, event);
1520 internal_->Draw(component);
1522std::string App::ResetCursorPosition() {
1523 return internal_->ResetCursorPosition();
1525void App::RequestCursorPosition(
bool force) {
1526 internal_->RequestCursorPosition(force);
1528void App::TerminalSend(std::string_view s) {
1529 internal_->TerminalSend(s);
1531void App::TerminalFlush() {
1532 internal_->TerminalFlush();
1534void App::InstallPipedInputHandling() {
1535 internal_->InstallPipedInputHandling();
1537void App::InstallTerminalInfo() {
1538 internal_->InstallTerminalInfo();
1540void App::Signal(
int signal) {
1541 internal_->Signal(signal);
1543size_t App::FetchTerminalEvents() {
1544 return internal_->FetchTerminalEvents();
1546void App::PostAnimationTask() {
1547 internal_->PostAnimationTask();
1550Loop::Loop(App* screen,
Component component)
1551 : screen_(screen), component_(std::move(component)) {
1556 screen_->PostMain();
1559bool Loop::HasQuitted() {
1560 return screen_->HasQuitted();
1563void Loop::RunOnce() {
1564 screen_->RunOnce(component_);
1567void Loop::RunOnceBlocking() {
1568 screen_->RunOnceBlocking(component_);
1572 while (!HasQuitted()) {
auto PostDelayedTask(Task task, std::chrono::steady_clock::duration duration) -> void
Schedules a task to be executed after a certain duration.
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()
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
The FTXUI ftxui:: namespace.
std::unique_ptr< CapturedMouseInterface > CapturedMouse
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