@@ -0,0 +1,221 @@
2+ * Copyright 2016 Canonical Ltd.
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
17+#include "consolelog.h"
23+#include <sys/ioctl.h>
30+int wait_fd(std::function<int(void)> fn)
33+ bool fd_blocked = false;
37+ fd_blocked = (errno == EINTR || errno == EBUSY);
45+#define PIPE_WAIT(val) wait_fd(std::bind(pipe, val))
46+#define DUP_WAIT(fd) wait_fd(std::bind(dup, fd))
47+#define DUP2_WAIT(fd1, fd2) wait_fd(std::bind(dup2, fd1, fd2))
48+#define CLOSE_WAIT(fd) wait_fd(std::bind(close, fd))
52+LogRedirector::LogRedirector()
56+ setObjectName("ConsoleLog");
58+ // make stdout & stderr streams unbuffered
59+ // so that we don't need to flush the streams
60+ // before capture and after capture
61+ // (fflush can cause a deadlock if the stream is currently being used)
62+ setvbuf(stdout,NULL,_IONBF,0);
63+ setvbuf(stderr,NULL,_IONBF,0);
66+void LogRedirector::run()
70+ int oldStdOut = DUP_WAIT(fileno(stdout));
71+ int oldStdErr = DUP_WAIT(fileno(stderr));
73+ DUP2_WAIT(m_pipe[WRITE], fileno(stdout));
74+ DUP2_WAIT(m_pipe[WRITE], fileno(stderr));
75+ CLOSE_WAIT(m_pipe[WRITE]);
79+ QMutexLocker lock(&m_mutex);
86+ DUP2_WAIT(oldStdOut, fileno(stdout));
87+ DUP2_WAIT(oldStdErr, fileno(stderr));
89+ CLOSE_WAIT(oldStdOut);
90+ CLOSE_WAIT(oldStdErr);
91+ CLOSE_WAIT(m_pipe[READ]);
95+void LogRedirector::checkLog()
97+ // Do not allow read to block with no data.
98+ // If we stop the thread while waiting a read,
99+ // it will block the main thread waiting for this thread to stop.
101+ ioctl(m_pipe[READ], FIONREAD, &count);
102+ if (count <= 0) return;
104+ std::string captured;
106+ const int bufSize = 1024;
107+ buf.resize(bufSize);
109+ bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
110+ while(bytesRead == bufSize)
114+ bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
118+ buf.resize(bytesRead);
122+ if (!captured.empty()) {
123+ Q_EMIT log(QString::fromStdString(captured));
127+LogRedirector *LogRedirector::instance()
129+ static LogRedirector* log = nullptr;
131+ log = new LogRedirector();
136+void LogRedirector::add(ConsoleLog* logger)
138+ QMutexLocker lock(&m_mutex);
139+ connect(this, &LogRedirector::log, logger, &ConsoleLog::logged, Qt::UniqueConnection);
142+ if (!LogRedirector::instance()->isRunning()) {
144+ LogRedirector::instance()->start();
148+void LogRedirector::remove(ConsoleLog* logger)
150+ QMutexLocker lock(&m_mutex);
151+ disconnect(this, &LogRedirector::log, logger, &ConsoleLog::logged);
153+ m_ref = qMax(m_ref-1, 0);
154+ if (m_ref == 0 && LogRedirector::instance()->isRunning()) {
157+ LogRedirector::instance()->wait();
161+ConsoleLog::ConsoleLog(QObject *parent)
166+ auto updateEnabled = [this]() {
168+ LogRedirector::instance()->add(this);
170+ LogRedirector::instance()->remove(this);
173+ connect(this, &ConsoleLog::enabledChanged, this, updateEnabled);
176+ConsoleLog::~ConsoleLog()
179+ LogRedirector::instance()->remove(this);
183+void ConsoleLog::setEnabled(bool enabled)
185+ if (m_enabled == enabled) {
189+ m_enabled = enabled;
190+ Q_EMIT enabledChanged();
193+QString ConsoleLog::out() const
195+ return m_out.join("\n");
198+void ConsoleLog::setMaxLines(int maxLines)
200+ if (m_maxLines == maxLines) {
204+ m_maxLines = maxLines;
205+ while (m_out.count() > m_maxLines) {
208+ Q_EMIT outChanged();
209+ Q_EMIT maxLinesChanged();
212+void ConsoleLog::logged(QString captured)
214+ QStringList li = captured.split("\n", QString::SkipEmptyParts);
217+ while (m_out.count() > m_maxLines) {
220+ Q_EMIT outChanged();