summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/fs/fs_paths.h1
-rw-r--r--src/common/fs/path_util.cpp1
-rw-r--r--src/common/fs/path_util.h1
-rw-r--r--src/yuzu/game_list.cpp4
-rw-r--r--src/yuzu/main.cpp85
-rw-r--r--src/yuzu/util/util.cpp77
-rw-r--r--src/yuzu/util/util.h14
7 files changed, 157 insertions, 26 deletions
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 59d66f71e..441c8af97 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -23,6 +23,7 @@
23#define SDMC_DIR "sdmc" 23#define SDMC_DIR "sdmc"
24#define SHADER_DIR "shader" 24#define SHADER_DIR "shader"
25#define TAS_DIR "tas" 25#define TAS_DIR "tas"
26#define ICONS_DIR "icons"
26 27
27// yuzu-specific files 28// yuzu-specific files
28 29
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index 84ed0ad10..0abd81a45 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -129,6 +129,7 @@ public:
129 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); 129 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
130 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); 130 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
131 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); 131 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
132 GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
132 } 133 }
133 134
134private: 135private:
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 289974e32..63801c924 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -25,6 +25,7 @@ enum class YuzuPath {
25 SDMCDir, // Where the emulated SDMC is stored. 25 SDMCDir, // Where the emulated SDMC is stored.
26 ShaderDir, // Where shaders are stored. 26 ShaderDir, // Where shaders are stored.
27 TASDir, // Where TAS scripts are stored. 27 TASDir, // Where TAS scripts are stored.
28 IconsDir, // Where Icons for Windows shortcuts are stored.
28}; 29};
29 30
30/** 31/**
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 3f04fcb66..74f48031a 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -564,9 +564,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
564 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); 564 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
565 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 565 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
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#ifndef WIN32
568 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); 567 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
569 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 = 570 QAction* create_applications_menu_shortcut =
571 shortcut_menu->addAction(tr("Add to Applications Menu")); 571 shortcut_menu->addAction(tr("Add to Applications Menu"));
572#endif 572#endif
@@ -644,10 +644,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
644 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 644 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
645 emit NavigateToGamedbEntryRequested(program_id, compatibility_list); 645 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
646 }); 646 });
647#ifndef WIN32
648 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { 647 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
649 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); 648 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
650 }); 649 });
650#ifndef WIN32
651 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { 651 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
652 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); 652 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
653 }); 653 });
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index e0bfa7185..89361fa3f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -98,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
98#include "common/scm_rev.h" 98#include "common/scm_rev.h"
99#include "common/scope_exit.h" 99#include "common/scope_exit.h"
100#ifdef _WIN32 100#ifdef _WIN32
101#include <shlobj.h>
101#include "common/windows/timer_resolution.h" 102#include "common/windows/timer_resolution.h"
102#endif 103#endif
103#ifdef ARCHITECTURE_x86_64 104#ifdef ARCHITECTURE_x86_64
@@ -2842,7 +2843,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2842 const QStringList args = QApplication::arguments(); 2843 const QStringList args = QApplication::arguments();
2843 std::filesystem::path yuzu_command = args[0].toStdString(); 2844 std::filesystem::path yuzu_command = args[0].toStdString();
2844 2845
2845#if defined(__linux__) || defined(__FreeBSD__)
2846 // If relative path, make it an absolute path 2846 // If relative path, make it an absolute path
2847 if (yuzu_command.c_str()[0] == '.') { 2847 if (yuzu_command.c_str()[0] == '.') {
2848 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; 2848 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2865,12 +2865,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2865 UISettings::values.shortcut_already_warned = true; 2865 UISettings::values.shortcut_already_warned = true;
2866 } 2866 }
2867#endif // __linux__ 2867#endif // __linux__
2868#endif // __linux__ || __FreeBSD__
2869 2868
2870 std::filesystem::path target_directory{}; 2869 std::filesystem::path target_directory{};
2871 // Determine target directory for shortcut 2870 // Determine target directory for shortcut
2872#if defined(__linux__) || defined(__FreeBSD__) 2871#if defined(WIN32)
2872 const char* home = std::getenv("USERPROFILE");
2873#else
2873 const char* home = std::getenv("HOME"); 2874 const char* home = std::getenv("HOME");
2875#endif
2874 const std::filesystem::path home_path = (home == nullptr ? "~" : home); 2876 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2875 const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); 2877 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2876 2878
@@ -2880,7 +2882,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2880 QMessageBox::critical( 2882 QMessageBox::critical(
2881 this, tr("Create Shortcut"), 2883 this, tr("Create Shortcut"),
2882 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") 2884 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
2883 .arg(QString::fromStdString(target_directory)), 2885 .arg(QString::fromStdString(target_directory.generic_string())),
2884 QMessageBox::StandardButton::Ok); 2886 QMessageBox::StandardButton::Ok);
2885 return; 2887 return;
2886 } 2888 }
@@ -2888,15 +2890,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2888 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / 2890 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
2889 "applications"; 2891 "applications";
2890 if (!Common::FS::CreateDirs(target_directory)) { 2892 if (!Common::FS::CreateDirs(target_directory)) {
2891 QMessageBox::critical(this, tr("Create Shortcut"), 2893 QMessageBox::critical(
2892 tr("Cannot create shortcut in applications menu. Path \"%1\" " 2894 this, tr("Create Shortcut"),
2893 "does not exist and cannot be created.") 2895 tr("Cannot create shortcut in applications menu. Path \"%1\" "
2894 .arg(QString::fromStdString(target_directory)), 2896 "does not exist and cannot be created.")
2895 QMessageBox::StandardButton::Ok); 2897 .arg(QString::fromStdString(target_directory.generic_string())),
2898 QMessageBox::StandardButton::Ok);
2896 return; 2899 return;
2897 } 2900 }
2898 } 2901 }
2899#endif
2900 2902
2901 const std::string game_file_name = std::filesystem::path(game_path).filename().string(); 2903 const std::string game_file_name = std::filesystem::path(game_path).filename().string();
2902 // Determine full paths for icon and shortcut 2904 // Determine full paths for icon and shortcut
@@ -2918,9 +2920,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2918 const std::filesystem::path shortcut_path = 2920 const std::filesystem::path shortcut_path =
2919 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) 2921 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
2920 : fmt::format("yuzu-{:016X}.desktop", program_id)); 2922 : fmt::format("yuzu-{:016X}.desktop", program_id));
2923#elif defined(WIN32)
2924 std::filesystem::path icons_path =
2925 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
2926 std::filesystem::path icon_path =
2927 icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
2928 : fmt::format("yuzu-{:016X}.ico", program_id)));
2921#else 2929#else
2922 const std::filesystem::path icon_path{}; 2930 std::string icon_extension;
2923 const std::filesystem::path shortcut_path{};
2924#endif 2931#endif
2925 2932
2926 // Get title from game file 2933 // Get title from game file
@@ -2945,29 +2952,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2945 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); 2952 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
2946 } 2953 }
2947 2954
2948 QImage icon_jpeg = 2955 QImage icon_data =
2949 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); 2956 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
2950#if defined(__linux__) || defined(__FreeBSD__) 2957#if defined(__linux__) || defined(__FreeBSD__)
2951 // Convert and write the icon as a PNG 2958 // Convert and write the icon as a PNG
2952 if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) { 2959 if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
2953 LOG_ERROR(Frontend, "Could not write icon as PNG to file"); 2960 LOG_ERROR(Frontend, "Could not write icon as PNG to file");
2954 } else { 2961 } else {
2955 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); 2962 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
2956 } 2963 }
2964#elif defined(WIN32)
2965 if (!SaveIconToFile(icon_path.string(), icon_data)) {
2966 LOG_ERROR(Frontend, "Could not write icon to file");
2967 return;
2968 }
2957#endif // __linux__ 2969#endif // __linux__
2958 2970
2959#if defined(__linux__) || defined(__FreeBSD__) 2971#ifdef _WIN32
2972 // Replace characters that are illegal in Windows filenames by a dash
2973 const std::string illegal_chars = "<>:\"/\\|?*";
2974 for (char c : illegal_chars) {
2975 std::replace(title.begin(), title.end(), c, '_');
2976 }
2977 const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
2978#endif
2979
2960 const std::string comment = 2980 const std::string comment =
2961 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); 2981 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
2962 const std::string arguments = fmt::format("-g \"{:s}\"", game_path); 2982 const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2963 const std::string categories = "Game;Emulator;Qt;"; 2983 const std::string categories = "Game;Emulator;Qt;";
2964 const std::string keywords = "Switch;Nintendo;"; 2984 const std::string keywords = "Switch;Nintendo;";
2965#else 2985
2966 const std::string comment{};
2967 const std::string arguments{};
2968 const std::string categories{};
2969 const std::string keywords{};
2970#endif
2971 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), 2986 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
2972 yuzu_command.string(), arguments, categories, keywords)) { 2987 yuzu_command.string(), arguments, categories, keywords)) {
2973 QMessageBox::critical(this, tr("Create Shortcut"), 2988 QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3989,6 +4004,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
3989 shortcut_stream.close(); 4004 shortcut_stream.close();
3990 4005
3991 return true; 4006 return true;
4007#elif defined(WIN32)
4008 IShellLinkW* shell_link;
4009 auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
4010 (void**)&shell_link);
4011 if (FAILED(hres)) {
4012 return false;
4013 }
4014 shell_link->SetPath(
4015 Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
4016 shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
4017 shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
4018 shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
4019
4020 IPersistFile* persist_file;
4021 hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
4022 if (FAILED(hres)) {
4023 return false;
4024 }
4025
4026 hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
4027 if (FAILED(hres)) {
4028 return false;
4029 }
4030
4031 persist_file->Release();
4032 shell_link->Release();
4033
4034 return true;
3992#endif 4035#endif
3993 return false; 4036 return false;
3994} 4037}
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..61cf00176 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -5,6 +5,10 @@
5#include <cmath> 5#include <cmath>
6#include <QPainter> 6#include <QPainter>
7#include "yuzu/util/util.h" 7#include "yuzu/util/util.h"
8#ifdef _WIN32
9#include <windows.h>
10#include "common/fs/file.h"
11#endif
8 12
9QFont GetMonospaceFont() { 13QFont GetMonospaceFont() {
10 QFont font(QStringLiteral("monospace")); 14 QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,76 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
37 painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); 41 painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
38 return circle_pixmap; 42 return circle_pixmap;
39} 43}
44
45bool SaveIconToFile(const std::string_view path, const QImage& image) {
46#if defined(WIN32)
47#pragma pack(push, 2)
48 struct IconDir {
49 WORD id_reserved;
50 WORD id_type;
51 WORD id_count;
52 };
53
54 struct IconDirEntry {
55 BYTE width;
56 BYTE height;
57 BYTE color_count;
58 BYTE reserved;
59 WORD planes;
60 WORD bit_count;
61 DWORD bytes_in_res;
62 DWORD image_offset;
63 };
64#pragma pack(pop)
65
66 QImage source_image = image.convertToFormat(QImage::Format_RGB32);
67 constexpr int bytes_per_pixel = 4;
68 const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
69
70 BITMAPINFOHEADER info_header{};
71 info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
72 info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
73 info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
74
75 const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
76 const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
77 .height = static_cast<BYTE>(source_image.height() * 2),
78 .color_count = 0,
79 .reserved = 0,
80 .planes = 1,
81 .bit_count = bytes_per_pixel * 8,
82 .bytes_in_res =
83 static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
84 .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
85
86 Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
87 Common::FS::FileType::BinaryFile);
88 if (!icon_file.IsOpen()) {
89 return false;
90 }
91
92 if (!icon_file.Write(icon_dir)) {
93 return false;
94 }
95 if (!icon_file.Write(icon_entry)) {
96 return false;
97 }
98 if (!icon_file.Write(info_header)) {
99 return false;
100 }
101
102 for (int y = 0; y < image.height(); y++) {
103 const auto* line = source_image.scanLine(source_image.height() - 1 - y);
104 std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
105 std::memcpy(line_data.data(), line, line_data.size());
106 if (!icon_file.Write(line_data)) {
107 return false;
108 }
109 }
110 icon_file.Close();
111
112 return true;
113#else
114 return false;
115#endif
116}
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 39dd2d895..09c14ce3f 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -7,14 +7,22 @@
7#include <QString> 7#include <QString>
8 8
9/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. 9/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
10QFont GetMonospaceFont(); 10[[nodiscard]] QFont GetMonospaceFont();
11 11
12/// Convert a size in bytes into a readable format (KiB, MiB, etc.) 12/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
13QString ReadableByteSize(qulonglong size); 13[[nodiscard]] QString ReadableByteSize(qulonglong size);
14 14
15/** 15/**
16 * Creates a circle pixmap from a specified color 16 * Creates a circle pixmap from a specified color
17 * @param color The color the pixmap shall have 17 * @param color The color the pixmap shall have
18 * @return QPixmap circle pixmap 18 * @return QPixmap circle pixmap
19 */ 19 */
20QPixmap CreateCirclePixmapFromColor(const QColor& color); 20[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
21
22/**
23 * Saves a windows icon to a file
24 * @param path The icons path
25 * @param image The image to save
26 * @return bool If the operation succeeded
27 */
28[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);