9#include <initializer_list>
32#define DEFINE_CONSOLEV2_PROPERTIES
33#define WIN32_LEAN_AND_MEAN
39#error Must be compiled in UNICODE mode
42#include <sys/select.h>
48#if defined(__clang__) && defined(__APPLE__)
49#define quick_exit(a) exit(a)
58 screen->RequestAnimationFrame();
65ScreenInteractive* g_active_screen =
nullptr;
69 std::cout <<
'\0' << std::flush;
72constexpr int timeout_milliseconds = 20;
73constexpr int timeout_microseconds = timeout_milliseconds * 1000;
76void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
77 auto console = GetStdHandle(STD_INPUT_HANDLE);
78 auto parser = TerminalInputParser(out->Clone());
82 auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
83 if (wait_result == WAIT_TIMEOUT) {
84 parser.Timeout(timeout_milliseconds);
88 DWORD number_of_events = 0;
89 if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
91 if (number_of_events <= 0)
94 std::vector<INPUT_RECORD> records{number_of_events};
95 DWORD number_of_events_read = 0;
96 ReadConsoleInput(console, records.data(), (DWORD)records.size(),
97 &number_of_events_read);
98 records.resize(number_of_events_read);
100 for (
const auto& r : records) {
101 switch (r.EventType) {
103 auto key_event = r.Event.KeyEvent;
105 if (key_event.bKeyDown == FALSE)
107 std::wstring wstring;
108 wstring += key_event.uChar.UnicodeChar;
113 case WINDOW_BUFFER_SIZE_EVENT:
126#elif defined(__EMSCRIPTEN__)
127#include <emscripten.h>
130void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
131 (void)timeout_microseconds;
132 auto parser = TerminalInputParser(std::move(out));
136 while (read(STDIN_FILENO, &c, 1), c)
146void ftxui_on_resize(
int columns,
int rows) {
151 std::raise(SIGWINCH);
157int CheckStdinReady(
int usec_timeout) {
158 timeval tv = {0, usec_timeout};
161 FD_SET(STDIN_FILENO, &fds);
162 select(STDIN_FILENO + 1, &fds,
nullptr,
nullptr, &tv);
163 return FD_ISSET(STDIN_FILENO, &fds);
167void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
168 auto parser = TerminalInputParser(std::move(out));
171 if (!CheckStdinReady(timeout_microseconds)) {
172 parser.Timeout(timeout_milliseconds);
176 const size_t buffer_size = 100;
177 std::array<char, buffer_size> buffer;
178 size_t l = read(fileno(stdin), buffer.data(), buffer_size);
179 for (
size_t i = 0; i < l; ++i) {
180 parser.Add(buffer[i]);
186std::stack<Closure> on_exit_functions;
188 while (!on_exit_functions.empty()) {
189 on_exit_functions.top()();
190 on_exit_functions.pop();
194std::atomic<int> g_signal_exit_count = 0;
196std::atomic<int> g_signal_stop_count = 0;
197std::atomic<int> g_signal_resize_count = 0;
201void RecordSignal(
int signal) {
209 g_signal_exit_count++;
214 g_signal_stop_count++;
218 g_signal_resize_count++;
227void ExecuteSignalHandlers() {
228 int signal_exit_count = g_signal_exit_count.exchange(0);
229 while (signal_exit_count--) {
234 int signal_stop_count = g_signal_stop_count.exchange(0);
235 while (signal_stop_count--) {
239 int signal_resize_count = g_signal_resize_count.exchange(0);
240 while (signal_resize_count--) {
246void InstallSignalHandler(
int sig) {
247 auto old_signal_handler = std::signal(sig, RecordSignal);
248 on_exit_functions.push(
249 [=] { std::ignore = std::signal(sig, old_signal_handler); });
252const std::string CSI =
"\x1b[";
261 kMouseVt200Highlight = 1001,
263 kMouseBtnEventMouse = 1002,
264 kMouseAnyEvent = 1003,
267 kMouseSgrExtMode = 1006,
268 kMouseUrxvtMode = 1015,
269 kMouseSgrPixelsMode = 1016,
270 kAlternateScreen = 1049,
278std::string Serialize(
const std::vector<DECMode>& parameters) {
281 for (
const DECMode parameter : parameters) {
285 out += std::to_string(
int(parameter));
292std::string Set(
const std::vector<DECMode>& parameters) {
293 return CSI +
"?" + Serialize(parameters) +
"h";
297std::string Reset(
const std::vector<DECMode>& parameters) {
298 return CSI +
"?" + Serialize(parameters) +
"l";
302std::string DeviceStatusReport(DSRMode ps) {
303 return CSI + std::to_string(
int(ps)) +
"n";
306class CapturedMouseImpl :
public CapturedMouseInterface {
308 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
309 : callback_(std::move(callback)) {}
310 ~CapturedMouseImpl()
override { callback_(); }
311 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
312 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
313 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
314 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
317 std::function<void(
void)> callback_;
320void AnimationListener(std::atomic<bool>* quit,
Sender<Task> out) {
322 const auto time_delta = std::chrono::milliseconds(15);
324 out->Send(AnimationTask());
325 std::this_thread::sleep_for(time_delta);
331ScreenInteractive::ScreenInteractive(
int dimx,
334 bool use_alternative_screen)
335 : Screen(dimx, dimy),
336 dimension_(dimension),
337 use_alternative_screen_(use_alternative_screen) {
356 Dimension::Fullscreen,
366 Dimension::TerminalOutput,
376 Dimension::FitComponent,
388 task_sender_->Send(std::move(task));
396 if (animation_requested_) {
399 animation_requested_ =
true;
400 auto now = animation::Clock::now();
401 const auto time_histeresis = std::chrono::milliseconds(33);
402 if (now - previous_animation_time_ >= time_histeresis) {
403 previous_animation_time_ = now;
408 if (mouse_captured) {
411 mouse_captured =
true;
412 return std::make_unique<CapturedMouseImpl>(
413 [
this] { mouse_captured =
false; });
417 class Loop loop(this, std::move(component));
421bool ScreenInteractive::HasQuitted() {
425void ScreenInteractive::PreMain() {
427 if (g_active_screen) {
428 std::swap(suspended_screen_, g_active_screen);
430 suspended_screen_->ResetCursorPosition();
432 suspended_screen_->
dimx_ = 0;
433 suspended_screen_->
dimy_ = 0;
436 suspended_screen_->Uninstall();
440 g_active_screen =
this;
441 g_active_screen->Install();
443 previous_animation_time_ = animation::Clock::now();
446void ScreenInteractive::PostMain() {
448 ResetCursorPosition();
450 g_active_screen =
nullptr;
453 if (suspended_screen_) {
459 std::swap(g_active_screen, suspended_screen_);
460 g_active_screen->Install();
467 if (!use_alternative_screen_) {
468 std::cout << std::endl;
486 return g_active_screen;
489void ScreenInteractive::Install() {
490 frame_valid_ =
false;
494 on_exit_functions.push([] { Flush(); });
500 for (
const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
501 InstallSignalHandler(signal);
507 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
508 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
512 GetConsoleMode(stdout_handle, &out_mode);
513 GetConsoleMode(stdin_handle, &in_mode);
514 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
515 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
518 const int enable_virtual_terminal_processing = 0x0004;
519 const int disable_newline_auto_return = 0x0008;
520 out_mode |= enable_virtual_terminal_processing;
521 out_mode |= disable_newline_auto_return;
524 const int enable_line_input = 0x0002;
525 const int enable_echo_input = 0x0004;
526 const int enable_virtual_terminal_input = 0x0200;
527 const int enable_window_input = 0x0008;
528 in_mode &= ~enable_echo_input;
529 in_mode &= ~enable_line_input;
530 in_mode |= enable_virtual_terminal_input;
531 in_mode |= enable_window_input;
533 SetConsoleMode(stdin_handle, in_mode);
534 SetConsoleMode(stdout_handle, out_mode);
536 for (
const int signal : {SIGWINCH, SIGTSTP}) {
537 InstallSignalHandler(signal);
540 struct termios terminal;
541 tcgetattr(STDIN_FILENO, &terminal);
542 on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
544 terminal.c_lflag &= ~ICANON;
545 terminal.c_lflag &= ~ECHO;
546 terminal.c_cc[VMIN] = 0;
547 terminal.c_cc[VTIME] = 0;
552 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
556 auto enable = [&](
const std::vector<DECMode>& parameters) {
557 std::cout << Set(parameters);
558 on_exit_functions.push([=] { std::cout << Reset(parameters); });
561 auto disable = [&](
const std::vector<DECMode>& parameters) {
562 std::cout << Reset(parameters);
563 on_exit_functions.push([=] { std::cout << Set(parameters); });
566 if (use_alternative_screen_) {
568 DECMode::kAlternateScreen,
572 on_exit_functions.push([=] {
573 std::cout <<
"\033[?25h";
574 std::cout <<
"\033[?1 q";
582 enable({DECMode::kMouseVt200});
583 enable({DECMode::kMouseAnyEvent});
584 enable({DECMode::kMouseUrxvtMode});
585 enable({DECMode::kMouseSgrExtMode});
592 task_sender_ = task_receiver_->MakeSender();
594 std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
595 animation_listener_ =
596 std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
599void ScreenInteractive::Uninstall() {
601 event_listener_.join();
602 animation_listener_.join();
607void ScreenInteractive::RunOnceBlocking(
Component component) {
608 ExecuteSignalHandlers();
610 if (task_receiver_->Receive(&task)) {
611 HandleTask(component, task);
616void ScreenInteractive::RunOnce(
Component component) {
618 while (task_receiver_->ReceiveNonBlocking(&task)) {
619 HandleTask(component, task);
620 ExecuteSignalHandlers();
622 Draw(std::move(component));
625void ScreenInteractive::HandleTask(
Component component,
Task& task) {
627 std::visit([&](
auto&& arg) {
628 using T = std::decay_t<
decltype(arg)>;
631 if constexpr (std::is_same_v<T, Event>) {
632 if (arg.is_cursor_reporting()) {
633 cursor_x_ = arg.cursor_x();
634 cursor_y_ = arg.cursor_y();
638 if (arg.is_mouse()) {
639 arg.mouse().x -= cursor_x_;
640 arg.mouse().y -= cursor_y_;
644 component->OnEvent(arg);
645 frame_valid_ =
false;
650 if constexpr (std::is_same_v<T, Closure>) {
656 if constexpr (std::is_same_v<T, AnimationTask>) {
657 if (!animation_requested_) {
661 animation_requested_ =
false;
664 previous_animation_time_ = now;
666 animation::Params params(delta);
667 component->OnAnimation(params);
668 frame_valid_ =
false;
677void ScreenInteractive::Draw(
Component component) {
681 auto document = component->Render();
685 document->ComputeRequirement();
686 switch (dimension_) {
687 case Dimension::Fixed:
691 case Dimension::TerminalOutput:
692 dimx = terminal.dimx;
693 dimy = document->requirement().min_y;
695 case Dimension::Fullscreen:
696 dimx = terminal.dimx;
697 dimy = terminal.dimy;
699 case Dimension::FitComponent:
700 dimx = std::min(document->requirement().min_x, terminal.dimx);
701 dimy = std::min(document->requirement().min_y, terminal.dimy);
706 ResetCursorPosition();
713 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
721#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
730 if (!use_alternative_screen_ && (i % 150 == 0)) {
731 std::cout << DeviceStatusReport(DSRMode::kCursor);
736 if (!use_alternative_screen_ &&
737 (previous_frame_resized_ || i % 40 == 0)) {
738 std::cout << DeviceStatusReport(DSRMode::kCursor);
741 previous_frame_resized_ = resized;
750 set_cursor_position =
"\x1B[" + std::to_string(dy) +
"A" +
751 "\x1B[" + std::to_string(dx) +
"D";
752 reset_cursor_position =
"\x1B[" + std::to_string(dy) +
"B" +
753 "\x1B[" + std::to_string(dx) +
"C";
756 set_cursor_position +=
"\033[?25l";
758 set_cursor_position +=
"\033[?25h";
759 set_cursor_position +=
764 std::cout <<
ToString() << set_cursor_position;
770void ScreenInteractive::ResetCursorPosition() {
771 std::cout << reset_cursor_position;
772 reset_cursor_position =
"";
776 return [
this] {
Exit(); };
780 Post([
this] { ExitNow(); });
783void ScreenInteractive::ExitNow() {
785 task_sender_.reset();
788void ScreenInteractive::Signal(
int signal) {
789 if (signal == SIGABRT) {
796 if (signal == SIGTSTP) {
798 ResetCursorPosition();
804 std::ignore = std::raise(SIGTSTP);
810 if (signal == SIGWINCH) {
static void Signal(ScreenInteractive &s, int signal)
static ScreenInteractive TerminalOutput()
static ScreenInteractive FixedSize(int dimx, int dimy)
void PostEvent(Event event)
static ScreenInteractive FitComponent()
static ScreenInteractive Fullscreen()
static ScreenInteractive * Active()
CapturedMouse CaptureMouse()
void RequestAnimationFrame()
Closure ExitLoopClosure()
Closure WithRestoredIO(Closure)
Decorate a function. It executes the same way, but with the currently active screen terminal hooks te...
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 pixel from the screen.
std::vector< std::vector< Pixel > > pixels_
void SetFallbackSize(const Dimensions &fallbackSize)
Override terminal size in case auto-detection fails.
std::chrono::duration< double > Duration
std::chrono::time_point< Clock > TimePoint
void RequestAnimationFrame()
std::unique_ptr< CapturedMouseInterface > CapturedMouse
Receiver< T > MakeReceiver()
std::string to_string(const std::wstring &s)
Convert a UTF8 std::string into a std::wstring.
std::unique_ptr< SenderImpl< T > > Sender
std::variant< Event, Closure, AnimationTask > Task
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
std::function< void()> Closure
std::shared_ptr< ComponentBase > Component
Represent an event. It can be key press event, a terminal resize, or more ...
static Event Special(std::string)