windows desktop shortcut support
This commit is contained in:
parent
ae1421265a
commit
e88f16f8a2
7 changed files with 173 additions and 47 deletions
|
@ -22,6 +22,7 @@
|
|||
#define SDMC_DIR "sdmc"
|
||||
#define SHADER_DIR "shader"
|
||||
#define TAS_DIR "tas"
|
||||
#define ICONS_DIR "icons"
|
||||
|
||||
// yuzu-specific files
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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,12 +2674,9 @@ 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__)
|
||||
const char* home = std::getenv("HOME");
|
||||
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
|
||||
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
||||
|
@ -2689,7 +2687,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(target_directory.c_str()),
|
||||
QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
@ -2700,37 +2698,11 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||
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)),
|
||||
.arg(target_directory.c_str()),
|
||||
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(),
|
||||
|
@ -2746,6 +2718,47 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||
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(system_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.
|
||||
for (auto& c : title)
|
||||
if (c == ':')
|
||||
c = '-';
|
||||
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) {
|
||||
|
@ -2754,29 +2767,27 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||
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 +3800,36 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
|
|||
|
||||
return true;
|
||||
#endif
|
||||
#if defined(WIN32)
|
||||
auto wcommand = std::wstring(command.begin(), command.end());
|
||||
auto warguments = std::wstring(arguments.begin(), arguments.end());
|
||||
auto wcomment = std::wstring(comment.begin(), comment.end());
|
||||
auto wshortcut_path = std::wstring(shortcut_path.begin(), shortcut_path.end());
|
||||
auto wicon_path = std::wstring(icon_path.begin(), icon_path.end());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <cmath>
|
||||
#include <QPainter>
|
||||
#include "yuzu/util/util.h"
|
||||
#include <fstream>
|
||||
|
||||
QFont GetMonospaceFont() {
|
||||
QFont font(QStringLiteral("monospace"));
|
||||
|
@ -37,3 +38,82 @@ 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)
|
||||
#include <Windows.h>
|
||||
|
||||
#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 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 = 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(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 = (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
|
|
@ -18,3 +18,5 @@ QString ReadableByteSize(qulonglong size);
|
|||
* @return QPixmap circle pixmap
|
||||
*/
|
||||
QPixmap CreateCirclePixmapFromColor(const QColor& color);
|
||||
|
||||
bool SaveIconToFile(const char* path, QImage image);
|
||||
|
|
Loading…
Add table
Reference in a new issue