From 7114b9669aa21bb18d924c62a8376092ffc89366 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Fri, 13 Oct 2023 13:44:19 -0600 Subject: [PATCH] yuzu: refactor shortcut code --- src/yuzu/main.cpp | 206 +++++++++-------------------------------- src/yuzu/main.h | 4 - src/yuzu/util/util.cpp | 101 ++++++++++++++++++-- src/yuzu/util/util.h | 22 ++++- 4 files changed, 159 insertions(+), 174 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 1a6b638566..93a40d9dd8 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -77,7 +77,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include #include #include -#include #include #include #include @@ -100,7 +99,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "common/scm_rev.h" #include "common/scope_exit.h" #ifdef _WIN32 -#include #include "common/windows/timer_resolution.h" #endif #ifdef ARCHITECTURE_x86_64 @@ -2843,21 +2841,17 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, GameListShortcutTarget target) { - // Get path to yuzu executable - const QStringList args = QApplication::arguments(); - std::filesystem::path yuzu_command = args[0].toStdString(); - // If relative path, make it an absolute path - if (yuzu_command.c_str()[0] == '.') { - yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; - } + const auto yuzu_command = QApplication::applicationFilePath(); + const auto target_directory = GetTargetPath(target); + const std::string game_file_name = std::filesystem::path(game_path).filename().string(); + const std::filesystem::path icon_folder = + Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir); -#if defined(__linux__) // Warn once if we are making a shortcut to a volatile AppImage - const std::string appimage_ending = - std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage"); - if (yuzu_command.string().ends_with(appimage_ending) && - !UISettings::values.shortcut_already_warned) { + const QString appimage_ending = + QString::fromStdString(std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage")); + if (yuzu_command.endsWith(appimage_ending) && !UISettings::values.shortcut_already_warned) { if (QMessageBox::warning(this, tr("Create Shortcut"), tr("This will create a shortcut to the current AppImage. This may " "not work well if you update. Continue?"), @@ -2868,77 +2862,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga } UISettings::values.shortcut_already_warned = true; } -#endif // __linux__ - std::filesystem::path target_directory{}; - - switch (target) { - case GameListShortcutTarget::Desktop: { - const QString desktop_path = - QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); - target_directory = desktop_path.toUtf8().toStdString(); - break; - } - case GameListShortcutTarget::Applications: { - const QString applications_path = - QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); - if (applications_path.isEmpty()) { - const char* home = std::getenv("HOME"); - if (home != nullptr) { - target_directory = std::filesystem::path(home) / ".local/share/applications"; - } - } else { - target_directory = applications_path.toUtf8().toStdString(); - } - break; - } - default: - return; - } - - const QDir dir(QString::fromStdString(target_directory.generic_string())); - if (!dir.exists()) { - QMessageBox::critical(this, tr("Create Shortcut"), - tr("Cannot create shortcut. Path \"%1\" does not exist.") - .arg(QString::fromStdString(target_directory.generic_string())), - QMessageBox::StandardButton::Ok); - return; - } - - const std::string game_file_name = std::filesystem::path(game_path).filename().string(); - // Determine full paths for icon and shortcut -#if defined(__linux__) || defined(__FreeBSD__) - const char* home = std::getenv("HOME"); - const std::filesystem::path home_path = (home == nullptr ? "~" : home); - const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); - - std::filesystem::path system_icons_path = - (xdg_data_home == nullptr ? home_path / ".local/share/" - : std::filesystem::path(xdg_data_home)) / - "icons/hicolor/256x256"; - if (!Common::FS::CreateDirs(system_icons_path)) { + // Ensure directory exist + if (!QDir(target_directory).exists()) { QMessageBox::critical( - this, tr("Create Icon"), - tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") - .arg(QString::fromStdString(system_icons_path)), + this, tr("Create Shortcut"), + tr("Cannot create shortcut. Path \"%1\" does not exist.").arg(target_directory), QMessageBox::StandardButton::Ok); return; } - std::filesystem::path icon_path = - system_icons_path / (program_id == 0 ? fmt::format("yuzu-{}.png", game_file_name) - : fmt::format("yuzu-{:016X}.png", program_id)); - const std::filesystem::path shortcut_path = - target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) - : fmt::format("yuzu-{:016X}.desktop", program_id)); -#elif defined(WIN32) - std::filesystem::path icons_path = - Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir); - std::filesystem::path icon_path = - icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name) - : fmt::format("yuzu-{:016X}.ico", program_id))); -#else - std::string icon_extension; -#endif // Get title from game file const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), @@ -2954,6 +2886,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga loader->ReadTitle(title); } + // Remove characters that are illegal in Windows filenames + const std::string illegal_chars = "<>:\"/\\|?*"; + for (char c : illegal_chars) { + std::string temp_tile = ""; + std::remove_copy(title.begin(), title.end(), std::back_inserter(temp_tile), c); + title = temp_tile; + } + // Get icon from game file std::vector icon_image_file{}; if (control.second != nullptr) { @@ -2962,30 +2902,23 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); } - QImage icon_data = + std::string icon_name{}; +#ifdef _WIN32 + icon_name = program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name) + : fmt::format("yuzu-{:016X}.ico", program_id); +#else + icon_name = program_id == 0 ? fmt::format("yuzu-{}.png", game_file_name) + : fmt::format("yuzu-{:016X}.png", program_id); +#endif + + const QImage icon_data = QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); -#if defined(__linux__) || defined(__FreeBSD__) - // Convert and write the icon as a PNG - if (!icon_data.save(QString::fromStdString(icon_path.string()))) { - LOG_ERROR(Frontend, "Could not write icon as PNG to file"); - } else { - LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); - } -#elif defined(WIN32) - if (!SaveIconToFile(icon_path.string(), icon_data)) { + const auto qt_icon_file_path = + QString::fromStdString(fmt::format("{}/{}", icon_folder.string(), icon_name)); + if (!SaveIconToFile(qt_icon_file_path, icon_data)) { LOG_ERROR(Frontend, "Could not write icon to file"); return; } -#endif // __linux__ - -#ifdef _WIN32 - // Replace characters that are illegal in Windows filenames by a dash - const std::string illegal_chars = "<>:\"/\\|?*"; - for (char c : illegal_chars) { - std::replace(title.begin(), title.end(), c, '_'); - } - const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str(); -#endif const std::string comment = tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); @@ -2993,8 +2926,17 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga const std::string categories = "Game;Emulator;Qt;"; const std::string keywords = "Switch;Nintendo;"; - if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), - yuzu_command.string(), arguments, categories, keywords)) { + std::filesystem::path shortcut_path = ""; +#ifdef _WIN32 + shortcut_path = fmt::format("{}/{}.lnk", target_directory.toUtf8().toStdString(), title); +#else + shortcut_path = + fmt::format("{}/yuzu-{}.desktop", target_directory.toUtf8().toStdString(), title); +#endif + + if (!CreateShortcut(shortcut_path.string(), title, comment, + qt_icon_file_path.toUtf8().toStdString(), + yuzu_command.toUtf8().toStdString(), arguments, categories, keywords)) { QMessageBox::critical(this, tr("Create Shortcut"), tr("Failed to create a shortcut at %1") .arg(QString::fromStdString(shortcut_path.string()))); @@ -3986,66 +3928,6 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file } } -bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title, - const std::string& comment, const std::string& icon_path, - const std::string& command, const std::string& arguments, - const std::string& categories, const std::string& keywords) { -#if defined(__linux__) || defined(__FreeBSD__) - // This desktop file template was writing referencing - // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html - std::string shortcut_contents{}; - shortcut_contents.append("[Desktop Entry]\n"); - shortcut_contents.append("Type=Application\n"); - shortcut_contents.append("Version=1.0\n"); - shortcut_contents.append(fmt::format("Name={:s}\n", title)); - shortcut_contents.append(fmt::format("Comment={:s}\n", comment)); - shortcut_contents.append(fmt::format("Icon={:s}\n", icon_path)); - shortcut_contents.append(fmt::format("TryExec={:s}\n", command)); - shortcut_contents.append(fmt::format("Exec={:s} {:s}\n", command, arguments)); - shortcut_contents.append(fmt::format("Categories={:s}\n", categories)); - shortcut_contents.append(fmt::format("Keywords={:s}\n", keywords)); - - std::ofstream shortcut_stream(shortcut_path); - if (!shortcut_stream.is_open()) { - LOG_WARNING(Common, "Failed to create file {:s}", shortcut_path); - return false; - } - shortcut_stream << shortcut_contents; - shortcut_stream.close(); - - return true; -#elif defined(WIN32) - IShellLinkW* shell_link; - auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, - (void**)&shell_link); - if (FAILED(hres)) { - return false; - } - shell_link->SetPath( - Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to - shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data()); - shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data()); - shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0); - - IPersistFile* persist_file; - hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); - if (FAILED(hres)) { - return false; - } - - hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE); - if (FAILED(hres)) { - return false; - } - - persist_file->Release(); - shell_link->Release(); - - return true; -#endif - return false; -} - void GMainWindow::OnLoadAmiibo() { if (emu_thread == nullptr || !emu_thread->IsRunning()) { return; diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 2346eb3bde..0082588882 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -426,10 +426,6 @@ private: void ConfigureFilesystemProvider(const std::string& filepath); QString GetTasStateDescription() const; - bool CreateShortcut(const std::string& shortcut_path, const std::string& title, - const std::string& comment, const std::string& icon_path, - const std::string& command, const std::string& arguments, - const std::string& categories, const std::string& keywords); std::unique_ptr ui; diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index f2854c8ec3..1500fd30ea 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -3,11 +3,19 @@ #include #include +#include +#include #include -#include "yuzu/util/util.h" -#ifdef _WIN32 -#include +#include + #include "common/fs/file.h" +#include "common/string_util.h" +#include "yuzu/game_list.h" +#include "yuzu/util/util.h" + +#ifdef _WIN32 +#include +#include #endif QFont GetMonospaceFont() { @@ -42,7 +50,27 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) { return circle_pixmap; } -bool SaveIconToFile(const std::string_view path, const QImage& image) { +QString GetTargetPath(GameListShortcutTarget target) { + if (target == GameListShortcutTarget::Desktop) { + return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + } + + if (target != GameListShortcutTarget::Applications) { + return {}; + } + + const QString applications_path = + QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); + + if (!applications_path.isEmpty()) { + return applications_path; + } + + // If Qt fails to find the application path try to use the generic location + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); +} + +bool SaveIconToFile(const QString file_path, const QImage& image) { #if defined(WIN32) #pragma pack(push, 2) struct IconDir { @@ -73,7 +101,8 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) { .id_count = static_cast(scale_sizes.size()), }; - Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write, + Common::FS::IOFile icon_file(file_path.toUtf8().toStdString(), + Common::FS::FileAccessMode::Write, Common::FS::FileType::BinaryFile); if (!icon_file.IsOpen()) { return false; @@ -136,6 +165,66 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) { return true; #else - return false; + return image.save(file_path); #endif } + +bool CreateShortcut(const std::string& shortcut_path, const std::string& title, + const std::string& comment, const std::string& icon_path, + const std::string& command, const std::string& arguments, + const std::string& categories, const std::string& keywords) { +#if defined(__linux__) || defined(__FreeBSD__) + // This desktop file template was writing referencing + // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html + std::string shortcut_contents{}; + shortcut_contents.append("[Desktop Entry]\n"); + shortcut_contents.append("Type=Application\n"); + shortcut_contents.append("Version=1.0\n"); + shortcut_contents.append(fmt::format("Name={:s}\n", title)); + shortcut_contents.append(fmt::format("Comment={:s}\n", comment)); + shortcut_contents.append(fmt::format("Icon={:s}\n", icon_path)); + shortcut_contents.append(fmt::format("TryExec={:s}\n", command)); + shortcut_contents.append(fmt::format("Exec={:s} {:s}\n", command, arguments)); + shortcut_contents.append(fmt::format("Categories={:s}\n", categories)); + shortcut_contents.append(fmt::format("Keywords={:s}\n", keywords)); + + std::ofstream shortcut_stream(shortcut_path); + if (!shortcut_stream.is_open()) { + LOG_WARNING(Common, "Failed to create file {:s}", shortcut_path); + return false; + } + shortcut_stream << shortcut_contents; + shortcut_stream.close(); + + return true; +#elif defined(WIN32) + IShellLinkW* shell_link; + auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, + (void**)&shell_link); + if (FAILED(hres)) { + return false; + } + shell_link->SetPath( + Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to + shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data()); + shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data()); + shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0); + + IPersistFile* persist_file; + hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); + if (FAILED(hres)) { + return false; + } + + hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE); + if (FAILED(hres)) { + return false; + } + + persist_file->Release(); + shell_link->Release(); + + return true; +#endif + return false; +} diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h index 09c14ce3fc..a034b58a44 100644 --- a/src/yuzu/util/util.h +++ b/src/yuzu/util/util.h @@ -4,8 +4,11 @@ #pragma once #include +#include #include +enum class GameListShortcutTarget; + /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. [[nodiscard]] QFont GetMonospaceFont(); @@ -19,10 +22,25 @@ */ [[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color); +/** + * Returns the path of the provided shortcut target + * @return QString containing the path to the target + */ +[[nodiscard]] QString GetTargetPath(GameListShortcutTarget target); + /** * Saves a windows icon to a file - * @param path The icons path + * @param file_path The icon file path * @param image The image to save * @return bool If the operation succeeded */ -[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image); +[[nodiscard]] bool SaveIconToFile(const QString file_path, const QImage& image); + +/** + * Creates and saves a shortcut to the shortcut_path + * @return bool If the operation succeeded + */ +bool CreateShortcut(const std::string& shortcut_path, const std::string& title, + const std::string& comment, const std::string& icon_path, + const std::string& command, const std::string& arguments, + const std::string& categories, const std::string& keywords);