mirror of
https://github.com/shadps4-emu/shadps4-qtlauncher.git
synced 2026-01-31 01:05:16 +01:00
The perfect PR (#147)
* initial interface and logic to populate the version selector table (only GUI, doesn't do anything yet) * update LoadVersionComboBox to the new logic * implement adding a new version to the versions file, and hook it up with "add custom version" * implement deleting version and update logic for dl-ing a new version from the version manager * oof * Clang, Formatter of Night * fix building (how did it even build locally?) * + * Add back shortcut creation (needs better file selection) * Add the shortcut dialog * cleanup * Different file filters per OS * syntax and filter fix * change to launcher shortcut instead of emu shortcut (only default for now) * Update gui_context_menus.h * fix build * Version shortcut added * pass gamepath from args so automatic patch detection works * Update CMakeLists.txt * Fix linux path * support automatic patch detection if gamearg is a serial * linux fix * implement --game/-g instead of passing from emulator_args * fix -d * add --no-ipc flag * fix -e * versions is now using json file * fixing consts * introducing portable/non portable launcher dir * qt settings to launcher dir * compatibility + meta info (no trophies it will go elsewhere after usermanagement) * clang --------- Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Co-authored-by: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com>
This commit is contained in:
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -30,3 +30,7 @@
|
||||
path = externals/MoltenVK/MoltenVK
|
||||
url = https://github.com/KhronosGroup/MoltenVK
|
||||
shallow = true
|
||||
[submodule "externals/json"]
|
||||
path = externals/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
branch = develop
|
||||
|
||||
@@ -283,6 +283,8 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/bounded_threadsafe_queue.h
|
||||
src/common/config.cpp
|
||||
src/common/config.h
|
||||
src/common/versions.cpp
|
||||
src/common/versions.h
|
||||
src/common/endian.h
|
||||
src/common/enum.h
|
||||
src/common/io_file.cpp
|
||||
@@ -325,7 +327,7 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
|
||||
src/qt_gui/cheats_patches.h
|
||||
src/qt_gui/compatibility_info.cpp
|
||||
src/qt_gui/compatibility_info.h
|
||||
src/qt_gui/control_settings.cpp
|
||||
src/qt_gui/control_settings.cpp
|
||||
src/qt_gui/control_settings.h
|
||||
src/qt_gui/control_settings.ui
|
||||
src/qt_gui/kbm_gui.cpp
|
||||
@@ -371,6 +373,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
|
||||
src/qt_gui/version_dialog.cpp
|
||||
src/qt_gui/version_dialog.h
|
||||
src/qt_gui/version_dialog.ui
|
||||
src/qt_gui/create_shortcut.cpp
|
||||
src/qt_gui/create_shortcut.h
|
||||
${RESOURCE_FILES}
|
||||
${TRANSLATIONS}
|
||||
${UPDATER}
|
||||
@@ -421,7 +425,7 @@ if (APPLE)
|
||||
add_dependencies(shadPS4QtLauncher CopyMoltenVK)
|
||||
endif()
|
||||
|
||||
target_link_libraries(shadPS4QtLauncher PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia)
|
||||
target_link_libraries(shadPS4QtLauncher PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia nlohmann_json::nlohmann_json)
|
||||
if (ENABLE_UPDATER)
|
||||
add_definitions(-DENABLE_UPDATER)
|
||||
endif()
|
||||
|
||||
6
externals/CMakeLists.txt
vendored
6
externals/CMakeLists.txt
vendored
@@ -60,4 +60,8 @@ if (APPLE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(volk)
|
||||
add_subdirectory(volk)
|
||||
|
||||
#nlohmann json
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(json)
|
||||
|
||||
1
externals/json
vendored
Submodule
1
externals/json
vendored
Submodule
Submodule externals/json added at 54be9b04f0
@@ -88,7 +88,7 @@ static auto UserPaths = [] {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Try the portable user directory first.
|
||||
// Try the portable launcher directory first.
|
||||
auto user_dir = std::filesystem::current_path() / PORTABLE_DIR;
|
||||
if (!std::filesystem::exists(user_dir)) {
|
||||
// If it doesn't exist, use the standard path for the platform instead.
|
||||
@@ -110,6 +110,29 @@ static auto UserPaths = [] {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Try the portable user directory first.
|
||||
auto launcher_dir = std::filesystem::current_path() / PORTABLE_LAUNCHER_DIR;
|
||||
if (!std::filesystem::exists(launcher_dir)) {
|
||||
// If it doesn't exist, use the standard path for the platform instead.
|
||||
// NOTE: On Windows we currently just create the portable directory instead.
|
||||
#ifdef __APPLE__
|
||||
launcher_dir = std::filesystem::path(getenv("HOME")) / "Library" / "Application Support" /
|
||||
"shadPS4QtLauncher";
|
||||
#elif defined(__linux__)
|
||||
const char* xdg_data_home = getenv("XDG_DATA_HOME");
|
||||
if (xdg_data_home != nullptr && strlen(xdg_data_home) > 0) {
|
||||
launcher_dir = std::filesystem::path(xdg_data_home) / "shadPS4QtLauncher";
|
||||
} else {
|
||||
launcher_dir =
|
||||
std::filesystem::path(getenv("HOME")) / ".local" / "share" / "shadPS4QtLauncher";
|
||||
}
|
||||
#elif _WIN32
|
||||
TCHAR appdata[MAX_PATH] = {0};
|
||||
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, appdata);
|
||||
launcher_dir = std::filesystem::path(appdata) / "shadPS4QtLauncher";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unordered_map<PathType, fs::path> paths;
|
||||
|
||||
const auto create_path = [&](PathType shad_path, const fs::path& new_path) {
|
||||
@@ -131,7 +154,10 @@ static auto UserPaths = [] {
|
||||
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
|
||||
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
|
||||
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
|
||||
create_path(PathType::VersionDir, user_dir / VERSION_DIR);
|
||||
|
||||
create_path(PathType::LauncherDir, launcher_dir);
|
||||
create_path(PathType::LauncherMetaData, launcher_dir / METADATA_DIR);
|
||||
create_path(PathType::VersionDir, launcher_dir / VERSION_DIR);
|
||||
|
||||
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
|
||||
if (notice_file.is_open()) {
|
||||
|
||||
@@ -12,24 +12,27 @@ class QString; // to avoid including <QString> in this header
|
||||
namespace Common::FS {
|
||||
|
||||
enum class PathType {
|
||||
UserDir, // Where shadPS4 stores its data.
|
||||
LogDir, // Where log files are stored.
|
||||
ScreenshotsDir, // Where screenshots are stored.
|
||||
ShaderDir, // Where shaders are stored.
|
||||
TempDataDir, // Where game temp data is stored.
|
||||
GameDataDir, // Where game data is stored.
|
||||
SysModuleDir, // Where system modules are stored.
|
||||
DownloadDir, // Where downloads/temp files are stored.
|
||||
CapturesDir, // Where rdoc captures are stored.
|
||||
CheatsDir, // Where cheats are stored.
|
||||
PatchesDir, // Where patches are stored.
|
||||
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
||||
CustomTrophy, // Where custom files for trophies are stored.
|
||||
CustomConfigs, // Where custom files for different games are stored.
|
||||
VersionDir, // Where emulator versions are stored.
|
||||
UserDir, // Where shadPS4 stores its data.
|
||||
LogDir, // Where log files are stored.
|
||||
ScreenshotsDir, // Where screenshots are stored.
|
||||
ShaderDir, // Where shaders are stored.
|
||||
TempDataDir, // Where game temp data is stored.
|
||||
GameDataDir, // Where game data is stored.
|
||||
SysModuleDir, // Where system modules are stored.
|
||||
DownloadDir, // Where downloads/temp files are stored.
|
||||
CapturesDir, // Where rdoc captures are stored.
|
||||
CheatsDir, // Where cheats are stored.
|
||||
PatchesDir, // Where patches are stored.
|
||||
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
||||
CustomTrophy, // Where custom files for trophies are stored.
|
||||
CustomConfigs, // Where custom files for different games are stored.
|
||||
VersionDir, // Where emulator versions are stored.
|
||||
LauncherDir, // Where launcher stores its data.
|
||||
LauncherMetaData // Where launcher stores its game metadata.
|
||||
};
|
||||
|
||||
constexpr auto PORTABLE_DIR = "user";
|
||||
constexpr auto PORTABLE_LAUNCHER_DIR = "launcher";
|
||||
|
||||
// Sub-directories contained within a user data directory
|
||||
constexpr auto LOG_DIR = "log";
|
||||
|
||||
116
src/common/versions.cpp
Normal file
116
src/common/versions.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "logging/log.h"
|
||||
#include "path_util.h"
|
||||
#include "versions.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <fmt/core.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace VersionManager {
|
||||
|
||||
std::vector<Version> GetVersionList(std::filesystem::path const& path) {
|
||||
std::filesystem::path cfg_path =
|
||||
path.empty() ? Common::FS::GetUserPath(Common::FS::PathType::LauncherDir) / "versions.json"
|
||||
: path;
|
||||
|
||||
std::ifstream ifs(cfg_path);
|
||||
if (!ifs) {
|
||||
fmt::print(stderr, "VersionManager: Config file not found: {}\n", cfg_path.string());
|
||||
return {};
|
||||
}
|
||||
|
||||
json data;
|
||||
try {
|
||||
ifs >> data;
|
||||
} catch (const std::exception& ex) {
|
||||
fmt::print(stderr, "VersionManager: Failed to parse JSON: {}\n", ex.what());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!data.is_array()) {
|
||||
fmt::print(stderr, "VersionManager: Invalid JSON format (expected array)\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Version> versions;
|
||||
int id = 0;
|
||||
|
||||
for (const auto& entry : data) {
|
||||
if (!entry.is_object()) {
|
||||
fmt::print(stderr, "VersionManager: Skipping invalid entry (not an object)\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
Version v{
|
||||
.name = entry.value("name", std::string("no name")),
|
||||
.path = entry.value("path", std::string("")),
|
||||
.date = entry.value("date", std::string("never")),
|
||||
.codename = entry.value("codename", std::string("")),
|
||||
.type = static_cast<VersionType>(
|
||||
entry.value("type", static_cast<int>(VersionType::Custom))),
|
||||
.id = id++,
|
||||
};
|
||||
|
||||
versions.push_back(std::move(v));
|
||||
}
|
||||
|
||||
// Sort by id just for consistent ordering
|
||||
std::sort(versions.begin(), versions.end(),
|
||||
[](const Version& a, const Version& b) { return a.id < b.id; });
|
||||
|
||||
return versions;
|
||||
}
|
||||
|
||||
void SaveVersionList(std::vector<Version> const& versions, std::filesystem::path const& path) {
|
||||
std::filesystem::path out_path =
|
||||
path.empty() ? Common::FS::GetUserPath(Common::FS::PathType::LauncherDir) / "versions.json"
|
||||
: path;
|
||||
|
||||
json root = json::array();
|
||||
|
||||
for (const auto& v : versions) {
|
||||
root.push_back({{"name", v.name},
|
||||
{"path", v.path},
|
||||
{"date", v.date},
|
||||
{"codename", v.codename},
|
||||
{"type", static_cast<int>(v.type)}});
|
||||
}
|
||||
|
||||
std::ofstream ofs(out_path, std::ios::trunc);
|
||||
if (!ofs) {
|
||||
fmt::print(stderr, "Failed to open file for writing: {}\n", out_path.string());
|
||||
return;
|
||||
}
|
||||
|
||||
ofs << std::setw(4) << root;
|
||||
}
|
||||
|
||||
void AddNewVersion(Version const& v, std::filesystem::path const& path) {
|
||||
auto versions = GetVersionList(path);
|
||||
versions.push_back(v);
|
||||
SaveVersionList(versions, path);
|
||||
}
|
||||
|
||||
void RemoveVersion(std::string const& v_name, std::filesystem::path const& path) {
|
||||
auto versions = GetVersionList(path);
|
||||
auto it = std::find_if(versions.begin(), versions.end(),
|
||||
[&](const Version& i) { return i.name == v_name; });
|
||||
if (it != versions.end()) {
|
||||
versions.erase(it);
|
||||
}
|
||||
SaveVersionList(versions, path);
|
||||
}
|
||||
|
||||
void RemoveVersion(Version const& v, std::filesystem::path const& path) {
|
||||
RemoveVersion(v.name, path);
|
||||
}
|
||||
|
||||
} // namespace VersionManager
|
||||
35
src/common/versions.h
Normal file
35
src/common/versions.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <toml.hpp>
|
||||
#include "types.h"
|
||||
|
||||
namespace VersionManager {
|
||||
|
||||
enum VersionType {
|
||||
Release = 0,
|
||||
Nightly = 1,
|
||||
Custom = 2,
|
||||
};
|
||||
|
||||
struct Version {
|
||||
std::string name;
|
||||
std::string path;
|
||||
std::string date;
|
||||
std::string codename;
|
||||
VersionType type;
|
||||
s32 id;
|
||||
};
|
||||
|
||||
std::vector<Version> GetVersionList(std::filesystem::path const& path = "");
|
||||
|
||||
void AddNewVersion(Version const& v, std::filesystem::path const& path = "");
|
||||
void RemoveVersion(Version const& v, std::filesystem::path const& path = "");
|
||||
void RemoveVersion(std::string const& v, std::filesystem::path const& path = "");
|
||||
|
||||
} // namespace VersionManager
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
IpcClient::IpcClient(QObject* parent) : QObject(parent) {}
|
||||
|
||||
void IpcClient::startEmulator(const QFileInfo& exe, const QStringList& args,
|
||||
const QString& workDir) {
|
||||
void IpcClient::startEmulator(const QFileInfo& exe, const QStringList& args, const QString& workDir,
|
||||
bool disable_ipc) {
|
||||
if (process) {
|
||||
process->disconnect();
|
||||
process->deleteLater();
|
||||
@@ -26,7 +26,9 @@ void IpcClient::startEmulator(const QFileInfo& exe, const QStringList& args,
|
||||
process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert("SHADPS4_ENABLE_IPC", "true");
|
||||
if (!disable_ipc) {
|
||||
env.insert("SHADPS4_ENABLE_IPC", "true");
|
||||
}
|
||||
process->setProcessEnvironment(env);
|
||||
|
||||
process->setWorkingDirectory(workDir.isEmpty() ? exe.absolutePath() : workDir);
|
||||
|
||||
@@ -15,7 +15,7 @@ class IpcClient : public QObject {
|
||||
public:
|
||||
explicit IpcClient(QObject* parent = nullptr);
|
||||
void startEmulator(const QFileInfo& exe, const QStringList& args,
|
||||
const QString& workDir = QString());
|
||||
const QString& workDir = QString(), bool disable_ipc = false);
|
||||
void startGame();
|
||||
void pauseGame();
|
||||
void resumeGame();
|
||||
|
||||
33
src/main.cpp
33
src/main.cpp
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/versions.h"
|
||||
#include "qt_gui/game_install_dialog.h"
|
||||
#include "qt_gui/main_window.h"
|
||||
#ifdef _WIN32
|
||||
@@ -35,9 +36,10 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
const bool has_command_line_argument = argc > 1;
|
||||
bool has_emulator_argument = false;
|
||||
bool show_gui = false;
|
||||
bool show_gui = false, no_ipc = false;
|
||||
std::string emulator;
|
||||
QStringList emulator_args{};
|
||||
QString game_arg = "";
|
||||
|
||||
// Ignore Qt logs
|
||||
qInstallMessageHandler(customMessageHandler);
|
||||
@@ -52,11 +54,13 @@ int main(int argc, char* argv[]) {
|
||||
" No arguments: Opens the GUI.\n"
|
||||
" -e, --emulator <name|path> Specify the emulator version/path you want to "
|
||||
"use, or 'default' for using the version selected in the config.\n"
|
||||
" -g, --game <ID|path> Specify game to launch.\n"
|
||||
" -d Alias for '-e default'.\n"
|
||||
" -- ... Parameters passed to the emulator core. "
|
||||
"Needs to be at the end of the line, and everything after '--' is an "
|
||||
"emulator argument.\n"
|
||||
" -s, --show-gui Show the GUI.\n"
|
||||
" -i, --no-ipc Disable IPC.\n"
|
||||
" -h, --help Display this help message.\n";
|
||||
exit(0);
|
||||
}},
|
||||
@@ -64,14 +68,26 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
{"-s", [&](int&) { show_gui = true; }},
|
||||
{"--show-gui", [&](int& i) { arg_map["-s"](i); }},
|
||||
{"-i", [&](int&) { no_ipc = true; }},
|
||||
{"--no-ipc", [&](int& i) { arg_map["-i"](i); }},
|
||||
|
||||
{"-g",
|
||||
[&](int& i) {
|
||||
if (i + 1 < argc) {
|
||||
game_arg = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: Missing argument for -g/--game\n";
|
||||
exit(1);
|
||||
}
|
||||
}},
|
||||
{"--game", [&](int& i) { arg_map["-g"](i); }},
|
||||
{"-e",
|
||||
[&](int& i) {
|
||||
if (i + 1 < argc) {
|
||||
emulator = argv[++i];
|
||||
has_emulator_argument = true;
|
||||
} else {
|
||||
std::cerr << "Error: Missing argument for -g/--game\n";
|
||||
std::cerr << "Error: Missing argument for -e/--emulator\n";
|
||||
exit(1);
|
||||
}
|
||||
}},
|
||||
@@ -137,13 +153,12 @@ int main(int argc, char* argv[]) {
|
||||
emulator_path = emulator;
|
||||
} else if (emulator == "default") {
|
||||
gui_settings settings{};
|
||||
emulator_path = *std::filesystem::directory_iterator(
|
||||
settings.GetValue(gui::vm_versionSelected).toString().toStdString());
|
||||
emulator_path = settings.GetValue(gui::vm_versionSelected).toString().toStdString();
|
||||
} else {
|
||||
std::filesystem::path version_dir = user_dir / "versions";
|
||||
for (auto const& version : std::filesystem::directory_iterator(version_dir)) {
|
||||
if (version.is_directory() && version.path().filename() == emulator) {
|
||||
emulator_path = *std::filesystem::directory_iterator(version);
|
||||
auto const& versions = VersionManager::GetVersionList();
|
||||
for (auto const& v : versions) {
|
||||
if (v.name == emulator) {
|
||||
emulator_path = v.path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +170,7 @@ int main(int argc, char* argv[]) {
|
||||
if (!show_gui) {
|
||||
m_main_window->m_ipc_client->gameClosedFunc = StopProgram;
|
||||
}
|
||||
m_main_window->StartEmulatorExecutable(emulator_path, emulator_args);
|
||||
m_main_window->StartEmulatorExecutable(emulator_path, game_arg, emulator_args, no_ipc);
|
||||
}
|
||||
|
||||
if (!has_emulator_argument || show_gui) {
|
||||
|
||||
@@ -13,7 +13,7 @@ CompatibilityInfoClass::CompatibilityInfoClass()
|
||||
: m_network_manager(new QNetworkAccessManager(this)) {
|
||||
QStringList file_paths;
|
||||
std::filesystem::path compatibility_file_path =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / "compatibility_data.json";
|
||||
Common::FS::GetUserPath(Common::FS::PathType::LauncherDir) / "compatibility_data.json";
|
||||
Common::FS::PathToQString(m_compatibility_filename, compatibility_file_path);
|
||||
};
|
||||
CompatibilityInfoClass::~CompatibilityInfoClass() = default;
|
||||
|
||||
68
src/qt_gui/create_shortcut.cpp
Normal file
68
src/qt_gui/create_shortcut.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDirIterator>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "common/path_util.h"
|
||||
#include "create_shortcut.h"
|
||||
|
||||
ShortcutDialog::ShortcutDialog(std::shared_ptr<gui_settings> settings, QWidget* parent)
|
||||
: QDialog(parent), m_gui_settings(std::move(settings)) {
|
||||
setupUI();
|
||||
resize(600, 50);
|
||||
|
||||
this->setWindowTitle(tr("Select Version"));
|
||||
}
|
||||
|
||||
void ShortcutDialog::setupUI() {
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
|
||||
QLabel* versionLabel =
|
||||
new QLabel(QString("<b>%1</b>").arg(tr("Select version for shortcut creation")));
|
||||
|
||||
listWidget = new QListWidget(this);
|
||||
QString versionFolder = m_gui_settings->GetValue(gui::vm_versionPath).toString();
|
||||
QDirIterator versions(versionFolder, QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
|
||||
while (versions.hasNext()) {
|
||||
versions.next();
|
||||
new QListWidgetItem(versions.fileName(), listWidget);
|
||||
}
|
||||
|
||||
QDialogButtonBox* buttonBox =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
mainLayout->addWidget(versionLabel);
|
||||
mainLayout->addWidget(listWidget);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &ShortcutDialog::createShortcut);
|
||||
}
|
||||
|
||||
void ShortcutDialog::createShortcut() {
|
||||
if (listWidget->selectedItems().empty()) {
|
||||
QMessageBox::information(this, tr("No Version Selected"), tr("Select a version first"));
|
||||
return;
|
||||
}
|
||||
|
||||
QString versionFolder = m_gui_settings->GetValue(gui::vm_versionPath).toString();
|
||||
QString versionName = "/" + listWidget->currentItem()->text();
|
||||
QString exeName;
|
||||
#ifdef Q_OS_WIN
|
||||
exeName = "/shadPS4.exe";
|
||||
#elif defined(Q_OS_LINUX)
|
||||
exeName = "/Shadps4-sdl.AppImage";
|
||||
#elif defined(Q_OS_MACOS)
|
||||
exeName = "/shadps4";
|
||||
#endif
|
||||
|
||||
emit shortcutRequested(versionFolder + versionName + exeName);
|
||||
QWidget::close();
|
||||
}
|
||||
|
||||
ShortcutDialog::~ShortcutDialog() {}
|
||||
27
src/qt_gui/create_shortcut.h
Normal file
27
src/qt_gui/create_shortcut.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListWidget>
|
||||
|
||||
#include "gui_settings.h"
|
||||
|
||||
class ShortcutDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ShortcutDialog(std::shared_ptr<gui_settings> settings, QWidget* parent = nullptr);
|
||||
~ShortcutDialog();
|
||||
|
||||
signals:
|
||||
void shortcutRequested(QString version);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void createShortcut();
|
||||
|
||||
QListWidget* listWidget;
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
};
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
|
||||
// Cache path
|
||||
QDir cacheDir =
|
||||
QDir(Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game.serial);
|
||||
QDir(Common::FS::GetUserPath(Common::FS::PathType::LauncherMetaData) / game.serial);
|
||||
if (!cacheDir.exists()) {
|
||||
cacheDir.mkpath(".");
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#include <qt_gui/background_music_player.h>
|
||||
#include "background_music_player.h"
|
||||
#include "cheats_patches.h"
|
||||
#include "common/config.h"
|
||||
#include "common/log_analyzer.h"
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "common/path_util.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "compatibility_info.h"
|
||||
#include "create_shortcut.h"
|
||||
#include "game_info.h"
|
||||
#include "gui_settings.h"
|
||||
#include "ipc/ipc_client.h"
|
||||
@@ -26,6 +27,11 @@
|
||||
#include "trophy_viewer.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <ShlObj.h>
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
#include <shlguid.h>
|
||||
#include <shobjidl.h>
|
||||
#include <wrl/client.h>
|
||||
#endif
|
||||
|
||||
@@ -113,7 +119,15 @@ public:
|
||||
QAction openCheats(tr("Cheats / Patches"), widget);
|
||||
QAction openTrophyViewer(tr("Trophy Viewer"), widget);
|
||||
QAction openSfoViewer(tr("SFO Viewer"), widget);
|
||||
QAction createDefaultShortcut(tr("Create Shortcut for Selected Emulator Version"), widget);
|
||||
QAction createVersionShortcut(tr("Create Shortcut for Specified Emulator Version"), widget);
|
||||
|
||||
#ifndef Q_OS_APPLE
|
||||
QMenu* shortcutMenu = new QMenu(tr("Create Shortcut"), widget);
|
||||
menu.addMenu(shortcutMenu);
|
||||
shortcutMenu->addAction(&createDefaultShortcut);
|
||||
shortcutMenu->addAction(&createVersionShortcut);
|
||||
#endif
|
||||
menu.addAction(toggleFavorite);
|
||||
menu.addAction(&openCheats);
|
||||
menu.addAction(&openTrophyViewer);
|
||||
@@ -446,6 +460,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (selected == &createDefaultShortcut) {
|
||||
requestShortcut(m_games[itemID]);
|
||||
}
|
||||
|
||||
if (selected == &createVersionShortcut) {
|
||||
auto shortcutWindow = new ShortcutDialog(m_gui_settings);
|
||||
|
||||
QObject::connect(
|
||||
shortcutWindow, &ShortcutDialog::shortcutRequested, this,
|
||||
[=, this](QString version) { requestShortcut(m_games[itemID], version); });
|
||||
|
||||
shortcutWindow->exec();
|
||||
}
|
||||
|
||||
// Handle the "Copy" actions
|
||||
if (selected == copyName) {
|
||||
QClipboard* clipboard = QGuiApplication::clipboard();
|
||||
@@ -651,4 +679,170 @@ public:
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private:
|
||||
void requestShortcut(const GameInfo& selectedInfo, QString emuPath = "") {
|
||||
// Path to shortcut/link
|
||||
QString linkPath;
|
||||
|
||||
// Eboot path
|
||||
QString targetPath;
|
||||
Common::FS::PathToQString(targetPath, selectedInfo.path);
|
||||
QString ebootPath = targetPath + "/eboot.bin";
|
||||
|
||||
// Get the full path to the icon
|
||||
QString iconPath;
|
||||
Common::FS::PathToQString(iconPath, selectedInfo.icon_path);
|
||||
QFileInfo iconFileInfo(iconPath);
|
||||
QString icoPath = iconFileInfo.absolutePath() + "/" + iconFileInfo.baseName() + ".ico";
|
||||
|
||||
QString exePath;
|
||||
#ifdef Q_OS_WIN
|
||||
linkPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/" +
|
||||
QString::fromStdString(selectedInfo.name)
|
||||
.remove(QRegularExpression("[\\\\/:*?\"<>|]")) +
|
||||
".lnk";
|
||||
|
||||
exePath = QCoreApplication::applicationFilePath().replace("\\", "/");
|
||||
#else
|
||||
linkPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/" +
|
||||
QString::fromStdString(selectedInfo.name)
|
||||
.remove(QRegularExpression("[\\\\/:*?\"<>|]")) +
|
||||
".desktop";
|
||||
#endif
|
||||
|
||||
// Convert the icon to .ico if necessary
|
||||
if (iconFileInfo.suffix().toLower() == "png") {
|
||||
// Convert icon from PNG to ICO
|
||||
if (convertPngToIco(iconPath, icoPath)) {
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (createShortcutWin(linkPath, ebootPath, icoPath, exePath, emuPath)) {
|
||||
#else
|
||||
if (createShortcutLinux(linkPath, selectedInfo.name, ebootPath, iconPath,
|
||||
emuPath)) {
|
||||
#endif
|
||||
QMessageBox::information(
|
||||
nullptr, tr("Shortcut creation"),
|
||||
QString(tr("Shortcut created successfully!") + "\n%1").arg(linkPath));
|
||||
} else {
|
||||
QMessageBox::critical(
|
||||
nullptr, tr("Error"),
|
||||
QString(tr("Error creating shortcut!") + "\n%1").arg(linkPath));
|
||||
}
|
||||
} else {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("Failed to convert icon."));
|
||||
}
|
||||
|
||||
// If the icon is already in ICO format, we just create the shortcut
|
||||
} else {
|
||||
#ifdef Q_OS_WIN
|
||||
if (createShortcutWin(linkPath, ebootPath, iconPath, exePath, emuPath)) {
|
||||
#else
|
||||
if (createShortcutLinux(linkPath, selectedInfo.name, ebootPath, iconPath, emuPath)) {
|
||||
#endif
|
||||
QMessageBox::information(
|
||||
nullptr, tr("Shortcut creation"),
|
||||
QString(tr("Shortcut created successfully!") + "\n%1").arg(linkPath));
|
||||
} else {
|
||||
QMessageBox::critical(
|
||||
nullptr, tr("Error"),
|
||||
QString(tr("Error creating shortcut!") + "\n%1").arg(linkPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool convertPngToIco(const QString& pngFilePath, const QString& icoFilePath) {
|
||||
// Load the PNG image
|
||||
QImage image(pngFilePath);
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scale the image to the default icon size (256x256 pixels)
|
||||
QImage scaledImage =
|
||||
image.scaled(QSize(256, 256), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
// Convert the image to QPixmap
|
||||
QPixmap pixmap = QPixmap::fromImage(scaledImage);
|
||||
|
||||
// Save the pixmap as an ICO file
|
||||
if (pixmap.save(icoFilePath, "ICO")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
bool createShortcutWin(const QString& linkPath, const QString& targetPath,
|
||||
const QString& iconPath, const QString& exePath, QString emuPath) {
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
// Create the ShellLink object
|
||||
Microsoft::WRL::ComPtr<IShellLink> pShellLink;
|
||||
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&pShellLink));
|
||||
if (SUCCEEDED(hres)) {
|
||||
// Defines the path to the program executable
|
||||
pShellLink->SetPath((LPCWSTR)exePath.utf16());
|
||||
|
||||
// Sets the home directory ("Start in")
|
||||
pShellLink->SetWorkingDirectory((LPCWSTR)QFileInfo(exePath).absolutePath().utf16());
|
||||
|
||||
// Set arguments, eboot.bin file location
|
||||
|
||||
QString arguments;
|
||||
|
||||
if (emuPath == "") {
|
||||
arguments = QString("-d -g \"%1\"").arg(targetPath);
|
||||
} else {
|
||||
arguments = QString("-e \"%1\" -g \"%2\"").arg(emuPath, targetPath);
|
||||
}
|
||||
pShellLink->SetArguments((LPCWSTR)arguments.utf16());
|
||||
|
||||
// Set the icon for the shortcut
|
||||
pShellLink->SetIconLocation((LPCWSTR)iconPath.utf16(), 0);
|
||||
|
||||
// Save the shortcut
|
||||
Microsoft::WRL::ComPtr<IPersistFile> pPersistFile;
|
||||
hres = pShellLink.As(&pPersistFile);
|
||||
if (SUCCEEDED(hres)) {
|
||||
hres = pPersistFile->Save((LPCWSTR)linkPath.utf16(), TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
|
||||
return SUCCEEDED(hres);
|
||||
}
|
||||
#else
|
||||
bool createShortcutLinux(const QString& linkPath, const std::string& name,
|
||||
const QString& targetPath, const QString& iconPath, QString emuPath) {
|
||||
QFile shortcutFile(linkPath);
|
||||
if (!shortcutFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(nullptr, "Error",
|
||||
QString("Error creating shortcut!\n %1").arg(linkPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream out(&shortcutFile);
|
||||
out << "[Desktop Entry]\n";
|
||||
out << "Version=1.0\n";
|
||||
out << "Name=" << QString::fromStdString(name) << "\n";
|
||||
if (emuPath == "") {
|
||||
out << "Exec=" << QCoreApplication::applicationFilePath() << " -d -g" << " \""
|
||||
<< targetPath << "\"\n";
|
||||
} else {
|
||||
out << "Exec=" << QCoreApplication::applicationFilePath() << " -e" << " \"" << emuPath
|
||||
<< "\"" << " -g" << " \"" << targetPath << "\"\n";
|
||||
}
|
||||
out << "Icon=" << iconPath << "\n";
|
||||
out << "Terminal=false\n";
|
||||
out << "Type=Application\n";
|
||||
shortcutFile.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "common/memory_patcher.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/versions.h"
|
||||
#include "control_settings.h"
|
||||
#include "game_install_dialog.h"
|
||||
#include "hotkeys.h"
|
||||
@@ -1268,16 +1269,7 @@ tr("No emulator version was selected.\nThe Version Manager menu will then open.\
|
||||
Config::setGameRunning(true);
|
||||
last_game_path = path;
|
||||
|
||||
QString exeName;
|
||||
#ifdef Q_OS_WIN
|
||||
exeName = "/shadPS4.exe";
|
||||
#elif defined(Q_OS_LINUX)
|
||||
exeName = "/Shadps4-sdl.AppImage";
|
||||
#elif defined(Q_OS_MACOS)
|
||||
exeName = "/shadps4";
|
||||
#endif
|
||||
QString exe = selectedVersion + exeName;
|
||||
QFileInfo fileInfo(exe);
|
||||
QFileInfo fileInfo(selectedVersion);
|
||||
if (!fileInfo.exists()) {
|
||||
QMessageBox::critical(nullptr, "shadPS4",
|
||||
QString(tr("Could not find the emulator executable")));
|
||||
@@ -1294,26 +1286,71 @@ tr("No emulator version was selected.\nThe Version Manager menu will then open.\
|
||||
m_ipc_client->setActiveController(GamepadSelect::GetSelectedGamepad());
|
||||
}
|
||||
|
||||
void MainWindow::StartEmulatorExecutable(std::filesystem::path path, QStringList args) {
|
||||
void MainWindow::StartEmulatorExecutable(std::filesystem::path emuPath, QString gameArg,
|
||||
QStringList args, bool disable_ipc) {
|
||||
if (Config::getGameRunning()) {
|
||||
QMessageBox::critical(nullptr, tr("Run Emulator"),
|
||||
QString(tr("Emulator is already running!")));
|
||||
return;
|
||||
}
|
||||
|
||||
Config::setGameRunning(true);
|
||||
QFileInfo fileInfo(path);
|
||||
bool gameFound = false;
|
||||
if (std::filesystem::exists(Common::FS::PathFromQString(gameArg))) {
|
||||
last_game_path = Common::FS::PathFromQString(gameArg);
|
||||
gameFound = true;
|
||||
} else {
|
||||
// In install folders, find game folder with same name as gameArg
|
||||
const auto install_dir_array = Config::getGameInstallDirs();
|
||||
std::vector<bool> install_dirs_enabled;
|
||||
|
||||
try {
|
||||
install_dirs_enabled = Config::getGameInstallDirsEnabled();
|
||||
} catch (...) {
|
||||
// If it does not exist, assume that all are enabled.
|
||||
install_dirs_enabled.resize(install_dir_array.size(), true);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < install_dir_array.size(); i++) {
|
||||
std::filesystem::path dir = install_dir_array[i];
|
||||
bool enabled = install_dirs_enabled[i];
|
||||
|
||||
if (enabled && std::filesystem::exists(dir)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir)) {
|
||||
if (entry.is_directory()) {
|
||||
if (entry.path().filename().string() == gameArg.toStdString()) {
|
||||
last_game_path = entry.path() / "eboot.bin";
|
||||
gameFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameFound)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!gameArg.isEmpty()) {
|
||||
if (!gameFound) {
|
||||
QMessageBox::critical(nullptr, "shadPS4",
|
||||
QString(tr("Invalid game argument provided")));
|
||||
quick_exit(1);
|
||||
}
|
||||
|
||||
QStringList game_args{"--game", QString::fromStdWString(last_game_path.wstring())};
|
||||
args.append(game_args);
|
||||
}
|
||||
|
||||
QFileInfo fileInfo(emuPath);
|
||||
if (!fileInfo.exists()) {
|
||||
QMessageBox::critical(nullptr, "shadPS4",
|
||||
QString(tr("Could not find the emulator executable")));
|
||||
Config::setGameRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Config::setGameRunning(true);
|
||||
QString workDir = QDir::currentPath();
|
||||
|
||||
m_ipc_client->startEmulator(fileInfo, args, workDir);
|
||||
m_ipc_client->setActiveController(GamepadSelect::GetSelectedGamepad());
|
||||
m_ipc_client->startEmulator(fileInfo, args, workDir, disable_ipc);
|
||||
}
|
||||
|
||||
void MainWindow::RunGame() {
|
||||
@@ -1360,75 +1397,17 @@ void MainWindow::RestartEmulator() {
|
||||
}
|
||||
|
||||
void MainWindow::LoadVersionComboBox() {
|
||||
QString savedVersionPath = m_gui_settings->GetValue(gui::vm_versionSelected).toString();
|
||||
if (savedVersionPath.isEmpty() || !QDir(savedVersionPath).exists()) {
|
||||
ui->versionComboBox->clear();
|
||||
ui->versionComboBox->addItem(tr("No Version Selected"));
|
||||
ui->versionComboBox->setCurrentIndex(0);
|
||||
ui->versionComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
ui->versionComboBox->adjustSize();
|
||||
return;
|
||||
}
|
||||
|
||||
QString path = m_gui_settings->GetValue(gui::vm_versionPath).toString();
|
||||
if (path.isEmpty() || !QDir(path).exists())
|
||||
return;
|
||||
|
||||
ui->versionComboBox->clear();
|
||||
ui->versionComboBox->addItem(tr("None"));
|
||||
ui->versionComboBox->setCurrentIndex(0);
|
||||
ui->versionComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
|
||||
QStringList folders = QDir(path).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
QRegularExpression versionRegex("^v(\\d+)\\.(\\d+)\\.(\\d+)$");
|
||||
QString savedVersionPath = m_gui_settings->GetValue(gui::vm_versionSelected).toString();
|
||||
|
||||
QVector<QPair<QVector<int>, QString>> versionedDirs;
|
||||
QStringList otherDirs;
|
||||
|
||||
for (const QString& folder : folders) {
|
||||
if (folder == "Pre-release") {
|
||||
otherDirs.append(folder);
|
||||
continue;
|
||||
}
|
||||
|
||||
QRegularExpressionMatch match = versionRegex.match(folder.section(" - ", 0, 0));
|
||||
if (match.hasMatch()) {
|
||||
QVector<int> versionParts = {match.captured(1).toInt(), match.captured(2).toInt(),
|
||||
match.captured(3).toInt()};
|
||||
versionedDirs.append({versionParts, folder});
|
||||
} else {
|
||||
otherDirs.append(folder);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(otherDirs.begin(), otherDirs.end());
|
||||
|
||||
std::sort(versionedDirs.begin(), versionedDirs.end(), [](const auto& a, const auto& b) {
|
||||
if (a.first[0] != b.first[0])
|
||||
return a.first[0] > b.first[0];
|
||||
if (a.first[1] != b.first[1])
|
||||
return a.first[1] > b.first[1];
|
||||
return a.first[2] > b.first[2];
|
||||
});
|
||||
|
||||
auto addEntry = [&](const QString& folder) {
|
||||
QString fullPath = QDir(path).filePath(folder);
|
||||
QString label;
|
||||
|
||||
if (folder.startsWith("Pre-release-shadPS4")) {
|
||||
label = "Pre-release";
|
||||
} else if (folder.contains(" - ")) {
|
||||
label = folder.section(" - ", 0, 0);
|
||||
} else {
|
||||
label = folder;
|
||||
}
|
||||
|
||||
ui->versionComboBox->addItem(label, fullPath);
|
||||
};
|
||||
|
||||
for (const QString& folder : otherDirs) {
|
||||
addEntry(folder);
|
||||
}
|
||||
|
||||
for (const auto& pair : versionedDirs) {
|
||||
addEntry(pair.second);
|
||||
auto const& versions = VersionManager::GetVersionList();
|
||||
for (auto const& v : versions) {
|
||||
ui->versionComboBox->addItem(QString::fromStdString(v.name),
|
||||
QString::fromStdString(v.path));
|
||||
}
|
||||
|
||||
int selectedIndex = ui->versionComboBox->findData(savedVersionPath);
|
||||
|
||||
@@ -36,7 +36,8 @@ public:
|
||||
void StartGame();
|
||||
void StartGameWithArgs(QStringList args = {});
|
||||
void StartEmulator(std::filesystem::path path, QStringList args = {});
|
||||
void StartEmulatorExecutable(std::filesystem::path path, QStringList args = {});
|
||||
void StartEmulatorExecutable(std::filesystem::path emupath, QString gameArg,
|
||||
QStringList args = {}, bool disable_ipc = false);
|
||||
void PauseGame();
|
||||
void StopGame();
|
||||
void RestartGame();
|
||||
|
||||
@@ -21,7 +21,7 @@ QString settings::GetSettingsDir() const {
|
||||
}
|
||||
|
||||
QString settings::ComputeSettingsDir() {
|
||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::LauncherDir);
|
||||
return QString::fromStdString(config_dir.string() + "/");
|
||||
}
|
||||
|
||||
|
||||
@@ -328,17 +328,17 @@ SettingsDialog::SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
|
||||
});
|
||||
|
||||
connect(ui->PortableUserButton, &QPushButton::clicked, this, []() {
|
||||
QString userDir;
|
||||
Common::FS::PathToQString(userDir, std::filesystem::current_path() / "user");
|
||||
if (std::filesystem::exists(std::filesystem::current_path() / "user")) {
|
||||
QMessageBox::information(NULL, tr("Cannot create portable user folder"),
|
||||
tr("%1 already exists").arg(userDir));
|
||||
QString launcherDir;
|
||||
Common::FS::PathToQString(launcherDir, std::filesystem::current_path() / "launcher");
|
||||
if (std::filesystem::exists(std::filesystem::current_path() / "launcher")) {
|
||||
QMessageBox::information(NULL, tr("Cannot create portable launcher folder"),
|
||||
tr("%1 already exists").arg(launcherDir));
|
||||
} else {
|
||||
std::filesystem::copy(Common::FS::GetUserPath(Common::FS::PathType::UserDir),
|
||||
std::filesystem::current_path() / "user",
|
||||
std::filesystem::copy(Common::FS::GetUserPath(Common::FS::PathType::LauncherDir),
|
||||
std::filesystem::current_path() / "launcher",
|
||||
std::filesystem::copy_options::recursive);
|
||||
QMessageBox::information(NULL, tr("Portable user folder created"),
|
||||
tr("%1 successfully created.").arg(userDir));
|
||||
QMessageBox::information(NULL, tr("Portable launcherDir folder created"),
|
||||
tr("%1 successfully created.").arg(launcherDir));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <common/path_util.h>
|
||||
#include <common/versions.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "gui_settings.h"
|
||||
@@ -29,6 +30,8 @@ VersionDialog::VersionDialog(std::shared_ptr<gui_settings> gui_settings, QWidget
|
||||
: QDialog(parent), ui(new Ui::VersionDialog), m_gui_settings(std::move(gui_settings)) {
|
||||
ui->setupUi(this);
|
||||
|
||||
auto const& version_list = VersionManager::GetVersionList();
|
||||
|
||||
ui->checkOnStartupCheckBox->setChecked(
|
||||
m_gui_settings->GetValue(gui::vm_checkOnStartup).toBool());
|
||||
ui->showChangelogCheckBox->setChecked(m_gui_settings->GetValue(gui::vm_showChangeLog).toBool());
|
||||
@@ -81,7 +84,7 @@ VersionDialog::VersionDialog(std::shared_ptr<gui_settings> gui_settings, QWidget
|
||||
connect(ui->checkChangesVersionButton, &QPushButton::clicked, this,
|
||||
[this]() { LoadInstalledList(); });
|
||||
|
||||
connect(ui->addCustomVersionButton, &QPushButton::clicked, this, [this]() {
|
||||
connect(ui->addCustomVersionButton, &QPushButton::clicked, this, [this, version_list]() {
|
||||
QString exePath;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -89,46 +92,40 @@ VersionDialog::VersionDialog(std::shared_ptr<gui_settings> gui_settings, QWidget
|
||||
tr("Executable (*.exe)"));
|
||||
#elif defined(Q_OS_LINUX)
|
||||
exePath = QFileDialog::getOpenFileName(this, tr("Select executable"), QDir::rootPath(),
|
||||
tr("Executable (*.AppImage)"));
|
||||
"Executable (*)");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
exePath = QFileDialog::getOpenFileName(this, tr("Select executable"), QDir::rootPath(),
|
||||
tr("Executable (*.*)"));
|
||||
"Executable (*.*)");
|
||||
#endif
|
||||
|
||||
if (exePath.isEmpty())
|
||||
return;
|
||||
|
||||
bool ok;
|
||||
QString folderName =
|
||||
QString version_name =
|
||||
QInputDialog::getText(this, tr("Version name"),
|
||||
tr("Enter the name of this version as it appears in the list."),
|
||||
QLineEdit::Normal, "", &ok);
|
||||
if (!ok || folderName.trimmed().isEmpty())
|
||||
if (!ok || version_name.trimmed().isEmpty())
|
||||
return;
|
||||
|
||||
folderName = folderName.trimmed();
|
||||
version_name = version_name.trimmed();
|
||||
|
||||
QString basePath = m_gui_settings->GetValue(gui::vm_versionPath).toString();
|
||||
QString newFolderPath = QDir(basePath).filePath(folderName);
|
||||
|
||||
QDir dir;
|
||||
if (dir.exists(newFolderPath)) {
|
||||
QMessageBox::warning(this, tr("Error"), tr("A folder with that name already exists."));
|
||||
if (std::find_if(version_list.cbegin(), version_list.cend(), [version_name](auto i) {
|
||||
return i.name == version_name.toStdString();
|
||||
}) != version_list.cend()) {
|
||||
QMessageBox::warning(this, tr("Error"), tr("A version with that name already exists."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dir.mkpath(newFolderPath)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to create folder."));
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo exeInfo(exePath);
|
||||
QString targetFilePath = QDir(newFolderPath).filePath(exeInfo.fileName());
|
||||
|
||||
if (!QFile::copy(exePath, targetFilePath)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to copy executable."));
|
||||
return;
|
||||
}
|
||||
VersionManager::Version new_version = {
|
||||
.name = version_name.toStdString(),
|
||||
.path = exePath.toStdString(),
|
||||
.date = QDateTime::currentDateTime().toString("yyyy.MM.dd. HH:mm").toStdString(),
|
||||
.codename = "",
|
||||
.type = VersionManager::VersionType::Custom,
|
||||
};
|
||||
VersionManager::AddNewVersion(new_version);
|
||||
|
||||
QMessageBox::information(this, tr("Success"), tr("Version added successfully."));
|
||||
LoadInstalledList();
|
||||
@@ -149,21 +146,13 @@ VersionDialog::VersionDialog(std::shared_ptr<gui_settings> gui_settings, QWidget
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to determine the folder path."));
|
||||
return;
|
||||
}
|
||||
QString folderName = QDir(fullPath).dirName();
|
||||
auto reply = QMessageBox::question(this, tr("Delete version"),
|
||||
tr("Do you want to delete the version") +
|
||||
QString(" \"%1\" ?").arg(folderName),
|
||||
QString(" \"%1\" ?").arg(selectedItem->text(1)),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::Yes) {
|
||||
QDir dirToRemove(fullPath);
|
||||
if (dirToRemove.exists()) {
|
||||
if (!dirToRemove.removeRecursively()) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Failed to delete folder.") +
|
||||
QString("\n \"%1\"").arg(folderName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// not removing any files, as that might be a problem with local ones
|
||||
VersionManager::RemoveVersion(selectedItem->text(1).toStdString());
|
||||
LoadInstalledList();
|
||||
}
|
||||
});
|
||||
@@ -339,7 +328,7 @@ tr("First you need to choose a location to save the versions in\n'Path to save v
|
||||
.arg(versionName);
|
||||
}
|
||||
|
||||
{ // Menssage yes/no
|
||||
{ // Message yes/no
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(this, tr("Confirm Download"),
|
||||
tr("Do you want to download the version") +
|
||||
@@ -514,7 +503,8 @@ tr("First you need to choose a location to save the versions in\n'Path to save v
|
||||
QProcess::startDetached(process, args);
|
||||
|
||||
QTimer::singleShot(
|
||||
4000, this, [this, folderName, progressDialog, versionName]() {
|
||||
4000, this,
|
||||
[this, folderName, progressDialog, versionName, release]() {
|
||||
progressDialog->close();
|
||||
progressDialog->deleteLater();
|
||||
|
||||
@@ -522,13 +512,35 @@ tr("First you need to choose a location to save the versions in\n'Path to save v
|
||||
m_gui_settings->GetValue(gui::vm_versionPath).toString();
|
||||
QString fullPath = QDir(userPath).filePath(folderName);
|
||||
|
||||
m_gui_settings->SetValue(gui::vm_versionSelected, fullPath);
|
||||
|
||||
QMessageBox::information(
|
||||
this, tr("Confirm Download"),
|
||||
tr("Version %1 has been downloaded and selected.")
|
||||
.arg(versionName));
|
||||
|
||||
bool is_release = !versionName.contains("Pre-release");
|
||||
auto release_name = release["name"].toString();
|
||||
QString code_name = "";
|
||||
static constexpr QStringView marker = u" - codename ";
|
||||
int idx = release_name.indexOf(u" - codename ");
|
||||
if (idx != -1) {
|
||||
code_name = release_name.mid(idx + marker.size());
|
||||
}
|
||||
std::filesystem::path exe_path =
|
||||
*std::filesystem::directory_iterator{
|
||||
fullPath.toStdString()};
|
||||
VersionManager::Version new_version{
|
||||
.name = versionName.toStdString(),
|
||||
.path = exe_path.generic_string(),
|
||||
.date = release["published_at"]
|
||||
.toString()
|
||||
.left(10)
|
||||
.toStdString(),
|
||||
.codename = code_name.toStdString(),
|
||||
.type = is_release ? VersionManager::VersionType::Release
|
||||
: VersionManager::VersionType::Nightly,
|
||||
};
|
||||
m_gui_settings->SetValue(gui::vm_versionSelected,
|
||||
QString(exe_path.c_str()));
|
||||
VersionManager::AddNewVersion(new_version);
|
||||
LoadInstalledList();
|
||||
});
|
||||
} else {
|
||||
@@ -543,110 +555,24 @@ tr("First you need to choose a location to save the versions in\n'Path to save v
|
||||
}
|
||||
|
||||
void VersionDialog::LoadInstalledList() {
|
||||
QString path = m_gui_settings->GetValue(gui::vm_versionPath).toString();
|
||||
QDir dir(path);
|
||||
if (!dir.exists() || path.isEmpty())
|
||||
return;
|
||||
const auto path = Common::FS::GetUserPath(Common::FS::PathType::LauncherDir) / "versions.json";
|
||||
auto versions = VersionManager::GetVersionList(path);
|
||||
auto const& selected_version =
|
||||
m_gui_settings->GetValue(gui::vm_versionSelected).toString().toStdString();
|
||||
|
||||
ui->installedTreeWidget->clear();
|
||||
ui->installedTreeWidget->setColumnCount(5);
|
||||
ui->installedTreeWidget->setColumnHidden(4, true);
|
||||
|
||||
QStringList folders = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
|
||||
QRegularExpression versionRegex("^v(\\d+)\\.(\\d+)\\.(\\d+)$");
|
||||
|
||||
QVector<QPair<QVector<int>, QString>> versionedDirs;
|
||||
|
||||
QStringList otherDirs;
|
||||
QString savedVersionPath = m_gui_settings->GetValue(gui::vm_versionSelected).toString();
|
||||
|
||||
for (const QString& folder : folders) {
|
||||
if (folder == "Pre-release") {
|
||||
otherDirs.append(folder);
|
||||
continue;
|
||||
}
|
||||
QRegularExpressionMatch match = versionRegex.match(folder.section(" - ", 0, 0));
|
||||
if (match.hasMatch()) {
|
||||
QVector<int> versionParts = {match.captured(1).toInt(), match.captured(2).toInt(),
|
||||
match.captured(3).toInt()};
|
||||
versionedDirs.append({versionParts, folder});
|
||||
} else {
|
||||
otherDirs.append(folder);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(otherDirs.begin(), otherDirs.end());
|
||||
|
||||
std::sort(versionedDirs.begin(), versionedDirs.end(), [](const auto& a, const auto& b) {
|
||||
if (a.first[0] != b.first[0])
|
||||
return a.first[0] > b.first[0];
|
||||
if (a.first[1] != b.first[1])
|
||||
return a.first[1] > b.first[1];
|
||||
return a.first[2] > b.first[2];
|
||||
});
|
||||
|
||||
// Add (Pre-release, Test Build...)
|
||||
for (const QString& folder : otherDirs) {
|
||||
for (auto const& v : versions) {
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(ui->installedTreeWidget);
|
||||
QString fullPath = QDir(path).filePath(folder);
|
||||
item->setText(4, fullPath);
|
||||
item->setCheckState(0, Qt::Unchecked);
|
||||
|
||||
if (folder.startsWith("Pre-release-shadPS4")) {
|
||||
QStringList parts = folder.split('-');
|
||||
item->setText(1, "Pre-release");
|
||||
QString shortHash;
|
||||
if (parts.size() >= 7) {
|
||||
shortHash = parts[6].left(7);
|
||||
} else {
|
||||
shortHash = "";
|
||||
}
|
||||
item->setText(2, shortHash);
|
||||
if (parts.size() >= 6) {
|
||||
QString date = QString("%1-%2-%3").arg(parts[3], parts[4], parts[5]);
|
||||
item->setText(3, date);
|
||||
} else {
|
||||
item->setText(3, "");
|
||||
}
|
||||
} else if (folder.contains(" - ")) {
|
||||
QStringList parts = folder.split(" - ");
|
||||
item->setText(1, parts.value(0));
|
||||
item->setText(2, parts.value(1));
|
||||
item->setText(3, parts.value(2));
|
||||
} else {
|
||||
item->setText(1, folder);
|
||||
item->setText(2, "");
|
||||
item->setText(3, "");
|
||||
}
|
||||
|
||||
if (fullPath == savedVersionPath) {
|
||||
item->setCheckState(0, Qt::Checked);
|
||||
}
|
||||
item->setText(1, QString::fromStdString(v.name));
|
||||
item->setText(2, QString::fromStdString(v.codename));
|
||||
item->setText(3, QString::fromStdString(v.date));
|
||||
item->setText(4, QString::fromStdString(v.path));
|
||||
item->setCheckState(0, (selected_version == v.path) ? Qt::Checked : Qt::Unchecked);
|
||||
}
|
||||
|
||||
// Add versions
|
||||
for (const auto& pair : versionedDirs) {
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(ui->installedTreeWidget);
|
||||
QString fullPath = QDir(path).filePath(pair.second);
|
||||
item->setText(4, fullPath);
|
||||
item->setCheckState(0, Qt::Unchecked);
|
||||
|
||||
if (pair.second.contains(" - ")) {
|
||||
QStringList parts = pair.second.split(" - ");
|
||||
item->setText(1, parts.value(0));
|
||||
item->setText(2, parts.value(1));
|
||||
item->setText(3, parts.value(2));
|
||||
} else {
|
||||
item->setText(1, pair.second);
|
||||
item->setText(2, "");
|
||||
item->setText(3, "");
|
||||
}
|
||||
|
||||
if (fullPath == savedVersionPath) {
|
||||
item->setCheckState(0, Qt::Checked);
|
||||
}
|
||||
}
|
||||
ui->installedTreeWidget->resizeColumnToContents(0);
|
||||
ui->installedTreeWidget->resizeColumnToContents(1);
|
||||
ui->installedTreeWidget->resizeColumnToContents(2);
|
||||
|
||||
Reference in New Issue
Block a user