Full shortcut Windows support + question for fullscreen mode

This commit is contained in:
boludoz 2023-10-03 20:14:08 -03:00
parent 7a0da729b4
commit 604b1b6c86
11 changed files with 541 additions and 36 deletions

View file

@ -22,6 +22,7 @@
#define SDMC_DIR "sdmc"
#define SHADER_DIR "shader"
#define TAS_DIR "tas"
#define ICONS_DIR "icons"
// yuzu-specific files

View file

@ -2,14 +2,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <iostream>
#include <string>
#include "common/fs/fs_util.h"
#include "common/polyfill_ranges.h"
namespace Common::FS {
std::u8string ToU8String(std::string_view utf8_string) {
return std::u8string{utf8_string.begin(), utf8_string.end()};
std::u8string ToU8String(std::string_view string) {
return std::u8string{reinterpret_cast<const char8_t*>(string.data())};
}
std::u8string ToU8String(std::wstring_view w_string) {
return std::u8string{reinterpret_cast<const char8_t*>(w_string.data())};
}
std::u8string BufferToU8String(std::span<const u8> buffer) {
@ -20,6 +26,10 @@ std::u8string_view BufferToU8StringView(std::span<const u8> buffer) {
return std::u8string_view{reinterpret_cast<const char8_t*>(buffer.data())};
}
std::wstring ToWString(std::u8string_view utf8_string) {
return std::wstring{utf8_string.begin(), utf8_string.end()};
}
std::string ToUTF8String(std::u8string_view u8_string) {
return std::string{u8_string.begin(), u8_string.end()};
}
@ -36,4 +46,72 @@ std::string PathToUTF8String(const std::filesystem::path& path) {
return ToUTF8String(path.u8string());
}
/*
std::u8string UTF8FilenameSantizer(std::u8string u8filename) {
std::u8string u8path_santized = u8filename;
size_t eSizeSanitized =
u8path_santized.size(); // Cambiado a size_t para coincidir con el tipo de i
// Special case for ":", for example: 'Pepe: La secuela' --> 'Pepe - La
// secuela' or 'Pepe : La secuela' --> 'Pepe - La secuela'
for (size_t i = 0; i < eSizeSanitized; i++) {
switch (u8path_santized[i]) {
case u8':':
if (i == 0 || i == eSizeSanitized - 1) {
u8path_santized.replace(i, 1, u8"_");
} else if (u8path_santized[i - 1] == u8' ') {
u8path_santized.replace(i, 1, u8"-");
} else {
u8path_santized.replace(i, 1, u8" -");
eSizeSanitized++;
}
break;
case u8'\\':
[[fallthrough]];
case u8'/':
[[fallthrough]];
case u8'*':
[[fallthrough]];
case u8'?':
[[fallthrough]];
case u8'\"':
[[fallthrough]];
case u8'<':
[[fallthrough]];
case u8'>':
[[fallthrough]];
case u8'|':
[[fallthrough]];
case u8'\0':
u8path_santized.replace(i, 1, u8"_");
break;
default:
break;
}
}
// Delete duplicated spaces || Delete duplicated dots (MacOS i think)
for (size_t i = 0; i < eSizeSanitized; i++) {
if ((u8path_santized[i] == u8' ' && u8path_santized[i + 1] == u8' ') ||
(u8path_santized[i] == u8'.' && u8path_santized[i + 1] == u8'.')) {
u8path_santized.erase(i, 1);
i--;
}
}
// Delete all spaces and dots at the end (Windows almost)
while (u8path_santized.back() == u8' ' || u8path_santized.back() == u8'.') {
u8path_santized.pop_back();
}
if (u8path_santized.empty()) {
return u8"";
}
return u8path_santized;
}
*/
} // namespace Common::FS

View file

@ -18,11 +18,28 @@ concept IsChar = std::same_as<T, char>;
/**
* Converts a UTF-8 encoded std::string or std::string_view to a std::u8string.
*
* @param utf8_string UTF-8 encoded string
* @param string
*
* @returns UTF-8 encoded std::u8string.
*/
[[nodiscard]] std::u8string ToU8String(std::string_view utf8_string);
[[nodiscard]] std::u8string ToU8String(std::string_view string);
/**
* Converts a std::wstring or std::wstring_view to a std::u8string.
*
* @param wide encoded string
*
* @returns UTF-8 encoded std::u8string.
*/
[[nodiscard]] std::u8string ToU8String(std::wstring_view w_string);
/** Converts a UTF-8 encoded std::u8string or std::u8string_view to a std::wstring.
*
* @param utf8_string UTF-8 encoded string
*
* @returns UTF-8 encoded std::wstring.
*/
[[nodiscard]] std::wstring ToWString(std::u8string_view utf8_string);
/**
* Converts a buffer of bytes to a UTF8-encoded std::u8string.
@ -82,4 +99,13 @@ concept IsChar = std::same_as<T, char>;
*/
[[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path);
} // namespace Common::FS
/**
* Fix filename (remove invalid characters)
* @param dirty UTF-8 encoded
*
* @returns UTF-8 encoded fixed
*
*/
// [[nodiscard]] std::u8string UTF8FilenameSantizer(std::u8string &u8filename);
} // namespace Common::FS

View file

@ -14,7 +14,7 @@
#include "common/logging/log.h"
#ifdef _WIN32
#include <shlobj.h> // Used in GetExeDirectory()
#include <shlobj.h> // Used in GetExeDirectory() and GetWindowsDesktop()
#else
#include <cstdlib> // Used in Get(Home/Data)Directory()
#include <pwd.h> // Used in GetHomeDirectory()
@ -128,6 +128,7 @@ public:
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
}
private:
@ -274,6 +275,39 @@ fs::path GetAppDataRoamingDirectory() {
return fs_appdata_roaming_path;
}
fs::path GetWindowsDesktopPath() {
PWSTR DesktopPath = nullptr;
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &DesktopPath))) {
std::wstring wideDesktopPath(DesktopPath);
CoTaskMemFree(DesktopPath);
return fs::path{wideDesktopPath};
} else {
LOG_ERROR(Common_Filesystem,
"[GetWindowsDesktopPath] Failed to get the path to the desktop directory");
}
return fs::path{};
}
fs::path GetWindowsAppShortcutsPath() {
PWSTR AppShortcutsPath = nullptr;
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_CommonPrograms, 0, NULL, &AppShortcutsPath))) {
std::wstring wideAppShortcutsPath(AppShortcutsPath);
CoTaskMemFree(AppShortcutsPath);
return fs::path{wideAppShortcutsPath};
} else {
LOG_ERROR(
Common_Filesystem,
"[GetWindowsAppShortcutsPath] Failed to get the path to the App Shortcuts directory");
}
return fs::path{};
}
#else
fs::path GetHomeDirectory() {

View file

@ -24,6 +24,7 @@ enum class YuzuPath {
SDMCDir, // Where the emulated SDMC is stored.
ShaderDir, // Where shaders are stored.
TASDir, // Where TAS scripts are stored.
IconsDir, // Where Icons for windows shortcuts are stored.
};
/**
@ -243,6 +244,20 @@ void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) {
*/
[[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory();
/**
* Gets the path of the current user's desktop directory.
*
* @returns The path of the current user's desktop directory.
*/
[[nodiscard]] std::filesystem::path GetWindowsDesktopPath();
/**
* Gets FOLDERID_ApplicationShortcuts directory path on Windows.
*
* @returns The path of the current user's FOLDERID_ApplicationShortcuts directory.
*/
[[nodiscard]] std::filesystem::path GetWindowsAppShortcutsPath();
#else
/**

View file

@ -512,7 +512,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
case GameListItemType::Game:
AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(),
selected.data(GameListItemPath::FullPathRole).toString().toStdString());
selected.data(GameListItemPath::FullPathRole).toString());
break;
case GameListItemType::CustomDir:
AddPermDirPopup(context_menu, selected);
@ -532,7 +532,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}
void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) {
void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const QString & qpath) {
const std::string path = qpath.toStdString();
QAction* favorite = context_menu.addAction(tr("Favorite"));
context_menu.addSeparator();
QAction* start_game = context_menu.addAction(tr("Start Game"));
@ -560,12 +561,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
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"));
#ifndef WIN32
QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
QAction* create_applications_menu_shortcut =
shortcut_menu->addAction(tr("Add to Applications Menu"));
#endif
context_menu.addSeparator();
QAction* properties = context_menu.addAction(tr("Properties"));
@ -638,14 +637,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
});
#ifndef WIN32
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, qpath]() {
emit CreateShortcut(program_id, qpath, GameListShortcutTarget::Desktop);
});
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, qpath]() {
emit CreateShortcut(program_id, qpath, GameListShortcutTarget::Applications);
});
#endif
connect(properties, &QAction::triggered,
[this, path]() { emit OpenPerGameGeneralRequested(path); });
};

View file

@ -116,7 +116,7 @@ signals:
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void VerifyIntegrityRequested(const std::string& game_path);
void CopyTIDRequested(u64 program_id);
void CreateShortcut(u64 program_id, const std::string& game_path,
void CreateShortcut(u64 program_id, const QString& game_path,
GameListShortcutTarget target);
void NavigateToGamedbEntryRequested(u64 program_id,
const CompatibilityList& compatibility_list);
@ -146,7 +146,7 @@ private:
void RemoveFavorite(u64 program_id);
void PopupContextMenu(const QPoint& menu_location);
void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path);
void AddGamePopup(QMenu& context_menu, u64 program_id, const QString& path);
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
void AddFavoritesPopup(QMenu& context_menu);

View file

@ -2819,18 +2819,86 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
}
void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
std::u8string UTF8FilenameSantizer(std::u8string u8filename) {
std::u8string u8path_santized = u8filename;
size_t eSizeSanitized =
u8path_santized.size(); // Cambiado a size_t para coincidir con el tipo de i
// Special case for ":", for example: 'Pepe: La secuela' --> 'Pepe - La
// secuela' or 'Pepe : La secuela' --> 'Pepe - La secuela'
for (size_t i = 0; i < eSizeSanitized; i++) {
switch (u8path_santized[i]) {
case u8':':
if (i == 0 || i == eSizeSanitized - 1) {
u8path_santized.replace(i, 1, u8"_");
} else if (u8path_santized[i - 1] == u8' ') {
u8path_santized.replace(i, 1, u8"-");
} else {
u8path_santized.replace(i, 1, u8" -");
eSizeSanitized++;
}
break;
case u8'\\':
[[fallthrough]];
case u8'/':
[[fallthrough]];
case u8'*':
[[fallthrough]];
case u8'?':
[[fallthrough]];
case u8'\"':
[[fallthrough]];
case u8'<':
[[fallthrough]];
case u8'>':
[[fallthrough]];
case u8'|':
[[fallthrough]];
case u8'\0':
u8path_santized.replace(i, 1, u8"_");
break;
default:
break;
}
}
// Delete duplicated spaces || Delete duplicated dots (MacOS i think)
for (size_t i = 0; i < eSizeSanitized; i++) {
if ((u8path_santized[i] == u8' ' && u8path_santized[i + 1] == u8' ') ||
(u8path_santized[i] == u8'.' && u8path_santized[i + 1] == u8'.')) {
u8path_santized.erase(i, 1);
i--;
}
}
// Delete all spaces and dots at the end (Windows almost)
while (u8path_santized.back() == u8' ' || u8path_santized.back() == u8'.') {
u8path_santized.pop_back();
}
if (u8path_santized.empty()) {
return u8"";
}
return u8path_santized;
}
void GMainWindow::OnGameListCreateShortcut(u64 program_id, const QString& game_path_q,
GameListShortcutTarget target) {
const std::string game_path = game_path_q.toStdString();
// Get path to yuzu executable
const QStringList args = QApplication::arguments();
std::filesystem::path yuzu_command = args[0].toStdString();
#if defined(__linux__) || defined(__FreeBSD__)
// If relative path, make it an absolute path
if (yuzu_command.c_str()[0] == '.') {
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
}
#if defined(__linux__) || defined(__FreeBSD__)
#if defined(__linux__)
// Warn once if we are making a shortcut to a volatile AppImage
const std::string appimage_ending =
@ -2851,6 +2919,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
#endif // __linux__ || __FreeBSD__
std::filesystem::path target_directory{};
// Determine target directory for shortcut
#if defined(__linux__) || defined(__FreeBSD__)
const char* home = std::getenv("HOME");
@ -2901,8 +2970,18 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
const std::filesystem::path shortcut_path =
target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
: fmt::format("yuzu-{:016X}.desktop", program_id));
#elif defined(_WIN32)
const std::filesystem::path IconYuzuPath =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::IconsDir);
std::u8string u8game_ico = UTF8FilenameSantizer(
Common::FS::ToU8String((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
: fmt::format("yuzu-{:016X}.ico", program_id))));
const std::filesystem::path IconPath = IconYuzuPath / (u8game_ico);
#else
const std::filesystem::path icon_path{};
const std::filesystem::path IconPath{};
const std::filesystem::path shortcut_path{};
#endif
@ -2939,30 +3018,99 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
}
#endif // __linux__
#if defined(__linux__) || defined(__FreeBSD__) || defined(_WIN32)
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No;
int result = QMessageBox::information(
this, tr("Create Shortcut"), tr("Do you want to launch the game in fullscreen?"), buttons);
#endif // __linux__ || __FreeBSD__ || _WIN32
#if defined(__linux__) || defined(__FreeBSD__)
if (result == QMessageBox::Yes) {
const std::string arguments = fmt::format("-f -g \"{:s}\"", game_path);
} else {
const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
}
const std::string comment =
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 categories = "Game;Emulator;Qt;";
const std::string keywords = "Switch;Nintendo;";
#elif defined(_WIN32)
const auto file_path =
std::filesystem::path{Common::U16StringFromBuffer(game_path_q.utf16(), game_path_q.size())};
std::u8string arguments = u8"-g \"" + std::filesystem::path(file_path).u8string() + u8"\"";
if (result == QMessageBox::Yes) {
arguments = u8"-f " + arguments;
}
auto qtcomment = tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title));
const std::u8string comment = Common::FS::ToU8String(qtcomment.toStdString());
std::u8string title_u8 = Common::FS::ToU8String(title);
title_u8 = UTF8FilenameSantizer(title_u8);
if (target == GameListShortcutTarget::Desktop) {
target_directory = Common::FS::GetWindowsDesktopPath();
} else {
target_directory = Common::FS::GetWindowsAppShortcutsPath();
}
const std::filesystem::path sanitized_title = title_u8 + u8".lnk";
const std::filesystem::path shortcut_path = target_directory / (sanitized_title);
bool is_icon_ok = SaveIconToFile(IconPath, icon_jpeg);
if (!is_icon_ok) {
LOG_ERROR(Frontend, "Could not write icon as ICO to file");
}
#else
/* TODO: UNIMPLEMENTED for other platforms
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(),
yuzu_command.string(), arguments, categories, keywords)) {
QMessageBox::critical(this, tr("Create Shortcut"),
tr("Failed to create a shortcut at %1")
.arg(QString::fromStdString(shortcut_path.string())));
return;
}
LOG_INFO(Frontend, "Wrote a shortcut to {}", shortcut_path.string());
QMessageBox::information(
this, tr("Create Shortcut"),
tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title)));
bool is_success = false;
#if defined(__linux__) || defined(__FreeBSD__)
if (CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
yuzu_command.string(), arguments, categories, keywords)) {
is_success = true;
}
#elif defined(_WIN32)
if (CreateShortcut(shortcut_path, comment, IconPath, yuzu_command.u8string(), arguments)) {
is_success = true;
}
#endif
if (is_success) {
LOG_INFO(Frontend, "Wrote a shortcut to {}", shortcut_path.string());
QMessageBox::information(
this, tr("Create Shortcut"),
tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title)));
} else {
#if defined(_WIN32)
if (GameListShortcutTarget::Applications == target) {
QMessageBox::critical(this, tr("Create Shortcut"),
tr("Failed to create a shortcut at %1, check your admin rights")
.arg(QString::fromStdString(title)));
return;
}
#endif // _WIN32
QMessageBox::critical(
this, tr("Create Shortcut"),
tr("Failed to create a shortcut at %1").arg(QString::fromStdString(title)));
}
}
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
@ -3937,11 +4085,11 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
}
}
#if defined(__linux__) || defined(__FreeBSD__)
bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title,
const std::string& comment, const std::string& icon_path,
const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords) {
#if defined(__linux__) || defined(__FreeBSD__)
// This desktop file template was writing referencing
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
std::string shortcut_contents{};
@ -3965,9 +4113,99 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
shortcut_stream.close();
return true;
#endif
}
#elif defined(_WIN32)
#include <filesystem>
#include <iostream>
#include <ShlObj.h>
#include <Windows.h>
bool GMainWindow::CreateShortcut(const std::filesystem::path& shortcut_path,
const std::u8string& comment,
const std::filesystem::path& icon_path,
const std::u8string& command, const std::u8string& arguments) {
auto wshortcut_path = Common::FS::ToWString(shortcut_path.u8string());
auto wcomment = Common::FS::ToWString(comment);
auto wicon_path = Common::FS::ToWString(icon_path.u8string());
if (!std::filesystem::exists(icon_path)) {
LOG_WARNING(Common_Filesystem, "[GMainWindow - CreateShortcut] Shortcut ico dont exists");
wicon_path = L"";
}
auto wcommand = Common::FS::ToWString(command);
auto warguments = Common::FS::ToWString(arguments);
// Initialize COM
CoInitialize(NULL);
IShellLinkW* pShellLink;
auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
(void**)&pShellLink);
if (FAILED(hres)) {
LOG_ERROR(Common_Filesystem,
"[GMainWindow - CreateShortcut] Failed to create IShellLinkW instance");
return false;
}
if (!wcommand.empty()) {
pShellLink->SetPath(wcommand.c_str());
}
if (!warguments.empty()) {
pShellLink->SetArguments(warguments.c_str());
}
if (!wcomment.empty()) {
pShellLink->SetDescription(wcomment.c_str());
}
if (!wicon_path.empty()) {
pShellLink->SetIconLocation(wicon_path.c_str(), 0);
}
IPersistFile* pPersistFile;
hres = pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile);
if (FAILED(hres)) {
LOG_ERROR(Common_Filesystem,
"[GMainWindow - CreateShortcut] Failed to create IPersistFile instance");
pShellLink->Release();
return false;
}
hres = pPersistFile->Save(wshortcut_path.c_str(), TRUE);
if (FAILED(hres)) {
LOG_ERROR(Common_Filesystem, "[GMainWindow - CreateShortcut] Failed to save shortcut");
pPersistFile->Release();
pShellLink->Release();
return false;
}
pPersistFile->Release();
pShellLink->Release();
// Uninitialize COM
CoUninitialize();
if (std::filesystem::exists(shortcut_path)) {
LOG_INFO(Common_Filesystem, "[GMainWindow - CreateShortcut] Shortcut created");
return true;
}
LOG_ERROR(Common_Filesystem, "[GMainWindow - CreateShortcut] Shortcut created but icon dont "
"exists, please check if the icon path is correct");
return false;
}
#else
bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title,
const std::string& comment, const std::string& icon_path,
const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords) {
return false;
}
#endif
void GMainWindow::OnLoadAmiibo() {
if (emu_thread == nullptr || !emu_thread->IsRunning()) {

View file

@ -6,6 +6,9 @@
#include <memory>
#include <optional>
#include <filesystem>
#include <iostream>
#include <QMainWindow>
#include <QTimer>
#include <QTranslator>
@ -328,7 +331,7 @@ private slots:
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
void OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
void OnGameListCreateShortcut(u64 program_id, const QString& game_path,
GameListShortcutTarget target);
void OnGameListOpenDirectory(const QString& directory);
void OnGameListAddDirectory();
@ -419,10 +422,17 @@ private:
void ConfigureFilesystemProvider(const std::string& filepath);
QString GetTasStateDescription() const;
#if defined(_WIN32)
bool CreateShortcut(const std::filesystem::path& shortcut_path, const std::u8string& comment,
const std::filesystem::path& icon_path = {},
const std::u8string& command = u8"", const std::u8string& arguments = u8"");
#else
bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
const std::string& comment, const std::string& icon_path,
const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords);
#endif
std::unique_ptr<Ui::MainWindow> ui;

View file

@ -3,6 +3,8 @@
#include <array>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <QPainter>
#include "yuzu/util/util.h"
@ -37,3 +39,93 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
return circle_pixmap;
}
#if defined(WIN32)
#define MATHFIX 0
#ifndef NOMINMAX
#define NOMINMAX
MATHFIX = 1
#endif
#include <Windows.h>
#if MATHFIX
#undef NOMINMAX
#endif
#pragma pack(push, 2)
struct ICONDIR {
WORD idReserved;
WORD idType;
WORD idCount;
};
struct ICONDIRENTRY {
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
DWORD dwBytesInRes;
DWORD dwImageOffset;
};
#pragma pack(pop)
bool SaveIconToFile(const std::filesystem::path IconPath, const QImage image) {
QImage sourceImage = image.convertToFormat(QImage::Format_RGB32);
const int bytesPerPixel = 4;
const int imageSize = sourceImage.width() * sourceImage.height() * bytesPerPixel;
BITMAPINFOHEADER bmih = {};
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biWidth = sourceImage.width();
bmih.biHeight = sourceImage.height() * 2;
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biCompression = BI_RGB;
// Create an ICO header
ICONDIR iconDir;
iconDir.idReserved = 0;
iconDir.idType = 1;
iconDir.idCount = 1;
// Create an ICONDIRENTRY
ICONDIRENTRY iconEntry;
iconEntry.bWidth = sourceImage.width();
iconEntry.bHeight = sourceImage.height() * 2;
iconEntry.bColorCount = 0;
iconEntry.bReserved = 0;
iconEntry.wPlanes = 1;
iconEntry.wBitCount = 32;
iconEntry.dwBytesInRes = sizeof(BITMAPINFOHEADER) + imageSize;
iconEntry.dwImageOffset = sizeof(ICONDIR) + sizeof(ICONDIRENTRY);
// Save the icon data to a file
std::ofstream iconFile(IconPath, std::ios::binary | std::ios::trunc);
if (iconFile.fail())
return false;
iconFile.write((char*)&iconDir, sizeof(ICONDIR));
iconFile.write((char*)&iconEntry, sizeof(ICONDIRENTRY));
iconFile.write((char*)&bmih, sizeof(BITMAPINFOHEADER));
for (int y = 0; y < image.height(); y++) {
auto line = (char*)sourceImage.scanLine(sourceImage.height() - 1 - y);
iconFile.write(line, sourceImage.width() * 4);
}
iconFile.close();
return true;
}
#else
bool SaveAsIco(QImage image) {
return false;
}
#endif

View file

@ -3,6 +3,7 @@
#pragma once
#include <filesystem>
#include <QFont>
#include <QString>
@ -14,7 +15,20 @@ QString ReadableByteSize(qulonglong size);
/**
* Creates a circle pixmap from a specified color
*
* @param color The color the pixmap shall have
*
* @return QPixmap circle pixmap
*/
QPixmap CreateCirclePixmapFromColor(const QColor& color);
/**
* Creates a circle pixmap from a specified color
*
* @param color The color the pixmap shall have
*
* @return QPixmap circle pixmap
*/
bool SaveIconToFile(const std::filesystem::path IconPath, QImage image);