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;
53 constexpr
int timeout_milliseconds = 20;
54 constexpr
int timeout_microseconds = timeout_milliseconds * 1000;
57 void 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>
107 void 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)
122 #include <sys/time.h>
124 int 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);
134 void 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)
154 const std::string CSI =
"\x1b[";
162 kMouseAnyEvent = 1003,
164 kMouseSgrExtMode = 1006,
165 kMouseUrxvtMode = 1015,
166 kMouseSgrPixelsMode = 1016,
167 kAlternateScreen = 1049,
175 const std::string Serialize(std::vector<DECMode> parameters) {
178 for (DECMode parameter : parameters) {
188 const std::string Set(std::vector<DECMode> parameters) {
189 return CSI +
"?" + Serialize(parameters) +
"h";
193 const std::string Reset(std::vector<DECMode> parameters) {
194 return CSI +
"?" + Serialize(parameters) +
"l";
198 const std::string DeviceStatusReport(DSRMode ps) {
202 using SignalHandler = void(
int);
203 std::stack<std::function<void()>> on_exit_functions;
204 void OnExit(
int signal) {
206 while (!on_exit_functions.empty()) {
207 on_exit_functions.top()();
208 on_exit_functions.pop();
212 auto 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); });
217 std::function<void()> on_resize = [] {};
218 void OnResize(
int ) {
222 class CapturedMouseImpl :
public CapturedMouseInterface {
224 CapturedMouseImpl(std::function<
void(
void)> callback) : callback_(callback) {}
225 ~CapturedMouseImpl()
override { callback_(); }
228 std::function<void(
void)> callback_;
233 ScreenInteractive::ScreenInteractive(
int dimx,
236 bool use_alternative_screen)
237 : Screen(dimx, dimy),
238 dimension_(dimension),
239 use_alternative_screen_(use_alternative_screen) {
240 event_receiver_ = MakeReceiver<Event>();
241 event_sender_ = event_receiver_->MakeSender();
264 void ScreenInteractive::PostEvent(
Event event) {
266 event_sender_->Send(event);
272 mouse_captured =
true;
273 return std::make_unique<CapturedMouseImpl>(
274 [
this] { mouse_captured =
false; });
281 if (g_active_screen) {
282 std::swap(suspended_screen_, g_active_screen);
283 std::cout << suspended_screen_->reset_cursor_position
284 << suspended_screen_->ResetPosition(
true);
285 suspended_screen_->dimx_ = 0;
286 suspended_screen_->dimy_ = 0;
287 suspended_screen_->Uninstall();
291 g_active_screen =
this;
292 g_active_screen->Install();
293 g_active_screen->Main(component);
294 g_active_screen->Uninstall();
295 g_active_screen =
nullptr;
298 std::cout << reset_cursor_position;
301 if (suspended_screen_) {
302 std::cout << ResetPosition(
true);
305 std::swap(g_active_screen, suspended_screen_);
306 g_active_screen->Install();
310 std::cout << std::endl;
314 void ScreenInteractive::Install() {
315 on_exit_functions.push([
this] { ExitLoopClosure()(); });
319 for (
int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE})
320 install_signal_handler(signal, OnExit);
325 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
326 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
330 GetConsoleMode(stdout_handle, &out_mode);
331 GetConsoleMode(stdin_handle, &in_mode);
332 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
333 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
336 const int enable_virtual_terminal_processing = 0x0004;
337 const int disable_newline_auto_return = 0x0008;
338 out_mode |= enable_virtual_terminal_processing;
339 out_mode |= disable_newline_auto_return;
342 const int enable_line_input = 0x0002;
343 const int enable_echo_input = 0x0004;
344 const int enable_virtual_terminal_input = 0x0200;
345 const int enable_window_input = 0x0008;
346 in_mode &= ~enable_echo_input;
347 in_mode &= ~enable_line_input;
348 in_mode |= enable_virtual_terminal_input;
349 in_mode |= enable_window_input;
351 SetConsoleMode(stdin_handle, in_mode);
352 SetConsoleMode(stdout_handle, out_mode);
354 struct termios terminal;
355 tcgetattr(STDIN_FILENO, &terminal);
356 on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
358 terminal.c_lflag &= ~ICANON;
359 terminal.c_lflag &= ~ECHO;
360 terminal.c_cc[VMIN] = 0;
361 terminal.c_cc[VTIME] = 0;
366 tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
369 on_resize = [&] { event_sender_->Send(Event::Special({0})); };
370 install_signal_handler(SIGWINCH, OnResize);
376 on_exit_functions.push([] { Flush(); });
379 auto enable = [&](std::vector<DECMode> parameters) {
380 std::cout << Set(parameters);
381 on_exit_functions.push([=] { std::cout << Reset(parameters); });
384 auto disable = [&](std::vector<DECMode> parameters) {
385 std::cout << Reset(parameters);
386 on_exit_functions.push([=] { std::cout << Set(parameters); });
389 if (use_alternative_screen_) {
391 DECMode::kAlternateScreen,
402 DECMode::kMouseAnyEvent,
404 DECMode::kMouseSgrExtMode,
411 std::thread(&EventListener, &quit_, event_receiver_->MakeSender());
414 void ScreenInteractive::Uninstall() {
416 event_listener_.join();
421 void ScreenInteractive::Main(
Component component) {
423 if (!event_receiver_->HasPending()) {
425 std::cout << ToString() << set_cursor_position;
431 if (!event_receiver_->Receive(&event))
434 if (event.is_cursor_reporting()) {
435 cursor_x_ =
event.cursor_x();
436 cursor_y_ =
event.cursor_y();
440 if (event.is_mouse()) {
441 event.mouse().x -= cursor_x_;
442 event.mouse().y -= cursor_y_;
445 event.screen_ =
this;
446 component->OnEvent(event);
450 void ScreenInteractive::Draw(
Component component) {
451 auto document = component->Render();
454 switch (dimension_) {
459 case Dimension::TerminalOutput:
460 document->ComputeRequirement();
462 dimy = document->requirement().min_y;
464 case Dimension::Fullscreen:
468 case Dimension::FitComponent:
470 document->ComputeRequirement();
471 dimx = std::min(document->requirement().min_x, terminal.dimx);
472 dimy = std::min(document->requirement().min_y, terminal.dimy);
476 bool resized = (dimx != dimx_) || (dimy != dimy_);
477 std::cout << reset_cursor_position << ResetPosition(resized);
483 pixels_ = std::vector<std::vector<Pixel>>(dimy, std::vector<Pixel>(dimx));
484 cursor_.x = dimx_ - 1;
485 cursor_.y = dimy_ - 1;
491 static constexpr
int cursor_refresh_rate =
492 #if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
505 if (!use_alternative_screen_ && (i % cursor_refresh_rate == 0))
506 std::cout << DeviceStatusReport(DSRMode::kCursor);
511 set_cursor_position =
"";
512 reset_cursor_position =
"";
514 int dx = dimx_ - 1 - cursor_.x;
515 int dy = dimy_ - 1 - cursor_.y;
527 std::function<void()> ScreenInteractive::ExitLoopClosure() {
530 event_sender_.reset();