summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.cpp18
-rw-r--r--src/mainwindow.cpp175
-rw-r--r--src/mainwindow.h12
-rw-r--r--src/organizercore.cpp17
-rw-r--r--src/organizercore.h1
-rw-r--r--src/selectiondialog.cpp8
-rw-r--r--src/selectiondialog.h1
-rw-r--r--src/shared/util.cpp373
-rw-r--r--src/shared/util.h109
-rw-r--r--src/tutorials/tutorial_firststeps_settings.js8
10 files changed, 554 insertions, 168 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 09e27cd8..ec60b605 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -303,6 +303,11 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett
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);
}
@@ -335,15 +340,26 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett
while (selection.exec() != QDialog::Rejected) {
IPluginGame * game = selection.getChoiceData().value<IPluginGame *>();
+ QString gamePath = selection.getChoiceDescription();
+ QFileInfo directoryInfo(gamePath);
+ 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 != nullptr) {
return selectGame(settings, game->gameDirectory(), game);
}
- QString gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName)
+ 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);
+ 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));
+ }
QList<IPluginGame *> possibleGames;
for (IPluginGame * const game : plugins.plugins<IPluginGame>()) {
//If a game is already configured, skip any plugins that are not for that game
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 6c6eeee7..8a101be7 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -219,6 +219,9 @@ MainWindow::MainWindow(QSettings &initSettings
, m_PluginContainer(pluginContainer)
, m_DidUpdateMasterList(false)
, m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this))
+ , m_LinkToolbar(nullptr)
+ , m_LinkDesktop(nullptr)
+ , m_LinkStartMenu(nullptr)
{
QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800);
@@ -334,9 +337,9 @@ MainWindow::MainWindow(QSettings &initSettings
resizeLists(modListAdjusted, pluginListAdjusted);
QMenu *linkMenu = new QMenu(this);
- linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar()));
- linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop()));
- linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu()));
+ m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar()));
+ m_LinkDesktop = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop()));
+ m_LinkStartMenu = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu()));
ui->linkButton->setMenu(linkMenu);
QMenu *listOptionsMenu = new QMenu(ui->listOptionsBtn);
@@ -2403,88 +2406,6 @@ void MainWindow::on_startButton_clicked() {
ui->startButton->setEnabled(true);
}
-static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments,
- LPCSTR linkFileName, LPCWSTR description,
- LPCTSTR iconFileName, int iconNumber,
- LPCWSTR currentDirectory)
-{
- HRESULT result = E_INVALIDARG;
- if ((targetFileName != nullptr) && (wcslen(targetFileName) > 0) &&
- (arguments != nullptr) &&
- (linkFileName != nullptr) && (strlen(linkFileName) > 0) &&
- (description != nullptr) &&
- (currentDirectory != nullptr)) {
-
- IShellLink* shellLink;
- result = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
- IID_IShellLink, (LPVOID*)&shellLink);
-
- if (!SUCCEEDED(result)) {
- qCritical("failed to create IShellLink instance");
- return result;
- }
-
- result = shellLink->SetPath(targetFileName);
- if (!SUCCEEDED(result)) {
- qCritical("failed to set target path %ls", targetFileName);
- shellLink->Release();
- return result;
- }
-
- result = shellLink->SetArguments(arguments);
- if (!SUCCEEDED(result)) {
- qCritical("failed to set arguments: %ls", arguments);
- shellLink->Release();
- return result;
- }
-
- if (wcslen(description) > 0) {
- result = shellLink->SetDescription(description);
- if (!SUCCEEDED(result)) {
- qCritical("failed to set description: %ls", description);
- shellLink->Release();
- return result;
- }
- }
-
- if (wcslen(currentDirectory) > 0) {
- result = shellLink->SetWorkingDirectory(currentDirectory);
- if (!SUCCEEDED(result)) {
- qCritical("failed to set working directory: %ls", currentDirectory);
- shellLink->Release();
- return result;
- }
- }
-
- if (iconFileName != nullptr) {
- result = shellLink->SetIconLocation(iconFileName, iconNumber);
- if (!SUCCEEDED(result)) {
- qCritical("failed to load program icon: %ls %d", iconFileName, iconNumber);
- shellLink->Release();
- return result;
- }
- }
-
- IPersistFile *persistFile;
- result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile);
- if (SUCCEEDED(result)) {
- wchar_t linkFileNameW[MAX_PATH];
- if (MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH) > 0) {
- result = persistFile->Save(linkFileNameW, TRUE);
- } else {
- qCritical("failed to create link: %s", linkFileName);
- }
- persistFile->Release();
- } else {
- qCritical("failed to create IPersistFile instance");
- }
-
- shellLink->Release();
- }
- return result;
-}
-
-
bool MainWindow::modifyExecutablesDialog()
{
bool result = false;
@@ -5150,74 +5071,39 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos)
void MainWindow::linkToolbar()
{
- Executable &exe(getSelectedExecutable());
+ Executable& exe = getSelectedExecutable();
+
exe.setShownOnToolbar(!exe.isShownOnToolbar());
- ui->linkButton->menu()->actions().at(static_cast<int>(ShortcutType::Toolbar))->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link"));
updatePinnedExecutables();
}
-namespace {
-QString getLinkfile(const QString &dir, const Executable &exec)
+void MainWindow::linkDesktop()
{
- return QDir::fromNativeSeparators(dir) + "/" + exec.title() + ".lnk";
+ env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::Desktop);
}
-QString getDesktopLinkfile(const Executable &exec)
+void MainWindow::linkMenu()
{
- return getLinkfile(getDesktopDirectory(), exec);
+ env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::StartMenu);
}
-QString getStartMenuLinkfile(const Executable &exec)
+void MainWindow::on_linkButton_pressed()
{
- return getLinkfile(getStartMenuDirectory(), exec);
-}
-}
+ const Executable& exe = getSelectedExecutable();
-void MainWindow::addWindowsLink(const ShortcutType mapping)
-{
- const Executable &selectedExecutable(getSelectedExecutable());
- QString const linkName = getLinkfile(mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(),
- selectedExecutable);
+ const QIcon addIcon(":/MO/gui/link");
+ const QIcon removeIcon(":/MO/gui/remove");
- if (QFile::exists(linkName)) {
- if (QFile::remove(linkName)) {
- ui->linkButton->menu()->actions().at(static_cast<int>(mapping))->setIcon(QIcon(":/MO/gui/link"));
- } else {
- reportError(tr("failed to remove %1").arg(linkName));
- }
- } else {
- QFileInfo const exeInfo(qApp->applicationFilePath());
- // create link
- QString executable = QDir::toNativeSeparators(selectedExecutable.binaryInfo().absoluteFilePath());
+ env::Shortcut shortcut(exe);
- std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
- std::wstring parameter = ToWString(
- QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.title()));
- std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.title()));
- std::wstring iconFile = ToWString(executable);
- std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath()));
+ m_LinkToolbar->setIcon(
+ exe.isShownOnToolbar() ? removeIcon : addIcon);
- if (CreateShortcut(targetFile.c_str()
- , parameter.c_str()
- , QDir::toNativeSeparators(linkName).toUtf8().constData()
- , description.c_str()
- , (selectedExecutable.usesOwnIcon() ? iconFile.c_str() : nullptr), 0
- , currentDirectory.c_str()) == 0) {
- ui->linkButton->menu()->actions().at(static_cast<int>(mapping))->setIcon(QIcon(":/MO/gui/remove"));
- } else {
- reportError(tr("failed to create %1").arg(linkName));
- }
- }
-}
+ m_LinkDesktop->setIcon(
+ shortcut.exists(env::Shortcut::Desktop) ? removeIcon : addIcon);
-void MainWindow::linkDesktop()
-{
- addWindowsLink(ShortcutType::Desktop);
-}
-
-void MainWindow::linkMenu()
-{
- addWindowsLink(ShortcutType::StartMenu);
+ m_LinkStartMenu->setIcon(
+ shortcut.exists(env::Shortcut::StartMenu) ? removeIcon : addIcon);
}
void MainWindow::on_actionSettings_triggered()
@@ -6482,21 +6368,6 @@ Executable &MainWindow::getSelectedExecutable()
return m_OrganizerCore.executablesList()->get(name);
}
-void MainWindow::on_linkButton_pressed()
-{
- const Executable &selectedExecutable(getSelectedExecutable());
-
- const QIcon addIcon(":/MO/gui/link");
- const QIcon removeIcon(":/MO/gui/remove");
-
- const QFileInfo linkDesktopFile(getDesktopLinkfile(selectedExecutable));
- const QFileInfo linkMenuFile(getStartMenuLinkfile(selectedExecutable));
-
- ui->linkButton->menu()->actions().at(static_cast<int>(ShortcutType::Toolbar))->setIcon(selectedExecutable.isShownOnToolbar() ? removeIcon : addIcon);
- ui->linkButton->menu()->actions().at(static_cast<int>(ShortcutType::Desktop))->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon);
- ui->linkButton->menu()->actions().at(static_cast<int>(ShortcutType::StartMenu))->setIcon(linkMenuFile.exists() ? removeIcon : addIcon);
-}
-
void MainWindow::on_showHiddenBox_toggled(bool checked)
{
m_OrganizerCore.downloadManager()->setShowHidden(checked);
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 00f15a2b..eee269cf 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -403,18 +403,14 @@ private:
MOBase::DelayedFileWriter m_ArchiveListWriter;
+ QAction* m_LinkToolbar;
+ QAction* m_LinkDesktop;
+ QAction* m_LinkStartMenu;
+
// icon set by the stylesheet, used to remember its original appearance
// when painting the count
QIcon m_originalNotificationIcon;
- enum class ShortcutType {
- Toolbar,
- Desktop,
- StartMenu
- };
-
- void addWindowsLink(ShortcutType const);
-
Executable const &getSelectedExecutable() const;
Executable &getSelectedExecutable();
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 99ddda1d..81ec7f43 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -691,12 +691,27 @@ 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());
+ 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."));
+ return false;
+ }
+ return true;
+}
+
bool OrganizerCore::bootstrap() {
return createDirectory(m_Settings.getProfileDirectory()) &&
createDirectory(m_Settings.getModDirectory()) &&
createDirectory(m_Settings.getDownloadDirectory()) &&
createDirectory(m_Settings.getOverwriteDirectory()) &&
- createDirectory(QString::fromStdWString(crashDumpsPath())) && cycleDiagnostics();
+ createDirectory(QString::fromStdWString(crashDumpsPath())) &&
+ checkPathSymlinks() && cycleDiagnostics();
}
void OrganizerCore::createDefaultProfile()
diff --git a/src/organizercore.h b/src/organizercore.h
index 4dd11831..99b1c5f2 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -183,6 +183,7 @@ public:
void loginFailedUpdate(const QString &message);
static bool createAndMakeWritable(const QString &path);
+ bool checkPathSymlinks();
bool bootstrap();
void createDefaultProfile();
diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp
index 55728751..089760f9 100644
--- a/src/selectiondialog.cpp
+++ b/src/selectiondialog.cpp
@@ -79,6 +79,14 @@ QString SelectionDialog::getChoiceString()
}
}
+QString SelectionDialog::getChoiceDescription()
+{
+ if (m_Choice == nullptr)
+ return QString();
+ else
+ return m_Choice->accessibleDescription();
+}
+
void SelectionDialog::disableCancel()
{
ui->cancelButton->setEnabled(false);
diff --git a/src/selectiondialog.h b/src/selectiondialog.h
index 3c4b25df..5a70aa5e 100644
--- a/src/selectiondialog.h
+++ b/src/selectiondialog.h
@@ -52,6 +52,7 @@ public:
QVariant getChoiceData();
QString getChoiceString();
+ QString getChoiceDescription();
void disableCancel();
diff --git a/src/shared/util.cpp b/src/shared/util.cpp
index 072cee2d..17df3b92 100644
--- a/src/shared/util.cpp
+++ b/src/shared/util.cpp
@@ -20,6 +20,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "util.h"
#include "windows_error.h"
#include "error_report.h"
+#include "executableslist.h"
+#include "instancemanager.h"
#include <utility.h>
#include <sstream>
@@ -307,10 +309,377 @@ struct COMReleaser
}
};
+
template <class T>
using COMPtr = std::unique_ptr<T, COMReleaser>;
+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<IShellLink> m_link;
+ COMPtr<IPersistFile> m_file;
+
+ void throwOnFail(HRESULT r, const QString& s)
+ {
+ if (FAILED(r)) {
+ throw ShellLinkException(QString("%1, %2")
+ .arg(s)
+ .arg(formatSystemMessageQ(r)));
+ }
+ }
+
+ COMPtr<IShellLink> 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<IShellLink>(static_cast<IShellLink*>(link));
+ }
+
+ COMPtr<IPersistFile> 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<IPersistFile>(static_cast<IPersistFile*>(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<int>(loc));
+ }
+}
+
+
+
class WMI
{
public:
@@ -674,7 +1043,7 @@ std::optional<SecurityProduct> Environment::getWindowsFirewall() const
if (FAILED(hr) || !rawPolicy) {
qCritical()
<< "CoCreateInstance for NetFwPolicy2 failed, "
- << formatSystemMessage(hr);
+ << formatSystemMessageQ(hr);
return {};
}
@@ -690,7 +1059,7 @@ std::optional<SecurityProduct> Environment::getWindowsFirewall() const
{
qCritical()
<< "get_FirewallEnabled failed, "
- << formatSystemMessage(hr);
+ << formatSystemMessageQ(hr);
return {};
}
diff --git a/src/shared/util.h b/src/shared/util.h
index 4df1d13c..c4a2ed7d 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -29,6 +29,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <versioninfo.h>
+class Executable;
+
namespace MOShared {
/// Test if a file (or directory) by the specified name exists
@@ -52,6 +54,113 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs);
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);
+
+
// represents one module
//
class Module
diff --git a/src/tutorials/tutorial_firststeps_settings.js b/src/tutorials/tutorial_firststeps_settings.js
index 792712be..b0e0c3c3 100644
--- a/src/tutorials/tutorial_firststeps_settings.js
+++ b/src/tutorials/tutorial_firststeps_settings.js
@@ -16,10 +16,10 @@ function getTutorialSteps()
function() {
highlightItem("nexusBox", false)
- tutorial.text = qsTr("Use this interface to obtain an API key from NexusMods."
- +"This is used for all API connections - downloads, updates"
- +"etc. MO2 uses the Windows Credential Manager to store"
- +"this data securely. If the SSO page on Nexus is failing,"
+ tutorial.text = qsTr("Use this interface to obtain an API key from NexusMods. "
+ +"This is used for all API connections - downloads, updates "
+ +"etc. MO2 uses the Windows Credential Manager to store "
+ +"this data securely. If the SSO page on Nexus is failing, "
+"use the manual entry and copy the API key from your profile.")
waitForClick()
}