14#include <initializer_list>
37#define DEFINE_CONSOLEV2_PROPERTIES
38#define WIN32_LEAN_AND_MEAN
44#error Must be compiled in UNICODE mode
48#include <sys/select.h>
55#if defined(__clang__) && defined(__APPLE__)
56#define quick_exit(a) exit(a)
61struct ScreenInteractive::Internal {
63 TerminalInputParser terminal_input_parser;
65 task::TaskRunner task_runner;
68 std::chrono::time_point<std::chrono::steady_clock> last_char_time =
69 std::chrono::steady_clock::now();
71 explicit Internal(std::function<
void(Event)> out)
72 : terminal_input_parser(std::move(out)) {}
79 screen->RequestAnimationFrame();
86ScreenInteractive* g_active_screen =
nullptr;
90 std::cout <<
'\0' << std::flush;
93constexpr int timeout_milliseconds = 20;
94[[maybe_unused]]
constexpr int timeout_microseconds =
95 timeout_milliseconds * 1000;
98#elif defined(__EMSCRIPTEN__)
99#include <emscripten.h>
103void ftxui_on_resize(
int columns,
int rows) {
108 std::raise(SIGWINCH);
114int CheckStdinReady(
int fd) {
119 select(fd + 1, &fds,
nullptr,
nullptr, &tv);
120 return FD_ISSET(fd, &fds);
125std::stack<Closure> on_exit_functions;
127 while (!on_exit_functions.empty()) {
128 on_exit_functions.top()();
129 on_exit_functions.pop();
133std::atomic<int> g_signal_exit_count = 0;
135std::atomic<int> g_signal_stop_count = 0;
136std::atomic<int> g_signal_resize_count = 0;
140void RecordSignal(
int signal) {
148 g_signal_exit_count++;
153 g_signal_stop_count++;
157 g_signal_resize_count++;
166void ExecuteSignalHandlers() {
167 int signal_exit_count = g_signal_exit_count.exchange(0);
168 while (signal_exit_count--) {
173 int signal_stop_count = g_signal_stop_count.exchange(0);
174 while (signal_stop_count--) {
178 int signal_resize_count = g_signal_resize_count.exchange(0);
179 while (signal_resize_count--) {
185void InstallSignalHandler(
int sig) {
186 auto old_signal_handler = std::signal(sig, RecordSignal);
187 on_exit_functions.emplace(
188 [=] { std::ignore = std::signal(sig, old_signal_handler); });
192const std::string CSI =
"\x1b[";
195const std::string DCS =
"\x1bP";
197const std::string ST =
"\x1b\\";
201const std::string DECRQSS_DECSCUSR = DCS +
"$q q" + ST;
204enum class DECMode : std::uint16_t {
210 kMouseVt200Highlight = 1001,
212 kMouseBtnEventMouse = 1002,
213 kMouseAnyEvent = 1003,
216 kMouseSgrExtMode = 1006,
217 kMouseUrxvtMode = 1015,
218 kMouseSgrPixelsMode = 1016,
219 kAlternateScreen = 1049,
223enum class DSRMode : std::uint8_t {
227std::string Serialize(
const std::vector<DECMode>& parameters) {
230 for (
const DECMode parameter : parameters) {
234 out += std::to_string(
int(parameter));
241std::string Set(
const std::vector<DECMode>& parameters) {
242 return CSI +
"?" + Serialize(parameters) +
"h";
246std::string Reset(
const std::vector<DECMode>& parameters) {
247 return CSI +
"?" + Serialize(parameters) +
"l";
251std::string DeviceStatusReport(DSRMode ps) {
252 return CSI + std::to_string(
int(ps)) +
"n";
255class CapturedMouseImpl :
public CapturedMouseInterface {
257 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
258 : callback_(std::move(callback)) {}
259 ~CapturedMouseImpl()
override { callback_(); }
260 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
261 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
262 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
263 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
266 std::function<void(
void)> callback_;
271ScreenInteractive::ScreenInteractive(
Dimension dimension,
274 bool use_alternative_screen)
275 : Screen(dimx, dimy),
276 dimension_(dimension),
277 use_alternative_screen_(use_alternative_screen) {
278 internal_ = std::make_unique<Internal>(
279 [&](Event event) { PostEvent(std::move(event)); });
304 Dimension::Fullscreen,
316 Dimension::Fullscreen,
328 Dimension::TerminalOutput,
342 Dimension::FitComponent,
364 track_mouse_ = enable;
374 handle_piped_input_ = enable;
380 internal_->task_runner.
PostTask([
this, task = std::move(task)]()
mutable {
381 HandleTask(component_, task);
393 if (animation_requested_) {
396 animation_requested_ =
true;
397 auto now = animation::Clock::now();
398 const auto time_histeresis = std::chrono::milliseconds(33);
399 if (now - previous_animation_time_ >= time_histeresis) {
400 previous_animation_time_ = now;
407 if (mouse_captured) {
410 mouse_captured =
true;
411 return std::make_unique<CapturedMouseImpl>(
412 [
this] { mouse_captured =
false; });
418 class Loop loop(this, std::move(component));
423bool ScreenInteractive::HasQuitted() {
428void ScreenInteractive::PreMain() {
430 if (g_active_screen) {
431 std::swap(suspended_screen_, g_active_screen);
433 suspended_screen_->ResetCursorPosition();
435 suspended_screen_->
dimx_ = 0;
436 suspended_screen_->
dimy_ = 0;
439 suspended_screen_->Uninstall();
443 g_active_screen =
this;
444 g_active_screen->Install();
446 previous_animation_time_ = animation::Clock::now();
450void ScreenInteractive::PostMain() {
452 ResetCursorPosition();
454 g_active_screen =
nullptr;
457 if (suspended_screen_) {
463 std::swap(g_active_screen, suspended_screen_);
464 g_active_screen->Install();
471 if (!use_alternative_screen_) {
473 std::cout << std::flush;
490 force_handle_ctrl_c_ = force;
495 force_handle_ctrl_z_ = force;
503 return selection_->GetParts();
507 selection_on_change_ = std::move(callback);
513 return g_active_screen;
517void ScreenInteractive::Install() {
518 frame_valid_ =
false;
523 InstallPipedInputHandling();
526 on_exit_functions.emplace([] { Flush(); });
531 std::cout << DECRQSS_DECSCUSR;
532 on_exit_functions.emplace([
this] {
533 std::cout <<
"\033[?25h";
534 std::cout <<
"\033[" + std::to_string(cursor_reset_shape_) +
" q";
538 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
539 InstallSignalHandler(signal);
545 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
546 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
550 GetConsoleMode(stdout_handle, &out_mode);
551 GetConsoleMode(stdin_handle, &in_mode);
552 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
553 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
556 const int enable_virtual_terminal_processing = 0x0004;
557 const int disable_newline_auto_return = 0x0008;
558 out_mode |= enable_virtual_terminal_processing;
559 out_mode |= disable_newline_auto_return;
562 const int enable_line_input = 0x0002;
563 const int enable_echo_input = 0x0004;
564 const int enable_virtual_terminal_input = 0x0200;
565 const int enable_window_input = 0x0008;
566 in_mode &= ~enable_echo_input;
567 in_mode &= ~enable_line_input;
568 in_mode |= enable_virtual_terminal_input;
569 in_mode |= enable_window_input;
571 SetConsoleMode(stdin_handle, in_mode);
572 SetConsoleMode(stdout_handle, out_mode);
582 for (
const int signal : {SIGWINCH, SIGTSTP}) {
583 InstallSignalHandler(signal);
586 struct termios terminal;
587 tcgetattr(tty_fd_, &terminal);
588 on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
589 tcsetattr(tty_fd_, TCSANOW, &terminal);
593 terminal.c_iflag &= ~IGNBRK;
594 terminal.c_iflag &= ~BRKINT;
596 terminal.c_iflag &= ~PARMRK;
597 terminal.c_iflag &= ~ISTRIP;
598 terminal.c_iflag &= ~INLCR;
599 terminal.c_iflag &= ~IGNCR;
600 terminal.c_iflag &= ~ICRNL;
601 terminal.c_iflag &= ~IXON;
603 terminal.c_lflag &= ~ECHO;
604 terminal.c_lflag &= ~ECHONL;
605 terminal.c_lflag &= ~ICANON;
606 terminal.c_lflag &= ~ISIG;
611 terminal.c_lflag &= ~IEXTEN;
612 terminal.c_cflag |= CS8;
614 terminal.c_cc[VMIN] = 0;
616 terminal.c_cc[VTIME] = 0;
618 tcsetattr(tty_fd_, TCSANOW, &terminal);
622 auto enable = [&](
const std::vector<DECMode>& parameters) {
623 std::cout << Set(parameters);
624 on_exit_functions.emplace([=] { std::cout << Reset(parameters); });
627 auto disable = [&](
const std::vector<DECMode>& parameters) {
628 std::cout << Reset(parameters);
629 on_exit_functions.emplace([=] { std::cout << Set(parameters); });
632 if (use_alternative_screen_) {
634 DECMode::kAlternateScreen,
644 enable({DECMode::kMouseVt200});
645 enable({DECMode::kMouseAnyEvent});
646 enable({DECMode::kMouseUrxvtMode});
647 enable({DECMode::kMouseSgrExtMode});
658void ScreenInteractive::InstallPipedInputHandling() {
659#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
660 tty_fd_ = STDIN_FILENO;
663 if (!handle_piped_input_) {
668 if (isatty(STDIN_FILENO)) {
673 tty_fd_ = open(
"/dev/tty", O_RDONLY);
680 on_exit_functions.emplace([
this] {
688void ScreenInteractive::Uninstall() {
695void ScreenInteractive::RunOnceBlocking(
Component component) {
697 const auto time_per_frame = std::chrono::microseconds(16666);
699 auto time = std::chrono::steady_clock::now();
700 size_t executed_task = internal_->task_runner.
ExecutedTasks();
703 while (executed_task == internal_->task_runner.
ExecutedTasks() &&
707 const auto now = std::chrono::steady_clock::now();
708 const auto delta = now - time;
711 if (delta < time_per_frame) {
712 const auto sleep_duration = time_per_frame - delta;
713 std::this_thread::sleep_for(sleep_duration);
719void ScreenInteractive::RunOnce(
Component component) {
720 AutoReset set_component(&component_, component);
721 ExecuteSignalHandlers();
722 FetchTerminalEvents();
725 const size_t executed_task = internal_->task_runner.
ExecutedTasks();
728 if (executed_task == internal_->task_runner.
ExecutedTasks()) {
732 ExecuteSignalHandlers();
735 if (selection_data_previous_ != selection_data_) {
736 selection_data_previous_ = selection_data_;
737 if (selection_on_change_) {
738 selection_on_change_();
746void ScreenInteractive::HandleTask(
Component component,
Task& task) {
749 using T = std::decay_t<
decltype(arg)>;
753 if constexpr (std::is_same_v<T, Event>) {
755 if (arg.is_cursor_position()) {
756 cursor_x_ = arg.cursor_x();
757 cursor_y_ = arg.cursor_y();
761 if (arg.is_cursor_shape()) {
762 cursor_reset_shape_= arg.cursor_shape();
766 if (arg.is_mouse()) {
767 arg.mouse().x -= cursor_x_;
768 arg.mouse().y -= cursor_y_;
773 bool handled = component->OnEvent(arg);
775 handled = HandleSelection(handled, arg);
777 if (arg ==
Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
778 RecordSignal(SIGABRT);
782 if (arg ==
Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
783 RecordSignal(SIGTSTP);
787 frame_valid_ =
false;
792 if constexpr (std::is_same_v<T, Closure>) {
798 if constexpr (std::is_same_v<T, AnimationTask>) {
799 if (!animation_requested_) {
803 animation_requested_ =
false;
806 previous_animation_time_ = now;
808 animation::Params params(delta);
809 component->OnAnimation(params);
810 frame_valid_ =
false;
819bool ScreenInteractive::HandleSelection(
bool handled, Event event) {
821 selection_pending_ =
nullptr;
822 selection_data_.empty =
true;
823 selection_ =
nullptr;
827 if (!event.is_mouse()) {
831 auto& mouse =
event.mouse();
838 selection_data_.start_x = mouse.x;
839 selection_data_.start_y = mouse.y;
840 selection_data_.end_x = mouse.x;
841 selection_data_.end_y = mouse.y;
845 if (!selection_pending_) {
850 if ((mouse.x != selection_data_.end_x) ||
851 (mouse.y != selection_data_.end_y)) {
852 selection_data_.end_x = mouse.x;
853 selection_data_.end_y = mouse.y;
854 selection_data_.empty =
false;
861 selection_pending_ =
nullptr;
862 selection_data_.end_x = mouse.x;
863 selection_data_.end_y = mouse.y;
864 selection_data_.empty =
false;
873void ScreenInteractive::Draw(
Component component) {
877 auto document = component->Render();
881 document->ComputeRequirement();
882 switch (dimension_) {
883 case Dimension::Fixed:
887 case Dimension::TerminalOutput:
888 dimx = terminal.dimx;
891 case Dimension::Fullscreen:
892 dimx = terminal.dimx;
893 dimy = terminal.dimy;
895 case Dimension::FitComponent:
902 ResetCursorPosition();
906 if ((
dimx <
dimx_) && !use_alternative_screen_) {
907 std::cout <<
"\033[J";
908 std::cout <<
"\033[H";
915 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
921#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
927 if (!use_alternative_screen_ && (i % 150 == 0)) {
928 std::cout << DeviceStatusReport(DSRMode::kCursor);
933 if (!use_alternative_screen_ &&
934 (previous_frame_resized_ || i % 40 == 0)) {
935 std::cout << DeviceStatusReport(DSRMode::kCursor);
938 previous_frame_resized_ = resized;
940 selection_ = selection_data_.empty
941 ? std::make_unique<Selection>()
942 : std::make_unique<Selection>(
943 selection_data_.start_x, selection_data_.start_y,
944 selection_data_.end_x, selection_data_.end_y);
945 Render(*
this, document.get(), *selection_);
952 set_cursor_position.clear();
953 reset_cursor_position.clear();
956 set_cursor_position +=
"\x1B[" + std::to_string(dy) +
"A";
957 reset_cursor_position +=
"\x1B[" + std::to_string(dy) +
"B";
961 set_cursor_position +=
"\x1B[" + std::to_string(dx) +
"D";
962 reset_cursor_position +=
"\x1B[" + std::to_string(dx) +
"C";
966 set_cursor_position +=
"\033[?25l";
968 set_cursor_position +=
"\033[?25h";
969 set_cursor_position +=
974 std::cout <<
ToString() << set_cursor_position;
982void ScreenInteractive::ResetCursorPosition() {
983 std::cout << reset_cursor_position;
984 reset_cursor_position =
"";
989 return [
this] {
Exit(); };
994 Post([
this] { ExitNow(); });
998void ScreenInteractive::ExitNow() {
1003void ScreenInteractive::Signal(
int signal) {
1004 if (signal == SIGABRT) {
1011 if (signal == SIGTSTP) {
1013 ResetCursorPosition();
1019 std::ignore = std::raise(SIGTSTP);
1025 if (signal == SIGWINCH) {
1032void ScreenInteractive::FetchTerminalEvents() {
1034 auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
1036 auto console = GetStdHandle(STD_INPUT_HANDLE);
1037 DWORD number_of_events = 0;
1038 if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
1039 return std::vector<INPUT_RECORD>();
1041 if (number_of_events <= 0) {
1043 return std::vector<INPUT_RECORD>();
1046 std::vector<INPUT_RECORD> records(number_of_events);
1047 DWORD number_of_events_read = 0;
1048 if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
1049 &number_of_events_read)) {
1050 return std::vector<INPUT_RECORD>();
1052 records.resize(number_of_events_read);
1056 auto records = get_input_records();
1057 if (records.size() == 0) {
1058 const auto timeout =
1059 std::chrono::steady_clock::now() - internal_->last_char_time;
1060 const size_t timeout_microseconds =
1061 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1062 internal_->terminal_input_parser.
Timeout(timeout_microseconds);
1065 internal_->last_char_time = std::chrono::steady_clock::now();
1069 for (
const auto& r : records) {
1070 switch (r.EventType) {
1072 auto key_event = r.Event.KeyEvent;
1074 if (key_event.bKeyDown == FALSE) {
1077 std::wstring wstring;
1078 wstring += key_event.uChar.UnicodeChar;
1080 internal_->terminal_input_parser.
Add(it);
1083 case WINDOW_BUFFER_SIZE_EVENT:
1093#elif defined(__EMSCRIPTEN__)
1096 std::array<char, 128> out{};
1097 size_t l = read(STDIN_FILENO, out.data(), out.size());
1099 const auto timeout =
1100 std::chrono::steady_clock::now() - internal_->last_char_time;
1101 const size_t timeout_microseconds =
1102 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1103 internal_->terminal_input_parser.
Timeout(timeout_microseconds);
1107 for (
size_t i = 0; i < l; ++i) {
1108 internal_->terminal_input_parser.
Add(out[i]);
1111 if (!CheckStdinReady(tty_fd_)) {
1112 const auto timeout =
1113 std::chrono::steady_clock::now() - internal_->last_char_time;
1114 const size_t timeout_ms =
1115 std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
1116 internal_->terminal_input_parser.
Timeout(timeout_ms);
1119 internal_->last_char_time = std::chrono::steady_clock::now();
1122 std::array<char, 128> out{};
1123 size_t l = read(tty_fd_, out.data(), out.size());
1126 for (
size_t i = 0; i < l; ++i) {
1127 internal_->terminal_input_parser.
Add(out[i]);
1132void ScreenInteractive::PostAnimationTask() {
1133 Post(AnimationTask());
1136 internal_->task_runner.
PostDelayedTask([
this] { PostAnimationTask(); },
1137 std::chrono::milliseconds(15));
1140bool ScreenInteractive::SelectionData::operator==(
1141 const ScreenInteractive::SelectionData& other)
const {
1142 if (empty && other.empty) {
1145 if (empty || other.empty) {
1148 return start_x == other.start_x && start_y == other.start_y &&
1149 end_x == other.end_x && end_y == other.end_y;
1152bool ScreenInteractive::SelectionData::operator!=(
1153 const ScreenInteractive::SelectionData& other)
const {
1154 return !(*
this == other);
static void Signal(ScreenInteractive &s, int signal)
auto PostTask(Task task) -> void
タスクを即座に実行するようにスケジュールします。
auto RunUntilIdle() -> std::optional< std::chrono::steady_clock::duration >
キュー内のタスクを実行し、次の遅延タスクが実行されるまでの遅延を返します。
auto PostDelayedTask(Task task, std::chrono::steady_clock::duration duration) -> void
特定の期間の後に実行されるようにタスクをスケジュールします。
size_t ExecutedTasks() const
static ScreenInteractive TerminalOutput()
ターミナル出力の幅に一致し、描画されるコンポーネントの高さに一致するScreenInteractiveを作成します。
void HandlePipedInput(bool enable=true)
自動パイプ入力処理を有効または無効にします。 有効にすると、FTXUIはパイプ入力を検出し、キーボード入力のためにstdinを/dev/ttyからリダイレクトし、アプリケーションがパイプデータを読み取り...
static ScreenInteractive FixedSize(int dimx, int dimy)
void PostEvent(Event event)
メインループにイベントを追加します。 これは、他のすべてのスケジュールされたイベントの後に実行されます。
void Post(Task task)
メインループにタスクを追加します。 これは、他のすべてのスケジュールされたタスクの後に実行されます。
static ScreenInteractive FitComponent()
描画されるコンポーネントの幅と高さに一致するScreenInteractiveを作成します。
static ScreenInteractive Fullscreen()
static const Event Custom
static ScreenInteractive FullscreenPrimaryScreen()
ターミナルサイズの全体を使用するScreenInteractiveを作成します。プライマリスクリーンバッファが使用されます。これは、ターミナルがリサイズされた場合、以前のコンテンツがターミナルコンテンツ...
static ScreenInteractive * Active()
現在アクティブな画面を返します。アクティブな画面がない場合はヌルを返します。
CapturedMouse CaptureMouse()
マウスをキャプチャできることに関するユニークロックを取得しようとします。
std::string GetSelection()
現在の選択内容を返します
static ScreenInteractive FullscreenAlternateScreen()
ターミナルサイズの全体を使用するScreenInteractiveを作成します。これは、ターミナルコンテンツを乱すことを避けるために、代替スクリーンバッファを使用します。
void TrackMouse(bool enable=true)
マウスが追跡され、イベントが報告されるかどうかを設定します。 メインループの外側で呼び出されます。例: ScreenInteractive::Loop(...)。
void SelectionChange(std::function< void()> callback)
void RequestAnimationFrame()
すべてのアニメーションが完了するまで、画面をもう一度描画するタスクを追加します。
Closure ExitLoopClosure()
メインループを終了する関数を返します。
void ForceHandleCtrlC(bool force)
コンポーネントがEvent::CtrlCをキャッチした場合でも、FTXUIにCtrl-Cを処理させるか処理させないかを強制します。
~ScreenInteractive() override
void ForceHandleCtrlZ(bool force)
コンポーネントがEvent::CtrlZをキャッチした場合でも、FTXUIにCtrl-Zを処理させるか処理させないかを強制します。
Closure WithRestoredIO(Closure)
関数を装飾します。それは同じように実行されますが、実行中に現在アクティブなスクリーンターミナルフックは一時的にアンインストールされます。
static Event Special(std::string)
ライブラリのユーザーによって意味が定義されるカスタムイベント。
Loopは、コンポーネントのイベントループを管理するクラスです。
ScreenInteractive はイベントを処理し、メインループを実行し、コンポーネントを管理できる Screen です。
void RequestAnimationFrame()
RequestAnimationFrameは、次のアニメーションサイクルで新しいフレームが描画されるよう要求する関数です。
イベントを表します。キープレスイベント、ターミナルのリサイズなど、さまざまなイベントがあります。
void Render(Screen &screen, const Element &element)
要素をftxui::Screenに表示します。
std::string ToString() const
std::string ResetPosition(bool clear=false) const
カーソル位置を画面の先頭にリセットするために出力する文字列を返します。
void Clear()
画面からすべてのピクセルをクリアします。
std::vector< std::vector< Pixel > > pixels_
Dimensions Size()
ターミナルサイズを取得します。
FTXUI ftxui::Dimension::名前空間
FTXUI ftxui::animation::名前空間
void SetFallbackSize(const Dimensions &fallbackSize)
自動検出が失敗した場合にターミナルサイズを上書きします
std::chrono::duration< float > Duration
std::chrono::time_point< Clock > TimePoint
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::string to_string(const std::wstring &s)
Element select(Element e)
子要素を兄弟要素の中でフォーカスされたものとして設定します。
std::variant< Event, Closure, AnimationTask > Task
std::function< void()> Closure
std::shared_ptr< ComponentBase > Component