Last active
November 20, 2018 07:56
-
-
Save JanosGit/0efbe3ef629334ef398004e151b2283b to your computer and use it in GitHub Desktop.
A JUCE component displaying the stdout and stderr on unix systems
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// !!!!!!!! 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; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Find the final Unix and Windows compatible version here