#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