#include "envshortcut.h" #include "env.h" #include "executableslist.h" #include "filesystemutilities.h" #include "instancemanager.h" #include #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(formatSystemMessage(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() { const auto& i = *InstanceManager::singleton().currentInstance(); m_name = MOBase::sanitizeFileName(exe.title()); m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); m_arguments = QString("\"moshortcut://%1:%2\"") .arg(i.isPortable() ? "" : i.displayName()) .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 = MOBase::sanitizeFileName(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) { log::debug("adding shortcut to {}:\n" " . name: '{}'\n" " . target: '{}'\n" " . arguments: '{}'\n" " . description: '{}'\n" " . icon: '{}' @ {}\n" " . working directory: '{}'", toString(loc), m_name, m_target, m_arguments, m_description, m_icon, m_iconIndex, m_workingDirectory); if (m_target.isEmpty()) { log::error("shortcut: target is empty"); return false; } const auto path = shortcutPath(loc); if (path.isEmpty()) { return false; } log::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) { log::error("{}\nshortcut file was not saved", e.what()); } return false; } bool Shortcut::remove(Locations loc) { log::debug("removing shortcut for '{}' from {}", m_name, toString(loc)); const auto path = shortcutPath(loc); if (path.isEmpty()) { return false; } log::debug("path to shortcut file is '{}'", path); if (!QFile::exists(path)) { log::error("can't remove shortcut '{}', file not found", path); return false; } if (!MOBase::shellDelete({path})) { const auto e = ::GetLastError(); log::error("failed to remove shortcut '{}', {}", path, formatSystemMessage(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: log::error("shortcut: bad location {}", loc); break; } } catch (std::exception&) { } return QDir::toNativeSeparators(dir); } QString Shortcut::shortcutFilename() const { if (m_name.isEmpty()) { log::error("shortcut name is empty"); return {}; } return m_name + ".lnk"; } 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 env