FTXUI  5.0.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
screen_interactive.cpp
Go to the documentation of this file.
1// Copyright 2020 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
5#include <algorithm> // for copy, max, min
6#include <array> // for array
7#include <atomic>
8#include <chrono> // for operator-, milliseconds, operator>=, duration, common_type<>::type, time_point
9#include <csignal> // for signal, SIGTSTP, SIGABRT, SIGWINCH, raise, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, __sighandler_t, size_t
10#include <cstdint>
11#include <cstdio> // for fileno, stdin
12#include <ftxui/component/task.hpp> // for Task, Closure, AnimationTask
13#include <ftxui/screen/screen.hpp> // for Pixel, Screen::Cursor, Screen, Screen::Cursor::Hidden
14#include <functional> // for function
15#include <initializer_list> // for initializer_list
16#include <iostream> // for cout, ostream, operator<<, basic_ostream, endl, flush
17#include <memory>
18#include <stack> // for stack
19#include <string>
20#include <thread> // for thread, sleep_for
21#include <tuple> // for _Swallow_assign, ignore
22#include <type_traits> // for decay_t
23#include <utility> // for move, swap
24#include <variant> // for visit, variant
25#include <vector> // for vector
26#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
27#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
28#include "ftxui/component/component_base.hpp" // for ComponentBase
29#include "ftxui/component/event.hpp" // for Event
30#include "ftxui/component/loop.hpp" // for Loop
31#include "ftxui/component/receiver.hpp" // for ReceiverImpl, Sender, MakeReceiver, SenderImpl, Receiver
32#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
33#include "ftxui/dom/node.hpp" // for Node, Render
34#include "ftxui/dom/requirement.hpp" // for Requirement
35#include "ftxui/screen/pixel.hpp" // for Pixel
36#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
37
38#if defined(_WIN32)
39#define DEFINE_CONSOLEV2_PROPERTIES
40#define WIN32_LEAN_AND_MEAN
41#ifndef NOMINMAX
42#define NOMINMAX
43#endif
44#include <windows.h>
45#ifndef UNICODE
46#error Must be compiled in UNICODE mode
47#endif
48#else
49#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set, timeval
50#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
51#include <unistd.h> // for STDIN_FILENO, read
52#endif
53
54// Quick exit is missing in standard CLang headers
55#if defined(__clang__) && defined(__APPLE__)
56#define quick_exit(a) exit(a)
57#endif
58
59namespace ftxui {
60
61namespace animation {
64 if (screen) {
65 screen->RequestAnimationFrame();
66 }
67}
68} // namespace animation
69
70namespace {
71
72ScreenInteractive* g_active_screen = nullptr; // NOLINT
73
74void Flush() {
75 // Emscripten doesn't implement flush. We interpret zero as flush.
76 std::cout << '\0' << std::flush;
77}
78
79constexpr int timeout_milliseconds = 20;
80[[maybe_unused]] constexpr int timeout_microseconds =
82#if defined(_WIN32)
83
84void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
86 auto parser = TerminalInputParser(out->Clone());
87 while (!*quit) {
88 // Throttle ReadConsoleInput by waiting 250ms, this wait function will
89 // return if there is input in the console.
93 continue;
94 }
95
98 continue;
99 if (number_of_events <= 0)
100 continue;
101
102 std::vector<INPUT_RECORD> records{number_of_events};
107
108 for (const auto& r : records) {
109 switch (r.EventType) {
110 case KEY_EVENT: {
111 auto key_event = r.Event.KeyEvent;
112 // ignore UP key events
113 if (key_event.bKeyDown == FALSE)
114 continue;
115 std::wstring wstring;
116 wstring += key_event.uChar.UnicodeChar;
117 for (auto it : to_string(wstring)) {
118 parser.Add(it);
119 }
120 } break;
122 out->Send(Event::Special({0}));
123 break;
124 case MENU_EVENT:
125 case FOCUS_EVENT:
126 case MOUSE_EVENT:
127 // TODO(mauve): Implement later.
128 break;
129 }
130 }
131 }
132}
133
134#elif defined(__EMSCRIPTEN__)
135#include <emscripten.h>
136
137// Read char from the terminal.
138void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
139 auto parser = TerminalInputParser(std::move(out));
140
141 char c;
142 while (!*quit) {
143 while (read(STDIN_FILENO, &c, 1), c)
144 parser.Add(c);
145
147 parser.Timeout(1);
148 }
149}
150
151extern "C" {
153void ftxui_on_resize(int columns, int rows) {
155 columns,
156 rows,
157 });
158 std::raise(SIGWINCH);
159}
160}
161
162#else // POSIX (Linux & Mac)
163
165 timeval tv = {0, usec_timeout}; // NOLINT
166 fd_set fds;
167 FD_ZERO(&fds); // NOLINT
168 FD_SET(STDIN_FILENO, &fds); // NOLINT
169 select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv); // NOLINT
170 return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
171}
172
173// Read char from the terminal.
174void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
175 auto parser = TerminalInputParser(std::move(out));
176
177 while (!*quit) {
180 continue;
181 }
182
183 const size_t buffer_size = 100;
184 std::array<char, buffer_size> buffer; // NOLINT;
185 size_t l = read(fileno(stdin), buffer.data(), buffer_size); // NOLINT
186 for (size_t i = 0; i < l; ++i) {
187 parser.Add(buffer[i]); // NOLINT
188 }
189 }
190}
191#endif
192
193std::stack<Closure> on_exit_functions; // NOLINT
194void OnExit() {
195 while (!on_exit_functions.empty()) {
196 on_exit_functions.top()();
197 on_exit_functions.pop();
198 }
199}
200
201std::atomic<int> g_signal_exit_count = 0; // NOLINT
202#if !defined(_WIN32)
203std::atomic<int> g_signal_stop_count = 0; // NOLINT
204std::atomic<int> g_signal_resize_count = 0; // NOLINT
205#endif
206
207// Async signal safe function
208void RecordSignal(int signal) {
209 switch (signal) {
210 case SIGABRT:
211 case SIGFPE:
212 case SIGILL:
213 case SIGINT:
214 case SIGSEGV:
215 case SIGTERM:
217 break;
218
219#if !defined(_WIN32)
220 case SIGTSTP: // NOLINT
222 break;
223
224 case SIGWINCH: // NOLINT
226 break;
227#endif
228
229 default:
230 break;
231 }
232}
233
235 int signal_exit_count = g_signal_exit_count.exchange(0);
236 while (signal_exit_count--) {
238 }
239
240#if !defined(_WIN32)
241 int signal_stop_count = g_signal_stop_count.exchange(0);
242 while (signal_stop_count--) {
244 }
245
247 while (signal_resize_count--) {
249 }
250#endif
251}
252
253void InstallSignalHandler(int sig) {
254 auto old_signal_handler = std::signal(sig, RecordSignal);
255 on_exit_functions.emplace(
256 [=] { std::ignore = std::signal(sig, old_signal_handler); });
257}
258
259// CSI: Control Sequence Introducer
260const std::string CSI = "\x1b["; // NOLINT
261 //
262// DCS: Device Control String
263const std::string DCS = "\x1bP"; // NOLINT
264// ST: String Terminator
265const std::string ST = "\x1b\\"; // NOLINT
266
267// DECRQSS: Request Status String
268// DECSCUSR: Set Cursor Style
269const std::string DECRQSS_DECSCUSR = DCS + "$q q" + ST; // NOLINT
270
271// DEC: Digital Equipment Corporation
272enum class DECMode : std::uint16_t {
273 kLineWrap = 7,
274 kCursor = 25,
275
276 kMouseX10 = 9,
277 kMouseVt200 = 1000,
278 kMouseVt200Highlight = 1001,
279
280 kMouseBtnEventMouse = 1002,
281 kMouseAnyEvent = 1003,
282
283 kMouseUtf8 = 1005,
284 kMouseSgrExtMode = 1006,
285 kMouseUrxvtMode = 1015,
286 kMouseSgrPixelsMode = 1016,
287 kAlternateScreen = 1049,
288};
289
290// Device Status Report (DSR) {
291enum class DSRMode : std::uint8_t {
292 kCursor = 6,
293};
294
295std::string Serialize(const std::vector<DECMode>& parameters) {
296 bool first = true;
297 std::string out;
298 for (const DECMode parameter : parameters) {
299 if (!first) {
300 out += ";";
301 }
302 out += std::to_string(int(parameter));
303 first = false;
304 }
305 return out;
306}
307
308// DEC Private Mode Set (DECSET)
309std::string Set(const std::vector<DECMode>& parameters) {
310 return CSI + "?" + Serialize(parameters) + "h";
311}
312
313// DEC Private Mode Reset (DECRST)
314std::string Reset(const std::vector<DECMode>& parameters) {
315 return CSI + "?" + Serialize(parameters) + "l";
316}
317
318// Device Status Report (DSR)
319std::string DeviceStatusReport(DSRMode ps) {
320 return CSI + std::to_string(int(ps)) + "n";
321}
322
323class CapturedMouseImpl : public CapturedMouseInterface {
324 public:
325 explicit CapturedMouseImpl(std::function<void(void)> callback)
326 : callback_(std::move(callback)) {}
327 ~CapturedMouseImpl() override { callback_(); }
328 CapturedMouseImpl(const CapturedMouseImpl&) = delete;
329 CapturedMouseImpl(CapturedMouseImpl&&) = delete;
330 CapturedMouseImpl& operator=(const CapturedMouseImpl&) = delete;
331 CapturedMouseImpl& operator=(CapturedMouseImpl&&) = delete;
332
333 private:
334 std::function<void(void)> callback_;
335};
336
337void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
338 // Animation at around 60fps.
339 const auto time_delta = std::chrono::milliseconds(15);
340 while (!*quit) {
341 out->Send(AnimationTask());
342 std::this_thread::sleep_for(time_delta);
343 }
344}
345
346} // namespace
347
348ScreenInteractive::ScreenInteractive(int dimx,
349 int dimy,
350 Dimension dimension,
352 : Screen(dimx, dimy),
353 dimension_(dimension),
354 use_alternative_screen_(use_alternative_screen) {
355 task_receiver_ = MakeReceiver<Task>();
356}
357
358// static
360 return {
361 dimx,
362 dimy,
363 Dimension::Fixed,
364 false,
365 };
366}
367
368/// @ingroup component
369/// Create a ScreenInteractive taking the full terminal size. This is using the
370/// alternate screen buffer to avoid messing with the terminal content.
371/// @note This is the same as `ScreenInteractive::FullscreenAlternateScreen()`
372// static
376
377/// @ingroup component
378/// Create a ScreenInteractive taking the full terminal size. The primary screen
379/// buffer is being used. It means if the terminal is resized, the previous
380/// content might mess up with the terminal content.
381// static
383 return {
384 0,
385 0,
386 Dimension::Fullscreen,
387 false,
388 };
389}
390
391/// @ingroup component
392/// Create a ScreenInteractive taking the full terminal size. This is using the
393/// alternate screen buffer to avoid messing with the terminal content.
394// static
396 return {
397 0,
398 0,
399 Dimension::Fullscreen,
400 true,
401 };
402}
403
404// static
406 return {
407 0,
408 0,
409 Dimension::TerminalOutput,
410 false,
411 };
412}
413
414// static
416 return {
417 0,
418 0,
419 Dimension::FitComponent,
420 false,
421 };
422}
423
424/// @ingroup component
425/// @brief Set whether mouse is tracked and events reported.
426/// called outside of the main loop. E.g `ScreenInteractive::Loop(...)`.
427/// @param enable Whether to enable mouse event tracking.
428/// @note This muse be called outside of the main loop. E.g. before calling
429/// `ScreenInteractive::Loop`.
430/// @note Mouse tracking is enabled by default.
431/// @note Mouse tracking is only supported on terminals that supports it.
432///
433/// ### Example
434///
435/// ```cpp
436/// auto screen = ScreenInteractive::TerminalOutput();
437/// screen.TrackMouse(false);
438/// screen.Loop(component);
439/// ```
441 track_mouse_ = enable;
442}
443
444/// @brief Add a task to the main loop.
445/// It will be executed later, after every other scheduled tasks.
446/// @ingroup component
448 // Task/Events sent toward inactive screen or screen waiting to become
449 // inactive are dropped.
450 if (!task_sender_) {
451 return;
452 }
453
454 task_sender_->Send(std::move(task));
455}
456
457/// @brief Add an event to the main loop.
458/// It will be executed later, after every other scheduled events.
459/// @ingroup component
463
464/// @brief Add a task to draw the screen one more time, until all the animations
465/// are done.
467 if (animation_requested_) {
468 return;
469 }
470 animation_requested_ = true;
471 auto now = animation::Clock::now();
472 const auto time_histeresis = std::chrono::milliseconds(33);
473 if (now - previous_animation_time_ >= time_histeresis) {
474 previous_animation_time_ = now;
475 }
476}
477
478/// @brief Try to get the unique lock about behing able to capture the mouse.
479/// @return A unique lock if the mouse is not already captured, otherwise a
480/// null.
481/// @ingroup component
483 if (mouse_captured) {
484 return nullptr;
485 }
486 mouse_captured = true;
487 return std::make_unique<CapturedMouseImpl>(
488 [this] { mouse_captured = false; });
489}
490
491/// @brief Execute the main loop.
492/// @param component The component to draw.
493/// @ingroup component
495 class Loop loop(this, std::move(component));
496 loop.Run();
497}
498
499/// @brief Return whether the main loop has been quit.
500/// @ingroup component
501bool ScreenInteractive::HasQuitted() {
502 return task_receiver_->HasQuitted();
503}
504
505// private
506void ScreenInteractive::PreMain() {
507 // Suspend previously active screen:
508 if (g_active_screen) {
509 std::swap(suspended_screen_, g_active_screen);
510 // Reset cursor position to the top of the screen and clear the screen.
511 suspended_screen_->ResetCursorPosition();
512 std::cout << suspended_screen_->ResetPosition(/*clear=*/true);
513 suspended_screen_->dimx_ = 0;
514 suspended_screen_->dimy_ = 0;
515
516 // Reset dimensions to force drawing the screen again next time:
517 suspended_screen_->Uninstall();
518 }
519
520 // This screen is now active:
521 g_active_screen = this;
522 g_active_screen->Install();
523
524 previous_animation_time_ = animation::Clock::now();
525}
526
527// private
528void ScreenInteractive::PostMain() {
529 // Put cursor position at the end of the drawing.
530 ResetCursorPosition();
531
532 g_active_screen = nullptr;
533
534 // Restore suspended screen.
535 if (suspended_screen_) {
536 // Clear screen, and put the cursor at the beginning of the drawing.
537 std::cout << ResetPosition(/*clear=*/true);
538 dimx_ = 0;
539 dimy_ = 0;
540 Uninstall();
541 std::swap(g_active_screen, suspended_screen_);
542 g_active_screen->Install();
543 } else {
544 Uninstall();
545
546 std::cout << '\r';
547 // On final exit, keep the current drawing and reset cursor position one
548 // line after it.
549 if (!use_alternative_screen_) {
550 std::cout << '\n';
551 std::cout << std::flush;
552 }
553 }
554}
555
556/// @brief Decorate a function. It executes the same way, but with the currently
557/// active screen terminal hooks temporarilly uninstalled during its execution.
558/// @param fn The function to decorate.
560 return [this, fn] {
561 Uninstall();
562 fn();
563 Install();
564 };
565}
566
567/// @brief Force FTXUI to handle or not handle Ctrl-C, even if the component
568/// catches the Event::CtrlC.
570 force_handle_ctrl_c_ = force;
571}
572
573/// @brief Force FTXUI to handle or not handle Ctrl-Z, even if the component
574/// catches the Event::CtrlZ.
576 force_handle_ctrl_z_ = force;
577}
578
579/// @brief Return the currently active screen, or null if none.
580// static
584
585// private
586void ScreenInteractive::Install() {
587 frame_valid_ = false;
588
589 // Flush the buffer for stdout to ensure whatever the user has printed before
590 // is fully applied before we start modifying the terminal configuration. This
591 // is important, because we are using two different channels (stdout vs
592 // termios/WinAPI) to communicate with the terminal emulator below. See
593 // https://github.com/ArthurSonzogni/FTXUI/issues/846
594 Flush();
595
596 // After uninstalling the new configuration, flush it to the terminal to
597 // ensure it is fully applied:
598 on_exit_functions.emplace([] { Flush(); });
599
600 on_exit_functions.emplace([this] { ExitLoopClosure()(); });
601
602 // Request the terminal to report the current cursor shape. We will restore it
603 // on exit.
604 std::cout << DECRQSS_DECSCUSR;
605 on_exit_functions.emplace([this] {
606 std::cout << "\033[?25h"; // Enable cursor.
607 std::cout << "\033[" + std::to_string(cursor_reset_shape_) + " q";
608 });
609
610 // Install signal handlers to restore the terminal state on exit. The default
611 // signal handlers are restored on exit.
612 for (const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
614 }
615
616// Save the old terminal configuration and restore it on exit.
617#if defined(_WIN32)
618 // Enable VT processing on stdout and stdin
621
622 DWORD out_mode = 0;
623 DWORD in_mode = 0;
628
629 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
630 const int enable_virtual_terminal_processing = 0x0004;
631 const int disable_newline_auto_return = 0x0008;
634
635 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
636 const int enable_line_input = 0x0002;
637 const int enable_echo_input = 0x0004;
638 const int enable_virtual_terminal_input = 0x0200;
639 const int enable_window_input = 0x0008;
644
647#else
648 for (const int signal : {SIGWINCH, SIGTSTP}) {
650 }
651
652 struct termios terminal; // NOLINT
654 on_exit_functions.emplace(
656
657 // Enabling raw terminal input mode
658 terminal.c_iflag &= ~IGNBRK; // Disable ignoring break condition
659 terminal.c_iflag &= ~BRKINT; // Disable break causing input and output to be
660 // flushed
661 terminal.c_iflag &= ~PARMRK; // Disable marking parity errors.
662 terminal.c_iflag &= ~ISTRIP; // Disable striping 8th bit off characters.
663 terminal.c_iflag &= ~INLCR; // Disable mapping NL to CR.
664 terminal.c_iflag &= ~IGNCR; // Disable ignoring CR.
665 terminal.c_iflag &= ~ICRNL; // Disable mapping CR to NL.
666 terminal.c_iflag &= ~IXON; // Disable XON/XOFF flow control on output
667
668 terminal.c_lflag &= ~ECHO; // Disable echoing input characters.
669 terminal.c_lflag &= ~ECHONL; // Disable echoing new line characters.
670 terminal.c_lflag &= ~ICANON; // Disable Canonical mode.
671 terminal.c_lflag &= ~ISIG; // Disable sending signal when hitting:
672 // - => DSUSP
673 // - C-Z => SUSP
674 // - C-C => INTR
675 // - C-d => QUIT
676 terminal.c_lflag &= ~IEXTEN; // Disable extended input processing
677 terminal.c_cflag |= CS8; // 8 bits per byte
678
679 terminal.c_cc[VMIN] = 0; // Minimum number of characters for non-canonical
680 // read.
681 terminal.c_cc[VTIME] = 0; // Timeout in deciseconds for non-canonical read.
682
684
685#endif
686
687 auto enable = [&](const std::vector<DECMode>& parameters) {
688 std::cout << Set(parameters);
689 on_exit_functions.emplace([=] { std::cout << Reset(parameters); });
690 };
691
692 auto disable = [&](const std::vector<DECMode>& parameters) {
693 std::cout << Reset(parameters);
694 on_exit_functions.emplace([=] { std::cout << Set(parameters); });
695 };
696
697 if (use_alternative_screen_) {
698 enable({
699 DECMode::kAlternateScreen,
700 });
701 }
702
703 disable({
704 // DECMode::kCursor,
705 DECMode::kLineWrap,
706 });
707
708 if (track_mouse_) {
709 enable({DECMode::kMouseVt200});
710 enable({DECMode::kMouseAnyEvent});
711 enable({DECMode::kMouseUrxvtMode});
712 enable({DECMode::kMouseSgrExtMode});
713 }
714
715 // After installing the new configuration, flush it to the terminal to
716 // ensure it is fully applied:
717 Flush();
718
719 quit_ = false;
720 task_sender_ = task_receiver_->MakeSender();
721 event_listener_ =
722 std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
723 animation_listener_ =
724 std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
725}
726
727// private
728void ScreenInteractive::Uninstall() {
729 ExitNow();
730 event_listener_.join();
731 animation_listener_.join();
732 OnExit();
733}
734
735// private
736// NOLINTNEXTLINE
737void ScreenInteractive::RunOnceBlocking(Component component) {
739 Task task;
740 if (task_receiver_->Receive(&task)) {
741 HandleTask(component, task);
742 }
743 RunOnce(component);
744}
745
746// private
747void ScreenInteractive::RunOnce(Component component) {
748 Task task;
749 while (task_receiver_->ReceiveNonBlocking(&task)) {
750 HandleTask(component, task);
752 }
753 Draw(std::move(component));
754}
755
756// private
757// NOLINTNEXTLINE
758void ScreenInteractive::HandleTask(Component component, Task& task) {
759 std::visit(
760 [&](auto&& arg) {
761 using T = std::decay_t<decltype(arg)>;
762
763 // clang-format off
764 // Handle Event.
765 if constexpr (std::is_same_v<T, Event>) {
766 if (arg.is_cursor_position()) {
767 cursor_x_ = arg.cursor_x();
768 cursor_y_ = arg.cursor_y();
769 return;
770 }
771
772 if (arg.is_cursor_shape()) {
773 cursor_reset_shape_= arg.cursor_shape();
774 return;
775 }
776
777 if (arg.is_mouse()) {
778 arg.mouse().x -= cursor_x_;
779 arg.mouse().y -= cursor_y_;
780 }
781
782 arg.screen_ = this;
783
784 const bool handled = component->OnEvent(arg);
785
786 if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
788 }
789
790#if !defined(_WIN32)
791 if (arg == Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
793 }
794#endif
795
796 frame_valid_ = false;
797 return;
798 }
799
800 // Handle callback
801 if constexpr (std::is_same_v<T, Closure>) {
802 arg();
803 return;
804 }
805
806 // Handle Animation
807 if constexpr (std::is_same_v<T, AnimationTask>) {
808 if (!animation_requested_) {
809 return;
810 }
811
812 animation_requested_ = false;
813 const animation::TimePoint now = animation::Clock::now();
814 const animation::Duration delta = now - previous_animation_time_;
815 previous_animation_time_ = now;
816
817 animation::Params params(delta);
818 component->OnAnimation(params);
819 frame_valid_ = false;
820 return;
821 }
822 },
823 task);
824 // clang-format on
825}
826
827// private
828// NOLINTNEXTLINE
829void ScreenInteractive::Draw(Component component) {
830 if (frame_valid_) {
831 return;
832 }
833 auto document = component->Render();
834 int dimx = 0;
835 int dimy = 0;
836 auto terminal = Terminal::Size();
837 document->ComputeRequirement();
838 switch (dimension_) {
839 case Dimension::Fixed:
840 dimx = dimx_;
841 dimy = dimy_;
842 break;
843 case Dimension::TerminalOutput:
844 dimx = terminal.dimx;
845 dimy = document->requirement().min_y;
846 break;
847 case Dimension::Fullscreen:
848 dimx = terminal.dimx;
849 dimy = terminal.dimy;
850 break;
851 case Dimension::FitComponent:
852 dimx = std::min(document->requirement().min_x, terminal.dimx);
853 dimy = std::min(document->requirement().min_y, terminal.dimy);
854 break;
855 }
856
857 const bool resized = (dimx != dimx_) || (dimy != dimy_);
858 ResetCursorPosition();
859 std::cout << ResetPosition(/*clear=*/resized);
860
861 // If the terminal width decrease, the terminal emulator will start wrapping
862 // lines and make the display dirty. We should clear it completely.
863 if ((dimx < dimx_) && !use_alternative_screen_) {
864 std::cout << "\033[J"; // clear terminal output
865 std::cout << "\033[H"; // move cursor to home position
866 }
867
868 // Resize the screen if needed.
869 if (resized) {
870 dimx_ = dimx;
871 dimy_ = dimy;
872 pixels_ = std::vector<std::vector<Pixel>>(dimy, std::vector<Pixel>(dimx));
873 cursor_.x = dimx_ - 1;
874 cursor_.y = dimy_ - 1;
875 }
876
877 // Periodically request the terminal emulator the frame position relative to
878 // the screen. This is useful for converting mouse position reported in
879 // screen's coordinates to frame's coordinates.
880#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
881 // Microsoft's terminal suffers from a [bug]. When reporting the cursor
882 // position, several output sequences are mixed together into garbage.
883 // This causes FTXUI user to see some "1;1;R" sequences into the Input
884 // component. See [issue]. Solution is to request cursor position less
885 // often. [bug]: https://github.com/microsoft/terminal/pull/7583 [issue]:
886 // https://github.com/ArthurSonzogni/FTXUI/issues/136
887 static int i = -3;
888 ++i;
889 if (!use_alternative_screen_ && (i % 150 == 0)) { // NOLINT
890 std::cout << DeviceStatusReport(DSRMode::kCursor);
891 }
892#else
893 static int i = -3;
894 ++i;
895 if (!use_alternative_screen_ &&
896 (previous_frame_resized_ || i % 40 == 0)) { // NOLINT
897 std::cout << DeviceStatusReport(DSRMode::kCursor);
898 }
899#endif
900 previous_frame_resized_ = resized;
901
902 Render(*this, document);
903
904 // Set cursor position for user using tools to insert CJK characters.
905 {
906 const int dx = dimx_ - 1 - cursor_.x + int(dimx_ != terminal.dimx);
907 const int dy = dimy_ - 1 - cursor_.y;
908
909 set_cursor_position.clear();
910 reset_cursor_position.clear();
911
912 if (dy != 0) {
913 set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
914 reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
915 }
916
917 if (dx != 0) {
918 set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
919 reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
920 }
921
922 if (cursor_.shape == Cursor::Hidden) {
923 set_cursor_position += "\033[?25l";
924 } else {
925 set_cursor_position += "\033[?25h";
926 set_cursor_position +=
927 "\033[" + std::to_string(int(cursor_.shape)) + " q";
928 }
929 }
930
931 std::cout << ToString() << set_cursor_position;
932 Flush();
933 Clear();
934 frame_valid_ = true;
935}
936
937// private
938void ScreenInteractive::ResetCursorPosition() {
939 std::cout << reset_cursor_position;
940 reset_cursor_position = "";
941}
942
943/// @brief Return a function to exit the main loop.
944/// @ingroup component
946 return [this] { Exit(); };
947}
948
949/// @brief Exit the main loop.
950/// @ingroup component
952 Post([this] { ExitNow(); });
953}
954
955// private:
956void ScreenInteractive::ExitNow() {
957 quit_ = true;
958 task_sender_.reset();
959}
960
961// private:
962void ScreenInteractive::Signal(int signal) {
963 if (signal == SIGABRT) {
964 Exit();
965 return;
966 }
967
968// Windows do no support SIGTSTP / SIGWINCH
969#if !defined(_WIN32)
970 if (signal == SIGTSTP) {
971 Post([&] {
972 ResetCursorPosition();
973 std::cout << ResetPosition(/*clear*/ true); // Cursor to the beginning
974 Uninstall();
975 dimx_ = 0;
976 dimy_ = 0;
977 Flush();
978 std::ignore = std::raise(SIGTSTP);
979 Install();
980 });
981 return;
982 }
983
984 if (signal == SIGWINCH) {
985 Post(Event::Special({0}));
986 return;
987 }
988#endif
989}
990
991} // namespace ftxui.
int dimy() const
Definition image.hpp:33
int dimx() const
Definition image.hpp:32
std::vector< std::vector< Pixel > > pixels_
Definition image.hpp:43
bool HasQuitted()
Whether the loop has quitted.
Definition loop.cpp:32
static void Signal(ScreenInteractive &s, int signal)
static ScreenInteractive TerminalOutput()
void Exit()
Exit the main loop.
static ScreenInteractive FixedSize(int dimx, int dimy)
void PostEvent(Event event)
Add an event to the main loop. It will be executed later, after every other scheduled events.
void Post(Task task)
Add a task to the main loop. It will be executed later, after every other scheduled tasks.
static ScreenInteractive FitComponent()
static ScreenInteractive Fullscreen()
static ScreenInteractive FullscreenPrimaryScreen()
static ScreenInteractive * Active()
Return the currently active screen, or null if none.
CapturedMouse CaptureMouse()
Try to get the unique lock about behing able to capture the mouse.
static ScreenInteractive FullscreenAlternateScreen()
void TrackMouse(bool enable=true)
Set whether mouse is tracked and events reported. called outside of the main loop....
void RequestAnimationFrame()
Add a task to draw the screen one more time, until all the animations are done.
Closure ExitLoopClosure()
Return a function to exit the main loop.
void ForceHandleCtrlC(bool force)
Force FTXUI to handle or not handle Ctrl-C, even if the component catches the Event::CtrlC.
void ForceHandleCtrlZ(bool force)
Force FTXUI to handle or not handle Ctrl-Z, even if the component catches the Event::CtrlZ.
Closure WithRestoredIO(Closure)
Decorate a function. It executes the same way, but with the currently active screen terminal hooks te...
std::string ToString() const
Definition screen.cpp:409
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.
Definition screen.cpp:469
Cursor cursor_
Definition screen.hpp:71
void Clear()
Clear all the pixel from the screen.
Definition screen.cpp:488
void SetFallbackSize(const Dimensions &fallbackSize)
Override terminal size in case auto-detection fails.
Definition terminal.cpp:124
Dimensions Size()
Get the terminal size.
Definition terminal.cpp:94
std::chrono::duration< float > Duration
Definition animation.hpp:20
std::chrono::time_point< Clock > TimePoint
Definition animation.hpp:19
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:26
std::shared_ptr< ComponentBase > Component
std::string to_string(const std::wstring &s)
Convert a UTF8 std::string into a std::wstring.
Definition string.cpp:1565
Element select(Element)
Set the child to be the one selected among its siblings.
Definition frame.cpp:149
std::variant< Event, Closure, AnimationTask > Task
Definition task.hpp:14
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition node.cpp:47
std::function< void()> Closure
Definition task.hpp:13
Represent an event. It can be key press event, a terminal resize, or more ...
Definition event.hpp:27
static const Event CtrlC
Definition event.hpp:69
static const Event CtrlZ
Definition event.hpp:92
static Event Special(std::string)
An custom event whose meaning is defined by the user of the library.
Definition event.cpp:79