fix appimage shortcuts (#137)

Properly recognizes AppImage launches and deduplicates some code b/t
home menu and game list shortcuts

Signed-off-by: swurl <swurl@swurl.xyz>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/137
Co-authored-by: swurl <swurl@swurl.xyz>
Co-committed-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-29 06:35:00 +00:00 committed by crueter
parent e9e17b8fc2
commit 54bfb6e905
2 changed files with 129 additions and 164 deletions

View file

@ -97,6 +97,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
#include <QCheckBox> #include <QCheckBox>
#include <QStringLiteral>
#include <SDL.h> // For SDL ScreenSaver functions #include <SDL.h> // For SDL ScreenSaver functions
#endif #endif
@ -424,7 +425,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
(strstr(Common::g_build_fullname, "beta") != NULL) || (strstr(Common::g_build_fullname, "beta") != NULL) ||
(strstr(Common::g_build_fullname, "rc") != NULL)); (strstr(Common::g_build_fullname, "rc") != NULL));
const std::optional<std::string> latest_release_tag = const std::optional<std::string> latest_release_tag =
UpdateChecker::GetLatestRelease(is_prerelease); UpdateChecker::GetLatestRelease(is_prerelease);
if (latest_release_tag && latest_release_tag.value() != Common::g_build_fullname) { if (latest_release_tag && latest_release_tag.value() != Common::g_build_fullname) {
return QString::fromStdString(latest_release_tag.value()); return QString::fromStdString(latest_release_tag.value());
} }
@ -3096,99 +3097,10 @@ bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_vi
void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
GameListShortcutTarget target) { GameListShortcutTarget target) {
// Get path to yuzu executable
const QStringList args = QApplication::arguments();
std::filesystem::path yuzu_command = args[0].toStdString();
// If relative path, make it an absolute path
if (yuzu_command.c_str()[0] == '.') {
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
}
// Shortcut path
std::filesystem::path shortcut_path{};
if (target == GameListShortcutTarget::Desktop) {
shortcut_path =
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
} else if (target == GameListShortcutTarget::Applications) {
shortcut_path =
QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString();
}
if (!std::filesystem::exists(shortcut_path)) {
GMainWindow::CreateShortcutMessagesGUI(
this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
QString::fromStdString(shortcut_path.generic_string()));
LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string());
return;
}
// Get title from game file
const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
system->GetContentProvider()};
const auto control = pm.GetControlMetadata();
const auto loader =
Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read));
std::string game_title = fmt::format("{:016X}", program_id);
if (control.first != nullptr) {
game_title = control.first->GetApplicationName();
} else {
loader->ReadTitle(game_title);
}
// Delete illegal characters from title
const std::string illegal_chars = "<>:\"/\\|?*.";
for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) {
if (illegal_chars.find(*it) != std::string::npos) {
game_title.erase(it.base() - 1);
}
}
const QString qt_game_title = QString::fromStdString(game_title);
// Get icon from game file
std::vector<u8> icon_image_file{};
if (control.second != nullptr) {
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_data =
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
std::filesystem::path out_icon_path;
if (GMainWindow::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
if (!SaveIconToFile(out_icon_path, icon_data)) {
LOG_ERROR(Frontend, "Could not write icon to file");
}
}
#if defined(__linux__)
// Special case for AppImages
// 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");
if (yuzu_command.string().ends_with(appimage_ending) &&
!UISettings::values.shortcut_already_warned) {
if (GMainWindow::CreateShortcutMessagesGUI(
this, GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, qt_game_title)) {
return;
}
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
// Create shortcut // Create shortcut
std::string arguments = fmt::format("-g \"{:s}\"", game_path); std::string arguments = fmt::format("-g \"{:s}\"", game_path);
if (GMainWindow::CreateShortcutMessagesGUI(
this, GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, qt_game_title)) {
arguments = "-f " + arguments;
}
const std::string comment = fmt::format("Start {:s} with the eden Emulator", game_title);
const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "Switch;Nintendo;";
if (GMainWindow::CreateShortcutLink(shortcut_path, comment, out_icon_path, yuzu_command, CreateShortcut(game_path, program_id, "", target, arguments, true);
arguments, categories, keywords, game_title)) {
GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS,
qt_game_title);
return;
}
GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
qt_game_title);
} }
void GMainWindow::OnGameListOpenDirectory(const QString& directory) { void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
@ -4628,16 +4540,28 @@ void GMainWindow::OnCreateHomeMenuApplicationMenuShortcut()
OnCreateHomeMenuShortcut(GameListShortcutTarget::Applications); OnCreateHomeMenuShortcut(GameListShortcutTarget::Applications);
} }
void GMainWindow::OnCreateHomeMenuShortcut(GameListShortcutTarget target) std::filesystem::path GMainWindow::GetEdenCommand()
{ {
// Get path to yuzu executable std::filesystem::path command;
const QStringList args = QApplication::arguments();
std::filesystem::path yuzu_command = args[0].toStdString(); QString appimage = QString::fromLocal8Bit(getenv("APPIMAGE"));
// If relative path, make it an absolute path if (!appimage.isEmpty()) {
if (yuzu_command.c_str()[0] == '.') { command = std::filesystem::path{appimage.toStdString()};
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; } else {
const QStringList args = QApplication::arguments();
command = args[0].toStdString();
} }
// Shortcut path
// If relative path, make it an absolute path
if (command.c_str()[0] == '.') {
command = Common::FS::GetCurrentDir() / command;
}
return command;
}
std::filesystem::path GMainWindow::GetShortcutPath(GameListShortcutTarget target)
{
std::filesystem::path shortcut_path{}; std::filesystem::path shortcut_path{};
if (target == GameListShortcutTarget::Desktop) { if (target == GameListShortcutTarget::Desktop) {
shortcut_path = shortcut_path =
@ -4647,6 +4571,16 @@ void GMainWindow::OnCreateHomeMenuShortcut(GameListShortcutTarget target)
QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString(); QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString();
} }
return shortcut_path;
}
void GMainWindow::CreateShortcut(const std::string &game_path, const u64 program_id, const std::string& game_title_, GameListShortcutTarget target, std::string arguments_, const bool needs_title) {
// Get path to yuzu executable
std::filesystem::path command = GetEdenCommand();
// Shortcut path
std::filesystem::path shortcut_path = GetShortcutPath(target);
if (!std::filesystem::exists(shortcut_path)) { if (!std::filesystem::exists(shortcut_path)) {
GMainWindow::CreateShortcutMessagesGUI( GMainWindow::CreateShortcutMessagesGUI(
this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
@ -4655,11 +4589,89 @@ void GMainWindow::OnCreateHomeMenuShortcut(GameListShortcutTarget target)
return; return;
} }
const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
system->GetContentProvider()};
const auto control = pm.GetControlMetadata();
const auto loader =
Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read));
std::string game_title{game_title_};
// Delete illegal characters from title
if (needs_title) {
game_title = fmt::format("{:016X}", program_id);
if (control.first != nullptr) {
game_title = control.first->GetApplicationName();
} else {
loader->ReadTitle(game_title);
}
}
const std::string illegal_chars = "<>:\"/\\|?*.";
for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) {
if (illegal_chars.find(*it) != std::string::npos) {
game_title.erase(it.base() - 1);
}
}
const QString qgame_title = QString::fromStdString(game_title);
// Get icon from game file
std::vector<u8> icon_image_file{};
if (control.second != nullptr) {
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_data =
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
std::filesystem::path out_icon_path;
if (GMainWindow::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
if (!SaveIconToFile(out_icon_path, icon_data)) {
LOG_ERROR(Frontend, "Could not write icon to file");
}
}
#if defined(__linux__)
// Special case for AppImages
// Warn once if we are making a shortcut to a volatile AppImage
if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) {
if (!GMainWindow::CreateShortcutMessagesGUI(
this, GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, qgame_title)) {
return;
}
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
// Create shortcut
std::string arguments{arguments_};
if (GMainWindow::CreateShortcutMessagesGUI(
this, GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, qgame_title)) {
arguments = "-f " + arguments;
}
const std::string comment = fmt::format("Start {:s} with the eden Emulator", game_title);
const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "Switch;Nintendo;";
if (GMainWindow::CreateShortcutLink(shortcut_path, comment, out_icon_path, command,
arguments, categories, keywords, game_title)) {
GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS,
qgame_title);
return;
}
GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
qgame_title);
}
void GMainWindow::OnCreateHomeMenuShortcut(GameListShortcutTarget target)
{
constexpr u64 QLaunchId = static_cast<u64>(Service::AM::AppletProgramId::QLaunch); constexpr u64 QLaunchId = static_cast<u64>(Service::AM::AppletProgramId::QLaunch);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) { if (!bis_system) {
QMessageBox::warning(this, tr("No firmware available"), QMessageBox::warning(this, tr("No firmware available"),
tr("Please install the firmware to use the home menu.")); tr("Please install firmware to use the home menu."));
return; return;
} }
@ -4670,67 +4682,10 @@ void GMainWindow::OnCreateHomeMenuShortcut(GameListShortcutTarget target)
return; return;
} }
const std::string game_title = "QLaunch";
const QString qt_game_title = tr("QLaunch");
auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
const auto game_path = QString::fromStdString((qlaunch_applet_nca->GetFullPath())); const auto game_path = qlaunch_applet_nca->GetFullPath();
const FileSys::PatchManager pm{static_cast<u64>(Service::AM::AppletProgramId::QLaunch), system->GetFileSystemController(), CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false);
system->GetContentProvider()};
const auto control = pm.GetControlMetadata();
const auto loader =
Loader::GetLoader(*system, vfs->OpenFile(game_path.toStdString(), FileSys::OpenMode::Read));
// Get icon from game file
std::vector<u8> icon_image_file{};
if (control.second != nullptr) {
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.toStdString());
}
QImage icon_data =
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
std::filesystem::path out_icon_path;
if (GMainWindow::MakeShortcutIcoPath(QLaunchId, game_title, out_icon_path)) {
if (!SaveIconToFile(out_icon_path, icon_data)) {
LOG_ERROR(Frontend, "Could not write icon to file");
}
}
#if defined(__linux__)
// Special case for AppImages
// 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");
if (yuzu_command.string().ends_with(appimage_ending) &&
!UISettings::values.shortcut_already_warned) {
if (GMainWindow::CreateShortcutMessagesGUI(
this, GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, qt_game_title)) {
return;
}
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
// Create shortcut
std::string arguments = "-qlaunch";
if (GMainWindow::CreateShortcutMessagesGUI(
this, GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, qt_game_title)) {
arguments = "-f " + arguments;
}
const std::string comment = fmt::format("Start {:s} with the eden Emulator", game_title);
const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "Switch;Nintendo;";
if (GMainWindow::CreateShortcutLink(shortcut_path, comment, out_icon_path, yuzu_command,
arguments, categories, keywords, game_title)) {
GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS,
qt_game_title);
return;
}
GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
qt_game_title);
} }
void GMainWindow::OnCaptureScreenshot() { void GMainWindow::OnCaptureScreenshot() {
@ -4805,12 +4760,12 @@ void GMainWindow::OnEmulatorUpdateAvailable() {
update_prompt.addButton(QMessageBox::Yes); update_prompt.addButton(QMessageBox::Yes);
update_prompt.addButton(QMessageBox::Ignore); update_prompt.addButton(QMessageBox::Ignore);
update_prompt.setText(tr("Update %1 for Eden is available.\nWould you like to download it?") update_prompt.setText(tr("Update %1 for Eden is available.\nWould you like to download it?")
.arg(version_string)); .arg(version_string));
update_prompt.exec(); update_prompt.exec();
if (update_prompt.button(QMessageBox::Yes) == update_prompt.clickedButton()) { if (update_prompt.button(QMessageBox::Yes) == update_prompt.clickedButton()) {
QDesktopServices::openUrl( QDesktopServices::openUrl(
QUrl(QString::fromStdString("https://github.com/eden-emulator/Releases/releases/tag/") + QUrl(QString::fromStdString("https://github.com/eden-emulator/Releases/releases/tag/") +
version_string)); version_string));
} }
} }
#endif #endif
@ -5572,7 +5527,7 @@ int main(int argc, char* argv[]) {
// Fix the Wayland appId. This needs to match the name of the .desktop file without the .desktop // Fix the Wayland appId. This needs to match the name of the .desktop file without the .desktop
// suffix. // suffix.
QGuiApplication::setDesktopFileName(QStringLiteral("org.eden_emu.eden")); QGuiApplication::setDesktopFileName(QStringLiteral("eden"));
#endif #endif
SetHighDPIAttributes(); SetHighDPIAttributes();
@ -5587,7 +5542,6 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv); QApplication app(argc, argv);
#ifdef _WIN32 #ifdef _WIN32
OverrideWindowsFont(); OverrideWindowsFont();
#endif #endif

View file

@ -178,6 +178,8 @@ public:
bool DropAction(QDropEvent* event); bool DropAction(QDropEvent* event);
void AcceptDropEvent(QDropEvent* event); void AcceptDropEvent(QDropEvent* event);
std::filesystem::path GetShortcutPath(GameListShortcutTarget target);
signals: signals:
/** /**
@ -593,6 +595,15 @@ private:
static std::array<int, 3> sig_interrupt_fds; static std::array<int, 3> sig_interrupt_fds;
#endif #endif
std::filesystem::path GetEdenCommand();
void CreateShortcut(const std::string& game_path,
const u64 program_id,
const std::string& game_title,
GameListShortcutTarget target,
std::string arguments,
const bool needs_title);
protected: protected:
void dropEvent(QDropEvent* event) override; void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override;