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