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 // Cette dimension a été choisie arbitrairement pour pouvoir afficher :
55 // https://arthursonzogni.com/FTXUI/examples
56 // Cela devra être amélioré lorsque quelqu'un aura le temps de l'implémenter et en aura besoin.
57 constexpr int fallback_width = 140;
58 constexpr int fallback_height = 43;
59#else
60 // La taille du terminal en VT100 était de 80x24. Elle est encore utilisée de nos jours par
61 // défaut dans de nombreux émulateurs de terminal. C'est un bon choix pour une valeur de
62 // repli.
63 constexpr int fallback_width = 80;
64 constexpr int fallback_height = 24;
65#endif
66 static Dimensions g_fallback_size{
67 fallback_width,
68 fallback_height,
69 };
70 return g_fallback_size;
71}
72
73bool Contains(std::string_view s, std::string_view key) {
74 if (key.empty()) {
75 return true;
76 }
77 const auto it = std::search( // NOLINT
78 s.begin(), s.end(), key.begin(), key.end(), [](char a, char b) {
79 return std::tolower(static_cast<unsigned char>(a)) ==
80 std::tolower(static_cast<unsigned char>(b));
81 });
82 return it != s.end();
83}
84
85bool ContainsAny(std::string_view s,
86 std::initializer_list<std::string_view> keys) {
87 for (const std::string_view key : keys) {
88 if (Contains(s, key)) {
89 return true;
90 }
91 }
92 return false;
93}
94
95Terminal::Color ComputeColorSupportInternal() {
96 static const std::vector<int> empty_capabilities;
98 util::GetEnv("TERM"), util::GetEnv("COLORTERM"),
99 util::GetEnv("TERM_PROGRAM"), "unknown", "unknown", empty_capabilities);
100}
101
102} // namespace
103
104namespace Terminal {
105
106struct Quirks::Impl {
107 bool block_characters = true;
108 bool cursor_hiding = true;
109 bool component_ascii = false;
110 Color color_support = Palette256;
111};
112
113Quirks::Quirks() : impl_(std::make_unique<Impl>()) {}
114Quirks::~Quirks() = default;
115Quirks::Quirks(const Quirks& other)
116 : impl_(std::make_unique<Impl>(*other.impl_)) {}
117Quirks& Quirks::operator=(const Quirks& other) {
118 if (this != &other) {
119 *impl_ = *other.impl_;
120 }
121 return *this;
122}
123Quirks::Quirks(Quirks&&) noexcept = default;
124Quirks& Quirks::operator=(Quirks&&) noexcept = default;
125
126bool Quirks::BlockCharacters() const {
127 return impl_->block_characters;
128}
129void Quirks::SetBlockCharacters(bool v) {
130 impl_->block_characters = v;
131}
132
133bool Quirks::CursorHiding() const {
134 return impl_->cursor_hiding;
135}
136void Quirks::SetCursorHiding(bool v) {
137 impl_->cursor_hiding = v;
138}
139
140bool Quirks::ComponentAscii() const {
141 return impl_->component_ascii;
142}
143void Quirks::SetComponentAscii(bool v) {
144 impl_->component_ascii = v;
145}
146
147Color Quirks::ColorSupport() const {
148 return impl_->color_support;
149}
150void Quirks::SetColorSupport(Color v) {
151 impl_->color_support = v;
152}
153
154struct TerminalInfo::Impl {
155 std::string term;
156 std::string colorterm;
157 std::string term_program;
158 std::string terminal_name;
159 std::string terminal_emulator_name;
160 std::vector<int> capabilities;
161};
162
163TerminalInfo::TerminalInfo() : impl_(std::make_unique<Impl>()) {}
164TerminalInfo::~TerminalInfo() = default;
165TerminalInfo::TerminalInfo(TerminalInfo&&) noexcept = default;
166TerminalInfo& TerminalInfo::operator=(TerminalInfo&&) noexcept = default;
167
168void TerminalInfo::SetTerm(std::string_view term) {
169 impl_->term = term;
170}
171void TerminalInfo::SetColorterm(std::string_view colorterm) {
172 impl_->colorterm = colorterm;
173}
174void TerminalInfo::SetTermProgram(std::string_view term_program) {
175 impl_->term_program = term_program;
176}
177void TerminalInfo::SetTerminalName(std::string_view terminal_name) {
178 impl_->terminal_name = terminal_name;
179}
180void TerminalInfo::SetTerminalEmulatorName(
181 std::string_view terminal_emulator_name) {
182 impl_->terminal_emulator_name = terminal_emulator_name;
183}
184void TerminalInfo::SetCapabilities(std::vector<int> capabilities) {
185 impl_->capabilities = std::move(capabilities);
186}
187
188/// @brief Calcule la prise en charge des couleurs en fonction des variables d'environnement et de
189/// l'identification du terminal.
190/// @param term La variable d'environnement TERM.
191/// @param colorterm La variable d'environnement COLORTERM.
192/// @param term_program La variable d'environnement TERM_PROGRAM.
193/// @param terminal_name Le nom du terminal (depuis DA2).
194/// @param terminal_emulator_name Le nom de l'émulateur de terminal (depuis XTVERSION).
195/// @param capabilities Les capacités du terminal (depuis DA1).
196Color ComputeColorSupport(std::string_view term,
197 std::string_view colorterm,
198 std::string_view term_program,
199 std::string_view terminal_name,
200 std::string_view terminal_emulator_name,
201 const std::vector<int>& capabilities) {
202 TerminalInfo info;
203 info.SetTerm(term);
204 info.SetColorterm(colorterm);
205 info.SetTermProgram(term_program);
206 info.SetTerminalName(terminal_name);
207 info.SetTerminalEmulatorName(terminal_emulator_name);
208 info.SetCapabilities(capabilities);
209 return info.ComputeColorSupport();
210}
211
212Color TerminalInfo::ComputeColorSupport() const {
213 // TODO(v8) : Lire NO_COLOR et WT_SESSION depuis ComputeColorSupportInternal()
214 // et les passer en paramètres, afin que cette fonction reste une fonction
215 // pure de ses entrées. Cela nécessite d'étendre la signature publique de
216 // Terminal::ComputeColorSupport(), c'est-à-dire un changement cassant l'API.
217
218 // 0. Préférence utilisateur. Voir https://no-color.org.
219 if (util::GetEnv("NO_COLOR")[0] != '\0') {
220 return Terminal::Color::Palette1;
221 }
222
223 // 1. Surcharges spécifiques à la plateforme.
224#if defined(__EMSCRIPTEN__)
225 return Terminal::Color::TrueColor;
226#endif
227#if defined(_WIN32)
228 // Vérifie si l'on s'exécute dans une console, et si cette console prend en charge le traitement VT.
229 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
230 DWORD out_mode = 0;
231 if (GetConsoleMode(stdout_handle, &out_mode)) {
232 const int enable_virtual_terminal_processing = 0x0004;
233 const int disable_newline_auto_return = 0x0008;
234 out_mode |= enable_virtual_terminal_processing;
235 out_mode |= disable_newline_auto_return;
236 if (!SetConsoleMode(stdout_handle, out_mode)) {
237 return Terminal::Color::Palette16;
238 }
239 }
240 return Terminal::Color::TrueColor;
241#endif
242
243 // Vérifie WT_SESSION pour Windows Terminal (par exemple lors de l'exécution sous WSL).
244 if (util::GetEnv("WT_SESSION")[0] != '\0') {
245 return Terminal::Color::TrueColor;
246 }
247
248 // 2. Variables d'environnement term / colorterm.
249 if (ContainsAny(impl_->colorterm, {"24bit", "truecolor"})) {
250 return Terminal::Color::TrueColor;
251 }
252 if (ContainsAny(impl_->term,
253 {"direct", "truecolor", "kitty", "alacritty", "foot"})) {
254 return Terminal::Color::TrueColor;
255 }
256 if (ContainsAny(impl_->colorterm, {"256"}) ||
257 ContainsAny(impl_->term, {"256", "xterm", "screen", "tmux"})) {
258 return Terminal::Color::Palette256;
259 }
260
261 // 3. term_program
262 if (ContainsAny(impl_->term_program, {
263 "iterm",
264 "vscode",
265 "warp",
266 "ghostty",
267 "wezterm",
268 })) {
269 return Terminal::Color::TrueColor;
270 }
271 // Terminal.app d'Apple (TERM_PROGRAM=Apple_Terminal) prend en charge 256
272 // couleurs, mais pas les 24 bits.
273 if (Contains(impl_->term_program, "apple_terminal")) {
274 return Terminal::Color::Palette256;
275 }
276
277 // 4. identification du terminal.
278 // Un nom vide signifie que le terminal n'a pas été identifié, comme pour
279 // "unknown".
280 if (!impl_->terminal_emulator_name.empty() &&
281 impl_->terminal_emulator_name != "unknown") {
282 return Terminal::Color::TrueColor;
283 }
284 if (impl_->terminal_name == "xterm") {
285 return Terminal::Color::TrueColor;
286 }
287 for (const int x : impl_->capabilities) {
288 // La valeur 22 est la capacité SGR pour 256 couleurs. Si le terminal la
289 // prend en charge, c'est une forte indication qu'il prend en charge 256
290 // couleurs. Ce n'est pas une méthode de détection parfaite, mais c'est une
291 // heuristique raisonnable en l'absence d'informations plus spécifiques.
292 if (x == 22) {
293 return Terminal::Color::Palette256;
294 }
295 }
296
297 return Terminal::Color::Palette16;
298}
299
300/// @brief Obtenir la taille du terminal.
301/// @return La taille du terminal.
302/// @ingroup screen
303Dimensions Size() {
304#if defined(__EMSCRIPTEN__)
305 // Cette dimension a été choisie arbitrairement pour pouvoir afficher :
306 // https://arthursonzogni.com/FTXUI/examples
307 // Cela devra être amélioré lorsque quelqu'un aura le temps de l'implémenter et en aura besoin.
308 return FallbackSize();
309#elif defined(_WIN32)
310 CONSOLE_SCREEN_BUFFER_INFO csbi;
311
312 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
313 return Dimensions{csbi.srWindow.Right - csbi.srWindow.Left + 1,
314 csbi.srWindow.Bottom - csbi.srWindow.Top + 1};
315 }
316
317 return FallbackSize();
318#else
319 winsize w{};
320 const int status = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); // NOLINT
321 // The ioctl return value result should be checked. Some operating systems
322 // don't support TIOCGWINSZ.
323 if (w.ws_col == 0 || w.ws_row == 0 || status < 0) {
324 return FallbackSize();
325 }
326 return Dimensions{w.ws_col, w.ws_row};
327#endif
328}
329
330/// @brief Outrepasser la taille du terminal en cas d'échec de la détection automatique.
331/// @param fallbackSize Dimensions du terminal à utiliser en cas de repli.
332void SetFallbackSize(const Dimensions& fallbackSize) {
333 FallbackSize() = fallbackSize;
334}
335
336/// @brief Obtenir le support couleur du terminal.
337/// @ingroup screen
338Color ColorSupport() {
339 if (!ColorSupportDetected()) {
340 GetQuirksInternal().SetColorSupport(ComputeColorSupportInternal());
341 ColorSupportDetected() = true;
342 }
343 return GetQuirksInternal().ColorSupport();
344}
345
346/// @brief Outrepasser le support couleur du terminal en cas d'échec de la détection automatique.
347/// @ingroup dom
348void SetColorSupport(Color color) {
349 GetQuirksInternal().SetColorSupport(color);
350 ColorSupportDetected() = true;
351}
352
353/// @brief Récupère les particularités du terminal.
354/// @ingroup screen
355Quirks GetQuirks() {
356 if (!ColorSupportDetected()) {
357 GetQuirksInternal().SetColorSupport(ComputeColorSupportInternal());
358 ColorSupportDetected() = true;
359 }
360 return GetQuirksInternal();
361}
362
363/// @brief Redéfinit les particularités du terminal.
364/// @ingroup screen
365void SetQuirks(const Quirks& quirks) {
366 GetQuirksInternal() = quirks;
367 ColorSupportDetected() = true;
368}
369
370} // namespace Terminal
371} // namespace ftxui
Color
Color est une énumération qui représente le support des couleurs du terminal.
Definition terminal.hpp:31
L'espace de noms ftxui::Terminal:: de FTXUI.
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)
Calcule la prise en charge des couleurs en fonction des variables d'environnement et de l'identificat...
Definition terminal.cpp:196
const char * GetEnv(const char *name)
Definition util.hpp:18
L'espace de noms FTXUI ftxui::
Definition animation.hpp:11