From d91580cc2669e2ab018c2aaab472cd763e5441d8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 18 Oct 2019 15:39:07 -0400 Subject: initial Spawner and SpawnedProcess added steam app id to spawn parameters removed threadHandle, unused moved most of the stuff from OrganizerCore::spawnBinaryProcess() to Spawner replaced m_UserInterface by m_MainWindow --- src/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 12ed40b3..b5b9ab0d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -411,7 +411,7 @@ MainWindow::MainWindow(Settings &settings m_Tutorial.expose("modList", m_OrganizerCore.modList()); m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); - m_OrganizerCore.setUserInterface(this, this); + m_OrganizerCore.setUserInterface(this); for (const QString &fileName : m_PluginContainer.pluginFileNames()) { installTranslator(QFileInfo(fileName).baseName()); } @@ -595,7 +595,7 @@ MainWindow::~MainWindow() cleanup(); m_PluginContainer.setUserInterface(nullptr, nullptr); - m_OrganizerCore.setUserInterface(nullptr, nullptr); + m_OrganizerCore.setUserInterface(nullptr); m_IntegratedBrowser.close(); delete ui; } catch (std::exception &e) { -- cgit v1.3.1 From e43500eaf5d40bcd28ba5a820cdbc754cfb47e40 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 02:28:20 -0400 Subject: renamed runExecutable() to runExecutablefile() added runExecutable() for Executable class, also used by runShortcut() --- src/mainwindow.cpp | 24 ++----------- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 79 +++++++++++++++++++++++++----------------- src/organizercore.h | 10 ++++-- 4 files changed, 59 insertions(+), 56 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5b9ab0d..2474fdc0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1542,26 +1542,8 @@ void MainWindow::startExeAction() } action->setEnabled(false); - const Executable& exe = *itor; - auto& profile = *m_OrganizerCore.currentProfile(); - - QString customOverwrite = profile.setting("custom_overwrites", exe.title()).toString(); - auto forcedLibraries = profile.determineForcedLibraries(exe.title()); - - if (!profile.forcedLibrariesEnabled(exe.title())) { - forcedLibraries.clear(); - } - - m_OrganizerCore.spawnBinary( - exe.binaryInfo(), exe.arguments(), - exe.workingDirectory().length() != 0 - ? exe.workingDirectory() - : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - customOverwrite, - forcedLibraries); + m_OrganizerCore.runExecutable(*itor); action->setEnabled(true); - } void MainWindow::activateSelectedProfile() @@ -2387,7 +2369,7 @@ void MainWindow::on_startButton_clicked() forcedLibraries.clear(); } - m_OrganizerCore.spawnBinary( + m_OrganizerCore.runExecutableFile( selectedExecutable->binaryInfo(), selectedExecutable->arguments(), selectedExecutable->workingDirectory().length() != 0 ? @@ -5477,7 +5459,7 @@ void MainWindow::openDataFile() } QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - m_OrganizerCore.executeFileVirtualized(this, targetInfo); + m_OrganizerCore.runFile(this, targetInfo); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 36559a75..68b1be6b 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().executeFileVirtualized(parentWidget(), item->fileName()); + core().runFile(parentWidget(), item->fileName()); return true; }); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 4270f3b4..6f36ee3e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1188,7 +1188,7 @@ bool OrganizerCore::runFile( switch (type) { case FileExecutionTypes::Executable: { - runExecutable( + runExecutableFile( binaryInfo, arguments, currentProfile()->name(), targetInfo.absolutePath()); @@ -1208,25 +1208,33 @@ bool OrganizerCore::runFile( return false; } -void OrganizerCore::runExecutable( +bool OrganizerCore::runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, - const QList &forcedLibraries) + const QList &forcedLibraries, + bool refresh) { DWORD processExitCode = 0; HANDLE processHandle = spawnAndWait( binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode); - if (processHandle != INVALID_HANDLE_VALUE) { + 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); @@ -1235,6 +1243,42 @@ void OrganizerCore::runExecutable( //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 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::spawnAndWait( @@ -1318,33 +1362,6 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } - -HANDLE 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()); - - auto forcedLibaries = m_CurrentProfile->determineForcedLibraries(shortcut.executable()); - if (!m_CurrentProfile->forcedLibrariesEnabled(shortcut.executable())) { - forcedLibaries.clear(); - } - - return spawnAndWait( - exe.binaryInfo(), exe.arguments(), - m_CurrentProfile->name(), - exe.workingDirectory().length() != 0 - ? exe.workingDirectory() - : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - "", - forcedLibaries); -} - HANDLE OrganizerCore::startApplication(const QString &executable, const QStringList &args, const QString &cwd, diff --git a/src/organizercore.h b/src/organizercore.h index 6628452c..ca87a05c 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -163,11 +163,16 @@ public: bool runFile(QWidget* parent, const QFileInfo& targetInfo); - void runExecutable( + bool runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID={}, const QString &customOverwrite={}, - const QList &forcedLibraries={}); + const QList &forcedLibraries={}, + bool refresh=true); + + bool runExecutable(const Executable& exe, bool refresh=true); + + bool runShortcut(const MOShortcut& shortcut); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -222,7 +227,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - HANDLE runShortcut(const MOShortcut& shortcut); HANDLE startApplication(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); HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); -- cgit v1.3.1 From 4e8dcc5157706e1478396179f5dc11305532b159 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 04:27:22 -0400 Subject: moved findJavaInstallation() and getFileExecutionContext() to spawn fixed env::get() returning garbage after value --- src/editexecutablesdialog.cpp | 3 +- src/env.cpp | 31 +++-- src/mainwindow.cpp | 68 +++++------ src/organizercore.cpp | 264 +++++++++++++++--------------------------- src/organizercore.h | 12 -- src/spawn.cpp | 183 ++++++++++++++++++++++++++++- src/spawn.h | 19 +++ 7 files changed, 351 insertions(+), 229 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 8535b7a7..32b31357 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "modlist.h" #include "forcedloaddialog.h" #include "organizercore.h" +#include "spawn.h" #include #include @@ -800,7 +801,7 @@ QFileInfo EditExecutablesDialog::browseBinary(const QString& initial) void EditExecutablesDialog::setJarBinary(const QFileInfo& binary) { - auto java = OrganizerCore::findJavaInstallation(binary.absoluteFilePath()); + auto java = spawn::findJavaInstallation(binary.absoluteFilePath()); if (java.isEmpty()) { QMessageBox::information( diff --git a/src/env.cpp b/src/env.cpp index 78b5dc96..507607d1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -190,19 +190,36 @@ QString setPath(const QString& s) QString get(const QString& name) { - std::wstring s(4000, L' '); + std::size_t bufferSize = 4000; + auto buffer = std::make_unique(bufferSize); DWORD realSize = ::GetEnvironmentVariableW( - name.toStdWString().c_str(), s.data(), static_cast(s.size())); + name.toStdWString().c_str(), + buffer.get(), static_cast(bufferSize)); - if (realSize > s.size()) { - s.resize(realSize); + if (realSize > bufferSize) { + bufferSize = realSize; + buffer = std::make_unique(bufferSize); - ::GetEnvironmentVariableW( - name.toStdWString().c_str(), s.data(), static_cast(s.size())); + realSize = ::GetEnvironmentVariableW( + name.toStdWString().c_str(), + buffer.get(), static_cast(bufferSize)); } - return QString::fromStdWString(s); + if (realSize == 0) { + const auto e = ::GetLastError(); + + // don't log if not found + if (e != ERROR_ENVVAR_NOT_FOUND) { + log::error( + "failed to get environment variable '{}', {}", + name, formatSystemMessage(e)); + } + + return {}; + } + + return QString::fromWCharArray(buffer.get(), realSize); } QString set(const QString& n, const QString& v) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2474fdc0..0181a335 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5295,43 +5295,42 @@ void MainWindow::addAsExecutable() return; } - using FileExecutionTypes = OrganizerCore::FileExecutionTypes; + const QFileInfo target(m_ContextItem->data(0, Qt::UserRole).toString()); + const auto fec = spawn::getFileExecutionContext(this, target); - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; - - if (!OrganizerCore::getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { - return; - } - - switch (type) + switch (fec.type) { - case FileExecutionTypes::Executable: { - QString name = QInputDialog::getText(this, tr("Enter Name"), - tr("Please enter a name for the executable"), QLineEdit::Normal, - targetInfo.completeBaseName()); - - if (!name.isEmpty()) { - //Note: If this already exists, you'll lose custom settings - m_OrganizerCore.executablesList()->setExecutable(Executable() - .title(name) - .binaryInfo(binaryInfo) - .arguments(arguments) - .workingDirectory(targetInfo.absolutePath())); - - refreshExecutablesList(); - } - - break; + case spawn::FileExecutionTypes::Executable: + { + const QString name = QInputDialog::getText( + this, tr("Enter Name"), + tr("Enter a name for the executable"), + QLineEdit::Normal, + target.completeBaseName()); + + if (!name.isEmpty()) { + //Note: If this already exists, you'll lose custom settings + m_OrganizerCore.executablesList()->setExecutable(Executable() + .title(name) + .binaryInfo(fec.binary) + .arguments(fec.arguments) + .workingDirectory(target.absolutePath())); + + refreshExecutablesList(); } - case FileExecutionTypes::Other: // fall-through - default: { - QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); - break; - } + break; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + QMessageBox::information( + this, tr("Not an executable"), + tr("This is not a recognized executable.")); + + break; + } } } @@ -5458,7 +5457,8 @@ void MainWindow::openDataFile() return; } - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); m_OrganizerCore.runFile(this, targetInfo); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 6bf2c290..78f9517c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -976,76 +976,6 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res; } -QString OrganizerCore::findJavaInstallation(const QString& jarFile) -{ - if (!jarFile.isEmpty()) { - // try to find java automatically based on the given jar file - std::wstring jarFileW = jarFile.toStdWString(); - - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(buffer, &binaryType)) { - log::debug( - "failed to determine binary type of \"{}\": {}", - QString::fromWCharArray(buffer), ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) { - return QString::fromWCharArray(buffer); - } - } - } - - // second attempt: look to the registry - QSettings reg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); - if (reg.contains("CurrentVersion")) { - QString currentVersion = reg.value("CurrentVersion").toString(); - return reg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); - } - - // not found - return {}; -} - -bool OrganizerCore::getFileExecutionContext( - QWidget* parent, const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type) -{ - QString extension = targetInfo.suffix(); - if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) || - (extension.compare("com", Qt::CaseInsensitive) == 0) || - (extension.compare("bat", Qt::CaseInsensitive) == 0)) { - binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); - arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - type = FileExecutionTypes::Executable; - return true; - } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { - binaryInfo = targetInfo; - type = FileExecutionTypes::Executable; - return true; - } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { - auto java = findJavaInstallation(targetInfo.absoluteFilePath()); - - if (java.isEmpty()) { - java = QFileDialog::getOpenFileName( - parent, QObject::tr("Select binary"), - QString(), QObject::tr("Binary") + " (*.exe)"); - } - - if (java.isEmpty()) { - return false; - } - - binaryInfo = QFileInfo(java); - arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - type = FileExecutionTypes::Executable; - - return true; - } else { - type = FileExecutionTypes::Other; - return true; - } -} - bool OrganizerCore::previewFileWithAlternatives( QWidget* parent, QString fileName, int selectedOrigin) { @@ -1177,35 +1107,23 @@ bool OrganizerCore::previewFile( bool OrganizerCore::runFile( QWidget* parent, const QFileInfo& targetInfo) { - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { - return false; - } - - switch (type) + switch (fec.type) { - case FileExecutionTypes::Executable: { - runExecutableFile( - binaryInfo, arguments, currentProfile()->name(), - targetInfo.absolutePath()); - + case spawn::FileExecutionTypes::Executable: + { + runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); return true; } - case FileExecutionTypes::Other: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - - return true; + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + const auto r = shell::Open(targetInfo.absoluteFilePath()); + return r.success(); } } - - // nop - return false; } bool OrganizerCore::runExecutableFile( @@ -1281,6 +1199,87 @@ bool OrganizerCore::runShortcut(const MOShortcut& shortcut) 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 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 ¤tDirectory, const QString &steamAppID, @@ -1362,87 +1361,6 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -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 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); -} - bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) { if (!Settings::instance().interface().lockGUI()) diff --git a/src/organizercore.h b/src/organizercore.h index fa05a20f..f802c8cb 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -91,12 +91,6 @@ private: typedef boost::signals2::signal SignalModInstalled; public: - enum class FileExecutionTypes - { - Executable = 1, - Other = 2 - }; - static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } OrganizerCore(Settings &settings); @@ -152,12 +146,6 @@ public: void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } void loggedInAction(QWidget* parent, std::function f); - static QString findJavaInstallation(const QString& jarFile={}); - - static bool getFileExecutionContext( - QWidget* parent, const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); - bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); diff --git a/src/spawn.cpp b/src/spawn.cpp index 3c7d64ce..dd93bfaa 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -901,11 +901,11 @@ SpawnedProcess Spawner::spawn( return {INVALID_HANDLE_VALUE, sp}; } - if (!spawn::checkEnvironment(parent, sp)) { + if (!checkEnvironment(parent, sp)) { return {INVALID_HANDLE_VALUE, sp}; } - if (!spawn::checkBlacklist(parent, sp, settings)) { + if (!checkBlacklist(parent, sp, settings)) { return {INVALID_HANDLE_VALUE, sp}; } @@ -914,6 +914,185 @@ SpawnedProcess Spawner::spawn( return {startBinary(parent, sp), sp}; } + +QString getExecutableForJarFile(const QString& jarFile) +{ + const std::wstring jarFileW = jarFile.toStdWString(); + + WCHAR buffer[MAX_PATH]; + + const auto hinst = ::FindExecutableW(jarFileW.c_str(), nullptr, buffer); + const auto r = static_cast(reinterpret_cast(hinst)); + + // anything <= 32 signals failure + if (r <= 32) { + log::warn( + "failed to find executable associated with file '{}', {}", + jarFile, shell::formatError(r)); + + return {}; + } + + DWORD binaryType = 0; + + if (!::GetBinaryTypeW(buffer, &binaryType)) { + const auto e = ::GetLastError(); + + log::warn( + "failed to determine binary type of '{}', {}", + QString::fromWCharArray(buffer), formatSystemMessage(e)); + + return {}; + } + + if (binaryType != SCS_32BIT_BINARY && binaryType != SCS_64BIT_BINARY) { + log::warn( + "unexpected binary type {} for file '{}'", + binaryType, QString::fromWCharArray(buffer)); + + return {}; + } + + return QString::fromWCharArray(buffer); +} + +QString getJavaHome() +{ + const QString key = "HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment"; + const QString value = "CurrentVersion"; + + QSettings reg(key, QSettings::NativeFormat); + + if (!reg.contains(value)) { + log::warn("key '{}\\{}' doesn't exist", key, value); + return {}; + } + + const QString currentVersion = reg.value("CurrentVersion").toString(); + const QString javaHome = QString("%1/JavaHome").arg(currentVersion); + + if (!reg.contains(javaHome)) { + log::warn( + "java version '{}' was found at '{}\\{}', but '{}\\{}' doesn't exist", + currentVersion, key, value, key, javaHome); + + return {}; + } + + const auto path = reg.value(javaHome).toString(); + return path + "\\bin\\javaw.exe"; +} + +QString findJavaInstallation(const QString& jarFile) +{ + // try to find java automatically based on the given jar file + if (!jarFile.isEmpty()) { + const auto s = getExecutableForJarFile(jarFile); + if (!s.isEmpty()) { + return s; + } + } + + // second attempt: look to the registry + const auto s = getJavaHome(); + if (!s.isEmpty()) { + return s; + } + + // not found + return {}; +} + +bool isBatchFile(const QFileInfo& target) +{ + const auto batchExtensions = {"cmd", "bat"}; + + const QString extension = target.suffix(); + for (auto&& e : batchExtensions) { + if (extension.compare(e, Qt::CaseInsensitive) == 0) { + return true; + } + } + + return false; +} + +bool isExeFile(const QFileInfo& target) +{ + return (target.suffix().compare("exe", Qt::CaseInsensitive) == 0); +} + +bool isJavaFile(const QFileInfo& target) +{ + return (target.suffix().compare("jar", Qt::CaseInsensitive) == 0); +} + +QFileInfo getCmdPath() +{ + const auto p = env::get("COMSPEC2"); + if (!p.isEmpty()) { + return p; + } + + QString systemDirectory; + + const std::size_t buffer_size = 1000; + wchar_t buffer[buffer_size + 1] = {}; + + const auto length = ::GetSystemDirectoryW(buffer, buffer_size); + if (length != 0) { + systemDirectory = QString::fromWCharArray(buffer, length); + + if (!systemDirectory.endsWith("\\")) { + systemDirectory += "\\"; + } + } else { + systemDirectory = "C:\\Windows\\System32\\"; + } + + return systemDirectory + "cmd.exe"; +} + +FileExecutionContext getFileExecutionContext( + QWidget* parent, const QFileInfo& target) +{ + if (isExeFile(target)) { + return { + target, + "", + FileExecutionTypes::Executable + }; + } + + if (isBatchFile(target)) { + return { + getCmdPath(), + QString("/C \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable + }; + } + + if (isJavaFile(target)) { + auto java = findJavaInstallation(target.absoluteFilePath()); + + if (java.isEmpty()) { + java = QFileDialog::getOpenFileName( + parent, QObject::tr("Select binary"), + QString(), QObject::tr("Binary") + " (*.exe)"); + } + + if (!java.isEmpty()) { + return { + QFileInfo(java), + QString("-jar \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable + }; + } + } + + return {{}, {}, FileExecutionTypes::Other}; +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index d2853cd5..866e1795 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -103,6 +103,25 @@ public: private: }; + +enum class FileExecutionTypes +{ + Executable = 1, + Other +}; + +struct FileExecutionContext +{ + QFileInfo binary; + QString arguments; + FileExecutionTypes type; +}; + +QString findJavaInstallation(const QString& jarFile); + +FileExecutionContext getFileExecutionContext( + QWidget* parent, const QFileInfo& target); + } // namespace -- cgit v1.3.1 From bff5a22f48b933fe9eba3a15497882ddf2a03990 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 07:11:08 -0400 Subject: spawning an executable now only waits for that particular process added waitForAllUSVFSProcesses() to OrganizerCore, used when closing MO --- src/envmodule.cpp | 73 ++++++++++++++++++++++--- src/envmodule.h | 3 ++ src/mainwindow.cpp | 14 ++--- src/organizercore.cpp | 145 ++++++++++++++++++++++++++------------------------ src/organizercore.h | 11 +++- src/spawn.cpp | 63 ++++++++++++++++++++++ src/spawn.h | 14 +++++ 7 files changed, 233 insertions(+), 90 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 3f1f8912..abbe02e5 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -408,7 +408,8 @@ std::vector getLoadedModules() } -std::vector getRunningProcesses() +template +void forEachRunningProcess(F&& f) { HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); @@ -416,7 +417,7 @@ std::vector getRunningProcesses() { const auto e = GetLastError(); log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); - return {}; + return; } PROCESSENTRY32 entry = {}; @@ -427,16 +428,14 @@ std::vector getRunningProcesses() if (!Process32First(snapshot.get(), &entry)) { const auto e = GetLastError(); log::error("Process32First() failed, {}", formatSystemMessage(e)); - return {}; + return; } - std::vector v; - for (;;) { - v.push_back(Process( - entry.th32ProcessID, - QString::fromStdWString(entry.szExeFile))); + if (!f(entry)) { + break; + } // next process if (!Process32Next(snapshot.get(), &entry)) @@ -450,8 +449,66 @@ std::vector getRunningProcesses() break; } } +} + +std::vector getRunningProcesses() +{ + std::vector v; + + forEachRunningProcess([&](auto&& entry) { + v.push_back(Process( + entry.th32ProcessID, + QString::fromStdWString(entry.szExeFile))); + + return true; + }); return v; } +QString getProcessName(HANDLE process) +{ + const QString badName = "unknown"; + + if (process == 0 || process == INVALID_HANDLE_VALUE) { + return badName; + } + + const DWORD bufferSize = MAX_PATH; + wchar_t buffer[bufferSize + 1] = {}; + + const auto realSize = ::GetProcessImageFileNameW(process, buffer, bufferSize); + + if (realSize == 0) { + const auto e = ::GetLastError(); + log::error("GetProcessImageFileNameW() failed, {}", formatSystemMessage(e)); + return badName; + } + + auto s = QString::fromWCharArray(buffer, realSize); + + const auto lastSlash = s.lastIndexOf("\\"); + if (lastSlash != -1) { + s = s.mid(lastSlash + 1); + } + + return s; +} + +DWORD getProcessParentID(DWORD pid) +{ + DWORD ppid = 0; + + forEachRunningProcess([&](auto&& entry) { + if (entry.th32ProcessID == pid) { + ppid = entry.th32ParentProcessID; + return false; + } + + return true; + }); + + return ppid; +} + } // namespace diff --git a/src/envmodule.h b/src/envmodule.h index deb7520f..212f6f7b 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -120,6 +120,9 @@ private: std::vector getRunningProcesses(); std::vector getLoadedModules(); +QString getProcessName(HANDLE process); +DWORD getProcessParentID(DWORD pid); + } // namespace env #endif // ENV_MODULE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0181a335..bbb63333 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1311,16 +1311,10 @@ bool MainWindow::canExit() } } - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); - if (injected_process_still_running != INVALID_HANDLE_VALUE) - { - m_exitAfterWait = true; - m_OrganizerCore.waitForApplication(injected_process_still_running); - if (!m_exitAfterWait) { // if operation cancelled - return false; - } + m_exitAfterWait = true; + m_OrganizerCore.waitForAllUSVFSProcessesWithLock(); + if (!m_exitAfterWait) { // if operation cancelled + return false; } setCursor(Qt::WaitCursor); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d3f4a83c..2a228fda 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -34,6 +34,7 @@ #include "instancemanager.h" #include #include "previewdialog.h" +#include "envmodule.h" #include #include @@ -74,47 +75,6 @@ using namespace MOBase; //static CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; -static std::wstring getProcessName(HANDLE process) -{ - wchar_t buffer[MAX_PATH]; - const wchar_t *fileName = L"unknown"; - - if (process == nullptr) return fileName; - - if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { - fileName = wcsrchr(buffer, L'\\'); - if (fileName == nullptr) { - fileName = buffer; - } - else { - fileName += 1; - } - } - - return fileName; -} - -// Get parent PID for the given process, return 0 on failure -static DWORD getProcessParentID(DWORD pid) -{ - HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 pe = { 0 }; - pe.dwSize = sizeof(PROCESSENTRY32); - - DWORD res = 0; - if (Process32First(th, &pe)) - do { - if (pe.th32ProcessID == pid) { - res = pe.th32ParentProcessID; - break; - } - } while (Process32Next(th, &pe)); - - CloseHandle(th); - - return res; -} - template QStringList toStringList(InputIterator current, InputIterator end) { @@ -1348,35 +1308,46 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -bool OrganizerCore::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) +void OrganizerCore::withLock(std::function f) { - if (Settings::instance().interface().lockGUI()) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; + std::unique_ptr 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) { - 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(); - } + m_MainWindow->unlock(); + } }); - 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; - waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); cycleDiagnostics(); - } + }); - return handle; + return r; } bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) @@ -1393,30 +1364,64 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) if (m_MainWindow != nullptr) { m_MainWindow->unlock(); } }); + return waitForProcessCompletion(handle, exitCode, uilock); } -bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +bool OrganizerCore::waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto r = spawn::waitForProcess(handle, exitCode, uilock); + + switch (r) + { + case spawn::WaitResults::Completed: // fall-through + case spawn::WaitResults::Unlocked: + return true; + + case spawn::WaitResults::Error: // fall-through + default: + return false; + } +} + +bool OrganizerCore::waitForAllUSVFSProcessesWithLock() { + bool r = false; + + withLock([&](auto* uilock) { + r = waitForAllUSVFSProcesses(uilock); + }); + + return r; +} + +bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + // Certain process names we wish to "hide" for aesthetic reason: + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); + bool originalHandle = true; bool newHandle = true; bool uiunlocked = false; + HANDLE handle = findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); + DWORD* exitCode = nullptr; DWORD currentPID = 0; QString processName; + auto waitForChildUntil = GetTickCount64(); if (handle != INVALID_HANDLE_VALUE) { currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); + processName = env::getProcessName(handle); } - // Certain process names we wish to "hide" for aesthetic reason: bool waitingOnHidden = false; - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); for (QString hide : hiddenList) if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; + // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes. // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden" @@ -1489,7 +1494,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL newHandle = handle != INVALID_HANDLE_VALUE; if (newHandle) { currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); + processName = env::getProcessName(handle); for (QString hide : hiddenList) if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; @@ -1541,13 +1546,13 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde continue; } - QString pname = QString::fromStdWString(getProcessName(handle)); + QString pname = env::getProcessName(handle); bool phidden = false; for (auto hide : hiddenList) if (pname.contains(hide, Qt::CaseInsensitive)) phidden = true; - bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid; + bool pprefered = preferedParentPid && env::getProcessParentID(pids[i]) == preferedParentPid; if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { if (best_match != INVALID_HANDLE_VALUE) diff --git a/src/organizercore.h b/src/organizercore.h index 3d3c7325..ffdb6830 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -167,6 +167,8 @@ public: 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); @@ -221,8 +223,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); bool onModInstalled(const std::function &func); bool onAboutToRun(const std::function &func); bool onFinishedRun(const std::function &func); @@ -311,6 +311,13 @@ private: bool waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); + + void withLock(std::function f); + + HANDLE findAndOpenAUSVFSProcess( + const std::vector& hiddenList, DWORD preferedParentPid); + private slots: void directory_refreshed(); diff --git a/src/spawn.cpp b/src/spawn.cpp index fe1e9e3e..f0b3b2c7 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "settingsdialogworkarounds.h" #include +#include #include #include #include @@ -1093,6 +1094,68 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } +WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + const DWORD pid = ::GetProcessId(handle); + const QString processName = QString("%1 (%2)") + .arg(env::getProcessName(handle)) + .arg(pid); + + if (uilock) + uilock->setProcessName(processName); + + constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; + DWORD res = WAIT_TIMEOUT; + + log::debug( + "waiting for process completion '{}' ({})", + processName, pid); + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion '{}' ({}), {}", + processName, pid, formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res == WAIT_OBJECT_0) { + // completed + log::debug("process '{}' ({}) completed", processName, pid); + + if (exitCode) { + if (!::GetExitCodeProcess(handle, exitCode)) { + const auto e = ::GetLastError(); + log::warn( + "failed to get exit code of process '{}' ({}): {}", + processName, pid, formatSystemMessage(e)); + } + } + + return WaitResults::Completed; + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); + + if (uilock && uilock->unlockForced()) { + log::debug("waiting for process '{}' ({}) aborted by UI", processName, pid); + return WaitResults::Unlocked; + } + } +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index 866e1795..441cad2c 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,6 +27,8 @@ along with Mod Organizer. If not, see . #include class Settings; +class ILockedWaitingForProcess; + namespace MOBase { class IPluginGame; } namespace spawn @@ -84,6 +86,7 @@ public: ~SpawnedProcess(); HANDLE releaseHandle(); + void wait(); private: HANDLE m_handle; @@ -122,6 +125,17 @@ QString findJavaInstallation(const QString& jarFile); FileExecutionContext getFileExecutionContext( QWidget* parent, const QFileInfo& target); + +enum class WaitResults +{ + Completed = 1, + Error, + Unlocked +}; + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock); + } // namespace -- cgit v1.3.1 From 4d269c2e1a625e6d50b7e6272b4f474a921c6bfa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 11:34:06 -0400 Subject: split to processrunner added IUserInterface::qtWidget() put back IUserInterface in OrganizerCore now that there's a way to get the widget --- src/CMakeLists.txt | 3 + src/iuserinterface.h | 2 + src/main.cpp | 4 +- src/mainwindow.cpp | 13 +- src/mainwindow.h | 5 +- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 601 ++++++++------------------------------ src/organizercore.h | 60 +--- src/organizerproxy.cpp | 4 +- src/processrunner.cpp | 634 +++++++++++++++++++++++++++++++++++++++++ src/processrunner.h | 104 +++++++ src/spawn.cpp | 198 ------------- src/spawn.h | 48 ---- 13 files changed, 892 insertions(+), 786 deletions(-) create mode 100644 src/processrunner.cpp create mode 100644 src/processrunner.h (limited to 'src/mainwindow.cpp') 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& processes) -{ - if (processes.empty()) { - return nullptr; - } - - // Certain process names we wish to "hide" for aesthetic reason: - const std::vector 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 ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &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 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 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 ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &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 f) -{ - std::unique_ptr 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 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 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 &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(QMaparchivesWriter().writeImmediately(false); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().writeImmediately(false); } std::vector 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 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& 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 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 #include #include @@ -26,7 +26,7 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; -class MainWindow; +class IUserInterface; namespace MOBase { template 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& 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 ¤tDirectory, const QString &steamAppID={}, - const QString &customOverwrite={}, - const QList &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 ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries = QList(), - 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 f); - - HANDLE findAndOpenAUSVFSProcess( - const std::vector& 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 &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 + +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& processes) +{ + if (processes.empty()) { + return nullptr; + } + + // Certain process names we wish to "hide" for aesthetic reason: + const std::vector 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 ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &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 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 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 ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &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 f) +{ + std::unique_ptr 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 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 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 progress) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + + std::vector handles; + handles.push_back(handle); + + std::vector 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& handles, std::vector& exitCodes, + std::function progress) +{ + if (handles.empty()) { + return WaitResults::Completed; + } + + const auto WAIT_OBJECT_N = static_cast(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(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 + +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 ¤tDirectory, const QString &steamAppID={}, + const QString &customOverwrite={}, + const QList &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 ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries={}, + LPDWORD exitCode = nullptr); + + SpawnedProcess spawn(spawn::SpawnParameters sp); + + void withLock(std::function 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 progress); + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function 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 progress) -{ - if (handle == INVALID_HANDLE_VALUE) { - return WaitResults::Error; - } - - log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); - - std::vector handles; - handles.push_back(handle); - - std::vector 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& handles, std::vector& exitCodes, - std::function progress) -{ - if (handles.empty()) { - return WaitResults::Completed; - } - - const auto WAIT_OBJECT_N = static_cast(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(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 progress); - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress); - } // namespace -- cgit v1.3.1 From db0b92776b5c9a34ebb1a5ce5c3f0b105844ee16 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 30 Oct 2019 23:42:59 -0400 Subject: 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 --- src/CMakeLists.txt | 3 + src/envmodule.cpp | 1 - src/iuserinterface.h | 6 +- src/lockwidget.cpp | 224 ++++++++++++++++ src/lockwidget.h | 68 +++++ src/mainwindow.cpp | 44 +--- src/mainwindow.h | 7 - src/organizercore.cpp | 8 +- src/organizercore.h | 3 +- src/organizerproxy.cpp | 19 +- src/pch.h | 47 ++-- src/processrunner.cpp | 687 ++++++++++++++++++++++++++++--------------------- src/processrunner.h | 70 ++++- 13 files changed, 806 insertions(+), 381 deletions(-) create mode 100644 src/lockwidget.cpp create mode 100644 src/lockwidget.h (limited to 'src/mainwindow.cpp') 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 #include #include - #include + 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 +#include +#include + +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(m_parent)) { + disable(mw->centralWidget()); + disable(mw->menuBar()); + disable(mw->statusBar()); + } + + for (auto* tb : m_parent->findChildren()) { + disable(tb); + } + + for (auto* d : m_parent->findChildren()) { + 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 + +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 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 m_overlay; + QLabel* m_info; + Results m_result; + std::unique_ptr m_filter; + std::vector 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 . #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 . //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 &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 #include #include +#include #include #include -#include #include #include #include @@ -111,13 +111,14 @@ #include #include #include -#include +#include #include +#include #include +#include #include #include #include -#include #include #include #include @@ -126,20 +127,21 @@ #include #include #include -#include #include +#include #include #include #include #include #include #include -#include #include #include +#include #include #include #include +#include #include #include #include @@ -190,53 +192,42 @@ #include #include #include -#include #include #include +#include #include #include -#include #include +#include +#include #include #include #include #include -#include #include #include #include +#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include +#include #include #include #include #include #include #include -#include -#include #include #include #include #include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include @@ -255,4 +246,16 @@ #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include 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 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 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 findInterestingProcessInTrees( std::pair getInterestingProcess( const std::vector& initialProcesses) { + if (initialProcesses.empty()) { + log::debug("nothing to wait for"); + return {{}, Interest::None}; + } + std::vector processes; log::debug("getting process tree for {} processes", initialProcesses.size()); @@ -143,7 +169,7 @@ std::pair 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 getInterestingProcess( const std::chrono::milliseconds Infinite(-1); -WaitResults timedWait( - HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock, - std::chrono::milliseconds wait) +std::optional 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(lock.result()), pid); + + return ProcessRunner::Error; + } } if (wait != Infinite) { const auto now = high_resolution_clock::now(); if (duration_cast(now - start) >= wait) { - return WaitResults::StillRunning; + return {}; } } } } -WaitResults waitForProcesses( - const std::vector& initialProcesses, - LPDWORD exitCode, ILockedWaitingForProcess* uilock) +ProcessRunner::Results waitForProcesses( + const std::vector& 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 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 ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &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 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 forcedLibraries; + //QFileInfo binary; + //QString arguments = args.join(" "); + //QString currentDirectory = cwd; + //QString steamAppID; + //QString customOverwrite; + //QList 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 ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &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 ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + bool refresh) +{ + setBinary(binary); + setArguments(arguments); + setCurrentDirectory(currentDirectory); + setSteamID(steamAppID); + setCustomOverwrite(customOverwrite); + setForcedLibraries(forcedLibraries); + setWaitForCompletion(LockWidget::LockUI, refresh); + + const auto r = run(); + return (r != Error); +} - if (!checkBlacklist(parent, sp, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } +bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +{ + setFromExecutable(exe); + setWaitForCompletion(LockWidget::LockUI, refresh); + + const auto r = run(); + return (r != Error); +} - adjustForVirtualized(game, sp, settings); +bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +{ + setFromShortcut(shortcut); + setWaitForCompletion(LockWidget::LockUI, false); - return {startBinary(parent, sp), sp}; + const auto r = run(); + return (r != Error); } -void ProcessRunner::withLock(std::function f) +HANDLE ProcessRunner::runExecutableOrExecutableFile( + const QString& executable, const QStringList &args, const QString &cwd, + const QString& profileOverride, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; + setFromFileOrExecutable( + executable, args, cwd, profileOverride, forcedCustomOverwrite, + ignoreCustomOverwrite); - 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(); - } + setWaitForCompletion(LockWidget::LockUI, true); - Guard g([&]() { - if (m_ui != nullptr) { - m_ui->unlock(); - } - }); + run(); + return m_handle; +} - f(uilock); +void ProcessRunner::withLock( + LockWidget::Reasons reason, std::function f) +{ + auto lock = std::make_unique( + m_ui ? m_ui->qtWidget() : nullptr, reason); + + f(*lock); } -bool ProcessRunner::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) +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 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; + + 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 f); + void withLock( + LockWidget::Reasons reason, std::function 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 -- cgit v1.3.1 From 0cea4833eb48400feb652e883c70d8a2907701c3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 01:47:08 -0400 Subject: explicit refresh parameter for setWaitForCompletion(), some parts of the ui will crash if things refresh unexpectedly removed runFile() fixed crash when unlocking if some widgets were destroyed in the meantime lock widget will now pick the active window and disable all top levels --- src/lockwidget.cpp | 73 +++++++++++++++++++++++++++--------------- src/lockwidget.h | 2 +- src/mainwindow.cpp | 6 +++- src/modinfodialogconflicts.cpp | 6 +++- src/processrunner.cpp | 25 +++++---------- src/processrunner.h | 13 +++++--- 6 files changed, 75 insertions(+), 50 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 720cac36..ad9aefe3 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -59,17 +59,22 @@ LockWidget::Results LockWidget::result() const void LockWidget::createUi(Reasons reason) { - if (m_parent) { - m_overlay.reset(createTransparentWidget(m_parent)); + QWidget* overlayTarget = m_parent; + if (auto* w = qApp->activeWindow()) { + overlayTarget = w; + } + + if (overlayTarget) { + m_overlay.reset(createTransparentWidget(overlayTarget)); m_overlay->setWindowFlags(m_overlay->windowFlags() & Qt::FramelessWindowHint); - m_overlay->setGeometry(m_parent->rect()); + m_overlay->setGeometry(overlayTarget->rect()); } else { m_overlay.reset(new QDialog); } auto* center = new QFrame; - if (m_parent) { + if (overlayTarget) { center->setFrameStyle(QFrame::StyledPanel); center->setLineWidth(1); center->setAutoFillBackground(true); @@ -86,7 +91,7 @@ void LockWidget::createUi(Reasons reason) auto* ly = new QVBoxLayout(center); - if (!m_parent) { + if (!overlayTarget) { ly->setContentsMargins(0, 0, 0, 0); } @@ -115,7 +120,7 @@ void LockWidget::createUi(Reasons reason) case OutputRequired: { message->setText(QObject::tr( - "The executable must run to completion because a its output is " + "The executable must run to completion because its output is " "required.")); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); @@ -149,7 +154,7 @@ void LockWidget::createUi(Reasons reason) grid->addWidget(createTransparentWidget(), 1, 2); grid->addWidget(center, 1, 1); - if (!m_parent) { + if (!overlayTarget) { grid->setContentsMargins(0, 0, 0, 0); } @@ -160,10 +165,10 @@ void LockWidget::createUi(Reasons reason) disableAll(); - if (m_parent) { + if (overlayTarget) { m_filter.reset(new Filter); - m_filter->resized = [=]{ m_overlay->setGeometry(m_parent->rect()); }; - m_parent->installEventFilter(m_filter.get()); + m_filter->resized = [=]{ m_overlay->setGeometry(overlayTarget->rect()); }; + overlayTarget->installEventFilter(m_filter.get()); } m_overlay->setFocusPolicy(Qt::TabFocus); @@ -184,32 +189,48 @@ void LockWidget::onCancel() unlock(); } +template +QList findChildrenImmediate(QWidget* parent) +{ + return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); +} + void LockWidget::disableAll() { - if (!m_parent) { - // nothing to disable without a main window - return; - } + const auto topLevels = QApplication::topLevelWidgets(); - if (auto* mw=dynamic_cast(m_parent)) { - disable(mw->centralWidget()); - disable(mw->menuBar()); - disable(mw->statusBar()); - } + for (auto* w : topLevels) { + if (auto* mw=dynamic_cast(w)) { + disable(mw->centralWidget()); + disable(mw->menuBar()); + disable(mw->statusBar()); - for (auto* tb : m_parent->findChildren()) { - disable(tb); - } + for (auto* tb : findChildrenImmediate(w)) { + disable(tb); + } + + for (auto* d : findChildrenImmediate(w)) { + disable(d); + } + } - for (auto* d : m_parent->findChildren()) { - disable(d); + if (auto* d=dynamic_cast(w)) { + // no central widget, just disable the children, except for the overlay + for (auto* child : findChildrenImmediate(d)) { + if (child != m_overlay.get()) { + disable(child); + } + } + } } } void LockWidget::enableAll() { - for (auto* w : m_disabled) { - w->setEnabled(true); + for (auto w : m_disabled) { + if (w) { + w->setEnabled(true); + } } m_disabled.clear(); diff --git a/src/lockwidget.h b/src/lockwidget.h index bfb0b30f..9c555ac9 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -55,7 +55,7 @@ private: QLabel* m_info; Results m_result; std::unique_ptr m_filter; - std::vector m_disabled; + std::vector> m_disabled; void createUi(Reasons reason); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7d3d20b3..63f6c680 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5422,7 +5422,11 @@ void MainWindow::openDataFile() const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); - m_OrganizerCore.processRunner().runFile(this, targetInfo); + + m_OrganizerCore.processRunner() + .setFromFile(this, targetInfo) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 758112cc..8aefd4c6 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,11 @@ 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().processRunner().runFile(parentWidget(), item->fileName()); + core().processRunner() + .setFromFile(parentWidget(), item->fileName()) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + return true; }); } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 62c77efc..1d9da96b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -368,7 +368,7 @@ void SpawnedProcess::destroy() ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(false), + m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(NoRefresh), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { m_sp.hooked = true; @@ -417,10 +417,10 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - LockWidget::Reasons reason, bool refresh) + RefreshModes refresh, LockWidget::Reasons reason) { - m_lock = reason; m_refresh = refresh; + m_lock = reason; return *this; } @@ -655,7 +655,7 @@ ProcessRunner::Results ProcessRunner::run() const auto r = waitForProcessCompletionWithLock( m_handle, &m_exitCode, m_lock); - if (r == Completed && m_refresh) { + if (r == Completed && m_refresh == Refresh) { m_core.afterRun(m_sp.binary, m_exitCode); } @@ -669,15 +669,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) -{ - setFromFile(parent, targetInfo); - setWaitForCompletion(LockWidget::LockUI, true); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, @@ -691,7 +682,7 @@ bool ProcessRunner::runExecutableFile( setSteamID(steamAppID); setCustomOverwrite(customOverwrite); setForcedLibraries(forcedLibraries); - setWaitForCompletion(LockWidget::LockUI, refresh); + setWaitForCompletion(refresh ? Refresh : NoRefresh); const auto r = run(); return (r != Error); @@ -700,7 +691,7 @@ bool ProcessRunner::runExecutableFile( bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) { setFromExecutable(exe); - setWaitForCompletion(LockWidget::LockUI, refresh); + setWaitForCompletion(refresh ? Refresh : NoRefresh); const auto r = run(); return (r != Error); @@ -709,7 +700,7 @@ bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) bool ProcessRunner::runShortcut(const MOShortcut& shortcut) { setFromShortcut(shortcut); - setWaitForCompletion(LockWidget::LockUI, false); + setWaitForCompletion(NoRefresh); const auto r = run(); return (r != Error); @@ -724,7 +715,7 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile( executable, args, cwd, profileOverride, forcedCustomOverwrite, ignoreCustomOverwrite); - setWaitForCompletion(LockWidget::LockUI, true); + setWaitForCompletion(Refresh); run(); return m_handle; diff --git a/src/processrunner.h b/src/processrunner.h index b7895903..21840bfe 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -44,6 +44,12 @@ public: ForceUnlocked }; + enum RefreshModes + { + NoRefresh = 1, + Refresh + }; + using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); @@ -55,7 +61,8 @@ public: ProcessRunner& setCustomOverwrite(const QString& customOverwrite); ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); - ProcessRunner& setWaitForCompletion(LockWidget::Reasons reason, bool refresh); + ProcessRunner& setWaitForCompletion( + RefreshModes refresh, LockWidget::Reasons reason=LockWidget::LockUI); ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); @@ -73,8 +80,6 @@ public: DWORD exitCode(); - bool runFile(QWidget* parent, const QFileInfo& targetInfo); - bool runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID={}, @@ -108,7 +113,7 @@ private: ForcedLibraries m_forcedLibraries; QString m_profileName; LockWidget::Reasons m_lock; - bool m_refresh; + RefreshModes m_refresh; QString m_shellOpen; HANDLE m_handle; DWORD m_exitCode; -- cgit v1.3.1 From c8e101e19eed4417d42bef678cd60d3efb414eb7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 01:54:49 -0400 Subject: removed runExecutableFile() turns out on_startButton_clicked() had redundant code --- src/mainwindow.cpp | 43 ++++++++++--------------------------------- src/processrunner.cpp | 26 -------------------------- src/processrunner.h | 7 ------- 3 files changed, 10 insertions(+), 66 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 63f6c680..c1789b0a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2312,41 +2312,18 @@ void MainWindow::installMod(QString fileName) void MainWindow::on_startButton_clicked() { - try { - const Executable* selectedExecutable = getSelectedExecutable(); - if (!selectedExecutable) { - return; - } - - ui->startButton->setEnabled(false); - - auto* profile = m_OrganizerCore.currentProfile(); - - const QString customOverwrite = profile->setting( - "custom_overwrites", selectedExecutable->title()).toString(); - - auto forcedLibraries = profile->determineForcedLibraries( - selectedExecutable->title()); - - if (!profile->forcedLibrariesEnabled(selectedExecutable->title())) { - forcedLibraries.clear(); - } - - m_OrganizerCore.processRunner().runExecutableFile( - selectedExecutable->binaryInfo(), - selectedExecutable->arguments(), - selectedExecutable->workingDirectory().length() != 0 ? - selectedExecutable->workingDirectory() : - selectedExecutable->binaryInfo().absolutePath(), - selectedExecutable->steamAppID(), - customOverwrite, - forcedLibraries); - } catch (...) { - ui->startButton->setEnabled(true); - throw; + const Executable* selectedExecutable = getSelectedExecutable(); + if (!selectedExecutable) { + return; } - ui->startButton->setEnabled(true); + ui->startButton->setEnabled(false); + Guard g([&]{ ui->startButton->setEnabled(true); }); + + m_OrganizerCore.processRunner() + .setFromExecutable(*selectedExecutable) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); } bool MainWindow::modifyExecutablesDialog(int selection) diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 1d9da96b..dba29bd2 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -515,13 +515,6 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( setProfileName(profileOverride); - //QFileInfo binary; - //QString arguments = args.join(" "); - //QString currentDirectory = cwd; - //QString steamAppID; - //QString customOverwrite; - //QList forcedLibraries; - if (executable.contains('\\') || executable.contains('/')) { // file path @@ -669,25 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - setBinary(binary); - setArguments(arguments); - setCurrentDirectory(currentDirectory); - setSteamID(steamAppID); - setCustomOverwrite(customOverwrite); - setForcedLibraries(forcedLibraries); - setWaitForCompletion(refresh ? Refresh : NoRefresh); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) { setFromExecutable(exe); diff --git a/src/processrunner.h b/src/processrunner.h index 21840bfe..2e9550e0 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,13 +80,6 @@ public: DWORD exitCode(); - bool runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID={}, - const QString &customOverwrite={}, - const QList &forcedLibraries={}, - bool refresh=true); - bool runExecutable(const Executable& exe, bool refresh=true); bool runShortcut(const MOShortcut& shortcut); -- cgit v1.3.1 From 2c3079c1dc2aaeb84cbe7e4a021f6b3f22c785a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:00:58 -0400 Subject: removed runExecutable() --- src/mainwindow.cpp | 8 ++++++-- src/processrunner.cpp | 9 --------- src/processrunner.h | 2 -- 3 files changed, 6 insertions(+), 13 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c1789b0a..3d32aa16 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1536,8 +1536,12 @@ void MainWindow::startExeAction() } action->setEnabled(false); - m_OrganizerCore.processRunner().runExecutable(*itor); - action->setEnabled(true); + Guard g([&]{ action->setEnabled(true); }); + + m_OrganizerCore.processRunner() + .setFromExecutable(*itor) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); } void MainWindow::activateSelectedProfile() diff --git a/src/processrunner.cpp b/src/processrunner.cpp index dba29bd2..1c8c923b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -662,15 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) -{ - setFromExecutable(exe); - setWaitForCompletion(refresh ? Refresh : NoRefresh); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runShortcut(const MOShortcut& shortcut) { setFromShortcut(shortcut); diff --git a/src/processrunner.h b/src/processrunner.h index 2e9550e0..d3437412 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,8 +80,6 @@ public: DWORD exitCode(); - bool runExecutable(const Executable& exe, bool refresh=true); - bool runShortcut(const MOShortcut& shortcut); HANDLE runExecutableOrExecutableFile( -- cgit v1.3.1 From 9ff8a471aaf9a696610a055c091de39c41f985b7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 04:03:16 -0400 Subject: added waitForAllUSVFSProcesses() wrapper in OrganizerCore, the fact that it's in ProcessRunner is a bit of a hack --- src/mainwindow.cpp | 4 +--- src/organizercore.cpp | 6 ++++++ src/organizercore.h | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3d32aa16..d304f8b7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1310,9 +1310,7 @@ bool MainWindow::canExit() } } - const auto r = m_OrganizerCore.processRunner() - .waitForAllUSVFSProcessesWithLock(LockWidget::PreventExit); - + const auto r = m_OrganizerCore.waitForAllUSVFSProcesses(); if (r == ProcessRunner::Cancelled) { return false; } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 57e24f3b..943750ed 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1796,6 +1796,12 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) m_FinishedRun(binary.absoluteFilePath(), exitCode); } +ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses( + LockWidget::Reasons reason) +{ + return processRunner().waitForAllUSVFSProcessesWithLock(reason); +} + std::vector OrganizerCore::fileMapping(const QString &profileName, const QString &customOverwrite) { diff --git a/src/organizercore.h b/src/organizercore.h index d4882b92..0ba52284 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -143,6 +143,9 @@ public: void afterRun(const QFileInfo& binary, DWORD exitCode); + ProcessRunner::Results waitForAllUSVFSProcesses( + LockWidget::Reasons reason=LockWidget::PreventExit); + void refreshESPList(bool force = false); void refreshBSAList(); -- cgit v1.3.1 From 7ebd4debeef2cfdf268679e7b680021d3dc20687 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:19:14 -0400 Subject: added a ForceWait flag to bypass disabled locking merged a bunch of unnecessary functions in ProcessRunner --- src/main.cpp | 4 +- src/mainwindow.cpp | 2 +- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 2 +- src/organizerproxy.cpp | 6 +- src/processrunner.cpp | 155 ++++++++++++++++++++--------------------- src/processrunner.h | 43 ++++++------ 7 files changed, 107 insertions(+), 107 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/main.cpp b/src/main.cpp index 2b9a2f4d..02347ee3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -634,7 +634,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, try { organizer.processRunner() .setFromShortcut(shortcut) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return 0; @@ -662,7 +662,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, // pass the remaining parameters to the binary organizer.processRunner() .setFromFileOrExecutable(exeName, arguments) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return 0; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d304f8b7..c2d91bcc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5404,7 +5404,7 @@ void MainWindow::openDataFile() m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 8aefd4c6..d37f068c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -529,7 +529,7 @@ void ConflictsTab::openItems(QTreeView* tree) for_each_in_selection(tree, [&](const ConflictItem* item) { core().processRunner() .setFromFile(parentWidget(), item->fileName()) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return true; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 943750ed..96ae84a8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -357,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) if(moshortcut.hasExecutable()) { processRunner() .setFromShortcut(moshortcut) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); } } diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 0b6f8df1..420e2d82 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -128,7 +128,9 @@ HANDLE OrganizerProxy::startApplication( .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) .run(); - return runner.processHandle(); + // the plugin is in charge of closing the handle, unless waitForApplication() + // is called on it + return runner.stealProcessHandle().release(); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const @@ -142,7 +144,7 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const auto runner = m_Proxied->processRunner(); const auto r = runner - .setWaitForCompletion(ProcessRunner::NoRefresh, LockWidget::OutputRequired) + .setWaitForCompletion(ProcessRunner::ForceWait, LockWidget::OutputRequired) .attachToProcess(handle); if (exitCode) { diff --git a/src/processrunner.cpp b/src/processrunner.cpp index b732204c..9cc2ce52 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -368,8 +368,8 @@ void SpawnedProcess::destroy() ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(NoRefresh), - m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) + m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), + m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { m_sp.hooked = true; } @@ -417,10 +417,10 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - RefreshModes refresh, LockWidget::Reasons reason) + WaitFlags flags, LockWidget::Reasons reason) { - m_refresh = refresh; - m_lock = reason; + m_waitFlags = flags; + m_lockReason = reason; return *this; } @@ -593,13 +593,13 @@ ProcessRunner::Results ProcessRunner::run() return Error; } - m_handle = r.stealProcessHandle(); + m_handle.reset(r.stealProcessHandle()); // not all files will return a valid handle even if opening them was // successful, such as inproc handlers (like the photo viewer); in this // case it's impossible to determine the status, so just say it's still // running - if (m_handle == INVALID_HANDLE_VALUE) { + if (m_handle.get() == INVALID_HANDLE_VALUE) { return Running; } } else { @@ -642,8 +642,8 @@ ProcessRunner::Results ProcessRunner::run() adjustForVirtualized(game, m_sp, settings); - m_handle = startBinary(parent, m_sp); - if (m_handle == INVALID_HANDLE_VALUE) { + m_handle.reset(startBinary(parent, m_sp)); + if (m_handle.get() == INVALID_HANDLE_VALUE) { return Error; } } @@ -653,14 +653,46 @@ ProcessRunner::Results ProcessRunner::run() ProcessRunner::Results ProcessRunner::postRun() { - if (m_lock == LockWidget::NoReason) { - return Running; + const bool mustWait = (m_waitFlags & ForceWait); + + if (mustWait && m_lockReason == LockWidget::NoReason) { + // never lock the ui without an escape hatch for the user + log::debug( + "the ForceWait flag is set but the lock reason wasn't, " + "defaulting to LockUI"); + + m_lockReason = LockWidget::LockUI; + } + + if (mustWait) { + if (!Settings::instance().interface().lockGUI()) { + // at least tell the user what's going on + log::debug( + "locking is disabled, but the output of the application is required; " + "overriding this setting and locking the ui"); + } + } else { + // no force wait + + if (m_lockReason == LockWidget::NoReason) { + // no locking requested + return Running; + } + + if (!Settings::instance().interface().lockGUI()) { + // disabling locking is like clicking on unlock immediately + log::debug("not waiting for process because locking is disabled"); + return ForceUnlocked; + } } - const auto r = waitForProcessCompletionWithLock( - m_handle, &m_exitCode, m_lock); + auto r = Error; + + withLock([&](auto& lock) { + r = waitForProcess(m_handle.get(), &m_exitCode, lock); + }); - if (r == Completed && m_refresh == Refresh) { + if (r == Completed && (m_waitFlags & Refresh)) { m_core.afterRun(m_sp.binary, m_exitCode); } @@ -669,101 +701,64 @@ ProcessRunner::Results ProcessRunner::postRun() ProcessRunner::Results ProcessRunner::attachToProcess(HANDLE h) { - m_handle = h; + m_handle.reset(h); return postRun(); } -DWORD ProcessRunner::exitCode() +DWORD ProcessRunner::exitCode() const { return m_exitCode; } -HANDLE ProcessRunner::processHandle() +HANDLE ProcessRunner::getProcessHandle() const { - return m_handle; + return m_handle.get(); } - -void ProcessRunner::withLock( - LockWidget::Reasons reason, std::function f) +env::HandlePtr ProcessRunner::stealProcessHandle() { - auto lock = std::make_unique( - m_ui ? m_ui->qtWidget() : nullptr, reason); - - f(*lock); + return std::move(m_handle); } -ProcessRunner::Results ProcessRunner::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) +ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( + LockWidget::Reasons reason) { + m_lockReason = reason; + if (!Settings::instance().interface().lockGUI()) { - log::debug("not waiting for process because user has disabled locking"); + // disabling locking is like clicking on unlock immediately return ForceUnlocked; } - return waitForApplication(handle, exitCode, reason); -} - -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: - // 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; - // - // 2) waitForProcessCompletionWithLock() above, which has already checked the - // lock setting - auto r = Error; - withLock(reason, [&](auto& lock) { - r = waitForProcess(handle, exitCode, lock); - }); + withLock([&](auto& lock) { + for (;;) { + const auto processes = getRunningUSVFSProcesses(); + if (processes.empty()) { + break; + } - return r; -} + r = waitForProcesses(processes, lock); -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 ForceUnlocked; - } + if (r != Completed) { + // error, cancelled, or unlocked + return; + } - auto r = Error; + // this process is completed, check for others + } - withLock(reason, [&](auto& lock) { - r = waitForAllUSVFSProcesses(lock); + r = Completed; }); return r; } -ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcesses(LockWidget& lock) +void ProcessRunner::withLock(std::function f) { - for (;;) { - const auto processes = getRunningUSVFSProcesses(); - if (processes.empty()) { - break; - } - - const auto r = waitForProcesses(processes, lock); - - if (r != Completed) { - // error, cancelled, or unlocked - return r; - } - - // this process is completed, check for others - } + auto lk = std::make_unique( + m_ui ? m_ui->qtWidget() : nullptr, m_lockReason); - return Completed; + f(*lk); } diff --git a/src/processrunner.h b/src/processrunner.h index 62ec7a9e..41c0f12c 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -3,6 +3,7 @@ #include "spawn.h" #include "lockwidget.h" +#include "envmodule.h" #include class OrganizerCore; @@ -44,16 +45,25 @@ public: ForceUnlocked }; - enum RefreshModes + enum WaitFlag { - NoRefresh = 1, - Refresh + NoFlags = 0x00, + Refresh = 0x01, + ForceWait = 0x02 }; + using WaitFlags = QFlags; + using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); + // move only + ProcessRunner(ProcessRunner&&) = default; + ProcessRunner& operator=(const ProcessRunner&) = delete; + ProcessRunner(const ProcessRunner&) = delete; + ProcessRunner& operator=(ProcessRunner&&) = delete; + ProcessRunner& setBinary(const QFileInfo &binary); ProcessRunner& setArguments(const QString& arguments); ProcessRunner& setCurrentDirectory(const QDir& directory); @@ -62,7 +72,7 @@ public: ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( - RefreshModes refresh, LockWidget::Reasons reason=LockWidget::LockUI); + WaitFlags flags=NoFlags, LockWidget::Reasons reason=LockWidget::LockUI); ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); @@ -79,8 +89,9 @@ public: Results run(); Results attachToProcess(HANDLE h); - DWORD exitCode(); - HANDLE processHandle(); + DWORD exitCode() const; + HANDLE getProcessHandle() const; + env::HandlePtr stealProcessHandle(); Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); @@ -91,24 +102,16 @@ private: QString m_customOverwrite; ForcedLibraries m_forcedLibraries; QString m_profileName; - LockWidget::Reasons m_lock; - RefreshModes m_refresh; + LockWidget::Reasons m_lockReason; + WaitFlags m_waitFlags; QString m_shellOpen; - HANDLE m_handle; + env::HandlePtr m_handle; DWORD m_exitCode; Results postRun(); - - void withLock( - LockWidget::Reasons reason, std::function f); - - Results waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason); - - Results waitForApplication( - HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); - - Results waitForAllUSVFSProcesses(LockWidget& lock); + void withLock(std::function f); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); + #endif // PROCESSRUNNER_H -- cgit v1.3.1 From 719a2f20b4bb6b722097b63df8468d073d67e65f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:40:46 -0400 Subject: refresh ui after running an exe from 1) an external message, and 2) the data tab --- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c2d91bcc..631a31d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5404,7 +5404,7 @@ void MainWindow::openDataFile() m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) - .setWaitForCompletion() + .setWaitForCompletion(ProcessRunner::Refresh) .run(); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 96ae84a8..3c535868 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -357,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) if(moshortcut.hasExecutable()) { processRunner() .setFromShortcut(moshortcut) - .setWaitForCompletion() + .setWaitForCompletion(ProcessRunner::Refresh) .run(); } } -- cgit v1.3.1 From 72dd230cdc60e74446caceb5cfb4c6d32e4f6f68 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:58:41 -0400 Subject: removed unused files fixed handle leak when starting steam --- src/CMakeLists.txt | 17 +----- src/ilockedwaitingforprocess.h | 14 ----- src/iuserinterface.h | 1 - src/lockeddialog.cpp | 71 ------------------------ src/lockeddialog.h | 57 -------------------- src/lockeddialog.ui | 98 --------------------------------- src/lockeddialogbase.cpp | 78 --------------------------- src/lockeddialogbase.h | 64 ---------------------- src/mainwindow.cpp | 1 - src/organizercore.cpp | 1 - src/spawn.cpp | 3 +- src/spawn.h | 2 - src/waitingonclosedialog.cpp | 71 ------------------------ src/waitingonclosedialog.h | 56 ------------------- src/waitingonclosedialog.ui | 119 ----------------------------------------- 15 files changed, 2 insertions(+), 651 deletions(-) delete mode 100644 src/ilockedwaitingforprocess.h delete mode 100644 src/lockeddialog.cpp delete mode 100644 src/lockeddialog.h delete mode 100644 src/lockeddialog.ui delete mode 100644 src/lockeddialogbase.cpp delete mode 100644 src/lockeddialogbase.h delete mode 100644 src/waitingonclosedialog.cpp delete mode 100644 src/waitingonclosedialog.h delete mode 100644 src/waitingonclosedialog.ui (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 168b79dc..06f2cd1e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -82,9 +82,6 @@ SET(organizer_SRCS main.cpp loghighlighter.cpp loglist.cpp - lockeddialogbase.cpp - lockeddialog.cpp - waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp filedialogmemory.cpp @@ -206,9 +203,6 @@ SET(organizer_HDRS mainwindow.h loghighlighter.h loglist.h - lockeddialogbase.h - lockeddialog.h - waitingonclosedialog.h loadmechanism.h installationmanager.h filedialogmemory.h @@ -246,7 +240,6 @@ SET(organizer_HDRS viewmarkingscrollbar.h plugincontainer.h organizercore.h - ilockedwaitingforprocess.h iuserinterface.h instancemanager.h usvfsconnector.h @@ -293,8 +286,6 @@ SET(organizer_UIS modinfodialog.ui messagedialog.ui mainwindow.ui - lockeddialog.ui - waitingonclosedialog.ui editexecutablesdialog.ui credentialsdialog.ui categoriesdialog.ui @@ -397,12 +388,6 @@ set(executables editexecutablesdialog ) -set(locking - ilockedwaitingforprocess - lockeddialog - lockeddialogbase -) - set(modinfo modinfo modinfobackup @@ -500,7 +485,7 @@ set(widgets ) set(src_filters - application core browser dialogs downloads env executables locking modinfo + application core browser dialogs downloads env executables modinfo modinfo\\dialog modlist plugins previews profiles settings settingsdialog utilities widgets ) diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h deleted file mode 100644 index 4d1e786f..00000000 --- a/src/ilockedwaitingforprocess.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef ILOCKEDWAITINGFORPROCESS_H -#define ILOCKEDWAITINGFORPROCESS_H - -class QString; - -class ILockedWaitingForProcess -{ -public: - virtual bool unlockForced() const = 0; - virtual void setProcessName(QString const &) = 0; - virtual void setProcessInformation(DWORD pid, const QString& name) = 0; -}; - -#endif // ILOCKEDWAITINGFORPROCESS_H diff --git a/src/iuserinterface.h b/src/iuserinterface.h index e5755f03..99caceb1 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -3,7 +3,6 @@ #include "modinfodialogfwd.h" -#include "ilockedwaitingforprocess.h" #include "lockwidget.h" #include #include diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp deleted file mode 100644 index 143d5838..00000000 --- a/src/lockeddialog.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "lockeddialog.h" -#include "ui_lockeddialog.h" - -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) - : LockedDialogBase(parent, !unlockByButton) - , ui(new Ui::LockedDialog) -{ - ui->setupUi(this); - - // Supposedly the Qt::CustomizeWindowHint should use a customized window - // allowing us to select if there is a close button. In practice this doesn't - // seem to work. We will ignore pressing the close button if unlockByButton == true - Qt::WindowFlags flags = - this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (m_allowClose) - flags |= Qt::WindowCloseButtonHint; - this->setWindowFlags(flags); - - if (!unlockByButton) - { - ui->unlockButton->hide(); - ui->verticalLayout->addItem( - new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); - } -} - -LockedDialog::~LockedDialog() -{ - delete ui; -} - - -void LockedDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - -void LockedDialog::on_unlockButton_clicked() -{ - unlock(); -} - -void LockedDialog::unlock() { - LockedDialogBase::unlock(); - ui->label->setText("unlocking may take a few seconds"); - ui->unlockButton->setEnabled(false); -} diff --git a/src/lockeddialog.h b/src/lockeddialog.h deleted file mode 100644 index 36c16429..00000000 --- a/src/lockeddialog.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "lockeddialogbase.h" - -namespace Ui { - class LockedDialog; -} - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialog : public LockedDialogBase -{ - Q_OBJECT - -public: - explicit LockedDialog(QWidget *parent = 0, bool unlockByButton = false); - ~LockedDialog(); - - void setProcessName(const QString &name) override; - -protected: - - void unlock() override; - -private slots: - - void on_unlockButton_clicked(); - -private: - - Ui::LockedDialog *ui; -}; diff --git a/src/lockeddialog.ui b/src/lockeddialog.ui deleted file mode 100644 index 0ec2e467..00000000 --- a/src/lockeddialog.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - LockedDialog - - - - 0 - 0 - 317 - 151 - - - - Running virtualized processes - - - - - - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - - - MO is locked while the executable is running. - - - Qt::AlignCenter - - - true - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - true - - - - color: grey; - - - - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - Unlock - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp deleted file mode 100644 index 0876a511..00000000 --- a/src/lockeddialogbase.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "lockeddialogbase.h" -#include "envmodule.h" -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -LockedDialogBase::LockedDialogBase(QWidget *parent, bool allowClose) - : QDialog(parent) - , m_Unlocked(false) - , m_Canceled(false) - , m_allowClose(allowClose) -{ - if (parent != nullptr) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } -} - -void LockedDialogBase::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != nullptr) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - -void LockedDialogBase::reject() -{ - if (m_allowClose) - unlock(); -} - -bool LockedDialogBase::unlockForced() const { - return m_Unlocked; -} - -bool LockedDialogBase::canceled() const { - return m_Canceled; -} - -void LockedDialogBase::setProcessInformation(DWORD pid, const QString& name) -{ - setProcessName(QString("%1 (%2)").arg(name).arg(pid)); -} - -void LockedDialogBase::unlock() { - m_Unlocked = true; -} - -void LockedDialogBase::cancel() { - m_Canceled = true; -} - diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h deleted file mode 100644 index 4ebad4c7..00000000 --- a/src/lockeddialogbase.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "ilockedwaitingforprocess.h" -#include // for QDialog -#include // for Q_OBJECT, slots -#include // for QString - -class QResizeEvent; -class QWidget; - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialogBase : public QDialog, public ILockedWaitingForProcess -{ - Q_OBJECT - -public: - explicit LockedDialogBase(QWidget *parent, bool allowClose); - - bool unlockForced() const override; - - virtual bool canceled() const; - - void setProcessInformation(DWORD pid, const QString& name) override; - -protected: - - virtual void resizeEvent(QResizeEvent *event); - - virtual void reject(); - - virtual void unlock(); - - virtual void cancel(); - - bool m_Unlocked; - bool m_Canceled; - bool m_allowClose; -}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 631a31d8..9453f07c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -57,7 +57,6 @@ along with Mod Organizer. If not, see . #include "downloadlistwidget.h" #include "messagedialog.h" #include "installationmanager.h" -#include "waitingonclosedialog.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3c535868..dda60f76 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -29,7 +29,6 @@ #include "appconfig.h" #include #include -#include "lockeddialog.h" #include "instancemanager.h" #include #include "previewdialog.h" diff --git a/src/spawn.cpp b/src/spawn.cpp index b331db01..62745542 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -27,8 +27,6 @@ along with Mod Organizer. If not, see . #include "envmodule.h" #include "settings.h" #include "settingsdialogworkarounds.h" -#include -#include #include #include #include @@ -645,6 +643,7 @@ bool startSteam(QWidget* parent) HANDLE ph = INVALID_HANDLE_VALUE; const auto e = spawn(sp, ph); + ::CloseHandle(ph); if (e != ERROR_SUCCESS) { // make sure username and passwords are not shown diff --git a/src/spawn.h b/src/spawn.h index 0464ffd6..9fb346b0 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -28,8 +28,6 @@ along with Mod Organizer. If not, see . class Settings; -namespace MOBase { class IPluginGame; } - namespace spawn { diff --git a/src/waitingonclosedialog.cpp b/src/waitingonclosedialog.cpp deleted file mode 100644 index 565d0a36..00000000 --- a/src/waitingonclosedialog.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "waitingonclosedialog.h" -#include "ui_waitingonclosedialog.h" - -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget *parent) - : LockedDialogBase(parent,true) - , ui(new Ui::WaitingOnCloseDialog) -{ - ui->setupUi(this); - - // Supposedly the Qt::CustomizeWindowHint should use a customized window - // allowing us to select if there is a close button. In practice this doesn't - // seem to work. We will ignore pressing the close button if unlockByButton == true - Qt::WindowFlags flags = - this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (m_allowClose) - flags |= Qt::WindowCloseButtonHint; - this->setWindowFlags(flags); -} - -WaitingOnCloseDialog::~WaitingOnCloseDialog() -{ - delete ui; -} - - -void WaitingOnCloseDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - -void WaitingOnCloseDialog::on_closeButton_clicked() -{ - unlock(); -} - -void WaitingOnCloseDialog::on_cancelButton_clicked() -{ - cancel(); - unlock(); -} - -void WaitingOnCloseDialog::unlock() { - LockedDialogBase::unlock(); - ui->label->setText("unlocking may take a few seconds"); - ui->closeButton->setEnabled(false); - ui->cancelButton->setEnabled(false); -} diff --git a/src/waitingonclosedialog.h b/src/waitingonclosedialog.h deleted file mode 100644 index 6650c390..00000000 --- a/src/waitingonclosedialog.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "lockeddialogbase.h" - -namespace Ui { - class WaitingOnCloseDialog; -} - -/** - * Similar to the LockedDialog but used for waiting on running process during - * a process close request which requries a slightly different dialog. - **/ -class WaitingOnCloseDialog : public LockedDialogBase -{ - Q_OBJECT - -public: - explicit WaitingOnCloseDialog(QWidget *parent = 0); - ~WaitingOnCloseDialog(); - - bool canceled() const { return m_Canceled; } - - void setProcessName(const QString &name) override; - -protected: - - void unlock() override; - -private slots: - - void on_closeButton_clicked(); - void on_cancelButton_clicked(); - -private: - - Ui::WaitingOnCloseDialog *ui; -}; diff --git a/src/waitingonclosedialog.ui b/src/waitingonclosedialog.ui deleted file mode 100644 index 9c7818e0..00000000 --- a/src/waitingonclosedialog.ui +++ /dev/null @@ -1,119 +0,0 @@ - - - WaitingOnCloseDialog - - - - 0 - 0 - 317 - 151 - - - - Waiting for virtualized processes - - - - - - This dialog should disappear automatically if the application/game is done. - - - Virtualized processes are still running, it is prefered to keep MO running until they are finished. - - - true - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - true - - - - color: grey; - - - - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - - Close Now - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Cancel - - - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - -- cgit v1.3.1