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.
This commit is contained in:
DanielSvoboda 2023-09-13 00:20:19 -03:00
parent ce5320c49f
commit 8d0a4b374e
3 changed files with 151 additions and 35 deletions

View file

@ -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);
});

View file

@ -8,6 +8,9 @@
#include <iostream>
#include <memory>
#include <thread>
#include <cstdlib>
#include <filesystem>
#include <string>
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
@ -167,6 +170,10 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#ifdef _WIN32
#include <windows.h>
#include <objbase.h>
#include <ShObjIdl_core.h>
#include <ShlGuid.h>
#include <shlobj.h>
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<char> 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<u8> 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<int>(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() {

View file

@ -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);