Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcoloroutput.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <QtCore/qfile.h>
8#include <QtCore/qhash.h>
9
10#ifdef Q_OS_WIN
11#include <qt_windows.h>
12#else
13#include <unistd.h>
14#endif
15
16QT_BEGIN_NAMESPACE
17
18class QColorOutputPrivate
19{
20public:
21 QColorOutputPrivate()
22 {
23 m_coloringEnabled = isColoringPossible();
24 }
25
26 ~QColorOutputPrivate() { fflush(stderr); }
27
28 static const char *const foregrounds[];
29 static const char *const backgrounds[];
30
31 inline void write(const QString &msg)
32 {
33 m_buffer.append(msg.toLocal8Bit());
34 }
35
36 static QString escapeCode(const QString &in)
37 {
38 const ushort escapeChar = 0x1B;
39 QString result;
40 result.append(QChar(escapeChar));
41 result.append(QLatin1Char('['));
42 result.append(in);
43 result.append(QLatin1Char('m'));
44 return result;
45 }
46
47 void insertColor(int id, QColorOutput::ColorCode code) { m_colorMapping.insert(id, code); }
48 QColorOutput::ColorCode color(int id) const { return m_colorMapping.value(id); }
49 bool containsColor(int id) const { return m_colorMapping.contains(id); }
50
51 void setSilent(bool silent) { m_silent = silent; }
52 bool isSilent() const { return m_silent; }
53
54 void setCurrentColorID(int colorId) { m_currentColorID = colorId; }
55
56 bool coloringEnabled() const { return m_coloringEnabled; }
57
58 void flushBuffer()
59 {
60 fwrite(m_buffer.constData(), size_t(1), size_t(m_buffer.size()), stderr);
61 m_buffer.clear();
62 }
63
64 void discardBuffer()
65 {
66 m_buffer.clear();
67 }
68
69private:
70 QByteArray m_buffer;
71 QColorOutput::ColorMapping m_colorMapping;
72 int m_currentColorID = -1;
73 bool m_coloringEnabled = false;
74 bool m_silent = false;
75
76 /*
77 Returns true if it's suitable to send colored output to \c stderr.
78 */
79 inline bool isColoringPossible() const
80 {
81 static std::optional<bool> canColor;
82 if (canColor.has_value())
83 return canColor.value();
84
85#if defined(Q_OS_WIN)
86 HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE);
87 DWORD mode = 0;
88
89 if (GetConsoleMode(hErr, &mode))
90 canColor = SetConsoleMode(hErr, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
91 else
92 canColor = false;
93#else
94 /* We use QFile::handle() to get the file descriptor. It's a bit unsure
95 * whether it's 2 on all platforms and in all cases, so hopefully this layer
96 * of abstraction helps handle such cases. */
97 canColor = isatty(fileno(stderr));
98#endif
99 return canColor.value();
100 }
101};
102
103const char *const QColorOutputPrivate::foregrounds[] =
104{
105 "0;30",
106 "0;34",
107 "0;32",
108 "0;36",
109 "0;31",
110 "0;35",
111 "0;33",
112 "0;37",
113 "1;30",
114 "1;34",
115 "1;32",
116 "1;36",
117 "1;31",
118 "1;35",
119 "1;33",
120 "1;37"
121};
122
123const char *const QColorOutputPrivate::backgrounds[] =
124{
125 "0;40",
126 "0;44",
127 "0;42",
128 "0;46",
129 "0;41",
130 "0;45",
131 "0;43"
132};
133
134/*!
135 \class QColorOutput
136 \nonreentrant
137 \brief Outputs colored messages to \c stderr.
138 \internal
139
140 QColorOutput is a convenience class for outputting messages to \c
141 stderr using color escape codes, as mandated in ECMA-48. QColorOutput
142 will only color output when it is detected to be suitable. For
143 instance, if \c stderr is detected to be attached to a file instead
144 of a TTY, no coloring will be done.
145
146 QColorOutput does its best attempt. but it is generally undefined
147 what coloring or effect the various coloring flags has. It depends
148 strongly on what terminal software that is being used.
149
150 When using `echo -e 'my escape sequence'`, \c{\033} works as an
151 initiator but not when printing from a C++ program, despite having
152 escaped the backslash. That's why we below use characters with
153 value 0x1B.
154
155 It can be convenient to subclass QColorOutput with a private scope,
156 such that the functions are directly available in the class using
157 it.
158
159 \section1 Usage
160
161 To output messages, call write() or writeUncolored(). write() takes
162 as second argument an integer, which QColorOutput uses as a lookup
163 key to find the color it should color the text in. The mapping from
164 keys to colors is done using insertMapping(). Typically this is used
165 by having enums for the various kinds of messages, which
166 subsequently are registered.
167
168 \code
169 enum MyMessage
170 {
171 Error,
172 Important
173 };
174
175 QColorOutput output;
176 output.insertMapping(Error, QColorOutput::RedForeground);
177 output.insertMapping(Import, QColorOutput::BlueForeground);
178
179 output.write("This is important", Important);
180 output.write("Jack, I'm only the selected official!", Error);
181 \endcode
182
183 \sa {http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html}{Bash Prompt HOWTO, 6.1. Colors},
184 {http://linuxgazette.net/issue51/livingston-blade.html}{Linux Gazette, Tweaking Eterm, Edward Livingston-Blade},
185 {http://www.ecma-international.org/publications/standards/Ecma-048.htm}{Standard ECMA-48, Control Functions for Coded Character Sets, ECMA International},
186 {http://en.wikipedia.org/wiki/ANSI_escape_code}{Wikipedia, ANSI escape code},
187 {http://linuxgazette.net/issue65/padala.html}{Linux Gazette, So You Like Color!, Pradeep Padala}
188 */
189
190/*!
191 \internal
192 \enum QColorOutput::ColorCodeComponent
193 \value BlackForeground
194 \value BlueForeground
195 \value GreenForeground
196 \value CyanForeground
197 \value RedForeground
198 \value PurpleForeground
199 \value BrownForeground
200 \value LightGrayForeground
201 \value DarkGrayForeground
202 \value LightBlueForeground
203 \value LightGreenForeground
204 \value LightCyanForeground
205 \value LightRedForeground
206 \value LightPurpleForeground
207 \value YellowForeground
208 \value WhiteForeground
209 \value BlackBackground
210 \value BlueBackground
211 \value GreenBackground
212 \value CyanBackground
213 \value RedBackground
214 \value PurpleBackground
215 \value BrownBackground
216
217 \value DefaultColor QColorOutput performs no coloring. This typically
218 means black on white or white on black, depending
219 on the settings of the user's terminal.
220 */
221
222/*!
223 \internal
224 Constructs a QColorOutput instance, ready for use.
225 */
226QColorOutput::QColorOutput() : d(new QColorOutputPrivate) {}
227
228// must be here so that QScopedPointer has access to the complete type
229QColorOutput::~QColorOutput() = default;
230
231bool QColorOutput::isSilent() const { return d->isSilent(); }
232void QColorOutput::setSilent(bool silent) { d->setSilent(silent); }
233
234/*!
235 \internal
236 Sends \a message to \c stderr, using the color looked up in the color mapping using \a colorID.
237
238 If \a color isn't available in the color mapping, result and behavior is undefined.
239
240 If \a colorID is 0, which is the default value, the previously used coloring is used. QColorOutput
241 is initialized to not color at all.
242
243 If \a message is empty, effects are undefined.
244
245 \a message will be printed as is. For instance, no line endings will be inserted.
246 */
247void QColorOutput::write(QStringView message, int colorID)
248{
249 if (!d->isSilent())
250 d->write(colorify(message, colorID));
251}
252
253void QColorOutput::writePrefixedMessage(const QString &message, QtMsgType type,
254 const QString &prefix)
255{
256 static const QHash<QtMsgType, QString> prefixes = {
257 {QtMsgType::QtCriticalMsg, QStringLiteral("Error")},
258 {QtMsgType::QtWarningMsg, QStringLiteral("Warning")},
259 {QtMsgType::QtInfoMsg, QStringLiteral("Info")},
260 {QtMsgType::QtDebugMsg, QStringLiteral("Hint")}
261 };
262
263 Q_ASSERT(prefixes.contains(type));
264 Q_ASSERT(prefix.isEmpty() || prefix.front().isUpper());
265 write((prefix.isEmpty() ? prefixes[type] : prefix) + QStringLiteral(": "), type);
266 writeUncolored(message);
267}
268
269/*!
270 \internal
271 Writes \a message to \c stderr as if for instance
272 QTextStream would have been used, and adds a line ending at the end.
273
274 This function can be practical to use such that one can use QColorOutput for all forms of writing.
275 */
276void QColorOutput::writeUncolored(const QString &message)
277{
278 if (!d->isSilent())
279 d->write(message + QLatin1Char('\n'));
280}
281
282/*!
283 \internal
284 Treats \a message and \a colorID identically to write(), but instead of writing
285 \a message to \c stderr, it is prepared for being written to \c stderr, but is then
286 returned.
287
288 This is useful when the colored string is inserted into a translated string(dividing
289 the string into several small strings prevents proper translation).
290 */
291QString QColorOutput::colorify(const QStringView message, int colorID) const
292{
293 Q_ASSERT_X(colorID == -1 || d->containsColor(colorID), Q_FUNC_INFO,
294 qPrintable(QString::fromLatin1("There is no color registered by id %1")
295 .arg(colorID)));
296 Q_ASSERT_X(!message.isEmpty(), Q_FUNC_INFO,
297 "It makes no sense to attempt to print an empty string.");
298
299 if (colorID != -1)
300 d->setCurrentColorID(colorID);
301
302 if (d->coloringEnabled() && colorID != -1) {
303 const int color = d->color(colorID);
304
305 /* If DefaultColor is set, we don't want to color it. */
306 if (color & DefaultColor)
307 return message.toString();
308
309 const int foregroundCode = (color & ForegroundMask) >> ForegroundShift;
310 const int backgroundCode = (color & BackgroundMask) >> BackgroundShift;
311 QString finalMessage;
312 bool closureNeeded = false;
313
314 if (foregroundCode > 0) {
315 finalMessage.append(
316 QColorOutputPrivate::escapeCode(
317 QLatin1String(QColorOutputPrivate::foregrounds[foregroundCode - 1])));
318 closureNeeded = true;
319 }
320
321 if (backgroundCode > 0) {
322 finalMessage.append(
323 QColorOutputPrivate::escapeCode(
324 QLatin1String(QColorOutputPrivate::backgrounds[backgroundCode - 1])));
325 closureNeeded = true;
326 }
327
328 finalMessage.append(message);
329
330 if (closureNeeded)
331 finalMessage.append(QColorOutputPrivate::escapeCode(QLatin1String("0")));
332
333 return finalMessage;
334 }
335
336 return message.toString();
337}
338
339void QColorOutput::flushBuffer()
340{
341 d->flushBuffer();
342}
343
344void QColorOutput::discardBuffer()
345{
346 d->discardBuffer();
347}
348
349/*!
350 \internal
351 Adds a color mapping from \a colorID to \a colorCode, for this QColorOutput instance.
352 */
353void QColorOutput::insertMapping(int colorID, const ColorCode colorCode)
354{
355 d->insertColor(colorID, colorCode);
356}
357
358QT_END_NAMESPACE