summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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 61bac9eba..c54ce7654 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -22,6 +22,7 @@
22#define SDMC_DIR "sdmc" 22#define SDMC_DIR "sdmc"
23#define SHADER_DIR "shader" 23#define SHADER_DIR "shader"
24#define TAS_DIR "tas" 24#define TAS_DIR "tas"
25#define ICONS_DIR "icons"
25 26
26// yuzu-specific files 27// yuzu-specific files
27 28
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index dce219fcf..461c170f7 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -128,6 +128,7 @@ public:
128 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); 128 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
129 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); 129 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
130 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); 130 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
131 GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
131 } 132 }
132 133
133private: 134private:
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index ba28964d0..61593bdf7 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -24,6 +24,7 @@ enum class YuzuPath {
24 SDMCDir, // Where the emulated SDMC is stored. 24 SDMCDir, // Where the emulated SDMC is stored.
25 ShaderDir, // Where shaders are stored. 25 ShaderDir, // Where shaders are stored.
26 TASDir, // Where TAS scripts are stored. 26 TASDir, // Where TAS scripts are stored.
27 IconsDir, // Where Icons for Windows shortcuts are stored.
27}; 28};
28 29
29/** 30/**
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f254c1e1c..a9f19e316 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -560,9 +560,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
560 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); 560 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
561 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 561 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
562 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 562 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
563#ifndef WIN32
564 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); 563 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
565 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); 564 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
565#ifndef WIN32
566 QAction* create_applications_menu_shortcut = 566 QAction* create_applications_menu_shortcut =
567 shortcut_menu->addAction(tr("Add to Applications Menu")); 567 shortcut_menu->addAction(tr("Add to Applications Menu"));
568#endif 568#endif
@@ -638,10 +638,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
638 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 638 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
639 emit NavigateToGamedbEntryRequested(program_id, compatibility_list); 639 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
640 }); 640 });
641#ifndef WIN32
642 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { 641 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
643 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); 642 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
644 }); 643 });
644#ifndef WIN32
645 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { 645 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
646 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); 646 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
647 }); 647 });
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 16fa92e2c..7a93921d7 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
@@ -2825,7 +2826,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2825 const QStringList args = QApplication::arguments(); 2826 const QStringList args = QApplication::arguments();
2826 std::filesystem::path yuzu_command = args[0].toStdString(); 2827 std::filesystem::path yuzu_command = args[0].toStdString();
2827 2828
2828#if defined(__linux__) || defined(__FreeBSD__)
2829 // If relative path, make it an absolute path 2829 // If relative path, make it an absolute path
2830 if (yuzu_command.c_str()[0] == '.') { 2830 if (yuzu_command.c_str()[0] == '.') {
2831 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; 2831 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2848,12 +2848,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2848 UISettings::values.shortcut_already_warned = true; 2848 UISettings::values.shortcut_already_warned = true;
2849 } 2849 }
2850#endif // __linux__ 2850#endif // __linux__
2851#endif // __linux__ || __FreeBSD__
2852 2851
2853 std::filesystem::path target_directory{}; 2852 std::filesystem::path target_directory{};
2854 // Determine target directory for shortcut 2853 // Determine target directory for shortcut
2855#if defined(__linux__) || defined(__FreeBSD__) 2854#if defined(WIN32)
2855 const char* home = std::getenv("USERPROFILE");
2856#else
2856 const char* home = std::getenv("HOME"); 2857 const char* home = std::getenv("HOME");
2858#endif
2857 const std::filesystem::path home_path = (home == nullptr ? "~" : home); 2859 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2858 const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); 2860 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2859 2861
@@ -2863,7 +2865,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2863 QMessageBox::critical( 2865 QMessageBox::critical(
2864 this, tr("Create Shortcut"), 2866 this, tr("Create Shortcut"),
2865 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") 2867 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
2866 .arg(QString::fromStdString(target_directory)), 2868 .arg(QString::fromStdString(target_directory.generic_string())),
2867 QMessageBox::StandardButton::Ok); 2869 QMessageBox::StandardButton::Ok);
2868 return; 2870 return;
2869 } 2871 }
@@ -2871,15 +2873,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2871 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / 2873 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
2872 "applications"; 2874 "applications";
2873 if (!Common::FS::CreateDirs(target_directory)) { 2875 if (!Common::FS::CreateDirs(target_directory)) {
2874 QMessageBox::critical(this, tr("Create Shortcut"), 2876 QMessageBox::critical(
2875 tr("Cannot create shortcut in applications menu. Path \"%1\" " 2877 this, tr("Create Shortcut"),
2876 "does not exist and cannot be created.") 2878 tr("Cannot create shortcut in applications menu. Path \"%1\" "
2877 .arg(QString::fromStdString(target_directory)), 2879 "does not exist and cannot be created.")
2878 QMessageBox::StandardButton::Ok); 2880 .arg(QString::fromStdString(target_directory.generic_string())),
2881 QMessageBox::StandardButton::Ok);
2879 return; 2882 return;
2880 } 2883 }
2881 } 2884 }
2882#endif
2883 2885
2884 const std::string game_file_name = std::filesystem::path(game_path).filename().string(); 2886 const std::string game_file_name = std::filesystem::path(game_path).filename().string();
2885 // Determine full paths for icon and shortcut 2887 // Determine full paths for icon and shortcut
@@ -2901,9 +2903,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2901 const std::filesystem::path shortcut_path = 2903 const std::filesystem::path shortcut_path =
2902 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) 2904 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
2903 : fmt::format("yuzu-{:016X}.desktop", program_id)); 2905 : fmt::format("yuzu-{:016X}.desktop", program_id));
2906#elif defined(WIN32)
2907 std::filesystem::path icons_path =
2908 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
2909 std::filesystem::path icon_path =
2910 icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
2911 : fmt::format("yuzu-{:016X}.ico", program_id)));
2904#else 2912#else
2905 const std::filesystem::path icon_path{}; 2913 std::string icon_extension;
2906 const std::filesystem::path shortcut_path{};
2907#endif 2914#endif
2908 2915
2909 // Get title from game file 2916 // Get title from game file
@@ -2928,29 +2935,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2928 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); 2935 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
2929 } 2936 }
2930 2937
2931 QImage icon_jpeg = 2938 QImage icon_data =
2932 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); 2939 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
2933#if defined(__linux__) || defined(__FreeBSD__) 2940#if defined(__linux__) || defined(__FreeBSD__)
2934 // Convert and write the icon as a PNG 2941 // Convert and write the icon as a PNG
2935 if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) { 2942 if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
2936 LOG_ERROR(Frontend, "Could not write icon as PNG to file"); 2943 LOG_ERROR(Frontend, "Could not write icon as PNG to file");
2937 } else { 2944 } else {
2938 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); 2945 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
2939 } 2946 }
2947#elif defined(WIN32)
2948 if (!SaveIconToFile(icon_path.string(), icon_data)) {
2949 LOG_ERROR(Frontend, "Could not write icon to file");
2950 return;
2951 }
2940#endif // __linux__ 2952#endif // __linux__
2941 2953
2942#if defined(__linux__) || defined(__FreeBSD__) 2954#ifdef _WIN32
2955 // Replace characters that are illegal in Windows filenames by a dash
2956 const std::string illegal_chars = "<>:\"/\\|?*";
2957 for (char c : illegal_chars) {
2958 std::replace(title.begin(), title.end(), c, '_');
2959 }
2960 const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
2961#endif
2962
2943 const std::string comment = 2963 const std::string comment =
2944 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); 2964 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
2945 const std::string arguments = fmt::format("-g \"{:s}\"", game_path); 2965 const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2946 const std::string categories = "Game;Emulator;Qt;"; 2966 const std::string categories = "Game;Emulator;Qt;";
2947 const std::string keywords = "Switch;Nintendo;"; 2967 const std::string keywords = "Switch;Nintendo;";
2948#else 2968
2949 const std::string comment{};
2950 const std::string arguments{};
2951 const std::string categories{};
2952 const std::string keywords{};
2953#endif
2954 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), 2969 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
2955 yuzu_command.string(), arguments, categories, keywords)) { 2970 yuzu_command.string(), arguments, categories, keywords)) {
2956 QMessageBox::critical(this, tr("Create Shortcut"), 2971 QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3965,6 +3980,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
3965 shortcut_stream.close(); 3980 shortcut_stream.close();
3966 3981
3967 return true; 3982 return true;
3983#elif defined(WIN32)
3984 IShellLinkW* shell_link;
3985 auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
3986 (void**)&shell_link);
3987 if (FAILED(hres)) {
3988 return false;
3989 }
3990 shell_link->SetPath(
3991 Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
3992 shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
3993 shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
3994 shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
3995
3996 IPersistFile* persist_file;
3997 hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
3998 if (FAILED(hres)) {
3999 return false;
4000 }
4001
4002 hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
4003 if (FAILED(hres)) {
4004 return false;
4005 }
4006
4007 persist_file->Release();
4008 shell_link->Release();
4009
4010 return true;
3968#endif 4011#endif
3969 return false; 4012 return false;
3970} 4013}
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);