mirror of
				https://git.tardis.systems/mirrors/yuzu
				synced 2025-10-31 18:54:14 +01:00 
			
		
		
		
	yuzu: Add desktop shortcut support for Windows
Allows creating desktop shortcuts with icons for yuzu games. Co-Authored-By: Jeroen van Schijndel <13182141+roenyroeny@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									7a0da729b4
								
							
						
					
					
						commit
						9ef9ca0927
					
				| @ -22,6 +22,7 @@ | |||||||
| #define SDMC_DIR "sdmc" | #define SDMC_DIR "sdmc" | ||||||
| #define SHADER_DIR "shader" | #define SHADER_DIR "shader" | ||||||
| #define TAS_DIR "tas" | #define TAS_DIR "tas" | ||||||
|  | #define ICONS_DIR "icons" | ||||||
| 
 | 
 | ||||||
| // yuzu-specific files
 | // yuzu-specific files
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -128,6 +128,7 @@ public: | |||||||
|         GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); |         GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); | ||||||
|         GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); |         GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); | ||||||
|         GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); |         GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); | ||||||
|  |         GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  | |||||||
| @ -24,6 +24,7 @@ enum class YuzuPath { | |||||||
|     SDMCDir,        // Where the emulated SDMC is stored.
 |     SDMCDir,        // Where the emulated SDMC is stored.
 | ||||||
|     ShaderDir,      // Where shaders are stored.
 |     ShaderDir,      // Where shaders are stored.
 | ||||||
|     TASDir,         // Where TAS scripts are stored.
 |     TASDir,         // Where TAS scripts are stored.
 | ||||||
|  |     IconsDir,       // Where Icons for Windows shortcuts are stored.
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  | |||||||
| @ -560,9 +560,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||||||
|     QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); |     QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); | ||||||
|     QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); |     QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | ||||||
|     QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |     QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | ||||||
| #ifndef WIN32 |  | ||||||
|     QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); |     QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); | ||||||
|     QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); |     QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); | ||||||
|  | #ifndef WIN32 | ||||||
|     QAction* create_applications_menu_shortcut = |     QAction* create_applications_menu_shortcut = | ||||||
|         shortcut_menu->addAction(tr("Add to Applications Menu")); |         shortcut_menu->addAction(tr("Add to Applications Menu")); | ||||||
| #endif | #endif | ||||||
| @ -638,10 +638,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||||||
|     connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { |     connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { | ||||||
|         emit NavigateToGamedbEntryRequested(program_id, compatibility_list); |         emit NavigateToGamedbEntryRequested(program_id, compatibility_list); | ||||||
|     }); |     }); | ||||||
| #ifndef WIN32 |  | ||||||
|     connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { |     connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { | ||||||
|         emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); |         emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); | ||||||
|     }); |     }); | ||||||
|  | #ifndef WIN32 | ||||||
|     connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { |     connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { | ||||||
|         emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); |         emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); | ||||||
|     }); |     }); | ||||||
|  | |||||||
| @ -98,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||||||
| #include "common/scm_rev.h" | #include "common/scm_rev.h" | ||||||
| #include "common/scope_exit.h" | #include "common/scope_exit.h" | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|  | #include <shlobj.h> | ||||||
| #include "common/windows/timer_resolution.h" | #include "common/windows/timer_resolution.h" | ||||||
| #endif | #endif | ||||||
| #ifdef ARCHITECTURE_x86_64 | #ifdef ARCHITECTURE_x86_64 | ||||||
| @ -2825,7 +2826,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga | |||||||
|     const QStringList args = QApplication::arguments(); |     const QStringList args = QApplication::arguments(); | ||||||
|     std::filesystem::path yuzu_command = args[0].toStdString(); |     std::filesystem::path yuzu_command = args[0].toStdString(); | ||||||
| 
 | 
 | ||||||
| #if defined(__linux__) || defined(__FreeBSD__) |  | ||||||
|     // If relative path, make it an absolute path
 |     // If relative path, make it an absolute path
 | ||||||
|     if (yuzu_command.c_str()[0] == '.') { |     if (yuzu_command.c_str()[0] == '.') { | ||||||
|         yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; |         yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; | ||||||
| @ -2848,12 +2848,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga | |||||||
|         UISettings::values.shortcut_already_warned = true; |         UISettings::values.shortcut_already_warned = true; | ||||||
|     } |     } | ||||||
| #endif // __linux__
 | #endif // __linux__
 | ||||||
| #endif // __linux__ || __FreeBSD__
 |  | ||||||
| 
 | 
 | ||||||
|     std::filesystem::path target_directory{}; |     std::filesystem::path target_directory{}; | ||||||
|     // Determine target directory for shortcut
 |     // Determine target directory for shortcut
 | ||||||
| #if defined(__linux__) || defined(__FreeBSD__) | #if defined(WIN32) | ||||||
|  |     const char* home = std::getenv("USERPROFILE"); | ||||||
|  | #else | ||||||
|     const char* home = std::getenv("HOME"); |     const char* home = std::getenv("HOME"); | ||||||
|  | #endif | ||||||
|     const std::filesystem::path home_path = (home == nullptr ? "~" : home); |     const std::filesystem::path home_path = (home == nullptr ? "~" : home); | ||||||
|     const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); |     const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); | ||||||
| 
 | 
 | ||||||
| @ -2863,7 +2865,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga | |||||||
|             QMessageBox::critical( |             QMessageBox::critical( | ||||||
|                 this, tr("Create Shortcut"), |                 this, tr("Create Shortcut"), | ||||||
|                 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") |                 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") | ||||||
|                     .arg(QString::fromStdString(target_directory)), |                     .arg(QString::fromStdString(target_directory.generic_string())), | ||||||
|                 QMessageBox::StandardButton::Ok); |                 QMessageBox::StandardButton::Ok); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -2871,15 +2873,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga | |||||||
|         target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / |         target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / | ||||||
|                            "applications"; |                            "applications"; | ||||||
|         if (!Common::FS::CreateDirs(target_directory)) { |         if (!Common::FS::CreateDirs(target_directory)) { | ||||||
|             QMessageBox::critical(this, tr("Create Shortcut"), |             QMessageBox::critical( | ||||||
|                                   tr("Cannot create shortcut in applications menu. Path \"%1\" " |                 this, tr("Create Shortcut"), | ||||||
|                                      "does not exist and cannot be created.") |                 tr("Cannot create shortcut in applications menu. Path \"%1\" " | ||||||
|                                       .arg(QString::fromStdString(target_directory)), |                    "does not exist and cannot be created.") | ||||||
|                                   QMessageBox::StandardButton::Ok); |                     .arg(QString::fromStdString(target_directory.generic_string())), | ||||||
|  |                 QMessageBox::StandardButton::Ok); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
|     const std::string game_file_name = std::filesystem::path(game_path).filename().string(); |     const std::string game_file_name = std::filesystem::path(game_path).filename().string(); | ||||||
|     // Determine full paths for icon and shortcut
 |     // Determine full paths for icon and shortcut
 | ||||||
| @ -2901,9 +2903,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga | |||||||
|     const std::filesystem::path shortcut_path = |     const std::filesystem::path shortcut_path = | ||||||
|         target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) |         target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) | ||||||
|                                             : fmt::format("yuzu-{:016X}.desktop", program_id)); |                                             : fmt::format("yuzu-{:016X}.desktop", program_id)); | ||||||
|  | #elif defined(WIN32) | ||||||
|  |     std::filesystem::path icons_path = | ||||||
|  |         Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir); | ||||||
|  |     std::filesystem::path icon_path = | ||||||
|  |         icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name) | ||||||
|  |                                        : fmt::format("yuzu-{:016X}.ico", program_id))); | ||||||
| #else | #else | ||||||
|     const std::filesystem::path icon_path{}; |     std::string icon_extension; | ||||||
|     const std::filesystem::path shortcut_path{}; |  | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|     // Get title from game file
 |     // Get title from game file
 | ||||||
| @ -2928,29 +2935,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga | |||||||
|         LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); |         LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     QImage icon_jpeg = |     QImage icon_data = | ||||||
|         QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); |         QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); | ||||||
| #if defined(__linux__) || defined(__FreeBSD__) | #if defined(__linux__) || defined(__FreeBSD__) | ||||||
|     // Convert and write the icon as a PNG
 |     // Convert and write the icon as a PNG
 | ||||||
|     if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) { |     if (!icon_data.save(QString::fromStdString(icon_path.string()))) { | ||||||
|         LOG_ERROR(Frontend, "Could not write icon as PNG to file"); |         LOG_ERROR(Frontend, "Could not write icon as PNG to file"); | ||||||
|     } else { |     } else { | ||||||
|         LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); |         LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); | ||||||
|     } |     } | ||||||
|  | #elif defined(WIN32) | ||||||
|  |     if (!SaveIconToFile(icon_path.string(), icon_data)) { | ||||||
|  |         LOG_ERROR(Frontend, "Could not write icon to file"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
| #endif // __linux__
 | #endif // __linux__
 | ||||||
| 
 | 
 | ||||||
| #if defined(__linux__) || defined(__FreeBSD__) | #ifdef _WIN32 | ||||||
|  |     // Replace characters that are illegal in Windows filenames by a dash
 | ||||||
|  |     const std::string illegal_chars = "<>:\"/\\|?*"; | ||||||
|  |     for (char c : illegal_chars) { | ||||||
|  |         std::replace(title.begin(), title.end(), c, '_'); | ||||||
|  |     } | ||||||
|  |     const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str(); | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|     const std::string comment = |     const std::string comment = | ||||||
|         tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); |         tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); | ||||||
|     const std::string arguments = fmt::format("-g \"{:s}\"", game_path); |     const std::string arguments = fmt::format("-g \"{:s}\"", game_path); | ||||||
|     const std::string categories = "Game;Emulator;Qt;"; |     const std::string categories = "Game;Emulator;Qt;"; | ||||||
|     const std::string keywords = "Switch;Nintendo;"; |     const std::string keywords = "Switch;Nintendo;"; | ||||||
| #else | 
 | ||||||
|     const std::string comment{}; |  | ||||||
|     const std::string arguments{}; |  | ||||||
|     const std::string categories{}; |  | ||||||
|     const std::string keywords{}; |  | ||||||
| #endif |  | ||||||
|     if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), |     if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), | ||||||
|                         yuzu_command.string(), arguments, categories, keywords)) { |                         yuzu_command.string(), arguments, categories, keywords)) { | ||||||
|         QMessageBox::critical(this, tr("Create Shortcut"), |         QMessageBox::critical(this, tr("Create Shortcut"), | ||||||
| @ -3964,6 +3979,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st | |||||||
|     shortcut_stream << shortcut_contents; |     shortcut_stream << shortcut_contents; | ||||||
|     shortcut_stream.close(); |     shortcut_stream.close(); | ||||||
| 
 | 
 | ||||||
|  |     return true; | ||||||
|  | #elif defined(WIN32) | ||||||
|  |     IShellLinkW* shell_link; | ||||||
|  |     auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, | ||||||
|  |                                  (void**)&shell_link); | ||||||
|  |     if (FAILED(hres)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     shell_link->SetPath( | ||||||
|  |         Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
 | ||||||
|  |     shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data()); | ||||||
|  |     shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data()); | ||||||
|  |     shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0); | ||||||
|  | 
 | ||||||
|  |     IPersistFile* persist_file; | ||||||
|  |     hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); | ||||||
|  |     if (FAILED(hres)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE); | ||||||
|  |     if (FAILED(hres)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     persist_file->Release(); | ||||||
|  |     shell_link->Release(); | ||||||
|  | 
 | ||||||
|     return true; |     return true; | ||||||
| #endif | #endif | ||||||
|     return false; |     return false; | ||||||
|  | |||||||
| @ -5,6 +5,10 @@ | |||||||
| #include <cmath> | #include <cmath> | ||||||
| #include <QPainter> | #include <QPainter> | ||||||
| #include "yuzu/util/util.h" | #include "yuzu/util/util.h" | ||||||
|  | #ifdef _WIN32 | ||||||
|  | #include <windows.h> | ||||||
|  | #include "common/fs/file.h" | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| QFont GetMonospaceFont() { | QFont GetMonospaceFont() { | ||||||
|     QFont font(QStringLiteral("monospace")); |     QFont font(QStringLiteral("monospace")); | ||||||
| @ -37,3 +41,76 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) { | |||||||
|     painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); |     painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); | ||||||
|     return circle_pixmap; |     return circle_pixmap; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool SaveIconToFile(const std::string_view path, const QImage& image) { | ||||||
|  | #if defined(WIN32) | ||||||
|  | #pragma pack(push, 2) | ||||||
|  |     struct IconDir { | ||||||
|  |         WORD id_reserved; | ||||||
|  |         WORD id_type; | ||||||
|  |         WORD id_count; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     struct IconDirEntry { | ||||||
|  |         BYTE width; | ||||||
|  |         BYTE height; | ||||||
|  |         BYTE color_count; | ||||||
|  |         BYTE reserved; | ||||||
|  |         WORD planes; | ||||||
|  |         WORD bit_count; | ||||||
|  |         DWORD bytes_in_res; | ||||||
|  |         DWORD image_offset; | ||||||
|  |     }; | ||||||
|  | #pragma pack(pop) | ||||||
|  | 
 | ||||||
|  |     QImage source_image = image.convertToFormat(QImage::Format_RGB32); | ||||||
|  |     constexpr int bytes_per_pixel = 4; | ||||||
|  |     const int image_size = source_image.width() * source_image.height() * bytes_per_pixel; | ||||||
|  | 
 | ||||||
|  |     BITMAPINFOHEADER info_header{}; | ||||||
|  |     info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(), | ||||||
|  |     info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1, | ||||||
|  |     info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB; | ||||||
|  | 
 | ||||||
|  |     const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1}; | ||||||
|  |     const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()), | ||||||
|  |                                   .height = static_cast<BYTE>(source_image.height() * 2), | ||||||
|  |                                   .color_count = 0, | ||||||
|  |                                   .reserved = 0, | ||||||
|  |                                   .planes = 1, | ||||||
|  |                                   .bit_count = bytes_per_pixel * 8, | ||||||
|  |                                   .bytes_in_res = | ||||||
|  |                                       static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size), | ||||||
|  |                                   .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)}; | ||||||
|  | 
 | ||||||
|  |     Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write, | ||||||
|  |                                  Common::FS::FileType::BinaryFile); | ||||||
|  |     if (!icon_file.IsOpen()) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!icon_file.Write(icon_dir)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if (!icon_file.Write(icon_entry)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if (!icon_file.Write(info_header)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (int y = 0; y < image.height(); y++) { | ||||||
|  |         const auto* line = source_image.scanLine(source_image.height() - 1 - y); | ||||||
|  |         std::vector<u8> line_data(source_image.width() * bytes_per_pixel); | ||||||
|  |         std::memcpy(line_data.data(), line, line_data.size()); | ||||||
|  |         if (!icon_file.Write(line_data)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     icon_file.Close(); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | #else | ||||||
|  |     return false; | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | |||||||
| @ -7,14 +7,22 @@ | |||||||
| #include <QString> | #include <QString> | ||||||
| 
 | 
 | ||||||
| /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
 | /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
 | ||||||
| QFont GetMonospaceFont(); | [[nodiscard]] QFont GetMonospaceFont(); | ||||||
| 
 | 
 | ||||||
| /// Convert a size in bytes into a readable format (KiB, MiB, etc.)
 | /// Convert a size in bytes into a readable format (KiB, MiB, etc.)
 | ||||||
| QString ReadableByteSize(qulonglong size); | [[nodiscard]] QString ReadableByteSize(qulonglong size); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Creates a circle pixmap from a specified color |  * Creates a circle pixmap from a specified color | ||||||
|  * @param color The color the pixmap shall have |  * @param color The color the pixmap shall have | ||||||
|  * @return QPixmap circle pixmap |  * @return QPixmap circle pixmap | ||||||
|  */ |  */ | ||||||
| QPixmap CreateCirclePixmapFromColor(const QColor& color); | [[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Saves a windows icon to a file | ||||||
|  |  * @param path The icons path | ||||||
|  |  * @param image The image to save | ||||||
|  |  * @return bool If the operation succeeded | ||||||
|  |  */ | ||||||
|  | [[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user