Compare commits

...

7 Commits

Author SHA1 Message Date
Jeroen van Schijndel
3405e7fba8 Update src/yuzu/main.cpp
Co-authored-by: Tobias <thm.frey@gmail.com>
2023-09-29 12:07:47 +02:00
Jeroen
ff5b7673c3 linux fixes 2023-09-17 15:57:07 +02:00
Jeroen
22540ec02d formatting 2023-08-25 10:38:44 +02:00
Jeroen
9fa69dbad6 code style fixes 2023-08-24 08:10:43 +02:00
Jeroen
7161a2416c formatting fixes 2023-08-21 22:11:33 +02:00
Jeroen
afab51e9ac check for windows specific home directory 2023-08-21 21:49:35 +02:00
Jeroen
e88f16f8a2 windows desktop shortcut support 2023-08-21 21:17:03 +02:00
7 changed files with 184 additions and 53 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

@@ -126,6 +126,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:

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.
};
/**

View File

@@ -559,9 +559,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
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"));
#ifndef WIN32
QAction* create_applications_menu_shortcut =
shortcut_menu->addAction(tr("Add to Applications Menu"));
#endif
@@ -633,10 +633,10 @@ 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);
});
#ifndef WIN32
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
});

View File

@@ -15,6 +15,9 @@
#include <csignal>
#include <sys/socket.h>
#endif
#ifdef WIN32
#include <shlobj.h>
#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/qt_amiibo_settings.h"
@@ -2651,13 +2654,11 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
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__)
// Warn once if we are making a shortcut to a volatile AppImage
const std::string appimage_ending =
std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
@@ -2673,13 +2674,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
}
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
#endif // __linux__ || __FreeBSD__
std::filesystem::path target_directory{};
// 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");
#endif
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
@@ -2689,7 +2691,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
QMessageBox::critical(
this, tr("Create Shortcut"),
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);
return;
}
@@ -2697,40 +2699,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
"applications";
if (!Common::FS::CreateDirs(target_directory)) {
QMessageBox::critical(this, tr("Create Shortcut"),
tr("Cannot create shortcut in applications menu. Path \"%1\" "
"does not exist and cannot be created.")
.arg(QString::fromStdString(target_directory)),
QMessageBox::StandardButton::Ok);
QMessageBox::critical(
this, tr("Create Shortcut"),
tr("Cannot create shortcut in applications menu. Path \"%1\" "
"does not exist and cannot be created.")
.arg(QString::fromStdString(target_directory.generic_string())),
QMessageBox::StandardButton::Ok);
return;
}
}
#endif
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
// Determine full paths for icon and shortcut
#if defined(__linux__) || defined(__FreeBSD__)
std::filesystem::path system_icons_path =
(xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) /
"icons/hicolor/256x256";
if (!Common::FS::CreateDirs(system_icons_path)) {
QMessageBox::critical(
this, tr("Create Icon"),
tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
.arg(QString::fromStdString(system_icons_path)),
QMessageBox::StandardButton::Ok);
return;
}
std::filesystem::path icon_path =
system_icons_path / (program_id == 0 ? fmt::format("yuzu-{}.png", game_file_name)
: fmt::format("yuzu-{:016X}.png", program_id));
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));
#else
const std::filesystem::path icon_path{};
const std::filesystem::path shortcut_path{};
#endif
// Get title from game file
const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
@@ -2740,43 +2717,79 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
std::string title{fmt::format("{:016X}", program_id)};
if (control.first != nullptr) {
if (control.first) {
title = control.first->GetApplicationName();
} else {
loader->ReadTitle(title);
}
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
// Determine full paths for icon and shortcut
#if defined(__linux__) || defined(__FreeBSD__)
std::string icon_extension = ".png";
std::filesystem::path icons_path =
(xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) /
"icons/hicolor/256x256";
#elif defined(WIN32)
std::filesystem::path icons_path =
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
std::string icon_extension = ".ico";
#else
std::string icon_extension;
#endif
#if defined(__linux__) || defined(__FreeBSD__)
if (!Common::FS::CreateDirs(icons_path)) {
QMessageBox::critical(
this, tr("Create Icon"),
tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
.arg(QString::fromStdString(icons_path)),
QMessageBox::StandardButton::Ok);
return;
}
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)
// replace colons, which are illegal in windows filenames, by a dash.
std::replace(title.begin(), title.end(), ':', '-');
const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
#else
const std::filesystem::path shortcut_path{};
#endif
std::filesystem::path icon_path =
icons_path / ((program_id == 0 ? fmt::format("yuzu-{}", game_file_name)
: fmt::format("yuzu-{:016X}", program_id)) +
icon_extension);
// Get icon from game file
std::vector<u8> icon_image_file{};
if (control.second != nullptr) {
if (control.second) {
icon_image_file = control.second->ReadAllBytes();
} else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
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()));
#if defined(__linux__) || defined(__FreeBSD__)
// Convert and write the icon as a PNG
if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
// Convert and write the icon
if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon to file");
} else {
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
}
#endif // __linux__
#elif defined(WIN32)
SaveIconToFile(icon_path.string().c_str(), icon_data);
#endif
#if defined(__linux__) || defined(__FreeBSD__)
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;";
#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(),
yuzu_command.string(), arguments, categories, keywords)) {
QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3789,6 +3802,39 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
return true;
#endif
#if defined(WIN32)
auto wcommand = Common::UTF8ToUTF16W(command);
auto warguments = Common::UTF8ToUTF16W(arguments);
auto wcomment = Common::UTF8ToUTF16W(comment);
auto wshortcut_path = Common::UTF8ToUTF16W(shortcut_path);
auto wicon_path = Common::UTF8ToUTF16W(icon_path);
IShellLink* pShellLink;
auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
(void**)&pShellLink);
if (FAILED(hres)) {
return false;
}
pShellLink->SetPath(wcommand.data()); // Path to the object we are referring to
pShellLink->SetArguments(warguments.data());
pShellLink->SetDescription(wcomment.data());
pShellLink->SetIconLocation(wicon_path.data(), 0);
IPersistFile* pPersistFile;
hres = pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile);
if (FAILED(hres)) {
return false;
}
hres = pPersistFile->Save(wshortcut_path.data(), TRUE);
if (FAILED(hres)) {
return false;
}
pPersistFile->Release();
pShellLink->Release();
return true;
#endif
return false;
}

View File

@@ -5,6 +5,10 @@
#include <cmath>
#include <QPainter>
#include "yuzu/util/util.h"
#if defined(WIN32)
#include <fstream>
#include <Windows.h>
#endif
QFont GetMonospaceFont() {
QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,79 @@ 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)
#pragma pack(push, 2)
struct IconDir {
WORD id_reserved;
WORD id_type;
WORD id_count;
};
struct IconDirEntry {
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
DWORD dwBytesInRes;
DWORD dwImageOffset;
};
#pragma pack(pop)
bool SaveIconToFile(const char* path, 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 = bytesPerPixel * 8;
bmih.biCompression = BI_RGB;
// Create an ICO header
IconDir iconDir;
iconDir.id_reserved = 0;
iconDir.id_type = 1;
iconDir.id_count = 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 = bytesPerPixel * 8;
iconEntry.dwBytesInRes = sizeof(BITMAPINFOHEADER) + imageSize;
iconEntry.dwImageOffset = sizeof(IconDir) + sizeof(IconDirEntry);
// Save the icon data to a file
std::ofstream iconFile(path, std::ios::binary);
if (iconFile.fail())
return false;
iconFile.write(reinterpret_cast<const char*>(&iconDir), sizeof(IconDir));
iconFile.write(reinterpret_cast<const char*>(&iconEntry), sizeof(IconDirEntry));
iconFile.write(reinterpret_cast<const char*>(&bmih), sizeof(BITMAPINFOHEADER));
for (int y = 0; y < image.height(); y++) {
auto line =
reinterpret_cast<const char*>(sourceImage.scanLine(sourceImage.height() - 1 - y));
iconFile.write(line, sourceImage.width() * bytesPerPixel);
}
iconFile.close();
return true;
}
#else
bool SaveAsIco(QImage image) {
return false;
}
#endif

View File

@@ -18,3 +18,5 @@ QString ReadableByteSize(qulonglong size);
* @return QPixmap circle pixmap
*/
QPixmap CreateCirclePixmapFromColor(const QColor& color);
bool SaveIconToFile(const char* path, QImage image);