FTXUI 7.0.3
C++ functional terminal UI.
Loading...
Searching...
No Matches
terminal.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 std::search
5#include <cctype> // for std::tolower
6#include <initializer_list>
7#include <string>
8#include <string_view> // for string_view
9
11#include "ftxui/screen/util.hpp" // for util::GetEnv
12
13#if defined(_WIN32)
14#define WIN32_LEAN_AND_MEAN
15
16#ifndef NOMINMAX
17#define NOMINMAX
18#endif
19
20#include <windows.h>
21#else
22#include <sys/ioctl.h> // for winsize, ioctl, TIOCGWINSZ
23#include <unistd.h> // for STDOUT_FILENO
24#endif
25#if defined(__sun) || defined(__illumos__)
26#include <sys/termios.h> // for winsize on illumos
27#endif
28
29namespace ftxui {
30
31namespace {
32
33std::unique_ptr<Terminal::Quirks> g_quirks;
34bool g_color_support_detected = false;
35
36bool& ColorSupportDetected() {
37 return g_color_support_detected;
38}
39
40Terminal::Quirks& GetQuirksInternal() {
41 if (!g_quirks) {
42 g_quirks = std::make_unique<Terminal::Quirks>();
43#if defined(_WIN32)
44 g_quirks->SetBlockCharacters(false);
45 g_quirks->SetCursorHiding(false);
46 g_quirks->SetComponentAscii(true);
47#endif
48 }
49 return *g_quirks;
50}
51
52Dimensions& FallbackSize() {
53#if defined(__EMSCRIPTEN__)
54 // 選擇此尺寸是為了能夠顯示:
55 // https://arthursonzogni.com/FTXUI/examples
56 // 當有人有時間實作並需要時,這將會改進。
57 constexpr int fallback_width = 140;
58 constexpr int fallback_height = 43;
59#else
60 // VT100 中的終端機大小為 80x24。它至今仍被許多終端機模擬器預設使用。這是一個很好的後備值。
61 constexpr int fallback_width = 80;
62 constexpr int fallback_height = 24;
63#endif
64 static Dimensions g_fallback_size{
65 fallback_width,
66 fallback_height,
67 };
68 return g_fallback_size;
69}
70
71bool Contains(std::string_view s, std::string_view key) {
72 if (key.empty()) {
73 return true;
74 }
75 const auto it = std::search( // NOLINT
76 s.begin(), s.end(), key.begin(), key.end(), [](char a, char b) {
77 return std::tolower(static_cast<unsigned char>(a)) ==
78 std::tolower(static_cast<unsigned char>(b));
79 });
80 return it != s.end();
81}
82
83bool ContainsAny(std::string_view s,
84 std::initializer_list<std::string_view> keys) {
85 for (const std::string_view key : keys) {
86 if (Contains(s, key)) {
87 return true;
88 }
89 }
90 return false;
91}
92
93Terminal::Color ComputeColorSupportInternal() {
94 static const std::vector<int> empty_capabilities;
96 util::GetEnv("TERM"), util::GetEnv("COLORTERM"),
97 util::GetEnv("TERM_PROGRAM"), "unknown", "unknown", empty_capabilities);
98}
99
100} // namespace
101
102namespace Terminal {
103
104struct Quirks::Impl {
105 bool block_characters = true;
106 bool cursor_hiding = true;
107 bool component_ascii = false;
108 Color color_support = Palette256;
109};
110
111Quirks::Quirks() : impl_(std::make_unique<Impl>()) {}
112Quirks::~Quirks() = default;
113Quirks::Quirks(const Quirks& other)
114 : impl_(std::make_unique<Impl>(*other.impl_)) {}
115Quirks& Quirks::operator=(const Quirks& other) {
116 if (this != &other) {
117 *impl_ = *other.impl_;
118 }
119 return *this;
120}
121Quirks::Quirks(Quirks&&) noexcept = default;
122Quirks& Quirks::operator=(Quirks&&) noexcept = default;
123
124bool Quirks::BlockCharacters() const {
125 return impl_->block_characters;
126}
127void Quirks::SetBlockCharacters(bool v) {
128 impl_->block_characters = v;
129}
130
131bool Quirks::CursorHiding() const {
132 return impl_->cursor_hiding;
133}
134void Quirks::SetCursorHiding(bool v) {
135 impl_->cursor_hiding = v;
136}
137
138bool Quirks::ComponentAscii() const {
139 return impl_->component_ascii;
140}
141void Quirks::SetComponentAscii(bool v) {
142 impl_->component_ascii = v;
143}
144
145Color Quirks::ColorSupport() const {
146 return impl_->color_support;
147}
148void Quirks::SetColorSupport(Color v) {
149 impl_->color_support = v;
150}
151
152struct TerminalInfo::Impl {
153 std::string term;
154 std::string colorterm;
155 std::string term_program;
156 std::string terminal_name;
157 std::string terminal_emulator_name;
158 std::vector<int> capabilities;
159};
160
161TerminalInfo::TerminalInfo() : impl_(std::make_unique<Impl>()) {}
162TerminalInfo::~TerminalInfo() = default;
163TerminalInfo::TerminalInfo(TerminalInfo&&) noexcept = default;
164TerminalInfo& TerminalInfo::operator=(TerminalInfo&&) noexcept = default;
165
166void TerminalInfo::SetTerm(std::string_view term) {
167 impl_->term = term;
168}
169void TerminalInfo::SetColorterm(std::string_view colorterm) {
170 impl_->colorterm = colorterm;
171}
172void TerminalInfo::SetTermProgram(std::string_view term_program) {
173 impl_->term_program = term_program;
174}
175void TerminalInfo::SetTerminalName(std::string_view terminal_name) {
176 impl_->terminal_name = terminal_name;
177}
178void TerminalInfo::SetTerminalEmulatorName(
179 std::string_view terminal_emulator_name) {
180 impl_->terminal_emulator_name = terminal_emulator_name;
181}
182void TerminalInfo::SetCapabilities(std::vector<int> capabilities) {
183 impl_->capabilities = std::move(capabilities);
184}
185
186/// @brief 根據環境變數與終端機辨識資訊,計算
187/// 顏色支援等級。
188/// @param term TERM 環境變數。
189/// @param colorterm COLORTERM 環境變數。
190/// @param term_program TERM_PROGRAM 環境變數。
191/// @param terminal_name 終端機名稱(來自 DA2)。
192/// @param terminal_emulator_name 終端機模擬器名稱(來自 XTVERSION)。
193/// @param capabilities 終端機能力(來自 DA1)。
194Color ComputeColorSupport(std::string_view term,
195 std::string_view colorterm,
196 std::string_view term_program,
197 std::string_view terminal_name,
198 std::string_view terminal_emulator_name,
199 const std::vector<int>& capabilities) {
200 TerminalInfo info;
201 info.SetTerm(term);
202 info.SetColorterm(colorterm);
203 info.SetTermProgram(term_program);
204 info.SetTerminalName(terminal_name);
205 info.SetTerminalEmulatorName(terminal_emulator_name);
206 info.SetCapabilities(capabilities);
207 return info.ComputeColorSupport();
208}
209
210Color TerminalInfo::ComputeColorSupport() const {
211 // TODO(v8): 從 ComputeColorSupportInternal() 讀取 NO_COLOR 和
212 // WT_SESSION,並將它們作為參數傳入,使此函式
213 // 維持為輸入的純函式。這需要擴充公開的
214 // Terminal::ComputeColorSupport() 簽章,也就是一項會破壞 API 的變更。
215
216 // 0. 使用者偏好設定。參見 https://no-color.org。
217 if (util::GetEnv("NO_COLOR")[0] != '\0') {
218 return Terminal::Color::Palette1;
219 }
220
221 // 1. 平台特定的覆寫。
222#if defined(__EMSCRIPTEN__)
223 return Terminal::Color::TrueColor;
224#endif
225#if defined(_WIN32)
226 // 檢查我們是否在主控台中執行,以及該主控台是否支援 VT 處理。
227 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
228 DWORD out_mode = 0;
229 if (GetConsoleMode(stdout_handle, &out_mode)) {
230 const int enable_virtual_terminal_processing = 0x0004;
231 const int disable_newline_auto_return = 0x0008;
232 out_mode |= enable_virtual_terminal_processing;
233 out_mode |= disable_newline_auto_return;
234 if (!SetConsoleMode(stdout_handle, out_mode)) {
235 return Terminal::Color::Palette16;
236 }
237 }
238 return Terminal::Color::TrueColor;
239#endif
240
241 // 檢查 WT_SESSION 以判斷是否為 Windows Terminal(例如在 WSL 下執行時)。
242 if (util::GetEnv("WT_SESSION")[0] != '\0') {
243 return Terminal::Color::TrueColor;
244 }
245
246 // 2. term / colorterm 環境變數。
247 if (ContainsAny(impl_->colorterm, {"24bit", "truecolor"})) {
248 return Terminal::Color::TrueColor;
249 }
250 if (ContainsAny(impl_->term,
251 {"direct", "truecolor", "kitty", "alacritty", "foot"})) {
252 return Terminal::Color::TrueColor;
253 }
254 if (ContainsAny(impl_->colorterm, {"256"}) ||
255 ContainsAny(impl_->term, {"256", "xterm", "screen", "tmux"})) {
256 return Terminal::Color::Palette256;
257 }
258
259 // 3. term_program
260 if (ContainsAny(impl_->term_program, {
261 "iterm",
262 "vscode",
263 "warp",
264 "ghostty",
265 "wezterm",
266 })) {
267 return Terminal::Color::TrueColor;
268 }
269 // Apple 的 Terminal.app (TERM_PROGRAM=Apple_Terminal) 支援 256 色,
270 // 但不支援 24 位元色。
271 if (Contains(impl_->term_program, "apple_terminal")) {
272 return Terminal::Color::Palette256;
273 }
274
275 // 4. 終端機識別。
276 // 空名稱代表終端機未被識別,等同於
277 // “unknown”。
278 if (!impl_->terminal_emulator_name.empty() &&
279 impl_->terminal_emulator_name != "unknown") {
280 return Terminal::Color::TrueColor;
281 }
282 if (impl_->terminal_name == "xterm") {
283 return Terminal::Color::TrueColor;
284 }
285 for (const int x : impl_->capabilities) {
286 // 值 22 是支援 256 色的 SGR 能力。如果終端機
287 // 支援它,這強烈表示該終端機支援 256
288 // 色。這並非完美的偵測方法,但在缺乏更具體
289 // 資訊的情況下,是合理的啟發式方法。
290 if (x == 22) {
291 return Terminal::Color::Palette256;
292 }
293 }
294
295 return Terminal::Color::Palette16;
296}
297
298/// @brief 獲取終端機大小。
299/// @return 終端機大小。
300/// @ingroup screen
301Dimensions Size() {
302#if defined(__EMSCRIPTEN__)
303 // 選擇此尺寸是為了能夠顯示:
304 // https://arthursonzogni.com/FTXUI/examples
305 // 當有人有時間實作並需要時,這將會改進。
306 return FallbackSize();
307#elif defined(_WIN32)
308 CONSOLE_SCREEN_BUFFER_INFO csbi;
309
310 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
311 return Dimensions{csbi.srWindow.Right - csbi.srWindow.Left + 1,
312 csbi.srWindow.Bottom - csbi.srWindow.Top + 1};
313 }
314
315 return FallbackSize();
316#else
317 winsize w{};
318 const int status = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); // NOLINT
319 // The ioctl return value result should be checked. Some operating systems
320 // don't support TIOCGWINSZ.
321 if (w.ws_col == 0 || w.ws_row == 0 || status < 0) {
322 return FallbackSize();
323 }
324 return Dimensions{w.ws_col, w.ws_row};
325#endif
326}
327
328/// @brief 在自動偵測失敗時覆寫終端機大小
329/// @param fallbackSize 要回退到的終端機尺寸
330void SetFallbackSize(const Dimensions& fallbackSize) {
331 FallbackSize() = fallbackSize;
332}
333
334/// @brief 獲取終端機的顏色支援。
335/// @ingroup screen
336Color ColorSupport() {
337 if (!ColorSupportDetected()) {
338 GetQuirksInternal().SetColorSupport(ComputeColorSupportInternal());
339 ColorSupportDetected() = true;
340 }
341 return GetQuirksInternal().ColorSupport();
342}
343
344/// @brief 在自動偵測失敗時覆寫終端機顏色支援
345/// @ingroup dom
346void SetColorSupport(Color color) {
347 GetQuirksInternal().SetColorSupport(color);
348 ColorSupportDetected() = true;
349}
350
351/// @brief 取得終端機的怪癖行為(quirks)。
352/// @ingroup screen
353Quirks GetQuirks() {
354 if (!ColorSupportDetected()) {
355 GetQuirksInternal().SetColorSupport(ComputeColorSupportInternal());
356 ColorSupportDetected() = true;
357 }
358 return GetQuirksInternal();
359}
360
361/// @brief 覆寫終端機的怪癖行為(quirks)。
362/// @ingroup screen
363void SetQuirks(const Quirks& quirks) {
364 GetQuirksInternal() = quirks;
365 ColorSupportDetected() = true;
366}
367
368} // namespace Terminal
369} // namespace ftxui
Color
Color 是一個列舉,表示終端機的色彩支援
Definition terminal.hpp:30
FTXUI ftxui::Terminal:: 命名空間
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
const char * GetEnv(const char *name)
Definition util.hpp:18
FTXUI ftxui:: 命名空間
Definition animation.hpp:11