From aa9a1fc07fb612547c1d1c5074d669b2dd258af9 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 13:04:27 -0500 Subject: Refactor obsolete methods --- src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index c6c87a64..b83e581b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -646,8 +646,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, const int monitor = settings.value("window_monitor").toInt(); if (monitor != -1) { - QDesktopWidget* desktop = QApplication::desktop(); - const QPoint center = desktop->availableGeometry(monitor).center(); + QGuiApplication::screens().at(monitor)->geometry().center(); + const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); splash.move(center - splash.rect().center()); } } -- cgit v1.3.1 From 3a2f056e8adf07f31ea1f6b2856a6d57da1f4777 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 14:12:38 -0500 Subject: More refactoring --- src/browserdialog.cpp | 1 - src/main.cpp | 5 ++++- src/mainwindow.cpp | 7 ++++--- src/modinfodialogconflicts.cpp | 2 +- src/pch.h | 1 - 5 files changed, 9 insertions(+), 7 deletions(-) (limited to 'src/main.cpp') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index c34d211f..70190433 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -35,7 +35,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include diff --git a/src/main.cpp b/src/main.cpp index b83e581b..1e60483f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -645,10 +645,13 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (settings.contains("window_monitor")) { const int monitor = settings.value("window_monitor").toInt(); - if (monitor != -1) { + 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()); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7681b482..9726b27b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -99,7 +99,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -1340,7 +1339,7 @@ void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) } m_CurrentSaveView->setSave(save); - QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView); + QRect screenRect = m_CurrentSaveView->window()->windowHandle()->screen()->geometry(); QPoint pos = QCursor::pos(); if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { @@ -2279,7 +2278,9 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("menubar_visible", m_menuBarVisible); settings.setValue("statusbar_visible", m_statusBarVisible); settings.setValue("window_split", ui->splitter->saveState()); - settings.setValue("window_monitor", QApplication::desktop()->screenNumber(this)); + 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()); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 6dd1fe4e..698c8534 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -1224,5 +1224,5 @@ std::optional AdvancedConflictsTab::createItem( return ConflictItem( std::move(beforeQS), std::move(relativeName), std::move(afterQS), - index, std::move(fileName), hasAlts, QString::null, archive); + index, std::move(fileName), hasAlts, QString(), archive); } diff --git a/src/pch.h b/src/pch.h index 1d8df43a..9640b09d 100644 --- a/src/pch.h +++ b/src/pch.h @@ -91,7 +91,6 @@ #include #include #include -#include #include #include #include -- cgit v1.3.1 From c38e864c92b7958099d74048fdfc88a5d3b40dcc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 7 Jul 2019 17:48:48 -0400 Subject: set the progress dialog's parent to main window as soon as it's available --- src/main.cpp | 3 +++ src/nxmaccessmanager.cpp | 37 ++++++++++++++++++++++++++++++++----- src/nxmaccessmanager.h | 5 ++++- 3 files changed, 39 insertions(+), 6 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index f2571685..0b078f03 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -697,6 +697,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, // set up main window and its data structures MainWindow mainWindow(settings, organizer, pluginContainer); + NexusInterface::instance(&pluginContainer) + ->getAccessManager()->setTopLevelWidget(&mainWindow); + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index ee6c03f1..5c72d3e2 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -47,7 +47,9 @@ namespace { NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) + , m_TopLevel(nullptr) , m_ValidateReply(nullptr) + , m_ProgressDialog(nullptr) , m_MOVersion(moVersion) { m_ValidateTimeout.setSingleShot(true); @@ -70,6 +72,20 @@ NXMAccessManager::~NXMAccessManager() } } +void NXMAccessManager::setTopLevelWidget(QWidget* w) +{ + m_TopLevel = w; + + if (m_ProgressDialog) { + const auto wasVisible = m_ProgressDialog->isVisible(); + + m_ProgressDialog->hide(); + m_ProgressDialog->setParent(w, m_ProgressDialog->windowFlags() | Qt::Dialog); + m_ProgressDialog->setModal(false); + m_ProgressDialog->setVisible(wasVisible); + } +} + QNetworkReply *NXMAccessManager::createRequest( QNetworkAccessManager::Operation operation, const QNetworkRequest &request, QIODevice *device) @@ -124,7 +140,11 @@ void NXMAccessManager::startValidationCheck() request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", m_MOVersion.toUtf8()); - m_ProgressDialog = new QProgressDialog(nullptr); + if (!m_ProgressDialog) { + m_ProgressDialog = new QProgressDialog(m_TopLevel); + m_ProgressDialog->setModal(false); + } + m_ProgressDialog->setLabelText(tr("Validating Nexus Connection")); QList buttons = m_ProgressDialog->findChildren(); buttons.at(0)->setEnabled(false); @@ -142,16 +162,23 @@ void NXMAccessManager::startValidationCheck() bool NXMAccessManager::validated() const { if (m_ValidateState == VALIDATE_CHECKING) { - QProgressDialog progress; - progress.setLabelText(tr("Validating Nexus Connection")); + if (!m_ProgressDialog) { + m_ProgressDialog = new QProgressDialog(m_TopLevel); + m_ProgressDialog->setModal(false); + } + + m_ProgressDialog->setLabelText(tr("Validating Nexus Connection")); QList buttons = m_ProgressDialog->findChildren(); buttons.at(0)->setEnabled(false); - progress.show(); + m_ProgressDialog->show(); while (m_ValidateState == VALIDATE_CHECKING) { QCoreApplication::processEvents(); QThread::msleep(100); } - progress.hide(); + + m_ProgressDialog->hide(); + m_ProgressDialog->deleteLater(); + m_ProgressDialog = nullptr; } return m_ValidateState == VALIDATE_VALID; diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 1bdeae40..16dde612 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -41,6 +41,8 @@ public: ~NXMAccessManager(); + void setTopLevelWidget(QWidget* w); + bool validated() const; bool validateAttempted() const; @@ -94,9 +96,10 @@ protected: QIODevice *device); private: + QWidget* m_TopLevel; QTimer m_ValidateTimeout; QNetworkReply *m_ValidateReply; - QProgressDialog *m_ProgressDialog { nullptr }; + mutable QProgressDialog* m_ProgressDialog; QString m_MOVersion; -- cgit v1.3.1 From 45f0a9e78ac876a2a956bc538c6d34358703e338 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 12 Jul 2019 02:46:31 -0400 Subject: nexus info and stats in settings cleaned up double logging for github reset validation progress dialog parent just before the main window dies removed unused APIStats from APIUserAccount, added isValid() --- src/apiuseraccount.cpp | 22 +++++ src/apiuseraccount.h | 8 +- src/main.cpp | 8 +- src/nxmaccessmanager.cpp | 2 +- src/organizercore.cpp | 2 +- src/selfupdater.cpp | 2 +- src/settingsdialog.cpp | 98 ++++++++++++++++----- src/settingsdialog.h | 6 ++ src/settingsdialog.ui | 218 +++++++++++++++++++++++------------------------ 9 files changed, 226 insertions(+), 140 deletions(-) (limited to 'src/main.cpp') diff --git a/src/apiuseraccount.cpp b/src/apiuseraccount.cpp index 596f8aa7..35a868d5 100644 --- a/src/apiuseraccount.cpp +++ b/src/apiuseraccount.cpp @@ -1,10 +1,32 @@ #include "apiuseraccount.h" +QString localizedUserAccountType(APIUserAccountTypes t) +{ + switch (t) + { + case APIUserAccountTypes::Regular: + return QObject::tr("Regular"); + + case APIUserAccountTypes::Premium: + return QObject::tr("Premium"); + + case APIUserAccountTypes::None: // fall-through + default: + return QObject::tr("None"); + } +} + + APIUserAccount::APIUserAccount() : m_type(APIUserAccountTypes::None) { } +bool APIUserAccount::isValid() const +{ + return !m_key.isEmpty(); +} + const QString& APIUserAccount::apiKey() const { return m_key; diff --git a/src/apiuseraccount.h b/src/apiuseraccount.h index 7dd16128..ea4e8685 100644 --- a/src/apiuseraccount.h +++ b/src/apiuseraccount.h @@ -18,6 +18,8 @@ enum class APIUserAccountTypes Premium }; +QString localizedUserAccountType(APIUserAccountTypes t); + /** * current limits imposed on the user account @@ -61,6 +63,11 @@ public: APIUserAccount(); + /** + * whether the user is logged in + */ + bool isValid() const; + /** * api key */ @@ -134,7 +141,6 @@ private: QString m_key, m_id, m_name; APIUserAccountTypes m_type; APILimits m_limits; - APIStats m_stats; }; #endif // APIUSERACCOUNT_H diff --git a/src/main.cpp b/src/main.cpp index 0b078f03..4359c645 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -713,7 +713,13 @@ int runApplication(MOApplication &application, SingleInstance &instance, mainWindow.activateWindow(); splash.finish(&mainWindow); - return application.exec(); + + const auto ret = application.exec(); + + NexusInterface::instance(&pluginContainer) + ->getAccessManager()->setTopLevelWidget(nullptr); + + return ret; } } catch (const std::exception &e) { reportError(e.what()); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index a331b2e8..196368fd 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -167,7 +167,7 @@ QString NexusSSOLogin::stateToString(States s, const QString& e) return QObject::tr("Opened browser, waiting for user..."); case Finished: - return QObject::tr("Connected."); + return QObject::tr("Finished."); case Timeout: return QObject::tr( diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 87668f4b..eeb69e61 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -2484,7 +2484,7 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary) void OrganizerCore::loginFailed(const QString &message) { - qDebug().nospace().noquote() + qCritical().nospace().noquote() << "Nexus API validation failed: " << message; if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 271c621b..e967b27c 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -130,7 +130,7 @@ void SelfUpdater::testForUpdate() m_GitHub.releases(GitHub::Repository("Modorganizer2", "modorganizer"), [this](const QJsonArray &releases) { if (releases.isEmpty()) { - qDebug("Unable to connect to github.com to check version"); + // error message already logged return; } diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index df957f87..0dae31ac 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -55,6 +55,7 @@ public: : QDialog(parent), ui(new Ui::NexusManualKeyDialog) { ui->setupUi(this); + ui->key->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); connect(ui->openBrowser, &QPushButton::clicked, [&]{ openBrowser(); }); connect(ui->paste, &QPushButton::clicked, [&]{ paste(); }); @@ -111,7 +112,7 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); - updateNexusButtons(); + updateNexusState(); } SettingsDialog::~SettingsDialog() @@ -399,7 +400,7 @@ void SettingsDialog::on_nexusConnect_clicked() ui->nexusLog->clear(); m_nexusLogin->start(); - updateNexusButtons(); + updateNexusState(); } void SettingsDialog::on_nexusManualKey_clicked() @@ -421,8 +422,18 @@ void SettingsDialog::on_nexusManualKey_clicked() } ui->nexusLog->clear(); - ui->nexusLog->addItem(tr("Checking API key...")); + validateKey(key); +} + +void SettingsDialog::on_nexusDisconnect_clicked() +{ + clearKey(); + ui->nexusLog->clear(); + addNexusLog(tr("Disconnected.")); +} +void SettingsDialog::validateKey(const QString& key) +{ if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( *NexusInterface::instance(m_PluginContainer)->getAccessManager())); @@ -436,6 +447,7 @@ void SettingsDialog::on_nexusManualKey_clicked() }; } + addNexusLog(tr("Checking API key...")); m_nexusValidator->start(key); } @@ -444,45 +456,62 @@ void SettingsDialog::onSSOKeyChanged(const QString& key) if (key.isEmpty()) { clearKey(); } else { - setKey(key); + addNexusLog(tr("Received API key.")); + validateKey(key); } } void SettingsDialog::onSSOStateChanged(NexusSSOLogin::States s, const QString& e) { - const auto log = NexusSSOLogin::stateToString(s, e); + if (s != NexusSSOLogin::Finished) { + // finished state is handled in onSSOKeyChanged() + const auto log = NexusSSOLogin::stateToString(s, e); - for (auto&& line : log.split("\n")) { - ui->nexusLog->addItem(line); + for (auto&& line : log.split("\n")) { + addNexusLog(line); + } } - updateNexusButtons(); + updateNexusState(); } void SettingsDialog::onValidatorStateChanged( NexusKeyValidator::States s, const QString& e) { - const auto log = NexusKeyValidator::stateToString(s, e); + if (s != NexusKeyValidator::Finished) { + // finished state is handled in onValidatorFinished() + const auto log = NexusKeyValidator::stateToString(s, e); - for (auto&& line : log.split("\n")) { - ui->nexusLog->addItem(line); + for (auto&& line : log.split("\n")) { + addNexusLog(line); + } } - updateNexusButtons(); + updateNexusState(); } void SettingsDialog::onValidatorFinished(const APIUserAccount& user) { + NexusInterface::instance(m_PluginContainer)->setUserAccount(user); + if (!user.apiKey().isEmpty()) { - setKey(user.apiKey()); + if (setKey(user.apiKey())) { + addNexusLog(tr("Linked with Nexus successfully.")); + } } } +void SettingsDialog::addNexusLog(const QString& s) +{ + ui->nexusLog->addItem(s); + ui->nexusLog->scrollToBottom(); +} + bool SettingsDialog::setKey(const QString& key) { m_keyChanged = true; const bool ret = m_settings->setNexusApiKey(key); - updateNexusButtons(); + updateNexusState(); return ret; } @@ -490,13 +519,19 @@ bool SettingsDialog::clearKey() { m_keyChanged = true; const auto ret = m_settings->clearNexusApiKey(); - updateNexusButtons(); NexusInterface::instance(m_PluginContainer)->getAccessManager()->clearApiKey(); + updateNexusState(); return ret; } +void SettingsDialog::updateNexusState() +{ + updateNexusButtons(); + updateNexusData(); +} + void SettingsDialog::updateNexusButtons() { if (m_nexusLogin && m_nexusLogin->isActive()) { @@ -532,6 +567,32 @@ void SettingsDialog::updateNexusButtons() } } +void SettingsDialog::updateNexusData() +{ + const auto user = NexusInterface::instance(m_PluginContainer) + ->getAPIUserAccount(); + + if (user.isValid()) { + ui->nexusUserID->setText(user.id()); + ui->nexusName->setText(user.name()); + ui->nexusAccount->setText(localizedUserAccountType(user.type())); + + ui->nexusDailyRequests->setText(QString("%1/%2") + .arg(user.limits().remainingDailyRequests) + .arg(user.limits().maxDailyRequests)); + + ui->nexusHourlyRequests->setText(QString("%1/%2") + .arg(user.limits().remainingHourlyRequests) + .arg(user.limits().maxHourlyRequests)); + } else { + ui->nexusUserID->setText(tr("N/A")); + ui->nexusName->setText(tr("N/A")); + ui->nexusAccount->setText(tr("N/A")); + ui->nexusDailyRequests->setText(tr("N/A")); + ui->nexusHourlyRequests->setText(tr("N/A")); + } +} + void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) { if (pluginItem != nullptr) { @@ -599,13 +660,6 @@ void SettingsDialog::on_clearCacheButton_clicked() NexusInterface::instance(m_PluginContainer)->clearCache(); } -void SettingsDialog::on_nexusDisconnect_clicked() -{ - clearKey(); - ui->nexusLog->clear(); - ui->nexusLog->addItem(tr("Disconnected.")); -} - void SettingsDialog::normalizePath(QLineEdit *lineEdit) { QString text = lineEdit->text(); diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 1741fc13..c5f487fd 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -145,15 +145,21 @@ private: std::unique_ptr m_nexusLogin; std::unique_ptr m_nexusValidator; + void validateKey(const QString& key); bool setKey(const QString& key); bool clearKey(); + + void updateNexusState(); void updateNexusButtons(); + void updateNexusData(); void onSSOKeyChanged(const QString& key); void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); void onValidatorStateChanged(NexusKeyValidator::States s, const QString& e); void onValidatorFinished(const APIUserAccount& user); + + void addNexusLog(const QString& s); }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index dfbde943..fccc8be0 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -451,103 +451,7 @@ If you use pre-releases, never contact me directly by e-mail or via private mess Nexus - - - - - Nexus Connection - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Connect to Nexus - - - - - - - Manually enter the API key and try to login - - - Enter API Key Manually - - - - - - - Clear the stored Nexus API key and force reauthorization. - - - Disconnect from Nexus - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QAbstractScrollArea::AdjustToContents - - - - - - - - - + @@ -569,10 +473,13 @@ If you use pre-releases, never contact me directly by e-mail or via private mess Nexus Account + + 10 + - User ID + User ID: Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse @@ -589,21 +496,21 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - Username + Name: - + - username + name - Account + Account: @@ -623,17 +530,27 @@ If you use pre-releases, never contact me directly by e-mail or via private mess Statistics + + 10 + - Daily requests + Daily requests: + + + + + + + daily requests - Hourly requests + Hourly requests: @@ -644,24 +561,99 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - - + + + + + + + + + + Nexus Connection + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + - Requests queued + Connect to Nexus - - + + + + Manually enter the API key and try to login + - queued + Enter API Key Manually - - + + + + Clear the stored Nexus API key and force reauthorization. + - daily requests + Disconnect from Nexus + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractScrollArea::AdjustToContents -- cgit v1.3.1 From bc9f286bce224743d244e540d55f26b55affbd4a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 12 Jul 2019 08:30:40 -0400 Subject: moved the log to a dock widget added a menu item in the view menu for it --- src/main.cpp | 4 ++ src/mainwindow.cpp | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/mainwindow.h | 5 ++- src/mainwindow.ui | 69 ++++++++++++++++++++++-------- 4 files changed, 176 insertions(+), 22 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 4359c645..db0c8f93 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -706,6 +706,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, SLOT(externalMessage(QString))); mainWindow.processUpdates(); + + // this must be before readSettings(), see DockFixer in mainwindow.cpp + splash.finish(&mainWindow); + mainWindow.readSettings(); qDebug("displaying main window"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9586cb93..4b5ef9ed 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -195,6 +195,102 @@ const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +// this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock +// sizes are not restored when the main window is maximized; it is used in +// MainWindow::readSettings() and MainWindow::storeSettings() +// +// there's also https://stackoverflow.com/questions/44005852, which has what +// seems to be a popular fix, but it breaks the restored size of the window +// by setting it to the desktop's resolution, so that doesn't work +// +// the only fix I could find is to remember the sizes of the docks and manually +// setting them back; saving is straightforward, but restoring is messy +// +// this also depends on the window being visible before the timer in restore() +// is fired and the timer must be processed by application.exec(); therefore, +// the splash screen _must_ be closed before readSettings() is called, because +// it has its own event loop, which seems to interfere with this +// +// all of this should become unnecessary when QTBUG-46620 is fixed +// +class DockFixer +{ +public: + static void save(MainWindow* mw, QSettings& settings) + { + const auto docks = mw->findChildren(); + + // saves the size of each dock + for (int i=0; isize().width(); + } else { + size = docks[i]->size().height(); + } + + settings.setValue(settingName(docks[i]), size); + } + } + + static void restore(MainWindow* mw, const QSettings& settings) + { + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; + + std::vector dockInfos; + + const auto docks = mw->findChildren(); + + // for each dock + for (int i=0; iresizeDocks({info.d}, {info.size}, info.ori); + } + }); + } + + static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d) + { + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(d) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } + } + + static QString settingName(QDockWidget* d) + { + return "geometry/" + d->objectName() + "_size"; + } +}; + + MainWindow::MainWindow(QSettings &initSettings , OrganizerCore &organizerCore , PluginContainer &pluginContainer @@ -422,7 +518,8 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); - connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ toolbarMenu_aboutToShow(); }); + connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); + connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); @@ -764,7 +861,7 @@ void MainWindow::updatePinnedExecutables() ui->menuRun->menuAction()->setVisible(hasLinks); } -void MainWindow::toolbarMenu_aboutToShow() +void MainWindow::updateToolbarMenu() { // well, this is a bit of a hack to allow the same toolbar menu to be shown // in both the main menu and the context menu @@ -788,6 +885,11 @@ void MainWindow::toolbarMenu_aboutToShow() ui->actionToolBarIconsAndText->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon); } +void MainWindow::updateViewMenu() +{ + ui->actionViewLog->setChecked(ui->logDock->isVisible()); +} + QMenu* MainWindow::createPopupMenu() { return ui->menuToolbars; @@ -838,6 +940,11 @@ void MainWindow::on_actionToolBarIconsAndText_triggered() setToolbarButtonStyle(Qt::ToolButtonTextUnderIcon); } +void MainWindow::on_actionViewLog_triggered() +{ + ui->logDock->setVisible(!ui->logDock->isVisible()); +} + void MainWindow::setToolbarSize(const QSize& s) { for (auto* tb : findChildren()) { @@ -1257,7 +1364,7 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().registerAsNXMHandler(false); m_WasVisible = true; - updateProblemsButton(); + updateProblemsButton(); } } @@ -2204,6 +2311,8 @@ void MainWindow::readSettings() if (settings.value("Settings/use_proxy", false).toBool()) { activateProxy(true); } + + DockFixer::restore(this, settings); } void MainWindow::processUpdates() { @@ -2285,10 +2394,13 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + for (const std::pair kv : m_PersistedGeometry) { QString key = QString("geometry/") + kv.first; settings.setValue(key, kv.second->saveState()); } + + DockFixer::save(this, settings); } } @@ -2569,7 +2681,7 @@ void MainWindow::directory_refreshed() if (ui->tabWidget->currentIndex() == 2) { refreshDataTreeKeepExpandedNodes(); } - + } void MainWindow::esplist_changed() diff --git a/src/mainwindow.h b/src/mainwindow.h index 80508787..d7dbfd90 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -219,7 +219,9 @@ private: void updatePinnedExecutables(); void setToolbarSize(const QSize& s); void setToolbarButtonStyle(Qt::ToolButtonStyle s); - void toolbarMenu_aboutToShow(); + + void updateToolbarMenu(); + void updateViewMenu(); QMenu* createPopupMenu() override; void activateSelectedProfile(); @@ -654,6 +656,7 @@ private slots: // ui slots void on_actionToolBarIconsOnly_triggered(); void on_actionToolBarTextOnly_triggered(); void on_actionToolBarIconsAndText_triggered(); + void on_actionViewLog_triggered(); void on_centralWidget_customContextMenuRequested(const QPoint &pos); void on_bsaList_customContextMenuRequested(const QPoint &pos); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 70d1cf39..d83c68ca 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1286,23 +1286,6 @@ p, li { white-space: pre-wrap; } - - - Qt::ActionsContextMenu - - - QAbstractItemView::NoSelection - - - true - - - false - - - true - - @@ -1403,6 +1386,7 @@ p, li { white-space: pre-wrap; } + @@ -1417,6 +1401,49 @@ p, li { white-space: pre-wrap; } + + + Log + + + 8 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::ActionsContextMenu + + + QAbstractItemView::NoSelection + + + true + + + false + + + true + + + + + + @@ -1736,6 +1763,14 @@ p, li { white-space: pre-wrap; } Status &bar + + + true + + + Log + + -- cgit v1.3.1 From 54d98e291701f2187174a67c186f1ea762c6b959 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 16 Jul 2019 05:38:32 -0400 Subject: removed unused or redundant stuff in error_report.h renamed log() to vlog() for now extracted console creation to Console class rewrote LogBuffer to work with logging from uibase, renamed to LogModel added fmt dependency --- CMakeLists.txt | 8 +- src/CMakeLists.txt | 6 +- src/logbuffer.cpp | 263 +++++++++++++----------------------------- src/logbuffer.h | 70 +++-------- src/main.cpp | 116 ++++++++++++++++--- src/mainwindow.cpp | 35 ++++-- src/mainwindow.h | 1 + src/mainwindow.ui | 15 ++- src/organizercore.cpp | 1 - src/profile.cpp | 5 +- src/shared/directoryentry.cpp | 12 +- src/shared/error_report.cpp | 45 -------- src/shared/error_report.h | 21 +--- 13 files changed, 254 insertions(+), 344 deletions(-) (limited to 'src/main.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 94c76373..ac9d8fc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) -ADD_COMPILE_OPTIONS($<$:/MP> $<$:$<$:/O2>> $<$:$<$:/O2>>) +ADD_COMPILE_OPTIONS( + $<$:/MP> + $<$:/D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING> + $<$:$<$:/O2>> + $<$:$<$:/O2>>) PROJECT(organizer) @@ -11,9 +15,11 @@ set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) set(CMAKE_INSTALL_MESSAGE NEVER) SET(DEPENDENCIES_DIR CACHE PATH "") + # hint to find qt in dependencies path LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll) +LIST(APPEND CMAKE_PREFIX_PATH ${FMT_ROOT}/build) ADD_SUBDIRECTORY(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f197211a..a359b8a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -520,6 +520,9 @@ LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) FIND_PACKAGE(zlib REQUIRED) # TODO FindZlib doesn't find the static zlib library +# fmt +find_package(fmt REQUIRED) + INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/bsatk/src ${project_path}/esptk/src @@ -551,10 +554,11 @@ ELSE() ENDIF() ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm}) + TARGET_LINK_LIBRARIES(ModOrganizer Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick Qt5::Qml Qt5::QuickWidgets Qt5::Network Qt5::WebSockets - ${Boost_LIBRARIES} + ${Boost_LIBRARIES} fmt::fmt zlibstatic uibase esptk bsatk githubpp ${usvfs_name} diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index dfe8f943..9e3cd712 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -20,239 +20,140 @@ along with Mod Organizer. If not, see . #include "logbuffer.h" #include #include +#include #include #include #include #include #include -using MOBase::reportError; +using namespace MOBase; -QScopedPointer LogBuffer::s_Instance; -QMutex LogBuffer::s_Mutex; +static LogModel* g_instance = nullptr; +const std::size_t MaxLines = 1000; -LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, - const QString &outputFileName) - : QAbstractItemModel(nullptr) - , m_OutFileName(outputFileName) - , m_ShutDown(false) - , m_MinMsgType(minMsgType) - , m_NumMessages(0) +LogModel::LogModel() { - m_Messages.resize(messageCount); + connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); } -LogBuffer::~LogBuffer() +void LogModel::create() { - qInstallMessageHandler(0); - write(); + g_instance = new LogModel; } -void LogBuffer::logMessage(QtMsgType type, const QString &message) +LogModel& LogModel::instance() { - if (type >= m_MinMsgType) { - QStringList messagelist = message.split("\n"); - for (auto split_message : messagelist) { - Message msg = {type, QTime::currentTime(), split_message}; - if (m_NumMessages < m_Messages.size()) { - beginInsertRows(QModelIndex(), static_cast(m_NumMessages), - static_cast(m_NumMessages) + 1); - } - m_Messages.at(m_NumMessages % m_Messages.size()) = msg; - if (m_NumMessages < m_Messages.size()) { - endInsertRows(); - } else { - emit dataChanged(createIndex(0, 0), - createIndex(static_cast(m_Messages.size()), 0)); - } - ++m_NumMessages; - if (type >= QtCriticalMsg) { - write(); - } - } - } -} - -void LogBuffer::write() const -{ - if (m_NumMessages == 0) { - return; - } - - DWORD lastError = ::GetLastError(); - - QFile file(m_OutFileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write log to %1: %2") - .arg(m_OutFileName) - .arg(file.errorString())); - return; - } - - unsigned int i - = (m_NumMessages > m_Messages.size()) - ? static_cast(m_NumMessages - m_Messages.size()) - : 0U; - for (; i < m_NumMessages; ++i) { - file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8()); - file.write("\r\n"); - } - ::SetLastError(lastError); + return *g_instance; } -void LogBuffer::init(int messageCount, QtMsgType minMsgType, - const QString &outputFileName) +void LogModel::add(MOBase::log::Entry e) { - QMutexLocker guard(&s_Mutex); - - s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); - qInstallMessageHandler(LogBuffer::log); + emit entryAdded(std::move(e)); } -char LogBuffer::msgTypeID(QtMsgType type) +void LogModel::onEntryAdded(MOBase::log::Entry e) { - switch (type) { - case QtDebugMsg: - return 'D'; - case QtInfoMsg: - return 'I'; - case QtWarningMsg: - return 'W'; - case QtCriticalMsg: - return 'C'; - case QtFatalMsg: - return 'F'; - default: - return '?'; + bool full = false; + if (m_messages.size() > MaxLines) { + m_messages.pop_front(); + full = true; } -} -void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, - const QString &message) -{ - // QMutexLocker doesn't support timeout... - if (!s_Mutex.tryLock(100)) { - fprintf(stderr, "failed to log: %s", qUtf8Printable(message)); - return; - } - ON_BLOCK_EXIT([]() { s_Mutex.unlock(); }); + const int row = static_cast(m_messages.size()); - if (!s_Instance.isNull()) { - s_Instance->logMessage(type, message); + if (!full) { + beginInsertRows(QModelIndex(), row, row + 1); } - if (type == QtDebugMsg) { - fprintf(stdout, "%s [%c] %s\n", qUtf8Printable(QTime::currentTime().toString()), - msgTypeID(type), qUtf8Printable(message)); + m_messages.emplace_back(std::move(e)); + + if (!full) { + endInsertRows(); } else { - if (context.line != 0) { - fprintf(stdout, "%s [%c] (%s:%u) %s\n", - qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type), - context.file, context.line, qUtf8Printable(message)); - } else { - fprintf(stdout, "%s [%c] %s\n", - qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type), - qUtf8Printable(message)); - } + emit dataChanged( + createIndex(row, 0), + createIndex(row + 1, columnCount({}))); } - fflush(stdout); } -QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const +QModelIndex LogModel::index(int row, int column, const QModelIndex&) const { return createIndex(row, column, row); } -QModelIndex LogBuffer::parent(const QModelIndex &) const +QModelIndex LogModel::parent(const QModelIndex&) const { return QModelIndex(); } -int LogBuffer::rowCount(const QModelIndex &parent) const +int LogModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; else - return static_cast(std::min(m_NumMessages, m_Messages.size())); + return static_cast(m_messages.size()); } -int LogBuffer::columnCount(const QModelIndex &) const +int LogModel::columnCount(const QModelIndex&) const { - return 2; + return 3; } -QVariant LogBuffer::data(const QModelIndex &index, int role) const +QVariant LogModel::data(const QModelIndex& index, int role) const { - unsigned int offset - = m_NumMessages < m_Messages.size() - ? 0 - : static_cast(m_NumMessages - m_Messages.size()); - unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size(); - switch (role) { - case Qt::DisplayRole: { - if (index.column() == 0) { - return m_Messages[msgIndex].time.toString("H: mm: ss"); - } else if (index.column() == 1) { - const QString &msg = m_Messages[msgIndex].message; - if (msg.length() < 200) { - return msg; - } else { - return msg.mid(0, 200) + "..."; - } - } - } break; - case Qt::DecorationRole: { - if (index.column() == 1) { - switch (m_Messages[msgIndex].type) { - case QtDebugMsg: - case QtInfoMsg: - return QIcon(":/MO/gui/information"); - case QtWarningMsg: - return QIcon(":/MO/gui/warning"); - case QtCriticalMsg: - return QIcon(":/MO/gui/important"); - case QtFatalMsg: - return QIcon(":/MO/gui/problem"); - } - } - } break; - case Qt::UserRole: { - if (index.column() == 1) { - switch (m_Messages[msgIndex].type) { - case QtDebugMsg: - return "D"; - case QtInfoMsg: - return "I"; - case QtWarningMsg: - return "W"; - case QtCriticalMsg: - return "C"; - case QtFatalMsg: - return "F"; - } - } - } break; + using namespace std::chrono; + + const auto row = static_cast(index.row()); + if (row >= m_messages.size()) { + return {}; } - return QVariant(); -} -void LogBuffer::writeNow() -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->write(); + const auto& e = m_messages[row]; + + if (role == Qt::DisplayRole) { + if (index.column() == 1) { + const auto ms = duration_cast(e.time.time_since_epoch()); + const auto s = duration_cast(ms); + + const std::time_t t = s.count(); + const std::size_t frac = ms.count() % 1000; + + auto time = QDateTime::fromTime_t(t).time(); + time = time.addMSecs(frac); + + return time.toString("hh:mm:ss.zzz"); + } else if (index.column() == 2) { + return QString::fromStdString(e.message); + } + } + + if (role == Qt::DecorationRole) { + if (index.column() == 0) { + switch (e.level) { + case log::Warning: + return QIcon(":/MO/gui/warning"); + + case log::Error: + return QIcon(":/MO/gui/problem"); + + case log::Debug: // fall-through + case log::Info: + default: + return {}; + } + } } + + return QVariant(); } -void LogBuffer::cleanQuit() +QVariant LogModel::headerData(int, Qt::Orientation, int) const { - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->m_ShutDown = true; - } + return {}; } -void log(const char *format, ...) +void vlog(const char *format, ...) { va_list argList; va_start(argList, format); @@ -268,11 +169,3 @@ void log(const char *format, ...) va_end(argList); } - -QString LogBuffer::Message::toString() const -{ - return QString("%1 [%2] %3") - .arg(time.toString()) - .arg(msgTypeID(type)) - .arg(message); -} diff --git a/src/logbuffer.h b/src/logbuffer.h index 0cfecfa2..1bf8901b 100644 --- a/src/logbuffer.h +++ b/src/logbuffer.h @@ -26,70 +26,36 @@ along with Mod Organizer. If not, see . #include #include #include +#include - -class LogBuffer : public QAbstractItemModel +class LogModel : public QAbstractItemModel { Q_OBJECT public: + static void create(); + static LogModel& instance(); - static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName); - static void log(QtMsgType type, const QMessageLogContext &context, const QString &message); - - static void writeNow(); - static void cleanQuit(); - - static LogBuffer *instance() { return s_Instance.data(); } - -public: - - virtual ~LogBuffer(); - - void logMessage(QtMsgType type, const QString &message); + void add(MOBase::log::Entry e); - // QAbstractItemModel interface -public: - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &child) const; - int rowCount(const QModelIndex &parent) const; - int columnCount(const QModelIndex &parent) const; - QVariant data(const QModelIndex &index, int role) const; +protected: + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QVariant headerData( + int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; signals: - -public slots: + void entryAdded(MOBase::log::Entry e); private: + std::deque m_messages; - explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName); - LogBuffer(const LogBuffer &reference); // not implemented - LogBuffer &operator=(const LogBuffer &reference); // not implemented - - void write() const; - - static char msgTypeID(QtMsgType type); - -private: - - struct Message { - QtMsgType type; - QTime time; - QString message; - QString toString() const; - }; - -private: - - static QScopedPointer s_Instance; - static QMutex s_Mutex; - - QString m_OutFileName; - bool m_ShutDown; - QtMsgType m_MinMsgType; - size_t m_NumMessages; - std::vector m_Messages; - + LogModel(); + void onEntryAdded(MOBase::log::Entry e); }; #endif // LOGBUFFER_H diff --git a/src/main.cpp b/src/main.cpp index db0c8f93..23ea234a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,6 +51,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include @@ -731,18 +732,46 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } -int doCoreDump(env::CoreDumpTypes type) +class Console { - // open a console - AllocConsole(); +public: + Console() + { + // open a console + AllocConsole(); - // redirect stdin, stdout and stderr to it - FILE* in=nullptr; - FILE* out=nullptr; - FILE* err=nullptr; - freopen_s(&in, "CONIN$", "r", stdin); - freopen_s(&out, "CONOUT$", "w", stdout); - freopen_s(&err, "CONOUT$", "w", stderr); + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); + } + + ~Console() + { + // close redirected handles + std::fclose(m_err); + std::fclose(m_out); + std::fclose(m_in); + + // close console + FreeConsole(); + + // redirect stdin, stdout and stderr to NUL, don't bother closing the + // handles + freopen_s(&m_in, "NUL", "r", stdin); + freopen_s(&m_out, "NUL", "w", stdout); + freopen_s(&m_err, "NUL", "w", stderr); + } + +private: + FILE* m_in = nullptr; + FILE* m_out = nullptr; + FILE* m_err = nullptr; +}; + +int doCoreDump(env::CoreDumpTypes type) +{ + Console c; // dump const auto b = env::coredumpOther(type); @@ -753,15 +782,66 @@ int doCoreDump(env::CoreDumpTypes type) std::wcerr << L"Press enter to continue..."; std::wcin.get(); - // close redirected handles - std::fclose(err); - std::fclose(out); - std::fclose(in); + return (b ? 0 : 1); +} + +log::Levels convertQtLevel(QtMsgType t) +{ + switch (t) + { + case QtDebugMsg: + return log::Debug; - // close console - FreeConsole(); + case QtWarningMsg: + return log::Warning; - return (b ? 0 : 1); + case QtCriticalMsg: // fall-through + case QtFatalMsg: + return log::Error; + + case QtInfoMsg: // fall-through + default: + return log::Info; + } +} + +void qtLogCallback( + QtMsgType type, const QMessageLogContext& context, const QString& message) +{ + std::string_view file = ""; + + if (type != QtDebugMsg) { + if (context.file) { + file = context.file; + + const auto lastSep = file.find_last_of("/\\"); + if (lastSep != std::string_view::npos) { + file = {context.file + lastSep + 1}; + } + } + } + + if (file.empty()) { + log::log( + convertQtLevel(type), "{}", + message.toStdString()); + } else { + log::log( + convertQtLevel(type), "[{}:{}] {}", + file, context.line, message.toStdString()); + } +} + +void initLogging(const QString& logFile) +{ + LogModel::create(); + + log::init( + true, MOBase::log::File::rotating(logFile.toStdWString(), 5*1024*1024, 5), + MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$", + [](log::Entry e){ LogModel::instance().add(e); }); + + qInstallMessageHandler(qtLogCallback); } int main(int argc, char *argv[]) @@ -839,7 +919,7 @@ int main(int argc, char *argv[]) // initialize dump collection only after "dataPath" since the crashes are stored under it prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - LogBuffer::init(1000000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + initLogging(qApp->property("dataPath").toString() + "/logs/mo_interface.log"); QString splash = dataPath + "/splash.png"; if (!QFile::exists(dataPath + "/splash.png")) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4b5ef9ed..cd224414 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -357,17 +357,10 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - ui->logList->setModel(LogBuffer::instance()); - ui->logList->setColumnWidth(0, 100); - ui->logList->setAutoScroll(true); - ui->logList->scrollToBottom(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + setupLogList(); + 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); - connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), - ui->logList, SLOT(scrollToBottom())); - connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - ui->logList, SLOT(scrollToBottom())); updateProblemsButton(); @@ -593,6 +586,30 @@ MainWindow::MainWindow(QSettings &initSettings updateModCount(); } +void MainWindow::setupLogList() +{ + ui->logList->setModel(&LogModel::instance()); + + const int timestampWidth = + QFontMetrics(ui->logList->font()).width("00:00:00.000"); + + ui->logList->header()->setMinimumSectionSize(0); + ui->logList->header()->resizeSection(0, 20); + ui->logList->header()->resizeSection(1, timestampWidth + 8); + + ui->logList->setAutoScroll(true); + ui->logList->scrollToBottom(); + ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + + connect( + ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + ui->logList, SLOT(scrollToBottom())); + + connect( + ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + ui->logList, SLOT(scrollToBottom())); +} + void MainWindow::resetActionIcons() { // this is a bit of a hack diff --git a/src/mainwindow.h b/src/mainwindow.h index d7dbfd90..9ca3e5c3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -636,6 +636,7 @@ private slots: void resetActionIcons(); void updateModCount(); void updatePluginCount(); + void setupLogList(); private slots: // ui slots // actions diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d83c68ca..5a45b3b4 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1402,6 +1402,9 @@ p, li { white-space: pre-wrap; } + + QDockWidget::AllDockWidgetFeatures + Log @@ -1428,17 +1431,17 @@ p, li { white-space: pre-wrap; } Qt::ActionsContextMenu - QAbstractItemView::NoSelection - - - true + QAbstractItemView::ExtendedSelection - + false - + true + + false + diff --git a/src/organizercore.cpp b/src/organizercore.cpp index eeb69e61..5f5c3afe 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -342,7 +342,6 @@ OrganizerCore::~OrganizerCore() m_CurrentProfile = nullptr; ModInfo::clear(); - LogBuffer::cleanQuit(); m_ModList.setProfile(nullptr); // NexusInterface::instance()->cleanup(); diff --git a/src/profile.cpp b/src/profile.cpp index ef387027..01906903 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -287,8 +287,11 @@ void Profile::createTweakedIniFile() } if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorString().c_str())); + const auto e = ::GetLastError(); + reportError(tr("failed to create tweaked ini: %1") + .arg(formatSystemMessageQ(e))); } + qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(tweakedIni))); } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index bde515a9..9d9edd85 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -103,7 +103,7 @@ public: m_OriginsNameMap.erase(iter); m_OriginsNameMap[newName] = idx; } else { - log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); + vlog("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); } } @@ -714,12 +714,12 @@ void DirectoryEntry::removeFile(FileEntry::Index index) if (iter != m_Files.end()) { m_Files.erase(iter); } else { - log("file \"%ls\" not in directory \"%ls\"", + vlog("file \"%ls\" not in directory \"%ls\"", m_FileRegister->getFile(index)->getName().c_str(), this->getName().c_str()); } } else { - log("file \"%ls\" not in directory \"%ls\", directory empty", + vlog("file \"%ls\" not in directory \"%ls\", directory empty", m_FileRegister->getFile(index)->getName().c_str(), this->getName().c_str()); } @@ -844,7 +844,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry *temp = findSubDirectory(pathComponent); if (temp != nullptr) { if (len >= path.size()) { - log("unexpected end of path"); + vlog("unexpected end of path"); return FileEntry::Ptr(); } return temp->searchFile(path.substr(len + 1), directory); @@ -988,7 +988,7 @@ bool FileRegister::removeFile(FileEntry::Index index) m_Files.erase(index); return true; } else { - log("invalid file index for remove: %lu", index); + vlog("invalid file index for remove: %lu", index); return false; } } @@ -1002,7 +1002,7 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) m_Files.erase(iter); } } else { - log("invalid file index for remove (for origin): %lu", index); + vlog("invalid file index for remove (for origin): %lu", index); } } diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp index 6d091630..4185b544 100644 --- a/src/shared/error_report.cpp +++ b/src/shared/error_report.cpp @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . namespace MOShared { - void reportError(LPCSTR format, ...) { char buffer[1025]; @@ -52,48 +51,4 @@ void reportError(LPCWSTR format, ...) MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR); } - -std::string getCurrentErrorStringA() -{ - LPSTR buffer = nullptr; - - DWORD errorCode = ::GetLastError(); - - if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) { - ::SetLastError(errorCode); - return std::string(); - } else { - LPSTR lastChar = buffer + strlen(buffer) - 2; - *lastChar = '\0'; - - std::string result(buffer); - - LocalFree(buffer); - ::SetLastError(errorCode); - return result; - } -} - -std::wstring getCurrentErrorStringW() -{ - LPWSTR buffer = nullptr; - - DWORD errorCode = ::GetLastError(); - - if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, nullptr) == 0) { - ::SetLastError(errorCode); - return std::wstring(); - } else { - LPWSTR lastChar = buffer + wcslen(buffer) - 2; - *lastChar = '\0'; - - std::wstring result(buffer); - - LocalFree(buffer); - ::SetLastError(errorCode); - return result; - } -} } // namespace MOShared diff --git a/src/shared/error_report.h b/src/shared/error_report.h index c09ad75b..a003ee09 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -24,28 +24,11 @@ along with Mod Organizer. If not, see . #include #include -namespace std { -#ifdef UNICODE -typedef wstring tstring; -#else -typedef string tstring; -#endif -} - -extern void log(const char* format, ...); - namespace MOShared { void reportError(LPCSTR format, ...); void reportError(LPCWSTR format, ...); -std::string getCurrentErrorStringA(); -std::wstring getCurrentErrorStringW(); - -#ifdef UNICODE -#define getCurrentErrorString getCurrentErrorStringW -#else -#define getCurrentErrorString getCurrentErrorStringA -#endif - } // namespace MOShared + +void vlog(const char* format, ...); -- cgit v1.3.1 From 6217f910f095adea4b06f370726636b09efea4ed Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 16 Jul 2019 05:42:22 -0400 Subject: renamed logbuffer files to loglist --- src/CMakeLists.txt | 6 +- src/logbuffer.cpp | 171 -------------------------------------------------- src/logbuffer.h | 61 ------------------ src/loglist.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/loglist.h | 61 ++++++++++++++++++ src/main.cpp | 2 +- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 1 - 8 files changed, 237 insertions(+), 238 deletions(-) delete mode 100644 src/logbuffer.cpp delete mode 100644 src/logbuffer.h create mode 100644 src/loglist.cpp create mode 100644 src/loglist.h (limited to 'src/main.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a359b8a9..9dbab132 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,7 +73,7 @@ SET(organizer_SRCS mainwindow.cpp main.cpp loghighlighter.cpp - logbuffer.cpp + loglist.cpp lockeddialogbase.cpp lockeddialog.cpp waitingonclosedialog.cpp @@ -181,7 +181,7 @@ SET(organizer_HDRS messagedialog.h mainwindow.h loghighlighter.h - logbuffer.h + loglist.h lockeddialogbase.h lockeddialog.h waitingonclosedialog.h @@ -436,7 +436,7 @@ set(widgets filterwidget icondelegate lcdnumber - logbuffer + loglist loghighlighter modflagicondelegate modidlineedit diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp deleted file mode 100644 index 9e3cd712..00000000 --- a/src/logbuffer.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "logbuffer.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - -static LogModel* g_instance = nullptr; -const std::size_t MaxLines = 1000; - -LogModel::LogModel() -{ - connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); -} - -void LogModel::create() -{ - g_instance = new LogModel; -} - -LogModel& LogModel::instance() -{ - return *g_instance; -} - -void LogModel::add(MOBase::log::Entry e) -{ - emit entryAdded(std::move(e)); -} - -void LogModel::onEntryAdded(MOBase::log::Entry e) -{ - bool full = false; - if (m_messages.size() > MaxLines) { - m_messages.pop_front(); - full = true; - } - - const int row = static_cast(m_messages.size()); - - if (!full) { - beginInsertRows(QModelIndex(), row, row + 1); - } - - m_messages.emplace_back(std::move(e)); - - if (!full) { - endInsertRows(); - } else { - emit dataChanged( - createIndex(row, 0), - createIndex(row + 1, columnCount({}))); - } -} - -QModelIndex LogModel::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - -QModelIndex LogModel::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -int LogModel::rowCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - return 0; - else - return static_cast(m_messages.size()); -} - -int LogModel::columnCount(const QModelIndex&) const -{ - return 3; -} - -QVariant LogModel::data(const QModelIndex& index, int role) const -{ - using namespace std::chrono; - - const auto row = static_cast(index.row()); - if (row >= m_messages.size()) { - return {}; - } - - const auto& e = m_messages[row]; - - if (role == Qt::DisplayRole) { - if (index.column() == 1) { - const auto ms = duration_cast(e.time.time_since_epoch()); - const auto s = duration_cast(ms); - - const std::time_t t = s.count(); - const std::size_t frac = ms.count() % 1000; - - auto time = QDateTime::fromTime_t(t).time(); - time = time.addMSecs(frac); - - return time.toString("hh:mm:ss.zzz"); - } else if (index.column() == 2) { - return QString::fromStdString(e.message); - } - } - - if (role == Qt::DecorationRole) { - if (index.column() == 0) { - switch (e.level) { - case log::Warning: - return QIcon(":/MO/gui/warning"); - - case log::Error: - return QIcon(":/MO/gui/problem"); - - case log::Debug: // fall-through - case log::Info: - default: - return {}; - } - } - } - - return QVariant(); -} - -QVariant LogModel::headerData(int, Qt::Orientation, int) const -{ - return {}; -} - -void vlog(const char *format, ...) -{ - va_list argList; - va_start(argList, format); - - static const int BUFFERSIZE = 1000; - - char buffer[BUFFERSIZE + 1]; - buffer[BUFFERSIZE] = '\0'; - - vsnprintf(buffer, BUFFERSIZE, format, argList); - - qCritical("%s", buffer); - - va_end(argList); -} diff --git a/src/logbuffer.h b/src/logbuffer.h deleted file mode 100644 index 1bf8901b..00000000 --- a/src/logbuffer.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef LOGBUFFER_H -#define LOGBUFFER_H - -#include -#include -#include -#include -#include -#include -#include - -class LogModel : public QAbstractItemModel -{ - Q_OBJECT - -public: - static void create(); - static LogModel& instance(); - - void add(MOBase::log::Entry e); - -protected: - QModelIndex index(int row, int column, const QModelIndex& parent) const override; - QModelIndex parent(const QModelIndex &child) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - - QVariant headerData( - int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; - -signals: - void entryAdded(MOBase::log::Entry e); - -private: - std::deque m_messages; - - LogModel(); - void onEntryAdded(MOBase::log::Entry e); -}; - -#endif // LOGBUFFER_H diff --git a/src/loglist.cpp b/src/loglist.cpp new file mode 100644 index 00000000..cb927272 --- /dev/null +++ b/src/loglist.cpp @@ -0,0 +1,171 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "loglist.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +static LogModel* g_instance = nullptr; +const std::size_t MaxLines = 1000; + +LogModel::LogModel() +{ + connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); +} + +void LogModel::create() +{ + g_instance = new LogModel; +} + +LogModel& LogModel::instance() +{ + return *g_instance; +} + +void LogModel::add(MOBase::log::Entry e) +{ + emit entryAdded(std::move(e)); +} + +void LogModel::onEntryAdded(MOBase::log::Entry e) +{ + bool full = false; + if (m_messages.size() > MaxLines) { + m_messages.pop_front(); + full = true; + } + + const int row = static_cast(m_messages.size()); + + if (!full) { + beginInsertRows(QModelIndex(), row, row + 1); + } + + m_messages.emplace_back(std::move(e)); + + if (!full) { + endInsertRows(); + } else { + emit dataChanged( + createIndex(row, 0), + createIndex(row + 1, columnCount({}))); + } +} + +QModelIndex LogModel::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column, row); +} + +QModelIndex LogModel::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +int LogModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + else + return static_cast(m_messages.size()); +} + +int LogModel::columnCount(const QModelIndex&) const +{ + return 3; +} + +QVariant LogModel::data(const QModelIndex& index, int role) const +{ + using namespace std::chrono; + + const auto row = static_cast(index.row()); + if (row >= m_messages.size()) { + return {}; + } + + const auto& e = m_messages[row]; + + if (role == Qt::DisplayRole) { + if (index.column() == 1) { + const auto ms = duration_cast(e.time.time_since_epoch()); + const auto s = duration_cast(ms); + + const std::time_t t = s.count(); + const std::size_t frac = ms.count() % 1000; + + auto time = QDateTime::fromTime_t(t).time(); + time = time.addMSecs(frac); + + return time.toString("hh:mm:ss.zzz"); + } else if (index.column() == 2) { + return QString::fromStdString(e.message); + } + } + + if (role == Qt::DecorationRole) { + if (index.column() == 0) { + switch (e.level) { + case log::Warning: + return QIcon(":/MO/gui/warning"); + + case log::Error: + return QIcon(":/MO/gui/problem"); + + case log::Debug: // fall-through + case log::Info: + default: + return {}; + } + } + } + + return QVariant(); +} + +QVariant LogModel::headerData(int, Qt::Orientation, int) const +{ + return {}; +} + +void vlog(const char *format, ...) +{ + va_list argList; + va_start(argList, format); + + static const int BUFFERSIZE = 1000; + + char buffer[BUFFERSIZE + 1]; + buffer[BUFFERSIZE] = '\0'; + + vsnprintf(buffer, BUFFERSIZE, format, argList); + + qCritical("%s", buffer); + + va_end(argList); +} diff --git a/src/loglist.h b/src/loglist.h new file mode 100644 index 00000000..1bf8901b --- /dev/null +++ b/src/loglist.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef LOGBUFFER_H +#define LOGBUFFER_H + +#include +#include +#include +#include +#include +#include +#include + +class LogModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + static void create(); + static LogModel& instance(); + + void add(MOBase::log::Entry e); + +protected: + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QVariant headerData( + int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; + +signals: + void entryAdded(MOBase::log::Entry e); + +private: + std::deque m_messages; + + LogModel(); + void onEntryAdded(MOBase::log::Entry e); +}; + +#endif // LOGBUFFER_H diff --git a/src/main.cpp b/src/main.cpp index 23ea234a..8b5648c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,7 +39,7 @@ along with Mod Organizer. If not, see . #include "singleinstance.h" #include "utility.h" #include "helper.h" -#include "logbuffer.h" +#include "loglist.h" #include "selectiondialog.h" #include "moapplication.h" #include "tutorialmanager.h" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cd224414..107f3e09 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,7 +59,7 @@ along with Mod Organizer. If not, see . #include "installationmanager.h" #include "lockeddialog.h" #include "waitingonclosedialog.h" -#include "logbuffer.h" +#include "loglist.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5f5c3afe..d3cd54ee 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -14,7 +14,6 @@ #include "plugincontainer.h" #include "pluginlistsortproxy.h" #include "profile.h" -#include "logbuffer.h" #include "credentialsdialog.h" #include "filedialogmemory.h" #include "modinfodialog.h" -- cgit v1.3.1 From ad77e315f5c53994d75056608df0f9ff0a390530 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 09:10:14 -0400 Subject: moved Console to util --- src/main.cpp | 39 +-------------------------------------- src/shared/util.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 14 ++++++++++++++ 3 files changed, 58 insertions(+), 38 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 8b5648c9..65f4bd05 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -732,46 +732,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } -class Console -{ -public: - Console() - { - // open a console - AllocConsole(); - - // redirect stdin, stdout and stderr to it - freopen_s(&m_in, "CONIN$", "r", stdin); - freopen_s(&m_out, "CONOUT$", "w", stdout); - freopen_s(&m_err, "CONOUT$", "w", stderr); - } - - ~Console() - { - // close redirected handles - std::fclose(m_err); - std::fclose(m_out); - std::fclose(m_in); - - // close console - FreeConsole(); - - // redirect stdin, stdout and stderr to NUL, don't bother closing the - // handles - freopen_s(&m_in, "NUL", "r", stdin); - freopen_s(&m_out, "NUL", "w", stdout); - freopen_s(&m_err, "NUL", "w", stderr); - } - -private: - FILE* m_in = nullptr; - FILE* m_out = nullptr; - FILE* m_err = nullptr; -}; - int doCoreDump(env::CoreDumpTypes type) { - Console c; + env::Console c; // dump const auto b = env::coredumpOther(type); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 17df3b92..29e52f40 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -441,6 +441,49 @@ private: }; +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + Shortcut::Shortcut() : m_iconIndex(0) { diff --git a/src/shared/util.h b/src/shared/util.h index c4a2ed7d..a5d096ac 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -54,6 +54,20 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); namespace env { +class Console +{ +public: + Console(); + ~Console(); + +private: + bool m_hasConsole; + FILE* m_in; + FILE* m_out; + FILE* m_err; +}; + + // an application shortcut that can be either on the desktop or the start menu // class Shortcut -- cgit v1.3.1 From 4dfaa363c05eb7691e2c7ea755c35758b62e0fc9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:00:47 -0400 Subject: logging initialized early, log file set later replaced a few qDebug() --- src/loglist.cpp | 8 +++--- src/main.cpp | 80 +++++++++++++++++++++++++++++++-------------------------- 2 files changed, 47 insertions(+), 41 deletions(-) (limited to 'src/main.cpp') diff --git a/src/loglist.cpp b/src/loglist.cpp index 9e876d37..207f412b 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -121,12 +121,10 @@ QVariant LogModel::data(const QModelIndex& index, int role) const const auto ms = duration_cast(e.time.time_since_epoch()); const auto s = duration_cast(ms); - const std::time_t t = s.count(); - const std::size_t frac = ms.count() % 1000; - - auto time = QDateTime::fromTime_t(t).time(); - time = time.addMSecs(frac); + const std::time_t tt = s.count(); + const int frac = static_cast(ms.count() % 1000); + const auto time = QDateTime::fromTime_t(tt).time().addMSecs(frac); return time.toString("hh:mm:ss.zzz"); } else if (index.column() == 2) { return QString::fromStdString(e.message); diff --git a/src/main.cpp b/src/main.cpp index 65f4bd05..f55c32f2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -253,17 +253,17 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { - qDebug("profile overwritten on command line"); + log::debug("profile overwritten on command line"); selectedProfileName = arguments.at(profileIndex + 1); } arguments.removeAt(profileIndex); arguments.removeAt(profileIndex); } if (selectedProfileName.isEmpty()) { - qDebug("no configured profile"); + log::debug("no configured profile"); selectedProfileName = "Default"; } else { - qDebug("configured profile: %s", qUtf8Printable(selectedProfileName)); + log::debug("configured profile: {}", selectedProfileName); } return selectedProfileName; @@ -425,8 +425,7 @@ void setupPath() { static const int BUFSIZE = 4096; - qDebug("MO at: %s", qUtf8Printable(QDir::toNativeSeparators( - QCoreApplication::applicationDirPath()))); + log::debug("MO at {}", QCoreApplication::applicationDirPath()); QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); @@ -447,7 +446,7 @@ void setupPath() void preloadDll(const QString& filename) { - qDebug().nospace() << "preloading " << filename; + log::debug("preloading {}", filename); if (GetModuleHandleW(filename.toStdWString().c_str())) { // already loaded, this can happen when "restarting" MO by switching @@ -490,41 +489,43 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } +void dumpEnvironment() +{ + env::Environment env; + + log::debug("windows: {}", env.windowsInfo().toString()); + + if (env.windowsInfo().compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : env.securityProducts()) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : env.loadedModules()) { + log::debug(" . {}", m.toString()); + } +} + int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { - qDebug().nospace() - << "Starting Mod Organizer version " - << getVersionDisplayString() << " revision " << GITID; + log::info( + "Starting Mod Organizer version {} revision {}", + getVersionDisplayString(), GITID); #if !defined(QT_NO_SSL) preloadSsl(); - qDebug("ssl support: %d", QSslSocket::supportsSsl()); + log::info("ssl support: {}", QSslSocket::supportsSsl()); #else - qDebug("non-ssl build"); + log::info("non-ssl build"); #endif - { - env::Environment env; - - qDebug().nospace().noquote() - << "windows: " << env.windowsInfo().toString(); - - if (env.windowsInfo().compatibilityMode()) { - qWarning() << "MO seems to be running in compatibility mode"; - } - - qDebug().nospace().noquote() << "security products:"; - for (const auto& sp : env.securityProducts()) { - qDebug().nospace().noquote() << " . " << sp.toString(); - } - - qDebug() << "modules loaded in process:"; - for (const auto& m : env.loadedModules()) { - qDebug().nospace().noquote() << " . " << m.toString(); - } - } + dumpEnvironment(); QString dataPath = application.property("dataPath").toString(); qDebug("data path: %s", qUtf8Printable(dataPath)); @@ -795,18 +796,19 @@ void qtLogCallback( } } -void initLogging(const QString& logFile) +void initLogging() { LogModel::create(); - log::init( - true, MOBase::log::File::rotating(logFile.toStdWString(), 5*1024*1024, 5), - MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$", + log::createDefault(MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$"); + + log::getDefault().setCallback( [](log::Entry e){ LogModel::instance().add(e); }); qInstallMessageHandler(qtLogCallback); } + int main(int argc, char *argv[]) { // handle --crashdump first @@ -820,6 +822,8 @@ int main(int argc, char *argv[]) } } + initLogging(); + //Make sure the configured temp folder exists QDir tempDir = QDir::temp(); if (!tempDir.exists()) @@ -882,7 +886,11 @@ int main(int argc, char *argv[]) // initialize dump collection only after "dataPath" since the crashes are stored under it prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - initLogging(qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + const auto logFile = + qApp->property("dataPath").toString() + "/logs/mo_interface.log"; + + log::getDefault().setFile(MOBase::log::File::rotating( + logFile.toStdWString(), 5*1024*1024, 5)); QString splash = dataPath + "/splash.png"; if (!QFile::exists(dataPath + "/splash.png")) { -- 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/main.cpp') 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 28c46eed919cf3044147f642ea0a8d9909fea2ea Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:53:25 -0400 Subject: reversed log level menu actions to fit settings combobox replaced qWarnings() and qCritical() --- src/main.cpp | 11 ++++------- src/mainwindow.cpp | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index aa842ead..b89e4a80 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -133,9 +133,9 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except int dumpRes = CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); if (!dumpRes) - qCritical("ModOrganizer has crashed, crash dump created."); + log::error("ModOrganizer has crashed, crash dump created."); else - qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError()); + log::error("ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", dumpRes, GetLastError()); if (prevUnhandledExceptionFilter) return prevUnhandledExceptionFilter(exceptionPtrs); @@ -456,16 +456,13 @@ void preloadDll(const QString& filename) const auto dllPath = appPath + "\\" + filename; if (!QFile::exists(dllPath)) { - qWarning().nospace() << dllPath << "not found"; + log::warn("{} not found", dllPath); return; } if (!LoadLibraryW(dllPath.toStdWString().c_str())) { const auto e = GetLastError(); - - qWarning().nospace() - << "failed to load " << dllPath << ": " - << formatSystemMessage(e); + log::warn("failed to load {}: {}", dllPath, formatSystemMessageQ(e)); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f65bf4e1..6c2ab389 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -833,10 +833,10 @@ void MainWindow::setupLogMenu() levels->addAction(a); }; - addAction(tr("&Errors"), log::Error); - addAction(tr("&Warnings"), log::Warning); - addAction(tr("&Info"), log::Info); 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)); }); -- cgit v1.3.1 From eb190380e3044900a30ba41c81a23d813fd708e9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 12:15:08 -0400 Subject: dump executables on startup --- src/executableslist.cpp | 30 +++++++++++++++++++++++++++++- src/executableslist.h | 4 ++++ src/main.cpp | 35 +++++++++++++++++++++++------------ 3 files changed, 56 insertions(+), 13 deletions(-) (limited to 'src/main.cpp') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 077d2a93..0ca880cd 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 #include #include @@ -65,7 +66,7 @@ bool ExecutablesList::empty() const void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) { - qDebug("setting up configured executables"); + log::debug("loading executables"); m_Executables.clear(); @@ -103,6 +104,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) if (needsUpgrade) upgradeFromCustom(game); + + dump(); } void ExecutablesList::store(QSettings& settings) @@ -332,6 +335,31 @@ void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game) } } +void ExecutablesList::dump() const +{ + for (const auto& e : m_Executables) { + QStringList flags; + + if (e.flags() & Executable::ShowInToolbar) { + flags.push_back("toolbar"); + } + + if (e.flags() & Executable::UseApplicationIcon) { + flags.push_back("icon"); + } + + log::debug( + " . executable '{}'\n" + " binary: {}\n" + " arguments: {}\n" + " steam ID: {}\n" + " directory: {}\n" + " flags: {} ({})", + e.title(), e.binaryInfo().absoluteFilePath(), e.arguments(), + e.steamAppID(), e.workingDirectory(), flags.join("|"), e.flags()); + } +} + Executable::Executable(QString title) : m_title(title) diff --git a/src/executableslist.h b/src/executableslist.h index 2d1dd28e..eda2034e 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -214,6 +214,10 @@ private: * called when MO is still using the old custom executables from 2.2.0 **/ void upgradeFromCustom(const MOBase::IPluginGame* game); + + // logs all executables + // + void dump() const; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags) diff --git a/src/main.cpp b/src/main.cpp index b89e4a80..6b4280b4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -503,6 +503,27 @@ void dumpEnvironment() } } +void dumpSettings(QSettings& settings) +{ + static 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(); +} + int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { @@ -538,22 +559,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, Settings settings(initSettings); log::getDefault().setLevel(settings.logLevel()); - 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()); - 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(); - + dumpEnvironment(); + dumpSettings(initSettings); log::debug("initializing core"); OrganizerCore organizer(settings); -- cgit v1.3.1 From 35ae099d3fcc6c5b42fbd8d10e5efc2427bcf2dc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 12:23:26 -0400 Subject: check for files likely to be eaten by an AV on startup --- src/main.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 6b4280b4..15d36428 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -505,7 +505,7 @@ void dumpEnvironment() void dumpSettings(QSettings& settings) { - static QStringList ignore({ + static const QStringList ignore({ "username", "password", "nexus_api_key" }); @@ -524,11 +524,33 @@ void dumpSettings(QSettings& settings) settings.endGroup(); } +void sanityChecks() +{ + // files that are likely to be eaten + static const QStringList files({ + "helper.exe", "nxmhandler.exe", + "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", + "usvfs_x64.dll", "usvfs_x86.dll" + }); + + const auto dir = QCoreApplication::applicationDirPath(); + + for (const auto& name : files) { + const QFileInfo file(dir + QDir::separator() + name); + if (!file.exists()) { + log::warn( + "'{}' seems to be missing, an antivirus may have deleted it", + file.absoluteFilePath()); + } + } +} + + int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { log::info( - "Starting Mod Organizer version {} revision {} in {}", + "starting Mod Organizer version {} revision {} in {}", getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); preloadSsl(); @@ -565,6 +587,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, dumpEnvironment(); dumpSettings(initSettings); + sanityChecks(); log::debug("initializing core"); OrganizerCore organizer(settings); -- cgit v1.3.1 From 2ef32d12306ac21c0c900ff8f353ff0b573e67ae Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 12:39:01 -0400 Subject: moved environment dump to member function added check for nahimic --- src/main.cpp | 51 +++++++++++++++++++++++++++------------------------ src/shared/util.cpp | 25 ++++++++++++++++++++++--- src/shared/util.h | 6 +++++- 3 files changed, 54 insertions(+), 28 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 15d36428..ef698dd4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -482,27 +482,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void dumpEnvironment() -{ - env::Environment env; - - log::debug("windows: {}", env.windowsInfo().toString()); - - if (env.windowsInfo().compatibilityMode()) { - log::warn("MO seems to be running in compatibility mode"); - } - - log::debug("security products:"); - for (const auto& sp : env.securityProducts()) { - log::debug(" . {}", sp.toString()); - } - - log::debug("modules loaded in process:"); - for (const auto& m : env.loadedModules()) { - log::debug(" . {}", m.toString()); - } -} - void dumpSettings(QSettings& settings) { static const QStringList ignore({ @@ -524,7 +503,7 @@ void dumpSettings(QSettings& settings) settings.endGroup(); } -void sanityChecks() +void checkMissingFiles() { // files that are likely to be eaten static const QStringList files({ @@ -545,6 +524,28 @@ void sanityChecks() } } +void checkNahimic(const env::Environment& e) +{ + for (auto&& m : e.loadedModules()) { + const QFileInfo file(m.path()); + + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive)) { + log::warn( + "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "Mod Organizer, such as freezing or blank windows. Consider " + "uninstalling it."); + + break; + } + } +} + +void sanityChecks(const env::Environment& e) +{ + checkMissingFiles(); + checkNahimic(e); +} + int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) @@ -585,9 +586,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, // update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); - dumpEnvironment(); + env::Environment env; + + env.dump(); dumpSettings(initSettings); - sanityChecks(); + sanityChecks(env); log::debug("initializing core"); OrganizerCore organizer(settings); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 8d8c2000..eacd1f88 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "executableslist.h" #include "instancemanager.h" #include +#include #include #include @@ -41,8 +42,7 @@ along with Mod Organizer. If not, see . #pragma comment(lib, "Wbemuuid.lib") -using MOBase::formatSystemMessage; -using MOBase::formatSystemMessageQ; +using namespace MOBase; namespace fs = std::filesystem; namespace MOShared { @@ -870,7 +870,7 @@ Environment::Environment() m_security = getSecurityProducts(); } -const std::vector& Environment::loadedModules() +const std::vector& Environment::loadedModules() const { return m_modules; } @@ -885,6 +885,25 @@ const std::vector& Environment::securityProducts() const return m_security; } +void Environment::dump() const +{ + log::debug("windows: {}", windowsInfo().toString()); + + if (windowsInfo().compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : securityProducts()) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : loadedModules()) { + log::debug(" . {}", m.toString()); + } +} + std::vector Environment::getLoadedModules() const { HandlePtr snapshot(CreateToolhelp32Snapshot( diff --git a/src/shared/util.h b/src/shared/util.h index a5d096ac..267bb780 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -410,7 +410,7 @@ public: // list of loaded modules in the current process // - const std::vector& loadedModules(); + const std::vector& loadedModules() const; // information about the operating system // @@ -420,6 +420,10 @@ public: // const std::vector& securityProducts() const; + // logs the environment + // + void dump() const; + private: std::vector m_modules; WindowsInfo m_windows; -- cgit v1.3.1 From 20ac714bf880ab7e3762428c7154d1c81b5188ba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 19:00:33 -0400 Subject: fixed bad compare for nahimic log displays on startup --- src/main.cpp | 2 +- src/shared/util.cpp | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 27 +++++++ 3 files changed, 251 insertions(+), 1 deletion(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index ef698dd4..09da9408 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -529,7 +529,7 @@ void checkNahimic(const env::Environment& e) for (auto&& m : e.loadedModules()) { const QFileInfo file(m.path()); - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive)) { + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { log::warn( "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " "Mod Organizer, such as freezing or blank windows. Consider " diff --git a/src/shared/util.cpp b/src/shared/util.cpp index eacd1f88..8c4a3f17 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -39,6 +39,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #pragma comment(lib, "Wbemuuid.lib") @@ -864,6 +865,196 @@ private: }; +class DisplayEnumerator +{ +public: + DisplayEnumerator() + : m_GetDpiForMonitor(nullptr) + { + m_shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (m_shcore) { + // windows 8.1+ only + m_GetDpiForMonitor = reinterpret_cast( + GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); + } + + // gets all monitors and the device they're running on + getDisplayDevices(); + } + + std::vector&& displays() && + { + return std::move(m_displays); + } + + const std::vector& displays() const & + { + return m_displays; + } + +private: + using GetDpiForMonitorFunction = + HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + std::unique_ptr m_shcore; + GetDpiForMonitorFunction* m_GetDpiForMonitor; + std::vector m_displays; + + void getDisplayDevices() + { + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.push_back(createDisplay(device)); + } + } + + Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) + { + Metrics::Display d; + + d.adapter = QString::fromWCharArray(device.DeviceString); + d.monitor = QString::fromWCharArray(device.DeviceName); + d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); + + getDisplaySettings(device.DeviceName, d); + getDpi(d); + + return d; + } + + void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", d.monitor); + return; + } + + // all these fields should be available + + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + d.refreshRate = dm.dmDisplayFrequency; + } + + if (dm.dmFields & DM_PELSWIDTH) { + d.resX = dm.dmPelsWidth; + } + + if (dm.dmFields & DM_PELSHEIGHT) { + d.resY = dm.dmPelsHeight; + } + } + + void getDpi(Metrics::Display& d) + { + if (!m_GetDpiForMonitor) { + // this happens on windows 7, get the desktop dpi instead + getDesktopDpi(d); + return; + } + + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(d.monitor); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", d.monitor); + return; + } + + UINT dpiX=0, dpiY=0; + const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + d.monitor, formatSystemMessageQ(r)); + + return; + } + + // dpiX and dpiY are always identical, as per the documentation + d.dpi = dpiX; + } + + void getDesktopDpi(Metrics::Display& d) + { + // desktop dc + HDC dc = GetDC(0); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + return; + } + + d.dpi = GetDeviceCaps(dc, LOGPIXELSX); + + ReleaseDC(0, dc); + } + + HMONITOR findMonitor(const QString& name) + { + // passed to the enumeration callback + struct Data + { + DisplayEnumerator* self; + QString name; + HMONITOR hm; + }; + + Data data = {this, name, 0}; + + // for each monitor + EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessageQ(e)); + + // error for this monitor, but continue + return TRUE; + } + + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } + + // not found, continue to the next monitor + return TRUE; + }, reinterpret_cast(&data)); + + return data.hm; + } +}; + + Environment::Environment() { m_modules = getLoadedModules(); @@ -885,6 +1076,11 @@ const std::vector& Environment::securityProducts() const return m_security; } +const Metrics& Environment::metrics() const +{ + return m_metrics; +} + void Environment::dump() const { log::debug("windows: {}", windowsInfo().toString()); @@ -902,6 +1098,11 @@ void Environment::dump() const for (const auto& m : loadedModules()) { log::debug(" . {}", m.toString()); } + + log::debug("displays:"); + for (const auto& d : m_metrics.displays()) { + log::debug(" . {}", d.toString()); + } } std::vector Environment::getLoadedModules() const @@ -1137,6 +1338,28 @@ std::optional Environment::getWindowsFirewall() const } +Metrics::Metrics() +{ + m_displays = DisplayEnumerator().displays(); +} + +const std::vector& Metrics::displays() const +{ + return m_displays; +} + +QString Metrics::Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(resX) + .arg(resY) + .arg(refreshRate) + .arg(dpi) + .arg(adapter) + .arg(primary ? " (primary)" : ""); +} + + Module::Module(QString path, std::size_t fileSize) : m_path(std::move(path)), m_fileSize(fileSize) { diff --git a/src/shared/util.h b/src/shared/util.h index 267bb780..fc2028db 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -401,6 +401,28 @@ private: }; +class Metrics +{ +public: + struct Display + { + int resX=0, resY=0, dpi=0; + bool primary=false; + int refreshRate = 0; + QString monitor, adapter; + + QString toString() const; + }; + + Metrics(); + + const std::vector& displays() const; + +private: + std::vector m_displays; +}; + + // represents the process's environment // class Environment @@ -420,6 +442,10 @@ public: // const std::vector& securityProducts() const; + // information about displays + // + const Metrics& metrics() const; + // logs the environment // void dump() const; @@ -428,6 +454,7 @@ private: std::vector m_modules; WindowsInfo m_windows; std::vector m_security; + Metrics m_metrics; std::vector getLoadedModules() const; std::vector getSecurityProducts() const; -- cgit v1.3.1 From b2a1e1391fdd6bdee1c5e8d337b273447c70a506 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 18 Jul 2019 23:13:57 -0400 Subject: split env --- src/CMakeLists.txt | 25 +- src/env.cpp | 479 ++++++++++++ src/env.h | 117 +++ src/envmetrics.cpp | 224 ++++++ src/envmetrics.h | 28 + src/envmodule.cpp | 390 ++++++++++ src/envmodule.h | 98 +++ src/envsecurity.cpp | 418 ++++++++++ src/envsecurity.h | 49 ++ src/envshortcut.cpp | 376 +++++++++ src/envshortcut.h | 114 +++ src/envwindows.cpp | 236 ++++++ src/envwindows.h | 106 +++ src/main.cpp | 2 + src/mainwindow.cpp | 1 + src/shared/util.cpp | 2123 +-------------------------------------------------- src/shared/util.h | 443 ----------- 17 files changed, 2663 insertions(+), 2566 deletions(-) create mode 100644 src/env.cpp create mode 100644 src/env.h create mode 100644 src/envmetrics.cpp create mode 100644 src/envmetrics.h create mode 100644 src/envmodule.cpp create mode 100644 src/envmodule.h create mode 100644 src/envsecurity.cpp create mode 100644 src/envsecurity.h create mode 100644 src/envshortcut.cpp create mode 100644 src/envshortcut.h create mode 100644 src/envwindows.cpp create mode 100644 src/envwindows.h (limited to 'src/main.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9dbab132..9785dc3d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,6 +129,12 @@ SET(organizer_SRCS filerenamer.cpp texteditor.cpp expanderwidget.cpp + env.cpp + envmetrics.cpp + envmodule.cpp + envsecurity.cpp + envshortcut.cpp + envwindows.cpp shared/windows_error.cpp shared/error_report.cpp @@ -239,6 +245,12 @@ SET(organizer_HDRS filerenamer.h texteditor.h expanderwidget.h + env.h + envmetrics.h + envmodule.h + envsecurity.h + envshortcut.h + envwindows.h shared/windows_error.h shared/error_report.h @@ -350,6 +362,15 @@ set(downloads downloadmanager ) +set(env + env + envmetrics + envmodule + envsecurity + envshortcut + envwindows +) + set(executables executableslist editexecutablesdialog @@ -447,8 +468,8 @@ set(widgets ) set(src_filters - application core browser dialogs downloads executables locking modinfo modinfo\\dialog - modlist plugins previews profiles settings utilities widgets + application core browser dialogs downloads env executables locking modinfo + modinfo\\dialog modlist plugins previews profiles settings utilities widgets ) foreach(filter in list ${src_filters}) diff --git a/src/env.cpp b/src/env.cpp new file mode 100644 index 00000000..641eb4a7 --- /dev/null +++ b/src/env.cpp @@ -0,0 +1,479 @@ +#include "env.h" +#include "envmetrics.h" +#include "envmodule.h" +#include "envsecurity.h" +#include "envshortcut.h" +#include "envwindows.h" +#include +#include + +namespace env +{ + +using namespace MOBase; + +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + +Environment::Environment() + : m_windows(new WindowsInfo), m_metrics(new Metrics) +{ + m_modules = getLoadedModules(); + m_security = getSecurityProducts(); +} + +// anchor +Environment::~Environment() = default; + +const std::vector& Environment::loadedModules() const +{ + return m_modules; +} + +const WindowsInfo& Environment::windowsInfo() const +{ + return *m_windows; +} + +const std::vector& Environment::securityProducts() const +{ + return m_security; +} + +const Metrics& Environment::metrics() const +{ + return *m_metrics; +} + +void Environment::dump() const +{ + log::debug("windows: {}", m_windows->toString()); + + if (m_windows->compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : m_security) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : m_modules) { + log::debug(" . {}", m.toString()); + } + + log::debug("displays:"); + for (const auto& d : m_metrics->displays()) { + log::debug(" . {}", d.toString()); + } +} + + +struct Process +{ + std::wstring filename; + DWORD pid; + + Process(std::wstring f, DWORD id) + : filename(std::move(f)), pid(id) + { + } +}; + + +// returns the filename of the given process or the current one +// +std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +{ + // double the buffer size 10 times + const int MaxTries = 10; + + DWORD bufferSize = MAX_PATH; + + for (int tries=0; tries(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + DWORD writtenSize = 0; + + if (process == INVALID_HANDLE_VALUE) { + // query this process + writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); + } else { + // query another process + writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); + } + + if (writtenSize == 0) { + // hard failure + const auto e = GetLastError(); + std::wcerr << formatSystemMessage(e) << L"\n"; + break; + } else if (writtenSize >= bufferSize) { + // buffer is too small, try again + bufferSize *= 2; + } else { + // if GetModuleFileName() works, `writtenSize` does not include the null + // terminator + const std::wstring s(buffer.get(), writtenSize); + const std::filesystem::path path(s); + + return path.filename().native(); + } + } + + // something failed or the path is way too long to make sense + + std::wstring what; + if (process == INVALID_HANDLE_VALUE) { + what = L"the current process"; + } else { + what = L"pid " + std::to_wstring(reinterpret_cast(process)); + } + + std::wcerr << L"failed to get filename for " << what << L"\n"; + return {}; +} + +std::vector runningProcessesIds() +{ + // double the buffer size 10 times + const int MaxTries = 10; + + // initial size of 300 processes, unlikely to be more than that + std::size_t size = 300; + + for (int tries=0; tries(size); + std::fill(ids.get(), ids.get() + size, 0); + + DWORD bytesGiven = static_cast(size * sizeof(ids[0])); + DWORD bytesWritten = 0; + + if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) + { + const auto e = GetLastError(); + + std::wcerr + << L"failed to enumerate processes, " + << formatSystemMessage(e) << L"\n"; + + return {}; + } + + if (bytesWritten == bytesGiven) { + // no way to distinguish between an exact fit and not enough space, + // just try again + size *= 2; + continue; + } + + const auto count = bytesWritten / sizeof(ids[0]); + return std::vector(ids.get(), ids.get() + count); + } + + std::cerr << L"too many processes to enumerate"; + return {}; +} + +std::vector runningProcesses() +{ + const auto pids = runningProcessesIds(); + std::vector v; + + for (const auto& pid : pids) { + if (pid == 0) { + // the idle process has pid 0 and seems to be picked up by EnumProcesses() + continue; + } + + HandlePtr h(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + + if (e != ERROR_ACCESS_DENIED) { + // don't log access denied, will happen a lot for system processes, even + // when elevated + std::wcerr + << L"failed to open process " << pid << L", " + << formatSystemMessage(e) << L"\n"; + } + + continue; + } + + auto filename = processFilename(h.get()); + if (!filename.empty()) { + v.emplace_back(std::move(filename), pid); + } + } + + return v; +} + +DWORD findOtherPid() +{ + const std::wstring defaultName = L"ModOrganizer.exe"; + + std::wclog << L"looking for the other process...\n"; + + // used to skip the current process below + const auto thisPid = GetCurrentProcessId(); + std::wclog << L"this process id is " << thisPid << L"\n"; + + // getting the filename for this process, assumes the other process has the + // smae one + auto filename = processFilename(); + if (filename.empty()) { + std::wcerr + << L"can't get current process filename, defaulting to " + << defaultName << L"\n"; + + filename = defaultName; + } else { + std::wclog << L"this process filename is " << filename << L"\n"; + } + + // getting all running processes + const auto processes = runningProcesses(); + std::wclog << L"there are " << processes.size() << L" processes running\n"; + + // going through processes, trying to find one with the same name and a + // different pid than this process has + for (const auto& p : processes) { + if (p.filename == filename) { + if (p.pid != thisPid) { + return p.pid; + } + } + } + + std::wclog + << L"no process with this filename\n" + << L"MO may not be running, or it may be running as administrator\n" + << L"you can try running this again as administrator\n"; + + return 0; +} + +std::wstring tempDir() +{ + const DWORD bufferSize = MAX_PATH + 1; + wchar_t buffer[bufferSize + 1] = {}; + + const auto written = GetTempPathW(bufferSize, buffer); + if (written == 0) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + // `written` does not include the null terminator + return std::wstring(buffer, buffer + written); +} + +HandlePtr tempFile(const std::wstring dir) +{ + // maximum tries of incrementing the counter + const int MaxTries = 100; + + // UTC time and date will be in the filename + const auto now = std::time(0); + const auto tm = std::gmtime(&now); + + // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where + // i can go until MaxTries + std::wostringstream oss; + oss + << L"ModOrganizer-" + << std::setw(4) << (1900 + tm->tm_year) + << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) + << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" + << std::setw(2) << std::setfill(L'0') << tm->tm_hour + << std::setw(2) << std::setfill(L'0') << tm->tm_min + << std::setw(2) << std::setfill(L'0') << tm->tm_sec; + + const std::wstring prefix = oss.str(); + const std::wstring ext = L".dmp"; + + // first path to try, without counter in it + std::wstring path = dir + L"\\" + prefix + ext; + + for (int i=0; i; + + +struct LibraryFreer +{ + using pointer = HINSTANCE; + + void operator()(HINSTANCE h) + { + if (h != 0) { + ::FreeLibrary(h); + } + } +}; + +struct COMReleaser +{ + void operator()(IUnknown* p) + { + if (p) { + p->Release(); + } + } +}; + + +template +using COMPtr = std::unique_ptr; + + +class Console +{ +public: + Console(); + ~Console(); + +private: + bool m_hasConsole; + FILE* m_in; + FILE* m_out; + FILE* m_err; +}; + + +// represents the process's environment +// +class Environment +{ +public: + Environment(); + ~Environment(); + + // list of loaded modules in the current process + // + const std::vector& loadedModules() const; + + // information about the operating system + // + const WindowsInfo& windowsInfo() const; + + // information about the installed security products + // + const std::vector& securityProducts() const; + + // information about displays + // + const Metrics& metrics() const; + + // logs the environment + // + void dump() const; + +private: + std::vector m_modules; + std::unique_ptr m_windows; + std::vector m_security; + std::unique_ptr m_metrics; +}; + + +enum class CoreDumpTypes +{ + Mini = 1, + Data, + Full +}; + +// creates a minidump file for the given process +// +bool coredump(CoreDumpTypes type); + +// finds another process with the same name as this one and creates a minidump +// file for it +// +bool coredumpOther(CoreDumpTypes type); + +} // namespace env diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp new file mode 100644 index 00000000..a6988909 --- /dev/null +++ b/src/envmetrics.cpp @@ -0,0 +1,224 @@ +#include "envmetrics.h" +#include "env.h" +#include +#include +#include +#include + +namespace env +{ + +using namespace MOBase; + +class DisplayEnumerator +{ +public: + DisplayEnumerator() + : m_GetDpiForMonitor(nullptr) + { + m_shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (m_shcore) { + // windows 8.1+ only + m_GetDpiForMonitor = reinterpret_cast( + GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); + } + + // gets all monitors and the device they're running on + getDisplayDevices(); + } + + std::vector&& displays() && + { + return std::move(m_displays); + } + + const std::vector& displays() const & + { + return m_displays; + } + +private: + using GetDpiForMonitorFunction = + HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + std::unique_ptr m_shcore; + GetDpiForMonitorFunction* m_GetDpiForMonitor; + std::vector m_displays; + + void getDisplayDevices() + { + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.push_back(createDisplay(device)); + } + } + + Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) + { + Metrics::Display d; + + d.adapter = QString::fromWCharArray(device.DeviceString); + d.monitor = QString::fromWCharArray(device.DeviceName); + d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); + + getDisplaySettings(device.DeviceName, d); + getDpi(d); + + return d; + } + + void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", d.monitor); + return; + } + + // all these fields should be available + + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + d.refreshRate = dm.dmDisplayFrequency; + } + + if (dm.dmFields & DM_PELSWIDTH) { + d.resX = dm.dmPelsWidth; + } + + if (dm.dmFields & DM_PELSHEIGHT) { + d.resY = dm.dmPelsHeight; + } + } + + void getDpi(Metrics::Display& d) + { + if (!m_GetDpiForMonitor) { + // this happens on windows 7, get the desktop dpi instead + getDesktopDpi(d); + return; + } + + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(d.monitor); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", d.monitor); + return; + } + + UINT dpiX=0, dpiY=0; + const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + d.monitor, formatSystemMessageQ(r)); + + return; + } + + // dpiX and dpiY are always identical, as per the documentation + d.dpi = dpiX; + } + + void getDesktopDpi(Metrics::Display& d) + { + // desktop dc + HDC dc = GetDC(0); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + return; + } + + d.dpi = GetDeviceCaps(dc, LOGPIXELSX); + + ReleaseDC(0, dc); + } + + HMONITOR findMonitor(const QString& name) + { + // passed to the enumeration callback + struct Data + { + DisplayEnumerator* self; + QString name; + HMONITOR hm; + }; + + Data data = {this, name, 0}; + + // for each monitor + EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessageQ(e)); + + // error for this monitor, but continue + return TRUE; + } + + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } + + // not found, continue to the next monitor + return TRUE; + }, reinterpret_cast(&data)); + + return data.hm; + } +}; + + +Metrics::Metrics() +{ + m_displays = DisplayEnumerator().displays(); +} + +const std::vector& Metrics::displays() const +{ + return m_displays; +} + +QString Metrics::Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(resX) + .arg(resY) + .arg(refreshRate) + .arg(dpi) + .arg(adapter) + .arg(primary ? " (primary)" : ""); +} + +} // namespace diff --git a/src/envmetrics.h b/src/envmetrics.h new file mode 100644 index 00000000..62fc8c49 --- /dev/null +++ b/src/envmetrics.h @@ -0,0 +1,28 @@ +#include +#include + +namespace env +{ + +class Metrics +{ +public: + struct Display + { + int resX=0, resY=0, dpi=0; + bool primary=false; + int refreshRate = 0; + QString monitor, adapter; + + QString toString() const; + }; + + Metrics(); + + const std::vector& displays() const; + +private: + std::vector m_displays; +}; + +} // namespace diff --git a/src/envmodule.cpp b/src/envmodule.cpp new file mode 100644 index 00000000..1717da15 --- /dev/null +++ b/src/envmodule.cpp @@ -0,0 +1,390 @@ +#include "envmodule.h" +#include "env.h" +#include + +namespace env +{ + +using namespace MOBase; + +Module::Module(QString path, std::size_t fileSize) + : m_path(std::move(path)), m_fileSize(fileSize) +{ + const auto fi = getFileInfo(); + + m_version = getVersion(fi.ffi); + m_timestamp = getTimestamp(fi.ffi); + m_versionString = fi.fileDescription; + m_md5 = getMD5(); +} + +const QString& Module::path() const +{ + return m_path; +} + +QString Module::displayPath() const +{ + return QDir::fromNativeSeparators(m_path.toLower()); +} + +std::size_t Module::fileSize() const +{ + return m_fileSize; +} + +const QString& Module::version() const +{ + return m_version; +} + +const QString& Module::versionString() const +{ + return m_versionString; +} + +const QDateTime& Module::timestamp() const +{ + return m_timestamp; +} + +const QString& Module::md5() const +{ + return m_md5; +} + +QString Module::timestampString() const +{ + if (!m_timestamp.isValid()) { + return "(no timestamp)"; + } + + return m_timestamp.toString(Qt::DateFormat::ISODate); +} + +QString Module::toString() const +{ + QStringList sl; + + // file size + sl.push_back(displayPath()); + sl.push_back(QString("%1 B").arg(m_fileSize)); + + // version + if (m_version.isEmpty() && m_versionString.isEmpty()) { + sl.push_back("(no version)"); + } else { + if (!m_version.isEmpty()) { + sl.push_back(m_version); + } + + if (!m_versionString.isEmpty() && m_versionString != m_version) { + sl.push_back(versionString()); + } + } + + // timestamp + if (m_timestamp.isValid()) { + sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); + } else { + sl.push_back("(no timestamp)"); + } + + // md5 + if (!m_md5.isEmpty()) { + sl.push_back(m_md5); + } + + return sl.join(", "); +} + +Module::FileInfo Module::getFileInfo() const +{ + const auto wspath = m_path.toStdWString(); + + // getting version info size + DWORD dummy = 0; + const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); + + if (size == 0) { + const auto e = GetLastError(); + + if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { + // not an error, no version information built into that module + return {}; + } + + qCritical().nospace().noquote() + << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + + // getting version info + auto buffer = std::make_unique(size); + + if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "GetFileVersionInfoW() failed on '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + + // the version info has two major parts: a fixed version and a localizable + // set of strings + + FileInfo fi; + fi.ffi = getFixedFileInfo(buffer.get()); + fi.fileDescription = getFileDescription(buffer.get()); + + return fi; +} + +VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const +{ + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // the fixed version info is in the root + const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no fixed file info + return {}; + } + + const auto* fi = static_cast(valuePointer); + + // signature is always 0xfeef04bd + if (fi->dwSignature != 0xfeef04bd) { + qCritical().nospace().noquote() + << "bad file info signature 0x" << hex << fi->dwSignature << " for " + << "'" << m_path << "'"; + + return {}; + } + + return *fi; +} + +QString Module::getFileDescription(std::byte* buffer) const +{ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + }; + + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // getting list of available languages + auto ret = VerQueryValueW( + buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + qCritical().nospace().noquote() + << "VerQueryValueW() for translations failed on '" << m_path << "'"; + + return {}; + } + + // number of languages + const auto count = valueSize / sizeof(LANGANDCODEPAGE); + if (count == 0) { + return {}; + } + + // using the first language in the list to get FileVersion + const auto* lcp = static_cast(valuePointer); + + const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") + .arg(lcp->wLanguage, 4, 16, QChar('0')) + .arg(lcp->wCodePage, 4, 16, QChar('0')); + + ret = VerQueryValueW( + buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no file version + return {}; + } + + // valueSize includes the null terminator + return QString::fromWCharArray( + static_cast(valuePointer), valueSize - 1); +} + +QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const +{ + if (fi.dwSignature == 0) { + return {}; + } + + const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; + const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; + const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; + const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; + + if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { + return {}; + } + + return QString("%1.%2.%3.%4") + .arg(major).arg(minor).arg(maintenance).arg(build); +} + +QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +{ + FILETIME ft = {}; + + if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { + // if the file info is invalid or doesn't have a date, use the creation + // time on the file + + // opening the file + HandlePtr h(CreateFileW( + m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); + + if (h.get() == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "can't open file '" << m_path << "' for timestamp, " + << formatSystemMessageQ(e); + + return {}; + } + + // getting the file time + if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { + const auto e = GetLastError(); + qCritical().nospace().noquote() + << "can't get file time for '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + } else { + // use the time from the file info + ft.dwHighDateTime = fi.dwFileDateMS; + ft.dwLowDateTime = fi.dwFileDateLS; + } + + + // converting to SYSTEMTIME + SYSTEMTIME utc = {}; + + if (!FileTimeToSystemTime(&ft, &utc)) { + qCritical().nospace().noquote() + << "FileTimeToSystemTime() failed on timestamp " + << "high=0x" << hex << ft.dwHighDateTime << " " + << "low=0x" << hex << ft.dwLowDateTime << " for " + << "'" << m_path << "'"; + + return {}; + } + + return QDateTime( + QDate(utc.wYear, utc.wMonth, utc.wDay), + QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); +} + +QString Module::getMD5() const +{ + if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { + // don't calculate md5 for system files, it's not really relevant and + // it takes a while + return {}; + } + + // opening the file + QFile f(m_path); + + if (!f.open(QFile::ReadOnly)) { + qCritical().nospace().noquote() + << "failed to open file '" << m_path << "' for md5"; + + return {}; + } + + // hashing + QCryptographicHash hash(QCryptographicHash::Md5); + if (!hash.addData(&f)) { + qCritical().nospace().noquote() + << "failed to calculate md5 for '" << m_path << "'"; + + return {}; + } + + return hash.result().toHex(); +} + + +std::vector getLoadedModules() +{ + HandlePtr snapshot(CreateToolhelp32Snapshot( + TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "CreateToolhelp32Snapshot() failed, " + << formatSystemMessageQ(e); + + return {}; + } + + MODULEENTRY32 me = {}; + me.dwSize = sizeof(me); + + // first module, this shouldn't fail because there's at least the executable + if (!Module32First(snapshot.get(), &me)) + { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "Module32First() failed, " << formatSystemMessageQ(e); + + return {}; + } + + std::vector v; + + for (;;) + { + const auto path = QString::fromWCharArray(me.szExePath); + if (!path.isEmpty()) { + v.push_back(Module(path, me.modBaseSize)); + } + + // next module + if (!Module32Next(snapshot.get(), &me)) { + const auto e = GetLastError(); + + // no more modules is not an error + if (e != ERROR_NO_MORE_FILES) { + qCritical().nospace().noquote() + << "Module32Next() failed, " << formatSystemMessageQ(e); + } + + break; + } + } + + // sorting by display name + std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { + return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); + }); + + return v; +} + +} // namespace diff --git a/src/envmodule.h b/src/envmodule.h new file mode 100644 index 00000000..ea1156bd --- /dev/null +++ b/src/envmodule.h @@ -0,0 +1,98 @@ +#include +#include + +namespace env +{ + +// represents one module +// +class Module +{ +public: + explicit Module(QString path, std::size_t fileSize); + + // returns the module's path + // + const QString& path() const; + + // returns the module's path in lowercase and using forward slashes + // + QString displayPath() const; + + // returns the size in bytes, may be 0 + // + std::size_t fileSize() const; + + // returns the x.x.x.x version embedded from the version info, may be empty + // + const QString& version() const; + + // returns the FileVersion entry from the resource file, returns + // "(no version)" if not available + // + const QString& versionString() const; + + // returns the build date from the version info, or the creation time of the + // file on the filesystem, may be empty + // + const QDateTime& timestamp() const; + + // returns the md5 of the file, may be empty for system files + // + const QString& md5() const; + + // converts timestamp() to a string for display, returns "(no timestamp)" if + // not available + // + QString timestampString() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + // contains the information from the version resource + // + struct FileInfo + { + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; + + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + QString m_md5; + + // returns information from the version resource + // + FileInfo getFileInfo() const; + + // uses VS_FIXEDFILEINFO to build the version string + // + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + + // uses the file date from VS_FIXEDFILEINFO if available, or gets the + // creation date on the file + // + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + + // returns the md5 hash unless the path contains "\windows\" + // + QString getMD5() const; + + // gets VS_FIXEDFILEINFO from the file version info buffer + // + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + + // gets FileVersion from the file version info buffer + // + QString getFileDescription(std::byte* buffer) const; +}; + + +std::vector getLoadedModules(); + +} // namespace env diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp new file mode 100644 index 00000000..559ce4ad --- /dev/null +++ b/src/envsecurity.cpp @@ -0,0 +1,418 @@ +#include "envsecurity.h" +#include "env.h" +#include + +#include +#include +#include +#include +#pragma comment(lib, "Wbemuuid.lib") + +namespace env +{ + +using namespace MOBase; + +class WMI +{ +public: + class failed {}; + + WMI(const std::string& ns) + { + try + { + createLocator(); + createService(ns); + setSecurity(); + } + catch(failed&) + { + } + } + + template + void query(const std::string& q, F&& f) + { + if (!m_locator || !m_service) { + return; + } + + auto enumerator = getEnumerator(q); + if (!enumerator) { + return; + } + + for (;;) + { + COMPtr object; + + { + IWbemClassObject* rawObject = nullptr; + ULONG count = 0; + auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); + + if (count == 0 || !rawObject) { + break; + } + + if (FAILED(ret)) { + qCritical() + << "enumerator->next() failed, " << formatSystemMessageQ(ret); + break; + } + + object.reset(rawObject); + } + + f(object.get()); + } + } + +private: + COMPtr m_locator; + COMPtr m_service; + + void createLocator() + { + void* rawLocator = nullptr; + + const auto ret = CoCreateInstance( + CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, &rawLocator); + + if (FAILED(ret) || !rawLocator) { + qCritical() + << "CoCreateInstance for WbemLocator failed, " + << formatSystemMessageQ(ret); + + throw failed(); + } + + m_locator.reset(static_cast(rawLocator)); + } + + void createService(const std::string& ns) + { + IWbemServices* rawService = nullptr; + + const auto res = m_locator->ConnectServer( + _bstr_t(ns.c_str()), + nullptr, nullptr, nullptr, 0, nullptr, nullptr, + &rawService); + + if (FAILED(res) || !rawService) { + qCritical() + << "locator->ConnectServer() failed for namespace " + << "'" << QString::fromStdString(ns) << "', " + << formatSystemMessageQ(res); + + throw failed(); + } + + m_service.reset(rawService); + } + + void setSecurity() + { + auto ret = CoSetProxyBlanket( + m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); + + if (FAILED(ret)) + { + qCritical() + << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); + + throw failed(); + } + } + + COMPtr getEnumerator( + const std::string& query) + { + IEnumWbemClassObject* rawEnumerator = NULL; + + auto ret = m_service->ExecQuery( + bstr_t("WQL"), + bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &rawEnumerator); + + if (FAILED(ret) || !rawEnumerator) + { + qCritical() + << "query '" << QString::fromStdString(query) << "' failed, " + << formatSystemMessageQ(ret); + + return {}; + } + + return COMPtr(rawEnumerator); + } +}; + + +SecurityProduct::SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate) : + m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), + m_active(active), m_upToDate(upToDate) +{ +} + +const QString& SecurityProduct::name() const +{ + return m_name; +} + +int SecurityProduct::provider() const +{ + return m_provider; +} + +bool SecurityProduct::active() const +{ + return m_active; +} + +bool SecurityProduct::upToDate() const +{ + return m_upToDate; +} + +QString SecurityProduct::toString() const +{ + QString s; + + s += m_name + " (" + providerToString() + ")"; + + if (!m_active) { + s += ", inactive"; + } + + if (!m_upToDate) { + s += ", definitions outdated"; + } + + if (!m_guid.isNull()) { + s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); + } + + return s; +} + +QString SecurityProduct::providerToString() const +{ + QStringList ps; + + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { + ps.push_back("firewall"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + ps.push_back("autoupdate"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + ps.push_back("antivirus"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + ps.push_back("antispyware"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + ps.push_back("settings"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + ps.push_back("uac"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { + ps.push_back("service"); + } + + if (ps.empty()) { + return "doesn't provider anything"; + } + + return ps.join("|"); +} + + +std::vector getSecurityProductsFromWMI() +{ + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map map; + + auto handleProduct = [&](auto* o) { + VARIANT prop; + + // display name + auto ret = o->Get(L"displayName", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get displayName, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "displayName is a " << prop.vt << ", not a bstr"; + return; + } + + const std::wstring name = prop.bstrVal; + VariantClear(&prop); + + // product state + ret = o->Get(L"productState", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get productState, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_UI4 && prop.vt != VT_I4) { + qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; + return; + } + + DWORD state = 0; + if (prop.vt == VT_I4) { + state = prop.lVal; + } else { + state = prop.ulVal; + } + + VariantClear(&prop); + + // guid + ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get instanceGuid, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; + return; + } + + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); + + const auto provider = static_cast((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; + + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); + + map.insert({ + guid, + {guid, QString::fromStdWString(name), provider, active, upToDate}}); + }; + + { + WMI wmi("root\\SecurityCenter2"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + { + WMI wmi("root\\SecurityCenter"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + std::vector v; + + for (auto&& p : map) { + v.push_back(p.second); + } + + return v; +} + +std::optional getWindowsFirewall() +{ + HRESULT hr = 0; + + COMPtr policy; + + { + void* rawPolicy = nullptr; + + hr = CoCreateInstance( + __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(INetFwPolicy2), &rawPolicy); + + if (FAILED(hr) || !rawPolicy) { + qCritical() + << "CoCreateInstance for NetFwPolicy2 failed, " + << formatSystemMessageQ(hr); + + return {}; + } + + policy.reset(static_cast(rawPolicy)); + } + + VARIANT_BOOL enabledVariant; + + if (policy) { + hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); + if (FAILED(hr)) + { + qCritical() + << "get_FirewallEnabled failed, " + << formatSystemMessageQ(hr); + + return {}; + } + } + + const auto enabled = (enabledVariant != VARIANT_FALSE); + if (!enabled) { + return {}; + } + + return SecurityProduct( + {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); +} + + +std::vector getSecurityProducts() +{ + std::vector v; + + { + auto fromWMI = getSecurityProductsFromWMI(); + v.insert( + v.end(), + std::make_move_iterator(fromWMI.begin()), + std::make_move_iterator(fromWMI.end())); + } + + if (auto p=getWindowsFirewall()) { + v.push_back(std::move(*p)); + } + + return v; +} + +} // namespace diff --git a/src/envsecurity.h b/src/envsecurity.h new file mode 100644 index 00000000..200cb531 --- /dev/null +++ b/src/envsecurity.h @@ -0,0 +1,49 @@ +#include +#include + +namespace env +{ + +// represents a security product, such as an antivirus or a firewall +// +class SecurityProduct +{ +public: + SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate); + + // display name of the product + // + const QString& name() const; + + // a bunch of _WSC_SECURITY_PROVIDER flags + // + int provider() const; + + // whether the product is active + // + bool active() const; + + // whether its definitions are up-to-date + // + bool upToDate() const; + + // string representation of the above + // + QString toString() const; + +private: + QUuid m_guid; + QString m_name; + int m_provider; + bool m_active; + bool m_upToDate; + + QString providerToString() const; +}; + + +std::vector getSecurityProducts(); + +} // namespace env diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp new file mode 100644 index 00000000..30ef4633 --- /dev/null +++ b/src/envshortcut.cpp @@ -0,0 +1,376 @@ +#include "envshortcut.h" +#include "env.h" +#include "executableslist.h" +#include "instancemanager.h" +#include + +namespace env +{ + +using namespace MOBase; + +class ShellLinkException +{ +public: + ShellLinkException(QString s) + : m_what(std::move(s)) + { + } + + const QString& what() const + { + return m_what; + } + +private: + QString m_what; +}; + +// just a wrapper around IShellLink operations that throws ShellLinkException +// on errors +// +class ShellLinkWrapper +{ +public: + ShellLinkWrapper() + { + m_link = createShellLink(); + m_file = createPersistFile(); + } + + void setPath(const QString& s) + { + if (s.isEmpty()) { + throw ShellLinkException("path cannot be empty"); + } + + const auto r = m_link->SetPath(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set target path '%1'").arg(s)); + } + + void setArguments(const QString& s) + { + const auto r = m_link->SetArguments(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); + } + + void setDescription(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetDescription(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set description '%1'").arg(s)); + } + + void setIcon(const QString& file, int i) + { + if (file.isEmpty()) { + return; + } + + const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); + throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); + } + + void setWorkingDirectory(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); + } + + void save(const QString& path) + { + const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); + throwOnFail(r, QString("failed to save link '%1'").arg(path)); + } + +private: + COMPtr m_link; + COMPtr m_file; + + void throwOnFail(HRESULT r, const QString& s) + { + if (FAILED(r)) { + throw ShellLinkException(QString("%1, %2") + .arg(s) + .arg(formatSystemMessageQ(r))); + } + } + + COMPtr createShellLink() + { + void* link = nullptr; + + const auto r = CoCreateInstance( + CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, &link); + + throwOnFail(r, "failed to create IShellLink instance"); + + if (!link) { + throw ShellLinkException("creating IShellLink worked, pointer is null"); + } + + return COMPtr(static_cast(link)); + } + + COMPtr createPersistFile() + { + void* file = nullptr; + + const auto r = m_link->QueryInterface(IID_IPersistFile, &file); + throwOnFail(r, "failed to get IPersistFile interface"); + + if (!file) { + throw ShellLinkException("querying IPersistFile worked, pointer is null"); + } + + return COMPtr(static_cast(file)); + } +}; + + +Shortcut::Shortcut() + : m_iconIndex(0) +{ +} + +Shortcut::Shortcut(const Executable& exe) + : Shortcut() +{ + m_name = exe.title(); + m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); + + m_arguments = QString("\"moshortcut://%1:%2\"") + .arg(InstanceManager::instance().currentInstance()) + .arg(exe.title()); + + m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); + + if (exe.usesOwnIcon()) { + m_icon = exe.binaryInfo().absoluteFilePath(); + } + + m_workingDirectory = qApp->applicationDirPath(); +} + +Shortcut& Shortcut::name(const QString& s) +{ + m_name = s; + return *this; +} + +Shortcut& Shortcut::target(const QString& s) +{ + m_target = s; + return *this; +} + +Shortcut& Shortcut::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Shortcut& Shortcut::description(const QString& s) +{ + m_description = s; + return *this; +} + +Shortcut& Shortcut::icon(const QString& s, int index) +{ + m_icon = s; + m_iconIndex = index; + return *this; +} + +Shortcut& Shortcut::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +bool Shortcut::exists(Locations loc) const +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + return QFileInfo(path).exists(); +} + +bool Shortcut::toggle(Locations loc) +{ + if (exists(loc)) { + return remove(loc); + } else { + return add(loc); + } +} + +bool Shortcut::add(Locations loc) +{ + debug() + << "adding shortcut to " << toString(loc) << ":\n" + << " . name: '" << m_name << "'\n" + << " . target: '" << m_target << "'\n" + << " . arguments: '" << m_arguments << "'\n" + << " . description: '" << m_description << "'\n" + << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" + << " . working directory: '" << m_workingDirectory << "'"; + + if (m_target.isEmpty()) { + critical() << "target is empty"; + return false; + } + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + debug() << "shorcut file will be saved at '" << path << "'"; + + try + { + ShellLinkWrapper link; + + link.setPath(m_target); + link.setArguments(m_arguments); + link.setDescription(m_description); + link.setIcon(m_icon, m_iconIndex); + link.setWorkingDirectory(m_workingDirectory); + + link.save(path); + + return true; + } + catch(ShellLinkException& e) + { + critical() << e.what() << "\nshortcut file was not saved"; + } + + return false; +} + +bool Shortcut::remove(Locations loc) +{ + debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + debug() << "path to shortcut file is '" << path << "'"; + + if (!QFile::exists(path)) { + critical() << "can't remove '" << path << "', file not found"; + return false; + } + + if (!MOBase::shellDelete({path})) { + const auto e = ::GetLastError(); + + critical() + << "failed to remove '" << path << "', " + << formatSystemMessageQ(e); + + return false; + } + + return true; +} + +QString Shortcut::shortcutPath(Locations loc) const +{ + const auto dir = shortcutDirectory(loc); + if (dir.isEmpty()) { + return {}; + } + + const auto file = shortcutFilename(); + if (file.isEmpty()) { + return {}; + } + + return dir + QDir::separator() + file; +} + +QString Shortcut::shortcutDirectory(Locations loc) const +{ + QString dir; + + try + { + switch (loc) + { + case Desktop: + dir = MOBase::getDesktopDirectory(); + break; + + case StartMenu: + dir = MOBase::getStartMenuDirectory(); + break; + + case None: + default: + critical() << "bad location " << loc; + break; + } + } + catch(std::exception&) + { + } + + return QDir::toNativeSeparators(dir); +} + +QString Shortcut::shortcutFilename() const +{ + if (m_name.isEmpty()) { + critical() << "name is empty"; + return {}; + } + + return m_name + ".lnk"; +} + +QDebug Shortcut::debug() const +{ + return qDebug().noquote().nospace() << "system shortcut: "; +} + +QDebug Shortcut::critical() const +{ + return qCritical().noquote().nospace() << "system shortcut: "; +} + + +QString toString(Shortcut::Locations loc) +{ + switch (loc) + { + case Shortcut::None: + return "none"; + + case Shortcut::Desktop: + return "desktop"; + + case Shortcut::StartMenu: + return "start menu"; + + default: + return QString("? (%1)").arg(static_cast(loc)); + } +} + +} // namespace diff --git a/src/envshortcut.h b/src/envshortcut.h new file mode 100644 index 00000000..904b3ab7 --- /dev/null +++ b/src/envshortcut.h @@ -0,0 +1,114 @@ +#include + +class Executable; + +namespace env +{ + +// an application shortcut that can be either on the desktop or the start menu +// +class Shortcut +{ +public: + // location of a shortcut + // + enum Locations + { + None = 0, + + // on the desktop + Desktop, + + // in the start menu + StartMenu + }; + + + // empty shortcut + // + Shortcut(); + + // shortcut from an executable + // + explicit Shortcut(const Executable& exe); + + // sets the name of the shortcut, shown on icons and start menu entries + // + Shortcut& name(const QString& s); + + // the program to start + // + Shortcut& target(const QString& s); + + // arguments to pass + // + Shortcut& arguments(const QString& s); + + // shows in the status bar of explorer, for example + // + Shortcut& description(const QString& s); + + // path to a binary that contains the icon and its index + // + Shortcut& icon(const QString& s, int index=0); + + // "start in" option for this shortcut + // + Shortcut& workingDirectory(const QString& s); + + + // returns whether this shortcut already exists at the given location; this + // does not check whether the shortcut parameters are different, it merely if + // the .lnk file exists + // + bool exists(Locations loc) const; + + // calls remove() if exists(), or add() + // + bool toggle(Locations loc); + + // adds the shortcut to the given location + // + bool add(Locations loc); + + // removes the shortcut from the given location + // + bool remove(Locations loc); + +private: + QString m_name; + QString m_target; + QString m_arguments; + QString m_description; + QString m_icon; + int m_iconIndex; + QString m_workingDirectory; + + // returns a qCritical() logger with a prefix already logged + // + QDebug critical() const; + + // returns a qDebug() logger with a prefix already logged + // + QDebug debug() const; + + + // returns the path where the shortcut file should be saved + // + QString shortcutPath(Locations loc) const; + + // returns the directory where the shortcut file should be saved + // + QString shortcutDirectory(Locations loc) const; + + // returns the filename of the shortcut file that should be used when saving + // + QString shortcutFilename() const; +}; + + +// returns a string representation of the given location +// +QString toString(Shortcut::Locations loc); + +} // namespace diff --git a/src/envwindows.cpp b/src/envwindows.cpp new file mode 100644 index 00000000..718cf2ce --- /dev/null +++ b/src/envwindows.cpp @@ -0,0 +1,236 @@ +#include "envwindows.h" +#include "env.h" +#include + +namespace env +{ + +using namespace MOBase; + +WindowsInfo::WindowsInfo() +{ + // loading ntdll.dll, the functions will be found with GetProcAddress() + std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + qCritical() << "failed to load ntdll.dll while getting version"; + return; + } else { + m_reported = getReportedVersion(ntdll.get()); + m_real = getRealVersion(ntdll.get()); + } + + m_release = getRelease(); + m_elevated = getElevated(); +} + +bool WindowsInfo::compatibilityMode() const +{ + if (m_real == Version()) { + // don't know the real version, can't guess compatibility mode + return false; + } + + return (m_real != m_reported); +} + +const WindowsInfo::Version& WindowsInfo::reportedVersion() const +{ + return m_reported; +} + +const WindowsInfo::Version& WindowsInfo::realVersion() const +{ + return m_real; +} + +const WindowsInfo::Release& WindowsInfo::release() const +{ + return m_release; +} + +std::optional WindowsInfo::isElevated() const +{ + return m_elevated; +} + +QString WindowsInfo::toString() const +{ + QStringList sl; + + const QString reported = m_reported.toString(); + const QString real = m_real.toString(); + + // version + sl.push_back("version " + reported); + + // real version if different + if (compatibilityMode()) { + sl.push_back("real version " + real); + } + + // build.UBR, such as 17763.557 + if (m_release.UBR != 0) { + DWORD build = 0; + + if (compatibilityMode()) { + build = m_real.build; + } else { + build = m_reported.build; + } + + sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); + } + + // release ID + if (!m_release.ID.isEmpty()) { + sl.push_back("release " + m_release.ID); + } + + // buildlab string + if (!m_release.buildLab.isEmpty()) { + sl.push_back(m_release.buildLab); + } + + // product name + if (!m_release.productName.isEmpty()) { + sl.push_back(m_release.productName); + } + + // elevated + QString elevated = "?"; + if (m_elevated.has_value()) { + elevated = (*m_elevated ? "yes" : "no"); + } + + sl.push_back("elevated: " + elevated); + + return sl.join(", "); +} + +WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const +{ + // windows has been deprecating pretty much all the functions having to do + // with getting version information because apparently, people keep misusing + // them for feature detection + // + // there's still RtlGetVersion() though + + using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); + + auto* RtlGetVersion = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetVersion")); + + if (!RtlGetVersion) { + qCritical() << "RtlGetVersion() not found in ntdll.dll"; + return {}; + } + + OSVERSIONINFOEX vi = {}; + vi.dwOSVersionInfoSize = sizeof(vi); + + // this apparently never fails + RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); + + return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; +} + +WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const +{ + // getting the actual windows version is more difficult because all the + // functions are lying when running in compatibility mode + // + // RtlGetNtVersionNumbers() is an undocumented function that seems to work + // fine, but it might not in the future + + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (!RtlGetNtVersionNumbers) { + qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; + return {}; + } + + DWORD major=0, minor=0, build=0; + RtlGetNtVersionNumbers(&major, &minor, &build); + + // for whatever reason, the build number has 0xf0000000 set + build = 0x0fffffff & build; + + return {major, minor, build}; +} + +WindowsInfo::Release WindowsInfo::getRelease() const +{ + // there are several interesting items in the registry, but most of them + // are undocumented, not always available, and localizable + // + // most of them are used to provide as much information as possible in case + // any of the other versions fail to work + + QSettings settings( + R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", + QSettings::NativeFormat); + + Release r; + + // buildlab seems to be an internal name from the build system + r.buildLab = settings.value("BuildLabEx", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildLab", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildBranch", "").toString(); + } + } + + // localized name of windows, such as "Windows 10 Pro" + r.productName = settings.value("ProductName", "").toString(); + + // release ID, such as 1803 + r.ID = settings.value("ReleaseId", "").toString(); + + // some other build number, shown in winver.exe + r.UBR = settings.value("UBR", 0).toUInt(); + + return r; +} + +std::optional WindowsInfo::getElevated() const +{ + HandlePtr token; + + { + HANDLE rawToken = 0; + + if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "OpenProcessToken() failed: " << formatSystemMessageQ(e); + + return {}; + } + + token.reset(rawToken); + } + + TOKEN_ELEVATION e = {}; + DWORD size = sizeof(TOKEN_ELEVATION); + + if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "GetTokenInformation() failed: " << formatSystemMessageQ(e); + + return {}; + } + + return (e.TokenIsElevated != 0); +} + +} // namespace diff --git a/src/envwindows.h b/src/envwindows.h new file mode 100644 index 00000000..c23f99f4 --- /dev/null +++ b/src/envwindows.h @@ -0,0 +1,106 @@ +#include +#include + +namespace env +{ + +// a variety of information on windows +// +class WindowsInfo +{ +public: + struct Version + { + DWORD major=0, minor=0, build=0; + + QString toString() const + { + return QString("%1.%2.%3").arg(major).arg(minor).arg(build); + } + + friend bool operator==(const Version& a, const Version& b) + { + return + a.major == b.major && + a.minor == b.minor && + a.build == b.build; + } + + friend bool operator!=(const Version& a, const Version& b) + { + return !(a == b); + } + }; + + struct Release + { + // the BuildLab entry from the registry, may be empty + QString buildLab; + + // product name such as "Windows 10 Pro", may not be in English, may be + // empty + QString productName; + + // release ID such as 1809, may be mepty + QString ID; + + // some sub-build number, undocumented, may be empty + DWORD UBR; + + Release() + : UBR(0) + { + } + }; + + + WindowsInfo(); + + // tries to guess whether this process is running in compatibility mode + // + bool compatibilityMode() const; + + // returns the Windows version, may not correspond to the actual version + // if the process is running in compatibility mode + // + const Version& reportedVersion() const; + + // tries to guess the real Windows version that's running, can be empty + // + const Version& realVersion() const; + + // various information about the current release + // + const Release& release() const; + + // whether this process is running as administrator, may be empty if the + // information is not available + std::optional isElevated() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + Version m_reported, m_real; + Release m_release; + std::optional m_elevated; + + // uses RtlGetVersion() to get the version number as reported by Windows + // + Version getReportedVersion(HINSTANCE ntdll) const; + + // uses RtlGetNtVersionNumbers() to get the real version number + // + Version getRealVersion(HINSTANCE ntdll) const; + + // gets various information from the registry + // + Release getRelease() const; + + // gets whether the process is elevated + // + std::optional getElevated() const; +}; + +} // namespace diff --git a/src/main.cpp b/src/main.cpp index 09da9408..5c5ce945 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,8 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "moshortcut.h" #include "organizercore.h" +#include "env.h" +#include "envmodule.h" #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6c2ab389..87ee2c8e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -86,6 +86,7 @@ along with Mod Organizer. If not, see . #include #include "localsavegames.h" #include "listdialog.h" +#include "envshortcut.h" #include #include diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 4ee4b766..07983e12 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,35 +19,9 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" -#include "error_report.h" -#include "executableslist.h" -#include "instancemanager.h" -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#pragma comment(lib, "Wbemuuid.lib") - -using namespace MOBase; -namespace fs = std::filesystem; - -namespace MOShared { +namespace MOShared +{ bool FileExists(const std::string &filename) { @@ -269,2097 +243,4 @@ MOBase::VersionInfo createVersionInfo() } } - -namespace env -{ - -struct HandleCloser -{ - using pointer = HANDLE; - - void operator()(HANDLE h) - { - if (h != INVALID_HANDLE_VALUE) { - ::CloseHandle(h); - } - } -}; - -using HandlePtr = std::unique_ptr; - - -struct LibraryFreer -{ - using pointer = HINSTANCE; - - void operator()(HINSTANCE h) - { - if (h != 0) { - ::FreeLibrary(h); - } - } -}; - -struct COMReleaser -{ - void operator()(IUnknown* p) - { - if (p) { - p->Release(); - } - } -}; - - -template -using COMPtr = std::unique_ptr; - - -class ShellLinkException -{ -public: - ShellLinkException(QString s) - : m_what(std::move(s)) - { - } - - const QString& what() const - { - return m_what; - } - -private: - QString m_what; -}; - -// just a wrapper around IShellLink operations that throws ShellLinkException -// on errors -// -class ShellLinkWrapper -{ -public: - ShellLinkWrapper() - { - m_link = createShellLink(); - m_file = createPersistFile(); - } - - void setPath(const QString& s) - { - if (s.isEmpty()) { - throw ShellLinkException("path cannot be empty"); - } - - const auto r = m_link->SetPath(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set target path '%1'").arg(s)); - } - - void setArguments(const QString& s) - { - const auto r = m_link->SetArguments(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); - } - - void setDescription(const QString& s) - { - if (s.isEmpty()) { - return; - } - - const auto r = m_link->SetDescription(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set description '%1'").arg(s)); - } - - void setIcon(const QString& file, int i) - { - if (file.isEmpty()) { - return; - } - - const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); - throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); - } - - void setWorkingDirectory(const QString& s) - { - if (s.isEmpty()) { - return; - } - - const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); - } - - void save(const QString& path) - { - const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); - throwOnFail(r, QString("failed to save link '%1'").arg(path)); - } - -private: - COMPtr m_link; - COMPtr m_file; - - void throwOnFail(HRESULT r, const QString& s) - { - if (FAILED(r)) { - throw ShellLinkException(QString("%1, %2") - .arg(s) - .arg(formatSystemMessageQ(r))); - } - } - - COMPtr createShellLink() - { - void* link = nullptr; - - const auto r = CoCreateInstance( - CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLink, &link); - - throwOnFail(r, "failed to create IShellLink instance"); - - if (!link) { - throw ShellLinkException("creating IShellLink worked, pointer is null"); - } - - return COMPtr(static_cast(link)); - } - - COMPtr createPersistFile() - { - void* file = nullptr; - - const auto r = m_link->QueryInterface(IID_IPersistFile, &file); - throwOnFail(r, "failed to get IPersistFile interface"); - - if (!file) { - throw ShellLinkException("querying IPersistFile worked, pointer is null"); - } - - return COMPtr(static_cast(file)); - } -}; - - -Console::Console() - : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) -{ - // open a console - if (!AllocConsole()) { - // failed, ignore - } - - m_hasConsole = true; - - // redirect stdin, stdout and stderr to it - freopen_s(&m_in, "CONIN$", "r", stdin); - freopen_s(&m_out, "CONOUT$", "w", stdout); - freopen_s(&m_err, "CONOUT$", "w", stderr); -} - -Console::~Console() -{ - // close redirected handles and redirect standard stream to NUL in case - // they're used after this - - if (m_err) { - std::fclose(m_err); - freopen_s(&m_err, "NUL", "w", stderr); - } - - if (m_out) { - std::fclose(m_out); - freopen_s(&m_out, "NUL", "w", stdout); - } - - if (m_in) { - std::fclose(m_in); - freopen_s(&m_in, "NUL", "r", stdin); - } - - // close console - if (m_hasConsole) { - FreeConsole(); - } -} - - -Shortcut::Shortcut() - : m_iconIndex(0) -{ -} - -Shortcut::Shortcut(const Executable& exe) - : Shortcut() -{ - m_name = exe.title(); - m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); - - m_arguments = QString("\"moshortcut://%1:%2\"") - .arg(InstanceManager::instance().currentInstance()) - .arg(exe.title()); - - m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); - - if (exe.usesOwnIcon()) { - m_icon = exe.binaryInfo().absoluteFilePath(); - } - - m_workingDirectory = qApp->applicationDirPath(); -} - -Shortcut& Shortcut::name(const QString& s) -{ - m_name = s; - return *this; -} - -Shortcut& Shortcut::target(const QString& s) -{ - m_target = s; - return *this; -} - -Shortcut& Shortcut::arguments(const QString& s) -{ - m_arguments = s; - return *this; -} - -Shortcut& Shortcut::description(const QString& s) -{ - m_description = s; - return *this; -} - -Shortcut& Shortcut::icon(const QString& s, int index) -{ - m_icon = s; - m_iconIndex = index; - return *this; -} - -Shortcut& Shortcut::workingDirectory(const QString& s) -{ - m_workingDirectory = s; - return *this; -} - -bool Shortcut::exists(Locations loc) const -{ - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - return QFileInfo(path).exists(); -} - -bool Shortcut::toggle(Locations loc) -{ - if (exists(loc)) { - return remove(loc); - } else { - return add(loc); - } -} - -bool Shortcut::add(Locations loc) -{ - debug() - << "adding shortcut to " << toString(loc) << ":\n" - << " . name: '" << m_name << "'\n" - << " . target: '" << m_target << "'\n" - << " . arguments: '" << m_arguments << "'\n" - << " . description: '" << m_description << "'\n" - << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" - << " . working directory: '" << m_workingDirectory << "'"; - - if (m_target.isEmpty()) { - critical() << "target is empty"; - return false; - } - - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - debug() << "shorcut file will be saved at '" << path << "'"; - - try - { - ShellLinkWrapper link; - - link.setPath(m_target); - link.setArguments(m_arguments); - link.setDescription(m_description); - link.setIcon(m_icon, m_iconIndex); - link.setWorkingDirectory(m_workingDirectory); - - link.save(path); - - return true; - } - catch(ShellLinkException& e) - { - critical() << e.what() << "\nshortcut file was not saved"; - } - - return false; -} - -bool Shortcut::remove(Locations loc) -{ - debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); - - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - debug() << "path to shortcut file is '" << path << "'"; - - if (!QFile::exists(path)) { - critical() << "can't remove '" << path << "', file not found"; - return false; - } - - if (!MOBase::shellDelete({path})) { - const auto e = ::GetLastError(); - - critical() - << "failed to remove '" << path << "', " - << formatSystemMessageQ(e); - - return false; - } - - return true; -} - -QString Shortcut::shortcutPath(Locations loc) const -{ - const auto dir = shortcutDirectory(loc); - if (dir.isEmpty()) { - return {}; - } - - const auto file = shortcutFilename(); - if (file.isEmpty()) { - return {}; - } - - return dir + QDir::separator() + file; -} - -QString Shortcut::shortcutDirectory(Locations loc) const -{ - QString dir; - - try - { - switch (loc) - { - case Desktop: - dir = MOBase::getDesktopDirectory(); - break; - - case StartMenu: - dir = MOBase::getStartMenuDirectory(); - break; - - case None: - default: - critical() << "bad location " << loc; - break; - } - } - catch(std::exception&) - { - } - - return QDir::toNativeSeparators(dir); -} - -QString Shortcut::shortcutFilename() const -{ - if (m_name.isEmpty()) { - critical() << "name is empty"; - return {}; - } - - return m_name + ".lnk"; -} - -QDebug Shortcut::debug() const -{ - return qDebug().noquote().nospace() << "system shortcut: "; -} - -QDebug Shortcut::critical() const -{ - return qCritical().noquote().nospace() << "system shortcut: "; -} - - -QString toString(Shortcut::Locations loc) -{ - switch (loc) - { - case Shortcut::None: - return "none"; - - case Shortcut::Desktop: - return "desktop"; - - case Shortcut::StartMenu: - return "start menu"; - - default: - return QString("? (%1)").arg(static_cast(loc)); - } -} - - - -class WMI -{ -public: - class failed {}; - - WMI(const std::string& ns) - { - try - { - createLocator(); - createService(ns); - setSecurity(); - } - catch(failed&) - { - } - } - - template - void query(const std::string& q, F&& f) - { - if (!m_locator || !m_service) { - return; - } - - auto enumerator = getEnumerator(q); - if (!enumerator) { - return; - } - - for (;;) - { - COMPtr object; - - { - IWbemClassObject* rawObject = nullptr; - ULONG count = 0; - auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); - - if (count == 0 || !rawObject) { - break; - } - - if (FAILED(ret)) { - qCritical() - << "enumerator->next() failed, " << formatSystemMessageQ(ret); - break; - } - - object.reset(rawObject); - } - - f(object.get()); - } - } - -private: - COMPtr m_locator; - COMPtr m_service; - - void createLocator() - { - void* rawLocator = nullptr; - - const auto ret = CoCreateInstance( - CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, - IID_IWbemLocator, &rawLocator); - - if (FAILED(ret) || !rawLocator) { - qCritical() - << "CoCreateInstance for WbemLocator failed, " - << formatSystemMessageQ(ret); - - throw failed(); - } - - m_locator.reset(static_cast(rawLocator)); - } - - void createService(const std::string& ns) - { - IWbemServices* rawService = nullptr; - - const auto res = m_locator->ConnectServer( - _bstr_t(ns.c_str()), - nullptr, nullptr, nullptr, 0, nullptr, nullptr, - &rawService); - - if (FAILED(res) || !rawService) { - qCritical() - << "locator->ConnectServer() failed for namespace " - << "'" << QString::fromStdString(ns) << "', " - << formatSystemMessageQ(res); - - throw failed(); - } - - m_service.reset(rawService); - } - - void setSecurity() - { - auto ret = CoSetProxyBlanket( - m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, - RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); - - if (FAILED(ret)) - { - qCritical() - << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); - - throw failed(); - } - } - - COMPtr getEnumerator( - const std::string& query) - { - IEnumWbemClassObject* rawEnumerator = NULL; - - auto ret = m_service->ExecQuery( - bstr_t("WQL"), - bstr_t(query.c_str()), - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - NULL, - &rawEnumerator); - - if (FAILED(ret) || !rawEnumerator) - { - qCritical() - << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessageQ(ret); - - return {}; - } - - return COMPtr(rawEnumerator); - } -}; - - -class DisplayEnumerator -{ -public: - DisplayEnumerator() - : m_GetDpiForMonitor(nullptr) - { - m_shcore.reset(LoadLibraryW(L"Shcore.dll")); - - if (m_shcore) { - // windows 8.1+ only - m_GetDpiForMonitor = reinterpret_cast( - GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); - } - - // gets all monitors and the device they're running on - getDisplayDevices(); - } - - std::vector&& displays() && - { - return std::move(m_displays); - } - - const std::vector& displays() const & - { - return m_displays; - } - -private: - using GetDpiForMonitorFunction = - HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - - std::unique_ptr m_shcore; - GetDpiForMonitorFunction* m_GetDpiForMonitor; - std::vector m_displays; - - void getDisplayDevices() - { - // don't bother if it goes over 100 - for (int i=0; i<100; ++i) { - DISPLAY_DEVICEW device = {}; - device.cb = sizeof(device); - - if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { - // no more - break; - } - - // EnumDisplayDevices() seems to be returning a lot of devices that are - // not actually monitors, but those don't have the - // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set - if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { - continue; - } - - m_displays.push_back(createDisplay(device)); - } - } - - Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) - { - Metrics::Display d; - - d.adapter = QString::fromWCharArray(device.DeviceString); - d.monitor = QString::fromWCharArray(device.DeviceName); - d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); - - getDisplaySettings(device.DeviceName, d); - getDpi(d); - - return d; - } - - void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) - { - DEVMODEW dm = {}; - dm.dmSize = sizeof(dm); - - if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { - log::error("EnumDisplaySettings() failed for '{}'", d.monitor); - return; - } - - // all these fields should be available - - if (dm.dmFields & DM_DISPLAYFREQUENCY) { - d.refreshRate = dm.dmDisplayFrequency; - } - - if (dm.dmFields & DM_PELSWIDTH) { - d.resX = dm.dmPelsWidth; - } - - if (dm.dmFields & DM_PELSHEIGHT) { - d.resY = dm.dmPelsHeight; - } - } - - void getDpi(Metrics::Display& d) - { - if (!m_GetDpiForMonitor) { - // this happens on windows 7, get the desktop dpi instead - getDesktopDpi(d); - return; - } - - // there's no way to get an HMONITOR from a device name, so all monitors - // will have to be enumerated and their name checked - HMONITOR hm = findMonitor(d.monitor); - if (!hm) { - log::error("can't get dpi for monitor '{}', not found", d.monitor); - return; - } - - UINT dpiX=0, dpiY=0; - const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); - - if (FAILED(r)) { - log::error( - "GetDpiForMonitor() failed for '{}', {}", - d.monitor, formatSystemMessageQ(r)); - - return; - } - - // dpiX and dpiY are always identical, as per the documentation - d.dpi = dpiX; - } - - void getDesktopDpi(Metrics::Display& d) - { - // desktop dc - HDC dc = GetDC(0); - - if (!dc) { - const auto e = GetLastError(); - log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); - return; - } - - d.dpi = GetDeviceCaps(dc, LOGPIXELSX); - - ReleaseDC(0, dc); - } - - HMONITOR findMonitor(const QString& name) - { - // passed to the enumeration callback - struct Data - { - DisplayEnumerator* self; - QString name; - HMONITOR hm; - }; - - Data data = {this, name, 0}; - - // for each monitor - EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { - auto& data = *reinterpret_cast(lp); - - MONITORINFOEX mi = {}; - mi.cbSize = sizeof(mi); - - // monitor info will include the name - if (!GetMonitorInfoW(hm, &mi)) { - const auto e = GetLastError(); - log::error( - "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessageQ(e)); - - // error for this monitor, but continue - return TRUE; - } - - if (QString::fromWCharArray(mi.szDevice) == data.name) { - // found, stop - data.hm = hm; - return FALSE; - } - - // not found, continue to the next monitor - return TRUE; - }, reinterpret_cast(&data)); - - return data.hm; - } -}; - - -Environment::Environment() -{ - m_modules = getLoadedModules(); - m_security = getSecurityProducts(); -} - -const std::vector& Environment::loadedModules() const -{ - return m_modules; -} - -const WindowsInfo& Environment::windowsInfo() const -{ - return m_windows; -} - -const std::vector& Environment::securityProducts() const -{ - return m_security; -} - -const Metrics& Environment::metrics() const -{ - return m_metrics; -} - -void Environment::dump() const -{ - log::debug("windows: {}", windowsInfo().toString()); - - if (windowsInfo().compatibilityMode()) { - log::warn("MO seems to be running in compatibility mode"); - } - - log::debug("security products:"); - for (const auto& sp : securityProducts()) { - log::debug(" . {}", sp.toString()); - } - - log::debug("modules loaded in process:"); - for (const auto& m : loadedModules()) { - log::debug(" . {}", m.toString()); - } - - log::debug("displays:"); - for (const auto& d : m_metrics.displays()) { - log::debug(" . {}", d.toString()); - } -} - -std::vector Environment::getLoadedModules() const -{ - HandlePtr snapshot(CreateToolhelp32Snapshot( - TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); - - if (snapshot.get() == INVALID_HANDLE_VALUE) - { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "CreateToolhelp32Snapshot() failed, " - << formatSystemMessageQ(e); - - return {}; - } - - MODULEENTRY32 me = {}; - me.dwSize = sizeof(me); - - // first module, this shouldn't fail because there's at least the executable - if (!Module32First(snapshot.get(), &me)) - { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "Module32First() failed, " << formatSystemMessageQ(e); - - return {}; - } - - std::vector v; - - for (;;) - { - const auto path = QString::fromWCharArray(me.szExePath); - if (!path.isEmpty()) { - v.push_back(Module(path, me.modBaseSize)); - } - - // next module - if (!Module32Next(snapshot.get(), &me)) { - const auto e = GetLastError(); - - // no more modules is not an error - if (e != ERROR_NO_MORE_FILES) { - qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessageQ(e); - } - - break; - } - } - - // sorting by display name - std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { - return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); - }); - - return v; -} - -std::vector Environment::getSecurityProducts() const -{ - std::vector v; - - { - auto fromWMI = getSecurityProductsFromWMI(); - v.insert( - v.end(), - std::make_move_iterator(fromWMI.begin()), - std::make_move_iterator(fromWMI.end())); - } - - if (auto p=getWindowsFirewall()) { - v.push_back(std::move(*p)); - } - - return v; -} - -std::vector Environment::getSecurityProductsFromWMI() const -{ - // some products may be present in multiple queries, such as a product marked - // as both antivirus and antispyware, but they'll have the same GUID, so use - // that to avoid duplicating entries - std::map map; - - auto handleProduct = [&](auto* o) { - VARIANT prop; - - // display name - auto ret = o->Get(L"displayName", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get displayName, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_BSTR) { - qCritical() << "displayName is a " << prop.vt << ", not a bstr"; - return; - } - - const std::wstring name = prop.bstrVal; - VariantClear(&prop); - - // product state - ret = o->Get(L"productState", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get productState, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; - return; - } - - DWORD state = 0; - if (prop.vt == VT_I4) { - state = prop.lVal; - } else { - state = prop.ulVal; - } - - VariantClear(&prop); - - // guid - ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get instanceGuid, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_BSTR) { - qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; - return; - } - - const QUuid guid(QString::fromWCharArray(prop.bstrVal)); - VariantClear(&prop); - - const auto provider = static_cast((state >> 16) & 0xff); - const auto scanner = (state >> 8) & 0xff; - const auto definitions = state & 0xff; - - const bool active = ((scanner & 0x10) != 0); - const bool upToDate = (definitions == 0); - - map.insert({ - guid, - {guid, QString::fromStdWString(name), provider, active, upToDate}}); - }; - - { - WMI wmi("root\\SecurityCenter2"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); - } - - { - WMI wmi("root\\SecurityCenter"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); - } - - std::vector v; - - for (auto&& p : map) { - v.push_back(p.second); - } - - return v; -} - -std::optional Environment::getWindowsFirewall() const -{ - HRESULT hr = 0; - - COMPtr policy; - - { - void* rawPolicy = nullptr; - - hr = CoCreateInstance( - __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, - __uuidof(INetFwPolicy2), &rawPolicy); - - if (FAILED(hr) || !rawPolicy) { - qCritical() - << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessageQ(hr); - - return {}; - } - - policy.reset(static_cast(rawPolicy)); - } - - VARIANT_BOOL enabledVariant; - - if (policy) { - hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); - if (FAILED(hr)) - { - qCritical() - << "get_FirewallEnabled failed, " - << formatSystemMessageQ(hr); - - return {}; - } - } - - const auto enabled = (enabledVariant != VARIANT_FALSE); - if (!enabled) { - return {}; - } - - return SecurityProduct( - {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); -} - - -Metrics::Metrics() -{ - m_displays = DisplayEnumerator().displays(); -} - -const std::vector& Metrics::displays() const -{ - return m_displays; -} - -QString Metrics::Display::toString() const -{ - return QString("%1*%2 %3hz dpi=%4 on %5%6") - .arg(resX) - .arg(resY) - .arg(refreshRate) - .arg(dpi) - .arg(adapter) - .arg(primary ? " (primary)" : ""); -} - - -Module::Module(QString path, std::size_t fileSize) - : m_path(std::move(path)), m_fileSize(fileSize) -{ - const auto fi = getFileInfo(); - - m_version = getVersion(fi.ffi); - m_timestamp = getTimestamp(fi.ffi); - m_versionString = fi.fileDescription; - m_md5 = getMD5(); -} - -const QString& Module::path() const -{ - return m_path; -} - -QString Module::displayPath() const -{ - return QDir::fromNativeSeparators(m_path.toLower()); -} - -std::size_t Module::fileSize() const -{ - return m_fileSize; -} - -const QString& Module::version() const -{ - return m_version; -} - -const QString& Module::versionString() const -{ - return m_versionString; -} - -const QDateTime& Module::timestamp() const -{ - return m_timestamp; -} - -const QString& Module::md5() const -{ - return m_md5; -} - -QString Module::timestampString() const -{ - if (!m_timestamp.isValid()) { - return "(no timestamp)"; - } - - return m_timestamp.toString(Qt::DateFormat::ISODate); -} - -QString Module::toString() const -{ - QStringList sl; - - // file size - sl.push_back(displayPath()); - sl.push_back(QString("%1 B").arg(m_fileSize)); - - // version - if (m_version.isEmpty() && m_versionString.isEmpty()) { - sl.push_back("(no version)"); - } else { - if (!m_version.isEmpty()) { - sl.push_back(m_version); - } - - if (!m_versionString.isEmpty() && m_versionString != m_version) { - sl.push_back(versionString()); - } - } - - // timestamp - if (m_timestamp.isValid()) { - sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); - } else { - sl.push_back("(no timestamp)"); - } - - // md5 - if (!m_md5.isEmpty()) { - sl.push_back(m_md5); - } - - return sl.join(", "); -} - -Module::FileInfo Module::getFileInfo() const -{ - const auto wspath = m_path.toStdWString(); - - // getting version info size - DWORD dummy = 0; - const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); - - if (size == 0) { - const auto e = GetLastError(); - - if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { - // not an error, no version information built into that module - return {}; - } - - qCritical().nospace().noquote() - << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - - // getting version info - auto buffer = std::make_unique(size); - - if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "GetFileVersionInfoW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - - // the version info has two major parts: a fixed version and a localizable - // set of strings - - FileInfo fi; - fi.ffi = getFixedFileInfo(buffer.get()); - fi.fileDescription = getFileDescription(buffer.get()); - - return fi; -} - -VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const -{ - void* valuePointer = nullptr; - unsigned int valueSize = 0; - - // the fixed version info is in the root - const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - // not an error, no fixed file info - return {}; - } - - const auto* fi = static_cast(valuePointer); - - // signature is always 0xfeef04bd - if (fi->dwSignature != 0xfeef04bd) { - qCritical().nospace().noquote() - << "bad file info signature 0x" << hex << fi->dwSignature << " for " - << "'" << m_path << "'"; - - return {}; - } - - return *fi; -} - -QString Module::getFileDescription(std::byte* buffer) const -{ - struct LANGANDCODEPAGE - { - WORD wLanguage; - WORD wCodePage; - }; - - void* valuePointer = nullptr; - unsigned int valueSize = 0; - - // getting list of available languages - auto ret = VerQueryValueW( - buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - qCritical().nospace().noquote() - << "VerQueryValueW() for translations failed on '" << m_path << "'"; - - return {}; - } - - // number of languages - const auto count = valueSize / sizeof(LANGANDCODEPAGE); - if (count == 0) { - return {}; - } - - // using the first language in the list to get FileVersion - const auto* lcp = static_cast(valuePointer); - - const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") - .arg(lcp->wLanguage, 4, 16, QChar('0')) - .arg(lcp->wCodePage, 4, 16, QChar('0')); - - ret = VerQueryValueW( - buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - // not an error, no file version - return {}; - } - - // valueSize includes the null terminator - return QString::fromWCharArray( - static_cast(valuePointer), valueSize - 1); -} - -QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const -{ - if (fi.dwSignature == 0) { - return {}; - } - - const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; - const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; - const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; - const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; - - if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { - return {}; - } - - return QString("%1.%2.%3.%4") - .arg(major).arg(minor).arg(maintenance).arg(build); -} - -QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const -{ - FILETIME ft = {}; - - if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { - // if the file info is invalid or doesn't have a date, use the creation - // time on the file - - // opening the file - HandlePtr h(CreateFileW( - m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); - - if (h.get() == INVALID_HANDLE_VALUE) { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "can't open file '" << m_path << "' for timestamp, " - << formatSystemMessageQ(e); - - return {}; - } - - // getting the file time - if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { - const auto e = GetLastError(); - qCritical().nospace().noquote() - << "can't get file time for '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - } else { - // use the time from the file info - ft.dwHighDateTime = fi.dwFileDateMS; - ft.dwLowDateTime = fi.dwFileDateLS; - } - - - // converting to SYSTEMTIME - SYSTEMTIME utc = {}; - - if (!FileTimeToSystemTime(&ft, &utc)) { - qCritical().nospace().noquote() - << "FileTimeToSystemTime() failed on timestamp " - << "high=0x" << hex << ft.dwHighDateTime << " " - << "low=0x" << hex << ft.dwLowDateTime << " for " - << "'" << m_path << "'"; - - return {}; - } - - return QDateTime( - QDate(utc.wYear, utc.wMonth, utc.wDay), - QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); -} - -QString Module::getMD5() const -{ - if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { - // don't calculate md5 for system files, it's not really relevant and - // it takes a while - return {}; - } - - // opening the file - QFile f(m_path); - - if (!f.open(QFile::ReadOnly)) { - qCritical().nospace().noquote() - << "failed to open file '" << m_path << "' for md5"; - - return {}; - } - - // hashing - QCryptographicHash hash(QCryptographicHash::Md5); - if (!hash.addData(&f)) { - qCritical().nospace().noquote() - << "failed to calculate md5 for '" << m_path << "'"; - - return {}; - } - - return hash.result().toHex(); -} - - -WindowsInfo::WindowsInfo() -{ - // loading ntdll.dll, the functions will be found with GetProcAddress() - std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); - - if (!ntdll) { - qCritical() << "failed to load ntdll.dll while getting version"; - return; - } else { - m_reported = getReportedVersion(ntdll.get()); - m_real = getRealVersion(ntdll.get()); - } - - m_release = getRelease(); - m_elevated = getElevated(); -} - -bool WindowsInfo::compatibilityMode() const -{ - if (m_real == Version()) { - // don't know the real version, can't guess compatibility mode - return false; - } - - return (m_real != m_reported); -} - -const WindowsInfo::Version& WindowsInfo::reportedVersion() const -{ - return m_reported; -} - -const WindowsInfo::Version& WindowsInfo::realVersion() const -{ - return m_real; -} - -const WindowsInfo::Release& WindowsInfo::release() const -{ - return m_release; -} - -std::optional WindowsInfo::isElevated() const -{ - return m_elevated; -} - -QString WindowsInfo::toString() const -{ - QStringList sl; - - const QString reported = m_reported.toString(); - const QString real = m_real.toString(); - - // version - sl.push_back("version " + reported); - - // real version if different - if (compatibilityMode()) { - sl.push_back("real version " + real); - } - - // build.UBR, such as 17763.557 - if (m_release.UBR != 0) { - DWORD build = 0; - - if (compatibilityMode()) { - build = m_real.build; - } else { - build = m_reported.build; - } - - sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); - } - - // release ID - if (!m_release.ID.isEmpty()) { - sl.push_back("release " + m_release.ID); - } - - // buildlab string - if (!m_release.buildLab.isEmpty()) { - sl.push_back(m_release.buildLab); - } - - // product name - if (!m_release.productName.isEmpty()) { - sl.push_back(m_release.productName); - } - - // elevated - QString elevated = "?"; - if (m_elevated.has_value()) { - elevated = (*m_elevated ? "yes" : "no"); - } - - sl.push_back("elevated: " + elevated); - - return sl.join(", "); -} - -WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const -{ - // windows has been deprecating pretty much all the functions having to do - // with getting version information because apparently, people keep misusing - // them for feature detection - // - // there's still RtlGetVersion() though - - using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); - - auto* RtlGetVersion = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetVersion")); - - if (!RtlGetVersion) { - qCritical() << "RtlGetVersion() not found in ntdll.dll"; - return {}; - } - - OSVERSIONINFOEX vi = {}; - vi.dwOSVersionInfoSize = sizeof(vi); - - // this apparently never fails - RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); - - return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; -} - -WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const -{ - // getting the actual windows version is more difficult because all the - // functions are lying when running in compatibility mode - // - // RtlGetNtVersionNumbers() is an undocumented function that seems to work - // fine, but it might not in the future - - using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); - - auto* RtlGetNtVersionNumbers = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); - - if (!RtlGetNtVersionNumbers) { - qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; - return {}; - } - - DWORD major=0, minor=0, build=0; - RtlGetNtVersionNumbers(&major, &minor, &build); - - // for whatever reason, the build number has 0xf0000000 set - build = 0x0fffffff & build; - - return {major, minor, build}; -} - -WindowsInfo::Release WindowsInfo::getRelease() const -{ - // there are several interesting items in the registry, but most of them - // are undocumented, not always available, and localizable - // - // most of them are used to provide as much information as possible in case - // any of the other versions fail to work - - QSettings settings( - R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", - QSettings::NativeFormat); - - Release r; - - // buildlab seems to be an internal name from the build system - r.buildLab = settings.value("BuildLabEx", "").toString(); - if (r.buildLab.isEmpty()) { - r.buildLab = settings.value("BuildLab", "").toString(); - if (r.buildLab.isEmpty()) { - r.buildLab = settings.value("BuildBranch", "").toString(); - } - } - - // localized name of windows, such as "Windows 10 Pro" - r.productName = settings.value("ProductName", "").toString(); - - // release ID, such as 1803 - r.ID = settings.value("ReleaseId", "").toString(); - - // some other build number, shown in winver.exe - r.UBR = settings.value("UBR", 0).toUInt(); - - return r; -} - -std::optional WindowsInfo::getElevated() const -{ - HandlePtr token; - - { - HANDLE rawToken = 0; - - if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { - const auto e = GetLastError(); - - qCritical() - << "while trying to check if process is elevated, " - << "OpenProcessToken() failed: " << formatSystemMessageQ(e); - - return {}; - } - - token.reset(rawToken); - } - - TOKEN_ELEVATION e = {}; - DWORD size = sizeof(TOKEN_ELEVATION); - - if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { - const auto e = GetLastError(); - - qCritical() - << "while trying to check if process is elevated, " - << "GetTokenInformation() failed: " << formatSystemMessageQ(e); - - return {}; - } - - return (e.TokenIsElevated != 0); -} - - -SecurityProduct::SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate) : - m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), - m_active(active), m_upToDate(upToDate) -{ -} - -const QString& SecurityProduct::name() const -{ - return m_name; -} - -int SecurityProduct::provider() const -{ - return m_provider; -} - -bool SecurityProduct::active() const -{ - return m_active; -} - -bool SecurityProduct::upToDate() const -{ - return m_upToDate; -} - -QString SecurityProduct::toString() const -{ - QString s; - - s += m_name + " (" + providerToString() + ")"; - - if (!m_active) { - s += ", inactive"; - } - - if (!m_upToDate) { - s += ", definitions outdated"; - } - - if (!m_guid.isNull()) { - s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); - } - - return s; -} - -QString SecurityProduct::providerToString() const -{ - QStringList ps; - - if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { - ps.push_back("firewall"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { - ps.push_back("autoupdate"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { - ps.push_back("antivirus"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { - ps.push_back("antispyware"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { - ps.push_back("settings"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { - ps.push_back("uac"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { - ps.push_back("service"); - } - - if (ps.empty()) { - return "doesn't provider anything"; - } - - return ps.join("|"); -} - - -struct Process -{ - std::wstring filename; - DWORD pid; - - Process(std::wstring f, DWORD id) - : filename(std::move(f)), pid(id) - { - } -}; - -// returns the filename of the given process or the current one -// -std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) -{ - // double the buffer size 10 times - const int MaxTries = 10; - - DWORD bufferSize = MAX_PATH; - - for (int tries=0; tries(bufferSize + 1); - std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); - - DWORD writtenSize = 0; - - if (process == INVALID_HANDLE_VALUE) { - // query this process - writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); - } else { - // query another process - writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); - } - - if (writtenSize == 0) { - // hard failure - const auto e = GetLastError(); - std::wcerr << formatSystemMessage(e) << L"\n"; - break; - } else if (writtenSize >= bufferSize) { - // buffer is too small, try again - bufferSize *= 2; - } else { - // if GetModuleFileName() works, `writtenSize` does not include the null - // terminator - const std::wstring s(buffer.get(), writtenSize); - const fs::path path(s); - - return path.filename().native(); - } - } - - // something failed or the path is way too long to make sense - - std::wstring what; - if (process == INVALID_HANDLE_VALUE) { - what = L"the current process"; - } else { - what = L"pid " + std::to_wstring(reinterpret_cast(process)); - } - - std::wcerr << L"failed to get filename for " << what << L"\n"; - return {}; -} - -std::vector runningProcessesIds() -{ - // double the buffer size 10 times - const int MaxTries = 10; - - // initial size of 300 processes, unlikely to be more than that - std::size_t size = 300; - - for (int tries=0; tries(size); - std::fill(ids.get(), ids.get() + size, 0); - - DWORD bytesGiven = static_cast(size * sizeof(ids[0])); - DWORD bytesWritten = 0; - - if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) - { - const auto e = GetLastError(); - - std::wcerr - << L"failed to enumerate processes, " - << formatSystemMessage(e) << L"\n"; - - return {}; - } - - if (bytesWritten == bytesGiven) { - // no way to distinguish between an exact fit and not enough space, - // just try again - size *= 2; - continue; - } - - const auto count = bytesWritten / sizeof(ids[0]); - return std::vector(ids.get(), ids.get() + count); - } - - std::cerr << L"too many processes to enumerate"; - return {}; -} - -std::vector runningProcesses() -{ - const auto pids = runningProcessesIds(); - std::vector v; - - for (const auto& pid : pids) { - if (pid == 0) { - // the idle process has pid 0 and seems to be picked up by EnumProcesses() - continue; - } - - HandlePtr h(OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); - - if (!h) { - const auto e = GetLastError(); - - if (e != ERROR_ACCESS_DENIED) { - // don't log access denied, will happen a lot for system processes, even - // when elevated - std::wcerr - << L"failed to open process " << pid << L", " - << formatSystemMessage(e) << L"\n"; - } - - continue; - } - - auto filename = processFilename(h.get()); - if (!filename.empty()) { - v.emplace_back(std::move(filename), pid); - } - } - - return v; -} - -DWORD findOtherPid() -{ - const std::wstring defaultName = L"ModOrganizer.exe"; - - std::wclog << L"looking for the other process...\n"; - - // used to skip the current process below - const auto thisPid = GetCurrentProcessId(); - std::wclog << L"this process id is " << thisPid << L"\n"; - - // getting the filename for this process, assumes the other process has the - // smae one - auto filename = processFilename(); - if (filename.empty()) { - std::wcerr - << L"can't get current process filename, defaulting to " - << defaultName << L"\n"; - - filename = defaultName; - } else { - std::wclog << L"this process filename is " << filename << L"\n"; - } - - // getting all running processes - const auto processes = runningProcesses(); - std::wclog << L"there are " << processes.size() << L" processes running\n"; - - // going through processes, trying to find one with the same name and a - // different pid than this process has - for (const auto& p : processes) { - if (p.filename == filename) { - if (p.pid != thisPid) { - return p.pid; - } - } - } - - std::wclog - << L"no process with this filename\n" - << L"MO may not be running, or it may be running as administrator\n" - << L"you can try running this again as administrator\n"; - - return 0; -} - -std::wstring tempDir() -{ - const DWORD bufferSize = MAX_PATH + 1; - wchar_t buffer[bufferSize + 1] = {}; - - const auto written = GetTempPathW(bufferSize, buffer); - if (written == 0) { - const auto e = GetLastError(); - - std::wcerr - << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; - - return {}; - } - - // `written` does not include the null terminator - return std::wstring(buffer, buffer + written); -} - -HandlePtr tempFile(const std::wstring dir) -{ - // maximum tries of incrementing the counter - const int MaxTries = 100; - - // UTC time and date will be in the filename - const auto now = std::time(0); - const auto tm = std::gmtime(&now); - - // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where - // i can go until MaxTries - std::wostringstream oss; - oss - << L"ModOrganizer-" - << std::setw(4) << (1900 + tm->tm_year) - << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) - << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" - << std::setw(2) << std::setfill(L'0') << tm->tm_hour - << std::setw(2) << std::setfill(L'0') << tm->tm_min - << std::setw(2) << std::setfill(L'0') << tm->tm_sec; - - const std::wstring prefix = oss.str(); - const std::wstring ext = L".dmp"; - - // first path to try, without counter in it - std::wstring path = dir + L"\\" + prefix + ext; - - for (int i=0; i. #ifndef UTIL_H #define UTIL_H - #include -#include - -#define WIN32_LEAN_AND_MEAN -#include - #include -#include class Executable; @@ -51,442 +44,6 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); - -namespace env -{ - -class Console -{ -public: - Console(); - ~Console(); - -private: - bool m_hasConsole; - FILE* m_in; - FILE* m_out; - FILE* m_err; -}; - - -// an application shortcut that can be either on the desktop or the start menu -// -class Shortcut -{ -public: - // location of a shortcut - // - enum Locations - { - None = 0, - - // on the desktop - Desktop, - - // in the start menu - StartMenu - }; - - - // empty shortcut - // - Shortcut(); - - // shortcut from an executable - // - explicit Shortcut(const Executable& exe); - - // sets the name of the shortcut, shown on icons and start menu entries - // - Shortcut& name(const QString& s); - - // the program to start - // - Shortcut& target(const QString& s); - - // arguments to pass - // - Shortcut& arguments(const QString& s); - - // shows in the status bar of explorer, for example - // - Shortcut& description(const QString& s); - - // path to a binary that contains the icon and its index - // - Shortcut& icon(const QString& s, int index=0); - - // "start in" option for this shortcut - // - Shortcut& workingDirectory(const QString& s); - - - // returns whether this shortcut already exists at the given location; this - // does not check whether the shortcut parameters are different, it merely if - // the .lnk file exists - // - bool exists(Locations loc) const; - - // calls remove() if exists(), or add() - // - bool toggle(Locations loc); - - // adds the shortcut to the given location - // - bool add(Locations loc); - - // removes the shortcut from the given location - // - bool remove(Locations loc); - -private: - QString m_name; - QString m_target; - QString m_arguments; - QString m_description; - QString m_icon; - int m_iconIndex; - QString m_workingDirectory; - - // returns a qCritical() logger with a prefix already logged - // - QDebug critical() const; - - // returns a qDebug() logger with a prefix already logged - // - QDebug debug() const; - - - // returns the path where the shortcut file should be saved - // - QString shortcutPath(Locations loc) const; - - // returns the directory where the shortcut file should be saved - // - QString shortcutDirectory(Locations loc) const; - - // returns the filename of the shortcut file that should be used when saving - // - QString shortcutFilename() const; -}; - - -// returns a string representation of the given location -// -QString toString(Shortcut::Locations loc); - - -// represents one module -// -class Module -{ -public: - explicit Module(QString path, std::size_t fileSize); - - // returns the module's path - // - const QString& path() const; - - // returns the module's path in lowercase and using forward slashes - // - QString displayPath() const; - - // returns the size in bytes, may be 0 - // - std::size_t fileSize() const; - - // returns the x.x.x.x version embedded from the version info, may be empty - // - const QString& version() const; - - // returns the FileVersion entry from the resource file, returns - // "(no version)" if not available - // - const QString& versionString() const; - - // returns the build date from the version info, or the creation time of the - // file on the filesystem, may be empty - // - const QDateTime& timestamp() const; - - // returns the md5 of the file, may be empty for system files - // - const QString& md5() const; - - // converts timestamp() to a string for display, returns "(no timestamp)" if - // not available - // - QString timestampString() const; - - // returns a string with all the above information on one line - // - QString toString() const; - -private: - // contains the information from the version resource - // - struct FileInfo - { - VS_FIXEDFILEINFO ffi; - QString fileDescription; - }; - - QString m_path; - std::size_t m_fileSize; - QString m_version; - QDateTime m_timestamp; - QString m_versionString; - QString m_md5; - - // returns information from the version resource - // - FileInfo getFileInfo() const; - - // uses VS_FIXEDFILEINFO to build the version string - // - QString getVersion(const VS_FIXEDFILEINFO& fi) const; - - // uses the file date from VS_FIXEDFILEINFO if available, or gets the - // creation date on the file - // - QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; - - // returns the md5 hash unless the path contains "\windows\" - // - QString getMD5() const; - - // gets VS_FIXEDFILEINFO from the file version info buffer - // - VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; - - // gets FileVersion from the file version info buffer - // - QString getFileDescription(std::byte* buffer) const; -}; - - -// a variety of information on windows -// -class WindowsInfo -{ -public: - struct Version - { - DWORD major=0, minor=0, build=0; - - QString toString() const - { - return QString("%1.%2.%3").arg(major).arg(minor).arg(build); - } - - friend bool operator==(const Version& a, const Version& b) - { - return - a.major == b.major && - a.minor == b.minor && - a.build == b.build; - } - - friend bool operator!=(const Version& a, const Version& b) - { - return !(a == b); - } - }; - - struct Release - { - // the BuildLab entry from the registry, may be empty - QString buildLab; - - // product name such as "Windows 10 Pro", may not be in English, may be - // empty - QString productName; - - // release ID such as 1809, may be mepty - QString ID; - - // some sub-build number, undocumented, may be empty - DWORD UBR; - - Release() - : UBR(0) - { - } - }; - - - WindowsInfo(); - - // tries to guess whether this process is running in compatibility mode - // - bool compatibilityMode() const; - - // returns the Windows version, may not correspond to the actual version - // if the process is running in compatibility mode - // - const Version& reportedVersion() const; - - // tries to guess the real Windows version that's running, can be empty - // - const Version& realVersion() const; - - // various information about the current release - // - const Release& release() const; - - // whether this process is running as administrator, may be empty if the - // information is not available - std::optional isElevated() const; - - // returns a string with all the above information on one line - // - QString toString() const; - -private: - Version m_reported, m_real; - Release m_release; - std::optional m_elevated; - - // uses RtlGetVersion() to get the version number as reported by Windows - // - Version getReportedVersion(HINSTANCE ntdll) const; - - // uses RtlGetNtVersionNumbers() to get the real version number - // - Version getRealVersion(HINSTANCE ntdll) const; - - // gets various information from the registry - // - Release getRelease() const; - - // gets whether the process is elevated - // - std::optional getElevated() const; -}; - - -// represents a security product, such as an antivirus or a firewall -// -class SecurityProduct -{ -public: - SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate); - - // display name of the product - // - const QString& name() const; - - // a bunch of _WSC_SECURITY_PROVIDER flags - // - int provider() const; - - // whether the product is active - // - bool active() const; - - // whether its definitions are up-to-date - // - bool upToDate() const; - - // string representation of the above - // - QString toString() const; - -private: - QUuid m_guid; - QString m_name; - int m_provider; - bool m_active; - bool m_upToDate; - - QString providerToString() const; -}; - - -class Metrics -{ -public: - struct Display - { - int resX=0, resY=0, dpi=0; - bool primary=false; - int refreshRate = 0; - QString monitor, adapter; - - QString toString() const; - }; - - Metrics(); - - const std::vector& displays() const; - -private: - std::vector m_displays; -}; - - -// represents the process's environment -// -class Environment -{ -public: - Environment(); - - // list of loaded modules in the current process - // - const std::vector& loadedModules() const; - - // information about the operating system - // - const WindowsInfo& windowsInfo() const; - - // information about the installed security products - // - const std::vector& securityProducts() const; - - // information about displays - // - const Metrics& metrics() const; - - // logs the environment - // - void dump() const; - -private: - std::vector m_modules; - WindowsInfo m_windows; - std::vector m_security; - Metrics m_metrics; - - std::vector getLoadedModules() const; - std::vector getSecurityProducts() const; - - std::vector getSecurityProductsFromWMI() const; - std::optional getWindowsFirewall() const; -}; - - -enum class CoreDumpTypes -{ - Mini = 1, - Data, - Full -}; - -// creates a minidump file for the given process -// -bool coredump(CoreDumpTypes type); - -// finds another process with the same name as this one and creates a minidump -// file for it -// -bool coredumpOther(CoreDumpTypes type); - -} // namespace env - - MOBase::VersionInfo createVersionInfo(); } // namespace MOShared -- cgit v1.3.1 From f49efd6d448dccd4100fa46e2ebf1690d97033cc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 04:54:47 -0400 Subject: replaced formatSystemMessageQ() with formatSystemMessage() replaced windowsErrorString() with formatSystemMessage() --- src/envmetrics.cpp | 6 +++--- src/envmodule.cpp | 14 +++++++------- src/envsecurity.cpp | 20 ++++++++++---------- src/envshortcut.cpp | 4 ++-- src/envwindows.cpp | 4 ++-- src/main.cpp | 2 +- src/mainwindow.cpp | 25 ++++++++++++++++++------- src/organizercore.cpp | 6 ++++-- src/profile.cpp | 7 +++++-- src/settings.cpp | 6 +++--- 10 files changed, 55 insertions(+), 39 deletions(-) (limited to 'src/main.cpp') diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp index 784e4baf..b1b9bd2e 100644 --- a/src/envmetrics.cpp +++ b/src/envmetrics.cpp @@ -19,7 +19,7 @@ int getDesktopDpi() if (!dc) { const auto e = GetLastError(); - log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + log::error("can't get desktop DC, {}", formatSystemMessage(e)); return 0; } @@ -52,7 +52,7 @@ HMONITOR findMonitor(const QString& name) const auto e = GetLastError(); log::error( "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessageQ(e)); + data.name, formatSystemMessage(e)); // error for this monitor, but continue return TRUE; @@ -121,7 +121,7 @@ int getDpi(const QString& monitorDevice) if (FAILED(r)) { log::error( "GetDpiForMonitor() failed for '{}', {}", - monitorDevice, formatSystemMessageQ(r)); + monitorDevice, formatSystemMessage(r)); return 0; } diff --git a/src/envmodule.cpp b/src/envmodule.cpp index aae4e0b1..8cea414a 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -117,7 +117,7 @@ Module::FileInfo Module::getFileInfo() const log::error( "GetFileVersionInfoSizeW() failed on '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -130,7 +130,7 @@ Module::FileInfo Module::getFileInfo() const log::error( "GetFileVersionInfoW() failed on '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -255,7 +255,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const log::error( "can't open file '{}' for timestamp, {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -266,7 +266,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const log::error( "can't get file time for '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -328,7 +328,7 @@ std::vector getLoadedModules() if (snapshot.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessageQ(e)); + log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); return {}; } @@ -339,7 +339,7 @@ std::vector getLoadedModules() if (!Module32First(snapshot.get(), &me)) { const auto e = GetLastError(); - log::error("Module32First() failed, {}", formatSystemMessageQ(e)); + log::error("Module32First() failed, {}", formatSystemMessage(e)); return {}; } @@ -358,7 +358,7 @@ std::vector getLoadedModules() // no more modules is not an error if (e != ERROR_NO_MORE_FILES) { - log::error("Module32Next() failed, {}", formatSystemMessageQ(e)); + log::error("Module32Next() failed, {}", formatSystemMessage(e)); } break; diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 015e4000..376be4df 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -58,7 +58,7 @@ public: } if (FAILED(ret)) { - log::error("enum->next() failed, {}", formatSystemMessageQ(ret)); + log::error("enum->next() failed, {}", formatSystemMessage(ret)); break; } @@ -84,7 +84,7 @@ private: if (FAILED(ret) || !rawLocator) { log::error( "CoCreateInstance for WbemLocator failed, {}", - formatSystemMessageQ(ret)); + formatSystemMessage(ret)); throw failed(); } @@ -104,7 +104,7 @@ private: if (FAILED(res) || !rawService) { log::error( "locator->ConnectServer() failed for namespace '{}', {}", - ns, formatSystemMessageQ(res)); + ns, formatSystemMessage(res)); throw failed(); } @@ -120,7 +120,7 @@ private: if (FAILED(ret)) { - log::error("CoSetProxyBlanket() failed, {}", formatSystemMessageQ(ret)); + log::error("CoSetProxyBlanket() failed, {}", formatSystemMessage(ret)); throw failed(); } } @@ -139,7 +139,7 @@ private: if (FAILED(ret) || !rawEnumerator) { - log::error("query '{}' failed, {}", query, formatSystemMessageQ(ret)); + log::error("query '{}' failed, {}", query, formatSystemMessage(ret)); return {}; } @@ -250,7 +250,7 @@ std::vector getSecurityProductsFromWMI() // display name auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get displayName, {}", formatSystemMessageQ(ret)); + log::error("failed to get displayName, {}", formatSystemMessage(ret)); return; } @@ -265,7 +265,7 @@ std::vector getSecurityProductsFromWMI() // product state ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get productState, {}", formatSystemMessageQ(ret)); + log::error("failed to get productState, {}", formatSystemMessage(ret)); return; } @@ -286,7 +286,7 @@ std::vector getSecurityProductsFromWMI() // guid ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get instanceGuid, {}", formatSystemMessageQ(ret)); + log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); return; } @@ -349,7 +349,7 @@ std::optional getWindowsFirewall() if (FAILED(hr) || !rawPolicy) { log::error( "CoCreateInstance for NetFwPolicy2 failed, {}", - formatSystemMessageQ(hr)); + formatSystemMessage(hr)); return {}; } @@ -363,7 +363,7 @@ std::optional getWindowsFirewall() hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); if (FAILED(hr)) { - log::error("get_FirewallEnabled failed, {}", formatSystemMessageQ(hr)); + log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr)); return {}; } } diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 1deb9dad..99495c39 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -100,7 +100,7 @@ private: if (FAILED(r)) { throw ShellLinkException(QString("%1, %2") .arg(s) - .arg(formatSystemMessageQ(r))); + .arg(formatSystemMessage(r))); } } @@ -290,7 +290,7 @@ bool Shortcut::remove(Locations loc) log::error( "failed to remove shortcut '{}', {}", - path, formatSystemMessageQ(e)); + path, formatSystemMessage(e)); return false; } diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 8a98036a..3932a9b5 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -210,7 +210,7 @@ std::optional WindowsInfo::getElevated() const log::error( "while trying to check if process is elevated, " - "OpenProcessToken() failed: {}", formatSystemMessageQ(e)); + "OpenProcessToken() failed: {}", formatSystemMessage(e)); return {}; } @@ -226,7 +226,7 @@ std::optional WindowsInfo::getElevated() const log::error( "while trying to check if process is elevated, " - "GetTokenInformation() failed: {}", formatSystemMessageQ(e)); + "GetTokenInformation() failed: {}", formatSystemMessage(e)); return {}; } diff --git a/src/main.cpp b/src/main.cpp index 5c5ce945..f53a574e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -464,7 +464,7 @@ void preloadDll(const QString& filename) if (!LoadLibraryW(dllPath.toStdWString().c_str())) { const auto e = GetLastError(); - log::warn("failed to load {}: {}", dllPath, formatSystemMessageQ(e)); + log::warn("failed to load {}: {}", dllPath, formatSystemMessage(e)); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e502bdb1..8a8a99ef 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4029,7 +4029,8 @@ void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) MessageDialog::showMessage(tr("Move successful."), this); } else { - log::error("Move operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("Move operation failed: {}", formatSystemMessage(e)); } m_OrganizerCore.refreshModList(); @@ -4058,7 +4059,8 @@ void MainWindow::clearOverwrite() updateProblemsButton(); m_OrganizerCore.refreshModList(); } else { - log::error("Delete operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("Delete operation failed: {}", formatSystemMessage(e)); } } } @@ -6819,8 +6821,13 @@ void MainWindow::on_restoreButton_clicked() if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || !shellCopy(lockedName + "." + choice, lockedName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + + const auto e = GetLastError(); + + QMessageBox::critical( + this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); } m_OrganizerCore.refreshESPList(true); } @@ -6841,8 +6848,11 @@ void MainWindow::on_restoreModsButton_clicked() QString choice = queryRestore(modlistName); if (!choice.isEmpty()) { if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + QMessageBox::critical( + this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(formatSystemMessage(e))); } m_OrganizerCore.refreshModList(false); } @@ -6956,7 +6966,8 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m success = shellCopy(file.absoluteFilePath(), target, true, this); } if (!success) { - log::error("file operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("file operation failed: {}", formatSystemMessage(e)); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index f6802673..b61ebde8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -354,7 +354,7 @@ QString OrganizerCore::commitSettings(const QString &iniFile) // make a second attempt using qt functions but if that fails print the // error from the first attempt if (!renameFile(iniFile + ".new", iniFile)) { - return windowsErrorString(err); + return QString::fromStdWString(formatSystemMessage(err)); } } return QString(); @@ -387,10 +387,12 @@ void OrganizerCore::storeSettings() + 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, windowsErrorString(::GetLastError()))); + .arg(iniFile) + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } } diff --git a/src/profile.cpp b/src/profile.cpp index 27616986..6de1b097 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -265,7 +265,10 @@ void Profile::createTweakedIniFile() QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { - reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + reportError( + tr("failed to update tweaked ini file, wrong settings may be used: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } @@ -287,7 +290,7 @@ void Profile::createTweakedIniFile() if (error) { const auto e = ::GetLastError(); reportError(tr("failed to create tweaked ini: %1") - .arg(formatSystemMessageQ(e))); + .arg(QString::fromStdWString(formatSystemMessage(e)))); } log::debug("{} saved", QDir::toNativeSeparators(tweakedIni)); diff --git a/src/settings.cpp b/src/settings.cpp index ff5b9976..5ad066b2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -220,7 +220,7 @@ QString Settings::deObfuscate(const QString key) } else { const auto e = GetLastError(); if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessageQ(e)); + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); } } delete[] keyData; @@ -365,7 +365,7 @@ bool Settings::setNexusApiKey(const QString& apiKey) { if (!obfuscate("APIKEY", apiKey)) { const auto e = GetLastError(); - log::error("Storing API key failed: {}", formatSystemMessageQ(e)); + log::error("Storing API key failed: {}", formatSystemMessage(e)); return false; } @@ -486,7 +486,7 @@ void Settings::setSteamLogin(QString username, QString password) } if (!obfuscate("steam_password", password)) { const auto e = GetLastError(); - log::error("Storing or deleting password failed: {}", formatSystemMessageQ(e)); + log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); } } -- cgit v1.3.1 From 5304d52f9373e0078674af79b656e2e4d010ca90 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 06:53:52 -0400 Subject: removed some useless logging initializing usvfs logging now logs strings for log level and crash dump type --- src/downloadmanager.cpp | 3 --- src/main.cpp | 2 -- src/organizercore.cpp | 27 ++++++++++--------------- src/usvfsconnector.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 61 insertions(+), 24 deletions(-) (limited to 'src/main.cpp') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index d2556faa..1f86f9aa 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -378,9 +378,6 @@ void DownloadManager::refreshList() } } - //if (m_ActiveDownloads.size() != downloadsBefore) { - log::debug("Downloads after refresh: {}", m_ActiveDownloads.size()); - //} emit update(-1); //let watcher trigger refreshes again diff --git a/src/main.cpp b/src/main.cpp index f53a574e..0adfc110 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -623,8 +623,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, QImage image(pluginSplash); if (!image.isNull()) { image.save(dataPath + "/splash.png"); - } else { - log::debug("no plugin splash"); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index b61ebde8..92372d82 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -77,26 +77,21 @@ CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; static bool isOnline() { - QList interfaces = QNetworkInterface::allInterfaces(); - - bool connected = false; - for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; - ++iter) { - if ((iter->flags() & QNetworkInterface::IsUp) - && (iter->flags() & QNetworkInterface::IsRunning) - && !(iter->flags() & QNetworkInterface::IsLoopBack)) { - auto addresses = iter->addressEntries(); - if (addresses.count() == 0) { - continue; + 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; + } } - log::debug("interface {} seems to be up (address: {})", - iter->humanReadableName(), - addresses[0].ip().toString()); - connected = true; } } - return connected; + return false; } static bool renameFile(const QString &oldName, const QString &newName, diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 5918c8a5..b5e6edb1 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -118,6 +118,48 @@ 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() { USVFSParameters params; @@ -129,9 +171,14 @@ UsvfsConnector::UsvfsConnector() InitLogging(false); log::debug( - "Initializing VFS <{}, {}, {}, {}>", - params.instanceName, static_cast(params.logLevel), - static_cast(params.crashDumpsType), params.crashDumpsPath); + "initializing usvfs:\n" + " . instance: {}\n" + " . log: {}\n" + " . dump: {} ({})", + params.instanceName, + toString(params.logLevel), + params.crashDumpsPath, + toString(params.crashDumpsType)); CreateVFS(¶ms); -- cgit v1.3.1 From 5e98855847590cfff4e04aa7c3d593f0d30e7202 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 2 Aug 2019 07:01:06 -0400 Subject: create log directory sooner to handle creating new instances reset the log file in case MO is restarted and the previously selected instance is deleted switched back to single `mo_interface.log` file --- src/main.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 0e61a781..911f11c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -120,10 +120,6 @@ bool bootstrap() removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), "usvfs*.log", 5, QDir::Name); - if (!createAndMakeWritable(AppConfig::logPath())) { - return false; - } - return true; } @@ -906,6 +902,10 @@ int main(int argc, char *argv[]) } // we continue for the primary instance OR if MO was called with parameters do { + // make sure the log file isn't locked in case MO was restarted and + // the previous instance gets deleted + log::getDefault().setFile({}); + QString dataPath; try { @@ -926,8 +926,12 @@ int main(int argc, char *argv[]) const auto logFile = qApp->property("dataPath").toString() + "/logs/mo_interface.log"; - log::getDefault().setFile(MOBase::log::File::rotating( - logFile.toStdWString(), 5*1024*1024, 5)); + if (!createAndMakeWritable(AppConfig::logPath())) { + reportError("Failed to create log folder"); + return 1; + } + + log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString())); QString splash = dataPath + "/splash.png"; if (!QFile::exists(dataPath + "/splash.png")) { -- 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/main.cpp') 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/main.cpp') 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 e4418b95fa24f9caea32adfe9d957ce37e46f127 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 02:30:20 -0400 Subject: moved settings updates to Settings::processUpdates() --- src/main.cpp | 2 +- src/mainwindow.cpp | 44 ++++++++++++-------------------------------- src/mainwindow.h | 2 +- src/settings.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 7 +++++++ 5 files changed, 67 insertions(+), 34 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 3e26ea17..506c6270 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -739,7 +739,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); - mainWindow.processUpdates(); + mainWindow.processUpdates(settings); // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e77d08b1..0618f949 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2280,11 +2280,15 @@ void MainWindow::readSettings(const Settings& settings) DockFixer::restore(this, settings); } -void MainWindow::processUpdates() { - QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); - QVersionNumber lastVersion = QVersionNumber::fromString(settings.value("version", "2.1.2").toString()).normalized(); - QVersionNumber currentVersion = QVersionNumber::fromString(m_OrganizerCore.getVersion().displayString()).normalized(); - if (!m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) { +void MainWindow::processUpdates(Settings& settings) { + const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); + + const auto lastVersion = settings.getVersion().value_or(earliest); + const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); + + settings.processUpdates(currentVersion, lastVersion); + + if (!settings.getFirstStart()) { if (lastVersion < QVersionNumber(2, 1, 3)) { bool lastHidden = true; for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) { @@ -2293,41 +2297,20 @@ void MainWindow::processUpdates() { lastHidden = hidden; } } + if (lastVersion < QVersionNumber(2, 1, 6)) { ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); } - if (lastVersion < QVersionNumber(2, 2, 0)) { - QSettings &instance = Settings::instance().directInterface(); - instance.beginGroup("Settings"); - instance.remove("steam_password"); - instance.remove("nexus_username"); - instance.remove("nexus_password"); - instance.remove("nexus_login"); - instance.remove("nexus_api_key"); - instance.remove("ask_for_nexuspw"); - instance.remove("nmm_version"); - instance.endGroup(); - instance.beginGroup("Servers"); - instance.remove(""); - instance.endGroup(); - } + if (lastVersion < QVersionNumber(2, 2, 1)) { // hide new columns by default for (int i=DownloadList::COL_MODNAME; idownloadView->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) { - //NOP - } else if (currentVersion < lastVersion) { + if (currentVersion < lastVersion) { const auto text = tr( "Notice: Your current MO version (%1) is lower than the previously used one (%2). " "The GUI may not downgrade gracefully, so you may experience oddities. " @@ -2337,9 +2320,6 @@ void MainWindow::processUpdates() { log::warn("{}", text); } - - //save version in all case - settings.setValue("version", currentVersion.toString()); } void MainWindow::storeSettings(Settings& s) { diff --git a/src/mainwindow.h b/src/mainwindow.h index d4513c0f..e8f60211 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -121,7 +121,7 @@ public: void storeSettings(Settings& settings) override; void readSettings(const Settings& settings); - void processUpdates(); + void processUpdates(Settings& settings); virtual ILockedWaitingForProcess* lock() override; virtual void unlock() override; diff --git a/src/settings.cpp b/src/settings.cpp index d843a0db..35be1298 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -97,6 +97,38 @@ Settings &Settings::instance() return *s_Instance; } +void Settings::processUpdates( + const QVersionNumber& currentVersion, const QVersionNumber& lastVersion) +{ + if (getFirstStart()) { + return; + } + + if (lastVersion < QVersionNumber(2, 2, 0)) { + m_Settings.beginGroup("Settings"); + m_Settings.remove("steam_password"); + m_Settings.remove("nexus_username"); + m_Settings.remove("nexus_password"); + m_Settings.remove("nexus_login"); + m_Settings.remove("nexus_api_key"); + m_Settings.remove("ask_for_nexuspw"); + m_Settings.remove("nmm_version"); + m_Settings.endGroup(); + + m_Settings.beginGroup("Servers"); + m_Settings.remove(""); + m_Settings.endGroup(); + } + + if (lastVersion < QVersionNumber(2, 2, 2)) { + // log splitter is gone, it's a dock now + m_Settings.remove("log_split"); + } + + //save version in all case + m_Settings.setValue("version", currentVersion.toString()); +} + QString Settings::getFilename() const { return m_Settings.fileName(); @@ -397,6 +429,20 @@ std::optional Settings::getUseProxy() const return getOptional(m_Settings, "Settings/use_proxy"); } +std::optional Settings::getVersion() const +{ + if (auto v=getOptional(m_Settings, "version")) { + return QVersionNumber::fromString(*v).normalized(); + } + + return {}; +} + +bool Settings::getFirstStart() const +{ + return getOptional(m_Settings, "first_start").value_or(true); +} + QString Settings::getProfileDirectory(bool resolve) const { return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); diff --git a/src/settings.h b/src/settings.h index 066843c2..bf66c0dd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -72,6 +72,9 @@ public: static Settings &instance(); + void processUpdates( + const QVersionNumber& currentVersion, const QVersionNumber& lastVersion); + QString getFilename() const; /** @@ -169,9 +172,13 @@ public: std::optional getSelectedExecutable() const; std::optional getUseProxy() const; + std::optional getVersion() const; + bool getFirstStart() const; + GeometrySettings& geometry(); const GeometrySettings& geometry() const; + /** * retrieve the directory where profiles stored (with native separators) **/ -- cgit v1.3.1 From 3f487a5a6c9c23824298fdde3d76dc82edf3ca46 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 07:03:52 -0400 Subject: merged toolbars into restoreToolbars() and saveToolbars() added centerOnMainWindowMonitor(), now also used by validation dialog added overloads for splitter, used by main splitter fixed saveState() for QMainWindow calling the wrong function --- src/main.cpp | 11 +------ src/mainwindow.cpp | 28 +++++----------- src/nxmaccessmanager.cpp | 13 ++++++-- src/nxmaccessmanager.h | 2 ++ src/pch.h | 1 + src/settings.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++------ src/settings.h | 19 ++++++++--- 7 files changed, 114 insertions(+), 46 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 506c6270..8eee41e4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -697,16 +697,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - 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(); - splash.move(center - splash.rect().center()); - } - + settings.geometry().centerOnMainWindowMonitor(&splash); splash.show(); splash.activateWindow(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 85be8563..6e6e3d22 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2261,14 +2261,8 @@ void MainWindow::readSettings(const Settings& settings) { settings.restoreGeometry(this); settings.restoreState(this); - - if (auto v=settings.geometry().getToolbarSize()) { - setToolbarSize(*v); - } - - if (auto v=settings.geometry().getToolbarButtonStyle()) { - setToolbarButtonStyle(*v); - } + settings.geometry().restoreToolbars(this); + settings.restoreState(ui->splitter); if (auto v=settings.geometry().getMenubarVisible()) { showMenuBar(*v); @@ -2278,10 +2272,6 @@ void MainWindow::readSettings(const Settings& settings) showStatusBar(*v); } - if (auto v=settings.geometry().getMainSplitterState()) { - ui->splitter->restoreState(*v); - } - { auto v = settings.geometry().getFiltersVisible().value_or(false); setCategoryListVisible(v); @@ -2366,14 +2356,12 @@ void MainWindow::storeSettings(Settings& s) { s.saveState(this); s.saveGeometry(this); - settings.setValue("toolbar_size", ui->toolBar->iconSize()); - settings.setValue("toolbar_button_style", static_cast(ui->toolBar->toolButtonStyle())); - settings.setValue("menubar_visible", m_menuBarVisible); - settings.setValue("statusbar_visible", m_statusBarVisible); - settings.setValue("window_split", ui->splitter->saveState()); - QScreen *screen = this->window()->windowHandle()->screen(); - int screenId = QGuiApplication::screens().indexOf(screen); - settings.setValue("window_monitor", screenId); + s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveToolbars(this); + s.geometry().setStatusbarVisible(m_statusBarVisible); + s.saveState(ui->splitter); + s.geometry().saveMainWindowMonitor(this); + settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index fd1dc0c1..16190ca4 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -48,8 +48,9 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) - : m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr) +ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) : + m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr), + m_first(true) { m_bar = new QProgressBar; m_bar->setTextVisible(false); @@ -103,6 +104,14 @@ void ValidationProgressDialog::stop() hide(); } +void ValidationProgressDialog::showEvent(QShowEvent* e) +{ + if (m_first) { + Settings::instance().geometry().centerOnMainWindowMonitor(this); + m_first = false; + } +} + void ValidationProgressDialog::closeEvent(QCloseEvent* e) { hide(); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index eed7c1c9..0c85153b 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -48,6 +48,7 @@ public: using QDialog::show; protected: + void showEvent(QShowEvent* e) override; void closeEvent(QCloseEvent* e) override; private: @@ -56,6 +57,7 @@ private: QDialogButtonBox* m_buttons; QTimer* m_timer; QElapsedTimer m_elapsed; + bool m_first; void onButton(QAbstractButton* b); void onTimer(); diff --git a/src/pch.h b/src/pch.h index 504ef8f1..dd65efbe 100644 --- a/src/pch.h +++ b/src/pch.h @@ -189,6 +189,7 @@ #include #include #include +#include #include #include #include diff --git a/src/settings.cpp b/src/settings.cpp index 91e667d5..a3d12070 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -958,7 +958,7 @@ bool Settings::restoreGeometry(QWidget* w) const void Settings::saveState(const QMainWindow* w) { - m_Settings.setValue(stateSettingName(w), w->saveGeometry()); + m_Settings.setValue(stateSettingName(w), w->saveState()); } bool Settings::restoreState(QMainWindow* w) const @@ -986,24 +986,61 @@ bool Settings::restoreState(QHeaderView* w) const return false; } +void Settings::saveState(const QSplitter* w) +{ + m_Settings.setValue(stateSettingName(w), w->saveState()); +} + +bool Settings::restoreState(QSplitter* w) const +{ + if (auto v=getOptional(m_Settings, stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; +} + GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s) { } -std::optional GeometrySettings::getToolbarSize() const +bool GeometrySettings::restoreToolbars(QMainWindow* w) const { - return getOptional(m_Settings, "toolbar_size"); + const auto size = getOptional(m_Settings, "toolbar_size"); + const auto style = getOptional(m_Settings, "toolbar_button_style"); + + if (!size && !style) { + return false; + } + + for (auto* tb : w->findChildren()) { + if (size) { + tb->setIconSize(*size); + } + + if (style) { + tb->setToolButtonStyle(static_cast(*style)); + } + } + + return true; } -std::optional GeometrySettings::getToolbarButtonStyle() const +void GeometrySettings::saveToolbars(const QMainWindow* w) { - if (auto v=getOptional(m_Settings, "toolbar_button_style")) { - return static_cast(*v); + // all toolbars are identical, just save the first one + const auto tbs = w->findChildren(); + if (tbs.isEmpty()) { + return; } - return {}; + const auto* tb = tbs[0]; + + m_Settings.setValue("toolbar_size", tb->iconSize()); + m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); } std::optional GeometrySettings::getMenubarVisible() const @@ -1011,14 +1048,19 @@ std::optional GeometrySettings::getMenubarVisible() const return getOptional(m_Settings, "menubar_visible"); } +void GeometrySettings::setMenubarVisible(bool b) +{ + m_Settings.setValue("menubar_visible", b); +} + std::optional GeometrySettings::getStatusbarVisible() const { return getOptional(m_Settings, "statusbar_visible"); } -std::optional GeometrySettings::getMainSplitterState() const +void GeometrySettings::setStatusbarVisible(bool b) { - return getOptional(m_Settings, "window_split"); + m_Settings.setValue("statusbar_visible", b); } std::optional GeometrySettings::getFiltersVisible() const @@ -1064,7 +1106,31 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) std::optional GeometrySettings::getMainWindowMonitor() const { - return getOptional(m_Settings, "window_monitor"); + return getOptional(m_Settings, "geometry/window_monitor"); +} + +void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) +{ + const auto monitor = getMainWindowMonitor(); + QPoint center; + + if (monitor && QGuiApplication::screens().size() > *monitor) { + center = QGuiApplication::screens().at(*monitor)->geometry().center(); + } else { + center = QGuiApplication::primaryScreen()->geometry().center(); + } + + w->move(center - w->rect().center()); +} + +void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) +{ + if (auto* handle=w->windowHandle()) { + if (auto* screen = handle->screen()) { + const int screenId = QGuiApplication::screens().indexOf(screen); + m_Settings.setValue("geometry/window_monitor", screenId); + } + } } void GeometrySettings::setDockSize(const QString& name, int size) diff --git a/src/settings.h b/src/settings.h index 1575b3cd..bbf008f0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -28,6 +28,8 @@ namespace MOBase { class IPluginGame; } +class QSplitter; + class PluginContainer; struct ServerInfo; class Settings; @@ -49,18 +51,24 @@ class GeometrySettings public: GeometrySettings(QSettings& s); - std::optional getToolbarSize() const; - std::optional getToolbarButtonStyle() const; - std::optional getMenubarVisible() const; + void setMenubarVisible(bool b); + + bool restoreToolbars(QMainWindow* w) const; + void saveToolbars(const QMainWindow* w); + std::optional getStatusbarVisible() const; - std::optional getMainSplitterState() const; + void setStatusbarVisible(bool b); + std::optional getFiltersVisible() const; QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); std::optional getMainWindowMonitor() const; + void centerOnMainWindowMonitor(QWidget* w); + void saveMainWindowMonitor(const QMainWindow* w); + void setDockSize(const QString& name, int size); std::optional getDockSize(const QString& name) const; @@ -215,6 +223,9 @@ public: void saveState(const QToolBar* toolbar); bool restoreState(QToolBar* toolbar) const; + void saveState(const QSplitter* splitter); + bool restoreState(QSplitter* splitter) const; + /** * retrieve the directory where profiles stored (with native separators) -- cgit v1.3.1 From a5cb39aaf44b1f84003fb2ec2d36f07bf28916e4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 08:02:14 -0400 Subject: moved all geometry save, restore and reset to GeometrySettings changed reset button in settings to restart immediately --- src/browserdialog.cpp | 3 +- src/main.cpp | 12 ++++-- src/mainwindow.cpp | 75 +++++++++++-------------------------- src/mainwindow.h | 1 - src/overwriteinfodialog.cpp | 4 +- src/settings.cpp | 79 ++++++++++++++++++++++++++------------- src/settings.h | 39 +++++++++---------- src/settingsdialog.cpp | 11 +----- src/settingsdialog.h | 2 - src/settingsdialog.ui | 3 -- src/settingsdialogworkarounds.cpp | 16 ++++++-- 11 files changed, 121 insertions(+), 124 deletions(-) (limited to 'src/main.cpp') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 73a6a2d0..70da0b9c 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -72,7 +72,7 @@ BrowserDialog::~BrowserDialog() void BrowserDialog::closeEvent(QCloseEvent *event) { -// m_AccessManager->showCookies(); + Settings::instance().geometry().saveGeometry(this); QDialog::closeEvent(event); } @@ -126,6 +126,7 @@ void BrowserDialog::urlChanged(const QUrl &url) void BrowserDialog::openUrl(const QUrl &url) { if (isHidden()) { + Settings::instance().geometry().restoreGeometry(this); show(); } openInNewTab(url); diff --git a/src/main.cpp b/src/main.cpp index 8eee41e4..6d4108fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -718,6 +718,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, } int res = 1; + { // scope to control lifetime of mainwindow // set up main window and its data structures MainWindow mainWindow(settings, organizer, pluginContainer); @@ -743,17 +744,20 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); - const auto ret = application.exec(); + res = application.exec(); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(nullptr); - - return ret; } + + settings.geometry().resetIfNeeded(); + return res; + } catch (const std::exception &e) { reportError(e.what()); - return 1; } + + return 1; } int doCoreDump(env::CoreDumpTypes type) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6e6e3d22..28e1de2e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -366,9 +366,11 @@ MainWindow::MainWindow(Settings &settings initDownloadView(); - const bool pluginListAdjusted = settings.restoreState(ui->espList->header()); - settings.restoreState(ui->dataTree->header()); - settings.restoreState(ui->downloadView->header()); + const bool pluginListAdjusted = + settings.geometry().restoreState(ui->espList->header()); + + settings.geometry().restoreState(ui->dataTree->header()); + settings.geometry().restoreState(ui->downloadView->header()); ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); @@ -575,7 +577,7 @@ void MainWindow::setupModList() ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - if (m_OrganizerCore.settings().restoreState(ui->modList->header())) { + if (m_OrganizerCore.settings().geometry().restoreState(ui->modList->header())) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { int sectionSize = ui->modList->header()->sectionSize(column); @@ -1417,12 +1419,6 @@ void MainWindow::cleanup() m_MetaSave.waitForFinished(); } - -void MainWindow::setBrowserGeometry(const QByteArray &geometry) -{ - m_IntegratedBrowser.restoreGeometry(geometry); -} - void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) { // don't display the widget if the main window doesn't have focus @@ -2259,10 +2255,10 @@ void MainWindow::activateProxy(bool activate) void MainWindow::readSettings(const Settings& settings) { - settings.restoreGeometry(this); - settings.restoreState(this); + settings.geometry().restoreGeometry(this); + settings.geometry().restoreState(this); settings.geometry().restoreToolbars(this); - settings.restoreState(ui->splitter); + settings.geometry().restoreState(ui->splitter); if (auto v=settings.geometry().getMenubarVisible()) { showMenuBar(*v); @@ -2340,38 +2336,22 @@ void MainWindow::storeSettings(Settings& s) { settings.setValue("selected_executable", ui->executablesListBox->currentIndex()); - if (settings.value("reset_geometry", false).toBool()) { - settings.remove("window_geometry"); - settings.remove("window_state"); - settings.remove("toolbar_size"); - settings.remove("toolbar_button_style"); - settings.remove("menubar_visible"); - settings.remove("window_split"); - settings.remove("window_monitor"); - settings.remove("filters_visible"); - settings.remove("browser_geometry"); - settings.remove("geometry"); - settings.remove("reset_geometry"); - } else { - s.saveState(this); - s.saveGeometry(this); - - s.geometry().setMenubarVisible(m_menuBarVisible); - s.geometry().saveToolbars(this); - s.geometry().setStatusbarVisible(m_statusBarVisible); - s.saveState(ui->splitter); - s.geometry().saveMainWindowMonitor(this); + s.geometry().saveState(this); + s.geometry().saveGeometry(this); - settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); - settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveToolbars(this); + s.geometry().setStatusbarVisible(m_statusBarVisible); + s.geometry().saveState(ui->splitter); + s.geometry().saveMainWindowMonitor(this); + s.geometry().setFiltersVisible(ui->displayCategoriesBtn->isChecked()); - s.saveState(ui->espList->header()); - s.saveState(ui->dataTree->header()); - s.saveState(ui->downloadView->header()); - s.saveState(ui->modList->header()); + s.geometry().saveState(ui->espList->header()); + s.geometry().saveState(ui->dataTree->header()); + s.geometry().saveState(ui->downloadView->header()); + s.geometry().saveState(ui->modList->header()); - DockFixer::save(this, s); - } + DockFixer::save(this, s); } ILockedWaitingForProcess* MainWindow::lock() @@ -6489,7 +6469,6 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe void MainWindow::on_bossButton_clicked() { - std::string reportURL; std::string errorMessages; //m_OrganizerCore.currentProfile()->writeModlistNow(); @@ -6637,16 +6616,6 @@ void MainWindow::on_bossButton_clicked() if (success) { m_DidUpdateMasterList = true; - if (reportURL.length() > 0) { - m_IntegratedBrowser.setWindowTitle("LOOT Report"); - QString report(reportURL.c_str()); - QStringList temp = report.split("?"); - QUrl url = QUrl::fromLocalFile(temp.at(0)); - if (temp.size() > 1) { - url.setQuery(temp.at(1).toUtf8()); - } - m_IntegratedBrowser.openUrl(url); - } m_OrganizerCore.refreshESPList(false); m_OrganizerCore.savePluginList(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 946a341b..8542dc8a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -293,7 +293,6 @@ private: static void setupNetworkProxy(bool activate); void activateProxy(bool activate); - void setBrowserGeometry(const QByteArray &geometry); bool createBackup(const QString &filePath, const QDateTime &time); QString queryRestore(const QString &filePath); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 47416311..fe1d8825 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -106,13 +106,13 @@ OverwriteInfoDialog::~OverwriteInfoDialog() void OverwriteInfoDialog::showEvent(QShowEvent* e) { - Settings::instance().restoreGeometry(this); + Settings::instance().geometry().restoreGeometry(this); QDialog::showEvent(e); } void OverwriteInfoDialog::done(int r) { - Settings::instance().saveGeometry(this); + Settings::instance().geometry().saveGeometry(this); QDialog::done(r); } diff --git a/src/settings.cpp b/src/settings.cpp index a3d12070..db6cecdf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -884,6 +884,7 @@ void Settings::dump() const m_Settings.endGroup(); } + QString widgetNameWithTopLevel(const QWidget* widget) { QStringList components; @@ -941,12 +942,46 @@ QString stateSettingName(const Widget* widget) return "geometry/" + widgetName(widget) + "_state"; } -void Settings::saveGeometry(const QWidget* w) + +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s), m_Reset(false) +{ +} + +void GeometrySettings::requestReset() +{ + m_Reset = true; +} + +void GeometrySettings::resetIfNeeded() +{ + if (!m_Reset) { + return; + } + + m_Settings.beginGroup("geometry"); + m_Settings.remove(""); + m_Settings.endGroup(); + + /*settings.remove("window_geometry"); + settings.remove("window_state"); + settings.remove("toolbar_size"); + settings.remove("toolbar_button_style"); + settings.remove("menubar_visible"); + settings.remove("window_split"); + settings.remove("window_monitor"); + settings.remove("filters_visible"); + settings.remove("browser_geometry"); + settings.remove("geometry"); + settings.remove("reset_geometry");*/ +} + +void GeometrySettings::saveGeometry(const QWidget* w) { m_Settings.setValue(geoSettingName(w), w->saveGeometry()); } -bool Settings::restoreGeometry(QWidget* w) const +bool GeometrySettings::restoreGeometry(QWidget* w) const { if (auto v=getOptional(m_Settings, geoSettingName(w))) { w->restoreGeometry(*v); @@ -956,12 +991,12 @@ bool Settings::restoreGeometry(QWidget* w) const return false; } -void Settings::saveState(const QMainWindow* w) +void GeometrySettings::saveState(const QMainWindow* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QMainWindow* w) const +bool GeometrySettings::restoreState(QMainWindow* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -971,12 +1006,12 @@ bool Settings::restoreState(QMainWindow* w) const return false; } -void Settings::saveState(const QHeaderView* w) +void GeometrySettings::saveState(const QHeaderView* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QHeaderView* w) const +bool GeometrySettings::restoreState(QHeaderView* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -986,12 +1021,12 @@ bool Settings::restoreState(QHeaderView* w) const return false; } -void Settings::saveState(const QSplitter* w) +void GeometrySettings::saveState(const QSplitter* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QSplitter* w) const +bool GeometrySettings::restoreState(QSplitter* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -1001,12 +1036,6 @@ bool Settings::restoreState(QSplitter* w) const return false; } - -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s) -{ -} - bool GeometrySettings::restoreToolbars(QMainWindow* w) const { const auto size = getOptional(m_Settings, "toolbar_size"); @@ -1068,6 +1097,11 @@ std::optional GeometrySettings::getFiltersVisible() const return getOptional(m_Settings, "filters_visible"); } +void GeometrySettings::setFiltersVisible(bool b) +{ + m_Settings.setValue("filters_visible", b); +} + QStringList GeometrySettings::getModInfoTabOrder() const { QStringList v; @@ -1106,7 +1140,7 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) std::optional GeometrySettings::getMainWindowMonitor() const { - return getOptional(m_Settings, "geometry/window_monitor"); + return getOptional(m_Settings, "geometry/MainWindow_monitor"); } void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) @@ -1128,34 +1162,29 @@ void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) if (auto* handle=w->windowHandle()) { if (auto* screen = handle->screen()) { const int screenId = QGuiApplication::screens().indexOf(screen); - m_Settings.setValue("geometry/window_monitor", screenId); + m_Settings.setValue("geometry/MainWindow_monitor", screenId); } } } void GeometrySettings::setDockSize(const QString& name, int size) { - m_Settings.setValue("geometry/" + name + "_size", size); + m_Settings.setValue("geometry/MainWindow_docks_" + 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"); + return getOptional(m_Settings, "geometry/MainWindow_docks_" + name + "_size"); } GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) : m_settings(s), m_dialog(dialog) { - m_settings.restoreGeometry(m_dialog); + m_settings.geometry().restoreGeometry(m_dialog); } GeometrySaver::~GeometrySaver() { - m_settings.saveGeometry(m_dialog); + m_settings.geometry().saveGeometry(m_dialog); } diff --git a/src/settings.h b/src/settings.h index bbf008f0..9ae58803 100644 --- a/src/settings.h +++ b/src/settings.h @@ -51,6 +51,24 @@ class GeometrySettings public: GeometrySettings(QSettings& s); + void requestReset(); + void resetIfNeeded(); + + void saveGeometry(const QWidget* w); + bool restoreGeometry(QWidget* w) const; + + void saveState(const QMainWindow* window); + bool restoreState(QMainWindow* window) const; + + void saveState(const QHeaderView* header); + bool restoreState(QHeaderView* header) const; + + void saveState(const QToolBar* toolbar); + bool restoreState(QToolBar* toolbar) const; + + void saveState(const QSplitter* splitter); + bool restoreState(QSplitter* splitter) const; + std::optional getMenubarVisible() const; void setMenubarVisible(bool b); @@ -61,6 +79,7 @@ public: void setStatusbarVisible(bool b); std::optional getFiltersVisible() const; + void setFiltersVisible(bool b); QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); @@ -73,10 +92,9 @@ public: std::optional getDockSize(const QString& name) const; - std::optional isCategoryListVisible() const; - private: QSettings& m_Settings; + bool m_Reset; }; @@ -210,23 +228,6 @@ public: GeometrySettings& geometry(); const GeometrySettings& geometry() const; - - void saveGeometry(const QWidget* w); - bool restoreGeometry(QWidget* w) const; - - void saveState(const QMainWindow* window); - bool restoreState(QMainWindow* window) const; - - void saveState(const QHeaderView* header); - bool restoreState(QHeaderView* header) const; - - void saveState(const QToolBar* toolbar); - bool restoreState(QToolBar* toolbar) const; - - void saveState(const QSplitter* splitter); - bool restoreState(QSplitter* splitter) const; - - /** * retrieve the directory where profiles stored (with native separators) **/ diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index fbd9ecd1..d74507c9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -34,7 +34,6 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti , ui(new Ui::SettingsDialog) , m_settings(settings) , m_PluginContainer(pluginContainer) - , m_GeometriesReset(false) , m_keyChanged(false) { ui->setupUi(this); @@ -101,10 +100,7 @@ int SettingsDialog::exec() if (getApiKeyChanged()) { restartNeeded = true; } - if (getResetGeometries()) { - restartNeeded = true; - qsettings.setValue("reset_geometry", true); - } + if (restartNeeded) { if (QMessageBox::question(nullptr, tr("Restart Mod Organizer?"), @@ -156,11 +152,6 @@ void SettingsDialog::accept() TutorableDialog::accept(); } -bool SettingsDialog::getResetGeometries() -{ - return ui->resetGeometryBtn->isChecked(); -} - bool SettingsDialog::getApiKeyChanged() { return m_keyChanged; diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 03bba7cf..efc4a095 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -71,7 +71,6 @@ public: // temp Ui::SettingsDialog *ui; bool m_keyChanged; - bool m_GeometriesReset; PluginContainer *m_PluginContainer; int exec() override; @@ -81,7 +80,6 @@ public slots: public: bool getApiKeyChanged(); - bool getResetGeometries(); private: Settings* m_settings; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index e011542e..e7676387 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1233,9 +1233,6 @@ programs you are intentionally running. Reset Window Geometries - - true - diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 9ac46ac1..fc859289 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -26,8 +26,6 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo ui->lockGUIBox->setChecked(m_parent->lockGUI()); ui->enableArchiveParsingBox->setChecked(m_parent->archiveParsing()); - ui->resetGeometryBtn->setChecked(m_parent->directInterface().value("reset_geometry", false).toBool()); - setExecutableBlacklist(m_parent->executablesBlacklist()); QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); @@ -89,6 +87,16 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() { - m_dialog.m_GeometriesReset = true; - ui->resetGeometryBtn->setChecked(true); + const auto caption = QObject::tr("Restart Mod Organizer?"); + const auto text = QObject::tr( + "In order to reset the geometry, Mod Organizer must be restarted.\n" + "Restart now?"); + + const auto res = QMessageBox::question( + nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel); + + if (res == QMessageBox::Yes) { + m_parent->geometry().requestReset(); + qApp->exit(INT_MAX); + } } -- cgit v1.3.1 From 0374291a3451c464fb27e53077da42ad21c27cd6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 09:00:31 -0400 Subject: StatusBar now inherits from QStatusBar to handle hide/show events merged settings into saveVisibility() and restoreVisibility() call MainWindow::storeSettings() earlier so widget visibility is still valid --- src/iuserinterface.h | 5 - src/main.cpp | 2 - src/mainwindow.cpp | 78 +- src/mainwindow.h | 15 +- src/mainwindow.ui | 2129 +++++++++++++++++++++++++------------------------ src/organizercore.cpp | 4 - src/settings.cpp | 96 +-- src/settings.h | 13 +- src/statusbar.cpp | 65 +- src/statusbar.h | 14 +- 10 files changed, 1200 insertions(+), 1221 deletions(-) (limited to 'src/main.cpp') diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 7205f982..a309ed9b 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -10,14 +10,9 @@ #include -class Settings; - class IUserInterface { public: - - 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; virtual void registerModPage(MOBase::IPluginModPage *modPage) = 0; diff --git a/src/main.cpp b/src/main.cpp index 6d4108fa..aa781c19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -736,8 +736,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); - mainWindow.readSettings(settings); - log::debug("displaying main window"); mainWindow.show(); mainWindow.activateWindow(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 28e1de2e..7e471d24 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -285,8 +285,6 @@ MainWindow::MainWindow(Settings &settings : QMainWindow(parent) , ui(new Ui::MainWindow) , m_WasVisible(false) - , m_menuBarVisible(true) - , m_statusBarVisible(true) , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) @@ -312,7 +310,7 @@ MainWindow::MainWindow(Settings &settings QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.getCacheDirectory()); ui->setupUi(this); - m_statusBar.reset(new StatusBar(statusBar(), ui)); + ui->statusBar->setup(ui); { auto* ni = NexusInterface::instance(&m_PluginContainer); @@ -336,7 +334,7 @@ MainWindow::MainWindow(Settings &settings // in the rare case where the user restarts MO through the settings, this // will correctly pick up the previous values updateWindowTitle(ni->getAPIUserAccount()); - m_statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); + ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } languageChange(settings.language()); @@ -708,7 +706,7 @@ void MainWindow::updateWindowTitle(const APIUserAccount& user) void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user) { - m_statusBar->setAPI(stats, user); + ui->statusBar->setAPI(stats, user); } @@ -902,7 +900,7 @@ QMenu* MainWindow::createPopupMenu() void MainWindow::on_actionMainMenuToggle_triggered() { - showMenuBar(!ui->menuBar->isVisible()); + ui->menuBar->setVisible(!ui->menuBar->isVisible()); } void MainWindow::on_actionToolBarMainToggle_triggered() @@ -912,7 +910,7 @@ void MainWindow::on_actionToolBarMainToggle_triggered() void MainWindow::on_actionStatusBarToggle_triggered() { - showStatusBar(!ui->statusBar->isVisible()); + ui->statusBar->setVisible(!ui->statusBar->isVisible()); } void MainWindow::on_actionToolBarSmallIcons_triggered() @@ -964,36 +962,6 @@ void MainWindow::setToolbarButtonStyle(Qt::ToolButtonStyle s) } } -void MainWindow::showMenuBar(bool b) -{ - ui->menuBar->setVisible(b); - m_menuBarVisible = b; -} - -void MainWindow::showStatusBar(bool b) -{ - ui->statusBar->setVisible(b); - m_statusBarVisible = b; - - // the central widget typically has no bottom padding because the status bar - // is more than enough, but when it's hidden, the bottom widget (currently - // the log) touches the bottom border of the window, which looks ugly - // - // when hiding the statusbar, the central widget is given the same border - // margin as it has on the top (which is typically 6, as it's the default from - // the qt designer) - - auto m = ui->centralWidget->layout()->contentsMargins(); - - if (b) { - m.setBottom(0); - } else { - m.setBottom(m.top()); - } - - ui->centralWidget->layout()->setContentsMargins(m); -} - void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos) { // this allows for getting the context menu even if both the menubar and all @@ -1075,8 +1043,8 @@ void MainWindow::updateProblemsButton() } // updating the status bar, may be null very early when MO is starting - if (m_statusBar) { - m_statusBar->setNotifications(numProblems > 0); + if (ui->statusBar) { + ui->statusBar->setNotifications(numProblems > 0); } } @@ -1319,6 +1287,8 @@ void MainWindow::hookUpWindowTutorials() void MainWindow::showEvent(QShowEvent *event) { + readSettings(m_OrganizerCore.settings()); + refreshFilters(); QMainWindow::showEvent(event); @@ -1378,7 +1348,10 @@ void MainWindow::closeEvent(QCloseEvent* event) { if (!confirmExit()) { event->ignore(); + return; } + + storeSettings(m_OrganizerCore.settings()); } bool MainWindow::confirmExit() @@ -2259,17 +2232,12 @@ void MainWindow::readSettings(const Settings& settings) settings.geometry().restoreState(this); settings.geometry().restoreToolbars(this); settings.geometry().restoreState(ui->splitter); - - if (auto v=settings.geometry().getMenubarVisible()) { - showMenuBar(*v); - } - - if (auto v=settings.geometry().getStatusbarVisible()) { - showStatusBar(*v); - } + settings.geometry().restoreVisibility(ui->menuBar); + settings.geometry().restoreVisibility(ui->statusBar); { - auto v = settings.geometry().getFiltersVisible().value_or(false); + settings.geometry().restoreVisibility(ui->categoriesGroup, false); + const auto v = ui->categoriesGroup->isVisible(); setCategoryListVisible(v); ui->displayCategoriesBtn->setChecked(v); } @@ -2339,12 +2307,12 @@ void MainWindow::storeSettings(Settings& s) { s.geometry().saveState(this); s.geometry().saveGeometry(this); - s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveVisibility(ui->menuBar); + s.geometry().saveVisibility(ui->statusBar); s.geometry().saveToolbars(this); - s.geometry().setStatusbarVisible(m_statusBarVisible); s.geometry().saveState(ui->splitter); s.geometry().saveMainWindowMonitor(this); - s.geometry().setFiltersVisible(ui->displayCategoriesBtn->isChecked()); + s.geometry().saveVisibility(ui->categoriesGroup); s.geometry().saveState(ui->espList->header()); s.geometry().saveState(ui->dataTree->header()); @@ -2606,7 +2574,7 @@ void MainWindow::setESPListSorting(int index) void MainWindow::refresher_progress(int percent) { setEnabled(percent == 100); - m_statusBar->setProgress(percent); + ui->statusBar->setProgress(percent); } void MainWindow::directory_refreshed() @@ -5216,7 +5184,7 @@ void MainWindow::on_actionSettings_triggered() activateProxy(settings.useProxy()); } - m_statusBar->checkSettings(m_OrganizerCore.settings()); + ui->statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); m_OrganizerCore.setLogLevel(settings.logLevel()); @@ -5525,7 +5493,7 @@ void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); ui->actionUpdate->setToolTip(tr("Update available")); - m_statusBar->setUpdateAvailable(true); + ui->statusBar->setUpdateAvailable(true); } @@ -6858,7 +6826,7 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) // if the menubar is hidden, pressing Alt will make it visible if (event->key() == Qt::Key_Alt) { if (!ui->menuBar->isVisible()) { - showMenuBar(true); + ui->menuBar->show(); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 8542dc8a..a905a163 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -40,7 +40,6 @@ class Executable; class CategoryFactory; class LockedDialogBase; class OrganizerCore; -class StatusBar; class PluginListSortProxy; namespace BSA { class Archive; } @@ -118,8 +117,6 @@ public: QWidget *parent = 0); ~MainWindow(); - void storeSettings(Settings& settings) override; - void readSettings(const Settings& settings); void processUpdates(Settings& settings); virtual ILockedWaitingForProcess* lock() override; @@ -331,12 +328,6 @@ private: bool m_WasVisible; - // this has to be remembered because by the time storeSettings() is called, - // the window is closed and the all bars are hidden - bool m_menuBarVisible, m_statusBarVisible; - - std::unique_ptr m_statusBar; - // last separator on the toolbar, used to add spacer for right-alignment and // as an insert point for executables QAction* m_linksSeparator; @@ -685,11 +676,9 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); + void storeSettings(Settings& settings); + void readSettings(const Settings& settings); void setupModList(); - void showMenuBar(bool b); - void showStatusBar(bool b); }; - - #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index e9910b83..02c6dec0 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -48,1239 +48,1239 @@ - + + + - - - - - Categories - - - + + + Categories + + + + 0 + + + 3 + + + 7 + + + 3 + + + 1 + + + + + + 120 + 0 + + + + + 214 + 16777215 + + + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + 0 - - 3 + + true - - 7 + + false + + + + 1 + + + + + + + + false - - 3 + + + 0 + 0 + - - 1 + + + 0 + 25 + - - - - - 120 - 0 - - - - - 214 - 16777215 - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - 0 - - - true - - - false - - - - 1 - - - - - - - - false - - - - 0 - 0 - - - - - 0 - 25 - - - - Clear - - - true - - - - - - - - 0 - 0 - - - - - - - If checked, only mods that match all selected categories are displayed. - - - And - - - true - - - - - - - If checked, all mods that match at least one of the selected categories are displayed. - - - Or - - - - - - - - - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - 2 - - - - - - - - 0 - 0 - - - - Profile - - - profileBox - - - - - - - Pick a module collection - - - <!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;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> -<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;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 16777215 - 16777215 - - - - Open list options... - - - Refresh list. This is usually not necessary unless you modified data outside the program. - - - - - - - :/MO/gui/settings:/MO/gui/settings - - - - 16 - 16 - - - - - - - - Show Open Folders menu... - - - - - - - :/MO/gui/open_folder:/MO/gui/open_folder - - - + + Clear + + + true + + + + + + + + 0 + 0 + + + - + - Restore Backup... + If checked, only mods that match all selected categories are displayed. - + And - - - :/MO/gui/restore:/MO/gui/restore + + true - + - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup + If checked, all mods that match at least one of the selected categories are displayed. - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 5 - - - QLCDNumber::Flat + Or + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 2 + + + + + + + + 0 + 0 + + + + Profile + + + profileBox + + - - + + + Pick a module collection + + + <!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;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<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;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> + + + + + + + Qt::Horizontal + + - 330 - 400 + 40 + 20 - - Qt::CustomContextMenu + + + + + + + 16777215 + 16777215 + - List of available mods. + Open list options... - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + Refresh list. This is usually not necessary unless you modified data outside the program. - + + + + + + :/MO/gui/settings:/MO/gui/settings + + + + 16 + 16 + + + + + + + + Show Open Folders menu... + + + + + + + :/MO/gui/open_folder:/MO/gui/open_folder + + + + + + + Restore Backup... + + - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + :/MO/gui/restore:/MO/gui/restore - - true + + + + + + Create Backup - - true + + - - QAbstractItemView::DragDrop + + + :/MO/gui/backup:/MO/gui/backup - - Qt::MoveAction + + + + + + Active: - - true + + + + + + + 0 + 26 + - - QAbstractItemView::ExtendedSelection + + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - QAbstractItemView::SelectRows + + QFrame::Sunken - - 20 + + 5 - - true + + QLCDNumber::Flat - - true + + + + + + + + + 330 + 400 + + + + Qt::CustomContextMenu + + + List of available mods. + + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + + + + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + 20 + + + true + + + true + + + true + + + false + + + 35 + + + true + + + false + + + + + + + + + + 20 + 16777215 + - - true + + x - - false + + + 20 + 20 + - - 35 - - + true - - + + + + + + + + 0 + 0 + + + + Filter + + + + + + + + 8 + true + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + + 95 + 0 + + + false - + + + Qt::RightToLeft + + + border:1px solid #ff0000; + + + Clear all Filters + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + 12 + 12 + + - - - - - - 20 - 16777215 - - - - x - - - - 20 - 20 - - - - true - - - - - - - - 0 - 0 - - - - Filter - - - - - - - - 8 - true - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - - 95 - 0 - - - - false - - - Qt::RightToLeft - - - border:1px solid #ff0000; - - - Clear all Filters - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - 12 - 12 - - - + + + + 220 + 0 + + + + Qt::ClickFocus + + + + No groups + - - - - 220 - 0 - - - - Qt::ClickFocus - - - - No groups - - - - - Categories - - - - - Nexus IDs - - - + + Categories + - - - - 220 - 0 - - - - Filter - - + + Nexus IDs + - + - - - - - - + + + + 220 + 0 + + + + Filter + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 9 + 75 + true + + + + Pick a program to run. + + + <!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;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<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;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + + + + 32 + 32 + + + + false + + + + + - + - + 0 0 - 0 - 40 + 120 + 0 + + + + + 16777215 + 16777215 - 9 + 10 75 true - Pick a program to run. + Run program <!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;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<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;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> +<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;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + + + + + + Run + + + + :/MO/gui/run:/MO/gui/run - 32 - 32 + 36 + 36 - - false - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 16777215 - 16777215 - - - - - 10 - 75 - true - - - - Run program - - - <!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;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - - - - - - Run - - - - :/MO/gui/run:/MO/gui/run - - - - 36 - 36 - - - - - - - - - 0 - 0 - - - - - 140 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - Create a shortcut in your start menu or on the desktop to the specified program - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + 0 + 0 + + + + + 140 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + Create a shortcut in your start menu or on the desktop to the specified program + + + <!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;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - - - Shortcut - - - - :/MO/gui/link:/MO/gui/link - - - - + + + Shortcut + + + + :/MO/gui/link:/MO/gui/link + + - - - - - - - 340 - 250 - - - - - 16777215 - 16777215 - + + + + + + + + + 340 + 250 + + + + + 16777215 + 16777215 + + + + Qt::NoContextMenu + + + QTabWidget::Rounded + + + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Plugins + + + + 6 - - Qt::NoContextMenu + + 6 - - QTabWidget::Rounded + + 6 - + 0 - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Plugins - - - - 6 + + + + + + true + + + Sort + + + + :/MO/gui/sort:/MO/gui/sort + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + 16 + 16 + + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + + + Active: + + + + + + + + 0 + 26 + + + + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + QFrame::Sunken + + + 4 + + + QLCDNumber::Flat + + + + + + + + + + 250 + 250 + + + + Qt::CustomContextMenu + + + List of available esp/esm files + + + <!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;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + false + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + 0 + + + true - - 6 + + false - - 6 + + true - - 0 + + false + + false + + + + + - - - - - true - - - Sort - - - - :/MO/gui/sort:/MO/gui/sort - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Restore Backup... - - - - - - - :/MO/gui/restore:/MO/gui/restore - - - - 16 - 16 - - - - - - - - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup - - - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 4 - - - QLCDNumber::Flat - - - - - - - - - - 250 - 250 - + + + - - Qt::CustomContextMenu + + Filter + + + + + + + + + false + + + Archives + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + - List of available esp/esm files - - - <!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;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked - - - true - - - false - - - QAbstractItemView::InternalMove - - - Qt::MoveAction - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - 0 - - - true + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - false + + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + true - - false - - - false - - - - - - - - - - Filter - - - - - - - - - false - - - Archives - - - - 6 + + + + + Qt::CustomContextMenu - - 6 + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - - 6 + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! + + BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - 6 + + false + + false + + + false + + + 20 + + + true + + + 1 + + + + + + + + Data + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + refresh data-directory overview + + + Refresh the overview. This may take a moment. + + + Refresh + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + + - - - - - <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - - <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - - - true - - - - - - - + Qt::CustomContextMenu - - List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. - By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! - - BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - - false - - - false - - - false + This is an overview of your data directory as visible to the game (and tools). - - 20 - - + true - - 1 + + true + + 400 + + + + File + + + + + Mod + + - - - - Data - - - - 6 - - - 6 - - - 6 - - - 6 - + + + - + - refresh data-directory overview + Filters the above list so that only conflicts are displayed. - Refresh the overview. This may take a moment. + Filters the above list so that only conflicts are displayed. - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + Show only conflicts - - - - - Qt::CustomContextMenu - - - This is an overview of your data directory as visible to the game (and tools). - - - true - - - true - - - 400 - - - - File - - - - - Mod - - - - - - - - - - - - Filters the above list so that only conflicts are displayed. - - - Filters the above list so that only conflicts are displayed. - - - Show only conflicts - - - - - - - Filters the above list so that files from archives are not shown - - - - - - Filters the above list so that files from archives are not shown - - - Show files from Archives - - - - + + + Filters the above list so that files from archives are not shown + + + + + + Filters the above list so that files from archives are not shown + + + Show files from Archives + + - - - - Saves - - - - 6 + + + + + + Saves + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Qt::CustomContextMenu + + + + + + <!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;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<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;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows - - 6 + + + + + + + Downloads + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Refresh downloads view - - 6 + + Refresh - - 6 + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + - + + + + 320 + 0 + + Qt::CustomContextMenu + + true + - + - <!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;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<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;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. + + + Qt::ScrollBarAlwaysOn + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ScrollPerPixel + + + 0 - - QAbstractItemView::ExtendedSelection + + false - - QAbstractItemView::SelectRows + + true - - - - Downloads - - - - 2 - - - 2 - - - 2 - - - 2 - + + + - - - Refresh downloads view - + - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + Show Hidden - - - - - - 320 - 0 - - - - Qt::CustomContextMenu - - - true - - - - - - This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - - - Qt::ScrollBarAlwaysOn - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - true - - - QAbstractItemView::ScrollPerPixel - - - 0 - - - false - - - true - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + - - - - - Show Hidden - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - + + + Filter + + - - - - - - - - + + + + + + + + + + @@ -1320,7 +1320,7 @@ p, li { white-space: pre-wrap; } - + @@ -1790,6 +1790,11 @@ p, li { white-space: pre-wrap; } QTreeView
loglist.h
+ + StatusBar + QStatusBar +
statusbar.h
+
diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2d11dafd..a2b0fd69 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -335,10 +335,6 @@ OrganizerCore::~OrganizerCore() void OrganizerCore::storeSettings() { - if (m_UserInterface != nullptr) { - m_UserInterface->storeSettings(m_Settings); - } - if (m_CurrentProfile != nullptr) { m_Settings.setSelectedProfileName(m_CurrentProfile->name()); } diff --git a/src/settings.cpp b/src/settings.cpp index db6cecdf..06b4446a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -942,6 +942,12 @@ QString stateSettingName(const Widget* widget) return "geometry/" + widgetName(widget) + "_state"; } +template +QString visibilitySettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_visibility"; +} + GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s), m_Reset(false) @@ -962,18 +968,6 @@ void GeometrySettings::resetIfNeeded() m_Settings.beginGroup("geometry"); m_Settings.remove(""); m_Settings.endGroup(); - - /*settings.remove("window_geometry"); - settings.remove("window_state"); - settings.remove("toolbar_size"); - settings.remove("toolbar_button_style"); - settings.remove("menubar_visible"); - settings.remove("window_split"); - settings.remove("window_monitor"); - settings.remove("filters_visible"); - settings.remove("browser_geometry"); - settings.remove("geometry"); - settings.remove("reset_geometry");*/ } void GeometrySettings::saveGeometry(const QWidget* w) @@ -1036,15 +1030,32 @@ bool GeometrySettings::restoreState(QSplitter* w) const return false; } -bool GeometrySettings::restoreToolbars(QMainWindow* w) const +void GeometrySettings::saveVisibility(const QWidget* w) { - const auto size = getOptional(m_Settings, "toolbar_size"); - const auto style = getOptional(m_Settings, "toolbar_button_style"); + m_Settings.setValue(visibilitySettingName(w), w->isVisible()); +} - if (!size && !style) { - return false; +bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const +{ + auto v = getOptional(m_Settings, visibilitySettingName(w)); + if (!v) { + v = def; + } + + if (v) { + w->setVisible(*v); + return true; } + return false; +} + +void GeometrySettings::restoreToolbars(QMainWindow* w) const +{ + // all toolbars have the same size and button style settings + const auto size = getOptional(m_Settings, "toolbar_size"); + const auto style = getOptional(m_Settings, "toolbar_button_style"); + for (auto* tb : w->findChildren()) { if (size) { tb->setIconSize(*size); @@ -1053,53 +1064,28 @@ bool GeometrySettings::restoreToolbars(QMainWindow* w) const if (style) { tb->setToolButtonStyle(static_cast(*style)); } - } - return true; + restoreVisibility(tb); + } } void GeometrySettings::saveToolbars(const QMainWindow* w) { - // all toolbars are identical, just save the first one const auto tbs = w->findChildren(); - if (tbs.isEmpty()) { - return; - } - - const auto* tb = tbs[0]; - - m_Settings.setValue("toolbar_size", tb->iconSize()); - m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); -} - -std::optional GeometrySettings::getMenubarVisible() const -{ - return getOptional(m_Settings, "menubar_visible"); -} - -void GeometrySettings::setMenubarVisible(bool b) -{ - m_Settings.setValue("menubar_visible", b); -} - -std::optional GeometrySettings::getStatusbarVisible() const -{ - return getOptional(m_Settings, "statusbar_visible"); -} -void GeometrySettings::setStatusbarVisible(bool b) -{ - m_Settings.setValue("statusbar_visible", b); -} + // save visibility for all + for (auto* tb : tbs) { + saveVisibility(tb); + } -std::optional GeometrySettings::getFiltersVisible() const -{ - return getOptional(m_Settings, "filters_visible"); -} + // all toolbars have the same size and button style settings, just save the + // first one + if (!tbs.isEmpty()) { + const auto* tb = tbs[0]; -void GeometrySettings::setFiltersVisible(bool b) -{ - m_Settings.setValue("filters_visible", b); + m_Settings.setValue("toolbar_size", tb->iconSize()); + m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); + } } QStringList GeometrySettings::getModInfoTabOrder() const diff --git a/src/settings.h b/src/settings.h index 9ae58803..072b4066 100644 --- a/src/settings.h +++ b/src/settings.h @@ -54,6 +54,7 @@ public: void requestReset(); void resetIfNeeded(); + void saveGeometry(const QWidget* w); bool restoreGeometry(QWidget* w) const; @@ -69,17 +70,13 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; - std::optional getMenubarVisible() const; - void setMenubarVisible(bool b); - bool restoreToolbars(QMainWindow* w) const; - void saveToolbars(const QMainWindow* w); + void saveVisibility(const QWidget* w); + bool restoreVisibility(QWidget* w, std::optional defaultValue={}) const; - std::optional getStatusbarVisible() const; - void setStatusbarVisible(bool b); - std::optional getFiltersVisible() const; - void setFiltersVisible(bool b); + void saveToolbars(const QMainWindow* w); + void restoreToolbars(QMainWindow* w) const; QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); diff --git a/src/statusbar.cpp b/src/statusbar.cpp index e9a6e658..d22010a5 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -3,26 +3,32 @@ #include "settings.h" #include "ui_mainwindow.h" -StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : - m_bar(bar), m_progress(new QProgressBar), - m_notifications(new StatusBarAction(ui->actionNotifications)), - m_update(new StatusBarAction(ui->actionUpdate)), - m_api(new QLabel) +StatusBar::StatusBar(QWidget* parent) : + QStatusBar(parent), ui(nullptr), m_progress(new QProgressBar), + m_notifications(nullptr), m_update(nullptr), m_api(new QLabel) { +} + +void StatusBar::setup(Ui::MainWindow* mainWindowUI) +{ + ui = mainWindowUI; + m_notifications = new StatusBarAction(ui->actionNotifications); + m_update = new StatusBarAction(ui->actionUpdate); + QWidget* spacer1 = new QWidget; spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); spacer1->setHidden(true); spacer1->setVisible(true); - m_bar->addPermanentWidget(spacer1, 0); - m_bar->addPermanentWidget(m_progress); + addPermanentWidget(spacer1, 0); + addPermanentWidget(m_progress); QWidget* spacer2 = new QWidget; spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); spacer2->setHidden(true); spacer2->setVisible(true); - m_bar->addPermanentWidget(spacer2,0); - m_bar->addPermanentWidget(m_notifications); - m_bar->addPermanentWidget(m_update); - m_bar->addPermanentWidget(m_api); + addPermanentWidget(spacer2,0); + addPermanentWidget(m_notifications); + addPermanentWidget(m_update); + addPermanentWidget(m_api); m_progress->setTextVisible(true); @@ -42,7 +48,7 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : "be unable to queue downloads, check updates, parse mod info, or even log " "in. Both pools must be consumed before this happens.")); - m_bar->clearMessage(); + clearMessage(); setProgress(-1); setAPI({}, {}); } @@ -50,10 +56,10 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : void StatusBar::setProgress(int percent) { if (percent < 0 || percent >= 100) { - m_bar->clearMessage(); + clearMessage(); m_progress->setVisible(false); } else { - m_bar->showMessage(QObject::tr("Loading...")); + showMessage(QObject::tr("Loading...")); m_progress->setVisible(true); m_progress->setValue(percent); } @@ -126,6 +132,37 @@ void StatusBar::checkSettings(const Settings& settings) m_api->setVisible(!settings.hideAPICounter()); } +void StatusBar::showEvent(QShowEvent*) +{ + visibilityChanged(true); +} + +void StatusBar::hideEvent(QHideEvent*) +{ + visibilityChanged(false); +} + +void StatusBar::visibilityChanged(bool visible) +{ + // the central widget typically has no bottom padding because the status bar + // is more than enough, but when it's hidden, the bottom widget (currently + // the log) touches the bottom border of the window, which looks ugly + // + // when hiding the statusbar, the central widget is given the same border + // margin as it has on the top (which is typically 6, as it's the default from + // the qt designer) + + auto m = ui->centralWidget->layout()->contentsMargins(); + + if (visible) { + m.setBottom(0); + } else { + m.setBottom(m.top()); + } + + ui->centralWidget->layout()->setContentsMargins(m); +} + StatusBarAction::StatusBarAction(QAction* action) : m_action(action), m_icon(new QLabel), m_text(new QLabel) diff --git a/src/statusbar.h b/src/statusbar.h index 2baf12ee..442b9acf 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -29,10 +29,12 @@ private: }; -class StatusBar +class StatusBar : public QStatusBar { public: - StatusBar(QStatusBar* bar, Ui::MainWindow* ui); + StatusBar(QWidget* parent=nullptr); + + void setup(Ui::MainWindow* ui); void setProgress(int percent); void setNotifications(bool hasNotifications); @@ -40,12 +42,18 @@ public: void setUpdateAvailable(bool b); void checkSettings(const Settings& settings); +protected: + void showEvent(QShowEvent* e); + void hideEvent(QHideEvent* e); + private: - QStatusBar* m_bar; + Ui::MainWindow* ui; QProgressBar* m_progress; StatusBarAction* m_notifications; StatusBarAction* m_update; QLabel* m_api; + + void visibilityChanged(bool visible); }; #endif // MO_STATUSBAR_H -- cgit v1.3.1 From e9dba260cb9548dd5863ac66da18c295f6499b92 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 14:52:02 -0400 Subject: split settings into a bunch of classes removed "get" from the getters that had it --- src/browserdialog.cpp | 2 +- src/downloadlistsortproxy.cpp | 2 +- src/downloadmanager.cpp | 2 +- src/executableslist.cpp | 2 +- src/filedialogmemory.cpp | 4 +- src/main.cpp | 26 +- src/mainwindow.cpp | 121 +-- src/modinfodialog.cpp | 2 +- src/modinfodialogconflicts.cpp | 16 +- src/modinfodialogimages.cpp | 4 +- src/modinfodialognexus.cpp | 2 +- src/modinfooverwrite.cpp | 2 +- src/modinforegular.cpp | 2 +- src/modlist.cpp | 8 +- src/nxmaccessmanager.cpp | 2 +- src/organizercore.cpp | 112 +- src/pluginlist.cpp | 2 +- src/profile.cpp | 8 +- src/profilesdialog.cpp | 4 +- src/settings.cpp | 2075 ++++++++++++++++++++----------------- src/settings.h | 569 ++++++---- src/settingsdialog.cpp | 6 +- src/settingsdialogdiagnostics.cpp | 13 +- src/settingsdialoggeneral.cpp | 28 +- src/settingsdialognexus.cpp | 32 +- src/settingsdialogpaths.cpp | 52 +- src/settingsdialogsteam.cpp | 4 +- src/settingsdialogworkarounds.cpp | 30 +- src/statusbar.cpp | 2 +- src/usvfsconnector.cpp | 4 +- 30 files changed, 1712 insertions(+), 1426 deletions(-) (limited to 'src/main.cpp') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 70da0b9c..72cb8862 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -49,7 +49,7 @@ BrowserDialog::BrowserDialog(QWidget *parent) ui->setupUi(this); m_AccessManager->setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/cookies.dat"))); + QDir::fromNativeSeparators(Settings::instance().paths().cache() + "/cookies.dat"))); Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index 7bda139b..a69993c0 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -110,7 +110,7 @@ bool DownloadListSortProxy::filterAcceptsRow(int sourceRow, const QModelIndex&) if (m_CurrentFilter.length() == 0) { return true; } else if (sourceRow < m_Manager->numTotalDownloads()) { - QString displayedName = Settings::instance().metaDownloads() + QString displayedName = Settings::instance().interface().metaDownloads() ? m_Manager->getDisplayName(sourceRow) : m_Manager->getFileName(sourceRow); return displayedName.contains(m_CurrentFilter, Qt::CaseInsensitive); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index a5dc164c..56238ef3 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1731,7 +1731,7 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int return; } - const auto servers = m_OrganizerCore->settings().getServers(); + const auto servers = m_OrganizerCore->settings().network().servers(); std::sort( resultList.begin(), diff --git a/src/executableslist.cpp b/src/executableslist.cpp index f2df2d6d..dce9181b 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -75,7 +75,7 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) // executables from 2.2.0, see upgradeFromCustom() bool needsUpgrade = false; - for (auto& map : s.getExecutables()) { + for (auto& map : s.executables()) { Executable::Flags flags; if (map["toolbar"].toBool()) diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 96587ac7..8cfeb6b5 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -25,12 +25,12 @@ static std::map g_Cache; void FileDialogMemory::save(Settings& s) { - s.setRecentDirectories(g_Cache); + s.paths().setRecent(g_Cache); } void FileDialogMemory::restore(const Settings& s) { - g_Cache = s.getRecentDirectories(); + g_Cache = s.paths().recent(); } QString FileDialogMemory::getOpenFileName( diff --git a/src/main.cpp b/src/main.cpp index aa781c19..b5568fec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -246,7 +246,7 @@ static bool HaveWriteAccess(const std::wstring &path) QString determineProfile(QStringList &arguments, const Settings &settings) { - auto selectedProfileName = settings.getSelectedProfileName(); + auto selectedProfileName = settings.game().selectedProfileName(); { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); @@ -271,12 +271,12 @@ QString determineProfile(QStringList &arguments, const Settings &settings) MOBase::IPluginGame *selectGame( Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) { - settings.setManagedGameName(game->gameName()); + settings.game().setName(game->gameName()); QString gameDir = gamePath.absolutePath(); game->setGamePath(gameDir); - settings.setManagedGameDirectory(gameDir); + settings.game().setDirectory(gameDir); return game; } @@ -289,7 +289,7 @@ MOBase::IPluginGame *determineCurrentGame( //user has done something odd. //If the game name has been set up, try to use that. - const auto gameName = settings.getManagedGameName(); + const auto gameName = settings.game().name(); const bool gameConfigured = (gameName.has_value() && *gameName != ""); if (gameConfigured) { @@ -299,7 +299,7 @@ MOBase::IPluginGame *determineCurrentGame( return nullptr; } - auto gamePath = settings.getManagedGameDirectory(); + auto gamePath = settings.game().directory(); if (!gamePath || *gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } @@ -320,7 +320,7 @@ 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 auto gamePath = settings.getManagedGameDirectory(); + const auto gamePath = settings.game().directory(); reportError( QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") @@ -570,11 +570,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, log::info("working directory: {}", QDir::currentPath()); Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); - log::getDefault().setLevel(settings.logLevel()); + log::getDefault().setLevel(settings.diagnostics().logLevel()); // 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()); + OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); env::Environment env; @@ -621,7 +621,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QString edition; - if (auto v=settings.getManagedGameEdition()) { + if (auto v=settings.game().edition()) { edition = *v; } else { QStringList editions = game->gameVariants(); @@ -640,7 +640,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, return 1; } else { edition = selection.getChoiceString(); - settings.setManagedGameEdition(edition); + settings.game().setEdition(edition); } } } @@ -702,7 +702,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.activateWindow(); QString apiKey; - if (settings.getNexusApiKey(apiKey)) { + if (settings.nexus().apiKey(apiKey)) { NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } @@ -712,9 +712,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.getStyleName().value_or(""))) { + if (!application.setStyleFile(settings.interface().styleName().value_or(""))) { // disable invalid stylesheet - settings.setStyleName(""); + settings.interface().setStyleName(""); } int res = 1; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42b19cb7..657c1a27 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -222,8 +222,8 @@ MainWindow::MainWindow(Settings &settings { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); - QWebEngineProfile::defaultProfile()->setCachePath(settings.getCacheDirectory()); - QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.getCacheDirectory()); + QWebEngineProfile::defaultProfile()->setCachePath(settings.paths().cache()); + QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache()); ui->setupUi(this); ui->statusBar->setup(ui); @@ -253,7 +253,7 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } - languageChange(settings.language()); + languageChange(settings.interface().language()); m_CategoryFactory.loadCategories(); @@ -1194,7 +1194,7 @@ void MainWindow::hookUpWindowTutorials() QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//WIN")) { QString windowName = firstLine.mid(6).trimmed(); - if (!m_OrganizerCore.settings().isTutorialCompleted(windowName)) { + if (!m_OrganizerCore.settings().interface().isTutorialCompleted(windowName)) { TutorialManager::instance().activateTutorial(windowName, fileName); } } @@ -1225,7 +1225,7 @@ void MainWindow::showEvent(QShowEvent *event) hookUpWindowTutorials(); - if (m_OrganizerCore.settings().getFirstStart()) { + if (m_OrganizerCore.settings().firstStart()) { QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { if (QMessageBox::question(this, tr("Show tutorial?"), @@ -1247,11 +1247,11 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().setFirstStart(false); } - m_OrganizerCore.settings().restoreIndex(ui->groupCombo); + m_OrganizerCore.settings().widgets().restoreIndex(ui->groupCombo); allowListResize(); - m_OrganizerCore.settings().registerAsNXMHandler(false); + m_OrganizerCore.settings().nexus().registerAsNXMHandler(false); m_WasVisible = true; updateProblemsButton(); } @@ -1751,7 +1751,7 @@ bool MainWindow::refreshProfiles(bool selectProfile) profileBox->clear(); profileBox->addItem(QObject::tr("")); - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); @@ -1990,7 +1990,7 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString newItem->setFlags(newItem->flags() & ~(Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable)); newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); newItem->setData(0, Qt::UserRole, false); - if (m_OrganizerCore.settings().forceEnableCoreFiles() + if (m_OrganizerCore.settings().game().forceEnableCoreFiles() && defaultArchives.contains(fileInfo.fileName())) { newItem->setCheckState(0, Qt::Checked); newItem->setDisabled(true); @@ -2140,7 +2140,7 @@ void MainWindow::readSettings(const Settings& settings) { // special case in case someone puts 0 in the INI - auto v = settings.getIndex(ui->executablesListBox); + auto v = settings.widgets().index(ui->executablesListBox); if (!v || v == 0) { v = 1; } @@ -2148,7 +2148,7 @@ void MainWindow::readSettings(const Settings& settings) ui->executablesListBox->setCurrentIndex(*v); } - settings.restoreIndex(ui->groupCombo); + settings.widgets().restoreIndex(ui->groupCombo); { settings.geometry().restoreVisibility(ui->categoriesGroup, false); @@ -2157,7 +2157,7 @@ void MainWindow::readSettings(const Settings& settings) ui->displayCategoriesBtn->setChecked(v); } - if (settings.getUseProxy()) { + if (settings.network().useProxy()) { activateProxy(true); } } @@ -2165,12 +2165,12 @@ void MainWindow::readSettings(const Settings& settings) void MainWindow::processUpdates(Settings& settings) { const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); - const auto lastVersion = settings.getVersion().value_or(earliest); + const auto lastVersion = settings.version().value_or(earliest); const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); settings.processUpdates(currentVersion, lastVersion); - if (!settings.getFirstStart()) { + if (!settings.firstStart()) { if (lastVersion < QVersionNumber(2, 1, 3)) { bool lastHidden = true; for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) { @@ -2222,8 +2222,8 @@ void MainWindow::storeSettings(Settings& s) s.geometry().saveState(ui->downloadView->header()); s.geometry().saveState(ui->modList->header()); - s.saveIndex(ui->groupCombo); - s.saveIndex(ui->executablesListBox); + s.widgets().saveIndex(ui->groupCombo); + s.widgets().saveIndex(ui->executablesListBox); } ILockedWaitingForProcess* MainWindow::lock() @@ -2751,7 +2751,7 @@ void MainWindow::restoreBackup_clicked() ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); if (backupRegEx.indexIn(modInfo->name()) != -1) { QString regName = backupRegEx.cap(1); - QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory())); + QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods())); if (!modDir.exists(regName) || (QMessageBox::question(this, tr("Overwrite?"), tr("This will replace the existing mod \"%1\". Continue?").arg(regName), @@ -2759,7 +2759,7 @@ void MainWindow::restoreBackup_clicked() if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { reportError(tr("failed to remove mod \"%1\"").arg(regName)); } else { - QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory()) + "/" + regName; + QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods()) + "/" + regName; if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); } @@ -3015,7 +3015,7 @@ void MainWindow::untrack_clicked() void MainWindow::windowTutorialFinished(const QString &windowName) { - m_OrganizerCore.settings().setTutorialCompleted(windowName); + m_OrganizerCore.settings().interface().setTutorialCompleted(windowName); } void MainWindow::overwriteClosed(int) @@ -3645,7 +3645,7 @@ void MainWindow::createSeparator_clicked() m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); } - if (auto c=m_OrganizerCore.settings().getPreviousSeparatorColor()) { + if (auto c=m_OrganizerCore.settings().colors().previousSeparatorColor()) { ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); } } @@ -3662,7 +3662,7 @@ void MainWindow::setColor_clicked() if (currentColor.isValid()) { dialog.setCurrentColor(currentColor); } - else if (auto c=settings.getPreviousSeparatorColor()) { + else if (auto c=settings.colors().previousSeparatorColor()) { dialog.setCurrentColor(*c); } @@ -3673,7 +3673,7 @@ void MainWindow::setColor_clicked() if (!currentColor.isValid()) return; - settings.setPreviousSeparatorColor(currentColor); + settings.colors().setPreviousSeparatorColor(currentColor); QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -3710,7 +3710,7 @@ void MainWindow::resetColor_clicked() modInfo->setColor(color); } - m_OrganizerCore.settings().removePreviousSeparatorColor(); + m_OrganizerCore.settings().colors().removePreviousSeparatorColor(); } void MainWindow::createModFromOverwrite() @@ -4184,7 +4184,7 @@ void MainWindow::checkModsForUpdates() NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { @@ -4387,12 +4387,12 @@ void MainWindow::openIniFolder() void MainWindow::openDownloadsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().getDownloadDirectory()); + shell::ExploreFile(m_OrganizerCore.settings().paths().downloads()); } void MainWindow::openModsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().getModDirectory()); + shell::ExploreFile(m_OrganizerCore.settings().paths().mods()); } void MainWindow::openGameFolder() @@ -4758,7 +4758,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addSeparator(); - if (info->getNexusID() > 0 && Settings::instance().endorsementIntegration()) { + if (info->getNexusID() > 0 && Settings::instance().nexus().endorsementIntegration()) { switch (info->endorsedState()) { case ModInfo::ENDORSED_TRUE: { menu.addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); @@ -5007,19 +5007,19 @@ void MainWindow::on_actionSettings_triggered() { Settings &settings = m_OrganizerCore.settings(); - QString oldModDirectory(settings.getModDirectory()); - QString oldCacheDirectory(settings.getCacheDirectory()); - QString oldProfilesDirectory(settings.getProfileDirectory()); - QString oldManagedGameDirectory(settings.getManagedGameDirectory().value_or("")); - bool oldDisplayForeign(settings.displayForeign()); - bool proxy = settings.getUseProxy(); + QString oldModDirectory(settings.paths().mods()); + QString oldCacheDirectory(settings.paths().cache()); + QString oldProfilesDirectory(settings.paths().profiles()); + QString oldManagedGameDirectory(settings.game().directory().value_or("")); + bool oldDisplayForeign(settings.interface().displayForeign()); + bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); - if (oldManagedGameDirectory != settings.getManagedGameDirectory()) { + if (oldManagedGameDirectory != settings.game().directory()) { QMessageBox::about(this, tr("Restarting MO"), tr("Changing the managed game directory requires restarting MO.\n" "Any pending downloads will be paused.\n\n" @@ -5029,28 +5029,28 @@ void MainWindow::on_actionSettings_triggered() } InstallationManager *instManager = m_OrganizerCore.installationManager(); - instManager->setModsDirectory(settings.getModDirectory()); - instManager->setDownloadDirectory(settings.getDownloadDirectory()); + instManager->setModsDirectory(settings.paths().mods()); + instManager->setDownloadDirectory(settings.paths().downloads()); fixCategories(); refreshFilters(); - if (settings.getProfileDirectory() != oldProfilesDirectory) { + if (settings.paths().profiles() != oldProfilesDirectory) { refreshProfiles(); } - if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) { + if (dlManager->getOutputDirectory() != settings.paths().downloads()) { if (dlManager->downloadsInProgress()) { MessageDialog::showMessage(tr("Can't change download directory while " "downloads are in progress!"), this); } else { - dlManager->setOutputDirectory(settings.getDownloadDirectory()); + dlManager->setOutputDirectory(settings.paths().downloads()); } } - if ((settings.getModDirectory() != oldModDirectory) - || (settings.displayForeign() != oldDisplayForeign)) { + if ((settings.paths().mods() != oldModDirectory) + || (settings.interface().displayForeign() != oldDisplayForeign)) { m_OrganizerCore.profileRefresh(); } @@ -5075,18 +5075,19 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.refreshLists(); } - if (settings.getCacheDirectory() != oldCacheDirectory) { - NexusInterface::instance(&m_PluginContainer)->setCacheDirectory(settings.getCacheDirectory()); + if (settings.paths().cache() != oldCacheDirectory) { + NexusInterface::instance(&m_PluginContainer)->setCacheDirectory( + settings.paths().cache()); } - if (proxy != settings.getUseProxy()) { - activateProxy(settings.getUseProxy()); + if (proxy != settings.network().useProxy()) { + activateProxy(settings.network().useProxy()); } ui->statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - m_OrganizerCore.setLogLevel(settings.logLevel()); + m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); @@ -5402,10 +5403,10 @@ void MainWindow::motdReceived(const QString &motd) // internet connection is faster next time if (m_StartTime.secsTo(QTime::currentTime()) < 5) { uint hash = qHash(motd); - if (hash != m_OrganizerCore.settings().getMotDHash()) { + if (hash != m_OrganizerCore.settings().motdHash()) { MotDDialog dialog(motd); dialog.exec(); - m_OrganizerCore.settings().setMotDHash(hash); + m_OrganizerCore.settings().setMotdHash(hash); } } } @@ -5528,7 +5529,7 @@ void MainWindow::initDownloadView() void MainWindow::updateDownloadView() { // set the view attribute and default row sizes - if (m_OrganizerCore.settings().compactDownloads()) { + if (m_OrganizerCore.settings().interface().compactDownloads()) { ui->downloadView->setProperty("downloadView", "compact"); setStyleSheet("DownloadListWidget::item { padding: 4px 2px; }"); } else { @@ -5541,7 +5542,7 @@ void MainWindow::updateDownloadView() // reapply global stylesheet on the widget level (!) to override the defaults //ui->downloadView->setStyleSheet(styleSheet()); - ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().metaDownloads()); + ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().interface().metaDownloads()); ui->downloadView->style()->unpolish(ui->downloadView); ui->downloadView->style()->polish(ui->downloadView); qobject_cast(ui->downloadView->header())->customResizeSections(); @@ -5554,7 +5555,7 @@ void MainWindow::modUpdateCheck(std::multimap IDs) ModInfo::manualUpdateCheck(&m_PluginContainer, this, IDs); } else { QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else @@ -5566,7 +5567,7 @@ void MainWindow::toggleMO2EndorseState() { const auto& s = m_OrganizerCore.settings(); - if (!s.endorsementIntegration()) { + if (!s.nexus().endorsementIntegration()) { ui->actionEndorseMO->setVisible(false); return; } @@ -5576,7 +5577,7 @@ void MainWindow::toggleMO2EndorseState() bool enabled = false; QString text; - switch (s.endorsementState()) + switch (s.nexus().endorsementState()) { case EndorsementState::Accepted: { @@ -5631,9 +5632,9 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData mod->setIsEndorsed(false); } - if (Settings::instance().endorsementIntegration()) { + if (Settings::instance().nexus().endorsementIntegration()) { if (result->first == "skyrimspecialedition" && result->second.first == gamePlugin->nexusModOrganizerID()) { - m_OrganizerCore.settings().setEndorsementState( + m_OrganizerCore.settings().nexus().setEndorsementState( endorsementStateFromString(result->second.second)); toggleMO2EndorseState(); @@ -5642,13 +5643,13 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData } } - if (!searchedMO2NexusGame && Settings::instance().endorsementIntegration()) { + if (!searchedMO2NexusGame && Settings::instance().nexus().endorsementIntegration()) { auto gamePlugin = m_OrganizerCore.getGame("SkyrimSE"); if (gamePlugin) { auto iter = sorted.equal_range(gamePlugin->gameNexusName()); for (auto result = iter.first; result != iter.second; ++result) { if (result->second.first == gamePlugin->nexusModOrganizerID()) { - m_OrganizerCore.settings().setEndorsementState( + m_OrganizerCore.settings().nexus().setEndorsementState( endorsementStateFromString(result->second.second)); toggleMO2EndorseState(); @@ -5862,7 +5863,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa } } - m_OrganizerCore.settings().setEndorsementState(s); + m_OrganizerCore.settings().nexus().setEndorsementState(s); toggleMO2EndorseState(); if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), @@ -5901,7 +5902,7 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { - auto servers = m_OrganizerCore.settings().getServers(); + auto servers = m_OrganizerCore.settings().network().servers(); for (const QVariant &var : resultData.toList()) { const QVariantMap map = var.toMap(); @@ -5929,7 +5930,7 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat } } - m_OrganizerCore.settings().updateServers(servers); + m_OrganizerCore.settings().network().updateServers(servers); } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f3840230..2178ef34 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -381,7 +381,7 @@ void ModInfoDialog::reAddTabs( Q_ASSERT(visibility.size() == m_tabs.size()); // ordered tab names from settings - const auto orderedNames = m_core->settings().geometry().getModInfoTabOrder(); + const auto orderedNames = m_core->settings().geometry().modInfoTabOrder(); // whether the tabs can be sorted; if the object name of a tab widget is not // found in orderedNames, the list cannot be sorted safely diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 7840269d..3a71b405 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -409,7 +409,7 @@ void ConflictsTab::clear() void ConflictsTab::saveState(Settings& s) { - s.saveIndex(ui->tabConflictsTabs); + s.widgets().saveIndex(ui->tabConflictsTabs); m_general.saveState(s); m_advanced.saveState(s); @@ -417,7 +417,7 @@ void ConflictsTab::saveState(Settings& s) void ConflictsTab::restoreState(const Settings& s) { - s.restoreIndex(ui->tabConflictsTabs, 0); + s.widgets().restoreIndex(ui->tabConflictsTabs, 0); m_general.restoreState(s); m_advanced.restoreState(s); @@ -1014,17 +1014,17 @@ void AdvancedConflictsTab::clear() void AdvancedConflictsTab::saveState(Settings& s) { s.geometry().saveState(ui->conflictsAdvancedList->header()); - s.saveChecked(ui->conflictsAdvancedShowNoConflict); - s.saveChecked(ui->conflictsAdvancedShowAll); - s.saveChecked(ui->conflictsAdvancedShowNearest); + s.widgets().saveChecked(ui->conflictsAdvancedShowNoConflict); + s.widgets().saveChecked(ui->conflictsAdvancedShowAll); + s.widgets().saveChecked(ui->conflictsAdvancedShowNearest); } void AdvancedConflictsTab::restoreState(const Settings& s) { s.geometry().restoreState(ui->conflictsAdvancedList->header()); - s.restoreChecked(ui->conflictsAdvancedShowNoConflict); - s.restoreChecked(ui->conflictsAdvancedShowAll); - s.restoreChecked(ui->conflictsAdvancedShowNearest); + s.widgets().restoreChecked(ui->conflictsAdvancedShowNoConflict); + s.widgets().restoreChecked(ui->conflictsAdvancedShowAll); + s.widgets().restoreChecked(ui->conflictsAdvancedShowNearest); } void AdvancedConflictsTab::update() diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 38c12d8a..9d347f57 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -130,13 +130,13 @@ void ImagesTab::update() void ImagesTab::saveState(Settings& s) { - s.saveChecked(ui->imagesShowDDS); + s.widgets().saveChecked(ui->imagesShowDDS); s.geometry().saveState(ui->tabImagesSplitter); } void ImagesTab::restoreState(const Settings& s) { - s.restoreChecked(ui->imagesShowDDS); + s.widgets().restoreChecked(ui->imagesShowDDS); s.geometry().restoreState(ui->tabImagesSplitter); } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 6d28cbe3..95e62328 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -19,7 +19,7 @@ NexusTab::NexusTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); - ui->endorse->setVisible(core().settings().endorsementIntegration()); + ui->endorse->setVisible(core().settings().nexus().endorsementIntegration()); connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); connect( diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 37c8c650..fb110abb 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -23,7 +23,7 @@ bool ModInfoOverwrite::isEmpty() const QString ModInfoOverwrite::absolutePath() const { - return Settings::instance().getOverwriteDirectory(); + return Settings::instance().paths().overwrite(); } std::vector ModInfoOverwrite::getFlags() const diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index ce29e11e..3cff914a 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -631,7 +631,7 @@ std::vector ModInfoRegular::getFlags() const std::vector result = ModInfoWithConflictInfo::getFlags(); if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE) && - Settings::instance().endorsementIntegration()) { + Settings::instance().nexus().endorsementIntegration()) { result.push_back(ModInfo::FLAG_NOTENDORSED); } if ((m_NexusID > 0) && diff --git a/src/modlist.cpp b/src/modlist.cpp index 94b4a387..6018d3d4 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -390,7 +390,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } else if (role == Qt::ForegroundRole) { if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid()) { - return Settings::getIdealTextColor(modInfo->getColor()); + return ColorSettings::idealTextColor(modInfo->getColor()); } else if (column == COL_NAME) { int highlight = modInfo->getHighlight(); if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) @@ -428,7 +428,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid() && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) - || Settings::instance().colorSeparatorScrollbar())) { + || Settings::instance().colors().colorSeparatorScrollbar())) { return modInfo->getColor(); } else { return QVariant(); @@ -999,8 +999,8 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa ModInfo::Ptr modInfo = ModInfo::getByIndex(row); QDir modDir = QDir(modInfo->absolutePath()); - QDir allModsDir(Settings::instance().getModDirectory()); - QDir overwriteDir(Settings::instance().getOverwriteDirectory()); + QDir allModsDir(Settings::instance().paths().mods()); + QDir overwriteDir(Settings::instance().paths().overwrite()); QStringList sourceList; QStringList targetList; diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 16190ca4..c6ef7bc7 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -572,7 +572,7 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( - Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); + Settings::instance().paths().cache() + "/nexus_cookies.dat"))); if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { // why is this necessary all of a sudden? diff --git a/src/organizercore.cpp b/src/organizercore.cpp index af0cf969..1a89641d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -146,7 +146,7 @@ static void startSteam(QWidget *widget) QStringList args; QString username; QString password; - if (Settings::instance().getSteamLogin(username, password)) { + if (Settings::instance().steam().login(username, password)) { args << "-login"; args << username; if (password != "") { @@ -275,12 +275,13 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_ArchivesInit(false) , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); + m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads()); - NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); + NexusInterface::instance(m_PluginContainer)->setCacheDirectory( + m_Settings.paths().cache()); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); + m_InstallationManager.setDownloadDirectory(m_Settings.paths().downloads()); connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this, SLOT(downloadSpeed(QString, int))); @@ -333,7 +334,7 @@ OrganizerCore::~OrganizerCore() void OrganizerCore::storeSettings() { if (m_CurrentProfile != nullptr) { - m_Settings.setSelectedProfileName(m_CurrentProfile->name()); + m_Settings.game().setSelectedProfileName(m_CurrentProfile->name()); } m_ExecutablesList.store(m_Settings); @@ -356,7 +357,7 @@ void OrganizerCore::storeSettings() QMessageBox::critical( 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)); + .arg(m_Settings.filename(), reason)); } } @@ -432,8 +433,9 @@ void OrganizerCore::updateExecutablesList() // TODO this has nothing to do with executables list move to an appropriate // function! - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } void OrganizerCore::setUserInterface(IUserInterface *userInterface, @@ -478,7 +480,7 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, if (userInterface != nullptr) { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (isOnline() && !m_Settings.offlineMode()) { + if (isOnline() && !m_Settings.network().offlineMode()) { m_Updater.testForUpdate(); } else { log::debug("user doesn't seem to be connected to the internet"); @@ -541,7 +543,7 @@ bool OrganizerCore::nexusApi(bool retry) return false; } else { QString apiKey; - if (m_Settings.getNexusApiKey(apiKey)) { + if (m_Settings.nexus().apiKey(apiKey)) { // credentials stored or user entered them manually log::debug("attempt to verify nexus api key"); accessManager->apiCheck(apiKey); @@ -608,7 +610,7 @@ void OrganizerCore::removeOrigin(const QString &name) void OrganizerCore::downloadSpeed(const QString &serverName, int bytesPerSecond) { - m_Settings.setDownloadSpeed(serverName, bytesPerSecond); + m_Settings.network().setDownloadSpeed(serverName, bytesPerSecond); } InstallationManager *OrganizerCore::installationManager() @@ -629,9 +631,9 @@ bool OrganizerCore::createDirectory(const QString &path) { } bool OrganizerCore::checkPathSymlinks() { - bool hasSymlink = (QFileInfo(m_Settings.getProfileDirectory()).isSymLink() || - QFileInfo(m_Settings.getModDirectory()).isSymLink() || - QFileInfo(m_Settings.getOverwriteDirectory()).isSymLink()); + bool hasSymlink = (QFileInfo(m_Settings.paths().profiles()).isSymLink() || + QFileInfo(m_Settings.paths().mods()).isSymLink() || + QFileInfo(m_Settings.paths().overwrite()).isSymLink()); if (hasSymlink) { QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) " @@ -643,17 +645,17 @@ bool OrganizerCore::checkPathSymlinks() { } bool OrganizerCore::bootstrap() { - return createDirectory(m_Settings.getProfileDirectory()) && - createDirectory(m_Settings.getModDirectory()) && - createDirectory(m_Settings.getDownloadDirectory()) && - createDirectory(m_Settings.getOverwriteDirectory()) && + return createDirectory(m_Settings.paths().profiles()) && + createDirectory(m_Settings.paths().mods()) && + createDirectory(m_Settings.paths().downloads()) && + createDirectory(m_Settings.paths().overwrite()) && createDirectory(QString::fromStdWString(crashDumpsPath())) && checkPathSymlinks() && cycleDiagnostics(); } void OrganizerCore::createDefaultProfile() { - QString profilesPath = settings().getProfileDirectory(); + QString profilesPath = settings().paths().profiles(); if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { Profile newProf("Default", managedGame(), false); @@ -674,18 +676,18 @@ void OrganizerCore::updateVFSParams( void OrganizerCore::setLogLevel(log::Levels level) { - m_Settings.setLogLevel(level); + m_Settings.diagnostics().setLogLevel(level); updateVFSParams( - m_Settings.logLevel(), - m_Settings.crashDumpsType(), + m_Settings.diagnostics().logLevel(), + m_Settings.diagnostics().crashDumpsType(), m_Settings.executablesBlacklist()); - log::getDefault().setLevel(m_Settings.logLevel()); + log::getDefault().setLevel(m_Settings.diagnostics().logLevel()); } bool OrganizerCore::cycleDiagnostics() { - if (int maxDumps = settings().crashDumpsMax()) + if (int maxDumps = settings().diagnostics().crashDumpsMax()) removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); return true; } @@ -720,7 +722,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) return; } - QDir profileBaseDir(settings().getProfileDirectory()); + QDir profileBaseDir(settings().paths().profiles()); QString profileDir = profileBaseDir.absoluteFilePath(profileName); if (!QDir(profileDir).exists()) { @@ -744,7 +746,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_CurrentProfile->deactivateInvalidation(); } - m_Settings.setSelectedProfileName(m_CurrentProfile->name()); + m_Settings.game().setSelectedProfileName(m_CurrentProfile->name()); connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); connect(m_CurrentProfile, SIGNAL(modStatusChanged(QList)), this, SLOT(modStatusChanged(QList))); @@ -776,22 +778,22 @@ QString OrganizerCore::profilePath() const QString OrganizerCore::downloadsPath() const { - return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().downloads()); } QString OrganizerCore::overwritePath() const { - return QDir::fromNativeSeparators(m_Settings.getOverwriteDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().overwrite()); } QString OrganizerCore::basePath() const { - return QDir::fromNativeSeparators(m_Settings.getBaseDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().base()); } QString OrganizerCore::modsPath() const { - return QDir::fromNativeSeparators(m_Settings.getModDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().mods()); } MOBase::VersionInfo OrganizerCore::appVersion() const @@ -821,10 +823,10 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) return nullptr; } - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); QString targetDirectory - = QDir::fromNativeSeparators(m_Settings.getModDirectory()) + = QDir::fromNativeSeparators(m_Settings.paths().mods()) .append("/") .append(name); @@ -912,7 +914,7 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, modName.update(initModName, GUESS_USER); } m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); @@ -977,7 +979,7 @@ void OrganizerCore::installDownload(int index) m_CurrentProfile->writeModlistNow(); bool hasIniTweaks = false; - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); @@ -1270,7 +1272,7 @@ bool OrganizerCore::previewFileWithAlternatives( else { // crude: we search for the next slash after the base mod directory to skip // everything up to the data-relative directory - int offset = settings().getModDirectory().size() + 1; + int offset = settings().paths().mods().size() + 1; offset = fileName.indexOf("/", offset); fileName = fileName.mid(offset + 1); } @@ -1412,7 +1414,7 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, LPDWORD exitCode) { HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries); - if (Settings::instance().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { + if (Settings::instance().interface().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; @@ -1461,7 +1463,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); } else { ::SetEnvironmentVariableW(L"SteamAPPId", - ToWString(m_Settings.getSteamAppID()).c_str()); + ToWString(m_Settings.steam().appID()).c_str()); } QWidget *window = qApp->activeWindow(); @@ -1477,7 +1479,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, || QFileInfo(managedGame()->gameDirectory().absoluteFilePath( "steam_api64.dll")) .exists()) - && (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { + && (m_Settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { bool steamFound = true; bool steamAccess = true; @@ -1592,7 +1594,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, } } - QString modsPath = settings().getModDirectory(); + 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: @@ -1749,7 +1751,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) { - if (!Settings::instance().lockGUI()) + if (!Settings::instance().interface().lockGUI()) return true; ILockedWaitingForProcess* uilock = nullptr; @@ -1960,8 +1962,10 @@ void OrganizerCore::refreshModList(bool saveChanges) if (saveChanges) { m_CurrentProfile->writeModlistNow(true); } - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); m_CurrentProfile->refreshModStatus(); @@ -2130,7 +2134,7 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); @@ -2156,7 +2160,7 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) f(); } else { QString apiKey; - if (settings().getNexusApiKey(apiKey)) { + if (settings().nexus().apiKey(apiKey)) { doAfterLogin([f]{ f(); }); NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { @@ -2295,8 +2299,10 @@ void OrganizerCore::profileRefresh() { // have to refresh mods twice (again in refreshModList), otherwise the refresh // isn't complete. Not sure why - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); + m_CurrentProfile->refreshModStatus(); refreshModList(); @@ -2463,7 +2469,7 @@ void OrganizerCore::syncOverwrite() SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, qApp->activeWindow()); if (syncDialog.exec() == QDialog::Accepted) { - syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); + syncDialog.apply(QDir::fromNativeSeparators(m_Settings.paths().mods())); modInfo->testValid(); refreshDirectoryStructure(); } @@ -2486,7 +2492,7 @@ std::vector OrganizerCore::activeProblems() const const auto& hookdll = oldMO1HookDll(); if (!hookdll.isEmpty()) { // This warning will now be shown every time the problems are checked, which is a bit - // of a "log spam". But since this is a sevre error which will most likely make the + // of a "log spam". But since this is a sever error which will most likely make the // game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it // easier for the user to notice the warning. log::warn("hook.dll found in game folder: {}", hookdll); @@ -2562,7 +2568,7 @@ void OrganizerCore::savePluginList() } m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(), m_CurrentProfile->getDeleterFileName(), - m_Settings.hideUncheckedPlugins()); + m_Settings.game().hideUncheckedPlugins()); m_PluginList.saveLoadOrder(*m_DirectoryStructure); } @@ -2574,7 +2580,7 @@ void OrganizerCore::prepareStart() m_CurrentProfile->writeModlist(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); - m_Settings.setupLoadMechanism(); + m_Settings.game().setupLoadMechanism(); storeSettings(); } @@ -2588,7 +2594,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, } IPluginGame *game = qApp->property("managed_game").value(); - Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName), + Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game); MappingType result; @@ -2634,7 +2640,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, } result.insert(result.end(), { - QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()), + QDir::toNativeSeparators(m_Settings.paths().overwrite()), dataPath, true, customOverwrite.isEmpty() diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index ddfe492e..33423225 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -191,7 +191,7 @@ void PluginList::refresh(const QString &profileName continue; } - bool forceEnabled = Settings::instance().forceEnableCoreFiles() && + bool forceEnabled = Settings::instance().game().forceEnableCoreFiles() && primaryPlugins.contains(filename, Qt::CaseInsensitive); //(std::find(primaryPlugins.begin(), primaryPlugins.end(), filename.toLower()) != primaryPlugins.end()); diff --git a/src/profile.cpp b/src/profile.cpp index 7f4ebcaa..e76060b9 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -73,7 +73,7 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) , m_GamePlugin(gamePlugin) { - QString profilesDir = Settings::instance().getProfileDirectory(); + QString profilesDir = Settings::instance().paths().profiles(); QDir profileBase(profilesDir); QString fixedName = name; if (!fixDirectoryName(fixedName)) { @@ -299,7 +299,7 @@ void Profile::createTweakedIniFile() // static void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName) { - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); while (profileIter.hasNext()) { @@ -655,7 +655,7 @@ void Profile::setModPriority(unsigned int index, int &newPriority) Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) { - QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name; + QString profileDirectory = Settings::instance().paths().profiles() + "/" + name; reference.copyFilesTo(profileDirectory); return new Profile(QDir(profileDirectory), gamePlugin); } @@ -906,7 +906,7 @@ QString Profile::savePath() const void Profile::rename(const QString &newName) { - QDir profileDir(Settings::instance().getProfileDirectory()); + QDir profileDir(Settings::instance().paths().profiles()); profileDir.rename(name(), newName); m_Directory.setPath(profileDir.absoluteFilePath(newName)); } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 2f1bd059..c91f48f4 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -57,7 +57,7 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c { ui->setupUi(this); - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); @@ -203,7 +203,7 @@ void ProfilesDialog::on_removeProfileButton_clicked() if (confirmBox.exec() == QMessageBox::Yes) { QString profilePath; if (profileToDelete.get() == nullptr) { - profilePath = Settings::instance().getProfileDirectory() + profilePath = Settings::instance().paths().profiles() + "/" + ui->profilesList->currentItem()->text(); if (QMessageBox::question(this, tr("Profile broken"), tr("This profile you're about to delete seems to be broken or the path is invalid. " diff --git a/src/settings.cpp b/src/settings.cpp index 71288950..8b063efb 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -150,7 +150,7 @@ std::optional getOptional( template T get( const QSettings& settings, - const QString& section, const QString& key, T def={}) + const QString& section, const QString& key, T def) { if (auto v=getOptional(settings, section, key)) { return *v; @@ -453,22 +453,74 @@ void warnIfNotCheckable(const QAbstractButton* b) } +bool setWindowsCredential(const QString key, const QString data) +{ + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + bool result = false; + if (data.isEmpty()) { + result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); + if (!result) + if (GetLastError() == ERROR_NOT_FOUND) + result = true; + } else { + wchar_t* charData = new wchar_t[data.size()]; + data.toWCharArray(charData); + + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = keyData; + cred.CredentialBlob = (LPBYTE)charData; + cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + + result = CredWriteW(&cred, 0); + delete[] charData; + } + delete[] keyData; + return result; +} + +QString getWindowsCredential(const QString key) +{ + QString result; + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + PCREDENTIALW creds; + if (CredReadW(keyData, 1, 0, &creds)) { + wchar_t *charData = (wchar_t *)creds->CredentialBlob; + result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); + CredFree(creds); + } else { + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); + } + } + delete[] keyData; + return result; +} + + Settings *Settings::s_Instance = nullptr; Settings::Settings(const QString& path) : m_Settings(path, QSettings::IniFormat), - m_Geometry(m_Settings), m_Colors(m_Settings), m_Plugins(m_Settings) + m_Game(m_Settings), m_Geometry(m_Settings), m_Widgets(m_Settings), + m_Colors(m_Settings), m_Plugins(m_Settings), m_Paths(m_Settings), + m_Network(m_Settings), m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), + m_Interface(m_Settings), m_Diagnostics(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); } else { s_Instance = this; } - - MOBase::QuestionBoxMemory::setCallbacks( - [this](auto&& w, auto&& f){ return getQuestionButton(w, f); }, - [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, - [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); } Settings::~Settings() @@ -488,7 +540,7 @@ Settings &Settings::instance() void Settings::processUpdates( const QVersionNumber& currentVersion, const QVersionNumber& lastVersion) { - if (getFirstStart()) { + if (firstStart()) { return; } @@ -523,1569 +575,1670 @@ void Settings::processUpdates( set(m_Settings, "General", "version", currentVersion.toString()); } -QString Settings::getFilename() const +QString Settings::filename() const { return m_Settings.fileName(); } -void Settings::registerAsNXMHandler(bool force) +bool Settings::usePrereleases() const { - const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; - const auto executable = QCoreApplication::applicationFilePath(); - - QString mode = force ? "forcereg" : "reg"; - QString parameters = mode + " " + m_GamePlugin->gameShortName(); - for (const QString& altGame : m_GamePlugin->validShortNames()) { - parameters += "," + altGame; - } - parameters += " \"" + executable + "\""; - - if (!shell::Execute(nxmPath, parameters)) { - QMessageBox::critical( - nullptr, tr("Failed"), tr("Failed to start the helper application")); - } + return get(m_Settings, "Settings", "use_prereleases", false); } -bool Settings::colorSeparatorScrollbar() const +void Settings::setUsePrereleases(bool b) { - return get(m_Settings, "Settings", "colorSeparatorScrollbars", true); + set(m_Settings, "Settings", "use_prereleases", b); } -void Settings::setColorSeparatorScrollbar(bool b) +std::optional Settings::version() const { - set(m_Settings, "Settings", "colorSeparatorScrollbars", b); + if (auto v=getOptional(m_Settings, "General", "version")) { + return QVersionNumber::fromString(*v).normalized(); + } + + return {}; } -void Settings::managedGameChanged(IPluginGame const *gamePlugin) +bool Settings::firstStart() const { - m_GamePlugin = gamePlugin; + return get(m_Settings, "General", "first_start", true); } -bool Settings::obfuscate(const QString key, const QString data) +void Settings::setFirstStart(bool b) { - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - bool result = false; - if (data.isEmpty()) { - result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); - if (!result) - if (GetLastError() == ERROR_NOT_FOUND) - result = true; - } else { - wchar_t* charData = new wchar_t[data.size()]; - data.toWCharArray(charData); + set(m_Settings, "General", "first_start", b); +} - CREDENTIALW cred = {}; - cred.Flags = 0; - cred.Type = CRED_TYPE_GENERIC; - cred.TargetName = keyData; - cred.CredentialBlob = (LPBYTE)charData; - cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); - cred.Persist = CRED_PERSIST_LOCAL_MACHINE; +QString Settings::executablesBlacklist() const +{ + static const QString def = (QStringList() + << "Chrome.exe" + << "Firefox.exe" + << "TSVNCache.exe" + << "TGitCache.exe" + << "Steam.exe" + << "GameOverlayUI.exe" + << "Discord.exe" + << "GalaxyClient.exe" + << "Spotify.exe" + ).join(";"); - result = CredWriteW(&cred, 0); - delete[] charData; - } - delete[] keyData; - return result; + return get(m_Settings, "Settings", "executable_blacklist", def); } -QString Settings::deObfuscate(const QString key) +void Settings::setExecutablesBlacklist(const QString& s) { - QString result; - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - PCREDENTIALW creds; - if (CredReadW(keyData, 1, 0, &creds)) { - wchar_t *charData = (wchar_t *)creds->CredentialBlob; - result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); - CredFree(creds); - } else { - const auto e = GetLastError(); - if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); - } - } - delete[] keyData; - return result; + set(m_Settings, "Settings", "executable_blacklist", s); } -QColor Settings::getIdealTextColor(const QColor& rBackgroundColor) +void Settings::setMotdHash(uint hash) { - if (rBackgroundColor.alpha() == 0) - return QColor(Qt::black); - - const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha(); - int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); - return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white); + set(m_Settings, "General", "motd_hash", hash); } - -bool Settings::hideUncheckedPlugins() const +unsigned int Settings::motdHash() const { - return get(m_Settings, "Settings", "hide_unchecked_plugins", false); + return get(m_Settings, "General", "motd_hash", 0); } -void Settings::setHideUncheckedPlugins(bool b) +bool Settings::archiveParsing() const { - set(m_Settings, "Settings", "hide_unchecked_plugins", b); + return get(m_Settings, "Settings", "archive_parsing_experimental", false); } -bool Settings::forceEnableCoreFiles() const +void Settings::setArchiveParsing(bool b) { - return get(m_Settings, "Settings", "force_enable_core_files", true); + set(m_Settings, "Settings", "archive_parsing_experimental", b); } -void Settings::setForceEnableCoreFiles(bool b) +std::vector> Settings::executables() const { - set(m_Settings, "Settings", "force_enable_core_files", b); + ScopedReadArray sra(m_Settings, "customExecutables"); + std::vector> v; + + sra.for_each([&]{ + std::map map; + + for (auto&& key : sra.keys()) { + map[key] = m_Settings.value(key); + } + + v.push_back(map); + }); + + return v; } -bool Settings::lockGUI() const +void Settings::setExecutables(const std::vector>& v) { - return get(m_Settings, "Settings", "lock_gui", true); + removeSection(m_Settings, "customExecutables"); + + ScopedWriteArray swa(m_Settings, "customExecutables"); + + for (const auto& map : v) { + swa.next(); + + for (auto&& p : map) { + swa.set(p.first, p.second); + } + } } -void Settings::setLockGUI(bool b) +bool Settings::keepBackupOnInstall() const { - set(m_Settings, "Settings", "lock_gui", b); + return get(m_Settings, "General", "backup_install", false); } -bool Settings::automaticLoginEnabled() const +void Settings::setKeepBackupOnInstall(bool b) { - return get(m_Settings, "Settings", "nexus_login", false); + set(m_Settings, "General", "backup_install", b); } -QString Settings::getSteamAppID() const +GameSettings& Settings::game() { - return get(m_Settings, "Settings", "app_id", m_GamePlugin->steamAPPId()); + return m_Game; } -void Settings::setSteamAppID(const QString& id) +const GameSettings& Settings::game() const { - if (id.isEmpty()) { - remove(m_Settings, "Settings", "app_id"); - } else { - set(m_Settings, "Settings", "app_id", id); - } + return m_Game; } -bool Settings::usePrereleases() const +GeometrySettings& Settings::geometry() { - return get(m_Settings, "Settings", "use_prereleases", false); + return m_Geometry; } -void Settings::setUsePrereleases(bool b) +const GeometrySettings& Settings::geometry() const { - set(m_Settings, "Settings", "use_prereleases", b); + return m_Geometry; } -QString Settings::getConfigurablePath(const QString &key, - const QString &def, - bool resolve) const +WidgetSettings& Settings::widgets() { - QString result = QDir::fromNativeSeparators( - get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); - - if (resolve) { - result.replace("%BASE_DIR%", getBaseDirectory()); - } - - return result; + return m_Widgets; } -void Settings::setConfigurablePath(const QString &key, const QString& path) +const WidgetSettings& Settings::widgets() const { - if (path.isEmpty()) { - remove(m_Settings, "Settings", key); - } else { - set(m_Settings, "Settings", key, path); - } + return m_Widgets; } -QString Settings::getBaseDirectory() const +ColorSettings& Settings::colors() { - return QDir::fromNativeSeparators(get(m_Settings, - "Settings", "base_directory", qApp->property("dataPath").toString())); + return m_Colors; } -QString Settings::getDownloadDirectory(bool resolve) const +const ColorSettings& Settings::colors() const { - return getConfigurablePath( - "download_directory", - ToQString(AppConfig::downloadPath()), - resolve); + return m_Colors; } -QString Settings::getCacheDirectory(bool resolve) const +PluginSettings& Settings::plugins() { - return getConfigurablePath( - "cache_directory", - ToQString(AppConfig::cachePath()), - resolve); + return m_Plugins; } -QString Settings::getModDirectory(bool resolve) const +const PluginSettings& Settings::plugins() const { - return getConfigurablePath( - "mod_directory", - ToQString(AppConfig::modsPath()), - resolve); + return m_Plugins; } -QString Settings::getProfileDirectory(bool resolve) const +PathSettings& Settings::paths() { - return getConfigurablePath( - "profiles_directory", - ToQString(AppConfig::profilesPath()), - resolve); + return m_Paths; } -QString Settings::getOverwriteDirectory(bool resolve) const +const PathSettings& Settings::paths() const { - return getConfigurablePath( - "overwrite_directory", - ToQString(AppConfig::overwritePath()), - resolve); + return m_Paths; } -void Settings::setBaseDirectory(const QString& path) +NetworkSettings& Settings::network() { - if (path.isEmpty()) { - remove(m_Settings, "Settings", "base_directory"); - } else { - set(m_Settings, "Settings", "base_directory", path); - } + return m_Network; } -void Settings::setDownloadDirectory(const QString& path) +const NetworkSettings& Settings::network() const { - setConfigurablePath("download_directory", path); + return m_Network; } -void Settings::setModDirectory(const QString& path) +NexusSettings& Settings::nexus() { - setConfigurablePath("mod_directory", path); + return m_Nexus; } -void Settings::setCacheDirectory(const QString& path) +const NexusSettings& Settings::nexus() const { - setConfigurablePath("cache_directory", path); + return m_Nexus; } -void Settings::setProfileDirectory(const QString& path) +SteamSettings& Settings::steam() { - setConfigurablePath("profiles_directory", path); + return m_Steam; } -void Settings::setOverwriteDirectory(const QString& path) +const SteamSettings& Settings::steam() const { - setConfigurablePath("overwrite_directory", path); + return m_Steam; } -std::optional Settings::getManagedGameDirectory() const +InterfaceSettings& Settings::interface() { - if (auto v=getOptional(m_Settings, "General", "gamePath")) { - return QString::fromUtf8(*v); - } - - return {}; + return m_Interface; } -void Settings::setManagedGameDirectory(const QString& path) +const InterfaceSettings& Settings::interface() const { - set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8()); + return m_Interface; } -std::optional Settings::getManagedGameName() const +DiagnosticsSettings& Settings::diagnostics() { - return getOptional(m_Settings, "General", "gameName"); + return m_Diagnostics; } -void Settings::setManagedGameName(const QString& name) +const DiagnosticsSettings& Settings::diagnostics() const { - set(m_Settings, "General", "gameName", name); + return m_Diagnostics; } -std::optional Settings::getManagedGameEdition() const +QSettings::Status Settings::sync() const { - return getOptional(m_Settings, "General", "game_edition"); + m_Settings.sync(); + return m_Settings.status(); } -void Settings::setManagedGameEdition(const QString& name) +void Settings::dump() const { - set(m_Settings, "General", "game_edition", name); -} + static const QStringList ignore({ + "username", "password", "nexus_api_key" + }); -std::optional Settings::getSelectedProfileName() const -{ - if (auto v=getOptional(m_Settings, "General", "selected_profile")) { - return QString::fromUtf8(*v); + log::debug("settings:"); + + { + ScopedGroup sg(m_Settings, "Settings"); + + for (auto k : m_Settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } + + log::debug(" . {}={}", k, m_Settings.value(k).toString()); + } } - return {}; + m_Network.dump(); } -void Settings::setSelectedProfileName(const QString& name) +void Settings::managedGameChanged(IPluginGame const *gamePlugin) { - set(m_Settings, "General", "selected_profile", name.toUtf8()); + m_Game.setPlugin(gamePlugin); } -std::optional Settings::getStyleName() const + +GameSettings::GameSettings(QSettings& settings) + : m_Settings(settings), m_GamePlugin(nullptr) { - return getOptional(m_Settings, "Settings", "style"); } -void Settings::setStyleName(const QString& name) +const MOBase::IPluginGame* GameSettings::plugin() { - set(m_Settings, "Settings", "style", name); + return m_GamePlugin; } -bool Settings::getUseProxy() const +void GameSettings::setPlugin(const MOBase::IPluginGame* gamePlugin) { - return get(m_Settings, "Settings", "use_proxy", false); + m_GamePlugin = gamePlugin; } -void Settings::setUseProxy(bool b) +bool GameSettings::forceEnableCoreFiles() const { - set(m_Settings, "Settings", "use_proxy", b); + return get(m_Settings, "Settings", "force_enable_core_files", true); } -std::optional Settings::getVersion() const +void GameSettings::setForceEnableCoreFiles(bool b) { - if (auto v=getOptional(m_Settings, "General", "version")) { - return QVersionNumber::fromString(*v).normalized(); - } - - return {}; + set(m_Settings, "Settings", "force_enable_core_files", b); } -bool Settings::getFirstStart() const +std::optional GameSettings::directory() const { - return get(m_Settings, "General", "first_start", true); + if (auto v=getOptional(m_Settings, "General", "gamePath")) { + return QString::fromUtf8(*v); + } + + return {}; } -void Settings::setFirstStart(bool b) +void GameSettings::setDirectory(const QString& path) { - set(m_Settings, "General", "first_start", b); + set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8()); } -std::optional Settings::getPreviousSeparatorColor() const +std::optional GameSettings::name() const { - const auto c = getOptional(m_Settings, "General", "previousSeparatorColor"); - if (c && c->isValid()) { - return c; - } - - return {}; + return getOptional(m_Settings, "General", "gameName"); } -void Settings::setPreviousSeparatorColor(const QColor& c) const +void GameSettings::setName(const QString& name) { - set(m_Settings, "General", "previousSeparatorColor", c); + set(m_Settings, "General", "gameName", name); } -void Settings::removePreviousSeparatorColor() +std::optional GameSettings::edition() const { - remove(m_Settings, "General", "previousSeparatorColor"); + return getOptional(m_Settings, "General", "game_edition"); } -bool Settings::getNexusApiKey(QString &apiKey) const +void GameSettings::setEdition(const QString& name) { - QString tempKey = deObfuscate("APIKEY"); - if (tempKey.isEmpty()) - return false; - - apiKey = tempKey; - return true; + set(m_Settings, "General", "game_edition", name); } -bool Settings::setNexusApiKey(const QString& apiKey) +std::optional GameSettings::selectedProfileName() const { - if (!obfuscate("APIKEY", apiKey)) { - const auto e = GetLastError(); - log::error("Storing API key failed: {}", formatSystemMessage(e)); - return false; + if (auto v=getOptional(m_Settings, "General", "selected_profile")) { + return QString::fromUtf8(*v); } - return true; + return {}; } -bool Settings::clearNexusApiKey() +void GameSettings::setSelectedProfileName(const QString& name) { - return setNexusApiKey(""); + set(m_Settings, "General", "selected_profile", name.toUtf8()); } -bool Settings::hasNexusApiKey() const +LoadMechanism::EMechanism GameSettings::loadMechanismType() const { - return !deObfuscate("APIKEY").isEmpty(); -} + const auto def = LoadMechanism::LOAD_MODORGANIZER; -bool Settings::getSteamLogin(QString &username, QString &password) const -{ - username = get(m_Settings, "Settings", "steam_username", ""); - password = deObfuscate("steam_password"); + const auto i = get(m_Settings, + "Settings", "load_mechanism", def); - return !username.isEmpty() && !password.isEmpty(); -} + switch (i) + { + // ok + case LoadMechanism::LOAD_MODORGANIZER: // fall-through + { + break; + } -bool Settings::compactDownloads() const -{ - return get(m_Settings, "Settings", "compact_downloads", false); -} + default: + { + log::error( + "invalid load mechanism {}, reverting to {}", + static_cast(i), toString(def)); -void Settings::setCompactDownloads(bool b) -{ - set(m_Settings, "Settings", "compact_downloads", b); -} + set(m_Settings, "Settings", "load_mechanism", def); -bool Settings::metaDownloads() const -{ - return get(m_Settings, "Settings", "meta_downloads", false); -} + return def; + } + } -void Settings::setMetaDownloads(bool b) -{ - set(m_Settings, "Settings", "meta_downloads", b); + return i; } -bool Settings::offlineMode() const +void GameSettings::setLoadMechanism(LoadMechanism::EMechanism m) { - return get(m_Settings, "Settings/offline_mode", false); + set(m_Settings, "Settings", "load_mechanism", m); } -void Settings::setOfflineMode(bool b) +const LoadMechanism& GameSettings::loadMechanism() const { - set(m_Settings, "Settings", "offline_mode", b); + return m_LoadMechanism; } -log::Levels Settings::logLevel() const +void GameSettings::setupLoadMechanism() { - return get(m_Settings, "Settings", "log_level", log::Levels::Info); + m_LoadMechanism.activate(loadMechanismType()); } -void Settings::setLogLevel(log::Levels level) +bool GameSettings::hideUncheckedPlugins() const { - set(m_Settings, "Settings", "log_level", level); + return get(m_Settings, "Settings", "hide_unchecked_plugins", false); } -CrashDumpsType Settings::crashDumpsType() const +void GameSettings::setHideUncheckedPlugins(bool b) { - return get(m_Settings, - "Settings", "crash_dumps_type", CrashDumpsType::Mini); + set(m_Settings, "Settings", "hide_unchecked_plugins", b); } -void Settings::setCrashDumpsType(CrashDumpsType type) -{ - set(m_Settings, "Settings", "crash_dumps_type", type); -} -int Settings::crashDumpsMax() const +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s), m_Reset(false) { - return get(m_Settings, "Settings", "crash_dumps_max", 5); } -void Settings::setCrashDumpsMax(int n) +void GeometrySettings::requestReset() { - set(m_Settings, "Settings", "crash_dumps_max", n); + m_Reset = true; } -QString Settings::executablesBlacklist() const +void GeometrySettings::resetIfNeeded() { - static const QString def = (QStringList() - << "Chrome.exe" - << "Firefox.exe" - << "TSVNCache.exe" - << "TGitCache.exe" - << "Steam.exe" - << "GameOverlayUI.exe" - << "Discord.exe" - << "GalaxyClient.exe" - << "Spotify.exe" - ).join(";"); + if (!m_Reset) { + return; + } - return get(m_Settings, "Settings", "executable_blacklist", def); + removeSection(m_Settings, "Geometry"); } -void Settings::setExecutablesBlacklist(const QString& s) +void GeometrySettings::saveGeometry(const QWidget* w) { - set(m_Settings, "Settings", "executable_blacklist", s); + set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); } -void Settings::setSteamLogin(QString username, QString password) +bool GeometrySettings::restoreGeometry(QWidget* w) const { - if (username == "") { - remove(m_Settings, "Settings", "steam_username"); - password = ""; - } else { - set(m_Settings, "Settings", "steam_username", username); + if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { + w->restoreGeometry(*v); + return true; } - if (!obfuscate("steam_password", password)) { - const auto e = GetLastError(); - log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); - } + return false; } -LoadMechanism::EMechanism Settings::getLoadMechanism() const +void GeometrySettings::saveState(const QMainWindow* w) { - const auto def = LoadMechanism::LOAD_MODORGANIZER; - - const auto i = get(m_Settings, - "Settings", "load_mechanism", def); - - switch (i) - { - // ok - case LoadMechanism::LOAD_MODORGANIZER: // fall-through - { - break; - } - - default: - { - log::error( - "invalid load mechanism {}, reverting to {}", - static_cast(i), toString(def)); - - set(m_Settings, "Settings", "load_mechanism", def); - - return def; - } - } - - return i; + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setLoadMechanism(LoadMechanism::EMechanism m) +bool GeometrySettings::restoreState(QMainWindow* w) const { - set(m_Settings, "Settings", "load_mechanism", m); -} + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; + } -void Settings::setupLoadMechanism() -{ - m_LoadMechanism.activate(getLoadMechanism()); + return false; } -bool Settings::endorsementIntegration() const +void GeometrySettings::saveState(const QHeaderView* w) { - return get(m_Settings, "Settings", "endorsement_integration", true); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setEndorsementIntegration(bool b) const +bool GeometrySettings::restoreState(QHeaderView* w) const { - set(m_Settings, "Settings", "endorsement_integration", b); + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; } -EndorsementState Settings::endorsementState() const +void GeometrySettings::saveState(const QSplitter* w) { - return endorsementStateFromString( - get(m_Settings, "General", "endorse_state", "")); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setEndorsementState(EndorsementState s) +bool GeometrySettings::restoreState(QSplitter* w) const { - const auto v = toString(s); - - if (v.isEmpty()) { - remove(m_Settings, "General", "endorse_state"); - } else { - set(m_Settings, "General", "endorse_state", v); + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; } -} -bool Settings::hideAPICounter() const -{ - return get(m_Settings, "Settings", "hide_api_counter", false); + return false; } -void Settings::setHideAPICounter(bool b) +void GeometrySettings::saveState(const ExpanderWidget* expander) { - set(m_Settings, "Settings", "hide_api_counter", b); + set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState()); } -bool Settings::displayForeign() const +bool GeometrySettings::restoreState(ExpanderWidget* expander) const { - return get(m_Settings, "Settings", "display_foreign", true); -} + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { + expander->restoreState(*v); + return true; + } -void Settings::setDisplayForeign(bool b) -{ - set(m_Settings, "Settings", "display_foreign", b); + return false; } -void Settings::setMotDHash(uint hash) +void GeometrySettings::saveVisibility(const QWidget* w) { - set(m_Settings, "General", "motd_hash", hash); + set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible()); } -unsigned int Settings::getMotDHash() const +bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const { - return get(m_Settings, "motd_hash", 0); -} + if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { + w->setVisible(*v); + return true; + } -bool Settings::archiveParsing() const -{ - return get(m_Settings, "Settings", "archive_parsing_experimental", false); + return false; } -void Settings::setArchiveParsing(bool b) +void GeometrySettings::restoreToolbars(QMainWindow* w) const { - set(m_Settings, "Settings", "archive_parsing_experimental", b); -} + // all toolbars have the same size and button style settings + const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); + const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); -QString Settings::language() + for (auto* tb : w->findChildren()) { + if (size) { + tb->setIconSize(*size); + } + + if (style) { + tb->setToolButtonStyle(static_cast(*style)); + } + + restoreVisibility(tb); + } +} + +void GeometrySettings::saveToolbars(const QMainWindow* w) { - QString result = get(m_Settings, "Settings", "language", ""); + const auto tbs = w->findChildren(); - if (result.isEmpty()) { - QStringList languagePreferences = QLocale::system().uiLanguages(); + // save visibility for all + for (auto* tb : tbs) { + saveVisibility(tb); + } - if (languagePreferences.length() > 0) { - // the users most favoritest language - result = languagePreferences.at(0); - } else { - // fallback system locale - result = QLocale::system().name(); + // all toolbars have the same size and button style settings, just save the + // first one + if (!tbs.isEmpty()) { + const auto* tb = tbs[0]; + + set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); + set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); + } +} + +QStringList GeometrySettings::modInfoTabOrder() const +{ + QStringList v; + + if (m_Settings.contains("mod_info_tabs")) { + // old byte array from 2.2.0 + QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray()); + + int count = 0; + stream >> count; + + for (int i=0; i> s; + v.push_back(s); + } + } else { + // string list since 2.2.1 + QString string = m_Settings.value("mod_info_tab_order").toString(); + QTextStream stream(&string); + + while (!stream.atEnd()) { + QString s; + stream >> s; + v.push_back(s); } } - return result; + return v; } -void Settings::setLanguage(const QString& name) +void GeometrySettings::setModInfoTabOrder(const QString& names) { - set(m_Settings, "Settings", "language", name); + set(m_Settings, "Geometry", "mod_info_tab_order", names); } -void Settings::setDownloadSpeed(const QString& name, int bytesPerSecond) +void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { - auto servers = getServers(); + const auto monitor = getOptional( + m_Settings, "Geometry", "MainWindow_monitor"); - for (auto& server : servers) { - if (server.name() == name) { - server.addDownload(bytesPerSecond); - updateServers(servers); - return; + QPoint center; + + if (monitor && QGuiApplication::screens().size() > *monitor) { + center = QGuiApplication::screens().at(*monitor)->geometry().center(); + } else { + center = QGuiApplication::primaryScreen()->geometry().center(); + } + + w->move(center - w->rect().center()); +} + +void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) +{ + if (auto* handle=w->windowHandle()) { + if (auto* screen = handle->screen()) { + const int screenId = QGuiApplication::screens().indexOf(screen); + set(m_Settings, "Geometry", "MainWindow_monitor", screenId); } } +} - log::error( - "server '{}' not found while trying to add a download with bps {}", - name, bytesPerSecond); +Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) +{ + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } } -ServerList Settings::getServers() const +void GeometrySettings::saveDocks(const QMainWindow* mw) { - // servers used to be a map of byte arrays until 2.2.1, it's now an array of - // individual values instead + // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock + // sizes are not restored when the main window is maximized; it is used in + // MainWindow::readSettings() and MainWindow::storeSettings() + // + // there's also https://stackoverflow.com/questions/44005852, which has what + // seems to be a popular fix, but it breaks the restored size of the window + // by setting it to the desktop's resolution, so that doesn't work + // + // the only fix I could find is to remember the sizes of the docks and manually + // setting them back; saving is straightforward, but restoring is messy + // + // this also depends on the window being visible before the timer in restore() + // is fired and the timer must be processed by application.exec(); therefore, + // the splash screen _must_ be closed before readSettings() is called, because + // it has its own event loop, which seems to interfere with this + // + // all of this should become unnecessary when QTBUG-46620 is fixed // - // so post 2.2.1, only one key is returned: "size", the size of the arrays; - // in 2.2.1, one key per server is returned - { - const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - if (!keys.empty() && keys[0] != "size") { - // old format - return getServersFromOldMap(); + // saves the size of each dock + for (const auto* dock : mw->findChildren()) { + int size = 0; + + // save the width for horizontal docks, or the height for vertical + if (dockOrientation(mw, dock) == Qt::Horizontal) { + size = dock->size().width(); + } else { + size = dock->size().height(); } + + set(m_Settings, "Geometry", dockSettingName(dock), size); } +} +void GeometrySettings::restoreDocks(QMainWindow* mw) const +{ + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; - // post 2.2.1 format, array of values + std::vector dockInfos; - ServerList list; + // for each dock + for (auto* dock : mw->findChildren()) { + if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { + // remember this dock, its size and orientation + dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); + } + } - { - ScopedReadArray sra(m_Settings, "Servers"); + // the main window must have had time to process the settings from + // readSettings() or it seems to override whatever is set here + // + // some people said a single processEvents() call is enough, but it doesn't + // look like it + QTimer::singleShot(5, [=] { + for (const auto& info : dockInfos) { + mw->resizeDocks({info.d}, {info.size}, info.ori); + } + }); +} - sra.for_each([&] { - ServerInfo::SpeedList lastDownloads; - const auto lastDownloadsString = sra.get("lastDownloads", ""); +WidgetSettings::WidgetSettings(QSettings& s) + : m_Settings(s) +{ + MOBase::QuestionBoxMemory::setCallbacks( + [this](auto&& w, auto&& f){ return questionButton(w, f); }, + [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, + [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); +} - for (const auto& s : lastDownloadsString.split(" ")) { - const auto bytesPerSecond = s.toInt(); - if (bytesPerSecond > 0) { - lastDownloads.push_back(bytesPerSecond); - } - } +std::optional WidgetSettings::index(const QComboBox* cb) const +{ + return getOptional(m_Settings, "Widgets", indexSettingName(cb)); +} - ServerInfo server( - sra.get("name", ""), - sra.get("premium", false), - QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), - sra.get("preferred", 0), - lastDownloads); +void WidgetSettings::saveIndex(const QComboBox* cb) +{ + set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex()); +} - list.add(std::move(server)); - }); +void WidgetSettings::restoreIndex(QComboBox* cb, std::optional def) const +{ + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { + cb->setCurrentIndex(*v); } - - return list; } -ServerList Settings::getServersFromOldMap() const +std::optional WidgetSettings::index(const QTabWidget* w) const { - // for 2.2.1 and before + return getOptional(m_Settings, "Widgets", indexSettingName(w)); +} - ServerList list; - const ScopedGroup sg(m_Settings, "Servers"); +void WidgetSettings::saveIndex(const QTabWidget* w) +{ + set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex()); +} - sg.for_each([&](auto&& serverKey) { - QVariantMap data = sg.get(serverKey); +void WidgetSettings::restoreIndex(QTabWidget* w, std::optional def) const +{ + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { + w->setCurrentIndex(*v); + } +} - ServerInfo server( - serverKey, - data["premium"].toBool(), - data["lastSeen"].toDate(), - data["preferred"].toInt(), - {}); +std::optional WidgetSettings::checked(const QAbstractButton* w) const +{ + warnIfNotCheckable(w); + return getOptional(m_Settings, "Widgets", checkedSettingName(w)); +} - // ignoring download count and speed, it's now a list of values instead of - // a total +void WidgetSettings::saveChecked(const QAbstractButton* w) +{ + warnIfNotCheckable(w); + set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked()); +} - list.add(std::move(server)); - }); +void WidgetSettings::restoreChecked(QAbstractButton* w, std::optional def) const +{ + warnIfNotCheckable(w); - return list; + if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { + w->setChecked(*v); + } } -void Settings::updateServers(ServerList servers) +QuestionBoxMemory::Button WidgetSettings::questionButton( + const QString& windowName, const QString& filename) const { - // clean up unavailable servers - servers.cleanup(); - - removeSection(m_Settings, "Servers"); + const QString sectionName("DialogChoices"); - { - ScopedWriteArray swa(m_Settings, "Servers"); + if (!filename.isEmpty()) { + const auto fileSetting = windowName + "/" + filename; + if (auto v=getOptional(m_Settings, sectionName, filename)) { + return static_cast(*v); + } + } - for (const auto& server : servers) { - swa.next(); + if (auto v=getOptional(m_Settings, sectionName, windowName)) { + return static_cast(*v); + } - swa.set("name", server.name()); - swa.set("premium", server.isPremium()); - swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); - swa.set("preferred", server.preferred()); + return QuestionBoxMemory::NoButton; +} - QString lastDownloads; - for (const auto& speed : server.lastDownloads()) { - if (speed > 0) { - lastDownloads += QString("%1 ").arg(speed); - } - } +void WidgetSettings::setQuestionWindowButton( + const QString& windowName, QuestionBoxMemory::Button button) +{ + const QString sectionName("DialogChoices"); - swa.set("lastDownloads", lastDownloads.trimmed()); - } + if (button == QuestionBoxMemory::NoButton) { + remove(m_Settings, sectionName, windowName); + } else { + set(m_Settings, sectionName, windowName, button); } } -std::map Settings::getRecentDirectories() const +void WidgetSettings::setQuestionFileButton( + const QString& windowName, const QString& filename, + QuestionBoxMemory::Button button) { - std::map map; + const QString sectionName("DialogChoices"); + const QString settingName(windowName + "/" + filename); - ScopedReadArray sra(m_Settings, "RecentDirectories"); + if (button == QuestionBoxMemory::NoButton) { + remove(m_Settings, sectionName, settingName); + } else { + set(m_Settings, sectionName, settingName, button); + } +} - sra.for_each([&] { - const QVariant name = sra.get("name"); - const QVariant dir = sra.get("directory"); +void WidgetSettings::resetQuestionButtons() +{ + removeSection(m_Settings, "DialogChoices"); +} - if (name.isValid() && dir.isValid()) { - map.emplace(name.toString(), dir.toString()); - } - }); - return map; +ColorSettings::ColorSettings(QSettings& s) + : m_Settings(s) +{ } -void Settings::setRecentDirectories(const std::map& map) +QColor ColorSettings::modlistOverwrittenLoose() const { - removeSection(m_Settings, "RecentDirectories"); + return get( + m_Settings, "Settings", "overwrittenLooseFilesColor", + QColor(0, 255, 0, 64)); +} - ScopedWriteArray swa(m_Settings, "recentDirectories"); +void ColorSettings::setModlistOverwrittenLoose(const QColor& c) +{ + set(m_Settings, "Settings", "overwrittenLooseFilesColor", c); +} - for (auto&& p : map) { - swa.next(); +QColor ColorSettings::modlistOverwritingLoose() const +{ + return get( + m_Settings, "Settings", "overwritingLooseFilesColor", + QColor(255, 0, 0, 64)); +} - swa.set("name", p.first); - swa.set("directory", p.second); - } +void ColorSettings::setModlistOverwritingLoose(const QColor& c) +{ + set(m_Settings, "Settings", "overwritingLooseFilesColor", c); } -std::vector> Settings::getExecutables() const +QColor ColorSettings::modlistOverwrittenArchive() const { - ScopedReadArray sra(m_Settings, "customExecutables"); - std::vector> v; + return get( + m_Settings, "Settings", "overwrittenArchiveFilesColor", + QColor(0, 255, 255, 64)); +} - sra.for_each([&]{ - std::map map; +void ColorSettings::setModlistOverwrittenArchive(const QColor& c) +{ + set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c); +} - for (auto&& key : sra.keys()) { - map[key] = m_Settings.value(key); - } +QColor ColorSettings::modlistOverwritingArchive() const +{ + return get( + m_Settings, "Settings", "overwritingArchiveFilesColor", + QColor(255, 0, 255, 64)); +} - v.push_back(map); - }); +void ColorSettings::setModlistOverwritingArchive(const QColor& c) +{ + set(m_Settings, "Settings", "overwritingArchiveFilesColor", c); +} - return v; +QColor ColorSettings::modlistContainsPlugin() const +{ + return get( + m_Settings, "Settings", "containsPluginColor", + QColor(0, 0, 255, 64)); } -void Settings::setExecutables(const std::vector>& v) +void ColorSettings::setModlistContainsPlugin(const QColor& c) { - removeSection(m_Settings, "customExecutables"); + set(m_Settings, "Settings", "containsPluginColor", c); +} - ScopedWriteArray swa(m_Settings, "customExecutables"); +QColor ColorSettings::pluginListContained() const +{ + return get( + m_Settings, "Settings", "containedColor", + QColor(0, 0, 255, 64)); +} - for (const auto& map : v) { - swa.next(); +void ColorSettings::setPluginListContained(const QColor& c) +{ + set(m_Settings, "Settings", "containedColor", c); +} - for (auto&& p : map) { - swa.set(p.first, p.second); - } +std::optional ColorSettings::previousSeparatorColor() const +{ + const auto c = getOptional(m_Settings, "General", "previousSeparatorColor"); + if (c && c->isValid()) { + return c; } + + return {}; } -bool Settings::isTutorialCompleted(const QString& windowName) const +void ColorSettings::setPreviousSeparatorColor(const QColor& c) const { - return get(m_Settings, "CompletedWindowTutorials", windowName, false); + set(m_Settings, "General", "previousSeparatorColor", c); } -void Settings::setTutorialCompleted(const QString& windowName, bool b) +void ColorSettings::removePreviousSeparatorColor() { - set(m_Settings, "CompletedWindowTutorials", windowName, b); + remove(m_Settings, "General", "previousSeparatorColor"); } -bool Settings::keepBackupOnInstall() const +bool ColorSettings::colorSeparatorScrollbar() const { - return get(m_Settings, "backup_install", false); + return get(m_Settings, "Settings", "colorSeparatorScrollbars", true); } -void Settings::setKeepBackupOnInstall(bool b) +void ColorSettings::setColorSeparatorScrollbar(bool b) { - set(m_Settings, "General", "backup_install", b); + set(m_Settings, "Settings", "colorSeparatorScrollbars", b); } -QuestionBoxMemory::Button Settings::getQuestionButton( - const QString& windowName, const QString& filename) const +QColor ColorSettings::idealTextColor(const QColor& rBackgroundColor) { - const QString sectionName("DialogChoices"); + if (rBackgroundColor.alpha() == 0) + return QColor(Qt::black); - if (!filename.isEmpty()) { - const auto fileSetting = windowName + "/" + filename; - if (auto v=getOptional(m_Settings, sectionName, filename)) { - return static_cast(*v); + const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha(); + int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); + return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white); +} + + + +PluginSettings::PluginSettings(QSettings& settings) + : m_Settings(settings) +{ +} + +void PluginSettings::clearPlugins() +{ + m_Plugins.clear(); + m_PluginSettings.clear(); + + m_PluginBlacklist.clear(); + + ScopedReadArray sra(m_Settings, "pluginBlacklist"); + sra.for_each([&]{ + m_PluginBlacklist.insert(sra.get("name")); + }); +} + +void PluginSettings::registerPlugin(IPlugin *plugin) +{ + m_Plugins.push_back(plugin); + m_PluginSettings.insert(plugin->name(), QVariantMap()); + m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + + for (const PluginSetting &setting : plugin->settings()) { + const QString settingName = plugin->name() + "/" + setting.key; + + QVariant temp = get( + m_Settings, "Plugins", settingName, setting.defaultValue); + + if (!temp.convert(setting.defaultValue.type())) { + log::warn( + "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", + temp.toString(), setting.key, plugin->name()); + + temp = setting.defaultValue; } - } - if (auto v=getOptional(m_Settings, sectionName, windowName)) { - return static_cast(*v); + m_PluginSettings[plugin->name()][setting.key] = temp; + + m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") + .arg(setting.description) + .arg(setting.defaultValue.toString()); } +} - return QuestionBoxMemory::NoButton; +bool PluginSettings::pluginBlacklisted(const QString &fileName) const +{ + return m_PluginBlacklist.contains(fileName); } -void Settings::setQuestionWindowButton( - const QString& windowName, QuestionBoxMemory::Button button) +QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString &key) const { - const QString sectionName("DialogChoices/"); + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + return QVariant(); + } - if (button == QuestionBoxMemory::NoButton) { - remove(m_Settings, sectionName, windowName); - } else { - set(m_Settings, sectionName, windowName, button); + auto iterSetting = iterPlugin->find(key); + if (iterSetting == iterPlugin->end()) { + return QVariant(); } + + return *iterSetting; } -void Settings::setQuestionFileButton( - const QString& windowName, const QString& filename, - QuestionBoxMemory::Button button) +void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { - const QString sectionName("DialogChoices"); - const QString settingName(windowName + "/" + filename); + auto iterPlugin = m_PluginSettings.find(pluginName); - if (button == QuestionBoxMemory::NoButton) { - remove(m_Settings, sectionName, settingName); - } else { - set(m_Settings, sectionName, settingName, button); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } + + // store the new setting both in memory and in the ini + m_PluginSettings[pluginName][key] = value; + set(m_Settings, "Plugins", pluginName + "/" + key, value); } -void Settings::resetQuestionButtons() +QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const { - removeSection(m_Settings, "DialogChoices"); + if (!m_PluginSettings.contains(pluginName)) { + return def; + } + + return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); } -std::optional Settings::getIndex(const QComboBox* cb) const +void PluginSettings::setPluginPersistent( + const QString &pluginName, const QString &key, const QVariant &value, bool sync) { - return getOptional(m_Settings, "Widgets", indexSettingName(cb)); + if (!m_PluginSettings.contains(pluginName)) { + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); + } + + set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); + + if (sync) { + m_Settings.sync(); + } } -void Settings::saveIndex(const QComboBox* cb) +void PluginSettings::addBlacklistPlugin(const QString &fileName) { - set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex()); + m_PluginBlacklist.insert(fileName); + writePluginBlacklist(); } -void Settings::restoreIndex(QComboBox* cb, std::optional def) const +void PluginSettings::writePluginBlacklist() { - if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { - cb->setCurrentIndex(*v); + removeSection(m_Settings, "PluginBlacklist"); + + ScopedWriteArray swa(m_Settings, "PluginBlacklist"); + + for (const QString &plugin : m_PluginBlacklist) { + swa.next(); + swa.set("name", plugin); } } -std::optional Settings::getIndex(const QTabWidget* w) const +QVariantMap PluginSettings::pluginSettings(const QString &pluginName) const { - return getOptional(m_Settings, "Widgets", indexSettingName(w)); + return m_PluginSettings[pluginName]; } -void Settings::saveIndex(const QTabWidget* w) +void PluginSettings::setPluginSettings(const QString &pluginName, const QVariantMap& map) { - set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex()); + m_PluginSettings[pluginName] = map; } -void Settings::restoreIndex(QTabWidget* w, std::optional def) const +QVariantMap PluginSettings::pluginDescriptions(const QString &pluginName) const { - if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { - w->setCurrentIndex(*v); - } + return m_PluginDescriptions[pluginName]; } -std::optional Settings::getChecked(const QAbstractButton* w) const +void PluginSettings::pluginDescriptions(const QString &pluginName, const QVariantMap& map) { - warnIfNotCheckable(w); - return getOptional(m_Settings, "Widgets", checkedSettingName(w)); + m_PluginDescriptions[pluginName] = map; } -void Settings::saveChecked(const QAbstractButton* w) +const QSet& PluginSettings::pluginBlacklist() const { - warnIfNotCheckable(w); - set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked()); + return m_PluginBlacklist; } -void Settings::restoreChecked(QAbstractButton* w, std::optional def) const +void PluginSettings::setPluginBlacklist(const QStringList& pluginNames) { - warnIfNotCheckable(w); + m_PluginBlacklist.clear(); - if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { - w->setChecked(*v); + for (const auto& name : pluginNames) { + m_PluginBlacklist.insert(name); } } -GeometrySettings& Settings::geometry() +void PluginSettings::save() { - return m_Geometry; -} + for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { + const auto key = iterPlugins.key() + "/" + iterSettings.key(); + set(m_Settings, "Plugins", key, iterSettings.value()); + } + } -const GeometrySettings& Settings::geometry() const -{ - return m_Geometry; + writePluginBlacklist(); } -ColorSettings& Settings::colors() -{ - return m_Colors; -} -const ColorSettings& Settings::colors() const +PathSettings::PathSettings(QSettings& settings) + : m_Settings(settings) { - return m_Colors; } -PluginSettings& Settings::plugins() +std::map PathSettings::recent() const { - return m_Plugins; -} + std::map map; -const PluginSettings& Settings::plugins() const -{ - return m_Plugins; -} + ScopedReadArray sra(m_Settings, "RecentDirectories"); -QSettings::Status Settings::sync() const -{ - m_Settings.sync(); - return m_Settings.status(); -} + sra.for_each([&] { + const QVariant name = sra.get("name"); + const QVariant dir = sra.get("directory"); -void Settings::dump() const -{ - static const QStringList ignore({ - "username", "password", "nexus_api_key" + if (name.isValid() && dir.isValid()) { + map.emplace(name.toString(), dir.toString()); + } }); - log::debug("settings:"); + return map; +} - { - ScopedGroup sg(m_Settings, "Settings"); +void PathSettings::setRecent(const std::map& map) +{ + removeSection(m_Settings, "RecentDirectories"); - for (auto k : m_Settings.allKeys()) { - if (ignore.contains(k, Qt::CaseInsensitive)) { - continue; - } + ScopedWriteArray swa(m_Settings, "recentDirectories"); - log::debug(" . {}={}", k, m_Settings.value(k).toString()); - } - } + for (auto&& p : map) { + swa.next(); - log::debug("servers:"); + swa.set("name", p.first); + swa.set("directory", p.second); + } +} - for (const auto& server : getServers()) { - QString lastDownloads; - for (auto speed : server.lastDownloads()) { - lastDownloads += QString("%1 ").arg(speed); - } +QString PathSettings::getConfigurablePath(const QString &key, + const QString &def, + bool resolve) const +{ + QString result = QDir::fromNativeSeparators( + get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); - log::debug( - " . {} premium={} lastSeen={} preferred={} lastDownloads={}", - server.name(), - server.isPremium() ? "yes" : "no", - server.lastSeen().toString(Qt::ISODate), - server.preferred(), - lastDownloads.trimmed()); + if (resolve) { + result.replace("%BASE_DIR%", base()); } -} + return result; +} -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s), m_Reset(false) +void PathSettings::setConfigurablePath(const QString &key, const QString& path) { + if (path.isEmpty()) { + remove(m_Settings, "Settings", key); + } else { + set(m_Settings, "Settings", key, path); + } } -void GeometrySettings::requestReset() +QString PathSettings::base() const { - m_Reset = true; + return QDir::fromNativeSeparators(get(m_Settings, + "Settings", "base_directory", qApp->property("dataPath").toString())); } -void GeometrySettings::resetIfNeeded() +QString PathSettings::downloads(bool resolve) const { - if (!m_Reset) { - return; - } - - removeSection(m_Settings, "Geometry"); + return getConfigurablePath( + "download_directory", + ToQString(AppConfig::downloadPath()), + resolve); } -void GeometrySettings::saveGeometry(const QWidget* w) +QString PathSettings::cache(bool resolve) const { - set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); + return getConfigurablePath( + "cache_directory", + ToQString(AppConfig::cachePath()), + resolve); } -bool GeometrySettings::restoreGeometry(QWidget* w) const +QString PathSettings::mods(bool resolve) const { - if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { - w->restoreGeometry(*v); - return true; - } + return getConfigurablePath( + "mod_directory", + ToQString(AppConfig::modsPath()), + resolve); +} - return false; +QString PathSettings::profiles(bool resolve) const +{ + return getConfigurablePath( + "profiles_directory", + ToQString(AppConfig::profilesPath()), + resolve); } -void GeometrySettings::saveState(const QMainWindow* w) +QString PathSettings::overwrite(bool resolve) const { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + return getConfigurablePath( + "overwrite_directory", + ToQString(AppConfig::overwritePath()), + resolve); } -bool GeometrySettings::restoreState(QMainWindow* w) const +void PathSettings::setBase(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; + if (path.isEmpty()) { + remove(m_Settings, "Settings", "base_directory"); + } else { + set(m_Settings, "Settings", "base_directory", path); } - - return false; } -void GeometrySettings::saveState(const QHeaderView* w) +void PathSettings::setDownloads(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + setConfigurablePath("download_directory", path); } -bool GeometrySettings::restoreState(QHeaderView* w) const +void PathSettings::setMods(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; - } - - return false; + setConfigurablePath("mod_directory", path); } -void GeometrySettings::saveState(const QSplitter* w) +void PathSettings::setCache(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + setConfigurablePath("cache_directory", path); } -bool GeometrySettings::restoreState(QSplitter* w) const +void PathSettings::setProfiles(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; - } - - return false; + setConfigurablePath("profiles_directory", path); } -void GeometrySettings::saveState(const ExpanderWidget* expander) +void PathSettings::setOverwrite(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState()); + setConfigurablePath("overwrite_directory", path); } -bool GeometrySettings::restoreState(ExpanderWidget* expander) const -{ - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { - expander->restoreState(*v); - return true; - } - return false; +NetworkSettings::NetworkSettings(QSettings& settings) + : m_Settings(settings) +{ } -void GeometrySettings::saveVisibility(const QWidget* w) +bool NetworkSettings::offlineMode() const { - set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible()); + return get(m_Settings, "Settings", "offline_mode", false); } -bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const +void NetworkSettings::setOfflineMode(bool b) { - if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { - w->setVisible(*v); - return true; - } + set(m_Settings, "Settings", "offline_mode", b); +} - return false; +bool NetworkSettings::useProxy() const +{ + return get(m_Settings, "Settings", "use_proxy", false); } -void GeometrySettings::restoreToolbars(QMainWindow* w) const +void NetworkSettings::setUseProxy(bool b) { - // all toolbars have the same size and button style settings - const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); - const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); + set(m_Settings, "Settings", "use_proxy", b); +} - for (auto* tb : w->findChildren()) { - if (size) { - tb->setIconSize(*size); - } +void NetworkSettings::setDownloadSpeed(const QString& name, int bytesPerSecond) +{ + auto current = servers(); - if (style) { - tb->setToolButtonStyle(static_cast(*style)); + for (auto& server : current) { + if (server.name() == name) { + server.addDownload(bytesPerSecond); + updateServers(current); + return; } - - restoreVisibility(tb); } + + log::error( + "server '{}' not found while trying to add a download with bps {}", + name, bytesPerSecond); } -void GeometrySettings::saveToolbars(const QMainWindow* w) +ServerList NetworkSettings::servers() const { - const auto tbs = w->findChildren(); + // servers used to be a map of byte arrays until 2.2.1, it's now an array of + // individual values instead + // + // so post 2.2.1, only one key is returned: "size", the size of the arrays; + // in 2.2.1, one key per server is returned + { + const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - // save visibility for all - for (auto* tb : tbs) { - saveVisibility(tb); + if (!keys.empty() && keys[0] != "size") { + // old format + return serversFromOldMap(); + } } - // all toolbars have the same size and button style settings, just save the - // first one - if (!tbs.isEmpty()) { - const auto* tb = tbs[0]; - set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); - set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); - } -} + // post 2.2.1 format, array of values -QStringList GeometrySettings::getModInfoTabOrder() const -{ - QStringList v; + ServerList list; - if (m_Settings.contains("mod_info_tabs")) { - // old byte array from 2.2.0 - QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray()); + { + ScopedReadArray sra(m_Settings, "Servers"); - int count = 0; - stream >> count; + sra.for_each([&] { + ServerInfo::SpeedList lastDownloads; - for (int i=0; i> s; - v.push_back(s); - } - } else { - // string list since 2.2.1 - QString string = m_Settings.value("mod_info_tab_order").toString(); - QTextStream stream(&string); + const auto lastDownloadsString = sra.get("lastDownloads", ""); - while (!stream.atEnd()) { - QString s; - stream >> s; - v.push_back(s); - } + for (const auto& s : lastDownloadsString.split(" ")) { + const auto bytesPerSecond = s.toInt(); + if (bytesPerSecond > 0) { + lastDownloads.push_back(bytesPerSecond); + } + } + + ServerInfo server( + sra.get("name", ""), + sra.get("premium", false), + QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), + sra.get("preferred", 0), + lastDownloads); + + list.add(std::move(server)); + }); } - return v; + return list; } -void GeometrySettings::setModInfoTabOrder(const QString& names) +ServerList NetworkSettings::serversFromOldMap() const { - set(m_Settings, "Geometry", "mod_info_tab_order", names); -} + // for 2.2.1 and before -void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) -{ - const auto monitor = getOptional( - m_Settings, "Geometry", "MainWindow_monitor"); + ServerList list; + const ScopedGroup sg(m_Settings, "Servers"); - QPoint center; + sg.for_each([&](auto&& serverKey) { + QVariantMap data = sg.get(serverKey); - if (monitor && QGuiApplication::screens().size() > *monitor) { - center = QGuiApplication::screens().at(*monitor)->geometry().center(); - } else { - center = QGuiApplication::primaryScreen()->geometry().center(); - } + ServerInfo server( + serverKey, + data["premium"].toBool(), + data["lastSeen"].toDate(), + data["preferred"].toInt(), + {}); - w->move(center - w->rect().center()); -} + // ignoring download count and speed, it's now a list of values instead of + // a total -void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) -{ - if (auto* handle=w->windowHandle()) { - if (auto* screen = handle->screen()) { - const int screenId = QGuiApplication::screens().indexOf(screen); - set(m_Settings, "Geometry", "MainWindow_monitor", screenId); - } - } + list.add(std::move(server)); + }); + + return list; } -Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) +void NetworkSettings::updateServers(ServerList servers) { - // docks in these areas are horizontal - const auto horizontalAreas = - Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + // clean up unavailable servers + servers.cleanup(); - if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { - return Qt::Horizontal; - } else { - return Qt::Vertical; - } -} + removeSection(m_Settings, "Servers"); -void GeometrySettings::saveDocks(const QMainWindow* mw) -{ - // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock - // sizes are not restored when the main window is maximized; it is used in - // MainWindow::readSettings() and MainWindow::storeSettings() - // - // there's also https://stackoverflow.com/questions/44005852, which has what - // seems to be a popular fix, but it breaks the restored size of the window - // by setting it to the desktop's resolution, so that doesn't work - // - // the only fix I could find is to remember the sizes of the docks and manually - // setting them back; saving is straightforward, but restoring is messy - // - // this also depends on the window being visible before the timer in restore() - // is fired and the timer must be processed by application.exec(); therefore, - // the splash screen _must_ be closed before readSettings() is called, because - // it has its own event loop, which seems to interfere with this - // - // all of this should become unnecessary when QTBUG-46620 is fixed - // + { + ScopedWriteArray swa(m_Settings, "Servers"); - // saves the size of each dock - for (const auto* dock : mw->findChildren()) { - int size = 0; + for (const auto& server : servers) { + swa.next(); - // save the width for horizontal docks, or the height for vertical - if (dockOrientation(mw, dock) == Qt::Horizontal) { - size = dock->size().width(); - } else { - size = dock->size().height(); - } + swa.set("name", server.name()); + swa.set("premium", server.isPremium()); + swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); + swa.set("preferred", server.preferred()); - set(m_Settings, "Geometry", dockSettingName(dock), size); + QString lastDownloads; + for (const auto& speed : server.lastDownloads()) { + if (speed > 0) { + lastDownloads += QString("%1 ").arg(speed); + } + } + + swa.set("lastDownloads", lastDownloads.trimmed()); + } } } -void GeometrySettings::restoreDocks(QMainWindow* mw) const +void NetworkSettings::dump() const { - struct DockInfo - { - QDockWidget* d; - int size = 0; - Qt::Orientation ori; - }; - - std::vector dockInfos; + log::debug("servers:"); - // for each dock - for (auto* dock : mw->findChildren()) { - if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { - // remember this dock, its size and orientation - dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); + for (const auto& server : servers()) { + QString lastDownloads; + for (auto speed : server.lastDownloads()) { + lastDownloads += QString("%1 ").arg(speed); } - } - // the main window must have had time to process the settings from - // readSettings() or it seems to override whatever is set here - // - // some people said a single processEvents() call is enough, but it doesn't - // look like it - QTimer::singleShot(5, [=] { - for (const auto& info : dockInfos) { - mw->resizeDocks({info.d}, {info.size}, info.ori); - } - }); + log::debug( + " . {} premium={} lastSeen={} preferred={} lastDownloads={}", + server.name(), + server.isPremium() ? "yes" : "no", + server.lastSeen().toString(Qt::ISODate), + server.preferred(), + lastDownloads.trimmed()); + } } -ColorSettings::ColorSettings(QSettings& s) - : m_Settings(s) +NexusSettings::NexusSettings(Settings& parent, QSettings& settings) + : m_Parent(parent), m_Settings(settings) { } -QColor ColorSettings::modlistOverwrittenLoose() const +bool NexusSettings::automaticLoginEnabled() const { - return get( - m_Settings, "Settings", "overwrittenLooseFilesColor", - QColor(0, 255, 0, 64)); + return get(m_Settings, "Settings", "nexus_login", false); } -void ColorSettings::setModlistOverwrittenLoose(const QColor& c) +bool NexusSettings::apiKey(QString &apiKey) const { - set(m_Settings, "Settings", "overwrittenLooseFilesColor", c); + QString tempKey = getWindowsCredential("APIKEY"); + if (tempKey.isEmpty()) + return false; + + apiKey = tempKey; + return true; } -QColor ColorSettings::modlistOverwritingLoose() const +bool NexusSettings::setApiKey(const QString& apiKey) { - return get( - m_Settings, "Settings", "overwritingLooseFilesColor", - QColor(255, 0, 0, 64)); + if (!setWindowsCredential("APIKEY", apiKey)) { + const auto e = GetLastError(); + log::error("Storing API key failed: {}", formatSystemMessage(e)); + return false; + } + + return true; } -void ColorSettings::setModlistOverwritingLoose(const QColor& c) +bool NexusSettings::clearApiKey() { - set(m_Settings, "Settings", "overwritingLooseFilesColor", c); + return setApiKey(""); } -QColor ColorSettings::modlistOverwrittenArchive() const +bool NexusSettings::hasApiKey() const { - return get( - m_Settings, "Settings", "overwrittenArchiveFilesColor", - QColor(0, 255, 255, 64)); + return !getWindowsCredential("APIKEY").isEmpty(); } -void ColorSettings::setModlistOverwrittenArchive(const QColor& c) +bool NexusSettings::endorsementIntegration() const { - set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c); + return get(m_Settings, "Settings", "endorsement_integration", true); } -QColor ColorSettings::modlistOverwritingArchive() const +void NexusSettings::setEndorsementIntegration(bool b) const { - return get( - m_Settings, "Settings", "overwritingArchiveFilesColor", - QColor(255, 0, 255, 64)); + set(m_Settings, "Settings", "endorsement_integration", b); } -void ColorSettings::setModlistOverwritingArchive(const QColor& c) +EndorsementState NexusSettings::endorsementState() const { - set(m_Settings, "Settings", "overwritingArchiveFilesColor", c); + return endorsementStateFromString( + get(m_Settings, "General", "endorse_state", "")); } -QColor ColorSettings::modlistContainsPlugin() const +void NexusSettings::setEndorsementState(EndorsementState s) { - return get( - m_Settings, "Settings", "containsPluginColor", - QColor(0, 0, 255, 64)); + const auto v = toString(s); + + if (v.isEmpty()) { + remove(m_Settings, "General", "endorse_state"); + } else { + set(m_Settings, "General", "endorse_state", v); + } } -void ColorSettings::setModlistContainsPlugin(const QColor& c) +void NexusSettings::registerAsNXMHandler(bool force) { - set(m_Settings, "Settings", "containsPluginColor", c); + const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; + const auto executable = QCoreApplication::applicationFilePath(); + + QString mode = force ? "forcereg" : "reg"; + QString parameters = mode + " " + m_Parent.game().plugin()->gameShortName(); + for (const QString& altGame : m_Parent.game().plugin()->validShortNames()) { + parameters += "," + altGame; + } + parameters += " \"" + executable + "\""; + + if (!shell::Execute(nxmPath, parameters)) { + QMessageBox::critical( + nullptr, QObject::tr("Failed"), + QObject::tr("Failed to start the helper application")); + } } -QColor ColorSettings::pluginListContained() const + +SteamSettings::SteamSettings(Settings& parent, QSettings& settings) + : m_Parent(parent), m_Settings(settings) { - return get( - m_Settings, "Settings", "containedColor", - QColor(0, 0, 255, 64)); } -void ColorSettings::setPluginListContained(const QColor& c) +QString SteamSettings::appID() const { - set(m_Settings, "Settings", "containedColor", c); + return get( + m_Settings, "Settings", "app_id", m_Parent.game().plugin()->steamAPPId()); } - -PluginSettings::PluginSettings(QSettings& settings) - : m_Settings(settings) +void SteamSettings::setAppID(const QString& id) { + if (id.isEmpty()) { + remove(m_Settings, "Settings", "app_id"); + } else { + set(m_Settings, "Settings", "app_id", id); + } } -void PluginSettings::clearPlugins() +bool SteamSettings::login(QString &username, QString &password) const { - m_Plugins.clear(); - m_PluginSettings.clear(); - - m_PluginBlacklist.clear(); + username = get(m_Settings, "Settings", "steam_username", ""); + password = getWindowsCredential("steam_password"); - ScopedReadArray sra(m_Settings, "pluginBlacklist"); - sra.for_each([&]{ - m_PluginBlacklist.insert(sra.get("name")); - }); + return !username.isEmpty() && !password.isEmpty(); } -void PluginSettings::registerPlugin(IPlugin *plugin) +void SteamSettings::setLogin(QString username, QString password) { - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QVariantMap()); - m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + if (username == "") { + remove(m_Settings, "Settings", "steam_username"); + password = ""; + } else { + set(m_Settings, "Settings", "steam_username", username); + } - for (const PluginSetting &setting : plugin->settings()) { - const QString settingName = plugin->name() + "/" + setting.key; + if (!setWindowsCredential("steam_password", password)) { + const auto e = GetLastError(); + log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); + } +} - QVariant temp = get( - m_Settings, "Plugins", settingName, setting.defaultValue); - if (!temp.convert(setting.defaultValue.type())) { - log::warn( - "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", - temp.toString(), setting.key, plugin->name()); +InterfaceSettings::InterfaceSettings(QSettings& settings) + : m_Settings(settings) +{ +} - temp = setting.defaultValue; - } +bool InterfaceSettings::lockGUI() const +{ + return get(m_Settings, "Settings", "lock_gui", true); +} - m_PluginSettings[plugin->name()][setting.key] = temp; +void InterfaceSettings::setLockGUI(bool b) +{ + set(m_Settings, "Settings", "lock_gui", b); +} - m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") - .arg(setting.description) - .arg(setting.defaultValue.toString()); - } +std::optional InterfaceSettings::styleName() const +{ + return getOptional(m_Settings, "Settings", "style"); } -bool PluginSettings::pluginBlacklisted(const QString &fileName) const +void InterfaceSettings::setStyleName(const QString& name) { - return m_PluginBlacklist.contains(fileName); + set(m_Settings, "Settings", "style", name); } -QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString &key) const +bool InterfaceSettings::compactDownloads() const { - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - return QVariant(); - } + return get(m_Settings, "Settings", "compact_downloads", false); +} - auto iterSetting = iterPlugin->find(key); - if (iterSetting == iterPlugin->end()) { - return QVariant(); - } +void InterfaceSettings::setCompactDownloads(bool b) +{ + set(m_Settings, "Settings", "compact_downloads", b); +} - return *iterSetting; +bool InterfaceSettings::metaDownloads() const +{ + return get(m_Settings, "Settings", "meta_downloads", false); } -void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +void InterfaceSettings::setMetaDownloads(bool b) { - auto iterPlugin = m_PluginSettings.find(pluginName); + set(m_Settings, "Settings", "meta_downloads", b); +} - if (iterPlugin == m_PluginSettings.end()) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } +bool InterfaceSettings::hideAPICounter() const +{ + return get(m_Settings, "Settings", "hide_api_counter", false); +} - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - set(m_Settings, "Plugins", pluginName + "/" + key, value); +void InterfaceSettings::setHideAPICounter(bool b) +{ + set(m_Settings, "Settings", "hide_api_counter", b); } -QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +bool InterfaceSettings::displayForeign() const { - if (!m_PluginSettings.contains(pluginName)) { - return def; - } + return get(m_Settings, "Settings", "display_foreign", true); +} - return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); +void InterfaceSettings::setDisplayForeign(bool b) +{ + set(m_Settings, "Settings", "display_foreign", b); } -void PluginSettings::setPluginPersistent( - const QString &pluginName, const QString &key, const QVariant &value, bool sync) +QString InterfaceSettings::language() { - if (!m_PluginSettings.contains(pluginName)) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } + QString result = get(m_Settings, "Settings", "language", ""); - set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); + if (result.isEmpty()) { + QStringList languagePreferences = QLocale::system().uiLanguages(); - if (sync) { - m_Settings.sync(); + if (languagePreferences.length() > 0) { + // the users most favoritest language + result = languagePreferences.at(0); + } else { + // fallback system locale + result = QLocale::system().name(); + } } + + return result; } -void PluginSettings::addBlacklistPlugin(const QString &fileName) +void InterfaceSettings::setLanguage(const QString& name) { - m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); + set(m_Settings, "Settings", "language", name); } -void PluginSettings::writePluginBlacklist() +bool InterfaceSettings::isTutorialCompleted(const QString& windowName) const { - removeSection(m_Settings, "PluginBlacklist"); - - ScopedWriteArray swa(m_Settings, "PluginBlacklist"); - - for (const QString &plugin : m_PluginBlacklist) { - swa.next(); - swa.set("name", plugin); - } + return get(m_Settings, "CompletedWindowTutorials", windowName, false); } -QVariantMap PluginSettings::pluginSettings(const QString &pluginName) const +void InterfaceSettings::setTutorialCompleted(const QString& windowName, bool b) { - return m_PluginSettings[pluginName]; + set(m_Settings, "CompletedWindowTutorials", windowName, b); } -void PluginSettings::setPluginSettings(const QString &pluginName, const QVariantMap& map) + +DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) + : m_Settings(settings) { - m_PluginSettings[pluginName] = map; } -QVariantMap PluginSettings::pluginDescriptions(const QString &pluginName) const +log::Levels DiagnosticsSettings::logLevel() const { - return m_PluginDescriptions[pluginName]; + return get(m_Settings, "Settings", "log_level", log::Levels::Info); } -void PluginSettings::pluginDescriptions(const QString &pluginName, const QVariantMap& map) +void DiagnosticsSettings::setLogLevel(log::Levels level) { - m_PluginDescriptions[pluginName] = map; + set(m_Settings, "Settings", "log_level", level); } -const QSet& PluginSettings::pluginBlacklist() const +CrashDumpsType DiagnosticsSettings::crashDumpsType() const { - return m_PluginBlacklist; + return get(m_Settings, + "Settings", "crash_dumps_type", CrashDumpsType::Mini); } -void PluginSettings::setPluginBlacklist(const QStringList& pluginNames) +void DiagnosticsSettings::setCrashDumpsType(CrashDumpsType type) { - m_PluginBlacklist.clear(); - - for (const auto& name : pluginNames) { - m_PluginBlacklist.insert(name); - } + set(m_Settings, "Settings", "crash_dumps_type", type); } -void PluginSettings::save() +int DiagnosticsSettings::crashDumpsMax() const { - for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { - const auto key = iterPlugins.key() + "/" + iterSettings.key(); - set(m_Settings, "Plugins", key, iterSettings.value()); - } - } + return get(m_Settings, "Settings", "crash_dumps_max", 5); +} - writePluginBlacklist(); +void DiagnosticsSettings::setCrashDumpsMax(int n) +{ + set(m_Settings, "Settings", "crash_dumps_max", n); } diff --git a/src/settings.h b/src/settings.h index 403c2d71..5c0a2542 100644 --- a/src/settings.h +++ b/src/settings.h @@ -25,6 +25,10 @@ along with Mod Organizer. If not, see . #include #include +#ifdef interface + #undef interface +#endif + namespace MOBase { class IPlugin; class IPluginGame; @@ -50,6 +54,58 @@ private: }; +class GameSettings +{ +public: + GameSettings(QSettings& setting); + + const MOBase::IPluginGame* plugin(); + void setPlugin(const MOBase::IPluginGame* gamePlugin); + + /** + * whether files of the core game are forced-enabled so the user can't + * accidentally disable them + */ + bool forceEnableCoreFiles() const; + void setForceEnableCoreFiles(bool b); + + /** + * the directory where the managed game is stored (with native separators) + **/ + std::optional directory() const; + void setDirectory(const QString& path); + + std::optional name() const; + void setName(const QString& name); + + std::optional edition() const; + void setEdition(const QString& name); + + std::optional selectedProfileName() const; + void setSelectedProfileName(const QString& name); + + /** + * @return the load mechanism to be used + **/ + LoadMechanism::EMechanism loadMechanismType() const; + void setLoadMechanism(LoadMechanism::EMechanism m); + const LoadMechanism& loadMechanism() const; + void setupLoadMechanism(); + + /** + * @return true if the user wants unchecked plugins (esp, esm) should be hidden from + * the virtual data directory + **/ + bool hideUncheckedPlugins() const; + void setHideUncheckedPlugins(bool b); + +private: + QSettings& m_Settings; + const MOBase::IPluginGame* m_GamePlugin; + LoadMechanism m_LoadMechanism; +}; + + class GeometrySettings { public: @@ -83,7 +139,7 @@ public: void saveDocks(const QMainWindow* w); void restoreDocks(QMainWindow* w) const; - QStringList getModInfoTabOrder() const; + QStringList modInfoTabOrder() const; void setModInfoTabOrder(const QString& names); void centerOnMainWindowMonitor(QWidget* w); @@ -95,6 +151,40 @@ private: }; +class WidgetSettings +{ +public: + WidgetSettings(QSettings& s); + + std::optional index(const QComboBox* cb) const; + void saveIndex(const QComboBox* cb); + void restoreIndex(QComboBox* cb, std::optional def={}) const; + + std::optional index(const QTabWidget* w) const; + void saveIndex(const QTabWidget* w); + void restoreIndex(QTabWidget* w, std::optional def={}) const; + + std::optional checked(const QAbstractButton* w) const; + void saveChecked(const QAbstractButton* w); + void restoreChecked(QAbstractButton* w, std::optional def={}) const; + + MOBase::QuestionBoxMemory::Button questionButton( + const QString& windowName, const QString& filename) const; + + void setQuestionWindowButton( + const QString& windowName, MOBase::QuestionBoxMemory::Button button); + + void setQuestionFileButton( + const QString& windowName, const QString& filename, + MOBase::QuestionBoxMemory::Button choice); + + void resetQuestionButtons(); + +private: + QSettings& m_Settings; +}; + + class ColorSettings { public: @@ -120,6 +210,19 @@ public: QColor pluginListContained() const; void setPluginListContained(const QColor& c) ; + std::optional previousSeparatorColor() const; + void setPreviousSeparatorColor(const QColor& c) const; + void removePreviousSeparatorColor(); + + /** + * @brief color the scrollbar of the mod list for custom separator colors? + * @return the state of the setting + */ + bool colorSeparatorScrollbar() const; + void setColorSeparatorScrollbar(bool b); + + static QColor idealTextColor(const QColor& rBackgroundColor); + private: QSettings& m_Settings; }; @@ -165,210 +268,229 @@ private: }; -enum class EndorsementState -{ - Accepted = 1, - Refused, - NoDecision -}; - -EndorsementState endorsementStateFromString(const QString& s); -QString toString(EndorsementState s); - - -/** - * 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 +class PathSettings { - Q_OBJECT; - public: - Settings(const QString& path); - ~Settings(); + PathSettings(QSettings& settings); - static Settings &instance(); + QString base() const; + QString downloads(bool resolve = true) const; + QString mods(bool resolve = true) const; + QString cache(bool resolve = true) const; + QString profiles(bool resolve = true) const; + QString overwrite(bool resolve = true) const; - void processUpdates( - const QVersionNumber& currentVersion, const QVersionNumber& lastVersion); + void setBase(const QString& path); + void setDownloads(const QString& path); + void setMods(const QString& path); + void setCache(const QString& path); + void setProfiles(const QString& path); + void setOverwrite(const QString& path); - QString getFilename() const; + std::map recent() const; + void setRecent(const std::map& map); - /** - * @return true if the user wants unchecked plugins (esp, esm) should be hidden from - * the virtual dat adirectory - **/ - bool hideUncheckedPlugins() const; - void setHideUncheckedPlugins(bool b); +private: + QSettings& m_Settings; - /** - * @return true if files of the core game are forced-enabled so the user can't accidentally disable them - */ - bool forceEnableCoreFiles() const; - void setForceEnableCoreFiles(bool b); + QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; + void setConfigurablePath(const QString &key, const QString& path); +}; - /** - * @return true if the GUI should be locked when running executables - */ - bool lockGUI() const; - void setLockGUI(bool b); - /** - * the steam appid is assigned by the steam platform to each product sold there. - * The appid may differ between different versions of a game so it may be impossible - * for Mod Organizer to automatically recognize it, though usually it does - * @return the steam appid for the game - **/ - QString getSteamAppID() const; - void setSteamAppID(const QString& id); - - QString getBaseDirectory() const; - QString getDownloadDirectory(bool resolve = true) const; - QString getModDirectory(bool resolve = true) const; - QString getCacheDirectory(bool resolve = true) const; - QString getProfileDirectory(bool resolve = true) const; - QString getOverwriteDirectory(bool resolve = true) const; - - void setBaseDirectory(const QString& path); - void setDownloadDirectory(const QString& path); - void setModDirectory(const QString& path); - void setCacheDirectory(const QString& path); - void setProfileDirectory(const QString& path); - void setOverwriteDirectory(const QString& path); +class NetworkSettings +{ +public: + NetworkSettings(QSettings& settings); /** - * retrieve the directory where the managed game is stored (with native separators) - **/ - std::optional getManagedGameDirectory() const; - void setManagedGameDirectory(const QString& path); - - std::optional getManagedGameName() const; - void setManagedGameName(const QString& name); - - std::optional getManagedGameEdition() const; - void setManagedGameEdition(const QString& name); + * @return true if the user disabled internet features + */ + bool offlineMode() const; + void setOfflineMode(bool b); - std::optional getSelectedProfileName() const; - void setSelectedProfileName(const QString& name); + /** + * @return true if the user configured the use of a network proxy + */ + bool useProxy() const; + void setUseProxy(bool b); - std::optional getStyleName() const; - void setStyleName(const QString& name); + void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + ServerList servers() const; + void updateServers(ServerList servers); - std::optional getVersion() const; + void dump() const; - bool getFirstStart() const; - void setFirstStart(bool b); +private: + QSettings& m_Settings; - std::optional getPreviousSeparatorColor() const; - void setPreviousSeparatorColor(const QColor& c) const; - void removePreviousSeparatorColor(); + ServerList serversFromOldMap() const; +}; - std::map getRecentDirectories() const; - void setRecentDirectories(const std::map& map); - std::vector> getExecutables() const; - void setExecutables(const std::vector>& v); +enum class EndorsementState +{ + Accepted = 1, + Refused, + NoDecision +}; - bool isTutorialCompleted(const QString& windowName) const; - void setTutorialCompleted(const QString& windowName, bool b=true); +EndorsementState endorsementStateFromString(const QString& s); +QString toString(EndorsementState s); - bool keepBackupOnInstall() const; - void setKeepBackupOnInstall(bool b); +class NexusSettings +{ +public: + NexusSettings(Settings& parent, QSettings& settings); - MOBase::QuestionBoxMemory::Button getQuestionButton( - const QString& windowName, const QString& filename) const; + /** + * @return true if the user has set up automatic login to nexus + **/ + bool automaticLoginEnabled() const; - void setQuestionWindowButton( - const QString& windowName, MOBase::QuestionBoxMemory::Button button); + /** + * @brief retrieve the login information for nexus + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if automatic login is active, false otherwise + **/ + bool apiKey(QString &apiKey) const; - void setQuestionFileButton( - const QString& windowName, const QString& filename, - MOBase::QuestionBoxMemory::Button choice); + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + bool setApiKey(const QString& apiKey); - void resetQuestionButtons(); + /** + * @brief clears the nexus login information + */ + bool clearApiKey(); - std::optional getIndex(const QComboBox* cb) const; - void saveIndex(const QComboBox* cb); - void restoreIndex(QComboBox* cb, std::optional def={}) const; + /** + * @brief returns whether an API key is currently stored + */ + bool hasApiKey() const; - std::optional getIndex(const QTabWidget* w) const; - void saveIndex(const QTabWidget* w); - void restoreIndex(QTabWidget* w, std::optional def={}) const; + /** + * @return true if endorsement integration is enabled + */ + bool endorsementIntegration() const; + void setEndorsementIntegration(bool b) const; - std::optional getChecked(const QAbstractButton* w) const; - void saveChecked(const QAbstractButton* w); - void restoreChecked(QAbstractButton* w, std::optional def={}) const; + EndorsementState endorsementState() const; + void setEndorsementState(EndorsementState s); - GeometrySettings& geometry(); - const GeometrySettings& geometry() const; + /** + * @brief register MO as the handler for nxm links + * @param force set to true to enforce the registration dialog to show up, + * even if the user said earlier not to + */ + void registerAsNXMHandler(bool force); - ColorSettings& colors(); - const ColorSettings& colors() const; +private: + Settings& m_Parent; + QSettings& m_Settings; +}; - PluginSettings& plugins(); - const PluginSettings& plugins() const; +class SteamSettings +{ +public: + SteamSettings(Settings& parent, QSettings& settings); /** - * @return true if the user has set up automatic login to nexus - **/ - bool automaticLoginEnabled() const; + * the steam appid is assigned by the steam platform to each product sold there. + * The appid may differ between different versions of a game so it may be impossible + * for Mod Organizer to automatically recognize it, though usually it does + * @return the steam appid for the game + **/ + QString appID() const; + void setAppID(const QString& id); /** - * @brief retrieve the login information for nexus - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if automatic login is active, false otherwise - **/ - bool getNexusApiKey(QString &apiKey) const; + * @brief retrieve the login information for steam + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if a username has been specified, false otherwise + **/ + bool login(QString &username, QString &password) const; /** - * @brief set the nexus login information + * @brief set the steam login information * * @param username username * @param password password */ - bool setNexusApiKey(const QString& apiKey); + void setLogin(QString username, QString password); - /** - * @brief clears the nexus login information - */ - bool clearNexusApiKey(); +private: + Settings& m_Parent; + QSettings& m_Settings; +}; - /** - * @brief returns whether an API key is currently stored - */ - bool hasNexusApiKey() const; - /** - * @brief retrieve the login information for steam - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if a username has been specified, false otherwise - **/ - bool getSteamLogin(QString &username, QString &password) const; +class InterfaceSettings +{ +public: + InterfaceSettings(QSettings& settings); /** - * @return true if the user disabled internet features - */ - bool offlineMode() const; - void setOfflineMode(bool b); + * @return true if the GUI should be locked when running executables + */ + bool lockGUI() const; + void setLockGUI(bool b); + + std::optional styleName() const; + void setStyleName(const QString& name); /** - * @return true if the user chose compact downloads - */ + * @return true if the user chose compact downloads + */ bool compactDownloads() const; void setCompactDownloads(bool b); /** - * @return true if the user chose meta downloads - */ + * @return true if the user chose meta downloads + */ bool metaDownloads() const; void setMetaDownloads(bool b); + /** + * @return true if the API counter should be hidden + */ + bool hideAPICounter() const; + void setHideAPICounter(bool b); + + /** + * @return true if the user wants to see non-official plugins installed outside MO in his mod list + */ + bool displayForeign() const; + void setDisplayForeign(bool b); + + /** + * @return short code of the configured language (corresponding to the translation files) + */ + QString language(); + void setLanguage(const QString& name); + + bool isTutorialCompleted(const QString& windowName) const; + void setTutorialCompleted(const QString& windowName, bool b=true); + +private: + QSettings& m_Settings; +}; + + +class DiagnosticsSettings +{ +public: + DiagnosticsSettings(QSettings& settings); + MOBase::log::Levels logLevel() const; void setLogLevel(MOBase::log::Levels level); @@ -378,60 +500,48 @@ public: int crashDumpsMax() const; void setCrashDumpsMax(int n); - QString executablesBlacklist() const; - void setExecutablesBlacklist(const QString& s); +private: + QSettings& m_Settings; +}; - /** - * @brief set the steam login information - * - * @param username username - * @param password password - */ - void setSteamLogin(QString username, QString password); - /** - * @return the load mechanism to be used - **/ - LoadMechanism::EMechanism getLoadMechanism() const; - void setLoadMechanism(LoadMechanism::EMechanism m); - /** - * @brief activate the load mechanism selected by the user - **/ - void setupLoadMechanism(); +/** + * 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; - /** - * @return true if the user configured the use of a network proxy - */ - bool getUseProxy() const; - void setUseProxy(bool b); +public: + Settings(const QString& path); + ~Settings(); - /** - * @return true if endorsement integration is enabled - */ - bool endorsementIntegration() const; - void setEndorsementIntegration(bool b) const; + static Settings &instance(); - EndorsementState endorsementState() const; - void setEndorsementState(EndorsementState s); + QString filename() const; - /** - * @return true if the API counter should be hidden - */ - bool hideAPICounter() const; - void setHideAPICounter(bool b); + std::optional version() const; + void processUpdates(const QVersionNumber& current, const QVersionNumber& last); - /** - * @return true if the user wants to see non-official plugins installed outside MO in his mod list - */ - bool displayForeign() const; - void setDisplayForeign(bool b); + bool firstStart() const; + void setFirstStart(bool b); + + std::vector> executables() const; + void setExecutables(const std::vector>& v); + + bool keepBackupOnInstall() const; + void setKeepBackupOnInstall(bool b); + + QString executablesBlacklist() const; + void setExecutablesBlacklist(const QString& s); /** * @brief sets the new motd hash **/ - unsigned int getMotDHash() const; - void setMotDHash(unsigned int hash); + unsigned int motdHash() const; + void setMotdHash(unsigned int hash); /** * @return true if the user wants to have archives being parsed to show conflicts and contents @@ -439,41 +549,45 @@ public: bool archiveParsing() const; void setArchiveParsing(bool b); - /** - * @return short code of the configured language (corresponding to the translation files) - */ - QString language(); - void setLanguage(const QString& name); - - void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - ServerList getServers() const; - ServerList getServersFromOldMap() const; - void updateServers(ServerList servers); - bool usePrereleases() const; void setUsePrereleases(bool b); - /** - * @brief register MO as the handler for nxm links - * @param force set to true to enforce the registration dialog to show up, - * even if the user said earlier not to - */ - void registerAsNXMHandler(bool force); - /** - * @brief color the scrollbar of the mod list for custom separator colors? - * @return the state of the setting - */ - bool colorSeparatorScrollbar() const; - void setColorSeparatorScrollbar(bool b); + GameSettings& game(); + const GameSettings& game() const; + + GeometrySettings& geometry(); + const GeometrySettings& geometry() const; - static QColor getIdealTextColor(const QColor& rBackgroundColor); + WidgetSettings& widgets(); + const WidgetSettings& widgets() const; - MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } - const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + ColorSettings& colors(); + const ColorSettings& colors() const; - QSettings::Status sync() const; + PluginSettings& plugins(); + const PluginSettings& plugins() const; + + PathSettings& paths(); + const PathSettings& paths() const; + + NetworkSettings& network(); + const NetworkSettings& network() const; + + NexusSettings& nexus(); + const NexusSettings& nexus() const; + + SteamSettings& steam(); + const SteamSettings& steam() const; + InterfaceSettings& interface(); + const InterfaceSettings& interface() const; + + DiagnosticsSettings& diagnostics(); + const DiagnosticsSettings& diagnostics() const; + + + QSettings::Status sync() const; void dump() const; public slots: @@ -485,18 +599,19 @@ signals: private: static Settings *s_Instance; - MOBase::IPluginGame const *m_GamePlugin; mutable QSettings m_Settings; + + GameSettings m_Game; GeometrySettings m_Geometry; + WidgetSettings m_Widgets; ColorSettings m_Colors; PluginSettings m_Plugins; - LoadMechanism m_LoadMechanism; - - static bool obfuscate(const QString key, const QString data); - static QString deObfuscate(const QString key); - - QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; - void setConfigurablePath(const QString &key, const QString& path); + PathSettings m_Paths; + NetworkSettings m_Network; + NexusSettings m_Nexus; + SteamSettings m_Steam; + InterfaceSettings m_Interface; + DiagnosticsSettings m_Diagnostics; }; #endif // SETTINGS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 35d14644..1d3d4a39 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -51,11 +51,11 @@ int SettingsDialog::exec() { GeometrySaver gs(m_settings, this); - m_settings.restoreIndex(ui->tabWidget); + m_settings.widgets().restoreIndex(ui->tabWidget); auto ret = TutorableDialog::exec(); - m_settings.saveIndex(ui->tabWidget); + m_settings.widgets().saveIndex(ui->tabWidget); if (ret == QDialog::Accepted) { for (auto&& tab : m_tabs) { @@ -109,7 +109,7 @@ void SettingsDialog::accept() if ((QDir::fromNativeSeparators(newModPath) != QDir::fromNativeSeparators( - Settings::instance().getModDirectory(true))) && + Settings::instance().paths().mods(true))) && (QMessageBox::question( nullptr, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 278da0bf..386c7425 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -12,7 +12,7 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) setLevelsBox(); setCrashDumpTypesBox(); - ui->dumpsMaxEdit->setValue(settings().crashDumpsMax()); + ui->dumpsMaxEdit->setValue(settings().diagnostics().crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); @@ -36,7 +36,7 @@ void DiagnosticsSettingsTab::setLevelsBox() ui->logLevelBox->addItem(QObject::tr("Error"), log::Error); for (int i=0; ilogLevelBox->count(); ++i) { - if (ui->logLevelBox->itemData(i) == settings().logLevel()) { + if (ui->logLevelBox->itemData(i) == settings().diagnostics().logLevel()) { ui->logLevelBox->setCurrentIndex(i); break; } @@ -56,7 +56,8 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() add(QObject::tr("Data"), CrashDumpsType::Data); add(QObject::tr("Full"), CrashDumpsType::Full); - const auto current = static_cast(settings().crashDumpsType()); + const auto current = static_cast( + settings().diagnostics().crashDumpsType()); for (int i=0; idumpsTypeBox->count(); ++i) { if (ui->dumpsTypeBox->itemData(i) == current) { @@ -68,11 +69,11 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() void DiagnosticsSettingsTab::update() { - settings().setLogLevel( + settings().diagnostics().setLogLevel( static_cast(ui->logLevelBox->currentData().toInt())); - settings().setCrashDumpsType( + settings().diagnostics().setCrashDumpsType( static_cast(ui->dumpsTypeBox->currentData().toInt())); - settings().setCrashDumpsMax(ui->dumpsMaxEdit->value()); + settings().diagnostics().setCrashDumpsMax(ui->dumpsMaxEdit->value()); } diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index e3d73037..3f7ece38 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -11,7 +11,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) { addLanguages(); { - QString languageCode = settings().language(); + QString languageCode = settings().interface().language(); int currentID = ui->languageBox->findData(languageCode); // I made a mess. :( Most languages are stored with only the iso country // code (2 characters like "de") but chinese @@ -29,7 +29,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) { const int currentID = ui->styleBox->findData( - settings().getStyleName().value_or("")); + settings().interface().styleName().value_or("")); if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); @@ -51,10 +51,10 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) setContainsColor(settings().colors().modlistContainsPlugin()); setContainedColor(settings().colors().pluginListContained()); - ui->compactBox->setChecked(settings().compactDownloads()); - ui->showMetaBox->setChecked(settings().metaDownloads()); + ui->compactBox->setChecked(settings().interface().compactDownloads()); + ui->showMetaBox->setChecked(settings().interface().metaDownloads()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); - ui->colorSeparatorsBox->setChecked(settings().colorSeparatorScrollbar()); + ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); QObject::connect(ui->overwritingArchiveBtn, &QPushButton::clicked, [&]{ on_overwritingArchiveBtn_clicked(); }); QObject::connect(ui->overwritingBtn, &QPushButton::clicked, [&]{ on_overwritingBtn_clicked(); }); @@ -69,18 +69,18 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) void GeneralSettingsTab::update() { - const QString oldLanguage = settings().language(); + const QString oldLanguage = settings().interface().language(); const QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { - settings().setLanguage(newLanguage); + settings().interface().setLanguage(newLanguage); emit settings().languageChanged(newLanguage); } - const QString oldStyle = settings().getStyleName().value_or(""); + const QString oldStyle = settings().interface().styleName().value_or(""); const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - settings().setStyleName(newStyle); + settings().interface().setStyleName(newStyle); emit settings().styleChanged(newStyle); } @@ -91,10 +91,10 @@ void GeneralSettingsTab::update() settings().colors().setModlistContainsPlugin(getContainsColor()); settings().colors().setPluginListContained(getContainedColor()); - settings().setCompactDownloads(ui->compactBox->isChecked()); - settings().setMetaDownloads(ui->showMetaBox->isChecked()); + settings().interface().setCompactDownloads(ui->compactBox->isChecked()); + settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); - settings().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); + settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } void GeneralSettingsTab::addLanguages() @@ -145,7 +145,7 @@ void GeneralSettingsTab::addStyles() void GeneralSettingsTab::resetDialogs() { - settings().resetQuestionButtons(); + settings().widgets().resetQuestionButtons(); } void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) @@ -161,7 +161,7 @@ void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color .arg(color.green()) .arg(color.blue()) .arg(color.alpha()) - .arg(Settings::getIdealTextColor(color).name()) + .arg(ColorSettings::idealTextColor(color).name()) ); }; diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 8822200e..0b08f13f 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -74,13 +74,13 @@ private: NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->offlineBox->setChecked(settings().offlineMode()); - ui->proxyBox->setChecked(settings().getUseProxy()); - ui->endorsementBox->setChecked(settings().endorsementIntegration()); - ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); + ui->offlineBox->setChecked(settings().network().offlineMode()); + ui->proxyBox->setChecked(settings().network().useProxy()); + ui->endorsementBox->setChecked(settings().nexus().endorsementIntegration()); + ui->hideAPICounterBox->setChecked(settings().interface().hideAPICounter()); // display server preferences - for (const auto& server : s.getServers()) { + for (const auto& server : s.network().servers()) { QString descriptor = server.name(); if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { @@ -117,12 +117,12 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) void NexusSettingsTab::update() { - settings().setOfflineMode(ui->offlineBox->isChecked()); - settings().setUseProxy(ui->proxyBox->isChecked()); - settings().setEndorsementIntegration(ui->endorsementBox->isChecked()); - settings().setHideAPICounter(ui->hideAPICounterBox->isChecked()); + settings().network().setOfflineMode(ui->offlineBox->isChecked()); + settings().network().setUseProxy(ui->proxyBox->isChecked()); + settings().nexus().setEndorsementIntegration(ui->endorsementBox->isChecked()); + settings().interface().setHideAPICounter(ui->hideAPICounterBox->isChecked()); - auto servers = settings().getServers(); + auto servers = settings().network().servers(); // store server preference for (int i = 0; i < ui->knownServersList->count(); ++i) { @@ -167,7 +167,7 @@ void NexusSettingsTab::update() } } - settings().updateServers(servers); + settings().network().updateServers(servers); } void NexusSettingsTab::on_nexusConnect_clicked() @@ -225,13 +225,13 @@ void NexusSettingsTab::on_nexusDisconnect_clicked() void NexusSettingsTab::on_clearCacheButton_clicked() { - QDir(Settings::instance().getCacheDirectory()).removeRecursively(); + QDir(Settings::instance().paths().cache()).removeRecursively(); NexusInterface::instance(dialog().m_PluginContainer)->clearCache(); } void NexusSettingsTab::on_associateButton_clicked() { - Settings::instance().registerAsNXMHandler(true); + Settings::instance().nexus().registerAsNXMHandler(true); } void NexusSettingsTab::validateKey(const QString& key) @@ -312,7 +312,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { dialog().m_keyChanged = true; - const bool ret = settings().setNexusApiKey(key); + const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; } @@ -320,7 +320,7 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { dialog().m_keyChanged = true; - const auto ret = settings().clearNexusApiKey(); + const auto ret = settings().nexus().clearApiKey(); NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey(); updateNexusState(); @@ -352,7 +352,7 @@ void NexusSettingsTab::updateNexusButtons() ui->nexusManualKey->setText(QObject::tr("Cancel")); ui->nexusManualKey->setEnabled(true); } - else if (settings().hasNexusApiKey()) { + else if (settings().nexus().hasApiKey()) { // api key is present ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); ui->nexusConnect->setEnabled(false); diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 32aaf4bf..aeb4dd5d 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -6,17 +6,23 @@ PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->baseDirEdit->setText(settings().getBaseDirectory()); - ui->managedGameDirEdit->setText(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); - QString basePath = settings().getBaseDirectory(); + ui->baseDirEdit->setText(settings().paths().base()); + + ui->managedGameDirEdit->setText( + settings().game().plugin()->gameDirectory().absoluteFilePath( + settings().game().plugin()->binaryName())); + + QString basePath = settings().paths().base(); QDir baseDir(basePath); + for (const auto &dir : { - std::make_pair(ui->downloadDirEdit, settings().getDownloadDirectory(false)), - std::make_pair(ui->modDirEdit, settings().getModDirectory(false)), - std::make_pair(ui->cacheDirEdit, settings().getCacheDirectory(false)), - std::make_pair(ui->profilesDirEdit, settings().getProfileDirectory(false)), - std::make_pair(ui->overwriteDirEdit, settings().getOverwriteDirectory(false)) + std::make_pair(ui->downloadDirEdit, settings().paths().downloads(false)), + std::make_pair(ui->modDirEdit, settings().paths().mods(false)), + std::make_pair(ui->cacheDirEdit, settings().paths().cache(false)), + std::make_pair(ui->profilesDirEdit, settings().paths().profiles(false)), + std::make_pair(ui->overwriteDirEdit, settings().paths().overwrite(false)) }) { + QString storePath = baseDir.relativeFilePath(dir.second); storePath = dir.second; dir.first->setText(storePath); @@ -40,17 +46,17 @@ PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) void PathsSettingsTab::update() { - using Setter = void (Settings::*)(const QString&); + using Setter = void (PathSettings::*)(const QString&); using Directory = std::tuple; - QString basePath = settings().getBaseDirectory(); + QString basePath = settings().paths().base(); for (const Directory &dir :{ - Directory{ui->downloadDirEdit->text(), &Settings::setDownloadDirectory, AppConfig::downloadPath()}, - Directory{ui->cacheDirEdit->text(), &Settings::setCacheDirectory, AppConfig::cachePath()}, - Directory{ui->modDirEdit->text(), &Settings::setModDirectory, AppConfig::modsPath()}, - Directory{ui->overwriteDirEdit->text(), &Settings::setOverwriteDirectory, AppConfig::overwritePath()}, - Directory{ui->profilesDirEdit->text(), &Settings::setProfileDirectory, AppConfig::profilesPath()} + Directory{ui->downloadDirEdit->text(), &PathSettings::setDownloads, AppConfig::downloadPath()}, + Directory{ui->cacheDirEdit->text(), &PathSettings::setCache, AppConfig::cachePath()}, + Directory{ui->modDirEdit->text(), &PathSettings::setMods, AppConfig::modsPath()}, + Directory{ui->overwriteDirEdit->text(), &PathSettings::setOverwrite, AppConfig::overwritePath()}, + Directory{ui->profilesDirEdit->text(), &PathSettings::setProfiles, AppConfig::profilesPath()} }) { QString path; Setter setter; @@ -70,22 +76,26 @@ void PathsSettingsTab::update() } if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { - (settings().*setter)(path); + (settings().paths().*setter)(path); } else { - (settings().*setter)(""); + (settings().paths().*setter)(""); } } if (QFileInfo(ui->baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) { - settings().setBaseDirectory(ui->baseDirEdit->text()); + settings().paths().setBase(ui->baseDirEdit->text()); } else { - settings().setBaseDirectory(""); + settings().paths().setBase(""); } - QFileInfo oldGameExe(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); + QFileInfo oldGameExe( + settings().game().plugin()->gameDirectory().absoluteFilePath( + settings().game().plugin()->binaryName())); + QFileInfo newGameExe(ui->managedGameDirEdit->text()); + if (oldGameExe != newGameExe) { - settings().setManagedGameDirectory(newGameExe.absolutePath()); + settings().game().setDirectory(newGameExe.absolutePath()); } } diff --git a/src/settingsdialogsteam.cpp b/src/settingsdialogsteam.cpp index 9ed93e47..3c4c5de6 100644 --- a/src/settingsdialogsteam.cpp +++ b/src/settingsdialogsteam.cpp @@ -5,7 +5,7 @@ SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { QString username, password; - settings().getSteamLogin(username, password); + settings().steam().login(username, password); ui->steamUserEdit->setText(username); ui->steamPassEdit->setText(password); @@ -13,5 +13,5 @@ SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d) void SteamSettingsTab::update() { - settings().setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); + settings().steam().setLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); } diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index b06bd77c..4d811e40 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -6,12 +6,12 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->appIDEdit->setText(settings().getSteamAppID()); + ui->appIDEdit->setText(settings().steam().appID()); - LoadMechanism::EMechanism mechanismID = settings().getLoadMechanism(); + LoadMechanism::EMechanism mechanismID = settings().game().loadMechanismType(); int index = 0; - if (settings().loadMechanism().isDirectLoadingSupported()) { + if (settings().game().loadMechanism().isDirectLoadingSupported()) { ui->mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { index = ui->mechanismBox->count() - 1; @@ -20,10 +20,10 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) ui->mechanismBox->setCurrentIndex(index); - ui->hideUncheckedBox->setChecked(settings().hideUncheckedPlugins()); - ui->forceEnableBox->setChecked(settings().forceEnableCoreFiles()); - ui->displayForeignBox->setChecked(settings().displayForeign()); - ui->lockGUIBox->setChecked(settings().lockGUI()); + ui->hideUncheckedBox->setChecked(settings().game().hideUncheckedPlugins()); + ui->forceEnableBox->setChecked(settings().game().forceEnableCoreFiles()); + ui->displayForeignBox->setChecked(settings().interface().displayForeign()); + ui->lockGUIBox->setChecked(settings().interface().lockGUI()); ui->enableArchiveParsingBox->setChecked(settings().archiveParsing()); setExecutableBlacklist(settings().executablesBlacklist()); @@ -35,19 +35,19 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) void WorkaroundsSettingsTab::update() { - if (ui->appIDEdit->text() != settings().gamePlugin()->steamAPPId()) { - settings().setSteamAppID(ui->appIDEdit->text()); + if (ui->appIDEdit->text() != settings().game().plugin()->steamAPPId()) { + settings().steam().setAppID(ui->appIDEdit->text()); } else { - settings().setSteamAppID(""); + settings().steam().setAppID(""); } - settings().setLoadMechanism(static_cast( + settings().game().setLoadMechanism(static_cast( ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt())); - settings().setHideUncheckedPlugins(ui->hideUncheckedBox->isChecked()); - settings().setForceEnableCoreFiles(ui->forceEnableBox->isChecked()); - settings().setDisplayForeign(ui->displayForeignBox->isChecked()); - settings().setLockGUI(ui->lockGUIBox->isChecked()); + settings().game().setHideUncheckedPlugins(ui->hideUncheckedBox->isChecked()); + settings().game().setForceEnableCoreFiles(ui->forceEnableBox->isChecked()); + settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked()); + settings().interface().setLockGUI(ui->lockGUIBox->isChecked()); settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked()); settings().setExecutablesBlacklist(getExecutableBlacklist()); } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index d22010a5..3734aa87 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -129,7 +129,7 @@ void StatusBar::setUpdateAvailable(bool b) void StatusBar::checkSettings(const Settings& settings) { - m_api->setVisible(!settings.hideAPICounter()); + m_api->setVisible(!settings.interface().hideAPICounter()); } void StatusBar::showEvent(QShowEvent*) diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 41f58308..4315ed92 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -163,8 +163,8 @@ QString toString(CrashDumpsType t) UsvfsConnector::UsvfsConnector() { USVFSParameters params; - LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel()); - CrashDumpsType dumpType = Settings::instance().crashDumpsType(); + LogLevel level = toUsvfsLogLevel(Settings::instance().diagnostics().logLevel()); + CrashDumpsType dumpType = Settings::instance().diagnostics().crashDumpsType(); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); -- cgit v1.3.1 From e08e605c85a1f62f4b6b83f5404457f5dc55654a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 03:49:33 -0400 Subject: added missing include guards log free space on drives involved in paths --- src/env.cpp | 40 +++++++++++++++++++++++++++++++++++++++- src/env.h | 13 ++++++++++++- src/envmetrics.h | 5 +++++ src/envmodule.h | 5 +++++ src/envsecurity.h | 5 +++++ src/envshortcut.h | 5 +++++ src/envwindows.h | 5 +++++ src/main.cpp | 3 +-- src/pch.h | 1 + 9 files changed, 78 insertions(+), 4 deletions(-) (limited to 'src/main.cpp') diff --git a/src/env.cpp b/src/env.cpp index 641eb4a7..4628e3f4 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -4,6 +4,7 @@ #include "envsecurity.h" #include "envshortcut.h" #include "envwindows.h" +#include "settings.h" #include #include @@ -85,7 +86,7 @@ const Metrics& Environment::metrics() const return *m_metrics; } -void Environment::dump() const +void Environment::dump(const Settings& s) const { log::debug("windows: {}", m_windows->toString()); @@ -107,6 +108,43 @@ void Environment::dump() const for (const auto& d : m_metrics->displays()) { log::debug(" . {}", d.toString()); } + + dumpDisks(s); +} + +void Environment::dumpDisks(const Settings& s) const +{ + std::set rootPaths; + + auto dump = [&](auto&& path) { + const QFileInfo fi(path); + const QStorageInfo si(fi.absoluteFilePath()); + + if (rootPaths.contains(si.rootPath())) { + // already seen + return; + } + + // remember + rootPaths.insert(si.rootPath()); + + log::debug( + " . {} free={} MB{}", + si.rootPath(), + (si.bytesFree() / 1000 / 1000), + (si.isReadOnly() ? " (readonly)" : "")); + }; + + log::debug("drives:"); + + dump(QStorageInfo::root().rootPath()); + dump(s.paths().base()); + dump(s.paths().downloads()); + dump(s.paths().mods()); + dump(s.paths().cache()); + dump(s.paths().profiles()); + dump(s.paths().overwrite()); + dump(QCoreApplication::applicationDirPath()); } diff --git a/src/env.h b/src/env.h index 0e88263b..1f146a08 100644 --- a/src/env.h +++ b/src/env.h @@ -1,3 +1,8 @@ +#ifndef ENV_ENV_H +#define ENV_ENV_H + +class Settings; + namespace env { @@ -125,13 +130,17 @@ public: // logs the environment // - void dump() const; + void dump(const Settings& s) const; private: std::vector m_modules; std::unique_ptr m_windows; std::vector m_security; std::unique_ptr m_metrics; + + // dumps all the disks involved in the settings + // + void dumpDisks(const Settings& s) const; }; @@ -152,3 +161,5 @@ bool coredump(CoreDumpTypes type); bool coredumpOther(CoreDumpTypes type); } // namespace env + +#endif // ENV_ENV_H diff --git a/src/envmetrics.h b/src/envmetrics.h index bede36fc..c5d2765a 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -1,3 +1,6 @@ +#ifndef ENV_METRICS_H +#define ENV_METRICS_H + #include #include @@ -70,3 +73,5 @@ private: }; } // namespace + +#endif // ENV_METRICS_H diff --git a/src/envmodule.h b/src/envmodule.h index ea1156bd..3f0f99ab 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -1,3 +1,6 @@ +#ifndef ENV_MODULE_H +#define ENV_MODULE_H + #include #include @@ -96,3 +99,5 @@ private: std::vector getLoadedModules(); } // namespace env + +#endif // ENV_MODULE_H diff --git a/src/envsecurity.h b/src/envsecurity.h index 200cb531..bc63c4a2 100644 --- a/src/envsecurity.h +++ b/src/envsecurity.h @@ -1,3 +1,6 @@ +#ifndef ENV_SECURITY_H +#define ENV_SECURITY_H + #include #include @@ -47,3 +50,5 @@ private: std::vector getSecurityProducts(); } // namespace env + +#endif // ENV_SECURITY_H diff --git a/src/envshortcut.h b/src/envshortcut.h index 82eea191..a05528d9 100644 --- a/src/envshortcut.h +++ b/src/envshortcut.h @@ -1,3 +1,6 @@ +#ifndef ENV_SHORTCUT_H +#define ENV_SHORTCUT_H + #include class Executable; @@ -103,3 +106,5 @@ private: QString toString(Shortcut::Locations loc); } // namespace + +#endif // ENV_SHORTCUT_H diff --git a/src/envwindows.h b/src/envwindows.h index c23f99f4..90655e49 100644 --- a/src/envwindows.h +++ b/src/envwindows.h @@ -1,3 +1,6 @@ +#ifndef ENV_WINDOWS_H +#define ENV_WINDOWS_H + #include #include @@ -104,3 +107,5 @@ private: }; } // namespace + +#endif // ENV_WINDOWS_H diff --git a/src/main.cpp b/src/main.cpp index b5568fec..04bc423b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -577,8 +577,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); env::Environment env; - - env.dump(); + env.dump(settings); settings.dump(); sanityChecks(env); diff --git a/src/pch.h b/src/pch.h index af1a4ade..01a97357 100644 --- a/src/pch.h +++ b/src/pch.h @@ -255,3 +255,4 @@ #include #include #include +#include -- cgit v1.3.1 From 12e1a91e4fe8de291fbe72c23031f3e79613c0dd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 04:58:17 -0400 Subject: log desktop geometry log more info on game plugin --- src/env.cpp | 5 +++++ src/envmetrics.cpp | 12 ++++++++++++ src/envmetrics.h | 4 ++++ src/main.cpp | 5 ++++- src/settings.cpp | 1 + 5 files changed, 26 insertions(+), 1 deletion(-) (limited to 'src/main.cpp') diff --git a/src/env.cpp b/src/env.cpp index 4628e3f4..411443c5 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -109,6 +109,11 @@ void Environment::dump(const Settings& s) const log::debug(" . {}", d.toString()); } + const auto r = m_metrics->desktopGeometry(); + log::debug( + "desktop geometry: ({},{})-({},{})", + r.left(), r.top(), r.right(), r.bottom()); + dumpDisks(s); } diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp index b1b9bd2e..5fb80449 100644 --- a/src/envmetrics.cpp +++ b/src/envmetrics.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace env { @@ -225,6 +226,17 @@ const std::vector& Metrics::displays() const return m_displays; } +QRect Metrics::desktopGeometry() const +{ + QRect r; + + for (auto* s : QGuiApplication::screens()) { + r = r.united(s->geometry()); + } + + return r; +} + void Metrics::getDisplays() { // don't bother if it goes over 100 diff --git a/src/envmetrics.h b/src/envmetrics.h index c5d2765a..8dfdb087 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -66,6 +66,10 @@ public: // const std::vector& displays() const; + // full resolution + // + QRect desktopGeometry() const; + private: std::vector m_displays; diff --git a/src/main.cpp b/src/main.cpp index 04bc423b..9cb8c08d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -648,7 +648,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, game->setGameVariant(edition); - log::info("managing game at {}", game->gameDirectory().absolutePath()); + log::info( + "using game plugin '{}' ('{}', steam id '{}') at {}", + game->gameName(), game->gameShortName(), game->steamAPPId(), + game->gameDirectory().absolutePath()); organizer.updateExecutablesList(); diff --git a/src/settings.cpp b/src/settings.cpp index 1f066100..7fdda2bf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1219,6 +1219,7 @@ void PluginSettings::setPersistent( m_Settings.sync(); } } + void PluginSettings::addBlacklist(const QString &fileName) { m_PluginBlacklist.insert(fileName); -- cgit v1.3.1 From 61956802b1bc42c4878944ddd22514578c188e86 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Sep 2019 04:24:12 -0400 Subject: now logs in utc, added year to avoid confusion --- src/main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 9cb8c08d..74b04970 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -827,7 +827,12 @@ void initLogging() { LogModel::create(); - log::createDefault(MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$"); + log::LoggerConfiguration conf; + conf.maxLevel = MOBase::log::Debug; + conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$"; + conf.utc = true; + + log::createDefault(conf); log::getDefault().setCallback( [](log::Entry e){ LogModel::instance().add(e); }); -- cgit v1.3.1 From c50722100c485d2945082d573158a7083efe2f23 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 10:11:56 -0400 Subject: removed helper.h and helper.cpp, merged into spawn --- src/CMakeLists.txt | 3 --- src/helper.cpp | 20 -------------------- src/helper.h | 26 -------------------------- src/main.cpp | 1 - src/organizercore.cpp | 1 - src/settingsdialogworkarounds.cpp | 2 +- src/spawn.cpp | 1 - 7 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 src/helper.cpp delete mode 100644 src/helper.h (limited to 'src/main.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 60822834..7c29ff48 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -87,7 +87,6 @@ SET(organizer_SRCS waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp - helper.cpp filedialogmemory.cpp executableslist.cpp editexecutablesdialog.cpp @@ -208,7 +207,6 @@ SET(organizer_HDRS waitingonclosedialog.h loadmechanism.h installationmanager.h - helper.h filedialogmemory.h executableslist.h editexecutablesdialog.h @@ -464,7 +462,6 @@ set(utilities csvbuilder shared/error_report eventfilter - helper shared/leaktrace persistentcookiejar serverinfo diff --git a/src/helper.cpp b/src/helper.cpp deleted file mode 100644 index 24446cb8..00000000 --- a/src/helper.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -// moved to spawn.cpp diff --git a/src/helper.h b/src/helper.h deleted file mode 100644 index 335b8647..00000000 --- a/src/helper.h +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef HELPER_H -#define HELPER_H - -// all helper code moved to spawn.h and spawn.cpp -#include "spawn.h" - -#endif // HELPER_H diff --git a/src/main.cpp b/src/main.cpp index 74b04970..ba988ae3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,7 +38,6 @@ along with Mod Organizer. If not, see . #include "executableslist.h" #include "singleinstance.h" #include "utility.h" -#include "helper.h" #include "loglist.h" #include "selectiondialog.h" #include "moapplication.h" diff --git a/src/organizercore.cpp b/src/organizercore.cpp index fbc9083b..0da5b604 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -33,7 +33,6 @@ #include "lockeddialog.h" #include "instancemanager.h" #include -#include "helper.h" #include "previewdialog.h" #include diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index f89b021c..ccbfcbfe 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -1,6 +1,6 @@ #include "settingsdialogworkarounds.h" #include "ui_settingsdialog.h" -#include "helper.h" +#include "spawn.h" #include WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) diff --git a/src/spawn.cpp b/src/spawn.cpp index 6c524681..64766adf 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -33,7 +33,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include "helper.h" #include #include #include -- cgit v1.3.1 From 633ef81972139f3c082429ada10ffb27d9f07898 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 27 Sep 2019 17:07:04 -0400 Subject: moved checks to sanitychecks.cpp added check for blocked files, only logs --- src/CMakeLists.txt | 2 + src/main.cpp | 46 +------------- src/sanitychecks.cpp | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 43 deletions(-) create mode 100644 src/sanitychecks.cpp (limited to 'src/main.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b21d1a8b..180422ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ SET(organizer_SRCS envshortcut.cpp envwindows.cpp colortable.cpp + sanitychecks.cpp shared/windows_error.cpp shared/error_report.cpp @@ -324,6 +325,7 @@ set(application mainwindow moapplication moshortcut + sanitychecks selfupdater singleinstance statusbar diff --git a/src/main.cpp b/src/main.cpp index ba988ae3..fe5fd87a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -91,6 +91,9 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; + +void sanityChecks(const env::Environment& env); + bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); @@ -496,49 +499,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void checkMissingFiles() -{ - // files that are likely to be eaten - static const QStringList files({ - "helper.exe", "nxmhandler.exe", - "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", - "usvfs_x64.dll", "usvfs_x86.dll" - }); - - const auto dir = QCoreApplication::applicationDirPath(); - - for (const auto& name : files) { - const QFileInfo file(dir + QDir::separator() + name); - if (!file.exists()) { - log::warn( - "'{}' seems to be missing, an antivirus may have deleted it", - file.absoluteFilePath()); - } - } -} - -void checkNahimic(const env::Environment& e) -{ - for (auto&& m : e.loadedModules()) { - const QFileInfo file(m.path()); - - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { - log::warn( - "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " - "Mod Organizer, such as freezing or blank windows. Consider " - "uninstalling it."); - - break; - } - } -} - -void sanityChecks(const env::Environment& e) -{ - checkMissingFiles(); - checkNahimic(e); -} - int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp new file mode 100644 index 00000000..5f9705b8 --- /dev/null +++ b/src/sanitychecks.cpp @@ -0,0 +1,174 @@ +#include "env.h" +#include "envmodule.h" +#include + +using namespace MOBase; + +enum class SecurityZone +{ + NoZone = -1, + MyComputer = 0, + Intranet = 1, + Trusted = 2, + Internet = 3, + Untrusted = 4, +}; + +QString toString(SecurityZone z) +{ + switch (z) + { + case SecurityZone::NoZone: return "NoZone"; + case SecurityZone::MyComputer: return "MyComputer"; + case SecurityZone::Intranet: return "Intranet"; + case SecurityZone::Trusted: return "Trusted"; + case SecurityZone::Internet: return "Internet"; + case SecurityZone::Untrusted: return "Untrusted"; + default: return QString("unknown (%1)").arg(static_cast(z)); + } +} + +bool isZoneBlocked(SecurityZone z) +{ + return (z == SecurityZone::Internet || z == SecurityZone::Untrusted); +} + +bool isFileBlocked(const QFileInfo& fi) +{ + const QString ads = "Zone.Identifier"; + const auto key = "ZoneTransfer/ZoneId"; + + const auto path = fi.absoluteFilePath(); + const auto adsPath = path + ":" + ads; + + QFile f(adsPath); + if (!f.exists()) { + return false; + } + + log::debug("file '{}' has an ADS for {}", path, adsPath); + + QSettings qs(adsPath, QSettings::IniFormat); + + if (!qs.contains(key)) { + log::debug("but key '{}' is not found", key); + return false; + } + + const auto v = qs.value(key); + if (v.isNull()) { + log::debug("but key '{}' is null", key); + return false; + } + + bool ok = false; + const auto z = static_cast(v.toInt(&ok)); + + if (!ok) { + log::debug( + "but key '{}' is not an int (value is '{}')", + key, v); + + return false; + } + + if (!isZoneBlocked(z)) { + log::debug( + "but zone id is {}, {}, which is fine", + static_cast(z), toString(z)); + + return false; + } + + log::warn( + "file '{}' is blocked (zone id is {}, {})", + path, static_cast(z), toString(z)); + + return true; +} + +void checkBlockedFiles(const QDir& dir) +{ + if (!dir.exists()) { + log::error( + "while checking for blocked files, directory '{}' not found", + dir.absolutePath()); + + return; + } + + const auto files = dir.entryInfoList({"*.dll", "*.exe"}, QDir::Files); + if (files.empty()) { + log::error( + "while checking for blocked files, directory '{}' is empty", + dir.absolutePath()); + + return; + } + + for (auto&& fi : files) { + isFileBlocked(fi); + } +} + +void checkBlocked() +{ + const QString appDir = QCoreApplication::applicationDirPath(); + + const QDir dirs[] = { + appDir, + appDir + "/dlls", + appDir + "/loot", + appDir + "/NCC", + appDir + "/platforms", + appDir + "/plugins" + }; + + for (const auto& d : dirs) { + checkBlockedFiles(d); + } +} + +void checkMissingFiles() +{ + // files that are likely to be eaten + static const QStringList files({ + "helper.exe", "nxmhandler.exe", + "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", + "usvfs_x64.dll", "usvfs_x86.dll" + }); + + const auto dir = QCoreApplication::applicationDirPath(); + + for (const auto& name : files) { + const QFileInfo file(dir + QDir::separator() + name); + if (!file.exists()) { + log::warn( + "'{}' seems to be missing, an antivirus may have deleted it", + file.absoluteFilePath()); + } + } +} + +void checkNahimic(const env::Environment& e) +{ + for (auto&& m : e.loadedModules()) { + const QFileInfo file(m.path()); + + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { + log::warn( + "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "Mod Organizer, such as freezing or blank windows. Consider " + "uninstalling it."); + + break; + } + } +} + +void sanityChecks(const env::Environment& e) +{ + checkBlocked(); + checkMissingFiles(); + checkNahimic(e); +} -- cgit v1.3.1 From 76df854483c40cb7610d79c5540a5e67f554d840 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 08:53:31 -0400 Subject: only log a warning for symlinks instead of exiting MO added logs: when dmp files are present, for portable instances, and the ini path clear the log widget when switching instances --- src/instancemanager.cpp | 7 ++++++- src/instancemanager.h | 1 + src/main.cpp | 8 ++++++++ src/organizercore.cpp | 37 ++++++++++++++++++++++++++----------- 4 files changed, 41 insertions(+), 12 deletions(-) (limited to 'src/main.cpp') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index c0d343de..ec3c1add 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -263,6 +263,11 @@ QStringList InstanceManager::instances() const } +bool InstanceManager::isPortablePath(const QString& dataPath) +{ + return (dataPath == qApp->applicationDirPath()); +} + bool InstanceManager::portableInstall() const { return QFile::exists(qApp->applicationDirPath() + "/" + @@ -303,7 +308,7 @@ void InstanceManager::createDataPath(const QString &dataPath) const QString InstanceManager::determineDataPath() { QString instanceId = currentInstance(); - if (portableInstallIsLocked()) + if (portableInstallIsLocked()) { instanceId.clear(); } diff --git a/src/instancemanager.h b/src/instancemanager.h index 0e31fb08..649c2195 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -39,6 +39,7 @@ public: QString currentInstance() const; bool allowedToChangeInstance() const; + static bool isPortablePath(const QString& dataPath); private: diff --git a/src/main.cpp b/src/main.cpp index fe5fd87a..a6918d99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -515,6 +515,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, const QString dataPath = application.property("dataPath").toString(); log::info("data path: {}", dataPath); + if (InstanceManager::isPortablePath(dataPath)) { + log::debug("this is a portable instance"); + } + if (!bootstrap()) { reportError("failed to set up data paths"); return 1; @@ -531,6 +535,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); log::getDefault().setLevel(settings.diagnostics().logLevel()); + log::debug("using ini at '{}'", settings.filename()); + // global crashDumpType sits in OrganizerCore to make a bit less ugly to // update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); @@ -860,6 +866,8 @@ int main(int argc, char *argv[]) } // we continue for the primary instance OR if MO was called with parameters do { + LogModel::instance().clear(); + // make sure the log file isn't locked in case MO was restarted and // the previous instance gets deleted log::getDefault().setFile({}); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a4a89c99..335910da 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -443,16 +443,20 @@ bool OrganizerCore::createDirectory(const QString &path) { } bool OrganizerCore::checkPathSymlinks() { - bool hasSymlink = (QFileInfo(m_Settings.paths().profiles()).isSymLink() || + const bool hasSymlink = ( + QFileInfo(m_Settings.paths().profiles()).isSymLink() || QFileInfo(m_Settings.paths().mods()).isSymLink() || QFileInfo(m_Settings.paths().overwrite()).isSymLink()); + if (hasSymlink) { - QMessageBox::critical(nullptr, QObject::tr("Error"), - QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) " - "is on a path containing a symbolic (or other) link. This is incompatible " - "with MO2's VFS system.")); + log::warn("{}", QObject::tr( + "One of the configured MO2 directories (profiles, mods, or overwrite) " + "is on a path containing a symbolic (or other) link. This is likely to " + "be incompatible with MO2's virtual filesystem.")); + return false; } + return true; } @@ -498,18 +502,29 @@ void OrganizerCore::setLogLevel(log::Levels level) log::getDefault().setLevel(m_Settings.diagnostics().logLevel()); } -bool OrganizerCore::cycleDiagnostics() { - if (int maxDumps = settings().diagnostics().crashDumpsMax()) - removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); +bool OrganizerCore::cycleDiagnostics() +{ + const auto maxDumps = settings().diagnostics().crashDumpsMax(); + const auto path = QString::fromStdWString(crashDumpsPath()); + + if (maxDumps > 0) { + removeOldFiles(path, "*.dmp", maxDumps, QDir::Time|QDir::Reversed); + } + + // log if there are any files left + const auto files = QDir(path).entryList({"*.dmp"}, QDir::Files); + if (!files.isEmpty()) { + log::debug("there are crash dumps in '{}'", path); + } + return true; } -//static -void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type) { +void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type) +{ m_globalCrashDumpsType = type; } -//static std::wstring OrganizerCore::crashDumpsPath() { return ( qApp->property("dataPath").toString() + "/" -- cgit v1.3.1 From 3d97b0efd04326c0252411fc6639c4c6df2f2160 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 7 Oct 2019 04:27:53 -0400 Subject: added ExitModOrganizer(), used instead of qApp->exit() reset geometry uses TaskDialog moved restart code for the settings dialog to MainWindow fixed settings sometimes not being saved when restarting don't log "crash dumps present" every time the settings dialog is closed --- src/main.cpp | 12 +++- src/mainwindow.cpp | 113 +++++++++++++++++++++++++------------- src/mainwindow.h | 11 ++-- src/settingsdialog.cpp | 21 +++---- src/settingsdialog.h | 9 ++- src/settingsdialognexus.cpp | 4 +- src/settingsdialogworkarounds.cpp | 23 ++++---- src/shared/util.cpp | 47 ++++++++++++++++ src/shared/util.h | 18 ++++++ src/spawn.cpp | 12 ++-- 10 files changed, 189 insertions(+), 81 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index a6918d99..f08ba066 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -563,10 +563,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); + if (instanceName.compare("Portable", Qt::CaseInsensitive) != 0) { instance.clearCurrentInstance(); - return INT_MAX; + return RestartExitCode; } + return 1; } @@ -710,6 +712,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); res = application.exec(); + mainWindow.onBeforeClose(); + mainWindow.close(); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(nullptr); @@ -867,6 +871,7 @@ int main(int argc, char *argv[]) do { LogModel::instance().clear(); + ResetExitFlag(); // make sure the log file isn't locked in case MO was restarted and // the previous instance gets deleted @@ -904,10 +909,11 @@ int main(int argc, char *argv[]) splash = ":/MO/gui/splash"; } - int result = runApplication(application, instance, splash); - if (result != INT_MAX) { + const int result = runApplication(application, instance, splash); + if (result != RestartExitCode) { return result; } + argc = 1; moshortcut = MOShortcut(""); } while (true); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 295c60a6..23733cac 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1217,7 +1217,7 @@ void MainWindow::showEvent(QShowEvent *event) QMainWindow::showEvent(event); if (!m_WasVisible) { - readSettings(m_OrganizerCore.settings()); + readSettings(); refreshFilters(); // this needs to be connected here instead of in the constructor because the @@ -1267,26 +1267,44 @@ void MainWindow::showEvent(QShowEvent *event) } } +void MainWindow::onBeforeClose() +{ + storeSettings(); +} void MainWindow::closeEvent(QCloseEvent* event) { - if (!confirmExit()) { + // this happens for two reasons: + // 1) the user requested to close the window, such as clicking the X + // 2) close() is called in runApplication() after application.exec() + // returns, which happens when qApp->exit() is called + // + // the window must never actually close for 1), because settings haven't been + // saved yet: the state of many widgets is saved to the ini, which relies on + // the window still being onscreen (or else everything is considered hidden) + // + // for 2), the settings have been saved and the window can just close + + if (ModOrganizerExiting()) { + // the user has confirmed if necessary and all settings have been saved, + // just close it + QMainWindow::closeEvent(event); + } else { + // never close the window because settings might need to be changed event->ignore(); - return; - } - storeSettings(m_OrganizerCore.settings()); + // start the process of exiting, which may require confirmation by calling + // canExit(), among other things + ExitModOrganizer(); + } } -bool MainWindow::confirmExit() +bool MainWindow::canExit() { - m_closing = true; - if (m_OrganizerCore.downloadManager()->downloadsInProgressNoPause()) { if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { - m_closing = false; return false; } else { m_OrganizerCore.downloadManager()->pauseAll(); @@ -1298,8 +1316,9 @@ bool MainWindow::confirmExit() 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_closing) { // if operation cancelled + if (!m_exitAfterWait) { // if operation cancelled return false; } } @@ -2138,20 +2157,22 @@ void MainWindow::activateProxy(bool activate) busyDialog.hide(); } -void MainWindow::readSettings(const Settings& settings) +void MainWindow::readSettings() { - settings.geometry().restoreGeometry(this); - settings.geometry().restoreState(this); - settings.geometry().restoreDocks(this); - settings.geometry().restoreToolbars(this); - settings.geometry().restoreState(ui->splitter); - settings.geometry().restoreState(ui->categoriesSplitter); - settings.geometry().restoreVisibility(ui->menuBar); - settings.geometry().restoreVisibility(ui->statusBar); + const auto& s = m_OrganizerCore.settings(); + + s.geometry().restoreGeometry(this); + s.geometry().restoreState(this); + s.geometry().restoreDocks(this); + s.geometry().restoreToolbars(this); + s.geometry().restoreState(ui->splitter); + s.geometry().restoreState(ui->categoriesSplitter); + s.geometry().restoreVisibility(ui->menuBar); + s.geometry().restoreVisibility(ui->statusBar); { // special case in case someone puts 0 in the INI - auto v = settings.widgets().index(ui->executablesListBox); + auto v = s.widgets().index(ui->executablesListBox); if (!v || v == 0) { v = 1; } @@ -2159,16 +2180,16 @@ void MainWindow::readSettings(const Settings& settings) ui->executablesListBox->setCurrentIndex(*v); } - settings.widgets().restoreIndex(ui->groupCombo); + s.widgets().restoreIndex(ui->groupCombo); { - settings.geometry().restoreVisibility(ui->categoriesGroup, false); + s.geometry().restoreVisibility(ui->categoriesGroup, false); const auto v = ui->categoriesGroup->isVisible(); setCategoryListVisible(v); ui->displayCategoriesBtn->setChecked(v); } - if (settings.network().useProxy()) { + if (s.network().useProxy()) { activateProxy(true); } } @@ -2215,8 +2236,10 @@ void MainWindow::processUpdates(Settings& settings) { } } -void MainWindow::storeSettings(Settings& s) +void MainWindow::storeSettings() { + auto& s = m_OrganizerCore.settings(); + s.geometry().saveState(this); s.geometry().saveGeometry(this); s.geometry().saveDocks(this); @@ -2244,7 +2267,7 @@ ILockedWaitingForProcess* MainWindow::lock() ++m_LockCount; return m_LockDialog; } - if (m_closing) + if (m_exitAfterWait) m_LockDialog = new WaitingOnCloseDialog(this); else m_LockDialog = new LockedDialog(this, true); @@ -2265,8 +2288,8 @@ void MainWindow::unlock() } --m_LockCount; if (m_LockCount == 0) { - if (m_closing && m_LockDialog->canceled()) - m_closing = false; + if (m_exitAfterWait && m_LockDialog->canceled()) + m_exitAfterWait = false; m_LockDialog->hide(); m_LockDialog->deleteLater(); m_LockDialog = nullptr; @@ -5018,18 +5041,31 @@ void MainWindow::on_actionSettings_triggered() bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); const bool oldCheckForUpdates = settings.checkForUpdates(); + const int oldMaxDumps = settings.diagnostics().crashDumpsMax(); SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); + auto e = dialog.exitNeeded(); + if (oldManagedGameDirectory != settings.game().directory()) { - QMessageBox::about(this, tr("Restarting MO"), - tr("Changing the managed game directory requires restarting MO.\n" - "Any pending downloads will be paused.\n\n" - "Click OK to restart MO now.")); - dlManager->pauseAll(); - qApp->exit(INT_MAX); + e |= Exit::Restart; + } + + if (e.testFlag(Exit::Restart)) { + const auto r = MOBase::TaskDialog(this) + .title(tr("Restart Mod Organizer")) + .main("Restart Mod Organizer") + .content(tr("Mod Organizer must restart to finish configuration changes")) + .icon(QMessageBox::Question) + .button({tr("Restart"), QMessageBox::Yes}) + .button({tr("Continue"), tr("Some things might be weird."), QMessageBox::No}) + .exec(); + + if (r == QMessageBox::Yes) { + ExitModOrganizer(e); + } } InstallationManager *instManager = m_OrganizerCore.installationManager(); @@ -5092,7 +5128,10 @@ void MainWindow::on_actionSettings_triggered() updateDownloadView(); m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel()); - m_OrganizerCore.cycleDiagnostics(); + + if (settings.diagnostics().crashDumpsMax() != oldMaxDumps) { + m_OrganizerCore.cycleDiagnostics(); + } toggleMO2EndorseState(); @@ -5475,9 +5514,7 @@ void MainWindow::on_actionUpdate_triggered() void MainWindow::on_actionExit_triggered() { - if (confirmExit()) { - qApp->exit(); - } + ExitModOrganizer(); } void MainWindow::actionEndorseMO() @@ -6121,7 +6158,7 @@ void MainWindow::on_actionChange_Game_triggered() if (r == QMessageBox::Yes) { InstanceManager::instance().clearCurrentInstance(); - qApp->exit(INT_MAX); + ExitModOrganizer(Exit::Restart); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 524e2b6e..b04096be 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -111,7 +111,6 @@ class MainWindow : public QMainWindow, public IUserInterface friend class OrganizerProxy; public: - explicit MainWindow(Settings &settings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent = 0); @@ -154,7 +153,8 @@ public: void displayModInformation( ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; - bool confirmExit(); + bool canExit(); + void onBeforeClose(); virtual bool closeWindow(); virtual void setWindowEnabled(bool enabled); @@ -383,9 +383,8 @@ private: LockedDialogBase *m_LockDialog { nullptr }; uint64_t m_LockCount { 0 }; - bool m_closing{ false }; - bool m_showArchiveData{ true }; + bool m_exitAfterWait{ false }; MOBase::DelayedFileWriter m_ArchiveListWriter; @@ -672,8 +671,8 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); - void storeSettings(Settings& settings); - void readSettings(const Settings& settings); + void storeSettings(); + void readSettings(); void setupModList(); }; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 8fb25b1c..daccfba9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) + , m_exit(Exit::None) , m_pluginContainer(pluginContainer) - , m_restartNeeded(false) { ui->setupUi(this); @@ -61,9 +61,14 @@ QWidget* SettingsDialog::parentWidgetForDialogs() } } -void SettingsDialog::setRestartNeeded() +void SettingsDialog::setExitNeeded(ExitFlags e) { - m_restartNeeded = true; + m_exit = e; +} + +ExitFlags SettingsDialog::exitNeeded() const +{ + return m_exit; } int SettingsDialog::exec() @@ -87,16 +92,6 @@ int SettingsDialog::exec() } } - if (m_restartNeeded) { - if (QMessageBox::question(parentWidgetForDialogs(), - tr("Restart Mod Organizer?"), - tr("In order to finish configuration changes, MO must be restarted.\n" - "Restart it now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - qApp->exit(INT_MAX); - } - } - return ret; } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index e89da665..bae4a469 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGSDIALOG_H #include "tutorabledialog.h" +#include "util.h" class PluginContainer; class Settings; @@ -74,7 +75,9 @@ public: PluginContainer* pluginContainer(); QWidget* parentWidgetForDialogs(); - void setRestartNeeded(); + + void setExitNeeded(ExitFlags e); + ExitFlags exitNeeded() const; int exec() override; @@ -82,10 +85,10 @@ public slots: virtual void accept(); private: + Ui::SettingsDialog* ui; Settings& m_settings; std::vector> m_tabs; - Ui::SettingsDialog* ui; - bool m_restartNeeded; + ExitFlags m_exit; PluginContainer* m_pluginContainer; }; diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 2dd8b998..d49e0a33 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -312,7 +312,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { - dialog().setRestartNeeded(); + dialog().setExitNeeded(Exit::Restart); const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; @@ -320,7 +320,7 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { - dialog().setRestartNeeded(); + dialog().setExitNeeded(Exit::Restart); const auto ret = settings().nexus().clearApiKey(); NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey(); diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 0e31fc4b..1c5fbe26 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -2,6 +2,7 @@ #include "ui_settingsdialog.h" #include "spawn.h" #include "settings.h" +#include #include WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) @@ -118,16 +119,18 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() { - const auto caption = QObject::tr("Restart Mod Organizer?"); - const auto text = QObject::tr( - "In order to reset the geometry, Mod Organizer must be restarted.\n" - "Restart now?"); - - const auto res = QMessageBox::question( - parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel); - - if (res == QMessageBox::Yes) { + const auto r = MOBase::TaskDialog(parentWidget()) + .title(QObject::tr("Restart Mod Organizer")) + .main(QObject::tr("Restart Mod Organizer")) + .content(QObject::tr("Geometries will be reset to their default values.")) + .icon(QMessageBox::Question) + .button({QObject::tr("Restart Mod Organizer"), QMessageBox::Ok}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + if (r == QMessageBox::Ok) { settings().geometry().requestReset(); - qApp->exit(INT_MAX); + ExitModOrganizer(Exit::Restart); + dialog().close(); } } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 07983e12..58f4eb2b 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" +#include "mainwindow.h" namespace MOShared { @@ -244,3 +245,49 @@ MOBase::VersionInfo createVersionInfo() } } // namespace MOShared + + +static bool g_exiting = false; + +MainWindow* findMainWindow() +{ + for (auto* tl : qApp->topLevelWidgets()) { + if (auto* mw=dynamic_cast(tl)) { + return mw; + } + } + + return nullptr; +} + +bool ExitModOrganizer(ExitFlags e) +{ + if (g_exiting) { + return true; + } + + if (!e.testFlag(Exit::Force)) { + if (auto* mw=findMainWindow()) { + if (!mw->canExit()) { + return false; + } + } + } + + g_exiting = true; + + const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); + qApp->exit(code); + + return true; +} + +bool ModOrganizerExiting() +{ + return g_exiting; +} + +void ResetExitFlag() +{ + g_exiting = false; +} diff --git a/src/shared/util.h b/src/shared/util.h index 7bae96f2..aea6f200 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -48,4 +48,22 @@ MOBase::VersionInfo createVersionInfo(); } // namespace MOShared + +enum class Exit +{ + None = 0x00, + Normal = 0x01, + Restart = 0x02, + Force = 0x04 +}; + +const int RestartExitCode = INT_MAX; + +using ExitFlags = QFlags; +Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); + +bool ExitModOrganizer(ExitFlags e=Exit::Normal); +bool ModOrganizerExiting(); +void ResetExitFlag(); + #endif // UTIL_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 079677f4..a34230b2 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -186,12 +186,12 @@ QMessageBox::StandardButton badSteamReg( .details(details) .icon(QMessageBox::Critical) .button({ - QObject::tr("Continue without starting Steam"), - QObject::tr("The program may fail to launch."), - QMessageBox::Yes}) + QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), + QMessageBox::Yes}) .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) + QObject::tr("Cancel"), + QMessageBox::Cancel}) .exec(); } @@ -517,7 +517,7 @@ bool restartAsAdmin(QWidget* parent) } log::debug("exiting MO"); - qApp->exit(0); + ExitModOrganizer(Exit::Force); return true; } -- cgit v1.3.1 From dfb3020f8982e325e804f9e43b28a33b54c798f9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 9 Oct 2019 01:38:53 -0400 Subject: added usvfs version in log and about dialog --- src/aboutdialog.cpp | 3 +++ src/aboutdialog.ui | 7 +++++++ src/main.cpp | 5 +++-- src/shared/util.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 1 + 5 files changed, 60 insertions(+), 2 deletions(-) (limited to 'src/main.cpp') diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index 41fd24f7..6ae7f56d 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "aboutdialog.h" #include "ui_aboutdialog.h" +#include "util.h" #include #include @@ -85,6 +86,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown"); #endif + + ui->usvfsLabel->setText(ui->usvfsLabel->text() + " " + MOShared::getUsvfsVersionString()); ui->licenseText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); } diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index c72a85f8..415ce0a7 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -104,6 +104,13 @@ + + + + usvfs: + + + diff --git a/src/main.cpp b/src/main.cpp index f08ba066..776c3775 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -504,8 +504,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { log::info( - "starting Mod Organizer version {} revision {} in {}", - getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); + "starting Mod Organizer version {} revision {} in {}, usvfs: {}", + getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath(), + MOShared::getUsvfsVersionString()); preloadSsl(); if (!QSslSocket::supportsSsl()) { diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 58f4eb2b..32eb825c 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -20,6 +20,8 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" #include "mainwindow.h" +#include +#include namespace MOShared { @@ -244,6 +246,50 @@ MOBase::VersionInfo createVersionInfo() } } +QString getUsvfsDLLVersion() +{ + // once 2.2.2 is released, this can be changed to call USVFSVersionString() + // directly; until then, using GetProcAddress() allows for mixing up devbuilds + // and usvfs dlls + + using USVFSVersionStringType = const char* WINAPI (); + + QString s; + + const auto m = ::LoadLibraryW(L"usvfs_x64.dll"); + + if (m) { + auto* f = reinterpret_cast( + ::GetProcAddress(m, "USVFSVersionString")); + + if (f) { + s = f(); + } + + ::FreeLibrary(m); + } + + if (s.isEmpty()) { + s = "?"; + } + + return s; +} + +QString getUsvfsVersionString() +{ + const QString dll = getUsvfsDLLVersion(); + const QString header = USVFS_VERSION_STRING; + + QString usvfsVersion; + + if (dll == header) { + return dll; + } else { + return "dll is " + dll + ", compiled against " + header; + } +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index aea6f200..e87244b6 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -45,6 +45,7 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); MOBase::VersionInfo createVersionInfo(); +QString getUsvfsVersionString(); } // namespace MOShared -- 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/main.cpp') 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 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/main.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 180422ef..5e909760 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -143,6 +143,7 @@ SET(organizer_SRCS envwindows.cpp colortable.cpp sanitychecks.cpp + processrunner.cpp shared/windows_error.cpp shared/error_report.cpp @@ -266,6 +267,7 @@ SET(organizer_HDRS envshortcut.h envwindows.h colortable.h + processrunner.h shared/windows_error.h shared/error_report.h @@ -348,6 +350,7 @@ set(core organizercore organizerproxy apiuseraccount + processrunner ) set(dialogs diff --git a/src/iuserinterface.h b/src/iuserinterface.h index a309ed9b..91487aee 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -33,6 +33,8 @@ public: virtual ILockedWaitingForProcess* lock() = 0; virtual void unlock() = 0; + + virtual QWidget* qtWidget() = 0; }; #endif // IUSERINTERFACE_H diff --git a/src/main.cpp b/src/main.cpp index 49ecc084..5ed7da5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -632,7 +632,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (MOShortcut shortcut{ arguments.at(1) }) { if (shortcut.hasExecutable()) { try { - organizer.runShortcut(shortcut); + organizer.processRunner().runShortcut(shortcut); return 0; } catch (const std::exception &e) { @@ -653,7 +653,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary try { - organizer.runExecutableOrExecutableFile( + organizer.processRunner().runExecutableOrExecutableFile( exeName, arguments, QString(), QString()); return 0; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bbb63333..ce5280a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1312,7 +1312,7 @@ bool MainWindow::canExit() } m_exitAfterWait = true; - m_OrganizerCore.waitForAllUSVFSProcessesWithLock(); + m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock(); if (!m_exitAfterWait) { // if operation cancelled return false; } @@ -1536,7 +1536,7 @@ void MainWindow::startExeAction() } action->setEnabled(false); - m_OrganizerCore.runExecutable(*itor); + m_OrganizerCore.processRunner().runExecutable(*itor); action->setEnabled(true); } @@ -2294,6 +2294,11 @@ void MainWindow::unlock() } } +QWidget* MainWindow::qtWidget() +{ + return this; +} + void MainWindow::on_btnRefreshData_clicked() { m_OrganizerCore.refreshDirectoryStructure(); @@ -2363,7 +2368,7 @@ void MainWindow::on_startButton_clicked() forcedLibraries.clear(); } - m_OrganizerCore.runExecutableFile( + m_OrganizerCore.processRunner().runExecutableFile( selectedExecutable->binaryInfo(), selectedExecutable->arguments(), selectedExecutable->workingDirectory().length() != 0 ? @@ -5453,7 +5458,7 @@ void MainWindow::openDataFile() const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); - m_OrganizerCore.runFile(this, targetInfo); + m_OrganizerCore.processRunner().runFile(this, targetInfo); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/mainwindow.h b/src/mainwindow.h index dbbd0bd9..19723480 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -118,8 +118,9 @@ public: void processUpdates(Settings& settings); - virtual ILockedWaitingForProcess* lock() override; - virtual void unlock() override; + ILockedWaitingForProcess* lock() override; + void unlock() override; + QWidget* qtWidget() override; bool addProfile(); void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 68b1be6b..758112cc 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,7 @@ void ConflictsTab::openItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().runFile(parentWidget(), item->fileName()); + core().processRunner().runFile(parentWidget(), item->fileName()); return true; }); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index eda64c1d..0f767f46 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,5 +1,4 @@ #include "organizercore.h" -#include "mainwindow.h" #include "delayedfilewriter.h" #include "guessedvalue.h" #include "imodinterface.h" @@ -86,51 +85,13 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } -env::Process* getInterestingProcess(std::vector& processes) -{ - if (processes.empty()) { - return nullptr; - } - - // Certain process names we wish to "hide" for aesthetic reason: - const std::vector hiddenList = { - QFileInfo(QCoreApplication::applicationFilePath()).fileName() - }; - - auto isHidden = [&](auto&& p) { - for (auto h : hiddenList) { - if (p.name().contains(h, Qt::CaseInsensitive)) { - return true; - } - } - - return false; - }; - - - for (auto&& root : processes) { - if (!isHidden(root)) { - return &root; - } - - for (auto&& child : root.children()) { - if (!isHidden(child)) { - return &child; - } - } - } - - - // everything is hidden, just pick the first one - return &processes[0]; -} - OrganizerCore::OrganizerCore(Settings &settings) - : m_MainWindow(nullptr) + : m_UserInterface(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) + , m_Runner(*this) , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() @@ -190,7 +151,7 @@ OrganizerCore::~OrganizerCore() m_RefresherThread.exit(); m_RefresherThread.wait(); - prepareStart(); + saveCurrentProfile(); // profile has to be cleaned up before the modinfo-buffer is cleared delete m_CurrentProfile; @@ -249,43 +210,49 @@ void OrganizerCore::updateExecutablesList() m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } -void OrganizerCore::setUserInterface(MainWindow* mainWindow) +void OrganizerCore::setUserInterface(IUserInterface* ui) { storeSettings(); - m_MainWindow = mainWindow; + m_UserInterface = ui; + + QWidget* w = nullptr; + if (m_UserInterface) { + w = m_UserInterface->qtWidget(); + } - if (m_MainWindow != nullptr) { - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow, + if (w) { + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow, + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w, SLOT(modlistChanged(QModelIndexList, int))); - connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w, SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(modUninstalled(QString)), w, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow, + connect(&m_ModList, SIGNAL(removeSelectedMods()), w, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow, + connect(&m_ModList, SIGNAL(clearOverwrite()), w, SLOT(clearOverwrite())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow, + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow, + connect(&m_ModList, SIGNAL(modorder_changed()), w, SLOT(modorder_changed())); - connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow, + connect(&m_PluginList, SIGNAL(writePluginsList()), w, SLOT(esplist_changed())); - connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow, + connect(&m_PluginList, SIGNAL(esplist_changed()), w, SLOT(esplist_changed())); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow, + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); } - m_InstallationManager.setParentWidget(m_MainWindow); - m_Updater.setUserInterface(m_MainWindow); + m_InstallationManager.setParentWidget(w); + m_Updater.setUserInterface(w); + m_Runner.setUserInterface(ui); checkForUpdates(); } @@ -294,7 +261,7 @@ void OrganizerCore::checkForUpdates() { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (m_MainWindow != nullptr) { + if (m_UserInterface != nullptr) { m_Updater.testForUpdate(m_Settings); } } @@ -390,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { if(moshortcut.hasExecutable()) - runShortcut(moshortcut); + m_Runner.runShortcut(moshortcut); } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); @@ -754,13 +721,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, int modIndex = ModInfo::getIndex(modName); if (modIndex != UINT_MAX) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && (m_MainWindow != nullptr) + if (hasIniTweaks && (m_UserInterface != nullptr) && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_MainWindow->displayModInformation( + m_UserInterface->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); @@ -821,13 +788,13 @@ void OrganizerCore::installDownload(int index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); - if (hasIniTweaks && m_MainWindow != nullptr + if (hasIniTweaks && m_UserInterface != nullptr && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_MainWindow->displayModInformation( + m_UserInterface->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } @@ -1104,412 +1071,6 @@ bool OrganizerCore::previewFile( return true; } -bool OrganizerCore::runFile( - QWidget* parent, const QFileInfo& targetInfo) -{ - const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - - switch (fec.type) - { - case spawn::FileExecutionTypes::Executable: - { - runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); - return true; - } - - case spawn::FileExecutionTypes::Other: // fall-through - default: - { - auto r = shell::Open(targetInfo.absoluteFilePath()); - if (!r.success()) { - return false; - } - - // not all files will return a valid handle even if opening them was - // successful, such as inproc handlers (like the photo viewer) - if (r.processHandle() != INVALID_HANDLE_VALUE) { - // steal because it gets closed after the wait - return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); - } - - return true; - } - } -} - -bool OrganizerCore::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - DWORD processExitCode = 0; - HANDLE processHandle = spawnAndWait( - binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, - customOverwrite, forcedLibraries, &processExitCode); - - if (processHandle == INVALID_HANDLE_VALUE) { - // failed - return false; - } - - if (refresh) { - refreshDirectoryStructure(); - - // need to remove our stored load order because it may be outdated if a foreign tool changed the - // file time. After removing that file, refreshESPList will use the file time as the order - if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - log::debug("removing loadorder.txt"); - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - } - - refreshDirectoryStructure(); - - refreshESPList(true); - savePluginList(); - - //These callbacks should not fiddle with directoy structure and ESPs. - m_FinishedRun(binary.absoluteFilePath(), processExitCode); - } - - return true; -} - -bool OrganizerCore::runExecutable(const Executable& exe, bool refresh) -{ - const QString customOverwrite = m_CurrentProfile->setting( - "custom_overwrites", exe.title()).toString(); - - QList forcedLibraries; - - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - - return runExecutableFile( - exe.binaryInfo(), - exe.arguments(), - exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - customOverwrite, - forcedLibraries, - refresh); -} - -bool OrganizerCore::runShortcut(const MOShortcut& shortcut) -{ - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { - throw std::runtime_error( - QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(),shortcut.executable()) - .toLocal8Bit().constData()); - } - - const Executable& exe = m_ExecutablesList.get(shortcut.executable()); - return runExecutable(exe, false); -} - -HANDLE OrganizerCore::runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) -{ - QString profileName = profile; - if (profile == "") { - if (m_CurrentProfile != nullptr) { - profileName = m_CurrentProfile->name(); - } else { - throw MyException(tr("No profile set")); - } - } - - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; - - if (executable.contains('\\') || executable.contains('/')) { - // file path - - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = managedGame()->gameDirectory().absoluteFilePath(executable); - } - - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); - } - - try { - const Executable& exe = m_ExecutablesList.getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - } catch (const std::runtime_error &) { - // nop - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - if (arguments == "") { - arguments = exe.arguments(); - } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); - } - } catch (const std::runtime_error &) { - log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); - } - } - - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); - - return spawnAndWait(binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); -} - -HANDLE OrganizerCore::spawnAndWait( - const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - LPDWORD exitCode) -{ - spawn::SpawnParameters sp; - sp.binary = binary; - sp.arguments = arguments; - sp.currentDirectory = currentDirectory; - sp.steamAppID = steamAppID; - sp.hooked = true; - - prepareStart(); - - while (m_DirectoryUpdate) { - ::Sleep(100); - QCoreApplication::processEvents(); - } - - // need to make sure all data is saved before we start the application - if (m_CurrentProfile != nullptr) { - m_CurrentProfile->writeModlistNow(true); - } - - // TODO: should also pass arguments - if (!m_AboutToRun(binary.absoluteFilePath())) { - log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); - return INVALID_HANDLE_VALUE; - } - - try { - m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); - m_USVFS.updateForcedLibraries(forcedLibraries); - - } catch (const UsvfsConnectorException &e) { - log::debug(e.what()); - return INVALID_HANDLE_VALUE; - } catch (const std::exception &e) { - QMessageBox::warning(m_MainWindow, tr("Error"), e.what()); - return INVALID_HANDLE_VALUE; - } - - HANDLE handle = spawn::Spawner() - .spawn(m_MainWindow, m_GamePlugin, sp, m_Settings) - .releaseHandle(); - - if (handle == INVALID_HANDLE_VALUE) { - // failed - return INVALID_HANDLE_VALUE; - } - - waitForProcessCompletionWithLock(handle, exitCode); - return handle; -} - -void OrganizerCore::withLock(std::function f) -{ - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; - - if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } - else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } - - ON_BLOCK_EXIT([&]() { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); - - f(uilock); -} - -bool OrganizerCore::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) -{ - if (!Settings::instance().interface().lockGUI()) { - return true; - } - - bool r = false; - - withLock([&](auto* uilock) { - DWORD ignoreExitCode; - r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); - cycleDiagnostics(); - }); - - return r; -} - -bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) -{ - if (!Settings::instance().interface().lockGUI()) - return true; - - bool r = false; - - withLock([&](auto* uilock) { - r = waitForProcessCompletion(handle, exitCode, uilock); - }); - - return r; -} - -bool OrganizerCore::waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) -{ - const auto tree = env::getProcessTree(handle); - std::vector processes = {tree}; - - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - return true; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - return true; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = spawn::waitForProcess( - interestingHandle.get(), exitCode, progress); - - switch (r) - { - case spawn::WaitResults::Completed: // fall-through - case spawn::WaitResults::Cancelled: - return true; - - case spawn::WaitResults::Error: // fall-through - default: - return false; - } -} - -bool OrganizerCore::waitForAllUSVFSProcessesWithLock() -{ - if (!Settings::instance().interface().lockGUI()) - return true; - - bool r = false; - - withLock([&](auto* uilock) { - r = waitForAllUSVFSProcesses(uilock); - }); - - return r; -} - -bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) -{ - for (;;) { - const auto handles = getRunningUSVFSProcesses(); - if (handles.empty()) { - break; - } - - std::vector processes; - for (auto&& h : handles) { - auto p = env::getProcessTree(h); - if (p.isValid()) { - processes.emplace_back(std::move(p)); - } - } - - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - break; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - break; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = spawn::waitForProcess( - interestingHandle.get(), nullptr, progress); - - switch (r) - { - case spawn::WaitResults::Completed: - // this process is completed, check for others - break; - - case spawn::WaitResults::Cancelled: - // force unlocked - log::debug("waiting for process completion aborted by UI"); - return true; - - case spawn::WaitResults::Error: // fall-through - default: - log::debug("waiting for process completion not successful"); - return false; - } - } - - log::debug("waiting for process completion successful"); - return true; -} - bool OrganizerCore::onAboutToRun( const std::function &func) { @@ -1594,8 +1155,8 @@ void OrganizerCore::refreshBSAList() m_ActiveArchives = m_DefaultArchives; } - if (m_MainWindow != nullptr) { - m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives); + if (m_UserInterface != nullptr) { + m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); } m_ArchivesInit = true; @@ -1711,8 +1272,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().writeImmediately(false); } std::vector archives = enabledArchives(); @@ -1896,8 +1457,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } modInfo->clearCaches(); @@ -1946,8 +1507,8 @@ void OrganizerCore::modStatusChanged(QList index) { origin.enable(false); } } - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } @@ -2122,8 +1683,8 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); @@ -2147,11 +1708,12 @@ void OrganizerCore::savePluginList() m_PluginList.saveLoadOrder(*m_DirectoryStructure); } -void OrganizerCore::prepareStart() +void OrganizerCore::saveCurrentProfile() { if (m_CurrentProfile == nullptr) { return; } + m_CurrentProfile->writeModlist(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); @@ -2159,6 +1721,79 @@ void OrganizerCore::prepareStart() storeSettings(); } +ProcessRunner& OrganizerCore::processRunner() +{ + return m_Runner; +} + +bool OrganizerCore::beforeRun( + const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries) +{ + saveCurrentProfile(); + + while (m_DirectoryUpdate) { + ::Sleep(100); + QCoreApplication::processEvents(); + } + + // need to make sure all data is saved before we start the application + if (m_CurrentProfile != nullptr) { + m_CurrentProfile->writeModlistNow(true); + } + + // TODO: should also pass arguments + if (!m_AboutToRun(binary.absoluteFilePath())) { + log::debug("start of \"{}\" cancelled by plugin", binary.absoluteFilePath()); + return false; + } + + try + { + m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + m_USVFS.updateForcedLibraries(forcedLibraries); + } + catch (const UsvfsConnectorException &e) + { + log::debug(e.what()); + return false; + } + catch (const std::exception &e) + { + QWidget* w = nullptr; + if (m_UserInterface) { + w = m_UserInterface->qtWidget(); + } + QMessageBox::warning(w, tr("Error"), e.what()); + return false; + } + + return true; +} + +void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) +{ + refreshDirectoryStructure(); + + // need to remove our stored load order because it may be outdated if a + // foreign tool changed the file time. After removing that file, + // refreshESPList will use the file time as the order + if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { + log::debug("removing loadorder.txt"); + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + + refreshDirectoryStructure(); + + refreshESPList(true); + savePluginList(); + cycleDiagnostics(); + + //These callbacks should not fiddle with directoy structure and ESPs. + m_FinishedRun(binary.absoluteFilePath(), exitCode); +} + std::vector OrganizerCore::fileMapping(const QString &profileName, const QString &customOverwrite) { diff --git a/src/organizercore.h b/src/organizercore.h index ffdb6830..2252c118 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -3,7 +3,6 @@ #include "selfupdater.h" -#include "ilockedwaitingforprocess.h" #include "settings.h" #include "modlist.h" #include "modinfo.h" @@ -14,6 +13,7 @@ #include "executableslist.h" #include "usvfsconnector.h" #include "moshortcut.h" +#include "processrunner.h" #include #include #include @@ -26,7 +26,7 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; -class MainWindow; +class IUserInterface; namespace MOBase { template class GuessedValue; @@ -97,7 +97,7 @@ public: ~OrganizerCore(); - void setUserInterface(MainWindow* mainWindow); + void setUserInterface(IUserInterface* ui); void connectPlugins(PluginContainer *container); void disconnectPlugins(); @@ -134,7 +134,14 @@ public: bool saveCurrentLists(); - void prepareStart(); + ProcessRunner& processRunner(); + + bool beforeRun( + const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries); + + void afterRun(const QFileInfo& binary, DWORD exitCode); void refreshESPList(bool force = false); void refreshBSAList(); @@ -149,27 +156,6 @@ public: bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); - bool runFile(QWidget* parent, const QFileInfo& targetInfo); - - bool runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID={}, - const QString &customOverwrite={}, - const QList &forcedLibraries={}, - bool refresh=true); - - bool runExecutable(const Executable& exe, bool refresh=true); - - bool runShortcut(const MOShortcut& shortcut); - - HANDLE runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); - - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - bool waitForAllUSVFSProcessesWithLock(); - void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -273,6 +259,7 @@ signals: private: + void saveCurrentProfile(); void storeSettings(); bool queryApi(QString &apiKey); @@ -298,26 +285,6 @@ private: const MOShared::DirectoryEntry *directoryEntry, int createDestination); - HANDLE spawnAndWait(const QFileInfo &binary, const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries = QList(), - LPDWORD exitCode = nullptr); - - bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); - - bool waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); - - bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); - - void withLock(std::function f); - - HANDLE findAndOpenAUSVFSProcess( - const std::vector& hiddenList, DWORD preferedParentPid); - private slots: void directory_refreshed(); @@ -331,13 +298,14 @@ private: static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; private: - MainWindow* m_MainWindow; + IUserInterface* m_UserInterface; PluginContainer *m_PluginContainer; QString m_GameName; MOBase::IPluginGame *m_GamePlugin; Profile *m_CurrentProfile; + ProcessRunner m_Runner; Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 2ea1761a..75b3ea41 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -112,14 +112,14 @@ HANDLE OrganizerProxy::startApplication( const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite) { - return m_Proxied->runExecutableOrExecutableFile( + return m_Proxied->processRunner().runExecutableOrExecutableFile( executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - return m_Proxied->waitForApplication(handle, exitCode); + return m_Proxied->processRunner().waitForApplication(handle, exitCode); } bool OrganizerProxy::onAboutToRun(const std::function &func) diff --git a/src/processrunner.cpp b/src/processrunner.cpp new file mode 100644 index 00000000..81c4b99e --- /dev/null +++ b/src/processrunner.cpp @@ -0,0 +1,634 @@ +#include "processrunner.h" +#include "organizercore.h" +#include "instancemanager.h" +#include "lockeddialog.h" +#include "iuserinterface.h" +#include "envmodule.h" +#include + +using namespace MOBase; + +void adjustForVirtualized( + const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) +{ + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} + +env::Process* getInterestingProcess(std::vector& processes) +{ + if (processes.empty()) { + return nullptr; + } + + // Certain process names we wish to "hide" for aesthetic reason: + const std::vector hiddenList = { + QFileInfo(QCoreApplication::applicationFilePath()).fileName() + }; + + auto isHidden = [&](auto&& p) { + for (auto h : hiddenList) { + if (p.name().contains(h, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }; + + + for (auto&& root : processes) { + if (!isHidden(root)) { + return &root; + } + + for (auto&& child : root.children()) { + if (!isHidden(child)) { + return &child; + } + } + } + + + // everything is hidden, just pick the first one + return &processes[0]; +} + + +SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) + : m_handle(handle), m_parameters(std::move(sp)) +{ +} + +SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) + : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) +{ + other.m_handle = INVALID_HANDLE_VALUE; +} + +SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) +{ + if (this != &other) { + destroy(); + + m_handle = other.m_handle; + other.m_handle = INVALID_HANDLE_VALUE; + + m_parameters = std::move(other.m_parameters); + } + + return *this; +} + +SpawnedProcess::~SpawnedProcess() +{ + destroy(); +} + +HANDLE SpawnedProcess::releaseHandle() +{ + const auto h = m_handle; + m_handle = INVALID_HANDLE_VALUE; + return h; +} + +void SpawnedProcess::destroy() +{ + if (m_handle != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; + } +} + + +ProcessRunner::ProcessRunner(OrganizerCore& core) + : m_core(core), m_ui(nullptr) +{ +} + +void ProcessRunner::setUserInterface(IUserInterface* ui) +{ + m_ui = ui; +} + +bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) +{ + if (!parent && m_ui) { + parent = m_ui->qtWidget(); + } + + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); + + switch (fec.type) + { + case spawn::FileExecutionTypes::Executable: + { + runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); + return true; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + auto r = shell::Open(targetInfo.absoluteFilePath()); + if (!r.success()) { + return false; + } + + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer) + if (r.processHandle() != INVALID_HANDLE_VALUE) { + // steal because it gets closed after the wait + return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); + } + + return true; + } + } +} + +bool ProcessRunner::runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + bool refresh) +{ + DWORD processExitCode = 0; + HANDLE processHandle = spawnAndWait( + binary, arguments, m_core.currentProfile()->name(), + currentDirectory, steamAppID, customOverwrite, forcedLibraries, + &processExitCode); + + if (processHandle == INVALID_HANDLE_VALUE) { + // failed + return false; + } + + if (refresh) { + m_core.afterRun(binary, processExitCode); + } + + return true; +} + +bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +{ + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } + + const QString customOverwrite = profile->setting( + "custom_overwrites", exe.title()).toString(); + + QList forcedLibraries; + + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + + return runExecutableFile( + exe.binaryInfo(), + exe.arguments(), + exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), + exe.steamAppID(), + customOverwrite, + forcedLibraries, + refresh); +} + +bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +{ + if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + } + + const Executable& exe = m_core.executablesList()->get(shortcut.executable()); + return runExecutable(exe, false); +} + +HANDLE ProcessRunner::runExecutableOrExecutableFile( + const QString& executable, const QStringList &args, const QString &cwd, + const QString& profileOverride, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } + + QString profileName = profileOverride; + if (profileName == "") { + profileName = profile->name(); + } + + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString steamAppID; + QString customOverwrite; + QList forcedLibraries; + + if (executable.contains('\\') || executable.contains('/')) { + // file path + + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable); + } + + if (currentDirectory == "") { + currentDirectory = binary.absolutePath(); + } + + try { + const Executable& exe = m_core.executablesList()->getByBinary(binary); + steamAppID = exe.steamAppID(); + customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + } catch (const std::runtime_error &) { + // nop + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_core.executablesList()->get(executable); + steamAppID = exe.steamAppID(); + customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + if (arguments == "") { + arguments = exe.arguments(); + } + binary = exe.binaryInfo(); + if (currentDirectory == "") { + currentDirectory = exe.workingDirectory(); + } + } catch (const std::runtime_error &) { + log::warn("\"{}\" not set up as executable", executable); + binary = QFileInfo(executable); + } + } + + if (!forcedCustomOverwrite.isEmpty()) + customOverwrite = forcedCustomOverwrite; + + if (ignoreCustomOverwrite) + customOverwrite.clear(); + + return spawnAndWait( + binary, + arguments, + profileName, + currentDirectory, + steamAppID, + customOverwrite, + forcedLibraries); +} + +HANDLE ProcessRunner::spawnAndWait( + const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + LPDWORD exitCode) +{ + spawn::SpawnParameters sp; + sp.binary = binary; + sp.arguments = arguments; + sp.currentDirectory = currentDirectory; + sp.steamAppID = steamAppID; + sp.hooked = true; + + if (!m_core.beforeRun(binary, profileName, customOverwrite, forcedLibraries)) { + return INVALID_HANDLE_VALUE; + } + + HANDLE handle = spawn(sp).releaseHandle(); + + if (handle == INVALID_HANDLE_VALUE) { + // failed + return INVALID_HANDLE_VALUE; + } + + waitForProcessCompletionWithLock(handle, exitCode); + return handle; +} + +SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp) +{ + QWidget* parent = nullptr; + if (m_ui) { + parent = m_ui->qtWidget(); + } + + if (!checkBinary(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + const auto* game = m_core.managedGame(); + auto& settings = m_core.settings(); + + if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkEnvironment(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkBlacklist(parent, sp, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + adjustForVirtualized(game, sp, settings); + + return {startBinary(parent, sp), sp}; +} + +void ProcessRunner::withLock(std::function f) +{ + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; + + if (m_ui != nullptr) { + uilock = m_ui->lock(); + } + else { + // i.e. when running command line shortcuts there is no user interface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); + } + + Guard g([&]() { + if (m_ui != nullptr) { + m_ui->unlock(); + } + }); + + f(uilock); +} + +bool ProcessRunner::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) { + return true; + } + + bool r = false; + + withLock([&](auto* uilock) { + DWORD ignoreExitCode; + r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + }); + + return r; +} + +bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) + return true; + + bool r = false; + + withLock([&](auto* uilock) { + r = waitForProcessCompletion(handle, exitCode, uilock); + }); + + return r; +} + +bool ProcessRunner::waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto tree = env::getProcessTree(handle); + std::vector processes = {tree}; + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + return true; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + return true; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = waitForProcess( + interestingHandle.get(), exitCode, progress); + + switch (r) + { + case WaitResults::Completed: // fall-through + case WaitResults::Cancelled: + return true; + + case WaitResults::Error: // fall-through + default: + return false; + } +} + +bool ProcessRunner::waitForAllUSVFSProcessesWithLock() +{ + if (!Settings::instance().interface().lockGUI()) + return true; + + bool r = false; + + withLock([&](auto* uilock) { + r = waitForAllUSVFSProcesses(uilock); + }); + + return r; +} + +bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + for (;;) { + const auto handles = getRunningUSVFSProcesses(); + if (handles.empty()) { + break; + } + + std::vector processes; + for (auto&& h : handles) { + auto p = env::getProcessTree(h); + if (p.isValid()) { + processes.emplace_back(std::move(p)); + } + } + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + break; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + break; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = waitForProcess( + interestingHandle.get(), nullptr, progress); + + switch (r) + { + case WaitResults::Completed: + // this process is completed, check for others + break; + + case WaitResults::Cancelled: + // force unlocked + log::debug("waiting for process completion aborted by UI"); + return true; + + case WaitResults::Error: // fall-through + default: + log::debug("waiting for process completion not successful"); + return false; + } + } + + log::debug("waiting for process completion successful"); + return true; +} + + + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + + std::vector handles; + handles.push_back(handle); + + std::vector exitCodes; + + const auto r = waitForProcesses(handles, exitCodes, progress); + + if (r == WaitResults::Completed) { + if (exitCode && !exitCodes.empty()) { + *exitCode = exitCodes[0]; + } + } + + return r; +} + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function progress) +{ + if (handles.empty()) { + return WaitResults::Completed; + } + + const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + static_cast(handles.size()), &handles[0], + TRUE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion, {}", formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { + // completed + exitCodes.resize(handles.size()); + std::fill(exitCodes.begin(), exitCodes.end(), 0); + + for (std::size_t i=0; i + +class OrganizerCore; +class ILockedWaitingForProcess; +class IUserInterface; +class Executable; +class MOShortcut; + +class SpawnedProcess +{ +public: + SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp); + + SpawnedProcess(const SpawnedProcess&) = delete; + SpawnedProcess& operator=(const SpawnedProcess&) = delete; + SpawnedProcess(SpawnedProcess&& other); + SpawnedProcess& operator=(SpawnedProcess&& other); + ~SpawnedProcess(); + + HANDLE releaseHandle(); + void wait(); + +private: + HANDLE m_handle; + spawn::SpawnParameters m_parameters; + + void destroy(); +}; + + +class ProcessRunner +{ +public: + ProcessRunner(OrganizerCore& core); + + void setUserInterface(IUserInterface* ui); + + bool runFile(QWidget* parent, const QFileInfo& targetInfo); + + bool runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID={}, + const QString &customOverwrite={}, + const QList &forcedLibraries={}, + bool refresh=true); + + bool runExecutable(const Executable& exe, bool refresh=true); + + bool runShortcut(const MOShortcut& shortcut); + + HANDLE runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false); + + bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + + bool waitForAllUSVFSProcessesWithLock(); + +private: + OrganizerCore& m_core; + IUserInterface* m_ui; + + HANDLE spawnAndWait( + const QFileInfo &binary, const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries={}, + LPDWORD exitCode = nullptr); + + SpawnedProcess spawn(spawn::SpawnParameters sp); + + void withLock(std::function f); + + bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); + + bool waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + + bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); +}; + + +enum class WaitResults +{ + Completed = 1, + Error, + Cancelled +}; + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress); + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function progress); + +#endif // PROCESSRUNNER_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 0ea60641..c8d7c76a 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -772,50 +772,6 @@ bool checkBlacklist( } } - -void adjustForVirtualized( - const IPluginGame* game, SpawnParameters& sp, const Settings& settings) -{ - const QString modsPath = settings.paths().mods(); - - // Check if this a request with either an executable or a working directory - // under our mods folder then will start the process in a virtualized - // "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = sp.currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = sp.binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = game->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; - - } - - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = game->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; - } - - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); - - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); - } -} - - HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) { HANDLE handle = INVALID_HANDLE_VALUE; @@ -842,80 +798,6 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) } } - - -SpawnedProcess::SpawnedProcess(HANDLE handle, SpawnParameters sp) - : m_handle(handle), m_parameters(std::move(sp)) -{ -} - -SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) - : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) -{ - other.m_handle = INVALID_HANDLE_VALUE; -} - -SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) -{ - if (this != &other) { - destroy(); - - m_handle = other.m_handle; - other.m_handle = INVALID_HANDLE_VALUE; - - m_parameters = std::move(other.m_parameters); - } - - return *this; -} - -SpawnedProcess::~SpawnedProcess() -{ - destroy(); -} - -HANDLE SpawnedProcess::releaseHandle() -{ - const auto h = m_handle; - m_handle = INVALID_HANDLE_VALUE; - return h; -} - -void SpawnedProcess::destroy() -{ - if (m_handle != INVALID_HANDLE_VALUE) { - ::CloseHandle(m_handle); - m_handle = INVALID_HANDLE_VALUE; - } -} - - -SpawnedProcess Spawner::spawn( - QWidget* parent, const IPluginGame* game, - SpawnParameters sp, Settings& settings) -{ - if (!checkBinary(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkEnvironment(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkBlacklist(parent, sp, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - adjustForVirtualized(game, sp, settings); - - return {startBinary(parent, sp), sp}; -} - - QString getExecutableForJarFile(const QString& jarFile) { const std::wstring jarFileW = jarFile.toStdWString(); @@ -1094,86 +976,6 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } -WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, std::function progress) -{ - if (handle == INVALID_HANDLE_VALUE) { - return WaitResults::Error; - } - - log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); - - std::vector handles; - handles.push_back(handle); - - std::vector exitCodes; - - const auto r = waitForProcesses(handles, exitCodes, progress); - - if (r == WaitResults::Completed) { - if (exitCode && !exitCodes.empty()) { - *exitCode = exitCodes[0]; - } - } - - return r; -} - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress) -{ - if (handles.empty()) { - return WaitResults::Completed; - } - - const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); - - for (;;) { - // Wait for a an event on the handle, a key press, mouse click or timeout - const auto res = MsgWaitForMultipleObjects( - static_cast(handles.size()), &handles[0], - TRUE, 50, QS_KEY | QS_MOUSEBUTTON); - - if (res == WAIT_FAILED) { - // error - const auto e = ::GetLastError(); - - log::error( - "failed waiting for process completion, {}", formatSystemMessage(e)); - - return WaitResults::Error; - } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { - // completed - exitCodes.resize(handles.size()); - std::fill(exitCodes.begin(), exitCodes.end(), 0); - - for (std::size_t i=0; i progress); - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress); - } // namespace -- cgit v1.3.1 From b855754c9708c825e6bc1e995cb0262c065c5d20 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:18:21 -0400 Subject: removed runShortcut() changed lock widget text when running without a ui --- src/lockwidget.cpp | 16 ++++++++++++---- src/main.cpp | 6 +++++- src/organizercore.cpp | 8 ++++++-- src/processrunner.cpp | 9 --------- src/processrunner.h | 2 -- 5 files changed, 23 insertions(+), 18 deletions(-) (limited to 'src/main.cpp') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index ad9aefe3..cc66112e 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -107,8 +107,16 @@ void LockWidget::createUi(Reasons reason) { case LockUI: { - message->setText(QObject::tr( - "Mod Organizer is locked while the executable is running.")); + QString s; + + if (!m_parent) { + s = QObject::tr("Mod Organizer is currently running an application."); + } else { + s = QObject::tr( + "Mod Organizer is locked while the application is running."); + } + + message->setText(s); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); @@ -120,7 +128,7 @@ void LockWidget::createUi(Reasons reason) case OutputRequired: { message->setText(QObject::tr( - "The executable must run to completion because its output is " + "The application must run to completion because its output is " "required.")); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); @@ -133,7 +141,7 @@ void LockWidget::createUi(Reasons reason) case PreventExit: { message->setText(QObject::tr( - "Mod Organizer is waiting on processes to finish before exiting.")); + "Mod Organizer is waiting on application to close before exiting.")); auto* exit = new QPushButton(QObject::tr("Exit Now")); QObject::connect(exit, &QPushButton::clicked, [&]{ onForceUnlock(); }); diff --git a/src/main.cpp b/src/main.cpp index 5ed7da5d..9decb94e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -632,7 +632,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (MOShortcut shortcut{ arguments.at(1) }) { if (shortcut.hasExecutable()) { try { - organizer.processRunner().runShortcut(shortcut); + organizer.processRunner() + .setFromShortcut(shortcut) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + return 0; } catch (const std::exception &e) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 89e8bd9e..57e24f3b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -354,8 +354,12 @@ void OrganizerCore::downloadRequestedNXM(const QString &url) void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { - if(moshortcut.hasExecutable()) - processRunner().runShortcut(moshortcut); + if(moshortcut.hasExecutable()) { + processRunner() + .setFromShortcut(moshortcut) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + } } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 1c8c923b..6ca05147 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -662,15 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runShortcut(const MOShortcut& shortcut) -{ - setFromShortcut(shortcut); - setWaitForCompletion(NoRefresh); - - const auto r = run(); - return (r != Error); -} - HANDLE ProcessRunner::runExecutableOrExecutableFile( const QString& executable, const QStringList &args, const QString &cwd, const QString& profileOverride, const QString &forcedCustomOverwrite, diff --git a/src/processrunner.h b/src/processrunner.h index d3437412..4860be7c 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,8 +80,6 @@ public: DWORD exitCode(); - bool runShortcut(const MOShortcut& shortcut); - HANDLE runExecutableOrExecutableFile( const QString &executable, const QStringList &args, -- cgit v1.3.1 From a88f9dd9a703c23263b5561d3cae126e90e36f3f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:52:14 -0400 Subject: removed runExecutableOrExecutableFile() log the actual spawning and requests to start as well as wait from plugins raise the lock widget when it's a dialog --- src/lockwidget.cpp | 5 +++++ src/main.cpp | 16 +++++++++++----- src/organizerproxy.cpp | 32 ++++++++++++++++++++++++++------ src/processrunner.cpp | 14 ++------------ src/processrunner.h | 19 +++++-------------- src/spawn.cpp | 44 ++++++++++++++++++++++++++++++++++---------- 6 files changed, 83 insertions(+), 47 deletions(-) (limited to 'src/main.cpp') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index cc66112e..7b6e8430 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -183,6 +183,11 @@ void LockWidget::createUi(Reasons reason) m_overlay->setFocus(); m_overlay->show(); m_overlay->setEnabled(true); + + if (!overlayTarget) { + m_overlay->raise(); + m_overlay->activateWindow(); + } } void LockWidget::onForceUnlock() diff --git a/src/main.cpp b/src/main.cpp index 9decb94e..2b9a2f4d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -653,15 +653,21 @@ int runApplication(MOApplication &application, SingleInstance &instance, else { QString exeName = arguments.at(1); log::debug("starting {} from command line", exeName); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.processRunner().runExecutableOrExecutableFile( - exeName, arguments, QString(), QString()); + + try + { + // pass the remaining parameters to the binary + organizer.processRunner() + .setFromFileOrExecutable(exeName, arguments) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); return 0; } - catch (const std::exception &e) { + catch (const std::exception &e) + { reportError( QObject::tr("failed to start application: %1").arg(e.what())); return 1; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 3ee35fe2..9de5b4ba 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -108,17 +108,37 @@ QString OrganizerProxy::pluginDataPath() const } HANDLE OrganizerProxy::startApplication( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) + const QString& exe, const QStringList& args, const QString &cwd, + const QString& profile, const QString &overwrite, bool ignoreOverwrite) { - return m_Proxied->processRunner().runExecutableOrExecutableFile( - executable, args, cwd, profile, - forcedCustomOverwrite, ignoreCustomOverwrite); + log::debug( + "a plugin has requested to start an application:\n" + " . executable: '{}'\n" + " . args: '{}'\n" + " . cwd: '{}'\n" + " . profile: '{}'\n" + " . overwrite: '{}'\n" + " . ignore overwrite: {}", + exe, args.join(" "), cwd, profile, overwrite, ignoreOverwrite); + + auto runner = m_Proxied->processRunner(); + + runner + .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); + + return runner.processHandle(); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { + const auto pid = ::GetProcessId(handle); + + log::debug( + "a plugin wants to wait for an application to complete, pid {}{}", + pid, (pid == 0 ? "unknown (probably already completed)" : "")); + const auto r = m_Proxied->processRunner().waitForApplication( handle, exitCode, LockWidget::OutputRequired); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 6ca05147..e6f916c5 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -661,22 +661,12 @@ DWORD ProcessRunner::exitCode() return m_exitCode; } - -HANDLE ProcessRunner::runExecutableOrExecutableFile( - const QString& executable, const QStringList &args, const QString &cwd, - const QString& profileOverride, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) +HANDLE ProcessRunner::processHandle() { - setFromFileOrExecutable( - executable, args, cwd, profileOverride, forcedCustomOverwrite, - ignoreCustomOverwrite); - - setWaitForCompletion(Refresh); - - run(); return m_handle; } + void ProcessRunner::withLock( LockWidget::Reasons reason, std::function f) { diff --git a/src/processrunner.h b/src/processrunner.h index 4860be7c..bdd0b260 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -71,23 +71,14 @@ public: ProcessRunner& setFromFileOrExecutable( const QString &executable, const QStringList &args, - const QString &cwd, - const QString &profile, - const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); + const QString &cwd={}, + const QString &profile={}, + const QString &forcedCustomOverwrite={}, + bool ignoreCustomOverwrite=false); Results run(); DWORD exitCode(); - - - HANDLE runExecutableOrExecutableFile( - const QString &executable, - const QStringList &args, - const QString &cwd, - const QString &profile, - const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); - + HANDLE processHandle(); Results waitForApplication( HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); diff --git a/src/spawn.cpp b/src/spawn.cpp index c8d7c76a..b331db01 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -442,6 +442,25 @@ QMessageBox::StandardButton confirmBlacklisted( namespace spawn { +void logSpawning(const SpawnParameters& sp, const QString& realCmd) +{ + log::debug( + "spawning binary:\n" + " . exe: '{}'\n" + " . args: '{}'\n" + " . cwd: '{}'\n" + " . steam id: '{}'\n" + " . hooked: {}\n" + " . stdout: {}\n" + " . stderr: {}\n" + " . real cmd: '{}'", + sp.binary.absoluteFilePath(), sp.arguments, + sp.currentDirectory.absolutePath(), sp.steamAppID, sp.hooked, + (sp.stdOut == INVALID_HANDLE_VALUE ? "no" : "yes"), + (sp.stdErr == INVALID_HANDLE_VALUE ? "no" : "yes"), + realCmd); +} + DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) { BOOL inheritHandles = FALSE; @@ -462,30 +481,35 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) si.dwFlags |= STARTF_USESTDHANDLES; } - const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); - const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); + const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()); + const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()); - std::wstring commandLine = L"\"" + bin + L"\""; - if (sp.arguments[0] != L'\0') { - commandLine += L" " + sp.arguments.toStdWString(); + QString commandLine = "\"" + bin + "\""; + if (!sp.arguments.isEmpty()) { + commandLine += " " + sp.arguments; } - QString moPath = QCoreApplication::applicationDirPath(); + const QString moPath = QCoreApplication::applicationDirPath(); const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath)); PROCESS_INFORMATION pi; BOOL success = FALSE; + logSpawning(sp, commandLine); + + const auto wcommandLine = commandLine.toStdWString(); + const auto wcwd = cwd.toStdWString(); + if (sp.hooked) { success = ::CreateProcessHooked( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); + wcwd.c_str(), &si, &pi); } else { success = ::CreateProcess( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); + wcwd.c_str(), &si, &pi); } const auto e = GetLastError(); -- cgit v1.3.1 From 7ebd4debeef2cfdf268679e7b680021d3dc20687 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:19:14 -0400 Subject: added a ForceWait flag to bypass disabled locking merged a bunch of unnecessary functions in ProcessRunner --- src/main.cpp | 4 +- src/mainwindow.cpp | 2 +- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 2 +- src/organizerproxy.cpp | 6 +- src/processrunner.cpp | 155 ++++++++++++++++++++--------------------- src/processrunner.h | 43 ++++++------ 7 files changed, 107 insertions(+), 107 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 2b9a2f4d..02347ee3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -634,7 +634,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, try { organizer.processRunner() .setFromShortcut(shortcut) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return 0; @@ -662,7 +662,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, // pass the remaining parameters to the binary organizer.processRunner() .setFromFileOrExecutable(exeName, arguments) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return 0; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d304f8b7..c2d91bcc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5404,7 +5404,7 @@ void MainWindow::openDataFile() m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 8aefd4c6..d37f068c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -529,7 +529,7 @@ void ConflictsTab::openItems(QTreeView* tree) for_each_in_selection(tree, [&](const ConflictItem* item) { core().processRunner() .setFromFile(parentWidget(), item->fileName()) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return true; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 943750ed..96ae84a8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -357,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) if(moshortcut.hasExecutable()) { processRunner() .setFromShortcut(moshortcut) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); } } diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 0b6f8df1..420e2d82 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -128,7 +128,9 @@ HANDLE OrganizerProxy::startApplication( .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) .run(); - return runner.processHandle(); + // the plugin is in charge of closing the handle, unless waitForApplication() + // is called on it + return runner.stealProcessHandle().release(); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const @@ -142,7 +144,7 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const auto runner = m_Proxied->processRunner(); const auto r = runner - .setWaitForCompletion(ProcessRunner::NoRefresh, LockWidget::OutputRequired) + .setWaitForCompletion(ProcessRunner::ForceWait, LockWidget::OutputRequired) .attachToProcess(handle); if (exitCode) { diff --git a/src/processrunner.cpp b/src/processrunner.cpp index b732204c..9cc2ce52 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -368,8 +368,8 @@ void SpawnedProcess::destroy() ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(NoRefresh), - m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) + m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), + m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { m_sp.hooked = true; } @@ -417,10 +417,10 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - RefreshModes refresh, LockWidget::Reasons reason) + WaitFlags flags, LockWidget::Reasons reason) { - m_refresh = refresh; - m_lock = reason; + m_waitFlags = flags; + m_lockReason = reason; return *this; } @@ -593,13 +593,13 @@ ProcessRunner::Results ProcessRunner::run() return Error; } - m_handle = r.stealProcessHandle(); + m_handle.reset(r.stealProcessHandle()); // not all files will return a valid handle even if opening them was // successful, such as inproc handlers (like the photo viewer); in this // case it's impossible to determine the status, so just say it's still // running - if (m_handle == INVALID_HANDLE_VALUE) { + if (m_handle.get() == INVALID_HANDLE_VALUE) { return Running; } } else { @@ -642,8 +642,8 @@ ProcessRunner::Results ProcessRunner::run() adjustForVirtualized(game, m_sp, settings); - m_handle = startBinary(parent, m_sp); - if (m_handle == INVALID_HANDLE_VALUE) { + m_handle.reset(startBinary(parent, m_sp)); + if (m_handle.get() == INVALID_HANDLE_VALUE) { return Error; } } @@ -653,14 +653,46 @@ ProcessRunner::Results ProcessRunner::run() ProcessRunner::Results ProcessRunner::postRun() { - if (m_lock == LockWidget::NoReason) { - return Running; + const bool mustWait = (m_waitFlags & ForceWait); + + if (mustWait && m_lockReason == LockWidget::NoReason) { + // never lock the ui without an escape hatch for the user + log::debug( + "the ForceWait flag is set but the lock reason wasn't, " + "defaulting to LockUI"); + + m_lockReason = LockWidget::LockUI; + } + + if (mustWait) { + if (!Settings::instance().interface().lockGUI()) { + // at least tell the user what's going on + log::debug( + "locking is disabled, but the output of the application is required; " + "overriding this setting and locking the ui"); + } + } else { + // no force wait + + if (m_lockReason == LockWidget::NoReason) { + // no locking requested + return Running; + } + + if (!Settings::instance().interface().lockGUI()) { + // disabling locking is like clicking on unlock immediately + log::debug("not waiting for process because locking is disabled"); + return ForceUnlocked; + } } - const auto r = waitForProcessCompletionWithLock( - m_handle, &m_exitCode, m_lock); + auto r = Error; + + withLock([&](auto& lock) { + r = waitForProcess(m_handle.get(), &m_exitCode, lock); + }); - if (r == Completed && m_refresh == Refresh) { + if (r == Completed && (m_waitFlags & Refresh)) { m_core.afterRun(m_sp.binary, m_exitCode); } @@ -669,101 +701,64 @@ ProcessRunner::Results ProcessRunner::postRun() ProcessRunner::Results ProcessRunner::attachToProcess(HANDLE h) { - m_handle = h; + m_handle.reset(h); return postRun(); } -DWORD ProcessRunner::exitCode() +DWORD ProcessRunner::exitCode() const { return m_exitCode; } -HANDLE ProcessRunner::processHandle() +HANDLE ProcessRunner::getProcessHandle() const { - return m_handle; + return m_handle.get(); } - -void ProcessRunner::withLock( - LockWidget::Reasons reason, std::function f) +env::HandlePtr ProcessRunner::stealProcessHandle() { - auto lock = std::make_unique( - m_ui ? m_ui->qtWidget() : nullptr, reason); - - f(*lock); + return std::move(m_handle); } -ProcessRunner::Results ProcessRunner::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) +ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( + LockWidget::Reasons reason) { + m_lockReason = reason; + if (!Settings::instance().interface().lockGUI()) { - log::debug("not waiting for process because user has disabled locking"); + // disabling locking is like clicking on unlock immediately return ForceUnlocked; } - return waitForApplication(handle, exitCode, reason); -} - -ProcessRunner::Results ProcessRunner::waitForApplication( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) -{ - // don't check for lockGUI() setting; this _always_ locks the ui and waits - // for completion - // - // this is typically called only from: - // 1) OrganizerProxy, which allows plugins to wait on applications until - // they're finished - // - // the check_fnis plugin for example will start FNIS, wait for it to - // complete, and then check the exit code; this has to work regardless of - // the locking setting; - // - // 2) waitForProcessCompletionWithLock() above, which has already checked the - // lock setting - auto r = Error; - withLock(reason, [&](auto& lock) { - r = waitForProcess(handle, exitCode, lock); - }); + withLock([&](auto& lock) { + for (;;) { + const auto processes = getRunningUSVFSProcesses(); + if (processes.empty()) { + break; + } - return r; -} + r = waitForProcesses(processes, lock); -ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( - LockWidget::Reasons reason) -{ - if (!Settings::instance().interface().lockGUI()) { - log::debug("not waiting for usvfs processes because user has disabled locking"); - return ForceUnlocked; - } + if (r != Completed) { + // error, cancelled, or unlocked + return; + } - auto r = Error; + // this process is completed, check for others + } - withLock(reason, [&](auto& lock) { - r = waitForAllUSVFSProcesses(lock); + r = Completed; }); return r; } -ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcesses(LockWidget& lock) +void ProcessRunner::withLock(std::function f) { - for (;;) { - const auto processes = getRunningUSVFSProcesses(); - if (processes.empty()) { - break; - } - - const auto r = waitForProcesses(processes, lock); - - if (r != Completed) { - // error, cancelled, or unlocked - return r; - } - - // this process is completed, check for others - } + auto lk = std::make_unique( + m_ui ? m_ui->qtWidget() : nullptr, m_lockReason); - return Completed; + f(*lk); } diff --git a/src/processrunner.h b/src/processrunner.h index 62ec7a9e..41c0f12c 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -3,6 +3,7 @@ #include "spawn.h" #include "lockwidget.h" +#include "envmodule.h" #include class OrganizerCore; @@ -44,16 +45,25 @@ public: ForceUnlocked }; - enum RefreshModes + enum WaitFlag { - NoRefresh = 1, - Refresh + NoFlags = 0x00, + Refresh = 0x01, + ForceWait = 0x02 }; + using WaitFlags = QFlags; + using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); + // move only + ProcessRunner(ProcessRunner&&) = default; + ProcessRunner& operator=(const ProcessRunner&) = delete; + ProcessRunner(const ProcessRunner&) = delete; + ProcessRunner& operator=(ProcessRunner&&) = delete; + ProcessRunner& setBinary(const QFileInfo &binary); ProcessRunner& setArguments(const QString& arguments); ProcessRunner& setCurrentDirectory(const QDir& directory); @@ -62,7 +72,7 @@ public: ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( - RefreshModes refresh, LockWidget::Reasons reason=LockWidget::LockUI); + WaitFlags flags=NoFlags, LockWidget::Reasons reason=LockWidget::LockUI); ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); @@ -79,8 +89,9 @@ public: Results run(); Results attachToProcess(HANDLE h); - DWORD exitCode(); - HANDLE processHandle(); + DWORD exitCode() const; + HANDLE getProcessHandle() const; + env::HandlePtr stealProcessHandle(); Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); @@ -91,24 +102,16 @@ private: QString m_customOverwrite; ForcedLibraries m_forcedLibraries; QString m_profileName; - LockWidget::Reasons m_lock; - RefreshModes m_refresh; + LockWidget::Reasons m_lockReason; + WaitFlags m_waitFlags; QString m_shellOpen; - HANDLE m_handle; + env::HandlePtr m_handle; DWORD m_exitCode; Results postRun(); - - void withLock( - LockWidget::Reasons reason, std::function f); - - Results waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason); - - Results waitForApplication( - HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); - - Results waitForAllUSVFSProcesses(LockWidget& lock); + void withLock(std::function f); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); + #endif // PROCESSRUNNER_H -- cgit v1.3.1 From 255f96ce7e27124f1bed071564e67a2e4a25c8aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 05:47:11 -0500 Subject: only log crash dumps message on startup --- src/main.cpp | 14 ++++++++++++++ src/organizercore.cpp | 6 ------ 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 02347ee3..41ce0eb5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -554,6 +554,20 @@ int runApplication(MOApplication &application, SingleInstance &instance, return 1; } + { + // log if there are any dmp files + const auto hasCrashDumps = + !QDir(QString::fromStdWString(organizer.crashDumpsPath())) + .entryList({"*.dmp"}, QDir::Files) + .empty(); + + if (hasCrashDumps) { + log::debug( + "there are crash dumps in '{}'", + QString::fromStdWString(organizer.crashDumpsPath())); + } + } + log::debug("initializing plugins"); PluginContainer pluginContainer(&organizer); pluginContainer.loadPlugins(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 9ceb149e..c585ba09 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -484,12 +484,6 @@ bool OrganizerCore::cycleDiagnostics() removeOldFiles(path, "*.dmp", maxDumps, QDir::Time|QDir::Reversed); } - // log if there are any files left - const auto files = QDir(path).entryList({"*.dmp"}, QDir::Files); - if (!files.isEmpty()) { - log::debug("there are crash dumps in '{}'", path); - } - return true; } -- cgit v1.3.1 From 2c2c47c21502db6471fe7d727f942c2400b150c1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 07:06:20 -0500 Subject: log new modules being loaded after startup --- src/env.cpp | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 24 ++++++++++ src/main.cpp | 4 ++ 3 files changed, 175 insertions(+) (limited to 'src/main.cpp') diff --git a/src/env.cpp b/src/env.cpp index b3f9525f..51631607 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -56,6 +56,55 @@ Console::~Console() } +ModuleNotification::ModuleNotification(std::function f) + : m_cookie(nullptr), m_f(std::move(f)) +{ +} + +ModuleNotification::~ModuleNotification() +{ + if (!m_cookie) { + return; + } + + typedef NTSTATUS NTAPI LdrUnregisterDllNotificationType( + PVOID Cookie + ); + + LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + log::error("failed to load ntdll.dll while unregistering for module notifications"); + return; + } + + auto* LdrUnregisterDllNotification = reinterpret_cast( + GetProcAddress(ntdll.get(), "LdrUnregisterDllNotification")); + + if (!LdrUnregisterDllNotification) { + log::error("LdrUnregisterDllNotification not found in ntdll.dll"); + return; + } + + const auto r = LdrUnregisterDllNotification(m_cookie); + if (r != 0) { + log::error("failed to unregister for module notifications, error {}", r); + } +} + +void ModuleNotification::setCookie(void* c) +{ + m_cookie = c; +} + +void ModuleNotification::fire(const Module& m) +{ + if (m_f) { + m_f(m); + } +} + + Environment::Environment() { } @@ -146,6 +195,104 @@ QString Environment::timezone() const return s; } +std::unique_ptr Environment::onModuleLoaded( + std::function f) +{ + typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; + } UNICODE_STRING, *PUNICODE_STRING; + + typedef const PUNICODE_STRING PCUNICODE_STRING; + + typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA { + ULONG Flags; //Reserved. + PCUNICODE_STRING FullDllName; //The full path name of the DLL module. + PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. + PVOID DllBase; //A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; //The size of the DLL image, in bytes. + } LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA; + + typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA { + ULONG Flags; //Reserved. + PCUNICODE_STRING FullDllName; //The full path name of the DLL module. + PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. + PVOID DllBase; //A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; //The size of the DLL image, in bytes. + } LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA; + + typedef union _LDR_DLL_NOTIFICATION_DATA { + LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; + LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; + } LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA; + + typedef VOID CALLBACK LDR_DLL_NOTIFICATION_FUNCTION( + ULONG NotificationReason, + const PLDR_DLL_NOTIFICATION_DATA NotificationData, + PVOID Context + ); + + typedef LDR_DLL_NOTIFICATION_FUNCTION* PLDR_DLL_NOTIFICATION_FUNCTION; + + typedef NTSTATUS NTAPI LdrRegisterDllNotificationType( + ULONG Flags, + PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, + PVOID Context, + PVOID *Cookie + ); + + const ULONG LDR_DLL_NOTIFICATION_REASON_LOADED = 1; + const ULONG LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2; + + + // loading ntdll.dll, the function will be found with GetProcAddress() + LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + log::error("failed to load ntdll.dll while registering for module notifications"); + return {}; + } + + auto* LdrRegisterDllNotification = reinterpret_cast( + GetProcAddress(ntdll.get(), "LdrRegisterDllNotification")); + + if (!LdrRegisterDllNotification) { + log::error("LdrRegisterDllNotification not found in ntdll.dll"); + return {}; + } + + + auto context = std::make_unique(f); + void* cookie = nullptr; + + auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void* context) { + if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) { + const Module m( + QString::fromWCharArray( + data->Loaded.FullDllName->Buffer, + data->Loaded.FullDllName->Length / sizeof(wchar_t)), + data->Loaded.SizeOfImage); + + if (context) { + static_cast(context)->fire(m); + } + } + }; + + const auto r = LdrRegisterDllNotification( + 0, OnDllLoaded, context.get(), &cookie); + + if (r != 0) { + log::error("failed to register for module notifications, error {}", r); + return {}; + } + + context->setCookie(cookie); + + return context; +} + void Environment::dump(const Settings& s) const { log::debug("windows: {}", windowsInfo().toString()); diff --git a/src/env.h b/src/env.h index f8b1eb70..946cb13b 100644 --- a/src/env.h +++ b/src/env.h @@ -119,6 +119,27 @@ private: }; +class ModuleNotification +{ +public: + ModuleNotification(std::function f); + ~ModuleNotification(); + + ModuleNotification(const ModuleNotification&) = delete; + ModuleNotification& operator=(const ModuleNotification&) = delete; + + ModuleNotification(ModuleNotification&&) = default; + ModuleNotification& operator=(ModuleNotification&&) = default; + + void setCookie(void* c); + void fire(const Module& m); + +private: + void* m_cookie; + std::function m_f; +}; + + // represents the process's environment // class Environment @@ -151,6 +172,9 @@ public: // QString timezone() const; + std::unique_ptr onModuleLoaded( + std::function f); + // logs the environment // void dump(const Settings& s) const; diff --git a/src/main.cpp b/src/main.cpp index 41ce0eb5..6e112479 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -547,6 +547,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, settings.dump(); sanityChecks(env); + const auto moduleNotification = env.onModuleLoaded([](auto&& m) { + log::debug("loaded module {}", m.toString()); + }); + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { -- cgit v1.3.1 From c3068ce50ab6e25e21452c2ea2f83086ddf555d5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 08:39:22 -0500 Subject: added rivatuner to sanity checks now also checks modules loaded after startup fixed crash on w7 when checking some modules --- src/env.cpp | 44 ++++++++++++++++++++++++++++++-------------- src/env.h | 11 ++++++++--- src/main.cpp | 4 +++- src/sanitychecks.cpp | 37 ++++++++++++++++++++++--------------- 4 files changed, 63 insertions(+), 33 deletions(-) (limited to 'src/main.cpp') diff --git a/src/env.cpp b/src/env.cpp index 51631607..f9507dc1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -56,8 +56,8 @@ Console::~Console() } -ModuleNotification::ModuleNotification(std::function f) - : m_cookie(nullptr), m_f(std::move(f)) +ModuleNotification::ModuleNotification(QObject* o, std::function f) + : m_cookie(nullptr), m_object(o), m_f(std::move(f)) { } @@ -97,10 +97,26 @@ void ModuleNotification::setCookie(void* c) m_cookie = c; } -void ModuleNotification::fire(const Module& m) +void ModuleNotification::fire(QString path, std::size_t fileSize) { + if (m_loaded.contains(path)) { + // don't notify if it's been loaded before + } + + m_loaded.insert(path); + + // constructing a Module will query the version info of the file, which seems + // to generate an access violation for at least plugin_python.dll on Windows 7 + // + // it's not clear what the problem is, but making sure this is deferred until + // _after_ the dll is loaded seems to fix it + // + // so this queues the callback in the main thread + if (m_f) { - m_f(m); + QMetaObject::invokeMethod(m_object, [path, fileSize, f=m_f] { + f(Module(path, fileSize)); + }, Qt::QueuedConnection); } } @@ -196,7 +212,7 @@ QString Environment::timezone() const } std::unique_ptr Environment::onModuleLoaded( - std::function f) + QObject* o, std::function f) { typedef struct _UNICODE_STRING { USHORT Length; @@ -263,19 +279,19 @@ std::unique_ptr Environment::onModuleLoaded( } - auto context = std::make_unique(f); + auto context = std::make_unique(o, f); void* cookie = nullptr; auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void* context) { if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) { - const Module m( - QString::fromWCharArray( - data->Loaded.FullDllName->Buffer, - data->Loaded.FullDllName->Length / sizeof(wchar_t)), - data->Loaded.SizeOfImage); - - if (context) { - static_cast(context)->fire(m); + if (data && data->Loaded.FullDllName) { + if (context) { + static_cast(context)->fire( + QString::fromWCharArray( + data->Loaded.FullDllName->Buffer, + data->Loaded.FullDllName->Length / sizeof(wchar_t)), + data->Loaded.SizeOfImage); + } } } }; diff --git a/src/env.h b/src/env.h index 946cb13b..dc0fd864 100644 --- a/src/env.h +++ b/src/env.h @@ -122,7 +122,7 @@ private: class ModuleNotification { public: - ModuleNotification(std::function f); + ModuleNotification(QObject* o, std::function f); ~ModuleNotification(); ModuleNotification(const ModuleNotification&) = delete; @@ -132,10 +132,12 @@ public: ModuleNotification& operator=(ModuleNotification&&) = default; void setCookie(void* c); - void fire(const Module& m); + void fire(QString path, std::size_t fileSize); private: void* m_cookie; + QObject* m_object; + std::set m_loaded; std::function m_f; }; @@ -172,8 +174,11 @@ public: // QString timezone() const; + // will call `f` on the same thread `o` is running on every time a module + // is loaded in the process + // std::unique_ptr onModuleLoaded( - std::function f); + QObject* o, std::function f); // logs the environment // diff --git a/src/main.cpp b/src/main.cpp index 6e112479..3cccf365 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,6 +93,7 @@ using namespace MOShared; void sanityChecks(const env::Environment& env); +int checkIncompatibleModule(const env::Module& m); bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); @@ -547,8 +548,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, settings.dump(); sanityChecks(env); - const auto moduleNotification = env.onModuleLoaded([](auto&& m) { + const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) { log::debug("loaded module {}", m.toString()); + checkIncompatibleModule(m); }); log::debug("initializing core"); diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 3b4185a7..495795f2 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -202,29 +202,36 @@ int checkMissingFiles() return n; } -bool checkNahimic(const env::Environment& e) +int checkIncompatibleModule(const env::Module& m) { - // Nahimic seems to interfere mostly with dialogs, like the mod info dialog: - // it renders dialogs fully white and makes it impossible to interact with - // them + // these dlls seems to interfere mostly with dialogs, like the mod info + // dialog: it renders dialogs fully white and makes it impossible to interact + // with them // - // NahimicOSD.dll is usually loaded on startup, but there has been some - // reports where it got loaded later, so this check is not entirely accurate + // the dlls is usually loaded on startup, but there has been some reports + // where it got loaded later, so this is also called every time a new module + // is loaded into this process - for (auto&& m : e.loadedModules()) { - const QFileInfo file(m.path()); + static const std::map names = { + {"NahimicOSD.dll", "Nahimic"}, + {"RTSSHooks64.dll", "RivaTuner Statistics Server"} + }; + + const QFileInfo file(m.path()); + int n = 0; - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { + for (auto&& p : names) { + if (file.fileName().compare(p.first, Qt::CaseInsensitive) == 0) { log::warn( - "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "{} is loaded. This program is known to cause issues with " "Mod Organizer, such as freezing or blank windows. Consider " - "uninstalling it."); + "uninstalling it. ({})", p.second, file.absoluteFilePath()); - return true; + ++n; } } - return false; + return n; } int checkIncompatibilities(const env::Environment& e) @@ -233,8 +240,8 @@ int checkIncompatibilities(const env::Environment& e) int n = 0; - if (checkNahimic(e)) { - ++n; + for (auto&& m : e.loadedModules()) { + n += checkIncompatibleModule(m); } return n; -- cgit v1.3.1 From 05759c4abcae0e54c7c6fb3aac43b55ed490b6f4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 16:03:42 -0500 Subject: added checks on startup for directories in program files --- src/main.cpp | 3 ++ src/sanitychecks.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 3cccf365..1f6c57d1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,6 +94,7 @@ using namespace MOShared; void sanityChecks(const env::Environment& env); int checkIncompatibleModule(const env::Module& m); +int checkPathsForSanity(MOBase::IPluginGame& game, const Settings& s); bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); @@ -593,6 +594,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, return 1; } + checkPathsForSanity(*game, settings); + if (splashPath.startsWith(':')) { // currently using MO splash, see if the plugin contains one QString pluginSplash diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index ef57a503..a767e8f9 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -1,6 +1,9 @@ #include "env.h" #include "envmodule.h" +#include "settings.h" +#include #include +#include using namespace MOBase; @@ -252,6 +255,83 @@ int checkIncompatibilities(const env::Environment& e) return n; } +std::vector> getSystemDirectories() +{ + // folder ids and display names for logging + const std::vector> systemFolderIDs = { + {FOLDERID_ProgramFiles, "Program Files"}, + {FOLDERID_ProgramFilesX86, "Program Files"} + }; + + std::vector> systemDirs; + + for (auto&& p : systemFolderIDs) { + try + { + const auto dir = MOBase::getKnownFolder(p.first); + + auto path = QDir::toNativeSeparators(dir.absolutePath()).toLower(); + if (!path.endsWith("\\")) { + path += "\\"; + } + + systemDirs.push_back({path, p.second}); + } + catch(std::exception&) + { + // ignore + } + } + + return systemDirs; +} + +int checkProtected(const QDir& d, const QString& what) +{ + static const auto systemDirs = getSystemDirectories(); + + const auto path = QDir::toNativeSeparators(d.absolutePath()).toLower(); + + log::debug(" . {}: {}", what, path); + + for (auto&& sd : systemDirs) { + if (path.startsWith(sd.first)) { + log::warn( + "{} is in {}; this may cause issues because it's a protected " + "system folder", + what, sd.second); + + log::debug("path '{}' starts with '{}'", path, sd.first); + + return 1; + } + } + + return 0; +} + +int checkPathsForSanity(IPluginGame& game, const Settings& s) +{ + log::debug("checking paths"); + + int n = 0; + + n += checkProtected(game.gameDirectory(), "the game"); + n += checkProtected(QApplication::applicationDirPath(), "Mod Organizer"); + + if (checkProtected(s.paths().base(), "the instance base directory")) { + ++n; + } else { + n += checkProtected(s.paths().downloads(), "the downloads directory"); + n += checkProtected(s.paths().mods(), "the mods directory"); + n += checkProtected(s.paths().cache(), "the cache directory"); + n += checkProtected(s.paths().profiles(), "the profiles directory"); + n += checkProtected(s.paths().overwrite(), "the overwrite directory"); + } + + return n; +} + void sanityChecks(const env::Environment& e) { log::debug("running sanity checks..."); -- cgit v1.3.1 From 44084bfa415f906b8ba8b5d19e1be60a7612d692 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sun, 8 Dec 2019 20:47:45 -0600 Subject: Rework column insertion to move to the intended position post-facto --- src/main.cpp | 2 -- src/mainwindow.cpp | 41 ++++++++++++++++++++++++++--------------- src/mainwindow.h | 2 +- src/modlist.h | 5 ++--- 4 files changed, 29 insertions(+), 21 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 1f6c57d1..eeabd497 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -735,8 +735,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); - mainWindow.processUpdates(settings); - // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8eff26c5..585be402 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -178,6 +178,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #ifdef TEST_MODELS #include "modeltest.h" @@ -511,6 +512,7 @@ MainWindow::MainWindow(Settings &settings resetActionIcons(); updatePluginCount(); updateModCount(); + processUpdates(); } void MainWindow::setupModList() @@ -2299,7 +2301,8 @@ void MainWindow::readSettings() } } -void MainWindow::processUpdates(Settings& settings) { +void MainWindow::processUpdates() { + auto& settings = m_OrganizerCore.settings(); const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); const auto lastVersion = settings.version().value_or(earliest); @@ -2308,15 +2311,6 @@ void MainWindow::processUpdates(Settings& settings) { settings.processUpdates(currentVersion, lastVersion); if (!settings.firstStart()) { - if (lastVersion < QVersionNumber(2, 1, 3)) { - bool lastHidden = true; - for (int i = ui->modList->header()->visualIndex(ModList::COL_GAME); i < ui->modList->header()->count(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); - ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); - lastHidden = hidden; - } - } - if (lastVersion < QVersionNumber(2, 1, 6)) { ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); } @@ -2329,11 +2323,28 @@ void MainWindow::processUpdates(Settings& settings) { } if (lastVersion < QVersionNumber(2, 2, 2)) { - bool lastHidden = true; - for (int i = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); i < ui->modList->header()->count(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); - ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); - lastHidden = hidden; + int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); + int pos2 = 0; + if (pos1) { + ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); + pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); + ui->modList->header()->moveSection( + ui->modList->header()->visualIndexAt(pos2), + ui->modList->header()->visualIndexAt(pos1) + ); + } + } + } else { + { // Move conflict flags + int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); + int pos2 = 0; + if (pos1) { + ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); + pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); + ui->modList->header()->moveSection( + ui->modList->header()->visualIndexAt(pos2), + ui->modList->header()->visualIndexAt(pos1) + ); } } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 2020b1a3..fcbbe039 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -116,7 +116,7 @@ public: QWidget *parent = 0); ~MainWindow(); - void processUpdates(Settings& settings); + void processUpdates(); QWidget* qtWidget() override; diff --git a/src/modlist.h b/src/modlist.h index 8841ec29..ce439f7b 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -57,7 +57,6 @@ public: enum EColumn { COL_NAME, - COL_CONFLICTFLAGS, COL_FLAGS, COL_CONTENT, COL_CATEGORY, @@ -67,8 +66,8 @@ public: COL_INSTALLTIME, COL_PRIORITY, COL_NOTES, - - COL_LASTCOLUMN = COL_NOTES + COL_CONFLICTFLAGS, + COL_LASTCOLUMN = COL_CONFLICTFLAGS, }; typedef boost::signals2::signal SignalModStateChanged; -- cgit v1.3.1 From 05231eab45f86e3d0d342c429e35c8f7c813ea42 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 15 Dec 2019 22:35:02 -0500 Subject: temporary fix to keep MO locked for all processes when closing save main window settings in closeEvent() --- src/main.cpp | 1 - src/mainwindow.cpp | 22 ++++++++++++++++++++++ src/processrunner.cpp | 26 ++++++++++++++++++-------- src/spawn.cpp | 6 ++++-- src/uilocker.cpp | 5 +++++ src/uilocker.h | 1 + 6 files changed, 50 insertions(+), 11 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index eeabd497..29d2d02c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -745,7 +745,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); res = application.exec(); - mainWindow.onBeforeClose(); mainWindow.close(); NexusInterface::instance(&pluginContainer) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d1578d85..284c33e3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1306,6 +1306,21 @@ void MainWindow::onBeforeClose() void MainWindow::closeEvent(QCloseEvent* event) { + if (isVisible()) { + // this is messy + // + // the main problem this is solving is when closing MO, then getting the + // lock overlay because processes are still running, then pressing the X + // again + // + // in this case, closeEvent() is _not_ called for the second event and the + // window is immediately hidden + // + // this always saves the settings here; in the event where a lock overlay + // is then shown, it might save settings multiple times, but it's harmless + onBeforeClose(); + } + // this happens for two reasons: // 1) the user requested to close the window, such as clicking the X // 2) close() is called in runApplication() after application.exec() @@ -1324,6 +1339,13 @@ void MainWindow::closeEvent(QCloseEvent* event) return; } + if (UILocker::instance().locked()) { + // don't bother asking the user to confirm if the ui is already locked + event->ignore(); + ExitModOrganizer(Exit::Force); + return; + } + if (ModOrganizerExiting()) { // ignore repeated attempts event->ignore(); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index aead42d1..945d61c3 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -344,6 +344,9 @@ ProcessRunner::Results waitForProcessesThreadImpl( // processes wait = std::min(wait * 2, milliseconds(2000)); } + + log::debug("waiting for processes interrupted"); + return ProcessRunner::ForceUnlocked; } void waitForProcessesThread( @@ -374,9 +377,12 @@ ProcessRunner::Results waitForProcesses( if (!::AssignProcessToJobObject(job.get(), h)) { const auto e = GetLastError(); - log::error( - "can't assign process to job to wait for processes, {}", - formatSystemMessage(e)); + // this happens when closing MO while multiple processes are running, + // so the logging is disabled until it gets fixed + + //log::error( + // "can't assign process to job to wait for processes, {}", + // formatSystemMessage(e)); // keep going } @@ -871,11 +877,12 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( auto r = Error; - withLock([&](auto& ls) { for (;;) { + withLock([&](auto& ls) { const auto processes = getRunningUSVFSProcesses(); if (processes.empty()) { - break; + r = Completed; + return; } r = waitForProcesses(processes, ls); @@ -886,11 +893,14 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( } // this process is completed, check for others - } - - r = Completed; + r = Running; }); + if (r != Running) { + break; + } + } + return r; } diff --git a/src/spawn.cpp b/src/spawn.cpp index f95846c8..33bdbc05 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -498,14 +498,16 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) const auto wcommandLine = commandLine.toStdWString(); const auto wcwd = cwd.toStdWString(); + const DWORD flags = CREATE_BREAKAWAY_FROM_JOB; + if (sp.hooked) { success = ::CreateProcessHooked( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); + inheritHandles, flags, nullptr, wcwd.c_str(), &si, &pi); } else { success = ::CreateProcess( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); + inheritHandles, flags, nullptr, wcwd.c_str(), &si, &pi); } const auto e = GetLastError(); diff --git a/src/uilocker.cpp b/src/uilocker.cpp index af9b9b96..07fe7f1b 100644 --- a/src/uilocker.cpp +++ b/src/uilocker.cpp @@ -461,6 +461,11 @@ std::shared_ptr UILocker::lock(Reasons reason) return ls; } +bool UILocker::locked() const +{ + return !m_sessions.empty(); +} + void UILocker::unlock(Session* s) { auto itor = m_sessions.begin(); diff --git a/src/uilocker.h b/src/uilocker.h index d8c22999..cc467184 100644 --- a/src/uilocker.h +++ b/src/uilocker.h @@ -70,6 +70,7 @@ public: void setUserInterface(QWidget* parent); std::shared_ptr lock(Reasons reason); + bool locked() const; Results result() const; -- cgit v1.3.1