FTXUI 7.0.1
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 <cstdlib> // for getenv
7#include <initializer_list>
8#include <string>
9#include <string_view> // for string_view
10
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 // This dimension was chosen arbitrarily to be able to display:
55 // https://arthursonzogni.com/FTXUI/examples
56 // This will have to be improved when someone has time to implement and need
57 // it.
58 constexpr int fallback_width = 140;
59 constexpr int fallback_height = 43;
60#else
61 // The terminal size in VT100 was 80x24. It is still used nowadays by
62 // default in many terminal emulator. That's a good choice for a fallback
63 // value.
64 constexpr int fallback_width = 80;
65 constexpr int fallback_height = 24;
66#endif
67 static Dimensions g_fallback_size{
68 fallback_width,
69 fallback_height,
70 };
71 return g_fallback_size;
72}
73
74const char* Safe(const char* c) {
75 return (c != nullptr) ? c : "";
76}
77
78bool Contains(std::string_view s, std::string_view key) {
79 if (key.empty()) {
80 return true;
81 }
82 const auto it = std::search( // NOLINT
83 s.begin(), s.end(), key.begin(), key.end(), [](char a, char b) {
84 return std::tolower(static_cast<unsigned char>(a)) ==
85 std::tolower(static_cast<unsigned char>(b));
86 });
87 return it != s.end();
88}
89
90bool ContainsAny(std::string_view s,
91 std::initializer_list<std::string_view> keys) {
92 for (const std::string_view key : keys) {
93 if (Contains(s, key)) {
94 return true;
95 }
96 }
97 return false;
98}
99
100Terminal::Color ComputeColorSupportInternal() {
101 static const std::vector<int> empty_capabilities;
103 Safe(std::getenv("TERM")), // NOLINT
104 Safe(std::getenv("COLORTERM")), // NOLINT
105 Safe(std::getenv("TERM_PROGRAM")), // NOLINT
106 "unknown", "unknown", empty_capabilities);
107}
108
109} // namespace
110
111namespace Terminal {
112
113struct Quirks::Impl {
114 bool block_characters = true;
115 bool cursor_hiding = true;
116 bool component_ascii = false;
117 Color color_support = Palette256;
118};
119
120Quirks::Quirks() : impl_(std::make_unique<Impl>()) {}
121Quirks::~Quirks() = default;
122Quirks::Quirks(const Quirks& other)
123 : impl_(std::make_unique<Impl>(*other.impl_)) {}
124Quirks& Quirks::operator=(const Quirks& other) {
125 if (this != &other) {
126 *impl_ = *other.impl_;
127 }
128 return *this;
129}
130Quirks::Quirks(Quirks&&) noexcept = default;
131Quirks& Quirks::operator=(Quirks&&) noexcept = default;
132
133bool Quirks::BlockCharacters() const {
134 return impl_->block_characters;
135}
136void Quirks::SetBlockCharacters(bool v) {
137 impl_->block_characters = v;
138}
139
140bool Quirks::CursorHiding() const {
141 return impl_->cursor_hiding;
142}
143void Quirks::SetCursorHiding(bool v) {
144 impl_->cursor_hiding = v;
145}
146
147bool Quirks::ComponentAscii() const {
148 return impl_->component_ascii;
149}
150void Quirks::SetComponentAscii(bool v) {
151 impl_->component_ascii = v;
152}
153
154Color Quirks::ColorSupport() const {
155 return impl_->color_support;
156}
157void Quirks::SetColorSupport(Color v) {
158 impl_->color_support = v;
159}
160
161struct TerminalInfo::Impl {
162 std::string term;
163 std::string colorterm;
164 std::string term_program;
165 std::string terminal_name;
166 std::string terminal_emulator_name;
167 std::vector<int> capabilities;
168};
169
170TerminalInfo::TerminalInfo() : impl_(std::make_unique<Impl>()) {}
171TerminalInfo::~TerminalInfo() = default;
172TerminalInfo::TerminalInfo(TerminalInfo&&) noexcept = default;
173TerminalInfo& TerminalInfo::operator=(TerminalInfo&&) noexcept = default;
174
175void TerminalInfo::SetTerm(std::string_view term) {
176 impl_->term = term;
177}
178void TerminalInfo::SetColorterm(std::string_view colorterm) {
179 impl_->colorterm = colorterm;
180}
181void TerminalInfo::SetTermProgram(std::string_view term_program) {
182 impl_->term_program = term_program;
183}
184void TerminalInfo::SetTerminalName(std::string_view terminal_name) {
185 impl_->terminal_name = terminal_name;
186}
187void TerminalInfo::SetTerminalEmulatorName(
188 std::string_view terminal_emulator_name) {
189 impl_->terminal_emulator_name = terminal_emulator_name;
190}
191void TerminalInfo::SetCapabilities(std::vector<int> capabilities) {
192 impl_->capabilities = std::move(capabilities);
193}
194
195/// @brief Compute the color support based on environment variables and terminal
196/// identification.
197/// @param term The TERM environment variable.
198/// @param colorterm The COLORTERM environment variable.
199/// @param term_program The TERM_PROGRAM environment variable.
200/// @param terminal_name The terminal name (from DA2).
201/// @param terminal_emulator_name The terminal emulator name (from XTVERSION).
202/// @param capabilities The terminal capabilities (from DA1).
203Color ComputeColorSupport(std::string_view term,
204 std::string_view colorterm,
205 std::string_view term_program,
206 std::string_view terminal_name,
207 std::string_view terminal_emulator_name,
208 const std::vector<int>& capabilities) {
209 TerminalInfo info;
210 info.SetTerm(term);
211 info.SetColorterm(colorterm);
212 info.SetTermProgram(term_program);
213 info.SetTerminalName(terminal_name);
214 info.SetTerminalEmulatorName(terminal_emulator_name);
215 info.SetCapabilities(capabilities);
216 return info.ComputeColorSupport();
217}
218
219Color TerminalInfo::ComputeColorSupport() const {
220 // TODO(v8): Read NO_COLOR and WT_SESSION from ComputeColorSupportInternal()
221 // and pass them in as parameters, so that this function remains a pure
222 // function of its inputs. This requires extending the public
223 // Terminal::ComputeColorSupport() signature, i.e. an API-breaking change.
224
225 // 0. User preference. See https://no-color.org.
226 const char* no_color = std::getenv("NO_COLOR"); // NOLINT
227 if (no_color != nullptr && no_color[0] != '\0') {
228 return Terminal::Color::Palette1;
229 }
230
231 // 1. Platform specific overrides.
232#if defined(__EMSCRIPTEN__)
233 return Terminal::Color::TrueColor;
234#endif
235#if defined(_WIN32)
236 // Check if we are running in a console, and if that console supports VT processing.
237 auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
238 DWORD out_mode = 0;
239 if (GetConsoleMode(stdout_handle, &out_mode)) {
240 const int enable_virtual_terminal_processing = 0x0004;
241 const int disable_newline_auto_return = 0x0008;
242 out_mode |= enable_virtual_terminal_processing;
243 out_mode |= disable_newline_auto_return;
244 if (!SetConsoleMode(stdout_handle, out_mode)) {
245 return Terminal::Color::Palette16;
246 }
247 }
248 return Terminal::Color::TrueColor;
249#endif
250
251 // Check WT_SESSION for Windows Terminal (e.g. when running under WSL).
252 const char* wt_session = std::getenv("WT_SESSION"); // NOLINT
253 if (wt_session != nullptr && wt_session[0] != '\0') {
254 return Terminal::Color::TrueColor;
255 }
256
257 // 2. term / colorterm environment variables.
258 if (ContainsAny(impl_->colorterm, {"24bit", "truecolor"})) {
259 return Terminal::Color::TrueColor;
260 }
261 if (ContainsAny(impl_->term,
262 {"direct", "truecolor", "kitty", "alacritty", "foot"})) {
263 return Terminal::Color::TrueColor;
264 }
265 if (ContainsAny(impl_->colorterm, {"256"}) ||
266 ContainsAny(impl_->term, {"256", "xterm", "screen", "tmux"})) {
267 return Terminal::Color::Palette256;
268 }
269
270 // 3. term_program
271 if (ContainsAny(impl_->term_program, {
272 "iterm",
273 "vscode",
274 "warp",
275 "ghostty",
276 "wezterm",
277 })) {
278 return Terminal::Color::TrueColor;
279 }
280 // Apple's Terminal.app (TERM_PROGRAM=Apple_Terminal) supports 256 colors,
281 // but not 24bit ones.
282 if (Contains(impl_->term_program, "apple_terminal")) {
283 return Terminal::Color::Palette256;
284 }
285
286 // 4. terminal identification.
287 // An empty name means the terminal was not identified, the same as
288 // "unknown".
289 if (!impl_->terminal_emulator_name.empty() &&
290 impl_->terminal_emulator_name != "unknown") {
291 return Terminal::Color::TrueColor;
292 }
293 if (impl_->terminal_name == "xterm") {
294 return Terminal::Color::TrueColor;
295 }
296 for (const int x : impl_->capabilities) {
297 // The value 22 is the SGR capability for 256 colors. If the terminal
298 // supports it, it is a strong indication that the terminal supports 256
299 // colors. This is not a perfect detection method, but it is a reasonable
300 // heuristic in the absence of more specific information.
301 if (x == 22) {
302 return Terminal::Color::Palette256;
303 }
304 }
305
306 return Terminal::Color::Palette16;
307}
308
309/// @brief Get the terminal size.
310/// @return The terminal size.
311/// @ingroup screen
312Dimensions Size() {
313#if defined(__EMSCRIPTEN__)
314 // This dimension was chosen arbitrarily to be able to display:
315 // https://arthursonzogni.com/FTXUI/examples
316 // This will have to be improved when someone has time to implement and need
317 // it.
318 return FallbackSize();
319#elif defined(_WIN32)
320 CONSOLE_SCREEN_BUFFER_INFO csbi;
321
322 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
323 return Dimensions{csbi.srWindow.Right - csbi.srWindow.Left + 1,
324 csbi.srWindow.Bottom - csbi.srWindow.Top + 1};
325 }
326
327 return FallbackSize();
328#else
329 winsize w{};
330 const int status = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); // NOLINT
331 // The ioctl return value result should be checked. Some operating systems
332 // don't support TIOCGWINSZ.
333 if (w.ws_col == 0 || w.ws_row == 0 || status < 0) {
334 return FallbackSize();
335 }
336 return Dimensions{w.ws_col, w.ws_row};
337#endif
338}
339
340/// @brief Override terminal size in case auto-detection fails
341/// @param fallbackSize Terminal dimensions to fallback to
342void SetFallbackSize(const Dimensions& fallbackSize) {
343 FallbackSize() = fallbackSize;
344}
345
346/// @brief Get the color support of the terminal.
347/// @ingroup screen
348Color ColorSupport() {
349 if (!ColorSupportDetected()) {
350 GetQuirksInternal().SetColorSupport(ComputeColorSupportInternal());
351 ColorSupportDetected() = true;
352 }
353 return GetQuirksInternal().ColorSupport();
354}
355
356/// @brief Override terminal color support in case auto-detection fails
357/// @ingroup dom
358void SetColorSupport(Color color) {
359 GetQuirksInternal().SetColorSupport(color);
360 ColorSupportDetected() = true;
361}
362
363/// @brief Get the terminal quirks.
364/// @ingroup screen
365Quirks GetQuirks() {
366 if (!ColorSupportDetected()) {
367 GetQuirksInternal().SetColorSupport(ComputeColorSupportInternal());
368 ColorSupportDetected() = true;
369 }
370 return GetQuirksInternal();
371}
372
373/// @brief Override terminal quirks.
374/// @ingroup screen
375void SetQuirks(const Quirks& quirks) {
376 GetQuirksInternal() = quirks;
377 ColorSupportDetected() = true;
378}
379
380} // namespace Terminal
381} // namespace ftxui
Color
Color is an enumeration that represents the color support of the terminal.
Definition terminal.hpp:31
The FTXUI ftxui::Terminal:: namespace.
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:203
The FTXUI ftxui:: namespace.
Definition animation.hpp:11