在 QProcess 输出中保留 ANSI 转义序列

Keep ANSI Escape Sequences in QProcess Output

我正在创建一个程序,其中我 运行 在启用了 C++ 11 的 Ubuntu 16.04 Qt 5.5.1 上使用 QProcess 框架在 Qt 中进行处理。我正在将进程输出流定向到 QTextEdit。

我想将此输出着色为使用本机终端通过使用嵌入式 ANSI 转义颜色序列解释的相同颜色。但是,我无法解析转义序列,因为它们似乎从 QProcess 输出中丢失了。我最初以为 QString 正在剥离它们,但经过一些测试后我不相信是这样。

如果我可以将转义序列保留在 QProcess 输出中,我发现 some information 指向 ANSI 转义颜色解释方向。

这是我在 Qt 代码中所做的示例项目。

源文件...

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QString>
#include <QProcess>
#include <QStringList>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QStringList input = {"gcc will_not_build.c"};
    QProcess * proc = new QProcess();

    proc->setReadChannel(QProcess::StandardOutput);
    proc->setProcessChannelMode(QProcess::MergedChannels);
    proc->setWorkingDirectory("/path/to/test/c/file/");

    //Start bash
    proc->start("bash");
    proc->waitForStarted();

    // Write as many commands to this process as needed
    foreach(QString str, input){
        proc->write(str.toUtf8() + "\n");
        proc->waitForBytesWritten(-1);
    }

    // Let bash close gracefully
    proc->write("exit $?\n");
    proc->waitForBytesWritten(-1);

    proc->closeWriteChannel();
    proc->waitForFinished();
    proc->waitForReadyRead();

    QByteArray read_data = proc->readAll();

    // The use of tr(read_data) also works here.
    QString output = tr(read_data);//QString::fromStdString (read_data.toStdString ());

    proc->closeReadChannel(QProcess::StandardOutput);

    proc->close();
    delete proc;

    // Add the output to the text box
    ui->textEdit->append (output);
}

MainWindow::~MainWindow()
{
    delete ui;
}

头文件...

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H 

表单文件...

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <widget class="QTextEdit" name="textEdit">
    <property name="geometry">
     <rect>
      <x>33</x>
      <y>19</y>
      <width>331</width>
      <height>211</height>
     </rect>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>400</width>
     <height>19</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

C 源文件...

int main(){
    // Intentionally will not build
    I will not build :)
}

我的输出如下所示:

QProcess gcc 输出

本机 Linux 终端的输出如下所示:

Linux 带有颜色的终端 gcc 输出

有谁知道我如何在 QProcess 输出中保留 ANSI 转义颜色序列以便我可以模拟 Linux 终端颜色?

作为附注,我在 Qt Creator 源代码中进行了深入研究,发现有一个 class 可以将 ANSI 转义颜色转换为 Rich Text 颜色,所以我知道有人走上了这条路。再一次,在构建项目时,Qt Creator 出于某种原因不会在其自己的终端中为构建输出着色。

QProcess 不会干扰进程输出,只是 gcc - 与许多其他发出彩色输出的程序一样 - 默认情况下仅在检测到它正在写入时才发出颜色转义序列在 TTY 设备上。

如果要禁用此启发式并要求始终生成彩色输出,则必须将 -fdiagnostics-color=always 选项添加到编译器命令行。

感谢对我的问题的非常有见地的回答,我能够找到解决问题的方法。我会分享...

QProcess 和 QString 都没有问题。问题在于程序执行的环境。由于这些程序 (gcc etc) 的输出未连接到 TTY 设备,因此所有 ANSI 转义序列都被剥离。不过有办法to trick the output to appear as if it were connected to a TTY device

只需在命令前添加 unbuffer

因为我的使用实际上是创建一个 Qt Creator 插件,所以我已经链接到大部分 Qt Creator 源代码。碰巧一个方便的 class 名为 AnsiEscapeCodeHandler 已经存在,可以将 ANSI 转义序列转换为 QTextCharFormat's 和相应的 ANSI 转义序列剥离字符串。

为了说明我是如何使用这个 class,但现在在我的示例中,我只是将 ansieescapecodehandler.hansiescapecodehandler.cpp 从可下载的 Qt Creator 源代码复制到我的测试项目.我不得不从 AnsiEscapeCodeHandler 源文件中删除几行,以便在 Qt Creator 源的其余部分的上下文之外进行编译,但仅此而已。

新的源文件...

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QString>
#include <QProcess>
#include <QStringList>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QStringList input = {"unbuffer gcc will_not_build.c"};
    QProcess * proc = new QProcess();

    proc->setReadChannel(QProcess::StandardOutput);
    proc->setProcessChannelMode(QProcess::MergedChannels);
    proc->setWorkingDirectory("/path/to/test/c/file/");

    //Start bash
    proc->start("bash");
    proc->waitForStarted();

    // Write as many commands to this process as needed
    foreach(QString str, input){
        proc->write(str.toUtf8() + "\n");
        proc->waitForBytesWritten(-1);
    }

    // Let bash close gracefully
    proc->write("exit $?\n");
    proc->waitForBytesWritten(-1);

    proc->closeWriteChannel();
    proc->waitForFinished();
    proc->waitForReadyRead();

    QByteArray read_data = proc->readAll();

    // The use of tr(read_data) also works here.
    QString output = tr(read_data);//QString::fromStdString (read_data.toStdString ());

    proc->closeReadChannel(QProcess::StandardOutput);

    proc->close();
    delete proc;

    // Strip default character set escape sequences, since those seem to be left
    // See 
    output.remove("\x1b(B", Qt::CaseInsensitive);

    // Since it is just one single text stream define here instead of globally
    Utils::AnsiEscapeCodeHandler ansi_handler;

    FormattedTextList result = ansi_handler.parseText (Utils::FormattedText(output, ui->textEdit->currentCharFormat ()));

    // Loop through the text/format results
    foreach(Utils::FormattedText ft, result){
        ui->textEdit->setCurrentCharFormat (ft.format);
        ui->textEdit->insertPlainText (ft.text);
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}

新的头文件...

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

// This exists in the qtcreator-src code and handles ansi escape code color parsing
#include "ansiescapecodehandler.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

    typedef QList<Utils::FormattedText> FormattedTextList;
};

#endif // MAINWINDOW_H

新的彩色输出... QProcess gcc output