5#include <initializer_list>
24#define DEFINE_CONSOLEV2_PROPERTIES
25#define WIN32_LEAN_AND_MEAN
31#error Must be compiled in UNICODE mode
34#include <sys/select.h>
40#if defined(__clang__) && defined(__APPLE__)
41#define quick_exit(a) exit(a)
50 std::cout <<
'\0' << std::flush;
53constexpr int timeout_milliseconds = 20;
54constexpr int timeout_microseconds = timeout_milliseconds * 1000;
57void EventListener(std::atomic<bool>* quit,
Sender<Event> out) {
58 auto console = GetStdHandle(STD_INPUT_HANDLE);
59 auto parser = TerminalInputParser(out->Clone());
63 auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
64 if (wait_result == WAIT_TIMEOUT) {
65 parser.Timeout(timeout_milliseconds);
69 DWORD number_of_events = 0;
70 if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
72 if (number_of_events <= 0)
75 std::vector<INPUT_RECORD> records{number_of_events};
76 DWORD number_of_events_read = 0;
77 ReadConsoleInput(console, records.data(), (DWORD)records.size(),
78 &number_of_events_read);
79 records.resize(number_of_events_read);
81 for (
const auto& r : records) {
82 switch (r.EventType) {
84 auto key_event = r.Event.KeyEvent;
86 if (key_event.bKeyDown == FALSE)
88 parser.Add((
char)key_event.uChar.UnicodeChar);
90 case WINDOW_BUFFER_SIZE_EVENT:
103#elif defined(__EMSCRIPTEN__)
104#include <emscripten.h>
107void EventListener(std::atomic<bool>* quit,
Sender<Event> out) {
108 (void)timeout_microseconds;
109 auto parser = TerminalInputParser(std::move(out));
113 while (read(STDIN_FILENO, &c, 1), c)
124int CheckStdinReady(
int usec_timeout) {
125 timeval tv = {0, usec_timeout};
128 FD_SET(STDIN_FILENO, &fds);
129 select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
130 return FD_ISSET(STDIN_FILENO, &fds);
134void EventListener(std::atomic<bool>* quit,
Sender<Event> out) {
135 const int buffer_size = 100;
137 auto parser = TerminalInputParser(std::move(out));
140 if (!CheckStdinReady(timeout_microseconds)) {
141 parser.Timeout(timeout_milliseconds);
145 char buff[buffer_size];
146 int l = read(fileno(stdin), buff, buffer_size);
147 for (
int i = 0; i < l; ++i)
154const std::string CSI =
"\x1b[";
162 kMouseAnyEvent = 1003,
164 kMouseSgrExtMode = 1006,
165 kMouseUrxvtMode = 1015,
166 kMouseSgrPixelsMode = 1016,
167 kAlternateScreen = 1049,
175const std::string Serialize(std::vector<DECMode> parameters) {
178 for (DECMode parameter : parameters) {
181 out += std::to_string(
int(parameter));
188const std::string Set(std::vector<DECMode> parameters) {
189 return CSI +
"?" + Serialize(parameters) +
"h";
193const std::string Reset(std::vector<DECMode> parameters) {
194 return CSI +
"?" + Serialize(parameters) +
"l";
198const std::string DeviceStatusReport(DSRMode ps) {
199 return CSI + std::to_string(
int(ps)) +
"n";
202using SignalHandler = void(
int);
203std::stack<ScreenInteractive::Callback> on_exit_functions;
204void OnExit(
int signal) {
206 while (!on_exit_functions.empty()) {
207 on_exit_functions.top()();
208 on_exit_functions.pop();
212auto install_signal_handler = [](
int sig, SignalHandler handler) {
213 auto old_signal_handler = std::signal(sig, handler);
214 on_exit_functions.push([&] { std::signal(sig, old_signal_handler); });
222class CapturedMouseImpl :
public CapturedMouseInterface {
224 CapturedMouseImpl(std::function<
void(
void)> callback) : callback_(callback) {}
225 ~CapturedMouseImpl()
override { callback_(); }
228 std::function<void(
void)> callback_;
235ScreenInteractive::ScreenInteractive(
int dimx,
238 bool use_alternative_screen)
240 dimension_(dimension),
241 use_alternative_screen_(use_alternative_screen) {
243 event_sender_ = event_receiver_->MakeSender();
266void ScreenInteractive::PostEvent(
Event event) {
268 event_sender_->Send(event);
274 mouse_captured =
true;
275 return std::make_unique<CapturedMouseImpl>(
276 [
this] { mouse_captured =
false; });
284 std::cout << suspended_screen_->reset_cursor_position
285 << suspended_screen_->ResetPosition(
true);
286 suspended_screen_->dimx_ = 0;
287 suspended_screen_->dimy_ = 0;
288 suspended_screen_->Uninstall();
299 std::cout << reset_cursor_position;
302 if (suspended_screen_) {
303 std::cout << ResetPosition(
true);
311 std::cout << std::endl;
326void ScreenInteractive::Install() {
329 on_exit_functions.push([] { Flush(); });
331 on_exit_functions.push([
this] { ExitLoopClosure()(); });
335 for (
int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE})
336 install_signal_handler(signal, OnExit);
341 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
342 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
346 GetConsoleMode(stdout_handle, &out_mode);
347 GetConsoleMode(stdin_handle, &in_mode);
348 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
349 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
352 const int enable_virtual_terminal_processing = 0x0004;
353 const int disable_newline_auto_return = 0x0008;
354 out_mode |= enable_virtual_terminal_processing;
355 out_mode |= disable_newline_auto_return;
358 const int enable_line_input = 0x0002;
359 const int enable_echo_input = 0x0004;
360 const int enable_virtual_terminal_input = 0x0200;
361 const int enable_window_input = 0x0008;
362 in_mode &= ~enable_echo_input;
363 in_mode &= ~enable_line_input;
364 in_mode |= enable_virtual_terminal_input;
365 in_mode |= enable_window_input;
367 SetConsoleMode(stdin_handle, in_mode);
368 SetConsoleMode(stdout_handle, out_mode);
370 struct termios terminal;
371 tcgetattr(STDIN_FILENO, &terminal);
372 on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
374 terminal.c_lflag &= ~ICANON;
375 terminal.c_lflag &= ~ECHO;
376 terminal.c_cc[VMIN] = 0;
377 terminal.c_cc[VTIME] = 0;
382 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
385 on_resize = [&] { event_sender_->Send(Event::Special({0})); };
386 install_signal_handler(SIGWINCH, OnResize);
389 auto enable = [&](std::vector<DECMode> parameters) {
390 std::cout << Set(parameters);
391 on_exit_functions.push([=] { std::cout << Reset(parameters); });
394 auto disable = [&](std::vector<DECMode> parameters) {
395 std::cout << Reset(parameters);
396 on_exit_functions.push([=] { std::cout << Set(parameters); });
399 if (use_alternative_screen_) {
401 DECMode::kAlternateScreen,
412 DECMode::kMouseAnyEvent,
414 DECMode::kMouseSgrExtMode,
423 std::thread(&EventListener, &quit_, event_receiver_->MakeSender());
426void ScreenInteractive::Uninstall() {
428 event_listener_.join();
433void ScreenInteractive::Main(Component component) {
435 if (!event_receiver_->HasPending()) {
437 std::cout << ToString() << set_cursor_position;
443 if (!event_receiver_->Receive(&event))
446 if (event.is_cursor_reporting()) {
447 cursor_x_ =
event.cursor_x();
448 cursor_y_ =
event.cursor_y();
452 if (event.is_mouse()) {
453 event.mouse().x -= cursor_x_;
454 event.mouse().y -= cursor_y_;
457 event.screen_ =
this;
458 component->OnEvent(event);
462void ScreenInteractive::Draw(Component component) {
463 auto document = component->Render();
466 switch (dimension_) {
467 case Dimension::Fixed:
471 case Dimension::TerminalOutput:
472 document->ComputeRequirement();
473 dimx = Terminal::Size().dimx;
474 dimy = document->requirement().min_y;
476 case Dimension::Fullscreen:
477 dimx = Terminal::Size().dimx;
478 dimy = Terminal::Size().dimy;
480 case Dimension::FitComponent:
481 auto terminal = Terminal::Size();
482 document->ComputeRequirement();
483 dimx = std::min(document->requirement().min_x, terminal.dimx);
484 dimy = std::min(document->requirement().min_y, terminal.dimy);
488 bool resized = (dimx != dimx_) || (dimy != dimy_);
489 std::cout << reset_cursor_position << ResetPosition(resized);
495 pixels_ = std::vector<std::vector<Pixel>>(dimy, std::vector<Pixel>(dimx));
496 cursor_.x = dimx_ - 1;
497 cursor_.y = dimy_ - 1;
503#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
512 if (!use_alternative_screen_ && (i % 150 == 0))
513 std::cout << DeviceStatusReport(DSRMode::kCursor);
517 if (!use_alternative_screen_ && (previous_frame_resized_ || i % 40 == 0))
518 std::cout << DeviceStatusReport(DSRMode::kCursor);
520 previous_frame_resized_ = resized;
525 set_cursor_position =
"";
526 reset_cursor_position =
"";
528 int dx = dimx_ - 1 - cursor_.x;
529 int dy = dimy_ - 1 - cursor_.y;
532 set_cursor_position +=
"\x1B[" + std::to_string(dx) +
"D";
533 reset_cursor_position +=
"\x1B[" + std::to_string(dx) +
"C";
536 set_cursor_position +=
"\x1B[" + std::to_string(dy) +
"A";
537 reset_cursor_position +=
"\x1B[" + std::to_string(dy) +
"B";
544 event_sender_.reset();
std::function< void()> Callback
A rectangular grid of Pixel.
std::unique_ptr< CapturedMouseInterface > CapturedMouse
Receiver< T > MakeReceiver()
ScreenInteractive * g_active_screen
std::unique_ptr< SenderImpl< T > > Sender
void Render(Screen &screen, const Element &node)
Display an element on a ftxui::Screen.
std::shared_ptr< ComponentBase > Component
Represent an event. It can be key press event, a terminal resize, or more ...
static Event Special(std::string)