FTXUI 7.0.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 <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
33bool g_color_support_detected = false; // NOLINT
34Terminal::Quirks g_quirks = [] { // NOLINT
35 Terminal::Quirks quirks;
36#if defined(_WIN32)
37 quirks.SetBlockCharacters(false);
38 quirks.SetCursorHiding(false);
39 quirks.SetComponentAscii(true);
40#endif
41 return quirks;
42}();
43
44Dimensions& FallbackSize() {
45#if defined(__EMSCRIPTEN__)
46 // This dimension was chosen arbitrarily to be able to display:
47 // https://arthursonzogni.com/FTXUI/examples
48 // This will have to be improved when someone has time to implement and need
49 // it.
50 constexpr int fallback_width = 140;
51 constexpr int fallback_height = 43;
52#else
53 // The terminal size in VT100 was 80x24. It is still used nowadays by
54 // default in many terminal emulator. That's a good choice for a fallback
55 // value.
56 constexpr int fallback_width = 80;
57 constexpr int fallback_height = 24;
58#endif
59 static Dimensions g_fallback_size{
60 fallback_width,
61 fallback_height,
62 };
63 return g_fallback_size;
64}
65
66const char* Safe(const char* c) {
67 return (c != nullptr) ? c : "";
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 Safe(std::getenv("TERM")), // NOLINT
96 Safe(std::getenv("COLORTERM")), // NOLINT
97 Safe(std::getenv("TERM_PROGRAM")), // NOLINT
98 "unknown", "unknown", empty_capabilities);
99}
100
101} // namespace
102
103namespace Terminal {
104
105struct Quirks::Impl {
106 bool block_characters = true;
107 bool cursor_hiding = true;
108 bool component_ascii = false;
109 Color color_support = Palette256;
110};
111
112Quirks::Quirks() : impl_(std::make_unique<Impl>()) {}
113Quirks::~Quirks() = default;
114Quirks::Quirks(const Quirks& other)
115 : impl_(std::make_unique<Impl>(*other.impl_)) {}
116Quirks& Quirks::operator=(const Quirks& other) {
117 if (this != &other) {
118 *impl_ = *other.impl_;
119 }
120 return *this;
121}
122Quirks::Quirks(Quirks&&) noexcept = default;
123Quirks& Quirks::operator=(Quirks&&) noexcept = default;
124
125bool Quirks::BlockCharacters() const {
126 return impl_->block_characters;
127}
128void Quirks::SetBlockCharacters(bool v) {
129 impl_->block_characters = v;
130}
131
132bool Quirks::CursorHiding() const {
133 return impl_->cursor_hiding;
134}
135void Quirks::SetCursorHiding(bool v) {
136 impl_->cursor_hiding = v;
137}
138
139bool Quirks::ComponentAscii() const {
140 return impl_->component_ascii;
141}
142void Quirks::SetComponentAscii(bool v) {
143 impl_->component_ascii = v;
144}
145
146Color Quirks::ColorSupport() const {
147 return impl_->color_support;
148}
149void Quirks::SetColorSupport(Color v) {
150 impl_->color_support = v;
151}
152
153struct TerminalInfo::Impl {
154 std::string term;
155 std::string colorterm;
156 std::string term_program;
157 std::string terminal_name;
158 std::string terminal_emulator_name;
159 std::vector<int> capabilities;
160};
161
162TerminalInfo::TerminalInfo() : impl_(std::make_unique<Impl>()) {}
163TerminalInfo::~TerminalInfo() = default;
164TerminalInfo::TerminalInfo(TerminalInfo&&) noexcept = default;
165TerminalInfo& TerminalInfo::operator=(TerminalInfo&&) noexcept = default;
166
167void TerminalInfo::SetTerm(std::string_view term) {
168 impl_->term = term;
169}
170void TerminalInfo::SetColorterm(std::string_view colorterm) {
171 impl_->colorterm = colorterm;
172}
173void TerminalInfo::SetTermProgram(std::string_view term_program) {
174 impl_->term_program = term_program;
175}
176void TerminalInfo::SetTerminalName(std::string_view terminal_name) {
177 impl_->terminal_name = terminal_name;
178}
179void TerminalInfo::SetTerminalEmulatorName(
180 std::string_view terminal_emulator_name) {
181 impl_->terminal_emulator_name = terminal_emulator_name;
182}
183void TerminalInfo::SetCapabilities(std::vector<int> capabilities) {
184 impl_->capabilities = std::move(capabilities);
185}
186
187/// @brief Compute the color support based on environment variables and terminal
188/// identification.
189/// @param term The TERM environment variable.
190/// @param colorterm The COLORTERM environment variable.
191/// @param term_program The TERM_PROGRAM environment variable.
192/// @param terminal_name The terminal name (from DA2).
193/// @param terminal_emulator_name The terminal emulator name (from XTVERSION).
194/// @param capabilities The terminal capabilities (from DA1).
195Color ComputeColorSupport(std::string_view term,
196 std::string_view colorterm,
197 std::string_view term_program,
198 std::string_view terminal_name,
199 std::string_view terminal_emulator_name,
200 const std::vector<int>& capabilities) {
201 TerminalInfo info;
202 info.SetTerm(term);
203 info.SetColorterm(colorterm);
204 info.SetTermProgram(term_program);
205 info.SetTerminalName(terminal_name);
206 info.SetTerminalEmulatorName(terminal_emulator_name);
207 info.SetCapabilities(capabilities);
208 return info.ComputeColorSupport();
209}
210
211Color TerminalInfo::ComputeColorSupport() const {
212 // 0. Platform specific overrides.
213#if defined(__EMSCRIPTEN__)
214 return Terminal::Color::TrueColor;
215#endif
216
217 // 1. term / colorterm environment variables.
218 if (ContainsAny(impl_->colorterm, {"24bit", "truecolor"})) {
219 return Terminal::Color::TrueColor;
220 }
221 if (ContainsAny(impl_->term,
222 {"direct", "truecolor", "kitty", "alacritty", "foot"})) {
223 return Terminal::Color::TrueColor;
224 }
225 if (ContainsAny(impl_->colorterm, {"256"}) ||
226 ContainsAny(impl_->term, {"256", "xterm", "screen", "tmux"})) {
227 return Terminal::Color::Palette256;
228 }
229
230 // 2. term_program
231 if (ContainsAny(impl_->term_program, {
232 "iterm",
233 "apple_terminal",
234 "vscode",
235 "warp",
236 "ghostty",
237 "wezterm",
238 })) {
239 return Terminal::Color::TrueColor;
240 }
241 if (Contains(impl_->term_program, "iterm")) {
242 return Terminal::Color::Palette256;
243 }
244
245 // 3. terminal identification.
246 if (impl_->terminal_emulator_name != "unknown") {
247 return Terminal::Color::TrueColor;
248 }
249 if (impl_->terminal_name == "xterm") {
250 return Terminal::Color::TrueColor;
251 }
252 for (const int x : impl_->capabilities) {
253 // The value 22 is the SGR capability for 256 colors. If the terminal
254 // supports it, it is a strong indication that the terminal supports 256
255 // colors. This is not a perfect detection method, but it is a reasonable
256 // heuristic in the absence of more specific information.
257 if (x == 22) {
258 return Terminal::Color::Palette256;
259 }
260 }
261
262 return Terminal::Color::Palette16;
263}
264
265/// @brief Get the terminal size.
266/// @return The terminal size.
267/// @ingroup screen
268Dimensions Size() {
269#if defined(__EMSCRIPTEN__)
270 // This dimension was chosen arbitrarily to be able to display:
271 // https://arthursonzogni.com/FTXUI/examples
272 // This will have to be improved when someone has time to implement and need
273 // it.
274 return FallbackSize();
275#elif defined(_WIN32)
276 CONSOLE_SCREEN_BUFFER_INFO csbi;
277
278 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
279 return Dimensions{csbi.srWindow.Right - csbi.srWindow.Left + 1,
280 csbi.srWindow.Bottom - csbi.srWindow.Top + 1};
281 }
282
283 return FallbackSize();
284#else
285 winsize w{};
286 const int status = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); // NOLINT
287 // The ioctl return value result should be checked. Some operating systems
288 // don't support TIOCGWINSZ.
289 if (w.ws_col == 0 || w.ws_row == 0 || status < 0) {
290 return FallbackSize();
291 }
292 return Dimensions{w.ws_col, w.ws_row};
293#endif
294}
295
296/// @brief Override terminal size in case auto-detection fails
297/// @param fallbackSize Terminal dimensions to fallback to
298void SetFallbackSize(const Dimensions& fallbackSize) {
299 FallbackSize() = fallbackSize;
300}
301
302/// @brief Get the color support of the terminal.
303/// @ingroup screen
304Color ColorSupport() {
305 if (!g_color_support_detected) {
306 g_quirks.SetColorSupport(ComputeColorSupportInternal());
307 g_color_support_detected = true;
308 }
309 return g_quirks.ColorSupport();
310}
311
312/// @brief Override terminal color support in case auto-detection fails
313/// @ingroup dom
314void SetColorSupport(Color color) {
315 g_quirks.SetColorSupport(color);
316 g_color_support_detected = true;
317}
318
319/// @brief Get the terminal quirks.
320/// @ingroup screen
321Quirks GetQuirks() {
322 if (!g_color_support_detected) {
323 g_quirks.SetColorSupport(ComputeColorSupportInternal());
324 g_color_support_detected = true;
325 }
326 return g_quirks;
327}
328
329/// @brief Override terminal quirks.
330/// @ingroup screen
331void SetQuirks(const Quirks& quirks) {
332 g_quirks = quirks;
333 g_color_support_detected = true;
334}
335
336} // namespace Terminal
337} // 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:195
The FTXUI ftxui:: namespace.
Definition animation.hpp:11