From 07ea3f86910ad2315153bdf1d5adf753d93eea61 Mon Sep 17 00:00:00 2001 From: Vadim Troshchinskiy Shmelev Date: Thu, 19 Oct 2023 13:15:46 +0200 Subject: [PATCH] Modernize browser for Qt6, fix command execution --- src/CMakeLists.txt | 5 +- src/mainwindow.cpp | 214 ++++++++++++++++++++++++++++++++++++------- src/mainwindow.h | 52 ++++++++--- src/ogbrowser.h | 4 + src/ogurlhandler.cpp | 72 +++++++++++++++ src/ogurlhandler.h | 44 +++++++++ 6 files changed, 341 insertions(+), 50 deletions(-) create mode 100644 src/ogbrowser.h create mode 100644 src/ogurlhandler.cpp create mode 100644 src/ogurlhandler.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 70071f3..1b81c6d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,7 +9,7 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -find_package(QT NAMES Qt5 COMPONENTS Widgets LinguistTools Network WebEngineWidgets REQUIRED) +find_package(QT NAMES Qt6 COMPONENTS Widgets LinguistTools Network WebEngineWidgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets LinguistTools Network WebEngineWidgets REQUIRED) @@ -18,6 +18,7 @@ message(STATUS "Building browser with Qt ${QT_VERSION}") set(SOURCES main.cpp mainwindow.cpp + ogurlhandler.cpp ) @@ -27,7 +28,7 @@ set_property(TARGET OGBrowser PROPERTY CXX_STANDARD 17) set_property(TARGET OGBrowser PROPERTY CXX_STANDARD_REQUIRED ON) target_include_directories(OGBrowser PRIVATE "digitalclock" "qtermwidget/lib") -target_link_libraries(OGBrowser PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::WebEngineWidgets DigitalClock qtermwidget5) +target_link_libraries(OGBrowser PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::WebEngineWidgets DigitalClock qtermwidget6) message(STATUS "Looking for headers in ${PROJECT_BINARY_DIR}") target_include_directories(OGBrowser PRIVATE ${qtermwidget_INCLUDE_DIRS} ${DigitalClock_INCLUDE_DIRS} ${qtermwidget_LIB_DIRS}/lib ${PROJECT_BINARY_DIR}/../lib) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b95bbeb..14f9f61 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -16,10 +16,16 @@ #include #include #include +#include +#include +#include + + #include #include "qtermwidget.h" #include "digitalclock.h" +#include "ogurlhandler.h" #define BUFFERSIZE 2048 #define REGEXP_STRING "^\\[(\\d+)\\]" @@ -28,15 +34,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),m_web(new QWebEngineView()),m_output(new QTextEdit()), - m_process(new QProcess(this)), m_logfile(0),m_logstream(0),m_numberTerminal(0) { // Graphic - showFullScreen(); setWindowTitle(tr("OpenGnsys Browser")); setCentralWidget(m_web); readEnvironmentValues(); + m_is_admin = qgetenv("ogactiveadmin") == "true"; + // Open the log file for append if(m_env.contains("OGLOGFILE") && m_env["OGLOGFILE"]!="") { @@ -74,7 +80,7 @@ MainWindow::MainWindow(QWidget *parent) // Assign tabs to dock dock->setWidget(m_tabs); // Assign tabs dock to the mainwindow if admin mode is active - if(m_env.contains("ogactiveadmin") && m_env["ogactiveadmin"] == "true") + if(isAdmin()) addDockWidget(Qt::BottomDockWidgetArea,dock); // Top Dock @@ -88,7 +94,7 @@ MainWindow::MainWindow(QWidget *parent) // WebBar to dock dock->setWidget(m_webBar); // Assign top dock to the mainwindow if admin mode is active - if(m_env.contains("ogactiveadmin") && m_env["ogactiveadmin"] == "true") + if(isAdmin()) addDockWidget(Qt::TopDockWidgetArea,dock); // Status bar @@ -139,23 +145,68 @@ MainWindow::MainWindow(QWidget *parent) // SIGNAL(sslErrors(QNetworkReply*, const QList &)), this, // SLOT(slotSslErrors(QNetworkReply*))); - // Process signals - connect(m_process,SIGNAL(started()),this,SLOT(slotProcessStarted())); - connect(m_process,SIGNAL(finished(int,QProcess::ExitStatus)), - this,SLOT(slotProcessFinished(int,QProcess::ExitStatus))); - connect(m_process,SIGNAL(error(QProcess::ProcessError)), - this,SLOT(slotProcessError(QProcess::ProcessError))); - connect(m_process,SIGNAL(readyReadStandardOutput()),this,SLOT(slotProcessOutput())); - connect(m_process,SIGNAL(readyReadStandardError()), - this,SLOT(slotProcessErrorOutput())); // Dock signals connect(button,SIGNAL(clicked()),this,SLOT(slotCreateTerminal())); connect(m_webBar,SIGNAL(returnPressed()),this,SLOT(slotWebBarReturnPressed())); + + QWebEngineUrlScheme schemeCommand("command"); + schemeCommand.setSyntax(QWebEngineUrlScheme::Syntax::Path); + schemeCommand.setDefaultPort(0); + schemeCommand.setFlags(QWebEngineUrlScheme::LocalScheme); + QWebEngineUrlScheme::registerScheme(schemeCommand); + + QWebEngineUrlScheme schemeCommandOut("command+output"); + schemeCommandOut.setSyntax(QWebEngineUrlScheme::Syntax::Path); + schemeCommandOut.setDefaultPort(0); + schemeCommandOut.setFlags(QWebEngineUrlScheme::LocalScheme); + QWebEngineUrlScheme::registerScheme(schemeCommandOut); + + QWebEngineUrlScheme schemeCommandConfirm("command+confirm"); + schemeCommandConfirm.setSyntax(QWebEngineUrlScheme::Syntax::Path); + schemeCommandConfirm.setDefaultPort(0); + schemeCommandConfirm.setFlags(QWebEngineUrlScheme::LocalScheme); + QWebEngineUrlScheme::registerScheme(schemeCommandConfirm); + + + OGBrowserUrlHandlerCommand *cmdHandler = new OGBrowserUrlHandlerCommand(this); + connect(cmdHandler, &OGBrowserUrlHandlerCommand::command, this, &MainWindow::commandQueued); + + + registerHandler("command", false, false); + registerHandler("command+output", false, true); + registerHandler("command+confirm", true, false); + registerHandler("command+confirm+output", true, true); + registerHandler("command+output+confirm", true, true); + + +/* + QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("command", cmdHandler); + + OGBrowserUrlHandlerCommand *cmdOutHandler = new OGBrowserUrlHandlerCommand(this); + connect(cmdOutHandler, &OGBrowserUrlHandlerCommand::command, this, &MainWindow::commandQueued); + cmdOutHandler->setReturnOutput(true); + QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("command+output", cmdOutHandler); + + + OGBrowserUrlHandlerCommand *cmdConfHandler = new OGBrowserUrlHandlerCommand(this); + connect(cmdConfHandler, &OGBrowserUrlHandlerCommand::command, this, &MainWindow::commandQueued); + cmdConfHandler->setAskConfirmation(true); + QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("command+confirm", cmdConfHandler); +*/ + + QStringList arguments=QCoreApplication::arguments(); - m_webBar->setText(arguments[1]); - m_web->load(QUrl(arguments[1])); + m_webBar->setText(arguments.at(1)); + m_web->load(QUrl(arguments.at(1))); + + + + + + showMaximized(); + showFullScreen(); } MainWindow::~MainWindow() @@ -169,10 +220,86 @@ MainWindow::~MainWindow() delete m_logstream; } + +void MainWindow::registerHandler(const QString &commandName, bool confirm, bool returnOutput) { + OGBrowserUrlHandlerCommand *handler = new OGBrowserUrlHandlerCommand(this); + connect(handler, &OGBrowserUrlHandlerCommand::command, this, &MainWindow::commandQueued); + handler->setAskConfirmation(confirm); + handler->setReturnOutput(returnOutput); + QWebEngineProfile::defaultProfile()->installUrlSchemeHandler(commandName.toLatin1(), handler); +} + +void MainWindow::commandQueued(const QString &command, bool confirm, bool returnOutput) { + //PendingCommand cmd; + + qInfo() << "Queued command:" << command; + + if (confirm) { + QMessageBox msgBox; + msgBox.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); + msgBox.setWindowTitle(tr(gettext("AVISO"))); + msgBox.setIcon(QMessageBox::Question); + msgBox.setTextFormat(Qt::RichText); + msgBox.setText(tr(gettext("La siguiente acción puede modificar datos o tardar varios minutos. El equipo no podrá ser utilizado durante su ejecución."))); + QPushButton *execButton = msgBox.addButton(tr(gettext("Ejecutar")), QMessageBox::ActionRole); + msgBox.addButton(tr(gettext("Cancelar")), QMessageBox::RejectRole); + msgBox.setDefaultButton(execButton); + msgBox.exec(); + + if (msgBox.clickedButton() != execButton) { + qInfo() << "User rejected running the command"; + return; + } + } + + if (returnOutput && !isAdmin()) { + int w=MainWindow::width(), h=MainWindow::height(); + m_output->setWindowFlags(Qt::Window); + m_output->move(100, 100); + m_output->setFixedSize(w*0.8-100, h*0.8-100); + m_output->show(); + } + + + m_command.command = command; + m_command.confirm = confirm; + m_command.returnOutput = returnOutput; + + + + + QStringList list=command.split(" ",Qt::SkipEmptyParts); + QString program=list.takeFirst(); + + m_command.process = new QProcess(this); + m_command.process->setReadChannel(QProcess::StandardOutput); + m_command.process->setEnvironment(QProcess::systemEnvironment()); + + // Process signals + connect(m_command.process, &QProcess::started,this, &MainWindow::slotProcessStarted); + connect(m_command.process, &QProcess::finished,this,&MainWindow::slotProcessFinished); + connect(m_command.process, &QProcess::errorOccurred, this,&MainWindow::slotProcessError); + connect(m_command.process, &QProcess::readyReadStandardOutput,this,&MainWindow::slotProcessOutput); + connect(m_command.process, &QProcess::readyReadStandardError,this,&MainWindow::slotProcessErrorOutput); + + + if(isAdmin()) { + m_output->setTextColor(QColor(Qt::darkGreen)); + print(tr(gettext("Lanzando el comando: "))+command); + m_output->setTextColor(QColor(Qt::black)); + } else { + write(tr(gettext("Lanzando el comando: "))+command); + } + + m_command.process->start(program,list); + startProgressBar(); + +} + void MainWindow::slotLinkHandle(const QUrl &url) { // Check if it's executing another process - if(m_process->state() != QProcess::NotRunning) + if(m_command.process->state() != QProcess::NotRunning) { print(tr(gettext("Hay otro proceso en ejecución. Por favor espere."))); return; @@ -180,7 +307,7 @@ void MainWindow::slotLinkHandle(const QUrl &url) QString urlString = url.toString(); QString urlScheme = url.scheme(); // Clear the output widget for a normal user - if(! m_env.contains("ogactiveadmin") || m_env["ogactiveadmin"] != "true") + if(!isAdmin()) { m_output->clear(); } @@ -202,8 +329,7 @@ void MainWindow::slotLinkHandle(const QUrl &url) if (msgBox.clickedButton() == execButton) { // For command with confirmation and output link, show an output window to non-admin user - if((urlScheme == COMMAND_CONFIRM_OUTPUT || urlScheme == COMMAND_OUTPUT_CONFIRM) && - (! m_env.contains("ogactiveadmin") || m_env["ogactiveadmin"] != "true")) + if((urlScheme == COMMAND_CONFIRM_OUTPUT || urlScheme == COMMAND_OUTPUT_CONFIRM) && !isAdmin()) { int w=MainWindow::width(), h=MainWindow::height(); m_output->setWindowFlags(Qt::Window); @@ -218,8 +344,7 @@ void MainWindow::slotLinkHandle(const QUrl &url) else if(urlScheme == COMMAND || urlScheme == COMMAND_OUTPUT) { // For command with output link, show an output window to non-admin user - if(urlScheme == COMMAND_OUTPUT && - (! m_env.contains("ogactiveadmin") || m_env["ogactiveadmin"] != "true")) + if(urlScheme == COMMAND_OUTPUT && !isAdmin()) { int w=MainWindow::width(), h=MainWindow::height(); m_output->setWindowFlags(Qt::Window); @@ -253,7 +378,10 @@ void MainWindow::slotWebLoadFinished(bool ok) // If any error ocurred, show a pop up // Sometimes when the url hasn't got a dot, i.e /var/www/pageweb, // the return value is always true so we check the bytes received too - if(ok == false) + qWarning() << "Load finished. URL: " << m_web->url() << "; ok = " << ok; + +#if 0 + if(!ok && !m_web->url().isEmpty()) { QMessageBox msgBox; msgBox.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); @@ -279,6 +407,9 @@ void MainWindow::slotWebLoadFinished(bool ok) { finishProgressBar(); } +#endif + finishProgressBar(); + } void MainWindow::slotUrlChanged(const QUrl &url) @@ -298,12 +429,14 @@ void MainWindow::slotProcessStarted() void MainWindow::slotProcessOutput() { - m_process->setReadChannel(QProcess::StandardOutput); + m_command.process->setReadChannel(QProcess::StandardOutput); char buf[BUFFERSIZE]; - while((m_process->readLine(buf,BUFFERSIZE) > 0)) + while((m_command.process->readLine(buf,BUFFERSIZE) > 0)) { QString s(buf); - if(m_env.contains("ogactiveadmin") && m_env["ogactiveadmin"] == "true") + qInfo() << "OUT: " << buf; + + if(isAdmin()) { m_output->insertPlainText(tr("Proc. stdout: ")); } @@ -314,12 +447,18 @@ void MainWindow::slotProcessOutput() void MainWindow::slotProcessErrorOutput() { - m_process->setReadChannel(QProcess::StandardError); + + // QProcess *process = qobject_cast(sender()); + // QVector::iterator it=std::find(m_commands.begin(), m_commands.end(), []()) + + m_command.process->setReadChannel(QProcess::StandardError); char buf[BUFFERSIZE]; - while((m_process->readLine(buf,BUFFERSIZE) > 0)) + while((m_command.process->readLine(buf,BUFFERSIZE) > 0)) { QString s(buf); - if(m_env.contains("ogactiveadmin") && m_env["ogactiveadmin"] == "true") + qInfo() << "ERR: " << buf; + + if(isAdmin()) { m_output->insertPlainText(tr("Proc. stderr: ")); } @@ -331,7 +470,10 @@ void MainWindow::slotProcessErrorOutput() void MainWindow::slotProcessFinished(int code, QProcess::ExitStatus status) { - if(m_env.contains("ogactiveadmin") && m_env["ogactiveadmin"] == "true") + + qInfo() << "Finished: " << m_command.command << "with status" << status; + + if(isAdmin()) { // Admin user: show process status if(status==QProcess::NormalExit) @@ -367,6 +509,8 @@ void MainWindow::slotProcessFinished(int code, QProcess::ExitStatus status) void MainWindow::slotProcessError(QProcess::ProcessError error) { + qCritical() << "Error: " << m_command.command << "with status" << error; + QString errorMsg; switch(error) { @@ -493,10 +637,12 @@ void MainWindow::captureOutputForStatusBar(QString output) // Modify the status bar output=output.trimmed(); // Get percentage (string starts with "[Number]") - QRegExp regexp(REGEXP_STRING); - if(regexp.indexIn(output) != -1) + QRegularExpression regexp(REGEXP_STRING); + QRegularExpressionMatch match = regexp.match(output); + + if(match.hasMatch()) { - int pass=regexp.cap(1).toInt(); + int pass=match.captured(1).toInt(); output.replace(regexp,""); m_progressBar->setValue(pass); m_progressBar->setFormat("%p%"+output); @@ -527,7 +673,7 @@ void MainWindow::finishProgressBar() // Execute a command void MainWindow::executeCommand(QString &string) { - QStringList list=string.split(" ",Qt::SkipEmptyParts); +/* QStringList list=string.split(" ",Qt::SkipEmptyParts); QString program=list.takeFirst(); m_process->setReadChannel(QProcess::StandardOutput); // Assign the same Browser's environment to the process @@ -544,7 +690,7 @@ void MainWindow::executeCommand(QString &string) { write(tr(gettext("Lanzando el comando: "))+string); } - startProgressBar(); + startProgressBar(); */ } // Returns communication speed diff --git a/src/mainwindow.h b/src/mainwindow.h index 58aa925..eb44531 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -16,21 +16,31 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include + + #include "digitalclock.h" -class QWebEngineView; -class QTextEdit; -class QVBoxLayout; -class QProcess; -class QStringList; -class QString; -class QUrl; -class QFile; -class QTextStream; -class QTermWidget; -class QProgressBar; -class QLineEdit; -class QLabel; + + +struct PendingCommand { + QString command = ""; + bool confirm = false; + bool returnOutput = false; + + QProcess *process = nullptr; + + bool operator==(const PendingCommand &other) { + return other.process == process; + } +}; + class MainWindow : public QMainWindow { @@ -63,6 +73,16 @@ class MainWindow : public QMainWindow void slotWebBarReturnPressed(); void slotUrlChanged(const QUrl &url); + void commandQueued(const QString &command, bool confirm, bool returnOutput); + + + private: + bool isAdmin() const { return m_is_admin; } + void registerHandler(const QString &name, bool confirm, bool output); + + bool m_is_admin{false}; + + //Functions protected: int readEnvironmentValues(); @@ -85,12 +105,16 @@ class MainWindow : public QMainWindow QTabWidget *m_tabs; QLineEdit *m_webBar; - QProcess *m_process; + //QProcess *m_process; QMap m_env; QFile *m_logfile; QTextStream *m_logstream; + PendingCommand m_command; + //QVector m_commands; + + int m_numberTerminal; }; diff --git a/src/ogbrowser.h b/src/ogbrowser.h new file mode 100644 index 0000000..59827fe --- /dev/null +++ b/src/ogbrowser.h @@ -0,0 +1,4 @@ + +#pragma once + +#include < \ No newline at end of file diff --git a/src/ogurlhandler.cpp b/src/ogurlhandler.cpp new file mode 100644 index 0000000..3d3548b --- /dev/null +++ b/src/ogurlhandler.cpp @@ -0,0 +1,72 @@ + + +#include "ogurlhandler.h" +#include +#include +#include + + +void OGBrowserUrlHandlerCommand::requestStarted(QWebEngineUrlRequestJob *job) { + + qInfo() << "Command request: " << job->requestUrl(); + + + emit command(job->requestUrl().path(), askConfirmation(), returnOuput()); + //job->redirect(QUrl()); + job->fail(QWebEngineUrlRequestJob::NoError); + + return; + /* + // For all command with confirmation links, show a popup box + if (askConfirmation()) { + QMessageBox msgBox; + msgBox.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); + msgBox.setWindowTitle(tr(gettext("AVISO"))); + msgBox.setIcon(QMessageBox::Question); + msgBox.setTextFormat(Qt::RichText); + msgBox.setText(tr(gettext("La siguiente acción puede modificar datos o tardar varios minutos. El equipo no podrá ser utilizado durante su ejecución."))); + QPushButton *execButton = msgBox.addButton(tr(gettext("Ejecutar")), QMessageBox::ActionRole); + msgBox.addButton(tr(gettext("Cancelar")), QMessageBox::RejectRole); + msgBox.setDefaultButton(execButton); + msgBox.exec(); + // Continue if user press the execution button + if (msgBox.clickedButton() == execButton) + { + // For command with confirmation and output link, show an output window to non-admin user + if((urlScheme == COMMAND_CONFIRM_OUTPUT || urlScheme == COMMAND_OUTPUT_CONFIRM) && + (! m_env.contains("ogactiveadmin") || m_env["ogactiveadmin"] != "true")) + { + int w=MainWindow::width(), h=MainWindow::height(); + m_output->setWindowFlags(Qt::Window); + m_output->move(100, 100); + m_output->setFixedSize(w*0.8-100, h*0.8-100); + m_output->show(); + } + } else { + job->fail(QWebEngineUrlRequestJob::RequestDenied); + return; + } + } + + // Execute the command + executeCommand(urlString.remove(0, urlScheme.length()+1)); + + + + + else if(urlScheme == COMMAND || urlScheme == COMMAND_OUTPUT) + { + // For command with output link, show an output window to non-admin user + if(urlScheme == COMMAND_OUTPUT && + (! m_env.contains("ogactiveadmin") || m_env["ogactiveadmin"] != "true")) + { + int w=MainWindow::width(), h=MainWindow::height(); + m_output->setWindowFlags(Qt::Window); + m_output->move(100, 100); + m_output->setFixedSize(w*0.8-100, h*0.8-100); + m_output->show(); + } + // Execute the command + executeCommand(urlString.remove(0, urlScheme.length()+1)); + } */ +} \ No newline at end of file diff --git a/src/ogurlhandler.h b/src/ogurlhandler.h new file mode 100644 index 0000000..adb08b9 --- /dev/null +++ b/src/ogurlhandler.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#define COMMAND "command" +#define COMMAND_CONFIRM "command+confirm" +#define COMMAND_WITH_CONFIRMATION "commandwithconfirmation" // Backwards compatibility +#define COMMAND_OUTPUT "command+output" +#define COMMAND_CONFIRM_OUTPUT "command+confirm+output" +#define COMMAND_OUTPUT_CONFIRM "command+output+confirm" +#define ENVIRONMENT "OGLOGFILE,ogactiveadmin,DEFAULTSPEED" + +class OGBrowserUrlHandlerCommand : public QWebEngineUrlSchemeHandler { + Q_OBJECT +public: + + OGBrowserUrlHandlerCommand(QObject *parent = nullptr) : QWebEngineUrlSchemeHandler(parent) { + + }; + + virtual ~OGBrowserUrlHandlerCommand() { + + }; + + virtual void requestStarted(QWebEngineUrlRequestJob *job); + bool askConfirmation() const { return m_ask_confirmation; } + bool returnOuput() const { return m_return_output; } + + void setAskConfirmation(const bool ask) { m_ask_confirmation = ask; } + void setReturnOutput(const bool retOutput) { m_return_output = retOutput; } + +signals: + void command(const QString &command, bool confirm, bool returnOutput); + + +private: + + bool m_ask_confirmation = false; + bool m_return_output = false; + +}; + +