1#ifndef RANG_DOT_HPP
2#define RANG_DOT_HPP
3
4#if defined(__unix__) || defined(__unix) || defined(__linux__)
5#define OS_LINUX
6#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64)
7#define OS_WIN
8#elif defined(__APPLE__) || defined(__MACH__)
9#define OS_MAC
10#else
11#error Unknown Platform
12#endif
13
14#if defined(OS_LINUX) || defined(OS_MAC)
15#include <unistd.h>
16
17#elif defined(OS_WIN)
18
19#if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600)
20#error \
21 "Please include rang.hpp before any windows system headers or set _WIN32_WINNT at least to _WIN32_WINNT_VISTA"
22#elif !defined(_WIN32_WINNT)
23#define _WIN32_WINNT _WIN32_WINNT_VISTA
24#endif
25
26#include <windows.h>
27#include <io.h>
28#include <memory>
29
30// Only defined in windows 10 onwards, redefining in lower windows since it
31// doesn't gets used in lower versions
32// https://docs.microsoft.com/en-us/windows/console/getconsolemode
33#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
34#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
35#endif
36
37#endif
38
39#include <algorithm>
40#include <atomic>
41#include <cstdlib>
42#include <cstring>
43#include <iostream>
44
45namespace rang {
46
47/* For better compability with most of terminals do not use any style settings
48 * except of reset, bold and reversed.
49 * Note that on Windows terminals bold style is same as fgB color.
50 */
51enum class style {
52 reset = 0,
53 bold = 1,
54 dim = 2,
55 italic = 3,
56 underline = 4,
57 blink = 5,
58 rblink = 6,
59 reversed = 7,
60 conceal = 8,
61 crossed = 9
62};
63
64enum class fg {
65 black = 30,
66 red = 31,
67 green = 32,
68 yellow = 33,
69 blue = 34,
70 magenta = 35,
71 cyan = 36,
72 gray = 37,
73 reset = 39
74};
75
76enum class bg {
77 black = 40,
78 red = 41,
79 green = 42,
80 yellow = 43,
81 blue = 44,
82 magenta = 45,
83 cyan = 46,
84 gray = 47,
85 reset = 49
86};
87
88enum class fgB {
89 black = 90,
90 red = 91,
91 green = 92,
92 yellow = 93,
93 blue = 94,
94 magenta = 95,
95 cyan = 96,
96 gray = 97
97};
98
99enum class bgB {
100 black = 100,
101 red = 101,
102 green = 102,
103 yellow = 103,
104 blue = 104,
105 magenta = 105,
106 cyan = 106,
107 gray = 107
108};
109
110enum class control { // Behaviour of rang function calls
111 Off = 0, // toggle off rang style/color calls
112 Auto = 1, // (Default) autodect terminal and colorize if needed
113 Force = 2 // force ansi color output to non terminal streams
114};
115// Use rang::setControlMode to set rang control mode
116
117enum class winTerm { // Windows Terminal Mode
118 Auto = 0, // (Default) automatically detects wheter Ansi or Native API
119 Ansi = 1, // Force use Ansi API
120 Native = 2 // Force use Native API
121};
122// Use rang::setWinTermMode to explicitly set terminal API for Windows
123// Calling rang::setWinTermMode have no effect on other OS
124
125namespace rang_implementation {
126
127 inline std::atomic<control> &controlMode() noexcept
128 {
129 static std::atomic<control> value(control::Auto);
130 return value;
131 }
132
133 inline std::atomic<winTerm> &winTermMode() noexcept
134 {
135 static std::atomic<winTerm> termMode(winTerm::Auto);
136 return termMode;
137 }
138
139 inline bool supportsColor() noexcept
140 {
141#if defined(OS_LINUX) || defined(OS_MAC)
142
143 static const bool result = [] {
144 const char *Terms[]
145 = { "ansi", "color", "console", "cygwin", "gnome",
146 "konsole", "kterm", "linux", "msys", "putty",
147 "rxvt", "screen", "vt100", "xterm" };
148
149 const char *env_p = std::getenv("TERM");
150 if (env_p == nullptr) {
151 return false;
152 }
153 return std::any_of(std::begin(Terms), std::end(Terms),
154 [&](const char *term) {
155 return std::strstr(env_p, term) != nullptr;
156 });
157 }();
158
159#elif defined(OS_WIN)
160 // All windows versions support colors through native console methods
161 static constexpr bool result = true;
162#endif
163 return result;
164 }
165
166#ifdef OS_WIN
167
168
169 inline bool isMsysPty(int fd) noexcept
170 {
171 // Dynamic load for binary compability with old Windows
172 const auto ptrGetFileInformationByHandleEx
173 = reinterpret_cast<decltype(&GetFileInformationByHandleEx)>(
174 GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
175 "GetFileInformationByHandleEx"));
176 if (!ptrGetFileInformationByHandleEx) {
177 return false;
178 }
179
180 HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
181 if (h == INVALID_HANDLE_VALUE) {
182 return false;
183 }
184
185 // Check that it's a pipe:
186 if (GetFileType(h) != FILE_TYPE_PIPE) {
187 return false;
188 }
189
190 // POD type is binary compatible with FILE_NAME_INFO from WinBase.h
191 // It have the same alignment and used to avoid UB in caller code
192 struct MY_FILE_NAME_INFO {
193 DWORD FileNameLength;
194 WCHAR FileName[MAX_PATH];
195 };
196
197 auto pNameInfo = std::unique_ptr<MY_FILE_NAME_INFO>(
198 new (std::nothrow) MY_FILE_NAME_INFO());
199 if (!pNameInfo) {
200 return false;
201 }
202
203 // Check pipe name is template of
204 // {"cygwin-","msys-"}XXXXXXXXXXXXXXX-ptyX-XX
205 if (!ptrGetFileInformationByHandleEx(h, FileNameInfo, pNameInfo.get(),
206 sizeof(MY_FILE_NAME_INFO))) {
207 return false;
208 }
209 std::wstring name(pNameInfo->FileName, pNameInfo->FileNameLength / sizeof(WCHAR));
210 if ((name.find(L"msys-") == std::wstring::npos
211 && name.find(L"cygwin-") == std::wstring::npos)
212 || name.find(L"-pty") == std::wstring::npos) {
213 return false;
214 }
215
216 return true;
217 }
218
219#endif
220
221 inline bool isTerminal(const std::streambuf *osbuf) noexcept
222 {
223 using std::cerr;
224 using std::clog;
225 using std::cout;
226#if defined(OS_LINUX) || defined(OS_MAC)
227 if (osbuf == cout.rdbuf()) {
228 static const bool cout_term = isatty(fileno(stdout)) != 0;
229 return cout_term;
230 } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) {
231 static const bool cerr_term = isatty(fileno(stderr)) != 0;
232 return cerr_term;
233 }
234#elif defined(OS_WIN)
235 if (osbuf == cout.rdbuf()) {
236 static const bool cout_term
237 = (_isatty(_fileno(stdout)) || isMsysPty(_fileno(stdout)));
238 return cout_term;
239 } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) {
240 static const bool cerr_term
241 = (_isatty(_fileno(stderr)) || isMsysPty(_fileno(stderr)));
242 return cerr_term;
243 }
244#endif
245 return false;
246 }
247
248 template <typename T>
249 using enableStd = typename std::enable_if<
250 std::is_same<T, rang::style>::value || std::is_same<T, rang::fg>::value
251 || std::is_same<T, rang::bg>::value || std::is_same<T, rang::fgB>::value
252 || std::is_same<T, rang::bgB>::value,
253 std::ostream &>::type;
254
255
256#ifdef OS_WIN
257
258 struct SGR { // Select Graphic Rendition parameters for Windows console
259 BYTE fgColor; // foreground color (0-15) lower 3 rgb bits + intense bit
260 BYTE bgColor; // background color (0-15) lower 3 rgb bits + intense bit
261 BYTE bold; // emulated as FOREGROUND_INTENSITY bit
262 BYTE underline; // emulated as BACKGROUND_INTENSITY bit
263 BOOLEAN inverse; // swap foreground/bold & background/underline
264 BOOLEAN conceal; // set foreground/bold to background/underline
265 };
266
267 enum class AttrColor : BYTE { // Color attributes for console screen buffer
268 black = 0,
269 red = 4,
270 green = 2,
271 yellow = 6,
272 blue = 1,
273 magenta = 5,
274 cyan = 3,
275 gray = 7
276 };
277
278 inline HANDLE getConsoleHandle(const std::streambuf *osbuf) noexcept
279 {
280 if (osbuf == std::cout.rdbuf()) {
281 static const HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
282 return hStdout;
283 } else if (osbuf == std::cerr.rdbuf() || osbuf == std::clog.rdbuf()) {
284 static const HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE);
285 return hStderr;
286 }
287 return INVALID_HANDLE_VALUE;
288 }
289
290 inline bool setWinTermAnsiColors(const std::streambuf *osbuf) noexcept
291 {
292 HANDLE h = getConsoleHandle(osbuf);
293 if (h == INVALID_HANDLE_VALUE) {
294 return false;
295 }
296 DWORD dwMode = 0;
297 if (!GetConsoleMode(h, &dwMode)) {
298 return false;
299 }
300 dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
301 if (!SetConsoleMode(h, dwMode)) {
302 return false;
303 }
304 return true;
305 }
306
307 inline bool supportsAnsi(const std::streambuf *osbuf) noexcept
308 {
309 using std::cerr;
310 using std::clog;
311 using std::cout;
312 if (osbuf == cout.rdbuf()) {
313 static const bool cout_ansi
314 = (isMsysPty(_fileno(stdout)) || setWinTermAnsiColors(osbuf));
315 return cout_ansi;
316 } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) {
317 static const bool cerr_ansi
318 = (isMsysPty(_fileno(stderr)) || setWinTermAnsiColors(osbuf));
319 return cerr_ansi;
320 }
321 return false;
322 }
323
324 inline const SGR &defaultState() noexcept
325 {
326 static const SGR defaultSgr = []() -> SGR {
327 CONSOLE_SCREEN_BUFFER_INFO info;
328 WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
329 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
330 &info)
331 || GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE),
332 &info)) {
333 attrib = info.wAttributes;
334 }
335 SGR sgr = { 0, 0, 0, 0, FALSE, FALSE };
336 sgr.fgColor = attrib & 0x0F;
337 sgr.bgColor = (attrib & 0xF0) >> 4;
338 return sgr;
339 }();
340 return defaultSgr;
341 }
342
343 inline BYTE ansi2attr(BYTE rgb) noexcept
344 {
345 static const AttrColor rev[8]
346 = { AttrColor::black, AttrColor::red, AttrColor::green,
347 AttrColor::yellow, AttrColor::blue, AttrColor::magenta,
348 AttrColor::cyan, AttrColor::gray };
349 return static_cast<BYTE>(rev[rgb]);
350 }
351
352 inline void setWinSGR(rang::bg col, SGR &state) noexcept
353 {
354 if (col != rang::bg::reset) {
355 state.bgColor = ansi2attr(static_cast<BYTE>(col) - 40);
356 } else {
357 state.bgColor = defaultState().bgColor;
358 }
359 }
360
361 inline void setWinSGR(rang::fg col, SGR &state) noexcept
362 {
363 if (col != rang::fg::reset) {
364 state.fgColor = ansi2attr(static_cast<BYTE>(col) - 30);
365 } else {
366 state.fgColor = defaultState().fgColor;
367 }
368 }
369
370 inline void setWinSGR(rang::bgB col, SGR &state) noexcept
371 {
372 state.bgColor = (BACKGROUND_INTENSITY >> 4)
373 | ansi2attr(static_cast<BYTE>(col) - 100);
374 }
375
376 inline void setWinSGR(rang::fgB col, SGR &state) noexcept
377 {
378 state.fgColor
379 = FOREGROUND_INTENSITY | ansi2attr(static_cast<BYTE>(col) - 90);
380 }
381
382 inline void setWinSGR(rang::style style, SGR &state) noexcept
383 {
384 switch (style) {
385 case rang::style::reset: state = defaultState(); break;
386 case rang::style::bold: state.bold = FOREGROUND_INTENSITY; break;
387 case rang::style::underline:
388 case rang::style::blink:
389 state.underline = BACKGROUND_INTENSITY;
390 break;
391 case rang::style::reversed: state.inverse = TRUE; break;
392 case rang::style::conceal: state.conceal = TRUE; break;
393 default: break;
394 }
395 }
396
397 inline SGR &current_state() noexcept
398 {
399 static SGR state = defaultState();
400 return state;
401 }
402
403 inline WORD SGR2Attr(const SGR &state) noexcept
404 {
405 WORD attrib = 0;
406 if (state.conceal) {
407 if (state.inverse) {
408 attrib = (state.fgColor << 4) | state.fgColor;
409 if (state.bold)
410 attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
411 } else {
412 attrib = (state.bgColor << 4) | state.bgColor;
413 if (state.underline)
414 attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
415 }
416 } else if (state.inverse) {
417 attrib = (state.fgColor << 4) | state.bgColor;
418 if (state.bold) attrib |= BACKGROUND_INTENSITY;
419 if (state.underline) attrib |= FOREGROUND_INTENSITY;
420 } else {
421 attrib = state.fgColor | (state.bgColor << 4) | state.bold
422 | state.underline;
423 }
424 return attrib;
425 }
426
427 template <typename T>
428 inline void setWinColorAnsi(std::ostream &os, T const value)
429 {
430 os << "\033[" << static_cast<int>(value) << "m";
431 }
432
433 template <typename T>
434 inline void setWinColorNative(std::ostream &os, T const value)
435 {
436 const HANDLE h = getConsoleHandle(os.rdbuf());
437 if (h != INVALID_HANDLE_VALUE) {
438 setWinSGR(value, current_state());
439 // Out all buffered text to console with previous settings:
440 os.flush();
441 SetConsoleTextAttribute(h, SGR2Attr(current_state()));
442 }
443 }
444
445 template <typename T>
446 inline enableStd<T> setColor(std::ostream &os, T const value)
447 {
448 if (winTermMode() == winTerm::Auto) {
449 if (supportsAnsi(os.rdbuf())) {
450 setWinColorAnsi(os, value);
451 } else {
452 setWinColorNative(os, value);
453 }
454 } else if (winTermMode() == winTerm::Ansi) {
455 setWinColorAnsi(os, value);
456 } else {
457 setWinColorNative(os, value);
458 }
459 return os;
460 }
461#else
462 template <typename T>
463 inline enableStd<T> setColor(std::ostream &os, T const value)
464 {
465 return os << "\033[" << static_cast<int>(value) << "m";
466 }
467#endif
468} // namespace rang_implementation
469
470template <typename T>
471inline rang_implementation::enableStd<T> operator<<(std::ostream &os,
472 const T value)
473{
474 const control option = rang_implementation::controlMode();
475 switch (option) {
476 case control::Auto:
477 return rang_implementation::supportsColor()
478 && rang_implementation::isTerminal(os.rdbuf())
479 ? rang_implementation::setColor(os, value)
480 : os;
481 case control::Force: return rang_implementation::setColor(os, value);
482 default: return os;
483 }
484}
485
486inline void setWinTermMode(const rang::winTerm value) noexcept
487{
488 rang_implementation::winTermMode() = value;
489}
490
491inline void setControlMode(const control value) noexcept
492{
493 rang_implementation::controlMode() = value;
494}
495
496} // namespace rang
497
498#undef OS_LINUX
499#undef OS_WIN
500#undef OS_MAC
501
502#endif /* ifndef RANG_DOT_HPP */
503