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 +- src/organizercore.cpp | 192 ++++++++++++++++-------------------------------- src/organizercore.h | 9 ++- src/plugincontainer.cpp | 1 + src/spawn.cpp | 131 +++++++++++++++++++++++++++++++-- src/spawn.h | 34 +++++++++ 6 files changed, 230 insertions(+), 141 deletions(-) (limited to 'src') 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) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5613e8ce..3c986004 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,5 +1,5 @@ #include "organizercore.h" - +#include "mainwindow.h" #include "delayedfilewriter.h" #include "guessedvalue.h" #include "imodinterface.h" @@ -127,7 +127,7 @@ QStringList toStringList(InputIterator current, InputIterator end) OrganizerCore::OrganizerCore(Settings &settings) - : m_UserInterface(nullptr) + : m_MainWindow(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) @@ -249,44 +249,43 @@ void OrganizerCore::updateExecutablesList() m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } -void OrganizerCore::setUserInterface(IUserInterface *userInterface, - QWidget *widget) +void OrganizerCore::setUserInterface(MainWindow* mainWindow) { storeSettings(); - m_UserInterface = userInterface; + m_MainWindow = mainWindow; - if (widget != nullptr) { - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), widget, + if (m_MainWindow != nullptr) { + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), widget, + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow, SLOT(modlistChanged(QModelIndexList, int))); - connect(&m_ModList, SIGNAL(showMessage(QString)), widget, + connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget, + connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow, SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget, + connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, + connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(clearOverwrite()), widget, + connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow, SLOT(clearOverwrite())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow, SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), widget, + connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow, SLOT(modorder_changed())); - connect(&m_PluginList, SIGNAL(writePluginsList()), widget, + connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow, SLOT(esplist_changed())); - connect(&m_PluginList, SIGNAL(esplist_changed()), widget, + connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow, SLOT(esplist_changed())); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget, + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow, SLOT(showMessage(QString))); } - m_InstallationManager.setParentWidget(widget); - m_Updater.setUserInterface(widget); + m_InstallationManager.setParentWidget(m_MainWindow); + m_Updater.setUserInterface(m_MainWindow); checkForUpdates(); } @@ -295,7 +294,7 @@ void OrganizerCore::checkForUpdates() { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (m_UserInterface != nullptr) { + if (m_MainWindow != nullptr) { m_Updater.testForUpdate(m_Settings); } } @@ -755,13 +754,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_UserInterface != nullptr) + if (hasIniTweaks && (m_MainWindow != 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_UserInterface->displayModInformation( + m_MainWindow->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); @@ -822,13 +821,13 @@ void OrganizerCore::installDownload(int index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); - if (hasIniTweaks && m_UserInterface != nullptr + if (hasIniTweaks && m_MainWindow != 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_UserInterface->displayModInformation( + m_MainWindow->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } @@ -1250,11 +1249,11 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; - if (m_UserInterface != nullptr) { - uilock = m_UserInterface->lock(); + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); } else { - // i.e. when running command line shortcuts there is no m_UserInterface + // i.e. when running command line shortcuts there is no user interface dlg.reset(new LockedDialog); dlg->show(); dlg->setEnabled(true); @@ -1262,8 +1261,8 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, } ON_BLOCK_EXIT([&]() { - if (m_UserInterface != nullptr) { - m_UserInterface->unlock(); + if (m_MainWindow != nullptr) { + m_MainWindow->unlock(); } }); DWORD ignoreExitCode; @@ -1275,35 +1274,21 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, } -HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, - const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries) +HANDLE OrganizerCore::spawnBinaryProcess( + const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries) { spawn::SpawnParameters sp; sp.binary = binary; sp.arguments = arguments; sp.currentDirectory = currentDirectory; + sp.steamAppID = steamAppID; sp.hooked = true; prepareStart(); - QWidget *window = qApp->activeWindow(); - if ((window != nullptr) && (!window->isVisible())) { - window = nullptr; - } - - if (!spawn::checkBinary(window, sp)) { - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) { - return INVALID_HANDLE_VALUE; - } - while (m_DirectoryUpdate) { ::Sleep(100); QCoreApplication::processEvents(); @@ -1315,72 +1300,25 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, } // TODO: should also pass arguments - if (m_AboutToRun(binary.absoluteFilePath())) { - 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(window, tr("Error"), e.what()); - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkEnvironment(window, sp)) { - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkBlacklist(window, sp, m_Settings)) { - return INVALID_HANDLE_VALUE; - } - - 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 = currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = 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 = m_GamePlugin->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; - - } - - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = m_GamePlugin->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; - } - - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), arguments); + if (!m_AboutToRun(binary.absoluteFilePath())) { + log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); + return INVALID_HANDLE_VALUE; + } - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + try { + m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + m_USVFS.updateForcedLibraries(forcedLibraries); - return spawn::startBinary(window, sp); - } else { - log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); - return spawn::startBinary(window, sp); - } - } else { - log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); + } 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; } + + auto process = spawn::Spawner().spawn(m_MainWindow, m_GamePlugin, sp, m_Settings); + return process.releaseHandle(); } HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) @@ -1498,13 +1436,13 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) return true; ILockedWaitingForProcess* uilock = nullptr; - if (m_UserInterface != nullptr) { - uilock = m_UserInterface->lock(); + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); } ON_BLOCK_EXIT([&] () { - if (m_UserInterface != nullptr) { - m_UserInterface->unlock(); + if (m_MainWindow != nullptr) { + m_MainWindow->unlock(); } }); return waitForProcessCompletion(handle, exitCode, uilock); } @@ -1762,8 +1700,8 @@ void OrganizerCore::refreshBSAList() m_ActiveArchives = m_DefaultArchives; } - if (m_UserInterface != nullptr) { - m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); + if (m_MainWindow != nullptr) { + m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives); } m_ArchivesInit = true; @@ -1879,8 +1817,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().writeImmediately(false); } std::vector archives = enabledArchives(); @@ -2064,8 +2002,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } modInfo->clearCaches(); @@ -2114,8 +2052,8 @@ void OrganizerCore::modStatusChanged(QList index) { origin.enable(false); } } - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } @@ -2290,8 +2228,8 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); diff --git a/src/organizercore.h b/src/organizercore.h index 0d0a092c..04c96ba6 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -3,7 +3,7 @@ #include "selfupdater.h" -#include "iuserinterface.h" //should be class IUserInterface; +#include "ilockedwaitingforprocess.h" #include "settings.h" #include "modlist.h" #include "modinfo.h" @@ -26,6 +26,8 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; +class MainWindow; + namespace MOBase { template class GuessedValue; class IModInterface; @@ -101,7 +103,7 @@ public: ~OrganizerCore(); - void setUserInterface(IUserInterface *userInterface, QWidget *widget); + void setUserInterface(MainWindow* mainWindow); void connectPlugins(PluginContainer *container); void disconnectPlugins(); @@ -328,8 +330,7 @@ private: static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; private: - - IUserInterface *m_UserInterface; + MainWindow* m_MainWindow; PluginContainer *m_PluginContainer; QString m_GameName; MOBase::IPluginGame *m_GamePlugin; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index c0706ba8..767d3eb8 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -3,6 +3,7 @@ #include "organizerproxy.h" #include "report.h" #include +#include #include #include #include diff --git a/src/spawn.cpp b/src/spawn.cpp index a34230b2..3c7d64ce 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "envmodule.h" #include "settings.h" #include "settingsdialogworkarounds.h" +#include #include #include #include @@ -440,7 +441,7 @@ QMessageBox::StandardButton confirmBlacklisted( namespace spawn { -DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) +DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) { BOOL inheritHandles = FALSE; @@ -494,7 +495,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand } processHandle = pi.hProcess; - threadHandle = pi.hThread; + ::CloseHandle(pi.hThread); return ERROR_SUCCESS; } @@ -618,8 +619,7 @@ bool startSteam(QWidget* parent) (password.isEmpty() ? "no" : "yes")); HANDLE ph = INVALID_HANDLE_VALUE; - HANDLE th = INVALID_HANDLE_VALUE; - const auto e = spawn(sp, ph, th); + const auto e = spawn(sp, ph); if (e != ERROR_SUCCESS) { // make sure username and passwords are not shown @@ -772,17 +772,59 @@ 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 processHandle, threadHandle; - const auto e = spawn(sp, processHandle, threadHandle); + HANDLE handle = INVALID_HANDLE_VALUE; + const auto e = spawn::spawn(sp, handle); switch (e) { case ERROR_SUCCESS: { - ::CloseHandle(threadHandle); - return processHandle; + return handle; } case ERROR_ELEVATION_REQUIRED: @@ -799,6 +841,79 @@ 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 (!spawn::checkEnvironment(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!spawn::checkBlacklist(parent, sp, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + adjustForVirtualized(game, sp, settings); + + return {startBinary(parent, sp), sp}; +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index 31b44739..d2853cd5 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include class Settings; +namespace MOBase { class IPluginGame; } namespace spawn { @@ -46,6 +47,7 @@ struct SpawnParameters QFileInfo binary; QString arguments; QDir currentDirectory; + QString steamAppID; bool hooked = false; HANDLE stdOut = INVALID_HANDLE_VALUE; HANDLE stdErr = INVALID_HANDLE_VALUE; @@ -69,6 +71,38 @@ bool checkBlacklist( **/ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); + +class SpawnedProcess +{ +public: + SpawnedProcess(HANDLE handle, SpawnParameters sp); + + SpawnedProcess(const SpawnedProcess&) = delete; + SpawnedProcess& operator=(const SpawnedProcess&) = delete; + SpawnedProcess(SpawnedProcess&& other); + SpawnedProcess& operator=(SpawnedProcess&& other); + ~SpawnedProcess(); + + HANDLE releaseHandle(); + +private: + HANDLE m_handle; + SpawnParameters m_parameters; + + void destroy(); +}; + + +class Spawner +{ +public: + SpawnedProcess spawn( + QWidget* parent, const MOBase::IPluginGame* game, + SpawnParameters sp, Settings& settings); + +private: +}; + } // namespace -- cgit v1.3.1 From 74631ae88e36f7ccc1ea80c5a56d686637c325c6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 18 Oct 2019 22:42:47 -0400 Subject: merged spawnBinaryDirect() and spawnBinaryProcess() --- src/organizercore.cpp | 98 +++++++++++++++++++++++++-------------------------- src/organizercore.h | 23 +++++------- 2 files changed, 56 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3c986004..3bb6af70 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1208,15 +1208,17 @@ bool OrganizerCore::previewFile( return true; } -void OrganizerCore::spawnBinary(const QFileInfo &binary, - const QString &arguments, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries) +void OrganizerCore::spawnBinary( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries) { DWORD processExitCode = 0; - HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode); + HANDLE processHandle = spawnBinaryDirect( + binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, + customOverwrite, forcedLibraries, &processExitCode); + if (processHandle != INVALID_HANDLE_VALUE) { refreshDirectoryStructure(); // need to remove our stored load order because it may be outdated if a foreign tool changed the @@ -1235,50 +1237,12 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, } } -HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, - const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - LPDWORD exitCode) -{ - HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries); - if (Settings::instance().interface().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { - 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(); - } }); - - DWORD ignoreExitCode; - waitForProcessCompletion(processHandle, exitCode ? exitCode : &ignoreExitCode, uilock); - cycleDiagnostics(); - } - - return processHandle; -} - - -HANDLE OrganizerCore::spawnBinaryProcess( +HANDLE OrganizerCore::spawnBinaryDirect( const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, - const QList &forcedLibraries) + const QList &forcedLibraries, + LPDWORD exitCode) { spawn::SpawnParameters sp; sp.binary = binary; @@ -1317,10 +1281,44 @@ HANDLE OrganizerCore::spawnBinaryProcess( return INVALID_HANDLE_VALUE; } - auto process = spawn::Spawner().spawn(m_MainWindow, m_GamePlugin, sp, m_Settings); - return process.releaseHandle(); + HANDLE handle = spawn::Spawner() + .spawn(m_MainWindow, m_GamePlugin, sp, m_Settings) + .releaseHandle(); + + if (handle == INVALID_HANDLE_VALUE) { + // failed + return INVALID_HANDLE_VALUE; + } + + if (Settings::instance().interface().lockGUI()) { + 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(); + } }); + + DWORD ignoreExitCode; + waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + cycleDiagnostics(); + } + + return handle; } + HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) { if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) diff --git a/src/organizercore.h b/src/organizercore.h index 04c96ba6..4a8f90d1 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -168,21 +168,6 @@ public: const QString &customOverwrite = "", const QList &forcedLibraries = QList()); - HANDLE spawnBinaryDirect(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); - - HANDLE spawnBinaryProcess(const QFileInfo &binary, const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries = QList()); - void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -315,6 +300,14 @@ private: const MOShared::DirectoryEntry *directoryEntry, int createDestination); + HANDLE spawnBinaryDirect(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 waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); private slots: -- cgit v1.3.1 From 4fb79cae456986795be3f504d440048e476dffb3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 18 Oct 2019 23:11:19 -0400 Subject: renamed executeFileVirtualized() to runFile() renamed spawnBinary() to runExecutable() renamed spawnBinaryDirect() to spawnAndWait() --- src/organizercore.cpp | 78 +++++++++++++++++++++++++-------------------------- src/organizercore.h | 15 +++++----- 2 files changed, 47 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3bb6af70..4270f3b4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1046,40 +1046,6 @@ bool OrganizerCore::getFileExecutionContext( } } -bool OrganizerCore::executeFileVirtualized( - QWidget* parent, const QFileInfo& targetInfo) -{ - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; - - if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { - return false; - } - - switch (type) - { - case FileExecutionTypes::Executable: { - spawnBinaryDirect( - binaryInfo, arguments, currentProfile()->name(), - targetInfo.absolutePath(), "", ""); - - return true; - } - - case FileExecutionTypes::Other: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - - return true; - } - } - - // nop - return false; -} - bool OrganizerCore::previewFileWithAlternatives( QWidget* parent, QString fileName, int selectedOrigin) { @@ -1208,14 +1174,48 @@ bool OrganizerCore::previewFile( return true; } -void OrganizerCore::spawnBinary( +bool OrganizerCore::runFile( + QWidget* parent, const QFileInfo& targetInfo) +{ + QFileInfo binaryInfo; + QString arguments; + FileExecutionTypes type; + + if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { + return false; + } + + switch (type) + { + case FileExecutionTypes::Executable: { + runExecutable( + binaryInfo, arguments, currentProfile()->name(), + targetInfo.absolutePath()); + + return true; + } + + case FileExecutionTypes::Other: { + ::ShellExecuteW(nullptr, L"open", + ToWString(targetInfo.absoluteFilePath()).c_str(), + nullptr, nullptr, SW_SHOWNORMAL); + + return true; + } + } + + // nop + return false; +} + +void OrganizerCore::runExecutable( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, const QList &forcedLibraries) { DWORD processExitCode = 0; - HANDLE processHandle = spawnBinaryDirect( + HANDLE processHandle = spawnAndWait( binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode); @@ -1237,7 +1237,7 @@ void OrganizerCore::spawnBinary( } } -HANDLE OrganizerCore::spawnBinaryDirect( +HANDLE OrganizerCore::spawnAndWait( const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, @@ -1334,7 +1334,7 @@ HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) forcedLibaries.clear(); } - return spawnBinaryDirect( + return spawnAndWait( exe.binaryInfo(), exe.arguments(), m_CurrentProfile->name(), exe.workingDirectory().length() != 0 @@ -1419,7 +1419,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, if (ignoreCustomOverwrite) customOverwrite.clear(); - return spawnBinaryDirect(binary, + return spawnAndWait(binary, arguments, profileName, currentDirectory, diff --git a/src/organizercore.h b/src/organizercore.h index 4a8f90d1..6628452c 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -158,15 +158,16 @@ public: QWidget* parent, const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); - bool executeFileVirtualized(QWidget* parent, const QFileInfo& targetInfo); bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); - void spawnBinary(const QFileInfo &binary, const QString &arguments = "", - const QDir ¤tDirectory = QDir(), - const QString &steamAppID = "", - const QString &customOverwrite = "", - const QList &forcedLibraries = QList()); + bool runFile(QWidget* parent, const QFileInfo& targetInfo); + + void runExecutable( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID={}, + const QString &customOverwrite={}, + const QList &forcedLibraries={}); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -300,7 +301,7 @@ private: const MOShared::DirectoryEntry *directoryEntry, int createDestination); - HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, + HANDLE spawnAndWait(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, -- 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') 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 b60f2aa786cf748e1839f4320604030863279032 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 02:52:54 -0400 Subject: renamed startApplication() to runExecutableOrExecutableFile() --- src/main.cpp | 3 ++- src/organizercore.cpp | 40 +++++++++++++++++++--------------------- src/organizercore.h | 7 ++++++- src/organizerproxy.cpp | 12 ++++++++---- 4 files changed, 35 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 776c3775..49ecc084 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -653,7 +653,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary try { - organizer.startApplication(exeName, arguments, QString(), QString()); + organizer.runExecutableOrExecutableFile( + exeName, arguments, QString(), QString()); return 0; } catch (const std::exception &e) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 6f36ee3e..6bf2c290 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1362,45 +1362,44 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -HANDLE OrganizerCore::startApplication(const QString &executable, - const QStringList &args, - const QString &cwd, - const QString &profile, - const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) +HANDLE OrganizerCore::runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) { - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; QString profileName = profile; - if (profile.length() == 0) { + 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 = QFileInfo( - managedGame()->gameDirectory().absoluteFilePath(executable)); + binary = managedGame()->gameDirectory().absoluteFilePath(executable); } - if (cwd.length() == 0) { + + if (currentDirectory == "") { currentDirectory = binary.absolutePath(); } + try { - const Executable &exe = m_ExecutablesList.getByBinary(binary); + const Executable& exe = m_ExecutablesList.getByBinary(binary); steamAppID = exe.steamAppID(); - customOverwrite - = m_CurrentProfile->setting("custom_overwrites", exe.title()) - .toString(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); } @@ -1412,9 +1411,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, try { const Executable &exe = m_ExecutablesList.get(executable); steamAppID = exe.steamAppID(); - customOverwrite - = m_CurrentProfile->setting("custom_overwrites", exe.title()) - .toString(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); } @@ -1422,7 +1419,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, arguments = exe.arguments(); } binary = exe.binaryInfo(); - if (cwd.length() == 0) { + if (currentDirectory == "") { currentDirectory = exe.workingDirectory(); } } catch (const std::runtime_error &) { @@ -1433,6 +1430,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, if (!forcedCustomOverwrite.isEmpty()) customOverwrite = forcedCustomOverwrite; + if (ignoreCustomOverwrite) customOverwrite.clear(); diff --git a/src/organizercore.h b/src/organizercore.h index ca87a05c..fa05a20f 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -174,6 +174,12 @@ public: 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); + + void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -227,7 +233,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - 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); bool onModInstalled(const std::function &func); diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index d6996b3a..2ea1761a 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -107,10 +107,14 @@ QString OrganizerProxy::pluginDataPath() const return m_Proxied->pluginDataPath(); } -HANDLE OrganizerProxy::startApplication(const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite) -{ - return m_Proxied->startApplication(executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); +HANDLE OrganizerProxy::startApplication( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + return m_Proxied->runExecutableOrExecutableFile( + executable, args, cwd, profile, + forcedCustomOverwrite, ignoreCustomOverwrite); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const -- 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') 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 8f24f6298f62e36db1c7a624052e70b41c5e7e27 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 05:27:39 -0400 Subject: wait for executable when opening files --- src/organizercore.cpp | 22 ++++++++++++++++++++-- src/organizercore.h | 5 ++++- src/spawn.cpp | 4 ++-- 3 files changed, 26 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 78f9517c..d3f4a83c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1120,8 +1120,19 @@ bool OrganizerCore::runFile( case spawn::FileExecutionTypes::Other: // fall-through default: { - const auto r = shell::Open(targetInfo.absoluteFilePath()); - return r.success(); + 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; } } } @@ -1333,6 +1344,13 @@ HANDLE OrganizerCore::spawnAndWait( return INVALID_HANDLE_VALUE; } + waitForProcessCompletionWithLock(handle, exitCode); + return handle; +} + +bool OrganizerCore::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ if (Settings::instance().interface().lockGUI()) { std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; diff --git a/src/organizercore.h b/src/organizercore.h index f802c8cb..3d3c7325 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -306,7 +306,10 @@ private: const QList &forcedLibraries = QList(), LPDWORD exitCode = nullptr); - bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); + + bool waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); private slots: diff --git a/src/spawn.cpp b/src/spawn.cpp index dd93bfaa..fe1e9e3e 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1029,7 +1029,7 @@ bool isJavaFile(const QFileInfo& target) QFileInfo getCmdPath() { - const auto p = env::get("COMSPEC2"); + const auto p = env::get("COMSPEC"); if (!p.isEmpty()) { return p; } @@ -1111,7 +1111,7 @@ bool helperExec( { SHELLEXECUTEINFOW execInfo = {}; - ULONG flags = SEE_MASK_FLAG_NO_UI ; + ULONG flags = SEE_MASK_FLAG_NO_UI; if (!async) flags |= SEE_MASK_NOCLOSEPROCESS; -- 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') 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 18b438cf27a552e69e984bfee63187b6471682ab Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 07:28:12 -0400 Subject: split to getRunningUSVFSProcesses() simplified waitForAllUSVFSProcesses() to always get the list of running processes after one process completes --- src/envmodule.cpp | 6 ++ src/envmodule.h | 2 + src/organizercore.cpp | 224 ++++++++++++++----------------------------------- src/spawn.cpp | 53 ++++++++---- src/spawn.h | 4 + src/usvfsconnector.cpp | 47 +++++++++++ src/usvfsconnector.h | 2 + 7 files changed, 163 insertions(+), 175 deletions(-) (limited to 'src') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index abbe02e5..160a54fa 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -511,4 +511,10 @@ DWORD getProcessParentID(DWORD pid) return ppid; } + +DWORD getProcessParentID(HANDLE handle) +{ + return getProcessParentID(GetProcessId(handle)); +} + } // namespace diff --git a/src/envmodule.h b/src/envmodule.h index 212f6f7b..6c0a028d 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -121,7 +121,9 @@ std::vector getRunningProcesses(); std::vector getLoadedModules(); QString getProcessName(HANDLE process); + DWORD getProcessParentID(DWORD pid); +DWORD getProcessParentID(HANDLE handle); } // namespace env diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2a228fda..2a97b998 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1355,17 +1355,13 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) if (!Settings::instance().interface().lockGUI()) return true; - ILockedWaitingForProcess* uilock = nullptr; - if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } + bool r = false; - ON_BLOCK_EXIT([&] () { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); + withLock([&](auto* uilock) { + r = waitForProcessCompletion(handle, exitCode, uilock); + }); - return waitForProcessCompletion(handle, exitCode, uilock); + return r; } bool OrganizerCore::waitForProcessCompletion( @@ -1387,6 +1383,9 @@ bool OrganizerCore::waitForProcessCompletion( bool OrganizerCore::waitForAllUSVFSProcessesWithLock() { + if (!Settings::instance().interface().lockGUI()) + return true; + bool r = false; withLock([&](auto* uilock) { @@ -1396,178 +1395,81 @@ bool OrganizerCore::waitForAllUSVFSProcessesWithLock() return r; } -bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +HANDLE getInterestingProcess( + const std::vector& handles, + const std::vector& hidden, DWORD preferedParentPid) { - // 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 = env::getProcessName(handle); - } - - bool waitingOnHidden = false; - 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" - // process. For this reason we use exponential backoff and also start with a delibrately low value to improve - // the responsiveness of the initial update - DWORD64 nextHiddenCheck = GetTickCount64(); - DWORD64 nextHiddenCheckDelay = 50; - - constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; - DWORD res = WAIT_TIMEOUT; - while (handle != INVALID_HANDLE_VALUE && (newHandle || res == WAIT_TIMEOUT || res == INPUT_EVENT)) - { - if (newHandle) { - processName += QString(" (%1)").arg(currentPID); - if (uilock) - uilock->setProcessName(processName); - - log::debug( - "Waiting for {} process completion: {}", - (originalHandle ? "spawned" : "usvfs"), processName); - - newHandle = false; - } + HANDLE best_match = INVALID_HANDLE_VALUE; + bool best_match_hidden = true; - // Wait for a an event on the handle, a key press, mouse click or timeout - res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON); - if (res == WAIT_FAILED) { - log::warn("Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED {}", GetLastError()); - break; - } + for (auto handle : handles) { + const QString pname = env::getProcessName(handle); - // keep processing events so the app doesn't appear dead - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); + bool phidden = false; + for (auto h : hidden) + if (pname.contains(h, Qt::CaseInsensitive)) + phidden = true; - if (uilock && uilock->unlockForced()) { - uiunlocked = true; - break; - } + bool pprefered = preferedParentPid && env::getProcessParentID(handle) == preferedParentPid; - if (res == WAIT_OBJECT_0) { - // process we were waiting on has completed - if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode)) - log::warn("Failed getting exit code of complete process: {}", GetLastError()); - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; - originalHandle = false; - // if the previous process spawned a child process and immediately exits we may miss it if we check immediately - waitForChildUntil = GetTickCount64() + 800; + if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { + best_match = handle; + best_match_hidden = phidden; } - // search for another process to wait on if either: - // 1. we just completed waiting for a process and need to find/wait for an inject child - // 2. we are currently waiting on a hidden process so periodically check if there is a non-hidden process to wait on - bool firstIteration = true; - while ((handle == INVALID_HANDLE_VALUE && GetTickCount64() <= waitForChildUntil) - || (waitingOnHidden && GetTickCount64() >= nextHiddenCheck)) - { - if (firstIteration) - firstIteration = false; - else { - QThread::msleep(200); - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); - } - - // search if there is another usvfs process active - handle = findAndOpenAUSVFSProcess(hiddenList, currentPID); - waitingOnHidden = false; - newHandle = handle != INVALID_HANDLE_VALUE; - if (newHandle) { - currentPID = GetProcessId(handle); - processName = env::getProcessName(handle); - for (QString hide : hiddenList) - if (processName.contains(hide, Qt::CaseInsensitive)) - waitingOnHidden = true; - } - if (waitingOnHidden) { - nextHiddenCheck = GetTickCount64() + nextHiddenCheckDelay; - nextHiddenCheckDelay = std::min(nextHiddenCheckDelay * 2, (DWORD64) 2000); - } - else { - nextHiddenCheck = GetTickCount64(); - nextHiddenCheckDelay = 200; - } - } + if (!phidden && pprefered) + return best_match; } - if (res == WAIT_OBJECT_0) - log::debug("Waiting for process completion successfull"); - else if (uiunlocked) - log::debug("Waiting for process completion aborted by UI"); - else - log::debug("Waiting for process completion not successfull: {}", res); - - if (handle != INVALID_HANDLE_VALUE) - ::CloseHandle(handle); - - return res == WAIT_OBJECT_0; + return best_match; } -HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid) { - // for practical reasons a querySize of 1 is probably enough, we use a larger query as a heuristics - // to find a more "aesthetic injected processes (attempting to comply to hiddenList and preferedParentPid) - constexpr size_t querySize = 100; - DWORD pids[querySize]; - size_t found = querySize; - if (!::GetVFSProcessList(&found, pids)) { - log::warn("Failed seeking USVFS processes : GetVFSProcessList failed?!"); - return INVALID_HANDLE_VALUE; - } - - HANDLE best_match = INVALID_HANDLE_VALUE; - bool best_match_hidden = true; - for (size_t i = 0; i < found; ++i) { - if (pids[i] == GetCurrentProcessId()) - continue; // obviously don't wait for MO process +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()); - HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); - if (handle == INVALID_HANDLE_VALUE) { - log::warn("Failed opening USVFS process {}: OpenProcess failed {}", pids[i], GetLastError()); - continue; + for (;;) { + const auto handles = getRunningUSVFSProcesses(); + if (handles.empty()) { + break; } - QString pname = env::getProcessName(handle); - bool phidden = false; - for (auto hide : hiddenList) - if (pname.contains(hide, Qt::CaseInsensitive)) - phidden = true; + const auto interesting = getInterestingProcess( + handles, hiddenList, GetCurrentProcessId()); - bool pprefered = preferedParentPid && env::getProcessParentID(pids[i]) == preferedParentPid; + if (uilock) { + const DWORD pid = ::GetProcessId(interesting); + const QString processName = QString("%1 (%2)") + .arg(env::getProcessName(interesting)) + .arg(pid); - if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { - if (best_match != INVALID_HANDLE_VALUE) - CloseHandle(best_match); - best_match = handle; - best_match_hidden = phidden; + uilock->setProcessName(processName); } - else - CloseHandle(handle); - if (!phidden && pprefered) - return best_match; + const auto r = spawn::waitForProcess(interesting, nullptr, uilock); + + switch (r) + { + case spawn::WaitResults::Completed: + // this process is completed, check for others + break; + + case spawn::WaitResults::Unlocked: + // 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; + } } - return best_match; + log::debug("Waiting for process completion successful"); + return true; } bool OrganizerCore::onAboutToRun( diff --git a/src/spawn.cpp b/src/spawn.cpp index f0b3b2c7..1003024f 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1094,7 +1094,8 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } -WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) { if (handle == INVALID_HANDLE_VALUE) { return WaitResults::Error; @@ -1108,37 +1109,62 @@ WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProc 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); + std::vector handles; + handles.push_back(handle); + + std::vector exitCodes; + + const auto r = waitForProcesses(handles, exitCodes, uilock); + if (exitCode && !exitCodes.empty()) { + *exitCode = exitCodes[0]; + } + + return r; +} + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + ILockedWaitingForProcess* uilock) +{ + 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( - 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); + 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 '{}' ({}), {}", - processName, pid, formatSystemMessage(e)); + "failed waiting for process completion, {}", formatSystemMessage(e)); return WaitResults::Error; - } else if (res == WAIT_OBJECT_0) { + } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { // completed - log::debug("process '{}' ({}) completed", processName, pid); + exitCodes.resize(handles.size()); + std::fill(exitCodes.begin(), exitCodes.end(), 0); + + for (std::size_t i=0; iunlockForced()) { - log::debug("waiting for process '{}' ({}) aborted by UI", processName, pid); return WaitResults::Unlocked; } } diff --git a/src/spawn.h b/src/spawn.h index 441cad2c..6b947f2f 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -136,6 +136,10 @@ enum class WaitResults WaitResults waitForProcess( HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock); +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + ILockedWaitingForProcess* uilock); + } // namespace diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 311c6dd3..3dba3efc 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "usvfsconnector.h" #include "settings.h" #include "organizercore.h" +#include "envmodule.h" #include "shared/util.h" #include #include @@ -256,3 +257,49 @@ void UsvfsConnector::updateForcedLibraries(const QList getRunningUSVFSProcesses() +{ + std::vector pids; + + { + size_t count = 0; + DWORD* buffer = nullptr; + if (!::GetVFSProcessList2(&count, &buffer)) { + log::error("failed to get usvfs process list"); + return {}; + } + + if (buffer) { + pids.assign(buffer, buffer + count); + std::free(buffer); + } + } + + const auto thisPid = GetCurrentProcessId(); + std::vector v; + + for (auto&& pid : pids) { + if (pid == thisPid) { + continue; // obviously don't wait for MO process + } + + HANDLE handle = ::OpenProcess( + PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pid); + + if (handle == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + + log::warn( + "failed to open usvfs process {}: {}", + pid, formatSystemMessage(e)); + + continue; + } + + v.push_back(handle); + } + + return v; +} diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index d0071678..5982778b 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -103,4 +103,6 @@ private: CrashDumpsType crashDumpsType(int type); +std::vector getRunningUSVFSProcesses(); + #endif // USVFSCONNECTOR_H -- cgit v1.3.1 From 8c72077febaea485200adcf1e9f615902e930def Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 10:03:59 -0400 Subject: replaced uilock by a progress callback in spawn waiting for process now gets the whole process tree to find an interesting process --- src/env.h | 17 ------ src/envmodule.cpp | 107 +++++++++++++++++++++++++++++++++- src/envmodule.h | 35 ++++++++++- src/envsecurity.cpp | 1 + src/envwindows.cpp | 1 + src/ilockedwaitingforprocess.h | 1 + src/lockeddialogbase.cpp | 7 ++- src/lockeddialogbase.h | 4 +- src/organizercore.cpp | 128 ++++++++++++++++++++++++++--------------- src/spawn.cpp | 29 ++++------ src/spawn.h | 7 +-- 11 files changed, 246 insertions(+), 91 deletions(-) (limited to 'src') diff --git a/src/env.h b/src/env.h index 1760c7fe..f95d1013 100644 --- a/src/env.h +++ b/src/env.h @@ -13,23 +13,6 @@ class WindowsInfo; class Metrics; -// used by HandlePtr, calls CloseHandle() as the deleter -// -struct HandleCloser -{ - using pointer = HANDLE; - - void operator()(HANDLE h) - { - if (h != INVALID_HANDLE_VALUE) { - ::CloseHandle(h); - } - } -}; - -using HandlePtr = std::unique_ptr; - - // used by DesktopDCPtr, calls ReleaseDC(0, dc) as the deleter // struct DesktopDCReleaser diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 160a54fa..0e2e8ec7 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -320,19 +320,61 @@ QString Module::getMD5() const } -Process::Process(DWORD pid, QString name) - : m_pid(pid), m_name(std::move(name)) +Process::Process() + : Process(0, 0, {}) { } +Process::Process(HANDLE h) + : Process(::GetProcessId(h), 0, {}) +{ +} + +Process::Process(DWORD pid, DWORD ppid, QString name) + : m_pid(pid), m_ppid(ppid), m_name(std::move(name)) +{ +} + +bool Process::isValid() const +{ + return (m_pid != 0); +} + DWORD Process::pid() const { return m_pid; } +DWORD Process::ppid() const +{ + if (!m_ppid) { + m_ppid = getProcessParentID(m_pid); + } + + return *m_ppid; +} + const QString& Process::name() const { - return m_name; + if (!m_name) { + m_name = getProcessName(m_pid); + } + + return *m_name; +} + +HandlePtr Process::openHandleForWait() const +{ + HandlePtr h(OpenProcess( + PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, m_pid)); + + if (!h) { + const auto e = GetLastError(); + log::error("can't get name of process {}, {}", m_pid, formatSystemMessage(e)); + return {}; + } + + return h; } // whether this process can be accessed; fails if the current process doesn't @@ -353,6 +395,16 @@ bool Process::canAccess() const return true; } +void Process::addChild(Process p) +{ + m_children.push_back(p); +} + +std::vector& Process::children() +{ + return m_children; +} + std::vector getLoadedModules() { @@ -458,6 +510,7 @@ std::vector getRunningProcesses() forEachRunningProcess([&](auto&& entry) { v.push_back(Process( entry.th32ProcessID, + entry.th32ParentProcessID, QString::fromStdWString(entry.szExeFile))); return true; @@ -466,6 +519,54 @@ std::vector getRunningProcesses() return v; } +void findChildren(Process& parent, const std::vector& processes) +{ + for (auto&& p : processes) { + if (p.ppid() == parent.pid()) { + Process child = p; + findChildren(child, processes); + + parent.addChild(child); + } + } +} + +Process getProcessTree(HANDLE parent) +{ + const auto parentPID = ::GetProcessId(parent); + const auto v = getRunningProcesses(); + + Process root; + for (auto&& p : v) { + if (p.pid() == parentPID) { + root = p; + break; + } + } + + if (root.pid() == 0) { + log::error("process {} is not running", parentPID); + return {}; + } + + findChildren(root, v); + + return root; +} + +QString getProcessName(DWORD pid) +{ + HandlePtr h(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + log::error("can't get name of process {}, {}", pid, formatSystemMessage(e)); + return {}; + } + + return getProcessName(h.get()); +} + QString getProcessName(HANDLE process) { const QString badName = "unknown"; diff --git a/src/envmodule.h b/src/envmodule.h index 6c0a028d..d152b840 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -7,6 +7,23 @@ namespace env { +// used by HandlePtr, calls CloseHandle() as the deleter +// +struct HandleCloser +{ + using pointer = HANDLE; + + void operator()(HANDLE h) + { + if (h != INVALID_HANDLE_VALUE) { + ::CloseHandle(h); + } + } +}; + +using HandlePtr = std::unique_ptr; + + // represents one module // class Module @@ -101,25 +118,39 @@ private: class Process { public: - Process(DWORD pid, QString name); + Process(); + explicit Process(HANDLE h); + Process(DWORD pid, DWORD ppid, QString name); + bool isValid() const; DWORD pid() const; + DWORD ppid() const; const QString& name() const; + HandlePtr openHandleForWait() const; + // whether this process can be accessed; fails if the current process doesn't // have the proper permissions // bool canAccess() const; + void addChild(Process p); + std::vector& children(); + private: DWORD m_pid; - QString m_name; + mutable std::optional m_ppid; + mutable std::optional m_name; + std::vector m_children; }; std::vector getRunningProcesses(); std::vector getLoadedModules(); +Process getProcessTree(HANDLE parent); + +QString getProcessName(DWORD pid); QString getProcessName(HANDLE process); DWORD getProcessParentID(DWORD pid); diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 786291c6..6d62728b 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -1,5 +1,6 @@ #include "envsecurity.h" #include "env.h" +#include "envmodule.h" #include #include diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 3932a9b5..98e78a3e 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -1,5 +1,6 @@ #include "envwindows.h" #include "env.h" +#include "envmodule.h" #include #include diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h index 9475ddb9..4d1e786f 100644 --- a/src/ilockedwaitingforprocess.h +++ b/src/ilockedwaitingforprocess.h @@ -8,6 +8,7 @@ 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/lockeddialogbase.cpp b/src/lockeddialogbase.cpp index b18f7429..0876a511 100644 --- a/src/lockeddialogbase.cpp +++ b/src/lockeddialogbase.cpp @@ -18,7 +18,7 @@ along with Mod Organizer. If not, see . */ #include "lockeddialogbase.h" - +#include "envmodule.h" #include #include #include @@ -63,6 +63,11 @@ 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; } diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h index 3c974a38..4ebad4c7 100644 --- a/src/lockeddialogbase.h +++ b/src/lockeddialogbase.h @@ -30,7 +30,7 @@ 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 @@ -46,6 +46,8 @@ public: virtual bool canceled() const; + void setProcessInformation(DWORD pid, const QString& name) override; + protected: virtual void resizeEvent(QResizeEvent *event); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2a97b998..eda64c1d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -34,6 +34,7 @@ #include "instancemanager.h" #include #include "previewdialog.h" +#include "env.h" #include "envmodule.h" #include @@ -85,6 +86,45 @@ 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) @@ -1367,12 +1407,31 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) bool OrganizerCore::waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) { - const auto r = spawn::waitForProcess(handle, exitCode, 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::Unlocked: + case spawn::WaitResults::Cancelled: return true; case spawn::WaitResults::Error: // fall-through @@ -1395,60 +1454,39 @@ bool OrganizerCore::waitForAllUSVFSProcessesWithLock() return r; } -HANDLE getInterestingProcess( - const std::vector& handles, - const std::vector& hidden, DWORD preferedParentPid) -{ - HANDLE best_match = INVALID_HANDLE_VALUE; - bool best_match_hidden = true; - - for (auto handle : handles) { - const QString pname = env::getProcessName(handle); - - bool phidden = false; - for (auto h : hidden) - if (pname.contains(h, Qt::CaseInsensitive)) - phidden = true; - - bool pprefered = preferedParentPid && env::getProcessParentID(handle) == preferedParentPid; - - if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { - best_match = handle; - best_match_hidden = phidden; - } - - if (!phidden && pprefered) - return best_match; - } - - return best_match; -} - 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()); - for (;;) { const auto handles = getRunningUSVFSProcesses(); if (handles.empty()) { break; } - const auto interesting = getInterestingProcess( - handles, hiddenList, GetCurrentProcessId()); + 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) { - const DWORD pid = ::GetProcessId(interesting); - const QString processName = QString("%1 (%2)") - .arg(env::getProcessName(interesting)) - .arg(pid); + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } - uilock->setProcessName(processName); + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + break; } - const auto r = spawn::waitForProcess(interesting, nullptr, uilock); + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = spawn::waitForProcess( + interestingHandle.get(), nullptr, progress); switch (r) { @@ -1456,7 +1494,7 @@ bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) // this process is completed, check for others break; - case spawn::WaitResults::Unlocked: + case spawn::WaitResults::Cancelled: // force unlocked log::debug("waiting for process completion aborted by UI"); return true; @@ -1468,7 +1506,7 @@ bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) } } - log::debug("Waiting for process completion successful"); + log::debug("waiting for process completion successful"); return true; } diff --git a/src/spawn.cpp b/src/spawn.cpp index 1003024f..0ea60641 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1095,32 +1095,25 @@ FileExecutionContext getFileExecutionContext( } WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) + HANDLE handle, DWORD* exitCode, std::function progress) { 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); - - log::debug( - "waiting for process completion '{}' ({})", - processName, pid); + 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, uilock); - if (exitCode && !exitCodes.empty()) { - *exitCode = exitCodes[0]; + const auto r = waitForProcesses(handles, exitCodes, progress); + + if (r == WaitResults::Completed) { + if (exitCode && !exitCodes.empty()) { + *exitCode = exitCodes[0]; + } } return r; @@ -1128,7 +1121,7 @@ WaitResults waitForProcess( WaitResults waitForProcesses( const std::vector& handles, std::vector& exitCodes, - ILockedWaitingForProcess* uilock) + std::function progress) { if (handles.empty()) { return WaitResults::Completed; @@ -1175,8 +1168,8 @@ WaitResults waitForProcesses( QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); - if (uilock && uilock->unlockForced()) { - return WaitResults::Unlocked; + if (progress && progress()) { + return WaitResults::Cancelled; } } } diff --git a/src/spawn.h b/src/spawn.h index 6b947f2f..ad50bde6 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,7 +27,6 @@ along with Mod Organizer. If not, see . #include class Settings; -class ILockedWaitingForProcess; namespace MOBase { class IPluginGame; } @@ -130,15 +129,15 @@ enum class WaitResults { Completed = 1, Error, - Unlocked + Cancelled }; WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock); + HANDLE handle, DWORD* exitCode, std::function progress); WaitResults waitForProcesses( const std::vector& handles, std::vector& exitCodes, - ILockedWaitingForProcess* uilock); + std::function progress); } // 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') 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 b05cbeed900fbb3492e3cb5b624f5d9efaa288ea Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 12:08:13 -0400 Subject: refactored waiting into waitForProcesses() --- src/processrunner.cpp | 276 +++++++++++++++++++++----------------------------- src/processrunner.h | 15 --- 2 files changed, 116 insertions(+), 175 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 81c4b99e..dbcee3ab 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -8,45 +8,62 @@ using namespace MOBase; -void adjustForVirtualized( - const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) +enum class WaitResults { - const QString modsPath = settings.paths().mods(); + Completed = 1, + Error, + Cancelled +}; - // 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; - } +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::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; + log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + + std::vector handles; + handles.push_back(handle); + + std::vector exitCodes; + + 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, {}", formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res == 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; } - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + if (progress && progress()) { + return WaitResults::Cancelled; + } } } @@ -84,11 +101,32 @@ env::Process* getInterestingProcess(std::vector& processes) } } - // everything is hidden, just pick the first one return &processes[0]; } +WaitResults waitForProcesses( + std::vector& processes, + LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + return WaitResults::Error; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + return WaitResults::Error; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + return waitForProcess(interestingHandle.get(), exitCode, progress); +} + SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) : m_handle(handle), m_parameters(std::move(sp)) @@ -358,6 +396,48 @@ HANDLE ProcessRunner::spawnAndWait( return handle; } +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()); + } +} + SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp) { QWidget* parent = nullptr; @@ -451,34 +531,8 @@ bool ProcessRunner::waitForProcessCompletion( 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; - } + const auto r = waitForProcesses(processes, exitCode, uilock); + return (r != WaitResults::Error); } bool ProcessRunner::waitForAllUSVFSProcessesWithLock() @@ -511,23 +565,7 @@ bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) } } - 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); + const auto r = waitForProcesses(processes, nullptr, uilock); switch (r) { @@ -550,85 +588,3 @@ bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) 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 progress); - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress); - #endif // PROCESSRUNNER_H -- cgit v1.3.1 From b5a5ea1b4d97d79fe6f4ff8a2da2e0120936b179 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 13:17:44 -0400 Subject: recheck the process tree if the current process is not that interesting --- src/processrunner.cpp | 223 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 165 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index dbcee3ab..4e1dda46 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -12,38 +12,27 @@ enum class WaitResults { Completed = 1, Error, - Cancelled + Cancelled, + StillRunning }; -WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, std::function progress) +WaitResults singleWait(HANDLE handle, DWORD* exitCode) { if (handle == INVALID_HANDLE_VALUE) { return WaitResults::Error; } - log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + const DWORD WAIT_EVENT = WAIT_OBJECT_0 + 1; - std::vector handles; - handles.push_back(handle); + // 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); - std::vector exitCodes; - - 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, {}", formatSystemMessage(e)); - - return WaitResults::Error; - } else if (res == WAIT_OBJECT_0) { + switch (res) + { + case WAIT_OBJECT_0: + { // completed if (exitCode) { if (!::GetExitCodeProcess(handle, exitCode)) { @@ -57,20 +46,55 @@ WaitResults waitForProcess( return WaitResults::Completed; } - // keep processing events so the app doesn't appear dead - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); + case WAIT_TIMEOUT: + case WAIT_EVENT: + { + return WaitResults::StillRunning; + } - if (progress && progress()) { - return WaitResults::Cancelled; + case WAIT_FAILED: // fall-through + default: + { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion, {}", formatSystemMessage(e)); + + return WaitResults::Error; } } } -env::Process* getInterestingProcess(std::vector& processes) +enum class Interest +{ + None = 0, + Weak, + Strong +}; + +QString toString(Interest i) +{ + switch (i) + { + case Interest::Weak: + return "weak"; + + case Interest::Strong: + return "strong"; + + case Interest::None: // fall-through + default: + return "no"; + } +} + + +std::pair findInterestingProcessInTrees( + std::vector& processes) { if (processes.empty()) { - return nullptr; + return {{}, Interest::None}; } // Certain process names we wish to "hide" for aesthetic reason: @@ -91,40 +115,132 @@ env::Process* getInterestingProcess(std::vector& processes) for (auto&& root : processes) { if (!isHidden(root)) { - return &root; + return {root, Interest::Strong}; } for (auto&& child : root.children()) { if (!isHidden(child)) { - return &child; + return {child, Interest::Strong}; } } } // everything is hidden, just pick the first one - return &processes[0]; + return {processes[0], Interest::Weak}; } -WaitResults waitForProcesses( - std::vector& processes, - LPDWORD exitCode, ILockedWaitingForProcess* uilock) +std::pair getInterestingProcess( + const std::vector& initialProcesses) { - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - return WaitResults::Error; + std::vector processes; + + log::debug("getting process tree for {} processes", initialProcesses.size()); + for (auto&& h : initialProcesses) { + auto tree = env::getProcessTree(h); + if (tree.isValid()) { + processes.push_back(tree); + } } - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); + if (processes.empty()) { + log::debug("nothing to wait for"); + return {{}, Interest::None}; } - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - return WaitResults::Error; + const auto interest = findInterestingProcessInTrees(processes); + if (!interest.first.isValid()) { + log::debug("no interesting process to wait for"); + return {{}, Interest::None}; } - auto progress = [&]{ return uilock->unlockForced(); }; - return waitForProcess(interestingHandle.get(), exitCode, progress); + return interest; +} + +const std::chrono::milliseconds Infinite(-1); + +WaitResults timedWait( + HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock, + std::chrono::milliseconds wait) +{ + using namespace std::chrono; + + high_resolution_clock::time_point start; + if (wait != Infinite) { + start = high_resolution_clock::now(); + } + + for (;;) { + const auto r = singleWait(handle, exitCode); + + if (r != WaitResults::StillRunning) { + return r; + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); + + if (uilock && uilock->unlockForced()) { + return WaitResults::Cancelled; + } + + if (wait != Infinite) { + const auto now = high_resolution_clock::now(); + if (duration_cast(now - start) >= wait) { + return WaitResults::StillRunning; + } + } + } +} + +WaitResults waitForProcesses( + const std::vector& initialProcesses, + LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + using namespace std::chrono; + + if (initialProcesses.empty()) { + return WaitResults::Completed; + } + + DWORD currentPID = 0; + milliseconds wait(50); + + for (;;) { + auto [p, interest] = getInterestingProcess(initialProcesses); + + if (uilock) { + uilock->setProcessInformation(p.pid(), p.name()); + } + + auto interestingHandle = p.openHandleForWait(); + if (!interestingHandle) { + return WaitResults::Error; + } + + if (p.pid() != currentPID) { + currentPID = p.pid(); + + log::debug( + "waiting for completion on {} ({}), {} interest", + p.name(), p.pid(), toString(interest)); + } + + if (interest == Interest::Strong) { + wait = Infinite; + } + + const auto r = timedWait(interestingHandle.get(), exitCode, uilock, wait); + if (r != WaitResults::StillRunning) { + return r; + } + + wait = std::min(wait * 2, milliseconds(2000)); + + log::debug( + "looking for a more interesting process (next check in {}ms)", + wait.count()); + } } @@ -528,10 +644,9 @@ bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) bool ProcessRunner::waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) { - const auto tree = env::getProcessTree(handle); - std::vector processes = {tree}; - + std::vector processes = {handle}; const auto r = waitForProcesses(processes, exitCode, uilock); + return (r != WaitResults::Error); } @@ -552,19 +667,11 @@ bool ProcessRunner::waitForAllUSVFSProcessesWithLock() bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) { for (;;) { - const auto handles = getRunningUSVFSProcesses(); - if (handles.empty()) { + const auto processes = getRunningUSVFSProcesses(); + if (processes.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 r = waitForProcesses(processes, nullptr, uilock); switch (r) -- cgit v1.3.1 From f2cc71779feab647c508084c5dd5c175904c7f0f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 13:40:45 -0400 Subject: always wait until completion in waitForApplication(), regardless of lock setting --- src/processrunner.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 4e1dda46..014f98ee 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -592,8 +592,7 @@ void ProcessRunner::withLock(std::function f) if (m_ui != nullptr) { uilock = m_ui->lock(); - } - else { + } else { // i.e. when running command line shortcuts there is no user interface dlg.reset(new LockedDialog); dlg->show(); @@ -614,14 +613,14 @@ bool ProcessRunner::waitForProcessCompletionWithLock( HANDLE handle, LPDWORD exitCode) { if (!Settings::instance().interface().lockGUI()) { + log::debug("not waiting for process because user has disabled locking"); return true; } bool r = false; withLock([&](auto* uilock) { - DWORD ignoreExitCode; - r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + r = waitForProcessCompletion(handle, exitCode, uilock); }); return r; @@ -629,8 +628,14 @@ bool ProcessRunner::waitForProcessCompletionWithLock( bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) { - if (!Settings::instance().interface().lockGUI()) - return true; + // don't check for lockGUI() setting; this _always_ locks the ui + // + // this is typically called only from 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 bool r = false; @@ -652,8 +657,10 @@ bool ProcessRunner::waitForProcessCompletion( bool ProcessRunner::waitForAllUSVFSProcessesWithLock() { - if (!Settings::instance().interface().lockGUI()) + if (!Settings::instance().interface().lockGUI()) { + log::debug("not waiting for usvfs processes because user has disabled locking"); return true; + } bool r = false; -- cgit v1.3.1 From 2aa70de49e89245467299d94d76825bad31c63a2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 13:46:33 -0400 Subject: return failure if the user has unlocked in waitForApplication() --- src/processrunner.cpp | 34 ++++++++++++++++++---------------- src/processrunner.h | 3 --- 2 files changed, 18 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 014f98ee..5d4a9fde 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -243,6 +243,14 @@ WaitResults waitForProcesses( } } +WaitResults waitForProcess( + HANDLE initialProcess, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + std::vector processes = {initialProcess}; + return waitForProcesses(processes, exitCode, uilock); +} + + SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) : m_handle(handle), m_parameters(std::move(sp)) @@ -617,18 +625,20 @@ bool ProcessRunner::waitForProcessCompletionWithLock( return true; } - bool r = false; + auto r = WaitResults::Error; withLock([&](auto* uilock) { - r = waitForProcessCompletion(handle, exitCode, uilock); + r = waitForProcess(handle, exitCode, uilock); }); - return r; + // completed/unlocked is fine + return (r != WaitResults::Error); } bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) { - // don't check for lockGUI() setting; this _always_ locks the ui + // 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 @@ -637,22 +647,14 @@ bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) // and then check the exit code; this has to work regardless of the locking // setting - bool r = false; + auto r = WaitResults::Error; withLock([&](auto* uilock) { - r = waitForProcessCompletion(handle, exitCode, uilock); + r = waitForProcess(handle, exitCode, uilock); }); - return r; -} - -bool ProcessRunner::waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) -{ - std::vector processes = {handle}; - const auto r = waitForProcesses(processes, exitCode, uilock); - - return (r != WaitResults::Error); + // treat unlocked as an error since this should always wait for completion + return (r == WaitResults::Completed); } bool ProcessRunner::waitForAllUSVFSProcessesWithLock() diff --git a/src/processrunner.h b/src/processrunner.h index d9423893..28f4da75 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,9 +80,6 @@ private: bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); - bool waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); - bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); }; -- 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') 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') 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') 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') 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 b855754c9708c825e6bc1e995cb0262c065c5d20 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:18:21 -0400 Subject: removed runShortcut() changed lock widget text when running without a ui --- src/lockwidget.cpp | 16 ++++++++++++---- src/main.cpp | 6 +++++- src/organizercore.cpp | 8 ++++++-- src/processrunner.cpp | 9 --------- src/processrunner.h | 2 -- 5 files changed, 23 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index ad9aefe3..cc66112e 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -107,8 +107,16 @@ void LockWidget::createUi(Reasons reason) { case LockUI: { - message->setText(QObject::tr( - "Mod Organizer is locked while the executable is running.")); + QString s; + + if (!m_parent) { + s = QObject::tr("Mod Organizer is currently running an application."); + } else { + s = QObject::tr( + "Mod Organizer is locked while the application is running."); + } + + message->setText(s); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); @@ -120,7 +128,7 @@ void LockWidget::createUi(Reasons reason) case OutputRequired: { message->setText(QObject::tr( - "The executable must run to completion because its output is " + "The application must run to completion because its output is " "required.")); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); @@ -133,7 +141,7 @@ void LockWidget::createUi(Reasons reason) case PreventExit: { message->setText(QObject::tr( - "Mod Organizer is waiting on processes to finish before exiting.")); + "Mod Organizer is waiting on application to close before exiting.")); auto* exit = new QPushButton(QObject::tr("Exit Now")); QObject::connect(exit, &QPushButton::clicked, [&]{ onForceUnlock(); }); diff --git a/src/main.cpp b/src/main.cpp index 5ed7da5d..9decb94e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -632,7 +632,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (MOShortcut shortcut{ arguments.at(1) }) { if (shortcut.hasExecutable()) { try { - organizer.processRunner().runShortcut(shortcut); + organizer.processRunner() + .setFromShortcut(shortcut) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + return 0; } catch (const std::exception &e) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 89e8bd9e..57e24f3b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -354,8 +354,12 @@ void OrganizerCore::downloadRequestedNXM(const QString &url) void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { - if(moshortcut.hasExecutable()) - processRunner().runShortcut(moshortcut); + if(moshortcut.hasExecutable()) { + processRunner() + .setFromShortcut(moshortcut) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + } } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 1c8c923b..6ca05147 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -662,15 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runShortcut(const MOShortcut& shortcut) -{ - setFromShortcut(shortcut); - setWaitForCompletion(NoRefresh); - - const auto r = run(); - return (r != Error); -} - HANDLE ProcessRunner::runExecutableOrExecutableFile( const QString& executable, const QStringList &args, const QString &cwd, const QString& profileOverride, const QString &forcedCustomOverwrite, diff --git a/src/processrunner.h b/src/processrunner.h index d3437412..4860be7c 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,8 +80,6 @@ public: DWORD exitCode(); - bool runShortcut(const MOShortcut& shortcut); - HANDLE runExecutableOrExecutableFile( const QString &executable, const QStringList &args, -- cgit v1.3.1 From a88f9dd9a703c23263b5561d3cae126e90e36f3f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:52:14 -0400 Subject: removed runExecutableOrExecutableFile() log the actual spawning and requests to start as well as wait from plugins raise the lock widget when it's a dialog --- src/lockwidget.cpp | 5 +++++ src/main.cpp | 16 +++++++++++----- src/organizerproxy.cpp | 32 ++++++++++++++++++++++++++------ src/processrunner.cpp | 14 ++------------ src/processrunner.h | 19 +++++-------------- src/spawn.cpp | 44 ++++++++++++++++++++++++++++++++++---------- 6 files changed, 83 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index cc66112e..7b6e8430 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -183,6 +183,11 @@ void LockWidget::createUi(Reasons reason) m_overlay->setFocus(); m_overlay->show(); m_overlay->setEnabled(true); + + if (!overlayTarget) { + m_overlay->raise(); + m_overlay->activateWindow(); + } } void LockWidget::onForceUnlock() diff --git a/src/main.cpp b/src/main.cpp index 9decb94e..2b9a2f4d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -653,15 +653,21 @@ int runApplication(MOApplication &application, SingleInstance &instance, else { QString exeName = arguments.at(1); log::debug("starting {} from command line", exeName); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.processRunner().runExecutableOrExecutableFile( - exeName, arguments, QString(), QString()); + + try + { + // pass the remaining parameters to the binary + organizer.processRunner() + .setFromFileOrExecutable(exeName, arguments) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); return 0; } - catch (const std::exception &e) { + catch (const std::exception &e) + { reportError( QObject::tr("failed to start application: %1").arg(e.what())); return 1; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 3ee35fe2..9de5b4ba 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -108,17 +108,37 @@ QString OrganizerProxy::pluginDataPath() const } HANDLE OrganizerProxy::startApplication( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) + const QString& exe, const QStringList& args, const QString &cwd, + const QString& profile, const QString &overwrite, bool ignoreOverwrite) { - return m_Proxied->processRunner().runExecutableOrExecutableFile( - executable, args, cwd, profile, - forcedCustomOverwrite, ignoreCustomOverwrite); + log::debug( + "a plugin has requested to start an application:\n" + " . executable: '{}'\n" + " . args: '{}'\n" + " . cwd: '{}'\n" + " . profile: '{}'\n" + " . overwrite: '{}'\n" + " . ignore overwrite: {}", + exe, args.join(" "), cwd, profile, overwrite, ignoreOverwrite); + + auto runner = m_Proxied->processRunner(); + + runner + .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); + + return runner.processHandle(); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { + const auto pid = ::GetProcessId(handle); + + log::debug( + "a plugin wants to wait for an application to complete, pid {}{}", + pid, (pid == 0 ? "unknown (probably already completed)" : "")); + const auto r = m_Proxied->processRunner().waitForApplication( handle, exitCode, LockWidget::OutputRequired); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 6ca05147..e6f916c5 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -661,22 +661,12 @@ DWORD ProcessRunner::exitCode() return m_exitCode; } - -HANDLE ProcessRunner::runExecutableOrExecutableFile( - const QString& executable, const QStringList &args, const QString &cwd, - const QString& profileOverride, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) +HANDLE ProcessRunner::processHandle() { - setFromFileOrExecutable( - executable, args, cwd, profileOverride, forcedCustomOverwrite, - ignoreCustomOverwrite); - - setWaitForCompletion(Refresh); - - run(); return m_handle; } + void ProcessRunner::withLock( LockWidget::Reasons reason, std::function f) { diff --git a/src/processrunner.h b/src/processrunner.h index 4860be7c..bdd0b260 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -71,23 +71,14 @@ public: ProcessRunner& setFromFileOrExecutable( const QString &executable, const QStringList &args, - const QString &cwd, - const QString &profile, - const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); + const QString &cwd={}, + const QString &profile={}, + const QString &forcedCustomOverwrite={}, + bool ignoreCustomOverwrite=false); Results run(); DWORD exitCode(); - - - HANDLE runExecutableOrExecutableFile( - const QString &executable, - const QStringList &args, - const QString &cwd, - const QString &profile, - const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); - + HANDLE processHandle(); Results waitForApplication( HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); diff --git a/src/spawn.cpp b/src/spawn.cpp index c8d7c76a..b331db01 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -442,6 +442,25 @@ QMessageBox::StandardButton confirmBlacklisted( namespace spawn { +void logSpawning(const SpawnParameters& sp, const QString& realCmd) +{ + log::debug( + "spawning binary:\n" + " . exe: '{}'\n" + " . args: '{}'\n" + " . cwd: '{}'\n" + " . steam id: '{}'\n" + " . hooked: {}\n" + " . stdout: {}\n" + " . stderr: {}\n" + " . real cmd: '{}'", + sp.binary.absoluteFilePath(), sp.arguments, + sp.currentDirectory.absolutePath(), sp.steamAppID, sp.hooked, + (sp.stdOut == INVALID_HANDLE_VALUE ? "no" : "yes"), + (sp.stdErr == INVALID_HANDLE_VALUE ? "no" : "yes"), + realCmd); +} + DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) { BOOL inheritHandles = FALSE; @@ -462,30 +481,35 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) si.dwFlags |= STARTF_USESTDHANDLES; } - const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); - const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); + const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()); + const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()); - std::wstring commandLine = L"\"" + bin + L"\""; - if (sp.arguments[0] != L'\0') { - commandLine += L" " + sp.arguments.toStdWString(); + QString commandLine = "\"" + bin + "\""; + if (!sp.arguments.isEmpty()) { + commandLine += " " + sp.arguments; } - QString moPath = QCoreApplication::applicationDirPath(); + const QString moPath = QCoreApplication::applicationDirPath(); const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath)); PROCESS_INFORMATION pi; BOOL success = FALSE; + logSpawning(sp, commandLine); + + const auto wcommandLine = commandLine.toStdWString(); + const auto wcwd = cwd.toStdWString(); + if (sp.hooked) { success = ::CreateProcessHooked( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); + wcwd.c_str(), &si, &pi); } else { success = ::CreateProcess( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); + wcwd.c_str(), &si, &pi); } const auto e = GetLastError(); -- cgit v1.3.1 From d72e94a92f31bcc720d12ed0cb2cc75b590e6770 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 03:54:18 -0400 Subject: added attachToProcess(), made waitForApplication() private changed OrganizerProxy::startApplication() so it _doesn't_ wait for completion, which is its original behaviour --- src/organizerproxy.cpp | 13 ++++++++++--- src/processrunner.cpp | 37 +++++++++++++++++++++++++++---------- src/processrunner.h | 19 ++++++------------- 3 files changed, 43 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 9de5b4ba..0b6f8df1 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -123,9 +123,9 @@ HANDLE OrganizerProxy::startApplication( auto runner = m_Proxied->processRunner(); + // don't wait for completion runner .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) - .setWaitForCompletion(ProcessRunner::Refresh) .run(); return runner.processHandle(); @@ -139,8 +139,15 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const "a plugin wants to wait for an application to complete, pid {}{}", pid, (pid == 0 ? "unknown (probably already completed)" : "")); - const auto r = m_Proxied->processRunner().waitForApplication( - handle, exitCode, LockWidget::OutputRequired); + auto runner = m_Proxied->processRunner(); + + const auto r = runner + .setWaitForCompletion(ProcessRunner::NoRefresh, LockWidget::OutputRequired) + .attachToProcess(handle); + + if (exitCode) { + *exitCode = runner.exitCode(); + } switch (r) { diff --git a/src/processrunner.cpp b/src/processrunner.cpp index e6f916c5..b732204c 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -593,9 +593,15 @@ ProcessRunner::Results ProcessRunner::run() return Error; } - // 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(); + + // 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) { + return Running; + } } else { if (m_profileName.isEmpty()) { const auto* profile = m_core.currentProfile(); @@ -642,18 +648,29 @@ ProcessRunner::Results ProcessRunner::run() } } - if (m_handle == INVALID_HANDLE_VALUE || m_lock == LockWidget::NoReason) { + return postRun(); +} + +ProcessRunner::Results ProcessRunner::postRun() +{ + if (m_lock == LockWidget::NoReason) { return Running; - } else { - const auto r = waitForProcessCompletionWithLock( - m_handle, &m_exitCode, m_lock); + } - if (r == Completed && m_refresh == Refresh) { - m_core.afterRun(m_sp.binary, m_exitCode); - } + const auto r = waitForProcessCompletionWithLock( + m_handle, &m_exitCode, m_lock); - return r; + if (r == Completed && m_refresh == Refresh) { + m_core.afterRun(m_sp.binary, m_exitCode); } + + return r; +} + +ProcessRunner::Results ProcessRunner::attachToProcess(HANDLE h) +{ + m_handle = h; + return postRun(); } DWORD ProcessRunner::exitCode() diff --git a/src/processrunner.h b/src/processrunner.h index bdd0b260..62ec7a9e 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -77,12 +77,11 @@ public: bool ignoreCustomOverwrite=false); Results run(); + Results attachToProcess(HANDLE h); + DWORD exitCode(); HANDLE processHandle(); - Results waitForApplication( - HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); - Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); private: @@ -98,16 +97,7 @@ private: HANDLE m_handle; DWORD m_exitCode; - 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); + Results postRun(); void withLock( LockWidget::Reasons reason, std::function f); @@ -115,6 +105,9 @@ private: Results waitForProcessCompletionWithLock( HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason); + Results waitForApplication( + HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); + Results waitForAllUSVFSProcesses(LockWidget& lock); }; -- 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') 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') 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 7db5f938841933fb4846441448dafefc414ed5e7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:33:53 -0400 Subject: removed unused SpawnedProcess lock widget now interprets closing the dialog as a forced unlock --- src/lockwidget.cpp | 1 + src/lockwidget.h | 5 +++++ src/processrunner.cpp | 47 ----------------------------------------------- src/processrunner.h | 25 +++---------------------- 4 files changed, 9 insertions(+), 69 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 7b6e8430..bbffd390 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -176,6 +176,7 @@ void LockWidget::createUi(Reasons reason) if (overlayTarget) { m_filter.reset(new Filter); m_filter->resized = [=]{ m_overlay->setGeometry(overlayTarget->rect()); }; + m_filter->closed = [=]{ onForceUnlock(); }; overlayTarget->installEventFilter(m_filter.get()); } diff --git a/src/lockwidget.h b/src/lockwidget.h index 9c555ac9..18ff76dd 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -35,6 +35,7 @@ private: { public: std::function resized; + std::function closed; protected: bool eventFilter(QObject* o, QEvent* e) override @@ -43,6 +44,10 @@ private: if (resized) { resized(); } + } else if (e->type() == QEvent::Close) { + if (closed) { + closed(); + } } return QObject::eventFilter(o, e); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 9cc2ce52..58b2a771 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -320,53 +320,6 @@ ProcessRunner::Results waitForProcess( } - -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, IUserInterface* ui) : m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) diff --git a/src/processrunner.h b/src/processrunner.h index 41c0f12c..0fb3f59a 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -11,28 +11,9 @@ 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(); -}; - - +// handles spawning a process and waiting for it, including setting up the lock +// widget if required +// class ProcessRunner { public: -- 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') 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') 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 From ada2ac0cb5d0ef2039ce55794928c4d05255ed65 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 07:37:24 -0400 Subject: removed redundant checkBinary(), which used to check for non existing binaries, that's handled when spawning removed useless checkEnvironment(), which checked for EventLog removed CREATE_BREAKAWAY_FROM_JOB from CreateProcess() calls, didn't do anything fixed setFromFileOrExecutable() when running just a filename that's not an executable name split run() fixed lock widget being disabled when running without a ui --- src/lockwidget.cpp | 12 ++- src/processrunner.cpp | 197 ++++++++++++++++++++++++++++++++------------------ src/processrunner.h | 76 ++++++++++++++++++- src/spawn.cpp | 43 +---------- src/spawn.h | 4 - 5 files changed, 212 insertions(+), 120 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index bbffd390..35d51dab 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -229,10 +229,14 @@ void LockWidget::disableAll() } 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); + // don't disable stuff if this dialog is the overlay, which happens when + // there's no ui + if (d != m_overlay.get()) { + // no central widget, just disable the children, except for the overlay + for (auto* child : findChildrenImmediate(d)) { + if (child != m_overlay.get()) { + disable(child); + } } } } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 58b2a771..220ffe38 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -111,6 +111,9 @@ QString toString(Interest i) } +// returns a process that's in the hidden list, or the top-level process if +// they're all hidden; returns an invalid process if the list is empty +// std::pair findInterestingProcessInTrees( std::vector& processes) { @@ -150,6 +153,8 @@ std::pair findInterestingProcessInTrees( return {processes[0], Interest::Weak}; } +// gets the most interesting process in the list +// std::pair getInterestingProcess( const std::vector& initialProcesses) { @@ -160,7 +165,7 @@ std::pair getInterestingProcess( std::vector processes; - log::debug("getting process tree for {} processes", initialProcesses.size()); + // getting process trees for all processes for (auto&& h : initialProcesses) { auto tree = env::getProcessTree(h); if (tree.isValid()) { @@ -169,12 +174,15 @@ std::pair getInterestingProcess( } if (processes.empty()) { + // if the initial list wasn't empty but this one is, it means all the + // processes were already completed log::debug("processes are already completed"); return {{}, Interest::None}; } const auto interest = findInterestingProcessInTrees(processes); if (!interest.first.isValid()) { + // this shouldn't happen log::debug("no interesting process to wait for"); return {{}, Interest::None}; } @@ -184,6 +192,8 @@ std::pair getInterestingProcess( const std::chrono::milliseconds Infinite(-1); +// waits for completion, times out after `wait` if not Infinite +// std::optional timedWait( HANDLE handle, DWORD pid, LockWidget& lock, std::chrono::milliseconds wait) { @@ -195,18 +205,21 @@ std::optional timedWait( } for (;;) { + // wait for a very short while, allows for processing events below const auto r = singleWait(handle, pid); if (r) { + // the process has either completed or an error was returned return *r; } - // still running + // the process is still running // keep processing events so the app doesn't appear dead QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); + // check the lock widget switch (lock.result()) { case LockWidget::StillLocked: @@ -239,8 +252,10 @@ std::optional timedWait( } if (wait != Infinite) { + // check if enough time has elapsed const auto now = high_resolution_clock::now(); if (duration_cast(now - start) >= wait) { + // if so, return an empty result return {}; } } @@ -258,6 +273,10 @@ ProcessRunner::Results waitForProcesses( } DWORD currentPID = 0; + + // if the interesting process that was found is weak (such as ModOrganizer.exe + // when starting a program from within the Data directory), start with a short + // wait and check for more interesting children milliseconds wait(50); for (;;) { @@ -267,14 +286,17 @@ ProcessRunner::Results waitForProcesses( return ProcessRunner::Completed; } + // update the lock widget lock.setInfo(p.pid(), p.name()); + // open the process auto interestingHandle = p.openHandleForWait(); if (!interestingHandle) { return ProcessRunner::Error; } if (p.pid() != currentPID) { + // log any change in the process being waited for currentPID = p.pid(); log::debug( @@ -283,19 +305,19 @@ ProcessRunner::Results waitForProcesses( } if (interest == Interest::Strong) { + // don't bother with short wait, this is a good process to wait for wait = Infinite; } const auto r = timedWait(interestingHandle.get(), p.pid(), lock, wait); if (r) { + // the process has completed or returned an error return *r; } + // exponentially increase the wait time between checks for interesting + // processes wait = std::min(wait * 2, milliseconds(2000)); - - log::debug( - "looking for a more interesting process (next check in {}ms)", - wait.count()); } } @@ -324,6 +346,7 @@ ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { + // all processes started in ProcessRunner are hooked m_sp.hooked = true; } @@ -383,6 +406,9 @@ ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targ parent = m_ui->qtWidget(); } + // if the file is a .exe, start it directory; if it's anything else, ask the + // shell to start it + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); switch (fec.type) @@ -466,28 +492,25 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( throw MyException(QObject::tr("No profile set")); } + setBinary(executable); + setArguments(args.join(" ")); + setCurrentDirectory(cwd); setProfileName(profileOverride); if (executable.contains('\\') || executable.contains('/')) { // file path - auto binary = QFileInfo(executable); - - if (binary.isRelative()) { + if (m_sp.binary.isRelative()) { // relative path, should be relative to game directory - binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable); + setBinary(m_core.managedGame()->gameDirectory().absoluteFilePath(executable)); } - setBinary(binary); - if (cwd == "") { - setCurrentDirectory(binary.absolutePath()); - } else { - setCurrentDirectory(cwd); + setCurrentDirectory(m_sp.binary.absolutePath()); } try { - const Executable& exe = m_core.executablesList()->getByBinary(binary); + const Executable& exe = m_core.executablesList()->getByBinary(m_sp.binary); setSteamID(exe.steamAppID()); setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString()); @@ -512,20 +535,15 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( if (args.isEmpty()) { setArguments(exe.arguments()); - } else { - setArguments(args.join(" ")); } setBinary(exe.binaryInfo()); if (cwd == "") { setCurrentDirectory(exe.workingDirectory()); - } else { - setCurrentDirectory(cwd); } } catch (const std::runtime_error &) { log::warn("\"{}\" not set up as executable", executable); - setBinary(QFileInfo(executable)); } } @@ -540,68 +558,91 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( ProcessRunner::Results ProcessRunner::run() { + std::optional r; + if (!m_shellOpen.isEmpty()) { - auto r = shell::Open(m_shellOpen); - if (!r.success()) { - return Error; - } + r = runShell(); + } else { + r = runBinary(); + } - m_handle.reset(r.stealProcessHandle()); + if (r) { + // early result: something went wrong and the process cannot be waited for + return *r; + } - // 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.get() == INVALID_HANDLE_VALUE) { - return Running; - } - } else { - if (m_profileName.isEmpty()) { - const auto* profile = m_core.currentProfile(); - if (!profile) { - throw MyException(QObject::tr("No profile set")); - } + return postRun(); +} - m_profileName = profile->name(); - } +std::optional ProcessRunner::runShell() +{ + log::debug("executing from shell: '{}'", m_shellOpen); - if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) { - return Error; - } + auto r = shell::Open(m_shellOpen); + if (!r.success()) { + return Error; + } - QWidget* parent = nullptr; - if (m_ui) { - parent = m_ui->qtWidget(); - } + m_handle.reset(r.stealProcessHandle()); - if (!checkBinary(parent, m_sp)) { - return Error; - } + // 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.get() == INVALID_HANDLE_VALUE) { + log::debug("shell didn't report an error, but no handle is available"); + return Running; + } - const auto* game = m_core.managedGame(); - auto& settings = m_core.settings(); + return {}; +} - if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) { - return Error; +std::optional ProcessRunner::runBinary() +{ + if (m_profileName.isEmpty()) { + // get the current profile name if it wasn't overridden + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); } - if (!checkEnvironment(parent, m_sp)) { - return Error; - } + m_profileName = profile->name(); + } - if (!checkBlacklist(parent, m_sp, settings)) { - return Error; - } + // saves profile, sets up usvfs, notifies plugins, etc.; can return false if + // a plugin doesn't want the program to run (such as when checkFNIS fails to + // run FNIS and the user clicks cancel) + if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) { + return Error; + } - adjustForVirtualized(game, m_sp, settings); + // parent widget used for any dialog popped up while checking for things + QWidget* parent = (m_ui ? m_ui->qtWidget() : nullptr); - m_handle.reset(startBinary(parent, m_sp)); - if (m_handle.get() == INVALID_HANDLE_VALUE) { - return Error; - } + const auto* game = m_core.managedGame(); + auto& settings = m_core.settings(); + + // start steam if needed + if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) { + return Error; } - return postRun(); + // warn if the executable is on the blacklist + if (!checkBlacklist(parent, m_sp, settings)) { + return Error; + } + + // if the executable is inside the mods folder another instance of + // ModOrganizer.exe is spawned instead to launch it + adjustForVirtualized(game, m_sp, settings); + + // run the binary + m_handle.reset(startBinary(parent, m_sp)); + if (m_handle.get() == INVALID_HANDLE_VALUE) { + return Error; + } + + return {}; } ProcessRunner::Results ProcessRunner::postRun() @@ -618,7 +659,7 @@ ProcessRunner::Results ProcessRunner::postRun() } if (mustWait) { - if (!Settings::instance().interface().lockGUI()) { + if (!m_core.settings().interface().lockGUI()) { // at least tell the user what's going on log::debug( "locking is disabled, but the output of the application is required; " @@ -632,7 +673,7 @@ ProcessRunner::Results ProcessRunner::postRun() return Running; } - if (!Settings::instance().interface().lockGUI()) { + if (!m_core.settings().interface().lockGUI()) { // disabling locking is like clicking on unlock immediately log::debug("not waiting for process because locking is disabled"); return ForceUnlocked; @@ -646,6 +687,18 @@ ProcessRunner::Results ProcessRunner::postRun() }); if (r == Completed && (m_waitFlags & Refresh)) { + // afterRun() is only called with the Refresh flag; it refreshes the + // directory structure and notifies plugins + // + // refreshing is not always required and can actually cause problems: + // + // 1) running shortcuts doesn't need refreshing because MO closes right + // after + // + // 2) the mod info dialog is not set up to deal with refreshes, so that + // it will crash because the old DirectoryEntry's are still being used + // in the list + // m_core.afterRun(m_sp.binary, m_exitCode); } @@ -670,7 +723,9 @@ HANDLE ProcessRunner::getProcessHandle() const env::HandlePtr ProcessRunner::stealProcessHandle() { - return std::move(m_handle); + auto h = m_handle.release(); + m_handle.reset(INVALID_HANDLE_VALUE); + return env::HandlePtr(h); } ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( @@ -678,7 +733,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( { m_lockReason = reason; - if (!Settings::instance().interface().lockGUI()) { + if (!m_core.settings().interface().lockGUI()) { // disabling locking is like clicking on unlock immediately return ForceUnlocked; } diff --git a/src/processrunner.h b/src/processrunner.h index 0fb3f59a..fd451e38 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -19,22 +19,34 @@ class ProcessRunner public: enum Results { + // the process is still running Running = 1, + + // the process has run to completion Completed, + + // the process couldn't be started or waited for Error, + + // the user has clicked the cancel button in the lock widget Cancelled, + + // the user has clicked the unlock button in the lock widget ForceUnlocked }; enum WaitFlag { NoFlags = 0x00, + + // the ui will be refreshed once the process has completed Refresh = 0x01, + + // the process will be waited for even if locking is disabled ForceWait = 0x02 }; using WaitFlags = QFlags; - using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); @@ -55,10 +67,29 @@ public: ProcessRunner& setWaitForCompletion( WaitFlags flags=NoFlags, LockWidget::Reasons reason=LockWidget::LockUI); + // if the target is an executable file, runs that; for anything else, calls + // ShellExecute() on it + // ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); + ProcessRunner& setFromExecutable(const Executable& exe); ProcessRunner& setFromShortcut(const MOShortcut& shortcut); + // this is a messy one that's used for running an arbitrary file from the + // command line, or by plugins (see OrganizerProxy::startApplication()) + // + // 1) if `executable` contains a path separator, it's treated as a binary on + // disk and will be launched with given settings; it's also looked up in + // the list of configured executables, which sets the steam ID and forced + // libraries + // + // 2) if `executable` has no path separators, it's treated purely as an + // executable, but its arguments, current directory and custom overwrite + // can also be overridden + // + // if the executable is not found in the list, the binary is run solely + // based on the parameters given + // ProcessRunner& setFromFileOrExecutable( const QString &executable, const QStringList &args, @@ -67,13 +98,42 @@ public: const QString &forcedCustomOverwrite={}, bool ignoreCustomOverwrite=false); + // spawns the process and waits for it if required + // Results run(); + + // takes ownership of the given handle and waits for it if required + // Results attachToProcess(HANDLE h); + // exit code of the process, will return -1 if the process wasn't waited for + // DWORD exitCode() const; + + // this may be INVALID_HANDLE_VALUE if: + // + // 1) no process was started, or + // 2) the process was started successfully, but the system didn't return a + // handle for it; this can happen for inproc handlers, for example, such + // the photo viewer + // + // note that the handle is still owned by this ProcessRunner and will be + // closed when destroyed; see stealProcessHandle() + // HANDLE getProcessHandle() const; + + // releases ownership of the process handle; if this is called after the + // process is completed, exitCode() will still return the correct value + // env::HandlePtr stealProcessHandle(); + // waits for all usvfs processes spawned by this instance of MO; returns + // immediately with ForceUnlocked if locking is disabled + // + // strictly speaking, this shouldn't be here, as it has nothing to do with + // running a process, but it uses the same internal stuff as when running a + // process + // Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); private: @@ -89,7 +149,21 @@ private: env::HandlePtr m_handle; DWORD m_exitCode; + + // runs the command in m_shellOpen; returns empty if it can be waited for + // + std::optional runShell(); + + // runs the binary; returns empty if it can be waited for + // + std::optional runBinary(); + + // waits for process completion if required + // Results postRun(); + + // creates the lock widget and calls f() + // void withLock(std::function f); }; diff --git a/src/spawn.cpp b/src/spawn.cpp index 62745542..f95846c8 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -490,7 +490,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) const QString moPath = QCoreApplication::applicationDirPath(); const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath)); - PROCESS_INFORMATION pi; + PROCESS_INFORMATION pi = {}; BOOL success = FALSE; logSpawning(sp, commandLine); @@ -501,13 +501,11 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) if (sp.hooked) { success = ::CreateProcessHooked( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - wcwd.c_str(), &si, &pi); + inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); } else { success = ::CreateProcess( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - wcwd.c_str(), &si, &pi); + inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); } const auto e = GetLastError(); @@ -557,16 +555,6 @@ void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp) restartAsAdmin(parent); } -bool checkBinary(QWidget* parent, const SpawnParameters& sp) -{ - if (!sp.binary.exists()) { - dialogs::spawnFailed(parent, sp, ERROR_FILE_NOT_FOUND); - return false; - } - - return true; -} - struct SteamStatus { bool running=false; @@ -754,31 +742,6 @@ bool checkSteam( return true; } -bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) -{ - // check if the Windows Event Logging service is running; for some reason, - // this seems to be critical to the successful running of usvfs. - const auto serviceName = "EventLog"; - - const auto s = env::getService(serviceName); - - if (!s.isValid()) { - log::error( - "cannot determine the status of the {} service, continuing", - serviceName); - - return true; - } - - if (s.status() == env::Service::Status::Running) { - log::debug("{}", s.toString()); - return true; - } - - log::error("{}", s.toString()); - return dialogs::eventLogNotRunning(parent, s, sp); -} - bool checkBlacklist( QWidget* parent, const SpawnParameters& sp, Settings& settings) { diff --git a/src/spawn.h b/src/spawn.h index 9fb346b0..a615b5ff 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -53,14 +53,10 @@ struct SpawnParameters }; -bool checkBinary(QWidget* parent, const SpawnParameters& sp); - bool checkSteam( QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, const QString &steamAppID, const Settings& settings); -bool checkEnvironment(QWidget* parent, const SpawnParameters& sp); - bool checkBlacklist( QWidget* parent, const SpawnParameters& sp, Settings& settings); -- cgit v1.3.1 From 3d0197fa5e6c4209d021398415f779994c21bd24 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 1 Nov 2019 06:47:53 -0400 Subject: comments --- src/lockwidget.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src') diff --git a/src/lockwidget.h b/src/lockwidget.h index 18ff76dd..d3ff2f7d 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -5,22 +5,40 @@ class LockWidget { public: + // reason to show the widget + // enum Reasons { NoReason = 0, + + // lock the ui LockUI, + + // because the output is required OutputRequired, + + // to prevent exiting until all processes are completed PreventExit }; + // returned by result() + // enum Results { NoResult = 0, + + // the widget is still up StillLocked, + + // force unlock was clicked ForceUnlocked, + + // cancel was clicked Cancelled }; + // if `reason` is not NoReason, lock() is called with it + // LockWidget(QWidget* parent, Reasons reason=NoReason); ~LockWidget(); -- cgit v1.3.1 From 642ddacab802df75d21a1ccf7508268d3efa141a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 05:23:22 -0500 Subject: moved LockWidget back to OrganizerCore to support having multiple locks active moved the ui out of LockWidget into LockInterface added LockWidget::Session to manage multi locks --- src/lockwidget.cpp | 490 ++++++++++++++++++++++++++++++++++++-------------- src/lockwidget.h | 65 +++---- src/organizercore.cpp | 1 + src/organizercore.h | 3 + src/processrunner.cpp | 33 ++-- src/processrunner.h | 2 +- 6 files changed, 414 insertions(+), 180 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 35d51dab..68eb9c2d 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -4,203 +4,431 @@ #include #include -QWidget* createTransparentWidget(QWidget* parent=nullptr) +class LockInterface { - auto* w = new QWidget(parent); +public: + LockInterface() : + m_hasMainUI(false), m_message(nullptr), m_info(nullptr), m_buttons(nullptr) + { + } - w->setWindowOpacity(0); - w->setAttribute(Qt::WA_NoSystemBackground); - w->setAttribute(Qt::WA_TranslucentBackground); + ~LockInterface() + { + } - return w; -} + void set(QWidget* target) + { + QFrame* center = nullptr; + if (target) { + center = createOverlay(target); + } else { + center = createDialog(); + } -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); + createMessageLabel(); + createInfoLabel(); + createButtonsPanel(); + + center->layout()->addWidget(m_message); + center->layout()->addWidget(m_info); + center->layout()->addWidget(m_buttons); + + m_topLevel->setFocusPolicy(Qt::TabFocus); + m_topLevel->setFocus(); + m_topLevel->show(); + m_topLevel->setEnabled(true); + + m_topLevel->raise(); + m_topLevel->activateWindow(); } -} -LockWidget::~LockWidget() -{ - unlock(); -} + void update(LockWidget::Reasons reason) + { + updateMessage(reason); + updateButtons(reason); + } -void LockWidget::lock(Reasons reason) -{ - m_result = StillLocked; - createUi(reason); -} + void setInfo(const QString& s) + { + m_info->setText(s); + } -void LockWidget::unlock() -{ - m_overlay.reset(); + QWidget* topLevel() + { + return m_topLevel.get(); + } + +private: + class Filter : public QObject + { + public: + std::function resized; + std::function closed; + + protected: + bool eventFilter(QObject* o, QEvent* e) override + { + if (e->type() == QEvent::Resize) { + if (resized) { + resized(); + } + } else if (e->type() == QEvent::Close) { + if (closed) { + closed(); + } + } + + return QObject::eventFilter(o, e); + } + }; + + + bool m_hasMainUI; + std::unique_ptr m_topLevel; + QLabel* m_message; + QLabel* m_info; + QWidget* m_buttons; + std::unique_ptr m_filter; + + QWidget* createTransparentWidget(QWidget* parent=nullptr) + { + auto* w = new QWidget(parent); + + w->setWindowOpacity(0); + w->setAttribute(Qt::WA_NoSystemBackground); + w->setAttribute(Qt::WA_TranslucentBackground); - if (m_filter && m_parent) { - m_parent->removeEventFilter(m_filter.get()); + return w; } - enableAll(); -} + QFrame* createOverlay(QWidget* mainUI) + { + m_hasMainUI = true; -void LockWidget::setInfo(DWORD pid, const QString& name) -{ - m_info->setText(QString("%1 (%2)").arg(name).arg(pid)); -} + m_topLevel.reset(createTransparentWidget(mainUI)); + m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); + m_topLevel->setGeometry(mainUI->rect()); -LockWidget::Results LockWidget::result() const -{ - return m_result; -} + m_filter.reset(new Filter); + m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; + m_filter->closed = [=]{ LockWidget::instance().onForceUnlock(); }; -void LockWidget::createUi(Reasons reason) -{ - QWidget* overlayTarget = m_parent; - if (auto* w = qApp->activeWindow()) { - overlayTarget = w; + mainUI->installEventFilter(m_filter.get()); + + return createFrame(); } - if (overlayTarget) { - m_overlay.reset(createTransparentWidget(overlayTarget)); - m_overlay->setWindowFlags(m_overlay->windowFlags() & Qt::FramelessWindowHint); - m_overlay->setGeometry(overlayTarget->rect()); - } else { - m_overlay.reset(new QDialog); + QFrame* createDialog() + { + m_hasMainUI = false; + m_topLevel.reset(new QDialog); + + return createFrame(); } - auto* center = new QFrame; + QFrame* createFrame() + { + auto* frame = new QFrame; + auto* ly = new QVBoxLayout(frame); + + if (m_hasMainUI) { + frame->setFrameStyle(QFrame::StyledPanel); + frame->setLineWidth(1); + frame->setAutoFillBackground(true); + + auto* shadow = new QGraphicsDropShadowEffect; + shadow->setBlurRadius(50); + shadow->setOffset(0); + shadow->setColor(QColor(0, 0, 0, 100)); + frame->setGraphicsEffect(shadow); + } else { + ly->setContentsMargins(0, 0, 0, 0); + } - if (overlayTarget) { - center->setFrameStyle(QFrame::StyledPanel); - center->setLineWidth(1); - center->setAutoFillBackground(true); + auto* grid = new QGridLayout(m_topLevel.get()); + grid->addWidget(createTransparentWidget(), 0, 1); + grid->addWidget(createTransparentWidget(), 2, 1); + grid->addWidget(createTransparentWidget(), 1, 0); + grid->addWidget(createTransparentWidget(), 1, 2); + grid->addWidget(frame, 1, 1); - auto* shadow = new QGraphicsDropShadowEffect; - shadow->setBlurRadius(50); - shadow->setOffset(0); - shadow->setColor(QColor(0, 0, 0, 100)); - center->setGraphicsEffect(shadow); - } + if (!m_hasMainUI) { + grid->setContentsMargins(0, 0, 0, 0); + } - m_info = new QLabel(" "); - m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); + grid->setRowStretch(0, 1); + grid->setRowStretch(2, 1); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(2, 1); - auto* ly = new QVBoxLayout(center); + return frame; + } - if (!overlayTarget) { - ly->setContentsMargins(0, 0, 0, 0); + void createMessageLabel() + { + m_message = new QLabel; + } + + void createInfoLabel() + { + m_info = new QLabel(" "); + m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); } - auto* message = new QLabel; - ly->addWidget(message); - ly->addWidget(m_info); + void createButtonsPanel() + { + m_buttons = new QWidget; + m_buttons->setLayout(new QHBoxLayout); + } - auto* buttons = new QWidget; - auto* buttonsLayout = new QHBoxLayout(buttons); - ly->addWidget(buttons); - switch (reason) + void updateMessage(LockWidget::Reasons reason) { - case LockUI: + switch (reason) { - QString s; + case LockWidget::LockUI: + { + QString s; + + if (m_hasMainUI) { + s = QObject::tr( + "Mod Organizer is locked while the application is running."); + } else { + s = QObject::tr("Mod Organizer is currently running an application."); + } - if (!m_parent) { - s = QObject::tr("Mod Organizer is currently running an application."); - } else { - s = QObject::tr( - "Mod Organizer is locked while the application is running."); + m_message->setText(s); + + break; } - message->setText(s); + case LockWidget::OutputRequired: + { + m_message->setText(QObject::tr( + "The application must run to completion because its output is " + "required.")); - auto* unlockButton = new QPushButton(QObject::tr("Unlock")); - QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); - buttonsLayout->addWidget(unlockButton); + break; + } - break; + case LockWidget::PreventExit: + { + m_message->setText(QObject::tr( + "Mod Organizer is waiting on application to close before exiting.")); + + break; + } } + } - case OutputRequired: + void updateButtons(LockWidget::Reasons reason) + { + MOBase::deleteChildWidgets(m_buttons); + auto* ly = m_buttons->layout(); + + switch (reason) { - message->setText(QObject::tr( - "The application must run to completion because its output is " - "required.")); + case LockWidget::LockUI: // fall-through + case LockWidget::OutputRequired: + { + auto* unlock = new QPushButton(QObject::tr("Unlock")); - auto* unlockButton = new QPushButton(QObject::tr("Unlock")); - QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); - buttonsLayout->addWidget(unlockButton); + QObject::connect(unlock, &QPushButton::clicked, [&]{ + LockWidget::instance().onForceUnlock(); + }); - break; + ly->addWidget(unlock); + + break; + } + + case LockWidget::PreventExit: + { + auto* exit = new QPushButton(QObject::tr("Exit Now")); + QObject::connect(exit, &QPushButton::clicked, [&]{ + LockWidget::instance().onForceUnlock(); + }); + + ly->addWidget(exit); + + auto* cancel = new QPushButton(QObject::tr("Cancel")); + QObject::connect(cancel, &QPushButton::clicked, [&]{ + LockWidget::instance().onCancel(); + }); + + ly->addWidget(cancel); + + break; + } } + } +}; - case PreventExit: - { - message->setText(QObject::tr( - "Mod Organizer is waiting on application to close before exiting.")); +LockWidget::Session::~Session() +{ + LockWidget::instance().unlock(this); +} + +void LockWidget::Session::setInfo(DWORD pid, const QString& name) +{ + m_pid = pid; + m_name = name; + + LockWidget::instance().updateLabel(); +} + +DWORD LockWidget::Session::pid() const +{ + return m_pid; +} + +const QString& LockWidget::Session::name() const +{ + return m_name; +} - auto* exit = new QPushButton(QObject::tr("Exit Now")); - QObject::connect(exit, &QPushButton::clicked, [&]{ onForceUnlock(); }); - buttonsLayout->addWidget(exit); +LockWidget::Results LockWidget::Session::result() const +{ + return LockWidget::instance().result(); +} + + +static LockWidget* g_instance = nullptr; + + +LockWidget::LockWidget() + : m_parent(nullptr), m_result(NoResult) +{ + Q_ASSERT(!g_instance); + g_instance = this; +} + +LockWidget::~LockWidget() +{ + const auto v = m_sessions; + + for (auto& wp : v) { + if (auto s=wp.lock()) { + unlock(s.get()); + } + } +} - auto* cancel = new QPushButton(QObject::tr("Cancel")); - QObject::connect(cancel, &QPushButton::clicked, [&]{ onCancel(); }); - buttonsLayout->addWidget(cancel); +LockWidget& LockWidget::instance() +{ + Q_ASSERT(g_instance); + return *g_instance; +} + +void LockWidget::setUserInterface(QWidget* parent) +{ + m_parent = parent; +} +std::shared_ptr LockWidget::lock(Reasons reason) +{ + m_result = StillLocked; + createUi(reason); + + auto ls = std::make_shared(); + m_sessions.push_back(ls); + + updateLabel(); + + return ls; +} + +void LockWidget::unlock(Session* s) +{ + auto itor = m_sessions.begin(); + for (;;) { + if (itor == m_sessions.end()) { break; } + + if (auto ss=itor->lock()) { + if (ss.get() == s) { + itor = m_sessions.erase(itor); + continue; + } + } else { + itor = m_sessions.erase(itor); + continue; + } + + ++itor; } - 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_sessions.empty()) { + m_ui.reset(); + enableAll(); + } else { + updateLabel(); + } +} - if (!overlayTarget) { - grid->setContentsMargins(0, 0, 0, 0); +void LockWidget::unlockCurrent() +{ + if (m_sessions.empty()) { + return; } - grid->setRowStretch(0, 1); - grid->setRowStretch(2, 1); - grid->setColumnStretch(0, 1); - grid->setColumnStretch(2, 1); + auto s = m_sessions.back().lock(); + if (!s) { + m_sessions.pop_back(); + return; + } - disableAll(); + unlock(s.get()); +} - if (overlayTarget) { - m_filter.reset(new Filter); - m_filter->resized = [=]{ m_overlay->setGeometry(overlayTarget->rect()); }; - m_filter->closed = [=]{ onForceUnlock(); }; - overlayTarget->installEventFilter(m_filter.get()); +void LockWidget::updateLabel() +{ + QString label; + + for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { + if (auto ss=itor->lock()) { + label += QString("%1 (%2)").arg(ss->name()).arg(ss->pid()); + break; + } } - m_overlay->setFocusPolicy(Qt::TabFocus); - m_overlay->setFocus(); - m_overlay->show(); - m_overlay->setEnabled(true); + m_ui->setInfo(label); +} - if (!overlayTarget) { - m_overlay->raise(); - m_overlay->activateWindow(); +LockWidget::Results LockWidget::result() const +{ + return m_result; +} + +void LockWidget::createUi(Reasons reason) +{ + QWidget* target = m_parent; + if (auto* w = qApp->activeWindow()) { + target = w; } + + if (!m_ui) { + m_ui.reset(new LockInterface); + } + + m_ui->set(target); + m_ui->update(reason); + + disableAll(); } void LockWidget::onForceUnlock() { m_result = ForceUnlocked; - unlock(); + unlockCurrent(); } void LockWidget::onCancel() { m_result = Cancelled; - unlock(); + unlockCurrent(); } template @@ -231,10 +459,10 @@ void LockWidget::disableAll() if (auto* d=dynamic_cast(w)) { // don't disable stuff if this dialog is the overlay, which happens when // there's no ui - if (d != m_overlay.get()) { + if (d != m_ui->topLevel()) { // no central widget, just disable the children, except for the overlay for (auto* child : findChildrenImmediate(d)) { - if (child != m_overlay.get()) { + if (child != m_ui->topLevel()) { disable(child); } } diff --git a/src/lockwidget.h b/src/lockwidget.h index d3ff2f7d..8062c478 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -2,8 +2,12 @@ #include +class LockInterface; + class LockWidget { + friend class LockInterface; + public: // reason to show the widget // @@ -37,51 +41,50 @@ public: Cancelled }; + + class Session + { + public: + ~Session(); + + void setInfo(DWORD pid, const QString& name); + Results result() const; + + DWORD pid() const; + const QString& name() const; + + private: + DWORD m_pid; + QString m_name; + }; + + // if `reason` is not NoReason, lock() is called with it // - LockWidget(QWidget* parent, Reasons reason=NoReason); + LockWidget(); ~LockWidget(); - void lock(Reasons reason); - void unlock(); + static LockWidget& instance(); - void setInfo(DWORD pid, const QString& name); - Results result() const; + void setUserInterface(QWidget* parent); -private: - class Filter : public QObject - { - public: - std::function resized; - std::function closed; - - protected: - bool eventFilter(QObject* o, QEvent* e) override - { - if (e->type() == QEvent::Resize) { - if (resized) { - resized(); - } - } else if (e->type() == QEvent::Close) { - if (closed) { - closed(); - } - } - - return QObject::eventFilter(o, e); - } - }; + std::shared_ptr lock(Reasons reason); + Results result() const; +private: QWidget* m_parent; - std::unique_ptr m_overlay; - QLabel* m_info; + std::unique_ptr m_ui; + std::vector> m_sessions; Results m_result; - std::unique_ptr m_filter; std::vector> m_disabled; void createUi(Reasons reason); + void unlockCurrent(); + void unlock(Session* s); + void updateLabel(); + void onForceUnlock(); void onCancel(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dda60f76..3d4d8e4b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -250,6 +250,7 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) m_InstallationManager.setParentWidget(w); m_Updater.setUserInterface(w); + m_LockWidget.setUserInterface(w); checkForUpdates(); } diff --git a/src/organizercore.h b/src/organizercore.h index 0ba52284..7e0e4b7f 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -14,6 +14,7 @@ #include "usvfsconnector.h" #include "moshortcut.h" #include "processrunner.h" +#include "lockwidget.h" #include #include #include @@ -343,6 +344,8 @@ private: MOBase::DelayedFileWriter m_PluginListsWriter; UsvfsConnector m_USVFS; + LockWidget m_LockWidget; + static CrashDumpsType m_globalCrashDumpsType; }; diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 220ffe38..d97c00ef 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -195,7 +195,8 @@ const std::chrono::milliseconds Infinite(-1); // waits for completion, times out after `wait` if not Infinite // std::optional timedWait( - HANDLE handle, DWORD pid, LockWidget& lock, std::chrono::milliseconds wait) + HANDLE handle, DWORD pid, LockWidget::Session& ls, + std::chrono::milliseconds wait) { using namespace std::chrono; @@ -220,7 +221,7 @@ std::optional timedWait( QCoreApplication::processEvents(); // check the lock widget - switch (lock.result()) + switch (ls.result()) { case LockWidget::StillLocked: { @@ -245,7 +246,7 @@ std::optional timedWait( // shouldn't happen log::debug( "unexpected result {} while waiting for {}", - static_cast(lock.result()), pid); + static_cast(ls.result()), pid); return ProcessRunner::Error; } @@ -263,7 +264,7 @@ std::optional timedWait( } ProcessRunner::Results waitForProcesses( - const std::vector& initialProcesses, LockWidget& lock) + const std::vector& initialProcesses, LockWidget::Session& ls) { using namespace std::chrono; @@ -287,7 +288,7 @@ ProcessRunner::Results waitForProcesses( } // update the lock widget - lock.setInfo(p.pid(), p.name()); + ls.setInfo(p.pid(), p.name()); // open the process auto interestingHandle = p.openHandleForWait(); @@ -309,7 +310,7 @@ ProcessRunner::Results waitForProcesses( wait = Infinite; } - const auto r = timedWait(interestingHandle.get(), p.pid(), lock, wait); + const auto r = timedWait(interestingHandle.get(), p.pid(), ls, wait); if (r) { // the process has completed or returned an error return *r; @@ -322,11 +323,11 @@ ProcessRunner::Results waitForProcesses( } ProcessRunner::Results waitForProcess( - HANDLE initialProcess, LPDWORD exitCode, LockWidget& lock) + HANDLE initialProcess, LPDWORD exitCode, LockWidget::Session& ls) { std::vector processes = {initialProcess}; - const auto r = waitForProcesses(processes, lock); + const auto r = waitForProcesses(processes, ls); // as long as it's not running anymore, try to get the exit code if (exitCode && r != ProcessRunner::Running) { @@ -682,8 +683,8 @@ ProcessRunner::Results ProcessRunner::postRun() auto r = Error; - withLock([&](auto& lock) { - r = waitForProcess(m_handle.get(), &m_exitCode, lock); + withLock([&](auto& ls) { + r = waitForProcess(m_handle.get(), &m_exitCode, ls); }); if (r == Completed && (m_waitFlags & Refresh)) { @@ -740,14 +741,14 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( auto r = Error; - withLock([&](auto& lock) { + withLock([&](auto& ls) { for (;;) { const auto processes = getRunningUSVFSProcesses(); if (processes.empty()) { break; } - r = waitForProcesses(processes, lock); + r = waitForProcesses(processes, ls); if (r != Completed) { // error, cancelled, or unlocked @@ -763,10 +764,8 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( return r; } -void ProcessRunner::withLock(std::function f) +void ProcessRunner::withLock(std::function f) { - auto lk = std::make_unique( - m_ui ? m_ui->qtWidget() : nullptr, m_lockReason); - - f(*lk); + auto ls = LockWidget::instance().lock(m_lockReason); + f(*ls); } diff --git a/src/processrunner.h b/src/processrunner.h index fd451e38..276c46b1 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -164,7 +164,7 @@ private: // creates the lock widget and calls f() // - void withLock(std::function f); + void withLock(std::function f); }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); -- cgit v1.3.1 From 96cc662b187f465713d3963f7c8cf9a1b4856aa5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 06:09:12 -0500 Subject: lock widget now moves between dialogs and the main window when closing dialogs --- src/lockwidget.cpp | 72 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 68eb9c2d..aae0316b 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -7,21 +7,47 @@ class LockInterface { public: - LockInterface() : - m_hasMainUI(false), m_message(nullptr), m_info(nullptr), m_buttons(nullptr) + LockInterface(QWidget* mainUI) : + m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), + m_buttons(nullptr), m_reason(LockWidget::NoReason) { + m_timer.reset(new QTimer); + QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); + m_timer->start(200); + + set(); } ~LockInterface() { } - void set(QWidget* target) + void checkTarget() + { + if (set()) { + update(m_reason); + } + } + + bool set() { + QWidget* newTarget = nullptr; + + newTarget = m_mainUI; + if (auto* w = QApplication::activeModalWidget()) { + newTarget = w; + } + + if (newTarget == m_target) { + return false; + } + + m_target = newTarget; + QFrame* center = nullptr; - if (target) { - center = createOverlay(target); + if (m_target) { + center = createOverlay(m_target); } else { center = createDialog(); } @@ -41,16 +67,21 @@ public: m_topLevel->raise(); m_topLevel->activateWindow(); + + return true; } void update(LockWidget::Reasons reason) { + m_reason = reason; updateMessage(reason); updateButtons(reason); + setInfo(m_infoText); } void setInfo(const QString& s) { + m_infoText = s; m_info->setText(s); } @@ -84,12 +115,22 @@ private: }; - bool m_hasMainUI; + std::unique_ptr m_timer; + QWidget* m_mainUI; + QWidget* m_target; std::unique_ptr m_topLevel; QLabel* m_message; QLabel* m_info; + QString m_infoText; QWidget* m_buttons; std::unique_ptr m_filter; + LockWidget::Reasons m_reason; + + + bool hasMainUI() const + { + return (m_target != nullptr); + } QWidget* createTransparentWidget(QWidget* parent=nullptr) { @@ -104,15 +145,13 @@ private: QFrame* createOverlay(QWidget* mainUI) { - m_hasMainUI = true; - m_topLevel.reset(createTransparentWidget(mainUI)); m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); m_topLevel->setGeometry(mainUI->rect()); m_filter.reset(new Filter); m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; - m_filter->closed = [=]{ LockWidget::instance().onForceUnlock(); }; + m_filter->closed = [=]{ checkTarget(); }; mainUI->installEventFilter(m_filter.get()); @@ -121,7 +160,6 @@ private: QFrame* createDialog() { - m_hasMainUI = false; m_topLevel.reset(new QDialog); return createFrame(); @@ -132,7 +170,7 @@ private: auto* frame = new QFrame; auto* ly = new QVBoxLayout(frame); - if (m_hasMainUI) { + if (hasMainUI()) { frame->setFrameStyle(QFrame::StyledPanel); frame->setLineWidth(1); frame->setAutoFillBackground(true); @@ -153,7 +191,7 @@ private: grid->addWidget(createTransparentWidget(), 1, 2); grid->addWidget(frame, 1, 1); - if (!m_hasMainUI) { + if (!hasMainUI()) { grid->setContentsMargins(0, 0, 0, 0); } @@ -191,7 +229,7 @@ private: { QString s; - if (m_hasMainUI) { + if (hasMainUI()) { s = QObject::tr( "Mod Organizer is locked while the application is running."); } else { @@ -404,16 +442,10 @@ LockWidget::Results LockWidget::result() const void LockWidget::createUi(Reasons reason) { - QWidget* target = m_parent; - if (auto* w = qApp->activeWindow()) { - target = w; - } - if (!m_ui) { - m_ui.reset(new LockInterface); + m_ui.reset(new LockInterface(m_parent)); } - m_ui->set(target); m_ui->update(reason); disableAll(); -- cgit v1.3.1 From 8f40ec0bdcb99cc1a0a2bde3074842391844f5d6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 06:50:05 -0500 Subject: threaded wait for process --- src/lockwidget.cpp | 22 ++++++++++++++++++---- src/lockwidget.h | 5 ++++- src/processrunner.cpp | 42 +++++++++++++++++++++++++++++++----------- 3 files changed, 53 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index aae0316b..8b654674 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -305,24 +305,38 @@ private: LockWidget::Session::~Session() { - LockWidget::instance().unlock(this); + unlock(); +} + +void LockWidget::Session::unlock() +{ + QMetaObject::invokeMethod(qApp, [this]{ + LockWidget::instance().unlock(this); + }); } void LockWidget::Session::setInfo(DWORD pid, const QString& name) { - m_pid = pid; - m_name = name; + { + std::scoped_lock lock(m_mutex); + m_pid = pid; + m_name = name; + } - LockWidget::instance().updateLabel(); + QMetaObject::invokeMethod(qApp, [this]{ + LockWidget::instance().updateLabel(); + }); } DWORD LockWidget::Session::pid() const { + std::scoped_lock lock(m_mutex); return m_pid; } const QString& LockWidget::Session::name() const { + std::scoped_lock lock(m_mutex); return m_name; } diff --git a/src/lockwidget.h b/src/lockwidget.h index 8062c478..640d0076 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -1,6 +1,7 @@ #pragma once #include +#include class LockInterface; @@ -47,6 +48,7 @@ public: public: ~Session(); + void unlock(); void setInfo(DWORD pid, const QString& name); Results result() const; @@ -54,6 +56,7 @@ public: const QString& name() const; private: + mutable std::mutex m_mutex; DWORD m_pid; QString m_name; }; @@ -76,7 +79,7 @@ private: QWidget* m_parent; std::unique_ptr m_ui; std::vector> m_sessions; - Results m_result; + std::atomic m_result; std::vector> m_disabled; void createUi(Reasons reason); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index d97c00ef..68acca92 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -55,11 +55,7 @@ std::optional singleWait(HANDLE handle, DWORD pid) return ProcessRunner::Error; } - const DWORD WAIT_EVENT = WAIT_OBJECT_0 + 1; - - // 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); + const auto res = WaitForSingleObject(handle, 50); switch (res) { @@ -70,7 +66,6 @@ std::optional singleWait(HANDLE handle, DWORD pid) } case WAIT_TIMEOUT: - case WAIT_EVENT: { // still running return {}; @@ -216,10 +211,6 @@ std::optional timedWait( // the process is still running - // keep processing events so the app doesn't appear dead - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); - // check the lock widget switch (ls.result()) { @@ -263,7 +254,7 @@ std::optional timedWait( } } -ProcessRunner::Results waitForProcesses( +ProcessRunner::Results waitForProcessesThreadImpl( const std::vector& initialProcesses, LockWidget::Session& ls) { using namespace std::chrono; @@ -322,6 +313,35 @@ ProcessRunner::Results waitForProcesses( } } +void waitForProcessesThread( + ProcessRunner::Results& result, + const std::vector& initialProcesses, LockWidget::Session& ls) +{ + result = waitForProcessesThreadImpl(initialProcesses, ls); + ls.unlock(); +} + +ProcessRunner::Results waitForProcesses( + const std::vector& initialProcesses, LockWidget::Session& ls) +{ + auto results = ProcessRunner::Running; + + auto* t = QThread::create( + waitForProcessesThread, std::ref(results), initialProcesses, std::ref(ls)); + + QEventLoop events; + QObject::connect(t, &QThread::finished, [&]{ + events.quit(); + }); + + t->start(); + events.exec(); + + delete t; + + return results; +} + ProcessRunner::Results waitForProcess( HANDLE initialProcess, LPDWORD exitCode, LockWidget::Session& ls) { -- cgit v1.3.1 From 4f84565085e19b6f6939783c64ebf95599f879be Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 07:20:56 -0500 Subject: renamed LockWidget to UILocker lock interface up to two processes --- src/lockwidget.cpp | 113 +++++++++++++++++++++++++++---------------------- src/lockwidget.h | 16 +++---- src/organizercore.cpp | 4 +- src/organizercore.h | 4 +- src/organizerproxy.cpp | 2 +- src/processrunner.cpp | 34 +++++++-------- src/processrunner.h | 8 ++-- 7 files changed, 95 insertions(+), 86 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 8b654674..150d2847 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -4,12 +4,12 @@ #include #include -class LockInterface +class UILockerInterface { public: - LockInterface(QWidget* mainUI) : + UILockerInterface(QWidget* mainUI) : m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), - m_buttons(nullptr), m_reason(LockWidget::NoReason) + m_buttons(nullptr), m_reason(UILocker::NoReason) { m_timer.reset(new QTimer); QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); @@ -18,7 +18,7 @@ public: set(); } - ~LockInterface() + ~UILockerInterface() { } @@ -71,17 +71,28 @@ public: return true; } - void update(LockWidget::Reasons reason) + void update(UILocker::Reasons reason) { m_reason = reason; updateMessage(reason); updateButtons(reason); - setInfo(m_infoText); + setInfo(m_labels); } - void setInfo(const QString& s) + void setInfo(const QStringList& labels) { - m_infoText = s; + const int MaxLabels = 2; + + m_labels = labels; + + QString s; + + if (labels.size() > MaxLabels) { + s = labels.mid(0, MaxLabels).join(", ") + "..."; + } else { + s = labels.join(", "); + } + m_info->setText(s); } @@ -121,10 +132,10 @@ private: std::unique_ptr m_topLevel; QLabel* m_message; QLabel* m_info; - QString m_infoText; + QStringList m_labels; QWidget* m_buttons; std::unique_ptr m_filter; - LockWidget::Reasons m_reason; + UILocker::Reasons m_reason; bool hasMainUI() const @@ -206,6 +217,7 @@ private: void createMessageLabel() { m_message = new QLabel; + m_message->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); } void createInfoLabel() @@ -221,11 +233,11 @@ private: } - void updateMessage(LockWidget::Reasons reason) + void updateMessage(UILocker::Reasons reason) { switch (reason) { - case LockWidget::LockUI: + case UILocker::LockUI: { QString s; @@ -241,7 +253,7 @@ private: break; } - case LockWidget::OutputRequired: + case UILocker::OutputRequired: { m_message->setText(QObject::tr( "The application must run to completion because its output is " @@ -250,7 +262,7 @@ private: break; } - case LockWidget::PreventExit: + case UILocker::PreventExit: { m_message->setText(QObject::tr( "Mod Organizer is waiting on application to close before exiting.")); @@ -260,20 +272,20 @@ private: } } - void updateButtons(LockWidget::Reasons reason) + void updateButtons(UILocker::Reasons reason) { MOBase::deleteChildWidgets(m_buttons); auto* ly = m_buttons->layout(); switch (reason) { - case LockWidget::LockUI: // fall-through - case LockWidget::OutputRequired: + case UILocker::LockUI: // fall-through + case UILocker::OutputRequired: { auto* unlock = new QPushButton(QObject::tr("Unlock")); QObject::connect(unlock, &QPushButton::clicked, [&]{ - LockWidget::instance().onForceUnlock(); + UILocker::instance().onForceUnlock(); }); ly->addWidget(unlock); @@ -281,18 +293,18 @@ private: break; } - case LockWidget::PreventExit: + case UILocker::PreventExit: { auto* exit = new QPushButton(QObject::tr("Exit Now")); QObject::connect(exit, &QPushButton::clicked, [&]{ - LockWidget::instance().onForceUnlock(); + UILocker::instance().onForceUnlock(); }); ly->addWidget(exit); auto* cancel = new QPushButton(QObject::tr("Cancel")); QObject::connect(cancel, &QPushButton::clicked, [&]{ - LockWidget::instance().onCancel(); + UILocker::instance().onCancel(); }); ly->addWidget(cancel); @@ -303,19 +315,19 @@ private: } }; -LockWidget::Session::~Session() +UILocker::Session::~Session() { unlock(); } -void LockWidget::Session::unlock() +void UILocker::Session::unlock() { QMetaObject::invokeMethod(qApp, [this]{ - LockWidget::instance().unlock(this); + UILocker::instance().unlock(this); }); } -void LockWidget::Session::setInfo(DWORD pid, const QString& name) +void UILocker::Session::setInfo(DWORD pid, const QString& name) { { std::scoped_lock lock(m_mutex); @@ -324,39 +336,39 @@ void LockWidget::Session::setInfo(DWORD pid, const QString& name) } QMetaObject::invokeMethod(qApp, [this]{ - LockWidget::instance().updateLabel(); + UILocker::instance().updateLabel(); }); } -DWORD LockWidget::Session::pid() const +DWORD UILocker::Session::pid() const { std::scoped_lock lock(m_mutex); return m_pid; } -const QString& LockWidget::Session::name() const +const QString& UILocker::Session::name() const { std::scoped_lock lock(m_mutex); return m_name; } -LockWidget::Results LockWidget::Session::result() const +UILocker::Results UILocker::Session::result() const { - return LockWidget::instance().result(); + return UILocker::instance().result(); } -static LockWidget* g_instance = nullptr; +static UILocker* g_instance = nullptr; -LockWidget::LockWidget() +UILocker::UILocker() : m_parent(nullptr), m_result(NoResult) { Q_ASSERT(!g_instance); g_instance = this; } -LockWidget::~LockWidget() +UILocker::~UILocker() { const auto v = m_sessions; @@ -367,18 +379,18 @@ LockWidget::~LockWidget() } } -LockWidget& LockWidget::instance() +UILocker& UILocker::instance() { Q_ASSERT(g_instance); return *g_instance; } -void LockWidget::setUserInterface(QWidget* parent) +void UILocker::setUserInterface(QWidget* parent) { m_parent = parent; } -std::shared_ptr LockWidget::lock(Reasons reason) +std::shared_ptr UILocker::lock(Reasons reason) { m_result = StillLocked; createUi(reason); @@ -391,7 +403,7 @@ std::shared_ptr LockWidget::lock(Reasons reason) return ls; } -void LockWidget::unlock(Session* s) +void UILocker::unlock(Session* s) { auto itor = m_sessions.begin(); for (;;) { @@ -420,7 +432,7 @@ void LockWidget::unlock(Session* s) } } -void LockWidget::unlockCurrent() +void UILocker::unlockCurrent() { if (m_sessions.empty()) { return; @@ -435,29 +447,28 @@ void LockWidget::unlockCurrent() unlock(s.get()); } -void LockWidget::updateLabel() +void UILocker::updateLabel() { - QString label; + QStringList labels; for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { if (auto ss=itor->lock()) { - label += QString("%1 (%2)").arg(ss->name()).arg(ss->pid()); - break; + labels.push_back(QString("%1 (%2)").arg(ss->name()).arg(ss->pid())); } } - m_ui->setInfo(label); + m_ui->setInfo(labels); } -LockWidget::Results LockWidget::result() const +UILocker::Results UILocker::result() const { return m_result; } -void LockWidget::createUi(Reasons reason) +void UILocker::createUi(Reasons reason) { if (!m_ui) { - m_ui.reset(new LockInterface(m_parent)); + m_ui.reset(new UILockerInterface(m_parent)); } m_ui->update(reason); @@ -465,13 +476,13 @@ void LockWidget::createUi(Reasons reason) disableAll(); } -void LockWidget::onForceUnlock() +void UILocker::onForceUnlock() { m_result = ForceUnlocked; unlockCurrent(); } -void LockWidget::onCancel() +void UILocker::onCancel() { m_result = Cancelled; unlockCurrent(); @@ -483,7 +494,7 @@ QList findChildrenImmediate(QWidget* parent) return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); } -void LockWidget::disableAll() +void UILocker::disableAll() { const auto topLevels = QApplication::topLevelWidgets(); @@ -517,7 +528,7 @@ void LockWidget::disableAll() } } -void LockWidget::enableAll() +void UILocker::enableAll() { for (auto w : m_disabled) { if (w) { @@ -528,7 +539,7 @@ void LockWidget::enableAll() m_disabled.clear(); } -void LockWidget::disable(QWidget* w) +void UILocker::disable(QWidget* w) { if (w->isEnabled()) { w->setEnabled(false); diff --git a/src/lockwidget.h b/src/lockwidget.h index 640d0076..d8c22999 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -3,11 +3,11 @@ #include #include -class LockInterface; +class UILockerInterface; -class LockWidget +class UILocker { - friend class LockInterface; + friend class UILockerInterface; public: // reason to show the widget @@ -62,12 +62,10 @@ public: }; - // if `reason` is not NoReason, lock() is called with it - // - LockWidget(); - ~LockWidget(); + UILocker(); + ~UILocker(); - static LockWidget& instance(); + static UILocker& instance(); void setUserInterface(QWidget* parent); @@ -77,7 +75,7 @@ public: private: QWidget* m_parent; - std::unique_ptr m_ui; + std::unique_ptr m_ui; std::vector> m_sessions; std::atomic m_result; std::vector> m_disabled; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3d4d8e4b..9ceb149e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -250,7 +250,7 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) m_InstallationManager.setParentWidget(w); m_Updater.setUserInterface(w); - m_LockWidget.setUserInterface(w); + m_UILocker.setUserInterface(w); checkForUpdates(); } @@ -1797,7 +1797,7 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) } ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses( - LockWidget::Reasons reason) + UILocker::Reasons reason) { return processRunner().waitForAllUSVFSProcessesWithLock(reason); } diff --git a/src/organizercore.h b/src/organizercore.h index 7e0e4b7f..47630dc2 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -145,7 +145,7 @@ public: void afterRun(const QFileInfo& binary, DWORD exitCode); ProcessRunner::Results waitForAllUSVFSProcesses( - LockWidget::Reasons reason=LockWidget::PreventExit); + UILocker::Reasons reason=UILocker::PreventExit); void refreshESPList(bool force = false); void refreshBSAList(); @@ -344,7 +344,7 @@ private: MOBase::DelayedFileWriter m_PluginListsWriter; UsvfsConnector m_USVFS; - LockWidget m_LockWidget; + UILocker m_UILocker; static CrashDumpsType m_globalCrashDumpsType; }; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 420e2d82..613de742 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -144,7 +144,7 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const auto runner = m_Proxied->processRunner(); const auto r = runner - .setWaitForCompletion(ProcessRunner::ForceWait, LockWidget::OutputRequired) + .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::OutputRequired) .attachToProcess(handle); if (exitCode) { diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 68acca92..cfddbb63 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -190,7 +190,7 @@ const std::chrono::milliseconds Infinite(-1); // waits for completion, times out after `wait` if not Infinite // std::optional timedWait( - HANDLE handle, DWORD pid, LockWidget::Session& ls, + HANDLE handle, DWORD pid, UILocker::Session& ls, std::chrono::milliseconds wait) { using namespace std::chrono; @@ -214,24 +214,24 @@ std::optional timedWait( // check the lock widget switch (ls.result()) { - case LockWidget::StillLocked: + case UILocker::StillLocked: { break; } - case LockWidget::ForceUnlocked: + case UILocker::ForceUnlocked: { log::debug("waiting for {} force unlocked by user", pid); return ProcessRunner::ForceUnlocked; } - case LockWidget::Cancelled: + case UILocker::Cancelled: { log::debug("waiting for {} cancelled by user", pid); return ProcessRunner::Cancelled; } - case LockWidget::NoResult: // fall-through + case UILocker::NoResult: // fall-through default: { // shouldn't happen @@ -255,7 +255,7 @@ std::optional timedWait( } ProcessRunner::Results waitForProcessesThreadImpl( - const std::vector& initialProcesses, LockWidget::Session& ls) + const std::vector& initialProcesses, UILocker::Session& ls) { using namespace std::chrono; @@ -315,14 +315,14 @@ ProcessRunner::Results waitForProcessesThreadImpl( void waitForProcessesThread( ProcessRunner::Results& result, - const std::vector& initialProcesses, LockWidget::Session& ls) + const std::vector& initialProcesses, UILocker::Session& ls) { result = waitForProcessesThreadImpl(initialProcesses, ls); ls.unlock(); } ProcessRunner::Results waitForProcesses( - const std::vector& initialProcesses, LockWidget::Session& ls) + const std::vector& initialProcesses, UILocker::Session& ls) { auto results = ProcessRunner::Running; @@ -343,7 +343,7 @@ ProcessRunner::Results waitForProcesses( } ProcessRunner::Results waitForProcess( - HANDLE initialProcess, LPDWORD exitCode, LockWidget::Session& ls) + HANDLE initialProcess, LPDWORD exitCode, UILocker::Session& ls) { std::vector processes = {initialProcess}; @@ -364,7 +364,7 @@ ProcessRunner::Results waitForProcess( ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), + m_core(core), m_ui(ui), m_lockReason(UILocker::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { // all processes started in ProcessRunner are hooked @@ -414,7 +414,7 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - WaitFlags flags, LockWidget::Reasons reason) + WaitFlags flags, UILocker::Reasons reason) { m_waitFlags = flags; m_lockReason = reason; @@ -670,13 +670,13 @@ ProcessRunner::Results ProcessRunner::postRun() { const bool mustWait = (m_waitFlags & ForceWait); - if (mustWait && m_lockReason == LockWidget::NoReason) { + if (mustWait && m_lockReason == UILocker::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; + m_lockReason = UILocker::LockUI; } if (mustWait) { @@ -689,7 +689,7 @@ ProcessRunner::Results ProcessRunner::postRun() } else { // no force wait - if (m_lockReason == LockWidget::NoReason) { + if (m_lockReason == UILocker::NoReason) { // no locking requested return Running; } @@ -750,7 +750,7 @@ env::HandlePtr ProcessRunner::stealProcessHandle() } ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( - LockWidget::Reasons reason) + UILocker::Reasons reason) { m_lockReason = reason; @@ -784,8 +784,8 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( return r; } -void ProcessRunner::withLock(std::function f) +void ProcessRunner::withLock(std::function f) { - auto ls = LockWidget::instance().lock(m_lockReason); + auto ls = UILocker::instance().lock(m_lockReason); f(*ls); } diff --git a/src/processrunner.h b/src/processrunner.h index 276c46b1..af5cd416 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -65,7 +65,7 @@ public: ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( - WaitFlags flags=NoFlags, LockWidget::Reasons reason=LockWidget::LockUI); + WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); // if the target is an executable file, runs that; for anything else, calls // ShellExecute() on it @@ -134,7 +134,7 @@ public: // running a process, but it uses the same internal stuff as when running a // process // - Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); + Results waitForAllUSVFSProcessesWithLock(UILocker::Reasons reason); private: OrganizerCore& m_core; @@ -143,7 +143,7 @@ private: QString m_customOverwrite; ForcedLibraries m_forcedLibraries; QString m_profileName; - LockWidget::Reasons m_lockReason; + UILocker::Reasons m_lockReason; WaitFlags m_waitFlags; QString m_shellOpen; env::HandlePtr m_handle; @@ -164,7 +164,7 @@ private: // creates the lock widget and calls f() // - void withLock(std::function f); + void withLock(std::function f); }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); -- cgit v1.3.1 From decd5c1828f495be4e230c9fc6fb79dd9bfdfb81 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 07:28:11 -0500 Subject: renamed lockwidget files to uilocker --- src/CMakeLists.txt | 6 +- src/iuserinterface.h | 1 - src/lockwidget.cpp | 548 --------------------------------------------------- src/lockwidget.h | 95 --------- src/organizercore.h | 2 +- src/processrunner.h | 2 +- src/uilocker.cpp | 548 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/uilocker.h | 95 +++++++++ 8 files changed, 648 insertions(+), 649 deletions(-) delete mode 100644 src/lockwidget.cpp delete mode 100644 src/lockwidget.h create mode 100644 src/uilocker.cpp create mode 100644 src/uilocker.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 06f2cd1e..d935981d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,7 +141,7 @@ SET(organizer_SRCS colortable.cpp sanitychecks.cpp processrunner.cpp - lockwidget.cpp + uilocker.cpp shared/windows_error.cpp shared/error_report.cpp @@ -262,7 +262,7 @@ SET(organizer_HDRS envwindows.h colortable.h processrunner.h - lockwidget.h + uilocker.h shared/windows_error.h shared/error_report.h @@ -344,6 +344,7 @@ set(core organizerproxy apiuseraccount processrunner + uilocker ) set(dialogs @@ -473,7 +474,6 @@ set(widgets filterwidget icondelegate lcdnumber - lockwidget loglist loghighlighter modflagicondelegate diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 99caceb1..aa48194f 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -3,7 +3,6 @@ #include "modinfodialogfwd.h" -#include "lockwidget.h" #include #include #include diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp deleted file mode 100644 index 150d2847..00000000 --- a/src/lockwidget.cpp +++ /dev/null @@ -1,548 +0,0 @@ -#include "lockwidget.h" -#include "mainwindow.h" -#include -#include -#include - -class UILockerInterface -{ -public: - UILockerInterface(QWidget* mainUI) : - m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), - m_buttons(nullptr), m_reason(UILocker::NoReason) - { - m_timer.reset(new QTimer); - QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); - m_timer->start(200); - - set(); - } - - ~UILockerInterface() - { - } - - void checkTarget() - { - if (set()) { - update(m_reason); - } - } - - bool set() - { - QWidget* newTarget = nullptr; - - newTarget = m_mainUI; - if (auto* w = QApplication::activeModalWidget()) { - newTarget = w; - } - - if (newTarget == m_target) { - return false; - } - - m_target = newTarget; - - QFrame* center = nullptr; - - if (m_target) { - center = createOverlay(m_target); - } else { - center = createDialog(); - } - - createMessageLabel(); - createInfoLabel(); - createButtonsPanel(); - - center->layout()->addWidget(m_message); - center->layout()->addWidget(m_info); - center->layout()->addWidget(m_buttons); - - m_topLevel->setFocusPolicy(Qt::TabFocus); - m_topLevel->setFocus(); - m_topLevel->show(); - m_topLevel->setEnabled(true); - - m_topLevel->raise(); - m_topLevel->activateWindow(); - - return true; - } - - void update(UILocker::Reasons reason) - { - m_reason = reason; - updateMessage(reason); - updateButtons(reason); - setInfo(m_labels); - } - - void setInfo(const QStringList& labels) - { - const int MaxLabels = 2; - - m_labels = labels; - - QString s; - - if (labels.size() > MaxLabels) { - s = labels.mid(0, MaxLabels).join(", ") + "..."; - } else { - s = labels.join(", "); - } - - m_info->setText(s); - } - - QWidget* topLevel() - { - return m_topLevel.get(); - } - -private: - class Filter : public QObject - { - public: - std::function resized; - std::function closed; - - protected: - bool eventFilter(QObject* o, QEvent* e) override - { - if (e->type() == QEvent::Resize) { - if (resized) { - resized(); - } - } else if (e->type() == QEvent::Close) { - if (closed) { - closed(); - } - } - - return QObject::eventFilter(o, e); - } - }; - - - std::unique_ptr m_timer; - QWidget* m_mainUI; - QWidget* m_target; - std::unique_ptr m_topLevel; - QLabel* m_message; - QLabel* m_info; - QStringList m_labels; - QWidget* m_buttons; - std::unique_ptr m_filter; - UILocker::Reasons m_reason; - - - bool hasMainUI() const - { - return (m_target != nullptr); - } - - 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; - } - - QFrame* createOverlay(QWidget* mainUI) - { - m_topLevel.reset(createTransparentWidget(mainUI)); - m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); - m_topLevel->setGeometry(mainUI->rect()); - - m_filter.reset(new Filter); - m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; - m_filter->closed = [=]{ checkTarget(); }; - - mainUI->installEventFilter(m_filter.get()); - - return createFrame(); - } - - QFrame* createDialog() - { - m_topLevel.reset(new QDialog); - - return createFrame(); - } - - QFrame* createFrame() - { - auto* frame = new QFrame; - auto* ly = new QVBoxLayout(frame); - - if (hasMainUI()) { - frame->setFrameStyle(QFrame::StyledPanel); - frame->setLineWidth(1); - frame->setAutoFillBackground(true); - - auto* shadow = new QGraphicsDropShadowEffect; - shadow->setBlurRadius(50); - shadow->setOffset(0); - shadow->setColor(QColor(0, 0, 0, 100)); - frame->setGraphicsEffect(shadow); - } else { - ly->setContentsMargins(0, 0, 0, 0); - } - - auto* grid = new QGridLayout(m_topLevel.get()); - grid->addWidget(createTransparentWidget(), 0, 1); - grid->addWidget(createTransparentWidget(), 2, 1); - grid->addWidget(createTransparentWidget(), 1, 0); - grid->addWidget(createTransparentWidget(), 1, 2); - grid->addWidget(frame, 1, 1); - - if (!hasMainUI()) { - grid->setContentsMargins(0, 0, 0, 0); - } - - grid->setRowStretch(0, 1); - grid->setRowStretch(2, 1); - grid->setColumnStretch(0, 1); - grid->setColumnStretch(2, 1); - - return frame; - } - - void createMessageLabel() - { - m_message = new QLabel; - m_message->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); - } - - void createInfoLabel() - { - m_info = new QLabel(" "); - m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); - } - - void createButtonsPanel() - { - m_buttons = new QWidget; - m_buttons->setLayout(new QHBoxLayout); - } - - - void updateMessage(UILocker::Reasons reason) - { - switch (reason) - { - case UILocker::LockUI: - { - QString s; - - if (hasMainUI()) { - s = QObject::tr( - "Mod Organizer is locked while the application is running."); - } else { - s = QObject::tr("Mod Organizer is currently running an application."); - } - - m_message->setText(s); - - break; - } - - case UILocker::OutputRequired: - { - m_message->setText(QObject::tr( - "The application must run to completion because its output is " - "required.")); - - break; - } - - case UILocker::PreventExit: - { - m_message->setText(QObject::tr( - "Mod Organizer is waiting on application to close before exiting.")); - - break; - } - } - } - - void updateButtons(UILocker::Reasons reason) - { - MOBase::deleteChildWidgets(m_buttons); - auto* ly = m_buttons->layout(); - - switch (reason) - { - case UILocker::LockUI: // fall-through - case UILocker::OutputRequired: - { - auto* unlock = new QPushButton(QObject::tr("Unlock")); - - QObject::connect(unlock, &QPushButton::clicked, [&]{ - UILocker::instance().onForceUnlock(); - }); - - ly->addWidget(unlock); - - break; - } - - case UILocker::PreventExit: - { - auto* exit = new QPushButton(QObject::tr("Exit Now")); - QObject::connect(exit, &QPushButton::clicked, [&]{ - UILocker::instance().onForceUnlock(); - }); - - ly->addWidget(exit); - - auto* cancel = new QPushButton(QObject::tr("Cancel")); - QObject::connect(cancel, &QPushButton::clicked, [&]{ - UILocker::instance().onCancel(); - }); - - ly->addWidget(cancel); - - break; - } - } - } -}; - -UILocker::Session::~Session() -{ - unlock(); -} - -void UILocker::Session::unlock() -{ - QMetaObject::invokeMethod(qApp, [this]{ - UILocker::instance().unlock(this); - }); -} - -void UILocker::Session::setInfo(DWORD pid, const QString& name) -{ - { - std::scoped_lock lock(m_mutex); - m_pid = pid; - m_name = name; - } - - QMetaObject::invokeMethod(qApp, [this]{ - UILocker::instance().updateLabel(); - }); -} - -DWORD UILocker::Session::pid() const -{ - std::scoped_lock lock(m_mutex); - return m_pid; -} - -const QString& UILocker::Session::name() const -{ - std::scoped_lock lock(m_mutex); - return m_name; -} - -UILocker::Results UILocker::Session::result() const -{ - return UILocker::instance().result(); -} - - -static UILocker* g_instance = nullptr; - - -UILocker::UILocker() - : m_parent(nullptr), m_result(NoResult) -{ - Q_ASSERT(!g_instance); - g_instance = this; -} - -UILocker::~UILocker() -{ - const auto v = m_sessions; - - for (auto& wp : v) { - if (auto s=wp.lock()) { - unlock(s.get()); - } - } -} - -UILocker& UILocker::instance() -{ - Q_ASSERT(g_instance); - return *g_instance; -} - -void UILocker::setUserInterface(QWidget* parent) -{ - m_parent = parent; -} - -std::shared_ptr UILocker::lock(Reasons reason) -{ - m_result = StillLocked; - createUi(reason); - - auto ls = std::make_shared(); - m_sessions.push_back(ls); - - updateLabel(); - - return ls; -} - -void UILocker::unlock(Session* s) -{ - auto itor = m_sessions.begin(); - for (;;) { - if (itor == m_sessions.end()) { - break; - } - - if (auto ss=itor->lock()) { - if (ss.get() == s) { - itor = m_sessions.erase(itor); - continue; - } - } else { - itor = m_sessions.erase(itor); - continue; - } - - ++itor; - } - - if (m_sessions.empty()) { - m_ui.reset(); - enableAll(); - } else { - updateLabel(); - } -} - -void UILocker::unlockCurrent() -{ - if (m_sessions.empty()) { - return; - } - - auto s = m_sessions.back().lock(); - if (!s) { - m_sessions.pop_back(); - return; - } - - unlock(s.get()); -} - -void UILocker::updateLabel() -{ - QStringList labels; - - for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { - if (auto ss=itor->lock()) { - labels.push_back(QString("%1 (%2)").arg(ss->name()).arg(ss->pid())); - } - } - - m_ui->setInfo(labels); -} - -UILocker::Results UILocker::result() const -{ - return m_result; -} - -void UILocker::createUi(Reasons reason) -{ - if (!m_ui) { - m_ui.reset(new UILockerInterface(m_parent)); - } - - m_ui->update(reason); - - disableAll(); -} - -void UILocker::onForceUnlock() -{ - m_result = ForceUnlocked; - unlockCurrent(); -} - -void UILocker::onCancel() -{ - m_result = Cancelled; - unlockCurrent(); -} - -template -QList findChildrenImmediate(QWidget* parent) -{ - return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); -} - -void UILocker::disableAll() -{ - const auto topLevels = QApplication::topLevelWidgets(); - - for (auto* w : topLevels) { - if (auto* mw=dynamic_cast(w)) { - disable(mw->centralWidget()); - disable(mw->menuBar()); - disable(mw->statusBar()); - - for (auto* tb : findChildrenImmediate(w)) { - disable(tb); - } - - for (auto* d : findChildrenImmediate(w)) { - disable(d); - } - } - - if (auto* d=dynamic_cast(w)) { - // don't disable stuff if this dialog is the overlay, which happens when - // there's no ui - if (d != m_ui->topLevel()) { - // no central widget, just disable the children, except for the overlay - for (auto* child : findChildrenImmediate(d)) { - if (child != m_ui->topLevel()) { - disable(child); - } - } - } - } - } -} - -void UILocker::enableAll() -{ - for (auto w : m_disabled) { - if (w) { - w->setEnabled(true); - } - } - - m_disabled.clear(); -} - -void UILocker::disable(QWidget* w) -{ - if (w->isEnabled()) { - w->setEnabled(false); - m_disabled.push_back(w); - } -} diff --git a/src/lockwidget.h b/src/lockwidget.h deleted file mode 100644 index d8c22999..00000000 --- a/src/lockwidget.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include - -class UILockerInterface; - -class UILocker -{ - friend class UILockerInterface; - -public: - // reason to show the widget - // - enum Reasons - { - NoReason = 0, - - // lock the ui - LockUI, - - // because the output is required - OutputRequired, - - // to prevent exiting until all processes are completed - PreventExit - }; - - // returned by result() - // - enum Results - { - NoResult = 0, - - // the widget is still up - StillLocked, - - // force unlock was clicked - ForceUnlocked, - - // cancel was clicked - Cancelled - }; - - - class Session - { - public: - ~Session(); - - void unlock(); - void setInfo(DWORD pid, const QString& name); - Results result() const; - - DWORD pid() const; - const QString& name() const; - - private: - mutable std::mutex m_mutex; - DWORD m_pid; - QString m_name; - }; - - - UILocker(); - ~UILocker(); - - static UILocker& instance(); - - void setUserInterface(QWidget* parent); - - std::shared_ptr lock(Reasons reason); - - Results result() const; - -private: - QWidget* m_parent; - std::unique_ptr m_ui; - std::vector> m_sessions; - std::atomic m_result; - std::vector> m_disabled; - - void createUi(Reasons reason); - - void unlockCurrent(); - void unlock(Session* s); - void updateLabel(); - - void onForceUnlock(); - void onCancel(); - - void disableAll(); - void enableAll(); - void disable(QWidget* w); -}; diff --git a/src/organizercore.h b/src/organizercore.h index 47630dc2..6c9edb9f 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -14,7 +14,7 @@ #include "usvfsconnector.h" #include "moshortcut.h" #include "processrunner.h" -#include "lockwidget.h" +#include "uilocker.h" #include #include #include diff --git a/src/processrunner.h b/src/processrunner.h index af5cd416..c61d6b70 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -2,7 +2,7 @@ #define PROCESSRUNNER_H #include "spawn.h" -#include "lockwidget.h" +#include "uilocker.h" #include "envmodule.h" #include diff --git a/src/uilocker.cpp b/src/uilocker.cpp new file mode 100644 index 00000000..01794d6b --- /dev/null +++ b/src/uilocker.cpp @@ -0,0 +1,548 @@ +#include "uilocker.h" +#include "mainwindow.h" +#include +#include +#include + +class UILockerInterface +{ +public: + UILockerInterface(QWidget* mainUI) : + m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), + m_buttons(nullptr), m_reason(UILocker::NoReason) + { + m_timer.reset(new QTimer); + QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); + m_timer->start(200); + + set(); + } + + ~UILockerInterface() + { + } + + void checkTarget() + { + if (set()) { + update(m_reason); + } + } + + bool set() + { + QWidget* newTarget = nullptr; + + newTarget = m_mainUI; + if (auto* w = QApplication::activeModalWidget()) { + newTarget = w; + } + + if (newTarget == m_target) { + return false; + } + + m_target = newTarget; + + QFrame* center = nullptr; + + if (m_target) { + center = createOverlay(m_target); + } else { + center = createDialog(); + } + + createMessageLabel(); + createInfoLabel(); + createButtonsPanel(); + + center->layout()->addWidget(m_message); + center->layout()->addWidget(m_info); + center->layout()->addWidget(m_buttons); + + m_topLevel->setFocusPolicy(Qt::TabFocus); + m_topLevel->setFocus(); + m_topLevel->show(); + m_topLevel->setEnabled(true); + + m_topLevel->raise(); + m_topLevel->activateWindow(); + + return true; + } + + void update(UILocker::Reasons reason) + { + m_reason = reason; + updateMessage(reason); + updateButtons(reason); + setInfo(m_labels); + } + + void setInfo(const QStringList& labels) + { + const int MaxLabels = 2; + + m_labels = labels; + + QString s; + + if (labels.size() > MaxLabels) { + s = labels.mid(0, MaxLabels).join(", ") + "..."; + } else { + s = labels.join(", "); + } + + m_info->setText(s); + } + + QWidget* topLevel() + { + return m_topLevel.get(); + } + +private: + class Filter : public QObject + { + public: + std::function resized; + std::function closed; + + protected: + bool eventFilter(QObject* o, QEvent* e) override + { + if (e->type() == QEvent::Resize) { + if (resized) { + resized(); + } + } else if (e->type() == QEvent::Close) { + if (closed) { + closed(); + } + } + + return QObject::eventFilter(o, e); + } + }; + + + std::unique_ptr m_timer; + QWidget* m_mainUI; + QWidget* m_target; + std::unique_ptr m_topLevel; + QLabel* m_message; + QLabel* m_info; + QStringList m_labels; + QWidget* m_buttons; + std::unique_ptr m_filter; + UILocker::Reasons m_reason; + + + bool hasMainUI() const + { + return (m_target != nullptr); + } + + 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; + } + + QFrame* createOverlay(QWidget* mainUI) + { + m_topLevel.reset(createTransparentWidget(mainUI)); + m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); + m_topLevel->setGeometry(mainUI->rect()); + + m_filter.reset(new Filter); + m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; + m_filter->closed = [=]{ checkTarget(); }; + + mainUI->installEventFilter(m_filter.get()); + + return createFrame(); + } + + QFrame* createDialog() + { + m_topLevel.reset(new QDialog); + + return createFrame(); + } + + QFrame* createFrame() + { + auto* frame = new QFrame; + auto* ly = new QVBoxLayout(frame); + + if (hasMainUI()) { + frame->setFrameStyle(QFrame::StyledPanel); + frame->setLineWidth(1); + frame->setAutoFillBackground(true); + + auto* shadow = new QGraphicsDropShadowEffect; + shadow->setBlurRadius(50); + shadow->setOffset(0); + shadow->setColor(QColor(0, 0, 0, 100)); + frame->setGraphicsEffect(shadow); + } else { + ly->setContentsMargins(0, 0, 0, 0); + } + + auto* grid = new QGridLayout(m_topLevel.get()); + grid->addWidget(createTransparentWidget(), 0, 1); + grid->addWidget(createTransparentWidget(), 2, 1); + grid->addWidget(createTransparentWidget(), 1, 0); + grid->addWidget(createTransparentWidget(), 1, 2); + grid->addWidget(frame, 1, 1); + + if (!hasMainUI()) { + grid->setContentsMargins(0, 0, 0, 0); + } + + grid->setRowStretch(0, 1); + grid->setRowStretch(2, 1); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(2, 1); + + return frame; + } + + void createMessageLabel() + { + m_message = new QLabel; + m_message->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); + } + + void createInfoLabel() + { + m_info = new QLabel(" "); + m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); + } + + void createButtonsPanel() + { + m_buttons = new QWidget; + m_buttons->setLayout(new QHBoxLayout); + } + + + void updateMessage(UILocker::Reasons reason) + { + switch (reason) + { + case UILocker::LockUI: + { + QString s; + + if (hasMainUI()) { + s = QObject::tr( + "Mod Organizer is locked while the application is running."); + } else { + s = QObject::tr("Mod Organizer is currently running an application."); + } + + m_message->setText(s); + + break; + } + + case UILocker::OutputRequired: + { + m_message->setText(QObject::tr( + "The application must run to completion because its output is " + "required.")); + + break; + } + + case UILocker::PreventExit: + { + m_message->setText(QObject::tr( + "Mod Organizer is waiting on application to close before exiting.")); + + break; + } + } + } + + void updateButtons(UILocker::Reasons reason) + { + MOBase::deleteChildWidgets(m_buttons); + auto* ly = m_buttons->layout(); + + switch (reason) + { + case UILocker::LockUI: // fall-through + case UILocker::OutputRequired: + { + auto* unlock = new QPushButton(QObject::tr("Unlock")); + + QObject::connect(unlock, &QPushButton::clicked, [&]{ + UILocker::instance().onForceUnlock(); + }); + + ly->addWidget(unlock); + + break; + } + + case UILocker::PreventExit: + { + auto* exit = new QPushButton(QObject::tr("Exit Now")); + QObject::connect(exit, &QPushButton::clicked, [&]{ + UILocker::instance().onForceUnlock(); + }); + + ly->addWidget(exit); + + auto* cancel = new QPushButton(QObject::tr("Cancel")); + QObject::connect(cancel, &QPushButton::clicked, [&]{ + UILocker::instance().onCancel(); + }); + + ly->addWidget(cancel); + + break; + } + } + } +}; + +UILocker::Session::~Session() +{ + unlock(); +} + +void UILocker::Session::unlock() +{ + QMetaObject::invokeMethod(qApp, [this]{ + UILocker::instance().unlock(this); + }); +} + +void UILocker::Session::setInfo(DWORD pid, const QString& name) +{ + { + std::scoped_lock lock(m_mutex); + m_pid = pid; + m_name = name; + } + + QMetaObject::invokeMethod(qApp, [this]{ + UILocker::instance().updateLabel(); + }); +} + +DWORD UILocker::Session::pid() const +{ + std::scoped_lock lock(m_mutex); + return m_pid; +} + +const QString& UILocker::Session::name() const +{ + std::scoped_lock lock(m_mutex); + return m_name; +} + +UILocker::Results UILocker::Session::result() const +{ + return UILocker::instance().result(); +} + + +static UILocker* g_instance = nullptr; + + +UILocker::UILocker() + : m_parent(nullptr), m_result(NoResult) +{ + Q_ASSERT(!g_instance); + g_instance = this; +} + +UILocker::~UILocker() +{ + const auto v = m_sessions; + + for (auto& wp : v) { + if (auto s=wp.lock()) { + unlock(s.get()); + } + } +} + +UILocker& UILocker::instance() +{ + Q_ASSERT(g_instance); + return *g_instance; +} + +void UILocker::setUserInterface(QWidget* parent) +{ + m_parent = parent; +} + +std::shared_ptr UILocker::lock(Reasons reason) +{ + m_result = StillLocked; + createUi(reason); + + auto ls = std::make_shared(); + m_sessions.push_back(ls); + + updateLabel(); + + return ls; +} + +void UILocker::unlock(Session* s) +{ + auto itor = m_sessions.begin(); + for (;;) { + if (itor == m_sessions.end()) { + break; + } + + if (auto ss=itor->lock()) { + if (ss.get() == s) { + itor = m_sessions.erase(itor); + continue; + } + } else { + itor = m_sessions.erase(itor); + continue; + } + + ++itor; + } + + if (m_sessions.empty()) { + m_ui.reset(); + enableAll(); + } else { + updateLabel(); + } +} + +void UILocker::unlockCurrent() +{ + if (m_sessions.empty()) { + return; + } + + auto s = m_sessions.back().lock(); + if (!s) { + m_sessions.pop_back(); + return; + } + + unlock(s.get()); +} + +void UILocker::updateLabel() +{ + QStringList labels; + + for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { + if (auto ss=itor->lock()) { + labels.push_back(QString("%1 (%2)").arg(ss->name()).arg(ss->pid())); + } + } + + m_ui->setInfo(labels); +} + +UILocker::Results UILocker::result() const +{ + return m_result; +} + +void UILocker::createUi(Reasons reason) +{ + if (!m_ui) { + m_ui.reset(new UILockerInterface(m_parent)); + } + + m_ui->update(reason); + + disableAll(); +} + +void UILocker::onForceUnlock() +{ + m_result = ForceUnlocked; + unlockCurrent(); +} + +void UILocker::onCancel() +{ + m_result = Cancelled; + unlockCurrent(); +} + +template +QList findChildrenImmediate(QWidget* parent) +{ + return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); +} + +void UILocker::disableAll() +{ + const auto topLevels = QApplication::topLevelWidgets(); + + for (auto* w : topLevels) { + if (auto* mw=dynamic_cast(w)) { + disable(mw->centralWidget()); + disable(mw->menuBar()); + disable(mw->statusBar()); + + for (auto* tb : findChildrenImmediate(w)) { + disable(tb); + } + + for (auto* d : findChildrenImmediate(w)) { + disable(d); + } + } + + if (auto* d=dynamic_cast(w)) { + // don't disable stuff if this dialog is the overlay, which happens when + // there's no ui + if (d != m_ui->topLevel()) { + // no central widget, just disable the children, except for the overlay + for (auto* child : findChildrenImmediate(d)) { + if (child != m_ui->topLevel()) { + disable(child); + } + } + } + } + } +} + +void UILocker::enableAll() +{ + for (auto w : m_disabled) { + if (w) { + w->setEnabled(true); + } + } + + m_disabled.clear(); +} + +void UILocker::disable(QWidget* w) +{ + if (w->isEnabled()) { + w->setEnabled(false); + m_disabled.push_back(w); + } +} diff --git a/src/uilocker.h b/src/uilocker.h new file mode 100644 index 00000000..d8c22999 --- /dev/null +++ b/src/uilocker.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +class UILockerInterface; + +class UILocker +{ + friend class UILockerInterface; + +public: + // reason to show the widget + // + enum Reasons + { + NoReason = 0, + + // lock the ui + LockUI, + + // because the output is required + OutputRequired, + + // to prevent exiting until all processes are completed + PreventExit + }; + + // returned by result() + // + enum Results + { + NoResult = 0, + + // the widget is still up + StillLocked, + + // force unlock was clicked + ForceUnlocked, + + // cancel was clicked + Cancelled + }; + + + class Session + { + public: + ~Session(); + + void unlock(); + void setInfo(DWORD pid, const QString& name); + Results result() const; + + DWORD pid() const; + const QString& name() const; + + private: + mutable std::mutex m_mutex; + DWORD m_pid; + QString m_name; + }; + + + UILocker(); + ~UILocker(); + + static UILocker& instance(); + + void setUserInterface(QWidget* parent); + + std::shared_ptr lock(Reasons reason); + + Results result() const; + +private: + QWidget* m_parent; + std::unique_ptr m_ui; + std::vector> m_sessions; + std::atomic m_result; + std::vector> m_disabled; + + void createUi(Reasons reason); + + void unlockCurrent(); + void unlock(Session* s); + void updateLabel(); + + void onForceUnlock(); + void onCancel(); + + void disableAll(); + void enableAll(); + void disable(QWidget* w); +}; -- cgit v1.3.1