mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-27 13:39:02 +02:00
[qt] refactor: qt_common lib (#94)
This is part of a series of PRs made in preparation for the QML rewrite. this PR specifically moves a bunch of utility functions from main.cpp into qt_common, with the biggest benefit being that QML can reuse the exact same code through ctx passthrough. Also, QtCommon::Frontend is an abstraction layer over several previously Widgets-specific stuff like QMessageBox that gets used everywhere. The idea is that once QML is implemented, these functions can have a Quick version implemented for systems that don't work well with Widgets (sun) or for those on Plasma 6+ (reduces memory usage w/o Widgets linkage) although Quick from C++ is actually anal, but whatever. Other than that this should also just kinda reduce the size of main.cpp which is a 6000-line behemoth rn, and clangd straight up gives up with it for me (likely caused by the massive amount of headers, which this DOES reduce). In the future, I probably want to create a common strings lookup table that both Qt and QML can reference--though I'm not sure how much linguist likes that--which should give us a way to keep language consistent (use frozen-map). TODO: Docs for Qt stuff Co-authored-by: MaranBr <maranbr@outlook.com> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/94 Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
This commit is contained in:
parent
4c5d03f5de
commit
f4386423e8
111 changed files with 2235 additions and 1544 deletions
47
src/qt_common/CMakeLists.txt
Normal file
47
src/qt_common/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||
|
||||
add_library(qt_common STATIC
|
||||
qt_common.h
|
||||
qt_common.cpp
|
||||
|
||||
uisettings.cpp
|
||||
uisettings.h
|
||||
|
||||
qt_config.cpp
|
||||
qt_config.h
|
||||
|
||||
shared_translation.cpp
|
||||
shared_translation.h
|
||||
qt_path_util.h qt_path_util.cpp
|
||||
qt_game_util.h qt_game_util.cpp
|
||||
qt_frontend_util.h qt_frontend_util.cpp
|
||||
qt_meta.h qt_meta.cpp
|
||||
qt_content_util.h qt_content_util.cpp
|
||||
qt_rom_util.h qt_rom_util.cpp
|
||||
qt_applet_util.h qt_applet_util.cpp
|
||||
qt_progress_dialog.h qt_progress_dialog.cpp
|
||||
|
||||
)
|
||||
|
||||
create_target_directory_groups(qt_common)
|
||||
|
||||
# TODO(crueter)
|
||||
if (ENABLE_QT)
|
||||
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
add_subdirectory(externals)
|
||||
|
||||
target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip frozen::frozen)
|
||||
target_link_libraries(qt_common PRIVATE Qt6::Core)
|
||||
|
||||
if (NOT WIN32)
|
||||
target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
20
src/qt_common/externals/CMakeLists.txt
vendored
Normal file
20
src/qt_common/externals/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
include(CPMUtil)
|
||||
|
||||
# Disable tests/tools in all externals supporting the standard option name
|
||||
set(BUILD_TESTING OFF)
|
||||
|
||||
# Build only static externals
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
# Skip install rules for all externals
|
||||
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
|
||||
|
||||
# QuaZip
|
||||
AddJsonPackage(quazip)
|
||||
|
||||
# frozen
|
||||
# TODO(crueter): Qt String Lookup
|
||||
AddJsonPackage(frozen)
|
||||
19
src/qt_common/externals/cpmfile.json
vendored
Normal file
19
src/qt_common/externals/cpmfile.json
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"quazip": {
|
||||
"package": "QuaZip-Qt6",
|
||||
"repo": "crueter/quazip-qt6",
|
||||
"sha": "f838774d63",
|
||||
"hash": "9f629a438699801244a106c8df6d5f8f8d19e80df54f530a89403a10c8c4e37a6e95606bbdd307f23636961e8ce34eb37a2186d589a1f227ac9c8e2c678e326e",
|
||||
"version": "1.3",
|
||||
"options": [
|
||||
"QUAZIP_INSTALL OFF"
|
||||
]
|
||||
},
|
||||
"frozen": {
|
||||
"package": "frozen",
|
||||
"repo": "serge-sans-paille/frozen",
|
||||
"sha": "61dce5ae18",
|
||||
"hash": "1ae3d073e659c1f24b2cdd76379c90d6af9e06bc707d285a4fafce05f7a4c9e592ff208c94a9ae0f0d07620b3c6cec191f126b03d70ad4dfa496a86ed5658a6d",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
4
src/qt_common/qt_applet_util.cpp
Normal file
4
src/qt_common/qt_applet_util.cpp
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_applet_util.h"
|
||||
11
src/qt_common/qt_applet_util.h
Normal file
11
src/qt_common/qt_applet_util.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_APPLET_UTIL_H
|
||||
#define QT_APPLET_UTIL_H
|
||||
|
||||
// TODO
|
||||
namespace QtCommon::Applets {
|
||||
|
||||
}
|
||||
#endif // QT_APPLET_UTIL_H
|
||||
122
src/qt_common/qt_common.cpp
Normal file
122
src/qt_common/qt_common.cpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_common.h"
|
||||
#include "common/fs/fs.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QStringLiteral>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <JlCompress.h>
|
||||
|
||||
#if !defined(WIN32) && !defined(__APPLE__)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <objc/message.h>
|
||||
#endif
|
||||
|
||||
namespace QtCommon {
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
QWidget* rootObject = nullptr;
|
||||
#else
|
||||
QObject* rootObject = nullptr;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Core::System> system = nullptr;
|
||||
std::shared_ptr<FileSys::RealVfsFilesystem> vfs = nullptr;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> provider = nullptr;
|
||||
|
||||
Core::Frontend::WindowSystemType GetWindowSystemType()
|
||||
{
|
||||
// Determine WSI type based on Qt platform.
|
||||
QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("windows"))
|
||||
return Core::Frontend::WindowSystemType::Windows;
|
||||
else if (platform_name == QStringLiteral("xcb"))
|
||||
return Core::Frontend::WindowSystemType::X11;
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
return Core::Frontend::WindowSystemType::Wayland;
|
||||
else if (platform_name == QStringLiteral("wayland-egl"))
|
||||
return Core::Frontend::WindowSystemType::Wayland;
|
||||
else if (platform_name == QStringLiteral("cocoa"))
|
||||
return Core::Frontend::WindowSystemType::Cocoa;
|
||||
else if (platform_name == QStringLiteral("android"))
|
||||
return Core::Frontend::WindowSystemType::Android;
|
||||
|
||||
LOG_CRITICAL(Frontend, "Unknown Qt platform {}!", platform_name.toStdString());
|
||||
return Core::Frontend::WindowSystemType::Windows;
|
||||
} // namespace Core::Frontend::WindowSystemType
|
||||
|
||||
Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
||||
{
|
||||
Core::Frontend::EmuWindow::WindowSystemInfo wsi;
|
||||
wsi.type = GetWindowSystemType();
|
||||
|
||||
#if defined(WIN32)
|
||||
// Our Win32 Qt external doesn't have the private API.
|
||||
wsi.render_surface = reinterpret_cast<void*>(window->winId());
|
||||
#elif defined(__APPLE__)
|
||||
wsi.render_surface = reinterpret_cast<void* (*) (id, SEL)>(
|
||||
objc_msgSend)(reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
wsi.display_connection = pni->nativeResourceForWindow("display", window);
|
||||
if (wsi.type == Core::Frontend::WindowSystemType::Wayland)
|
||||
wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
|
||||
else
|
||||
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
|
||||
#endif
|
||||
wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
|
||||
|
||||
return wsi;
|
||||
}
|
||||
|
||||
const QString tr(const char* str)
|
||||
{
|
||||
return QGuiApplication::tr(str);
|
||||
}
|
||||
|
||||
const QString tr(const std::string& str)
|
||||
{
|
||||
return QGuiApplication::tr(str.c_str());
|
||||
}
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
void Init(QWidget* root)
|
||||
#else
|
||||
void Init(QObject* root)
|
||||
#endif
|
||||
{
|
||||
system = std::make_unique<Core::System>();
|
||||
rootObject = root;
|
||||
vfs = std::make_unique<FileSys::RealVfsFilesystem>();
|
||||
provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
}
|
||||
|
||||
std::filesystem::path GetEdenCommand() {
|
||||
std::filesystem::path command;
|
||||
|
||||
QString appimage = QString::fromLocal8Bit(getenv("APPIMAGE"));
|
||||
if (!appimage.isEmpty()) {
|
||||
command = std::filesystem::path{appimage.toStdString()};
|
||||
} else {
|
||||
const QStringList args = QGuiApplication::arguments();
|
||||
command = args[0].toStdString();
|
||||
}
|
||||
|
||||
// If relative path, make it an absolute path
|
||||
if (command.c_str()[0] == '.') {
|
||||
command = Common::FS::GetCurrentDir() / command;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
} // namespace QtCommon
|
||||
44
src/qt_common/qt_common.h
Normal file
44
src/qt_common/qt_common.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_COMMON_H
|
||||
#define QT_COMMON_H
|
||||
|
||||
#include <QWindow>
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include <core/frontend/emu_window.h>
|
||||
#include <memory>
|
||||
|
||||
#include <core/file_sys/vfs/vfs_real.h>
|
||||
|
||||
namespace QtCommon {
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
extern QWidget *rootObject;
|
||||
#else
|
||||
extern QObject *rootObject;
|
||||
#endif
|
||||
|
||||
extern std::unique_ptr<Core::System> system;
|
||||
extern std::shared_ptr<FileSys::RealVfsFilesystem> vfs;
|
||||
extern std::unique_ptr<FileSys::ManualContentProvider> provider;
|
||||
|
||||
typedef std::function<bool(std::size_t, std::size_t)> QtProgressCallback;
|
||||
|
||||
Core::Frontend::WindowSystemType GetWindowSystemType();
|
||||
|
||||
Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window);
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
void Init(QWidget *root);
|
||||
#else
|
||||
void Init(QObject *root);
|
||||
#endif
|
||||
|
||||
const QString tr(const char *str);
|
||||
const QString tr(const std::string &str);
|
||||
|
||||
std::filesystem::path GetEdenCommand();
|
||||
} // namespace QtCommon
|
||||
#endif
|
||||
564
src/qt_common/qt_config.cpp
Normal file
564
src/qt_common/qt_config.cpp
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/main.h"
|
||||
#include "qt_config.h"
|
||||
#include "uisettings.h"
|
||||
|
||||
const std::array<int, Settings::NativeButton::NumButtons> QtConfig::default_buttons = {
|
||||
Qt::Key_C, Qt::Key_X, Qt::Key_V, Qt::Key_Z, Qt::Key_F,
|
||||
Qt::Key_G, Qt::Key_Q, Qt::Key_E, Qt::Key_R, Qt::Key_T,
|
||||
Qt::Key_M, Qt::Key_N, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right,
|
||||
Qt::Key_Down, Qt::Key_Q, Qt::Key_E, 0, 0,
|
||||
Qt::Key_Q, Qt::Key_E,
|
||||
};
|
||||
|
||||
const std::array<int, Settings::NativeMotion::NumMotions> QtConfig::default_motions = {
|
||||
Qt::Key_7,
|
||||
Qt::Key_8,
|
||||
};
|
||||
|
||||
const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> QtConfig::default_analogs{{
|
||||
{
|
||||
Qt::Key_W,
|
||||
Qt::Key_S,
|
||||
Qt::Key_A,
|
||||
Qt::Key_D,
|
||||
},
|
||||
{
|
||||
Qt::Key_I,
|
||||
Qt::Key_K,
|
||||
Qt::Key_J,
|
||||
Qt::Key_L,
|
||||
},
|
||||
}};
|
||||
|
||||
const std::array<int, 2> QtConfig::default_stick_mod = {
|
||||
Qt::Key_Shift,
|
||||
0,
|
||||
};
|
||||
|
||||
const std::array<int, 2> QtConfig::default_ringcon_analogs{{
|
||||
Qt::Key_A,
|
||||
Qt::Key_D,
|
||||
}};
|
||||
|
||||
QtConfig::QtConfig(const std::string& config_name, const ConfigType config_type)
|
||||
: Config(config_type) {
|
||||
|
||||
Initialize(config_name);
|
||||
if (config_type != ConfigType::InputProfile) {
|
||||
ReadQtValues();
|
||||
SaveQtValues();
|
||||
}
|
||||
}
|
||||
|
||||
QtConfig::~QtConfig() {
|
||||
if (global) {
|
||||
QtConfig::SaveAllValues();
|
||||
}
|
||||
}
|
||||
|
||||
void QtConfig::ReloadAllValues() {
|
||||
Reload();
|
||||
ReadQtValues();
|
||||
SaveQtValues();
|
||||
}
|
||||
|
||||
void QtConfig::SaveAllValues() {
|
||||
SaveValues();
|
||||
SaveQtValues();
|
||||
}
|
||||
|
||||
void QtConfig::ReadQtValues() {
|
||||
if (global) {
|
||||
ReadUIValues();
|
||||
}
|
||||
ReadQtControlValues();
|
||||
}
|
||||
|
||||
void QtConfig::ReadQtPlayerValues(const std::size_t player_index) {
|
||||
std::string player_prefix;
|
||||
if (type != ConfigType::InputProfile) {
|
||||
player_prefix.append("player_").append(ToString(player_index)).append("_");
|
||||
}
|
||||
|
||||
auto& player = Settings::values.players.GetValue()[player_index];
|
||||
if (IsCustomConfig()) {
|
||||
const auto profile_name =
|
||||
ReadStringSetting(std::string(player_prefix).append("profile_name"));
|
||||
if (profile_name.empty()) {
|
||||
// Use the global input config
|
||||
player = Settings::values.players.GetValue(true)[player_index];
|
||||
player.profile_name = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
auto& player_buttons = player.buttons[i];
|
||||
|
||||
player_buttons = ReadStringSetting(
|
||||
std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
|
||||
if (player_buttons.empty()) {
|
||||
player_buttons = default_param;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_stick_mod[i], 0.5f);
|
||||
auto& player_analogs = player.analogs[i];
|
||||
|
||||
player_analogs = ReadStringSetting(
|
||||
std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
|
||||
if (player_analogs.empty()) {
|
||||
player_analogs = default_param;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
|
||||
auto& player_motions = player.motions[i];
|
||||
|
||||
player_motions = ReadStringSetting(
|
||||
std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
|
||||
if (player_motions.empty()) {
|
||||
player_motions = default_param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtConfig::ReadHidbusValues() {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
|
||||
auto& ringcon_analogs = Settings::values.ringcon_analogs;
|
||||
|
||||
ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param);
|
||||
if (ringcon_analogs.empty()) {
|
||||
ringcon_analogs = default_param;
|
||||
}
|
||||
}
|
||||
|
||||
void QtConfig::ReadDebugControlValues() {
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
|
||||
|
||||
debug_pad_buttons = ReadStringSetting(
|
||||
std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param);
|
||||
if (debug_pad_buttons.empty()) {
|
||||
debug_pad_buttons = default_param;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_stick_mod[i], 0.5f);
|
||||
auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
|
||||
|
||||
debug_pad_analogs = ReadStringSetting(
|
||||
std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param);
|
||||
if (debug_pad_analogs.empty()) {
|
||||
debug_pad_analogs = default_param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtConfig::ReadQtControlValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
||||
|
||||
Settings::values.players.SetGlobal(!IsCustomConfig());
|
||||
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
|
||||
ReadQtPlayerValues(p);
|
||||
}
|
||||
if (IsCustomConfig()) {
|
||||
EndGroup();
|
||||
return;
|
||||
}
|
||||
ReadDebugControlValues();
|
||||
ReadHidbusValues();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::ReadPathValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
|
||||
|
||||
UISettings::values.roms_path = ReadStringSetting(std::string("romsPath"));
|
||||
UISettings::values.game_dir_deprecated =
|
||||
ReadStringSetting(std::string("gameListRootDir"), std::string("."));
|
||||
UISettings::values.game_dir_deprecated_deepscan =
|
||||
ReadBooleanSetting(std::string("gameListDeepScan"), std::make_optional(false));
|
||||
|
||||
const int gamedirs_size = BeginArray(std::string("gamedirs"));
|
||||
for (int i = 0; i < gamedirs_size; ++i) {
|
||||
SetArrayIndex(i);
|
||||
UISettings::GameDir game_dir;
|
||||
game_dir.path = ReadStringSetting(std::string("path"));
|
||||
game_dir.deep_scan =
|
||||
ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false));
|
||||
game_dir.expanded = ReadBooleanSetting(std::string("expanded"), std::make_optional(true));
|
||||
UISettings::values.game_dirs.append(game_dir);
|
||||
}
|
||||
EndArray();
|
||||
|
||||
// Create NAND and SD card directories if empty, these are not removable through the UI,
|
||||
// also carries over old game list settings if present
|
||||
if (UISettings::values.game_dirs.empty()) {
|
||||
UISettings::GameDir game_dir;
|
||||
game_dir.path = std::string("SDMC");
|
||||
game_dir.expanded = true;
|
||||
UISettings::values.game_dirs.append(game_dir);
|
||||
game_dir.path = std::string("UserNAND");
|
||||
UISettings::values.game_dirs.append(game_dir);
|
||||
game_dir.path = std::string("SysNAND");
|
||||
UISettings::values.game_dirs.append(game_dir);
|
||||
if (UISettings::values.game_dir_deprecated != std::string(".")) {
|
||||
game_dir.path = UISettings::values.game_dir_deprecated;
|
||||
game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
|
||||
UISettings::values.game_dirs.append(game_dir);
|
||||
}
|
||||
}
|
||||
UISettings::values.recent_files =
|
||||
QString::fromStdString(ReadStringSetting(std::string("recentFiles")))
|
||||
.split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
||||
|
||||
ReadCategory(Settings::Category::Paths);
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::ReadShortcutValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
|
||||
|
||||
for (const auto& [name, group, shortcut] : UISettings::default_hotkeys) {
|
||||
BeginGroup(group);
|
||||
BeginGroup(name);
|
||||
|
||||
// No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1
|
||||
// for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
|
||||
// a file dialog in windowed mode
|
||||
UISettings::values.shortcuts.push_back(
|
||||
{name,
|
||||
group,
|
||||
{ReadStringSetting(std::string("KeySeq"), shortcut.keyseq),
|
||||
ReadStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq),
|
||||
shortcut.context,
|
||||
ReadBooleanSetting(std::string("Repeat"), std::optional(shortcut.repeat))}});
|
||||
|
||||
EndGroup(); // name
|
||||
EndGroup(); // group
|
||||
}
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::ReadUIValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
|
||||
|
||||
UISettings::values.theme = ReadStringSetting(
|
||||
std::string("theme"),
|
||||
std::string(UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second));
|
||||
|
||||
ReadUIGamelistValues();
|
||||
ReadUILayoutValues();
|
||||
ReadPathValues();
|
||||
ReadScreenshotValues();
|
||||
ReadShortcutValues();
|
||||
ReadMultiplayerValues();
|
||||
|
||||
ReadCategory(Settings::Category::Ui);
|
||||
ReadCategory(Settings::Category::UiGeneral);
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::ReadUIGamelistValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
|
||||
|
||||
ReadCategory(Settings::Category::UiGameList);
|
||||
|
||||
const int favorites_size = BeginArray("favorites");
|
||||
for (int i = 0; i < favorites_size; i++) {
|
||||
SetArrayIndex(i);
|
||||
UISettings::values.favorited_ids.append(
|
||||
ReadUnsignedIntegerSetting(std::string("program_id")));
|
||||
}
|
||||
EndArray();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::ReadUILayoutValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
|
||||
|
||||
ReadCategory(Settings::Category::UiLayout);
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::ReadMultiplayerValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Multiplayer));
|
||||
|
||||
ReadCategory(Settings::Category::Multiplayer);
|
||||
|
||||
// Read ban list back
|
||||
int size = BeginArray(std::string("username_ban_list"));
|
||||
UISettings::values.multiplayer_ban_list.first.resize(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
SetArrayIndex(i);
|
||||
UISettings::values.multiplayer_ban_list.first[i] =
|
||||
ReadStringSetting(std::string("username"), std::string(""));
|
||||
}
|
||||
EndArray();
|
||||
|
||||
size = BeginArray(std::string("ip_ban_list"));
|
||||
UISettings::values.multiplayer_ban_list.second.resize(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
UISettings::values.multiplayer_ban_list.second[i] =
|
||||
ReadStringSetting("username", std::string(""));
|
||||
}
|
||||
EndArray();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::SaveQtValues() {
|
||||
if (global) {
|
||||
LOG_DEBUG(Config, "Saving global Qt configuration values");
|
||||
SaveUIValues();
|
||||
} else {
|
||||
LOG_DEBUG(Config, "Saving Qt configuration values");
|
||||
}
|
||||
SaveQtControlValues();
|
||||
|
||||
WriteToIni();
|
||||
}
|
||||
|
||||
void QtConfig::SaveQtPlayerValues(const std::size_t player_index) {
|
||||
std::string player_prefix;
|
||||
if (type != ConfigType::InputProfile) {
|
||||
player_prefix = std::string("player_").append(ToString(player_index)).append("_");
|
||||
}
|
||||
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
if (IsCustomConfig() && player.profile_name.empty()) {
|
||||
// No custom profile selected
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
|
||||
player.buttons[i], std::make_optional(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_stick_mod[i], 0.5f);
|
||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
|
||||
player.analogs[i], std::make_optional(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
|
||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
|
||||
player.motions[i], std::make_optional(default_param));
|
||||
}
|
||||
}
|
||||
|
||||
void QtConfig::SaveDebugControlValues() {
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
|
||||
Settings::values.debug_pad_buttons[i],
|
||||
std::make_optional(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_stick_mod[i], 0.5f);
|
||||
WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
|
||||
Settings::values.debug_pad_analogs[i],
|
||||
std::make_optional(default_param));
|
||||
}
|
||||
}
|
||||
|
||||
void QtConfig::SaveHidbusValues() {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
|
||||
WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
|
||||
std::make_optional(default_param));
|
||||
}
|
||||
|
||||
void QtConfig::SaveQtControlValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
||||
|
||||
Settings::values.players.SetGlobal(!IsCustomConfig());
|
||||
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
|
||||
SaveQtPlayerValues(p);
|
||||
}
|
||||
if (IsCustomConfig()) {
|
||||
EndGroup();
|
||||
return;
|
||||
}
|
||||
SaveDebugControlValues();
|
||||
SaveHidbusValues();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::SavePathValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
|
||||
|
||||
WriteCategory(Settings::Category::Paths);
|
||||
|
||||
WriteStringSetting(std::string("romsPath"), UISettings::values.roms_path);
|
||||
BeginArray(std::string("gamedirs"));
|
||||
for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
|
||||
SetArrayIndex(i);
|
||||
const auto& game_dir = UISettings::values.game_dirs[i];
|
||||
WriteStringSetting(std::string("path"), game_dir.path);
|
||||
WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
|
||||
std::make_optional(false));
|
||||
WriteBooleanSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true));
|
||||
}
|
||||
EndArray();
|
||||
|
||||
WriteStringSetting(std::string("recentFiles"),
|
||||
UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::SaveShortcutValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
|
||||
|
||||
// Lengths of UISettings::values.shortcuts & default_hotkeys are same.
|
||||
// However, their ordering must also be the same.
|
||||
for (std::size_t i = 0; i < UISettings::default_hotkeys.size(); i++) {
|
||||
const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
|
||||
const auto& default_hotkey = UISettings::default_hotkeys[i].shortcut;
|
||||
|
||||
BeginGroup(group);
|
||||
BeginGroup(name);
|
||||
|
||||
WriteStringSetting(std::string("KeySeq"), shortcut.keyseq,
|
||||
std::make_optional(default_hotkey.keyseq));
|
||||
WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq,
|
||||
std::make_optional(default_hotkey.controller_keyseq));
|
||||
WriteIntegerSetting(std::string("Context"), shortcut.context,
|
||||
std::make_optional(default_hotkey.context));
|
||||
WriteBooleanSetting(std::string("Repeat"), shortcut.repeat,
|
||||
std::make_optional(default_hotkey.repeat));
|
||||
|
||||
EndGroup(); // name
|
||||
EndGroup(); // group
|
||||
}
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::SaveUIValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
|
||||
|
||||
WriteCategory(Settings::Category::Ui);
|
||||
WriteCategory(Settings::Category::UiGeneral);
|
||||
|
||||
WriteStringSetting(
|
||||
std::string("theme"), UISettings::values.theme,
|
||||
std::make_optional(std::string(
|
||||
UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second)));
|
||||
|
||||
SaveUIGamelistValues();
|
||||
SaveUILayoutValues();
|
||||
SavePathValues();
|
||||
SaveScreenshotValues();
|
||||
SaveShortcutValues();
|
||||
SaveMultiplayerValues();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::SaveUIGamelistValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
|
||||
|
||||
WriteCategory(Settings::Category::UiGameList);
|
||||
|
||||
BeginArray(std::string("favorites"));
|
||||
for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
|
||||
SetArrayIndex(i);
|
||||
WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]);
|
||||
}
|
||||
EndArray(); // favorites
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::SaveUILayoutValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::UiLayout));
|
||||
|
||||
WriteCategory(Settings::Category::UiLayout);
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::SaveMultiplayerValues() {
|
||||
BeginGroup(std::string("Multiplayer"));
|
||||
|
||||
WriteCategory(Settings::Category::Multiplayer);
|
||||
|
||||
// Write ban list
|
||||
BeginArray(std::string("username_ban_list"));
|
||||
for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
|
||||
SetArrayIndex(static_cast<int>(i));
|
||||
WriteStringSetting(std::string("username"),
|
||||
UISettings::values.multiplayer_ban_list.first[i]);
|
||||
}
|
||||
EndArray(); // username_ban_list
|
||||
|
||||
BeginArray(std::string("ip_ban_list"));
|
||||
for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
|
||||
SetArrayIndex(static_cast<int>(i));
|
||||
WriteStringSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]);
|
||||
}
|
||||
EndArray(); // ip_ban_list
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
std::vector<Settings::BasicSetting*>& QtConfig::FindRelevantList(Settings::Category category) {
|
||||
auto& map = Settings::values.linkage.by_category;
|
||||
if (map.contains(category)) {
|
||||
return Settings::values.linkage.by_category[category];
|
||||
}
|
||||
return UISettings::values.linkage.by_category[category];
|
||||
}
|
||||
|
||||
void QtConfig::ReadQtControlPlayerValues(std::size_t player_index) {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
||||
|
||||
ReadPlayerValues(player_index);
|
||||
ReadQtPlayerValues(player_index);
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void QtConfig::SaveQtControlPlayerValues(std::size_t player_index) {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
||||
|
||||
LOG_DEBUG(Config, "Saving players control configuration values");
|
||||
SavePlayerValues(player_index);
|
||||
SaveQtPlayerValues(player_index);
|
||||
|
||||
EndGroup();
|
||||
|
||||
WriteToIni();
|
||||
}
|
||||
58
src/qt_common/qt_config.h
Normal file
58
src/qt_common/qt_config.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
#include "frontend_common/config.h"
|
||||
|
||||
class QtConfig final : public Config {
|
||||
public:
|
||||
explicit QtConfig(const std::string& config_name = "qt-config",
|
||||
ConfigType config_type = ConfigType::GlobalConfig);
|
||||
~QtConfig() override;
|
||||
|
||||
void ReloadAllValues() override;
|
||||
void SaveAllValues() override;
|
||||
|
||||
void ReadQtControlPlayerValues(std::size_t player_index);
|
||||
void SaveQtControlPlayerValues(std::size_t player_index);
|
||||
|
||||
protected:
|
||||
void ReadQtValues();
|
||||
void ReadQtPlayerValues(std::size_t player_index);
|
||||
void ReadQtControlValues();
|
||||
void ReadHidbusValues() override;
|
||||
void ReadDebugControlValues() override;
|
||||
void ReadPathValues() override;
|
||||
void ReadShortcutValues() override;
|
||||
void ReadUIValues() override;
|
||||
void ReadUIGamelistValues() override;
|
||||
void ReadUILayoutValues() override;
|
||||
void ReadMultiplayerValues() override;
|
||||
|
||||
void SaveQtValues();
|
||||
void SaveQtPlayerValues(std::size_t player_index);
|
||||
void SaveQtControlValues();
|
||||
void SaveHidbusValues() override;
|
||||
void SaveDebugControlValues() override;
|
||||
void SavePathValues() override;
|
||||
void SaveShortcutValues() override;
|
||||
void SaveUIValues() override;
|
||||
void SaveUIGamelistValues() override;
|
||||
void SaveUILayoutValues() override;
|
||||
void SaveMultiplayerValues() override;
|
||||
|
||||
std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
|
||||
|
||||
public:
|
||||
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
||||
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
|
||||
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
||||
static const std::array<int, 2> default_stick_mod;
|
||||
static const std::array<int, 2> default_ringcon_analogs;
|
||||
};
|
||||
313
src/qt_common/qt_content_util.cpp
Normal file
313
src/qt_common/qt_content_util.cpp
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_content_util.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
#include "frontend_common/firmware_manager.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
#include "qt_common/qt_progress_dialog.h"
|
||||
#include "qt_frontend_util.h"
|
||||
|
||||
#include <JlCompress.h>
|
||||
|
||||
namespace QtCommon::Content {
|
||||
|
||||
bool CheckGameFirmware(u64 program_id, QObject* parent)
|
||||
{
|
||||
if (FirmwareManager::GameRequiresFirmware(program_id)
|
||||
&& !FirmwareManager::CheckFirmwarePresence(*system)) {
|
||||
auto result = QtCommon::Frontend::ShowMessage(
|
||||
QMessageBox::Warning,
|
||||
"Game Requires Firmware",
|
||||
"The game you are trying to launch requires firmware to boot or to get past the "
|
||||
"opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>"
|
||||
"dump and install firmware</a>, or press \"OK\" to launch anyways.",
|
||||
QMessageBox::Ok | QMessageBox::Cancel,
|
||||
parent);
|
||||
|
||||
return result == QMessageBox::Ok;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallFirmware(const QString& location, bool recursive)
|
||||
{
|
||||
QtCommon::Frontend::QtProgressDialog progress(tr("Installing Firmware..."),
|
||||
tr("Cancel"),
|
||||
0,
|
||||
100,
|
||||
rootObject);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
progress.setAutoReset(false);
|
||||
progress.show();
|
||||
|
||||
// Declare progress callback.
|
||||
auto callback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
static constexpr const char* failedTitle = "Firmware Install Failed";
|
||||
static constexpr const char* successTitle = "Firmware Install Succeeded";
|
||||
QMessageBox::Icon icon;
|
||||
FirmwareInstallResult result;
|
||||
|
||||
const auto ShowMessage = [&]() {
|
||||
QtCommon::Frontend::ShowMessage(icon,
|
||||
failedTitle,
|
||||
GetFirmwareInstallResultString(result));
|
||||
};
|
||||
|
||||
LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString());
|
||||
|
||||
// Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in
|
||||
// there.)
|
||||
std::filesystem::path firmware_source_path = location.toStdString();
|
||||
if (!Common::FS::IsDir(firmware_source_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> out;
|
||||
const Common::FS::DirEntryCallable dir_callback =
|
||||
[&out](const std::filesystem::directory_entry& entry) {
|
||||
if (entry.path().has_extension() && entry.path().extension() == ".nca") {
|
||||
out.emplace_back(entry.path());
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
callback(100, 10);
|
||||
|
||||
if (recursive) {
|
||||
Common::FS::IterateDirEntriesRecursively(firmware_source_path,
|
||||
dir_callback,
|
||||
Common::FS::DirEntryFilter::File);
|
||||
} else {
|
||||
Common::FS::IterateDirEntries(firmware_source_path,
|
||||
dir_callback,
|
||||
Common::FS::DirEntryFilter::File);
|
||||
}
|
||||
|
||||
if (out.size() <= 0) {
|
||||
result = FirmwareInstallResult::NoNCAs;
|
||||
icon = QMessageBox::Warning;
|
||||
ShowMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Locate and erase the content of nand/system/Content/registered/*.nca, if any.
|
||||
auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
|
||||
if (sysnand_content_vdir->IsWritable()
|
||||
&& !sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
|
||||
result = FirmwareInstallResult::FailedDelete;
|
||||
icon = QMessageBox::Critical;
|
||||
ShowMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Frontend,
|
||||
"Cleaned nand/system/Content/registered folder in preparation for new firmware.");
|
||||
|
||||
callback(100, 20);
|
||||
|
||||
auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
|
||||
|
||||
bool success = true;
|
||||
int i = 0;
|
||||
for (const auto& firmware_src_path : out) {
|
||||
i++;
|
||||
auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(),
|
||||
FileSys::OpenMode::Read);
|
||||
auto firmware_dst_vfile = firmware_vdir
|
||||
->CreateFileRelative(firmware_src_path.filename().string());
|
||||
|
||||
if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
|
||||
LOG_ERROR(Frontend,
|
||||
"Failed to copy firmware file {} to {} in registered folder!",
|
||||
firmware_src_path.generic_string(),
|
||||
firmware_src_path.filename().string());
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (callback(100, 20 + static_cast<int>(((i) / static_cast<float>(out.size())) * 70.0))) {
|
||||
result = FirmwareInstallResult::FailedCorrupted;
|
||||
icon = QMessageBox::Warning;
|
||||
ShowMessage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
result = FirmwareInstallResult::FailedCopy;
|
||||
icon = QMessageBox::Critical;
|
||||
ShowMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-scan VFS for the newly placed firmware files.
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
|
||||
auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(90 + static_cast<int>((processed_size * 10) / total_size));
|
||||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
auto results = ContentManager::VerifyInstalledContents(*QtCommon::system,
|
||||
*QtCommon::provider,
|
||||
VerifyFirmwareCallback,
|
||||
true);
|
||||
|
||||
if (results.size() > 0) {
|
||||
const auto failed_names = QString::fromStdString(
|
||||
fmt::format("{}", fmt::join(results, "\n")));
|
||||
progress.close();
|
||||
QtCommon::Frontend::Critical(tr("Firmware integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1")
|
||||
.arg(failed_names));
|
||||
return;
|
||||
}
|
||||
|
||||
progress.close();
|
||||
|
||||
const auto pair = FirmwareManager::GetFirmwareVersion(*system);
|
||||
const auto firmware_data = pair.first;
|
||||
const std::string display_version(firmware_data.display_version.data());
|
||||
|
||||
result = FirmwareInstallResult::Success;
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
tr(successTitle),
|
||||
tr(GetFirmwareInstallResultString(result))
|
||||
.arg(QString::fromStdString(display_version)));
|
||||
}
|
||||
|
||||
QString UnzipFirmwareToTmp(const QString& location)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
fs::path tmp{fs::temp_directory_path()};
|
||||
|
||||
if (!fs::create_directories(tmp / "eden" / "firmware")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
tmp /= "eden";
|
||||
tmp /= "firmware";
|
||||
|
||||
QString qCacheDir = QString::fromStdString(tmp.string());
|
||||
|
||||
QFile zip(location);
|
||||
|
||||
QStringList result = JlCompress::extractDir(&zip, qCacheDir);
|
||||
if (result.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return qCacheDir;
|
||||
}
|
||||
|
||||
// Content //
|
||||
void VerifyGameContents(const std::string& game_path)
|
||||
{
|
||||
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."),
|
||||
tr("Cancel"),
|
||||
0,
|
||||
100,
|
||||
rootObject);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
progress.setAutoReset(false);
|
||||
|
||||
const auto callback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
const auto result = ContentManager::VerifyGameContents(*system, game_path, callback);
|
||||
|
||||
switch (result) {
|
||||
case ContentManager::GameVerificationResult::Success:
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
tr("Integrity verification succeeded!"),
|
||||
tr("The operation completed successfully."));
|
||||
break;
|
||||
case ContentManager::GameVerificationResult::Failed:
|
||||
QtCommon::Frontend::Critical(rootObject,
|
||||
tr("Integrity verification failed!"),
|
||||
tr("File contents may be corrupt or missing."));
|
||||
break;
|
||||
case ContentManager::GameVerificationResult::NotImplemented:
|
||||
QtCommon::Frontend::Warning(
|
||||
rootObject,
|
||||
tr("Integrity verification couldn't be performed"),
|
||||
tr("Firmware installation cancelled, firmware may be in a bad state or corrupted. "
|
||||
"File contents could not be checked for validity."));
|
||||
}
|
||||
}
|
||||
|
||||
void InstallKeys()
|
||||
{
|
||||
const QString key_source_location
|
||||
= QtCommon::Frontend::GetOpenFileName(tr("Select Dumped Keys Location"),
|
||||
{},
|
||||
QStringLiteral("Decryption Keys (*.keys)"),
|
||||
{},
|
||||
QtCommon::Frontend::Option::ReadOnly);
|
||||
|
||||
if (key_source_location.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location
|
||||
.toStdString(),
|
||||
"keys");
|
||||
|
||||
system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||
|
||||
switch (result) {
|
||||
case FirmwareManager::KeyInstallResult::Success:
|
||||
QtCommon::Frontend::Information(tr("Decryption Keys install succeeded"),
|
||||
tr("Decryption Keys were successfully installed"));
|
||||
break;
|
||||
default:
|
||||
QtCommon::Frontend::Critical(tr("Decryption Keys install failed"),
|
||||
tr(FirmwareManager::GetKeyInstallResultString(result)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyInstalledContents() {
|
||||
// Initialize a progress dialog.
|
||||
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
progress.setAutoReset(false);
|
||||
|
||||
// Declare progress callback.
|
||||
auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
const std::vector<std::string> result =
|
||||
ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback);
|
||||
progress.close();
|
||||
|
||||
if (result.empty()) {
|
||||
QtCommon::Frontend::Information(tr("Integrity verification succeeded!"),
|
||||
tr("The operation completed successfully."));
|
||||
} else {
|
||||
const auto failed_names =
|
||||
QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Content
|
||||
49
src/qt_common/qt_content_util.h
Normal file
49
src/qt_common/qt_content_util.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_CONTENT_UTIL_H
|
||||
#define QT_CONTENT_UTIL_H
|
||||
|
||||
#include <QObject>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace QtCommon::Content {
|
||||
|
||||
//
|
||||
bool CheckGameFirmware(u64 program_id, QObject *parent);
|
||||
|
||||
static constexpr std::array<const char *, 6> FIRMWARE_RESULTS
|
||||
= {"Successfully installed firmware version %1",
|
||||
"",
|
||||
"Unable to locate potential firmware NCA files",
|
||||
"Failed to delete one or more firmware files.",
|
||||
"One or more firmware files failed to copy into NAND.",
|
||||
"Firmware installation cancelled, firmware may be in a bad state or corrupted."
|
||||
"Restart Eden or re-install firmware."};
|
||||
|
||||
enum class FirmwareInstallResult {
|
||||
Success,
|
||||
NoOp,
|
||||
NoNCAs,
|
||||
FailedDelete,
|
||||
FailedCopy,
|
||||
FailedCorrupted,
|
||||
};
|
||||
|
||||
inline constexpr const char *GetFirmwareInstallResultString(FirmwareInstallResult result)
|
||||
{
|
||||
return FIRMWARE_RESULTS.at(static_cast<std::size_t>(result));
|
||||
}
|
||||
|
||||
void InstallFirmware(const QString &location, bool recursive);
|
||||
|
||||
QString UnzipFirmwareToTmp(const QString &location);
|
||||
|
||||
// Keys //
|
||||
void InstallKeys();
|
||||
|
||||
// Content //
|
||||
void VerifyGameContents(const std::string &game_path);
|
||||
void VerifyInstalledContents();
|
||||
}
|
||||
#endif // QT_CONTENT_UTIL_H
|
||||
35
src/qt_common/qt_frontend_util.cpp
Normal file
35
src/qt_common/qt_frontend_util.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_frontend_util.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
#include <QFileDialog>
|
||||
#endif
|
||||
|
||||
namespace QtCommon::Frontend {
|
||||
|
||||
StandardButton ShowMessage(
|
||||
Icon icon, const QString &title, const QString &text, StandardButtons buttons, QObject *parent)
|
||||
{
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
QMessageBox *box = new QMessageBox(icon, title, text, buttons, (QWidget *) parent);
|
||||
return static_cast<QMessageBox::StandardButton>(box->exec());
|
||||
#endif
|
||||
// TODO(crueter): If Qt Widgets is disabled...
|
||||
// need a way to reference icon/buttons too
|
||||
}
|
||||
|
||||
const QString GetOpenFileName(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter,
|
||||
Options options)
|
||||
{
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
return QFileDialog::getOpenFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
148
src/qt_common/qt_frontend_util.h
Normal file
148
src/qt_common/qt_frontend_util.h
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_FRONTEND_UTIL_H
|
||||
#define QT_FRONTEND_UTIL_H
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
#include <QFileDialog>
|
||||
#include <QWidget>
|
||||
#include <QMessageBox>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* manages common functionality e.g. message boxes and such for Qt/QML
|
||||
*/
|
||||
namespace QtCommon::Frontend {
|
||||
|
||||
Q_NAMESPACE
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
using Options = QFileDialog::Options;
|
||||
using Option = QFileDialog::Option;
|
||||
|
||||
using StandardButton = QMessageBox::StandardButton;
|
||||
using StandardButtons = QMessageBox::StandardButtons;
|
||||
|
||||
using Icon = QMessageBox::Icon;
|
||||
#else
|
||||
enum Option {
|
||||
ShowDirsOnly = 0x00000001,
|
||||
DontResolveSymlinks = 0x00000002,
|
||||
DontConfirmOverwrite = 0x00000004,
|
||||
DontUseNativeDialog = 0x00000008,
|
||||
ReadOnly = 0x00000010,
|
||||
HideNameFilterDetails = 0x00000020,
|
||||
DontUseCustomDirectoryIcons = 0x00000040
|
||||
};
|
||||
Q_ENUM_NS(Option)
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
Q_FLAG_NS(Options)
|
||||
|
||||
enum StandardButton {
|
||||
// keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton
|
||||
NoButton = 0x00000000,
|
||||
Ok = 0x00000400,
|
||||
Save = 0x00000800,
|
||||
SaveAll = 0x00001000,
|
||||
Open = 0x00002000,
|
||||
Yes = 0x00004000,
|
||||
YesToAll = 0x00008000,
|
||||
No = 0x00010000,
|
||||
NoToAll = 0x00020000,
|
||||
Abort = 0x00040000,
|
||||
Retry = 0x00080000,
|
||||
Ignore = 0x00100000,
|
||||
Close = 0x00200000,
|
||||
Cancel = 0x00400000,
|
||||
Discard = 0x00800000,
|
||||
Help = 0x01000000,
|
||||
Apply = 0x02000000,
|
||||
Reset = 0x04000000,
|
||||
RestoreDefaults = 0x08000000,
|
||||
|
||||
FirstButton = Ok, // internal
|
||||
LastButton = RestoreDefaults, // internal
|
||||
|
||||
YesAll = YesToAll, // obsolete
|
||||
NoAll = NoToAll, // obsolete
|
||||
|
||||
Default = 0x00000100, // obsolete
|
||||
Escape = 0x00000200, // obsolete
|
||||
FlagMask = 0x00000300, // obsolete
|
||||
ButtonMask = ~FlagMask // obsolete
|
||||
};
|
||||
Q_ENUM_NS(StandardButton)
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
|
||||
typedef StandardButton Button;
|
||||
#endif
|
||||
Q_DECLARE_FLAGS(StandardButtons, StandardButton)
|
||||
Q_FLAG_NS(StandardButtons)
|
||||
|
||||
enum Icon {
|
||||
// keep this in sync with QMessageDialogOptions::StandardIcon
|
||||
NoIcon = 0,
|
||||
Information = 1,
|
||||
Warning = 2,
|
||||
Critical = 3,
|
||||
Question = 4
|
||||
};
|
||||
Q_ENUM_NS(Icon)
|
||||
|
||||
#endif
|
||||
|
||||
// TODO(crueter) widgets-less impl, choices et al.
|
||||
StandardButton ShowMessage(Icon icon,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
StandardButtons buttons = StandardButton::NoButton,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
#define UTIL_OVERRIDES(level) \
|
||||
inline StandardButton level(QObject *parent, \
|
||||
const QString &title, \
|
||||
const QString &text, \
|
||||
StandardButtons buttons = StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, title, text, buttons, parent); \
|
||||
} \
|
||||
inline StandardButton level(QObject *parent, \
|
||||
const char *title, \
|
||||
const char *text, \
|
||||
StandardButtons buttons \
|
||||
= StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \
|
||||
} \
|
||||
inline StandardButton level(const char *title, \
|
||||
const char *text, \
|
||||
StandardButtons buttons \
|
||||
= StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \
|
||||
} \
|
||||
inline StandardButton level(const QString title, \
|
||||
const QString &text, \
|
||||
StandardButtons buttons \
|
||||
= StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, title, text, buttons, rootObject); \
|
||||
}
|
||||
|
||||
UTIL_OVERRIDES(Information)
|
||||
UTIL_OVERRIDES(Warning)
|
||||
UTIL_OVERRIDES(Critical)
|
||||
UTIL_OVERRIDES(Question)
|
||||
|
||||
const QString GetOpenFileName(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter = nullptr,
|
||||
Options options = Options());
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
#endif // QT_FRONTEND_UTIL_H
|
||||
577
src/qt_common/qt_game_util.cpp
Normal file
577
src/qt_common/qt_game_util.cpp
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_game_util.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/hle/service/am/am_types.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
#include "qt_common.h"
|
||||
#include "qt_common/uisettings.h"
|
||||
#include "qt_frontend_util.h"
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QStandardPaths>
|
||||
#include <QUrl>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
#include <shlobj.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include "fmt/ostream.h"
|
||||
#include <fstream>
|
||||
#endif
|
||||
|
||||
namespace QtCommon::Game {
|
||||
|
||||
bool CreateShortcutLink(const std::filesystem::path& shortcut_path,
|
||||
const std::string& comment,
|
||||
const std::filesystem::path& icon_path,
|
||||
const std::filesystem::path& command,
|
||||
const std::string& arguments,
|
||||
const std::string& categories,
|
||||
const std::string& keywords,
|
||||
const std::string& name)
|
||||
try {
|
||||
#ifdef _WIN32 // Windows
|
||||
HRESULT hr = CoInitialize(nullptr);
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR(Frontend, "CoInitialize failed");
|
||||
return false;
|
||||
}
|
||||
SCOPE_EXIT
|
||||
{
|
||||
CoUninitialize();
|
||||
};
|
||||
IShellLinkW* ps1 = nullptr;
|
||||
IPersistFile* persist_file = nullptr;
|
||||
SCOPE_EXIT
|
||||
{
|
||||
if (persist_file != nullptr) {
|
||||
persist_file->Release();
|
||||
}
|
||||
if (ps1 != nullptr) {
|
||||
ps1->Release();
|
||||
}
|
||||
};
|
||||
HRESULT hres = CoCreateInstance(CLSID_ShellLink,
|
||||
nullptr,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_IShellLinkW,
|
||||
reinterpret_cast<void**>(&ps1));
|
||||
if (FAILED(hres)) {
|
||||
LOG_ERROR(Frontend, "Failed to create IShellLinkW instance");
|
||||
return false;
|
||||
}
|
||||
hres = ps1->SetPath(command.c_str());
|
||||
if (FAILED(hres)) {
|
||||
LOG_ERROR(Frontend, "Failed to set path");
|
||||
return false;
|
||||
}
|
||||
if (!arguments.empty()) {
|
||||
hres = ps1->SetArguments(Common::UTF8ToUTF16W(arguments).data());
|
||||
if (FAILED(hres)) {
|
||||
LOG_ERROR(Frontend, "Failed to set arguments");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!comment.empty()) {
|
||||
hres = ps1->SetDescription(Common::UTF8ToUTF16W(comment).data());
|
||||
if (FAILED(hres)) {
|
||||
LOG_ERROR(Frontend, "Failed to set description");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (std::filesystem::is_regular_file(icon_path)) {
|
||||
hres = ps1->SetIconLocation(icon_path.c_str(), 0);
|
||||
if (FAILED(hres)) {
|
||||
LOG_ERROR(Frontend, "Failed to set icon location");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
hres = ps1->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&persist_file));
|
||||
if (FAILED(hres)) {
|
||||
LOG_ERROR(Frontend, "Failed to get IPersistFile interface");
|
||||
return false;
|
||||
}
|
||||
hres = persist_file->Save(std::filesystem::path{shortcut_path / (name + ".lnk")}.c_str(), TRUE);
|
||||
if (FAILED(hres)) {
|
||||
LOG_ERROR(Frontend, "Failed to save shortcut");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Any desktop NIX
|
||||
std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
|
||||
std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
|
||||
if (!shortcut_stream.is_open()) {
|
||||
LOG_ERROR(Frontend, "Failed to create shortcut");
|
||||
return false;
|
||||
}
|
||||
// TODO: Migrate fmt::print to std::print in futures STD C++ 23.
|
||||
fmt::print(shortcut_stream, "[Desktop Entry]\n");
|
||||
fmt::print(shortcut_stream, "Type=Application\n");
|
||||
fmt::print(shortcut_stream, "Version=1.0\n");
|
||||
fmt::print(shortcut_stream, "Name={}\n", name);
|
||||
if (!comment.empty()) {
|
||||
fmt::print(shortcut_stream, "Comment={}\n", comment);
|
||||
}
|
||||
if (std::filesystem::is_regular_file(icon_path)) {
|
||||
fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
|
||||
}
|
||||
fmt::print(shortcut_stream, "TryExec={}\n", command.string());
|
||||
fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
|
||||
if (!categories.empty()) {
|
||||
fmt::print(shortcut_stream, "Categories={}\n", categories);
|
||||
}
|
||||
if (!keywords.empty()) {
|
||||
fmt::print(shortcut_stream, "Keywords={}\n", keywords);
|
||||
}
|
||||
return true;
|
||||
#else // Unsupported platform
|
||||
return false;
|
||||
#endif
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Frontend, "Failed to create shortcut: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MakeShortcutIcoPath(const u64 program_id,
|
||||
const std::string_view game_file_name,
|
||||
std::filesystem::path& out_icon_path)
|
||||
{
|
||||
// Get path to Yuzu icons directory & icon extension
|
||||
std::string ico_extension = "png";
|
||||
#if defined(_WIN32)
|
||||
out_icon_path = Common::FS::GetEdenPath(Common::FS::EdenPath::IconsDir);
|
||||
ico_extension = "ico";
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256";
|
||||
#endif
|
||||
// Create icons directory if it doesn't exist
|
||||
if (!Common::FS::CreateDirs(out_icon_path)) {
|
||||
out_icon_path.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create icon file path
|
||||
out_icon_path /= (program_id == 0 ? fmt::format("eden-{}.{}", game_file_name, ico_extension)
|
||||
: fmt::format("eden-{:016X}.{}", program_id, ico_extension));
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenEdenFolder(const Common::FS::EdenPath& path)
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(Common::FS::GetEdenPathString(path))));
|
||||
}
|
||||
|
||||
void OpenRootDataFolder()
|
||||
{
|
||||
OpenEdenFolder(Common::FS::EdenPath::EdenDir);
|
||||
}
|
||||
|
||||
void OpenNANDFolder()
|
||||
{
|
||||
OpenEdenFolder(Common::FS::EdenPath::NANDDir);
|
||||
}
|
||||
|
||||
void OpenSDMCFolder()
|
||||
{
|
||||
OpenEdenFolder(Common::FS::EdenPath::SDMCDir);
|
||||
}
|
||||
|
||||
void OpenModFolder()
|
||||
{
|
||||
OpenEdenFolder(Common::FS::EdenPath::LoadDir);
|
||||
}
|
||||
|
||||
void OpenLogFolder()
|
||||
{
|
||||
OpenEdenFolder(Common::FS::EdenPath::LogDir);
|
||||
}
|
||||
|
||||
static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type)
|
||||
{
|
||||
switch (type) {
|
||||
case QtCommon::Game::InstalledEntryType::Game:
|
||||
return tr("Error Removing Contents");
|
||||
case QtCommon::Game::InstalledEntryType::Update:
|
||||
return tr("Error Removing Update");
|
||||
case QtCommon::Game::InstalledEntryType::AddOnContent:
|
||||
return tr("Error Removing DLC");
|
||||
default:
|
||||
return QStringLiteral("Error Removing <Invalid Type>");
|
||||
}
|
||||
}
|
||||
|
||||
// Game Content //
|
||||
void RemoveBaseContent(u64 program_id, InstalledEntryType type)
|
||||
{
|
||||
const auto res = ContentManager::RemoveBaseContent(system->GetFileSystemController(),
|
||||
program_id);
|
||||
if (res) {
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
"Successfully Removed",
|
||||
"Successfully removed the installed base game.");
|
||||
} else {
|
||||
QtCommon::Frontend::Warning(
|
||||
rootObject,
|
||||
GetGameListErrorRemoving(type),
|
||||
tr("The base game is not installed in the NAND and cannot be removed."));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveUpdateContent(u64 program_id, InstalledEntryType type)
|
||||
{
|
||||
const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id);
|
||||
if (res) {
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
"Successfully Removed",
|
||||
"Successfully removed the installed update.");
|
||||
} else {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
GetGameListErrorRemoving(type),
|
||||
tr("There is no update installed for this title."));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveAddOnContent(u64 program_id, InstalledEntryType type)
|
||||
{
|
||||
const size_t count = ContentManager::RemoveAllDLC(*system, program_id);
|
||||
if (count == 0) {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
GetGameListErrorRemoving(type),
|
||||
tr("There are no DLCs installed for this title."));
|
||||
return;
|
||||
}
|
||||
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
tr("Successfully Removed"),
|
||||
tr("Successfully removed %1 installed DLC.").arg(count));
|
||||
}
|
||||
|
||||
// Global Content //
|
||||
|
||||
void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target)
|
||||
{
|
||||
const auto target_file_name = [target] {
|
||||
switch (target) {
|
||||
case GameListRemoveTarget::GlShaderCache:
|
||||
return "opengl.bin";
|
||||
case GameListRemoveTarget::VkShaderCache:
|
||||
return "vulkan.bin";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}();
|
||||
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
const auto shader_cache_folder_path = shader_cache_dir / fmt::format("{:016x}", program_id);
|
||||
const auto target_file = shader_cache_folder_path / target_file_name;
|
||||
|
||||
if (!Common::FS::Exists(target_file)) {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
tr("Error Removing Transferable Shader Cache"),
|
||||
tr("A shader cache for this title does not exist."));
|
||||
return;
|
||||
}
|
||||
if (Common::FS::RemoveFile(target_file)) {
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
tr("Successfully Removed"),
|
||||
tr("Successfully removed the transferable shader cache."));
|
||||
} else {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
tr("Error Removing Transferable Shader Cache"),
|
||||
tr("Failed to remove the transferable shader cache."));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveVulkanDriverPipelineCache(u64 program_id)
|
||||
{
|
||||
static constexpr std::string_view target_file_name = "vulkan_pipelines.bin";
|
||||
|
||||
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
const auto shader_cache_folder_path = shader_cache_dir / fmt::format("{:016x}", program_id);
|
||||
const auto target_file = shader_cache_folder_path / target_file_name;
|
||||
|
||||
if (!Common::FS::Exists(target_file)) {
|
||||
return;
|
||||
}
|
||||
if (!Common::FS::RemoveFile(target_file)) {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
tr("Error Removing Vulkan Driver Pipeline Cache"),
|
||||
tr("Failed to remove the driver pipeline cache."));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveAllTransferableShaderCaches(u64 program_id)
|
||||
{
|
||||
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
const auto program_shader_cache_dir = shader_cache_dir / fmt::format("{:016x}", program_id);
|
||||
|
||||
if (!Common::FS::Exists(program_shader_cache_dir)) {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
tr("Error Removing Transferable Shader Caches"),
|
||||
tr("A shader cache for this title does not exist."));
|
||||
return;
|
||||
}
|
||||
if (Common::FS::RemoveDirRecursively(program_shader_cache_dir)) {
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
tr("Successfully Removed"),
|
||||
tr("Successfully removed the transferable shader caches."));
|
||||
} else {
|
||||
QtCommon::Frontend::Warning(
|
||||
rootObject,
|
||||
tr("Error Removing Transferable Shader Caches"),
|
||||
tr("Failed to remove the transferable shader cache directory."));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path)
|
||||
{
|
||||
const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path));
|
||||
const auto config_file_name = program_id == 0
|
||||
? Common::FS::PathToUTF8String(file_path.filename())
|
||||
.append(".ini")
|
||||
: fmt::format("{:016X}.ini", program_id);
|
||||
const auto custom_config_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir)
|
||||
/ "custom" / config_file_name;
|
||||
|
||||
if (!Common::FS::Exists(custom_config_file_path)) {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
tr("Error Removing Custom Configuration"),
|
||||
tr("A custom configuration for this title does not exist."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Common::FS::RemoveFile(custom_config_file_path)) {
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
tr("Successfully Removed"),
|
||||
tr("Successfully removed the custom game configuration."));
|
||||
} else {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
tr("Error Removing Custom Configuration"),
|
||||
tr("Failed to remove the custom game configuration."));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCacheStorage(u64 program_id)
|
||||
{
|
||||
const auto nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir);
|
||||
auto vfs_nand_dir = vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir),
|
||||
FileSys::OpenMode::Read);
|
||||
|
||||
const auto cache_storage_path
|
||||
= FileSys::SaveDataFactory::GetFullPath({},
|
||||
vfs_nand_dir,
|
||||
FileSys::SaveDataSpaceId::User,
|
||||
FileSys::SaveDataType::Cache,
|
||||
0 /* program_id */,
|
||||
{},
|
||||
0);
|
||||
|
||||
const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path);
|
||||
|
||||
// Not an error if it wasn't cleared.
|
||||
Common::FS::RemoveDirRecursively(path);
|
||||
}
|
||||
|
||||
// Metadata //
|
||||
void ResetMetadata()
|
||||
{
|
||||
const QString title = tr("Reset Metadata Cache");
|
||||
|
||||
if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir)
|
||||
/ "game_list/")) {
|
||||
QtCommon::Frontend::Warning(rootObject, title, tr("The metadata cache is already empty."));
|
||||
} else if (Common::FS::RemoveDirRecursively(
|
||||
Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) {
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
title,
|
||||
tr("The operation completed successfully."));
|
||||
UISettings::values.is_game_list_reload_pending.exchange(true);
|
||||
} else {
|
||||
QtCommon::Frontend::Warning(
|
||||
rootObject,
|
||||
title,
|
||||
tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
|
||||
}
|
||||
}
|
||||
|
||||
// Uhhh //
|
||||
|
||||
// Messages in pre-defined message boxes for less code spaghetti
|
||||
inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title)
|
||||
{
|
||||
int result = 0;
|
||||
QMessageBox::StandardButtons buttons;
|
||||
switch (imsg) {
|
||||
case ShortcutMessages::Fullscreen:
|
||||
buttons = QMessageBox::Yes | QMessageBox::No;
|
||||
result
|
||||
= QtCommon::Frontend::Information(tr("Create Shortcut"),
|
||||
tr("Do you want to launch the game in fullscreen?"),
|
||||
buttons);
|
||||
return result == QMessageBox::Yes;
|
||||
case ShortcutMessages::Success:
|
||||
QtCommon::Frontend::Information(tr("Shortcut Created"),
|
||||
tr("Successfully created a shortcut to %1").arg(game_title));
|
||||
return false;
|
||||
case ShortcutMessages::Volatile:
|
||||
buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel;
|
||||
result = QtCommon::Frontend::Warning(
|
||||
tr("Shortcut may be Volatile!"),
|
||||
tr("This will create a shortcut to the current AppImage. This may "
|
||||
"not work well if you update. Continue?"),
|
||||
buttons);
|
||||
return result == QMessageBox::Ok;
|
||||
default:
|
||||
buttons = QMessageBox::Ok;
|
||||
QtCommon::Frontend::Critical(tr("Failed to Create Shortcut"),
|
||||
tr("Failed to create a shortcut to %1").arg(game_title),
|
||||
buttons);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateShortcut(const std::string& game_path,
|
||||
const u64 program_id,
|
||||
const std::string& game_title_,
|
||||
const ShortcutTarget &target,
|
||||
std::string arguments_,
|
||||
const bool needs_title)
|
||||
{
|
||||
// Get path to Eden executable
|
||||
std::filesystem::path command = GetEdenCommand();
|
||||
|
||||
// Shortcut path
|
||||
std::filesystem::path shortcut_path = GetShortcutPath(target);
|
||||
|
||||
if (!std::filesystem::exists(shortcut_path)) {
|
||||
CreateShortcutMessagesGUI(ShortcutMessages::Failed,
|
||||
QString::fromStdString(shortcut_path.generic_string()));
|
||||
LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string());
|
||||
return;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(),
|
||||
QtCommon::system->GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
const auto loader =
|
||||
Loader::GetLoader(*QtCommon::system, QtCommon::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 (QtCommon::Game::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
|
||||
if (!SaveIconToFile(out_icon_path, icon_data)) {
|
||||
LOG_ERROR(Frontend, "Could not write icon to file");
|
||||
}
|
||||
} else {
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Create Icon"),
|
||||
tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
|
||||
.arg(QString::fromStdString(out_icon_path.string())));
|
||||
}
|
||||
|
||||
#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
|
||||
// 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 (!CreateShortcutMessagesGUI(ShortcutMessages::Volatile, qgame_title)) {
|
||||
return;
|
||||
}
|
||||
UISettings::values.shortcut_already_warned = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create shortcut
|
||||
std::string arguments{arguments_};
|
||||
if (CreateShortcutMessagesGUI(ShortcutMessages::Fullscreen, 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 (QtCommon::Game::CreateShortcutLink(shortcut_path, comment, out_icon_path, command,
|
||||
arguments, categories, keywords, game_title)) {
|
||||
CreateShortcutMessagesGUI(ShortcutMessages::Success,
|
||||
qgame_title);
|
||||
return;
|
||||
}
|
||||
CreateShortcutMessagesGUI(ShortcutMessages::Failed,
|
||||
qgame_title);
|
||||
}
|
||||
|
||||
constexpr std::string GetShortcutPath(ShortcutTarget target) {
|
||||
{
|
||||
std::string shortcut_path{};
|
||||
if (target == ShortcutTarget::Desktop) {
|
||||
shortcut_path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)
|
||||
.toStdString();
|
||||
} else if (target == ShortcutTarget::Applications) {
|
||||
shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)
|
||||
.toStdString();
|
||||
}
|
||||
|
||||
return shortcut_path;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateHomeMenuShortcut(ShortcutTarget target) {
|
||||
constexpr u64 QLaunchId = static_cast<u64>(Service::AM::AppletProgramId::QLaunch);
|
||||
auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents();
|
||||
if (!bis_system) {
|
||||
QtCommon::Frontend::Warning(tr("No firmware available"),
|
||||
tr("Please install firmware to use the home menu."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
|
||||
if (!qlaunch_nca) {
|
||||
QtCommon::Frontend::Warning(tr("Home Menu Applet"),
|
||||
tr("Home Menu is not available. Please reinstall firmware."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
|
||||
const auto game_path = qlaunch_applet_nca->GetFullPath();
|
||||
|
||||
// TODO(crueter): Make this use the Eden icon
|
||||
CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false);
|
||||
}
|
||||
|
||||
|
||||
} // namespace QtCommon::Game
|
||||
85
src/qt_common/qt_game_util.h
Normal file
85
src/qt_common/qt_game_util.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_GAME_UTIL_H
|
||||
#define QT_GAME_UTIL_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include "common/fs/path_util.h"
|
||||
|
||||
namespace QtCommon::Game {
|
||||
|
||||
enum class InstalledEntryType {
|
||||
Game,
|
||||
Update,
|
||||
AddOnContent,
|
||||
};
|
||||
|
||||
enum class GameListRemoveTarget {
|
||||
GlShaderCache,
|
||||
VkShaderCache,
|
||||
AllShaderCache,
|
||||
CustomConfiguration,
|
||||
CacheStorage,
|
||||
};
|
||||
|
||||
enum class ShortcutTarget {
|
||||
Desktop,
|
||||
Applications,
|
||||
};
|
||||
|
||||
enum class ShortcutMessages{
|
||||
Fullscreen = 0,
|
||||
Success = 1,
|
||||
Volatile = 2,
|
||||
Failed = 3
|
||||
};
|
||||
|
||||
bool CreateShortcutLink(const std::filesystem::path& shortcut_path,
|
||||
const std::string& comment,
|
||||
const std::filesystem::path& icon_path,
|
||||
const std::filesystem::path& command,
|
||||
const std::string& arguments,
|
||||
const std::string& categories,
|
||||
const std::string& keywords,
|
||||
const std::string& name);
|
||||
|
||||
bool MakeShortcutIcoPath(const u64 program_id,
|
||||
const std::string_view game_file_name,
|
||||
std::filesystem::path& out_icon_path);
|
||||
|
||||
void OpenEdenFolder(const Common::FS::EdenPath &path);
|
||||
void OpenRootDataFolder();
|
||||
void OpenNANDFolder();
|
||||
void OpenSDMCFolder();
|
||||
void OpenModFolder();
|
||||
void OpenLogFolder();
|
||||
|
||||
void RemoveBaseContent(u64 program_id, InstalledEntryType type);
|
||||
void RemoveUpdateContent(u64 program_id, InstalledEntryType type);
|
||||
void RemoveAddOnContent(u64 program_id, InstalledEntryType type);
|
||||
|
||||
void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target);
|
||||
void RemoveVulkanDriverPipelineCache(u64 program_id);
|
||||
void RemoveAllTransferableShaderCaches(u64 program_id);
|
||||
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
|
||||
void RemoveCacheStorage(u64 program_id);
|
||||
|
||||
// Metadata //
|
||||
void ResetMetadata();
|
||||
|
||||
// Shortcuts //
|
||||
void CreateShortcut(const std::string& game_path,
|
||||
const u64 program_id,
|
||||
const std::string& game_title_,
|
||||
const ShortcutTarget& target,
|
||||
std::string arguments_,
|
||||
const bool needs_title);
|
||||
|
||||
constexpr std::string GetShortcutPath(ShortcutTarget target);
|
||||
void CreateHomeMenuShortcut(ShortcutTarget target);
|
||||
|
||||
}
|
||||
|
||||
#endif // QT_GAME_UTIL_H
|
||||
75
src/qt_common/qt_meta.cpp
Normal file
75
src/qt_common/qt_meta.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_meta.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/applets/cabinet.h"
|
||||
#include "core/frontend/applets/controller.h"
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
#include "core/hle/service/am/frontend/applet_web_browser_types.h"
|
||||
|
||||
namespace QtCommon::Meta {
|
||||
|
||||
void RegisterMetaTypes()
|
||||
{
|
||||
// Register integral and floating point types
|
||||
qRegisterMetaType<u8>("u8");
|
||||
qRegisterMetaType<u16>("u16");
|
||||
qRegisterMetaType<u32>("u32");
|
||||
qRegisterMetaType<u64>("u64");
|
||||
qRegisterMetaType<u128>("u128");
|
||||
qRegisterMetaType<s8>("s8");
|
||||
qRegisterMetaType<s16>("s16");
|
||||
qRegisterMetaType<s32>("s32");
|
||||
qRegisterMetaType<s64>("s64");
|
||||
qRegisterMetaType<f32>("f32");
|
||||
qRegisterMetaType<f64>("f64");
|
||||
|
||||
// Register string types
|
||||
qRegisterMetaType<std::string>("std::string");
|
||||
qRegisterMetaType<std::wstring>("std::wstring");
|
||||
qRegisterMetaType<std::u8string>("std::u8string");
|
||||
qRegisterMetaType<std::u16string>("std::u16string");
|
||||
qRegisterMetaType<std::u32string>("std::u32string");
|
||||
qRegisterMetaType<std::string_view>("std::string_view");
|
||||
qRegisterMetaType<std::wstring_view>("std::wstring_view");
|
||||
qRegisterMetaType<std::u8string_view>("std::u8string_view");
|
||||
qRegisterMetaType<std::u16string_view>("std::u16string_view");
|
||||
qRegisterMetaType<std::u32string_view>("std::u32string_view");
|
||||
|
||||
// Register applet types
|
||||
|
||||
// Cabinet Applet
|
||||
qRegisterMetaType<Core::Frontend::CabinetParameters>("Core::Frontend::CabinetParameters");
|
||||
qRegisterMetaType<std::shared_ptr<Service::NFC::NfcDevice>>(
|
||||
"std::shared_ptr<Service::NFC::NfcDevice>");
|
||||
|
||||
// Controller Applet
|
||||
qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters");
|
||||
|
||||
// Profile Select Applet
|
||||
qRegisterMetaType<Core::Frontend::ProfileSelectParameters>(
|
||||
"Core::Frontend::ProfileSelectParameters");
|
||||
|
||||
// Software Keyboard Applet
|
||||
qRegisterMetaType<Core::Frontend::KeyboardInitializeParameters>(
|
||||
"Core::Frontend::KeyboardInitializeParameters");
|
||||
qRegisterMetaType<Core::Frontend::InlineAppearParameters>(
|
||||
"Core::Frontend::InlineAppearParameters");
|
||||
qRegisterMetaType<Core::Frontend::InlineTextParameters>("Core::Frontend::InlineTextParameters");
|
||||
qRegisterMetaType<Service::AM::Frontend::SwkbdResult>("Service::AM::Frontend::SwkbdResult");
|
||||
qRegisterMetaType<Service::AM::Frontend::SwkbdTextCheckResult>(
|
||||
"Service::AM::Frontend::SwkbdTextCheckResult");
|
||||
qRegisterMetaType<Service::AM::Frontend::SwkbdReplyType>(
|
||||
"Service::AM::Frontend::SwkbdReplyType");
|
||||
|
||||
// Web Browser Applet
|
||||
qRegisterMetaType<Service::AM::Frontend::WebExitReason>("Service::AM::Frontend::WebExitReason");
|
||||
|
||||
// Register loader types
|
||||
qRegisterMetaType<Core::SystemResultStatus>("Core::SystemResultStatus");
|
||||
}
|
||||
|
||||
}
|
||||
15
src/qt_common/qt_meta.h
Normal file
15
src/qt_common/qt_meta.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_META_H
|
||||
#define QT_META_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace QtCommon::Meta {
|
||||
|
||||
//
|
||||
void RegisterMetaTypes();
|
||||
|
||||
}
|
||||
#endif // QT_META_H
|
||||
28
src/qt_common/qt_path_util.cpp
Normal file
28
src/qt_common/qt_path_util.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_path_util.h"
|
||||
#include <QDesktopServices>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "qt_common/qt_frontend_util.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace QtCommon::Path {
|
||||
|
||||
bool OpenShaderCache(u64 program_id, QObject *parent)
|
||||
{
|
||||
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)};
|
||||
if (!Common::FS::CreateDirs(shader_cache_folder_path)) {
|
||||
QtCommon::Frontend::ShowMessage(QMessageBox::Warning, "Error Opening Shader Cache", "Failed to create or open shader cache for this title, ensure your app data directory has write permissions.", QMessageBox::Ok, parent);
|
||||
}
|
||||
|
||||
const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)};
|
||||
const auto qt_shader_cache_path = QString::fromStdString(shader_path_string);
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path));
|
||||
}
|
||||
|
||||
}
|
||||
12
src/qt_common/qt_path_util.h
Normal file
12
src/qt_common/qt_path_util.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_PATH_UTIL_H
|
||||
#define QT_PATH_UTIL_H
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include <QObject>
|
||||
|
||||
namespace QtCommon::Path { bool OpenShaderCache(u64 program_id, QObject *parent); }
|
||||
|
||||
#endif // QT_PATH_UTIL_H
|
||||
4
src/qt_common/qt_progress_dialog.cpp
Normal file
4
src/qt_common/qt_progress_dialog.cpp
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_progress_dialog.h"
|
||||
47
src/qt_common/qt_progress_dialog.h
Normal file
47
src/qt_common/qt_progress_dialog.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_PROGRESS_DIALOG_H
|
||||
#define QT_PROGRESS_DIALOG_H
|
||||
|
||||
#include <QWindow>
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
#include <QProgressDialog>
|
||||
#endif
|
||||
|
||||
namespace QtCommon::Frontend {
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
|
||||
using QtProgressDialog = QProgressDialog;
|
||||
|
||||
// TODO(crueter): QML impl
|
||||
#else
|
||||
class QtProgressDialog
|
||||
{
|
||||
public:
|
||||
QtProgressDialog(const QString &labelText,
|
||||
const QString &cancelButtonText,
|
||||
int minimum,
|
||||
int maximum,
|
||||
QObject *parent = nullptr,
|
||||
Qt::WindowFlags f = Qt::WindowFlags());
|
||||
|
||||
bool wasCanceled() const;
|
||||
void setWindowModality(Qt::WindowModality modality);
|
||||
void setMinimumDuration(int durationMs);
|
||||
void setAutoClose(bool autoClose);
|
||||
void setAutoReset(bool autoReset);
|
||||
|
||||
public slots:
|
||||
void setLabelText(QString &text);
|
||||
void setRange(int min, int max);
|
||||
void setValue(int progress);
|
||||
bool close();
|
||||
|
||||
void show();
|
||||
};
|
||||
#endif // YUZU_QT_WIDGETS
|
||||
|
||||
}
|
||||
#endif // QT_PROGRESS_DIALOG_H
|
||||
78
src/qt_common/qt_rom_util.cpp
Normal file
78
src/qt_common/qt_rom_util.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_rom_util.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace QtCommon::ROM {
|
||||
|
||||
bool RomFSRawCopy(size_t total_size,
|
||||
size_t& read_size,
|
||||
QtProgressCallback callback,
|
||||
const FileSys::VirtualDir& src,
|
||||
const FileSys::VirtualDir& dest,
|
||||
bool full)
|
||||
{
|
||||
// TODO(crueter)
|
||||
// if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
|
||||
// return false;
|
||||
// if (dialog.wasCanceled())
|
||||
// return false;
|
||||
|
||||
// std::vector<u8> buffer(CopyBufferSize);
|
||||
// auto last_timestamp = std::chrono::steady_clock::now();
|
||||
|
||||
// const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file,
|
||||
// const FileSys::VirtualFile& dest_file) {
|
||||
// if (src_file == nullptr || dest_file == nullptr) {
|
||||
// return false;
|
||||
// }
|
||||
// if (!dest_file->Resize(src_file->GetSize())) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) {
|
||||
// if (dialog.wasCanceled()) {
|
||||
// dest_file->Resize(0);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// using namespace std::literals::chrono_literals;
|
||||
// const auto new_timestamp = std::chrono::steady_clock::now();
|
||||
|
||||
// if ((new_timestamp - last_timestamp) > 33ms) {
|
||||
// last_timestamp = new_timestamp;
|
||||
// dialog.setValue(
|
||||
// static_cast<int>(std::min(read_size, total_size) * 100 / total_size));
|
||||
// QCoreApplication::processEvents();
|
||||
// }
|
||||
|
||||
// const auto read = src_file->Read(buffer.data(), buffer.size(), i);
|
||||
// dest_file->Write(buffer.data(), read, i);
|
||||
|
||||
// read_size += read;
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// };
|
||||
|
||||
// if (full) {
|
||||
// for (const auto& file : src->GetFiles()) {
|
||||
// const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
|
||||
// if (!QtRawCopy(file, out))
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (const auto& dir : src->GetSubdirectories()) {
|
||||
// const auto out = dest->CreateSubdirectory(dir->GetName());
|
||||
// if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full))
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
20
src/qt_common/qt_rom_util.h
Normal file
20
src/qt_common/qt_rom_util.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_ROM_UTIL_H
|
||||
#define QT_ROM_UTIL_H
|
||||
|
||||
#include "qt_common/qt_common.h"
|
||||
#include <cstddef>
|
||||
|
||||
namespace QtCommon::ROM {
|
||||
|
||||
bool RomFSRawCopy(size_t total_size,
|
||||
size_t& read_size,
|
||||
QtProgressCallback callback,
|
||||
const FileSys::VirtualDir& src,
|
||||
const FileSys::VirtualDir& dest,
|
||||
bool full);
|
||||
|
||||
}
|
||||
#endif // QT_ROM_UTIL_H
|
||||
740
src/qt_common/shared_translation.cpp
Normal file
740
src/qt_common/shared_translation.cpp
Normal file
|
|
@ -0,0 +1,740 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "shared_translation.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "common/settings_setting.h"
|
||||
#include "common/time_zone.h"
|
||||
#include "qt_common/uisettings.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace ConfigurationShared {
|
||||
|
||||
std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
{
|
||||
std::unique_ptr<TranslationMap> translations = std::make_unique<TranslationMap>();
|
||||
const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); };
|
||||
|
||||
#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \
|
||||
translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}})
|
||||
|
||||
// A setting can be ignored by giving it a blank name
|
||||
|
||||
// Applets
|
||||
INSERT(Settings, cabinet_applet_mode, tr("Amiibo editor"), QString());
|
||||
INSERT(Settings, controller_applet_mode, tr("Controller configuration"), QString());
|
||||
INSERT(Settings, data_erase_applet_mode, tr("Data erase"), QString());
|
||||
INSERT(Settings, error_applet_mode, tr("Error"), QString());
|
||||
INSERT(Settings, net_connect_applet_mode, tr("Net connect"), QString());
|
||||
INSERT(Settings, player_select_applet_mode, tr("Player select"), QString());
|
||||
INSERT(Settings, swkbd_applet_mode, tr("Software keyboard"), QString());
|
||||
INSERT(Settings, mii_edit_applet_mode, tr("Mii Edit"), QString());
|
||||
INSERT(Settings, web_applet_mode, tr("Online web"), QString());
|
||||
INSERT(Settings, shop_applet_mode, tr("Shop"), QString());
|
||||
INSERT(Settings, photo_viewer_applet_mode, tr("Photo viewer"), QString());
|
||||
INSERT(Settings, offline_web_applet_mode, tr("Offline web"), QString());
|
||||
INSERT(Settings, login_share_applet_mode, tr("Login share"), QString());
|
||||
INSERT(Settings, wifi_web_auth_applet_mode, tr("Wifi web auth"), QString());
|
||||
INSERT(Settings, my_page_applet_mode, tr("My page"), QString());
|
||||
|
||||
// Audio
|
||||
INSERT(Settings, sink_id, tr("Output Engine:"), QString());
|
||||
INSERT(Settings, audio_output_device_id, tr("Output Device:"), QString());
|
||||
INSERT(Settings, audio_input_device_id, tr("Input Device:"), QString());
|
||||
INSERT(Settings, audio_muted, tr("Mute audio"), QString());
|
||||
INSERT(Settings, volume, tr("Volume:"), QString());
|
||||
INSERT(Settings, dump_audio_commands, QString(), QString());
|
||||
INSERT(UISettings, mute_when_in_background, tr("Mute audio when in background"), QString());
|
||||
|
||||
// Core
|
||||
INSERT(
|
||||
Settings,
|
||||
use_multi_core,
|
||||
tr("Multicore CPU Emulation"),
|
||||
tr("This option increases CPU emulation thread use from 1 to the Switch’s maximum of 4.\n"
|
||||
"This is mainly a debug option and shouldn’t be disabled."));
|
||||
INSERT(
|
||||
Settings,
|
||||
memory_layout_mode,
|
||||
tr("Memory Layout"),
|
||||
tr("Increases the amount of emulated RAM from the stock 4GB of the retail Switch to the "
|
||||
"developer kit's 8/6GB.\nIt’s doesn’t improve stability or performance and is intended "
|
||||
"to let big texture mods fit in emulated RAM.\nEnabling it will increase memory "
|
||||
"use. It is not recommended to enable unless a specific game with a texture mod needs "
|
||||
"it."));
|
||||
INSERT(Settings, use_speed_limit, QString(), QString());
|
||||
INSERT(Settings,
|
||||
speed_limit,
|
||||
tr("Limit Speed Percent"),
|
||||
tr("Controls the game's maximum rendering speed, but it’s up to each game if it runs "
|
||||
"faster or not.\n200% for a 30 FPS game is 60 FPS, and for a "
|
||||
"60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the "
|
||||
"maximum your PC can reach."));
|
||||
INSERT(Settings,
|
||||
sync_core_speed,
|
||||
tr("Synchronize Core Speed"),
|
||||
tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS "
|
||||
"without affecting game speed (animations, physics, etc.).\n"
|
||||
"Compatibility varies by game; many (especially older ones) may not respond well.\n"
|
||||
"Can help reduce stuttering at lower framerates."));
|
||||
|
||||
// Cpu
|
||||
INSERT(Settings,
|
||||
cpu_accuracy,
|
||||
tr("Accuracy:"),
|
||||
tr("This setting controls the accuracy of the emulated CPU.\nDon't change this unless "
|
||||
"you know what you are doing."));
|
||||
INSERT(Settings, cpu_backend, tr("Backend:"), QString());
|
||||
|
||||
INSERT(Settings, use_fast_cpu_time, QString(), QString());
|
||||
INSERT(Settings,
|
||||
fast_cpu_time,
|
||||
tr("Fast CPU Time"),
|
||||
tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, "
|
||||
"and certain games may behave improperly.\nUse Boost (1700MHz) to run at the Switch's highest native "
|
||||
"clock, or Fast (2000MHz) to run at 2x clock."));
|
||||
|
||||
INSERT(Settings, use_custom_cpu_ticks, QString(), QString());
|
||||
INSERT(Settings,
|
||||
cpu_ticks,
|
||||
tr("Custom CPU Ticks"),
|
||||
tr("Set a custom value of CPU ticks. Higher values can increase performance, but may "
|
||||
"also cause the game to freeze. A range of 77–21000 is recommended."));
|
||||
INSERT(Settings, cpu_backend, tr("Backend:"), QString());
|
||||
|
||||
// Cpu Debug
|
||||
|
||||
// Cpu Unsafe
|
||||
INSERT(Settings, cpuopt_unsafe_host_mmu, tr("Enable Host MMU Emulation (fastmem)"),
|
||||
tr("This optimization speeds up memory accesses by the guest program.\nEnabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.\nDisabling this forces all memory accesses to use Software MMU Emulation."));
|
||||
INSERT(
|
||||
Settings,
|
||||
cpuopt_unsafe_unfuse_fma,
|
||||
tr("Unfuse FMA (improve performance on CPUs without FMA)"),
|
||||
tr("This option improves speed by reducing accuracy of fused-multiply-add instructions on "
|
||||
"CPUs without native FMA support."));
|
||||
INSERT(
|
||||
Settings,
|
||||
cpuopt_unsafe_reduce_fp_error,
|
||||
tr("Faster FRSQRTE and FRECPE"),
|
||||
tr("This option improves the speed of some approximate floating-point functions by using "
|
||||
"less accurate native approximations."));
|
||||
INSERT(Settings,
|
||||
cpuopt_unsafe_ignore_standard_fpcr,
|
||||
tr("Faster ASIMD instructions (32 bits only)"),
|
||||
tr("This option improves the speed of 32 bits ASIMD floating-point functions by running "
|
||||
"with incorrect rounding modes."));
|
||||
INSERT(Settings,
|
||||
cpuopt_unsafe_inaccurate_nan,
|
||||
tr("Inaccurate NaN handling"),
|
||||
tr("This option improves speed by removing NaN checking.\nPlease note this also reduces "
|
||||
"accuracy of certain floating-point instructions."));
|
||||
INSERT(Settings,
|
||||
cpuopt_unsafe_fastmem_check,
|
||||
tr("Disable address space checks"),
|
||||
tr("This option improves speed by eliminating a safety check before every memory "
|
||||
"read/write in guest.\nDisabling it may allow a game to read/write the emulator's "
|
||||
"memory."));
|
||||
INSERT(
|
||||
Settings,
|
||||
cpuopt_unsafe_ignore_global_monitor,
|
||||
tr("Ignore global monitor"),
|
||||
tr("This option improves speed by relying only on the semantics of cmpxchg to ensure "
|
||||
"safety of exclusive access instructions.\nPlease note this may result in deadlocks and "
|
||||
"other race conditions."));
|
||||
|
||||
// Renderer
|
||||
INSERT(
|
||||
Settings,
|
||||
renderer_backend,
|
||||
tr("API:"),
|
||||
tr("Switches between the available graphics APIs.\nVulkan is recommended in most cases."));
|
||||
INSERT(Settings,
|
||||
vulkan_device,
|
||||
tr("Device:"),
|
||||
tr("This setting selects the GPU to use with the Vulkan backend."));
|
||||
INSERT(Settings,
|
||||
shader_backend,
|
||||
tr("Shader Backend:"),
|
||||
tr("The shader backend to use for the OpenGL renderer.\nGLSL is the fastest in "
|
||||
"performance and the best in rendering accuracy.\n"
|
||||
"GLASM is a deprecated NVIDIA-only backend that offers much better shader building "
|
||||
"performance at the cost of FPS and rendering accuracy.\n"
|
||||
"SPIR-V compiles the fastest, but yields poor results on most GPU drivers."));
|
||||
INSERT(Settings,
|
||||
resolution_setup,
|
||||
tr("Resolution:"),
|
||||
tr("Forces the game to render at a different resolution.\nHigher resolutions require "
|
||||
"much more VRAM and bandwidth.\n"
|
||||
"Options lower than 1X can cause rendering issues."));
|
||||
INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString());
|
||||
INSERT(Settings,
|
||||
fsr_sharpening_slider,
|
||||
tr("FSR Sharpness:"),
|
||||
tr("Determines how sharpened the image will look while using FSR’s dynamic contrast."));
|
||||
INSERT(Settings,
|
||||
anti_aliasing,
|
||||
tr("Anti-Aliasing Method:"),
|
||||
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA has a "
|
||||
"lower performance impact and can produce a better and more stable picture under "
|
||||
"very low resolutions."));
|
||||
INSERT(Settings,
|
||||
fullscreen_mode,
|
||||
tr("Fullscreen Mode:"),
|
||||
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
|
||||
"compatibility with the on-screen keyboard that some games request for "
|
||||
"input.\nExclusive "
|
||||
"fullscreen may offer better performance and better Freesync/Gsync support."));
|
||||
INSERT(Settings,
|
||||
aspect_ratio,
|
||||
tr("Aspect Ratio:"),
|
||||
tr("Stretches the game to fit the specified aspect ratio.\nSwitch games only support "
|
||||
"16:9, so custom game mods are required to get other ratios.\nAlso controls the "
|
||||
"aspect ratio of captured screenshots."));
|
||||
INSERT(Settings,
|
||||
use_disk_shader_cache,
|
||||
tr("Use disk pipeline cache"),
|
||||
tr("Allows saving shaders to storage for faster loading on following game "
|
||||
"boots.\nDisabling "
|
||||
"it is only intended for debugging."));
|
||||
INSERT(Settings,
|
||||
optimize_spirv_output,
|
||||
tr("Optimize SPIRV output shader"),
|
||||
tr("Runs an additional optimization pass over generated SPIRV shaders.\n"
|
||||
"Will increase time required for shader compilation.\nMay slightly improve "
|
||||
"performance.\nThis feature is experimental."));
|
||||
INSERT(
|
||||
Settings,
|
||||
use_asynchronous_gpu_emulation,
|
||||
tr("Use asynchronous GPU emulation"),
|
||||
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
|
||||
INSERT(Settings,
|
||||
nvdec_emulation,
|
||||
tr("NVDEC emulation:"),
|
||||
tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
|
||||
"decoding, or perform no decoding at all (black screen on videos).\n"
|
||||
"In most cases, GPU decoding provides the best performance."));
|
||||
INSERT(Settings,
|
||||
accelerate_astc,
|
||||
tr("ASTC Decoding Method:"),
|
||||
tr("This option controls how ASTC textures should be decoded.\n"
|
||||
"CPU: Use the CPU for decoding, slowest but safest method.\n"
|
||||
"GPU: Use the GPU's compute shaders to decode ASTC textures, recommended for most "
|
||||
"games and users.\n"
|
||||
"CPU Asynchronously: Use the CPU to decode ASTC textures as they arrive. Completely "
|
||||
"eliminates ASTC decoding\nstuttering at the cost of rendering issues while the "
|
||||
"texture is being decoded."));
|
||||
INSERT(
|
||||
Settings,
|
||||
astc_recompression,
|
||||
tr("ASTC Recompression Method:"),
|
||||
tr("Almost all desktop and laptop dedicated GPUs lack support for ASTC textures, forcing "
|
||||
"the emulator to decompress to an intermediate format any card supports, RGBA8.\n"
|
||||
"This option recompresses RGBA8 to either the BC1 or BC3 format, saving VRAM but "
|
||||
"negatively affecting image quality."));
|
||||
INSERT(Settings,
|
||||
vram_usage_mode,
|
||||
tr("VRAM Usage Mode:"),
|
||||
tr("Selects whether the emulator should prefer to conserve memory or make maximum usage "
|
||||
"of available video memory for performance.\nHas no effect on integrated graphics. "
|
||||
"Aggressive mode may severely impact the performance of other applications such as "
|
||||
"recording software."));
|
||||
INSERT(Settings,
|
||||
skip_cpu_inner_invalidation,
|
||||
tr("Skip CPU Inner Invalidation"),
|
||||
tr("Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and "
|
||||
"improving it's performance. This may cause glitches or crashes on some games."));
|
||||
INSERT(
|
||||
Settings,
|
||||
vsync_mode,
|
||||
tr("VSync Mode:"),
|
||||
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
|
||||
"refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from "
|
||||
"a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop "
|
||||
"frames.\nImmediate (no synchronization) just presents whatever is available and can "
|
||||
"exhibit tearing."));
|
||||
INSERT(Settings, bg_red, QString(), QString());
|
||||
INSERT(Settings, bg_green, QString(), QString());
|
||||
INSERT(Settings, bg_blue, QString(), QString());
|
||||
|
||||
// Renderer (Advanced Graphics)
|
||||
INSERT(Settings, sync_memory_operations, tr("Sync Memory Operations"),
|
||||
tr("Ensures data consistency between compute and memory operations.\nThis option should fix issues in some games, but may also reduce performance in some cases.\nUnreal Engine 4 games often see the most significant changes thereof."));
|
||||
INSERT(Settings,
|
||||
async_presentation,
|
||||
tr("Enable asynchronous presentation (Vulkan only)"),
|
||||
tr("Slightly improves performance by moving presentation to a separate CPU thread."));
|
||||
INSERT(
|
||||
Settings,
|
||||
renderer_force_max_clock,
|
||||
tr("Force maximum clocks (Vulkan only)"),
|
||||
tr("Runs work in the background while waiting for graphics commands to keep the GPU from "
|
||||
"lowering its clock speed."));
|
||||
INSERT(Settings,
|
||||
max_anisotropy,
|
||||
tr("Anisotropic Filtering:"),
|
||||
tr("Controls the quality of texture rendering at oblique angles.\nIt’s a light setting "
|
||||
"and safe to set at 16x on most GPUs."));
|
||||
INSERT(Settings,
|
||||
gpu_accuracy,
|
||||
tr("GPU Level:"),
|
||||
tr("Controls the GPU emulation accuracy.\nMost games render fine with Normal, but High is still "
|
||||
"required for some.\nParticles tend to only render correctly with High "
|
||||
"accuracy.\nExtreme should only be used for debugging.\nThis option can "
|
||||
"be changed while playing.\nSome games may require booting on high to render "
|
||||
"properly."));
|
||||
INSERT(Settings,
|
||||
dma_accuracy,
|
||||
tr("DMA Level:"),
|
||||
tr("Controls the DMA precision accuracy. Higher precision can fix issues in some games, but it can also impact performance in some cases.\nIf unsure, leave it at Default."));
|
||||
INSERT(Settings,
|
||||
use_asynchronous_shaders,
|
||||
tr("Use asynchronous shader building (Hack)"),
|
||||
tr("Enables asynchronous shader compilation, which may reduce shader stutter.\nThis "
|
||||
"feature "
|
||||
"is experimental."));
|
||||
INSERT(Settings, use_fast_gpu_time, QString(), QString());
|
||||
INSERT(Settings,
|
||||
fast_gpu_time,
|
||||
tr("Fast GPU Time (Hack)"),
|
||||
tr("Overclocks the emulated GPU to increase dynamic resolution and render "
|
||||
"distance.\nUse 128 for maximal performance and 512 for maximal graphics fidelity."));
|
||||
|
||||
INSERT(Settings,
|
||||
use_vulkan_driver_pipeline_cache,
|
||||
tr("Use Vulkan pipeline cache"),
|
||||
tr("Enables GPU vendor-specific pipeline cache.\nThis option can improve shader loading "
|
||||
"time significantly in cases where the Vulkan driver does not store pipeline cache "
|
||||
"files internally."));
|
||||
INSERT(
|
||||
Settings,
|
||||
enable_compute_pipelines,
|
||||
tr("Enable Compute Pipelines (Intel Vulkan Only)"),
|
||||
tr("Enable compute pipelines, required by some games.\nThis setting only exists for Intel "
|
||||
"proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled "
|
||||
"on all other drivers."));
|
||||
INSERT(
|
||||
Settings,
|
||||
use_reactive_flushing,
|
||||
tr("Enable Reactive Flushing"),
|
||||
tr("Uses reactive flushing instead of predictive flushing, allowing more accurate memory "
|
||||
"syncing."));
|
||||
INSERT(Settings,
|
||||
use_video_framerate,
|
||||
tr("Sync to framerate of video playback"),
|
||||
tr("Run the game at normal speed during video playback, even when the framerate is "
|
||||
"unlocked."));
|
||||
INSERT(Settings,
|
||||
barrier_feedback_loops,
|
||||
tr("Barrier feedback loops"),
|
||||
tr("Improves rendering of transparency effects in specific games."));
|
||||
|
||||
// Renderer (Extensions)
|
||||
INSERT(Settings,
|
||||
enable_raii,
|
||||
tr("RAII"),
|
||||
tr("A method of automatic resource management in Vulkan "
|
||||
"that ensures proper release of resources "
|
||||
"when they are no longer needed, but may cause crashes in bundled games."));
|
||||
INSERT(Settings,
|
||||
dyna_state,
|
||||
tr("Extended Dynamic State"),
|
||||
tr("Controls the number of features that can be used in Extended Dynamic State.\nHigher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors.\nThe default value may vary depending on your system and hardware capabilities.\nThis value can be changed until stability and a better visual quality are achieved."));
|
||||
|
||||
INSERT(Settings,
|
||||
provoking_vertex,
|
||||
tr("Provoking Vertex"),
|
||||
tr("Improves lighting and vertex handling in certain games.\n"
|
||||
"Only Vulkan 1.0+ devices support this extension."));
|
||||
|
||||
INSERT(Settings,
|
||||
descriptor_indexing,
|
||||
tr("Descriptor Indexing"),
|
||||
tr("Improves texture & buffer handling and the Maxwell translation layer.\n"
|
||||
"Some Vulkan 1.1+ and all 1.2+ devices support this extension."));
|
||||
|
||||
INSERT(Settings, sample_shading, QString(), QString());
|
||||
|
||||
INSERT(Settings,
|
||||
sample_shading_fraction,
|
||||
tr("Sample Shading"),
|
||||
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
|
||||
"instead once per fragment. Improves graphics quality at the cost of some performance.\n"
|
||||
"Higher values improve quality more but also reduce performance to a greater extent."));
|
||||
|
||||
// Renderer (Debug)
|
||||
|
||||
// System
|
||||
INSERT(Settings,
|
||||
rng_seed,
|
||||
tr("RNG Seed"),
|
||||
tr("Controls the seed of the random number generator.\nMainly used for speedrunning "
|
||||
"purposes."));
|
||||
INSERT(Settings, rng_seed_enabled, QString(), QString());
|
||||
INSERT(Settings, device_name, tr("Device Name"), tr("The name of the emulated Switch."));
|
||||
INSERT(Settings,
|
||||
custom_rtc,
|
||||
tr("Custom RTC Date:"),
|
||||
tr("This option allows to change the emulated clock of the Switch.\n"
|
||||
"Can be used to manipulate time in games."));
|
||||
INSERT(Settings, custom_rtc_enabled, QString(), QString());
|
||||
INSERT(Settings,
|
||||
custom_rtc_offset,
|
||||
QStringLiteral(" "),
|
||||
QStringLiteral("The number of seconds from the current unix time"));
|
||||
INSERT(Settings,
|
||||
language_index,
|
||||
tr("Language:"),
|
||||
tr("Note: this can be overridden when region setting is auto-select"));
|
||||
INSERT(Settings, region_index, tr("Region:"), tr("The region of the emulated Switch."));
|
||||
INSERT(Settings, time_zone_index, tr("Time Zone:"), tr("The time zone of the emulated Switch."));
|
||||
INSERT(Settings, sound_index, tr("Sound Output Mode:"), QString());
|
||||
INSERT(Settings,
|
||||
use_docked_mode,
|
||||
tr("Console Mode:"),
|
||||
tr("Selects if the console is emulated in Docked or Handheld mode.\nGames will change "
|
||||
"their resolution, details and supported controllers and depending on this setting.\n"
|
||||
"Setting to Handheld can help improve performance for low end systems."));
|
||||
INSERT(Settings, current_user, QString(), QString());
|
||||
|
||||
// Controls
|
||||
|
||||
// Data Storage
|
||||
|
||||
// Debugging
|
||||
|
||||
// Debugging Graphics
|
||||
|
||||
// Network
|
||||
|
||||
// Web Service
|
||||
|
||||
// Ui
|
||||
|
||||
// Ui General
|
||||
INSERT(UISettings,
|
||||
select_user_on_boot,
|
||||
tr("Prompt for user on game boot"),
|
||||
tr("Ask to select a user profile on each boot, useful if multiple people use Eden on "
|
||||
"the same PC."));
|
||||
INSERT(UISettings,
|
||||
pause_when_in_background,
|
||||
tr("Pause emulation when in background"),
|
||||
tr("This setting pauses Eden when focusing other windows."));
|
||||
INSERT(UISettings,
|
||||
confirm_before_stopping,
|
||||
tr("Confirm before stopping emulation"),
|
||||
tr("This setting overrides game prompts asking to confirm stopping the game.\nEnabling "
|
||||
"it bypasses such prompts and directly exits the emulation."));
|
||||
INSERT(UISettings,
|
||||
hide_mouse,
|
||||
tr("Hide mouse on inactivity"),
|
||||
tr("This setting hides the mouse after 2.5s of inactivity."));
|
||||
INSERT(UISettings,
|
||||
controller_applet_disabled,
|
||||
tr("Disable controller applet"),
|
||||
tr("Forcibly disables the use of the controller applet by guests.\nWhen a guest "
|
||||
"attempts to open the controller applet, it is immediately closed."));
|
||||
INSERT(UISettings,
|
||||
check_for_updates,
|
||||
tr("Check for updates"),
|
||||
tr("Whether or not to check for updates upon startup."));
|
||||
|
||||
// Linux
|
||||
INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QString());
|
||||
|
||||
// Ui Debugging
|
||||
|
||||
// Ui Multiplayer
|
||||
|
||||
// Ui Games list
|
||||
|
||||
#undef INSERT
|
||||
|
||||
return translations;
|
||||
}
|
||||
|
||||
std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
|
||||
{
|
||||
std::unique_ptr<ComboboxTranslationMap> translations = std::make_unique<ComboboxTranslationMap>();
|
||||
const auto& tr = [&](const char* text, const char* context = "") {
|
||||
return parent->tr(text, context);
|
||||
};
|
||||
|
||||
#define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)}
|
||||
|
||||
// Intentionally skipping VSyncMode to let the UI fill that one out
|
||||
translations->insert({Settings::EnumMetadata<Settings::AppletMode>::Index(),
|
||||
{
|
||||
PAIR(AppletMode, HLE, tr("Custom frontend")),
|
||||
PAIR(AppletMode, LLE, tr("Real applet")),
|
||||
}});
|
||||
|
||||
translations->insert({Settings::EnumMetadata<Settings::SpirvOptimizeMode>::Index(),
|
||||
{
|
||||
PAIR(SpirvOptimizeMode, Never, tr("Never")),
|
||||
PAIR(SpirvOptimizeMode, OnLoad, tr("On Load")),
|
||||
PAIR(SpirvOptimizeMode, Always, tr("Always")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(),
|
||||
{
|
||||
PAIR(AstcDecodeMode, Cpu, tr("CPU")),
|
||||
PAIR(AstcDecodeMode, Gpu, tr("GPU")),
|
||||
PAIR(AstcDecodeMode, CpuAsynchronous, tr("CPU Asynchronous")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::AstcRecompression>::Index(),
|
||||
{
|
||||
PAIR(AstcRecompression, Uncompressed, tr("Uncompressed (Best quality)")),
|
||||
PAIR(AstcRecompression, Bc1, tr("BC1 (Low quality)")),
|
||||
PAIR(AstcRecompression, Bc3, tr("BC3 (Medium quality)")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::VramUsageMode>::Index(),
|
||||
{
|
||||
PAIR(VramUsageMode, Conservative, tr("Conservative")),
|
||||
PAIR(VramUsageMode, Aggressive, tr("Aggressive")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(),
|
||||
{
|
||||
#ifdef HAS_OPENGL
|
||||
PAIR(RendererBackend, OpenGL, tr("OpenGL")),
|
||||
#endif
|
||||
PAIR(RendererBackend, Vulkan, tr("Vulkan")),
|
||||
PAIR(RendererBackend, Null, tr("Null")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
|
||||
{
|
||||
PAIR(ShaderBackend, Glsl, tr("GLSL")),
|
||||
PAIR(ShaderBackend, Glasm, tr("GLASM (Assembly Shaders, NVIDIA Only)")),
|
||||
PAIR(ShaderBackend, SpirV, tr("SPIR-V (Experimental, AMD/Mesa Only)")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(),
|
||||
{
|
||||
PAIR(GpuAccuracy, Normal, tr("Normal")),
|
||||
PAIR(GpuAccuracy, High, tr("High")),
|
||||
PAIR(GpuAccuracy, Extreme, tr("Extreme")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::DmaAccuracy>::Index(),
|
||||
{
|
||||
PAIR(DmaAccuracy, Default, tr("Default")),
|
||||
PAIR(DmaAccuracy, Normal, tr("Normal")),
|
||||
PAIR(DmaAccuracy, High, tr("High")),
|
||||
PAIR(DmaAccuracy, Extreme, tr("Extreme")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
|
||||
{
|
||||
PAIR(CpuAccuracy, Auto, tr("Auto")),
|
||||
PAIR(CpuAccuracy, Accurate, tr("Accurate")),
|
||||
PAIR(CpuAccuracy, Unsafe, tr("Unsafe")),
|
||||
PAIR(CpuAccuracy, Paranoid, tr("Paranoid (disables most optimizations)")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::CpuBackend>::Index(),
|
||||
{
|
||||
PAIR(CpuBackend, Dynarmic, tr("Dynarmic")),
|
||||
PAIR(CpuBackend, Nce, tr("NCE")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::FullscreenMode>::Index(),
|
||||
{
|
||||
PAIR(FullscreenMode, Borderless, tr("Borderless Windowed")),
|
||||
PAIR(FullscreenMode, Exclusive, tr("Exclusive Fullscreen")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::NvdecEmulation>::Index(),
|
||||
{
|
||||
PAIR(NvdecEmulation, Off, tr("No Video Output")),
|
||||
PAIR(NvdecEmulation, Cpu, tr("CPU Video Decoding")),
|
||||
PAIR(NvdecEmulation, Gpu, tr("GPU Video Decoding (Default)")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::ResolutionSetup>::Index(),
|
||||
{
|
||||
PAIR(ResolutionSetup, Res1_4X, tr("0.25X (180p/270p) [EXPERIMENTAL]")),
|
||||
PAIR(ResolutionSetup, Res1_2X, tr("0.5X (360p/540p) [EXPERIMENTAL]")),
|
||||
PAIR(ResolutionSetup, Res3_4X, tr("0.75X (540p/810p) [EXPERIMENTAL]")),
|
||||
PAIR(ResolutionSetup, Res1X, tr("1X (720p/1080p)")),
|
||||
PAIR(ResolutionSetup, Res3_2X, tr("1.5X (1080p/1620p) [EXPERIMENTAL]")),
|
||||
PAIR(ResolutionSetup, Res2X, tr("2X (1440p/2160p)")),
|
||||
PAIR(ResolutionSetup, Res3X, tr("3X (2160p/3240p)")),
|
||||
PAIR(ResolutionSetup, Res4X, tr("4X (2880p/4320p)")),
|
||||
PAIR(ResolutionSetup, Res5X, tr("5X (3600p/5400p)")),
|
||||
PAIR(ResolutionSetup, Res6X, tr("6X (4320p/6480p)")),
|
||||
PAIR(ResolutionSetup, Res7X, tr("7X (5040p/7560p)")),
|
||||
PAIR(ResolutionSetup, Res8X, tr("8X (5760p/8640p)")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::ScalingFilter>::Index(),
|
||||
{
|
||||
PAIR(ScalingFilter, NearestNeighbor, tr("Nearest Neighbor")),
|
||||
PAIR(ScalingFilter, Bilinear, tr("Bilinear")),
|
||||
PAIR(ScalingFilter, Bicubic, tr("Bicubic")),
|
||||
PAIR(ScalingFilter, Gaussian, tr("Gaussian")),
|
||||
PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")),
|
||||
PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")),
|
||||
PAIR(ScalingFilter, Area, tr("Area")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::AntiAliasing>::Index(),
|
||||
{
|
||||
PAIR(AntiAliasing, None, tr("None")),
|
||||
PAIR(AntiAliasing, Fxaa, tr("FXAA")),
|
||||
PAIR(AntiAliasing, Smaa, tr("SMAA")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::AspectRatio>::Index(),
|
||||
{
|
||||
PAIR(AspectRatio, R16_9, tr("Default (16:9)")),
|
||||
PAIR(AspectRatio, R4_3, tr("Force 4:3")),
|
||||
PAIR(AspectRatio, R21_9, tr("Force 21:9")),
|
||||
PAIR(AspectRatio, R16_10, tr("Force 16:10")),
|
||||
PAIR(AspectRatio, Stretch, tr("Stretch to Window")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::AnisotropyMode>::Index(),
|
||||
{
|
||||
PAIR(AnisotropyMode, Automatic, tr("Automatic")),
|
||||
PAIR(AnisotropyMode, Default, tr("Default")),
|
||||
PAIR(AnisotropyMode, X2, tr("2x")),
|
||||
PAIR(AnisotropyMode, X4, tr("4x")),
|
||||
PAIR(AnisotropyMode, X8, tr("8x")),
|
||||
PAIR(AnisotropyMode, X16, tr("16x")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::Language>::Index(),
|
||||
{
|
||||
PAIR(Language, Japanese, tr("Japanese (日本語)")),
|
||||
PAIR(Language, EnglishAmerican, tr("American English")),
|
||||
PAIR(Language, French, tr("French (français)")),
|
||||
PAIR(Language, German, tr("German (Deutsch)")),
|
||||
PAIR(Language, Italian, tr("Italian (italiano)")),
|
||||
PAIR(Language, Spanish, tr("Spanish (español)")),
|
||||
PAIR(Language, Chinese, tr("Chinese")),
|
||||
PAIR(Language, Korean, tr("Korean (한국어)")),
|
||||
PAIR(Language, Dutch, tr("Dutch (Nederlands)")),
|
||||
PAIR(Language, Portuguese, tr("Portuguese (português)")),
|
||||
PAIR(Language, Russian, tr("Russian (Русский)")),
|
||||
PAIR(Language, Taiwanese, tr("Taiwanese")),
|
||||
PAIR(Language, EnglishBritish, tr("British English")),
|
||||
PAIR(Language, FrenchCanadian, tr("Canadian French")),
|
||||
PAIR(Language, SpanishLatin, tr("Latin American Spanish")),
|
||||
PAIR(Language, ChineseSimplified, tr("Simplified Chinese")),
|
||||
PAIR(Language, ChineseTraditional, tr("Traditional Chinese (正體中文)")),
|
||||
PAIR(Language, PortugueseBrazilian, tr("Brazilian Portuguese (português do Brasil)")),
|
||||
PAIR(Language, Serbian, tr("Serbian (српски)")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::Region>::Index(),
|
||||
{
|
||||
PAIR(Region, Japan, tr("Japan")),
|
||||
PAIR(Region, Usa, tr("USA")),
|
||||
PAIR(Region, Europe, tr("Europe")),
|
||||
PAIR(Region, Australia, tr("Australia")),
|
||||
PAIR(Region, China, tr("China")),
|
||||
PAIR(Region, Korea, tr("Korea")),
|
||||
PAIR(Region, Taiwan, tr("Taiwan")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::TimeZone>::Index(),
|
||||
{
|
||||
{static_cast<u32>(Settings::TimeZone::Auto),
|
||||
tr("Auto (%1)", "Auto select time zone")
|
||||
.arg(QString::fromStdString(
|
||||
Settings::GetTimeZoneString(Settings::TimeZone::Auto)))},
|
||||
{static_cast<u32>(Settings::TimeZone::Default),
|
||||
tr("Default (%1)", "Default time zone")
|
||||
.arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))},
|
||||
PAIR(TimeZone, Cet, tr("CET")),
|
||||
PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")),
|
||||
PAIR(TimeZone, Cuba, tr("Cuba")),
|
||||
PAIR(TimeZone, Eet, tr("EET")),
|
||||
PAIR(TimeZone, Egypt, tr("Egypt")),
|
||||
PAIR(TimeZone, Eire, tr("Eire")),
|
||||
PAIR(TimeZone, Est, tr("EST")),
|
||||
PAIR(TimeZone, Est5Edt, tr("EST5EDT")),
|
||||
PAIR(TimeZone, Gb, tr("GB")),
|
||||
PAIR(TimeZone, GbEire, tr("GB-Eire")),
|
||||
PAIR(TimeZone, Gmt, tr("GMT")),
|
||||
PAIR(TimeZone, GmtPlusZero, tr("GMT+0")),
|
||||
PAIR(TimeZone, GmtMinusZero, tr("GMT-0")),
|
||||
PAIR(TimeZone, GmtZero, tr("GMT0")),
|
||||
PAIR(TimeZone, Greenwich, tr("Greenwich")),
|
||||
PAIR(TimeZone, Hongkong, tr("Hongkong")),
|
||||
PAIR(TimeZone, Hst, tr("HST")),
|
||||
PAIR(TimeZone, Iceland, tr("Iceland")),
|
||||
PAIR(TimeZone, Iran, tr("Iran")),
|
||||
PAIR(TimeZone, Israel, tr("Israel")),
|
||||
PAIR(TimeZone, Jamaica, tr("Jamaica")),
|
||||
PAIR(TimeZone, Japan, tr("Japan")),
|
||||
PAIR(TimeZone, Kwajalein, tr("Kwajalein")),
|
||||
PAIR(TimeZone, Libya, tr("Libya")),
|
||||
PAIR(TimeZone, Met, tr("MET")),
|
||||
PAIR(TimeZone, Mst, tr("MST")),
|
||||
PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")),
|
||||
PAIR(TimeZone, Navajo, tr("Navajo")),
|
||||
PAIR(TimeZone, Nz, tr("NZ")),
|
||||
PAIR(TimeZone, NzChat, tr("NZ-CHAT")),
|
||||
PAIR(TimeZone, Poland, tr("Poland")),
|
||||
PAIR(TimeZone, Portugal, tr("Portugal")),
|
||||
PAIR(TimeZone, Prc, tr("PRC")),
|
||||
PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")),
|
||||
PAIR(TimeZone, Roc, tr("ROC")),
|
||||
PAIR(TimeZone, Rok, tr("ROK")),
|
||||
PAIR(TimeZone, Singapore, tr("Singapore")),
|
||||
PAIR(TimeZone, Turkey, tr("Turkey")),
|
||||
PAIR(TimeZone, Uct, tr("UCT")),
|
||||
PAIR(TimeZone, Universal, tr("Universal")),
|
||||
PAIR(TimeZone, Utc, tr("UTC")),
|
||||
PAIR(TimeZone, WSu, tr("W-SU")),
|
||||
PAIR(TimeZone, Wet, tr("WET")),
|
||||
PAIR(TimeZone, Zulu, tr("Zulu")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::AudioMode>::Index(),
|
||||
{
|
||||
PAIR(AudioMode, Mono, tr("Mono")),
|
||||
PAIR(AudioMode, Stereo, tr("Stereo")),
|
||||
PAIR(AudioMode, Surround, tr("Surround")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::MemoryLayout>::Index(),
|
||||
{
|
||||
PAIR(MemoryLayout, Memory_4Gb, tr("4GB DRAM (Default)")),
|
||||
PAIR(MemoryLayout, Memory_6Gb, tr("6GB DRAM (Unsafe)")),
|
||||
PAIR(MemoryLayout, Memory_8Gb, tr("8GB DRAM")),
|
||||
PAIR(MemoryLayout, Memory_10Gb, tr("10GB DRAM (Unsafe)")),
|
||||
PAIR(MemoryLayout, Memory_12Gb, tr("12GB DRAM (Unsafe)")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
|
||||
{
|
||||
PAIR(ConsoleMode, Docked, tr("Docked")),
|
||||
PAIR(ConsoleMode, Handheld, tr("Handheld")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::CpuClock>::Index(),
|
||||
{
|
||||
PAIR(CpuClock, Boost, tr("Boost (1700MHz)")),
|
||||
PAIR(CpuClock, Fast, tr("Fast (2000MHz)")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
|
||||
{
|
||||
PAIR(ConfirmStop, Ask_Always, tr("Always ask (Default)")),
|
||||
PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")),
|
||||
PAIR(ConfirmStop, Ask_Never, tr("Never ask")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuOverclock>::Index(),
|
||||
{
|
||||
PAIR(GpuOverclock, Low, tr("Low (128)")),
|
||||
PAIR(GpuOverclock, Medium, tr("Medium (256)")),
|
||||
PAIR(GpuOverclock, High, tr("High (512)")),
|
||||
}});
|
||||
|
||||
#undef PAIR
|
||||
#undef CTX_PAIR
|
||||
|
||||
return translations;
|
||||
}
|
||||
} // namespace ConfigurationShared
|
||||
72
src/qt_common/shared_translation.h
Normal file
72
src/qt_common/shared_translation.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <QString>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_enums.h"
|
||||
|
||||
namespace ConfigurationShared {
|
||||
using TranslationMap = std::map<u32, std::pair<QString, QString>>;
|
||||
using ComboboxTranslations = std::vector<std::pair<u32, QString>>;
|
||||
using ComboboxTranslationMap = std::map<u32, ComboboxTranslations>;
|
||||
|
||||
std::unique_ptr<TranslationMap> InitializeTranslations(QObject *parent);
|
||||
|
||||
std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent);
|
||||
|
||||
static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map = {
|
||||
{Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))},
|
||||
{Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))},
|
||||
{Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))},
|
||||
};
|
||||
|
||||
static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map = {
|
||||
{Settings::ScalingFilter::NearestNeighbor,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Nearest"))},
|
||||
{Settings::ScalingFilter::Bilinear,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))},
|
||||
{Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))},
|
||||
{Settings::ScalingFilter::Gaussian,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))},
|
||||
{Settings::ScalingFilter::ScaleForce,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))},
|
||||
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
|
||||
{Settings::ScalingFilter::Area, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Area"))},
|
||||
};
|
||||
|
||||
static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map = {
|
||||
{Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
|
||||
{Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
|
||||
};
|
||||
|
||||
static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map = {
|
||||
{Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
|
||||
{Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
|
||||
{Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
|
||||
};
|
||||
|
||||
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map = {
|
||||
{Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))},
|
||||
{Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))},
|
||||
{Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
|
||||
};
|
||||
|
||||
static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map = {
|
||||
{Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
|
||||
{Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
|
||||
{Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
|
||||
};
|
||||
|
||||
} // namespace ConfigurationShared
|
||||
110
src/qt_common/uisettings.cpp
Normal file
110
src/qt_common/uisettings.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QSettings>
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "qt_common/uisettings.h"
|
||||
|
||||
#ifndef CANNOT_EXPLICITLY_INSTANTIATE
|
||||
namespace Settings {
|
||||
template class Setting<bool>;
|
||||
template class Setting<std::string>;
|
||||
template class Setting<u16, true>;
|
||||
template class Setting<u32>;
|
||||
template class Setting<u8, true>;
|
||||
template class Setting<u8>;
|
||||
template class Setting<unsigned long long>;
|
||||
} // namespace Settings
|
||||
#endif
|
||||
|
||||
namespace FS = Common::FS;
|
||||
|
||||
namespace UISettings {
|
||||
|
||||
const Themes themes{{
|
||||
{"Default", "default"},
|
||||
{"Default Colorful", "colorful"},
|
||||
{"Dark", "qdarkstyle"},
|
||||
{"Dark Colorful", "colorful_dark"},
|
||||
{"Midnight Blue", "qdarkstyle_midnight_blue"},
|
||||
{"Midnight Blue Colorful", "colorful_midnight_blue"},
|
||||
}};
|
||||
|
||||
bool IsDarkTheme() {
|
||||
const auto& theme = UISettings::values.theme;
|
||||
return theme == std::string("qdarkstyle") || theme == std::string("qdarkstyle_midnight_blue") ||
|
||||
theme == std::string("colorful_dark") || theme == std::string("colorful_midnight_blue");
|
||||
}
|
||||
|
||||
Values values = {};
|
||||
|
||||
u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) {
|
||||
switch (ratio) {
|
||||
case Settings::AspectRatio::R4_3:
|
||||
return height * 4 / 3;
|
||||
case Settings::AspectRatio::R21_9:
|
||||
return height * 21 / 9;
|
||||
case Settings::AspectRatio::R16_10:
|
||||
return height * 16 / 10;
|
||||
case Settings::AspectRatio::R16_9:
|
||||
case Settings::AspectRatio::Stretch:
|
||||
// TODO: Move this function wherever appropriate to implement Stretched aspect
|
||||
break;
|
||||
}
|
||||
return height * 16 / 9;
|
||||
}
|
||||
|
||||
void SaveWindowState() {
|
||||
const auto window_state_config_loc =
|
||||
FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "window_state.ini");
|
||||
|
||||
void(FS::CreateParentDir(window_state_config_loc));
|
||||
QSettings config(QString::fromStdString(window_state_config_loc), QSettings::IniFormat);
|
||||
|
||||
config.setValue(QStringLiteral("geometry"), values.geometry);
|
||||
config.setValue(QStringLiteral("state"), values.state);
|
||||
config.setValue(QStringLiteral("geometryRenderWindow"), values.renderwindow_geometry);
|
||||
config.setValue(QStringLiteral("gameListHeaderState"), values.gamelist_header_state);
|
||||
|
||||
config.sync();
|
||||
}
|
||||
|
||||
void RestoreWindowState(std::unique_ptr<QtConfig>& qtConfig) {
|
||||
const auto window_state_config_loc =
|
||||
FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "window_state.ini");
|
||||
|
||||
// Migrate window state from old location
|
||||
if (!FS::Exists(window_state_config_loc) && qtConfig->Exists("UI", "UILayout\\geometry")) {
|
||||
const auto config_loc =
|
||||
FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "qt-config.ini");
|
||||
QSettings config(QString::fromStdString(config_loc), QSettings::IniFormat);
|
||||
|
||||
config.beginGroup(QStringLiteral("UI"));
|
||||
config.beginGroup(QStringLiteral("UILayout"));
|
||||
values.geometry = config.value(QStringLiteral("geometry")).toByteArray();
|
||||
values.state = config.value(QStringLiteral("state")).toByteArray();
|
||||
values.renderwindow_geometry =
|
||||
config.value(QStringLiteral("geometryRenderWindow")).toByteArray();
|
||||
values.gamelist_header_state =
|
||||
config.value(QStringLiteral("gameListHeaderState")).toByteArray();
|
||||
config.endGroup();
|
||||
config.endGroup();
|
||||
return;
|
||||
}
|
||||
|
||||
void(FS::CreateParentDir(window_state_config_loc));
|
||||
const QSettings config(QString::fromStdString(window_state_config_loc), QSettings::IniFormat);
|
||||
|
||||
values.geometry = config.value(QStringLiteral("geometry")).toByteArray();
|
||||
values.state = config.value(QStringLiteral("state")).toByteArray();
|
||||
values.renderwindow_geometry =
|
||||
config.value(QStringLiteral("geometryRenderWindow")).toByteArray();
|
||||
values.gamelist_header_state =
|
||||
config.value(QStringLiteral("gameListHeaderState")).toByteArray();
|
||||
}
|
||||
|
||||
} // namespace UISettings
|
||||
286
src/qt_common/uisettings.h
Normal file
286
src/qt_common/uisettings.h
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <QByteArray>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "qt_common/qt_config.h"
|
||||
|
||||
using Settings::Category;
|
||||
using Settings::ConfirmStop;
|
||||
using Settings::Setting;
|
||||
using Settings::SwitchableSetting;
|
||||
|
||||
#ifndef CANNOT_EXPLICITLY_INSTANTIATE
|
||||
namespace Settings {
|
||||
extern template class Setting<bool>;
|
||||
extern template class Setting<std::string>;
|
||||
extern template class Setting<u16, true>;
|
||||
extern template class Setting<u32>;
|
||||
extern template class Setting<u8, true>;
|
||||
extern template class Setting<u8>;
|
||||
extern template class Setting<unsigned long long>;
|
||||
} // namespace Settings
|
||||
#endif
|
||||
|
||||
namespace UISettings {
|
||||
|
||||
bool IsDarkTheme();
|
||||
|
||||
struct ContextualShortcut {
|
||||
std::string keyseq;
|
||||
std::string controller_keyseq;
|
||||
int context;
|
||||
bool repeat;
|
||||
};
|
||||
|
||||
struct Shortcut {
|
||||
std::string name;
|
||||
std::string group;
|
||||
ContextualShortcut shortcut;
|
||||
};
|
||||
|
||||
enum class Theme {
|
||||
Default,
|
||||
DefaultColorful,
|
||||
Dark,
|
||||
DarkColorful,
|
||||
MidnightBlue,
|
||||
MidnightBlueColorful,
|
||||
};
|
||||
|
||||
static constexpr Theme default_theme{
|
||||
#ifdef _WIN32
|
||||
Theme::DarkColorful
|
||||
#else
|
||||
Theme::DefaultColorful
|
||||
#endif
|
||||
};
|
||||
|
||||
using Themes = std::array<std::pair<const char*, const char*>, 6>;
|
||||
extern const Themes themes;
|
||||
|
||||
struct GameDir {
|
||||
std::string path;
|
||||
bool deep_scan = false;
|
||||
bool expanded = false;
|
||||
bool operator==(const GameDir& rhs) const {
|
||||
return path == rhs.path;
|
||||
}
|
||||
bool operator!=(const GameDir& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
struct Values {
|
||||
Settings::Linkage linkage{1000};
|
||||
|
||||
QByteArray geometry;
|
||||
QByteArray state;
|
||||
|
||||
QByteArray renderwindow_geometry;
|
||||
|
||||
QByteArray gamelist_header_state;
|
||||
|
||||
Setting<bool> single_window_mode{linkage, true, "singleWindowMode", Category::Ui};
|
||||
Setting<bool> fullscreen{linkage, false, "fullscreen", Category::Ui};
|
||||
Setting<bool> display_titlebar{linkage, true, "displayTitleBars", Category::Ui};
|
||||
Setting<bool> show_filter_bar{linkage, true, "showFilterBar", Category::Ui};
|
||||
Setting<bool> show_status_bar{linkage, true, "showStatusBar", Category::Ui};
|
||||
|
||||
SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
|
||||
ConfirmStop::Ask_Always,
|
||||
"confirmStop",
|
||||
Category::UiGeneral,
|
||||
Settings::Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
|
||||
Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
|
||||
Setting<bool> pause_when_in_background{linkage,
|
||||
false,
|
||||
"pauseWhenInBackground",
|
||||
Category::UiGeneral,
|
||||
Settings::Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
Setting<bool> mute_when_in_background{linkage,
|
||||
false,
|
||||
"muteWhenInBackground",
|
||||
Category::UiAudio,
|
||||
Settings::Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
Setting<bool> hide_mouse{
|
||||
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
|
||||
true, true};
|
||||
Setting<bool> controller_applet_disabled{linkage, false, "disableControllerApplet",
|
||||
Category::UiGeneral};
|
||||
// Set when Vulkan is known to crash the application
|
||||
bool has_broken_vulkan = false;
|
||||
|
||||
Setting<bool> select_user_on_boot{linkage,
|
||||
false,
|
||||
"select_user_on_boot",
|
||||
Category::UiGeneral,
|
||||
Settings::Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Ui};
|
||||
Setting<bool> check_for_updates{linkage, true, "check_for_updates", Category::UiGeneral};
|
||||
|
||||
// Discord RPC
|
||||
Setting<bool> enable_discord_presence{linkage, false, "enable_discord_presence", Category::Ui};
|
||||
|
||||
// logging
|
||||
Setting<bool> show_console{linkage, false, "showConsole", Category::Ui};
|
||||
|
||||
// Screenshots
|
||||
Setting<bool> enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as",
|
||||
Category::Screenshots};
|
||||
Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots};
|
||||
|
||||
std::string roms_path;
|
||||
std::string game_dir_deprecated;
|
||||
bool game_dir_deprecated_deepscan;
|
||||
QVector<GameDir> game_dirs;
|
||||
QStringList recent_files;
|
||||
Setting<std::string> language{linkage, {}, "language", Category::Paths};
|
||||
|
||||
std::string theme;
|
||||
|
||||
// Shortcut name <Shortcut, context>
|
||||
std::vector<Shortcut> shortcuts;
|
||||
|
||||
Setting<u32> callout_flags{linkage, 0, "calloutFlags", Category::Ui};
|
||||
|
||||
// multiplayer settings
|
||||
Setting<std::string> multiplayer_nickname{linkage, {}, "nickname", Category::Multiplayer};
|
||||
Setting<std::string> multiplayer_filter_text{linkage, {}, "filter_text", Category::Multiplayer};
|
||||
Setting<bool> multiplayer_filter_games_owned{linkage, false, "filter_games_owned",
|
||||
Category::Multiplayer};
|
||||
Setting<bool> multiplayer_filter_hide_empty{linkage, false, "filter_games_hide_empty",
|
||||
Category::Multiplayer};
|
||||
Setting<bool> multiplayer_filter_hide_full{linkage, false, "filter_games_hide_full",
|
||||
Category::Multiplayer};
|
||||
Setting<std::string> multiplayer_ip{linkage, {}, "ip", Category::Multiplayer};
|
||||
Setting<u16, true> multiplayer_port{linkage, 24872, 0,
|
||||
UINT16_MAX, "port", Category::Multiplayer};
|
||||
Setting<std::string> multiplayer_room_nickname{
|
||||
linkage, {}, "room_nickname", Category::Multiplayer};
|
||||
Setting<std::string> multiplayer_room_name{linkage, {}, "room_name", Category::Multiplayer};
|
||||
Setting<u8, true> multiplayer_max_player{linkage, 8, 0, 8, "max_player", Category::Multiplayer};
|
||||
Setting<u16, true> multiplayer_room_port{linkage, 24872, 0,
|
||||
UINT16_MAX, "room_port", Category::Multiplayer};
|
||||
Setting<u8, true> multiplayer_host_type{linkage, 0, 0, 1, "host_type", Category::Multiplayer};
|
||||
Setting<unsigned long long> multiplayer_game_id{linkage, {}, "game_id", Category::Multiplayer};
|
||||
Setting<std::string> multiplayer_room_description{
|
||||
linkage, {}, "room_description", Category::Multiplayer};
|
||||
std::pair<std::vector<std::string>, std::vector<std::string>> multiplayer_ban_list;
|
||||
|
||||
// Game List
|
||||
Setting<bool> show_add_ons{linkage, true, "show_add_ons", Category::UiGameList};
|
||||
Setting<u32> game_icon_size{linkage, 64, "game_icon_size", Category::UiGameList};
|
||||
Setting<u32> folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList};
|
||||
Setting<u8> row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList};
|
||||
Setting<u8> row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList};
|
||||
std::atomic_bool is_game_list_reload_pending{false};
|
||||
Setting<bool> cache_game_list{linkage, true, "cache_game_list", Category::UiGameList};
|
||||
Setting<bool> favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList};
|
||||
QVector<u64> favorited_ids;
|
||||
|
||||
// Compatibility List
|
||||
Setting<bool> show_compat{linkage, false, "show_compat", Category::UiGameList};
|
||||
|
||||
// Size & File Types Column
|
||||
Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
|
||||
Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
|
||||
|
||||
// Play time
|
||||
Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
|
||||
|
||||
// misc
|
||||
Setting<bool> show_fw_warning{linkage, true, "show_fw_warning", Category::Miscellaneous};
|
||||
|
||||
bool configuration_applied;
|
||||
bool reset_to_defaults;
|
||||
bool shortcut_already_warned{false};
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
u32 CalculateWidth(u32 height, Settings::AspectRatio ratio);
|
||||
|
||||
void SaveWindowState();
|
||||
void RestoreWindowState(std::unique_ptr<QtConfig>& qtConfig);
|
||||
|
||||
// This shouldn't have anything except static initializers (no functions). So
|
||||
// QKeySequence(...).toString() is NOT ALLOWED HERE.
|
||||
// This must be in alphabetical order according to action name as it must have the same order as
|
||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||
// clang-format off
|
||||
const std::array<Shortcut, 30> default_hotkeys{{
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+P"), std::string("Screenshot"), Qt::WidgetWithChildrenShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F8"), std::string("Home+L"), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F10"), std::string("Home+X"), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F9"), std::string("Home+R"), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Configure")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+,"), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Configure Current Game")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+."), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F4"), std::string("Home+Plus"), Qt::WindowShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Esc"), std::string(""), Qt::WindowShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Eden")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+Q"), std::string("Home+Minus"), Qt::WindowShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F11"), std::string("Home+B"), Qt::WindowShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+O"), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F2"), std::string("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Browse Public Game Lobby")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+B"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Create Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+N"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Direct Connect to Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+C"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Leave Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+L"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Show Current Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+R"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F6"), std::string("R+Plus+Minus"), Qt::WindowShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F5"), std::string("L+Plus+Minus"), Qt::WindowShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F6"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F5"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F"), std::string(""), Qt::WindowShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+U"), std::string("Home+Y"), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F9"), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string(""), std::string(""), Qt::ApplicationShortcut, false}},
|
||||
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+S"), std::string(""), Qt::WindowShortcut, false}},
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
} // namespace UISettings
|
||||
|
||||
Q_DECLARE_METATYPE(UISettings::GameDir*);
|
||||
|
||||
// These metatype declarations cannot be in common/settings.h because core is devoid of QT
|
||||
Q_DECLARE_METATYPE(Settings::CpuAccuracy);
|
||||
Q_DECLARE_METATYPE(Settings::GpuAccuracy);
|
||||
Q_DECLARE_METATYPE(Settings::DmaAccuracy);
|
||||
Q_DECLARE_METATYPE(Settings::FullscreenMode);
|
||||
Q_DECLARE_METATYPE(Settings::NvdecEmulation);
|
||||
Q_DECLARE_METATYPE(Settings::ResolutionSetup);
|
||||
Q_DECLARE_METATYPE(Settings::ScalingFilter);
|
||||
Q_DECLARE_METATYPE(Settings::AntiAliasing);
|
||||
Q_DECLARE_METATYPE(Settings::RendererBackend);
|
||||
Q_DECLARE_METATYPE(Settings::ShaderBackend);
|
||||
Q_DECLARE_METATYPE(Settings::AstcRecompression);
|
||||
Q_DECLARE_METATYPE(Settings::AstcDecodeMode);
|
||||
Q_DECLARE_METATYPE(Settings::SpirvOptimizeMode);
|
||||
Loading…
Add table
Add a link
Reference in a new issue