FTXUI 6.1.9
C++ functional terminal UI.
Loading...
Searching...
No Matches
app.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.
4#include <algorithm> // for any_of, copy, max, min
5#include <array> // for array
6#include <atomic>
7#include <chrono> // for operator-, milliseconds, operator>=, duration, common_type<>::type, time_point
8#include <csignal> // for signal, SIGTSTP, SIGABRT, SIGWINCH, raise, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, __sighandler_t, size_t
9#include <cstdint>
10#include <cstdio> // for fileno, stdin
12#include <ftxui/component/task.hpp> // for Task, Closure, AnimationTask
13#include <ftxui/screen/screen.hpp> // for Cell, 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 <string_view>
21#include <thread> // for thread, sleep_for
22#include <tuple> // for _Swallow_assign, ignore
23#include <type_traits>
24#include <utility> // for move, swap
25#include <variant> // for visit, variant
26#include <vector> // for vector
27
28#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
29#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
30#include "ftxui/component/component_base.hpp" // for ComponentBase
31#include "ftxui/component/event.hpp" // for Event
32#include "ftxui/component/loop.hpp" // for Loop
35#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
36#include "ftxui/dom/node.hpp" // for Node, Render
37#include "ftxui/screen/cell.hpp" // for Cell
38#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
39#include "ftxui/screen/util.hpp" // for util::clamp
40#include "ftxui/util/autoreset.hpp" // for AutoReset
41
42#if defined(_WIN32)
43#define DEFINE_CONSOLEV2_PROPERTIES
44#define WIN32_LEAN_AND_MEAN
45#ifndef NOMINMAX
46#define NOMINMAX
47#endif
48#include <io.h>
49#include <windows.h>
50#else
51#include <fcntl.h>
52#include <poll.h>
53#include <sys/poll.h>
54#include <sys/types.h>
55#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
56#include <unistd.h> // for STDIN_FILENO, STDOUT_FILENO, read
57#endif
58
59#if defined(__EMSCRIPTEN__)
60#include <emscripten.h>
61#endif
62
63namespace ftxui {
64
65enum class AppDimension {
67 Fixed,
70};
71
72namespace animation {
74 auto* screen = App::Active();
75 if (screen) {
76 screen->RequestAnimationFrame();
77 }
78}
79} // namespace animation
80
81#if defined(__EMSCRIPTEN__)
82extern "C" {
83EMSCRIPTEN_KEEPALIVE
84void ftxui_on_resize(int columns, int rows) {
86 columns,
87 rows,
88 });
89 std::raise(SIGWINCH);
90}
91}
92#endif
93
94struct App::Internal {
95 App* public_;
96
97 App* suspended_screen_ = nullptr;
98 const AppDimension dimension_;
99 const bool use_alternative_screen_;
100
101 bool track_mouse_ = true;
102
103 std::string set_cursor_position_;
104 std::string reset_cursor_position_;
105
106 std::atomic<bool> quit_{false};
107 bool installed_ = false;
108 bool animation_requested_ = false;
109 animation::TimePoint previous_animation_time_;
110
111 int cursor_x_ = 1;
112 int cursor_y_ = 1;
113
114 std::uint64_t frame_count_ = 0;
115 bool mouse_captured = false;
116 bool previous_frame_resized_ = false;
117
118 bool frame_valid_ = false;
119
120 bool force_handle_ctrl_c_ = true;
121 bool force_handle_ctrl_z_ = true;
122
123 int cursor_reset_shape_ = 1;
124
125 // Piped input handling state (POSIX only)
126 bool handle_piped_input_ = true;
127 bool is_stdin_a_tty_ = false;
128 bool is_stdout_a_tty_ = false;
129 // File descriptor for /dev/tty, used for piped input handling.
130 int tty_fd_ = -1;
131
132 std::string terminal_name_ = "unknown";
133 int terminal_version_ = 0;
134
135 std::string terminal_emulator_name_ = "unknown";
136 std::string terminal_emulator_version_ = "unknown";
137
138 std::vector<int> terminal_capabilities_;
139
140 // Selection API:
141 CapturedMouse selection_pending_;
142 struct SelectionData {
143 int start_x = -1;
144 int start_y = -1;
145 int end_x = -2;
146 int end_y = -2;
147 bool empty = true;
148 bool operator==(const SelectionData& other) const {
149 if (empty && other.empty) {
150 return true;
151 }
152 if (empty || other.empty) {
153 return false;
154 }
155 return start_x == other.start_x && start_y == other.start_y &&
156 end_x == other.end_x && end_y == other.end_y;
157 }
158 bool operator!=(const SelectionData& other) const {
159 return !(*this == other);
160 }
161 };
162 SelectionData selection_data_;
163 SelectionData selection_data_previous_;
164 std::unique_ptr<Selection> selection_;
165 std::function<void()> selection_on_change_;
166
167 Component component_;
168
169 // Pre-existing in Internal:
170 TerminalInputParser terminal_input_parser;
171 task::TaskRunner task_runner;
172 std::chrono::time_point<std::chrono::steady_clock> last_char_time =
173 std::chrono::steady_clock::now();
174 std::string output_buffer;
175
176 class ThrottledRequest {
177 public:
178 ThrottledRequest(App::Internal* internal, std::function<void()> send)
179 : internal_(internal), send_(std::move(send)) {}
180
181 void Request(bool force = false) {
182 if (!internal_->is_stdin_a_tty_) {
183 return;
184 }
185
186 if (force) {
187 Send();
188 return;
189 }
190
191 // Allow only one pending request at a time. This is to avoid flooding the
192 // terminal with requests.
193 if (HasPending()) {
194 return;
195 }
196
197 const auto now = std::chrono::steady_clock::now();
198 const auto delta = now - last_request_time_;
199 const auto delay = std::chrono::milliseconds(500) - delta;
200
201 if (delay <= std::chrono::milliseconds(0)) {
202 Send();
203 return;
204 }
205
206 request_queued_ = true;
207 internal_->task_runner.PostDelayedTask(
208 [this] {
209 request_queued_ = false;
210 Request();
211 },
212 delay);
213 }
214
215 void OnReply() { pending_request_ = false; }
216
217 bool HasPending() const {
218 if (pending_request_) {
219 const auto now = std::chrono::steady_clock::now();
220 if (now - last_sent_time_ < std::chrono::seconds(5)) {
221 return true;
222 }
223 }
224 return request_queued_;
225 }
226
227 private:
228 void Send() {
229 last_sent_time_ = std::chrono::steady_clock::now();
230 pending_request_ = true;
231 send_();
232 }
233
234 App::Internal* internal_;
235 std::function<void()> send_;
236 bool pending_request_ = false;
237 std::chrono::steady_clock::time_point last_request_time_ =
238 std::chrono::steady_clock::now() - std::chrono::hours(1);
239 std::chrono::steady_clock::time_point last_sent_time_ =
240 std::chrono::steady_clock::now() - std::chrono::hours(1);
241 bool request_queued_ = false;
242 };
243
244 ThrottledRequest cursor_position_request;
245
246 MultiReceiverBuffer<Event> event_buffer;
247 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> setup_receiver;
248 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> main_loop_receiver;
249
250 Internal(App* app, AppDimension dimension, bool use_alternative_screen);
251
252 void ExitNow();
253 void Install();
254 void Uninstall();
255 void PreMain();
256 void PostMain();
257 bool HasQuitted();
258 void RunOnce(const Component& component);
259 void RunOnceBlocking(Component component);
260 void HandleTask(Component component, Task& task);
261 bool HandleSelection(bool handled, Event event);
262 void Draw(Component component);
263 std::string ResetCursorPosition();
264 void RequestCursorPosition(bool force = false);
265 void TerminalSend(std::string_view);
266 void TerminalFlush();
267 void InstallPipedInputHandling();
268 void InstallTerminalInfo();
269 void Signal(int signal);
270 size_t FetchTerminalEvents();
271 void PostAnimationTask();
272};
273
274namespace {
275
276App* g_active_screen = nullptr; // NOLINT
277
278std::stack<Closure> on_exit_functions; // NOLINT
279
280void OnExit() {
281 while (!on_exit_functions.empty()) {
282 on_exit_functions.top()();
283 on_exit_functions.pop();
284 }
285}
286
287// CSI: Control Sequence Introducer
288const std::string CSI = "\x1b["; // NOLINT
289 //
290// DCS: Device Control String
291const std::string DCS = "\x1bP"; // NOLINT
292
293// ST: String Terminator
294const std::string ST = "\x1b\\"; // NOLINT
295
296// DECRQSS: Request Status String
297// DECSCUSR: Set Cursor Style
298const std::string DECRQSS_DECSCUSR = DCS + "$q q" + ST; // NOLINT
299
300// DEC: Digital Equipment Corporation
301enum class DECMode : std::uint16_t {
302 kLineWrap = 7,
303 kCursor = 25,
304
305 kMouseX10 = 9,
306 kMouseVt200 = 1000,
307 kMouseVt200Highlight = 1001,
308
309 kMouseBtnEventMouse = 1002,
310 kMouseAnyEvent = 1003,
311
312 kMouseUtf8 = 1005,
313 kMouseSgrExtMode = 1006,
314 kMouseUrxvtMode = 1015,
315 kMouseSgrPixelsMode = 1016,
316 kAlternateScreen = 1049,
317};
318
319// Device Status Report (DSR) {
320enum class DSRMode : std::uint8_t {
321 kCursor = 6,
322};
323
324std::string Serialize(const std::vector<DECMode>& parameters) {
325 bool first = true;
326 std::string out;
327 for (const DECMode parameter : parameters) {
328 if (!first) {
329 out += ";";
330 }
331 out += std::to_string(int(parameter));
332 first = false;
333 }
334 return out;
335}
336
337// DEC Private Mode Set (DECSET)
338std::string Set(const std::vector<DECMode>& parameters) {
339 return CSI + "?" + Serialize(parameters) + "h";
340}
341
342// DEC Private Mode Reset (DECRST)
343std::string Reset(const std::vector<DECMode>& parameters) {
344 return CSI + "?" + Serialize(parameters) + "l";
345}
346
347// Device Status Report (DSR)
348std::string DeviceStatusReport(DSRMode ps) {
349 return CSI + std::to_string(int(ps)) + "n";
350}
351
352class CapturedMouseImpl : public CapturedMouseInterface {
353 public:
354 explicit CapturedMouseImpl(std::function<void(void)> callback)
355 : callback_(std::move(callback)) {}
356 ~CapturedMouseImpl() override { callback_(); }
357 CapturedMouseImpl(const CapturedMouseImpl&) = delete;
358 CapturedMouseImpl(CapturedMouseImpl&&) = delete;
359 CapturedMouseImpl& operator=(const CapturedMouseImpl&) = delete;
360 CapturedMouseImpl& operator=(CapturedMouseImpl&&) = delete;
361
362 private:
363 std::function<void(void)> callback_;
364};
365
366#if !defined(_WIN32)
367std::atomic<int> g_signal_exit_count = 0; // NOLINT
368std::atomic<int> g_signal_stop_count = 0; // NOLINT
369std::atomic<int> g_signal_resize_count = 0; // NOLINT
370#else
371std::atomic<int> g_signal_exit_count = 0; // NOLINT
372#endif
373
374// Async signal safe function
375void RecordSignal(int signal) {
376 switch (signal) {
377 case SIGABRT:
378 case SIGFPE:
379 case SIGILL:
380 case SIGINT:
381 case SIGSEGV:
382 case SIGTERM:
383 g_signal_exit_count++;
384 break;
385
386#if !defined(_WIN32)
387 case SIGTSTP: // NOLINT
388 g_signal_stop_count++;
389 break;
390
391 case SIGWINCH: // NOLINT
392 g_signal_resize_count++;
393 break;
394#endif
395
396 default:
397 break;
398 }
399}
400
401void ExecuteSignalHandlers() {
402 int signal_exit_count = g_signal_exit_count.exchange(0);
403 while (signal_exit_count--) {
404 App::Private::Signal(*g_active_screen, SIGABRT);
405 }
406
407#if !defined(_WIN32)
408 int signal_stop_count = g_signal_stop_count.exchange(0);
409 while (signal_stop_count--) {
410 App::Private::Signal(*g_active_screen, SIGTSTP);
411 }
412
413 int signal_resize_count = g_signal_resize_count.exchange(0);
414 while (signal_resize_count--) {
415 App::Private::Signal(*g_active_screen, SIGWINCH);
416 }
417#endif
418}
419
420void InstallSignalHandler(int sig) {
421 auto old_signal_handler = std::signal(sig, RecordSignal);
422 on_exit_functions.emplace(
423 [=] { std::ignore = std::signal(sig, old_signal_handler); });
424}
425
426} // namespace
427
428App::Internal::Internal(App* app,
429 AppDimension dimension,
430 bool use_alternative_screen)
431 : public_(app),
432 dimension_(dimension),
433 use_alternative_screen_(use_alternative_screen),
434 terminal_input_parser([&](Event event) {
435 event_buffer.Push(std::move(event));
436 public_->RequestAnimationFrame();
437 }),
438 cursor_position_request(this, [this] {
439 TerminalSend(DeviceStatusReport(DSRMode::kCursor));
440 }) {
441 setup_receiver = event_buffer.CreateReceiver();
442 main_loop_receiver = event_buffer.CreateReceiver();
443}
444
445void App::Internal::ExitNow() {
446 quit_ = true;
447}
448
449void App::Internal::Install() {
450 frame_valid_ = false;
451
452 // Flush the buffer for stdout to ensure whatever the user has printed before
453 // is fully applied before we start modifying the terminal configuration. This
454 // is important, because we are using two different channels (stdout vs
455 // termios/WinAPI) to communicate with the terminal emulator below. See
456 // https://github.com/ArthurSonzogni/FTXUI/issues/846
457 TerminalFlush();
458
459 InstallPipedInputHandling();
460
461 // After uninstalling the new configuration, flush it to the terminal to
462 // ensure it is fully applied:
463 on_exit_functions.emplace([this] { TerminalFlush(); });
464
465 // Install signal handlers to restore the terminal state on exit. The default
466 // signal handlers are restored on exit.
467 for (const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
468 InstallSignalHandler(signal);
469 }
470
471// Save the old terminal configuration and restore it on exit.
472#if defined(_WIN32)
473 // Enable VT processing on stdout and stdin
474 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
475 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
476
477 DWORD out_mode = 0;
478 DWORD in_mode = 0;
479 GetConsoleMode(stdout_handle, &out_mode);
480 GetConsoleMode(stdin_handle, &in_mode);
481 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
482 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
483
484 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
485 const int enable_virtual_terminal_processing = 0x0004;
486 const int disable_newline_auto_return = 0x0008;
487 out_mode |= enable_virtual_terminal_processing;
488 out_mode |= disable_newline_auto_return;
489
490 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
491 const int enable_line_input = 0x0002;
492 const int enable_echo_input = 0x0004;
493 const int enable_virtual_terminal_input = 0x0200;
494 const int enable_window_input = 0x0008;
495 in_mode &= ~enable_echo_input;
496 in_mode &= ~enable_line_input;
497 in_mode |= enable_virtual_terminal_input;
498 in_mode |= enable_window_input;
499
500 SetConsoleMode(stdin_handle, in_mode);
501 SetConsoleMode(stdout_handle, out_mode);
502#else // POSIX (Linux & Mac)
503 for (const int signal : {SIGWINCH, SIGTSTP}) {
504 InstallSignalHandler(signal);
505 }
506
507 struct termios terminal; // NOLINT
508 tcgetattr(tty_fd_, &terminal);
509 on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
510 tcsetattr(tty_fd_, TCSANOW, &terminal);
511 });
512
513 // Enabling raw terminal input mode
514 terminal.c_iflag &= ~IGNBRK; // Disable ignoring break condition
515 terminal.c_iflag &= ~BRKINT; // Disable break causing input and output to be
516 // flushed
517 terminal.c_iflag &= ~PARMRK; // Disable marking parity errors.
518 terminal.c_iflag &= ~ISTRIP; // Disable stripping 8th bit off characters.
519 terminal.c_iflag &= ~INLCR; // Disable mapping NL to CR.
520 terminal.c_iflag &= ~IGNCR; // Disable ignoring CR.
521 terminal.c_iflag &= ~ICRNL; // Disable mapping CR to NL.
522 terminal.c_iflag &= ~IXON; // Disable XON/XOFF flow control on output
523
524 terminal.c_lflag &= ~ECHO; // Disable echoing input characters.
525 terminal.c_lflag &= ~ECHONL; // Disable echoing new line characters.
526 terminal.c_lflag &= ~ICANON; // Disable Canonical mode.
527 terminal.c_lflag &= ~ISIG; // Disable sending signal when hitting:
528 // - => DSUSP
529 // - C-Z => SUSP
530 // - C-C => INTR
531 // - C-d => QUIT
532 terminal.c_lflag &= ~IEXTEN; // Disable extended input processing
533 terminal.c_cflag |= CS8; // 8 bits per byte
534
535 terminal.c_cc[VMIN] = 0; // Minimum number of characters for non-canonical
536 // read.
537 terminal.c_cc[VTIME] = 0; // Timeout in deciseconds for non-canonical read.
538
539 tcsetattr(tty_fd_, TCSANOW, &terminal);
540
541#endif
542
543 auto enable = [&](const std::vector<DECMode>& parameters) {
544 TerminalSend(Set(parameters));
545 on_exit_functions.emplace(
546 [this, parameters] { TerminalSend(Reset(parameters)); });
547 };
548
549 auto disable = [&](const std::vector<DECMode>& parameters) {
550 TerminalSend(Reset(parameters));
551 on_exit_functions.emplace(
552 [this, parameters] { TerminalSend(Set(parameters)); });
553 };
554
555 if (use_alternative_screen_) {
556 enable({
557 DECMode::kAlternateScreen,
558 });
559 }
560
561 disable({
562 DECMode::kLineWrap,
563 });
564
565 if (track_mouse_) {
566 enable({DECMode::kMouseVt200});
567 enable({DECMode::kMouseAnyEvent});
568 enable({DECMode::kMouseUrxvtMode});
569 enable({DECMode::kMouseSgrExtMode});
570 }
571
572 // After installing the new configuration, flush it to the terminal to
573 // ensure it is fully applied:
574 TerminalFlush();
575
576 InstallTerminalInfo();
577
578 quit_ = false;
579
580 PostAnimationTask();
581
582 installed_ = true;
583}
584
585void App::Internal::Uninstall() {
586 installed_ = false;
587
588 // During shutdown, wait for all of the replies.
589 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
590 auto closing_receiver =
591 event_buffer.CreateReceiverAt(main_loop_receiver->index());
592 auto start = std::chrono::steady_clock::now();
593 while (cursor_position_request.HasPending()) {
594 FetchTerminalEvents();
595
596 while (closing_receiver->Has()) {
597 const auto event = closing_receiver->Pop();
598 if (event.is_cursor_position()) {
599 cursor_x_ = event.cursor_x();
600 cursor_y_ = event.cursor_y();
601 cursor_position_request.OnReply();
602 }
603 }
604
605 task_runner.RunUntilIdle();
606
607 if (std::chrono::steady_clock::now() - start >
608 std::chrono::milliseconds(400)) {
609 break;
610 }
611 std::this_thread::sleep_for(std::chrono::milliseconds(10));
612 }
613 }
614
615 OnExit();
616}
617
618void App::Internal::PreMain() {
619 // Suspend previously active screen:
620 if (g_active_screen) {
621 std::swap(suspended_screen_, g_active_screen);
622 // Reset cursor position to the top of the screen and clear the screen.
623 suspended_screen_->internal_->TerminalSend(
624 suspended_screen_->internal_->ResetCursorPosition());
625 suspended_screen_->ResetPosition(
626 suspended_screen_->internal_->output_buffer,
627 /*clear=*/true);
628 suspended_screen_->dimx_ = 0;
629 suspended_screen_->dimy_ = 0;
630
631 // Reset dimensions to force drawing the screen again next time:
632 suspended_screen_->internal_->Uninstall();
633 }
634
635 // This screen is now active:
636 g_active_screen = public_;
637 g_active_screen->internal_->Install();
638
639 previous_animation_time_ = animation::Clock::now();
640}
641
642void App::Internal::PostMain() {
643 // Put cursor position at the end of the drawing.
644 TerminalSend(ResetCursorPosition());
645
646 g_active_screen = nullptr;
647
648 // Restore suspended screen.
649 if (suspended_screen_) {
650 // Clear screen, and put the cursor at the beginning of the drawing.
651 public_->ResetPosition(output_buffer, /*clear=*/true);
652 public_->dimx_ = 0;
653 public_->dimy_ = 0;
654 Uninstall();
655 std::swap(g_active_screen, suspended_screen_);
656 g_active_screen->internal_->Install();
657 } else {
658 Uninstall();
659
660 std::cout << "\r";
661 // On final exit, keep the current drawing and reset cursor position one
662 // line after it.
663 if (!use_alternative_screen_) {
664 std::cout << "\n";
665 }
666 std::cout << std::flush;
667 }
668}
669
670bool App::Internal::HasQuitted() {
671 return quit_;
672}
673
674void App::Internal::RunOnce(const Component& component) {
675 const AutoReset set_component(&component_, component);
676 ExecuteSignalHandlers();
677 FetchTerminalEvents();
678
679 while (!quit_ && main_loop_receiver->Has()) {
680 public_->Post(main_loop_receiver->Pop());
681 }
682
683 // Execute the pending tasks from the queue.
684 const size_t executed_task = task_runner.ExecutedTasks();
685 task_runner.RunUntilIdle();
686 // If no executed task, we can return early without redrawing the screen.
687 if (executed_task == task_runner.ExecutedTasks()) {
688 return;
689 }
690
691 ExecuteSignalHandlers();
692 Draw(component);
693
694 if (selection_data_previous_ != selection_data_) {
695 selection_data_previous_ = selection_data_;
696 if (selection_on_change_) {
697 selection_on_change_();
698 public_->Post(Event::Custom);
699 }
700 }
701}
702
703void App::Internal::RunOnceBlocking(Component component) {
704 // Set FPS to 60 at most.
705 const auto time_per_frame = std::chrono::microseconds(16666); // 1s / 60fps
706
707 auto time = std::chrono::steady_clock::now();
708 const size_t executed_task = task_runner.ExecutedTasks();
709
710 // Wait for at least one task to execute.
711 while (executed_task == task_runner.ExecutedTasks() && !HasQuitted()) {
712 RunOnce(component);
713
714 const auto now = std::chrono::steady_clock::now();
715 const auto delta = now - time;
716 time = now;
717
718 if (delta < time_per_frame) {
719 const auto sleep_duration = time_per_frame - delta;
720 std::this_thread::sleep_for(sleep_duration);
721 }
722 }
723}
724
725void App::Internal::HandleTask(Component component, Task& task) {
726 std::visit(
727 [&](auto&& arg) {
728 using T = std::decay_t<decltype(arg)>;
729 // clang-format off
730
731 // Handle Event.
732 if constexpr (std::is_same_v<T, Event>) {
733
734 if (arg.is_cursor_position()) {
735 cursor_x_ = arg.cursor_x();
736 cursor_y_ = arg.cursor_y();
737 cursor_position_request.OnReply();
738 return;
739 }
740
741 if (arg.is_cursor_shape()) {
742 cursor_reset_shape_ = arg.cursor_shape();
743 return;
744 }
745
746 if (arg.IsTerminalCapabilities()) {
747 terminal_capabilities_ = arg.TerminalCapabilities();
748 return;
749 }
750
751 if (arg.IsTerminalNameVersion()) {
752 terminal_name_ = arg.TerminalName();
753 terminal_version_ = arg.TerminalVersion();
754 return;
755 }
756
757 if (arg.IsTerminalEmulator()) {
758 terminal_emulator_name_ = arg.TerminalEmulatorName();
759 terminal_emulator_version_ = arg.TerminalEmulatorVersion();
760 return;
761 }
762
763 if (arg.is_mouse()) {
764 arg.mouse().x -= cursor_x_;
765 arg.mouse().y -= cursor_y_;
766 }
767
768 arg.screen_ = public_;
769
770 bool handled = component->OnEvent(arg);
771 handled = HandleSelection(handled, arg);
772
773 if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
774 RecordSignal(SIGABRT);
775 }
776
777#if !defined(_WIN32)
778 if (arg == Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
779 RecordSignal(SIGTSTP);
780 }
781#endif
782
783 frame_valid_ = false;
784 return;
785 }
786
787 // Handle callback
788 if constexpr (std::is_same_v<T, Closure>) {
789 arg();
790 return;
791 }
792
793 // Handle Animation
794 if constexpr (std::is_same_v<T, AnimationTask>) {
795 if (!animation_requested_) {
796 return;
797 }
798
799 animation_requested_ = false;
800 const animation::TimePoint now = animation::Clock::now();
801 const animation::Duration delta = now - previous_animation_time_;
802 previous_animation_time_ = now;
803
804 animation::Params params(delta);
805 component->OnAnimation(params);
806 frame_valid_ = false;
807 return;
808 }
809 },
810 task);
811 // clang-format on
812}
813
814bool App::Internal::HandleSelection(bool handled, Event event) {
815 if (handled) {
816 selection_pending_ = nullptr;
817 selection_data_.empty = true;
818 selection_ = nullptr;
819 return true;
820 }
821
822 if (!event.is_mouse()) {
823 return false;
824 }
825
826 auto& mouse = event.mouse();
827 if (mouse.button != Mouse::Left) {
828 return false;
829 }
830
831 if (mouse.motion == Mouse::Pressed) {
832 selection_pending_ = public_->CaptureMouse();
833 selection_data_.start_x = mouse.x;
834 selection_data_.start_y = mouse.y;
835 selection_data_.end_x = mouse.x;
836 selection_data_.end_y = mouse.y;
837 return false;
838 }
839
840 if (!selection_pending_) {
841 return false;
842 }
843
844 if (mouse.motion == Mouse::Moved) {
845 if ((mouse.x != selection_data_.end_x) ||
846 (mouse.y != selection_data_.end_y)) {
847 selection_data_.end_x = mouse.x;
848 selection_data_.end_y = mouse.y;
849 selection_data_.empty = false;
850 }
851
852 return true;
853 }
854
855 if (mouse.motion == Mouse::Released) {
856 selection_pending_ = nullptr;
857 selection_data_.end_x = mouse.x;
858 selection_data_.end_y = mouse.y;
859 selection_data_.empty = false;
860 return true;
861 }
862
863 return false;
864}
865
866void App::Internal::Draw(Component component) {
867 if (frame_valid_) {
868 return;
869 }
870 auto document = component->Render();
871 int dimx = 0;
872 int dimy = 0;
873 auto terminal = Terminal::Size();
874 document->ComputeRequirement();
875 switch (dimension_) {
877 dimx = public_->dimx_;
878 dimy = public_->dimy_;
879 break;
881 dimx = terminal.dimx;
882 dimy = util::clamp(document->requirement().min_y, 0, terminal.dimy);
883 break;
885 dimx = terminal.dimx;
886 dimy = terminal.dimy;
887 break;
889 dimx = util::clamp(document->requirement().min_x, 0, terminal.dimx);
890 dimy = util::clamp(document->requirement().min_y, 0, terminal.dimy);
891 break;
892 }
893
894 // Hide cursor to prevent flickering during reset.
895 TerminalSend("\033[?25l");
896
897 const bool resized =
898 frame_count_ == 0 || (dimx != public_->dimx_) || (dimy != public_->dimy_);
899 TerminalSend(ResetCursorPosition());
900
901 if (frame_count_ != 0) {
902 // Reset the cursor position to the lower left corner to start drawing the
903 // new frame.
904 public_->ResetPosition(output_buffer, resized);
905
906 // If the terminal width decrease, the terminal emulator will start wrapping
907 // lines and make the display dirty. We should clear it completely.
908 if ((dimx < public_->dimx_) && !use_alternative_screen_) {
909 TerminalSend("\033[J"); // clear terminal output
910 TerminalSend("\033[H"); // move cursor to home position
911 }
912 }
913
914 // Resize the screen if needed.
915 if (resized) {
916 public_->dimx_ = dimx;
917 public_->dimy_ = dimy;
918 public_->cells_ =
919 std::vector<Cell>(static_cast<size_t>(dimx) * static_cast<size_t>(dimy));
920 Cursor cursor = public_->cursor_;
921 cursor.x = dimx - 1;
922 cursor.y = dimy - 1;
923 public_->SetCursor(cursor);
924 }
925
926 // Periodically request the terminal emulator the frame position relative to
927 // the screen. This is useful for converting mouse position reported in
928 // screen's coordinates to frame's coordinates.
929 if (!use_alternative_screen_ && is_stdout_a_tty_) {
930 RequestCursorPosition(previous_frame_resized_);
931 }
932 previous_frame_resized_ = resized;
933
934 selection_ = selection_data_.empty
935 ? std::make_unique<Selection>()
936 : std::make_unique<Selection>(
937 selection_data_.start_x, selection_data_.start_y, //
938 selection_data_.end_x, selection_data_.end_y);
939 Render(*public_, document.get(), *selection_);
940
941 // Set cursor position for user using tools to insert CJK characters.
942 {
943 const int dx = public_->dimx_ - 1 - public_->cursor_.x +
944 int(public_->dimx_ != terminal.dimx);
945 const int dy = public_->dimy_ - 1 - public_->cursor_.y;
946
947 set_cursor_position_.clear();
948 reset_cursor_position_.clear();
949
950 if (dy != 0) {
951 set_cursor_position_ += "\x1B[" + std::to_string(dy) + "A";
952 reset_cursor_position_ += "\x1B[" + std::to_string(dy) + "B";
953 }
954
955 if (dx != 0) {
956 set_cursor_position_ += "\x1B[" + std::to_string(dx) + "D";
957 reset_cursor_position_ += "\x1B[" + std::to_string(dx) + "C";
958 }
959
960 if (public_->cursor_.shape != Screen::Cursor::Hidden) {
961 set_cursor_position_ += "\033[?25h";
962 set_cursor_position_ +=
963 "\033[" + std::to_string(int(public_->cursor_.shape)) + " q";
964 }
965 }
966
967 public_->ToString(output_buffer);
968 TerminalSend(set_cursor_position_);
969 TerminalFlush();
970
971 public_->Clear();
972 frame_valid_ = true;
973 frame_count_++;
974}
975
976std::string App::Internal::ResetCursorPosition() {
977 std::string result = std::move(reset_cursor_position_);
978 reset_cursor_position_ = "";
979 return result;
980}
981
982void App::Internal::RequestCursorPosition(bool force) {
983 cursor_position_request.Request(force);
984}
985
986void App::Internal::TerminalSend(std::string_view s) {
987 output_buffer += s;
988}
989
990void App::Internal::TerminalFlush() {
991 // Emscripten doesn't implement flush. We interpret zero as flush.
992 output_buffer += '\0';
993 std::cout << output_buffer << std::flush;
994 output_buffer.clear();
995}
996
997void App::Internal::InstallPipedInputHandling() {
998 is_stdin_a_tty_ = false;
999 is_stdout_a_tty_ = false;
1000#if defined(__EMSCRIPTEN__)
1001 is_stdin_a_tty_ = true;
1002 is_stdout_a_tty_ = true;
1003#elif defined(_WIN32)
1004 is_stdin_a_tty_ = _isatty(_fileno(stdin));
1005 is_stdout_a_tty_ = _isatty(_fileno(stdout));
1006#else
1007 tty_fd_ = STDIN_FILENO;
1008 is_stdout_a_tty_ = isatty(STDOUT_FILENO);
1009 // Handle piped input redirection if explicitly enabled by the application.
1010 // This allows applications to read data from stdin while still receiving
1011 // keyboard input from the terminal for interactive use.
1012 if (!handle_piped_input_) {
1013 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1014 } else if (isatty(STDIN_FILENO)) {
1015 is_stdin_a_tty_ = true;
1016 } else {
1017 // Open /dev/tty for keyboard input.
1018 tty_fd_ = open("/dev/tty", O_RDONLY); // NOLINT
1019 if (tty_fd_ < 0) {
1020 // Failed to open /dev/tty (containers, headless systems, etc.)
1021 tty_fd_ = STDIN_FILENO; // Fallback to stdin.
1022 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1023 } else {
1024 is_stdin_a_tty_ = true;
1025 // Close the /dev/tty file descriptor on exit.
1026 on_exit_functions.emplace([this] {
1027 close(tty_fd_);
1028 tty_fd_ = -1;
1029 });
1030 }
1031 }
1032#endif
1033}
1034
1035void App::Internal::InstallTerminalInfo() {
1036 // Request the terminal to report the current cursor shape. We will restore it
1037 // on exit.
1038 if (is_stdout_a_tty_) {
1039 TerminalSend(DECRQSS_DECSCUSR);
1040 TerminalSend("\033[>q"); // XTVERSION
1041 TerminalSend("\033[>c"); // DA2
1042 TerminalSend("\033[c"); // DA1
1043 TerminalFlush();
1044 }
1045
1046 // Wait for the cursor shape reply using the setup head.
1047 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
1048 auto start = std::chrono::steady_clock::now();
1049 bool terminal_capabilities_received = false;
1050 // Wait for the cursor shape reply using the setup head.
1051 while (true) {
1052 FetchTerminalEvents();
1053 while (setup_receiver->Has()) {
1054 const auto event = setup_receiver->Pop();
1055 if (event.is_cursor_shape()) {
1056 cursor_reset_shape_ = event.cursor_shape();
1057 }
1058
1059 if (event.IsTerminalCapabilities()) {
1060 terminal_capabilities_ = event.TerminalCapabilities();
1061 terminal_capabilities_received = true;
1062 }
1063
1064 if (event.IsTerminalNameVersion()) {
1065 terminal_name_ = event.TerminalName();
1066 terminal_version_ = event.TerminalVersion();
1067 }
1068
1069 if (event.IsTerminalEmulator()) {
1070 terminal_emulator_name_ = event.TerminalEmulatorName();
1071 terminal_emulator_version_ = event.TerminalEmulatorVersion();
1072 }
1073 }
1074
1075 // Response are expected to be received in order, so we can break when
1076 // the last one (XTVERSION) is received. We also set a timeout to prevent
1077 // waiting forever in case the terminal doesn't support these queries.
1078 if (terminal_capabilities_received) {
1079 break;
1080 }
1081
1082 if (std::chrono::steady_clock::now() - start >
1083 std::chrono::milliseconds(500)) {
1084 break;
1085 }
1086 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1087 }
1088 }
1089
1090 // Set quirks and color support based on terminal identification.
1091 Terminal::Quirks quirks = Terminal::GetQuirks();
1092
1093 auto safe_getenv = [](const char* name) -> std::string_view {
1094 const char* value = std::getenv(name);
1095 return value ? value : "";
1096 };
1097
1098 auto color_support = Terminal::ComputeColorSupport(
1099 safe_getenv("TERM"), safe_getenv("COLORTERM"),
1100 safe_getenv("TERM_PROGRAM"), terminal_name_, terminal_emulator_name_,
1101 terminal_capabilities_);
1102
1103 quirks.SetColorSupport(color_support);
1104
1105 const bool is_modern_emulator = (terminal_emulator_name_ != "unknown");
1106 const bool is_vt220_plus =
1107 (terminal_name_ != "vt100" && terminal_name_ != "unknown");
1108 bool reports_utf8 = false;
1109 for (const int x : terminal_capabilities_) {
1110 if (x == 52) {
1111 reports_utf8 = true;
1112 break;
1113 }
1114 }
1115
1116 // Heuristic: If the terminal emulator is modern, or it reports supporting
1117 // UTF-8 or color, we can assume it supports block characters and cursor
1118 // hiding, which are essential for a good experience. This is a heuristic, but
1119 // it allows us to work around some older terminal emulators that don't
1120 // support these features, while still providing a good experience on modern
1121 // terminal emulators that do support these features.
1122 bool modern = is_modern_emulator || is_vt220_plus || reports_utf8;
1123 if (modern) {
1124 quirks.SetBlockCharacters(true);
1125 quirks.SetCursorHiding(true);
1126 quirks.SetComponentAscii(false);
1127 }
1128
1129 Terminal::SetQuirks(quirks);
1130
1131 on_exit_functions.emplace([this] {
1132 TerminalSend("\033[?25h"); // Enable cursor.
1133 if (is_stdout_a_tty_) {
1134 TerminalSend("\033[" + std::to_string(cursor_reset_shape_) + " q");
1135 }
1136 });
1137}
1138
1139void App::Internal::Signal(int signal) {
1140 if (signal == SIGABRT) {
1141 public_->Exit();
1142 return;
1143 }
1144
1145// Windows do no support SIGTSTP / SIGWINCH
1146#if !defined(_WIN32)
1147 if (signal == SIGTSTP) {
1148 public_->Post([&] {
1149 TerminalSend(ResetCursorPosition());
1150 public_->ResetPosition(output_buffer, /*clear*/ true);
1151 Uninstall();
1152 public_->dimx_ = 0;
1153 public_->dimy_ = 0;
1154 (void)std::raise(SIGTSTP);
1155 Install();
1156 });
1157 return;
1158 }
1159
1160 if (signal == SIGWINCH) {
1161 public_->Post(Event::Special({0}));
1162 return;
1163 }
1164#endif
1165}
1166
1167size_t App::Internal::FetchTerminalEvents() {
1168#if defined(_WIN32)
1169 auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
1170 // Check if there is input in the console.
1171 auto console = GetStdHandle(STD_INPUT_HANDLE);
1172 DWORD number_of_events = 0;
1173 if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
1174 return std::vector<INPUT_RECORD>();
1175 }
1176 if (number_of_events <= 0) {
1177 // No input, return.
1178 return std::vector<INPUT_RECORD>();
1179 }
1180 // Read the input events.
1181 std::vector<INPUT_RECORD> records(number_of_events);
1182 DWORD number_of_events_read = 0;
1183 if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
1184 &number_of_events_read)) {
1185 return std::vector<INPUT_RECORD>();
1186 }
1187 records.resize(number_of_events_read);
1188 return records;
1189 };
1190
1191 auto records = get_input_records();
1192 if (records.size() == 0) {
1193 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1194 const size_t timeout_microseconds =
1195 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1196 terminal_input_parser.Timeout(timeout_microseconds);
1197 return 0;
1198 }
1199 last_char_time = std::chrono::steady_clock::now();
1200
1201 // Convert the input events to FTXUI events.
1202 // For each event, we call the terminal input parser to convert it to
1203 // Event.
1204 std::wstring wstring;
1205 for (const auto& r : records) {
1206 switch (r.EventType) {
1207 case KEY_EVENT: {
1208 auto key_event = r.Event.KeyEvent;
1209 // ignore UP key events
1210 if (key_event.bKeyDown == FALSE) {
1211 continue;
1212 }
1213 const wchar_t wc = key_event.uChar.UnicodeChar;
1214 wstring += wc;
1215 if (wc >= 0xd800 && wc <= 0xdbff) {
1216 // Wait for the Low Surrogate to arrive in the next record.
1217 continue;
1218 }
1219 for (auto it : to_string(wstring)) {
1220 terminal_input_parser.Add(it);
1221 }
1222 wstring.clear();
1223 } break;
1224 case WINDOW_BUFFER_SIZE_EVENT:
1225 public_->Post(Event::Special({0}));
1226 break;
1227 case MENU_EVENT:
1228 case FOCUS_EVENT:
1229 case MOUSE_EVENT:
1230 // TODO(mauve): Implement later.
1231 break;
1232 }
1233 }
1234 return records.size();
1235#elif defined(__EMSCRIPTEN__)
1236 // Read chars from the terminal.
1237 // We configured it to be non blocking.
1238 std::array<char, 128> out{};
1239 const ssize_t l = read(STDIN_FILENO, out.data(), out.size());
1240 if (l <= 0) {
1241 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1242 const size_t timeout_microseconds =
1243 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1244 terminal_input_parser.Timeout(timeout_microseconds);
1245 return 0;
1246 }
1247 last_char_time = std::chrono::steady_clock::now();
1248
1249 // Convert the chars to events.
1250 for (ssize_t i = 0; i < l; ++i) {
1251 terminal_input_parser.Add(out.at(static_cast<size_t>(i)));
1252 }
1253 return (size_t)l;
1254#else // POSIX (Linux & Mac)
1255 struct pollfd pfd = {tty_fd_, POLLIN, 0};
1256 const int poll_result = poll(&pfd, 1, 0);
1257 if (poll_result <= 0) {
1258 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1259 const size_t timeout_ms =
1260 std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
1261 terminal_input_parser.Timeout(static_cast<int>(timeout_ms));
1262 return 0;
1263 }
1264 last_char_time = std::chrono::steady_clock::now();
1265
1266 // Read chars from the terminal.
1267 std::array<char, 128> out{};
1268 const ssize_t l = read(tty_fd_, out.data(), out.size());
1269 if (l <= 0) {
1270 return 0;
1271 }
1272
1273 // Convert the chars to events.
1274 for (ssize_t i = 0; i < l; ++i) {
1275 terminal_input_parser.Add(out.at(static_cast<size_t>(i)));
1276 }
1277 return (size_t)l;
1278#endif
1279}
1280
1281void App::Internal::PostAnimationTask() {
1282 public_->Post(AnimationTask());
1283
1284 // Repeat the animation task every 15ms. This correspond to a frame rate
1285 // of around 66fps.
1286 task_runner.PostDelayedTask([this] { PostAnimationTask(); },
1287 std::chrono::milliseconds(15));
1288}
1289
1290App::App(std::unique_ptr<Internal> internal, int dimx, int dimy)
1291 : Screen(dimx, dimy), internal_(std::move(internal)) {
1292 internal_->public_ = this;
1293}
1294
1295App::App(App&& other) noexcept : Screen(std::move(other)) {
1296 internal_ = std::move(other.internal_);
1297 if (internal_) {
1298 internal_->public_ = this;
1299 }
1300}
1301
1302App& App::operator=(App&& other) noexcept {
1303 Screen::operator=(std::move(other));
1304 internal_ = std::move(other.internal_);
1305 if (internal_) {
1306 internal_->public_ = this;
1307 }
1308 return *this;
1309}
1310
1311App::~App() = default;
1312
1313// static
1314App App::FixedSize(int dimx, int dimy) {
1315 auto internal =
1316 std::make_unique<Internal>(nullptr, AppDimension::Fixed, false);
1317 return App(std::move(internal), dimx, dimy);
1318}
1319
1320// static
1321App App::Fullscreen() {
1322 return FullscreenAlternateScreen();
1323}
1324
1325// static
1326App App::FullscreenPrimaryScreen() {
1327 auto terminal = Terminal::Size();
1328 auto internal =
1329 std::make_unique<Internal>(nullptr, AppDimension::Fullscreen, false);
1330 return App(std::move(internal), terminal.dimx, terminal.dimy);
1331}
1332
1333// static
1334App App::FullscreenAlternateScreen() {
1335 auto terminal = Terminal::Size();
1336 auto internal =
1337 std::make_unique<Internal>(nullptr, AppDimension::Fullscreen, true);
1338 return App(std::move(internal), terminal.dimx, terminal.dimy);
1339}
1340
1341// static
1342App App::FitComponent() {
1343 auto terminal = Terminal::Size();
1344 auto internal =
1345 std::make_unique<Internal>(nullptr, AppDimension::FitComponent, false);
1346 return App(std::move(internal), terminal.dimx, terminal.dimy);
1347}
1348
1349// static
1350App App::TerminalOutput() {
1351 auto terminal = Terminal::Size();
1352 auto internal =
1353 std::make_unique<Internal>(nullptr, AppDimension::TerminalOutput, false);
1354 return App(std::move(internal), terminal.dimx, terminal.dimy);
1355}
1356
1357void App::TrackMouse(bool enable) {
1358 internal_->track_mouse_ = enable;
1359}
1360
1361void App::HandlePipedInput(bool enable) {
1362 internal_->handle_piped_input_ = enable;
1363}
1364
1365// static
1366App* App::Active() {
1367 return g_active_screen;
1368}
1369
1370void App::Loop(Component component) {
1371 class Loop loop(this, std::move(component));
1372 loop.Run();
1373}
1374
1375void App::Exit() {
1376 Post([this] { internal_->ExitNow(); });
1377}
1378
1379Closure App::ExitLoopClosure() {
1380 return [this] { Exit(); };
1381}
1382
1383void App::Post(Task task) {
1384 internal_->task_runner.PostTask([this, task = std::move(task)]() mutable {
1385 if (internal_->component_) {
1386 internal_->HandleTask(internal_->component_, task);
1387 return;
1388 }
1389
1390 // If there is no component, we can still execute closures.
1391 if (std::holds_alternative<Closure>(task)) {
1392 std::get<Closure>(task)();
1393 }
1394 });
1395}
1396
1397void App::PostEvent(Event event) {
1398 internal_->event_buffer.Push(std::move(event));
1399 RequestAnimationFrame();
1400}
1401
1402// static
1403void App::PostEventOrExecute(Closure closure) {
1404 if (!closure) {
1405 return;
1406 }
1407 if (auto* app = App::Active()) {
1408 app->Post(std::move(closure));
1409 } else {
1410 closure();
1411 }
1412}
1413
1414void App::RequestAnimationFrame() {
1415 if (internal_->animation_requested_) {
1416 return;
1417 }
1418 internal_->animation_requested_ = true;
1419 auto now = animation::Clock::now();
1420 const auto time_histeresis = std::chrono::milliseconds(33);
1421 if (now - internal_->previous_animation_time_ >= time_histeresis) {
1422 internal_->previous_animation_time_ = now;
1423 }
1424}
1425
1426CapturedMouse App::CaptureMouse() {
1427 if (internal_->mouse_captured) {
1428 return nullptr;
1429 }
1430 internal_->mouse_captured = true;
1431 return std::make_unique<CapturedMouseImpl>(
1432 [this] { internal_->mouse_captured = false; });
1433}
1434
1435Closure App::WithRestoredIO(Closure fn) {
1436 return [this, fn] {
1437 internal_->Uninstall();
1438 fn();
1439 internal_->Install();
1440 };
1441}
1442
1443void App::ForceHandleCtrlC(bool force) {
1444 internal_->force_handle_ctrl_c_ = force;
1445}
1446
1447void App::ForceHandleCtrlZ(bool force) {
1448 internal_->force_handle_ctrl_z_ = force;
1449}
1450
1451std::string App::GetSelection() {
1452 if (!internal_->selection_) {
1453 return "";
1454 }
1455 return internal_->selection_->GetParts();
1456}
1457
1458void App::SelectionChange(std::function<void()> callback) {
1459 internal_->selection_on_change_ = std::move(callback);
1460}
1461
1462const std::string& App::TerminalName() const {
1463 return internal_->terminal_name_;
1464}
1465
1466int App::TerminalVersion() const {
1467 return internal_->terminal_version_;
1468}
1469
1470const std::string& App::TerminalEmulatorName() const {
1471 return internal_->terminal_emulator_name_;
1472}
1473
1474const std::string& App::TerminalEmulatorVersion() const {
1475 return internal_->terminal_emulator_version_;
1476}
1477
1478const std::vector<int>& App::TerminalCapabilities() const {
1479 return internal_->terminal_capabilities_;
1480}
1481
1482std::vector<std::string> App::TerminalCapabilityNames() const {
1483 return Event::TerminalCapabilities("", internal_->terminal_capabilities_)
1484 .TerminalCapabilityNames();
1485}
1486
1487// Loop calls these:
1488
1489void App::ExitNow() {
1490 internal_->ExitNow();
1491}
1492void App::Install() {
1493 internal_->Install();
1494}
1495void App::Uninstall() {
1496 internal_->Uninstall();
1497}
1498void App::PreMain() {
1499 internal_->PreMain();
1500}
1501void App::PostMain() {
1502 internal_->PostMain();
1503}
1504bool App::HasQuitted() {
1505 return internal_->HasQuitted();
1506}
1507void App::RunOnce(const Component& component) {
1508 internal_->RunOnce(component);
1509}
1510void App::RunOnceBlocking(Component component) {
1511 internal_->RunOnceBlocking(component);
1512}
1513void App::HandleTask(Component component, Task& task) {
1514 internal_->HandleTask(component, task);
1515}
1516bool App::HandleSelection(bool handled, Event event) {
1517 return internal_->HandleSelection(handled, event);
1518}
1519void App::Draw(Component component) {
1520 internal_->Draw(component);
1521}
1522std::string App::ResetCursorPosition() {
1523 return internal_->ResetCursorPosition();
1524}
1525void App::RequestCursorPosition(bool force) {
1526 internal_->RequestCursorPosition(force);
1527}
1528void App::TerminalSend(std::string_view s) {
1529 internal_->TerminalSend(s);
1530}
1531void App::TerminalFlush() {
1532 internal_->TerminalFlush();
1533}
1534void App::InstallPipedInputHandling() {
1535 internal_->InstallPipedInputHandling();
1536}
1537void App::InstallTerminalInfo() {
1538 internal_->InstallTerminalInfo();
1539}
1540void App::Signal(int signal) {
1541 internal_->Signal(signal);
1542}
1543size_t App::FetchTerminalEvents() {
1544 return internal_->FetchTerminalEvents();
1545}
1546void App::PostAnimationTask() {
1547 internal_->PostAnimationTask();
1548}
1549
1550Loop::Loop(App* screen, Component component)
1551 : screen_(screen), component_(std::move(component)) {
1552 screen_->PreMain();
1553}
1554
1555Loop::~Loop() {
1556 screen_->PostMain();
1557}
1558
1559bool Loop::HasQuitted() {
1560 return screen_->HasQuitted();
1561}
1562
1563void Loop::RunOnce() {
1564 screen_->RunOnce(component_);
1565}
1566
1567void Loop::RunOnceBlocking() {
1568 screen_->RunOnceBlocking(component_);
1569}
1570
1571void Loop::Run() {
1572 while (!HasQuitted()) {
1573 RunOnceBlocking();
1574 }
1575}
1576
1577} // namespace ftxui
auto PostDelayedTask(Task task, std::chrono::steady_clock::duration duration) -> void
Schedules a task to be executed after a certain duration.
Quirks GetQuirks()
Get the terminal quirks.
Definition terminal.cpp:317
Dimensions Size()
Get the terminal size.
Definition terminal.cpp:264
void SetQuirks(const Quirks &quirks)
Override terminal quirks.
Definition terminal.cpp:327
The FTXUI ftxui::animation:: namespace.
void SetFallbackSize(const Dimensions &fallbackSize)
Override terminal size in case auto-detection fails.
Definition terminal.cpp:294
Color ComputeColorSupport(std::string_view term, std::string_view colorterm, std::string_view term_program, std::string_view terminal_name, std::string_view terminal_emulator_name, const std::vector< int > &capabilities)
Compute the color support based on environment variables and terminal identification.
Definition terminal.cpp:191
std::chrono::duration< float > Duration
Definition animation.hpp:31
std::chrono::time_point< Clock > TimePoint
Definition animation.hpp:30
void RequestAnimationFrame()
Definition app.cpp:73
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:11
The FTXUI ftxui:: namespace.
Definition animation.hpp:11
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::string to_string(std::wstring_view s)
Convert a std::wstring into a UTF8 std::string.
Definition string.cpp:1594
AppDimension
Definition app.cpp:65
std::variant< Event, Closure, AnimationTask > Task
Definition task.hpp:14
void Render(Screen &screen, Node *node, Selection &selection)
Definition node.cpp:105
int value
Definition elements.hpp:178
std::function< void()> Closure
Definition task.hpp:13
std::shared_ptr< ComponentBase > Component
Definition app.hpp:23