FTXUI 7.0.3
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 <map>
18#include <memory>
19#include <stack> // for stack
20#include <string>
21#include <string_view>
22#include <thread> // for thread, sleep_for
23#include <tuple> // for _Swallow_assign, ignore
24#include <type_traits>
25#include <utility> // for move, swap
26#include <variant> // for visit, variant
27#include <vector> // for vector
28
29#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
30#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
31#include "ftxui/component/component_base.hpp" // for ComponentBase
32#include "ftxui/component/event.hpp" // for Event
33#include "ftxui/component/loop.hpp" // for Loop
36#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
37#include "ftxui/dom/node.hpp" // for Node, Render
38#include "ftxui/screen/cell.hpp" // for Cell
39#include "ftxui/screen/terminal.hpp" // for Dimensions, Size
40#include "ftxui/screen/util.hpp" // for util::clamp
41#include "ftxui/util/autoreset.hpp" // for AutoReset
42
43#if defined(_WIN32)
44#define DEFINE_CONSOLEV2_PROPERTIES
45#define WIN32_LEAN_AND_MEAN
46#ifndef NOMINMAX
47#define NOMINMAX
48#endif
49#include <io.h>
50#include <windows.h>
51#else
52#include <fcntl.h>
53#include <poll.h>
54#include <sys/poll.h>
55#include <sys/types.h>
56#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
57#include <unistd.h> // for STDIN_FILENO, STDOUT_FILENO, read
58#endif
59
60#if defined(__EMSCRIPTEN__)
61#include <emscripten.h>
62#endif
63
64namespace ftxui {
65
66enum class AppDimension {
68 Fixed,
71};
72
73namespace animation {
75 auto* screen = App::Active();
76 if (screen) {
77 screen->RequestAnimationFrame();
78 }
79}
80} // namespace animation
81
82#if defined(__EMSCRIPTEN__)
83extern "C" {
84EMSCRIPTEN_KEEPALIVE
85void ftxui_on_resize(int columns, int rows) {
87 columns,
88 rows,
89 });
90 std::raise(SIGWINCH);
91}
92}
93#endif
94
95struct App::Internal {
96 App* public_;
97
98 App* suspended_screen_ = nullptr;
99 const AppDimension dimension_;
100 const bool use_alternative_screen_;
101
102 bool track_mouse_ = true;
103
104 std::string set_cursor_position_;
105 std::string reset_cursor_position_;
106
107 std::atomic<bool> quit_{false};
108 bool installed_ = false;
109 bool animation_requested_ = false;
110 animation::TimePoint previous_animation_time_;
111
112 int cursor_x_ = 1;
113 int cursor_y_ = 1;
114
115 std::uint64_t frame_count_ = 0;
116 bool mouse_captured = false;
117 bool previous_frame_resized_ = false;
118
119 bool frame_valid_ = false;
120
121 bool force_handle_ctrl_c_ = true;
122 bool force_handle_ctrl_z_ = true;
123
124 int cursor_reset_shape_ = 1;
125
126 // 管道輸入處理狀態(僅限 POSIX)
127 bool handle_piped_input_ = true;
128 bool is_stdin_a_tty_ = false;
129 bool is_stdout_a_tty_ = false;
130 // /dev/tty 的檔案描述符,用於管道輸入處理。
131 int tty_fd_ = -1;
132
133 std::string terminal_name_ = "unknown";
134 int terminal_version_ = 0;
135
136 std::string terminal_emulator_name_ = "unknown";
137 std::string terminal_emulator_version_ = "unknown";
138
139 std::vector<int> terminal_capabilities_;
140
141 // 選取 API:
142 CapturedMouse selection_pending_;
143 struct SelectionData {
144 int start_x = -1;
145 int start_y = -1;
146 int end_x = -2;
147 int end_y = -2;
148 bool empty = true;
149 bool operator==(const SelectionData& other) const {
150 if (empty && other.empty) {
151 return true;
152 }
153 if (empty || other.empty) {
154 return false;
155 }
156 return start_x == other.start_x && start_y == other.start_y &&
157 end_x == other.end_x && end_y == other.end_y;
158 }
159 bool operator!=(const SelectionData& other) const {
160 return !(*this == other);
161 }
162 };
163 SelectionData selection_data_;
164 SelectionData selection_data_previous_;
165 std::unique_ptr<Selection> selection_;
166 std::function<void()> selection_on_change_;
167
168 Component component_;
169
170 // 已存在於 Internal 中:
171 TerminalInputParser terminal_input_parser;
172 task::TaskRunner task_runner;
173 std::chrono::time_point<std::chrono::steady_clock> last_char_time =
174 std::chrono::steady_clock::now();
175 std::string output_buffer;
176
177 class ThrottledRequest {
178 public:
179 ThrottledRequest(App::Internal* internal, std::function<void()> send)
180 : internal_(internal), send_(std::move(send)) {}
181
182 void Request(bool force = false) {
183 if (!internal_->is_stdin_a_tty_) {
184 return;
185 }
186
187 if (force) {
188 Send();
189 return;
190 }
191
192 // 一次只允許一個待處理的請求。這是為了避免以
193 // 請求淹沒終端機。
194 if (HasPending()) {
195 return;
196 }
197
198 const auto now = std::chrono::steady_clock::now();
199 if (now - last_request_time_ < std::chrono::milliseconds(500)) {
200 // 距離上一次請求時間太短。跳過此次:請求必須在 Draw() 中,
201 // 緊接在游標移到畫格原點之後同步發送,
202 // 這樣終端機的回應才能反映該
203 // 位置。Draw() 會在下一個畫格再次呼叫 Request(),
204 // 因此請求不會遺失,只是被延遲。
205 return;
206 }
207
208 Send();
209 }
210
211 void OnReply() { pending_request_ = false; }
212
213 bool HasPending() const {
214 if (!pending_request_) {
215 return false;
216 }
217 const auto now = std::chrono::steady_clock::now();
218 return now - last_sent_time_ < std::chrono::seconds(5);
219 }
220
221 private:
222 void Send() {
223 last_sent_time_ = std::chrono::steady_clock::now();
224 last_request_time_ = last_sent_time_;
225 pending_request_ = true;
226 send_();
227 }
228
229 App::Internal* internal_;
230 std::function<void()> send_;
231 bool pending_request_ = false;
232 std::chrono::steady_clock::time_point last_request_time_ =
233 std::chrono::steady_clock::now() - std::chrono::hours(1);
234 std::chrono::steady_clock::time_point last_sent_time_ =
235 std::chrono::steady_clock::now() - std::chrono::hours(1);
236 };
237
238 ThrottledRequest cursor_position_request;
239
240 MultiReceiverBuffer<Event> event_buffer;
241 std::unique_ptr<MultiReceiverBuffer<Event>::Receiver> main_loop_receiver;
242
243 Internal(App* app, AppDimension dimension, bool use_alternative_screen);
244
245 void ExitNow();
246 void Install();
247 void Uninstall();
248 void PreMain();
249 void PostMain();
250 bool HasQuitted();
251 void RunOnce(const Component& component);
252 void RunOnceBlocking(Component component);
253 void HandleTask(Component component, Task& task);
254 bool HandleSelection(bool handled, Event event);
255 void Draw(Component component);
256 std::string ResetCursorPosition();
257 void RequestCursorPosition(bool force = false);
258 void TerminalSend(std::string_view);
259 void TerminalFlush();
260 void InstallPipedInputHandling();
261 void InstallTerminalInfo();
262 void Signal(int signal);
263 size_t FetchTerminalEvents();
264 void PostAnimationTask();
265};
266
267namespace {
268
269App* g_active_screen = nullptr; // NOLINT
270
271std::stack<Closure> on_exit_functions; // NOLINT
272
273void OnExit() {
274 while (!on_exit_functions.empty()) {
275 on_exit_functions.top()();
276 on_exit_functions.pop();
277 }
278}
279
280// CSI:控制序列引導符(Control Sequence Introducer)
281const std::string CSI = "\x1b["; // NOLINT
282 //
283// DCS: Device Control String
284const std::string DCS = "\x1bP"; // NOLINT
285
286// ST:字串終結符(String Terminator)
287const std::string ST = "\x1b\\"; // NOLINT
288
289// DECRQSS:請求狀態字串
290// DECSCUSR:設定游標樣式
291const std::string DECRQSS_DECSCUSR = DCS + "$q q" + ST; // NOLINT
292
293// DEC:Digital Equipment Corporation
294enum class DECMode : std::uint16_t {
295 kLineWrap = 7,
296 kCursor = 25,
297
298 kMouseX10 = 9,
299 kMouseVt200 = 1000,
300 kMouseVt200Highlight = 1001,
301
302 kMouseBtnEventMouse = 1002,
303 kMouseAnyEvent = 1003,
304
305 kMouseUtf8 = 1005,
306 kMouseSgrExtMode = 1006,
307 kMouseUrxvtMode = 1015,
308 kMouseSgrPixelsMode = 1016,
309 kAlternateScreen = 1049,
310};
311
312// 裝置狀態回報 (DSR) {
313enum class DSRMode : std::uint8_t {
314 kCursor = 6,
315};
316
317std::string Serialize(const std::vector<DECMode>& parameters) {
318 bool first = true;
319 std::string out;
320 for (const DECMode parameter : parameters) {
321 if (!first) {
322 out += ";";
323 }
324 out += std::to_string(int(parameter));
325 first = false;
326 }
327 return out;
328}
329
330// DEC 私有模式設定 (DECSET)
331std::string Set(const std::vector<DECMode>& parameters) {
332 return CSI + "?" + Serialize(parameters) + "h";
333}
334
335// DEC 私有模式重設 (DECRST)
336std::string Reset(const std::vector<DECMode>& parameters) {
337 return CSI + "?" + Serialize(parameters) + "l";
338}
339
340// 裝置狀態回報 (DSR)
341std::string DeviceStatusReport(DSRMode ps) {
342 return CSI + std::to_string(int(ps)) + "n";
343}
344
345class CapturedMouseImpl : public CapturedMouseInterface {
346 public:
347 explicit CapturedMouseImpl(std::function<void(void)> callback)
348 : callback_(std::move(callback)) {}
349 ~CapturedMouseImpl() override { callback_(); }
350 CapturedMouseImpl(const CapturedMouseImpl&) = delete;
351 CapturedMouseImpl(CapturedMouseImpl&&) = delete;
352 CapturedMouseImpl& operator=(const CapturedMouseImpl&) = delete;
353 CapturedMouseImpl& operator=(CapturedMouseImpl&&) = delete;
354
355 private:
356 std::function<void(void)> callback_;
357};
358
359#if !defined(_WIN32)
360std::atomic<int> g_signal_exit_count = 0; // NOLINT
361std::atomic<int> g_signal_stop_count = 0; // NOLINT
362std::atomic<int> g_signal_resize_count = 0; // NOLINT
363#else
364std::atomic<int> g_signal_exit_count = 0; // NOLINT
365#endif
366
367// 追蹤終端機目前是否設定為原始模式(raw mode)。
368// 用於防止在緊急退出和正常退出時進行重複還原。
369std::atomic<bool> g_terminal_is_raw{false};
370
371// 儲存最後收到的延遲信號(例如 SIGINT、SIGTERM),
372// 以便在卸載/退出時重新引發。
373std::atomic<int> g_last_signal{0}; // NOLINT
374
375#if defined(_WIN32)
376using SignalHandler = void (*)(int);
377// 儲存 FTXUI 安裝自己的處理程式之前的原始信號處理程式。
378std::map<int, SignalHandler> g_old_signal_handlers;
379
380// 儲存原始的主控台模式,以便在退出時還原它們。
381DWORD g_original_stdout_mode = 0;
382DWORD g_original_stdin_mode = 0;
383bool g_has_original_console_mode = false;
384#else
385// 儲存 FTXUI 安裝自己的處理程式之前的原始 sigaction 結構。
386std::map<int, struct sigaction> g_old_sigactions;
387
388// 儲存原始的 termios 終端機設定,以便在退出時還原它們。
389struct termios g_original_termios;
390bool g_has_original_termios = false;
391int g_tty_fd = -1;
392#endif
393
394// 還原給定信號的原始信號處理程式並重新引發它。
395// 這是一個 async-signal-safe 函式。
396void RestoreSignalHandlerAndRaise(int signal) {
397#if defined(_WIN32)
398 auto it = g_old_signal_handlers.find(signal);
399 auto old_handler = (it != g_old_signal_handlers.end()) ? it->second : SIG_DFL;
400 std::signal(signal, old_handler);
401#else
402 auto it = g_old_sigactions.find(signal);
403 if (it != g_old_sigactions.end()) {
404 sigaction(signal, &it->second, nullptr);
405 } else {
406 struct sigaction sa;
407 sa.sa_handler = SIG_DFL;
408 sigemptyset(&sa.sa_mask);
409 sa.sa_flags = 0;
410 sigaction(signal, &sa, nullptr);
411 }
412#endif
413 std::raise(signal);
414}
415
416// 緊急終端機狀態還原。
417// 這是一個 async-signal-safe 函式。
418void RestoreTerminalEmergency() {
419 if (!g_terminal_is_raw.exchange(false)) {
420 return;
421 }
422#if defined(_WIN32)
423 if (g_has_original_console_mode) {
424 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
425 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
426 SetConsoleMode(stdout_handle, g_original_stdout_mode);
427 SetConsoleMode(stdin_handle, g_original_stdin_mode);
428 }
429#else
430 if (g_has_original_termios && g_tty_fd >= 0) {
431 const char restore_seq[] =
432 "\x1b[?25h" // 顯示游標。
433 "\x1b[?1049l" // 切換到一般畫面緩衝區。
434 "\x1b[?1000l" // 停用一般滑鼠追蹤。
435 "\x1b[?1002l" // 停用按鈕事件滑鼠追蹤。
436 "\x1b[?1003l" // 停用所有動作滑鼠追蹤。
437 "\x1b[?1006l" // 停用 SGR 滑鼠追蹤。
438 "\x1b[?1015l" // 停用 Urxvt 滑鼠追蹤。
439 "\x1b[?7h"; // 啟用換行。
440 std::ignore = write(STDOUT_FILENO, restore_seq, sizeof(restore_seq) - 1);
441 tcsetattr(g_tty_fd, TCSANOW, &g_original_termios);
442 }
443#endif
444}
445
446// Async signal safe 函式
447void RecordSignal(int signal) {
448 switch (signal) {
449 // 異常終止(例如 abort() 或斷言失敗)。
450 case SIGABRT:
451 // 錯誤的算術運算(例如除以零)。
452 case SIGFPE:
453 // 非法指令。
454 case SIGILL:
455 // 無效的記憶體參照(區段錯誤)。
456 case SIGSEGV:
457#if !defined(_WIN32)
458 // 匯流排錯誤(例如錯誤的記憶體存取對齊)。
459 case SIGBUS:
460 // 錯誤的系統呼叫。
461 case SIGSYS:
462#endif
463 {
464 RestoreTerminalEmergency();
465 RestoreSignalHandlerAndRaise(signal);
466 break;
467 }
468
469 // 終端機中斷(例如 Ctrl-C)。
470 case SIGINT:
471 // 終止要求。
472 case SIGTERM:
473#if !defined(_WIN32)
474 // 終端機結束(例如 Ctrl-\,會產生核心傾印)。
475 case SIGQUIT:
476 // 在控制終端機上偵測到掛斷,或控制程序已終止。
477 case SIGHUP:
478#endif
479 g_last_signal.store(signal);
480 g_signal_exit_count++;
481 break;
482
483#if !defined(_WIN32)
484 // 終端機停止信號(例如 Ctrl-Z)。
485 case SIGTSTP: // NOLINT
486 g_signal_stop_count++;
487 break;
488
489 // 終端機視窗大小變更。
490 case SIGWINCH: // NOLINT
491 g_signal_resize_count++;
492 break;
493#endif
494
495 default:
496 break;
497 }
498}
499
500void ExecuteSignalHandlers() {
501 if (g_last_signal.load() != 0) {
502 App::Private::Signal(*g_active_screen, SIGABRT);
503 }
504
505 int signal_exit_count = g_signal_exit_count.exchange(0);
506 while (signal_exit_count--) {
507 App::Private::Signal(*g_active_screen, SIGABRT);
508 }
509
510#if !defined(_WIN32)
511 int signal_stop_count = g_signal_stop_count.exchange(0);
512 while (signal_stop_count--) {
513 App::Private::Signal(*g_active_screen, SIGTSTP);
514 }
515
516 int signal_resize_count = g_signal_resize_count.exchange(0);
517 while (signal_resize_count--) {
518 App::Private::Signal(*g_active_screen, SIGWINCH);
519 }
520#endif
521}
522
523void InstallSignalHandler(int sig) {
524#if defined(_WIN32)
525 auto old_signal_handler = std::signal(sig, RecordSignal);
526 g_old_signal_handlers[sig] = old_signal_handler;
527 on_exit_functions.emplace(
528 [=] { std::ignore = std::signal(sig, old_signal_handler); });
529#else
530 struct sigaction sa;
531 sa.sa_handler = RecordSignal;
532 sigemptyset(&sa.sa_mask);
533 sa.sa_flags = SA_RESTART;
534 struct sigaction old_sa;
535 sigaction(sig, &sa, &old_sa);
536 g_old_sigactions[sig] = old_sa;
537 on_exit_functions.emplace([=] { sigaction(sig, &old_sa, nullptr); });
538#endif
539}
540
541} // namespace
542
543App::Internal::Internal(App* app,
544 AppDimension dimension,
545 bool use_alternative_screen)
546 : public_(app),
547 dimension_(dimension),
548 use_alternative_screen_(use_alternative_screen),
549 terminal_input_parser([&](Event event) {
550 event_buffer.Push(std::move(event));
551 }),
552 cursor_position_request(this, [this] {
553 TerminalSend(DeviceStatusReport(DSRMode::kCursor));
554 }) {
555 main_loop_receiver = event_buffer.CreateReceiver();
556}
557
558void App::Internal::ExitNow() {
559 quit_ = true;
560}
561
562void App::Internal::Install() {
563 frame_valid_ = false;
564
565 // 清空 stdout 的緩衝區,以確保使用者先前列印的內容
566 // 在我們開始修改終端機設定之前已完全套用。這一點
567 // 很重要,因為我們使用兩個不同的通道(stdout 與
568 // termios/WinAPI)與下方的終端機模擬器溝通。詳見
569 // https://github.com/ArthurSonzogni/FTXUI/issues/846
570 TerminalFlush();
571
572 InstallPipedInputHandling();
573
574 // 在卸載新的設定之後,將其清空到終端機以
575 // 確保它已完全套用:
576 on_exit_functions.emplace([this] { TerminalFlush(); });
577
578 // 安裝信號處理程式,以便在退出時還原終端機狀態。預設的
579 // 信號處理程式會在退出時被還原。
580 for (const int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
581 InstallSignalHandler(signal);
582 }
583
584// 儲存舊的終端機設定,並在退出時還原它。
585#if defined(_WIN32)
586 // 在 stdout 和 stdin 上啟用 VT 處理
587 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
588 auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
589
590 DWORD out_mode = 0;
591 DWORD in_mode = 0;
592 GetConsoleMode(stdout_handle, &out_mode);
593 GetConsoleMode(stdin_handle, &in_mode);
594 g_original_stdout_mode = out_mode;
595 g_original_stdin_mode = in_mode;
596 g_has_original_console_mode = true;
597 on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
598 on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
599
600 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
601 const int enable_virtual_terminal_processing = 0x0004;
602 const int disable_newline_auto_return = 0x0008;
603 out_mode |= enable_virtual_terminal_processing;
604 out_mode |= disable_newline_auto_return;
605
606 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
607 const int enable_line_input = 0x0002;
608 const int enable_echo_input = 0x0004;
609 const int enable_virtual_terminal_input = 0x0200;
610 const int enable_window_input = 0x0008;
611 in_mode &= ~enable_echo_input;
612 in_mode &= ~enable_line_input;
613 in_mode |= enable_virtual_terminal_input;
614 in_mode |= enable_window_input;
615
616 SetConsoleMode(stdin_handle, in_mode);
617 SetConsoleMode(stdout_handle, out_mode);
618#else // POSIX(Linux 與 Mac)
619 for (const int signal :
620 {SIGWINCH, SIGTSTP, SIGBUS, SIGSYS, SIGQUIT, SIGHUP}) {
621 InstallSignalHandler(signal);
622 }
623
624 struct termios terminal; // NOLINT
625 tcgetattr(tty_fd_, &terminal);
626 g_original_termios = terminal;
627 g_tty_fd = tty_fd_;
628 g_has_original_termios = true;
629 on_exit_functions.emplace([terminal = terminal, tty_fd_ = tty_fd_] {
630 tcsetattr(tty_fd_, TCSANOW, &terminal);
631 });
632
633 // 啟用原始終端機輸入模式
634 terminal.c_iflag &= ~IGNBRK; // 停用忽略中斷條件
635 terminal.c_iflag &= ~BRKINT; // 停用因中斷而導致輸入與輸出被
636 // 清空
637 terminal.c_iflag &= ~PARMRK; // 停用標記同位錯誤。
638 terminal.c_iflag &= ~ISTRIP; // 停用剝除字元的第 8 位元。
639 terminal.c_iflag &= ~INLCR; // 停用將 NL 映射為 CR。
640 terminal.c_iflag &= ~IGNCR; // 停用忽略 CR。
641 terminal.c_iflag &= ~ICRNL; // 停用將 CR 映射為 NL。
642 terminal.c_iflag &= ~IXON; // 停用輸出的 XON/XOFF 流量控制
643
644 terminal.c_lflag &= ~ECHO; // 停用回顯輸入字元。
645 terminal.c_lflag &= ~ECHONL; // 停用回顯換行字元。
646 terminal.c_lflag &= ~ICANON; // 停用正規模式(Canonical mode)。
647 terminal.c_lflag &= ~ISIG; // 停用在按下以下按鍵時傳送信號:
648 // - => DSUSP
649 // - C-Z => SUSP
650 // - C-C => INTR
651 // - C-d => QUIT
652 terminal.c_lflag &= ~IEXTEN; // 停用擴充輸入處理
653 terminal.c_cflag |= CS8; // 每個位元組 8 位元
654
655 terminal.c_cc[VMIN] = 0; // 非正規模式(non-canonical)讀取所需的
656 // 最小字元數。
657 terminal.c_cc[VTIME] = 0; // 非正規模式讀取的逾時時間,以十分之一秒為單位。
658
659 tcsetattr(tty_fd_, TCSANOW, &terminal);
660
661#endif
662
663 auto enable = [&](const std::vector<DECMode>& parameters) {
664 TerminalSend(Set(parameters));
665 on_exit_functions.emplace(
666 [this, parameters] { TerminalSend(Reset(parameters)); });
667 };
668
669 auto disable = [&](const std::vector<DECMode>& parameters) {
670 TerminalSend(Reset(parameters));
671 on_exit_functions.emplace(
672 [this, parameters] { TerminalSend(Set(parameters)); });
673 };
674
675 if (use_alternative_screen_) {
676 enable({
677 DECMode::kAlternateScreen,
678 });
679 }
680
681 disable({
682 DECMode::kLineWrap,
683 });
684
685 if (track_mouse_) {
686 enable({DECMode::kMouseVt200});
687 enable({DECMode::kMouseAnyEvent});
688 enable({DECMode::kMouseUrxvtMode});
689 enable({DECMode::kMouseSgrExtMode});
690 }
691
692 // 在安裝新的設定之後,將其清空到終端機以
693 // 確保它已完全套用:
694 TerminalFlush();
695
696 InstallTerminalInfo();
697
698 quit_ = false;
699
700 PostAnimationTask();
701
702 installed_ = true;
703 g_terminal_is_raw = true;
704}
705
706void App::Internal::Uninstall() {
707 g_terminal_is_raw = false;
708 installed_ = false;
709
710 // 在關閉期間,等待所有的回覆。
711 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
712 auto closing_receiver =
713 event_buffer.CreateReceiverAt(main_loop_receiver->index());
714 auto start = std::chrono::steady_clock::now();
715 while (cursor_position_request.HasPending()) {
716 FetchTerminalEvents();
717
718 while (closing_receiver->Has()) {
719 const auto event = closing_receiver->Pop();
720 if (event.is_cursor_position()) {
721 cursor_x_ = event.cursor_x();
722 cursor_y_ = event.cursor_y();
723 cursor_position_request.OnReply();
724 }
725 }
726
727 task_runner.RunUntilIdle();
728
729 if (std::chrono::steady_clock::now() - start >
730 std::chrono::milliseconds(400)) {
731 break;
732 }
733 std::this_thread::sleep_for(std::chrono::milliseconds(10));
734 }
735 }
736
737 OnExit();
738}
739
740void App::Internal::PreMain() {
741 // 暫停先前作用中的畫面:
742 if (g_active_screen) {
743 std::swap(suspended_screen_, g_active_screen);
744 // 將游標位置重設到畫面頂端並清除畫面。
745 suspended_screen_->internal_->TerminalSend(
746 suspended_screen_->internal_->ResetCursorPosition());
747 suspended_screen_->ResetPosition(
748 suspended_screen_->internal_->output_buffer,
749 /*clear=*/true);
750 suspended_screen_->dimx_ = 0;
751 suspended_screen_->dimy_ = 0;
752
753 // 重設尺寸以強制在下次繪製畫面:
754 suspended_screen_->internal_->Uninstall();
755 }
756
757 // 這個畫面現在是作用中的:
758 g_active_screen = public_;
759 g_active_screen->internal_->Install();
760
761 previous_animation_time_ = animation::Clock::now();
762}
763
764void App::Internal::PostMain() {
765 // 將游標位置設定在繪製內容的結尾。
766 TerminalSend(ResetCursorPosition());
767
768 g_active_screen = nullptr;
769
770 // 還原已暫停的畫面。
771 if (suspended_screen_) {
772 // 清除畫面,並將游標放在繪製內容的開頭。
773 public_->ResetPosition(output_buffer, /*clear=*/true);
774 public_->dimx_ = 0;
775 public_->dimy_ = 0;
776 Uninstall();
777 std::swap(g_active_screen, suspended_screen_);
778 g_active_screen->internal_->Install();
779 } else {
780 Uninstall();
781
782 std::cout << "\r";
783 // 在最終退出時,保留目前的繪製內容並將游標位置重設到
784 // 其後的下一行。
785 if (!use_alternative_screen_) {
786 std::cout << "\n";
787 }
788 std::cout << std::flush;
789 }
790
791 int sig = g_last_signal.exchange(0);
792 if (sig != 0) {
793 RestoreSignalHandlerAndRaise(sig);
794 }
795}
796
797bool App::Internal::HasQuitted() {
798 return quit_;
799}
800
801void App::Internal::RunOnce(const Component& component) {
802 const AutoReset set_component(&component_, component);
803 ExecuteSignalHandlers();
804 FetchTerminalEvents();
805
806 while (!quit_ && main_loop_receiver->Has()) {
807 public_->Post(main_loop_receiver->Pop());
808 }
809
810 // 執行佇列中待處理的工作。
811 const size_t executed_task = task_runner.ExecutedTasks();
812 task_runner.RunUntilIdle();
813 // 如果沒有執行任何工作,我們可以提早返回而不需要重繪畫面。
814 if (executed_task == task_runner.ExecutedTasks()) {
815 return;
816 }
817
818 ExecuteSignalHandlers();
819 Draw(component);
820
821 if (selection_data_previous_ != selection_data_) {
822 selection_data_previous_ = selection_data_;
823 if (selection_on_change_) {
824 selection_on_change_();
825 public_->Post(Event::Custom);
826 }
827 }
828}
829
830void App::Internal::RunOnceBlocking(Component component) {
831 // 將 FPS 最多設為 60。
832 const auto time_per_frame = std::chrono::microseconds(16666); // 1 秒 / 60fps
833
834 auto time = std::chrono::steady_clock::now();
835 const size_t executed_task = task_runner.ExecutedTasks();
836
837 // 至少等待一個工作執行。
838 while (executed_task == task_runner.ExecutedTasks() && !HasQuitted()) {
839 RunOnce(component);
840
841 const auto now = std::chrono::steady_clock::now();
842 const auto delta = now - time;
843 time = now;
844
845 if (delta < time_per_frame) {
846 const auto sleep_duration = time_per_frame - delta;
847 std::this_thread::sleep_for(sleep_duration);
848 }
849 }
850}
851
852void App::Internal::HandleTask(Component component, Task& task) {
853 std::visit(
854 [&](auto&& arg) {
855 using T = std::decay_t<decltype(arg)>;
856 // clang-format off
857
858 // 處理事件。
859 if constexpr (std::is_same_v<T, Event>) {
860
861 if (arg.is_cursor_position()) {
862 cursor_x_ = arg.cursor_x();
863 cursor_y_ = arg.cursor_y();
864 cursor_position_request.OnReply();
865 return;
866 }
867
868 if (arg.is_cursor_shape()) {
869 cursor_reset_shape_ = arg.cursor_shape();
870 return;
871 }
872
873 if (arg.IsTerminalCapabilities()) {
874 terminal_capabilities_ = arg.TerminalCapabilities();
875 return;
876 }
877
878 if (arg.IsTerminalNameVersion()) {
879 terminal_name_ = arg.TerminalName();
880 terminal_version_ = arg.TerminalVersion();
881 return;
882 }
883
884 if (arg.IsTerminalEmulator()) {
885 terminal_emulator_name_ = arg.TerminalEmulatorName();
886 terminal_emulator_version_ = arg.TerminalEmulatorVersion();
887 return;
888 }
889
890 if (arg.is_mouse()) {
891 arg.mouse().x -= cursor_x_;
892 arg.mouse().y -= cursor_y_;
893 }
894
895 arg.screen_ = public_;
896
897 bool handled = component->OnEvent(arg);
898 handled = HandleSelection(handled, arg);
899
900 if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) {
901 RecordSignal(SIGINT);
902 }
903
904#if !defined(_WIN32)
905 if (arg == Event::CtrlZ && (!handled || force_handle_ctrl_z_)) {
906 RecordSignal(SIGTSTP);
907 }
908#endif
909
910 frame_valid_ = false;
911 return;
912 }
913
914 // 處理回呼
915 if constexpr (std::is_same_v<T, Closure>) {
916 arg();
917 return;
918 }
919
920 // 處理動畫
921 if constexpr (std::is_same_v<T, AnimationTask>) {
922 if (!animation_requested_) {
923 return;
924 }
925
926 animation_requested_ = false;
927 const animation::TimePoint now = animation::Clock::now();
928 const animation::Duration delta = now - previous_animation_time_;
929 previous_animation_time_ = now;
930
931 animation::Params params(delta);
932 component->OnAnimation(params);
933 frame_valid_ = false;
934 return;
935 }
936 },
937 task);
938 // clang-format on
939}
940
941bool App::Internal::HandleSelection(bool handled, Event event) {
942 if (handled) {
943 selection_pending_ = nullptr;
944 selection_data_.empty = true;
945 selection_ = nullptr;
946 return true;
947 }
948
949 if (!event.is_mouse()) {
950 return false;
951 }
952
953 auto& mouse = event.mouse();
954 if (mouse.button != Mouse::Left) {
955 return false;
956 }
957
958 if (mouse.motion == Mouse::Pressed) {
959 selection_pending_ = public_->CaptureMouse();
960 selection_data_.start_x = mouse.x;
961 selection_data_.start_y = mouse.y;
962 selection_data_.end_x = mouse.x;
963 selection_data_.end_y = mouse.y;
964 return false;
965 }
966
967 if (!selection_pending_) {
968 return false;
969 }
970
971 if (mouse.motion == Mouse::Moved) {
972 if ((mouse.x != selection_data_.end_x) ||
973 (mouse.y != selection_data_.end_y)) {
974 selection_data_.end_x = mouse.x;
975 selection_data_.end_y = mouse.y;
976 selection_data_.empty = false;
977 }
978
979 return true;
980 }
981
982 if (mouse.motion == Mouse::Released) {
983 selection_pending_ = nullptr;
984 selection_data_.end_x = mouse.x;
985 selection_data_.end_y = mouse.y;
986 selection_data_.empty = false;
987 return true;
988 }
989
990 return false;
991}
992
993void App::Internal::Draw(Component component) {
994 if (frame_valid_) {
995 return;
996 }
997 auto document = component->Render();
998 int dimx = 0;
999 int dimy = 0;
1000 auto terminal = Terminal::Size();
1001 document->ComputeRequirement();
1002 switch (dimension_) {
1004 dimx = public_->dimx_;
1005 dimy = public_->dimy_;
1006 break;
1008 dimx = terminal.dimx;
1009 dimy = util::clamp(document->requirement().min_y, 0, terminal.dimy);
1010 break;
1012 dimx = terminal.dimx;
1013 dimy = terminal.dimy;
1014 break;
1016 dimx = util::clamp(document->requirement().min_x, 0, terminal.dimx);
1017 dimy = util::clamp(document->requirement().min_y, 0, terminal.dimy);
1018 break;
1019 }
1020
1021 // 隱藏游標以防止重設時閃爍。
1022 TerminalSend("\033[?25l");
1023
1024 const bool resized =
1025 frame_count_ == 0 || (dimx != public_->dimx_) || (dimy != public_->dimy_);
1026 TerminalSend(ResetCursorPosition());
1027
1028 if (frame_count_ != 0) {
1029 // 將游標位置重設到左下角,以開始繪製
1030 // 新的畫面。
1031 public_->ResetPosition(output_buffer, resized);
1032
1033 // 如果終端機寬度縮小,終端機模擬器會開始換行
1034 // 導致顯示變髒。我們應該完全清除它。
1035 if ((dimx < public_->dimx_) && !use_alternative_screen_) {
1036 TerminalSend("\033[J"); // 清除終端機輸出
1037 TerminalSend("\033[H"); // 將游標移到起始位置
1038 }
1039 }
1040
1041 // 如有需要,調整畫面大小。
1042 if (resized) {
1043 public_->dimx_ = dimx;
1044 public_->dimy_ = dimy;
1045 public_->cells_ = std::vector<Cell>(static_cast<size_t>(dimx) *
1046 static_cast<size_t>(dimy));
1047 Cursor cursor = public_->cursor_;
1048 cursor.x = dimx - 1;
1049 cursor.y = dimy - 1;
1050 public_->SetCursor(cursor);
1051 }
1052
1053 // 定期向終端機模擬器要求框架相對於
1054 // 畫面的位置。這對於將以畫面座標回報的滑鼠位置
1055 // 轉換為框架座標很有用。
1056 if (!use_alternative_screen_ && is_stdout_a_tty_) {
1057 RequestCursorPosition(previous_frame_resized_);
1058 }
1059 previous_frame_resized_ = resized;
1060
1061 selection_ = selection_data_.empty
1062 ? std::make_unique<Selection>()
1063 : std::make_unique<Selection>(
1064 selection_data_.start_x, selection_data_.start_y, //
1065 selection_data_.end_x, selection_data_.end_y);
1066 Render(*public_, document.get(), *selection_);
1067
1068 // 為使用工具插入 CJK 字元的使用者設定游標位置。
1069 {
1070 const int dx = public_->dimx_ - 1 - public_->cursor_.x +
1071 int(public_->dimx_ != terminal.dimx);
1072 const int dy = public_->dimy_ - 1 - public_->cursor_.y;
1073
1074 set_cursor_position_.clear();
1075 reset_cursor_position_.clear();
1076
1077 if (dy != 0) {
1078 set_cursor_position_ += "\x1B[" + std::to_string(dy) + "A";
1079 reset_cursor_position_ += "\x1B[" + std::to_string(dy) + "B";
1080 }
1081
1082 if (dx != 0) {
1083 set_cursor_position_ += "\x1B[" + std::to_string(dx) + "D";
1084 reset_cursor_position_ += "\x1B[" + std::to_string(dx) + "C";
1085 }
1086
1087 if (public_->cursor_.shape != Screen::Cursor::Hidden) {
1088 set_cursor_position_ += "\033[?25h";
1089 set_cursor_position_ +=
1090 "\033[" + std::to_string(int(public_->cursor_.shape)) + " q";
1091 }
1092 }
1093
1094 public_->ToString(output_buffer);
1095 TerminalSend(set_cursor_position_);
1096 TerminalFlush();
1097
1098 public_->Clear();
1099 frame_valid_ = true;
1100 frame_count_++;
1101}
1102
1103std::string App::Internal::ResetCursorPosition() {
1104 std::string result = std::move(reset_cursor_position_);
1105 reset_cursor_position_ = "";
1106 return result;
1107}
1108
1109void App::Internal::RequestCursorPosition(bool force) {
1110 cursor_position_request.Request(force);
1111}
1112
1113void App::Internal::TerminalSend(std::string_view s) {
1114 output_buffer += s;
1115}
1116
1117void App::Internal::TerminalFlush() {
1118 // Emscripten 沒有實作 flush。我們將零解讀為 flush。
1119 output_buffer += '\0';
1120 std::cout << output_buffer << std::flush;
1121 output_buffer.clear();
1122}
1123
1124void App::Internal::InstallPipedInputHandling() {
1125 is_stdin_a_tty_ = false;
1126 is_stdout_a_tty_ = false;
1127#if defined(__EMSCRIPTEN__)
1128 is_stdin_a_tty_ = true;
1129 is_stdout_a_tty_ = true;
1130#elif defined(_WIN32)
1131 is_stdin_a_tty_ = _isatty(_fileno(stdin));
1132 is_stdout_a_tty_ = _isatty(_fileno(stdout));
1133#else
1134 tty_fd_ = STDIN_FILENO;
1135 is_stdout_a_tty_ = isatty(STDOUT_FILENO);
1136 // 如果應用程式明確啟用,則處理管線輸入重新導向。
1137 // 這讓應用程式可以在仍然接收終端機互動用鍵盤輸入的
1138 // 同時,從 stdin 讀取資料。
1139 if (!handle_piped_input_) {
1140 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1141 } else if (isatty(STDIN_FILENO)) {
1142 is_stdin_a_tty_ = true;
1143 } else {
1144 // 開啟 /dev/tty 以取得鍵盤輸入。
1145 tty_fd_ = open("/dev/tty", O_RDONLY); // NOLINT
1146 if (tty_fd_ < 0) {
1147 // 無法開啟 /dev/tty(容器、無頭系統等)
1148 tty_fd_ = STDIN_FILENO; // 回退到 stdin。
1149 is_stdin_a_tty_ = isatty(STDIN_FILENO);
1150 } else {
1151 is_stdin_a_tty_ = true;
1152 // 在退出時關閉 /dev/tty 檔案描述符。
1153 on_exit_functions.emplace([this] {
1154 close(tty_fd_);
1155 tty_fd_ = -1;
1156 });
1157 }
1158 }
1159#endif
1160}
1161
1162void App::Internal::InstallTerminalInfo() {
1163 // 要求終端機回報目前的游標形狀。我們將在
1164 // 退出時還原它。
1165 if (is_stdout_a_tty_) {
1166 TerminalSend(DECRQSS_DECSCUSR);
1167 TerminalSend("\033[>q"); // XTVERSION
1168 TerminalSend("\033[>c"); // DA2
1169 TerminalSend("\033[c"); // DA1
1170 TerminalFlush();
1171 }
1172
1173 // 使用 setup head 等待游標形狀的回覆。
1174 if (is_stdin_a_tty_ && is_stdout_a_tty_) {
1175 // 一個範圍限定於 setup 的接收者:如果在 setup 結束後仍保持存活,
1176 // 會將後續每一個事件都固定在緩衝區中,導致緩衝區
1177 // 在整個應用程式生命週期中不斷增長。
1178 auto setup_receiver = event_buffer.CreateReceiver();
1179 auto start = std::chrono::steady_clock::now();
1180 bool terminal_capabilities_received = false;
1181 // 使用 setup head 等待游標形狀的回覆。
1182 while (true) {
1183 FetchTerminalEvents();
1184 while (setup_receiver->Has()) {
1185 const auto event = setup_receiver->Pop();
1186 if (event.is_cursor_shape()) {
1187 cursor_reset_shape_ = event.cursor_shape();
1188 }
1189
1190 if (event.IsTerminalCapabilities()) {
1191 terminal_capabilities_ = event.TerminalCapabilities();
1192 terminal_capabilities_received = true;
1193 }
1194
1195 if (event.IsTerminalNameVersion()) {
1196 terminal_name_ = event.TerminalName();
1197 terminal_version_ = event.TerminalVersion();
1198 }
1199
1200 if (event.IsTerminalEmulator()) {
1201 terminal_emulator_name_ = event.TerminalEmulatorName();
1202 terminal_emulator_version_ = event.TerminalEmulatorVersion();
1203 }
1204 }
1205
1206 // 預期回應會依順序收到,因此當收到最後一個
1207 // (XTVERSION)時,我們可以中斷。我們也設定了逾時,以防止
1208 // 在終端機不支援這些查詢時永遠等待。
1209 if (terminal_capabilities_received) {
1210 break;
1211 }
1212
1213 if (std::chrono::steady_clock::now() - start >
1214 std::chrono::milliseconds(500)) {
1215 break;
1216 }
1217 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1218 }
1219 }
1220
1221 // 根據終端機識別結果設定特性與色彩支援。
1222 Terminal::Quirks quirks = Terminal::GetQuirks();
1223
1224 auto color_support = Terminal::ComputeColorSupport(
1225 util::GetEnv("TERM"), util::GetEnv("COLORTERM"),
1226 util::GetEnv("TERM_PROGRAM"), terminal_name_, terminal_emulator_name_,
1227 terminal_capabilities_);
1228
1229 quirks.SetColorSupport(color_support);
1230
1231 const bool is_modern_emulator = (terminal_emulator_name_ != "unknown");
1232 const bool is_vt220_plus =
1233 (terminal_name_ != "vt100" && terminal_name_ != "unknown");
1234 bool reports_utf8 = false;
1235 for (const int x : terminal_capabilities_) {
1236 if (x == 52) {
1237 reports_utf8 = true;
1238 break;
1239 }
1240 }
1241
1242 // 經驗法則:如果終端機模擬器是現代的,或者它回報支援
1243 // UTF-8 或色彩,我們可以假設它支援區塊字元與隱藏游標,
1244 // 這些對於良好的使用體驗至關重要。這只是一個經驗法則,但
1245 // 它讓我們可以在某些不支援這些功能的舊終端機模擬器上進行
1246 // 變通,同時仍在支援這些功能的現代終端機模擬器上提供
1247 // 良好的體驗。
1248 bool modern = is_modern_emulator || is_vt220_plus || reports_utf8;
1249 if (modern) {
1250 quirks.SetBlockCharacters(true);
1251 quirks.SetCursorHiding(true);
1252 quirks.SetComponentAscii(false);
1253 }
1254
1255 Terminal::SetQuirks(quirks);
1256
1257 on_exit_functions.emplace([this] {
1258 TerminalSend("\033[?25h"); // 啟用游標。
1259 if (is_stdout_a_tty_) {
1260 TerminalSend("\033[" + std::to_string(cursor_reset_shape_) + " q");
1261 }
1262 });
1263}
1264
1265void App::Internal::Signal(int signal) {
1266 if (signal == SIGABRT) {
1267 public_->Exit();
1268 return;
1269 }
1270
1271// Windows 不支援 SIGTSTP / SIGWINCH
1272#if !defined(_WIN32)
1273 if (signal == SIGTSTP) {
1274 public_->Post([&] {
1275 TerminalSend(ResetCursorPosition());
1276 public_->ResetPosition(output_buffer, /*clear*/ true);
1277 Uninstall();
1278 public_->dimx_ = 0;
1279 public_->dimy_ = 0;
1280 (void)std::raise(SIGTSTP);
1281 Install();
1282 });
1283 return;
1284 }
1285
1286 if (signal == SIGWINCH) {
1287 public_->Post(Event::Special({0}));
1288 return;
1289 }
1290#endif
1291}
1292
1293size_t App::Internal::FetchTerminalEvents() {
1294#if defined(_WIN32)
1295 auto get_input_records = [&]() -> std::vector<INPUT_RECORD> {
1296 // 檢查主控台是否有輸入。
1297 auto console = GetStdHandle(STD_INPUT_HANDLE);
1298 DWORD number_of_events = 0;
1299 if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) {
1300 return std::vector<INPUT_RECORD>();
1301 }
1302 if (number_of_events <= 0) {
1303 // 沒有輸入,返回。
1304 return std::vector<INPUT_RECORD>();
1305 }
1306 // 讀取輸入事件。
1307 std::vector<INPUT_RECORD> records(number_of_events);
1308 DWORD number_of_events_read = 0;
1309 if (!ReadConsoleInput(console, records.data(), (DWORD)records.size(),
1310 &number_of_events_read)) {
1311 return std::vector<INPUT_RECORD>();
1312 }
1313 records.resize(number_of_events_read);
1314 return records;
1315 };
1316
1317 auto records = get_input_records();
1318 if (records.size() == 0) {
1319 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1320 const size_t timeout_microseconds =
1321 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1322 terminal_input_parser.Timeout(timeout_microseconds);
1323 return 0;
1324 }
1325 last_char_time = std::chrono::steady_clock::now();
1326
1327 // 將輸入事件轉換為 FTXUI 事件。
1328 // 對於每個事件,我們呼叫終端機輸入剖析器將其轉換為
1329 // Event。
1330 std::wstring wstring;
1331 for (const auto& r : records) {
1332 switch (r.EventType) {
1333 case KEY_EVENT: {
1334 auto key_event = r.Event.KeyEvent;
1335 // 忽略 UP 按鍵事件
1336 if (key_event.bKeyDown == FALSE) {
1337 continue;
1338 }
1339 const wchar_t wc = key_event.uChar.UnicodeChar;
1340 wstring += wc;
1341 if (wc >= 0xd800 && wc <= 0xdbff) {
1342 // 等待低代理項(Low Surrogate)在下一筆記錄中到達。
1343 continue;
1344 }
1345 for (auto it : to_string(wstring)) {
1346 terminal_input_parser.Add(it);
1347 }
1348 wstring.clear();
1349 } break;
1350 case WINDOW_BUFFER_SIZE_EVENT:
1351 public_->Post(Event::Special({0}));
1352 break;
1353 case MENU_EVENT:
1354 case FOCUS_EVENT:
1355 case MOUSE_EVENT:
1356 // TODO(mauve): 稍後實作。
1357 break;
1358 }
1359 }
1360 return records.size();
1361#elif defined(__EMSCRIPTEN__)
1362 // 從終端機讀取字元。
1363 // 我們已將其設定為非阻塞。
1364 std::array<char, 4096> out{};
1365 const ssize_t l = read(STDIN_FILENO, out.data(), out.size());
1366 if (l <= 0) {
1367 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1368 const size_t timeout_microseconds =
1369 std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
1370 terminal_input_parser.Timeout(timeout_microseconds);
1371 return 0;
1372 }
1373 last_char_time = std::chrono::steady_clock::now();
1374
1375 // 將字元轉換為事件。
1376 for (ssize_t i = 0; i < l; ++i) {
1377 terminal_input_parser.Add(out.at(static_cast<size_t>(i)));
1378 }
1379 return (size_t)l;
1380#else // POSIX(Linux 與 Mac)
1381 struct pollfd pfd = {tty_fd_, POLLIN, 0};
1382 const int poll_result = poll(&pfd, 1, 0);
1383 if (poll_result <= 0) {
1384 const auto timeout = std::chrono::steady_clock::now() - last_char_time;
1385 const size_t timeout_ms =
1386 std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
1387 terminal_input_parser.Timeout(static_cast<int>(timeout_ms));
1388 return 0;
1389 }
1390 last_char_time = std::chrono::steady_clock::now();
1391
1392 // 耗盡可用的輸入,以避免突發輸入(例如快速滑鼠滾輪
1393 // 滾動)在多個畫面之間累積。總量有上限,以在持續的輸入
1394 // 洪流下保持畫面的反應性。詳見 #1348。
1395 constexpr size_t kMaxBytesPerFetch = 64 * 1024;
1396 std::array<char, 4096> out{};
1397 size_t total = 0;
1398 while (total < kMaxBytesPerFetch) {
1399 const ssize_t l = read(tty_fd_, out.data(), out.size());
1400 if (l <= 0) {
1401 break;
1402 }
1403
1404 // 將字元轉換為事件。
1405 for (ssize_t i = 0; i < l; ++i) {
1406 terminal_input_parser.Add(out.at(static_cast<size_t>(i)));
1407 }
1408 total += static_cast<size_t>(l);
1409
1410 pfd.revents = 0;
1411 if (poll(&pfd, 1, 0) <= 0) {
1412 break;
1413 }
1414 }
1415 return total;
1416#endif
1417}
1418
1419void App::Internal::PostAnimationTask() {
1420 public_->Post(AnimationTask());
1421
1422 // 每 15ms 重複一次動畫工作。這相當於
1423 // 大約 66fps 的畫面更新率。
1424 task_runner.PostDelayedTask([this] { PostAnimationTask(); },
1425 std::chrono::milliseconds(15));
1426}
1427
1428App::App(std::unique_ptr<Internal> internal, int dimx, int dimy)
1429 : Screen(dimx, dimy), internal_(std::move(internal)) {
1430 internal_->public_ = this;
1431}
1432
1433App::App(App&& other) noexcept : Screen(std::move(other)) {
1434 internal_ = std::move(other.internal_);
1435 if (internal_) {
1436 internal_->public_ = this;
1437 }
1438}
1439
1440App& App::operator=(App&& other) noexcept {
1441 Screen::operator=(std::move(other));
1442 internal_ = std::move(other.internal_);
1443 if (internal_) {
1444 internal_->public_ = this;
1445 }
1446 return *this;
1447}
1448
1449App::~App() = default;
1450
1451// static
1452App App::FixedSize(int dimx, int dimy) {
1453 auto internal =
1454 std::make_unique<Internal>(nullptr, AppDimension::Fixed, false);
1455 return App(std::move(internal), dimx, dimy);
1456}
1457
1458// static
1459App App::Fullscreen() {
1460 return FullscreenAlternateScreen();
1461}
1462
1463// static
1464App App::FullscreenPrimaryScreen() {
1465 auto terminal = Terminal::Size();
1466 auto internal =
1467 std::make_unique<Internal>(nullptr, AppDimension::Fullscreen, false);
1468 return App(std::move(internal), terminal.dimx, terminal.dimy);
1469}
1470
1471// static
1472App App::FullscreenAlternateScreen() {
1473 auto terminal = Terminal::Size();
1474 auto internal =
1475 std::make_unique<Internal>(nullptr, AppDimension::Fullscreen, true);
1476 return App(std::move(internal), terminal.dimx, terminal.dimy);
1477}
1478
1479// static
1480App App::FitComponent() {
1481 auto terminal = Terminal::Size();
1482 auto internal =
1483 std::make_unique<Internal>(nullptr, AppDimension::FitComponent, false);
1484 return App(std::move(internal), terminal.dimx, terminal.dimy);
1485}
1486
1487// static
1488App App::TerminalOutput() {
1489 auto terminal = Terminal::Size();
1490 auto internal =
1491 std::make_unique<Internal>(nullptr, AppDimension::TerminalOutput, false);
1492 return App(std::move(internal), terminal.dimx, terminal.dimy);
1493}
1494
1495void App::TrackMouse(bool enable) {
1496 internal_->track_mouse_ = enable;
1497}
1498
1499void App::HandlePipedInput(bool enable) {
1500 internal_->handle_piped_input_ = enable;
1501}
1502
1503// static
1504App* App::Active() {
1505 return g_active_screen;
1506}
1507
1508void App::Loop(Component component) {
1509 class Loop loop(this, std::move(component));
1510 loop.Run();
1511}
1512
1513void App::Exit() {
1514 Post([this] { internal_->ExitNow(); });
1515}
1516
1517Closure App::ExitLoopClosure() {
1518 return [this] { Exit(); };
1519}
1520
1521void App::Post(Task task) {
1522 internal_->task_runner.PostTask([this, task = std::move(task)]() mutable {
1523 if (internal_->component_) {
1524 internal_->HandleTask(internal_->component_, task);
1525 return;
1526 }
1527
1528 // 如果沒有元件,我們仍然可以執行閉包。
1529 if (std::holds_alternative<Closure>(task)) {
1530 std::get<Closure>(task)();
1531 }
1532 });
1533}
1534
1535void App::PostEvent(Event event) {
1536 // PostEvent 被記載為執行緒安全:透過受互斥鎖保護的
1537 // 工作佇列進行。event_buffer 只有在主執行緒中使用才是安全的。
1538 Post(Task(std::move(event)));
1539}
1540
1541// static
1542void App::PostEventOrExecute(Closure closure) {
1543 if (!closure) {
1544 return;
1545 }
1546 if (auto* app = App::Active()) {
1547 app->Post(std::move(closure));
1548 } else {
1549 closure();
1550 }
1551}
1552
1553void App::RequestAnimationFrame() {
1554 if (internal_->animation_requested_) {
1555 return;
1556 }
1557 internal_->animation_requested_ = true;
1558 auto now = animation::Clock::now();
1559 const auto time_histeresis = std::chrono::milliseconds(33);
1560 if (now - internal_->previous_animation_time_ >= time_histeresis) {
1561 internal_->previous_animation_time_ = now;
1562 }
1563}
1564
1565CapturedMouse App::CaptureMouse() {
1566 if (internal_->mouse_captured) {
1567 return nullptr;
1568 }
1569 internal_->mouse_captured = true;
1570 return std::make_unique<CapturedMouseImpl>(
1571 [this] { internal_->mouse_captured = false; });
1572}
1573
1574Closure App::WithRestoredIO(Closure fn) {
1575 return [this, fn] {
1576 internal_->Uninstall();
1577 fn();
1578 internal_->Install();
1579 };
1580}
1581
1582void App::ForceHandleCtrlC(bool force) {
1583 internal_->force_handle_ctrl_c_ = force;
1584}
1585
1586void App::ForceHandleCtrlZ(bool force) {
1587 internal_->force_handle_ctrl_z_ = force;
1588}
1589
1590std::string App::GetSelection() {
1591 if (!internal_->selection_) {
1592 return "";
1593 }
1594 return internal_->selection_->GetParts();
1595}
1596
1597void App::SelectionChange(std::function<void()> callback) {
1598 internal_->selection_on_change_ = std::move(callback);
1599}
1600
1601const std::string& App::TerminalName() const {
1602 return internal_->terminal_name_;
1603}
1604
1605int App::TerminalVersion() const {
1606 return internal_->terminal_version_;
1607}
1608
1609const std::string& App::TerminalEmulatorName() const {
1610 return internal_->terminal_emulator_name_;
1611}
1612
1613const std::string& App::TerminalEmulatorVersion() const {
1614 return internal_->terminal_emulator_version_;
1615}
1616
1617const std::vector<int>& App::TerminalCapabilities() const {
1618 return internal_->terminal_capabilities_;
1619}
1620
1621std::vector<std::string> App::TerminalCapabilityNames() const {
1622 return Event::TerminalCapabilities("", internal_->terminal_capabilities_)
1623 .TerminalCapabilityNames();
1624}
1625
1626// 迴圈會呼叫這些:
1627
1628void App::ExitNow() {
1629 internal_->ExitNow();
1630}
1631void App::Install() {
1632 internal_->Install();
1633}
1634void App::Uninstall() {
1635 internal_->Uninstall();
1636}
1637void App::PreMain() {
1638 internal_->PreMain();
1639}
1640void App::PostMain() {
1641 internal_->PostMain();
1642}
1643bool App::HasQuitted() {
1644 return internal_->HasQuitted();
1645}
1646void App::RunOnce(const Component& component) {
1647 internal_->RunOnce(component);
1648}
1649void App::RunOnceBlocking(Component component) {
1650 internal_->RunOnceBlocking(component);
1651}
1652void App::HandleTask(Component component, Task& task) {
1653 internal_->HandleTask(component, task);
1654}
1655bool App::HandleSelection(bool handled, Event event) {
1656 return internal_->HandleSelection(handled, event);
1657}
1658void App::Draw(Component component) {
1659 internal_->Draw(component);
1660}
1661std::string App::ResetCursorPosition() {
1662 return internal_->ResetCursorPosition();
1663}
1664void App::RequestCursorPosition(bool force) {
1665 internal_->RequestCursorPosition(force);
1666}
1667void App::TerminalSend(std::string_view s) {
1668 internal_->TerminalSend(s);
1669}
1670void App::TerminalFlush() {
1671 internal_->TerminalFlush();
1672}
1673void App::InstallPipedInputHandling() {
1674 internal_->InstallPipedInputHandling();
1675}
1676void App::InstallTerminalInfo() {
1677 internal_->InstallTerminalInfo();
1678}
1679void App::Signal(int signal) {
1680 internal_->Signal(signal);
1681}
1682size_t App::FetchTerminalEvents() {
1683 return internal_->FetchTerminalEvents();
1684}
1685void App::PostAnimationTask() {
1686 internal_->PostAnimationTask();
1687}
1688
1689Loop::Loop(App* screen, Component component)
1690 : screen_(screen), component_(std::move(component)) {
1691 screen_->PreMain();
1692}
1693
1694Loop::~Loop() {
1695 screen_->PostMain();
1696}
1697
1698bool Loop::HasQuitted() {
1699 return screen_->HasQuitted();
1700}
1701
1702void Loop::RunOnce() {
1703 screen_->RunOnce(component_);
1704}
1705
1706void Loop::RunOnceBlocking() {
1707 screen_->RunOnceBlocking(component_);
1708}
1709
1710void Loop::Run() {
1711 while (!HasQuitted()) {
1712 RunOnceBlocking();
1713 }
1714}
1715
1716} // namespace ftxui
Quirks GetQuirks()
取得終端機的怪癖行為(quirks)。
Definition terminal.cpp:353
Dimensions Size()
獲取終端機大小。
Definition terminal.cpp:301
void SetQuirks(const Quirks &quirks)
覆寫終端機的怪癖行為(quirks)。
Definition terminal.cpp:363
FTXUI 的 ftxui::animation:: 命名空間
void SetFallbackSize(const Dimensions &fallbackSize)
在自動偵測失敗時覆寫終端機大小
Definition terminal.cpp:330
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)
根據環境變數與終端機辨識資訊,計算 顏色支援等級。
Definition terminal.cpp:194
std::chrono::duration< float > Duration
Definition animation.hpp:26
std::chrono::time_point< Clock > TimePoint
Definition animation.hpp:25
void RequestAnimationFrame()
Definition app.cpp:74
const char * GetEnv(const char *name)
Definition util.hpp:18
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:13
FTXUI ftxui:: 命名空間
Definition animation.hpp:11
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::string to_string(std::wstring_view s)
將 std::wstring 轉換為 UTF8 std::string。
Definition string.cpp:555
AppDimension
Definition app.cpp:66
std::variant< Event, Closure, AnimationTask > Task
Definition task.hpp:14
void Render(Screen &screen, Node *node, Selection &selection)
Definition node.cpp:105
std::function< void()> Closure
Definition task.hpp:13
std::shared_ptr< ComponentBase > Component
Definition app.hpp:23