summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-10-30 23:42:59 -0400
committerisanae <14251494+isanae@users.noreply.github.com>2019-11-06 07:44:57 -0500
commitdb0b92776b5c9a34ebb1a5ce5c3f0b105844ee16 (patch)
tree4024b434840a21d37cac3c43f229fb2c3ce5a91a /src
parent2aa70de49e89245467299d94d76825bad31c63a2 (diff)
added lockwidget to replace all the other dialogs
rewrote ProcessRunner to have a bunch of setters and then a run() fixed bad exit code when waiting on a process that's already completed removed lock()/unlock() from main window, ProcessRunner is in charge of that now
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/envmodule.cpp1
-rw-r--r--src/iuserinterface.h6
-rw-r--r--src/lockwidget.cpp224
-rw-r--r--src/lockwidget.h68
-rw-r--r--src/mainwindow.cpp44
-rw-r--r--src/mainwindow.h7
-rw-r--r--src/organizercore.cpp8
-rw-r--r--src/organizercore.h3
-rw-r--r--src/organizerproxy.cpp19
-rw-r--r--src/pch.h47
-rw-r--r--src/processrunner.cpp687
-rw-r--r--src/processrunner.h70
13 files changed, 806 insertions, 381 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5e909760..168b79dc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -144,6 +144,7 @@ SET(organizer_SRCS
colortable.cpp
sanitychecks.cpp
processrunner.cpp
+ lockwidget.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -268,6 +269,7 @@ SET(organizer_HDRS
envwindows.h
colortable.h
processrunner.h
+ lockwidget.h
shared/windows_error.h
shared/error_report.h
@@ -486,6 +488,7 @@ set(widgets
filterwidget
icondelegate
lcdnumber
+ lockwidget
loglist
loghighlighter
modflagicondelegate
diff --git a/src/envmodule.cpp b/src/envmodule.cpp
index 0e2e8ec7..09593e61 100644
--- a/src/envmodule.cpp
+++ b/src/envmodule.cpp
@@ -545,7 +545,6 @@ Process getProcessTree(HANDLE parent)
}
if (root.pid() == 0) {
- log::error("process {} is not running", parentPID);
return {};
}
diff --git a/src/iuserinterface.h b/src/iuserinterface.h
index 91487aee..e5755f03 100644
--- a/src/iuserinterface.h
+++ b/src/iuserinterface.h
@@ -4,12 +4,13 @@
#include "modinfodialogfwd.h"
#include "ilockedwaitingforprocess.h"
+#include "lockwidget.h"
#include <iplugintool.h>
#include <ipluginmodpage.h>
#include <delayedfilewriter.h>
-
#include <QMenu>
+
class IUserInterface
{
public:
@@ -31,9 +32,6 @@ public:
virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0;
- virtual ILockedWaitingForProcess* lock() = 0;
- virtual void unlock() = 0;
-
virtual QWidget* qtWidget() = 0;
};
diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp
new file mode 100644
index 00000000..720cac36
--- /dev/null
+++ b/src/lockwidget.cpp
@@ -0,0 +1,224 @@
+#include "lockwidget.h"
+#include "mainwindow.h"
+#include <QGraphicsDropShadowEffect>
+#include <QMenuBar>
+#include <QStatusBar>
+
+QWidget* createTransparentWidget(QWidget* parent=nullptr)
+{
+ auto* w = new QWidget(parent);
+
+ w->setWindowOpacity(0);
+ w->setAttribute(Qt::WA_NoSystemBackground);
+ w->setAttribute(Qt::WA_TranslucentBackground);
+
+ return w;
+}
+
+
+LockWidget::LockWidget(QWidget* parent, Reasons reason) :
+ m_parent(parent), m_overlay(nullptr), m_info(nullptr), m_result(NoResult),
+ m_filter(nullptr)
+{
+ if (reason != NoReason) {
+ lock(reason);
+ }
+}
+
+LockWidget::~LockWidget()
+{
+ unlock();
+}
+
+void LockWidget::lock(Reasons reason)
+{
+ m_result = StillLocked;
+ createUi(reason);
+}
+
+void LockWidget::unlock()
+{
+ m_overlay.reset();
+
+ if (m_filter && m_parent) {
+ m_parent->removeEventFilter(m_filter.get());
+ }
+
+ enableAll();
+}
+
+void LockWidget::setInfo(DWORD pid, const QString& name)
+{
+ m_info->setText(QString("%1 (%2)").arg(name).arg(pid));
+}
+
+LockWidget::Results LockWidget::result() const
+{
+ return m_result;
+}
+
+void LockWidget::createUi(Reasons reason)
+{
+ if (m_parent) {
+ m_overlay.reset(createTransparentWidget(m_parent));
+ m_overlay->setWindowFlags(m_overlay->windowFlags() & Qt::FramelessWindowHint);
+ m_overlay->setGeometry(m_parent->rect());
+ } else {
+ m_overlay.reset(new QDialog);
+ }
+
+ auto* center = new QFrame;
+
+ if (m_parent) {
+ center->setFrameStyle(QFrame::StyledPanel);
+ center->setLineWidth(1);
+ center->setAutoFillBackground(true);
+
+ auto* shadow = new QGraphicsDropShadowEffect;
+ shadow->setBlurRadius(50);
+ shadow->setOffset(0);
+ shadow->setColor(QColor(0, 0, 0, 100));
+ center->setGraphicsEffect(shadow);
+ }
+
+ m_info = new QLabel(" ");
+ m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter);
+
+ auto* ly = new QVBoxLayout(center);
+
+ if (!m_parent) {
+ ly->setContentsMargins(0, 0, 0, 0);
+ }
+
+ auto* message = new QLabel;
+ ly->addWidget(message);
+ ly->addWidget(m_info);
+
+ auto* buttons = new QWidget;
+ auto* buttonsLayout = new QHBoxLayout(buttons);
+ ly->addWidget(buttons);
+
+ switch (reason)
+ {
+ case LockUI:
+ {
+ message->setText(QObject::tr(
+ "Mod Organizer is locked while the executable is running."));
+
+ auto* unlockButton = new QPushButton(QObject::tr("Unlock"));
+ QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); });
+ buttonsLayout->addWidget(unlockButton);
+
+ break;
+ }
+
+ case OutputRequired:
+ {
+ message->setText(QObject::tr(
+ "The executable must run to completion because a its output is "
+ "required."));
+
+ auto* unlockButton = new QPushButton(QObject::tr("Unlock"));
+ QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); });
+ buttonsLayout->addWidget(unlockButton);
+
+ break;
+ }
+
+ case PreventExit:
+ {
+ message->setText(QObject::tr(
+ "Mod Organizer is waiting on processes to finish before exiting."));
+
+ auto* exit = new QPushButton(QObject::tr("Exit Now"));
+ QObject::connect(exit, &QPushButton::clicked, [&]{ onForceUnlock(); });
+ buttonsLayout->addWidget(exit);
+
+ auto* cancel = new QPushButton(QObject::tr("Cancel"));
+ QObject::connect(cancel, &QPushButton::clicked, [&]{ onCancel(); });
+ buttonsLayout->addWidget(cancel);
+
+ break;
+ }
+ }
+
+ auto* grid = new QGridLayout(m_overlay.get());
+ grid->addWidget(createTransparentWidget(), 0, 1);
+ grid->addWidget(createTransparentWidget(), 2, 1);
+ grid->addWidget(createTransparentWidget(), 1, 0);
+ grid->addWidget(createTransparentWidget(), 1, 2);
+ grid->addWidget(center, 1, 1);
+
+ if (!m_parent) {
+ grid->setContentsMargins(0, 0, 0, 0);
+ }
+
+ grid->setRowStretch(0, 1);
+ grid->setRowStretch(2, 1);
+ grid->setColumnStretch(0, 1);
+ grid->setColumnStretch(2, 1);
+
+ disableAll();
+
+ if (m_parent) {
+ m_filter.reset(new Filter);
+ m_filter->resized = [=]{ m_overlay->setGeometry(m_parent->rect()); };
+ m_parent->installEventFilter(m_filter.get());
+ }
+
+ m_overlay->setFocusPolicy(Qt::TabFocus);
+ m_overlay->setFocus();
+ m_overlay->show();
+ m_overlay->setEnabled(true);
+}
+
+void LockWidget::onForceUnlock()
+{
+ m_result = ForceUnlocked;
+ unlock();
+}
+
+void LockWidget::onCancel()
+{
+ m_result = Cancelled;
+ unlock();
+}
+
+void LockWidget::disableAll()
+{
+ if (!m_parent) {
+ // nothing to disable without a main window
+ return;
+ }
+
+ if (auto* mw=dynamic_cast<QMainWindow*>(m_parent)) {
+ disable(mw->centralWidget());
+ disable(mw->menuBar());
+ disable(mw->statusBar());
+ }
+
+ for (auto* tb : m_parent->findChildren<QToolBar*>()) {
+ disable(tb);
+ }
+
+ for (auto* d : m_parent->findChildren<QDockWidget*>()) {
+ disable(d);
+ }
+}
+
+void LockWidget::enableAll()
+{
+ for (auto* w : m_disabled) {
+ w->setEnabled(true);
+ }
+
+ m_disabled.clear();
+}
+
+void LockWidget::disable(QWidget* w)
+{
+ if (w->isEnabled()) {
+ w->setEnabled(false);
+ m_disabled.push_back(w);
+ }
+}
diff --git a/src/lockwidget.h b/src/lockwidget.h
new file mode 100644
index 00000000..bfb0b30f
--- /dev/null
+++ b/src/lockwidget.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <QMainWindow>
+
+class LockWidget
+{
+public:
+ enum Reasons
+ {
+ NoReason = 0,
+ LockUI,
+ OutputRequired,
+ PreventExit
+ };
+
+ enum Results
+ {
+ NoResult = 0,
+ StillLocked,
+ ForceUnlocked,
+ Cancelled
+ };
+
+ LockWidget(QWidget* parent, Reasons reason=NoReason);
+ ~LockWidget();
+
+ void lock(Reasons reason);
+ void unlock();
+
+ void setInfo(DWORD pid, const QString& name);
+ Results result() const;
+
+private:
+ class Filter : public QObject
+ {
+ public:
+ std::function<void ()> resized;
+
+ protected:
+ bool eventFilter(QObject* o, QEvent* e) override
+ {
+ if (e->type() == QEvent::Resize) {
+ if (resized) {
+ resized();
+ }
+ }
+
+ return QObject::eventFilter(o, e);
+ }
+ };
+
+
+ QWidget* m_parent;
+ std::unique_ptr<QWidget> m_overlay;
+ QLabel* m_info;
+ Results m_result;
+ std::unique_ptr<Filter> m_filter;
+ std::vector<QWidget*> m_disabled;
+
+ void createUi(Reasons reason);
+
+ void onForceUnlock();
+ void onCancel();
+
+ void disableAll();
+ void enableAll();
+ void disable(QWidget* w);
+};
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index ce5280a6..7d3d20b3 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -57,7 +57,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "downloadlistwidget.h"
#include "messagedialog.h"
#include "installationmanager.h"
-#include "lockeddialog.h"
#include "waitingonclosedialog.h"
#include "downloadlistsortproxy.h"
#include "motddialog.h"
@@ -1311,9 +1310,10 @@ bool MainWindow::canExit()
}
}
- m_exitAfterWait = true;
- m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock();
- if (!m_exitAfterWait) { // if operation cancelled
+ const auto r = m_OrganizerCore.processRunner()
+ .waitForAllUSVFSProcessesWithLock(LockWidget::PreventExit);
+
+ if (r == ProcessRunner::Cancelled) {
return false;
}
@@ -2258,42 +2258,6 @@ void MainWindow::storeSettings()
s.widgets().saveIndex(ui->executablesListBox);
}
-ILockedWaitingForProcess* MainWindow::lock()
-{
- if (m_LockDialog != nullptr) {
- ++m_LockCount;
- return m_LockDialog;
- }
- if (m_exitAfterWait)
- m_LockDialog = new WaitingOnCloseDialog(this);
- else
- m_LockDialog = new LockedDialog(this, true);
- m_LockDialog->setModal(true);
- m_LockDialog->show();
- setEnabled(false);
- m_LockDialog->setEnabled(true); //What's the point otherwise?
- ++m_LockCount;
- return m_LockDialog;
-}
-
-void MainWindow::unlock()
-{
- //If you come through here with a null lock pointer, it's a bug!
- if (m_LockDialog == nullptr) {
- log::debug("Unlocking main window when already unlocked");
- return;
- }
- --m_LockCount;
- if (m_LockCount == 0) {
- if (m_exitAfterWait && m_LockDialog->canceled())
- m_exitAfterWait = false;
- m_LockDialog->hide();
- m_LockDialog->deleteLater();
- m_LockDialog = nullptr;
- setEnabled(true);
- }
-}
-
QWidget* MainWindow::qtWidget()
{
return this;
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 19723480..c80287b2 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -38,7 +38,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
//when I get round to cleaning up main.cpp
class Executable;
class CategoryFactory;
-class LockedDialogBase;
class OrganizerCore;
class PluginListSortProxy;
@@ -118,8 +117,6 @@ public:
void processUpdates(Settings& settings);
- ILockedWaitingForProcess* lock() override;
- void unlock() override;
QWidget* qtWidget() override;
bool addProfile();
@@ -381,11 +378,7 @@ private:
bool m_DidUpdateMasterList;
- LockedDialogBase *m_LockDialog { nullptr };
- uint64_t m_LockCount { 0 };
-
bool m_showArchiveData{ true };
- bool m_exitAfterWait{ false };
MOBase::DelayedFileWriter m_ArchiveListWriter;
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 0f767f46..89e8bd9e 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -91,7 +91,6 @@ OrganizerCore::OrganizerCore(Settings &settings)
, m_PluginContainer(nullptr)
, m_GameName()
, m_CurrentProfile(nullptr)
- , m_Runner(*this)
, m_Settings(settings)
, m_Updater(NexusInterface::instance(m_PluginContainer))
, m_AboutToRun()
@@ -252,7 +251,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui)
m_InstallationManager.setParentWidget(w);
m_Updater.setUserInterface(w);
- m_Runner.setUserInterface(ui);
checkForUpdates();
}
@@ -357,7 +355,7 @@ void OrganizerCore::externalMessage(const QString &message)
{
if (MOShortcut moshortcut{ message } ) {
if(moshortcut.hasExecutable())
- m_Runner.runShortcut(moshortcut);
+ processRunner().runShortcut(moshortcut);
}
else if (isNxmLink(message)) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
@@ -1721,9 +1719,9 @@ void OrganizerCore::saveCurrentProfile()
storeSettings();
}
-ProcessRunner& OrganizerCore::processRunner()
+ProcessRunner OrganizerCore::processRunner()
{
- return m_Runner;
+ return ProcessRunner(*this, m_UserInterface);
}
bool OrganizerCore::beforeRun(
diff --git a/src/organizercore.h b/src/organizercore.h
index 2252c118..d4882b92 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -134,7 +134,7 @@ public:
bool saveCurrentLists();
- ProcessRunner& processRunner();
+ ProcessRunner processRunner();
bool beforeRun(
const QFileInfo& binary, const QString& profileName,
@@ -305,7 +305,6 @@ private:
Profile *m_CurrentProfile;
- ProcessRunner m_Runner;
Settings& m_Settings;
SelfUpdater m_Updater;
diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp
index 75b3ea41..3ee35fe2 100644
--- a/src/organizerproxy.cpp
+++ b/src/organizerproxy.cpp
@@ -119,7 +119,24 @@ HANDLE OrganizerProxy::startApplication(
bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const
{
- return m_Proxied->processRunner().waitForApplication(handle, exitCode);
+ const auto r = m_Proxied->processRunner().waitForApplication(
+ handle, exitCode, LockWidget::OutputRequired);
+
+ switch (r)
+ {
+ case ProcessRunner::Completed:
+ return true;
+
+ case ProcessRunner::Cancelled: // fall-through
+ case ProcessRunner::ForceUnlocked:
+ // this is always an error because the application should have run to
+ // completion
+ return false;
+
+ case ProcessRunner::Error: // fall-through
+ default:
+ return false;
+ }
}
bool OrganizerProxy::onAboutToRun(const std::function<bool (const QString &)> &func)
diff --git a/src/pch.h b/src/pch.h
index 01a97357..b7c5d695 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -100,9 +100,9 @@
#include <QDropEvent>
#include <QElapsedTimer>
#include <QEvent>
+#include <QFIleIconProvider>
#include <QFile>
#include <QFileDialog>
-#include <QFIleIconProvider>
#include <QFileInfo>
#include <QFileSystemModel>
#include <QFileSystemWatcher>
@@ -111,13 +111,14 @@
#include <QFontDatabase>
#include <QFuture>
#include <QFutureWatcher>
-#include <QHash>
+#include <QGraphicsDropShadowEffect>
#include <QHBoxLayout>
+#include <QHash>
#include <QHeaderView>
+#include <QIODevice>
#include <QIcon>
#include <QImageReader>
#include <QInputDialog>
-#include <QIODevice>
#include <QItemDelegate>
#include <QItemSelection>
#include <QItemSelectionModel>
@@ -126,20 +127,21 @@
#include <QJsonObject>
#include <QJsonValueRef>
#include <QKeyEvent>
-#include <QLabel>
#include <QLCDNumber>
+#include <QLabel>
#include <QLibrary>
#include <QLibraryInfo>
#include <QLineEdit>
#include <QList>
#include <QListWidget>
#include <QListWidgetItem>
-#include <QLocale>
#include <QLocalServer>
#include <QLocalSocket>
+#include <QLocale>
#include <QMainWindow>
#include <QMap>
#include <QMenu>
+#include <QMenuBar>
#include <QMessageBox>
#include <QMetaType>
#include <QMimeData>
@@ -190,53 +192,42 @@
#include <QSize>
#include <QSizePolicy>
#include <QSortFilterProxyModel>
-#include <QSplitter>
#include <QSpinBox>
#include <QSplashScreen>
+#include <QSplitter>
#include <QSslSocket>
#include <QStandardItemModel>
-#include <qstandardpaths.h>
#include <QStandardPaths>
+#include <QStatusBar>
+#include <QStorageInfo>
#include <QString>
#include <QStringList>
#include <QStringListModel>
#include <QStyle>
-#include <QStyledItemDelegate>
#include <QStyleFactory>
#include <QStyleOption>
#include <QStyleOptionSlider>
+#include <QStyledItemDelegate>
#include <QSyntaxHighlighter>
-#include <Qt>
-#include <QTableWidget>
#include <QTabWidget>
-#include <QtAlgorithms>
-#include <QtConcurrent/QtConcurrentRun>
-#include <QtCore/QAbstractItemModel>
-#include <QtCore/QObject>
-#include <QtCore/QStack>
-#include <QtDebug>
+#include <QTableWidget>
#include <QTemporaryFile>
#include <QTextBrowser>
#include <QTextCodec>
#include <QTextDocument>
#include <QTextEdit>
#include <QTextStream>
-#include <QtGlobal>
-#include <QtGui/QtGui>
#include <QThread>
#include <QTime>
#include <QTimer>
#include <QToolBar>
#include <QToolButton>
#include <QToolTip>
-#include <QtPlatformHeaders/QWindowsWindowFunctions>
-#include <QtPlugin>
#include <QTranslator>
#include <QTreeView>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QTreeWidgetItemIterator>
-#include <QtTest/QtTest>
#include <QUrl>
#include <QUrlQuery>
#include <QVariant>
@@ -255,4 +246,16 @@
#include <QWhatsThisClickedEvent>
#include <QWidget>
#include <QWidgetAction>
-#include <QStorageInfo>
+#include <Qt>
+#include <QtAlgorithms>
+#include <QtConcurrent/QtConcurrentRun>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QObject>
+#include <QtCore/QStack>
+#include <QtDebug>
+#include <QtGlobal>
+#include <QtGui/QtGui>
+#include <QtPlatformHeaders/QWindowsWindowFunctions>
+#include <QtPlugin>
+#include <QtTest/QtTest>
+#include <QStandardPaths>
diff --git a/src/processrunner.cpp b/src/processrunner.cpp
index 5d4a9fde..62c77efc 100644
--- a/src/processrunner.cpp
+++ b/src/processrunner.cpp
@@ -1,26 +1,58 @@
#include "processrunner.h"
#include "organizercore.h"
#include "instancemanager.h"
-#include "lockeddialog.h"
#include "iuserinterface.h"
#include "envmodule.h"
#include <log.h>
using namespace MOBase;
-enum class WaitResults
+void adjustForVirtualized(
+ const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings)
{
- Completed = 1,
- Error,
- Cancelled,
- StillRunning
-};
+ const QString modsPath = settings.paths().mods();
+
+ // Check if this a request with either an executable or a working directory
+ // under our mods folder then will start the process in a virtualized
+ // "environment" with the appropriate paths fixed:
+ // (i.e. mods\FNIS\path\exe => game\data\path\exe)
+ QString cwdPath = sp.currentDirectory.absolutePath();
+ bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive);
+ QString binPath = sp.binary.absoluteFilePath();
+ bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive);
+ if (virtualizedCwd || virtualizedBin) {
+ if (virtualizedCwd) {
+ int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1);
+ QString adjustedCwd = cwdPath.mid(cwdOffset, -1);
+ cwdPath = game->dataDirectory().absolutePath();
+ if (cwdOffset >= 0)
+ cwdPath += adjustedCwd;
+
+ }
+
+ if (virtualizedBin) {
+ int binOffset = binPath.indexOf('/', modsPath.length() + 1);
+ QString adjustedBin = binPath.mid(binOffset, -1);
+ binPath = game->dataDirectory().absolutePath();
+ if (binOffset >= 0)
+ binPath += adjustedBin;
+ }
+
+ QString cmdline
+ = QString("launch \"%1\" \"%2\" %3")
+ .arg(QDir::toNativeSeparators(cwdPath),
+ QDir::toNativeSeparators(binPath), sp.arguments);
+ sp.binary = QFileInfo(QCoreApplication::applicationFilePath());
+ sp.arguments = cmdline;
+ sp.currentDirectory.setPath(QCoreApplication::applicationDirPath());
+ }
+}
-WaitResults singleWait(HANDLE handle, DWORD* exitCode)
+std::optional<ProcessRunner::Results> singleWait(HANDLE handle, DWORD pid)
{
if (handle == INVALID_HANDLE_VALUE) {
- return WaitResults::Error;
+ return ProcessRunner::Error;
}
const DWORD WAIT_EVENT = WAIT_OBJECT_0 + 1;
@@ -33,23 +65,15 @@ WaitResults singleWait(HANDLE handle, DWORD* exitCode)
{
case WAIT_OBJECT_0:
{
- // completed
- if (exitCode) {
- if (!::GetExitCodeProcess(handle, exitCode)) {
- const auto e = ::GetLastError();
- log::warn(
- "failed to get exit code of process, {}",
- formatSystemMessage(e));
- }
- }
-
- return WaitResults::Completed;
+ log::debug("process {} completed", pid);
+ return ProcessRunner::Completed;
}
case WAIT_TIMEOUT:
case WAIT_EVENT:
{
- return WaitResults::StillRunning;
+ // still running
+ return {};
}
case WAIT_FAILED: // fall-through
@@ -57,11 +81,8 @@ WaitResults singleWait(HANDLE handle, DWORD* exitCode)
{
// error
const auto e = ::GetLastError();
-
- log::error(
- "failed waiting for process completion, {}", formatSystemMessage(e));
-
- return WaitResults::Error;
+ log::error("failed waiting for {}, {}", pid, formatSystemMessage(e));
+ return ProcessRunner::Error;
}
}
}
@@ -132,6 +153,11 @@ std::pair<env::Process, Interest> findInterestingProcessInTrees(
std::pair<env::Process, Interest> getInterestingProcess(
const std::vector<HANDLE>& initialProcesses)
{
+ if (initialProcesses.empty()) {
+ log::debug("nothing to wait for");
+ return {{}, Interest::None};
+ }
+
std::vector<env::Process> processes;
log::debug("getting process tree for {} processes", initialProcesses.size());
@@ -143,7 +169,7 @@ std::pair<env::Process, Interest> getInterestingProcess(
}
if (processes.empty()) {
- log::debug("nothing to wait for");
+ log::debug("processes are already completed");
return {{}, Interest::None};
}
@@ -158,9 +184,8 @@ std::pair<env::Process, Interest> getInterestingProcess(
const std::chrono::milliseconds Infinite(-1);
-WaitResults timedWait(
- HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock,
- std::chrono::milliseconds wait)
+std::optional<ProcessRunner::Results> timedWait(
+ HANDLE handle, DWORD pid, LockWidget& lock, std::chrono::milliseconds wait)
{
using namespace std::chrono;
@@ -170,37 +195,66 @@ WaitResults timedWait(
}
for (;;) {
- const auto r = singleWait(handle, exitCode);
+ const auto r = singleWait(handle, pid);
- if (r != WaitResults::StillRunning) {
- return r;
+ if (r) {
+ return *r;
}
+ // still running
+
// keep processing events so the app doesn't appear dead
QCoreApplication::sendPostedEvents();
QCoreApplication::processEvents();
- if (uilock && uilock->unlockForced()) {
- return WaitResults::Cancelled;
+ switch (lock.result())
+ {
+ case LockWidget::StillLocked:
+ {
+ break;
+ }
+
+ case LockWidget::ForceUnlocked:
+ {
+ log::debug("waiting for {} force unlocked by user", pid);
+ return ProcessRunner::ForceUnlocked;
+ }
+
+ case LockWidget::Cancelled:
+ {
+ log::debug("waiting for {} cancelled by user", pid);
+ return ProcessRunner::Cancelled;
+ }
+
+ case LockWidget::NoResult: // fall-through
+ default:
+ {
+ // shouldn't happen
+ log::debug(
+ "unexpected result {} while waiting for {}",
+ static_cast<int>(lock.result()), pid);
+
+ return ProcessRunner::Error;
+ }
}
if (wait != Infinite) {
const auto now = high_resolution_clock::now();
if (duration_cast<milliseconds>(now - start) >= wait) {
- return WaitResults::StillRunning;
+ return {};
}
}
}
}
-WaitResults waitForProcesses(
- const std::vector<HANDLE>& initialProcesses,
- LPDWORD exitCode, ILockedWaitingForProcess* uilock)
+ProcessRunner::Results waitForProcesses(
+ const std::vector<HANDLE>& initialProcesses, LockWidget& lock)
{
using namespace std::chrono;
if (initialProcesses.empty()) {
- return WaitResults::Completed;
+ // shouldn't happen
+ return ProcessRunner::Completed;
}
DWORD currentPID = 0;
@@ -208,14 +262,16 @@ WaitResults waitForProcesses(
for (;;) {
auto [p, interest] = getInterestingProcess(initialProcesses);
-
- if (uilock) {
- uilock->setProcessInformation(p.pid(), p.name());
+ if (!p.isValid()) {
+ // nothing to wait on
+ return ProcessRunner::Completed;
}
+ lock.setInfo(p.pid(), p.name());
+
auto interestingHandle = p.openHandleForWait();
if (!interestingHandle) {
- return WaitResults::Error;
+ return ProcessRunner::Error;
}
if (p.pid() != currentPID) {
@@ -230,9 +286,9 @@ WaitResults waitForProcesses(
wait = Infinite;
}
- const auto r = timedWait(interestingHandle.get(), exitCode, uilock, wait);
- if (r != WaitResults::StillRunning) {
- return r;
+ const auto r = timedWait(interestingHandle.get(), p.pid(), lock, wait);
+ if (r) {
+ return *r;
}
wait = std::min(wait * 2, milliseconds(2000));
@@ -243,11 +299,24 @@ WaitResults waitForProcesses(
}
}
-WaitResults waitForProcess(
- HANDLE initialProcess, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
+ProcessRunner::Results waitForProcess(
+ HANDLE initialProcess, LPDWORD exitCode, LockWidget& lock)
{
std::vector<HANDLE> processes = {initialProcess};
- return waitForProcesses(processes, exitCode, uilock);
+
+ const auto r = waitForProcesses(processes, lock);
+
+ // as long as it's not running anymore, try to get the exit code
+ if (exitCode && r != ProcessRunner::Running) {
+ if (!::GetExitCodeProcess(initialProcess, exitCode)) {
+ const auto e = ::GetLastError();
+ log::warn(
+ "failed to get exit code of process, {}",
+ formatSystemMessage(e));
+ }
+ }
+
+ return r;
}
@@ -298,17 +367,64 @@ void SpawnedProcess::destroy()
}
-ProcessRunner::ProcessRunner(OrganizerCore& core)
- : m_core(core), m_ui(nullptr)
+ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) :
+ m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(false),
+ m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1)
{
+ m_sp.hooked = true;
}
-void ProcessRunner::setUserInterface(IUserInterface* ui)
+ProcessRunner& ProcessRunner::setBinary(const QFileInfo &binary)
{
- m_ui = ui;
+ m_sp.binary = binary;
+ return *this;
}
-bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo)
+ProcessRunner& ProcessRunner::setArguments(const QString& arguments)
+{
+ m_sp.arguments = arguments;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setCurrentDirectory(const QDir& directory)
+{
+ m_sp.currentDirectory = directory;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setSteamID(const QString& steamID)
+{
+ m_sp.steamAppID = steamID;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setCustomOverwrite(const QString& customOverwrite)
+{
+ m_customOverwrite = customOverwrite;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setForcedLibraries(const ForcedLibraries& forcedLibraries)
+{
+ m_forcedLibraries = forcedLibraries;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setProfileName(const QString& profileName)
+{
+ m_profileName = profileName;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setWaitForCompletion(
+ LockWidget::Reasons reason, bool refresh)
+{
+ m_lock = reason;
+ m_refresh = refresh;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targetInfo)
{
if (!parent && m_ui) {
parent = m_ui->qtWidget();
@@ -320,56 +436,24 @@ bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo)
{
case spawn::FileExecutionTypes::Executable:
{
- runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir());
- return true;
+ setBinary(fec.binary);
+ setArguments(fec.arguments);
+ setCurrentDirectory(targetInfo.absoluteDir());
+ break;
}
case spawn::FileExecutionTypes::Other: // fall-through
default:
{
- auto r = shell::Open(targetInfo.absoluteFilePath());
- if (!r.success()) {
- return false;
- }
-
- // not all files will return a valid handle even if opening them was
- // successful, such as inproc handlers (like the photo viewer)
- if (r.processHandle() != INVALID_HANDLE_VALUE) {
- // steal because it gets closed after the wait
- return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr);
- }
-
- return true;
+ m_shellOpen = targetInfo.absoluteFilePath();
+ break;
}
}
-}
-bool ProcessRunner::runExecutableFile(
- const QFileInfo &binary, const QString &arguments,
- const QDir &currentDirectory, const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries,
- bool refresh)
-{
- DWORD processExitCode = 0;
- HANDLE processHandle = spawnAndWait(
- binary, arguments, m_core.currentProfile()->name(),
- currentDirectory, steamAppID, customOverwrite, forcedLibraries,
- &processExitCode);
-
- if (processHandle == INVALID_HANDLE_VALUE) {
- // failed
- return false;
- }
-
- if (refresh) {
- m_core.afterRun(binary, processExitCode);
- }
-
- return true;
+ return *this;
}
-bool ProcessRunner::runExecutable(const Executable& exe, bool refresh)
+ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe)
{
const auto* profile = m_core.currentProfile();
if (!profile) {
@@ -379,25 +463,31 @@ bool ProcessRunner::runExecutable(const Executable& exe, bool refresh)
const QString customOverwrite = profile->setting(
"custom_overwrites", exe.title()).toString();
- QList<MOBase::ExecutableForcedLoadSetting> forcedLibraries;
-
+ ForcedLibraries forcedLibraries;
if (profile->forcedLibrariesEnabled(exe.title())) {
forcedLibraries = profile->determineForcedLibraries(exe.title());
}
- return runExecutableFile(
- exe.binaryInfo(),
- exe.arguments(),
- exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(),
- exe.steamAppID(),
- customOverwrite,
- forcedLibraries,
- refresh);
+ QDir currentDirectory = exe.workingDirectory();
+ if (currentDirectory.isEmpty()) {
+ currentDirectory.setPath(exe.binaryInfo().absolutePath());
+ }
+
+ setBinary(exe.binaryInfo());
+ setArguments(exe.arguments());
+ setCurrentDirectory(currentDirectory);
+ setSteamID(exe.steamAppID());
+ setCustomOverwrite(customOverwrite);
+ setForcedLibraries(forcedLibraries);
+
+ return *this;
}
-bool ProcessRunner::runShortcut(const MOShortcut& shortcut)
+ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut)
{
- if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) {
+ const auto currentInstance = InstanceManager::instance().currentInstance();
+
+ if (shortcut.hasInstance() && shortcut.instance() != currentInstance) {
throw std::runtime_error(
QString("Refusing to run executable from different instance %1:%2")
.arg(shortcut.instance(),shortcut.executable())
@@ -405,12 +495,17 @@ bool ProcessRunner::runShortcut(const MOShortcut& shortcut)
}
const Executable& exe = m_core.executablesList()->get(shortcut.executable());
- return runExecutable(exe, false);
+ setFromExecutable(exe);
+
+ return *this;
}
-HANDLE ProcessRunner::runExecutableOrExecutableFile(
- const QString& executable, const QStringList &args, const QString &cwd,
- const QString& profileOverride, const QString &forcedCustomOverwrite,
+ProcessRunner& ProcessRunner::setFromFileOrExecutable(
+ const QString &executable,
+ const QStringList &args,
+ const QString &cwd,
+ const QString &profileOverride,
+ const QString &forcedCustomOverwrite,
bool ignoreCustomOverwrite)
{
const auto* profile = m_core.currentProfile();
@@ -418,37 +513,41 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile(
throw MyException(QObject::tr("No profile set"));
}
- QString profileName = profileOverride;
- if (profileName == "") {
- profileName = profile->name();
- }
+ setProfileName(profileOverride);
- QFileInfo binary;
- QString arguments = args.join(" ");
- QString currentDirectory = cwd;
- QString steamAppID;
- QString customOverwrite;
- QList<ExecutableForcedLoadSetting> forcedLibraries;
+ //QFileInfo binary;
+ //QString arguments = args.join(" ");
+ //QString currentDirectory = cwd;
+ //QString steamAppID;
+ //QString customOverwrite;
+ //QList<ExecutableForcedLoadSetting> forcedLibraries;
if (executable.contains('\\') || executable.contains('/')) {
// file path
- binary = QFileInfo(executable);
+ auto binary = QFileInfo(executable);
+
if (binary.isRelative()) {
// relative path, should be relative to game directory
binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable);
}
- if (currentDirectory == "") {
- currentDirectory = binary.absolutePath();
+ setBinary(binary);
+
+ if (cwd == "") {
+ setCurrentDirectory(binary.absolutePath());
+ } else {
+ setCurrentDirectory(cwd);
}
try {
const Executable& exe = m_core.executablesList()->getByBinary(binary);
- steamAppID = exe.steamAppID();
- customOverwrite = profile->setting("custom_overwrites", exe.title()).toString();
+
+ setSteamID(exe.steamAppID());
+ setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString());
+
if (profile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = profile->determineForcedLibraries(exe.title());
+ setForcedLibraries(profile->determineForcedLibraries(exe.title()));
}
} catch (const std::runtime_error &) {
// nop
@@ -457,223 +556,244 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile(
// only a file name, search executables list
try {
const Executable &exe = m_core.executablesList()->get(executable);
- steamAppID = exe.steamAppID();
- customOverwrite = profile->setting("custom_overwrites", exe.title()).toString();
+
+ setSteamID(exe.steamAppID());
+ setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString());
+
if (profile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = profile->determineForcedLibraries(exe.title());
+ setForcedLibraries(profile->determineForcedLibraries(exe.title()));
}
- if (arguments == "") {
- arguments = exe.arguments();
+
+ if (args.isEmpty()) {
+ setArguments(exe.arguments());
+ } else {
+ setArguments(args.join(" "));
}
- binary = exe.binaryInfo();
- if (currentDirectory == "") {
- currentDirectory = exe.workingDirectory();
+
+ setBinary(exe.binaryInfo());
+
+ if (cwd == "") {
+ setCurrentDirectory(exe.workingDirectory());
+ } else {
+ setCurrentDirectory(cwd);
}
} catch (const std::runtime_error &) {
log::warn("\"{}\" not set up as executable", executable);
- binary = QFileInfo(executable);
+ setBinary(QFileInfo(executable));
}
}
- if (!forcedCustomOverwrite.isEmpty())
- customOverwrite = forcedCustomOverwrite;
-
- if (ignoreCustomOverwrite)
- customOverwrite.clear();
+ if (ignoreCustomOverwrite) {
+ setCustomOverwrite("");
+ } else if (!forcedCustomOverwrite.isEmpty()) {
+ setCustomOverwrite(forcedCustomOverwrite);
+ }
- return spawnAndWait(
- binary,
- arguments,
- profileName,
- currentDirectory,
- steamAppID,
- customOverwrite,
- forcedLibraries);
+ return *this;
}
-HANDLE ProcessRunner::spawnAndWait(
- const QFileInfo &binary, const QString &arguments, const QString &profileName,
- const QDir &currentDirectory, const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries,
- LPDWORD exitCode)
+ProcessRunner::Results ProcessRunner::run()
{
- spawn::SpawnParameters sp;
- sp.binary = binary;
- sp.arguments = arguments;
- sp.currentDirectory = currentDirectory;
- sp.steamAppID = steamAppID;
- sp.hooked = true;
+ if (!m_shellOpen.isEmpty()) {
+ auto r = shell::Open(m_shellOpen);
+ if (!r.success()) {
+ return Error;
+ }
- if (!m_core.beforeRun(binary, profileName, customOverwrite, forcedLibraries)) {
- return INVALID_HANDLE_VALUE;
- }
+ // not all files will return a valid handle even if opening them was
+ // successful, such as inproc handlers (like the photo viewer)
+ m_handle = r.stealProcessHandle();
+ } else {
+ if (m_profileName.isEmpty()) {
+ const auto* profile = m_core.currentProfile();
+ if (!profile) {
+ throw MyException(QObject::tr("No profile set"));
+ }
- HANDLE handle = spawn(sp).releaseHandle();
+ m_profileName = profile->name();
+ }
- if (handle == INVALID_HANDLE_VALUE) {
- // failed
- return INVALID_HANDLE_VALUE;
- }
+ if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) {
+ return Error;
+ }
- waitForProcessCompletionWithLock(handle, exitCode);
- return handle;
-}
+ QWidget* parent = nullptr;
+ if (m_ui) {
+ parent = m_ui->qtWidget();
+ }
-void adjustForVirtualized(
- const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings)
-{
- const QString modsPath = settings.paths().mods();
+ if (!checkBinary(parent, m_sp)) {
+ return Error;
+ }
- // Check if this a request with either an executable or a working directory
- // under our mods folder then will start the process in a virtualized
- // "environment" with the appropriate paths fixed:
- // (i.e. mods\FNIS\path\exe => game\data\path\exe)
- QString cwdPath = sp.currentDirectory.absolutePath();
- bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive);
- QString binPath = sp.binary.absoluteFilePath();
- bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive);
- if (virtualizedCwd || virtualizedBin) {
- if (virtualizedCwd) {
- int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1);
- QString adjustedCwd = cwdPath.mid(cwdOffset, -1);
- cwdPath = game->dataDirectory().absolutePath();
- if (cwdOffset >= 0)
- cwdPath += adjustedCwd;
+ const auto* game = m_core.managedGame();
+ auto& settings = m_core.settings();
+ if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) {
+ return Error;
}
- if (virtualizedBin) {
- int binOffset = binPath.indexOf('/', modsPath.length() + 1);
- QString adjustedBin = binPath.mid(binOffset, -1);
- binPath = game->dataDirectory().absolutePath();
- if (binOffset >= 0)
- binPath += adjustedBin;
+ if (!checkEnvironment(parent, m_sp)) {
+ return Error;
}
- QString cmdline
- = QString("launch \"%1\" \"%2\" %3")
- .arg(QDir::toNativeSeparators(cwdPath),
- QDir::toNativeSeparators(binPath), sp.arguments);
+ if (!checkBlacklist(parent, m_sp, settings)) {
+ return Error;
+ }
- sp.binary = QFileInfo(QCoreApplication::applicationFilePath());
- sp.arguments = cmdline;
- sp.currentDirectory.setPath(QCoreApplication::applicationDirPath());
+ adjustForVirtualized(game, m_sp, settings);
+
+ m_handle = startBinary(parent, m_sp);
+ if (m_handle == INVALID_HANDLE_VALUE) {
+ return Error;
+ }
+ }
+
+ if (m_handle == INVALID_HANDLE_VALUE || m_lock == LockWidget::NoReason) {
+ return Running;
+ } else {
+ const auto r = waitForProcessCompletionWithLock(
+ m_handle, &m_exitCode, m_lock);
+
+ if (r == Completed && m_refresh) {
+ m_core.afterRun(m_sp.binary, m_exitCode);
+ }
+
+ return r;
}
}
-SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp)
+DWORD ProcessRunner::exitCode()
{
- QWidget* parent = nullptr;
- if (m_ui) {
- parent = m_ui->qtWidget();
- }
+ return m_exitCode;
+}
- if (!checkBinary(parent, sp)) {
- return {INVALID_HANDLE_VALUE, sp};
- }
- const auto* game = m_core.managedGame();
- auto& settings = m_core.settings();
+bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo)
+{
+ setFromFile(parent, targetInfo);
+ setWaitForCompletion(LockWidget::LockUI, true);
- if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) {
- return {INVALID_HANDLE_VALUE, sp};
- }
+ const auto r = run();
+ return (r != Error);
+}
- if (!checkEnvironment(parent, sp)) {
- return {INVALID_HANDLE_VALUE, sp};
- }
+bool ProcessRunner::runExecutableFile(
+ const QFileInfo &binary, const QString &arguments,
+ const QDir &currentDirectory, const QString &steamAppID,
+ const QString &customOverwrite,
+ const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries,
+ bool refresh)
+{
+ setBinary(binary);
+ setArguments(arguments);
+ setCurrentDirectory(currentDirectory);
+ setSteamID(steamAppID);
+ setCustomOverwrite(customOverwrite);
+ setForcedLibraries(forcedLibraries);
+ setWaitForCompletion(LockWidget::LockUI, refresh);
- if (!checkBlacklist(parent, sp, settings)) {
- return {INVALID_HANDLE_VALUE, sp};
- }
+ const auto r = run();
+ return (r != Error);
+}
- adjustForVirtualized(game, sp, settings);
+bool ProcessRunner::runExecutable(const Executable& exe, bool refresh)
+{
+ setFromExecutable(exe);
+ setWaitForCompletion(LockWidget::LockUI, refresh);
- return {startBinary(parent, sp), sp};
+ const auto r = run();
+ return (r != Error);
}
-void ProcessRunner::withLock(std::function<void (ILockedWaitingForProcess*)> f)
+bool ProcessRunner::runShortcut(const MOShortcut& shortcut)
{
- std::unique_ptr<LockedDialog> dlg;
- ILockedWaitingForProcess* uilock = nullptr;
+ setFromShortcut(shortcut);
+ setWaitForCompletion(LockWidget::LockUI, false);
- if (m_ui != nullptr) {
- uilock = m_ui->lock();
- } else {
- // i.e. when running command line shortcuts there is no user interface
- dlg.reset(new LockedDialog);
- dlg->show();
- dlg->setEnabled(true);
- uilock = dlg.get();
- }
+ const auto r = run();
+ return (r != Error);
+}
- Guard g([&]() {
- if (m_ui != nullptr) {
- m_ui->unlock();
- }
- });
+HANDLE ProcessRunner::runExecutableOrExecutableFile(
+ const QString& executable, const QStringList &args, const QString &cwd,
+ const QString& profileOverride, const QString &forcedCustomOverwrite,
+ bool ignoreCustomOverwrite)
+{
+ setFromFileOrExecutable(
+ executable, args, cwd, profileOverride, forcedCustomOverwrite,
+ ignoreCustomOverwrite);
+
+ setWaitForCompletion(LockWidget::LockUI, true);
- f(uilock);
+ run();
+ return m_handle;
}
-bool ProcessRunner::waitForProcessCompletionWithLock(
- HANDLE handle, LPDWORD exitCode)
+void ProcessRunner::withLock(
+ LockWidget::Reasons reason, std::function<void (LockWidget&)> f)
+{
+ auto lock = std::make_unique<LockWidget>(
+ m_ui ? m_ui->qtWidget() : nullptr, reason);
+
+ f(*lock);
+}
+
+ProcessRunner::Results ProcessRunner::waitForProcessCompletionWithLock(
+ HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason)
{
if (!Settings::instance().interface().lockGUI()) {
log::debug("not waiting for process because user has disabled locking");
- return true;
+ return ForceUnlocked;
}
- auto r = WaitResults::Error;
-
- withLock([&](auto* uilock) {
- r = waitForProcess(handle, exitCode, uilock);
- });
-
- // completed/unlocked is fine
- return (r != WaitResults::Error);
+ return waitForApplication(handle, exitCode, reason);
}
-bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode)
+ProcessRunner::Results ProcessRunner::waitForApplication(
+ HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason)
{
// don't check for lockGUI() setting; this _always_ locks the ui and waits
// for completion
//
- // this is typically called only from OrganizerProxy, which allows plugins
- // to wait on applications until they're finished
+ // this is typically called only from:
+ // 1) OrganizerProxy, which allows plugins to wait on applications until
+ // they're finished
+ //
+ // the check_fnis plugin for example will start FNIS, wait for it to
+ // complete, and then check the exit code; this has to work regardless of
+ // the locking setting;
//
- // the check_fnis plugin for example will start FNIS, wait for it to complete,
- // and then check the exit code; this has to work regardless of the locking
- // setting
+ // 2) waitForProcessCompletionWithLock() above, which has already checked the
+ // lock setting
- auto r = WaitResults::Error;
+ auto r = Error;
- withLock([&](auto* uilock) {
- r = waitForProcess(handle, exitCode, uilock);
+ withLock(reason, [&](auto& lock) {
+ r = waitForProcess(handle, exitCode, lock);
});
- // treat unlocked as an error since this should always wait for completion
- return (r == WaitResults::Completed);
+ return r;
}
-bool ProcessRunner::waitForAllUSVFSProcessesWithLock()
+ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock(
+ LockWidget::Reasons reason)
{
if (!Settings::instance().interface().lockGUI()) {
log::debug("not waiting for usvfs processes because user has disabled locking");
- return true;
+ return ForceUnlocked;
}
- bool r = false;
+ auto r = Error;
- withLock([&](auto* uilock) {
- r = waitForAllUSVFSProcesses(uilock);
+ withLock(reason, [&](auto& lock) {
+ r = waitForAllUSVFSProcesses(lock);
});
return r;
}
-bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
+ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcesses(LockWidget& lock)
{
for (;;) {
const auto processes = getRunningUSVFSProcesses();
@@ -681,26 +801,15 @@ bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
break;
}
- const auto r = waitForProcesses(processes, nullptr, uilock);
+ const auto r = waitForProcesses(processes, lock);
- switch (r)
- {
- case WaitResults::Completed:
- // this process is completed, check for others
- break;
-
- case WaitResults::Cancelled:
- // force unlocked
- log::debug("waiting for process completion aborted by UI");
- return true;
-
- case WaitResults::Error: // fall-through
- default:
- log::debug("waiting for process completion not successful");
- return false;
+ if (r != Completed) {
+ // error, cancelled, or unlocked
+ return r;
}
+
+ // this process is completed, check for others
}
- log::debug("waiting for process completion successful");
- return true;
+ return Completed;
}
diff --git a/src/processrunner.h b/src/processrunner.h
index 28f4da75..b7895903 100644
--- a/src/processrunner.h
+++ b/src/processrunner.h
@@ -2,10 +2,10 @@
#define PROCESSRUNNER_H
#include "spawn.h"
+#include "lockwidget.h"
#include <executableinfo.h>
class OrganizerCore;
-class ILockedWaitingForProcess;
class IUserInterface;
class Executable;
class MOShortcut;
@@ -35,9 +35,43 @@ private:
class ProcessRunner
{
public:
- ProcessRunner(OrganizerCore& core);
+ enum Results
+ {
+ Running = 1,
+ Completed,
+ Error,
+ Cancelled,
+ ForceUnlocked
+ };
+
+ using ForcedLibraries = QList<MOBase::ExecutableForcedLoadSetting>;
+
+ ProcessRunner(OrganizerCore& core, IUserInterface* ui);
+
+ ProcessRunner& setBinary(const QFileInfo &binary);
+ ProcessRunner& setArguments(const QString& arguments);
+ ProcessRunner& setCurrentDirectory(const QDir& directory);
+ ProcessRunner& setSteamID(const QString& steamID);
+ ProcessRunner& setCustomOverwrite(const QString& customOverwrite);
+ ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries);
+ ProcessRunner& setProfileName(const QString& profileName);
+ ProcessRunner& setWaitForCompletion(LockWidget::Reasons reason, bool refresh);
+
+ ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo);
+ ProcessRunner& setFromExecutable(const Executable& exe);
+ ProcessRunner& setFromShortcut(const MOShortcut& shortcut);
+
+ ProcessRunner& setFromFileOrExecutable(
+ const QString &executable,
+ const QStringList &args,
+ const QString &cwd,
+ const QString &profile,
+ const QString &forcedCustomOverwrite = "",
+ bool ignoreCustomOverwrite = false);
+
+ Results run();
+ DWORD exitCode();
- void setUserInterface(IUserInterface* ui);
bool runFile(QWidget* parent, const QFileInfo& targetInfo);
@@ -53,17 +87,31 @@ public:
bool runShortcut(const MOShortcut& shortcut);
HANDLE runExecutableOrExecutableFile(
- const QString &executable, const QStringList &args, const QString &cwd,
- const QString &profile, const QString &forcedCustomOverwrite = "",
+ const QString &executable,
+ const QStringList &args,
+ const QString &cwd,
+ const QString &profile,
+ const QString &forcedCustomOverwrite = "",
bool ignoreCustomOverwrite = false);
- bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr);
- bool waitForAllUSVFSProcessesWithLock();
+ Results waitForApplication(
+ HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason);
+
+ Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason);
private:
OrganizerCore& m_core;
IUserInterface* m_ui;
+ spawn::SpawnParameters m_sp;
+ QString m_customOverwrite;
+ ForcedLibraries m_forcedLibraries;
+ QString m_profileName;
+ LockWidget::Reasons m_lock;
+ bool m_refresh;
+ QString m_shellOpen;
+ HANDLE m_handle;
+ DWORD m_exitCode;
HANDLE spawnAndWait(
const QFileInfo &binary, const QString &arguments,
@@ -76,11 +124,13 @@ private:
SpawnedProcess spawn(spawn::SpawnParameters sp);
- void withLock(std::function<void (ILockedWaitingForProcess*)> f);
+ void withLock(
+ LockWidget::Reasons reason, std::function<void (LockWidget&)> f);
- bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode);
+ Results waitForProcessCompletionWithLock(
+ HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason);
- bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock);
+ Results waitForAllUSVFSProcesses(LockWidget& lock);
};
#endif // PROCESSRUNNER_H