7#include <initializer_list>
28#define DEFINE_CONSOLEV2_PROPERTIES
29#define WIN32_LEAN_AND_MEAN
35#error Must be compiled in UNICODE mode
38#include <sys/select.h>
44#if defined(__clang__) && defined(__APPLE__)
45#define quick_exit(a) exit(a)
54 screen->RequestAnimationFrame();
61ScreenInteractive* g_active_screen =
nullptr;
65 std::cout <<
'\0' << std::flush;
68constexpr int timeout_milliseconds = 20;
69constexpr int timeout_microseconds = timeout_milliseconds * 1000;
72void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
73 auto console = GetStdHandle(STD_INPUT_HANDLE);
74 auto parser = TerminalInputParser(out->Clone());
78 auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
79 if (wait_result == WAIT_TIMEOUT) {
80 parser.Timeout(timeout_milliseconds);
84 DWORD number_of_events = 0;
85 if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
87 if (number_of_events <= 0)
90 std::vector<INPUT_RECORD> records{number_of_events};
91 DWORD number_of_events_read = 0;
92 ReadConsoleInput(console, records.data(), (DWORD)records.size(),
93 &number_of_events_read);
94 records.resize(number_of_events_read);
96 for (
const auto& r : records) {
97 switch (r.EventType) {
99 auto key_event = r.Event.KeyEvent;
101 if (key_event.bKeyDown == FALSE)
103 parser.Add((
char)key_event.uChar.UnicodeChar);
105 case WINDOW_BUFFER_SIZE_EVENT:
118#elif defined(__EMSCRIPTEN__)
119#include <emscripten.h>
122void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
123 (void)timeout_microseconds;
124 auto parser = TerminalInputParser(std::move(out));
128 while (read(STDIN_FILENO, &c, 1), c)
139int CheckStdinReady(
int usec_timeout) {
140 timeval tv = {0, usec_timeout};
143 FD_SET(STDIN_FILENO, &fds);
144 select(STDIN_FILENO + 1, &fds,
nullptr,
nullptr, &tv);
145 return FD_ISSET(STDIN_FILENO, &fds);
149void EventListener(std::atomic<bool>* quit,
Sender<Task> out) {
150 auto parser = TerminalInputParser(std::move(out));
153 if (!CheckStdinReady(timeout_microseconds)) {
154 parser.Timeout(timeout_milliseconds);
158 const size_t buffer_size = 100;
159 std::array<char, buffer_size> buffer;
160 int l = read(fileno(stdin), buffer.data(), buffer_size);
161 for (
int i = 0; i < l; ++i) {
162 parser.Add(buffer[i]);
169const std::string CSI =
"\x1b[";
177 kMouseAnyEvent = 1003,
179 kMouseSgrExtMode = 1006,
180 kMouseUrxvtMode = 1015,
181 kMouseSgrPixelsMode = 1016,
182 kAlternateScreen = 1049,
190std::string Serialize(
const std::vector<DECMode>& parameters) {
193 for (DECMode parameter : parameters) {
197 out += std::to_string(
int(parameter));
204std::string Set(
const std::vector<DECMode>& parameters) {
205 return CSI +
"?" + Serialize(parameters) +
"h";
209std::string Reset(
const std::vector<DECMode>& parameters) {
210 return CSI +
"?" + Serialize(parameters) +
"l";
214std::string DeviceStatusReport(DSRMode ps) {
215 return CSI + std::to_string(
int(ps)) +
"n";
218using SignalHandler = void(
int);
219std::stack<Closure> on_exit_functions;
220void OnExit(
int signal) {
222 while (!on_exit_functions.empty()) {
223 on_exit_functions.top()();
224 on_exit_functions.pop();
228const auto install_signal_handler = [](
int sig, SignalHandler handler) {
229 auto old_signal_handler = std::signal(sig, handler);
230 on_exit_functions.push([=] { std::signal(sig, old_signal_handler); });
238void OnSigStop(
int ) {
242class CapturedMouseImpl :
public CapturedMouseInterface {
244 explicit CapturedMouseImpl(std::function<
void(
void)> callback)
245 : callback_(std::move(callback)) {}
246 ~CapturedMouseImpl()
override { callback_(); }
247 CapturedMouseImpl(
const CapturedMouseImpl&) =
delete;
248 CapturedMouseImpl(CapturedMouseImpl&&) =
delete;
249 CapturedMouseImpl& operator=(
const CapturedMouseImpl&) =
delete;
250 CapturedMouseImpl& operator=(CapturedMouseImpl&&) =
delete;
253 std::function<void(
void)> callback_;
256void AnimationListener(std::atomic<bool>* quit,
Sender<Task> out) {
258 const auto time_delta = std::chrono::milliseconds(15);
260 out->Send(AnimationTask());
261 std::this_thread::sleep_for(time_delta);
267ScreenInteractive::ScreenInteractive(
int dimx,
270 bool use_alternative_screen)
271 : Screen(dimx, dimy),
272 dimension_(dimension),
273 use_alternative_screen_(use_alternative_screen) {
299 task_sender_->Send(std::move(task));
307 if (animation_requested_) {
310 animation_requested_ =
true;
311 auto now = animation::Clock::now();
312 const auto time_histeresis = std::chrono::milliseconds(33);
313 if (now - previous_animation_time >= time_histeresis) {
314 previous_animation_time = now;
319 if (mouse_captured) {
322 mouse_captured =
true;
323 return std::make_unique<CapturedMouseImpl>(
324 [
this] { mouse_captured =
false; });
329 if (g_active_screen) {
330 std::swap(suspended_screen_, g_active_screen);
331 std::cout << suspended_screen_->reset_cursor_position
333 suspended_screen_->
dimx_ = 0;
334 suspended_screen_->
dimy_ = 0;
335 suspended_screen_->Uninstall();
339 g_active_screen =
this;
340 g_active_screen->Install();
341 g_active_screen->Main(std::move(component));
342 g_active_screen->Uninstall();
343 g_active_screen =
nullptr;
346 std::cout << reset_cursor_position;
349 if (suspended_screen_) {
353 std::swap(g_active_screen, suspended_screen_);
354 g_active_screen->Install();
358 std::cout << std::endl;
375 return g_active_screen;
378void ScreenInteractive::Install() {
381 on_exit_functions.push([] { Flush(); });
387 for (
int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
388 install_signal_handler(signal, OnExit);
394 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
395 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
399 GetConsoleMode(stdout_handle, &out_mode);
400 GetConsoleMode(stdin_handle, &in_mode);
401 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
402 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
405 const int enable_virtual_terminal_processing = 0x0004;
406 const int disable_newline_auto_return = 0x0008;
407 out_mode |= enable_virtual_terminal_processing;
408 out_mode |= disable_newline_auto_return;
411 const int enable_line_input = 0x0002;
412 const int enable_echo_input = 0x0004;
413 const int enable_virtual_terminal_input = 0x0200;
414 const int enable_window_input = 0x0008;
415 in_mode &= ~enable_echo_input;
416 in_mode &= ~enable_line_input;
417 in_mode |= enable_virtual_terminal_input;
418 in_mode |= enable_window_input;
420 SetConsoleMode(stdin_handle, in_mode);
421 SetConsoleMode(stdout_handle, out_mode);
423 struct termios terminal;
424 tcgetattr(STDIN_FILENO, &terminal);
425 on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
427 terminal.c_lflag &= ~ICANON;
428 terminal.c_lflag &= ~ECHO;
429 terminal.c_cc[VMIN] = 0;
430 terminal.c_cc[VTIME] = 0;
435 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
439 install_signal_handler(SIGWINCH, OnResize);
442 install_signal_handler(SIGTSTP, OnSigStop);
445 auto enable = [&](
const std::vector<DECMode>& parameters) {
446 std::cout << Set(parameters);
447 on_exit_functions.push([=] { std::cout << Reset(parameters); });
450 auto disable = [&](
const std::vector<DECMode>& parameters) {
451 std::cout << Reset(parameters);
452 on_exit_functions.push([=] { std::cout << Set(parameters); });
455 if (use_alternative_screen_) {
457 DECMode::kAlternateScreen,
468 DECMode::kMouseAnyEvent,
470 DECMode::kMouseSgrExtMode,
478 task_sender_ = task_receiver_->MakeSender();
480 std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
481 animation_listener_ =
482 std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
485void ScreenInteractive::Uninstall() {
487 event_listener_.join();
488 animation_listener_.join();
494void ScreenInteractive::Main(
Component component) {
495 previous_animation_time = animation::Clock::now();
499 std::cout <<
ToString() << set_cursor_position;
504 bool attempt_draw =
true;
506 if (attempt_draw && !task_receiver_->HasPending()) {
508 attempt_draw =
false;
512 if (!task_receiver_->Receive(&task)) {
517 std::visit([&](
auto&& arg) {
518 using T = std::decay_t<
decltype(arg)>;
521 if constexpr (std::is_same_v<T, Event>) {
522 if (arg.is_cursor_reporting()) {
523 cursor_x_ = arg.cursor_x();
524 cursor_y_ = arg.cursor_y();
528 if (arg.is_mouse()) {
529 arg.mouse().x -= cursor_x_;
530 arg.mouse().y -= cursor_y_;
534 component->OnEvent(arg);
540 if constexpr (std::is_same_v<T, Closure>) {
546 if constexpr (std::is_same_v<T, AnimationTask>) {
547 if (!animation_requested_) {
551 animation_requested_ =
false;
554 previous_animation_time = now;
556 animation::Params params(delta);
557 component->OnAnimation(params);
568void ScreenInteractive::Draw(
Component component) {
569 auto document = component->Render();
572 switch (dimension_) {
573 case Dimension::Fixed:
577 case Dimension::TerminalOutput:
578 document->ComputeRequirement();
580 dimy = document->requirement().min_y;
582 case Dimension::Fullscreen:
586 case Dimension::FitComponent:
588 document->ComputeRequirement();
589 dimx = std::min(document->requirement().min_x, terminal.dimx);
590 dimy = std::min(document->requirement().min_y, terminal.dimy);
595 std::cout << reset_cursor_position <<
ResetPosition(resized);
601 pixels_ = std::vector<std::vector<Pixel>>(
dimy, std::vector<Pixel>(
dimx));
609#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
618 if (!use_alternative_screen_ && (i % 150 == 0)) {
619 std::cout << DeviceStatusReport(DSRMode::kCursor);
624 if (!use_alternative_screen_ &&
625 (previous_frame_resized_ || i % 40 == 0)) {
626 std::cout << DeviceStatusReport(DSRMode::kCursor);
629 previous_frame_resized_ = resized;
634 set_cursor_position =
"";
635 reset_cursor_position =
"";
641 set_cursor_position +=
"\x1B[" + std::to_string(dx) +
"D";
642 reset_cursor_position +=
"\x1B[" + std::to_string(dx) +
"C";
645 set_cursor_position +=
"\x1B[" + std::to_string(dy) +
"A";
646 reset_cursor_position +=
"\x1B[" + std::to_string(dy) +
"B";
653 task_sender_.reset();
657void ScreenInteractive::SigStop() {
663 std::cout << reset_cursor_position;
664 reset_cursor_position =
"";
static void SigStop(ScreenInteractive &s)
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_
std::chrono::duration< double > Duration
std::chrono::time_point< Clock > TimePoint
void RequestAnimationFrame()
std::unique_ptr< CapturedMouseInterface > CapturedMouse
Receiver< T > MakeReceiver()
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)