Skip to content

Instantly share code, notes, and snippets.

@JanosGit
Last active November 20, 2018 07:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JanosGit/0efbe3ef629334ef398004e151b2283b to your computer and use it in GitHub Desktop.
Save JanosGit/0efbe3ef629334ef398004e151b2283b to your computer and use it in GitHub Desktop.
A JUCE component displaying the stdout and stderr on unix systems
// !!!!!!!! Find the final Version here:
https://git.fh-muenster.de/NTLab/CodeReUse/LogComponent4JUCE.git
// Leaving this old first approach here as I posted a link to this to a forum...
/*
==============================================================================
logComponent.h
Created: 18 Dec 2017 3:53:56pm
Author: Janos Buttgereit
==============================================================================
*/
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
/**
* A component that takes over stdout and stderr on Unix systems and displays them
*/
class LogComponent : public Component, private AsyncUpdater {
public:
LogComponent() {
addAndMakeVisible (consoleOutputEditor);
consoleOutputEditor.setMultiLine (true, true);
consoleOutputEditor.setReadOnly (true);
consoleOutputEditor.setFont (Font (Font::getDefaultMonospacedFontName(), 12.0f, Font::plain));
consoleOutputEditor.setColour (TextEditor::ColourIds::backgroundColourId, backgroundColour);
// save the original stdout and stderr to restore it later
originalStdout = dup (STDOUT_FILENO);
originalStderr = dup (STDERR_FILENO);
std::ios::sync_with_stdio();
}
~LogComponent() {
releaseStdOut();
releaseStdErr();
}
/** Clears the log */
void clear() {
ScopedLock scopedLock (linesLock);
consoleOutputEditor.clear();
lines.clear();
lineColours.clearQuick();
numLinesStored = 0;
numNewLinesSinceUpdate = 0;
}
/** Posts a message directly to the LogComponent */
void postLogMessage (String messageToPost, Colour messageColour = Colours::blue) {
linesLock.enter();
lines.add (messageToPost + "\n");
lineColours.add (messageColour);
numNewLinesSinceUpdate++;
numLinesStored++;
linesLock.exit();
triggerAsyncUpdate();
}
/** Redirects stdout to this component. Call releaseStdOut to restore the prior state */
bool captureStdOut() {
if (isActiveStdOut) {
return true;
}
fflush (stdout);
// create a pipe and assign stdout to that pipe
int retValue = pipe (logStdOutputPipe);
if (retValue != 0) {
return false;
}
dup2 (logStdOutputPipe[1], STDOUT_FILENO);
close (logStdOutputPipe[1]);
// no buffering - will make new content appear as soon as possible
setvbuf( stdout, NULL, _IONBF, 0 );
isActiveStdOut = true;
// this thread will wait for incomming data and redirect it to the gui
stdOutReaderThread = new std::thread (std::bind (&LogComponent::readStdOut, this));
return true;
}
/** Redirects stderr to this component. Call releaseStdErr to restore the prior state */
bool captureStdErr() {
if (isActiveStdErr) {
return true;
}
fflush (stderr);
// create a pipe and assign stdout to that pipe
int retValue = pipe (logErrOutputPipe);
if (retValue != 0) {
return false;
}
dup2 (logErrOutputPipe[1], STDERR_FILENO);
close (logErrOutputPipe[1]);
// no buffering - will make new content appear as soon as possible
setvbuf( stderr, NULL, _IONBF, 0 );
isActiveStdErr = true;
// this thread will wait for incomming data and redirect it to the gui
stdErrReaderThread = new std::thread (std::bind (&LogComponent::readStdErr, this));
return true;
}
/** Restores the original stdout */
void releaseStdOut() {
if (isActiveStdOut == false) {
return;
}
isActiveStdOut = false;
// send some empty string to trigger the read thread and let it come to an end
std::cout << " ";
fflush(stdout);
stdOutReaderThread->join();
// redirect stdout to its original destination
dup2 (originalStdout, STDOUT_FILENO);
// delete the read thread
stdOutReaderThread = nullptr;
std::cout << "Log component restored stdout" << std::endl;
}
/** Restores the original stderr */
void releaseStdErr() {
if (isActiveStdErr == false) {
return;
}
isActiveStdErr = false;
// send some empty string to trigger the read thread and let it come to an end
std::cerr << " ";
fflush(stderr);
stdErrReaderThread->join();
// redirect stderr to its original destination
dup2 (originalStderr, STDERR_FILENO);
// delete the read thread
stdErrReaderThread = nullptr;
std::cerr << "Log component restored stderr" << std::endl;
}
/** Sets a new background colour. Default is white */
void setBackgroundColour (Colour newBackgroundColour) {
backgroundColour = newBackgroundColour;
}
/** Sets a new colour for all stdout prints. Default is black */
void setNewStdOutColour (Colour newStdOutColour) {
stdOutColour = newStdOutColour;
}
/** Sets a new colour for all stderr prints. Default is red */
void setNewStdErrColour (Colour newStdErrColour) {
stdErrColour = newStdErrColour;
}
void paint (Graphics&) override {}
void resized() override {
consoleOutputEditor.setBounds(0, 0, getWidth(), getHeight());
}
private:
void readStdOut() {
while (true) {
char tmpStdOutBuf[tmpBufLen];
fflush(stdout);
size_t numCharsRead = read (logStdOutputPipe[0], tmpStdOutBuf, tmpBufLen - 1);
if (isActiveStdOut == false)
break;
tmpStdOutBuf[numCharsRead] = '\0';
addFromStd (tmpStdOutBuf, numCharsRead, stdOutColour);
}
}
void readStdErr() {
while (true) {
char tmpStdErrBuf[tmpBufLen];
fflush(stderr);
size_t numCharsRead = read (logErrOutputPipe[0], tmpStdErrBuf, tmpBufLen - 1);
if (isActiveStdErr == false)
break;
tmpStdErrBuf[numCharsRead] = '\0';
addFromStd (tmpStdErrBuf, numCharsRead, stdErrColour);
}
}
void addFromStd (char *stringBufferToAdd, size_t bufferSize, Colour colourOfString) {
linesLock.enter();
int numNewLines = lines.addTokens (stringBufferToAdd, "\n", "");
for (int i = 0; i < numNewLines; i++) {
lineColours.add (colourOfString);
}
numNewLinesSinceUpdate += numNewLines;
numLinesStored += numNewLines;
// add linebreaks
if (stringBufferToAdd[bufferSize - 1] == '\n') {
// only add a linebreak to the last line if it really had one - may contain an incomplete line
lines.getReference (numLinesStored - 1) += "\n";
}
for (int i = numLinesStored - numNewLines; i < numLinesStored - 1; i++) {
lines.getReference (i) += "\n";
}
linesLock.exit();
triggerAsyncUpdate();
}
void handleAsyncUpdate() override {
ScopedLock scopedLock (linesLock);
// check if the lines should be cleared
if (numLinesStored > numLinesToStore) {
// remove 20 or more lines at the beginning
int numLinesToRemove = numLinesStored - numLinesToStore;
if (numLinesToRemove < numLinesToRemoveWhenFull)
numLinesToRemove = numLinesToRemoveWhenFull;
lines.removeRange (0, numLinesToRemove);
lineColours.removeRange (0, numLinesToRemove);
numLinesStored = lines.size();
// clear the editor and flag all lines as new lines
consoleOutputEditor.clear();
numNewLinesSinceUpdate = numLinesStored;
}
// append new lines
Colour lastColour = Colours::black;
consoleOutputEditor.moveCaretToEnd();
consoleOutputEditor.setColour (TextEditor::ColourIds::textColourId, lastColour);
for (int i = numLinesStored - numNewLinesSinceUpdate; i < numLinesStored; i++) {
if (lineColours[i] != lastColour) {
lastColour = lineColours[i];
consoleOutputEditor.setColour (TextEditor::ColourIds::textColourId, lastColour);
}
consoleOutputEditor.insertTextAtCaret (lines[i]);
}
numNewLinesSinceUpdate = 0;
}
// filedescriptors to restore the standard console output streams
int originalStdout, originalStderr;
// pipes to redirect the streams to this component
int logStdOutputPipe[2];
int logErrOutputPipe[2];
ScopedPointer<std::thread> stdOutReaderThread;
ScopedPointer<std::thread> stdErrReaderThread;
bool running = false;
// indicates if it is the current stdout
bool isActiveStdOut = false;
bool isActiveStdErr = false;
static const int tmpBufLen = 512;
TextEditor consoleOutputEditor;
Colour stdOutColour = Colours::black;
Colour stdErrColour = Colours::red;
Colour backgroundColour = Colours::white;
// this is where the text is stored
int numLinesToStore = 200;
int numLinesToRemoveWhenFull = 20;
int numLinesStored = 0;
int numNewLinesSinceUpdate = 0;
StringArray lines;
Array<Colour> lineColours;
CriticalSection linesLock;
};
@JanosGit
Copy link
Author

Find the final Unix and Windows compatible version here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment