From 5c6049c4ae0678c2ce1777ae85255ae3f27a7ab1 Mon Sep 17 00:00:00 2001 From: KamFretoZ <14798312+kamfretoz@users.noreply.github.com> Date: Thu, 29 May 2025 19:25:09 +0700 Subject: [PATCH] Qt: Add Create game shortcut functionality --- bin/resources/icons/AppIconLarge.ico | Bin 0 -> 302430 bytes common/FileSystem.cpp | 17 +- common/FileSystem.h | 3 + pcsx2-qt/CMakeLists.txt | 8 + pcsx2-qt/MainWindow.cpp | 19 + pcsx2-qt/MainWindow.h | 3 + pcsx2-qt/Settings/InterfaceSettingsWidget.cpp | 2 +- pcsx2-qt/ShortcutCreationDialog.cpp | 518 ++++++++++++++++++ pcsx2-qt/ShortcutCreationDialog.h | 29 + pcsx2-qt/ShortcutCreationDialog.ui | 317 +++++++++++ pcsx2-qt/pcsx2-qt.vcxproj | 4 + pcsx2-qt/pcsx2-qt.vcxproj.filters | 6 + 12 files changed, 924 insertions(+), 2 deletions(-) create mode 100644 bin/resources/icons/AppIconLarge.ico create mode 100644 pcsx2-qt/ShortcutCreationDialog.cpp create mode 100644 pcsx2-qt/ShortcutCreationDialog.h create mode 100644 pcsx2-qt/ShortcutCreationDialog.ui diff --git a/bin/resources/icons/AppIconLarge.ico b/bin/resources/icons/AppIconLarge.ico new file mode 100644 index 0000000000000000000000000000000000000000..036be17254e5be15f5f905cc927fe54a2299ca0c GIT binary patch literal 302430 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 literal 0 HcmV?d00001 diff --git a/common/FileSystem.cpp b/common/FileSystem.cpp index 86e0e151ec..05fd4cd820 100644 --- a/common/FileSystem.cpp +++ b/common/FileSystem.cpp @@ -85,7 +85,7 @@ static inline bool FileSystemCharacterIsSane(char32_t c, bool strip_slashes) if (c == '*') return false; - // macos doesn't allow colons, apparently + // macos doesn't allow colons, apparently #ifdef __APPLE__ if (c == U':') return false; @@ -2490,6 +2490,21 @@ bool FileSystem::DeleteDirectory(const char* path) return (rmdir(path) == 0); } +std::string FileSystem::GetPackagePath() +{ + // NOTE: The reason this function is separated from FileSystem::GetProgramPath() is because + // This path check breaks other usages of FileSystem::GetProgramPath for the AppImage. + // Notably the CI-generated AppImage fails to start because PCSX2 can't find its resources + // since it tries to look for them relative to the .AppImage file instead of relative to the actual executable. + + // Check if we are running inside appimage. If so, return the path to the appimage instead. + if (const char* appimage_path = getenv("APPIMAGE")) + return std::string(appimage_path); + + // Otherwise, find the executable file directly + return GetProgramPath(); +} + std::string FileSystem::GetProgramPath() { #if defined(__linux__) diff --git a/common/FileSystem.h b/common/FileSystem.h index e0ae881e2c..3a6ea9b111 100644 --- a/common/FileSystem.h +++ b/common/FileSystem.h @@ -166,6 +166,9 @@ namespace FileSystem /// Copies one file to another, optionally replacing it if it already exists. bool CopyFilePath(const char* source, const char* destination, bool replace); + /// Returns the path to the current package (AppImage). + std::string GetPackagePath(); + /// Returns the path to the current executable. std::string GetProgramPath(); diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index ff823e36b9..2d18246208 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -264,6 +264,14 @@ target_sources(pcsx2-qt PRIVATE resources/resources.qrc ) +if (NOT APPLE) + target_sources(pcsx2-qt PRIVATE + ShortcutCreationDialog.cpp + ShortcutCreationDialog.h + ShortcutCreationDialog.ui + ) +endif() + file(GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Translations/*.ts) target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index b60c5c04c1..3481ba5b6b 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -21,6 +21,10 @@ #include "Tools/InputRecording/InputRecordingViewer.h" #include "Tools/InputRecording/NewInputRecordingDlg.h" +#if !defined(__APPLE__) +#include "ShortcutCreationDialog.h" +#endif + #include "pcsx2/Achievements.h" #include "pcsx2/CDVD/CDVDcommon.h" #include "pcsx2/CDVD/CDVDdiscReader.h" @@ -1452,6 +1456,10 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) action = menu.addAction(tr("Set Cover Image...")); connect(action, &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(entry); }); +#if !defined(__APPLE__) + connect(menu.addAction(tr("Create Game Shortcut...")), &QAction::triggered, [this]() { MainWindow::onCreateGameShortcutTriggered(); }); +#endif + connect(menu.addAction(tr("Exclude From List")), &QAction::triggered, [this, entry]() { getSettingsWindow()->getGameListSettingsWidget()->addExcludedPath(entry->path); }); @@ -1758,6 +1766,17 @@ void MainWindow::onToolsCoverDownloaderTriggered() dlg.exec(); } +#if !defined(__APPLE__) +void MainWindow::onCreateGameShortcutTriggered() +{ + const GameList::Entry* entry = m_game_list_widget->getSelectedEntry(); + const QString title = QString::fromStdString(entry->GetTitle()); + const QString path = QString::fromStdString(entry->path); + VMLock lock(pauseAndLockVM()); + ShortcutCreationDialog dlg(lock.getDialogParent(), title, path); + dlg.exec(); +} +#endif void MainWindow::onToolsEditCheatsPatchesTriggered(bool cheats) { if (s_current_disc_serial.isEmpty() || s_current_running_crc == 0) diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 7ccf30cde7..e86bbc6f0b 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -170,6 +170,9 @@ private Q_SLOTS: void onAboutActionTriggered(); void onToolsOpenDataDirectoryTriggered(); void onToolsCoverDownloaderTriggered(); +#if !defined(__APPLE__) + void onCreateGameShortcutTriggered(); +#endif void onToolsEditCheatsPatchesTriggered(bool cheats); void onCreateMemoryCardOpenRequested(); void updateTheme(); diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp index abfa7e5cc3..3aa9ca6478 100644 --- a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp @@ -10,7 +10,7 @@ #include "SettingsWindow.h" #include "QtHost.h" -static const char* IMAGE_FILE_FILTER = QT_TRANSLATE_NOOP(GameListWidget, +static const char* IMAGE_FILE_FILTER = QT_TRANSLATE_NOOP(InterfaceSettingsWidget, "Supported Image Types (*.bmp *.gif *.jpg *.jpeg *.png *.webp)"); const char* InterfaceSettingsWidget::THEME_NAMES[] = { diff --git a/pcsx2-qt/ShortcutCreationDialog.cpp b/pcsx2-qt/ShortcutCreationDialog.cpp new file mode 100644 index 0000000000..b64ed5421e --- /dev/null +++ b/pcsx2-qt/ShortcutCreationDialog.cpp @@ -0,0 +1,518 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "ShortcutCreationDialog.h" +#include "QtHost.h" +#include +#include +#include +#include "common/Console.h" +#include "common/FileSystem.h" +#include "common/Path.h" +#include "common/StringUtil.h" +#include "VMManager.h" + +#if defined(_WIN32) +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#endif + +ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path) + : QDialog(parent) + , m_title(title) + , m_path(path) +{ + m_ui.setupUi(this); + this->setWindowTitle(tr("Create Shortcut For %1").arg(title)); + this->setWindowIcon(QtHost::GetAppIcon()); + +#if defined(_WIN32) + m_ui.shortcutStartMenu->setText(tr("Start Menu")); +#else + m_ui.shortcutStartMenu->setText(tr("Application Launcher")); +#endif + + connect(m_ui.overrideBootELFButton, &QPushButton::clicked, [&]() { + const QString path = QFileDialog::getOpenFileName(this, tr("Select ELF File"), QString(), tr("ELF Files (*.elf);;All Files (*.*)")); + if (!path.isEmpty()) + m_ui.overrideBootELFPath->setText(Path::ToNativePath(path.toStdString()).c_str()); + }); + + connect(m_ui.loadStateFileBrowse, &QPushButton::clicked, [&]() { + const QString path = QFileDialog::getOpenFileName(this, tr("Select Save State File"), QString(), tr("Save States (*.p2s);;All Files (*.*)")); + if (!path.isEmpty()) + m_ui.loadStateFilePath->setText(Path::ToNativePath(path.toStdString()).c_str()); + }); + + connect(m_ui.overrideBootELFToggle, &QCheckBox::toggled, m_ui.overrideBootELFPath, &QLineEdit::setEnabled); + connect(m_ui.overrideBootELFToggle, &QCheckBox::toggled, m_ui.overrideBootELFButton, &QPushButton::setEnabled); + connect(m_ui.gameArgsToggle, &QCheckBox::toggled, m_ui.gameArgs, &QLineEdit::setEnabled); + connect(m_ui.loadStateIndexToggle, &QCheckBox::toggled, m_ui.loadStateIndex, &QSpinBox::setEnabled); + connect(m_ui.loadStateFileToggle, &QCheckBox::toggled, m_ui.loadStateFilePath, &QLineEdit::setEnabled); + connect(m_ui.loadStateFileToggle, &QCheckBox::toggled, m_ui.loadStateFileBrowse, &QPushButton::setEnabled); + connect(m_ui.bootOptionToggle, &QCheckBox::toggled, m_ui.bootOptionDropdown, &QPushButton::setEnabled); + connect(m_ui.fullscreenMode, &QCheckBox::toggled, m_ui.fullscreenModeDropdown, &QPushButton::setEnabled); + + m_ui.shortcutDesktop->setChecked(true); + m_ui.overrideBootELFPath->setEnabled(false); + m_ui.overrideBootELFButton->setEnabled(false); + m_ui.gameArgs->setEnabled(false); + m_ui.bootOptionDropdown->setEnabled(false); + m_ui.fullscreenModeDropdown->setEnabled(false); + m_ui.loadStateIndex->setEnabled(false); + m_ui.loadStateFileBrowse->setEnabled(false); + m_ui.loadStateFilePath->setEnabled(false); + + m_ui.loadStateIndex->setMaximum(VMManager::NUM_SAVE_STATE_SLOTS); + + if (std::getenv("container")) + { + m_ui.portableModeToggle->setEnabled(false); + m_ui.shortcutDesktop->setEnabled(false); + m_ui.shortcutStartMenu->setEnabled(false); + } + + connect(m_ui.dialogButtons, &QDialogButtonBox::accepted, this, [&]() { + std::vector args; + + if (m_ui.portableModeToggle->isChecked()) + args.push_back("-portable"); + + if (m_ui.overrideBootELFToggle->isChecked() && !m_ui.overrideBootELFPath->text().isEmpty()) + { + args.push_back("-elf"); + args.push_back(m_ui.overrideBootELFPath->text().toStdString()); + } + + if (m_ui.gameArgsToggle->isChecked() && !m_ui.gameArgs->text().isEmpty()) + { + args.push_back("-gameargs"); + args.push_back(m_ui.gameArgs->text().toStdString()); + } + + if (m_ui.bootOptionToggle->isChecked()) + args.push_back(m_ui.bootOptionDropdown->currentIndex() ? "-slowboot" : "-fastboot"); + + if (m_ui.loadStateIndexToggle->isChecked()) + { + const s32 load_state_index = m_ui.loadStateIndex->value(); + if (load_state_index >= 1 && load_state_index <= VMManager::NUM_SAVE_STATE_SLOTS) + { + args.push_back("-state"); + args.push_back(StringUtil::ToChars(load_state_index)); + } + } + + if (m_ui.fullscreenMode->isChecked()) + args.push_back(m_ui.fullscreenModeDropdown->currentIndex() ? "-nofullscreen" : "-fullscreen"); + + if (m_ui.bigPictureModeToggle->isChecked()) + args.push_back("-bigpicture"); + + m_desktop = m_ui.shortcutDesktop->isChecked(); + + std::string custom_args = m_ui.customArgsInput->text().toStdString(); + + ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), args, custom_args, m_desktop); + + accept(); + }); + connect(m_ui.dialogButtons, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::string game_path, std::vector passed_cli_args, std::string custom_args, bool is_desktop) +{ +#if defined(_WIN32) + if (name.empty()) + { + Console.Error("Cannot create shortcuts without a name."); + return; + } + + // Sanitize filename + const std::string clean_name = Path::SanitizeFileName(name).c_str(); + std::string clean_path = Path::ToNativePath(Path::RealPath(game_path)).c_str(); + if (!Path::IsValidFileName(clean_name)) + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + // Locate home directory + std::string link_file; + if (const char* home = std::getenv("USERPROFILE")) + { + if (is_desktop) + link_file = Path::ToNativePath(fmt::format("{}/Desktop/{}.lnk", home, clean_name)); + else + { + const std::string start_menu_dir = Path::ToNativePath(fmt::format("{}/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/PCSX2", home)); + if (!FileSystem::EnsureDirectoryExists(start_menu_dir.c_str(), false)) + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Could not create start menu directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", start_menu_dir, clean_name)); + } + } + else + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Home path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + // Check if the same shortcut already exists + if (FileSystem::FileExists(link_file.c_str())) + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + // Shortcut CmdLine Args + bool lossless = true; + for (std::string& arg : passed_cli_args) + lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg); + + if (!lossless) + QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s). The resulting shortcut may not work."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + + ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path); + std::string combined_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " "); + std::string final_args = fmt::format("{} {} -- {}", combined_args, custom_args, clean_path); + + Console.WriteLnFmt("Creating a shortcut '{}' with arguments '{}'", link_file, final_args); + + const auto str_error = [](HRESULT hr) -> std::string { + _com_error err(hr); + const TCHAR* errMsg = err.ErrorMessage(); + return fmt::format("{} [{}]", StringUtil::WideStringToUTF8String(errMsg), hr); + }; + + // Construct the shortcut + // https://stackoverflow.com/questions/3906974/how-to-programmatically-create-a-shortcut-using-win32 + HRESULT res = CoInitialize(NULL); + if (FAILED(res)) + { + Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res)); + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("CoInitialize failed (%1").arg(str_error(res)), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + Microsoft::WRL::ComPtr pShellLink; + Microsoft::WRL::ComPtr pPersistFile; + + const auto cleanup = [&](bool return_value, const QString& fail_reason) -> bool { + if (!return_value) + { + Console.ErrorFmt("Failed to create shortcut: {}", fail_reason.toStdString()); + QMessageBox::critical(this, tr("Failed to create shortcut"), fail_reason, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + } + CoUninitialize(); + return return_value; + }; + + res = CoCreateInstance(__uuidof(ShellLink), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellLink)); + if (FAILED(res)) + { + cleanup(false, tr("CoCreateInstance failed")); + return; + } + + // Set path to the executable + const std::wstring target_file = StringUtil::UTF8StringToWideString(FileSystem::GetProgramPath()); + res = pShellLink->SetPath(target_file.c_str()); + if (FAILED(res)) + { + cleanup(false, tr("SetPath failed (%1)").arg(str_error(res))); + return; + } + + // Set the working directory + const std::wstring working_dir = StringUtil::UTF8StringToWideString(FileSystem::GetWorkingDirectory()); + res = pShellLink->SetWorkingDirectory(working_dir.c_str()); + if (FAILED(res)) + { + cleanup(false, tr("SetWorkingDirectory failed (%1)").arg(str_error(res))); + return; + } + + // Set the launch arguments + if (!final_args.empty()) + { + const std::wstring target_cli_args = StringUtil::UTF8StringToWideString(final_args); + res = pShellLink->SetArguments(target_cli_args.c_str()); + if (FAILED(res)) + { + cleanup(false, tr("SetArguments failed (%1)").arg(str_error(res))); + return; + } + } + + // Set the icon + std::string icon_path = Path::ToNativePath(Path::Combine(Path::GetDirectory(FileSystem::GetProgramPath()), "resources/icons/AppIconLarge.ico")); + const std::wstring w_icon_path = StringUtil::UTF8StringToWideString(icon_path); + res = pShellLink->SetIconLocation(w_icon_path.c_str(), 0); + if (FAILED(res)) + { + cleanup(false, tr("SetIconLocation failed (%1)").arg(str_error(res))); + return; + } + + // Use the IPersistFile object to save the shell link + res = pShellLink.As(&pPersistFile); + if (FAILED(res)) + { + cleanup(false, tr("QueryInterface failed (%1)").arg(str_error(res))); + return; + } + + // Save shortcut link to disk + const std::wstring w_link_file = StringUtil::UTF8StringToWideString(link_file); + res = pPersistFile->Save(w_link_file.c_str(), TRUE); + if (FAILED(res)) + { + cleanup(false, tr("Failed to save the shortcut (%1)").arg(str_error(res))); + return; + } + + cleanup(true, {}); + +#else + + if (name.empty()) + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Cannot create a shortcut without a title."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + bool is_flatpak = (std::getenv("container")); + + // Sanitize filename and game path + const std::string clean_name = Path::SanitizeFileName(name); + std::string clean_path = Path::Canonicalize(Path::RealPath(game_path)); + if (!Path::IsValidFileName(clean_name)) + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + // Find the executable path + std::string executable_path = FileSystem::GetPackagePath(); + if (executable_path.empty()) + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Executable path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + if (is_flatpak) // Flatpak + executable_path = "flatpak run net.pcsx2.PCSX2"; + + // Find home directory + std::string link_path; + const char* home = std::getenv("HOME"); + const char* xdg_desktop_dir = std::getenv("XDG_DESKTOP_DIR"); + const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); + if (home) + { + if (is_desktop) + { + if (xdg_desktop_dir) + link_path = fmt::format("{}/{}.desktop", xdg_desktop_dir, clean_name); + else + link_path = fmt::format("{}/Desktop/{}.desktop", home, clean_name); + } + else + { + if (xdg_data_home) + link_path = fmt::format("{}/applications/{}.desktop", xdg_data_home, clean_name); + else + link_path = fmt::format("{}/.local/share/applications/{}.desktop", home, clean_name); + } + } + else + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Home path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + // Checks if a shortcut already exist + if (FileSystem::FileExists(link_path.c_str())) + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + // Shortcut CmdLine Args + bool lossless = true; + for (std::string& arg : passed_cli_args) + lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg); + + if (!lossless) + QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s). The resulting shortcut may not work."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + + std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " "); + + if (!is_flatpak) + { + // Copy PCSX2 icon + std::string icon_dest; + if (xdg_data_home) + icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", xdg_data_home); + else + icon_dest = fmt::format("{}/.local/share/icons/hicolor/512x512/apps/", home); + + std::string icon_name = "PCSX2.png"; + std::string icon_path = fmt::format("{}/{}", icon_dest, icon_name).c_str(); + if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true)) + FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path.c_str(), false); + } + + // Further string sanitization + if (!is_flatpak) + ShortcutCreationDialog::EscapeShortcutCommandLine(&executable_path); + ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path); + + // Assembling the .desktop file + std::string final_args; + final_args = fmt::format("{} {} {} -- {}", executable_path, cmdline, custom_args, clean_path); + std::string file_content = + "[Desktop Entry]\n" + "Encoding=UTF-8\n" + "Version=1.0\n" + "Type=Application\n" + "Terminal=false\n" + "StartupWMClass=PCSX2\n" + "Exec=" + final_args + "\n" + "Name=" + clean_name + "\n" + "Icon=net.pcsx2.PCSX2\n" + "Categories=Game;Emulator;\n"; + std::string_view sv(file_content); + + // Prompt user for shortcut saving destination + QString final_path(QStringLiteral("%1").arg(QString::fromStdString(link_path))); + const QString filter(tr("Desktop Shortcut Files (*.desktop)")); + + final_path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Select Shortcut Save Destination"), final_path, filter)); + + if (final_path.isEmpty()) + return; + + // Write to .desktop file + if (!FileSystem::WriteStringToFile(final_path.toStdString().c_str(), sv)) + { + QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Failed to create .desktop file"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok); + return; + } + + if (chmod(final_path.toStdString().c_str(), S_IRWXU) != 0) // enables user to execute file + Console.ErrorFmt("Failed to change file permissions for .desktop file: {} ({})", strerror(errno), errno); +#endif +} + +bool ShortcutCreationDialog::EscapeShortcutCommandLine(std::string* arg) +{ +#ifdef _WIN32 + if (!arg->empty() && arg->find_first_of(" \t\n\v\"") == std::string::npos) + return true; + + std::string temp; + temp.reserve(arg->length() + 10); + temp += '"'; + + for (auto it = arg->begin();; ++it) + { + int backslash_count = 0; + while (it != arg->end() && *it == '\\') + { + ++it; + ++backslash_count; + } + + if (it == arg->end()) + { + temp.append(backslash_count * 2, '\\'); + break; + } + + if (*it == '"') + { + temp.append(backslash_count * 2 + 1, '\\'); + temp += '"'; + } + else + { + temp.append(backslash_count, '\\'); + temp += *it; + } + } + + temp += '"'; + *arg = std::move(temp); + return true; +#else + const char* carg = arg->c_str(); + const char* cend = carg + arg->size(); + const char* RESERVED_CHARS = " \t\n\\\"'\\\\><~|%&;$*?#()`" + "\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"; + const char* next = carg + std::strcspn(carg, RESERVED_CHARS); + + if (next == cend) + return true; // No escaping needed, don't modify + + bool lossless = true; + std::string temp = "\""; + const char* NOT_VALID_IN_QUOTE = "%`$\"\\\n" + "\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"; + + while (true) + { + next = carg + std::strcspn(carg, NOT_VALID_IN_QUOTE); + temp.append(carg, next); + carg = next; + + if (carg == cend) + break; + + switch (*carg) + { + case '"': + case '`': + temp.push_back('\\'); + temp.push_back(*carg); + break; + case '\\': + temp.append("\\\\\\\\"); + break; + case '$': + temp.push_back('\\'); + temp.push_back('\\'); + temp.push_back(*carg); + break; + case '%': + temp.push_back('%'); + temp.push_back(*carg); + break; + default: + temp.push_back(' '); + lossless = false; + break; + } + ++carg; + } + + temp.push_back('"'); + *arg = std::move(temp); + return lossless; +#endif +} diff --git a/pcsx2-qt/ShortcutCreationDialog.h b/pcsx2-qt/ShortcutCreationDialog.h new file mode 100644 index 0000000000..51d8e386f2 --- /dev/null +++ b/pcsx2-qt/ShortcutCreationDialog.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once +#include "ui_ShortcutCreationDialog.h" + +#include + +class ShortcutCreationDialog final : public QDialog +{ + Q_OBJECT + +public: + ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path); + ~ShortcutCreationDialog() = default; + + /// Create desktop shortcut for games + void CreateShortcut(const std::string name, const std::string game_path, std::vector passed_cli_args, std::string custom_args, bool is_desktop); + + /// Escapes the given string for use with command line arguments. + /// Returns a bool that indicates whether the escaping operation are lossless or not. + bool EscapeShortcutCommandLine(std::string* cmdline); + +protected: + QString m_title; + QString m_path; + bool m_desktop; + Ui::ShortcutCreationDialog m_ui; +}; diff --git a/pcsx2-qt/ShortcutCreationDialog.ui b/pcsx2-qt/ShortcutCreationDialog.ui new file mode 100644 index 0000000000..a08db1af19 --- /dev/null +++ b/pcsx2-qt/ShortcutCreationDialog.ui @@ -0,0 +1,317 @@ + + + ShortcutCreationDialog + + + Qt::WindowModality::ApplicationModal + + + + 0 + 0 + 500 + 685 + + + + + + + + + + + + + Display Options + + + + + + + Override boot ELF: + + + + + + + Custom Arguments + + + customArgsInput + + + + + + + + + + + + + Fullscreen mode: + + + + + + + + Force Enable + + + + + Force Disable + + + + + + + + Use Big Picture mode + + + + + + + + + + + + + + Fast Boot + + + + + Full Boot + + + + + + + + Boot mode: + + + + + + + Browse... + + + + + + + + + + + + + + + + false + + + 1 + + + + + + + + + + Do not load save state + + + true + + + + + + + Load save state by slot: + + + + + + + Load save state from file: + + + + + + + Browse... + + + + + + + + + + Save State Options + + + + + + + Game arguments: + + + + + + + + + + + + + + + + You may add additional (space-separated) <a href="https://pcsx2.net/docs/post/cli/">custom arguments</a> that are not listed above here: + + + Qt::TextFormat::RichText + + + true + + + true + + + Qt::TextInteractionFlag::TextBrowserInteraction + + + customArgsInput + + + + + + + + + + Portable Mode (Stores data in local PCSX2 directory) + + + + + + + + + + Shortcut Type + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + Launch Options + + + + + + + + + + + + + Launcher + + + + + + + Desktop + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + shortcutDesktop + shortcutStartMenu + portableModeToggle + overrideBootELFToggle + overrideBootELFPath + overrideBootELFButton + gameArgsToggle + gameArgs + bootOptionToggle + bootOptionDropdown + fullscreenMode + fullscreenModeDropdown + bigPictureModeToggle + loadStateNone + loadStateIndexToggle + loadStateIndex + loadStateFileToggle + loadStateFilePath + loadStateFileBrowse + customArgsInput + + + + + + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 48beecbe7c..f271ae240d 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -171,6 +171,7 @@ + @@ -267,6 +268,7 @@ + @@ -350,6 +352,7 @@ + @@ -360,6 +363,7 @@ + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index 917889b35f..c49839a506 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -240,7 +240,11 @@ moc + + moc + + moc @@ -618,6 +622,7 @@ + Settings @@ -994,6 +999,7 @@ + Debugger