#include "MainWindow.hpp" #include "ui_MainWindow.h" #include #include #include #include #include #include "FileDirDialog.hpp" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_ui(new Ui::MainWindow) , m_heclProc(this) , m_dlManager(this) , m_settings("AxioDL", "HECL", this) { m_ui->setupUi(this); m_ui->heclTabs->setCurrentIndex(0); m_ui->splitter->setSizes({0,-1}); m_ui->aboutIcon->setPixmap(QApplication::windowIcon().pixmap(256, 256)); QFont mFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); mFont.setPointSize(m_ui->currentBinaryLabel->font().pointSize()); m_ui->currentBinaryLabel->setFont(mFont); m_ui->recommendedBinaryLabel->setFont(mFont); m_updateURDEButton = new QPushButton(QStringLiteral("Update URDE"), m_ui->centralwidget); m_ui->gridLayout->addWidget(m_updateURDEButton, 2, 3, 1, 1); m_updateURDEButton->hide(); QPalette pal = m_updateURDEButton->palette(); pal.setColor(QPalette::Button, QColor(53,53,72)); m_updateURDEButton->setPalette(pal); connect(m_updateURDEButton, SIGNAL(clicked()), this, SLOT(onUpdateURDEPressed())); setPath(m_settings.value(QStringLiteral("working_dir")).toString()); m_dlManager.connectWidgets(m_ui->downloadProgressBar, m_ui->downloadErrorLabel, std::bind(&MainWindow::onIndexDownloaded, this, std::placeholders::_1), std::bind(&MainWindow::onBinaryDownloaded, this, std::placeholders::_1)); initSlots(); m_dlManager.fetchIndex(); } MainWindow::~MainWindow() { m_heclProc.close(); m_heclProc.terminate(); delete m_ui; } /* TODO: more complete Vt102 emulation */ // based on information: http://en.m.wikipedia.org/wiki/ANSI_escape_code http://misc.flogisoft.com/bash/tip_colors_and_formatting http://invisible-island.net/xterm/ctlseqs/ctlseqs.html void MainWindow::parseEscapeSequence(int attribute, QListIterator< QString > & i, QTextCharFormat & textCharFormat, QTextCharFormat const & defaultTextCharFormat) { switch (attribute) { case 0 : { // Normal/Default (reset all attributes) textCharFormat = defaultTextCharFormat; break; } case 1 : { // Bold/Bright (bold or increased intensity) textCharFormat.setFontWeight(QFont::Bold); break; } case 2 : { // Dim/Faint (decreased intensity) textCharFormat.setFontWeight(QFont::Light); break; } case 3 : { // Italicized (italic on) textCharFormat.setFontItalic(true); break; } case 4 : { // Underscore (single underlined) textCharFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); textCharFormat.setFontUnderline(true); break; } case 5 : { // Blink (slow, appears as Bold) textCharFormat.setFontWeight(QFont::Bold); break; } case 6 : { // Blink (rapid, appears as very Bold) textCharFormat.setFontWeight(QFont::Black); break; } case 7 : { // Reverse/Inverse (swap foreground and background) QBrush foregroundBrush = textCharFormat.foreground(); textCharFormat.setForeground(textCharFormat.background()); textCharFormat.setBackground(foregroundBrush); break; } case 8 : { // Concealed/Hidden/Invisible (usefull for passwords) textCharFormat.setForeground(textCharFormat.background()); break; } case 9 : { // Crossed-out characters textCharFormat.setFontStrikeOut(true); break; } case 10 : { // Primary (default) font textCharFormat.setFont(defaultTextCharFormat.font()); break; } case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: { QFontDatabase fontDatabase; QString fontFamily = textCharFormat.fontFamily(); QStringList fontStyles = fontDatabase.styles(fontFamily); int fontStyleIndex = attribute - 11; if (fontStyleIndex < fontStyles.length()) { textCharFormat.setFont(fontDatabase.font(fontFamily, fontStyles.at(fontStyleIndex), textCharFormat.font().pointSize())); } break; } case 20 : { // Fraktur (unsupported) break; } case 21 : { // Set Bold off textCharFormat.setFontWeight(QFont::Normal); break; } case 22 : { // Set Dim off textCharFormat.setFontWeight(QFont::Normal); break; } case 23 : { // Unset italic and unset fraktur textCharFormat.setFontItalic(false); break; } case 24 : { // Unset underlining textCharFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); textCharFormat.setFontUnderline(false); break; } case 25 : { // Unset Blink/Bold textCharFormat.setFontWeight(QFont::Normal); break; } case 26 : { // Reserved break; } case 27 : { // Positive (non-inverted) QBrush backgroundBrush = textCharFormat.background(); textCharFormat.setBackground(textCharFormat.foreground()); textCharFormat.setForeground(backgroundBrush); break; } case 28 : { textCharFormat.setForeground(defaultTextCharFormat.foreground()); textCharFormat.setBackground(defaultTextCharFormat.background()); break; } case 29 : { textCharFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); textCharFormat.setFontUnderline(false); break; } case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: { int colorIndex = attribute - 30; QColor color; if (QFont::Normal < textCharFormat.fontWeight()) { switch (colorIndex) { case 0 : { color = Qt::darkGray; break; } case 1 : { color = Qt::red; break; } case 2 : { color = Qt::green; break; } case 3 : { color = Qt::yellow; break; } case 4 : { color = Qt::blue; break; } case 5 : { color = Qt::magenta; break; } case 6 : { color = Qt::cyan; break; } case 7 : { color = Qt::white; break; } default : { Q_ASSERT(false); } } } else { switch (colorIndex) { case 0 : { color = Qt::black; break; } case 1 : { color = Qt::darkRed; break; } case 2 : { color = Qt::darkGreen; break; } case 3 : { color = Qt::darkYellow; break; } case 4 : { color = Qt::darkBlue; break; } case 5 : { color = Qt::darkMagenta; break; } case 6 : { color = Qt::darkCyan; break; } case 7 : { color = Qt::lightGray; break; } default : { Q_ASSERT(false); } } } textCharFormat.setForeground(color); break; } case 38 : { if (i.hasNext()) { bool ok = false; int selector = i.next().toInt(&ok); Q_ASSERT(ok); QColor color; switch (selector) { case 2 : { if (!i.hasNext()) { break; } int red = i.next().toInt(&ok); Q_ASSERT(ok); if (!i.hasNext()) { break; } int green = i.next().toInt(&ok); Q_ASSERT(ok); if (!i.hasNext()) { break; } int blue = i.next().toInt(&ok); Q_ASSERT(ok); color.setRgb(red, green, blue); break; } case 5 : { if (!i.hasNext()) { break; } int index = i.next().toInt(&ok); Q_ASSERT(ok); if (index >= 0 && index <= 0x07) { // 0x00-0x07: standard colors (as in ESC [ 30..37 m) return parseEscapeSequence(index - 0x00 + 30, i, textCharFormat, defaultTextCharFormat); } else if (index >= 0x08 && index <= 0x0F) { // 0x08-0x0F: high intensity colors (as in ESC [ 90..97 m) return parseEscapeSequence(index - 0x08 + 90, i, textCharFormat, defaultTextCharFormat); } else if (index >= 0x10 && index <= 0xE7) { // 0x10-0xE7: 6*6*6=216 colors: 16 + 36*r + 6*g + b (0≤r,g,b≤5) index -= 0x10; int red = index % 6; index /= 6; int green = index % 6; index /= 6; int blue = index % 6; index /= 6; Q_ASSERT(index == 0); color.setRgb(red, green, blue); break; } else if (index >= 0xE8 && index <= 0xFF) { // 0xE8-0xFF: grayscale from black to white in 24 steps qreal intensity = qreal(index - 0xE8) / (0xFF - 0xE8); color.setRgbF(intensity, intensity, intensity); break; } textCharFormat.setForeground(color); break; } default : { break; } } } break; } case 39 : { textCharFormat.setForeground(defaultTextCharFormat.foreground()); break; } case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: { int colorIndex = attribute - 40; QColor color; switch (colorIndex) { case 0 : { color = Qt::darkGray; break; } case 1 : { color = Qt::red; break; } case 2 : { color = Qt::green; break; } case 3 : { color = Qt::yellow; break; } case 4 : { color = Qt::blue; break; } case 5 : { color = Qt::magenta; break; } case 6 : { color = Qt::cyan; break; } case 7 : { color = Qt::white; break; } default : { Q_ASSERT(false); } } textCharFormat.setBackground(color); break; } case 48 : { if (i.hasNext()) { bool ok = false; int selector = i.next().toInt(&ok); Q_ASSERT(ok); QColor color; switch (selector) { case 2 : { if (!i.hasNext()) { break; } int red = i.next().toInt(&ok); Q_ASSERT(ok); if (!i.hasNext()) { break; } int green = i.next().toInt(&ok); Q_ASSERT(ok); if (!i.hasNext()) { break; } int blue = i.next().toInt(&ok); Q_ASSERT(ok); color.setRgb(red, green, blue); break; } case 5 : { if (!i.hasNext()) { break; } int index = i.next().toInt(&ok); Q_ASSERT(ok); if (index >= 0x00 && index <= 0x07) { // 0x00-0x07: standard colors (as in ESC [ 40..47 m) return parseEscapeSequence(index - 0x00 + 40, i, textCharFormat, defaultTextCharFormat); } else if (index >= 0x08 && index <= 0x0F) { // 0x08-0x0F: high intensity colors (as in ESC [ 100..107 m) return parseEscapeSequence(index - 0x08 + 100, i, textCharFormat, defaultTextCharFormat); } else if (index >= 0x10 && index <= 0xE7) { // 0x10-0xE7: 6*6*6=216 colors: 16 + 36*r + 6*g + b (0≤r,g,b≤5) index -= 0x10; int red = index % 6; index /= 6; int green = index % 6; index /= 6; int blue = index % 6; index /= 6; Q_ASSERT(index == 0); color.setRgb(red, green, blue); break; } else if (index >= 0xE8 && index <= 0xFF) { // 0xE8-0xFF: grayscale from black to white in 24 steps qreal intensity = qreal(index - 0xE8) / (0xFF - 0xE8); color.setRgbF(intensity, intensity, intensity); } } textCharFormat.setBackground(color); break; } } break; } case 49: { textCharFormat.setBackground(defaultTextCharFormat.background()); break; } case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: { int colorIndex = attribute - 90; QColor color; switch (colorIndex) { case 0 : { color = Qt::darkGray; break; } case 1 : { color = Qt::red; break; } case 2 : { color = Qt::green; break; } case 3 : { color = Qt::yellow; break; } case 4 : { color = Qt::blue; break; } case 5 : { color = Qt::magenta; break; } case 6 : { color = Qt::cyan; break; } case 7 : { color = Qt::white; break; } default : { Q_ASSERT(false); } } color.setRedF(color.redF() * 0.8); color.setGreenF(color.greenF() * 0.8); color.setBlueF(color.blueF() * 0.8); textCharFormat.setForeground(color); break; } case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: { int colorIndex = attribute - 100; QColor color; switch (colorIndex) { case 0 : { color = Qt::darkGray; break; } case 1 : { color = Qt::red; break; } case 2 : { color = Qt::green; break; } case 3 : { color = Qt::yellow; break; } case 4 : { color = Qt::blue; break; } case 5 : { color = Qt::magenta; break; } case 6 : { color = Qt::cyan; break; } case 7 : { color = Qt::white; break; } default : { Q_ASSERT(false); } } color.setRedF(color.redF() * 0.8); color.setGreenF(color.greenF() * 0.8); color.setBlueF(color.blueF() * 0.8); textCharFormat.setBackground(color); break; } default : { break; } } } void MainWindow::onExtract() { } void MainWindow::onReturnPressed() { if (sender() == m_ui->pathEdit && !m_ui->pathEdit->text().isEmpty()) setPath(m_ui->pathEdit->text()); } void MainWindow::onIndexDownloaded(const QStringList& index) { int bestVersion = 0; m_ui->binaryComboBox->clear(); for (const QString& str : index) { URDEVersion version(str); if (m_ui->sysReqTable->willRun(version)) bestVersion = m_ui->binaryComboBox->count(); m_ui->binaryComboBox->addItem(version.fileString(false), QVariant::fromValue(version)); } m_ui->binaryComboBox->setCurrentIndex(bestVersion); m_recommendedVersion = m_ui->binaryComboBox->itemData(bestVersion).value(); m_ui->recommendedBinaryLabel->setText(m_recommendedVersion.fileString(false)); m_ui->binaryComboBox->setEnabled(true); if (!m_path.isEmpty()) { checkDownloadedBinary(); m_ui->downloadButton->setEnabled(true); } } void MainWindow::onDownloadPressed() { m_updateURDEButton->hide(); QString filename = m_ui->binaryComboBox->currentData().value().fileString(true); printf("Downloading %s\n", filename.toUtf8().data()); m_ui->launchBtn->setEnabled(false); m_dlManager.fetchBinary(filename, m_path + '/' + filename); } void MainWindow::onUpdateURDEPressed() { m_ui->heclTabs->setCurrentIndex(1); onDownloadPressed(); } void MainWindow::onBinaryDownloaded(const QString& file) { QFileInfo path(file); #ifndef _WIN32 QProcess untar; untar.setWorkingDirectory(path.dir().absolutePath()); untar.start("tar", {"-xvf", path.fileName()}); untar.waitForFinished(); #if __APPLE__ QFile::rename(path.dir().absoluteFilePath(path.baseName()) + ".app", "urde.app"); #else QFile::rename(path.dir().absoluteFilePath(path.baseName()), "urde"); #endif QFile::remove(file); #else QFile::rename(file, "urde.exe"); #endif checkDownloadedBinary(); } void MainWindow::checkDownloadedBinary() { m_updateURDEButton->hide(); #if __APPLE__ QString urdePath = m_path + "/urde.app/Contents/MacOS/urde"; #elif _WIN32 QString urdePath = m_path + "urde.exe"; #else QString urdePath = m_path + "urde"; #endif QProcess proc; proc.start(urdePath, {"--dlpackage"}, QIODevice::ReadOnly); if (proc.waitForStarted()) { proc.waitForFinished(); QString dlPackage = QString::fromUtf8(proc.readLine()).trimmed(); if (proc.exitCode() == 100) { if (dlPackage.isEmpty()) { m_ui->currentBinaryLabel->setText(QStringLiteral("unknown")); } else { URDEVersion v(dlPackage); m_ui->currentBinaryLabel->setText(v.fileString(false)); if (m_recommendedVersion.isValid() && v.isValid() && m_recommendedVersion.getVersion() > v.getVersion()) { m_updateURDEButton->show(); } } } else { m_ui->currentBinaryLabel->setText(QStringLiteral("unknown")); } } else { m_ui->currentBinaryLabel->setText(QStringLiteral("none")); } } void MainWindow::setPath(const QString& path) { if (!path.isEmpty()) { m_path = path; m_settings.setValue(QStringLiteral("working_dir"), m_path); m_ui->pathEdit->setText(m_path); m_ui->extractBtn->setEnabled(true); m_ui->packageBtn->setEnabled(true); m_ui->downloadButton->setToolTip(QString()); m_ui->downloadButton->setEnabled(m_ui->binaryComboBox->isEnabled()); checkDownloadedBinary(); } else { m_ui->downloadButton->setToolTip(QStringLiteral("Working directory must be set")); m_ui->downloadButton->setEnabled(false); m_ui->currentBinaryLabel->setText(QStringLiteral("none")); } } void MainWindow::initSlots() { #ifdef Q_OS_WIN m_heclProc.setEnvironment(QProcessEnvironment::systemEnvironment().toStringList() + QStringList("ConEmuANSI=ON")); #endif connect(&m_heclProc, &QProcess::readyRead, [=](){ m_ansiString = m_heclProc.readAll(); m_ui->processOutput->clear(); setTextTermFormatting(m_ui->processOutput, m_ansiString); m_ui->processOutput->ensureCursorVisible(); }); connect(m_ui->extractBtn, &QPushButton::clicked, [=](){ if (m_path.isEmpty()) return; QString path = QFileInfo(m_path).absolutePath(); m_ansiString.clear(); m_heclProc.close(); m_heclProc.terminate(); m_heclProc.setProcessChannelMode(QProcess::ProcessChannelMode::MergedChannels); m_heclProc.setWorkingDirectory(path); m_heclProc.start("../hecl/driver/hecl.exe", {"extract", m_path, "-y"}); }); connect(m_ui->browseBtn, &QPushButton::clicked, [=]() { FileDirDialog dialog(this); dialog.setWindowTitle("Select Working Directory"); int res = dialog.exec();//QFileDialog::getOpenFileName(this, "Select ISO or Directory"); if (res == QFileDialog::Rejected) return; if (dialog.selectedFiles().size() <= 0) return; /* TODO: Add beacon detection */ setPath(dialog.selectedFiles().at(0)); }); connect(m_ui->packageBtn, &QPushButton::clicked, [=](){ if (m_path.isEmpty()) return; QString projectDir = m_path; if (projectDir.endsWith(".iso", Qt::CaseInsensitive)) projectDir.remove(m_path.length() - 4, 4); m_ansiString.clear(); m_heclProc.close(); m_heclProc.terminate(); m_heclProc.setWorkingDirectory(projectDir); m_heclProc.setProcessChannelMode(QProcess::ProcessChannelMode::MergedChannels); m_heclProc.start("../hecl/driver/hecl.exe", {"package", "-y"}); }); connect(m_ui->downloadButton, SIGNAL(clicked()), this, SLOT(onDownloadPressed())); } void MainWindow::setTextTermFormatting(QTextEdit* textEdit, const QString& text) { QTextDocument * document = textEdit->document(); QRegExp const escapeSequenceExpression(R"(\x1B\[([\d;]+)m)"); QTextCursor cursor(document); QTextCharFormat defaultTextCharFormat = cursor.charFormat(); cursor.beginEditBlock(); int offset = escapeSequenceExpression.indexIn(text); cursor.insertText(text.mid(0, offset)); QTextCharFormat textCharFormat = defaultTextCharFormat; while (!(offset < 0)) { int previousOffset = offset + escapeSequenceExpression.matchedLength(); QStringList capturedTexts = escapeSequenceExpression.capturedTexts().back().split(';'); QListIterator< QString > i(capturedTexts); while (i.hasNext()) { bool ok = false; int attribute = i.next().toInt(&ok); Q_ASSERT(ok); parseEscapeSequence(attribute, i, textCharFormat, defaultTextCharFormat); } offset = escapeSequenceExpression.indexIn(text, previousOffset); if (offset < 0) { cursor.insertText(text.mid(previousOffset), textCharFormat); } else { cursor.insertText(text.mid(previousOffset, offset - previousOffset), textCharFormat); } } cursor.setCharFormat(defaultTextCharFormat); cursor.endEditBlock(); cursor.movePosition(QTextCursor::End); textEdit->setTextCursor(cursor); }