2017-09-22 3 views
0

Je crée un programme dans lequel j'exécute des processus dans Qt en utilisant l'infrastructure QProcess sur Ubuntu 16.04 Qt 5.5.1 avec C++ 11 activé. Je dirige le flux de sortie du processus vers un QTextEdit.Conserver les séquences d'échappement ANSI dans la sortie QProcess

Je voudrais coloriser cette sortie pour utiliser les mêmes couleurs que les terminaux natifs interprètent en utilisant les séquences de couleurs d'échappement ANSI intégrées. Cependant, je suis incapable d'analyser les séquences d'échappement car elles semblent être manquantes dans la sortie QProcess. Au départ, je pensais que QString les dépouillait, mais après quelques tests, je ne crois pas que ce soit le cas.

Je trouve some information me pointer dans la direction d'interprétation des couleurs d'échappement ANSI si je pouvais garder les séquences d'échappement dans la sortie de QProcess.

Voici un exemple de projet de ce que je fais dans le code Qt.

Le fichier source ...

#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; 
} 

Le fichier d'en-tête ...

#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 

Le fichier de forme ...

<?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> 

Le fichier source C ...

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

Ma sortie ressemble à ceci:

sortie gcc QProcess

La sortie du terminal Linux natif ressemble à ceci:

sortie gcc terminal Linux avec des couleurs

Est-ce que quelqu'un savoir comment je pourrais continuer à garder les séquences de couleurs d'échappement ANSI dans la sortie QProcess afin que je puisse simuler les couleurs du terminal Linux? En guise de remarque, j'ai trouvé le code source de Qt Creator et il existe une classe qui peut convertir les couleurs d'échappement ANSI en couleurs Rich Text, donc je sais que quelqu'un a été dans cette voie. Ensuite, lors de la création de projets, Qt Creator ne colorise pas la sortie de construction dans son propre terminal pour une raison quelconque.

Répondre

0

QProcess n'interfère pas avec la sortie de processus, il est juste que gcc - comme beaucoup d'autres programmes qui émettent colorés sortie - par défaut émettent des séquences d'échappement de couleur seulement quand il détecte qu'il est écrit sur un appareil ATS.

Si vous souhaitez désactiver cette heuristique et demander de toujours produire une sortie colorée, vous devez ajouter l'option -fdiagnostics-color=always à la ligne de commande du compilateur.

+0

Merci pour votre réponse! Cela explique exactement les résultats que j'ai. Gcc est l'un des nombreux programmes que j'utiliserai, donc les options de la ligne de commande peuvent ne pas toujours fonctionner. Un google rapide montre qu'il peut être possible de tromper les programmes en leur faisant croire qu'ils écrivent sur un appareil ATS. https://superuser.com/questions/352697/preserve-colors-while-piping-to-tee – konakid

+0

@konakid: oui, c'est une solution, mais gardez à l'esprit que les séquences d'échappement VT100 ne sont pas seulement des couleurs, donc vous devrez probablement filtrer fortement la sortie des programmes "plus intelligents". –

1

Merci à une réponse très perspicace à ma question, j'ai été en mesure de trouver une solution à mon problème. Je vais partager ...

QProcess n'est pas en faute, ni QString. Le problème réside dans l'environnement dans lequel les programmes sont exécutés.Puisque la sortie de ces programmes (gcc etc.) n'est pas connectée à un dispositif TTY, toutes les séquences d'échappement ANSI sont supprimées. Il y a un moyen to trick the output to appear as if it were connected to a TTY device cependant.

Ajoutez juste le préfixe unbuffer à la commande.

Depuis que mon utilisation crée réellement un plugin Qt Creator, je faisais déjà le lien avec une grande partie du code source de Qt Creator. Il se trouve qu'une classe pratique nommée AnsiEscapeCodeHandler existe déjà pour convertir les séquences d'échappement ANSI en QTextCharFormat's et les chaînes dépouillées de séquences ANSI correspondantes.

Pour illustrer comment j'ai utilisé cette classe, mais maintenant dans mon exemple, je vais simplement copier les ansieescapecodehandler.h et ansiescapecodehandler.cpp à mon projet de test à partir du code source téléchargeable Qt Creator. J'ai dû supprimer quelques lignes des fichiers source AnsiEscapeCodeHandler pour compiler en dehors du contexte du reste de la source Qt Creator, mais c'était tout.

Le nouveau fichier source ...

#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 https://stackoverflow.com/questions/36279015/what-does-x1bb-do 
    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; 
} 

Le nouveau fichier d'en-tête ...

#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 

La nouvelle sortie colorisée ... QProcess gcc output