Vuo 2.4.4
Loading...
Searching...
No Matches
VuoConsole.cc
Go to the documentation of this file.
1
10#include "VuoConsole.hh"
11#include "VuoConsoleWindow.hh"
12#include "VuoEditor.hh"
13
14VuoConsole *VuoConsole::singleton = nullptr;
15const int VuoConsole::maxLogs = 500;
16
20VuoConsole::VuoConsole(QObject *parent) :
21 QObject(parent)
22{
23 areModelUpdatesPaused = false;
24 oldLogsDeletedWhilePaused = 0;
25
26 window = nullptr;
27}
28
43{
44 startListeningInternal({LogStream::STDOUT, LogStream::STDERR});
45}
46
50void VuoConsole::startListeningInternal(const vector<LogStream> &streams)
51{
52 if (singleton)
53 return;
54
55 singleton = new VuoConsole;
56
57 connect(singleton, &VuoConsole::logsReceived, singleton, &VuoConsole::appendLogs);
58
59 dispatch_queue_t queue = dispatch_queue_create("org.vuo.editor.console", NULL);
60
61 for (LogStream stream : streams)
62 {
63 int origFd = (stream == LogStream::STDOUT ? STDOUT_FILENO : STDERR_FILENO);
64 const char *name = (stream == LogStream::STDOUT ? "stdout" : "stderr");
65
66 // Save a file descriptor (copyFd) that points to stdout/stderr, since the original file descriptor
67 // will be redirected and will no longer go to the original destination.
68 int copyFd = dup(origFd);
69 if (copyFd == -1)
70 {
71 VUserLog("Console can't show logs from %s (dup failed): %s", name, strerror(errno));
72 continue;
73 }
74
75 // Create the pipe and allocate a pair of file descriptors for it.
76 int readWriteFds[2];
77 int ret = pipe(readWriteFds);
78 if (ret == -1)
79 {
80 VUserLog("Console can't show logs from %s (pipe failed): %s", name, strerror(errno));
81 close(copyFd);
82 continue;
83 }
84
85 // Set the write end of the pipe to a file descriptor that points to stdout/stderr.
86 int writeFd = dup2(readWriteFds[1], origFd);
87 if (writeFd == -1)
88 {
89 VUserLog("Console can't show logs from %s (dup2 failed): %s", name, strerror(errno));
90 close(copyFd);
91 close(readWriteFds[0]);
92 close(readWriteFds[1]);
93 continue;
94 }
95
96 // Close the file descriptor on the write end of the pipe, since this function no longer needs
97 // to refer to it. Writes to stdout/stderr will still be redirected through the pipe.
98 close(readWriteFds[1]);
99
100 // Set up a dispatch source whose event handler is called when data becomes available on the
101 // read end of the pipe.
102 int readFd = readWriteFds[0];
103 dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, readFd, 0, queue);
104
105 dispatch_source_set_event_handler(source, ^{
106 size_t estimated = dispatch_source_get_data(source);
107 char *buffer = (char *)calloc(estimated + 1, sizeof(char));
108 if (buffer)
109 {
110 // Read the text that was written to our redirected stdout/stderr.
111 read(readFd, buffer, estimated);
112
113 // Write the text to the original stdout/stderr.
114 write(copyFd, buffer, estimated);
115
116 // Store the text in the list of logs.
117 singleton->parseLogs(buffer, stream);
118 }
119 });
120
121 dispatch_resume(source);
122 }
123}
124
132void VuoConsole::parseLogs(char *buffer, LogStream stream)
133{
134 QString bufferStr(buffer); // interprets buffer as UTF-8
135 free(buffer);
136
137 bufferStr.prepend(partialLog[stream]);
138 partialLog[stream] = "";
139
140 bufferStr.replace(QRegExp("\\033\[[0-9;]+m"), "");
141
142 QString separator = "\n";
143 QStringList newLogs = bufferStr.split(separator, QString::SkipEmptyParts);
144
145 if (bufferStr.back() != separator)
146 partialLog[stream] = newLogs.takeLast();
147
148 if (! newLogs.isEmpty())
149 emit logsReceived(newLogs);
150}
151
155void VuoConsole::appendLogs(QStringList newLogs)
156{
157 logs.append(newLogs);
158
159 int oldLogsDeleted = 0;
160 if (logs.size() > maxLogs)
161 {
162 oldLogsDeleted = logs.size() - maxLogs;
163 logs.erase(logs.begin(), logs.begin() + logs.size() - maxLogs);
164 }
165
166 updateModel(oldLogsDeleted);
167}
168
173{
174 logs.clear();
175
176 updateModel(0);
177}
178
186void VuoConsole::show(QWidget *screenMate)
187{
188 singleton->showInternal(screenMate);
189}
190
194void VuoConsole::showInternal(QWidget *screenMate)
195{
196 if (! window)
197 {
198 window = new VuoConsoleWindow(this, screenMate);
199 connect(window, &VuoConsoleWindow::destroyed, this, [this](){ window = nullptr; });
200
201 window->setModel(logs);
202
203 window->show();
204 }
205
206 window->raise();
207 window->activateWindow();
208}
209
216void VuoConsole::updateModel(int oldLogsDeleted)
217{
218 if (window)
219 {
220 if (! areModelUpdatesPaused)
221 {
222 emit modelAboutToChange(oldLogsDeleted);
223 window->setModel(logs);
224 }
225 else
226 {
227 oldLogsDeletedWhilePaused += oldLogsDeleted;
228 }
229 }
230}
231
235void VuoConsole::pauseModelUpdates(void)
236{
237 areModelUpdatesPaused = true;
238}
239
243void VuoConsole::resumeModelUpdates(void)
244{
245 areModelUpdatesPaused = false;
246 int oldLogsDeleted = oldLogsDeletedWhilePaused;
247 oldLogsDeletedWhilePaused = 0;
248 updateModel(oldLogsDeleted);
249}
250
255{
256 pauseModelUpdates();
257
258 QFileDialog dialog(window, Qt::Sheet);
259 dialog.setAcceptMode(QFileDialog::AcceptSave);
260 dialog.setDefaultSuffix("log");
261
262 QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh-mm-ss");
263 QString userName = QString::fromStdString(static_cast<VuoEditor *>(qApp)->getUserName());
264 dialog.selectFile(QString("Vuo %1 %2.log").arg(timestamp).arg(userName));
265
266 if (dialog.exec() == QDialog::Accepted)
267 {
268 QString path = dialog.selectedFiles()[0];
269 QFile file(path);
270 if (file.open(QIODevice::WriteOnly))
271 {
272 QTextStream stream(&file);
273
274 QStringList logsCopy = window->getModel();
275 for (QString log : logsCopy)
276 stream << log << endl;
277 }
278 else
279 VUserLog("Couldn't open file for writing: %s", path.toStdString().c_str());
280 }
281
282 resumeModelUpdates();
283}
284
289{
290 pauseModelUpdates();
291
292 QString copiedText;
293 QTextStream stream(&copiedText);
294
295 QStringList logsCopy = window->getModel();
296 QList<QVariant> selectedIndices = window->getSelectedIndices();
297
298 stream << "```" << endl;
299
300 if (! selectedIndices.isEmpty())
301 for (QVariant index : selectedIndices)
302 stream << logsCopy.at(index.toInt()) << endl;
303 else
304 for (QString log : logsCopy)
305 stream << log << endl;
306
307 stream << "```" << endl;
308
309 QClipboard *clipboard = QApplication::clipboard();
310 clipboard->setText(copiedText);
311
312 resumeModelUpdates();
313}