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 // このサイズは、https://arthursonzogni.com/FTXUI/examples を表示できるように任意に選択されました。
55 // これは、誰かが実装して必要になったときに改善される必要があります。
56 constexpr int fallback_width = 140;
57 constexpr int fallback_height = 43;
58#else
59 // VT100のターミナルサイズは80x24でした。これは今日の多くのターミナルエミュレータでデフォルトとして使用されています。これはフォールバック値として良い選択です。
60 constexpr int fallback_width = 80;
61 constexpr int fallback_height = 24;
62#endif
63 static Dimensions g_fallback_size{
64 fallback_width,
65 fallback_height,
66 };
67 return g_fallback_size;
68}
69
70bool Contains(std::string_view s, std::string_view key) {
71 if (key.empty()) {
72 return true;
73 }
74 const auto it = std::search( // NOLINT
75 s.begin(), s.end(), key.begin(), key.end(), [](char a, char b) {
76 return std::tolower(static_cast<unsigned char>(a)) ==
77 std::tolower(static_cast<unsigned char>(b));
78 });
79 return it != s.end();
80}
81
82bool ContainsAny(std::string_view s,
83 std::initializer_list<std::string_view> keys) {
84 for (const std::string_view key : keys) {
85 if (Contains(s, key)) {
86 return true;
87 }
88 }
89 return false;
90}
91
92Terminal::Color ComputeColorSupportInternal() {
93 static const std::vector<int> empty_capabilities;
95 util::GetEnv("TERM"), util::GetEnv("COLORTERM"),
96 util::GetEnv("TERM_PROGRAM"), "unknown", "unknown", empty_capabilities);
97}
98
99} // namespace
100
101namespace Terminal {
102
103struct Quirks::Impl {
104 bool block_characters = true;
105 bool cursor_hiding = true;
106 bool component_ascii = false;
107 Color color_support = Palette256;
108};
109
110Quirks::Quirks() : impl_(std::make_unique<Impl>()) {}
111Quirks::~Quirks() = default;
112Quirks::Quirks(const Quirks& other)
113 : impl_(std::make_unique<Impl>(*other.impl_)) {}
114Quirks& Quirks::operator=(const Quirks& other) {
115 if (this != &other) {
116 *impl_ = *other.impl_;
117 }
118 return *this;
119}
120Quirks::Quirks(Quirks&&) noexcept = default;
121Quirks& Quirks::operator=(Quirks&&) noexcept = default;
122
123bool Quirks::BlockCharacters() const {
124 return impl_->block_characters;
125}
126void Quirks::SetBlockCharacters(bool v) {
127 impl_->block_characters = v;
128}
129
130bool Quirks::CursorHiding() const {
131 return impl_->cursor_hiding;
132}
133void Quirks::SetCursorHiding(bool v) {
134 impl_->cursor_hiding = v;
135}
136
137bool Quirks::ComponentAscii() const {
138 return impl_->component_ascii;
139}
140void Quirks::SetComponentAscii(bool v) {
141 impl_->component_ascii = v;
142}
143
144Color Quirks::ColorSupport() const {
145 return impl_->color_support;
146}
147void Quirks::SetColorSupport(Color v) {
148 impl_->color_support = v;
149}
150
151struct TerminalInfo::Impl {
152 std::string term;
153 std::string colorterm;
154 std::string term_program;
155 std::string terminal_name;
156 std::string terminal_emulator_name;
157 std::vector<int> capabilities;
158};
159
160TerminalInfo::TerminalInfo() : impl_(std::make_unique<Impl>()) {}
161TerminalInfo::~TerminalInfo() = default;
162TerminalInfo::TerminalInfo(TerminalInfo&&) noexcept = default;
163TerminalInfo& TerminalInfo::operator=(TerminalInfo&&) noexcept = default;
164
165void TerminalInfo::SetTerm(std::string_view term) {
166 impl_->term = term;
167}
168void TerminalInfo::SetColorterm(std::string_view colorterm) {
169 impl_->colorterm = colorterm;
170}
171void TerminalInfo::SetTermProgram(std::string_view term_program) {
172 impl_->term_program = term_program;
173}
174void TerminalInfo::SetTerminalName(std::string_view terminal_name) {
175 impl_->terminal_name = terminal_name;
176}
177void TerminalInfo::SetTerminalEmulatorName(
178 std::string_view terminal_emulator_name) {
179 impl_->terminal_emulator_name = terminal_emulator_name;
180}
181void TerminalInfo::SetCapabilities(std::vector<int> capabilities) {
182 impl_->capabilities = std::move(capabilities);
183}
184
185/// @brief 環境変数とターミナル識別に基づいて色サポートを計算します。
186/// @param term TERM環境変数。
187/// @param colorterm COLORTERM環境変数。
188/// @param term_program TERM_PROGRAM環境変数。
189/// @param terminal_name ターミナル名 (DA2から)。
190/// @param terminal_emulator_name ターミナルエミュレータ名 (XTVERSIONから)。
191/// @param capabilities ターミナルの能力 (DA1から)。
192Color ComputeColorSupport(std::string_view term,
193 std::string_view colorterm,
194 std::string_view term_program,
195 std::string_view terminal_name,
196 std::string_view terminal_emulator_name,
197 const std::vector<int>& capabilities) {
198 TerminalInfo info;
199 info.SetTerm(term);
200 info.SetColorterm(colorterm);
201 info.SetTermProgram(term_program);
202 info.SetTerminalName(terminal_name);
203 info.SetTerminalEmulatorName(terminal_emulator_name);
204 info.SetCapabilities(capabilities);
205 return info.ComputeColorSupport();
206}
207
208Color TerminalInfo::ComputeColorSupport() const {
209 // TODO(v8): ComputeColorSupportInternal() から NO_COLOR と WT_SESSION を読み込み、
210 // パラメータとして渡すことで、この関数が入力に対する純粋な関数のままであるようにする。
211 // これには、公開の Terminal::ComputeColorSupport() のシグネチャを拡張する必要があり、
212 // つまり API を破壊する変更となる。
213
214 // 0. ユーザーの設定。https://no-color.org を参照。
215 if (util::GetEnv("NO_COLOR")[0] != '\0') {
216 return Terminal::Color::Palette1;
217 }
218
219 // 1. プラットフォーム固有のオーバーライド。
220#if defined(__EMSCRIPTEN__)
221 return Terminal::Color::TrueColor;
222#endif
223#if defined(_WIN32)
224 // コンソールで実行されているかどうか、およびそのコンソールが VT 処理をサポートしているかどうかを確認します。
225 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
226 DWORD out_mode = 0;
227 if (GetConsoleMode(stdout_handle, &out_mode)) {
228 const int enable_virtual_terminal_processing = 0x0004;
229 const int disable_newline_auto_return = 0x0008;
230 out_mode |= enable_virtual_terminal_processing;
231 out_mode |= disable_newline_auto_return;
232 if (!SetConsoleMode(stdout_handle, out_mode)) {
233 return Terminal::Color::Palette16;
234 }
235 }
236 return Terminal::Color::TrueColor;
237#endif
238
239 // (例えば WSL 上で実行している場合の) Windows Terminal を検出するために WT_SESSION を確認します。
240 if (util::GetEnv("WT_SESSION")[0] != '\0') {
241 return Terminal::Color::TrueColor;
242 }
243
244 // 2. term / colorterm 環境変数。
245 if (ContainsAny(impl_->colorterm, {"24bit", "truecolor"})) {
246 return Terminal::Color::TrueColor;
247 }
248 if (ContainsAny(impl_->term,
249 {"direct", "truecolor", "kitty", "alacritty", "foot"})) {
250 return Terminal::Color::TrueColor;
251 }
252 if (ContainsAny(impl_->colorterm, {"256"}) ||
253 ContainsAny(impl_->term, {"256", "xterm", "screen", "tmux"})) {
254 return Terminal::Color::Palette256;
255 }
256
257 // 3. term_program
258 if (ContainsAny(impl_->term_program, {
259 "iterm",
260 "vscode",
261 "warp",
262 "ghostty",
263 "wezterm",
264 })) {
265 return Terminal::Color::TrueColor;
266 }
267 // Apple の Terminal.app (TERM_PROGRAM=Apple_Terminal) は256色をサポートしますが、
268 // 24ビットカラーはサポートしません。
269 if (Contains(impl_->term_program, "apple_terminal")) {
270 return Terminal::Color::Palette256;
271 }
272
273 // 4. ターミナルの識別。
274 // 名前が空の場合、ターミナルが識別されなかったことを意味し、
275 // "unknown" と同じ扱いになります。
276 if (!impl_->terminal_emulator_name.empty() &&
277 impl_->terminal_emulator_name != "unknown") {
278 return Terminal::Color::TrueColor;
279 }
280 if (impl_->terminal_name == "xterm") {
281 return Terminal::Color::TrueColor;
282 }
283 for (const int x : impl_->capabilities) {
284 // 値 22 は256色に対応する SGR 機能です。ターミナルがこれを
285 // サポートしている場合、そのターミナルが256色をサポートしている強い
286 // 兆候となります。これは完全な検出方法ではありませんが、より具体的な
287 // 情報がない場合の妥当なヒューリスティックです。
288 if (x == 22) {
289 return Terminal::Color::Palette256;
290 }
291 }
292
293 return Terminal::Color::Palette16;
294}
295
296/// @brief ターミナルサイズを取得します。
297/// @return ターミナルサイズ。
298/// @ingroup screen
299Dimensions Size() {
300#if defined(__EMSCRIPTEN__)
301 // このサイズは、https://arthursonzogni.com/FTXUI/examples を表示できるように任意に選択されました。
302 // これは、誰かが実装して必要になったときに改善される必要があります。
303 return FallbackSize();
304#elif defined(_WIN32)
305 CONSOLE_SCREEN_BUFFER_INFO csbi;
306
307 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
308 return Dimensions{csbi.srWindow.Right - csbi.srWindow.Left + 1,
309 csbi.srWindow.Bottom - csbi.srWindow.Top + 1};
310 }
311
312 return FallbackSize();
313#else
314 winsize w{};
315 const int status = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); // NOLINT
316 // The ioctl return value result should be checked. Some operating systems
317 // don't support TIOCGWINSZ.
318 if (w.ws_col == 0 || w.ws_row == 0 || status < 0) {
319 return FallbackSize();
320 }
321 return Dimensions{w.ws_col, w.ws_row};
322#endif
323}
324
325/// @brief 自動検出が失敗した場合にターミナルサイズを上書きします
326/// @param fallbackSize フォールバックするターミナルサイズ
327void SetFallbackSize(const Dimensions& fallbackSize) {
328 FallbackSize() = fallbackSize;
329}
330
331/// @brief ターミナルの色サポートを取得します。
332/// @ingroup screen
333Color ColorSupport() {
334 if (!ColorSupportDetected()) {
335 GetQuirksInternal().SetColorSupport(ComputeColorSupportInternal());
336 ColorSupportDetected() = true;
337 }
338 return GetQuirksInternal().ColorSupport();
339}
340
341/// @brief 自動検出が失敗した場合にターミナルの色サポートを上書きします
342/// @ingroup dom
343void SetColorSupport(Color color) {
344 GetQuirksInternal().SetColorSupport(color);
345 ColorSupportDetected() = true;
346}
347
348/// @brief ターミナルのクイークを取得します。
349/// @ingroup screen
350Quirks GetQuirks() {
351 if (!ColorSupportDetected()) {
352 GetQuirksInternal().SetColorSupport(ComputeColorSupportInternal());
353 ColorSupportDetected() = true;
354 }
355 return GetQuirksInternal();
356}
357
358/// @brief ターミナルのクイークを上書きします。
359/// @ingroup screen
360void SetQuirks(const Quirks& quirks) {
361 GetQuirksInternal() = quirks;
362 ColorSupportDetected() = true;
363}
364
365} // namespace Terminal
366} // 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:192
const char * GetEnv(const char *name)
Definition util.hpp:18
FTXUI ftxui::名前空間
Definition animation.hpp:11