summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/yuzu/game_list.cpp6
-rw-r--r--src/yuzu/main.cpp425
-rw-r--r--src/yuzu/main.h21
-rw-r--r--src/yuzu/util/util.cpp12
-rw-r--r--src/yuzu/util/util.h3
5 files changed, 259 insertions, 208 deletions
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 7e7d8e252..f294dc23d 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -567,9 +567,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
567 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); 567 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
568 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 568 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
569 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 569 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
570// TODO: Implement shortcut creation for macOS
571#if !defined(__APPLE__)
570 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); 572 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
571 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); 573 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
572#ifndef WIN32
573 QAction* create_applications_menu_shortcut = 574 QAction* create_applications_menu_shortcut =
574 shortcut_menu->addAction(tr("Add to Applications Menu")); 575 shortcut_menu->addAction(tr("Add to Applications Menu"));
575#endif 576#endif
@@ -647,10 +648,11 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
647 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 648 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
648 emit NavigateToGamedbEntryRequested(program_id, compatibility_list); 649 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
649 }); 650 });
651// TODO: Implement shortcut creation for macOS
652#if !defined(__APPLE__)
650 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { 653 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
651 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); 654 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
652 }); 655 });
653#ifndef WIN32
654 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { 656 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
655 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); 657 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
656 }); 658 });
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f077d7f9c..f6b548fd3 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -10,6 +10,7 @@
10#include <thread> 10#include <thread>
11#include "core/loader/nca.h" 11#include "core/loader/nca.h"
12#include "core/tools/renderdoc.h" 12#include "core/tools/renderdoc.h"
13
13#ifdef __APPLE__ 14#ifdef __APPLE__
14#include <unistd.h> // for chdir 15#include <unistd.h> // for chdir
15#endif 16#endif
@@ -2847,170 +2848,259 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
2847 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); 2848 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
2848} 2849}
2849 2850
2850void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, 2851bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
2851 GameListShortcutTarget target) { 2852 const std::string& comment,
2852 // Get path to yuzu executable 2853 const std::filesystem::path& icon_path,
2853 const QStringList args = QApplication::arguments(); 2854 const std::filesystem::path& command,
2854 std::filesystem::path yuzu_command = args[0].toStdString(); 2855 const std::string& arguments, const std::string& categories,
2855 2856 const std::string& keywords, const std::string& name) try {
2856 // If relative path, make it an absolute path 2857#if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD
2857 if (yuzu_command.c_str()[0] == '.') { 2858 std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
2858 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; 2859 std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
2860 if (!shortcut_stream.is_open()) {
2861 LOG_ERROR(Frontend, "Failed to create shortcut");
2862 return false;
2859 } 2863 }
2860 2864 // TODO: Migrate fmt::print to std::print in futures STD C++ 23.
2861#if defined(__linux__) 2865 fmt::print(shortcut_stream, "[Desktop Entry]\n");
2862 // Warn once if we are making a shortcut to a volatile AppImage 2866 fmt::print(shortcut_stream, "Type=Application\n");
2863 const std::string appimage_ending = 2867 fmt::print(shortcut_stream, "Version=1.0\n");
2864 std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage"); 2868 fmt::print(shortcut_stream, "Name={}\n", name);
2865 if (yuzu_command.string().ends_with(appimage_ending) && 2869 if (!comment.empty()) {
2866 !UISettings::values.shortcut_already_warned) { 2870 fmt::print(shortcut_stream, "Comment={}\n", comment);
2867 if (QMessageBox::warning(this, tr("Create Shortcut"), 2871 }
2868 tr("This will create a shortcut to the current AppImage. This may " 2872 if (std::filesystem::is_regular_file(icon_path)) {
2869 "not work well if you update. Continue?"), 2873 fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
2870 QMessageBox::StandardButton::Ok | 2874 }
2871 QMessageBox::StandardButton::Cancel) == 2875 fmt::print(shortcut_stream, "TryExec={}\n", command.string());
2872 QMessageBox::StandardButton::Cancel) { 2876 fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
2873 return; 2877 if (!categories.empty()) {
2878 fmt::print(shortcut_stream, "Categories={}\n", categories);
2879 }
2880 if (!keywords.empty()) {
2881 fmt::print(shortcut_stream, "Keywords={}\n", keywords);
2882 }
2883 return true;
2884#elif defined(_WIN32) // Windows
2885 HRESULT hr = CoInitialize(nullptr);
2886 if (FAILED(hr)) {
2887 LOG_ERROR(Frontend, "CoInitialize failed");
2888 return false;
2889 }
2890 SCOPE_EXIT({ CoUninitialize(); });
2891 IShellLinkW* ps1 = nullptr;
2892 IPersistFile* persist_file = nullptr;
2893 SCOPE_EXIT({
2894 if (persist_file != nullptr) {
2895 persist_file->Release();
2874 } 2896 }
2875 UISettings::values.shortcut_already_warned = true; 2897 if (ps1 != nullptr) {
2898 ps1->Release();
2899 }
2900 });
2901 HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
2902 reinterpret_cast<void**>(&ps1));
2903 if (FAILED(hres)) {
2904 LOG_ERROR(Frontend, "Failed to create IShellLinkW instance");
2905 return false;
2876 } 2906 }
2877#endif // __linux__ 2907 hres = ps1->SetPath(command.c_str());
2878 2908 if (FAILED(hres)) {
2879 std::filesystem::path target_directory{}; 2909 LOG_ERROR(Frontend, "Failed to set path");
2880 2910 return false;
2881 switch (target) {
2882 case GameListShortcutTarget::Desktop: {
2883 const QString desktop_path =
2884 QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
2885 target_directory = desktop_path.toUtf8().toStdString();
2886 break;
2887 } 2911 }
2888 case GameListShortcutTarget::Applications: { 2912 if (!arguments.empty()) {
2889 const QString applications_path = 2913 hres = ps1->SetArguments(Common::UTF8ToUTF16W(arguments).data());
2890 QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); 2914 if (FAILED(hres)) {
2891 if (applications_path.isEmpty()) { 2915 LOG_ERROR(Frontend, "Failed to set arguments");
2892 const char* home = std::getenv("HOME"); 2916 return false;
2893 if (home != nullptr) {
2894 target_directory = std::filesystem::path(home) / ".local/share/applications";
2895 }
2896 } else {
2897 target_directory = applications_path.toUtf8().toStdString();
2898 } 2917 }
2899 break;
2900 } 2918 }
2901 default: 2919 if (!comment.empty()) {
2902 return; 2920 hres = ps1->SetDescription(Common::UTF8ToUTF16W(comment).data());
2921 if (FAILED(hres)) {
2922 LOG_ERROR(Frontend, "Failed to set description");
2923 return false;
2924 }
2903 } 2925 }
2904 2926 if (std::filesystem::is_regular_file(icon_path)) {
2905 const QDir dir(QString::fromStdString(target_directory.generic_string())); 2927 hres = ps1->SetIconLocation(icon_path.c_str(), 0);
2906 if (!dir.exists()) { 2928 if (FAILED(hres)) {
2907 QMessageBox::critical(this, tr("Create Shortcut"), 2929 LOG_ERROR(Frontend, "Failed to set icon location");
2908 tr("Cannot create shortcut. Path \"%1\" does not exist.") 2930 return false;
2909 .arg(QString::fromStdString(target_directory.generic_string())), 2931 }
2910 QMessageBox::StandardButton::Ok);
2911 return;
2912 } 2932 }
2933 hres = ps1->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&persist_file));
2934 if (FAILED(hres)) {
2935 LOG_ERROR(Frontend, "Failed to get IPersistFile interface");
2936 return false;
2937 }
2938 hres = persist_file->Save(std::filesystem::path{shortcut_path / (name + ".lnk")}.c_str(), TRUE);
2939 if (FAILED(hres)) {
2940 LOG_ERROR(Frontend, "Failed to save shortcut");
2941 return false;
2942 }
2943 return true;
2944#else // Unsupported platform
2945 return false;
2946#endif
2947} catch (const std::exception& e) {
2948 LOG_ERROR(Frontend, "Failed to create shortcut: {}", e.what());
2949 return false;
2950}
2951// Messages in pre-defined message boxes for less code spaghetti
2952bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title) {
2953 int result = 0;
2954 QMessageBox::StandardButtons buttons;
2955 switch (imsg) {
2956 case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES:
2957 buttons = QMessageBox::Yes | QMessageBox::No;
2958 result =
2959 QMessageBox::information(parent, tr("Create Shortcut"),
2960 tr("Do you want to launch the game in fullscreen?"), buttons);
2961 return result == QMessageBox::Yes;
2962 case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS:
2963 QMessageBox::information(parent, tr("Create Shortcut"),
2964 tr("Successfully created a shortcut to %1").arg(game_title));
2965 return false;
2966 case GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING:
2967 buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel;
2968 result =
2969 QMessageBox::warning(this, tr("Create Shortcut"),
2970 tr("This will create a shortcut to the current AppImage. This may "
2971 "not work well if you update. Continue?"),
2972 buttons);
2973 return result == QMessageBox::Ok;
2974 default:
2975 buttons = QMessageBox::Ok;
2976 QMessageBox::critical(parent, tr("Create Shortcut"),
2977 tr("Failed to create a shortcut to %1").arg(game_title), buttons);
2978 return false;
2979 }
2980}
2913 2981
2914 const std::string game_file_name = std::filesystem::path(game_path).filename().string(); 2982bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
2915 // Determine full paths for icon and shortcut 2983 std::filesystem::path& out_icon_path) {
2916#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) 2984 // Get path to Yuzu icons directory & icon extension
2917 const char* home = std::getenv("HOME"); 2985 std::string ico_extension = "png";
2918 const std::filesystem::path home_path = (home == nullptr ? "~" : home); 2986#if defined(_WIN32)
2919 const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); 2987 out_icon_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::IconsDir);
2920 2988 ico_extension = "ico";
2921 std::filesystem::path system_icons_path = 2989#elif defined(__linux__) || defined(__FreeBSD__)
2922 (xdg_data_home == nullptr ? home_path / ".local/share/" 2990 out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256";
2923 : std::filesystem::path(xdg_data_home)) / 2991#endif
2924 "icons/hicolor/256x256"; 2992 // Create icons directory if it doesn't exist
2925 if (!Common::FS::CreateDirs(system_icons_path)) { 2993 if (!Common::FS::CreateDirs(out_icon_path)) {
2926 QMessageBox::critical( 2994 QMessageBox::critical(
2927 this, tr("Create Icon"), 2995 this, tr("Create Icon"),
2928 tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") 2996 tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
2929 .arg(QString::fromStdString(system_icons_path)), 2997 .arg(QString::fromStdString(out_icon_path.string())),
2930 QMessageBox::StandardButton::Ok); 2998 QMessageBox::StandardButton::Ok);
2931 return; 2999 out_icon_path.clear();
3000 return false;
2932 } 3001 }
2933 std::filesystem::path icon_path =
2934 system_icons_path / (program_id == 0 ? fmt::format("yuzu-{}.png", game_file_name)
2935 : fmt::format("yuzu-{:016X}.png", program_id));
2936 const std::filesystem::path shortcut_path =
2937 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
2938 : fmt::format("yuzu-{:016X}.desktop", program_id));
2939#elif defined(WIN32)
2940 std::filesystem::path icons_path =
2941 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
2942 std::filesystem::path icon_path =
2943 icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
2944 : fmt::format("yuzu-{:016X}.ico", program_id)));
2945#else
2946 std::string icon_extension;
2947#endif
2948
2949 // Get title from game file
2950 const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
2951 system->GetContentProvider()};
2952 const auto control = pm.GetControlMetadata();
2953 const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
2954 3002
2955 std::string title{fmt::format("{:016X}", program_id)}; 3003 // Create icon file path
2956 3004 out_icon_path /= (program_id == 0 ? fmt::format("yuzu-{}.{}", game_file_name, ico_extension)
2957 if (control.first != nullptr) { 3005 : fmt::format("yuzu-{:016X}.{}", program_id, ico_extension));
2958 title = control.first->GetApplicationName(); 3006 return true;
2959 } else { 3007}
2960 loader->ReadTitle(title);
2961 }
2962 3008
2963 // Get icon from game file 3009void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
2964 std::vector<u8> icon_image_file{}; 3010 GameListShortcutTarget target) {
2965 if (control.second != nullptr) { 3011 std::string game_title;
2966 icon_image_file = control.second->ReadAllBytes(); 3012 QString qt_game_title;
2967 } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { 3013 std::filesystem::path out_icon_path;
2968 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); 3014 // Get path to yuzu executable
3015 const QStringList args = QApplication::arguments();
3016 std::filesystem::path yuzu_command = args[0].toStdString();
3017 // If relative path, make it an absolute path
3018 if (yuzu_command.c_str()[0] == '.') {
3019 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
2969 } 3020 }
2970 3021 // Shortcut path
2971 QImage icon_data = 3022 std::filesystem::path shortcut_path{};
2972 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); 3023 if (target == GameListShortcutTarget::Desktop) {
2973#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) 3024 shortcut_path =
2974 // Convert and write the icon as a PNG 3025 QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
2975 if (!icon_data.save(QString::fromStdString(icon_path.string()))) { 3026 } else if (target == GameListShortcutTarget::Applications) {
2976 LOG_ERROR(Frontend, "Could not write icon as PNG to file"); 3027 shortcut_path =
3028 QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString();
3029 }
3030 // Icon path and title
3031 if (std::filesystem::exists(shortcut_path)) {
3032 // Get title from game file
3033 const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
3034 system->GetContentProvider()};
3035 const auto control = pm.GetControlMetadata();
3036 const auto loader =
3037 Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
3038 game_title = fmt::format("{:016X}", program_id);
3039 if (control.first != nullptr) {
3040 game_title = control.first->GetApplicationName();
3041 } else {
3042 loader->ReadTitle(game_title);
3043 }
3044 // Delete illegal characters from title
3045 const std::string illegal_chars = "<>:\"/\\|?*.";
3046 for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) {
3047 if (illegal_chars.find(*it) != std::string::npos) {
3048 game_title.erase(it.base() - 1);
3049 }
3050 }
3051 qt_game_title = QString::fromStdString(game_title);
3052 // Get icon from game file
3053 std::vector<u8> icon_image_file{};
3054 if (control.second != nullptr) {
3055 icon_image_file = control.second->ReadAllBytes();
3056 } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
3057 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
3058 }
3059 QImage icon_data =
3060 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
3061 if (GMainWindow::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
3062 if (!SaveIconToFile(out_icon_path, icon_data)) {
3063 LOG_ERROR(Frontend, "Could not write icon to file");
3064 }
3065 }
2977 } else { 3066 } else {
2978 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); 3067 GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
2979 } 3068 qt_game_title);
2980#elif defined(WIN32) 3069 LOG_ERROR(Frontend, "Invalid shortcut target");
2981 if (!SaveIconToFile(icon_path.string(), icon_data)) {
2982 LOG_ERROR(Frontend, "Could not write icon to file");
2983 return; 3070 return;
2984 } 3071 }
3072#if defined(__linux__)
3073 // Special case for AppImages
3074 // Warn once if we are making a shortcut to a volatile AppImage
3075 const std::string appimage_ending =
3076 std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
3077 if (yuzu_command.string().ends_with(appimage_ending) &&
3078 !UISettings::values.shortcut_already_warned) {
3079 if (GMainWindow::CreateShortcutMessagesGUI(
3080 this, GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, qt_game_title)) {
3081 return;
3082 }
3083 UISettings::values.shortcut_already_warned = true;
3084 }
2985#endif // __linux__ 3085#endif // __linux__
2986 3086 // Create shortcut
2987#ifdef _WIN32 3087 std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2988 // Replace characters that are illegal in Windows filenames by a dash 3088 if (GMainWindow::CreateShortcutMessagesGUI(
2989 const std::string illegal_chars = "<>:\"/\\|?*"; 3089 this, GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, qt_game_title)) {
2990 for (char c : illegal_chars) { 3090 arguments = "-f " + arguments;
2991 std::replace(title.begin(), title.end(), c, '_');
2992 } 3091 }
2993 const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str(); 3092 const std::string comment = fmt::format("Start {:s} with the yuzu Emulator", game_title);
2994#endif
2995
2996 const std::string comment =
2997 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
2998 const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2999 const std::string categories = "Game;Emulator;Qt;"; 3093 const std::string categories = "Game;Emulator;Qt;";
3000 const std::string keywords = "Switch;Nintendo;"; 3094 const std::string keywords = "Switch;Nintendo;";
3001 3095
3002 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), 3096 if (GMainWindow::CreateShortcutLink(shortcut_path, comment, out_icon_path, yuzu_command,
3003 yuzu_command.string(), arguments, categories, keywords)) { 3097 arguments, categories, keywords, game_title)) {
3004 QMessageBox::critical(this, tr("Create Shortcut"), 3098 GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS,
3005 tr("Failed to create a shortcut at %1") 3099 qt_game_title);
3006 .arg(QString::fromStdString(shortcut_path.string())));
3007 return; 3100 return;
3008 } 3101 }
3009 3102 GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
3010 LOG_INFO(Frontend, "Wrote a shortcut to {}", shortcut_path.string()); 3103 qt_game_title);
3011 QMessageBox::information(
3012 this, tr("Create Shortcut"),
3013 tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title)));
3014} 3104}
3015 3105
3016void GMainWindow::OnGameListOpenDirectory(const QString& directory) { 3106void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
@@ -4005,66 +4095,6 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
4005 } 4095 }
4006} 4096}
4007 4097
4008bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title,
4009 const std::string& comment, const std::string& icon_path,
4010 const std::string& command, const std::string& arguments,
4011 const std::string& categories, const std::string& keywords) {
4012#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
4013 // This desktop file template was writing referencing
4014 // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
4015 std::string shortcut_contents{};
4016 shortcut_contents.append("[Desktop Entry]\n");
4017 shortcut_contents.append("Type=Application\n");
4018 shortcut_contents.append("Version=1.0\n");
4019 shortcut_contents.append(fmt::format("Name={:s}\n", title));
4020 shortcut_contents.append(fmt::format("Comment={:s}\n", comment));
4021 shortcut_contents.append(fmt::format("Icon={:s}\n", icon_path));
4022 shortcut_contents.append(fmt::format("TryExec={:s}\n", command));
4023 shortcut_contents.append(fmt::format("Exec={:s} {:s}\n", command, arguments));
4024 shortcut_contents.append(fmt::format("Categories={:s}\n", categories));
4025 shortcut_contents.append(fmt::format("Keywords={:s}\n", keywords));
4026
4027 std::ofstream shortcut_stream(shortcut_path);
4028 if (!shortcut_stream.is_open()) {
4029 LOG_WARNING(Common, "Failed to create file {:s}", shortcut_path);
4030 return false;
4031 }
4032 shortcut_stream << shortcut_contents;
4033 shortcut_stream.close();
4034
4035 return true;
4036#elif defined(WIN32)
4037 IShellLinkW* shell_link;
4038 auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
4039 (void**)&shell_link);
4040 if (FAILED(hres)) {
4041 return false;
4042 }
4043 shell_link->SetPath(
4044 Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
4045 shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
4046 shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
4047 shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
4048
4049 IPersistFile* persist_file;
4050 hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
4051 if (FAILED(hres)) {
4052 return false;
4053 }
4054
4055 hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
4056 if (FAILED(hres)) {
4057 return false;
4058 }
4059
4060 persist_file->Release();
4061 shell_link->Release();
4062
4063 return true;
4064#endif
4065 return false;
4066}
4067
4068void GMainWindow::OnLoadAmiibo() { 4098void GMainWindow::OnLoadAmiibo() {
4069 if (emu_thread == nullptr || !emu_thread->IsRunning()) { 4099 if (emu_thread == nullptr || !emu_thread->IsRunning()) {
4070 return; 4100 return;
@@ -4103,7 +4133,6 @@ void GMainWindow::OnLoadAmiibo() {
4103bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text, 4133bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
4104 QMessageBox::StandardButtons buttons, 4134 QMessageBox::StandardButtons buttons,
4105 QMessageBox::StandardButton defaultButton) { 4135 QMessageBox::StandardButton defaultButton) {
4106
4107 QMessageBox* box_dialog = new QMessageBox(parent); 4136 QMessageBox* box_dialog = new QMessageBox(parent);
4108 box_dialog->setWindowTitle(title); 4137 box_dialog->setWindowTitle(title);
4109 box_dialog->setText(text); 4138 box_dialog->setText(text);
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index f9c6efe4f..f67c4cfda 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -6,6 +6,7 @@
6#include <memory> 6#include <memory>
7#include <optional> 7#include <optional>
8 8
9#include <filesystem>
9#include <QMainWindow> 10#include <QMainWindow>
10#include <QMessageBox> 11#include <QMessageBox>
11#include <QPushButton> 12#include <QPushButton>
@@ -174,6 +175,13 @@ class GMainWindow : public QMainWindow {
174 UI_EMU_STOPPING, 175 UI_EMU_STOPPING,
175 }; 176 };
176 177
178 enum {
179 CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES,
180 CREATE_SHORTCUT_MSGBOX_SUCCESS,
181 CREATE_SHORTCUT_MSGBOX_ERROR,
182 CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING,
183 };
184
177public: 185public:
178 void filterBarSetChecked(bool state); 186 void filterBarSetChecked(bool state);
179 void UpdateUITheme(); 187 void UpdateUITheme();
@@ -456,11 +464,14 @@ private:
456 bool ConfirmShutdownGame(); 464 bool ConfirmShutdownGame();
457 465
458 QString GetTasStateDescription() const; 466 QString GetTasStateDescription() const;
459 bool CreateShortcut(const std::string& shortcut_path, const std::string& title, 467 bool CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title);
460 const std::string& comment, const std::string& icon_path, 468 bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
461 const std::string& command, const std::string& arguments, 469 std::filesystem::path& out_icon_path);
462 const std::string& categories, const std::string& keywords); 470 bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment,
463 471 const std::filesystem::path& icon_path,
472 const std::filesystem::path& command, const std::string& arguments,
473 const std::string& categories, const std::string& keywords,
474 const std::string& name);
464 /** 475 /**
465 * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog 476 * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
466 * The only difference is that it returns a boolean. 477 * The only difference is that it returns a boolean.
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index f2854c8ec..7b2a47496 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -42,7 +42,7 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
42 return circle_pixmap; 42 return circle_pixmap;
43} 43}
44 44
45bool SaveIconToFile(const std::string_view path, const QImage& image) { 45bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
46#if defined(WIN32) 46#if defined(WIN32)
47#pragma pack(push, 2) 47#pragma pack(push, 2)
48 struct IconDir { 48 struct IconDir {
@@ -73,7 +73,7 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
73 .id_count = static_cast<WORD>(scale_sizes.size()), 73 .id_count = static_cast<WORD>(scale_sizes.size()),
74 }; 74 };
75 75
76 Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write, 76 Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
77 Common::FS::FileType::BinaryFile); 77 Common::FS::FileType::BinaryFile);
78 if (!icon_file.IsOpen()) { 78 if (!icon_file.IsOpen()) {
79 return false; 79 return false;
@@ -135,6 +135,14 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
135 icon_file.Close(); 135 icon_file.Close();
136 136
137 return true; 137 return true;
138#elif defined(__linux__) || defined(__FreeBSD__)
139 // Convert and write the icon as a PNG
140 if (!image.save(QString::fromStdString(icon_path.string()))) {
141 LOG_ERROR(Frontend, "Could not write icon as PNG to file");
142 } else {
143 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
144 }
145 return true;
138#else 146#else
139 return false; 147 return false;
140#endif 148#endif
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 09c14ce3f..4094cf6c2 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <filesystem>
6#include <QFont> 7#include <QFont>
7#include <QString> 8#include <QString>
8 9
@@ -25,4 +26,4 @@
25 * @param image The image to save 26 * @param image The image to save
26 * @return bool If the operation succeeded 27 * @return bool If the operation succeeded
27 */ 28 */
28[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image); 29[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);