15#include <initializer_list>
38#define DEFINE_CONSOLEV2_PROPERTIES
39#define WIN32_LEAN_AND_MEAN
45#error Must be compiled in UNICODE mode
49#include <sys/select.h>
56#if defined(__clang__) && defined(__APPLE__)
57#define quick_exit(a) exit(a)
62struct ScreenInteractive::Internal {
64 TerminalInputParser terminal_input_parser;
66 task::TaskRunner task_runner;
69 std::chrono::time_point<std::chrono::steady_clock> last_char_time =
70 std::chrono::steady_clock::now();
72 explicit Internal(std::function<
void(Event)>
out)
73 : terminal_input_parser(std::move(
out)) {}
80 screen->RequestAnimationFrame();
87ScreenInteractive* g_active_screen =
nullptr;
91 std::cout <<
'\0' << std::flush;
94constexpr int timeout_milliseconds = 20;
95[[maybe_unused]]
constexpr int timeout_microseconds =
96 timeout_milliseconds * 1000;
99#elif defined(__EMSCRIPTEN__)
100#include <emscripten.h>
104void ftxui_on_resize(
int columns,
int rows) {
109 std::raise(SIGWINCH);
115int CheckStdinReady(
int fd) {
120 select(fd + 1, &fds,
nullptr,
nullptr, &tv);
121 return FD_ISSET(fd, &fds);
126std::stack<Closure> on_exit_functions;
128 while (!on_exit_functions.empty()) {
129 on_exit_functions.top()();
130 on_exit_functions.pop();
134std::atomic<int> g_signal_exit_count = 0;
136std::atomic<int> g_signal_stop_count = 0;
137std::atomic<int> g_signal_resize_count = 0;
141void RecordSignal(
int signal) {
149 g_signal_exit_count++;
154 g_signal_stop_count++;
158 g_signal_resize_count++;
167void ExecuteSignalHandlers() {
168 int signal_exit_count = g_signal_exit_count.exchange(0);
169 while (signal_exit_count--) {
174 int signal_stop_count = g_signal_stop_count.exchange(0);
175 while (signal_stop_count--) {
179 int signal_resize_count = g_signal_resize_count.exchange(0);
180 while (signal_resize_count--) {
186void InstallSignalHandler(
int sig) {
187 auto old_signal_handler = std::signal(sig, RecordSignal);
188 on_exit_functions.emplace(
189 [=] { std::ignore = std::signal(sig, old_signal_handler); });
195const std::string ST =
"\x1b\\";
201enum class DECMode : std::uint16_t {
207 kMouseVt200Highlight = 1001,
209 kMouseBtnEventMouse = 1002,
210 kMouseAnyEvent = 1003,
213 kMouseSgrExtMode = 1006,
214 kMouseUrxvtMode = 1015,
215 kMouseSgrPixelsMode = 1016,
216 kAlternateScreen = 1049,
220enum class DSRMode : std::uint8_t {
224std::string Serialize(
const std::vector<DECMode>& parameters) {
227 for (
const DECMode parameter : parameters) {
231 out += std::to_string(
int(parameter));
238std::string Set(
const std::vector<DECMode>& parameters) {
239 return CSI +
"?" + Serialize(parameters) +
"h";
243std::string Reset(
const std::vector<DECMode>& parameters) {
244 return CSI +
"?" + Serialize(parameters) +
"l";
248std::string DeviceStatusReport(DSRMode ps) {
249 return CSI + std::to_string(
int(ps)) +
"n";
252class CapturedMouseImpl :
public CapturedMouseInterface {
254 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
255 : callback_(std::move(callback)) {}
256 ~CapturedMouseImpl()
override { callback_(); }
257 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
258 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
259 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
260 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
263 std::function<void(
void)> callback_;
268ScreenInteractive::ScreenInteractive(
Dimension dimension,
271 bool use_alternative_screen)
272 : Screen(dimx, dimy),
273 dimension_(dimension),
274 use_alternative_screen_(use_alternative_screen) {
275 internal_ = std::make_unique<Internal>(
276 [&](Event event) { PostEvent(std::move(event)); });
304 Dimension::Fullscreen,
317 Dimension::Fullscreen,
330 Dimension::TerminalOutput,
345 Dimension::FitComponent,
368 track_mouse_ = enable;
380 handle_piped_input_ = enable;
386 internal_->task_runner.
PostTask([
this, task = std::move(task)]()
mutable {
387 HandleTask(component_, task);
400 if (animation_requested_) {
403 animation_requested_ =
true;
404 auto now = animation::Clock::now();
405 const auto time_histeresis = std::chrono::milliseconds(33);
406 if (now - previous_animation_time_ >= time_histeresis) {
407 previous_animation_time_ = now;
415 if (mouse_captured) {
418 mouse_captured =
true;
419 return std::make_unique<CapturedMouseImpl>(
420 [
this] { mouse_captured =
false; });
426 class Loop loop(this, std::move(component));
431bool ScreenInteractive::HasQuitted() {
436void ScreenInteractive::PreMain() {
438 if (g_active_screen) {
439 std::swap(suspended_screen_, g_active_screen);
441 suspended_screen_->ResetCursorPosition();
443 suspended_screen_->
dimx_ = 0;
444 suspended_screen_->
dimy_ = 0;
447 suspended_screen_->Uninstall();
451 g_active_screen =
this;
455void ScreenInteractive::PostMain() {
457 ResetCursorPosition();
459 g_active_screen =
nullptr;
462 if (suspended_screen_) {
468 std::swap(g_active_screen, suspended_screen_);
469 g_active_screen->Install();
476 if (!use_alternative_screen_) {
478 std::cout << std::flush;
497 force_handle_ctrl_c_ = force;
503 force_handle_ctrl_z_ = force;
511 return selection_->GetParts();
515 selection_on_change_ = std::move(callback);
521 return g_active_screen;
525void ScreenInteractive::Install() {
526 frame_valid_ =
false;
535 InstallPipedInputHandling();
539 on_exit_functions.emplace([] { Flush(); });
545 std::cout << DECRQSS_DECSCUSR;
546 on_exit_functions.emplace([
this] {
547 std::cout <<
"\033[?25h";
548 std::cout <<
"\033[" + std::to_string(cursor_reset_shape_) +
" q";
553 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
554 InstallSignalHandler(signal);
560 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
561 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
565 GetConsoleMode(stdout_handle, &out_mode);
566 GetConsoleMode(stdin_handle, &in_mode);
567 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
568 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
571 const int enable_virtual_terminal_processing = 0x0004;
572 const int disable_newline_auto_return = 0x0008;
573 out_mode |= enable_virtual_terminal_processing;
574 out_mode |= disable_newline_auto_return;
577 const int enable_line_input = 0x0002;
578 const int enable_echo_input = 0x0004;
579 const int enable_virtual_terminal_input = 0x0200;
580 const int enable_window_input = 0x0008;
581 in_mode &= ~enable_echo_input;
582 in_mode &= ~enable_line_input;
583 in_mode |= enable_virtual_terminal_input;
584 in_mode |= enable_window_input;
586 SetConsoleMode(stdin_handle, in_mode);
587 SetConsoleMode(stdout_handle, out_mode);
589 for (
const int signal : {SIGWINCH, SIGTSTP}) {
590 InstallSignalHandler(signal);
593 struct termios terminal;
594 tcgetattr(tty_fd_, &terminal);
595 on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
596 tcsetattr(tty_fd_, TCSANOW, &terminal);
600 terminal.c_iflag &= ~IGNBRK;
601 terminal.c_iflag &= ~BRKINT;
603 terminal.c_iflag &= ~PARMRK;
604 terminal.c_iflag &= ~ISTRIP;
605 terminal.c_iflag &= ~INLCR;
606 terminal.c_iflag &= ~IGNCR;
607 terminal.c_iflag &= ~ICRNL;
608 terminal.c_iflag &= ~IXON;
610 terminal.c_lflag &= ~ECHO;
611 terminal.c_lflag &= ~ECHONL;
612 terminal.c_lflag &= ~ICANON;
613 terminal.c_lflag &= ~ISIG;
618 terminal.c_lflag &= ~IEXTEN;
619 terminal.c_cflag |= CS8;
621 terminal.c_cc[VMIN] = 0;
622 terminal.c_cc[VTIME] = 0;
624 tcsetattr(tty_fd_, TCSANOW, &terminal);
628 auto enable = [&](
const std::vector<DECMode>& parameters) {
629 std::cout << Set(parameters);
630 on_exit_functions.emplace([=] { std::cout << Reset(parameters); });
633 auto disable = [&](
const std::vector<DECMode>& parameters) {
634 std::cout << Reset(parameters);
635 on_exit_functions.emplace([=] { std::cout << Set(parameters); });
638 if (use_alternative_screen_) {
640 DECMode::kAlternateScreen,
650 enable({DECMode::kMouseVt200});
651 enable({DECMode::kMouseAnyEvent});
652 enable({DECMode::kMouseUrxvtMode});
653 enable({DECMode::kMouseSgrExtMode});
665void ScreenInteractive::InstallPipedInputHandling() {
666#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
667 tty_fd_ = STDIN_FILENO;
671 if (!handle_piped_input_) {
676 if (isatty(STDIN_FILENO)) {
681 tty_fd_ = open(
"/dev/tty", O_RDONLY);
684 tty_fd_ = STDIN_FILENO;
689 on_exit_functions.emplace([
this] {
697void ScreenInteractive::Uninstall() {
704void ScreenInteractive::RunOnceBlocking(
Component component) {
706 const auto time_per_frame = std::chrono::microseconds(16666);
708 auto time = std::chrono::steady_clock::now();
709 size_t executed_task = internal_->task_runner.
ExecutedTasks();
712 while (executed_task == internal_->task_runner.
ExecutedTasks() &&
716 const auto now = std::chrono::steady_clock::now();
717 const auto delta = now - time;
720 if (delta < time_per_frame) {
721 const auto sleep_duration = time_per_frame - delta;
722 std::this_thread::sleep_for(sleep_duration);
728void ScreenInteractive::RunOnce(
Component component) {
729 AutoReset set_component(&component_, component);
730 ExecuteSignalHandlers();
731 FetchTerminalEvents();
734 const size_t executed_task = internal_->task_runner.
ExecutedTasks();
737 if (executed_task == internal_->task_runner.
ExecutedTasks()) {
741 ExecuteSignalHandlers();
744 if (selection_data_previous_ != selection_data_) {
745 selection_data_previous_ = selection_data_;
746 if (selection_on_change_) {
747 selection_on_change_();
755void ScreenInteractive::HandleTask(
Component component,
Task& task) {
758 using T = std::decay_t<
decltype(arg)>;
762 if constexpr (std::is_same_v<T, Event>) {
764 if (arg.is_cursor_position()) {
765 cursor_x_ = arg.cursor_x();
766 cursor_y_ = arg.cursor_y();
770 if (arg.is_cursor_shape()) {
771 cursor_reset_shape_= arg.cursor_shape();
775 if (arg.is_mouse()) {
776 arg.mouse().x -= cursor_x_;
777 arg.mouse().y -= cursor_y_;
782 bool handled = component->OnEvent(arg);
784 handled = HandleSelection(handled, arg);
786 if (arg ==
Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
787 RecordSignal(SIGABRT);
791 if (arg ==
Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
792 RecordSignal(SIGTSTP);
796 frame_valid_ =
false;
801 if constexpr (std::is_same_v<T, Closure>) {
807 if constexpr (std::is_same_v<T, AnimationTask>) {
808 if (!animation_requested_) {
812 animation_requested_ =
false;
815 previous_animation_time_ = now;
817 animation::Params params(delta);
818 component->OnAnimation(params);
819 frame_valid_ =
false;
828bool ScreenInteractive::HandleSelection(
bool handled, Event event) {
830 selection_pending_ =
nullptr;
831 selection_data_.empty =
true;
832 selection_ =
nullptr;
836 if (!event.is_mouse()) {
840 auto& mouse =
event.mouse();
847 selection_data_.start_x = mouse.x;
848 selection_data_.start_y = mouse.y;
849 selection_data_.end_x = mouse.x;
850 selection_data_.end_y = mouse.y;
854 if (!selection_pending_) {
859 if ((mouse.x != selection_data_.end_x) ||
860 (mouse.y != selection_data_.end_y)) {
861 selection_data_.end_x = mouse.x;
862 selection_data_.end_y = mouse.y;
863 selection_data_.empty =
false;
870 selection_pending_ =
nullptr;
871 selection_data_.end_x = mouse.x;
872 selection_data_.end_y = mouse.y;
873 selection_data_.empty =
false;
882void ScreenInteractive::Draw(
Component component) {
886 auto document = component->Render();
890 document->ComputeRequirement();
891 switch (dimension_) {
892 case Dimension::Fixed:
896 case Dimension::TerminalOutput:
897 dimx = terminal.dimx;
900 case Dimension::Fullscreen:
901 dimx = terminal.dimx;
902 dimy = terminal.dimy;
904 case Dimension::FitComponent:
911 ResetCursorPosition();
916 if ((
dimx <
dimx_) && !use_alternative_screen_) {
917 std::cout <<
"\033[J";
918 std::cout <<
"\033[H";
925 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
933#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
942 if (!use_alternative_screen_ && (i % 150 == 0)) {
943 std::cout << DeviceStatusReport(DSRMode::kCursor);
948 if (!use_alternative_screen_ &&
949 (previous_frame_resized_ || i % 40 == 0)) {
950 std::cout << DeviceStatusReport(DSRMode::kCursor);
953 previous_frame_resized_ = resized;
955 selection_ = selection_data_.empty
956 ? std::make_unique<Selection>()
957 : std::make_unique<Selection>(
958 selection_data_.start_x, selection_data_.start_y,
959 selection_data_.end_x, selection_data_.end_y);
960 Render(*
this, document.get(), *selection_);
967 set_cursor_position.clear();
968 reset_cursor_position.clear();
971 set_cursor_position +=
"\x1B[" + std::to_string(dy) +
"A";
972 reset_cursor_position +=
"\x1B[" + std::to_string(dy) +
"B";
976 set_cursor_position +=
"\x1B[" + std::to_string(dx) +
"D";
977 reset_cursor_position +=
"\x1B[" + std::to_string(dx) +
"C";
981 set_cursor_position +=
"\033[?25l";
983 set_cursor_position +=
"\033[?25h";
984 set_cursor_position +=
989 std::cout <<
ToString() << set_cursor_position;
997void ScreenInteractive::ResetCursorPosition() {
998 std::cout << reset_cursor_position;
999 reset_cursor_position =
"";
1004 return [
this] {
Exit(); };
1009 Post([
this] { ExitNow(); });
1013void ScreenInteractive::ExitNow() {
1018void ScreenInteractive::Signal(
int signal) {
1019 if (signal == SIGABRT) {
1026 if (signal == SIGTSTP) {
1028 ResetCursorPosition();
1034 std::ignore = std::raise(SIGTSTP);
1040 if (signal == SIGWINCH) {
1047void ScreenInteractive::FetchTerminalEvents() {
1049 auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
1051 auto console = GetStdHandle(STD_INPUT_HANDLE);
1052 DWORD number_of_events = 0;
1053 if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
1054 return std::vector<INPUT_RECORD>();
1056 if (number_of_events <= 0) {
1058 return std::vector<INPUT_RECORD>();
1061 std::vector<INPUT_RECORD> records(number_of_events);
1062 DWORD number_of_events_read = 0;
1063 if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
1064 &number_of_events_read)) {
1065 return std::vector<INPUT_RECORD>();
1067 records.resize(number_of_events_read);
1071 auto records = get_input_records();
1072 if (records.size() == 0) {
1073 const auto timeout =
1074 std::chrono::steady_clock::now() - internal_->last_char_time;
1075 const size_t timeout_microseconds =
1076 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1077 internal_->terminal_input_parser.
Timeout(timeout_microseconds);
1080 internal_->last_char_time = std::chrono::steady_clock::now();
1085 for (
const auto& r : records) {
1086 switch (r.EventType) {
1088 auto key_event = r.Event.KeyEvent;
1090 if (key_event.bKeyDown == FALSE) {
1093 std::wstring wstring;
1094 wstring += key_event.uChar.UnicodeChar;
1096 internal_->terminal_input_parser.
Add(it);
1099 case WINDOW_BUFFER_SIZE_EVENT:
1109#elif defined(__EMSCRIPTEN__)
1112 std::array<char, 128>
out{};
1113 size_t l = read(STDIN_FILENO,
out.data(),
out.size());
1115 const auto timeout =
1116 std::chrono::steady_clock::now() - internal_->last_char_time;
1117 const size_t timeout_microseconds =
1118 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1119 internal_->terminal_input_parser.
Timeout(timeout_microseconds);
1122 internal_->last_char_time = std::chrono::steady_clock::now();
1125 for (
size_t i = 0; i < l; ++i) {
1126 internal_->terminal_input_parser.
Add(
out[i]);
1129 if (!CheckStdinReady(tty_fd_)) {
1130 const auto timeout =
1131 std::chrono::steady_clock::now() - internal_->last_char_time;
1132 const size_t timeout_ms =
1133 std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
1134 internal_->terminal_input_parser.
Timeout(timeout_ms);
1137 internal_->last_char_time = std::chrono::steady_clock::now();
1140 std::array<char, 128>
out{};
1141 size_t l = read(tty_fd_,
out.data(),
out.size());
1144 for (
size_t i = 0; i < l; ++i) {
1145 internal_->terminal_input_parser.
Add(
out[i]);
1150void ScreenInteractive::PostAnimationTask() {
1151 Post(AnimationTask());
1155 internal_->task_runner.
PostDelayedTask([
this] { PostAnimationTask(); },
1156 std::chrono::milliseconds(15));
1159bool ScreenInteractive::SelectionData::operator==(
1160 const ScreenInteractive::SelectionData& other)
const {
1161 if (empty && other.empty) {
1164 if (empty || other.empty) {
1167 return start_x == other.start_x && start_y == other.start_y &&
1168 end_x == other.end_x && end_y == other.end_y;
1171bool ScreenInteractive::SelectionData::operator!=(
1172 const ScreenInteractive::SelectionData& other)
const {
1173 return !(*
this == other);
static void Signal(ScreenInteractive &s, int signal)
auto PostTask(Task task) -> void
Programa una tarea para ser ejecutada inmediatamente.
auto RunUntilIdle() -> std::optional< std::chrono::steady_clock::duration >
Ejecuta las tareas en la cola.
auto PostDelayedTask(Task task, std::chrono::steady_clock::duration duration) -> void
Programa una tarea para ser ejecutada después de una cierta duración.
size_t ExecutedTasks() const
static ScreenInteractive TerminalOutput()
void HandlePipedInput(bool enable=true)
Habilita o deshabilita el manejo automático de entrada por tubería. Cuando está habilitado,...
void Exit()
Sale del bucle principal.
static ScreenInteractive FixedSize(int dimx, int dimy)
void PostEvent(Event event)
Añade un evento al bucle principal. Se ejecutará más tarde, después de todos los demás eventos progra...
void Post(Task task)
Añade una tarea al bucle principal. Se ejecutará más tarde, después de todas las demás tareas program...
static ScreenInteractive FitComponent()
static ScreenInteractive Fullscreen()
static const Event Custom
static ScreenInteractive FullscreenPrimaryScreen()
static ScreenInteractive * Active()
Devuelve la pantalla actualmente activa, o nulo si no hay ninguna.
CapturedMouse CaptureMouse()
Intenta obtener el bloqueo único para poder capturar el ratón.
std::string GetSelection()
Devuelve el contenido de la selección actual.
static ScreenInteractive FullscreenAlternateScreen()
void TrackMouse(bool enable=true)
Establece si el ratón es rastreado y se informan los eventos. se llama fuera del bucle principal....
void SelectionChange(std::function< void()> callback)
void RequestAnimationFrame()
Añade una tarea para dibujar la pantalla una vez más, hasta que todas las animaciones hayan terminado...
Closure ExitLoopClosure()
Devuelve una función para salir del bucle principal.
void ForceHandleCtrlC(bool force)
Fuerza a FTXUI a manejar o no Ctrl-C, incluso si el componente captura el Event::CtrlC.
~ScreenInteractive() override
void ForceHandleCtrlZ(bool force)
Fuerza a FTXUI a manejar o no Ctrl-Z, incluso si el componente captura el Event::CtrlZ.
Closure WithRestoredIO(Closure)
Decora una función. Se ejecuta de la misma manera, pero con los hooks del terminal de la pantalla act...
static Event Special(std::string)
Un evento personalizado cuyo significado es definido por el usuario de la biblioteca.
Loop es una clase que gestiona el bucle de eventos de un componente.
ScreenInteractive es una Screen que puede manejar eventos, ejecutar un bucle principal y administrar ...
void RequestAnimationFrame()
RequestAnimationFrame es una función que solicita que se dibuje un nuevo fotograma en el siguiente ci...
Representa un evento. Puede ser un evento de pulsación de tecla, un redimensionamiento de terminal,...
void Render(Screen &screen, const Element &element)
Muestra un elemento en un ftxui::Screen.
std::string ToString() const
std::string ResetPosition(bool clear=false) const
Devuelve una cadena que se puede imprimir para restablecer la posición del cursor al principio de la ...
void Clear()
Borra todos los píxeles de la pantalla.
std::vector< std::vector< Pixel > > pixels_
Dimensions Size()
Obtiene el tamaño del terminal.
El espacio de nombres ftxui::Dimension:: de FTXUI.
El espacio de nombres ftxui::animation:: de FTXUI.
void SetFallbackSize(const Dimensions &fallbackSize)
Anula el tamaño del terminal en caso de que la autodetección falle.
std::chrono::duration< float > Duration
std::chrono::time_point< Clock > TimePoint
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
El espacio de nombres ftxui:: de FTXUI.
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::string to_string(const std::wstring &s)
Element select(Element e)
Establece que child sea el elemento enfocado entre sus hermanos.
std::variant< Event, Closure, AnimationTask > Task
std::function< void()> Closure
std::shared_ptr< ComponentBase > Component