From 8d0a4b374e064d8bfa1b33fc63cc06b272e8ba0c Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Wed, 13 Sep 2023 00:20:19 -0300 Subject: [PATCH] Create shortcuts on Windows Add the ability to create a shortcut in Windows. Use ffmpeg to convert PNG to ICO, however you need to include ffmpeg.exe in the yuzu directory, or find another solution to convert. --- src/yuzu/game_list.cpp | 15 ++-- src/yuzu/main.cpp | 169 ++++++++++++++++++++++++++++++++++------- src/yuzu/main.h | 2 +- 3 files changed, 151 insertions(+), 35 deletions(-) diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 6842ced3e3..26b6f2fd5c 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -538,6 +538,13 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri QAction* start_game = context_menu.addAction(tr("Start Game")); QAction* start_game_global = context_menu.addAction(tr("Start Game without Custom Configuration")); + QAction* create_desktop_shortcut = context_menu.addAction(tr("Create Shortcut")); +#ifndef WIN32 + QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); + QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); + QAction* create_applications_menu_shortcut = + shortcut_menu->addAction(tr("Add to Applications Menu")); +#endif context_menu.addSeparator(); QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); @@ -560,12 +567,6 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); -#ifndef WIN32 - QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); - QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); - QAction* create_applications_menu_shortcut = - shortcut_menu->addAction(tr("Add to Applications Menu")); -#endif context_menu.addSeparator(); QAction* properties = context_menu.addAction(tr("Properties")); @@ -636,10 +637,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); -#ifndef WIN32 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); }); +#ifndef WIN32 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); }); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 9cea60c326..f34c1ccbc6 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include #ifdef __APPLE__ #include // for chdir #endif @@ -167,6 +170,10 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #ifdef _WIN32 #include +#include +#include +#include +#include extern "C" { // tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable // graphics @@ -2780,6 +2787,39 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); } +std::filesystem::path convert_png_to_ico(const std::filesystem::path& png_path) { + + // ICO file path + std::filesystem::path ico_path = png_path; + ico_path.replace_extension(".ico"); + + // Convert the PNG image to an ICO icon, using ffmpeg + std::string command = "ffmpeg -y -i " + png_path.string() + " " + ico_path.string(); + const char* command_c_str = command.data(); + + WinExec(command_c_str, SW_HIDE); + + // Returns the path of the ICO file + return ico_path; +} + +std::string remove_special_characters(const std::string& str) { + // Create a list of special characters + std::set special_characters = {'\\', '/', ':', '?', '"', '<', '>', '|'}; + + // Create an empty string to store the new string + std::string new_str; + + // Scan the original string, removing special characters + for (char c : str) { + if (special_characters.find(c) == special_characters.end()) { + new_str += c; + } + } + + return new_str; +} + void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, GameListShortcutTarget target) { // Get path to yuzu executable @@ -2840,6 +2880,11 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga return; } } +#else + WCHAR path[MAX_PATH]; + SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, path); + target_directory = path; + #endif const std::string game_file_name = std::filesystem::path(game_path).filename().string(); @@ -2856,18 +2901,28 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga QMessageBox::StandardButton::Ok); return; } - std::filesystem::path icon_path = +#else + char* appdata; + size_t len; + _dupenv_s(&appdata, &len, "APPDATA"); + std::filesystem::path system_icons_path = appdata; + system_icons_path /= "yuzu\\IconsShortcut"; + + // Check if the directory exists and if not, create it + if (!std::filesystem::exists(system_icons_path)) { + std::filesystem::create_directories(system_icons_path); + } +#endif + std::filesystem::path png_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)); -#else - const std::filesystem::path icon_path{}; - const std::filesystem::path shortcut_path{}; -#endif - // Get title from game file + + //std::filesystem::path icon_path = convert_png_to_ico(png_path); + std::filesystem::path icon_path = png_path; + + + // Get title from game file const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), system->GetContentProvider()}; const auto control = pm.GetControlMetadata(); @@ -2881,6 +2936,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga loader->ReadTitle(title); } + const std::wstring shortcut_path = + target_directory / fmt::format("{}.lnk", remove_special_characters(title)); + +#if defined(__linux__) || defined(__FreeBSD__) + 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)); +#else +#endif // Get icon from game file std::vector icon_image_file{}; if (control.second != nullptr) { @@ -2891,36 +2955,33 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga QImage icon_jpeg = 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_jpeg.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()); } -#endif // __linux__ -#if defined(__linux__) || defined(__FreeBSD__) + icon_path = convert_png_to_ico(png_path); + const std::string comment = tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); - const std::string arguments = fmt::format("-g \"{:s}\"", game_path); + const std::string arguments = fmt::format("-f -g \"{:s}\"", game_path); const std::string categories = "Game;Emulator;Qt;"; const std::string keywords = "Switch;Nintendo;"; -#else - const std::string comment{}; - const std::string arguments{}; - const std::string categories{}; - const std::string keywords{}; -#endif - if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), - yuzu_command.string(), arguments, categories, keywords)) { - QMessageBox::critical(this, tr("Create Shortcut"), - tr("Failed to create a shortcut at %1") - .arg(QString::fromStdString(shortcut_path.string()))); + + + std::wstring shortcut_path_w = std::wstring(shortcut_path.begin(), shortcut_path.end()); + + if (!CreateShortcut(shortcut_path, title, comment, icon_path.string(), yuzu_command.string(), + arguments, categories, keywords)) { + QMessageBox::critical( + this, tr("Create Shortcut"), + tr("Failed to create a shortcut at %1").arg(QString::fromStdWString(shortcut_path_w))); return; } - LOG_INFO(Frontend, "Wrote a shortcut to {}", shortcut_path.string()); + LOG_INFO(Frontend, "Wrote a shortcut to {}", title); QMessageBox::information( this, tr("Create Shortcut"), tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title))); @@ -3900,7 +3961,7 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file } } -bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title, +bool GMainWindow::CreateShortcut(const std::wstring& 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) { @@ -3928,8 +3989,62 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st shortcut_stream.close(); return true; +#else + // std::string to std::wstring need to API Windows + std::wstring command_w = std::wstring(command.begin(), command.end()); + std::wstring arguments_w = std::wstring(arguments.begin(), arguments.end()); + std::wstring comment_w = std::wstring(comment.begin(), comment.end()); + + // converter png to ico + std::wstring icon_path_w = std::wstring(icon_path.begin(), icon_path.end()); + //std::filesystem::path icon_path_fs = icon_path; + //icon_path_fs.replace_extension(".ico"); + //std::wstring icon_path_w = icon_path_fs.wstring(); + + + // Initialize COM + CoInitialize(NULL); + + // Create the IShellLink interface + IShellLink* pShellLink; + HRESULT hres = + CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_ALL, IID_IShellLink, (void**)&pShellLink); + if (SUCCEEDED(hres)) { + // Set the path and parameters for the shortcut + pShellLink->SetPath(command_w.c_str()); + pShellLink->SetArguments(arguments_w.c_str()); + + // Set the comment for the shortcut + pShellLink->SetDescription(comment_w.c_str()); + + // Set the icon for the shortcut + pShellLink->SetIconLocation(icon_path_w.c_str(), 0); + + // Query IShellLink to the interface IPersistFile + IPersistFile* pPersistFile; + hres = pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile); + if (SUCCEEDED(hres)) { + // Set the path to the desktop shortcut + std::wstring shortcut_path_w = std::wstring(shortcut_path.begin(), shortcut_path.end()); + + // Save the shortcut to the desktop + pPersistFile->Save(shortcut_path_w.c_str(), TRUE); + + // Release the IPersistFile interface + pPersistFile->Release(); + } + else { + QMessageBox::information(this, tr("erro"), tr("erro")); + } + + // Release the IShellLink interface + pShellLink->Release(); + } + + // Uninitialize COM + CoUninitialize(); #endif - return false; + return true; } void GMainWindow::OnLoadAmiibo() { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1e4f6e477e..43d0aac34e 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -404,7 +404,7 @@ private: void ConfigureFilesystemProvider(const std::string& filepath); QString GetTasStateDescription() const; - bool CreateShortcut(const std::string& shortcut_path, const std::string& title, + bool CreateShortcut(const std::wstring& 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);