Full shortcut Windows support + question for fullscreen mode
This commit is contained in:
parent
7a0da729b4
commit
604b1b6c86
11 changed files with 541 additions and 36 deletions
|
@ -22,6 +22,7 @@
|
|||
#define SDMC_DIR "sdmc"
|
||||
#define SHADER_DIR "shader"
|
||||
#define TAS_DIR "tas"
|
||||
#define ICONS_DIR "icons"
|
||||
|
||||
// yuzu-specific files
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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); });
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue