From bca6283311cf1dea4c96f8ee5bf192bdb1640cb3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 08:56:16 -0400 Subject: use log::Levels instead of ints create log level combobox in code, set selected index based on value instead added log level to context menu in log list --- src/organizercore.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/organizercore.h') diff --git a/src/organizercore.h b/src/organizercore.h index 99b1c5f2..ef1a4133 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -21,6 +21,7 @@ #include #include #include "executableinfo.h" +#include class ModListSortProxy; class PluginListSortProxy; @@ -191,7 +192,9 @@ public: void prepareVFS(); - void updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist); + void updateVFSParams( + MOBase::log::Levels logLevel, int crashDumpsType, + QString executableBlacklist); bool cycleDiagnostics(); -- cgit v1.3.1 From 3bf7717c0a8507c9befce6b74c84c4dbdcac99de Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:41:09 -0400 Subject: moved Settings out of OrganizerCore so it can be created by itself to access settings early set log level on startup replaced more qDebug() --- src/main.cpp | 100 +++++++++++++++++++++++++------------------------- src/organizercore.cpp | 6 +-- src/organizercore.h | 4 +- src/shared/util.cpp | 4 +- 4 files changed, 57 insertions(+), 57 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/main.cpp b/src/main.cpp index f55c32f2..aa842ead 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -425,8 +425,6 @@ void setupPath() { static const int BUFSIZE = 4096; - log::debug("MO at {}", QCoreApplication::applicationDirPath()); - QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); boost::scoped_array oldPath(new TCHAR[BUFSIZE]); @@ -446,8 +444,6 @@ void setupPath() void preloadDll(const QString& filename) { - log::debug("preloading {}", filename); - if (GetModuleHandleW(filename.toStdWString().c_str())) { // already loaded, this can happen when "restarting" MO by switching // instances, for example @@ -513,62 +509,68 @@ void dumpEnvironment() int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { - log::info( - "Starting Mod Organizer version {} revision {}", - getVersionDisplayString(), GITID); + "Starting Mod Organizer version {} revision {} in {}", + getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); -#if !defined(QT_NO_SSL) preloadSsl(); - log::info("ssl support: {}", QSslSocket::supportsSsl()); -#else - log::info("non-ssl build"); -#endif - - dumpEnvironment(); + if (!QSslSocket::supportsSsl()) { + log::warn("no ssl support"); + } QString dataPath = application.property("dataPath").toString(); - qDebug("data path: %s", qUtf8Printable(dataPath)); + log::info("data path: {}", dataPath); if (!bootstrap()) { reportError("failed to set up data paths"); return 1; } - QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); + QWindowsWindowFunctions::setWindowActivationBehavior( + QWindowsWindowFunctions::AlwaysActivateWindow); QStringList arguments = application.arguments(); try { - qDebug("Working directory: %s", qUtf8Printable(QDir::toNativeSeparators(QDir::currentPath()))); + log::info("working directory: {}", QDir::currentPath()); - QSettings settings(dataPath + "/" - + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); + QSettings initSettings( + dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), + QSettings::IniFormat); - // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt()); + Settings settings(initSettings); + log::getDefault().setLevel(settings.logLevel()); - qDebug("Loaded settings:"); - settings.beginGroup("Settings"); - for (auto k : settings.allKeys()) - if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) - qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); - settings.endGroup(); + dumpEnvironment(); + // global crashDumpType sits in OrganizerCore to make a bit less ugly to + // update it when the settings are changed during runtime + OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); - qDebug("initializing core"); + log::debug("Loaded settings:"); + + initSettings.beginGroup("Settings"); + for (auto k : initSettings.allKeys()) { + if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) { + log::debug(" {}={}", k, initSettings.value(k).toString()); + } + } + initSettings.endGroup(); + + + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { reportError("failed to set up data paths"); return 1; } - qDebug("initialize plugins"); + + log::debug("initializing plugins"); PluginContainer pluginContainer(&organizer); pluginContainer.loadPlugins(); MOBase::IPluginGame *game = determineCurrentGame( - application.applicationDirPath(), settings, pluginContainer); + application.applicationDirPath(), initSettings, pluginContainer); if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); @@ -586,14 +588,14 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (!image.isNull()) { image.save(dataPath + "/splash.png"); } else { - qDebug("no plugin splash"); + log::debug("no plugin splash"); } } organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (!settings.contains("game_edition")) { + if (!initSettings.contains("game_edition")) { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -609,18 +611,17 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - settings.setValue("game_edition", selection.getChoiceString()); + initSettings.setValue("game_edition", selection.getChoiceString()); } } } - game->setGameVariant(settings.value("game_edition").toString()); + game->setGameVariant(initSettings.value("game_edition").toString()); - qDebug("managing game at %s", qUtf8Printable(QDir::toNativeSeparators( - game->gameDirectory().absolutePath()))); + log::info("managing game at {}", game->gameDirectory().absolutePath()); - organizer.updateExecutablesList(settings); + organizer.updateExecutablesList(initSettings); - QString selectedProfileName = determineProfile(arguments, settings); + QString selectedProfileName = determineProfile(arguments, initSettings); organizer.setCurrentProfile(selectedProfileName); // if we have a command line parameter, it is either a nxm link or @@ -640,13 +641,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } else if (OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("starting download from command line: %s", - qUtf8Printable(arguments.at(1))); + log::debug("starting download from command line: {}", arguments.at(1)); organizer.externalMessage(arguments.at(1)); } else { QString exeName = arguments.at(1); - qDebug("starting %s from command line", qUtf8Printable(exeName)); + 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 @@ -665,8 +665,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - if (settings.contains("window_monitor")) { - const int monitor = settings.value("window_monitor").toInt(); + if (initSettings.contains("window_monitor")) { + const int monitor = initSettings.value("window_monitor").toInt(); if (monitor != -1) { QDesktopWidget* desktop = QApplication::desktop(); @@ -683,21 +683,21 @@ int runApplication(MOApplication &application, SingleInstance &instance, NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } - qDebug("initializing tutorials"); + log::debug("initializing tutorials"); TutorialManager::init( qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { + if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) { // disable invalid stylesheet - settings.setValue("Settings/style", ""); + initSettings.setValue("Settings/style", ""); } int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(settings, organizer, pluginContainer); + MainWindow mainWindow(initSettings, organizer, pluginContainer); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(&mainWindow); @@ -714,7 +714,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, mainWindow.readSettings(); - qDebug("displaying main window"); + log::debug("displaying main window"); mainWindow.show(); mainWindow.activateWindow(); @@ -857,7 +857,7 @@ int main(int argc, char *argv[]) if (moshortcut || arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("not primary instance, sending shortcut/download message"); + log::debug("not primary instance, sending shortcut/download message"); instance.sendMessage(arguments.at(1)); return 0; } else if (arguments.size() == 1) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 25fbc7cd..400f5391 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -268,12 +268,12 @@ bool checkService() } -OrganizerCore::OrganizerCore(const QSettings &initSettings) +OrganizerCore::OrganizerCore(Settings &settings) : m_UserInterface(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) - , m_Settings(initSettings) + , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() , m_FinishedRun() @@ -294,7 +294,7 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings) NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); - MOBase::QuestionBoxMemory::init(initSettings.fileName()); + MOBase::QuestionBoxMemory::init(m_Settings.directInterface().fileName()); m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); diff --git a/src/organizercore.h b/src/organizercore.h index ef1a4133..c368d101 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -97,7 +97,7 @@ public: static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } - OrganizerCore(const QSettings &initSettings); + OrganizerCore(Settings &settings); ~OrganizerCore(); @@ -336,7 +336,7 @@ private: Profile *m_CurrentProfile; - Settings m_Settings; + Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 29e52f40..8d8c2000 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1492,11 +1492,11 @@ QString WindowsInfo::toString() const const QString real = m_real.toString(); // version - sl.push_back("version: " + reported); + sl.push_back("version " + reported); // real version if different if (compatibilityMode()) { - sl.push_back("real version: " + real); + sl.push_back("real version " + real); } // build.UBR, such as 17763.557 -- cgit v1.3.1 From 3c7712a32dd5079a9543485b6a85d548460faefd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 05:34:31 -0400 Subject: moved setLogLevel() to OrganizerCore moved context menu to LogList --- src/loglist.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++-------- src/loglist.h | 16 +++++++++----- src/mainwindow.cpp | 57 ++--------------------------------------------- src/mainwindow.h | 4 ---- src/mainwindow.ui | 14 ------------ src/organizercore.cpp | 12 ++++++++++ src/organizercore.h | 2 ++ 7 files changed, 78 insertions(+), 88 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/loglist.cpp b/src/loglist.cpp index c34ac76e..26aea682 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -18,14 +18,7 @@ along with Mod Organizer. If not, see . */ #include "loglist.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "organizercore.h" using namespace MOBase; @@ -158,7 +151,7 @@ QVariant LogModel::headerData(int, Qt::Orientation, int) const LogList::LogList(QWidget* parent) - : QTreeView(parent) + : QTreeView(parent), m_core(nullptr) { setModel(&LogModel::instance()); @@ -171,6 +164,10 @@ LogList::LogList(QWidget* parent) setAutoScroll(true); scrollToBottom(); + connect( + this, &QWidget::customContextMenuRequested, + [&](auto&& pos){ onContextMenu(pos); }); + connect( model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(scrollToBottom())); @@ -180,6 +177,11 @@ LogList::LogList(QWidget* parent) this, SLOT(scrollToBottom())); } +void LogList::setCore(OrganizerCore& core) +{ + m_core = &core; +} + void LogList::copyToClipboard() { std::string s; @@ -196,3 +198,44 @@ void LogList::copyToClipboard() QApplication::clipboard()->setText(QString::fromStdString(s)); } + +QMenu* LogList::createMenu(QWidget* parent) +{ + auto* menu = new QMenu(parent); + + menu->addAction(tr("Copy& Log"), [&]{ copyToClipboard(); }); + menu->addSeparator(); + + auto* levels = new QMenu(tr("&Level")); + menu->addMenu(levels); + + auto* ag = new QActionGroup(menu); + + auto addAction = [&](auto&& text, auto&& level) { + auto* a = new QAction(text, ag); + + a->setCheckable(true); + a->setChecked(log::getDefault().level() == level); + + connect(a, &QAction::triggered, [this, level]{ + if (m_core) { + m_core->setLogLevel(level); + } + }); + + levels->addAction(a); + }; + + addAction(tr("&Debug"), log::Debug); + addAction(tr("&Info"), log::Info); + addAction(tr("&Warnings"), log::Warning); + addAction(tr("&Errors"), log::Error); + + return menu; +} + +void LogList::onContextMenu(const QPoint& pos) +{ + auto* menu = createMenu(this); + menu->popup(viewport()->mapToGlobal(pos)); +} diff --git a/src/loglist.h b/src/loglist.h index d1f7a2ad..ae827ca7 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -20,14 +20,11 @@ along with Mod Organizer. If not, see . #ifndef LOGBUFFER_H #define LOGBUFFER_H -#include -#include -#include -#include -#include -#include +#include #include +class OrganizerCore; + class LogModel : public QAbstractItemModel { Q_OBJECT @@ -65,7 +62,14 @@ class LogList : public QTreeView public: LogList(QWidget* parent=nullptr); + void setCore(OrganizerCore& core); + void copyToClipboard(); + QMenu* createMenu(QWidget* parent=nullptr); + +private: + OrganizerCore* m_core; + void onContextMenu(const QPoint& pos); }; #endif // LOGBUFFER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8a8a99ef..761e9843 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,7 +59,6 @@ along with Mod Organizer. If not, see . #include "installationmanager.h" #include "lockeddialog.h" #include "waitingonclosedialog.h" -#include "loglist.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" @@ -358,7 +357,7 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - setupLogMenu(); + ui->logList->setCore(m_OrganizerCore); int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); @@ -813,36 +812,6 @@ void MainWindow::setupActionMenu(QAction* a) tb->setPopupMode(QToolButton::InstantPopup); } -void MainWindow::setupLogMenu() -{ - connect(ui->logList, &QWidget::customContextMenuRequested, [&](auto&& pos){ - auto* menu = new QMenu(ui->logList); - - menu->addAction(tr("Copy& Log"), [&]{ ui->logList->copyToClipboard(); }); - menu->addSeparator(); - - auto* levels = new QMenu(tr("&Level")); - menu->addMenu(levels); - - auto* ag = new QActionGroup(menu); - - auto addAction = [&](auto&& text, auto&& level) { - auto* a = new QAction(text, ag); - a->setCheckable(true); - a->setChecked(log::getDefault().level() == level); - connect(a, &QAction::triggered, [this, level]{ setLogLevel(level); }); - levels->addAction(a); - }; - - addAction(tr("&Debug"), log::Debug); - addAction(tr("&Info"), log::Info); - addAction(tr("&Warnings"), log::Warning); - addAction(tr("&Errors"), log::Error); - - menu->popup(ui->logList->viewport()->mapToGlobal(pos)); - }); -} - void MainWindow::updatePinnedExecutables() { for (auto* a : ui->toolBar->actions()) { @@ -1432,11 +1401,6 @@ bool MainWindow::confirmExit() void MainWindow::cleanup() { - if (ui->logList->model() != nullptr) { - disconnect(ui->logList->model(), nullptr, nullptr, nullptr); - ui->logList->setModel(nullptr); - } - QWebEngineProfile::defaultProfile()->clearAllVisitedLinks(); m_IntegratedBrowser.close(); m_SaveMetaTimer.stop(); @@ -5317,24 +5281,12 @@ void MainWindow::on_actionSettings_triggered() m_statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - setLogLevel(settings.logLevel()); + m_OrganizerCore.setLogLevel(settings.logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); } -void MainWindow::setLogLevel(log::Levels level) -{ - auto& s = m_OrganizerCore.settings(); - - s.setLogLevel(level); - - m_OrganizerCore.updateVFSParams( - s.logLevel(), s.crashDumpsType(), s.executablesBlacklist()); - - log::getDefault().setLevel(s.logLevel()); -} - void MainWindow::on_actionNexus_triggered() { const IPluginGame *game = m_OrganizerCore.managedGame(); @@ -6858,11 +6810,6 @@ void MainWindow::on_restoreModsButton_clicked() } } -void MainWindow::on_actionLogCopy_triggered() -{ - ui->logList->copyToClipboard(); -} - void MainWindow::on_categoriesAndBtn_toggled(bool checked) { if (checked) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 74993667..aa49205d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -636,13 +636,10 @@ private slots: void search_activated(); void searchClear_activated(); - void setupLogMenu(); void resetActionIcons(); void updateModCount(); void updatePluginCount(); - void setLogLevel(MOBase::log::Levels level); - private slots: // ui slots // actions void on_actionAdd_Profile_triggered(); @@ -696,7 +693,6 @@ private slots: // ui slots void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_actionLogCopy_triggered(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index fc2bcdd3..6c6d0bca 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1757,20 +1757,6 @@ p, li { white-space: pre-wrap; } Log - - - Copy &Log - - - Copy &Log - - - Copy log to clipboard - - - Copy log to clipboard - - diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 92372d82..1e164525 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -731,6 +731,18 @@ void OrganizerCore::updateVFSParams( m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); } +void OrganizerCore::setLogLevel(log::Levels level) +{ + m_Settings.setLogLevel(level); + + updateVFSParams( + m_Settings.logLevel(), + m_Settings.crashDumpsType(), + m_Settings.executablesBlacklist()); + + log::getDefault().setLevel(m_Settings.logLevel()); +} + bool OrganizerCore::cycleDiagnostics() { if (int maxDumps = settings().crashDumpsMax()) removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); diff --git a/src/organizercore.h b/src/organizercore.h index c368d101..2aa7e707 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -196,6 +196,8 @@ public: MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist); + void setLogLevel(MOBase::log::Levels level); + bool cycleDiagnostics(); static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; } -- cgit v1.3.1 From b6b01a52db1877b16531137289641fb9be9833aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 2 Aug 2019 23:24:27 -0400 Subject: removed mentions of QSettings from main.cpp added necessary member functions in Settings --- src/filedialogmemory.cpp | 1 + src/filedialogmemory.h | 1 + src/main.cpp | 185 +++++++++++++++++++++-------------------------- src/mainwindow.cpp | 6 +- src/mainwindow.h | 3 +- src/organizercore.cpp | 4 +- src/organizercore.h | 2 +- src/settings.cpp | 77 +++++++++++++++++++- src/settings.h | 22 +++++- 9 files changed, 188 insertions(+), 113 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 0e3e9793..308a175e 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -18,6 +18,7 @@ along with Mod Organizer. If not, see . */ #include "filedialogmemory.h" +#include "settings.h" #include diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index 81d7ba40..1a72b289 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include #include +class Settings; class FileDialogMemory { diff --git a/src/main.cpp b/src/main.cpp index 911f11c3..720ecbf9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -245,9 +245,10 @@ static bool HaveWriteAccess(const std::wstring &path) } -QString determineProfile(QStringList &arguments, const QSettings &settings) +QString determineProfile(QStringList &arguments, const Settings &settings) { - QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); + QString selectedProfileName = settings.getSelectedProfileName(); + { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { @@ -257,6 +258,7 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) arguments.removeAt(profileIndex); arguments.removeAt(profileIndex); } + if (selectedProfileName.isEmpty()) { log::debug("no configured profile"); selectedProfileName = "Default"; @@ -267,46 +269,50 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) return selectedProfileName; } -MOBase::IPluginGame *selectGame(QSettings &settings, QDir const &gamePath, MOBase::IPluginGame *game) +MOBase::IPluginGame *selectGame( + Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) { - settings.setValue("gameName", game->gameName()); - //Sadly, hookdll needs gamePath in order to run. So following code block is - //commented out - /*if (gamePath == game->gameDirectory()) { - settings.remove("gamePath"); - } else*/ { - QString gameDir = gamePath.absolutePath(); - game->setGamePath(gameDir); - settings.setValue("gamePath", QDir::toNativeSeparators(gameDir).toUtf8().constData()); - } - return game; //Woot + settings.setManagedGameName(game->gameName()); + + QString gameDir = gamePath.absolutePath(); + game->setGamePath(gameDir); + + settings.setManagedGameDirectory(gameDir); + + return game; } -MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &settings, PluginContainer const &plugins) +MOBase::IPluginGame *determineCurrentGame( + QString const &moPath, Settings &settings, PluginContainer const &plugins) { //Determine what game we are running where. Be very paranoid in case the //user has done something odd. //If the game name has been set up, try to use that. - QString gameName = settings.value("gameName", "").toString(); + const QString gameName = settings.getManagedGameName(); bool gameConfigured = !gameName.isEmpty(); + if (gameConfigured) { MOBase::IPluginGame *game = plugins.managedGame(gameName); if (game == nullptr) { reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); return nullptr; } - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + + QString gamePath = settings.getManagedGameDirectory(); if (gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } + QDir gameDir(gamePath); QFileInfo directoryInfo(gameDir.path()); + if (directoryInfo.isSymLink()) { reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); } + if (game->looksValid(gameDir)) { return selectGame(settings, gameDir, game); } @@ -315,7 +321,7 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett //If we've made it this far and the instance is already configured for a game, something has gone wrong. //Tell the user about it. if (gameConfigured) { - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + const QString gamePath = settings.getManagedGameDirectory(); reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). arg(gameName).arg(gamePath)); } @@ -480,27 +486,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void dumpSettings(QSettings& settings) -{ - static const QStringList ignore({ - "username", "password", "nexus_api_key" - }); - - log::debug("settings:"); - - settings.beginGroup("Settings"); - - for (auto k : settings.allKeys()) { - if (ignore.contains(k, Qt::CaseInsensitive)) { - continue; - } - - log::debug(" . {}={}", k, settings.value(k).toString()); - } - - settings.endGroup(); -} - void checkMissingFiles() { // files that are likely to be eaten @@ -557,7 +542,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, log::warn("no ssl support"); } - QString dataPath = application.property("dataPath").toString(); + const QString dataPath = application.property("dataPath").toString(); log::info("data path: {}", dataPath); if (!bootstrap()) { @@ -573,11 +558,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, try { log::info("working directory: {}", QDir::currentPath()); - QSettings initSettings( - dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); - - Settings settings(initSettings); + Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); log::getDefault().setLevel(settings.logLevel()); // global crashDumpType sits in OrganizerCore to make a bit less ugly to @@ -587,7 +568,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, env::Environment env; env.dump(); - dumpSettings(initSettings); + settings.dump(); sanityChecks(env); log::debug("initializing core"); @@ -602,7 +583,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, pluginContainer.loadPlugins(); MOBase::IPluginGame *game = determineCurrentGame( - application.applicationDirPath(), initSettings, pluginContainer); + application.applicationDirPath(), settings, pluginContainer); + if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); @@ -612,6 +594,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, } return 1; } + if (splashPath.startsWith(':')) { // currently using MO splash, see if the plugin contains one QString pluginSplash @@ -625,7 +608,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (!initSettings.contains("game_edition")) { + if (settings.getManagedGameEdition() == "") { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -641,78 +624,76 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - initSettings.setValue("game_edition", selection.getChoiceString()); + settings.setManagedGameEdition(selection.getChoiceString()); } } } - game->setGameVariant(initSettings.value("game_edition").toString()); + + game->setGameVariant(settings.getManagedGameEdition()); log::info("managing game at {}", game->gameDirectory().absolutePath()); - organizer.updateExecutablesList(initSettings); + organizer.updateExecutablesList(); - QString selectedProfileName = determineProfile(arguments, initSettings); + QString selectedProfileName = determineProfile(arguments, settings); organizer.setCurrentProfile(selectedProfileName); // if we have a command line parameter, it is either a nxm link or // a binary to start - if (arguments.size() > 1) { - if (MOShortcut shortcut{ arguments.at(1) }) { - if (shortcut.hasExecutable()) { - try { - organizer.runShortcut(shortcut); - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } - } - else if (OrganizerCore::isNxmLink(arguments.at(1))) { - log::debug("starting download from command line: {}", arguments.at(1)); - organizer.externalMessage(arguments.at(1)); - } - 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.startApplication(exeName, arguments, QString(), QString()); - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } - } - } + if (arguments.size() > 1) { + if (MOShortcut shortcut{ arguments.at(1) }) { + if (shortcut.hasExecutable()) { + try { + organizer.runShortcut(shortcut); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; + } + } + } + else if (OrganizerCore::isNxmLink(arguments.at(1))) { + log::debug("starting download from command line: {}", arguments.at(1)); + organizer.externalMessage(arguments.at(1)); + } + 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.startApplication(exeName, arguments, QString(), QString()); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } + } + } QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - if (initSettings.contains("window_monitor")) { - const int monitor = initSettings.value("window_monitor").toInt(); - - if (monitor != -1 && QGuiApplication::screens().size() > monitor) { - QGuiApplication::screens().at(monitor)->geometry().center(); - const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); - splash.move(center - splash.rect().center()); - } else { - const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); - splash.move(center - splash.rect().center()); - } + const int monitor = settings.getMainWindowMonitor(); + if (monitor != -1 && QGuiApplication::screens().size() > monitor) { + QGuiApplication::screens().at(monitor)->geometry().center(); + const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); + splash.move(center - splash.rect().center()); + } else { + const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); + splash.move(center - splash.rect().center()); } splash.show(); splash.activateWindow(); QString apiKey; - if (organizer.settings().getNexusApiKey(apiKey)) { + if (settings.getNexusApiKey(apiKey)) { NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } @@ -722,15 +703,15 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) { + if (!application.setStyleFile(settings.getStyleName())) { // disable invalid stylesheet - initSettings.setValue("Settings/style", ""); + settings.setStyleName(""); } int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(initSettings, organizer, pluginContainer); + MainWindow mainWindow(settings, organizer, pluginContainer); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 28405819..7f7ded80 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -291,7 +291,7 @@ public: }; -MainWindow::MainWindow(QSettings &initSettings +MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore , PluginContainer &pluginContainer , QWidget *parent) @@ -540,8 +540,8 @@ MainWindow::MainWindow(QSettings &initSettings connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); - setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); - FileDialogMemory::restore(initSettings); + setCategoryListVisible(settings.isCategoryListVisible()); + FileDialogMemory::restore(settings.directInterface()); fixCategories(); diff --git a/src/mainwindow.h b/src/mainwindow.h index aa49205d..7326425a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -103,6 +103,7 @@ namespace Ui { class MainWindow; } +class Settings; class MainWindow : public QMainWindow, public IUserInterface @@ -113,7 +114,7 @@ class MainWindow : public QMainWindow, public IUserInterface public: - explicit MainWindow(QSettings &initSettings, + explicit MainWindow(Settings &settings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent = 0); ~MainWindow(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1e164525..72c8dab5 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -480,14 +480,14 @@ return true; } -void OrganizerCore::updateExecutablesList(QSettings &settings) +void OrganizerCore::updateExecutablesList() { if (m_PluginContainer == nullptr) { log::error("can't update executables list now"); return; } - m_ExecutablesList.load(managedGame(), settings); + m_ExecutablesList.load(managedGame(), m_Settings.directInterface()); // TODO this has nothing to do with executables list move to an appropriate // function! diff --git a/src/organizercore.h b/src/organizercore.h index 2aa7e707..926a21f0 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -107,7 +107,7 @@ public: void setManagedGame(MOBase::IPluginGame *game); - void updateExecutablesList(QSettings &settings); + void updateExecutablesList(); void startMOUpdate(); diff --git a/src/settings.cpp b/src/settings.cpp index 77db6918..5d103267 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -28,8 +28,8 @@ using namespace MOBase; Settings *Settings::s_Instance = nullptr; -Settings::Settings(const QSettings &settingsSource) - : m_Settings(settingsSource.fileName(), settingsSource.format()) +Settings::Settings(const QString& path) + : m_Settings(path, QSettings::IniFormat) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -280,7 +280,57 @@ QString Settings::getModDirectory(bool resolve) const QString Settings::getManagedGameDirectory() const { - return m_Settings.value("gamePath", "").toString(); + return QString::fromUtf8(m_Settings.value("gamePath", "").toByteArray()); +} + +void Settings::setManagedGameDirectory(const QString& path) +{ + m_Settings.setValue("gamePath", QDir::toNativeSeparators(path).toUtf8()); +} + +QString Settings::getManagedGameName() const +{ + return m_Settings.value("gameName", "").toString(); +} + +void Settings::setManagedGameName(const QString& name) +{ + m_Settings.setValue("gameName", name); +} + +QString Settings::getManagedGameEdition() const +{ + return m_Settings.value("game_edition", "").toString(); +} + +void Settings::setManagedGameEdition(const QString& name) +{ + m_Settings.setValue("game_edition", name); +} + +QString Settings::getSelectedProfileName() const +{ + return QString::fromUtf8(m_Settings.value("selected_profile", "").toByteArray()); +} + +int Settings::getMainWindowMonitor() const +{ + return m_Settings.value("window_monitor", -1).toInt(); +} + +QString Settings::getStyleName() const +{ + return m_Settings.value("Settings/style", "").toString(); +} + +void Settings::setStyleName(const QString& name) +{ + m_Settings.setValue("Settings/style", name); +} + +bool Settings::isCategoryListVisible() const +{ + return m_Settings.value("categorylist_visible", true).toBool(); } QString Settings::getProfileDirectory(bool resolve) const @@ -608,3 +658,24 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } + +void Settings::dump() const +{ + static const QStringList ignore({ + "username", "password", "nexus_api_key" + }); + + log::debug("settings:"); + + m_Settings.beginGroup("Settings"); + + for (auto k : m_Settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } + + log::debug(" . {}={}", k, m_Settings.value(k).toString()); + } + + m_Settings.endGroup(); +} diff --git a/src/settings.h b/src/settings.h index 63718089..f06aece9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -40,7 +40,7 @@ class Settings : public QObject Q_OBJECT public: - Settings(const QSettings &settingsSource); + Settings(const QString& path); ~Settings(); static Settings &instance(); @@ -123,6 +123,24 @@ public: * retrieve the directory where the managed game is stored (with native separators) **/ QString getManagedGameDirectory() const; + void setManagedGameDirectory(const QString& path); + + QString getManagedGameName() const; + void setManagedGameName(const QString& name); + + QString getManagedGameEdition() const; + void setManagedGameEdition(const QString& name); + + QString getSelectedProfileName() const; + + // returns -1 if not set + // + int getMainWindowMonitor() const; + + QString getStyleName() const; + void setStyleName(const QString& name); + + bool isCategoryListVisible() const; /** * retrieve the directory where profiles stored (with native separators) @@ -370,6 +388,8 @@ public: MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + void dump() const; + // temp QMap m_PluginSettings; QMap m_PluginDescriptions; -- cgit v1.3.1 From 07f1ac7a96dcf4c91a24bb1d30af92851ecda78f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 01:55:21 -0400 Subject: split into GeometrySettings removed most of storeSettings() from OrganizerCore: QSettings handles saving by itself, no need for that removed topLevelSplitter from ui, unused since the log widget is in a dock removed QSettings from MainWindow::readSettings() replaced return values for some of the new getters in Settings to std::optional --- src/executableslist.cpp | 9 ++- src/executableslist.h | 5 +- src/filedialogmemory.cpp | 8 ++- src/filedialogmemory.h | 5 +- src/iuserinterface.h | 4 +- src/main.cpp | 96 +++++++++++++++---------- src/mainwindow.cpp | 116 ++++++++++++++---------------- src/mainwindow.h | 4 +- src/mainwindow.ui | 5 -- src/organizercore.cpp | 90 +++++------------------- src/organizercore.h | 4 -- src/settings.cpp | 180 +++++++++++++++++++++++++++++++++++++++++++---- src/settings.h | 55 ++++++++++++--- 13 files changed, 359 insertions(+), 222 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 3f76bb6f..2b3219df 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "iplugingame.h" #include "utility.h" +#include "settings.h" #include #include @@ -64,7 +65,7 @@ bool ExecutablesList::empty() const return m_Executables.empty(); } -void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) +void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) { log::debug("loading executables"); @@ -74,6 +75,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) // executables from 2.2.0, see upgradeFromCustom() bool needsUpgrade = false; + auto& settings = const_cast(s.directInterface()); + int numCustomExecutables = settings.beginReadArray("customExecutables"); for (int i = 0; i < numCustomExecutables; ++i) { settings.setArrayIndex(i); @@ -108,8 +111,10 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) dump(); } -void ExecutablesList::store(QSettings& settings) +void ExecutablesList::store(Settings& s) { + auto& settings = s.directInterface(); + settings.remove("customExecutables"); settings.beginWriteArray("customExecutables"); diff --git a/src/executableslist.h b/src/executableslist.h index eda2034e..23cf3cfe 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include namespace MOBase { class IPluginGame; class ExecutableInfo; } +class Settings; /*! * @brief Information about an executable @@ -103,7 +104,7 @@ public: /** * @brief initializes the list from the settings and the given plugin **/ - void load(const MOBase::IPluginGame* game, QSettings& settings); + void load(const MOBase::IPluginGame* game, const Settings& settings); /** * @brief re-adds all the executables from the plugin and renames existing @@ -114,7 +115,7 @@ public: /** * @brief writes the current list to the settings */ - void store(QSettings& settings); + void store(Settings& settings); /** * @brief get an executable by name diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 308a175e..48828563 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -27,8 +27,10 @@ FileDialogMemory::FileDialogMemory() } -void FileDialogMemory::save(QSettings &settings) +void FileDialogMemory::save(Settings& s) { + auto& settings = s.directInterface(); + settings.remove("recentDirectories"); settings.beginWriteArray("recentDirectories"); int index = 0; @@ -42,8 +44,10 @@ void FileDialogMemory::save(QSettings &settings) } -void FileDialogMemory::restore(QSettings &settings) +void FileDialogMemory::restore(const Settings& s) { + auto& settings = const_cast(s.directInterface()); + int size = settings.beginReadArray("recentDirectories"); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index 1a72b289..d214a8e6 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . #include #include -#include #include class Settings; @@ -31,8 +30,8 @@ class Settings; class FileDialogMemory { public: - static void save(QSettings &settings); - static void restore(QSettings &settings); + static void save(Settings& settings); + static void restore(const Settings& settings); static QString getOpenFileName( const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), diff --git a/src/iuserinterface.h b/src/iuserinterface.h index bba8de2b..7205f982 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -10,13 +10,13 @@ #include -class QSettings; +class Settings; class IUserInterface { public: - virtual void storeSettings(QSettings &settings) = 0; + virtual void storeSettings(Settings &settings) = 0; virtual void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr) = 0; virtual void registerPluginTools(std::vector toolPlugins) = 0; diff --git a/src/main.cpp b/src/main.cpp index 720ecbf9..3e26ea17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,7 +62,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -116,7 +115,7 @@ bool bootstrap() shellDelete(QStringList(backupDirectory)); } - // cycle logfile + // cycle log file removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), "usvfs*.log", 5, QDir::Name); @@ -247,7 +246,7 @@ static bool HaveWriteAccess(const std::wstring &path) QString determineProfile(QStringList &arguments, const Settings &settings) { - QString selectedProfileName = settings.getSelectedProfileName(); + auto selectedProfileName = settings.getSelectedProfileName(); { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); @@ -259,14 +258,14 @@ QString determineProfile(QStringList &arguments, const Settings &settings) arguments.removeAt(profileIndex); } - if (selectedProfileName.isEmpty()) { + if (!selectedProfileName) { log::debug("no configured profile"); selectedProfileName = "Default"; } else { - log::debug("configured profile: {}", selectedProfileName); + log::debug("configured profile: {}", *selectedProfileName); } - return selectedProfileName; + return *selectedProfileName; } MOBase::IPluginGame *selectGame( @@ -290,27 +289,27 @@ MOBase::IPluginGame *determineCurrentGame( //user has done something odd. //If the game name has been set up, try to use that. - const QString gameName = settings.getManagedGameName(); - bool gameConfigured = !gameName.isEmpty(); + const auto gameName = settings.getManagedGameName(); + const bool gameConfigured = (gameName.has_value() && *gameName != ""); if (gameConfigured) { - MOBase::IPluginGame *game = plugins.managedGame(gameName); + MOBase::IPluginGame *game = plugins.managedGame(*gameName); if (game == nullptr) { - reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); + reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(*gameName)); return nullptr; } - QString gamePath = settings.getManagedGameDirectory(); - if (gamePath == "") { + auto gamePath = settings.getManagedGameDirectory(); + if (!gamePath || *gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } - QDir gameDir(gamePath); + QDir gameDir(*gamePath); QFileInfo directoryInfo(gameDir.path()); if (directoryInfo.isSymLink()) { reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath)); } if (game->looksValid(gameDir)) { @@ -321,17 +320,20 @@ MOBase::IPluginGame *determineCurrentGame( //If we've made it this far and the instance is already configured for a game, something has gone wrong. //Tell the user about it. if (gameConfigured) { - const QString gamePath = settings.getManagedGameDirectory(); - reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). - arg(gameName).arg(gamePath)); + const auto gamePath = settings.getManagedGameDirectory(); + + reportError( + QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") + .arg(*gameName).arg(gamePath ? *gamePath : "")); } - SelectionDialog selection(gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); + SelectionDialog selection(gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); for (IPluginGame *game : plugins.plugins()) { //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName.compare(game->gameName(), Qt::CaseInsensitive) != 0) + if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) continue; //Only add games that are installed @@ -355,9 +357,11 @@ MOBase::IPluginGame *determineCurrentGame( return selectGame(settings, game->gameDirectory(), game); } - gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), - QString(), QFileDialog::ShowDirsOnly); + gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), + QString(), QFileDialog::ShowDirsOnly); + if (!gamePath.isEmpty()) { QDir gameDir(gamePath); QFileInfo directoryInfo(gamePath); @@ -368,7 +372,7 @@ MOBase::IPluginGame *determineCurrentGame( QList possibleGames; for (IPluginGame * const game : plugins.plugins()) { //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName.compare(game->gameName(), Qt::CaseInsensitive) != 0) + if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) continue; //Only try plugins that look valid for this directory @@ -376,24 +380,31 @@ MOBase::IPluginGame *determineCurrentGame( possibleGames.append(game); } } + if (possibleGames.count() > 1) { - SelectionDialog browseSelection(gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), - nullptr, QSize(32, 32)); + SelectionDialog browseSelection(gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), + nullptr, QSize(32, 32)); + for (IPluginGame *game : possibleGames) { browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game)); } + if (browseSelection.exec() == QDialog::Accepted) { return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); } else { - reportError(gameConfigured ? QObject::tr("Canceled finding %1 in \"%2\".").arg(gameName).arg(gamePath) - : QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); + reportError(gameConfigured ? + QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) : + QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); } } else if(possibleGames.count() == 1) { return selectGame(settings, gameDir, possibleGames[0]); } else { if (gameConfigured) { - reportError(QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.").arg(gameName).arg(gamePath)); + reportError( + QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.") + .arg(*gameName).arg(gamePath)); } else { QString supportedGames; @@ -608,7 +619,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (settings.getManagedGameEdition() == "") { + QString edition; + + if (auto v=settings.getManagedGameEdition()) { + edition = *v; + } else { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -624,12 +639,15 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - settings.setManagedGameEdition(selection.getChoiceString()); + edition = selection.getChoiceString(); + settings.setManagedGameEdition(edition); } } } - game->setGameVariant(settings.getManagedGameEdition()); + Q_ASSERT(!edition.isEmpty()); + + game->setGameVariant(edition); log::info("managing game at {}", game->gameDirectory().absolutePath()); @@ -679,10 +697,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - const int monitor = settings.getMainWindowMonitor(); - if (monitor != -1 && QGuiApplication::screens().size() > monitor) { - QGuiApplication::screens().at(monitor)->geometry().center(); - const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); + const auto monitor = settings.geometry().getMainWindowMonitor(); + if (monitor && QGuiApplication::screens().size() > *monitor) { + QGuiApplication::screens().at(*monitor)->geometry().center(); + const QPoint center = QGuiApplication::screens().at(*monitor)->geometry().center(); splash.move(center - splash.rect().center()); } else { const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); @@ -703,7 +721,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.getStyleName())) { + if (!application.setStyleFile(settings.getStyleName().value_or(""))) { // disable invalid stylesheet settings.setStyleName(""); } @@ -726,7 +744,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); - mainWindow.readSettings(); + mainWindow.readSettings(settings); log::debug("displaying main window"); mainWindow.show(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7f7ded80..e77d08b1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -216,26 +216,24 @@ const QSize LargeToolbarSize(42, 36); class DockFixer { public: - static void save(MainWindow* mw, QSettings& settings) + static void save(MainWindow* mw, Settings& settings) { - const auto docks = mw->findChildren(); - // saves the size of each dock - for (int i=0; ifindChildren()) { int size = 0; // save the width for horizontal docks, or the height for vertical - if (orientation(mw, docks[i]) == Qt::Horizontal) { - size = docks[i]->size().width(); + if (orientation(mw, dock) == Qt::Horizontal) { + size = dock->size().width(); } else { - size = docks[i]->size().height(); + size = dock->size().height(); } - settings.setValue(settingName(docks[i]), size); + settings.geometry().setDockSize(dock->objectName(), size); } } - static void restore(MainWindow* mw, const QSettings& settings) + static void restore(MainWindow* mw, const Settings& settings) { struct DockInfo { @@ -246,16 +244,11 @@ public: std::vector dockInfos; - const auto docks = mw->findChildren(); - // for each dock - for (int i=0; ifindChildren()) { + if (auto size=settings.geometry().getDockSize(dock->objectName())) { // remember this dock, its size and orientation - const auto size = settings.value(name).toInt(); - dockInfos.push_back({docks[i], size, orientation(mw, docks[i])}); + dockInfos.push_back({dock, *size, orientation(mw, dock)}); } } @@ -264,30 +257,25 @@ public: // // some people said a single processEvents() call is enough, but it doesn't // look like it - QTimer::singleShot(1, [=] { + QTimer::singleShot(5, [=] { for (const auto& info : dockInfos) { mw->resizeDocks({info.d}, {info.size}, info.ori); } - }); + }); } - static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d) + static Qt::Orientation orientation(QMainWindow* mw, const QDockWidget* d) { // docks in these areas are horizontal const auto horizontalAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; - if (mw->dockWidgetArea(d) & horizontalAreas) { + if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { return Qt::Horizontal; } else { return Qt::Vertical; } } - - static QString settingName(QDockWidget* d) - { - return "geometry/" + d->objectName() + "_size"; - } }; @@ -359,9 +347,6 @@ MainWindow::MainWindow(Settings &settings ui->logList->setCore(m_OrganizerCore); - int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value - ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); - updateProblemsButton(); setupToolbar(); @@ -540,8 +525,7 @@ MainWindow::MainWindow(Settings &settings connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); - setCategoryListVisible(settings.isCategoryListVisible()); - FileDialogMemory::restore(settings.directInterface()); + FileDialogMemory::restore(settings); fixCategories(); @@ -2247,52 +2231,50 @@ void MainWindow::activateProxy(bool activate) busyDialog.hide(); } -void MainWindow::readSettings() +void MainWindow::readSettings(const Settings& settings) { - QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); - - if (settings.contains("window_geometry")) { - restoreGeometry(settings.value("window_geometry").toByteArray()); + if (auto v=settings.geometry().getMainWindow()) { + restoreGeometry(*v); } - if (settings.contains("window_state")) { - restoreState(settings.value("window_state").toByteArray()); + if (auto v=settings.geometry().getMainWindowState()) { + restoreState(*v); } - if (settings.contains("toolbar_size")) { - setToolbarSize(settings.value("toolbar_size").toSize()); + if (auto v=settings.geometry().getToolbarSize()) { + setToolbarSize(*v); } - if (settings.contains("toolbar_button_style")) { - setToolbarButtonStyle(static_cast( - settings.value("toolbar_button_style").toInt())); + if (auto v=settings.geometry().getToolbarButtonStyle()) { + setToolbarButtonStyle(*v); } - if (settings.contains("menubar_visible")) { - showMenuBar(settings.value("menubar_visible").toBool()); + if (auto v=settings.geometry().getMenubarVisible()) { + showMenuBar(*v); } - if (settings.contains("statusbar_visible")) { - showStatusBar(settings.value("statusbar_visible").toBool()); + if (auto v=settings.geometry().getStatusbarVisible()) { + showStatusBar(*v); } - if (settings.contains("window_split")) { - ui->splitter->restoreState(settings.value("window_split").toByteArray()); + if (auto v=settings.geometry().getMainSplitterState()) { + ui->splitter->restoreState(*v); } - if (settings.contains("log_split")) { - ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray()); + { + auto v = settings.geometry().getFiltersVisible().value_or(false); + setCategoryListVisible(v); + ui->displayCategoriesBtn->setChecked(v); } - bool filtersVisible = settings.value("filters_visible", false).toBool(); - setCategoryListVisible(filtersVisible); - ui->displayCategoriesBtn->setChecked(filtersVisible); - - int selectedExecutable = settings.value("selected_executable").toInt(); - setExecutableIndex(selectedExecutable); + if (auto v=settings.getSelectedExecutable()) { + setExecutableIndex(*v); + } - if (settings.value("Settings/use_proxy", false).toBool()) { - activateProxy(true); + if (auto v=settings.getUseProxy()) { + if (*v) { + activateProxy(true); + } } DockFixer::restore(this, settings); @@ -2335,6 +2317,12 @@ void MainWindow::processUpdates() { ui->downloadView->header()->hideSection(i); } } + if (lastVersion < QVersionNumber(2, 2, 2)) { + QSettings &instance = Settings::instance().directInterface(); + + // log splitter is gone, it's a dock now + instance.remove("log_split"); + } } if (currentVersion > lastVersion) { @@ -2354,7 +2342,9 @@ void MainWindow::processUpdates() { settings.setValue("version", currentVersion.toString()); } -void MainWindow::storeSettings(QSettings &settings) { +void MainWindow::storeSettings(Settings& s) { + auto& settings = s.directInterface(); + settings.setValue("group_state", ui->groupCombo->currentIndex()); settings.setValue("selected_executable", ui->executablesListBox->currentIndex()); @@ -2367,7 +2357,6 @@ void MainWindow::storeSettings(QSettings &settings) { settings.remove("menubar_visible"); settings.remove("window_split"); settings.remove("window_monitor"); - settings.remove("log_split"); settings.remove("filters_visible"); settings.remove("browser_geometry"); settings.remove("geometry"); @@ -2383,7 +2372,6 @@ void MainWindow::storeSettings(QSettings &settings) { QScreen *screen = this->window()->windowHandle()->screen(); int screenId = QGuiApplication::screens().indexOf(screen); settings.setValue("window_monitor", screenId); - settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); @@ -2392,7 +2380,7 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue(key, kv.second->saveState()); } - DockFixer::save(this, settings); + DockFixer::save(this, s); } } @@ -5213,7 +5201,7 @@ void MainWindow::on_actionSettings_triggered() QString oldModDirectory(settings.getModDirectory()); QString oldCacheDirectory(settings.getCacheDirectory()); QString oldProfilesDirectory(settings.getProfileDirectory()); - QString oldManagedGameDirectory(settings.getManagedGameDirectory()); + QString oldManagedGameDirectory(settings.getManagedGameDirectory().value_or("")); bool oldDisplayForeign(settings.displayForeign()); bool proxy = settings.useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 7326425a..d4513c0f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -119,8 +119,8 @@ public: QWidget *parent = 0); ~MainWindow(); - void storeSettings(QSettings &settings) override; - void readSettings(); + void storeSettings(Settings& settings) override; + void readSettings(const Settings& settings); void processUpdates(); virtual ILockedWaitingForProcess* lock() override; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 6c6d0bca..e9910b83 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -47,10 +47,6 @@ 0 - - - Qt::Vertical - @@ -1286,7 +1282,6 @@ p, li { white-space: pre-wrap; } - diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 72c8dab5..a64d93b4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -94,15 +94,6 @@ static bool isOnline() return false; } -static bool renameFile(const QString &oldName, const QString &newName, - bool overwrite = true) -{ - if (overwrite && QFile::exists(newName)) { - QFile::remove(newName); - } - return QFile::rename(oldName, newName); -} - static std::wstring getProcessName(HANDLE process) { wchar_t buffer[MAX_PATH]; @@ -342,80 +333,37 @@ OrganizerCore::~OrganizerCore() delete m_DirectoryStructure; } -QString OrganizerCore::commitSettings(const QString &iniFile) -{ - if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) { - DWORD err = ::GetLastError(); - // make a second attempt using qt functions but if that fails print the - // error from the first attempt - if (!renameFile(iniFile + ".new", iniFile)) { - return QString::fromStdWString(formatSystemMessage(err)); - } - } - return QString(); -} - -QSettings::Status OrganizerCore::storeSettings(const QString &fileName) +void OrganizerCore::storeSettings() { - QSettings settings(fileName, QSettings::IniFormat); - if (m_UserInterface != nullptr) { - m_UserInterface->storeSettings(settings); + m_UserInterface->storeSettings(m_Settings); } if (m_CurrentProfile != nullptr) { - settings.setValue("selected_profile", - m_CurrentProfile->name().toUtf8().constData()); + m_Settings.setSelectedProfileName(m_CurrentProfile->name()); } - m_ExecutablesList.store(settings); - - FileDialogMemory::save(settings); + m_ExecutablesList.store(m_Settings); - settings.sync(); - return settings.status(); -} - -void OrganizerCore::storeSettings() -{ - QString iniFile = qApp->property("dataPath").toString() + "/" - + QString::fromStdWString(AppConfig::iniFileName()); - if (QFileInfo(iniFile).exists()) { - if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { - const auto e = GetLastError(); - QMessageBox::critical( - qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occurred trying to update MO settings to %1: %2") - .arg(iniFile) - .arg(QString::fromStdWString(formatSystemMessage(e)))); - return; - } - } + FileDialogMemory::save(m_Settings); - QString writeTarget = iniFile + ".new"; + const auto result = m_Settings.sync(); - QSettings::Status result = storeSettings(writeTarget); + if (result != QSettings::NoError) { + QString reason; - if (result == QSettings::NoError) { - QString errMsg = commitSettings(iniFile); - if (!errMsg.isEmpty()) { - log::warn( - "settings file not writable, may be locked by another " - "application, trying direct write"); - writeTarget = iniFile; - result = storeSettings(iniFile); + if (result == QSettings::AccessError) { + reason = tr("File is write protected"); + } else if (result == QSettings::FormatError) { + reason = tr("Invalid file format (probably a bug)"); + } else { + reason = tr("Unknown error %1").arg(result); } - } - if (result != QSettings::NoError) { - QString reason = result == QSettings::AccessError - ? tr("File is write protected") - : result == QSettings::FormatError - ? tr("Invalid file format (probably a bug)") - : tr("Unknown error %1").arg(result); + QMessageBox::critical( - qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occurred trying to write back MO settings to %1: %2") - .arg(writeTarget, reason)); + qApp->activeWindow(), tr("Failed to write settings"), + tr("An error occurred trying to write back MO settings to %1: %2") + .arg(m_Settings.getFilename(), reason)); } } @@ -487,7 +435,7 @@ void OrganizerCore::updateExecutablesList() return; } - m_ExecutablesList.load(managedGame(), m_Settings.directInterface()); + m_ExecutablesList.load(managedGame(), m_Settings); // TODO this has nothing to do with executables list move to an appropriate // function! diff --git a/src/organizercore.h b/src/organizercore.h index 926a21f0..4bcfe745 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -288,10 +288,6 @@ private: void storeSettings(); - QSettings::Status storeSettings(const QString &fileName); - - QString commitSettings(const QString &iniFile); - bool queryApi(QString &apiKey); void updateModActiveState(int index, bool active); diff --git a/src/settings.cpp b/src/settings.cpp index 5d103267..d843a0db 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -26,10 +26,56 @@ along with Mod Organizer. If not, see . using namespace MOBase; +template +T convertVariant(const QVariant& v); + +template <> +QByteArray convertVariant(const QVariant& v) +{ + return v.toByteArray(); +} + +template <> +QString convertVariant(const QVariant& v) +{ + return v.toString(); +} + +template <> +int convertVariant(const QVariant& v) +{ + return v.toInt(); +} + +template <> +bool convertVariant(const QVariant& v) +{ + return v.toBool(); +} + +template <> +QSize convertVariant(const QVariant& v) +{ + return v.toSize(); +} + + + +template +std::optional getOptional(const QSettings& s, const QString& name) +{ + if (s.contains(name)) { + return convertVariant(s.value(name)); + } + + return {}; +} + + Settings *Settings::s_Instance = nullptr; Settings::Settings(const QString& path) - : m_Settings(path, QSettings::IniFormat) + : m_Settings(path, QSettings::IniFormat), m_Geometry(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -51,6 +97,11 @@ Settings &Settings::instance() return *s_Instance; } +QString Settings::getFilename() const +{ + return m_Settings.fileName(); +} + void Settings::clearPlugins() { m_Plugins.clear(); @@ -278,9 +329,13 @@ QString Settings::getModDirectory(bool resolve) const return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve); } -QString Settings::getManagedGameDirectory() const +std::optional Settings::getManagedGameDirectory() const { - return QString::fromUtf8(m_Settings.value("gamePath", "").toByteArray()); + if (auto v=getOptional(m_Settings, "gamePath")) { + return QString::fromUtf8(*v); + } + + return {}; } void Settings::setManagedGameDirectory(const QString& path) @@ -288,9 +343,9 @@ void Settings::setManagedGameDirectory(const QString& path) m_Settings.setValue("gamePath", QDir::toNativeSeparators(path).toUtf8()); } -QString Settings::getManagedGameName() const +std::optional Settings::getManagedGameName() const { - return m_Settings.value("gameName", "").toString(); + return getOptional(m_Settings, "gameName"); } void Settings::setManagedGameName(const QString& name) @@ -298,9 +353,9 @@ void Settings::setManagedGameName(const QString& name) m_Settings.setValue("gameName", name); } -QString Settings::getManagedGameEdition() const +std::optional Settings::getManagedGameEdition() const { - return m_Settings.value("game_edition", "").toString(); + return getOptional(m_Settings, "game_edition"); } void Settings::setManagedGameEdition(const QString& name) @@ -308,19 +363,23 @@ void Settings::setManagedGameEdition(const QString& name) m_Settings.setValue("game_edition", name); } -QString Settings::getSelectedProfileName() const +std::optional Settings::getSelectedProfileName() const { - return QString::fromUtf8(m_Settings.value("selected_profile", "").toByteArray()); + if (auto v=getOptional(m_Settings, "selected_profile")) { + return QString::fromUtf8(*v); + } + + return {}; } -int Settings::getMainWindowMonitor() const +void Settings::setSelectedProfileName(const QString& name) { - return m_Settings.value("window_monitor", -1).toInt(); + m_Settings.setValue("selected_profile", name.toUtf8()); } -QString Settings::getStyleName() const +std::optional Settings::getStyleName() const { - return m_Settings.value("Settings/style", "").toString(); + return getOptional(m_Settings, "Settings/style"); } void Settings::setStyleName(const QString& name) @@ -328,9 +387,14 @@ void Settings::setStyleName(const QString& name) m_Settings.setValue("Settings/style", name); } -bool Settings::isCategoryListVisible() const +std::optional Settings::getSelectedExecutable() const { - return m_Settings.value("categorylist_visible", true).toBool(); + return getOptional(m_Settings, "selected_executable"); +} + +std::optional Settings::getUseProxy() const +{ + return getOptional(m_Settings, "Settings/use_proxy"); } QString Settings::getProfileDirectory(bool resolve) const @@ -659,6 +723,22 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } +GeometrySettings& Settings::geometry() +{ + return m_Geometry; +} + +const GeometrySettings& Settings::geometry() const +{ + return m_Geometry; +} + +QSettings::Status Settings::sync() const +{ + m_Settings.sync(); + return m_Settings.status(); +} + void Settings::dump() const { static const QStringList ignore({ @@ -679,3 +759,73 @@ void Settings::dump() const m_Settings.endGroup(); } + + +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s) +{ +} + +std::optional GeometrySettings::getMainWindow() const +{ + return getOptional(m_Settings, "window_geometry"); +} + +std::optional GeometrySettings::getMainWindowState() const +{ + return getOptional(m_Settings, "window_state"); +} + +std::optional GeometrySettings::getToolbarSize() const +{ + return getOptional(m_Settings, "toolbar_size"); +} + +std::optional GeometrySettings::getToolbarButtonStyle() const +{ + if (auto v=getOptional(m_Settings, "toolbar_button_style")) { + return static_cast(*v); + } + + return {}; +} + +std::optional GeometrySettings::getMenubarVisible() const +{ + return getOptional(m_Settings, "menubar_visible"); +} + +std::optional GeometrySettings::getStatusbarVisible() const +{ + return getOptional(m_Settings, "statusbar_visible"); +} + +std::optional GeometrySettings::getMainSplitterState() const +{ + return getOptional(m_Settings, "window_split"); +} + +std::optional GeometrySettings::getFiltersVisible() const +{ + return getOptional(m_Settings, "filters_visible"); +} + +std::optional GeometrySettings::getMainWindowMonitor() const +{ + return getOptional(m_Settings, "window_monitor"); +} + +void GeometrySettings::setDockSize(const QString& name, int size) +{ + m_Settings.setValue("geometry/" + name + "_size", size); +} + +std::optional GeometrySettings::getDockSize(const QString& name) const +{ + return getOptional(m_Settings, "geometry/" + name + "_size"); +} + +std::optional GeometrySettings::isCategoryListVisible() const +{ + return getOptional(m_Settings, "categorylist_visible"); +} diff --git a/src/settings.h b/src/settings.h index f06aece9..066843c2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -31,13 +31,40 @@ namespace MOBase { class PluginContainer; struct ServerInfo; + +class GeometrySettings +{ +public: + GeometrySettings(QSettings& s); + + std::optional getMainWindow() const; + std::optional getMainWindowState() const; + std::optional getToolbarSize() const; + std::optional getToolbarButtonStyle() const; + std::optional getMenubarVisible() const; + std::optional getStatusbarVisible() const; + std::optional getMainSplitterState() const; + std::optional getFiltersVisible() const; + + std::optional getMainWindowMonitor() const; + void setDockSize(const QString& name, int size); + + std::optional getDockSize(const QString& name) const; + + std::optional isCategoryListVisible() const; + +private: + QSettings& m_Settings; +}; + + /** * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc **/ class Settings : public QObject { - Q_OBJECT + Q_OBJECT; public: Settings(const QString& path); @@ -45,6 +72,8 @@ public: static Settings &instance(); + QString getFilename() const; + /** * unregister all plugins from settings */ @@ -122,25 +151,26 @@ public: /** * retrieve the directory where the managed game is stored (with native separators) **/ - QString getManagedGameDirectory() const; + std::optional getManagedGameDirectory() const; void setManagedGameDirectory(const QString& path); - QString getManagedGameName() const; + std::optional getManagedGameName() const; void setManagedGameName(const QString& name); - QString getManagedGameEdition() const; + std::optional getManagedGameEdition() const; void setManagedGameEdition(const QString& name); - QString getSelectedProfileName() const; - - // returns -1 if not set - // - int getMainWindowMonitor() const; + std::optional getSelectedProfileName() const; + void setSelectedProfileName(const QString& name); - QString getStyleName() const; + std::optional getStyleName() const; void setStyleName(const QString& name); - bool isCategoryListVisible() const; + std::optional getSelectedExecutable() const; + std::optional getUseProxy() const; + + GeometrySettings& geometry(); + const GeometrySettings& geometry() const; /** * retrieve the directory where profiles stored (with native separators) @@ -388,6 +418,8 @@ public: MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + QSettings::Status sync() const; + void dump() const; // temp @@ -407,6 +439,7 @@ private: static Settings *s_Instance; MOBase::IPluginGame const *m_GamePlugin; mutable QSettings m_Settings; + GeometrySettings m_Geometry; LoadMechanism m_LoadMechanism; std::vector m_Plugins; -- cgit v1.3.1 From 4f1b15f0a1b2e6cbca4b420608d81570af489067 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 24 Aug 2019 16:07:07 -0400 Subject: changed crash dump type to use enum instead of int added ColorSettings settings dialog general and diag tabs don't use qsettings anymore removed logging of setting changes, will be added back to Settings class --- src/modinfo.cpp | 1 - src/modlist.cpp | 10 +-- src/organizercore.cpp | 6 +- src/organizercore.h | 4 +- src/pluginlist.cpp | 2 +- src/settings.cpp | 160 ++++++++++++++++++++++++++++++-------- src/settings.h | 70 ++++++++++------- src/settingsdialog.cpp | 26 +------ src/settingsdialog.ui | 20 ----- src/settingsdialogdiagnostics.cpp | 38 ++++++++- src/settingsdialogdiagnostics.h | 1 + src/settingsdialoggeneral.cpp | 66 ++++++++-------- src/usvfsconnector.cpp | 7 +- src/usvfsconnector.h | 2 +- 14 files changed, 259 insertions(+), 154 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 5a05e7ca..e3daa4fd 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -42,7 +42,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include using namespace MOBase; using namespace MOShared; diff --git a/src/modlist.cpp b/src/modlist.cpp index c591c49b..94b4a387 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -416,15 +416,15 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end(); bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end(); if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { - return Settings::instance().modlistContainsPluginColor(); + return Settings::instance().colors().modlistContainsPlugin(); } else if (overwritten || archiveLooseOverwritten) { - return Settings::instance().modlistOverwritingLooseColor(); + return Settings::instance().colors().modlistOverwritingLoose(); } else if (overwrite || archiveLooseOverwrite) { - return Settings::instance().modlistOverwrittenLooseColor(); + return Settings::instance().colors().modlistOverwrittenLoose(); } else if (archiveOverwritten) { - return Settings::instance().modlistOverwritingArchiveColor(); + return Settings::instance().colors().modlistOverwritingArchive(); } else if (archiveOverwrite) { - return Settings::instance().modlistOverwrittenArchiveColor(); + return Settings::instance().colors().modlistOverwrittenArchive(); } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid() && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 73d0abac..5a8ee4c2 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -667,7 +667,7 @@ void OrganizerCore::prepareVFS() } void OrganizerCore::updateVFSParams( - log::Levels logLevel, int crashDumpsType, QString executableBlacklist) + log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist) { setGlobalCrashDumpsType(crashDumpsType); m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); @@ -692,8 +692,8 @@ bool OrganizerCore::cycleDiagnostics() { } //static -void OrganizerCore::setGlobalCrashDumpsType(int crashDumpsType) { - m_globalCrashDumpsType = ::crashDumpsType(crashDumpsType); +void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type) { + m_globalCrashDumpsType = type; } //static diff --git a/src/organizercore.h b/src/organizercore.h index 4bcfe745..a14d79a9 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -193,7 +193,7 @@ public: void prepareVFS(); void updateVFSParams( - MOBase::log::Levels logLevel, int crashDumpsType, + MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist); void setLogLevel(MOBase::log::Levels level); @@ -201,7 +201,7 @@ public: bool cycleDiagnostics(); static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; } - static void setGlobalCrashDumpsType(int crashDumpsType); + static void setGlobalCrashDumpsType(CrashDumpsType crashDumpsType); static std::wstring crashDumpsPath(); public: diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 8637f546..ddfe492e 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -931,7 +931,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const } else if (role == Qt::BackgroundRole || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { if (m_ESPs[index].m_ModSelected) { - return Settings::instance().pluginListContainedColor(); + return Settings::instance().colors().pluginListContained(); } else { return QVariant(); } diff --git a/src/settings.cpp b/src/settings.cpp index 844ee81e..2236fc9d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -24,7 +24,6 @@ along with Mod Organizer. If not, see . #include "expanderwidget.h" #include #include -#include using namespace MOBase; @@ -33,7 +32,13 @@ std::optional getOptional( const QSettings& s, const QString& name, std::optional def={}) { if (s.contains(name)) { - return s.value(name).value(); + const auto v = s.value(name); + + if constexpr (std::is_enum_v) { + return static_cast(v.value>()); + } else { + return v.value(); + } } return def; @@ -160,10 +165,12 @@ void warnIfNotCheckable(const QAbstractButton* b) } } + Settings *Settings::s_Instance = nullptr; -Settings::Settings(const QString& path) - : m_Settings(path, QSettings::IniFormat), m_Geometry(m_Settings) +Settings::Settings(const QString& path) : + m_Settings(path, QSettings::IniFormat), + m_Geometry(m_Settings), m_Colors(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -280,6 +287,11 @@ bool Settings::colorSeparatorScrollbar() const return m_Settings.value("Settings/colorSeparatorScrollbars", true).toBool(); } +void Settings::setColorSeparatorScrollbar(bool b) +{ + m_Settings.setValue("Settings/colorSeparatorScrollbars", b); +} + void Settings::managedGameChanged(IPluginGame const *gamePlugin) { m_GamePlugin = gamePlugin; @@ -397,6 +409,11 @@ bool Settings::usePrereleases() const return m_Settings.value("Settings/use_prereleases", false).toBool(); } +void Settings::setUsePrereleases(bool b) +{ + m_Settings.setValue("Settings/use_prereleases", b); +} + void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) { m_Settings.beginGroup("Servers"); @@ -615,16 +632,27 @@ bool Settings::getSteamLogin(QString &username, QString &password) const return !username.isEmpty() && !password.isEmpty(); } + bool Settings::compactDownloads() const { return m_Settings.value("Settings/compact_downloads", false).toBool(); } +void Settings::setCompactDownloads(bool b) +{ + m_Settings.setValue("Settings/compact_downloads", b); +} + bool Settings::metaDownloads() const { return m_Settings.value("Settings/meta_downloads", false).toBool(); } +void Settings::setMetaDownloads(bool b) +{ + m_Settings.setValue("Settings/meta_downloads", b); +} + bool Settings::offlineMode() const { return m_Settings.value("Settings/offline_mode", false).toBool(); @@ -640,44 +668,25 @@ void Settings::setLogLevel(log::Levels level) m_Settings.setValue("Settings/log_level", static_cast(level)); } -int Settings::crashDumpsType() const +CrashDumpsType Settings::crashDumpsType() const { - return m_Settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt(); + const auto v = getOptional(m_Settings, "Settings/crash_dumps_type"); + return v.value_or(CrashDumpsType::Mini); } -int Settings::crashDumpsMax() const +void Settings::setCrashDumpsType(CrashDumpsType type) { - return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); + m_Settings.setValue("Settings/crash_dumps_type", static_cast(type)); } -QColor Settings::modlistOverwrittenLooseColor() const -{ - return m_Settings.value("Settings/overwrittenLooseFilesColor", QColor(0, 255, 0, 64)).value(); -} - -QColor Settings::modlistOverwritingLooseColor() const -{ - return m_Settings.value("Settings/overwritingLooseFilesColor", QColor(255, 0, 0, 64)).value(); -} - -QColor Settings::modlistOverwrittenArchiveColor() const -{ - return m_Settings.value("Settings/overwrittenArchiveFilesColor", QColor(0, 255, 255, 64)).value(); -} - -QColor Settings::modlistOverwritingArchiveColor() const -{ - return m_Settings.value("Settings/overwritingArchiveFilesColor", QColor(255, 0, 255, 64)).value(); -} - -QColor Settings::modlistContainsPluginColor() const +int Settings::crashDumpsMax() const { - return m_Settings.value("Settings/containsPluginColor", QColor(0, 0, 255, 64)).value(); + return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); } -QColor Settings::pluginListContainedColor() const +void Settings::setCrashDumpsMax(int n) { - return m_Settings.value("Settings/containedColor", QColor(0, 0, 255, 64)).value(); + return m_Settings.setValue("Settings/crash_dumps_max", n); } QString Settings::executablesBlacklist() const @@ -850,6 +859,11 @@ QString Settings::language() return result; } +void Settings::setLanguage(const QString& name) +{ + m_Settings.setValue("Settings/language", name); +} + void Settings::updateServers(const QList &servers) { m_Settings.beginGroup("Servers"); @@ -1132,6 +1146,16 @@ const GeometrySettings& Settings::geometry() const return m_Geometry; } +ColorSettings& Settings::colors() +{ + return m_Colors; +} + +const ColorSettings& Settings::colors() const +{ + return m_Colors; +} + QSettings::Status Settings::sync() const { m_Settings.sync(); @@ -1451,6 +1475,78 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const } +ColorSettings::ColorSettings(QSettings& s) + : m_Settings(s) +{ +} + +QColor ColorSettings::modlistOverwrittenLoose() const +{ + return getOptional(m_Settings, "Settings/overwrittenLooseFilesColor") + .value_or(QColor(0, 255, 0, 64)); +} + +void ColorSettings::setModlistOverwrittenLoose(const QColor& c) +{ + m_Settings.setValue("Settings/overwrittenLooseFilesColor", c); +} + +QColor ColorSettings::modlistOverwritingLoose() const +{ + return getOptional(m_Settings, "Settings/overwritingLooseFilesColor") + .value_or(QColor(255, 0, 0, 64)); +} + +void ColorSettings::setModlistOverwritingLoose(const QColor& c) +{ + m_Settings.setValue("Settings/overwritingLooseFilesColor", c); +} + +QColor ColorSettings::modlistOverwrittenArchive() const +{ + return getOptional(m_Settings, "Settings/overwrittenArchiveFilesColor") + .value_or(QColor(0, 255, 255, 64)); +} + +void ColorSettings::setModlistOverwrittenArchive(const QColor& c) +{ + m_Settings.setValue("Settings/overwrittenArchiveFilesColor", c); +} + +QColor ColorSettings::modlistOverwritingArchive() const +{ + return getOptional(m_Settings, "Settings/overwritingArchiveFilesColor") + .value_or(QColor(255, 0, 255, 64)); +} + +void ColorSettings::setModlistOverwritingArchive(const QColor& c) +{ + m_Settings.setValue("Settings/overwritingArchiveFilesColor", c); +} + +QColor ColorSettings::modlistContainsPlugin() const +{ + return getOptional(m_Settings, "Settings/containsPluginColor") + .value_or(QColor(0, 0, 255, 64)); +} + +void ColorSettings::setModlistContainsPlugin(const QColor& c) +{ + m_Settings.setValue("Settings/containsPluginColor", c); +} + +QColor ColorSettings::pluginListContained() const +{ + return getOptional(m_Settings, "Settings/containedColor") + .value_or(QColor(0, 0, 255, 64)); +} + +void ColorSettings::setPluginListContained(const QColor& c) +{ + m_Settings.setValue("Settings/containedColor", c); +} + + GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) : m_settings(s), m_dialog(dialog) { diff --git a/src/settings.h b/src/settings.h index b25af15f..5b1d7bfc 100644 --- a/src/settings.h +++ b/src/settings.h @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "loadmechanism.h" #include #include +#include namespace MOBase { class IPlugin; @@ -96,6 +97,36 @@ private: }; +class ColorSettings +{ +public: + ColorSettings(QSettings& s); + + void setCrashDumpsMax(int i) const; + + QColor modlistOverwrittenLoose() const; + void setModlistOverwrittenLoose(const QColor& c); + + QColor modlistOverwritingLoose() const; + void setModlistOverwritingLoose(const QColor& c); + + QColor modlistOverwrittenArchive() const; + void setModlistOverwrittenArchive(const QColor& c); + + QColor modlistOverwritingArchive() const; + void setModlistOverwritingArchive(const QColor& c); + + QColor modlistContainsPlugin() const; + void setModlistContainsPlugin(const QColor& c); + + QColor pluginListContained() const; + void setPluginListContained(const QColor& c) ; + +private: + QSettings& m_Settings; +}; + + enum class EndorsementState { Accepted = 1, @@ -268,6 +299,10 @@ public: GeometrySettings& geometry(); const GeometrySettings& geometry() const; + ColorSettings& colors(); + const ColorSettings& colors() const; + + /** * retrieve the directory where profiles stored (with native separators) **/ @@ -329,43 +364,22 @@ public: * @return true if the user chose compact downloads */ bool compactDownloads() const; + void setCompactDownloads(bool b); /** * @return true if the user chose meta downloads */ bool metaDownloads() const; + void setMetaDownloads(bool b); - /** - * @return the configured log level - */ MOBase::log::Levels logLevel() const; - - /** - * sets the log level setting - */ void setLogLevel(MOBase::log::Levels level); - /** - * @return the configured crash dumps type - */ - int crashDumpsType() const; + CrashDumpsType crashDumpsType() const; + void setCrashDumpsType(CrashDumpsType type); - /** - * @return the configured crash dumps max - */ int crashDumpsMax() const; - - QColor modlistOverwrittenLooseColor() const; - - QColor modlistOverwritingLooseColor() const; - - QColor modlistOverwrittenArchiveColor() const; - - QColor modlistOverwritingArchiveColor() const; - - QColor modlistContainsPluginColor() const; - - QColor pluginListContainedColor() const; + void setCrashDumpsMax(int n); QString executablesBlacklist() const; @@ -473,6 +487,7 @@ public: * @return short code of the configured language (corresponding to the translation files) */ QString language(); + void setLanguage(const QString& name); /** * @brief updates the list of known servers @@ -499,6 +514,7 @@ public: std::vector plugins() const { return m_Plugins; } bool usePrereleases() const; + void setUsePrereleases(bool b); /** * @brief register MO as the handler for nxm links @@ -512,6 +528,7 @@ public: * @return the state of the setting */ bool colorSeparatorScrollbar() const; + void setColorSeparatorScrollbar(bool b); static QColor getIdealTextColor(const QColor& rBackgroundColor); @@ -540,6 +557,7 @@ private: MOBase::IPluginGame const *m_GamePlugin; mutable QSettings m_Settings; GeometrySettings m_Geometry; + ColorSettings m_Colors; LoadMechanism m_LoadMechanism; std::vector m_Plugins; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 097dafc8..9d031785 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -51,43 +51,19 @@ int SettingsDialog::exec() { GeometrySaver gs(m_settings, this); - auto& qsettings = m_settings.directInterface(); auto ret = TutorableDialog::exec(); if (ret == QDialog::Accepted) { - for (auto&& tab : m_tabs) { tab->closing(); } - // remember settings before change - QMap before; - qsettings.beginGroup("Settings"); - for (auto k : qsettings.allKeys()) - before[k] = qsettings.value(k).toString(); - qsettings.endGroup(); - - // transfer modified settings to configuration file + // update settings for each tab for (std::unique_ptr const &tab: m_tabs) { tab->update(); } - - // print "changed" settings - qsettings.beginGroup("Settings"); - bool first_update = true; - for (auto k : qsettings.allKeys()) - if (qsettings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) - { - if (first_update) { - log::debug("Changed settings:"); - first_update = false; - } - log::debug(" {}={}", k, qsettings.value(k).toString()); - } - qsettings.endGroup(); } - // These changes happen regardless of accepted or rejected bool restartNeeded = false; if (getApiKeyChanged()) { restartNeeded = true; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index e7676387..af230bc0 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1346,26 +1346,6 @@ programs you are intentionally running. "Full" Even larger dumps with a full memory dump of the process. - - - None - - - - - Mini (recommended) - - - - - Data - - - - - Full - - diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 227d1dfa..278da0bf 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -10,10 +10,13 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { setLevelsBox(); - ui->dumpsTypeBox->setCurrentIndex(settings().crashDumpsType()); + setCrashDumpTypesBox(); + ui->dumpsMaxEdit->setValue(settings().crashDumpsMax()); + QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); + ui->diagnosticsExplainedLabel->setText( ui->diagnosticsExplainedLabel->text() .replace("LOGS_FULL_PATH", logsPath) @@ -40,9 +43,36 @@ void DiagnosticsSettingsTab::setLevelsBox() } } +void DiagnosticsSettingsTab::setCrashDumpTypesBox() +{ + ui->dumpsTypeBox->clear(); + + auto add = [&](auto&& text, auto&& type) { + ui->dumpsTypeBox->addItem(text, static_cast(type)); + }; + + add(QObject::tr("None"), CrashDumpsType::None); + add(QObject::tr("Mini (recommended)"), CrashDumpsType::Mini); + add(QObject::tr("Data"), CrashDumpsType::Data); + add(QObject::tr("Full"), CrashDumpsType::Full); + + const auto current = static_cast(settings().crashDumpsType()); + + for (int i=0; idumpsTypeBox->count(); ++i) { + if (ui->dumpsTypeBox->itemData(i) == current) { + ui->dumpsTypeBox->setCurrentIndex(i); + break; + } + } +} + void DiagnosticsSettingsTab::update() { - qsettings().setValue("Settings/log_level", ui->logLevelBox->currentData().toInt()); - qsettings().setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex()); - qsettings().setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value()); + settings().setLogLevel( + static_cast(ui->logLevelBox->currentData().toInt())); + + settings().setCrashDumpsType( + static_cast(ui->dumpsTypeBox->currentData().toInt())); + + settings().setCrashDumpsMax(ui->dumpsMaxEdit->value()); } diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h index f20413f8..f0fbf770 100644 --- a/src/settingsdialogdiagnostics.h +++ b/src/settingsdialogdiagnostics.h @@ -13,6 +13,7 @@ public: private: void setLevelsBox(); + void setCrashDumpTypesBox(); }; #endif // SETTINGSDIALOGDIAGNOSTICS_H diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 35012db7..e3d73037 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -26,28 +26,30 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) } addStyles(); + { - int currentID = ui->styleBox->findData( - qsettings().value("Settings/style", "").toString()); + const int currentID = ui->styleBox->findData( + settings().getStyleName().value_or("")); + if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); } } //version with stylesheet - setButtonColor(ui->overwritingBtn, settings().modlistOverwritingLooseColor()); - setButtonColor(ui->overwrittenBtn, settings().modlistOverwrittenLooseColor()); - setButtonColor(ui->overwritingArchiveBtn, settings().modlistOverwritingArchiveColor()); - setButtonColor(ui->overwrittenArchiveBtn, settings().modlistOverwrittenArchiveColor()); - setButtonColor(ui->containsBtn, settings().modlistContainsPluginColor()); - setButtonColor(ui->containedBtn, settings().pluginListContainedColor()); - - setOverwritingColor(settings().modlistOverwritingLooseColor()); - setOverwrittenColor(settings().modlistOverwrittenLooseColor()); - setOverwritingArchiveColor(settings().modlistOverwritingArchiveColor()); - setOverwrittenArchiveColor(settings().modlistOverwrittenArchiveColor()); - setContainsColor(settings().modlistContainsPluginColor()); - setContainedColor(settings().pluginListContainedColor()); + setButtonColor(ui->overwritingBtn, settings().colors().modlistOverwritingLoose()); + setButtonColor(ui->overwrittenBtn, settings().colors().modlistOverwrittenLoose()); + setButtonColor(ui->overwritingArchiveBtn, settings().colors().modlistOverwritingArchive()); + setButtonColor(ui->overwrittenArchiveBtn, settings().colors().modlistOverwrittenArchive()); + setButtonColor(ui->containsBtn, settings().colors().modlistContainsPlugin()); + setButtonColor(ui->containedBtn, settings().colors().pluginListContained()); + + setOverwritingColor(settings().colors().modlistOverwritingLoose()); + setOverwrittenColor(settings().colors().modlistOverwrittenLoose()); + setOverwritingArchiveColor(settings().colors().modlistOverwritingArchive()); + setOverwrittenArchiveColor(settings().colors().modlistOverwrittenArchive()); + setContainsColor(settings().colors().modlistContainsPlugin()); + setContainedColor(settings().colors().pluginListContained()); ui->compactBox->setChecked(settings().compactDownloads()); ui->showMetaBox->setChecked(settings().metaDownloads()); @@ -67,30 +69,32 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) void GeneralSettingsTab::update() { - QString oldLanguage = settings().language(); - QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); + const QString oldLanguage = settings().language(); + const QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); + if (newLanguage != oldLanguage) { - qsettings().setValue("Settings/language", newLanguage); + settings().setLanguage(newLanguage); emit settings().languageChanged(newLanguage); } - QString oldStyle = qsettings().value("Settings/style", "").toString(); - QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); + const QString oldStyle = settings().getStyleName().value_or(""); + const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - qsettings().setValue("Settings/style", newStyle); + settings().setStyleName(newStyle); emit settings().styleChanged(newStyle); } - qsettings().setValue("Settings/overwritingLooseFilesColor", getOverwritingColor()); - qsettings().setValue("Settings/overwrittenLooseFilesColor", getOverwrittenColor()); - qsettings().setValue("Settings/overwritingArchiveFilesColor", getOverwritingArchiveColor()); - qsettings().setValue("Settings/overwrittenArchiveFilesColor", getOverwrittenArchiveColor()); - qsettings().setValue("Settings/containsPluginColor", getContainsColor()); - qsettings().setValue("Settings/containedColor", getContainedColor()); - qsettings().setValue("Settings/compact_downloads", ui->compactBox->isChecked()); - qsettings().setValue("Settings/meta_downloads", ui->showMetaBox->isChecked()); - qsettings().setValue("Settings/use_prereleases", ui->usePrereleaseBox->isChecked()); - qsettings().setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked()); + settings().colors().setModlistOverwritingLoose(getOverwritingColor()); + settings().colors().setModlistOverwrittenLoose(getOverwrittenColor()); + settings().colors().setModlistOverwritingArchive(getOverwritingArchiveColor()); + settings().colors().setModlistOverwrittenArchive(getOverwrittenArchiveColor()); + settings().colors().setModlistContainsPlugin(getContainsColor()); + settings().colors().setPluginListContained(getContainedColor()); + + settings().setCompactDownloads(ui->compactBox->isChecked()); + settings().setMetaDownloads(ui->showMetaBox->isChecked()); + settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); + settings().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } void GeneralSettingsTab::addLanguages() diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index b5e6edb1..41f58308 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -164,7 +164,7 @@ UsvfsConnector::UsvfsConnector() { USVFSParameters params; LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel()); - CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType()); + CrashDumpsType dumpType = Settings::instance().crashDumpsType(); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); @@ -249,9 +249,10 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) } void UsvfsConnector::updateParams( - MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist) + MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, + QString executableBlacklist) { - USVFSUpdateParams(toUsvfsLogLevel(logLevel), ::crashDumpsType(crashDumpsType)); + USVFSUpdateParams(toUsvfsLogLevel(logLevel), crashDumpsType); ClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { std::wstring buf = exec.toStdWString(); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index b0bd320c..cd5d56b2 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -87,7 +87,7 @@ public: void updateMapping(const MappingType &mapping); void updateParams( - MOBase::log::Levels logLevel, int crashDumpsType, + MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist); void updateForcedLibraries(const QList &forcedLibraries); -- cgit v1.3.1 From 088f27fe48cd8f218052090a97e8187eedf0c06e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 23 Sep 2019 19:10:19 -0400 Subject: changed the layout of the general settings tab added option to disable checking for updates removed online check, just try it and see --- src/mainwindow.cpp | 16 ++++ src/mainwindow.h | 1 + src/organizercore.cpp | 36 ++------ src/organizercore.h | 1 + src/selfupdater.cpp | 12 ++- src/selfupdater.h | 14 ++- src/settings.cpp | 10 +++ src/settings.h | 5 ++ src/settingsdialog.ui | 203 ++++++++++++++++++++++++++---------------- src/settingsdialoggeneral.cpp | 2 + 10 files changed, 187 insertions(+), 113 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42cbe919..cd650d2f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -263,6 +263,7 @@ MainWindow::MainWindow(Settings &settings setupToolbar(); toggleMO2EndorseState(); + toggleUpdateAction(); TaskProgressManager::instance().tryCreateTaskbar(); @@ -5007,6 +5008,7 @@ void MainWindow::on_actionSettings_triggered() bool oldDisplayForeign(settings.interface().displayForeign()); bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); + const bool oldCheckForUpdates = settings.checkForUpdates(); SettingsDialog dialog(&m_PluginContainer, settings, this); @@ -5084,6 +5086,14 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); + + if (oldCheckForUpdates != settings.checkForUpdates()) { + toggleUpdateAction(); + + if (settings.checkForUpdates()) { + m_OrganizerCore.checkForUpdates(); + } + } } void MainWindow::on_actionNexus_triggered() @@ -5596,6 +5606,12 @@ void MainWindow::toggleMO2EndorseState() ui->actionEndorseMO->setStatusTip(text); } +void MainWindow::toggleUpdateAction() +{ + const auto& s = m_OrganizerCore.settings(); + ui->actionUpdate->setVisible(s.checkForUpdates()); +} + void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int) { QVariantList data = resultData.toList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 1f997ab1..524e2b6e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -313,6 +313,7 @@ private: void sendSelectedPluginsToPriority(int newPriority); void toggleMO2EndorseState(); + void toggleUpdateAction(); private: diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0da5b604..a4a89c99 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -74,25 +74,6 @@ using namespace MOBase; //static CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; -static bool isOnline() -{ - const auto runningFlags = - QNetworkInterface::IsUp | QNetworkInterface::IsRunning; - - for (auto&& i : QNetworkInterface::allInterfaces()) { - if (!(i.flags() & QNetworkInterface::IsLoopBack)) { - if (i.flags() & runningFlags) { - auto addresses = i.addressEntries(); - if (!addresses.empty()) { - return true; - } - } - } - } - - return false; -} - static std::wstring getProcessName(HANDLE process) { wchar_t buffer[MAX_PATH]; @@ -307,14 +288,15 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, m_InstallationManager.setParentWidget(widget); m_Updater.setUserInterface(widget); - if (userInterface != nullptr) { - // this currently wouldn't work reliably if the ui isn't initialized yet to - // display the result - if (isOnline() && !m_Settings.network().offlineMode()) { - m_Updater.testForUpdate(); - } else { - log::debug("user doesn't seem to be connected to the internet"); - } + checkForUpdates(); +} + +void OrganizerCore::checkForUpdates() +{ + // this currently wouldn't work reliably if the ui isn't initialized yet to + // display the result + if (m_UserInterface != nullptr) { + m_Updater.testForUpdate(m_Settings); } } diff --git a/src/organizercore.h b/src/organizercore.h index a14d79a9..5de550df 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -109,6 +109,7 @@ public: void updateExecutablesList(); + void checkForUpdates(); void startMOUpdate(); Settings &settings(); diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 0ca39b19..8887927a 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -122,8 +122,18 @@ void SelfUpdater::setPluginContainer(PluginContainer *pluginContainer) m_Interface->setPluginContainer(pluginContainer); } -void SelfUpdater::testForUpdate() +void SelfUpdater::testForUpdate(const Settings& settings) { + if (settings.network().offlineMode()) { + log::debug("not checking for updates, in offline mode"); + return; + } + + if (!settings.checkForUpdates()) { + log::debug("not checking for updates, disabled"); + return; + } + // TODO: if prereleases are disabled we could just request the latest release // directly try { diff --git a/src/selfupdater.h b/src/selfupdater.h index bce49495..0c81efc5 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -37,7 +37,7 @@ namespace MOBase { class IPluginGame; } class QNetworkReply; class QProgressDialog; - +class Settings; /** * @brief manages updates for Mod Organizer itself @@ -80,6 +80,11 @@ public: void setPluginContainer(PluginContainer *pluginContainer); + /** + * @brief request information about the current version + **/ + void testForUpdate(const Settings& settings); + /** * @brief start the update process * @note this should not be called if there is no update available @@ -91,13 +96,6 @@ public: **/ MOBase::VersionInfo getVersion() const { return m_MOVersion; } -public slots: - - /** - * @brief request information about the current version - **/ - void testForUpdate(); - signals: /** diff --git a/src/settings.cpp b/src/settings.cpp index c3e8781e..462cd92a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -178,6 +178,16 @@ QString Settings::filename() const return m_Settings.fileName(); } +bool Settings::checkForUpdates() const +{ + return get(m_Settings, "Settings", "check_for_updates", true); +} + +void Settings::setCheckForUpdates(bool b) +{ + set(m_Settings, "Settings", "check_for_updates", b); +} + bool Settings::usePrereleases() const { return get(m_Settings, "Settings", "use_prereleases", false); diff --git a/src/settings.h b/src/settings.h index ee6ff3fe..1556ba1e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -692,6 +692,11 @@ public: bool archiveParsing() const; void setArchiveParsing(bool b); + // whether the user wants to check for updates + // + bool checkForUpdates() const; + void setCheckForUpdates(bool b); + // whether the user wants to upgrade to pre-releases // bool usePrereleases() const; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 9d1e4da1..78bae6d7 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -7,7 +7,7 @@ 0 0 586 - 486 + 491 @@ -23,75 +23,67 @@ General - - - - - - - Language - - - - - - - The display language - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - - - - - - - - - - - Style - - - - - - - graphical style - - - graphical style of the MO user interface - - - - - - - - - Update to non-stable releases. - - - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + + + + + Qt::Vertical - - Install Pre-releases (Betas) + + + 0 + 0 + - + - + User Interface - - + + + + + Style + + + + + + + Language + + + + + + + The display language + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + + + + + + + graphical style + + + graphical style of the MO user interface + + + + - + Dialogs will always be centered on the main window, but will remember their size. @@ -102,7 +94,7 @@ p, li { white-space: pre-wrap; } - + @@ -121,7 +113,7 @@ p, li { white-space: pre-wrap; } - + Modify the categories available to arrange your mods. @@ -137,36 +129,49 @@ p, li { white-space: pre-wrap; } - + Download List - + - + - If checked, the download interface will be more compact. + If checked, the download list will display meta information instead of file names. - Compact List + Show Meta Information - + - If checked, the download list will display meta information instead of file names. + If checked, the download interface will be more compact. - Show Meta Information + Compact List + + + + Qt::Vertical + + + + 0 + 0 + + + + - + Colors @@ -240,6 +245,54 @@ p, li { white-space: pre-wrap; } + + + + Updates + + + + + + Mod Organizer checks for updates on Github on startup. + + + Mod Organizer checks for updates on Github on startup. + + + Check for updates + + + + + + + Update to non-stable releases. + + + If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + + + Install Pre-releases (Betas) + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + @@ -1416,11 +1469,7 @@ programs you are intentionally running. - languageBox - styleBox logLevelBox - usePrereleaseBox - categoriesBtn baseDirEdit browseBaseDirBtn downloadDirEdit diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index ae924393..07aff4a1 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -54,6 +54,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); ui->compactBox->setChecked(settings().interface().compactDownloads()); ui->showMetaBox->setChecked(settings().interface().metaDownloads()); + ui->checkForUpdates->setChecked(settings().checkForUpdates()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); @@ -95,6 +96,7 @@ void GeneralSettingsTab::update() settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); + settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } -- cgit v1.3.1 From 324fa12a2d491be039c1cf720ee2786864d010bc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 10 Oct 2019 10:25:46 -0400 Subject: uses new usvfsParameters, also updates spawn delay --- src/organizercore.cpp | 6 ++++-- src/organizercore.h | 2 +- src/usvfsconnector.cpp | 44 ++++++++++++++++++++++++++++++++------------ src/usvfsconnector.h | 2 +- 4 files changed, 38 insertions(+), 16 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 335910da..df824c2b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -484,10 +484,11 @@ void OrganizerCore::prepareVFS() } void OrganizerCore::updateVFSParams( - log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist) + log::Levels logLevel, CrashDumpsType crashDumpsType, + std::chrono::seconds spawnDelay, QString executableBlacklist) { setGlobalCrashDumpsType(crashDumpsType); - m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); + m_USVFS.updateParams(logLevel, crashDumpsType, spawnDelay, executableBlacklist); } void OrganizerCore::setLogLevel(log::Levels level) @@ -497,6 +498,7 @@ void OrganizerCore::setLogLevel(log::Levels level) updateVFSParams( m_Settings.diagnostics().logLevel(), m_Settings.diagnostics().crashDumpsType(), + m_Settings.diagnostics().spawnDelay(), m_Settings.executablesBlacklist()); log::getDefault().setLevel(m_Settings.diagnostics().logLevel()); diff --git a/src/organizercore.h b/src/organizercore.h index 5de550df..5b7dd87b 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -195,7 +195,7 @@ public: void updateVFSParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - QString executableBlacklist); + std::chrono::seconds spawnDelay, QString executableBlacklist); void setLogLevel(MOBase::log::Levels level); diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 3ea811be..9a4fa742 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -162,15 +162,24 @@ QString toString(CrashDumpsType t) UsvfsConnector::UsvfsConnector() { + using namespace std::chrono; + const auto& s = Settings::instance(); - USVFSParameters params; - const LogLevel level = toUsvfsLogLevel(s.diagnostics().logLevel()); + const LogLevel logLevel = toUsvfsLogLevel(s.diagnostics().logLevel()); const CrashDumpsType dumpType = s.diagnostics().crashDumpsType(); - const auto delay = s.diagnostics().spawnDelay(); - + const auto delay = duration_cast(s.diagnostics().spawnDelay()); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); - USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str(), delay); + + usvfsParameters* params = usvfsCreateParameters(); + + usvfsSetInstanceName(params, SHMID); + usvfsSetDebugMode(params, false); + usvfsSetLogLevel(params, logLevel); + usvfsSetCrashDumpType(params, dumpType); + usvfsSetCrashDumpPath(params, dumpPath.c_str()); + usvfsSetProcessDelay(params, delay.count()); + InitLogging(false); log::debug( @@ -178,12 +187,13 @@ UsvfsConnector::UsvfsConnector() " . instance: {}\n" " . log: {}\n" " . dump: {} ({})", - params.instanceName, - toString(params.logLevel), - params.crashDumpsPath, - toString(params.crashDumpsType)); + SHMID, + toString(logLevel), + dumpPath.c_str(), + toString(dumpType)); - CreateVFS(¶ms); + usvfsCreateVFS(params); + usvfsFreeParameters(params); ClearExecutableBlacklist(); for (auto exec : s.executablesBlacklist().split(";")) { @@ -253,9 +263,19 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) void UsvfsConnector::updateParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - QString executableBlacklist) + std::chrono::seconds spawnDelay, QString executableBlacklist) { - USVFSUpdateParams(toUsvfsLogLevel(logLevel), crashDumpsType); + using namespace std::chrono; + + usvfsParameters* p = usvfsCreateParameters(); + + usvfsSetLogLevel(p, toUsvfsLogLevel(logLevel)); + usvfsSetCrashDumpType(p, crashDumpsType); + usvfsSetProcessDelay(p, duration_cast(spawnDelay).count()); + + usvfsUpdateParameters(p); + usvfsFreeParameters(p); + ClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { std::wstring buf = exec.toStdWString(); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index cd5d56b2..a4647eaf 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -88,7 +88,7 @@ public: void updateParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - QString executableBlacklist); + std::chrono::seconds spawnDelay, QString executableBlacklist); void updateForcedLibraries(const QList &forcedLibraries); -- cgit v1.3.1 From 33f5b59bee9fe5a1b3515737819ddfec4f596423 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 1 Nov 2019 10:14:17 -0400 Subject: added crash dump path to parameters, if a mechanism to customize it is ever created moved toString() for log level and dump type to usvfs, which needs them for logging --- src/organizercore.cpp | 5 ++++- src/organizercore.h | 3 ++- src/usvfsconnector.cpp | 51 ++++++-------------------------------------------- src/usvfsconnector.h | 6 ++++-- 4 files changed, 16 insertions(+), 49 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index df824c2b..5613e8ce 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -485,10 +485,12 @@ void OrganizerCore::prepareVFS() void OrganizerCore::updateVFSParams( log::Levels logLevel, CrashDumpsType crashDumpsType, + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, QString executableBlacklist) { setGlobalCrashDumpsType(crashDumpsType); - m_USVFS.updateParams(logLevel, crashDumpsType, spawnDelay, executableBlacklist); + m_USVFS.updateParams( + logLevel, crashDumpsType, crashDumpsPath, spawnDelay, executableBlacklist); } void OrganizerCore::setLogLevel(log::Levels level) @@ -498,6 +500,7 @@ void OrganizerCore::setLogLevel(log::Levels level) updateVFSParams( m_Settings.diagnostics().logLevel(), m_Settings.diagnostics().crashDumpsType(), + QString::fromStdWString(crashDumpsPath()), m_Settings.diagnostics().spawnDelay(), m_Settings.executablesBlacklist()); diff --git a/src/organizercore.h b/src/organizercore.h index 5b7dd87b..0d0a092c 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -195,7 +195,8 @@ public: void updateVFSParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - std::chrono::seconds spawnDelay, QString executableBlacklist); + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, + QString executableBlacklist); void setLogLevel(MOBase::log::Levels level); diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 9a4fa742..311c6dd3 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -118,48 +118,6 @@ CrashDumpsType crashDumpsType(int type) } } -QString toString(LogLevel lv) -{ - switch (lv) - { - case LogLevel::Debug: - return "debug"; - - case LogLevel::Info: - return "info"; - - case LogLevel::Warning: - return "warning"; - - case LogLevel::Error: - return "error"; - - default: - return QString("%1").arg(static_cast(lv)); - } -} - -QString toString(CrashDumpsType t) -{ - switch (t) - { - case CrashDumpsType::None: - return "none"; - - case CrashDumpsType::Mini: - return "mini"; - - case CrashDumpsType::Data: - return "data"; - - case CrashDumpsType::Full: - return "full"; - - default: - return QString("%1").arg(static_cast(t)); - } -} - UsvfsConnector::UsvfsConnector() { using namespace std::chrono; @@ -188,9 +146,9 @@ UsvfsConnector::UsvfsConnector() " . log: {}\n" " . dump: {} ({})", SHMID, - toString(logLevel), + usvfsLogLevelToString(logLevel), dumpPath.c_str(), - toString(dumpType)); + usvfsCrashDumpTypeToString(dumpType)); usvfsCreateVFS(params); usvfsFreeParameters(params); @@ -263,14 +221,17 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) void UsvfsConnector::updateParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - std::chrono::seconds spawnDelay, QString executableBlacklist) + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, + QString executableBlacklist) { using namespace std::chrono; usvfsParameters* p = usvfsCreateParameters(); + usvfsSetDebugMode(p, FALSE); usvfsSetLogLevel(p, toUsvfsLogLevel(logLevel)); usvfsSetCrashDumpType(p, crashDumpsType); + usvfsSetCrashDumpPath(p, crashDumpsPath.toStdString().c_str()); usvfsSetProcessDelay(p, duration_cast(spawnDelay).count()); usvfsUpdateParameters(p); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index a4647eaf..d0071678 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -88,9 +88,11 @@ public: void updateParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - std::chrono::seconds spawnDelay, QString executableBlacklist); + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, + QString executableBlacklist); - void updateForcedLibraries(const QList &forcedLibraries); + void updateForcedLibraries( + const QList &forcedLibraries); private: -- cgit v1.3.1 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/organizercore.h') 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/organizercore.h') 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/organizercore.h') 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/organizercore.h') 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/organizercore.h') 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/organizercore.h') 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/organizercore.h') 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/organizercore.h') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 3f1f8912..abbe02e5 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -408,7 +408,8 @@ std::vector getLoadedModules() } -std::vector getRunningProcesses() +template +void forEachRunningProcess(F&& f) { HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); @@ -416,7 +417,7 @@ std::vector getRunningProcesses() { const auto e = GetLastError(); log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); - return {}; + return; } PROCESSENTRY32 entry = {}; @@ -427,16 +428,14 @@ std::vector getRunningProcesses() if (!Process32First(snapshot.get(), &entry)) { const auto e = GetLastError(); log::error("Process32First() failed, {}", formatSystemMessage(e)); - return {}; + return; } - std::vector v; - for (;;) { - v.push_back(Process( - entry.th32ProcessID, - QString::fromStdWString(entry.szExeFile))); + if (!f(entry)) { + break; + } // next process if (!Process32Next(snapshot.get(), &entry)) @@ -450,8 +449,66 @@ std::vector getRunningProcesses() break; } } +} + +std::vector getRunningProcesses() +{ + std::vector v; + + forEachRunningProcess([&](auto&& entry) { + v.push_back(Process( + entry.th32ProcessID, + QString::fromStdWString(entry.szExeFile))); + + return true; + }); return v; } +QString getProcessName(HANDLE process) +{ + const QString badName = "unknown"; + + if (process == 0 || process == INVALID_HANDLE_VALUE) { + return badName; + } + + const DWORD bufferSize = MAX_PATH; + wchar_t buffer[bufferSize + 1] = {}; + + const auto realSize = ::GetProcessImageFileNameW(process, buffer, bufferSize); + + if (realSize == 0) { + const auto e = ::GetLastError(); + log::error("GetProcessImageFileNameW() failed, {}", formatSystemMessage(e)); + return badName; + } + + auto s = QString::fromWCharArray(buffer, realSize); + + const auto lastSlash = s.lastIndexOf("\\"); + if (lastSlash != -1) { + s = s.mid(lastSlash + 1); + } + + return s; +} + +DWORD getProcessParentID(DWORD pid) +{ + DWORD ppid = 0; + + forEachRunningProcess([&](auto&& entry) { + if (entry.th32ProcessID == pid) { + ppid = entry.th32ParentProcessID; + return false; + } + + return true; + }); + + return ppid; +} + } // namespace diff --git a/src/envmodule.h b/src/envmodule.h index deb7520f..212f6f7b 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -120,6 +120,9 @@ private: std::vector getRunningProcesses(); std::vector getLoadedModules(); +QString getProcessName(HANDLE process); +DWORD getProcessParentID(DWORD pid); + } // namespace env #endif // ENV_MODULE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0181a335..bbb63333 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1311,16 +1311,10 @@ bool MainWindow::canExit() } } - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); - if (injected_process_still_running != INVALID_HANDLE_VALUE) - { - m_exitAfterWait = true; - m_OrganizerCore.waitForApplication(injected_process_still_running); - if (!m_exitAfterWait) { // if operation cancelled - return false; - } + m_exitAfterWait = true; + m_OrganizerCore.waitForAllUSVFSProcessesWithLock(); + if (!m_exitAfterWait) { // if operation cancelled + return false; } setCursor(Qt::WaitCursor); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d3f4a83c..2a228fda 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -34,6 +34,7 @@ #include "instancemanager.h" #include #include "previewdialog.h" +#include "envmodule.h" #include #include @@ -74,47 +75,6 @@ using namespace MOBase; //static CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; -static std::wstring getProcessName(HANDLE process) -{ - wchar_t buffer[MAX_PATH]; - const wchar_t *fileName = L"unknown"; - - if (process == nullptr) return fileName; - - if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { - fileName = wcsrchr(buffer, L'\\'); - if (fileName == nullptr) { - fileName = buffer; - } - else { - fileName += 1; - } - } - - return fileName; -} - -// Get parent PID for the given process, return 0 on failure -static DWORD getProcessParentID(DWORD pid) -{ - HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 pe = { 0 }; - pe.dwSize = sizeof(PROCESSENTRY32); - - DWORD res = 0; - if (Process32First(th, &pe)) - do { - if (pe.th32ProcessID == pid) { - res = pe.th32ParentProcessID; - break; - } - } while (Process32Next(th, &pe)); - - CloseHandle(th); - - return res; -} - template QStringList toStringList(InputIterator current, InputIterator end) { @@ -1348,35 +1308,46 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -bool OrganizerCore::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) +void OrganizerCore::withLock(std::function f) { - if (Settings::instance().interface().lockGUI()) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; + + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); + } + else { + // i.e. when running command line shortcuts there is no user interface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); + } + ON_BLOCK_EXIT([&]() { if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } - else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } + m_MainWindow->unlock(); + } }); - ON_BLOCK_EXIT([&]() { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); + f(uilock); +} + +bool OrganizerCore::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) { + return true; + } + + bool r = false; + withLock([&](auto* uilock) { DWORD ignoreExitCode; - waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); cycleDiagnostics(); - } + }); - return handle; + return r; } bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) @@ -1393,30 +1364,64 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) if (m_MainWindow != nullptr) { m_MainWindow->unlock(); } }); + return waitForProcessCompletion(handle, exitCode, uilock); } -bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +bool OrganizerCore::waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto r = spawn::waitForProcess(handle, exitCode, uilock); + + switch (r) + { + case spawn::WaitResults::Completed: // fall-through + case spawn::WaitResults::Unlocked: + return true; + + case spawn::WaitResults::Error: // fall-through + default: + return false; + } +} + +bool OrganizerCore::waitForAllUSVFSProcessesWithLock() { + bool r = false; + + withLock([&](auto* uilock) { + r = waitForAllUSVFSProcesses(uilock); + }); + + return r; +} + +bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + // Certain process names we wish to "hide" for aesthetic reason: + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); + bool originalHandle = true; bool newHandle = true; bool uiunlocked = false; + HANDLE handle = findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); + DWORD* exitCode = nullptr; DWORD currentPID = 0; QString processName; + auto waitForChildUntil = GetTickCount64(); if (handle != INVALID_HANDLE_VALUE) { currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); + processName = env::getProcessName(handle); } - // Certain process names we wish to "hide" for aesthetic reason: bool waitingOnHidden = false; - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); for (QString hide : hiddenList) if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; + // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes. // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden" @@ -1489,7 +1494,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL newHandle = handle != INVALID_HANDLE_VALUE; if (newHandle) { currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); + processName = env::getProcessName(handle); for (QString hide : hiddenList) if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; @@ -1541,13 +1546,13 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde continue; } - QString pname = QString::fromStdWString(getProcessName(handle)); + QString pname = env::getProcessName(handle); bool phidden = false; for (auto hide : hiddenList) if (pname.contains(hide, Qt::CaseInsensitive)) phidden = true; - bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid; + bool pprefered = preferedParentPid && env::getProcessParentID(pids[i]) == preferedParentPid; if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { if (best_match != INVALID_HANDLE_VALUE) diff --git a/src/organizercore.h b/src/organizercore.h index 3d3c7325..ffdb6830 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -167,6 +167,8 @@ public: const QString &profile, const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); + bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + bool waitForAllUSVFSProcessesWithLock(); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -221,8 +223,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); bool onModInstalled(const std::function &func); bool onAboutToRun(const std::function &func); bool onFinishedRun(const std::function &func); @@ -311,6 +311,13 @@ private: bool waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); + + void withLock(std::function f); + + HANDLE findAndOpenAUSVFSProcess( + const std::vector& hiddenList, DWORD preferedParentPid); + private slots: void directory_refreshed(); diff --git a/src/spawn.cpp b/src/spawn.cpp index fe1e9e3e..f0b3b2c7 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "settingsdialogworkarounds.h" #include +#include #include #include #include @@ -1093,6 +1094,68 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } +WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + const DWORD pid = ::GetProcessId(handle); + const QString processName = QString("%1 (%2)") + .arg(env::getProcessName(handle)) + .arg(pid); + + if (uilock) + uilock->setProcessName(processName); + + constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; + DWORD res = WAIT_TIMEOUT; + + log::debug( + "waiting for process completion '{}' ({})", + processName, pid); + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion '{}' ({}), {}", + processName, pid, formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res == WAIT_OBJECT_0) { + // completed + log::debug("process '{}' ({}) completed", processName, pid); + + if (exitCode) { + if (!::GetExitCodeProcess(handle, exitCode)) { + const auto e = ::GetLastError(); + log::warn( + "failed to get exit code of process '{}' ({}): {}", + processName, pid, formatSystemMessage(e)); + } + } + + return WaitResults::Completed; + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); + + if (uilock && uilock->unlockForced()) { + log::debug("waiting for process '{}' ({}) aborted by UI", processName, pid); + return WaitResults::Unlocked; + } + } +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index 866e1795..441cad2c 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,6 +27,8 @@ along with Mod Organizer. If not, see . #include class Settings; +class ILockedWaitingForProcess; + namespace MOBase { class IPluginGame; } namespace spawn @@ -84,6 +86,7 @@ public: ~SpawnedProcess(); HANDLE releaseHandle(); + void wait(); private: HANDLE m_handle; @@ -122,6 +125,17 @@ QString findJavaInstallation(const QString& jarFile); FileExecutionContext getFileExecutionContext( QWidget* parent, const QFileInfo& target); + +enum class WaitResults +{ + Completed = 1, + Error, + Unlocked +}; + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock); + } // namespace -- cgit v1.3.1 From 4d269c2e1a625e6d50b7e6272b4f474a921c6bfa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 11:34:06 -0400 Subject: split to processrunner added IUserInterface::qtWidget() put back IUserInterface in OrganizerCore now that there's a way to get the widget --- src/CMakeLists.txt | 3 + src/iuserinterface.h | 2 + src/main.cpp | 4 +- src/mainwindow.cpp | 13 +- src/mainwindow.h | 5 +- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 601 ++++++++------------------------------ src/organizercore.h | 60 +--- src/organizerproxy.cpp | 4 +- src/processrunner.cpp | 634 +++++++++++++++++++++++++++++++++++++++++ src/processrunner.h | 104 +++++++ src/spawn.cpp | 198 ------------- src/spawn.h | 48 ---- 13 files changed, 892 insertions(+), 786 deletions(-) create mode 100644 src/processrunner.cpp create mode 100644 src/processrunner.h (limited to 'src/organizercore.h') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 180422ef..5e909760 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -143,6 +143,7 @@ SET(organizer_SRCS envwindows.cpp colortable.cpp sanitychecks.cpp + processrunner.cpp shared/windows_error.cpp shared/error_report.cpp @@ -266,6 +267,7 @@ SET(organizer_HDRS envshortcut.h envwindows.h colortable.h + processrunner.h shared/windows_error.h shared/error_report.h @@ -348,6 +350,7 @@ set(core organizercore organizerproxy apiuseraccount + processrunner ) set(dialogs diff --git a/src/iuserinterface.h b/src/iuserinterface.h index a309ed9b..91487aee 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -33,6 +33,8 @@ public: virtual ILockedWaitingForProcess* lock() = 0; virtual void unlock() = 0; + + virtual QWidget* qtWidget() = 0; }; #endif // IUSERINTERFACE_H diff --git a/src/main.cpp b/src/main.cpp index 49ecc084..5ed7da5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -632,7 +632,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (MOShortcut shortcut{ arguments.at(1) }) { if (shortcut.hasExecutable()) { try { - organizer.runShortcut(shortcut); + organizer.processRunner().runShortcut(shortcut); return 0; } catch (const std::exception &e) { @@ -653,7 +653,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary try { - organizer.runExecutableOrExecutableFile( + organizer.processRunner().runExecutableOrExecutableFile( exeName, arguments, QString(), QString()); return 0; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bbb63333..ce5280a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1312,7 +1312,7 @@ bool MainWindow::canExit() } m_exitAfterWait = true; - m_OrganizerCore.waitForAllUSVFSProcessesWithLock(); + m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock(); if (!m_exitAfterWait) { // if operation cancelled return false; } @@ -1536,7 +1536,7 @@ void MainWindow::startExeAction() } action->setEnabled(false); - m_OrganizerCore.runExecutable(*itor); + m_OrganizerCore.processRunner().runExecutable(*itor); action->setEnabled(true); } @@ -2294,6 +2294,11 @@ void MainWindow::unlock() } } +QWidget* MainWindow::qtWidget() +{ + return this; +} + void MainWindow::on_btnRefreshData_clicked() { m_OrganizerCore.refreshDirectoryStructure(); @@ -2363,7 +2368,7 @@ void MainWindow::on_startButton_clicked() forcedLibraries.clear(); } - m_OrganizerCore.runExecutableFile( + m_OrganizerCore.processRunner().runExecutableFile( selectedExecutable->binaryInfo(), selectedExecutable->arguments(), selectedExecutable->workingDirectory().length() != 0 ? @@ -5453,7 +5458,7 @@ void MainWindow::openDataFile() const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); - m_OrganizerCore.runFile(this, targetInfo); + m_OrganizerCore.processRunner().runFile(this, targetInfo); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/mainwindow.h b/src/mainwindow.h index dbbd0bd9..19723480 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -118,8 +118,9 @@ public: void processUpdates(Settings& settings); - virtual ILockedWaitingForProcess* lock() override; - virtual void unlock() override; + ILockedWaitingForProcess* lock() override; + void unlock() override; + QWidget* qtWidget() override; bool addProfile(); void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 68b1be6b..758112cc 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,7 @@ void ConflictsTab::openItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().runFile(parentWidget(), item->fileName()); + core().processRunner().runFile(parentWidget(), item->fileName()); return true; }); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index eda64c1d..0f767f46 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,5 +1,4 @@ #include "organizercore.h" -#include "mainwindow.h" #include "delayedfilewriter.h" #include "guessedvalue.h" #include "imodinterface.h" @@ -86,51 +85,13 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } -env::Process* getInterestingProcess(std::vector& processes) -{ - if (processes.empty()) { - return nullptr; - } - - // Certain process names we wish to "hide" for aesthetic reason: - const std::vector hiddenList = { - QFileInfo(QCoreApplication::applicationFilePath()).fileName() - }; - - auto isHidden = [&](auto&& p) { - for (auto h : hiddenList) { - if (p.name().contains(h, Qt::CaseInsensitive)) { - return true; - } - } - - return false; - }; - - - for (auto&& root : processes) { - if (!isHidden(root)) { - return &root; - } - - for (auto&& child : root.children()) { - if (!isHidden(child)) { - return &child; - } - } - } - - - // everything is hidden, just pick the first one - return &processes[0]; -} - OrganizerCore::OrganizerCore(Settings &settings) - : m_MainWindow(nullptr) + : m_UserInterface(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) + , m_Runner(*this) , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() @@ -190,7 +151,7 @@ OrganizerCore::~OrganizerCore() m_RefresherThread.exit(); m_RefresherThread.wait(); - prepareStart(); + saveCurrentProfile(); // profile has to be cleaned up before the modinfo-buffer is cleared delete m_CurrentProfile; @@ -249,43 +210,49 @@ void OrganizerCore::updateExecutablesList() m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } -void OrganizerCore::setUserInterface(MainWindow* mainWindow) +void OrganizerCore::setUserInterface(IUserInterface* ui) { storeSettings(); - m_MainWindow = mainWindow; + m_UserInterface = ui; + + QWidget* w = nullptr; + if (m_UserInterface) { + w = m_UserInterface->qtWidget(); + } - if (m_MainWindow != nullptr) { - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow, + if (w) { + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow, + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w, SLOT(modlistChanged(QModelIndexList, int))); - connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w, SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(modUninstalled(QString)), w, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow, + connect(&m_ModList, SIGNAL(removeSelectedMods()), w, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow, + connect(&m_ModList, SIGNAL(clearOverwrite()), w, SLOT(clearOverwrite())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow, + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow, + connect(&m_ModList, SIGNAL(modorder_changed()), w, SLOT(modorder_changed())); - connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow, + connect(&m_PluginList, SIGNAL(writePluginsList()), w, SLOT(esplist_changed())); - connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow, + connect(&m_PluginList, SIGNAL(esplist_changed()), w, SLOT(esplist_changed())); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow, + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); } - m_InstallationManager.setParentWidget(m_MainWindow); - m_Updater.setUserInterface(m_MainWindow); + m_InstallationManager.setParentWidget(w); + m_Updater.setUserInterface(w); + m_Runner.setUserInterface(ui); checkForUpdates(); } @@ -294,7 +261,7 @@ void OrganizerCore::checkForUpdates() { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (m_MainWindow != nullptr) { + if (m_UserInterface != nullptr) { m_Updater.testForUpdate(m_Settings); } } @@ -390,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { if(moshortcut.hasExecutable()) - runShortcut(moshortcut); + m_Runner.runShortcut(moshortcut); } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); @@ -754,13 +721,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, int modIndex = ModInfo::getIndex(modName); if (modIndex != UINT_MAX) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && (m_MainWindow != nullptr) + if (hasIniTweaks && (m_UserInterface != nullptr) && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_MainWindow->displayModInformation( + m_UserInterface->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); @@ -821,13 +788,13 @@ void OrganizerCore::installDownload(int index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); - if (hasIniTweaks && m_MainWindow != nullptr + if (hasIniTweaks && m_UserInterface != nullptr && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_MainWindow->displayModInformation( + m_UserInterface->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } @@ -1104,412 +1071,6 @@ bool OrganizerCore::previewFile( return true; } -bool OrganizerCore::runFile( - QWidget* parent, const QFileInfo& targetInfo) -{ - const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - - switch (fec.type) - { - case spawn::FileExecutionTypes::Executable: - { - runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); - return true; - } - - case spawn::FileExecutionTypes::Other: // fall-through - default: - { - auto r = shell::Open(targetInfo.absoluteFilePath()); - if (!r.success()) { - return false; - } - - // not all files will return a valid handle even if opening them was - // successful, such as inproc handlers (like the photo viewer) - if (r.processHandle() != INVALID_HANDLE_VALUE) { - // steal because it gets closed after the wait - return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); - } - - return true; - } - } -} - -bool OrganizerCore::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - DWORD processExitCode = 0; - HANDLE processHandle = spawnAndWait( - binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, - customOverwrite, forcedLibraries, &processExitCode); - - if (processHandle == INVALID_HANDLE_VALUE) { - // failed - return false; - } - - if (refresh) { - refreshDirectoryStructure(); - - // need to remove our stored load order because it may be outdated if a foreign tool changed the - // file time. After removing that file, refreshESPList will use the file time as the order - if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - log::debug("removing loadorder.txt"); - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - } - - refreshDirectoryStructure(); - - refreshESPList(true); - savePluginList(); - - //These callbacks should not fiddle with directoy structure and ESPs. - m_FinishedRun(binary.absoluteFilePath(), processExitCode); - } - - return true; -} - -bool OrganizerCore::runExecutable(const Executable& exe, bool refresh) -{ - const QString customOverwrite = m_CurrentProfile->setting( - "custom_overwrites", exe.title()).toString(); - - QList forcedLibraries; - - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - - return runExecutableFile( - exe.binaryInfo(), - exe.arguments(), - exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - customOverwrite, - forcedLibraries, - refresh); -} - -bool OrganizerCore::runShortcut(const MOShortcut& shortcut) -{ - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { - throw std::runtime_error( - QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(),shortcut.executable()) - .toLocal8Bit().constData()); - } - - const Executable& exe = m_ExecutablesList.get(shortcut.executable()); - return runExecutable(exe, false); -} - -HANDLE OrganizerCore::runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) -{ - QString profileName = profile; - if (profile == "") { - if (m_CurrentProfile != nullptr) { - profileName = m_CurrentProfile->name(); - } else { - throw MyException(tr("No profile set")); - } - } - - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; - - if (executable.contains('\\') || executable.contains('/')) { - // file path - - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = managedGame()->gameDirectory().absoluteFilePath(executable); - } - - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); - } - - try { - const Executable& exe = m_ExecutablesList.getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - } catch (const std::runtime_error &) { - // nop - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - if (arguments == "") { - arguments = exe.arguments(); - } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); - } - } catch (const std::runtime_error &) { - log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); - } - } - - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); - - return spawnAndWait(binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); -} - -HANDLE OrganizerCore::spawnAndWait( - const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - LPDWORD exitCode) -{ - spawn::SpawnParameters sp; - sp.binary = binary; - sp.arguments = arguments; - sp.currentDirectory = currentDirectory; - sp.steamAppID = steamAppID; - sp.hooked = true; - - prepareStart(); - - while (m_DirectoryUpdate) { - ::Sleep(100); - QCoreApplication::processEvents(); - } - - // need to make sure all data is saved before we start the application - if (m_CurrentProfile != nullptr) { - m_CurrentProfile->writeModlistNow(true); - } - - // TODO: should also pass arguments - if (!m_AboutToRun(binary.absoluteFilePath())) { - log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); - return INVALID_HANDLE_VALUE; - } - - try { - m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); - m_USVFS.updateForcedLibraries(forcedLibraries); - - } catch (const UsvfsConnectorException &e) { - log::debug(e.what()); - return INVALID_HANDLE_VALUE; - } catch (const std::exception &e) { - QMessageBox::warning(m_MainWindow, tr("Error"), e.what()); - return INVALID_HANDLE_VALUE; - } - - HANDLE handle = spawn::Spawner() - .spawn(m_MainWindow, m_GamePlugin, sp, m_Settings) - .releaseHandle(); - - if (handle == INVALID_HANDLE_VALUE) { - // failed - return INVALID_HANDLE_VALUE; - } - - waitForProcessCompletionWithLock(handle, exitCode); - return handle; -} - -void OrganizerCore::withLock(std::function f) -{ - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; - - if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } - else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } - - ON_BLOCK_EXIT([&]() { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); - - f(uilock); -} - -bool OrganizerCore::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) -{ - if (!Settings::instance().interface().lockGUI()) { - return true; - } - - bool r = false; - - withLock([&](auto* uilock) { - DWORD ignoreExitCode; - r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); - cycleDiagnostics(); - }); - - return r; -} - -bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) -{ - if (!Settings::instance().interface().lockGUI()) - return true; - - bool r = false; - - withLock([&](auto* uilock) { - r = waitForProcessCompletion(handle, exitCode, uilock); - }); - - return r; -} - -bool OrganizerCore::waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) -{ - const auto tree = env::getProcessTree(handle); - std::vector processes = {tree}; - - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - return true; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - return true; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = spawn::waitForProcess( - interestingHandle.get(), exitCode, progress); - - switch (r) - { - case spawn::WaitResults::Completed: // fall-through - case spawn::WaitResults::Cancelled: - return true; - - case spawn::WaitResults::Error: // fall-through - default: - return false; - } -} - -bool OrganizerCore::waitForAllUSVFSProcessesWithLock() -{ - if (!Settings::instance().interface().lockGUI()) - return true; - - bool r = false; - - withLock([&](auto* uilock) { - r = waitForAllUSVFSProcesses(uilock); - }); - - return r; -} - -bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) -{ - for (;;) { - const auto handles = getRunningUSVFSProcesses(); - if (handles.empty()) { - break; - } - - std::vector processes; - for (auto&& h : handles) { - auto p = env::getProcessTree(h); - if (p.isValid()) { - processes.emplace_back(std::move(p)); - } - } - - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - break; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - break; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = spawn::waitForProcess( - interestingHandle.get(), nullptr, progress); - - switch (r) - { - case spawn::WaitResults::Completed: - // this process is completed, check for others - break; - - case spawn::WaitResults::Cancelled: - // force unlocked - log::debug("waiting for process completion aborted by UI"); - return true; - - case spawn::WaitResults::Error: // fall-through - default: - log::debug("waiting for process completion not successful"); - return false; - } - } - - log::debug("waiting for process completion successful"); - return true; -} - bool OrganizerCore::onAboutToRun( const std::function &func) { @@ -1594,8 +1155,8 @@ void OrganizerCore::refreshBSAList() m_ActiveArchives = m_DefaultArchives; } - if (m_MainWindow != nullptr) { - m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives); + if (m_UserInterface != nullptr) { + m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); } m_ArchivesInit = true; @@ -1711,8 +1272,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().writeImmediately(false); } std::vector archives = enabledArchives(); @@ -1896,8 +1457,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } modInfo->clearCaches(); @@ -1946,8 +1507,8 @@ void OrganizerCore::modStatusChanged(QList index) { origin.enable(false); } } - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } @@ -2122,8 +1683,8 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); @@ -2147,11 +1708,12 @@ void OrganizerCore::savePluginList() m_PluginList.saveLoadOrder(*m_DirectoryStructure); } -void OrganizerCore::prepareStart() +void OrganizerCore::saveCurrentProfile() { if (m_CurrentProfile == nullptr) { return; } + m_CurrentProfile->writeModlist(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); @@ -2159,6 +1721,79 @@ void OrganizerCore::prepareStart() storeSettings(); } +ProcessRunner& OrganizerCore::processRunner() +{ + return m_Runner; +} + +bool OrganizerCore::beforeRun( + const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries) +{ + saveCurrentProfile(); + + while (m_DirectoryUpdate) { + ::Sleep(100); + QCoreApplication::processEvents(); + } + + // need to make sure all data is saved before we start the application + if (m_CurrentProfile != nullptr) { + m_CurrentProfile->writeModlistNow(true); + } + + // TODO: should also pass arguments + if (!m_AboutToRun(binary.absoluteFilePath())) { + log::debug("start of \"{}\" cancelled by plugin", binary.absoluteFilePath()); + return false; + } + + try + { + m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + m_USVFS.updateForcedLibraries(forcedLibraries); + } + catch (const UsvfsConnectorException &e) + { + log::debug(e.what()); + return false; + } + catch (const std::exception &e) + { + QWidget* w = nullptr; + if (m_UserInterface) { + w = m_UserInterface->qtWidget(); + } + QMessageBox::warning(w, tr("Error"), e.what()); + return false; + } + + return true; +} + +void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) +{ + refreshDirectoryStructure(); + + // need to remove our stored load order because it may be outdated if a + // foreign tool changed the file time. After removing that file, + // refreshESPList will use the file time as the order + if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { + log::debug("removing loadorder.txt"); + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + + refreshDirectoryStructure(); + + refreshESPList(true); + savePluginList(); + cycleDiagnostics(); + + //These callbacks should not fiddle with directoy structure and ESPs. + m_FinishedRun(binary.absoluteFilePath(), exitCode); +} + std::vector OrganizerCore::fileMapping(const QString &profileName, const QString &customOverwrite) { diff --git a/src/organizercore.h b/src/organizercore.h index ffdb6830..2252c118 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -3,7 +3,6 @@ #include "selfupdater.h" -#include "ilockedwaitingforprocess.h" #include "settings.h" #include "modlist.h" #include "modinfo.h" @@ -14,6 +13,7 @@ #include "executableslist.h" #include "usvfsconnector.h" #include "moshortcut.h" +#include "processrunner.h" #include #include #include @@ -26,7 +26,7 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; -class MainWindow; +class IUserInterface; namespace MOBase { template class GuessedValue; @@ -97,7 +97,7 @@ public: ~OrganizerCore(); - void setUserInterface(MainWindow* mainWindow); + void setUserInterface(IUserInterface* ui); void connectPlugins(PluginContainer *container); void disconnectPlugins(); @@ -134,7 +134,14 @@ public: bool saveCurrentLists(); - void prepareStart(); + ProcessRunner& processRunner(); + + bool beforeRun( + const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries); + + void afterRun(const QFileInfo& binary, DWORD exitCode); void refreshESPList(bool force = false); void refreshBSAList(); @@ -149,27 +156,6 @@ public: bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); - bool runFile(QWidget* parent, const QFileInfo& targetInfo); - - bool runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID={}, - const QString &customOverwrite={}, - const QList &forcedLibraries={}, - bool refresh=true); - - bool runExecutable(const Executable& exe, bool refresh=true); - - bool runShortcut(const MOShortcut& shortcut); - - HANDLE runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); - - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - bool waitForAllUSVFSProcessesWithLock(); - void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -273,6 +259,7 @@ signals: private: + void saveCurrentProfile(); void storeSettings(); bool queryApi(QString &apiKey); @@ -298,26 +285,6 @@ private: const MOShared::DirectoryEntry *directoryEntry, int createDestination); - HANDLE spawnAndWait(const QFileInfo &binary, const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries = QList(), - LPDWORD exitCode = nullptr); - - bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); - - bool waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); - - bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); - - void withLock(std::function f); - - HANDLE findAndOpenAUSVFSProcess( - const std::vector& hiddenList, DWORD preferedParentPid); - private slots: void directory_refreshed(); @@ -331,13 +298,14 @@ private: static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; private: - MainWindow* m_MainWindow; + IUserInterface* m_UserInterface; PluginContainer *m_PluginContainer; QString m_GameName; MOBase::IPluginGame *m_GamePlugin; Profile *m_CurrentProfile; + ProcessRunner m_Runner; Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 2ea1761a..75b3ea41 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -112,14 +112,14 @@ HANDLE OrganizerProxy::startApplication( const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite) { - return m_Proxied->runExecutableOrExecutableFile( + return m_Proxied->processRunner().runExecutableOrExecutableFile( executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - return m_Proxied->waitForApplication(handle, exitCode); + return m_Proxied->processRunner().waitForApplication(handle, exitCode); } bool OrganizerProxy::onAboutToRun(const std::function &func) diff --git a/src/processrunner.cpp b/src/processrunner.cpp new file mode 100644 index 00000000..81c4b99e --- /dev/null +++ b/src/processrunner.cpp @@ -0,0 +1,634 @@ +#include "processrunner.h" +#include "organizercore.h" +#include "instancemanager.h" +#include "lockeddialog.h" +#include "iuserinterface.h" +#include "envmodule.h" +#include + +using namespace MOBase; + +void adjustForVirtualized( + const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) +{ + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} + +env::Process* getInterestingProcess(std::vector& processes) +{ + if (processes.empty()) { + return nullptr; + } + + // Certain process names we wish to "hide" for aesthetic reason: + const std::vector hiddenList = { + QFileInfo(QCoreApplication::applicationFilePath()).fileName() + }; + + auto isHidden = [&](auto&& p) { + for (auto h : hiddenList) { + if (p.name().contains(h, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }; + + + for (auto&& root : processes) { + if (!isHidden(root)) { + return &root; + } + + for (auto&& child : root.children()) { + if (!isHidden(child)) { + return &child; + } + } + } + + + // everything is hidden, just pick the first one + return &processes[0]; +} + + +SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) + : m_handle(handle), m_parameters(std::move(sp)) +{ +} + +SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) + : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) +{ + other.m_handle = INVALID_HANDLE_VALUE; +} + +SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) +{ + if (this != &other) { + destroy(); + + m_handle = other.m_handle; + other.m_handle = INVALID_HANDLE_VALUE; + + m_parameters = std::move(other.m_parameters); + } + + return *this; +} + +SpawnedProcess::~SpawnedProcess() +{ + destroy(); +} + +HANDLE SpawnedProcess::releaseHandle() +{ + const auto h = m_handle; + m_handle = INVALID_HANDLE_VALUE; + return h; +} + +void SpawnedProcess::destroy() +{ + if (m_handle != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; + } +} + + +ProcessRunner::ProcessRunner(OrganizerCore& core) + : m_core(core), m_ui(nullptr) +{ +} + +void ProcessRunner::setUserInterface(IUserInterface* ui) +{ + m_ui = ui; +} + +bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) +{ + if (!parent && m_ui) { + parent = m_ui->qtWidget(); + } + + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); + + switch (fec.type) + { + case spawn::FileExecutionTypes::Executable: + { + runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); + return true; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + auto r = shell::Open(targetInfo.absoluteFilePath()); + if (!r.success()) { + return false; + } + + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer) + if (r.processHandle() != INVALID_HANDLE_VALUE) { + // steal because it gets closed after the wait + return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); + } + + return true; + } + } +} + +bool ProcessRunner::runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + bool refresh) +{ + DWORD processExitCode = 0; + HANDLE processHandle = spawnAndWait( + binary, arguments, m_core.currentProfile()->name(), + currentDirectory, steamAppID, customOverwrite, forcedLibraries, + &processExitCode); + + if (processHandle == INVALID_HANDLE_VALUE) { + // failed + return false; + } + + if (refresh) { + m_core.afterRun(binary, processExitCode); + } + + return true; +} + +bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +{ + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } + + const QString customOverwrite = profile->setting( + "custom_overwrites", exe.title()).toString(); + + QList forcedLibraries; + + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + + return runExecutableFile( + exe.binaryInfo(), + exe.arguments(), + exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), + exe.steamAppID(), + customOverwrite, + forcedLibraries, + refresh); +} + +bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +{ + if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + } + + const Executable& exe = m_core.executablesList()->get(shortcut.executable()); + return runExecutable(exe, false); +} + +HANDLE ProcessRunner::runExecutableOrExecutableFile( + const QString& executable, const QStringList &args, const QString &cwd, + const QString& profileOverride, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } + + QString profileName = profileOverride; + if (profileName == "") { + profileName = profile->name(); + } + + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString steamAppID; + QString customOverwrite; + QList forcedLibraries; + + if (executable.contains('\\') || executable.contains('/')) { + // file path + + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable); + } + + if (currentDirectory == "") { + currentDirectory = binary.absolutePath(); + } + + try { + const Executable& exe = m_core.executablesList()->getByBinary(binary); + steamAppID = exe.steamAppID(); + customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + } catch (const std::runtime_error &) { + // nop + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_core.executablesList()->get(executable); + steamAppID = exe.steamAppID(); + customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + if (arguments == "") { + arguments = exe.arguments(); + } + binary = exe.binaryInfo(); + if (currentDirectory == "") { + currentDirectory = exe.workingDirectory(); + } + } catch (const std::runtime_error &) { + log::warn("\"{}\" not set up as executable", executable); + binary = QFileInfo(executable); + } + } + + if (!forcedCustomOverwrite.isEmpty()) + customOverwrite = forcedCustomOverwrite; + + if (ignoreCustomOverwrite) + customOverwrite.clear(); + + return spawnAndWait( + binary, + arguments, + profileName, + currentDirectory, + steamAppID, + customOverwrite, + forcedLibraries); +} + +HANDLE ProcessRunner::spawnAndWait( + const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + LPDWORD exitCode) +{ + spawn::SpawnParameters sp; + sp.binary = binary; + sp.arguments = arguments; + sp.currentDirectory = currentDirectory; + sp.steamAppID = steamAppID; + sp.hooked = true; + + if (!m_core.beforeRun(binary, profileName, customOverwrite, forcedLibraries)) { + return INVALID_HANDLE_VALUE; + } + + HANDLE handle = spawn(sp).releaseHandle(); + + if (handle == INVALID_HANDLE_VALUE) { + // failed + return INVALID_HANDLE_VALUE; + } + + waitForProcessCompletionWithLock(handle, exitCode); + return handle; +} + +SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp) +{ + QWidget* parent = nullptr; + if (m_ui) { + parent = m_ui->qtWidget(); + } + + if (!checkBinary(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + const auto* game = m_core.managedGame(); + auto& settings = m_core.settings(); + + if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkEnvironment(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkBlacklist(parent, sp, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + adjustForVirtualized(game, sp, settings); + + return {startBinary(parent, sp), sp}; +} + +void ProcessRunner::withLock(std::function f) +{ + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; + + if (m_ui != nullptr) { + uilock = m_ui->lock(); + } + else { + // i.e. when running command line shortcuts there is no user interface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); + } + + Guard g([&]() { + if (m_ui != nullptr) { + m_ui->unlock(); + } + }); + + f(uilock); +} + +bool ProcessRunner::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) { + return true; + } + + bool r = false; + + withLock([&](auto* uilock) { + DWORD ignoreExitCode; + r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + }); + + return r; +} + +bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) + return true; + + bool r = false; + + withLock([&](auto* uilock) { + r = waitForProcessCompletion(handle, exitCode, uilock); + }); + + return r; +} + +bool ProcessRunner::waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto tree = env::getProcessTree(handle); + std::vector processes = {tree}; + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + return true; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + return true; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = waitForProcess( + interestingHandle.get(), exitCode, progress); + + switch (r) + { + case WaitResults::Completed: // fall-through + case WaitResults::Cancelled: + return true; + + case WaitResults::Error: // fall-through + default: + return false; + } +} + +bool ProcessRunner::waitForAllUSVFSProcessesWithLock() +{ + if (!Settings::instance().interface().lockGUI()) + return true; + + bool r = false; + + withLock([&](auto* uilock) { + r = waitForAllUSVFSProcesses(uilock); + }); + + return r; +} + +bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + for (;;) { + const auto handles = getRunningUSVFSProcesses(); + if (handles.empty()) { + break; + } + + std::vector processes; + for (auto&& h : handles) { + auto p = env::getProcessTree(h); + if (p.isValid()) { + processes.emplace_back(std::move(p)); + } + } + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + break; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + break; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = waitForProcess( + interestingHandle.get(), nullptr, progress); + + switch (r) + { + case WaitResults::Completed: + // this process is completed, check for others + break; + + case WaitResults::Cancelled: + // force unlocked + log::debug("waiting for process completion aborted by UI"); + return true; + + case WaitResults::Error: // fall-through + default: + log::debug("waiting for process completion not successful"); + return false; + } + } + + log::debug("waiting for process completion successful"); + return true; +} + + + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + + std::vector handles; + handles.push_back(handle); + + std::vector exitCodes; + + const auto r = waitForProcesses(handles, exitCodes, progress); + + if (r == WaitResults::Completed) { + if (exitCode && !exitCodes.empty()) { + *exitCode = exitCodes[0]; + } + } + + return r; +} + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function progress) +{ + if (handles.empty()) { + return WaitResults::Completed; + } + + const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + static_cast(handles.size()), &handles[0], + TRUE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion, {}", formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { + // completed + exitCodes.resize(handles.size()); + std::fill(exitCodes.begin(), exitCodes.end(), 0); + + for (std::size_t i=0; i + +class OrganizerCore; +class ILockedWaitingForProcess; +class IUserInterface; +class Executable; +class MOShortcut; + +class SpawnedProcess +{ +public: + SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp); + + SpawnedProcess(const SpawnedProcess&) = delete; + SpawnedProcess& operator=(const SpawnedProcess&) = delete; + SpawnedProcess(SpawnedProcess&& other); + SpawnedProcess& operator=(SpawnedProcess&& other); + ~SpawnedProcess(); + + HANDLE releaseHandle(); + void wait(); + +private: + HANDLE m_handle; + spawn::SpawnParameters m_parameters; + + void destroy(); +}; + + +class ProcessRunner +{ +public: + ProcessRunner(OrganizerCore& core); + + void setUserInterface(IUserInterface* ui); + + bool runFile(QWidget* parent, const QFileInfo& targetInfo); + + bool runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID={}, + const QString &customOverwrite={}, + const QList &forcedLibraries={}, + bool refresh=true); + + bool runExecutable(const Executable& exe, bool refresh=true); + + bool runShortcut(const MOShortcut& shortcut); + + HANDLE runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false); + + bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + + bool waitForAllUSVFSProcessesWithLock(); + +private: + OrganizerCore& m_core; + IUserInterface* m_ui; + + HANDLE spawnAndWait( + const QFileInfo &binary, const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries={}, + LPDWORD exitCode = nullptr); + + SpawnedProcess spawn(spawn::SpawnParameters sp); + + void withLock(std::function f); + + bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); + + bool waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + + bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); +}; + + +enum class WaitResults +{ + Completed = 1, + Error, + Cancelled +}; + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress); + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function progress); + +#endif // PROCESSRUNNER_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 0ea60641..c8d7c76a 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -772,50 +772,6 @@ bool checkBlacklist( } } - -void adjustForVirtualized( - const IPluginGame* game, SpawnParameters& sp, const Settings& settings) -{ - const QString modsPath = settings.paths().mods(); - - // Check if this a request with either an executable or a working directory - // under our mods folder then will start the process in a virtualized - // "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = sp.currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = sp.binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = game->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; - - } - - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = game->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; - } - - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); - - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); - } -} - - HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) { HANDLE handle = INVALID_HANDLE_VALUE; @@ -842,80 +798,6 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) } } - - -SpawnedProcess::SpawnedProcess(HANDLE handle, SpawnParameters sp) - : m_handle(handle), m_parameters(std::move(sp)) -{ -} - -SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) - : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) -{ - other.m_handle = INVALID_HANDLE_VALUE; -} - -SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) -{ - if (this != &other) { - destroy(); - - m_handle = other.m_handle; - other.m_handle = INVALID_HANDLE_VALUE; - - m_parameters = std::move(other.m_parameters); - } - - return *this; -} - -SpawnedProcess::~SpawnedProcess() -{ - destroy(); -} - -HANDLE SpawnedProcess::releaseHandle() -{ - const auto h = m_handle; - m_handle = INVALID_HANDLE_VALUE; - return h; -} - -void SpawnedProcess::destroy() -{ - if (m_handle != INVALID_HANDLE_VALUE) { - ::CloseHandle(m_handle); - m_handle = INVALID_HANDLE_VALUE; - } -} - - -SpawnedProcess Spawner::spawn( - QWidget* parent, const IPluginGame* game, - SpawnParameters sp, Settings& settings) -{ - if (!checkBinary(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkEnvironment(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkBlacklist(parent, sp, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - adjustForVirtualized(game, sp, settings); - - return {startBinary(parent, sp), sp}; -} - - QString getExecutableForJarFile(const QString& jarFile) { const std::wstring jarFileW = jarFile.toStdWString(); @@ -1094,86 +976,6 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } -WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, std::function progress) -{ - if (handle == INVALID_HANDLE_VALUE) { - return WaitResults::Error; - } - - log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); - - std::vector handles; - handles.push_back(handle); - - std::vector exitCodes; - - const auto r = waitForProcesses(handles, exitCodes, progress); - - if (r == WaitResults::Completed) { - if (exitCode && !exitCodes.empty()) { - *exitCode = exitCodes[0]; - } - } - - return r; -} - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress) -{ - if (handles.empty()) { - return WaitResults::Completed; - } - - const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); - - for (;;) { - // Wait for a an event on the handle, a key press, mouse click or timeout - const auto res = MsgWaitForMultipleObjects( - static_cast(handles.size()), &handles[0], - TRUE, 50, QS_KEY | QS_MOUSEBUTTON); - - if (res == WAIT_FAILED) { - // error - const auto e = ::GetLastError(); - - log::error( - "failed waiting for process completion, {}", formatSystemMessage(e)); - - return WaitResults::Error; - } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { - // completed - exitCodes.resize(handles.size()); - std::fill(exitCodes.begin(), exitCodes.end(), 0); - - for (std::size_t i=0; i progress); - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress); - } // namespace -- cgit v1.3.1 From db0b92776b5c9a34ebb1a5ce5c3f0b105844ee16 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 30 Oct 2019 23:42:59 -0400 Subject: added lockwidget to replace all the other dialogs rewrote ProcessRunner to have a bunch of setters and then a run() fixed bad exit code when waiting on a process that's already completed removed lock()/unlock() from main window, ProcessRunner is in charge of that now --- src/CMakeLists.txt | 3 + src/envmodule.cpp | 1 - src/iuserinterface.h | 6 +- src/lockwidget.cpp | 224 ++++++++++++++++ src/lockwidget.h | 68 +++++ src/mainwindow.cpp | 44 +--- src/mainwindow.h | 7 - src/organizercore.cpp | 8 +- src/organizercore.h | 3 +- src/organizerproxy.cpp | 19 +- src/pch.h | 47 ++-- src/processrunner.cpp | 687 ++++++++++++++++++++++++++++--------------------- src/processrunner.h | 70 ++++- 13 files changed, 806 insertions(+), 381 deletions(-) create mode 100644 src/lockwidget.cpp create mode 100644 src/lockwidget.h (limited to 'src/organizercore.h') 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 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/organizercore.h') 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 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/organizercore.h') 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 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/organizercore.h') 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/organizercore.h') 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