15#include <initializer_list>
42#define DEFINE_CONSOLEV2_PROPERTIES
43#define WIN32_LEAN_AND_MEAN
50#error Must be compiled in UNICODE mode
67 screen->RequestAnimationFrame();
72class ThrottledRequest {
74 ThrottledRequest(App* app,
75 std::function<
void()> send,
76 task::TaskRunner& task_runner)
77 : app_(app), send_(std::move(send)), task_runner_(&task_runner) {}
79 void Request(
bool force =
false) {
80 if (!app_->is_stdin_a_tty_) {
95 const auto now = std::chrono::steady_clock::now();
96 const auto delta = now - last_request_time_;
97 const auto delay = std::chrono::milliseconds(500) - delta;
99 if (delay <= std::chrono::milliseconds(0)) {
104 request_queued_ =
true;
107 request_queued_ =
false;
113 void OnReply() { pending_request_ =
false; }
115 bool HasPending()
const {
116 if (pending_request_) {
117 const auto now = std::chrono::steady_clock::now();
118 if (now - last_sent_time_ < std::chrono::seconds(5)) {
122 return request_queued_;
127 last_sent_time_ = std::chrono::steady_clock::now();
128 pending_request_ =
true;
133 std::function<void()> send_;
134 task::TaskRunner* task_runner_;
135 bool pending_request_ =
false;
136 std::chrono::steady_clock::time_point last_request_time_ =
137 std::chrono::steady_clock::now() - std::chrono::hours(1);
138 std::chrono::steady_clock::time_point last_sent_time_ =
139 std::chrono::steady_clock::now() - std::chrono::hours(1);
140 bool request_queued_ =
false;
143struct App::Internal {
145 TerminalInputParser terminal_input_parser;
147 task::TaskRunner task_runner;
150 std::chrono::time_point<std::chrono::steady_clock> last_char_time =
151 std::chrono::steady_clock::now();
158 std::string output_buffer;
162 MultiReceiverBuffer<Event> event_buffer;
163 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> setup_receiver;
164 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> main_loop_receiver;
166 explicit Internal(App* app, std::function<
void(Event)> out);
171App* g_active_screen =
nullptr;
173std::stack<Closure> on_exit_functions;
176 while (!on_exit_functions.empty()) {
177 on_exit_functions.top()();
178 on_exit_functions.pop();
184#elif defined(__EMSCRIPTEN__)
185#include <emscripten.h>
189void ftxui_on_resize(
int columns,
int rows) {
194 std::raise(SIGWINCH);
202std::atomic<int> g_signal_exit_count = 0;
204std::atomic<int> g_signal_stop_count = 0;
205std::atomic<int> g_signal_resize_count = 0;
209void RecordSignal(
int signal) {
217 g_signal_exit_count++;
222 g_signal_stop_count++;
226 g_signal_resize_count++;
235void ExecuteSignalHandlers() {
236 int signal_exit_count = g_signal_exit_count.exchange(0);
237 while (signal_exit_count--) {
242 int signal_stop_count = g_signal_stop_count.exchange(0);
243 while (signal_stop_count--) {
247 int signal_resize_count = g_signal_resize_count.exchange(0);
248 while (signal_resize_count--) {
254void InstallSignalHandler(
int sig) {
255 auto old_signal_handler = std::signal(sig, RecordSignal);
256 on_exit_functions.emplace(
257 [=] { std::ignore = std::signal(sig, old_signal_handler); });
261const std::string CSI =
"\x1b[";
264const std::string DCS =
"\x1bP";
267const std::string ST =
"\x1b\\";
271const std::string DECRQSS_DECSCUSR = DCS +
"$q q" + ST;
274enum class DECMode : std::uint16_t {
280 kMouseVt200Highlight = 1001,
282 kMouseBtnEventMouse = 1002,
283 kMouseAnyEvent = 1003,
286 kMouseSgrExtMode = 1006,
287 kMouseUrxvtMode = 1015,
288 kMouseSgrPixelsMode = 1016,
289 kAlternateScreen = 1049,
293enum class DSRMode : std::uint8_t {
297std::string Serialize(
const std::vector<DECMode>& parameters) {
300 for (
const DECMode parameter : parameters) {
304 out += std::to_string(
int(parameter));
311std::string Set(
const std::vector<DECMode>& parameters) {
312 return CSI +
"?" + Serialize(parameters) +
"h";
316std::string Reset(
const std::vector<DECMode>& parameters) {
317 return CSI +
"?" + Serialize(parameters) +
"l";
321std::string DeviceStatusReport(DSRMode ps) {
322 return CSI + std::to_string(
int(ps)) +
"n";
325class CapturedMouseImpl :
public CapturedMouseInterface {
327 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
328 : callback_(std::move(callback)) {}
329 ~CapturedMouseImpl()
override { callback_(); }
330 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
331 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
332 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
333 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
336 std::function<void(
void)> callback_;
341App::Internal::Internal(App* app, std::function<
void(Event)> out)
342 : terminal_input_parser(std::move(out)),
343 cursor_position_request(
345 [app] { app->TerminalSend(DeviceStatusReport(DSRMode::kCursor)); },
347 setup_receiver = event_buffer.CreateReceiver();
348 main_loop_receiver = event_buffer.CreateReceiver();
351App::App(
Dimension dimension,
int dimx,
int dimy,
bool use_alternative_screen)
352 : Screen(dimx, dimy),
353 dimension_(dimension),
354 use_alternative_screen_(use_alternative_screen) {
355 internal_ = std::make_unique<Internal>(
this, [&](Event event) {
356 internal_->event_buffer.Push(std::move(event));
386 Dimension::Fullscreen,
399 Dimension::Fullscreen,
412 Dimension::TerminalOutput,
427 Dimension::FitComponent,
450 track_mouse_ = enable;
462 handle_piped_input_ = enable;
468 internal_->task_runner.
PostTask([
this, task = std::move(task)]()
mutable {
470 HandleTask(component_, task);
475 if (std::holds_alternative<Closure>(task)) {
476 std::get<Closure>(task)();
484 internal_->event_buffer.Push(std::move(event));
491 if (animation_requested_) {
494 animation_requested_ =
true;
495 auto now = animation::Clock::now();
496 const auto time_histeresis = std::chrono::milliseconds(33);
497 if (now - previous_animation_time_ >= time_histeresis) {
498 previous_animation_time_ = now;
506 if (mouse_captured) {
509 mouse_captured =
true;
510 return std::make_unique<CapturedMouseImpl>(
511 [
this] { mouse_captured =
false; });
517 class Loop loop(this, std::move(component));
522bool App::HasQuitted() {
529 if (g_active_screen) {
530 std::swap(suspended_screen_, g_active_screen);
532 suspended_screen_->TerminalSend(suspended_screen_->ResetCursorPosition());
534 suspended_screen_->internal_->output_buffer,
536 suspended_screen_->
dimx_ = 0;
537 suspended_screen_->
dimy_ = 0;
540 suspended_screen_->Uninstall();
544 g_active_screen =
this;
545 g_active_screen->Install();
547 previous_animation_time_ = animation::Clock::now();
551void App::PostMain() {
553 TerminalSend(ResetCursorPosition());
555 g_active_screen =
nullptr;
558 if (suspended_screen_) {
564 std::swap(g_active_screen, suspended_screen_);
565 g_active_screen->Install();
572 if (!use_alternative_screen_) {
575 std::cout << std::flush;
593 force_handle_ctrl_c_ = force;
599 force_handle_ctrl_z_ = force;
607 return selection_->GetParts();
611 selection_on_change_ = std::move(callback);
617 return g_active_screen;
622 frame_valid_ =
false;
631 InstallPipedInputHandling();
635 on_exit_functions.emplace([
this] { TerminalFlush(); });
639 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
640 InstallSignalHandler(signal);
646 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
647 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
651 GetConsoleMode(stdout_handle, &out_mode);
652 GetConsoleMode(stdin_handle, &in_mode);
653 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
654 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
657 const int enable_virtual_terminal_processing = 0x0004;
658 const int disable_newline_auto_return = 0x0008;
659 out_mode |= enable_virtual_terminal_processing;
660 out_mode |= disable_newline_auto_return;
663 const int enable_line_input = 0x0002;
664 const int enable_echo_input = 0x0004;
665 const int enable_virtual_terminal_input = 0x0200;
666 const int enable_window_input = 0x0008;
667 in_mode &= ~enable_echo_input;
668 in_mode &= ~enable_line_input;
669 in_mode |= enable_virtual_terminal_input;
670 in_mode |= enable_window_input;
672 SetConsoleMode(stdin_handle, in_mode);
673 SetConsoleMode(stdout_handle, out_mode);
683 for (
const int signal : {SIGWINCH, SIGTSTP}) {
684 InstallSignalHandler(signal);
687 struct termios terminal;
688 tcgetattr(tty_fd_, &terminal);
689 on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
690 tcsetattr(tty_fd_, TCSANOW, &terminal);
694 terminal.c_iflag &= ~IGNBRK;
695 terminal.c_iflag &= ~BRKINT;
697 terminal.c_iflag &= ~PARMRK;
698 terminal.c_iflag &= ~ISTRIP;
699 terminal.c_iflag &= ~INLCR;
700 terminal.c_iflag &= ~IGNCR;
701 terminal.c_iflag &= ~ICRNL;
702 terminal.c_iflag &= ~IXON;
704 terminal.c_lflag &= ~ECHO;
705 terminal.c_lflag &= ~ECHONL;
706 terminal.c_lflag &= ~ICANON;
707 terminal.c_lflag &= ~ISIG;
712 terminal.c_lflag &= ~IEXTEN;
713 terminal.c_cflag |= CS8;
715 terminal.c_cc[VMIN] = 0;
717 terminal.c_cc[VTIME] = 0;
719 tcsetattr(tty_fd_, TCSANOW, &terminal);
723 auto enable = [&](
const std::vector<DECMode>& parameters) {
724 TerminalSend(Set(parameters));
725 on_exit_functions.emplace(
726 [
this, parameters] { TerminalSend(Reset(parameters)); });
729 auto disable = [&](
const std::vector<DECMode>& parameters) {
730 TerminalSend(Reset(parameters));
731 on_exit_functions.emplace(
732 [
this, parameters] { TerminalSend(Set(parameters)); });
735 if (use_alternative_screen_) {
737 DECMode::kAlternateScreen,
747 enable({DECMode::kMouseVt200});
748 enable({DECMode::kMouseAnyEvent});
749 enable({DECMode::kMouseUrxvtMode});
750 enable({DECMode::kMouseSgrExtMode});
757 InstallTerminalInfo();
766void App::InstallPipedInputHandling() {
767 is_stdin_a_tty_ =
false;
768 is_stdout_a_tty_ =
false;
769#if defined(__EMSCRIPTEN__)
770 is_stdin_a_tty_ =
true;
771 is_stdout_a_tty_ =
true;
773 is_stdin_a_tty_ = _isatty(_fileno(stdin));
774 is_stdout_a_tty_ = _isatty(_fileno(stdout));
776 tty_fd_ = STDIN_FILENO;
777 is_stdout_a_tty_ = isatty(STDOUT_FILENO);
781 if (!handle_piped_input_) {
782 is_stdin_a_tty_ = isatty(STDIN_FILENO);
783 }
else if (isatty(STDIN_FILENO)) {
784 is_stdin_a_tty_ =
true;
787 tty_fd_ = open(
"/dev/tty", O_RDONLY);
790 tty_fd_ = STDIN_FILENO;
791 is_stdin_a_tty_ = isatty(STDIN_FILENO);
793 is_stdin_a_tty_ =
true;
795 on_exit_functions.emplace([
this] {
807 .TerminalCapabilityNames();
812 return terminal_name_;
817 return terminal_version_;
822 return terminal_emulator_name_;
827 return terminal_emulator_version_;
832 return terminal_capabilities_;
836void App::InstallTerminalInfo() {
839 if (is_stdout_a_tty_) {
840 TerminalSend(DECRQSS_DECSCUSR);
841 TerminalSend(
"\033[c");
842 TerminalSend(
"\033[>c");
843 TerminalSend(
"\033[>q");
847 int cursor_reset_shape = 1;
850 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
851 auto start = std::chrono::steady_clock::now();
852 bool cursor_shape_received =
false;
853 bool da1_received =
false;
854 bool da2_received =
false;
855 bool xtversion_received =
false;
858 FetchTerminalEvents();
859 while (internal_->setup_receiver->Has()) {
860 const auto event = internal_->setup_receiver->Pop();
861 if (event.is_cursor_shape()) {
862 cursor_reset_shape =
event.cursor_shape();
863 cursor_shape_received =
true;
865 if (event.IsTerminalCapabilities()) {
866 terminal_capabilities_ =
event.TerminalCapabilities();
869 if (event.IsTerminalNameVersion()) {
870 terminal_name_ =
event.TerminalName();
871 terminal_version_ =
event.TerminalVersion();
874 if (event.IsTerminalEmulator()) {
875 terminal_emulator_name_ =
event.TerminalEmulatorName();
876 terminal_emulator_version_ =
event.TerminalEmulatorVersion();
877 xtversion_received =
true;
881 if (cursor_shape_received && da1_received && da2_received) {
885 if (std::chrono::steady_clock::now() - start >
886 std::chrono::milliseconds(1000)) {
889 std::this_thread::sleep_for(std::chrono::milliseconds(10));
894 if (!xtversion_received && cursor_shape_received && da1_received &&
896 for (
int i = 0; i < 10; ++i) {
897 FetchTerminalEvents();
898 while (internal_->setup_receiver->Has()) {
899 const auto event = internal_->setup_receiver->Pop();
900 if (event.IsTerminalEmulator()) {
901 terminal_emulator_name_ =
event.TerminalEmulatorName();
902 terminal_emulator_version_ =
event.TerminalEmulatorVersion();
903 xtversion_received =
true;
906 if (xtversion_received) {
909 std::this_thread::sleep_for(std::chrono::milliseconds(10));
919 const bool is_vt220_plus =
921 bool reports_utf8 =
false;
922 for (
const int x : terminal_capabilities_) {
928 bool reports_color =
false;
929 for (
const int x : terminal_capabilities_) {
931 reports_color =
true;
938 if (is_modern_emulator) {
939 quirks.color_support =
941 quirks.block_characters =
true;
942 quirks.cursor_hiding =
true;
943 quirks.component_ascii =
false;
944 }
else if (is_vt220_plus || reports_utf8 || reports_color) {
950 quirks.block_characters =
true;
951 quirks.cursor_hiding =
true;
952 quirks.component_ascii =
false;
962 on_exit_functions.emplace([
this, cursor_reset_shape] {
963 TerminalSend(
"\033[?25h");
964 if (is_stdout_a_tty_) {
965 TerminalSend(
"\033[" + std::to_string(cursor_reset_shape) +
" q");
971void App::Uninstall() {
975 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
976 auto closing_receiver = internal_->event_buffer.CreateReceiverAt(
977 internal_->main_loop_receiver->index());
978 auto start = std::chrono::steady_clock::now();
979 while (internal_->cursor_position_request.HasPending()) {
980 FetchTerminalEvents();
982 while (closing_receiver->Has()) {
983 const auto event = closing_receiver->Pop();
984 if (event.is_cursor_position()) {
985 cursor_x_ =
event.cursor_x();
986 cursor_y_ =
event.cursor_y();
987 internal_->cursor_position_request.OnReply();
993 if (std::chrono::steady_clock::now() - start >
994 std::chrono::milliseconds(400)) {
997 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1006void App::RunOnceBlocking(
Component component) {
1008 const auto time_per_frame = std::chrono::microseconds(16666);
1010 auto time = std::chrono::steady_clock::now();
1011 const size_t executed_task = internal_->task_runner.
ExecutedTasks();
1014 while (executed_task == internal_->task_runner.
ExecutedTasks() &&
1018 const auto now = std::chrono::steady_clock::now();
1019 const auto delta = now - time;
1022 if (delta < time_per_frame) {
1023 const auto sleep_duration = time_per_frame - delta;
1024 std::this_thread::sleep_for(sleep_duration);
1030void App::RunOnce(
const Component& component) {
1031 const AutoReset set_component(&component_, component);
1032 ExecuteSignalHandlers();
1033 FetchTerminalEvents();
1035 while (!quit_ && internal_->main_loop_receiver->Has()) {
1036 Post(internal_->main_loop_receiver->Pop());
1040 const size_t executed_task = internal_->task_runner.
ExecutedTasks();
1043 if (executed_task == internal_->task_runner.
ExecutedTasks()) {
1047 ExecuteSignalHandlers();
1050 if (selection_data_previous_ != selection_data_) {
1051 selection_data_previous_ = selection_data_;
1052 if (selection_on_change_) {
1053 selection_on_change_();
1064 using T = std::decay_t<
decltype(arg)>;
1068 if constexpr (std::is_same_v<T, Event>) {
1070 if (arg.is_cursor_position()) {
1071 cursor_x_ = arg.cursor_x();
1072 cursor_y_ = arg.cursor_y();
1073 internal_->cursor_position_request.OnReply();
1079 if (arg.is_mouse()) {
1080 arg.mouse().x -= cursor_x_;
1081 arg.mouse().y -= cursor_y_;
1086 bool handled = component->OnEvent(arg);
1087 handled = HandleSelection(handled, arg);
1089 if (arg ==
Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
1090 RecordSignal(SIGABRT);
1094 if (arg ==
Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
1095 RecordSignal(SIGTSTP);
1099 frame_valid_ =
false;
1104 if constexpr (std::is_same_v<T, Closure>) {
1110 if constexpr (std::is_same_v<T, AnimationTask>) {
1111 if (!animation_requested_) {
1115 animation_requested_ =
false;
1118 previous_animation_time_ = now;
1120 animation::Params params(delta);
1121 component->OnAnimation(params);
1122 frame_valid_ =
false;
1131bool App::HandleSelection(
bool handled, Event event) {
1133 selection_pending_ =
nullptr;
1134 selection_data_.empty =
true;
1135 selection_ =
nullptr;
1139 if (!event.is_mouse()) {
1143 auto& mouse =
event.mouse();
1150 selection_data_.start_x = mouse.x;
1151 selection_data_.start_y = mouse.y;
1152 selection_data_.end_x = mouse.x;
1153 selection_data_.end_y = mouse.y;
1157 if (!selection_pending_) {
1162 if ((mouse.x != selection_data_.end_x) ||
1163 (mouse.y != selection_data_.end_y)) {
1164 selection_data_.end_x = mouse.x;
1165 selection_data_.end_y = mouse.y;
1166 selection_data_.empty =
false;
1173 selection_pending_ =
nullptr;
1174 selection_data_.end_x = mouse.x;
1175 selection_data_.end_y = mouse.y;
1176 selection_data_.empty =
false;
1189 auto document = component->Render();
1193 document->ComputeRequirement();
1194 switch (dimension_) {
1195 case Dimension::Fixed:
1199 case Dimension::TerminalOutput:
1200 dimx = terminal.dimx;
1203 case Dimension::Fullscreen:
1204 dimx = terminal.dimx;
1205 dimy = terminal.dimy;
1207 case Dimension::FitComponent:
1214 TerminalSend(
"\033[?25l");
1217 TerminalSend(ResetCursorPosition());
1219 if (frame_count_ != 0) {
1226 if ((
dimx <
dimx_) && !use_alternative_screen_) {
1227 TerminalSend(
"\033[J");
1228 TerminalSend(
"\033[H");
1236 cells_ = std::vector<std::vector<Cell>>(
dimy, std::vector<Cell>(
dimx));
1244 if (!use_alternative_screen_ && is_stdout_a_tty_) {
1245 RequestCursorPosition(previous_frame_resized_);
1247 previous_frame_resized_ = resized;
1249 selection_ = selection_data_.empty
1250 ? std::make_unique<Selection>()
1251 : std::make_unique<Selection>(
1252 selection_data_.start_x, selection_data_.start_y,
1253 selection_data_.end_x, selection_data_.end_y);
1254 Render(*
this, document.get(), *selection_);
1261 set_cursor_position_.clear();
1262 reset_cursor_position_.clear();
1265 set_cursor_position_ +=
"\x1B[" + std::to_string(dy) +
"A";
1266 reset_cursor_position_ +=
"\x1B[" + std::to_string(dy) +
"B";
1270 set_cursor_position_ +=
"\x1B[" + std::to_string(dx) +
"D";
1271 reset_cursor_position_ +=
"\x1B[" + std::to_string(dx) +
"C";
1275 set_cursor_position_ +=
"\033[?25h";
1276 set_cursor_position_ +=
1281 ToString(internal_->output_buffer);
1282 TerminalSend(set_cursor_position_);
1286 frame_valid_ =
true;
1291std::string App::ResetCursorPosition() {
1292 std::string result = std::move(reset_cursor_position_);
1293 reset_cursor_position_ =
"";
1298void App::RequestCursorPosition(
bool force) {
1299 internal_->cursor_position_request.Request(force);
1305void App::TerminalSend(std::string_view s) {
1306 internal_->output_buffer += s;
1310void App::TerminalFlush() {
1312 internal_->output_buffer +=
'\0';
1313 std::cout << internal_->output_buffer << std::flush;
1314 internal_->output_buffer.clear();
1319 return [
this] {
Exit(); };
1324 Post([
this] { ExitNow(); });
1328void App::ExitNow() {
1333void App::Signal(
int signal) {
1334 if (signal == SIGABRT) {
1341 if (signal == SIGTSTP) {
1343 TerminalSend(ResetCursorPosition());
1348 (void)std::raise(SIGTSTP);
1354 if (signal == SIGWINCH) {
1361size_t App::FetchTerminalEvents() {
1363 auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
1365 auto console = GetStdHandle(STD_INPUT_HANDLE);
1366 DWORD number_of_events = 0;
1367 if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
1368 return std::vector<INPUT_RECORD>();
1370 if (number_of_events <= 0) {
1372 return std::vector<INPUT_RECORD>();
1375 std::vector<INPUT_RECORD> records(number_of_events);
1376 DWORD number_of_events_read = 0;
1377 if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
1378 &number_of_events_read)) {
1379 return std::vector<INPUT_RECORD>();
1381 records.resize(number_of_events_read);
1385 auto records = get_input_records();
1386 if (records.size() == 0) {
1387 const auto timeout =
1388 std::chrono::steady_clock::now() - internal_->last_char_time;
1389 const size_t timeout_microseconds =
1390 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1391 internal_->terminal_input_parser.
Timeout(timeout_microseconds);
1394 internal_->last_char_time = std::chrono::steady_clock::now();
1399 std::wstring wstring;
1400 for (
const auto& r : records) {
1401 switch (r.EventType) {
1403 auto key_event = r.Event.KeyEvent;
1405 if (key_event.bKeyDown == FALSE) {
1408 const wchar_t wc = key_event.uChar.UnicodeChar;
1410 if (wc >= 0xd800 && wc <= 0xdbff) {
1415 internal_->terminal_input_parser.
Add(it);
1419 case WINDOW_BUFFER_SIZE_EVENT:
1429 return records.size();
1430#elif defined(__EMSCRIPTEN__)
1433 std::array<char, 128> out{};
1434 const ssize_t l = read(STDIN_FILENO, out.data(), out.size());
1436 const auto timeout =
1437 std::chrono::steady_clock::now() - internal_->last_char_time;
1438 const size_t timeout_microseconds =
1439 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1440 internal_->terminal_input_parser.
Timeout(timeout_microseconds);
1443 internal_->last_char_time = std::chrono::steady_clock::now();
1446 for (ssize_t i = 0; i < l; ++i) {
1447 internal_->terminal_input_parser.
Add(out.at(
static_cast<size_t>(i)));
1451 struct pollfd pfd = {tty_fd_, POLLIN, 0};
1452 const int poll_result = poll(&pfd, 1, 0);
1453 if (poll_result <= 0) {
1454 const auto timeout =
1455 std::chrono::steady_clock::now() - internal_->last_char_time;
1456 const size_t timeout_ms =
1457 std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
1458 internal_->terminal_input_parser.
Timeout(
static_cast<int>(timeout_ms));
1461 internal_->last_char_time = std::chrono::steady_clock::now();
1464 std::array<char, 128> out{};
1465 const ssize_t l = read(tty_fd_, out.data(), out.size());
1471 for (ssize_t i = 0; i < l; ++i) {
1472 internal_->terminal_input_parser.
Add(out.at(
static_cast<size_t>(i)));
1478void App::PostAnimationTask() {
1479 Post(AnimationTask());
1483 internal_->task_runner.
PostDelayedTask([
this] { PostAnimationTask(); },
1484 std::chrono::milliseconds(15));
1487bool App::SelectionData::operator==(
const App::SelectionData& other)
const {
1488 if (empty && other.empty) {
1491 if (empty || other.empty) {
1494 return start_x == other.start_x && start_y == other.start_y &&
1495 end_x == other.end_x && end_y == other.end_y;
1498bool App::SelectionData::operator!=(
const App::SelectionData& other)
const {
1499 return !(*
this == other);
static void Signal(App &s, int signal)
auto PostTask(Task task) -> void
Schedules a task to be executed immediately.
auto RunUntilIdle() -> std::optional< std::chrono::steady_clock::duration >
Runs the tasks in the queue.
auto PostDelayedTask(Task task, std::chrono::steady_clock::duration duration) -> void
Schedules a task to be executed after a certain duration.
size_t ExecutedTasks() const
void HandlePipedInput(bool enable=true)
Enable or disable automatic piped input handling. When enabled, FTXUI will detect piped input and red...
void Exit()
Exit the main loop.
void PostEvent(Event event)
Add an event to the main loop. It will be executed later, after every other scheduled events.
static App FitComponent()
static App * Active()
Return the currently active screen, or null if none.
void Post(Task task)
Add a task to the main loop. It will be executed later, after every other scheduled tasks.
int TerminalVersion() const
Return the terminal version.
std::vector< std::string > TerminalCapabilityNames() const
Return the names of the terminal capabilities.
static Event Special(std::string_view)
An custom event whose meaning is defined by the user of the library.
static App FullscreenPrimaryScreen()
static const Event Custom
const std::string & TerminalName() const
Return the terminal name.
static App TerminalOutput()
static App FullscreenAlternateScreen()
CapturedMouse CaptureMouse()
Try to get the unique lock about being able to capture the mouse.
const std::string & TerminalEmulatorVersion() const
Return the terminal emulator version.
static App FixedSize(int dimx, int dimy)
std::string GetSelection()
Returns the content of the current selection.
const std::vector< int > & TerminalCapabilities() const
Return the terminal capabilities.
void TrackMouse(bool enable=true)
Set whether mouse is tracked and events reported. called outside of the main loop....
void SelectionChange(std::function< void()> callback)
const std::string & TerminalEmulatorName() const
Return the terminal emulator name.
void RequestAnimationFrame()
Add a task to draw the screen one more time, until all the animations are done.
friend class ThrottledRequest
Closure ExitLoopClosure()
Return a function to exit the main loop.
void ForceHandleCtrlC(bool force)
Force FTXUI to handle or not handle Ctrl-C, even if the component catches the Event::CtrlC.
void ForceHandleCtrlZ(bool force)
Force FTXUI to handle or not handle Ctrl-Z, even if the component catches the Event::CtrlZ.
Closure WithRestoredIO(Closure)
Decorate a function. It executes the same way, but with the currently active screen terminal hooks te...
App is a Screen that can handle events, run a main loop, and manage components.
Loop is a class that manages the event loop for a component.
void RequestAnimationFrame()
RequestAnimationFrame is a function that requests a new frame to be drawn in the next animation cycle...
Represent an event. It can be key press event, a terminal resize, or more ...
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
std::string ToString() const
std::string ResetPosition(bool clear=false) const
Return a string to be printed in order to reset the cursor position to the beginning of the screen.
void Clear()
Clear all the cells from the screen.
std::vector< std::vector< Cell > > cells_
Quirks GetQuirks()
Get the terminal quirks.
Dimensions Size()
Get the terminal size.
void SetQuirks(const Quirks &quirks)
Override terminal quirks.
The FTXUI ftxui::Dimension:: namespace.
The FTXUI ftxui::animation:: namespace.
void SetFallbackSize(const Dimensions &fallbackSize)
Override terminal size in case auto-detection fails.
std::chrono::duration< float > Duration
std::chrono::time_point< Clock > TimePoint
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
The FTXUI ftxui:: namespace.
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::string to_string(std::wstring_view s)
Convert a std::wstring into a UTF8 std::string.
std::variant< Event, Closure, AnimationTask > Task
std::function< void()> Closure
std::shared_ptr< ComponentBase > Component