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