summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-10-29 11:34:06 -0400
committerisanae <14251494+isanae@users.noreply.github.com>2019-11-06 07:44:55 -0500
commit4d269c2e1a625e6d50b7e6272b4f474a921c6bfa (patch)
treecd43db6b19152e98dd219de3350f8dbe6911662d
parent8c72077febaea485200adcf1e9f615902e930def (diff)
split to processrunner
added IUserInterface::qtWidget() put back IUserInterface in OrganizerCore now that there's a way to get the widget
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/iuserinterface.h2
-rw-r--r--src/main.cpp4
-rw-r--r--src/mainwindow.cpp13
-rw-r--r--src/mainwindow.h5
-rw-r--r--src/modinfodialogconflicts.cpp2
-rw-r--r--src/organizercore.cpp601
-rw-r--r--src/organizercore.h60
-rw-r--r--src/organizerproxy.cpp4
-rw-r--r--src/processrunner.cpp634
-rw-r--r--src/processrunner.h104
-rw-r--r--src/spawn.cpp198
-rw-r--r--src/spawn.h48
13 files changed, 892 insertions, 786 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 180422ef..5e909760 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -143,6 +143,7 @@ SET(organizer_SRCS
envwindows.cpp
colortable.cpp
sanitychecks.cpp
+ processrunner.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -266,6 +267,7 @@ SET(organizer_HDRS
envshortcut.h
envwindows.h
colortable.h
+ processrunner.h
shared/windows_error.h
shared/error_report.h
@@ -348,6 +350,7 @@ set(core
organizercore
organizerproxy
apiuseraccount
+ processrunner
)
set(dialogs
diff --git a/src/iuserinterface.h b/src/iuserinterface.h
index a309ed9b..91487aee 100644
--- a/src/iuserinterface.h
+++ b/src/iuserinterface.h
@@ -33,6 +33,8 @@ public:
virtual ILockedWaitingForProcess* lock() = 0;
virtual void unlock() = 0;
+
+ virtual QWidget* qtWidget() = 0;
};
#endif // IUSERINTERFACE_H
diff --git a/src/main.cpp b/src/main.cpp
index 49ecc084..5ed7da5d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -632,7 +632,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
if (MOShortcut shortcut{ arguments.at(1) }) {
if (shortcut.hasExecutable()) {
try {
- organizer.runShortcut(shortcut);
+ organizer.processRunner().runShortcut(shortcut);
return 0;
}
catch (const std::exception &e) {
@@ -653,7 +653,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
arguments.removeFirst(); // remove binary name
// pass the remaining parameters to the binary
try {
- organizer.runExecutableOrExecutableFile(
+ organizer.processRunner().runExecutableOrExecutableFile(
exeName, arguments, QString(), QString());
return 0;
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index bbb63333..ce5280a6 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1312,7 +1312,7 @@ bool MainWindow::canExit()
}
m_exitAfterWait = true;
- m_OrganizerCore.waitForAllUSVFSProcessesWithLock();
+ m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock();
if (!m_exitAfterWait) { // if operation cancelled
return false;
}
@@ -1536,7 +1536,7 @@ void MainWindow::startExeAction()
}
action->setEnabled(false);
- m_OrganizerCore.runExecutable(*itor);
+ m_OrganizerCore.processRunner().runExecutable(*itor);
action->setEnabled(true);
}
@@ -2294,6 +2294,11 @@ void MainWindow::unlock()
}
}
+QWidget* MainWindow::qtWidget()
+{
+ return this;
+}
+
void MainWindow::on_btnRefreshData_clicked()
{
m_OrganizerCore.refreshDirectoryStructure();
@@ -2363,7 +2368,7 @@ void MainWindow::on_startButton_clicked()
forcedLibraries.clear();
}
- m_OrganizerCore.runExecutableFile(
+ m_OrganizerCore.processRunner().runExecutableFile(
selectedExecutable->binaryInfo(),
selectedExecutable->arguments(),
selectedExecutable->workingDirectory().length() != 0 ?
@@ -5453,7 +5458,7 @@ void MainWindow::openDataFile()
const QString path = m_ContextItem->data(0, Qt::UserRole).toString();
const QFileInfo targetInfo(path);
- m_OrganizerCore.runFile(this, targetInfo);
+ m_OrganizerCore.processRunner().runFile(this, targetInfo);
}
void MainWindow::openDataOriginExplorer_clicked()
diff --git a/src/mainwindow.h b/src/mainwindow.h
index dbbd0bd9..19723480 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -118,8 +118,9 @@ public:
void processUpdates(Settings& settings);
- virtual ILockedWaitingForProcess* lock() override;
- virtual void unlock() override;
+ ILockedWaitingForProcess* lock() override;
+ void unlock() override;
+ QWidget* qtWidget() override;
bool addProfile();
void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives);
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
index 68b1be6b..758112cc 100644
--- a/src/modinfodialogconflicts.cpp
+++ b/src/modinfodialogconflicts.cpp
@@ -527,7 +527,7 @@ void ConflictsTab::openItems(QTreeView* tree)
// the menu item is only shown for a single selection, but handle all of them
// in case this changes
for_each_in_selection(tree, [&](const ConflictItem* item) {
- core().runFile(parentWidget(), item->fileName());
+ core().processRunner().runFile(parentWidget(), item->fileName());
return true;
});
}
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index eda64c1d..0f767f46 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1,5 +1,4 @@
#include "organizercore.h"
-#include "mainwindow.h"
#include "delayedfilewriter.h"
#include "guessedvalue.h"
#include "imodinterface.h"
@@ -86,51 +85,13 @@ QStringList toStringList(InputIterator current, InputIterator end)
return result;
}
-env::Process* getInterestingProcess(std::vector<env::Process>& processes)
-{
- if (processes.empty()) {
- return nullptr;
- }
-
- // Certain process names we wish to "hide" for aesthetic reason:
- const std::vector<QString> hiddenList = {
- QFileInfo(QCoreApplication::applicationFilePath()).fileName()
- };
-
- auto isHidden = [&](auto&& p) {
- for (auto h : hiddenList) {
- if (p.name().contains(h, Qt::CaseInsensitive)) {
- return true;
- }
- }
-
- return false;
- };
-
-
- for (auto&& root : processes) {
- if (!isHidden(root)) {
- return &root;
- }
-
- for (auto&& child : root.children()) {
- if (!isHidden(child)) {
- return &child;
- }
- }
- }
-
-
- // everything is hidden, just pick the first one
- return &processes[0];
-}
-
OrganizerCore::OrganizerCore(Settings &settings)
- : m_MainWindow(nullptr)
+ : m_UserInterface(nullptr)
, m_PluginContainer(nullptr)
, m_GameName()
, m_CurrentProfile(nullptr)
+ , m_Runner(*this)
, m_Settings(settings)
, m_Updater(NexusInterface::instance(m_PluginContainer))
, m_AboutToRun()
@@ -190,7 +151,7 @@ OrganizerCore::~OrganizerCore()
m_RefresherThread.exit();
m_RefresherThread.wait();
- prepareStart();
+ saveCurrentProfile();
// profile has to be cleaned up before the modinfo-buffer is cleared
delete m_CurrentProfile;
@@ -249,43 +210,49 @@ void OrganizerCore::updateExecutablesList()
m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
}
-void OrganizerCore::setUserInterface(MainWindow* mainWindow)
+void OrganizerCore::setUserInterface(IUserInterface* ui)
{
storeSettings();
- m_MainWindow = mainWindow;
+ m_UserInterface = ui;
+
+ QWidget* w = nullptr;
+ if (m_UserInterface) {
+ w = m_UserInterface->qtWidget();
+ }
- if (m_MainWindow != nullptr) {
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow,
+ if (w) {
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w,
SLOT(modlistChanged(QModelIndex, int)));
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w,
SLOT(modlistChanged(QModelIndexList, int)));
- connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(showMessage(QString)), w,
SLOT(showMessage(QString)));
- connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w,
SLOT(modRenamed(QString, QString)));
- connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(modUninstalled(QString)), w,
SLOT(modRemoved(QString)));
- connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow,
+ connect(&m_ModList, SIGNAL(removeSelectedMods()), w,
SLOT(removeMod_clicked()));
- connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow,
+ connect(&m_ModList, SIGNAL(clearOverwrite()), w,
SLOT(clearOverwrite()));
- connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w,
SLOT(displayColumnSelection(QPoint)));
- connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w,
SLOT(fileMoved(QString, QString, QString)));
- connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow,
+ connect(&m_ModList, SIGNAL(modorder_changed()), w,
SLOT(modorder_changed()));
- connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow,
+ connect(&m_PluginList, SIGNAL(writePluginsList()), w,
SLOT(esplist_changed()));
- connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow,
+ connect(&m_PluginList, SIGNAL(esplist_changed()), w,
SLOT(esplist_changed()));
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow,
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w,
SLOT(showMessage(QString)));
}
- m_InstallationManager.setParentWidget(m_MainWindow);
- m_Updater.setUserInterface(m_MainWindow);
+ m_InstallationManager.setParentWidget(w);
+ m_Updater.setUserInterface(w);
+ m_Runner.setUserInterface(ui);
checkForUpdates();
}
@@ -294,7 +261,7 @@ void OrganizerCore::checkForUpdates()
{
// this currently wouldn't work reliably if the ui isn't initialized yet to
// display the result
- if (m_MainWindow != nullptr) {
+ if (m_UserInterface != nullptr) {
m_Updater.testForUpdate(m_Settings);
}
}
@@ -390,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message)
{
if (MOShortcut moshortcut{ message } ) {
if(moshortcut.hasExecutable())
- runShortcut(moshortcut);
+ m_Runner.runShortcut(moshortcut);
}
else if (isNxmLink(message)) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
@@ -754,13 +721,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
int modIndex = ModInfo::getIndex(modName);
if (modIndex != UINT_MAX) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- if (hasIniTweaks && (m_MainWindow != nullptr)
+ if (hasIniTweaks && (m_UserInterface != nullptr)
&& (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"),
tr("This mod contains ini tweaks. Do you "
"want to configure them now?"),
QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes)) {
- m_MainWindow->displayModInformation(
+ m_UserInterface->displayModInformation(
modInfo, modIndex, ModInfoTabIDs::IniFiles);
}
m_ModInstalled(modName);
@@ -821,13 +788,13 @@ void OrganizerCore::installDownload(int index)
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
modInfo->addInstalledFile(modID, fileID);
- if (hasIniTweaks && m_MainWindow != nullptr
+ if (hasIniTweaks && m_UserInterface != nullptr
&& (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"),
tr("This mod contains ini tweaks. Do you "
"want to configure them now?"),
QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes)) {
- m_MainWindow->displayModInformation(
+ m_UserInterface->displayModInformation(
modInfo, modIndex, ModInfoTabIDs::IniFiles);
}
@@ -1104,412 +1071,6 @@ bool OrganizerCore::previewFile(
return true;
}
-bool OrganizerCore::runFile(
- QWidget* parent, const QFileInfo& targetInfo)
-{
- const auto fec = spawn::getFileExecutionContext(parent, targetInfo);
-
- switch (fec.type)
- {
- case spawn::FileExecutionTypes::Executable:
- {
- runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir());
- return true;
- }
-
- 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;
- }
- }
-}
-
-bool OrganizerCore::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_CurrentProfile->name(), currentDirectory, steamAppID,
- customOverwrite, forcedLibraries, &processExitCode);
-
- if (processHandle == INVALID_HANDLE_VALUE) {
- // failed
- return false;
- }
-
- if (refresh) {
- refreshDirectoryStructure();
-
- // need to remove our stored load order because it may be outdated if a foreign tool changed the
- // file time. After removing that file, refreshESPList will use the file time as the order
- if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- log::debug("removing loadorder.txt");
- QFile::remove(m_CurrentProfile->getLoadOrderFileName());
- }
-
- refreshDirectoryStructure();
-
- refreshESPList(true);
- savePluginList();
-
- //These callbacks should not fiddle with directoy structure and ESPs.
- m_FinishedRun(binary.absoluteFilePath(), processExitCode);
- }
-
- return true;
-}
-
-bool OrganizerCore::runExecutable(const Executable& exe, bool refresh)
-{
- const QString customOverwrite = m_CurrentProfile->setting(
- "custom_overwrites", exe.title()).toString();
-
- QList<MOBase::ExecutableForcedLoadSetting> forcedLibraries;
-
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
-
- return runExecutableFile(
- exe.binaryInfo(),
- exe.arguments(),
- exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(),
- exe.steamAppID(),
- customOverwrite,
- forcedLibraries,
- refresh);
-}
-
-bool OrganizerCore::runShortcut(const MOShortcut& shortcut)
-{
- if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) {
- throw std::runtime_error(
- QString("Refusing to run executable from different instance %1:%2")
- .arg(shortcut.instance(),shortcut.executable())
- .toLocal8Bit().constData());
- }
-
- const Executable& exe = m_ExecutablesList.get(shortcut.executable());
- return runExecutable(exe, false);
-}
-
-HANDLE OrganizerCore::runExecutableOrExecutableFile(
- const QString &executable, const QStringList &args, const QString &cwd,
- const QString &profile, const QString &forcedCustomOverwrite,
- bool ignoreCustomOverwrite)
-{
- QString profileName = profile;
- if (profile == "") {
- if (m_CurrentProfile != nullptr) {
- profileName = m_CurrentProfile->name();
- } else {
- throw MyException(tr("No profile set"));
- }
- }
-
- 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);
- if (binary.isRelative()) {
- // relative path, should be relative to game directory
- binary = managedGame()->gameDirectory().absoluteFilePath(executable);
- }
-
- if (currentDirectory == "") {
- currentDirectory = binary.absolutePath();
- }
-
- try {
- const Executable& exe = m_ExecutablesList.getByBinary(binary);
- steamAppID = exe.steamAppID();
- customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
- } catch (const std::runtime_error &) {
- // nop
- }
- } else {
- // only a file name, search executables list
- try {
- const Executable &exe = m_ExecutablesList.get(executable);
- steamAppID = exe.steamAppID();
- customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
- if (arguments == "") {
- arguments = exe.arguments();
- }
- binary = exe.binaryInfo();
- if (currentDirectory == "") {
- currentDirectory = exe.workingDirectory();
- }
- } catch (const std::runtime_error &) {
- log::warn("\"{}\" not set up as executable", executable);
- binary = QFileInfo(executable);
- }
- }
-
- if (!forcedCustomOverwrite.isEmpty())
- customOverwrite = forcedCustomOverwrite;
-
- if (ignoreCustomOverwrite)
- customOverwrite.clear();
-
- return spawnAndWait(binary,
- arguments,
- profileName,
- currentDirectory,
- steamAppID,
- customOverwrite,
- forcedLibraries);
-}
-
-HANDLE OrganizerCore::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)
-{
- spawn::SpawnParameters sp;
- sp.binary = binary;
- sp.arguments = arguments;
- sp.currentDirectory = currentDirectory;
- sp.steamAppID = steamAppID;
- sp.hooked = true;
-
- prepareStart();
-
- while (m_DirectoryUpdate) {
- ::Sleep(100);
- QCoreApplication::processEvents();
- }
-
- // need to make sure all data is saved before we start the application
- if (m_CurrentProfile != nullptr) {
- m_CurrentProfile->writeModlistNow(true);
- }
-
- // TODO: should also pass arguments
- if (!m_AboutToRun(binary.absoluteFilePath())) {
- log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
- return INVALID_HANDLE_VALUE;
- }
-
- try {
- m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
- m_USVFS.updateForcedLibraries(forcedLibraries);
-
- } catch (const UsvfsConnectorException &e) {
- log::debug(e.what());
- return INVALID_HANDLE_VALUE;
- } catch (const std::exception &e) {
- QMessageBox::warning(m_MainWindow, tr("Error"), e.what());
- return INVALID_HANDLE_VALUE;
- }
-
- HANDLE handle = spawn::Spawner()
- .spawn(m_MainWindow, m_GamePlugin, sp, m_Settings)
- .releaseHandle();
-
- if (handle == INVALID_HANDLE_VALUE) {
- // failed
- return INVALID_HANDLE_VALUE;
- }
-
- waitForProcessCompletionWithLock(handle, exitCode);
- return handle;
-}
-
-void OrganizerCore::withLock(std::function<void (ILockedWaitingForProcess*)> f)
-{
- std::unique_ptr<LockedDialog> dlg;
- ILockedWaitingForProcess* uilock = nullptr;
-
- if (m_MainWindow != nullptr) {
- uilock = m_MainWindow->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();
- }
-
- ON_BLOCK_EXIT([&]() {
- if (m_MainWindow != nullptr) {
- m_MainWindow->unlock();
- } });
-
- f(uilock);
-}
-
-bool OrganizerCore::waitForProcessCompletionWithLock(
- HANDLE handle, LPDWORD exitCode)
-{
- if (!Settings::instance().interface().lockGUI()) {
- return true;
- }
-
- bool r = false;
-
- withLock([&](auto* uilock) {
- DWORD ignoreExitCode;
- r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock);
- cycleDiagnostics();
- });
-
- return r;
-}
-
-bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
-{
- if (!Settings::instance().interface().lockGUI())
- return true;
-
- bool r = false;
-
- withLock([&](auto* uilock) {
- r = waitForProcessCompletion(handle, exitCode, uilock);
- });
-
- return r;
-}
-
-bool OrganizerCore::waitForProcessCompletion(
- HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
-{
- const auto tree = env::getProcessTree(handle);
- std::vector<env::Process> processes = {tree};
-
- const auto* interesting = getInterestingProcess(processes);
- if (!interesting) {
- return true;
- }
-
- if (uilock) {
- uilock->setProcessInformation(interesting->pid(), interesting->name());
- }
-
- auto interestingHandle = interesting->openHandleForWait();
- if (!interestingHandle) {
- return true;
- }
-
- auto progress = [&]{ return uilock->unlockForced(); };
- const auto r = spawn::waitForProcess(
- interestingHandle.get(), exitCode, progress);
-
- switch (r)
- {
- case spawn::WaitResults::Completed: // fall-through
- case spawn::WaitResults::Cancelled:
- return true;
-
- case spawn::WaitResults::Error: // fall-through
- default:
- return false;
- }
-}
-
-bool OrganizerCore::waitForAllUSVFSProcessesWithLock()
-{
- if (!Settings::instance().interface().lockGUI())
- return true;
-
- bool r = false;
-
- withLock([&](auto* uilock) {
- r = waitForAllUSVFSProcesses(uilock);
- });
-
- return r;
-}
-
-bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
-{
- for (;;) {
- const auto handles = getRunningUSVFSProcesses();
- if (handles.empty()) {
- break;
- }
-
- std::vector<env::Process> processes;
- for (auto&& h : handles) {
- auto p = env::getProcessTree(h);
- if (p.isValid()) {
- processes.emplace_back(std::move(p));
- }
- }
-
- const auto* interesting = getInterestingProcess(processes);
- if (!interesting) {
- break;
- }
-
- if (uilock) {
- uilock->setProcessInformation(interesting->pid(), interesting->name());
- }
-
- auto interestingHandle = interesting->openHandleForWait();
- if (!interestingHandle) {
- break;
- }
-
- auto progress = [&]{ return uilock->unlockForced(); };
- const auto r = spawn::waitForProcess(
- interestingHandle.get(), nullptr, progress);
-
- switch (r)
- {
- case spawn::WaitResults::Completed:
- // this process is completed, check for others
- break;
-
- case spawn::WaitResults::Cancelled:
- // force unlocked
- log::debug("waiting for process completion aborted by UI");
- return true;
-
- case spawn::WaitResults::Error: // fall-through
- default:
- log::debug("waiting for process completion not successful");
- return false;
- }
- }
-
- log::debug("waiting for process completion successful");
- return true;
-}
-
bool OrganizerCore::onAboutToRun(
const std::function<bool(const QString &)> &func)
{
@@ -1594,8 +1155,8 @@ void OrganizerCore::refreshBSAList()
m_ActiveArchives = m_DefaultArchives;
}
- if (m_MainWindow != nullptr) {
- m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives);
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives);
}
m_ArchivesInit = true;
@@ -1711,8 +1272,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap<unsigned int, ModInfo::P
// now we need to refresh the bsa list and save it so there is no confusion
// about what archives are available and active
refreshBSAList();
- if (m_MainWindow != nullptr) {
- m_MainWindow->archivesWriter().writeImmediately(false);
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->archivesWriter().writeImmediately(false);
}
std::vector<QString> archives = enabledArchives();
@@ -1896,8 +1457,8 @@ void OrganizerCore::modStatusChanged(unsigned int index)
= m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
origin.enable(false);
}
- if (m_MainWindow != nullptr) {
- m_MainWindow->archivesWriter().write();
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->archivesWriter().write();
}
}
modInfo->clearCaches();
@@ -1946,8 +1507,8 @@ void OrganizerCore::modStatusChanged(QList<unsigned int> index) {
origin.enable(false);
}
}
- if (m_MainWindow != nullptr) {
- m_MainWindow->archivesWriter().write();
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->archivesWriter().write();
}
}
@@ -2122,8 +1683,8 @@ bool OrganizerCore::saveCurrentLists()
try {
savePluginList();
- if (m_MainWindow != nullptr) {
- m_MainWindow->archivesWriter().write();
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->archivesWriter().write();
}
} catch (const std::exception &e) {
reportError(tr("failed to save load order: %1").arg(e.what()));
@@ -2147,11 +1708,12 @@ void OrganizerCore::savePluginList()
m_PluginList.saveLoadOrder(*m_DirectoryStructure);
}
-void OrganizerCore::prepareStart()
+void OrganizerCore::saveCurrentProfile()
{
if (m_CurrentProfile == nullptr) {
return;
}
+
m_CurrentProfile->writeModlist();
m_CurrentProfile->createTweakedIniFile();
saveCurrentLists();
@@ -2159,6 +1721,79 @@ void OrganizerCore::prepareStart()
storeSettings();
}
+ProcessRunner& OrganizerCore::processRunner()
+{
+ return m_Runner;
+}
+
+bool OrganizerCore::beforeRun(
+ const QFileInfo& binary, const QString& profileName,
+ const QString& customOverwrite,
+ const QList<MOBase::ExecutableForcedLoadSetting>& forcedLibraries)
+{
+ saveCurrentProfile();
+
+ while (m_DirectoryUpdate) {
+ ::Sleep(100);
+ QCoreApplication::processEvents();
+ }
+
+ // need to make sure all data is saved before we start the application
+ if (m_CurrentProfile != nullptr) {
+ m_CurrentProfile->writeModlistNow(true);
+ }
+
+ // TODO: should also pass arguments
+ if (!m_AboutToRun(binary.absoluteFilePath())) {
+ log::debug("start of \"{}\" cancelled by plugin", binary.absoluteFilePath());
+ return false;
+ }
+
+ try
+ {
+ m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
+ m_USVFS.updateForcedLibraries(forcedLibraries);
+ }
+ catch (const UsvfsConnectorException &e)
+ {
+ log::debug(e.what());
+ return false;
+ }
+ catch (const std::exception &e)
+ {
+ QWidget* w = nullptr;
+ if (m_UserInterface) {
+ w = m_UserInterface->qtWidget();
+ }
+ QMessageBox::warning(w, tr("Error"), e.what());
+ return false;
+ }
+
+ return true;
+}
+
+void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode)
+{
+ refreshDirectoryStructure();
+
+ // need to remove our stored load order because it may be outdated if a
+ // foreign tool changed the file time. After removing that file,
+ // refreshESPList will use the file time as the order
+ if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
+ log::debug("removing loadorder.txt");
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+
+ refreshDirectoryStructure();
+
+ refreshESPList(true);
+ savePluginList();
+ cycleDiagnostics();
+
+ //These callbacks should not fiddle with directoy structure and ESPs.
+ m_FinishedRun(binary.absoluteFilePath(), exitCode);
+}
+
std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
const QString &customOverwrite)
{
diff --git a/src/organizercore.h b/src/organizercore.h
index ffdb6830..2252c118 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -3,7 +3,6 @@
#include "selfupdater.h"
-#include "ilockedwaitingforprocess.h"
#include "settings.h"
#include "modlist.h"
#include "modinfo.h"
@@ -14,6 +13,7 @@
#include "executableslist.h"
#include "usvfsconnector.h"
#include "moshortcut.h"
+#include "processrunner.h"
#include <directoryentry.h>
#include <imoinfo.h>
#include <iplugindiagnose.h>
@@ -26,7 +26,7 @@
class ModListSortProxy;
class PluginListSortProxy;
class Profile;
-class MainWindow;
+class IUserInterface;
namespace MOBase {
template <typename T> class GuessedValue;
@@ -97,7 +97,7 @@ public:
~OrganizerCore();
- void setUserInterface(MainWindow* mainWindow);
+ void setUserInterface(IUserInterface* ui);
void connectPlugins(PluginContainer *container);
void disconnectPlugins();
@@ -134,7 +134,14 @@ public:
bool saveCurrentLists();
- void prepareStart();
+ ProcessRunner& processRunner();
+
+ bool beforeRun(
+ const QFileInfo& binary, const QString& profileName,
+ const QString& customOverwrite,
+ const QList<MOBase::ExecutableForcedLoadSetting>& forcedLibraries);
+
+ void afterRun(const QFileInfo& binary, DWORD exitCode);
void refreshESPList(bool force = false);
void refreshBSAList();
@@ -149,27 +156,6 @@ public:
bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1);
bool previewFile(QWidget* parent, const QString& originName, const QString& path);
- bool runFile(QWidget* parent, const QFileInfo& targetInfo);
-
- bool runExecutableFile(
- const QFileInfo &binary, const QString &arguments,
- const QDir &currentDirectory, const QString &steamAppID={},
- const QString &customOverwrite={},
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries={},
- bool refresh=true);
-
- bool runExecutable(const Executable& exe, bool refresh=true);
-
- bool runShortcut(const MOShortcut& shortcut);
-
- HANDLE runExecutableOrExecutableFile(
- 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();
-
void loginSuccessfulUpdate(bool necessary);
void loginFailedUpdate(const QString &message);
@@ -273,6 +259,7 @@ signals:
private:
+ void saveCurrentProfile();
void storeSettings();
bool queryApi(QString &apiKey);
@@ -298,26 +285,6 @@ private:
const MOShared::DirectoryEntry *directoryEntry,
int createDestination);
- HANDLE spawnAndWait(const QFileInfo &binary, const QString &arguments,
- const QString &profileName,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries = QList<MOBase::ExecutableForcedLoadSetting>(),
- LPDWORD exitCode = nullptr);
-
- bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode);
-
- bool waitForProcessCompletion(
- HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock);
-
- bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock);
-
- void withLock(std::function<void (ILockedWaitingForProcess*)> f);
-
- HANDLE findAndOpenAUSVFSProcess(
- const std::vector<QString>& hiddenList, DWORD preferedParentPid);
-
private slots:
void directory_refreshed();
@@ -331,13 +298,14 @@ private:
static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1;
private:
- MainWindow* m_MainWindow;
+ IUserInterface* m_UserInterface;
PluginContainer *m_PluginContainer;
QString m_GameName;
MOBase::IPluginGame *m_GamePlugin;
Profile *m_CurrentProfile;
+ ProcessRunner m_Runner;
Settings& m_Settings;
SelfUpdater m_Updater;
diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp
index 2ea1761a..75b3ea41 100644
--- a/src/organizerproxy.cpp
+++ b/src/organizerproxy.cpp
@@ -112,14 +112,14 @@ HANDLE OrganizerProxy::startApplication(
const QString &profile, const QString &forcedCustomOverwrite,
bool ignoreCustomOverwrite)
{
- return m_Proxied->runExecutableOrExecutableFile(
+ return m_Proxied->processRunner().runExecutableOrExecutableFile(
executable, args, cwd, profile,
forcedCustomOverwrite, ignoreCustomOverwrite);
}
bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const
{
- return m_Proxied->waitForApplication(handle, exitCode);
+ return m_Proxied->processRunner().waitForApplication(handle, exitCode);
}
bool OrganizerProxy::onAboutToRun(const std::function<bool (const QString &)> &func)
diff --git a/src/processrunner.cpp b/src/processrunner.cpp
new file mode 100644
index 00000000..81c4b99e
--- /dev/null
+++ b/src/processrunner.cpp
@@ -0,0 +1,634 @@
+#include "processrunner.h"
+#include "organizercore.h"
+#include "instancemanager.h"
+#include "lockeddialog.h"
+#include "iuserinterface.h"
+#include "envmodule.h"
+#include <log.h>
+
+using namespace MOBase;
+
+void adjustForVirtualized(
+ const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings)
+{
+ 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());
+ }
+}
+
+env::Process* getInterestingProcess(std::vector<env::Process>& processes)
+{
+ if (processes.empty()) {
+ return nullptr;
+ }
+
+ // Certain process names we wish to "hide" for aesthetic reason:
+ const std::vector<QString> hiddenList = {
+ QFileInfo(QCoreApplication::applicationFilePath()).fileName()
+ };
+
+ auto isHidden = [&](auto&& p) {
+ for (auto h : hiddenList) {
+ if (p.name().contains(h, Qt::CaseInsensitive)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+
+ for (auto&& root : processes) {
+ if (!isHidden(root)) {
+ return &root;
+ }
+
+ for (auto&& child : root.children()) {
+ if (!isHidden(child)) {
+ return &child;
+ }
+ }
+ }
+
+
+ // everything is hidden, just pick the first one
+ return &processes[0];
+}
+
+
+SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp)
+ : m_handle(handle), m_parameters(std::move(sp))
+{
+}
+
+SpawnedProcess::SpawnedProcess(SpawnedProcess&& other)
+ : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters))
+{
+ other.m_handle = INVALID_HANDLE_VALUE;
+}
+
+SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other)
+{
+ if (this != &other) {
+ destroy();
+
+ m_handle = other.m_handle;
+ other.m_handle = INVALID_HANDLE_VALUE;
+
+ m_parameters = std::move(other.m_parameters);
+ }
+
+ return *this;
+}
+
+SpawnedProcess::~SpawnedProcess()
+{
+ destroy();
+}
+
+HANDLE SpawnedProcess::releaseHandle()
+{
+ const auto h = m_handle;
+ m_handle = INVALID_HANDLE_VALUE;
+ return h;
+}
+
+void SpawnedProcess::destroy()
+{
+ if (m_handle != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(m_handle);
+ m_handle = INVALID_HANDLE_VALUE;
+ }
+}
+
+
+ProcessRunner::ProcessRunner(OrganizerCore& core)
+ : m_core(core), m_ui(nullptr)
+{
+}
+
+void ProcessRunner::setUserInterface(IUserInterface* ui)
+{
+ m_ui = ui;
+}
+
+bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo)
+{
+ if (!parent && m_ui) {
+ parent = m_ui->qtWidget();
+ }
+
+ const auto fec = spawn::getFileExecutionContext(parent, targetInfo);
+
+ switch (fec.type)
+ {
+ case spawn::FileExecutionTypes::Executable:
+ {
+ runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir());
+ return true;
+ }
+
+ 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;
+ }
+ }
+}
+
+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;
+}
+
+bool ProcessRunner::runExecutable(const Executable& exe, bool refresh)
+{
+ const auto* profile = m_core.currentProfile();
+ if (!profile) {
+ throw MyException(QObject::tr("No profile set"));
+ }
+
+ const QString customOverwrite = profile->setting(
+ "custom_overwrites", exe.title()).toString();
+
+ QList<MOBase::ExecutableForcedLoadSetting> 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);
+}
+
+bool ProcessRunner::runShortcut(const MOShortcut& shortcut)
+{
+ if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) {
+ throw std::runtime_error(
+ QString("Refusing to run executable from different instance %1:%2")
+ .arg(shortcut.instance(),shortcut.executable())
+ .toLocal8Bit().constData());
+ }
+
+ const Executable& exe = m_core.executablesList()->get(shortcut.executable());
+ return runExecutable(exe, false);
+}
+
+HANDLE ProcessRunner::runExecutableOrExecutableFile(
+ const QString& executable, const QStringList &args, const QString &cwd,
+ const QString& profileOverride, const QString &forcedCustomOverwrite,
+ bool ignoreCustomOverwrite)
+{
+ const auto* profile = m_core.currentProfile();
+ if (!profile) {
+ throw MyException(QObject::tr("No profile set"));
+ }
+
+ QString profileName = profileOverride;
+ if (profileName == "") {
+ profileName = profile->name();
+ }
+
+ 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);
+ if (binary.isRelative()) {
+ // relative path, should be relative to game directory
+ binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable);
+ }
+
+ if (currentDirectory == "") {
+ currentDirectory = binary.absolutePath();
+ }
+
+ try {
+ const Executable& exe = m_core.executablesList()->getByBinary(binary);
+ steamAppID = exe.steamAppID();
+ customOverwrite = profile->setting("custom_overwrites", exe.title()).toString();
+ if (profile->forcedLibrariesEnabled(exe.title())) {
+ forcedLibraries = profile->determineForcedLibraries(exe.title());
+ }
+ } catch (const std::runtime_error &) {
+ // nop
+ }
+ } else {
+ // 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();
+ if (profile->forcedLibrariesEnabled(exe.title())) {
+ forcedLibraries = profile->determineForcedLibraries(exe.title());
+ }
+ if (arguments == "") {
+ arguments = exe.arguments();
+ }
+ binary = exe.binaryInfo();
+ if (currentDirectory == "") {
+ currentDirectory = exe.workingDirectory();
+ }
+ } catch (const std::runtime_error &) {
+ log::warn("\"{}\" not set up as executable", executable);
+ binary = QFileInfo(executable);
+ }
+ }
+
+ if (!forcedCustomOverwrite.isEmpty())
+ customOverwrite = forcedCustomOverwrite;
+
+ if (ignoreCustomOverwrite)
+ customOverwrite.clear();
+
+ return spawnAndWait(
+ binary,
+ arguments,
+ profileName,
+ currentDirectory,
+ steamAppID,
+ customOverwrite,
+ forcedLibraries);
+}
+
+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)
+{
+ spawn::SpawnParameters sp;
+ sp.binary = binary;
+ sp.arguments = arguments;
+ sp.currentDirectory = currentDirectory;
+ sp.steamAppID = steamAppID;
+ sp.hooked = true;
+
+ if (!m_core.beforeRun(binary, profileName, customOverwrite, forcedLibraries)) {
+ return INVALID_HANDLE_VALUE;
+ }
+
+ HANDLE handle = spawn(sp).releaseHandle();
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ // failed
+ return INVALID_HANDLE_VALUE;
+ }
+
+ waitForProcessCompletionWithLock(handle, exitCode);
+ return handle;
+}
+
+SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp)
+{
+ QWidget* parent = nullptr;
+ if (m_ui) {
+ parent = m_ui->qtWidget();
+ }
+
+ if (!checkBinary(parent, sp)) {
+ return {INVALID_HANDLE_VALUE, sp};
+ }
+
+ const auto* game = m_core.managedGame();
+ auto& settings = m_core.settings();
+
+ if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) {
+ return {INVALID_HANDLE_VALUE, sp};
+ }
+
+ if (!checkEnvironment(parent, sp)) {
+ return {INVALID_HANDLE_VALUE, sp};
+ }
+
+ if (!checkBlacklist(parent, sp, settings)) {
+ return {INVALID_HANDLE_VALUE, sp};
+ }
+
+ adjustForVirtualized(game, sp, settings);
+
+ return {startBinary(parent, sp), sp};
+}
+
+void ProcessRunner::withLock(std::function<void (ILockedWaitingForProcess*)> f)
+{
+ std::unique_ptr<LockedDialog> dlg;
+ ILockedWaitingForProcess* uilock = nullptr;
+
+ 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();
+ }
+
+ Guard g([&]() {
+ if (m_ui != nullptr) {
+ m_ui->unlock();
+ }
+ });
+
+ f(uilock);
+}
+
+bool ProcessRunner::waitForProcessCompletionWithLock(
+ HANDLE handle, LPDWORD exitCode)
+{
+ if (!Settings::instance().interface().lockGUI()) {
+ return true;
+ }
+
+ bool r = false;
+
+ withLock([&](auto* uilock) {
+ DWORD ignoreExitCode;
+ r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock);
+ });
+
+ return r;
+}
+
+bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode)
+{
+ if (!Settings::instance().interface().lockGUI())
+ return true;
+
+ bool r = false;
+
+ withLock([&](auto* uilock) {
+ r = waitForProcessCompletion(handle, exitCode, uilock);
+ });
+
+ return r;
+}
+
+bool ProcessRunner::waitForProcessCompletion(
+ HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
+{
+ const auto tree = env::getProcessTree(handle);
+ std::vector<env::Process> processes = {tree};
+
+ const auto* interesting = getInterestingProcess(processes);
+ if (!interesting) {
+ return true;
+ }
+
+ if (uilock) {
+ uilock->setProcessInformation(interesting->pid(), interesting->name());
+ }
+
+ auto interestingHandle = interesting->openHandleForWait();
+ if (!interestingHandle) {
+ return true;
+ }
+
+ auto progress = [&]{ return uilock->unlockForced(); };
+ const auto r = waitForProcess(
+ interestingHandle.get(), exitCode, progress);
+
+ switch (r)
+ {
+ case WaitResults::Completed: // fall-through
+ case WaitResults::Cancelled:
+ return true;
+
+ case WaitResults::Error: // fall-through
+ default:
+ return false;
+ }
+}
+
+bool ProcessRunner::waitForAllUSVFSProcessesWithLock()
+{
+ if (!Settings::instance().interface().lockGUI())
+ return true;
+
+ bool r = false;
+
+ withLock([&](auto* uilock) {
+ r = waitForAllUSVFSProcesses(uilock);
+ });
+
+ return r;
+}
+
+bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
+{
+ for (;;) {
+ const auto handles = getRunningUSVFSProcesses();
+ if (handles.empty()) {
+ break;
+ }
+
+ std::vector<env::Process> processes;
+ for (auto&& h : handles) {
+ auto p = env::getProcessTree(h);
+ if (p.isValid()) {
+ processes.emplace_back(std::move(p));
+ }
+ }
+
+ const auto* interesting = getInterestingProcess(processes);
+ if (!interesting) {
+ break;
+ }
+
+ if (uilock) {
+ uilock->setProcessInformation(interesting->pid(), interesting->name());
+ }
+
+ auto interestingHandle = interesting->openHandleForWait();
+ if (!interestingHandle) {
+ break;
+ }
+
+ auto progress = [&]{ return uilock->unlockForced(); };
+ const auto r = waitForProcess(
+ interestingHandle.get(), nullptr, progress);
+
+ 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;
+ }
+ }
+
+ log::debug("waiting for process completion successful");
+ return true;
+}
+
+
+
+WaitResults waitForProcess(
+ HANDLE handle, DWORD* exitCode, std::function<bool ()> progress)
+{
+ if (handle == INVALID_HANDLE_VALUE) {
+ return WaitResults::Error;
+ }
+
+ log::debug("waiting for completion on pid {}", ::GetProcessId(handle));
+
+ std::vector<HANDLE> handles;
+ handles.push_back(handle);
+
+ std::vector<DWORD> exitCodes;
+
+ const auto r = waitForProcesses(handles, exitCodes, progress);
+
+ if (r == WaitResults::Completed) {
+ if (exitCode && !exitCodes.empty()) {
+ *exitCode = exitCodes[0];
+ }
+ }
+
+ return r;
+}
+
+WaitResults waitForProcesses(
+ const std::vector<HANDLE>& handles, std::vector<DWORD>& exitCodes,
+ std::function<bool ()> progress)
+{
+ if (handles.empty()) {
+ return WaitResults::Completed;
+ }
+
+ const auto WAIT_OBJECT_N = static_cast<DWORD>(WAIT_OBJECT_0 + handles.size());
+
+ for (;;) {
+ // Wait for a an event on the handle, a key press, mouse click or timeout
+ const auto res = MsgWaitForMultipleObjects(
+ static_cast<DWORD>(handles.size()), &handles[0],
+ TRUE, 50, QS_KEY | QS_MOUSEBUTTON);
+
+ if (res == WAIT_FAILED) {
+ // error
+ const auto e = ::GetLastError();
+
+ log::error(
+ "failed waiting for process completion, {}", formatSystemMessage(e));
+
+ return WaitResults::Error;
+ } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) {
+ // completed
+ exitCodes.resize(handles.size());
+ std::fill(exitCodes.begin(), exitCodes.end(), 0);
+
+ for (std::size_t i=0; i<handles.size(); ++i) {
+ DWORD exitCode = 0;
+
+ if (::GetExitCodeProcess(handles[i], &exitCode)) {
+ exitCodes[i] = exitCode;
+ } else {
+ const auto e = ::GetLastError();
+ log::warn(
+ "failed to get exit code of process, {}",
+ formatSystemMessage(e));
+ }
+ }
+
+ return WaitResults::Completed;
+ }
+
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::sendPostedEvents();
+ QCoreApplication::processEvents();
+
+ if (progress && progress()) {
+ return WaitResults::Cancelled;
+ }
+ }
+}
diff --git a/src/processrunner.h b/src/processrunner.h
new file mode 100644
index 00000000..38e79ca8
--- /dev/null
+++ b/src/processrunner.h
@@ -0,0 +1,104 @@
+#ifndef PROCESSRUNNER_H
+#define PROCESSRUNNER_H
+
+#include "spawn.h"
+#include <executableinfo.h>
+
+class OrganizerCore;
+class ILockedWaitingForProcess;
+class IUserInterface;
+class Executable;
+class MOShortcut;
+
+class SpawnedProcess
+{
+public:
+ SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp);
+
+ SpawnedProcess(const SpawnedProcess&) = delete;
+ SpawnedProcess& operator=(const SpawnedProcess&) = delete;
+ SpawnedProcess(SpawnedProcess&& other);
+ SpawnedProcess& operator=(SpawnedProcess&& other);
+ ~SpawnedProcess();
+
+ HANDLE releaseHandle();
+ void wait();
+
+private:
+ HANDLE m_handle;
+ spawn::SpawnParameters m_parameters;
+
+ void destroy();
+};
+
+
+class ProcessRunner
+{
+public:
+ ProcessRunner(OrganizerCore& core);
+
+ void setUserInterface(IUserInterface* ui);
+
+ bool runFile(QWidget* parent, const QFileInfo& targetInfo);
+
+ bool runExecutableFile(
+ const QFileInfo &binary, const QString &arguments,
+ const QDir &currentDirectory, const QString &steamAppID={},
+ const QString &customOverwrite={},
+ const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries={},
+ bool refresh=true);
+
+ bool runExecutable(const Executable& exe, bool refresh=true);
+
+ bool runShortcut(const MOShortcut& shortcut);
+
+ HANDLE runExecutableOrExecutableFile(
+ 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();
+
+private:
+ OrganizerCore& m_core;
+ IUserInterface* m_ui;
+
+ HANDLE 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 = nullptr);
+
+ SpawnedProcess spawn(spawn::SpawnParameters sp);
+
+ void withLock(std::function<void (ILockedWaitingForProcess*)> f);
+
+ bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode);
+
+ bool waitForProcessCompletion(
+ HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock);
+
+ bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock);
+};
+
+
+enum class WaitResults
+{
+ Completed = 1,
+ Error,
+ Cancelled
+};
+
+WaitResults waitForProcess(
+ HANDLE handle, DWORD* exitCode, std::function<bool ()> progress);
+
+WaitResults waitForProcesses(
+ const std::vector<HANDLE>& handles, std::vector<DWORD>& exitCodes,
+ std::function<bool ()> progress);
+
+#endif // PROCESSRUNNER_H
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 0ea60641..c8d7c76a 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -772,50 +772,6 @@ bool checkBlacklist(
}
}
-
-void adjustForVirtualized(
- const IPluginGame* game, SpawnParameters& sp, const Settings& settings)
-{
- 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());
- }
-}
-
-
HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
{
HANDLE handle = INVALID_HANDLE_VALUE;
@@ -842,80 +798,6 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
}
}
-
-
-SpawnedProcess::SpawnedProcess(HANDLE handle, SpawnParameters sp)
- : m_handle(handle), m_parameters(std::move(sp))
-{
-}
-
-SpawnedProcess::SpawnedProcess(SpawnedProcess&& other)
- : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters))
-{
- other.m_handle = INVALID_HANDLE_VALUE;
-}
-
-SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other)
-{
- if (this != &other) {
- destroy();
-
- m_handle = other.m_handle;
- other.m_handle = INVALID_HANDLE_VALUE;
-
- m_parameters = std::move(other.m_parameters);
- }
-
- return *this;
-}
-
-SpawnedProcess::~SpawnedProcess()
-{
- destroy();
-}
-
-HANDLE SpawnedProcess::releaseHandle()
-{
- const auto h = m_handle;
- m_handle = INVALID_HANDLE_VALUE;
- return h;
-}
-
-void SpawnedProcess::destroy()
-{
- if (m_handle != INVALID_HANDLE_VALUE) {
- ::CloseHandle(m_handle);
- m_handle = INVALID_HANDLE_VALUE;
- }
-}
-
-
-SpawnedProcess Spawner::spawn(
- QWidget* parent, const IPluginGame* game,
- SpawnParameters sp, Settings& settings)
-{
- if (!checkBinary(parent, sp)) {
- return {INVALID_HANDLE_VALUE, sp};
- }
-
- if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) {
- return {INVALID_HANDLE_VALUE, sp};
- }
-
- if (!checkEnvironment(parent, sp)) {
- return {INVALID_HANDLE_VALUE, sp};
- }
-
- if (!checkBlacklist(parent, sp, settings)) {
- return {INVALID_HANDLE_VALUE, sp};
- }
-
- adjustForVirtualized(game, sp, settings);
-
- return {startBinary(parent, sp), sp};
-}
-
-
QString getExecutableForJarFile(const QString& jarFile)
{
const std::wstring jarFileW = jarFile.toStdWString();
@@ -1094,86 +976,6 @@ FileExecutionContext getFileExecutionContext(
return {{}, {}, FileExecutionTypes::Other};
}
-WaitResults waitForProcess(
- HANDLE handle, DWORD* exitCode, std::function<bool ()> progress)
-{
- if (handle == INVALID_HANDLE_VALUE) {
- return WaitResults::Error;
- }
-
- log::debug("waiting for completion on pid {}", ::GetProcessId(handle));
-
- std::vector<HANDLE> handles;
- handles.push_back(handle);
-
- std::vector<DWORD> exitCodes;
-
- const auto r = waitForProcesses(handles, exitCodes, progress);
-
- if (r == WaitResults::Completed) {
- if (exitCode && !exitCodes.empty()) {
- *exitCode = exitCodes[0];
- }
- }
-
- return r;
-}
-
-WaitResults waitForProcesses(
- const std::vector<HANDLE>& handles, std::vector<DWORD>& exitCodes,
- std::function<bool ()> progress)
-{
- if (handles.empty()) {
- return WaitResults::Completed;
- }
-
- const auto WAIT_OBJECT_N = static_cast<DWORD>(WAIT_OBJECT_0 + handles.size());
-
- for (;;) {
- // Wait for a an event on the handle, a key press, mouse click or timeout
- const auto res = MsgWaitForMultipleObjects(
- static_cast<DWORD>(handles.size()), &handles[0],
- TRUE, 50, QS_KEY | QS_MOUSEBUTTON);
-
- if (res == WAIT_FAILED) {
- // error
- const auto e = ::GetLastError();
-
- log::error(
- "failed waiting for process completion, {}", formatSystemMessage(e));
-
- return WaitResults::Error;
- } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) {
- // completed
- exitCodes.resize(handles.size());
- std::fill(exitCodes.begin(), exitCodes.end(), 0);
-
- for (std::size_t i=0; i<handles.size(); ++i) {
- DWORD exitCode = 0;
-
- if (::GetExitCodeProcess(handles[i], &exitCode)) {
- exitCodes[i] = exitCode;
- } else {
- const auto e = ::GetLastError();
- log::warn(
- "failed to get exit code of process, {}",
- formatSystemMessage(e));
- }
- }
-
- return WaitResults::Completed;
- }
-
- // keep processing events so the app doesn't appear dead
- QCoreApplication::sendPostedEvents();
- QCoreApplication::processEvents();
-
- if (progress && progress()) {
- return WaitResults::Cancelled;
- }
- }
-}
-
} // namespace
diff --git a/src/spawn.h b/src/spawn.h
index ad50bde6..0464ffd6 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -73,39 +73,6 @@ bool checkBlacklist(
HANDLE startBinary(QWidget* parent, const SpawnParameters& sp);
-class SpawnedProcess
-{
-public:
- SpawnedProcess(HANDLE handle, SpawnParameters sp);
-
- SpawnedProcess(const SpawnedProcess&) = delete;
- SpawnedProcess& operator=(const SpawnedProcess&) = delete;
- SpawnedProcess(SpawnedProcess&& other);
- SpawnedProcess& operator=(SpawnedProcess&& other);
- ~SpawnedProcess();
-
- HANDLE releaseHandle();
- void wait();
-
-private:
- HANDLE m_handle;
- SpawnParameters m_parameters;
-
- void destroy();
-};
-
-
-class Spawner
-{
-public:
- SpawnedProcess spawn(
- QWidget* parent, const MOBase::IPluginGame* game,
- SpawnParameters sp, Settings& settings);
-
-private:
-};
-
-
enum class FileExecutionTypes
{
Executable = 1,
@@ -124,21 +91,6 @@ QString findJavaInstallation(const QString& jarFile);
FileExecutionContext getFileExecutionContext(
QWidget* parent, const QFileInfo& target);
-
-enum class WaitResults
-{
- Completed = 1,
- Error,
- Cancelled
-};
-
-WaitResults waitForProcess(
- HANDLE handle, DWORD* exitCode, std::function<bool ()> progress);
-
-WaitResults waitForProcesses(
- const std::vector<HANDLE>& handles, std::vector<DWORD>& exitCodes,
- std::function<bool ()> progress);
-
} // namespace