mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-20 03:46:11 +00:00
Redirect stdout/stderr to Console Widget (#1821)
* Added stdout/stderr redirection to the console
This commit is contained in:
parent
b81eed7f22
commit
235761941f
44
src/Main.cpp
44
src/Main.cpp
@ -121,6 +121,46 @@ static void migrateThemes()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Attempt to connect to a parent console and configure outputs.
|
||||||
|
*
|
||||||
|
* @note Doesn't do anything if the exe wasn't executed from a console.
|
||||||
|
*/
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
static void connectToConsole()
|
||||||
|
{
|
||||||
|
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid reconfiguring stderr/stdout if one of them is already connected to a stream.
|
||||||
|
// This can happen when running with stdout/stderr redirected to a file.
|
||||||
|
if (0 > fileno(stdout)) {
|
||||||
|
// Overwrite FD 1 and 2 for the benefit of any code that uses the FDs
|
||||||
|
// directly. This is safe because the CRT allocates FDs 0, 1 and
|
||||||
|
// 2 at startup even if they don't have valid underlying Windows
|
||||||
|
// handles. This means we won't be overwriting an FD created by
|
||||||
|
// _open() after startup.
|
||||||
|
_close(1);
|
||||||
|
|
||||||
|
if (freopen("CONOUT$", "a+", stdout)) {
|
||||||
|
// Avoid buffering stdout/stderr since IOLBF is replaced by IOFBF in Win32.
|
||||||
|
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0 > fileno(stderr)) {
|
||||||
|
_close(2);
|
||||||
|
|
||||||
|
if (freopen("CONOUT$", "a+", stderr)) {
|
||||||
|
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog.
|
||||||
|
std::ios::sync_with_stdio();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
@ -133,6 +173,10 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
initCrashHandler();
|
initCrashHandler();
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
connectToConsole();
|
||||||
|
#endif
|
||||||
|
|
||||||
qRegisterMetaType<QList<StringDescription>>();
|
qRegisterMetaType<QList<StringDescription>>();
|
||||||
qRegisterMetaType<QList<FunctionDescription>>();
|
qRegisterMetaType<QList<FunctionDescription>>();
|
||||||
|
|
||||||
|
@ -13,6 +13,20 @@
|
|||||||
#include "common/Helpers.h"
|
#include "common/Helpers.h"
|
||||||
#include "common/SvgIconEngine.h"
|
#include "common/SvgIconEngine.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <io.h>
|
||||||
|
#include <QUuid>
|
||||||
|
#define dup2 _dup2
|
||||||
|
#define dup _dup
|
||||||
|
#define fileno _fileno
|
||||||
|
#define fdopen _fdopen
|
||||||
|
#define PIPE_SIZE 65536 // Match Linux size
|
||||||
|
#define PIPE_NAME "\\\\.\\pipe\\cutteroutput-%1"
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#define PIPE_READ (0)
|
||||||
|
#define PIPE_WRITE (1)
|
||||||
|
#endif
|
||||||
|
|
||||||
static const int invalidHistoryPos = -1;
|
static const int invalidHistoryPos = -1;
|
||||||
|
|
||||||
@ -90,6 +104,8 @@ ConsoleWidget::ConsoleWidget(MainWindow *main, QAction *action) :
|
|||||||
connect(Config(), &Configuration::interfaceThemeChanged, this, &ConsoleWidget::setupFont);
|
connect(Config(), &Configuration::interfaceThemeChanged, this, &ConsoleWidget::setupFont);
|
||||||
|
|
||||||
completer->popup()->installEventFilter(this);
|
completer->popup()->installEventFilter(this);
|
||||||
|
|
||||||
|
redirectOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleWidget::~ConsoleWidget()
|
ConsoleWidget::~ConsoleWidget()
|
||||||
@ -156,36 +172,26 @@ void ConsoleWidget::executeCommand(const QString &command)
|
|||||||
}
|
}
|
||||||
ui->inputLineEdit->setEnabled(false);
|
ui->inputLineEdit->setEnabled(false);
|
||||||
|
|
||||||
const int originalLines = ui->outputTextEdit->blockCount();
|
QString cmd_line = "[" + RAddressString(Core()->getOffset()) + "]> " + command;
|
||||||
QTimer *timer = new QTimer(this);
|
addOutput(cmd_line);
|
||||||
timer->setInterval(500);
|
|
||||||
timer->setSingleShot(true);
|
|
||||||
connect(timer, &QTimer::timeout, [this]() {
|
|
||||||
ui->outputTextEdit->appendPlainText("Executing the command...");
|
|
||||||
});
|
|
||||||
|
|
||||||
QString cmd_line = "<br>[" + RAddressString(Core()->getOffset()) + "]> " + command + "<br>";
|
|
||||||
RVA oldOffset = Core()->getOffset();
|
RVA oldOffset = Core()->getOffset();
|
||||||
commandTask = QSharedPointer<CommandTask>(new CommandTask(command, CommandTask::ColorMode::MODE_256, true));
|
commandTask = QSharedPointer<CommandTask>(new CommandTask(command, CommandTask::ColorMode::MODE_256, true));
|
||||||
connect(commandTask.data(), &CommandTask::finished, this, [this, cmd_line,
|
connect(commandTask.data(), &CommandTask::finished, this, [this, cmd_line,
|
||||||
command, originalLines, oldOffset] (const QString & result) {
|
command, oldOffset] (const QString & result) {
|
||||||
|
|
||||||
if (originalLines < ui->outputTextEdit->blockCount()) {
|
ui->outputTextEdit->appendHtml(result);
|
||||||
removeLastLine();
|
|
||||||
}
|
|
||||||
ui->outputTextEdit->appendHtml(cmd_line + result);
|
|
||||||
scrollOutputToEnd();
|
scrollOutputToEnd();
|
||||||
historyAdd(command);
|
historyAdd(command);
|
||||||
commandTask.clear();
|
commandTask.clear();
|
||||||
ui->inputLineEdit->setEnabled(true);
|
ui->inputLineEdit->setEnabled(true);
|
||||||
ui->inputLineEdit->setFocus();
|
ui->inputLineEdit->setFocus();
|
||||||
|
|
||||||
if (oldOffset != Core()->getOffset()) {
|
if (oldOffset != Core()->getOffset()) {
|
||||||
Core()->updateSeek();
|
Core()->updateSeek();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(commandTask.data(), &CommandTask::finished, timer, &QTimer::stop);
|
|
||||||
|
|
||||||
timer->start();
|
|
||||||
Core()->getAsyncTaskManager()->start(commandTask);
|
Core()->getAsyncTaskManager()->start(commandTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,3 +330,63 @@ void ConsoleWidget::invalidateHistoryPosition()
|
|||||||
{
|
{
|
||||||
lastHistoryPosition = invalidHistoryPos;
|
lastHistoryPosition = invalidHistoryPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConsoleWidget::processQueuedOutput()
|
||||||
|
{
|
||||||
|
// Partial lines are ignored since carriage return is currently unsupported
|
||||||
|
while (pipeSocket->canReadLine()) {
|
||||||
|
QString output = QString(pipeSocket->readLine());
|
||||||
|
|
||||||
|
fprintf(origStderr, "%s", output.toStdString().c_str());
|
||||||
|
|
||||||
|
// Get the last segment that wasn't overwritten by carriage return
|
||||||
|
output = output.trimmed();
|
||||||
|
output = output.remove(0, output.lastIndexOf('\r')).trimmed();
|
||||||
|
ui->outputTextEdit->appendHtml(CutterCore::ansiEscapeToHtml(output));
|
||||||
|
scrollOutputToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleWidget::redirectOutput()
|
||||||
|
{
|
||||||
|
// Make sure that we are running in a valid console with initialized output handles
|
||||||
|
if (0 > fileno(stderr) && 0 > fileno(stdout)) {
|
||||||
|
addOutput("Run cutter in a console to enable r2 output redirection into this widget.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeSocket = new QLocalSocket(this);
|
||||||
|
|
||||||
|
origStderr = fdopen(dup(fileno(stderr)), "a");
|
||||||
|
origStdout = fdopen(dup(fileno(stdout)), "a");
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QString pipeName = QString::fromLatin1(PIPE_NAME).arg(QUuid::createUuid().toString());
|
||||||
|
|
||||||
|
SECURITY_ATTRIBUTES attributes = {sizeof(SECURITY_ATTRIBUTES), 0, false};
|
||||||
|
hWrite = CreateNamedPipeW((wchar_t*)pipeName.utf16(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
||||||
|
PIPE_TYPE_BYTE | PIPE_WAIT, 1, PIPE_SIZE, PIPE_SIZE, 0, &attributes);
|
||||||
|
|
||||||
|
int writeFd = _open_osfhandle((intptr_t)hWrite, _O_WRONLY | _O_TEXT);
|
||||||
|
dup2(writeFd, fileno(stdout));
|
||||||
|
dup2(writeFd, fileno(stderr));
|
||||||
|
|
||||||
|
pipeSocket->connectToServer(pipeName, QIODevice::ReadOnly);
|
||||||
|
#else
|
||||||
|
pipe(redirectPipeFds);
|
||||||
|
dup2(redirectPipeFds[PIPE_WRITE], fileno(stderr));
|
||||||
|
dup2(redirectPipeFds[PIPE_WRITE], fileno(stdout));
|
||||||
|
|
||||||
|
// Attempt to force line buffering to avoid calling processQueuedOutput
|
||||||
|
// for partial lines
|
||||||
|
setlinebuf(stderr);
|
||||||
|
setlinebuf(stdout);
|
||||||
|
|
||||||
|
// Configure the pipe to work in async mode
|
||||||
|
fcntl(redirectPipeFds[PIPE_READ], F_SETFL, O_ASYNC | O_NONBLOCK);
|
||||||
|
|
||||||
|
pipeSocket->setSocketDescriptor(redirectPipeFds[PIPE_READ]);
|
||||||
|
pipeSocket->connectToServer(QIODevice::ReadOnly);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
connect(pipeSocket, SIGNAL(readyRead()), this, SLOT(processQueuedOutput()));
|
||||||
|
}
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include "common/CommandTask.h"
|
#include "common/CommandTask.h"
|
||||||
|
|
||||||
#include <QStringListModel>
|
#include <QStringListModel>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@ -62,6 +64,11 @@ private slots:
|
|||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Passes redirected output from the pipe to the terminal and console
|
||||||
|
*/
|
||||||
|
void processQueuedOutput();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void scrollOutputToEnd();
|
void scrollOutputToEnd();
|
||||||
void historyAdd(const QString &input);
|
void historyAdd(const QString &input);
|
||||||
@ -70,6 +77,12 @@ private:
|
|||||||
void executeCommand(const QString &command);
|
void executeCommand(const QString &command);
|
||||||
void setWrap(bool wrap);
|
void setWrap(bool wrap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Redirects stderr and stdout to the output pipe which is handled by
|
||||||
|
* processQueuedOutput
|
||||||
|
*/
|
||||||
|
void redirectOutput();
|
||||||
|
|
||||||
QSharedPointer<CommandTask> commandTask;
|
QSharedPointer<CommandTask> commandTask;
|
||||||
|
|
||||||
std::unique_ptr<Ui::ConsoleWidget> ui;
|
std::unique_ptr<Ui::ConsoleWidget> ui;
|
||||||
@ -84,6 +97,17 @@ private:
|
|||||||
QCompleter *completer;
|
QCompleter *completer;
|
||||||
QShortcut *historyUpShortcut;
|
QShortcut *historyUpShortcut;
|
||||||
QShortcut *historyDownShortcut;
|
QShortcut *historyDownShortcut;
|
||||||
|
FILE *origStderr;
|
||||||
|
FILE *origStdout;
|
||||||
|
QLocalSocket *pipeSocket;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
HANDLE hRead;
|
||||||
|
HANDLE hWrite;
|
||||||
|
#else
|
||||||
|
int redirectPipeFds[2];
|
||||||
|
QVector<char> *redirectionBuffer;
|
||||||
|
QSocketNotifier *outputNotifier;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONSOLEWIDGET_H
|
#endif // CONSOLEWIDGET_H
|
||||||
|
Loading…
Reference in New Issue
Block a user