diff options
| author | 2023-10-15 02:02:22 -0300 | |
|---|---|---|
| committer | 2023-10-15 02:02:22 -0300 | |
| commit | 3062a35eb1297067446156c43e9d0df2f684edff (patch) | |
| tree | f2ca58e0b8a6c413f3c6783a1501204729e0394c /src | |
| parent | Merge pull request #11780 from Darkness4/master (diff) | |
| download | yuzu-3062a35eb1297067446156c43e9d0df2f684edff.tar.gz yuzu-3062a35eb1297067446156c43e9d0df2f684edff.tar.xz yuzu-3062a35eb1297067446156c43e9d0df2f684edff.zip | |
Improved shortcut: add games in applist for Windows, question for start game at fullscreen & better unicode support for some Windows path funcs.
Diffstat (limited to 'src')
| -rw-r--r-- | src/common/fs/fs_util.cpp | 59 | ||||
| -rw-r--r-- | src/common/fs/fs_util.h | 22 | ||||
| -rw-r--r-- | src/common/fs/path_util.cpp | 87 | ||||
| -rw-r--r-- | src/common/fs/path_util.h | 15 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 4 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 490 | ||||
| -rw-r--r-- | src/yuzu/main.h | 22 | ||||
| -rw-r--r-- | src/yuzu/util/util.cpp | 17 | ||||
| -rw-r--r-- | src/yuzu/util/util.h | 14 |
9 files changed, 515 insertions, 215 deletions
diff --git a/src/common/fs/fs_util.cpp b/src/common/fs/fs_util.cpp index 813a713c3..442f63728 100644 --- a/src/common/fs/fs_util.cpp +++ b/src/common/fs/fs_util.cpp | |||
| @@ -36,4 +36,63 @@ std::string PathToUTF8String(const std::filesystem::path& path) { | |||
| 36 | return ToUTF8String(path.u8string()); | 36 | return ToUTF8String(path.u8string()); |
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | std::u8string U8FilenameSantizer(const std::u8string_view u8filename) { | ||
| 40 | std::u8string u8path_santized{u8filename.begin(), u8filename.end()}; | ||
| 41 | size_t eSizeSanitized = u8path_santized.size(); | ||
| 42 | |||
| 43 | // Special case for ":", for example: 'Pepe: La secuela' --> 'Pepe - La | ||
| 44 | // secuela' or 'Pepe : La secuela' --> 'Pepe - La secuela' | ||
| 45 | for (size_t i = 0; i < eSizeSanitized; i++) { | ||
| 46 | switch (u8path_santized[i]) { | ||
| 47 | case u8':': | ||
| 48 | if (i == 0 || i == eSizeSanitized - 1) { | ||
| 49 | u8path_santized.replace(i, 1, u8"_"); | ||
| 50 | } else if (u8path_santized[i - 1] == u8' ') { | ||
| 51 | u8path_santized.replace(i, 1, u8"-"); | ||
| 52 | } else { | ||
| 53 | u8path_santized.replace(i, 1, u8" -"); | ||
| 54 | eSizeSanitized++; | ||
| 55 | } | ||
| 56 | break; | ||
| 57 | case u8'\\': | ||
| 58 | case u8'/': | ||
| 59 | case u8'*': | ||
| 60 | case u8'?': | ||
| 61 | case u8'\"': | ||
| 62 | case u8'<': | ||
| 63 | case u8'>': | ||
| 64 | case u8'|': | ||
| 65 | case u8'\0': | ||
| 66 | u8path_santized.replace(i, 1, u8"_"); | ||
| 67 | break; | ||
| 68 | default: | ||
| 69 | break; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | // Delete duplicated spaces || Delete duplicated dots (MacOS i think) | ||
| 74 | for (size_t i = 0; i < eSizeSanitized - 1; i++) { | ||
| 75 | if ((u8path_santized[i] == u8' ' && u8path_santized[i + 1] == u8' ') || | ||
| 76 | (u8path_santized[i] == u8'.' && u8path_santized[i + 1] == u8'.')) { | ||
| 77 | u8path_santized.erase(i, 1); | ||
| 78 | i--; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | // Delete all spaces and dots at the end (Windows almost) | ||
| 83 | while (u8path_santized.back() == u8' ' || u8path_santized.back() == u8'.') { | ||
| 84 | u8path_santized.pop_back(); | ||
| 85 | } | ||
| 86 | |||
| 87 | if (u8path_santized.empty()) { | ||
| 88 | return u8""; | ||
| 89 | } | ||
| 90 | |||
| 91 | return u8path_santized; | ||
| 92 | } | ||
| 93 | |||
| 94 | std::string UTF8FilenameSantizer(const std::string_view filename) { | ||
| 95 | return ToUTF8String(U8FilenameSantizer(ToU8String(filename))); | ||
| 96 | } | ||
| 97 | |||
| 39 | } // namespace Common::FS | 98 | } // namespace Common::FS |
diff --git a/src/common/fs/fs_util.h b/src/common/fs/fs_util.h index 2492a9f94..dbb4f5a9a 100644 --- a/src/common/fs/fs_util.h +++ b/src/common/fs/fs_util.h | |||
| @@ -82,4 +82,24 @@ concept IsChar = std::same_as<T, char>; | |||
| 82 | */ | 82 | */ |
| 83 | [[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path); | 83 | [[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path); |
| 84 | 84 | ||
| 85 | } // namespace Common::FS | 85 | /** |
| 86 | * Fix filename (remove invalid characters) | ||
| 87 | * | ||
| 88 | * @param u8_string dirty encoded filename string | ||
| 89 | * | ||
| 90 | * @returns utf8_string santized filename string | ||
| 91 | * | ||
| 92 | */ | ||
| 93 | [[nodiscard]] std::u8string U8FilenameSantizer(const std::u8string_view u8filename); | ||
| 94 | |||
| 95 | /** | ||
| 96 | * Fix filename (remove invalid characters) | ||
| 97 | * | ||
| 98 | * @param utf8_string dirty encoded filename string | ||
| 99 | * | ||
| 100 | * @returns utf8_string santized filename string | ||
| 101 | * | ||
| 102 | */ | ||
| 103 | [[nodiscard]] std::string UTF8FilenameSantizer(const std::string_view filename); | ||
| 104 | |||
| 105 | } // namespace Common::FS \ No newline at end of file | ||
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 0abd81a45..a461161ed 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include <unordered_map> | 6 | #include <unordered_map> |
| 7 | 7 | ||
| 8 | #include "common/fs/fs.h" | 8 | #include "common/fs/fs.h" |
| 9 | #include "common/string_util.h" | ||
| 9 | #ifdef ANDROID | 10 | #ifdef ANDROID |
| 10 | #include "common/fs/fs_android.h" | 11 | #include "common/fs/fs_android.h" |
| 11 | #endif | 12 | #endif |
| @@ -14,7 +15,7 @@ | |||
| 14 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 15 | 16 | ||
| 16 | #ifdef _WIN32 | 17 | #ifdef _WIN32 |
| 17 | #include <shlobj.h> // Used in GetExeDirectory() | 18 | #include <shlobj.h> // Used in GetExeDirectory() and GetWindowsDesktop() |
| 18 | #else | 19 | #else |
| 19 | #include <cstdlib> // Used in Get(Home/Data)Directory() | 20 | #include <cstdlib> // Used in Get(Home/Data)Directory() |
| 20 | #include <pwd.h> // Used in GetHomeDirectory() | 21 | #include <pwd.h> // Used in GetHomeDirectory() |
| @@ -250,30 +251,39 @@ void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { | |||
| 250 | #ifdef _WIN32 | 251 | #ifdef _WIN32 |
| 251 | 252 | ||
| 252 | fs::path GetExeDirectory() { | 253 | fs::path GetExeDirectory() { |
| 253 | wchar_t exe_path[MAX_PATH]; | 254 | WCHAR exe_path[MAX_PATH]; |
| 254 | 255 | ||
| 255 | if (GetModuleFileNameW(nullptr, exe_path, MAX_PATH) == 0) { | 256 | if (SUCCEEDED(GetModuleFileNameW(nullptr, exe_path, MAX_PATH))) { |
| 257 | std::wstring wideExePath(exe_path); | ||
| 258 | |||
| 259 | // UTF-16 filesystem lib to UTF-8 is broken, so we need to convert to UTF-8 with the with | ||
| 260 | // the Windows library (Filesystem converts the strings literally). | ||
| 261 | return fs::path{Common::UTF16ToUTF8(wideExePath)}.parent_path(); | ||
| 262 | } else { | ||
| 256 | LOG_ERROR(Common_Filesystem, | 263 | LOG_ERROR(Common_Filesystem, |
| 257 | "Failed to get the path to the executable of the current process"); | 264 | "[GetExeDirectory] Failed to get the path to the executable of the current " |
| 265 | "process"); | ||
| 258 | } | 266 | } |
| 259 | 267 | ||
| 260 | return fs::path{exe_path}.parent_path(); | 268 | return fs::path{}; |
| 261 | } | 269 | } |
| 262 | 270 | ||
| 263 | fs::path GetAppDataRoamingDirectory() { | 271 | fs::path GetAppDataRoamingDirectory() { |
| 264 | PWSTR appdata_roaming_path = nullptr; | 272 | PWSTR appdata_roaming_path = nullptr; |
| 265 | 273 | ||
| 266 | SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_roaming_path); | 274 | if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &appdata_roaming_path))) { |
| 267 | 275 | std::wstring wideAppdataRoamingPath(appdata_roaming_path); | |
| 268 | auto fs_appdata_roaming_path = fs::path{appdata_roaming_path}; | 276 | CoTaskMemFree(appdata_roaming_path); |
| 269 | |||
| 270 | CoTaskMemFree(appdata_roaming_path); | ||
| 271 | 277 | ||
| 272 | if (fs_appdata_roaming_path.empty()) { | 278 | // UTF-16 filesystem lib to UTF-8 is broken, so we need to convert to UTF-8 with the with |
| 273 | LOG_ERROR(Common_Filesystem, "Failed to get the path to the %APPDATA% directory"); | 279 | // the Windows library (Filesystem converts the strings literally). |
| 280 | return fs::path{Common::UTF16ToUTF8(wideAppdataRoamingPath)}; | ||
| 281 | } else { | ||
| 282 | LOG_ERROR(Common_Filesystem, | ||
| 283 | "[GetAppDataRoamingDirectory] Failed to get the path to the %APPDATA% directory"); | ||
| 274 | } | 284 | } |
| 275 | 285 | ||
| 276 | return fs_appdata_roaming_path; | 286 | return fs::path{}; |
| 277 | } | 287 | } |
| 278 | 288 | ||
| 279 | #else | 289 | #else |
| @@ -338,6 +348,57 @@ fs::path GetBundleDirectory() { | |||
| 338 | 348 | ||
| 339 | #endif | 349 | #endif |
| 340 | 350 | ||
| 351 | fs::path GetDesktopPath() { | ||
| 352 | #if defined(_WIN32) | ||
| 353 | PWSTR DesktopPath = nullptr; | ||
| 354 | |||
| 355 | if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &DesktopPath))) { | ||
| 356 | std::wstring wideDesktopPath(DesktopPath); | ||
| 357 | CoTaskMemFree(DesktopPath); | ||
| 358 | |||
| 359 | // UTF-16 filesystem lib to UTF-8 is broken, so we need to convert to UTF-8 with the with | ||
| 360 | // the Windows library (Filesystem converts the strings literally). | ||
| 361 | return fs::path{Common::UTF16ToUTF8(wideDesktopPath)}; | ||
| 362 | } else { | ||
| 363 | LOG_ERROR(Common_Filesystem, | ||
| 364 | "[GetDesktopPath] Failed to get the path to the desktop directory"); | ||
| 365 | } | ||
| 366 | #else | ||
| 367 | fs::path shortcut_path = GetHomeDirectory() / "Desktop"; | ||
| 368 | if (fs::exists(shortcut_path)) { | ||
| 369 | return shortcut_path; | ||
| 370 | } | ||
| 371 | #endif | ||
| 372 | return fs::path{}; | ||
| 373 | } | ||
| 374 | |||
| 375 | fs::path GetAppsShortcutsPath() { | ||
| 376 | #if defined(_WIN32) | ||
| 377 | PWSTR AppShortcutsPath = nullptr; | ||
| 378 | |||
| 379 | if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_CommonPrograms, 0, NULL, &AppShortcutsPath))) { | ||
| 380 | std::wstring wideAppShortcutsPath(AppShortcutsPath); | ||
| 381 | CoTaskMemFree(AppShortcutsPath); | ||
| 382 | |||
| 383 | // UTF-16 filesystem lib to UTF-8 is broken, so we need to convert to UTF-8 with the with | ||
| 384 | // the Windows library (Filesystem converts the strings literally). | ||
| 385 | return fs::path{Common::UTF16ToUTF8(wideAppShortcutsPath)}; | ||
| 386 | } else { | ||
| 387 | LOG_ERROR(Common_Filesystem, | ||
| 388 | "[GetAppsShortcutsPath] Failed to get the path to the App Shortcuts directory"); | ||
| 389 | } | ||
| 390 | #else | ||
| 391 | fs::path shortcut_path = GetHomeDirectory() / ".local/share/applications"; | ||
| 392 | if (!fs::exists(shortcut_path)) { | ||
| 393 | shortcut_path = std::filesystem::path("/usr/share/applications"); | ||
| 394 | return shortcut_path; | ||
| 395 | } else { | ||
| 396 | return shortcut_path; | ||
| 397 | } | ||
| 398 | #endif | ||
| 399 | return fs::path{}; | ||
| 400 | } | ||
| 401 | |||
| 341 | // vvvvvvvvvv Deprecated vvvvvvvvvv // | 402 | // vvvvvvvvvv Deprecated vvvvvvvvvv // |
| 342 | 403 | ||
| 343 | std::string_view RemoveTrailingSlash(std::string_view path) { | 404 | std::string_view RemoveTrailingSlash(std::string_view path) { |
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 63801c924..b88a388d1 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h | |||
| @@ -244,7 +244,6 @@ void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { | |||
| 244 | * @returns The path of the current user's %APPDATA% directory. | 244 | * @returns The path of the current user's %APPDATA% directory. |
| 245 | */ | 245 | */ |
| 246 | [[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory(); | 246 | [[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory(); |
| 247 | |||
| 248 | #else | 247 | #else |
| 249 | 248 | ||
| 250 | /** | 249 | /** |
| @@ -275,6 +274,20 @@ void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { | |||
| 275 | 274 | ||
| 276 | #endif | 275 | #endif |
| 277 | 276 | ||
| 277 | /** | ||
| 278 | * Gets the path of the current user's desktop directory. | ||
| 279 | * | ||
| 280 | * @returns The path of the current user's desktop directory. | ||
| 281 | */ | ||
| 282 | [[nodiscard]] std::filesystem::path GetDesktopPath(); | ||
| 283 | |||
| 284 | /** | ||
| 285 | * Gets the path of the current user's apps directory. | ||
| 286 | * | ||
| 287 | * @returns The path of the current user's apps directory. | ||
| 288 | */ | ||
| 289 | [[nodiscard]] std::filesystem::path GetAppsShortcutsPath(); | ||
| 290 | |||
| 278 | // vvvvvvvvvv Deprecated vvvvvvvvvv // | 291 | // vvvvvvvvvv Deprecated vvvvvvvvvv // |
| 279 | 292 | ||
| 280 | // Removes the final '/' or '\' if one exists | 293 | // Removes the final '/' or '\' if one exists |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 2bb1a0239..fbe099661 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -566,10 +566,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 566 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | 566 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |
| 567 | QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); | 567 | QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); |
| 568 | QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); | 568 | QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); |
| 569 | #ifndef WIN32 | ||
| 570 | QAction* create_applications_menu_shortcut = | 569 | QAction* create_applications_menu_shortcut = |
| 571 | shortcut_menu->addAction(tr("Add to Applications Menu")); | 570 | shortcut_menu->addAction(tr("Add to Applications Menu")); |
| 572 | #endif | ||
| 573 | context_menu.addSeparator(); | 571 | context_menu.addSeparator(); |
| 574 | QAction* properties = context_menu.addAction(tr("Properties")); | 572 | QAction* properties = context_menu.addAction(tr("Properties")); |
| 575 | 573 | ||
| @@ -647,11 +645,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 647 | connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { | 645 | connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { |
| 648 | emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); | 646 | emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); |
| 649 | }); | 647 | }); |
| 650 | #ifndef WIN32 | ||
| 651 | connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { | 648 | connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { |
| 652 | emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); | 649 | emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); |
| 653 | }); | 650 | }); |
| 654 | #endif | ||
| 655 | connect(properties, &QAction::triggered, | 651 | connect(properties, &QAction::triggered, |
| 656 | [this, path]() { emit OpenPerGameGeneralRequested(path); }); | 652 | [this, path]() { emit OpenPerGameGeneralRequested(path); }); |
| 657 | }; | 653 | }; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 1431cf2fe..e4dc717ed 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -2840,170 +2840,350 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | |||
| 2840 | QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); | 2840 | QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); |
| 2841 | } | 2841 | } |
| 2842 | 2842 | ||
| 2843 | void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, | 2843 | bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path, |
| 2844 | GameListShortcutTarget target) { | 2844 | const std::string& comment, |
| 2845 | // Get path to yuzu executable | 2845 | const std::filesystem::path& icon_path, |
| 2846 | const QStringList args = QApplication::arguments(); | 2846 | const std::filesystem::path& command, |
| 2847 | std::filesystem::path yuzu_command = args[0].toStdString(); | 2847 | const std::string& arguments, const std::string& categories, |
| 2848 | const std::string& keywords, const std::string& name) { | ||
| 2848 | 2849 | ||
| 2849 | // If relative path, make it an absolute path | 2850 | bool shortcut_succeeded = false; |
| 2850 | if (yuzu_command.c_str()[0] == '.') { | ||
| 2851 | yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; | ||
| 2852 | } | ||
| 2853 | 2851 | ||
| 2854 | #if defined(__linux__) | 2852 | // Replace characters that are illegal in Windows filenames |
| 2855 | // Warn once if we are making a shortcut to a volatile AppImage | 2853 | std::filesystem::path shortcut_path_full = |
| 2856 | const std::string appimage_ending = | 2854 | shortcut_path / Common::FS::UTF8FilenameSantizer(name); |
| 2857 | std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage"); | 2855 | |
| 2858 | if (yuzu_command.string().ends_with(appimage_ending) && | 2856 | #if defined(__linux__) || defined(__FreeBSD__) |
| 2859 | !UISettings::values.shortcut_already_warned) { | 2857 | shortcut_path_full += ".desktop"; |
| 2860 | if (QMessageBox::warning(this, tr("Create Shortcut"), | 2858 | #elif defined(_WIN32) |
| 2861 | tr("This will create a shortcut to the current AppImage. This may " | 2859 | shortcut_path_full += ".lnk"; |
| 2862 | "not work well if you update. Continue?"), | 2860 | #endif |
| 2863 | QMessageBox::StandardButton::Ok | | 2861 | |
| 2864 | QMessageBox::StandardButton::Cancel) == | 2862 | LOG_INFO(Common, "[GMainWindow::CreateShortcutLink] Create shortcut path: {}", |
| 2865 | QMessageBox::StandardButton::Cancel) { | 2863 | shortcut_path_full.string()); |
| 2866 | return; | 2864 | |
| 2865 | #if defined(__linux__) || defined(__FreeBSD__) | ||
| 2866 | // This desktop file template was writing referencing | ||
| 2867 | // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html | ||
| 2868 | try { | ||
| 2869 | |||
| 2870 | // Plus 'Type' is required | ||
| 2871 | if (name.empty()) { | ||
| 2872 | LOG_ERROR(Common, "[GMainWindow::CreateShortcutLink] Name is empty"); | ||
| 2873 | shortcut_succeeded = false; | ||
| 2874 | return shortcut_succeeded; | ||
| 2867 | } | 2875 | } |
| 2868 | UISettings::values.shortcut_already_warned = true; | 2876 | std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc); |
| 2869 | } | ||
| 2870 | #endif // __linux__ | ||
| 2871 | 2877 | ||
| 2872 | std::filesystem::path target_directory{}; | 2878 | if (shortcut_stream.is_open()) { |
| 2873 | 2879 | ||
| 2874 | switch (target) { | 2880 | fmt::print(shortcut_stream, "[Desktop Entry]\n"); |
| 2875 | case GameListShortcutTarget::Desktop: { | 2881 | fmt::print(shortcut_stream, "Type=Application\n"); |
| 2876 | const QString desktop_path = | 2882 | fmt::print(shortcut_stream, "Version=1.0\n"); |
| 2877 | QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); | 2883 | fmt::print(shortcut_stream, "Name={}\n", name); |
| 2878 | target_directory = desktop_path.toUtf8().toStdString(); | 2884 | |
| 2879 | break; | 2885 | if (!comment.empty()) { |
| 2880 | } | 2886 | fmt::print(shortcut_stream, "Comment={}\n", comment); |
| 2881 | case GameListShortcutTarget::Applications: { | ||
| 2882 | const QString applications_path = | ||
| 2883 | QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); | ||
| 2884 | if (applications_path.isEmpty()) { | ||
| 2885 | const char* home = std::getenv("HOME"); | ||
| 2886 | if (home != nullptr) { | ||
| 2887 | target_directory = std::filesystem::path(home) / ".local/share/applications"; | ||
| 2888 | } | 2887 | } |
| 2888 | |||
| 2889 | if (std::filesystem::is_regular_file(icon_path)) { | ||
| 2890 | fmt::print(shortcut_stream, "Icon={}\n", icon_path.string()); | ||
| 2891 | } | ||
| 2892 | |||
| 2893 | fmt::print(shortcut_stream, "TryExec={}\n", command.string()); | ||
| 2894 | fmt::print(shortcut_stream, "Exec={}", command.string()); | ||
| 2895 | |||
| 2896 | if (!arguments.empty()) { | ||
| 2897 | fmt::print(shortcut_stream, " {}", arguments); | ||
| 2898 | } | ||
| 2899 | |||
| 2900 | fmt::print(shortcut_stream, "\n"); | ||
| 2901 | |||
| 2902 | if (!categories.empty()) { | ||
| 2903 | fmt::print(shortcut_stream, "Categories={}\n", categories); | ||
| 2904 | } | ||
| 2905 | |||
| 2906 | if (!keywords.empty()) { | ||
| 2907 | fmt::print(shortcut_stream, "Keywords={}\n", keywords); | ||
| 2908 | } | ||
| 2909 | |||
| 2910 | shortcut_stream.close(); | ||
| 2911 | return true; | ||
| 2912 | |||
| 2889 | } else { | 2913 | } else { |
| 2890 | target_directory = applications_path.toUtf8().toStdString(); | 2914 | LOG_ERROR(Common, "[GMainWindow::CreateShortcutLink] Failed to create shortcut"); |
| 2915 | return false; | ||
| 2891 | } | 2916 | } |
| 2892 | break; | 2917 | |
| 2918 | shortcut_stream.close(); | ||
| 2919 | } catch (const std::exception& e) { | ||
| 2920 | LOG_ERROR(Common, "[GMainWindow::CreateShortcutLink] Failed to create shortcut: {}", | ||
| 2921 | e.what()); | ||
| 2893 | } | 2922 | } |
| 2894 | default: | 2923 | #elif defined(_WIN32) |
| 2895 | return; | 2924 | // Initialize COM |
| 2925 | auto hr = CoInitialize(NULL); | ||
| 2926 | if (FAILED(hr)) { | ||
| 2927 | return shortcut_succeeded; | ||
| 2896 | } | 2928 | } |
| 2897 | 2929 | ||
| 2898 | const QDir dir(QString::fromStdString(target_directory.generic_string())); | 2930 | IShellLinkW* ps1; |
| 2899 | if (!dir.exists()) { | 2931 | |
| 2900 | QMessageBox::critical(this, tr("Create Shortcut"), | 2932 | auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, |
| 2901 | tr("Cannot create shortcut. Path \"%1\" does not exist.") | 2933 | (void**)&ps1); |
| 2902 | .arg(QString::fromStdString(target_directory.generic_string())), | 2934 | |
| 2903 | QMessageBox::StandardButton::Ok); | 2935 | // The UTF-16 / UTF-8 conversion is broken in C++, it is necessary to perform these steps and |
| 2904 | return; | 2936 | // resort to the native Windows function. |
| 2937 | std::wstring wshortcut_path_full = Common::UTF8ToUTF16W(shortcut_path_full.string()); | ||
| 2938 | std::wstring wicon_path = Common::UTF8ToUTF16W(icon_path.string()); | ||
| 2939 | std::wstring wcommand = Common::UTF8ToUTF16W(command.string()); | ||
| 2940 | std::wstring warguments = Common::UTF8ToUTF16W(arguments); | ||
| 2941 | std::wstring wcomment = Common::UTF8ToUTF16W(comment); | ||
| 2942 | |||
| 2943 | if (SUCCEEDED(hres)) { | ||
| 2944 | if (std::filesystem::is_regular_file(command)) | ||
| 2945 | hres = ps1->SetPath(wcommand.data()); | ||
| 2946 | |||
| 2947 | if (SUCCEEDED(hres) && !arguments.empty()) | ||
| 2948 | hres = ps1->SetArguments(warguments.data()); | ||
| 2949 | |||
| 2950 | if (SUCCEEDED(hres) && !comment.empty()) | ||
| 2951 | hres = ps1->SetDescription(wcomment.data()); | ||
| 2952 | |||
| 2953 | if (SUCCEEDED(hres) && std::filesystem::is_regular_file(icon_path)) | ||
| 2954 | hres = ps1->SetIconLocation(wicon_path.data(), 0); | ||
| 2955 | |||
| 2956 | IPersistFile* pPersistFile = nullptr; | ||
| 2957 | |||
| 2958 | if (SUCCEEDED(hres)) { | ||
| 2959 | hres = ps1->QueryInterface(IID_IPersistFile, (void**)&pPersistFile); | ||
| 2960 | |||
| 2961 | if (SUCCEEDED(hres) && pPersistFile != nullptr) { | ||
| 2962 | hres = pPersistFile->Save(wshortcut_path_full.data(), TRUE); | ||
| 2963 | if (SUCCEEDED(hres)) { | ||
| 2964 | shortcut_succeeded = true; | ||
| 2965 | } | ||
| 2966 | } | ||
| 2967 | } | ||
| 2968 | |||
| 2969 | if (pPersistFile != nullptr) { | ||
| 2970 | pPersistFile->Release(); | ||
| 2971 | } | ||
| 2972 | } else { | ||
| 2973 | LOG_ERROR(Common, "[GMainWindow::CreateShortcutLink] Failed to create IShellLinkWinstance"); | ||
| 2905 | } | 2974 | } |
| 2906 | 2975 | ||
| 2907 | const std::string game_file_name = std::filesystem::path(game_path).filename().string(); | 2976 | ps1->Release(); |
| 2908 | // Determine full paths for icon and shortcut | 2977 | CoUninitialize(); |
| 2909 | #if defined(__linux__) || defined(__FreeBSD__) | 2978 | #endif |
| 2910 | const char* home = std::getenv("HOME"); | 2979 | |
| 2911 | const std::filesystem::path home_path = (home == nullptr ? "~" : home); | 2980 | if (shortcut_succeeded && std::filesystem::is_regular_file(shortcut_path_full)) { |
| 2912 | const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); | 2981 | LOG_INFO(Common, "[GMainWindow::CreateShortcutLink] Shortcut created"); |
| 2913 | 2982 | } else { | |
| 2914 | std::filesystem::path system_icons_path = | 2983 | LOG_ERROR(Common, "[GMainWindow::CreateShortcutLink] Shortcut error, failed to create it"); |
| 2915 | (xdg_data_home == nullptr ? home_path / ".local/share/" | 2984 | shortcut_succeeded = false; |
| 2916 | : std::filesystem::path(xdg_data_home)) / | 2985 | } |
| 2917 | "icons/hicolor/256x256"; | 2986 | |
| 2918 | if (!Common::FS::CreateDirs(system_icons_path)) { | 2987 | return shortcut_succeeded; |
| 2988 | } | ||
| 2989 | |||
| 2990 | // Messages in pre-defined message boxes for less code spaghetti | ||
| 2991 | bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, const int& imsg, | ||
| 2992 | const std::string title) { | ||
| 2993 | QMessageBox::StandardButtons buttons; | ||
| 2994 | int result = 0; | ||
| 2995 | |||
| 2996 | switch (imsg) { | ||
| 2997 | |||
| 2998 | case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES: | ||
| 2999 | buttons = QMessageBox::Yes | QMessageBox::No; | ||
| 3000 | |||
| 3001 | result = | ||
| 3002 | QMessageBox::information(parent, tr("Create Shortcut"), | ||
| 3003 | tr("Do you want to launch the game in fullscreen?"), buttons); | ||
| 3004 | |||
| 3005 | LOG_INFO(Frontend, "Shortcut will launch in fullscreen"); | ||
| 3006 | return (result == QMessageBox::No) ? false : true; | ||
| 3007 | |||
| 3008 | case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS: | ||
| 3009 | QMessageBox::information( | ||
| 3010 | parent, tr("Create Shortcut"), | ||
| 3011 | tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title))); | ||
| 3012 | LOG_INFO(Frontend, "Successfully created a shortcut to {}", title); | ||
| 3013 | return true; | ||
| 3014 | |||
| 3015 | case GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING: | ||
| 3016 | result = QMessageBox::warning( | ||
| 3017 | this, tr("Create Shortcut"), | ||
| 3018 | tr("This will create a shortcut to the current AppImage. This may " | ||
| 3019 | "not work well if you update. Continue?"), | ||
| 3020 | QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel); | ||
| 3021 | return (result == QMessageBox::StandardButton::Cancel) ? true : false; | ||
| 3022 | case GMainWindow::CREATE_SHORTCUT_MSGBOX_ADMIN: | ||
| 3023 | buttons = QMessageBox::Ok; | ||
| 3024 | QMessageBox::critical(parent, tr("Create Shortcut"), | ||
| 3025 | tr("Cannot create shortcut in Apps. Restart yuzu as administrator."), | ||
| 3026 | buttons); | ||
| 3027 | LOG_ERROR(Frontend, "Cannot create shortcut in Apps. Restart yuzu as administrator."); | ||
| 3028 | return true; | ||
| 3029 | default: | ||
| 3030 | buttons = QMessageBox::Ok; | ||
| 3031 | QMessageBox::critical( | ||
| 3032 | parent, tr("Create Shortcut"), | ||
| 3033 | tr("Failed to create a shortcut to %1").arg(QString::fromStdString(title)), buttons); | ||
| 3034 | LOG_ERROR(Frontend, "Failed to create a shortcut to {}", title); | ||
| 3035 | return true; | ||
| 3036 | } | ||
| 3037 | |||
| 3038 | return true; | ||
| 3039 | } | ||
| 3040 | |||
| 3041 | bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, | ||
| 3042 | std::filesystem::path& icons_path) { | ||
| 3043 | |||
| 3044 | // Get path to Yuzu icons directory & icon extension | ||
| 3045 | std::string ico_extension = "png"; | ||
| 3046 | #if defined(_WIN32) | ||
| 3047 | icons_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "icons"; | ||
| 3048 | ico_extension = "ico"; | ||
| 3049 | #elif defined(__linux__) || defined(__FreeBSD__) | ||
| 3050 | icons_path = GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256"; | ||
| 3051 | #endif | ||
| 3052 | |||
| 3053 | // Create icons directory if it doesn't exist | ||
| 3054 | if (!Common::FS::CreateDirs(icons_path)) { | ||
| 2919 | QMessageBox::critical( | 3055 | QMessageBox::critical( |
| 2920 | this, tr("Create Icon"), | 3056 | this, tr("Create Icon"), |
| 2921 | tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") | 3057 | tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") |
| 2922 | .arg(QString::fromStdString(system_icons_path)), | 3058 | .arg(QString::fromStdString(icons_path.string())), |
| 2923 | QMessageBox::StandardButton::Ok); | 3059 | QMessageBox::StandardButton::Ok); |
| 2924 | return; | 3060 | icons_path = ""; // Reset path |
| 3061 | return false; | ||
| 2925 | } | 3062 | } |
| 2926 | std::filesystem::path icon_path = | ||
| 2927 | system_icons_path / (program_id == 0 ? fmt::format("yuzu-{}.png", game_file_name) | ||
| 2928 | : fmt::format("yuzu-{:016X}.png", program_id)); | ||
| 2929 | const std::filesystem::path shortcut_path = | ||
| 2930 | target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) | ||
| 2931 | : fmt::format("yuzu-{:016X}.desktop", program_id)); | ||
| 2932 | #elif defined(WIN32) | ||
| 2933 | std::filesystem::path icons_path = | ||
| 2934 | Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir); | ||
| 2935 | std::filesystem::path icon_path = | ||
| 2936 | icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name) | ||
| 2937 | : fmt::format("yuzu-{:016X}.ico", program_id))); | ||
| 2938 | #else | ||
| 2939 | std::string icon_extension; | ||
| 2940 | #endif | ||
| 2941 | 3063 | ||
| 2942 | // Get title from game file | 3064 | // Create icon file path |
| 2943 | const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), | 3065 | icons_path /= (program_id == 0 ? fmt::format("yuzu-{}.{}", game_file_name, ico_extension) |
| 2944 | system->GetContentProvider()}; | 3066 | : fmt::format("yuzu-{:016X}.{}", program_id, ico_extension)); |
| 2945 | const auto control = pm.GetControlMetadata(); | 3067 | return true; |
| 2946 | const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); | 3068 | } |
| 2947 | 3069 | ||
| 2948 | std::string title{fmt::format("{:016X}", program_id)}; | 3070 | void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, |
| 3071 | GameListShortcutTarget target) { | ||
| 2949 | 3072 | ||
| 2950 | if (control.first != nullptr) { | 3073 | // Get path to yuzu executable |
| 2951 | title = control.first->GetApplicationName(); | 3074 | const QStringList args = QApplication::arguments(); |
| 2952 | } else { | 3075 | std::filesystem::path yuzu_command = args[0].toStdString(); |
| 2953 | loader->ReadTitle(title); | 3076 | |
| 3077 | // If relative path, make it an absolute path | ||
| 3078 | if (yuzu_command.c_str()[0] == '.') { | ||
| 3079 | yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; | ||
| 2954 | } | 3080 | } |
| 2955 | 3081 | ||
| 2956 | // Get icon from game file | 3082 | // Shortcut path |
| 2957 | std::vector<u8> icon_image_file{}; | 3083 | std::filesystem::path shortcut_path{}; |
| 2958 | if (control.second != nullptr) { | 3084 | if (target == GameListShortcutTarget::Desktop) { |
| 2959 | icon_image_file = control.second->ReadAllBytes(); | 3085 | shortcut_path = Common::FS::GetDesktopPath(); |
| 2960 | } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { | 3086 | if (!std::filesystem::exists(shortcut_path)) { |
| 2961 | LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); | 3087 | shortcut_path = |
| 3088 | QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString(); | ||
| 3089 | } | ||
| 3090 | } else if (target == GameListShortcutTarget::Applications) { | ||
| 3091 | |||
| 3092 | #if defined(_WIN32) | ||
| 3093 | HANDLE hProcess = GetCurrentProcess(); | ||
| 3094 | if (!IsUserAnAdmin()) { | ||
| 3095 | GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ADMIN, | ||
| 3096 | ""); | ||
| 3097 | return; | ||
| 3098 | } | ||
| 3099 | CloseHandle(hProcess); | ||
| 3100 | #endif // _WIN32 | ||
| 3101 | |||
| 3102 | shortcut_path = Common::FS::GetAppsShortcutsPath(); | ||
| 3103 | if (!std::filesystem::exists(shortcut_path)) { | ||
| 3104 | shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) | ||
| 3105 | .toStdString(); | ||
| 3106 | } | ||
| 2962 | } | 3107 | } |
| 2963 | 3108 | ||
| 2964 | QImage icon_data = | 3109 | // Icon path and title |
| 2965 | QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); | 3110 | std::string title; |
| 2966 | #if defined(__linux__) || defined(__FreeBSD__) | 3111 | std::filesystem::path icons_path; |
| 2967 | // Convert and write the icon as a PNG | 3112 | if (std::filesystem::exists(shortcut_path)) { |
| 2968 | if (!icon_data.save(QString::fromStdString(icon_path.string()))) { | 3113 | |
| 2969 | LOG_ERROR(Frontend, "Could not write icon as PNG to file"); | 3114 | // Get title from game file |
| 3115 | const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), | ||
| 3116 | system->GetContentProvider()}; | ||
| 3117 | const auto control = pm.GetControlMetadata(); | ||
| 3118 | const auto loader = | ||
| 3119 | Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 3120 | |||
| 3121 | title = fmt::format("{:016X}", program_id); | ||
| 3122 | |||
| 3123 | if (control.first != nullptr) { | ||
| 3124 | title = control.first->GetApplicationName(); | ||
| 3125 | } else { | ||
| 3126 | loader->ReadTitle(title); | ||
| 3127 | } | ||
| 3128 | |||
| 3129 | // Get icon from game file | ||
| 3130 | std::vector<u8> icon_image_file{}; | ||
| 3131 | if (control.second != nullptr) { | ||
| 3132 | icon_image_file = control.second->ReadAllBytes(); | ||
| 3133 | } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { | ||
| 3134 | LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); | ||
| 3135 | } | ||
| 3136 | |||
| 3137 | QImage icon_data = | ||
| 3138 | QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); | ||
| 3139 | |||
| 3140 | if (GMainWindow::MakeShortcutIcoPath(program_id, title, icons_path)) { | ||
| 3141 | if (!SaveIconToFile(icon_data, icons_path)) { | ||
| 3142 | LOG_ERROR(Frontend, "Could not write icon to file"); | ||
| 3143 | } | ||
| 3144 | } | ||
| 3145 | |||
| 2970 | } else { | 3146 | } else { |
| 2971 | LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); | 3147 | GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, |
| 2972 | } | 3148 | title); |
| 2973 | #elif defined(WIN32) | 3149 | LOG_ERROR(Frontend, "[GMainWindow::OnGameListCreateShortcut] Invalid shortcut target"); |
| 2974 | if (!SaveIconToFile(icon_path.string(), icon_data)) { | ||
| 2975 | LOG_ERROR(Frontend, "Could not write icon to file"); | ||
| 2976 | return; | 3150 | return; |
| 2977 | } | 3151 | } |
| 2978 | #endif // __linux__ | ||
| 2979 | 3152 | ||
| 2980 | #ifdef _WIN32 | 3153 | // Special case for AppImages |
| 2981 | // Replace characters that are illegal in Windows filenames by a dash | 3154 | #if defined(__linux__) |
| 2982 | const std::string illegal_chars = "<>:\"/\\|?*"; | 3155 | // Warn once if we are making a shortcut to a volatile AppImage |
| 2983 | for (char c : illegal_chars) { | 3156 | const std::string appimage_ending = |
| 2984 | std::replace(title.begin(), title.end(), c, '_'); | 3157 | std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage"); |
| 3158 | if (yuzu_command.string().ends_with(appimage_ending) && | ||
| 3159 | !UISettings::values.shortcut_already_warned) { | ||
| 3160 | if (GMainWindow::CreateShortcutMessagesGUI( | ||
| 3161 | this, GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, title)) { | ||
| 3162 | return; | ||
| 3163 | } | ||
| 3164 | UISettings::values.shortcut_already_warned = true; | ||
| 2985 | } | 3165 | } |
| 2986 | const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str(); | 3166 | #endif // __linux__ |
| 2987 | #endif | ||
| 2988 | 3167 | ||
| 3168 | // Create shortcut | ||
| 3169 | std::string arguments = fmt::format("-g \"{:s}\"", game_path); | ||
| 3170 | if (GMainWindow::CreateShortcutMessagesGUI( | ||
| 3171 | this, GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, title)) { | ||
| 3172 | arguments = "-f " + arguments; | ||
| 3173 | } | ||
| 2989 | const std::string comment = | 3174 | const std::string comment = |
| 2990 | tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); | 3175 | tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); |
| 2991 | const std::string arguments = fmt::format("-g \"{:s}\"", game_path); | ||
| 2992 | const std::string categories = "Game;Emulator;Qt;"; | 3176 | const std::string categories = "Game;Emulator;Qt;"; |
| 2993 | const std::string keywords = "Switch;Nintendo;"; | 3177 | const std::string keywords = "Switch;Nintendo;"; |
| 2994 | 3178 | ||
| 2995 | if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), | 3179 | if (GMainWindow::CreateShortcutLink(shortcut_path, comment, icons_path, yuzu_command, arguments, |
| 2996 | yuzu_command.string(), arguments, categories, keywords)) { | 3180 | categories, keywords, title)) { |
| 2997 | QMessageBox::critical(this, tr("Create Shortcut"), | 3181 | GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS, |
| 2998 | tr("Failed to create a shortcut at %1") | 3182 | title); |
| 2999 | .arg(QString::fromStdString(shortcut_path.string()))); | ||
| 3000 | return; | 3183 | return; |
| 3001 | } | 3184 | } |
| 3002 | 3185 | ||
| 3003 | LOG_INFO(Frontend, "Wrote a shortcut to {}", shortcut_path.string()); | 3186 | GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, title); |
| 3004 | QMessageBox::information( | ||
| 3005 | this, tr("Create Shortcut"), | ||
| 3006 | tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title))); | ||
| 3007 | } | 3187 | } |
| 3008 | 3188 | ||
| 3009 | void GMainWindow::OnGameListOpenDirectory(const QString& directory) { | 3189 | void GMainWindow::OnGameListOpenDirectory(const QString& directory) { |
| @@ -3998,66 +4178,6 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file | |||
| 3998 | } | 4178 | } |
| 3999 | } | 4179 | } |
| 4000 | 4180 | ||
| 4001 | bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title, | ||
| 4002 | const std::string& comment, const std::string& icon_path, | ||
| 4003 | const std::string& command, const std::string& arguments, | ||
| 4004 | const std::string& categories, const std::string& keywords) { | ||
| 4005 | #if defined(__linux__) || defined(__FreeBSD__) | ||
| 4006 | // This desktop file template was writing referencing | ||
| 4007 | // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html | ||
| 4008 | std::string shortcut_contents{}; | ||
| 4009 | shortcut_contents.append("[Desktop Entry]\n"); | ||
| 4010 | shortcut_contents.append("Type=Application\n"); | ||
| 4011 | shortcut_contents.append("Version=1.0\n"); | ||
| 4012 | shortcut_contents.append(fmt::format("Name={:s}\n", title)); | ||
| 4013 | shortcut_contents.append(fmt::format("Comment={:s}\n", comment)); | ||
| 4014 | shortcut_contents.append(fmt::format("Icon={:s}\n", icon_path)); | ||
| 4015 | shortcut_contents.append(fmt::format("TryExec={:s}\n", command)); | ||
| 4016 | shortcut_contents.append(fmt::format("Exec={:s} {:s}\n", command, arguments)); | ||
| 4017 | shortcut_contents.append(fmt::format("Categories={:s}\n", categories)); | ||
| 4018 | shortcut_contents.append(fmt::format("Keywords={:s}\n", keywords)); | ||
| 4019 | |||
| 4020 | std::ofstream shortcut_stream(shortcut_path); | ||
| 4021 | if (!shortcut_stream.is_open()) { | ||
| 4022 | LOG_WARNING(Common, "Failed to create file {:s}", shortcut_path); | ||
| 4023 | return false; | ||
| 4024 | } | ||
| 4025 | shortcut_stream << shortcut_contents; | ||
| 4026 | shortcut_stream.close(); | ||
| 4027 | |||
| 4028 | return true; | ||
| 4029 | #elif defined(WIN32) | ||
| 4030 | IShellLinkW* shell_link; | ||
| 4031 | auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, | ||
| 4032 | (void**)&shell_link); | ||
| 4033 | if (FAILED(hres)) { | ||
| 4034 | return false; | ||
| 4035 | } | ||
| 4036 | shell_link->SetPath( | ||
| 4037 | Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to | ||
| 4038 | shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data()); | ||
| 4039 | shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data()); | ||
| 4040 | shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0); | ||
| 4041 | |||
| 4042 | IPersistFile* persist_file; | ||
| 4043 | hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); | ||
| 4044 | if (FAILED(hres)) { | ||
| 4045 | return false; | ||
| 4046 | } | ||
| 4047 | |||
| 4048 | hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE); | ||
| 4049 | if (FAILED(hres)) { | ||
| 4050 | return false; | ||
| 4051 | } | ||
| 4052 | |||
| 4053 | persist_file->Release(); | ||
| 4054 | shell_link->Release(); | ||
| 4055 | |||
| 4056 | return true; | ||
| 4057 | #endif | ||
| 4058 | return false; | ||
| 4059 | } | ||
| 4060 | |||
| 4061 | void GMainWindow::OnLoadAmiibo() { | 4181 | void GMainWindow::OnLoadAmiibo() { |
| 4062 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { | 4182 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { |
| 4063 | return; | 4183 | return; |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 270a40c5f..bf6756b48 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 <QTimer> | 12 | #include <QTimer> |
| @@ -151,6 +152,14 @@ class GMainWindow : public QMainWindow { | |||
| 151 | UI_EMU_STOPPING, | 152 | UI_EMU_STOPPING, |
| 152 | }; | 153 | }; |
| 153 | 154 | ||
| 155 | const enum { | ||
| 156 | CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, | ||
| 157 | CREATE_SHORTCUT_MSGBOX_SUCCESS, | ||
| 158 | CREATE_SHORTCUT_MSGBOX_ERROR, | ||
| 159 | CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, | ||
| 160 | CREATE_SHORTCUT_MSGBOX_ADMIN, | ||
| 161 | }; | ||
| 162 | |||
| 154 | public: | 163 | public: |
| 155 | void filterBarSetChecked(bool state); | 164 | void filterBarSetChecked(bool state); |
| 156 | void UpdateUITheme(); | 165 | void UpdateUITheme(); |
| @@ -433,11 +442,14 @@ private: | |||
| 433 | bool ConfirmShutdownGame(); | 442 | bool ConfirmShutdownGame(); |
| 434 | 443 | ||
| 435 | QString GetTasStateDescription() const; | 444 | QString GetTasStateDescription() const; |
| 436 | bool CreateShortcut(const std::string& shortcut_path, const std::string& title, | 445 | bool CreateShortcutMessagesGUI(QWidget* parent, const int& imsg, const std::string title); |
| 437 | const std::string& comment, const std::string& icon_path, | 446 | bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, |
| 438 | const std::string& command, const std::string& arguments, | 447 | std::filesystem::path& icons_path); |
| 439 | const std::string& categories, const std::string& keywords); | 448 | bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, |
| 440 | 449 | const std::filesystem::path& icon_path, | |
| 450 | const std::filesystem::path& command, const std::string& arguments, | ||
| 451 | const std::string& categories, const std::string& keywords, | ||
| 452 | const std::string& name); | ||
| 441 | /** | 453 | /** |
| 442 | * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog | 454 | * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog |
| 443 | * The only difference is that it returns a boolean. | 455 | * 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..4d199ebd1 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 | ||
| 45 | bool SaveIconToFile(const std::string_view path, const QImage& image) { | 45 | bool SaveIconToFile(const QImage& image, const std::filesystem::path& icon_path) { |
| 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,7 +135,16 @@ 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 | #else | 138 | #elif defined(__linux__) || defined(__FreeBSD__) |
| 139 | return false; | 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 | |||
| 146 | return true; | ||
| 140 | #endif | 147 | #endif |
| 148 | |||
| 149 | return false; | ||
| 141 | } | 150 | } |
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h index 09c14ce3f..8839e160a 100644 --- a/src/yuzu/util/util.h +++ b/src/yuzu/util/util.h | |||
| @@ -3,26 +3,36 @@ | |||
| 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 | ||
| 9 | /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. | 10 | /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. |
| 11 | |||
| 10 | [[nodiscard]] QFont GetMonospaceFont(); | 12 | [[nodiscard]] QFont GetMonospaceFont(); |
| 11 | 13 | ||
| 12 | /// Convert a size in bytes into a readable format (KiB, MiB, etc.) | 14 | /// Convert a size in bytes into a readable format (KiB, MiB, etc.) |
| 15 | |||
| 13 | [[nodiscard]] QString ReadableByteSize(qulonglong size); | 16 | [[nodiscard]] QString ReadableByteSize(qulonglong size); |
| 14 | 17 | ||
| 15 | /** | 18 | /** |
| 16 | * Creates a circle pixmap from a specified color | 19 | * Creates a circle pixmap from a specified color |
| 20 | * | ||
| 17 | * @param color The color the pixmap shall have | 21 | * @param color The color the pixmap shall have |
| 22 | * | ||
| 18 | * @return QPixmap circle pixmap | 23 | * @return QPixmap circle pixmap |
| 19 | */ | 24 | */ |
| 25 | |||
| 20 | [[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color); | 26 | [[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color); |
| 21 | 27 | ||
| 22 | /** | 28 | /** |
| 23 | * Saves a windows icon to a file | 29 | * Saves a windows icon to a file |
| 24 | * @param path The icons path | 30 | * |
| 25 | * @param image The image to save | 31 | * @param image The image to save |
| 32 | * | ||
| 33 | * @param path The icons path | ||
| 34 | * | ||
| 26 | * @return bool If the operation succeeded | 35 | * @return bool If the operation succeeded |
| 27 | */ | 36 | */ |
| 28 | [[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image); | 37 | |
| 38 | [[nodiscard]] bool SaveIconToFile(const QImage& image, const std::filesystem::path& icon_path); | ||