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 | |
45 | namespace 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 | */ |
51 | enum 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 | |
64 | enum 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 | |
76 | enum 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 | |
88 | enum 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 | |
99 | enum 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 | |
110 | enum 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 | |
117 | enum 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 | |
125 | namespace 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 ¤t_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 | |
470 | template <typename T> |
471 | inline 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 | |
486 | inline void setWinTermMode(const rang::winTerm value) noexcept |
487 | { |
488 | rang_implementation::winTermMode() = value; |
489 | } |
490 | |
491 | inline 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 | |