Lime3DS/src/citra_qt/citra_qt.cpp

3833 lines
148 KiB
C++
Raw Normal View History

// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <clocale>
#include <iostream>
#include <memory>
#include <optional>
2014-10-28 05:36:00 -02:00
#include <thread>
2014-03-31 22:26:50 -04:00
#include <QFileDialog>
2017-12-02 15:35:20 -06:00
#include <QFutureWatcher>
2020-08-17 18:17:42 -03:00
#include <QLabel>
#include <QMessageBox>
2019-09-14 20:28:39 +10:00
#include <QSysInfo>
#include <QtConcurrent/QtConcurrentMap>
2017-12-02 15:35:20 -06:00
#include <QtConcurrent/QtConcurrentRun>
#include <QtGui>
#include <QtWidgets>
#include <boost/algorithm/string/replace.hpp>
#include <fmt/format.h>
2024-11-07 16:19:37 +00:00
#include <fmt/ostream.h>
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
#ifdef _WIN32
#include <shlobj.h>
#include <windows.h>
#else
#include <getopt.h>
#endif
#ifdef __unix__
#include <QVariant>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QtDBus>
#include "common/linux/gamemode.h"
#endif
#include "citra_meta/common_strings.h"
#include "citra_qt/aboutdialog.h"
#include "citra_qt/applets/mii_selector.h"
#include "citra_qt/applets/swkbd.h"
2015-09-11 00:23:00 -04:00
#include "citra_qt/bootmanager.h"
citra_qt: camera integration (#3566) * Implement camera feature * Make OpenCVCamera optional * Fix styling problems * CI configuration * Fix CI * Hopefully final fix * Hopefully final fix * Fix all the problems * Oops.. * Add Qt Multimedia Camera * Another oops * Try to fix broken linux CI * Try to fix broken linux CI * Fix problems * Improve UI * Fix problems * camera: Add support for Qt <5.10 and fix preview error * CI: try to fix linux-frozen travis build * camera: fix problems and add multiple handlers support * fix CI * remove most ServiceFramework changes * Fix format * Remove last ServiceFramework change * camera: remove unused interfaces; revert submodule change * camera: fix CI error * ci: use ccache for opencv build * citra_qt: fix configuration error; CI: add mediaservice plugin * citra_qt: fix clang-format * citra_qt: fix documentation error * citra_qt: fix configuration page; camera: fix pausing crash * citra_qt: fix preview not stopping * camera: extend handlers length * camera: fix camera resume error * camera: fix clang-format * camera: remove all OpenCV; citra_qt: rewrite configuration * camera: remove all OpenCV; citra_qt: rewrite configuration * camera: remove all OpenCV; citra_qt: rewrite configuration * CI: fix linux ci * camera: check settings update; citra_qt: fix configuration error * service_cam: use a better way to apply configuration * Service_CAM: rewrite camera reload * cam: fix clang format * citra_qt: fix argument load issue; camera: base of system camera selection * citra_qt: Add system camera selection * camera: fix image upside down, Implement SetFrameRate in Qt Multimedia Camera * camera: Add missing <array> include, update SetFrameRate and add settings in Qt Multimedia Camera header * camera: move started in Qt Multimedia Camera header * QtMultimediaCamera: Move frame rates to SetFrameRate; Set minimum and maximum frame rate * Update appveyor.yml
2018-05-12 01:42:23 +08:00
#include "citra_qt/camera/qt_multimedia_camera.h"
#include "citra_qt/camera/still_image_camera.h"
2024-11-10 21:02:54 +00:00
#include "citra_qt/citra_qt.h"
#include "citra_qt/compatibility_list.h"
#include "citra_qt/configuration/config.h"
#include "citra_qt/configuration/configure_dialog.h"
#include "citra_qt/configuration/configure_per_game.h"
#include "citra_qt/debugger/console.h"
#include "citra_qt/debugger/graphics/graphics.h"
#include "citra_qt/debugger/graphics/graphics_breakpoints.h"
#include "citra_qt/debugger/graphics/graphics_cmdlists.h"
#include "citra_qt/debugger/graphics/graphics_surface.h"
#include "citra_qt/debugger/graphics/graphics_tracing.h"
#include "citra_qt/debugger/graphics/graphics_vertex_shader.h"
#include "citra_qt/debugger/ipc/recorder.h"
#include "citra_qt/debugger/lle_service_modules.h"
2015-09-11 00:23:00 -04:00
#include "citra_qt/debugger/profiler.h"
#include "citra_qt/debugger/registers.h"
2016-04-08 19:28:54 +03:00
#include "citra_qt/debugger/wait_tree.h"
#include "citra_qt/discord.h"
#include "citra_qt/dumping/dumping_dialog.h"
#include "citra_qt/game_list.h"
#include "citra_qt/hotkeys.h"
#include "citra_qt/loading_screen.h"
#include "citra_qt/movie/movie_play_dialog.h"
#include "citra_qt/movie/movie_record_dialog.h"
#include "citra_qt/multiplayer/state.h"
#include "citra_qt/play_time_manager.h"
#include "citra_qt/qt_image_interface.h"
#include "citra_qt/uisettings.h"
2018-01-19 13:42:21 +00:00
#include "citra_qt/util/clickable_label.h"
#include "citra_qt/util/graphics_device_info.h"
#include "citra_qt/util/util.h"
#include "common/arch.h"
#include "common/common_paths.h"
#include "common/detached_tasks.h"
#include "common/dynamic_library/dynamic_library.h"
#include "common/file_util.h"
2022-11-15 11:16:45 +01:00
#include "common/literals.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "common/memory_detect.h"
2015-06-21 14:58:59 +01:00
#include "common/scm_rev.h"
2014-10-28 05:36:00 -02:00
#include "common/scope_exit.h"
#if CITRA_ARCH(x86_64)
2019-09-14 20:28:39 +10:00
#include "common/x64/cpu_detect.h"
#endif
#include "common/settings.h"
2024-11-07 16:19:37 +00:00
#include "common/string_util.h"
#include "core/core.h"
#include "core/dumping/backend.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h"
#include "core/frontend/applets/default_applets.h"
2023-05-03 12:24:10 -03:00
#include "core/hle/service/am/am.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/nfc/nfc.h"
2015-09-11 00:23:00 -04:00
#include "core/loader/loader.h"
2018-07-08 13:55:32 +08:00
#include "core/movie.h"
#include "core/savestate.h"
#include "core/system_titles.h"
#include "input_common/main.h"
2020-08-20 04:46:35 -03:00
#include "network/network_settings.h"
#include "ui_main.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
2015-05-18 21:21:33 -07:00
#ifdef __APPLE__
#include "common/apple_authorization.h"
#endif
#ifdef USE_DISCORD_PRESENCE
#include "citra_qt/discord_impl.h"
#endif
2016-08-01 15:13:35 -06:00
#ifdef QT_STATICPLUGIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
#ifdef HAVE_SDL2
#include <SDL.h>
#endif
constexpr int default_mouse_timeout = 2500;
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
* user. This is 32-bits - if we have more than 32 callouts, we should retire and recycle old ones.
*/
const int GMainWindow::max_recent_files_item;
static QString PrettyProductName() {
#ifdef _WIN32
// After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
// With that notation change they changed the registry key used to denote the current version
QSettings windows_registry(
QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
QSettings::NativeFormat);
const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString();
if (release_id == QStringLiteral("2009")) {
const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt();
const QString display_version =
windows_registry.value(QStringLiteral("DisplayVersion")).toString();
const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt();
const u32 version = current_build >= 22000 ? 11 : 10;
return QStringLiteral("Windows %1 Version %2 (Build %3.%4)")
.arg(QString::number(version), display_version, QString::number(current_build),
QString::number(ubr));
}
#endif
return QSysInfo::prettyProductName();
}
void GMainWindow::ShowCommandOutput(std::string title, std::string message) {
#ifdef _WIN32
boost::replace_all(message, " ", "\u00a0"); // Non-breaking space
boost::replace_all(message, "-", "\u2011"); // Non-breaking hyphen
QMessageBox::information(this, QString::fromStdString(title), QString::fromStdString(message));
#else
std::cout << message << std::endl;
#endif
}
GMainWindow::GMainWindow(Core::System& system_)
2023-08-01 03:57:38 +03:00
: ui{std::make_unique<Ui::MainWindow>()}, system{system_}, movie{system.Movie()},
emu_user_dir_exists{
std::filesystem::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))},
config{std::make_unique<QtConfig>()}, emu_thread{nullptr} {
Common::Log::Initialize();
2023-07-03 02:17:03 +03:00
Common::Log::Start();
Debugger::ToggleConsole();
QStringList args = QApplication::arguments();
QString game_path;
std::optional<bool> fullscreen_override;
for (int i = 1; i < args.size(); ++i) {
// Preserves drag/drop functionality
if (args.size() == 2 && !args[1].startsWith(QChar::fromLatin1('-'))) {
game_path = args[1];
break;
}
// Dump video
if (args[i] == QStringLiteral("--dump-video") || args[i] == QStringLiteral("-d")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
if (!DynamicLibrary::FFmpeg::LoadFFmpeg()) {
ShowFFmpegErrorMessage();
continue;
}
video_dumping_path = args[++i];
video_dumping_on_start = true;
continue;
}
// Launch game in fullscreen mode
if (args[i] == QStringLiteral("--fullscreen") || args[i] == QStringLiteral("-f")) {
fullscreen_override = true;
continue;
}
// Enable GDB stub
if (args[i] == QStringLiteral("--gdbport") || args[i] == QStringLiteral("-g")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
Settings::values.use_gdbstub = true;
Settings::values.gdbstub_port = strtoul(args[++i].toLatin1(), NULL, 0);
continue;
}
if (args[i] == QStringLiteral("--help") || args[i] == QStringLiteral("-h")) {
ShowCommandOutput("Help", fmt::format(Common::help_string, args[0].toStdString()));
exit(0);
}
if (args[i] == QStringLiteral("--install") || args[i] == QStringLiteral("-i")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
Service::AM::InstallStatus result = Service::AM::InstallCIA(args[++i].toStdString());
if (result != Service::AM::InstallStatus::Success) {
std::string failure_reason;
if (result == Service::AM::InstallStatus::ErrorFailedToOpenFile)
failure_reason = "Unable to open file.";
if (result == Service::AM::InstallStatus::ErrorFileNotFound)
failure_reason = "File not found.";
if (result == Service::AM::InstallStatus::ErrorAborted)
failure_reason = "Install was aborted.";
if (result == Service::AM::InstallStatus::ErrorInvalid)
failure_reason = "CIA is invalid.";
if (result == Service::AM::InstallStatus::ErrorEncrypted)
failure_reason = "CIA is encrypted.";
std::string failure_string = "Failed to install CIA: " + failure_reason;
ShowCommandOutput("Failure", failure_string);
exit((int)result +
2); // 2 is added here to avoid stepping on the toes of
// exit codes 1 and 2 which have pre-established conventional meanings
}
ShowCommandOutput("Success", "Installed CIA successfully.");
exit(0);
}
if (args[i] == QStringLiteral("--movie-play") || args[i] == QStringLiteral("-p")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
movie_playback_path = args[++i];
movie_playback_on_start = true;
continue;
}
if (args[i] == QStringLiteral("--movie-record") || args[i] == QStringLiteral("-r")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
movie_record_path = args[++i];
movie_record_on_start = true;
continue;
}
if (args[i] == QStringLiteral("--movie-record-author") || args[i] == QStringLiteral("-a")) {
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
movie_record_author = args[++i];
continue;
}
if (args[i] == QStringLiteral("--multiplayer") || args[i] == QStringLiteral("-m")) {
std::cout << "Warning: The --multiplayer option is not yet implemented for the Qt "
"frontend; Ignoring."
<< std::endl;
if (i < args.size() - 1 && !args[i + 1].startsWith(QChar::fromLatin1('-'))) {
i++;
}
continue;
}
if (args[i] == QStringLiteral("--version") || args[i] == QStringLiteral("-v")) {
const std::string version_string =
std::string("Azahar ") + Common::g_scm_branch + " " + Common::g_scm_desc;
ShowCommandOutput("Version", version_string);
exit(0);
}
// Launch game in windowed mode
if (args[i] == QStringLiteral("--windowed") || args[i] == QStringLiteral("-w")) {
fullscreen_override = false;
continue;
}
// Launch game at path
if (i == args.size() - 1 && !args[i].startsWith(QChar::fromLatin1('-'))) {
game_path = args[i];
continue;
}
}
// Check if data migration from Citra/Lime3DS needs to be performed
if (!emu_user_dir_exists) {
ShowMigrationPrompt();
}
#ifdef __unix__
SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
#endif
// register types to use in slots and signals
qRegisterMetaType<std::size_t>("std::size_t");
qRegisterMetaType<Service::AM::InstallStatus>("Service::AM::InstallStatus");
// Register CameraFactory
qt_cameras = std::make_shared<Camera::QtMultimediaCameraHandlerFactory>();
Camera::RegisterFactory("image", std::make_unique<Camera::StillImageCameraFactory>());
Camera::RegisterFactory("qt", std::make_unique<Camera::QtMultimediaCameraFactory>(qt_cameras));
LoadTranslation();
Pica::g_debug_context = Pica::DebugContext::Construct();
setAcceptDrops(true);
ui->setupUi(this);
statusBar()->hide();
default_theme_paths = QIcon::themeSearchPaths();
UpdateUITheme();
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
discord_rpc->Update();
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
Network::Init();
movie.SetPlaybackCompletionCallback([this] {
QMetaObject::invokeMethod(this, "OnMoviePlaybackCompleted", Qt::BlockingQueuedConnection);
});
InitializeWidgets();
InitializeDebugWidgets();
InitializeRecentFileMenuActions();
InitializeSaveStateMenuActions();
InitializeHotkeys();
SetDefaultUIGeometry();
RestoreUIState();
ui->action_Dump_Video->setChecked(video_dumping_on_start);
if (fullscreen_override) {
ui->action_Fullscreen->setChecked(*fullscreen_override);
}
ui->action_Close_Movie->setEnabled(movie_playback_on_start || movie_record_on_start);
ConnectAppEvents();
ConnectMenuEvents();
ConnectWidgetEvents();
LOG_INFO(Frontend, "Azahar Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
2018-06-29 16:56:12 +03:00
Common::g_scm_desc);
#if CITRA_ARCH(x86_64)
const auto& caps = Common::GetCPUCaps();
std::string cpu_string = caps.cpu_string;
if (caps.avx || caps.avx2 || caps.avx512) {
cpu_string += " | AVX";
if (caps.avx512) {
cpu_string += "512";
} else if (caps.avx2) {
cpu_string += '2';
}
if (caps.fma || caps.fma4) {
cpu_string += " | FMA";
}
}
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
2019-09-14 20:28:39 +10:00
#endif
LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
2022-11-15 11:16:45 +01:00
const auto& mem_info = Common::GetMemInfo();
using namespace Common::Literals;
LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", mem_info.total_physical_memory / f64{1_GiB});
LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", mem_info.total_swap_memory / f64{1_GiB});
UpdateWindowTitle();
show();
2018-04-16 00:42:58 +02:00
game_list->LoadCompatibilityList();
game_list->PopulateAsync(UISettings::values.game_dirs);
mouse_hide_timer.setInterval(default_mouse_timeout);
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::OnMouseActivity);
#ifdef ENABLE_OPENGL
gl_renderer = GetOpenGLRenderer();
#if defined(_WIN32)
if (gl_renderer.startsWith(QStringLiteral("D3D12"))) {
// OpenGLOn12 supports but does not yet advertise OpenGL 4.0+
// We can override the version here to allow Citra to work.
// TODO: Remove this when OpenGL 4.0+ is advertised.
qputenv("MESA_GL_VERSION_OVERRIDE", "4.6");
}
#endif
#endif
#ifdef ENABLE_VULKAN
Add vulkan backend (#6512) * code: Prepare frontend for vulkan support * citra_qt: Add vulkan options to the GUI * vk_instance: Collect tooling info * renderer_vulkan: Add vulkan backend * qt: Fix fullscreen and resize issues on macOS. (#47) * qt: Fix bugged macOS full screen transition. * renderer/vulkan: Fix swapchain recreation destroying in-use semaphore. * renderer/vulkan: Make gl_Position invariant. (#48) This fixes an issue with black artifacts in Pokemon games on Apple GPUs. If the vertex calculations differ slightly between render passes, it can cause parts of model faces to fail depth test. * vk_renderpass_cache: Bump pixel format count * android: Custom driver code * vk_instance: Set moltenvk configuration * rasterizer_cache: Proper surface unregister * citra_qt: Fix invalid characters * vk_rasterizer: Correct special unbind * android: Allow async presentation toggle * vk_graphics_pipeline: Fix async shader compilation * We were actually waiting for the pipelines regardless of the setting, oops * vk_rasterizer: More robust attribute loading * android: Move PollEvents to OpenGL window * Vulkan does not need this and it causes problems * vk_instance: Enable robust buffer access * Improves stability on mali devices * vk_renderpass_cache: Bring back renderpass flushing * externals: Update vulkan-headers * gl_rasterizer: Separable shaders for everyone * vk_blit_helper: Corect depth to color convertion * renderer_vulkan: Implement reinterpretation with copy * Allows reinterpreteration with simply copy on AMD * vk_graphics_pipeline: Only fast compile if no shaders are pending * With this shaders weren't being compiled in parallel * vk_swapchain: Ensure vsync doesn't lock framerate * vk_present_window: Match guest swapchain size to vulkan image count * Less latency and fixes crashes that were caused by images being deleted before free * vk_instance: Blacklist VK_EXT_pipeline_creation_cache_control with nvidia gpus * Resolves crashes when async shader compilation is enabled * vk_rasterizer: Bump async threshold to 6 * Many games have fullscreen quads with 6 vertices. Fixes pokemon textures missing with async shaders * android: More robust surface recreation * renderer_vulkan: Fix dynamic state being lost * vk_pipeline_cache: Skip cache save when no pipeline cache exists * This is the cache when loading a save state * sdl: Fix surface initialization on macOS. (#49) * sdl: Fix surface initialization on macOS. * sdl: Fix render window events not being handled under Vulkan. * renderer/vulkan: Fix binding/unbinding of shadow rendering buffer. * vk_stream_buffer: Respect non coherent access alignment * Required by nvidia GPUs on MacOS * renderer/vulkan: Support VK_EXT_fragment_shader_interlock for shadow rendering. (#51) * renderer_vulkan: Port some recent shader fixes * vk_pipeline_cache: Improve shadow detection * vk_swapchain: Add missing check * renderer_vulkan: Fix hybrid screen * Revert "gl_rasterizer: Separable shaders for everyone" Causes crashes on mali GPUs, will need separate PR This reverts commit d22d556d30ff641b62dfece85738c96b7fbf7061. * renderer_vulkan: Fix flipped screenshot --------- Co-authored-by: Steveice10 <1269164+Steveice10@users.noreply.github.com>
2023-09-13 01:28:50 +03:00
physical_devices = GetVulkanPhysicalDevices();
if (physical_devices.empty()) {
QMessageBox::warning(this, tr("No Suitable Vulkan Devices Detected"),
tr("Vulkan initialization failed during boot.<br/>"
"Your GPU may not support Vulkan 1.1, or you do not "
"have the latest graphics driver."));
}
#endif
Add vulkan backend (#6512) * code: Prepare frontend for vulkan support * citra_qt: Add vulkan options to the GUI * vk_instance: Collect tooling info * renderer_vulkan: Add vulkan backend * qt: Fix fullscreen and resize issues on macOS. (#47) * qt: Fix bugged macOS full screen transition. * renderer/vulkan: Fix swapchain recreation destroying in-use semaphore. * renderer/vulkan: Make gl_Position invariant. (#48) This fixes an issue with black artifacts in Pokemon games on Apple GPUs. If the vertex calculations differ slightly between render passes, it can cause parts of model faces to fail depth test. * vk_renderpass_cache: Bump pixel format count * android: Custom driver code * vk_instance: Set moltenvk configuration * rasterizer_cache: Proper surface unregister * citra_qt: Fix invalid characters * vk_rasterizer: Correct special unbind * android: Allow async presentation toggle * vk_graphics_pipeline: Fix async shader compilation * We were actually waiting for the pipelines regardless of the setting, oops * vk_rasterizer: More robust attribute loading * android: Move PollEvents to OpenGL window * Vulkan does not need this and it causes problems * vk_instance: Enable robust buffer access * Improves stability on mali devices * vk_renderpass_cache: Bring back renderpass flushing * externals: Update vulkan-headers * gl_rasterizer: Separable shaders for everyone * vk_blit_helper: Corect depth to color convertion * renderer_vulkan: Implement reinterpretation with copy * Allows reinterpreteration with simply copy on AMD * vk_graphics_pipeline: Only fast compile if no shaders are pending * With this shaders weren't being compiled in parallel * vk_swapchain: Ensure vsync doesn't lock framerate * vk_present_window: Match guest swapchain size to vulkan image count * Less latency and fixes crashes that were caused by images being deleted before free * vk_instance: Blacklist VK_EXT_pipeline_creation_cache_control with nvidia gpus * Resolves crashes when async shader compilation is enabled * vk_rasterizer: Bump async threshold to 6 * Many games have fullscreen quads with 6 vertices. Fixes pokemon textures missing with async shaders * android: More robust surface recreation * renderer_vulkan: Fix dynamic state being lost * vk_pipeline_cache: Skip cache save when no pipeline cache exists * This is the cache when loading a save state * sdl: Fix surface initialization on macOS. (#49) * sdl: Fix surface initialization on macOS. * sdl: Fix render window events not being handled under Vulkan. * renderer/vulkan: Fix binding/unbinding of shadow rendering buffer. * vk_stream_buffer: Respect non coherent access alignment * Required by nvidia GPUs on MacOS * renderer/vulkan: Support VK_EXT_fragment_shader_interlock for shadow rendering. (#51) * renderer_vulkan: Port some recent shader fixes * vk_pipeline_cache: Improve shadow detection * vk_swapchain: Add missing check * renderer_vulkan: Fix hybrid screen * Revert "gl_rasterizer: Separable shaders for everyone" Causes crashes on mali GPUs, will need separate PR This reverts commit d22d556d30ff641b62dfece85738c96b7fbf7061. * renderer_vulkan: Fix flipped screenshot --------- Co-authored-by: Steveice10 <1269164+Steveice10@users.noreply.github.com>
2023-09-13 01:28:50 +03:00
if (!game_path.isEmpty()) {
BootGame(game_path);
}
}
GMainWindow::~GMainWindow() {
// Will get automatically deleted otherwise
if (!render_window->parent()) {
delete render_window;
}
Pica::g_debug_context.reset();
2018-01-19 13:42:21 +00:00
Network::Shutdown();
}
void GMainWindow::InitializeWidgets() {
2023-06-24 01:59:18 +03:00
render_window = new GRenderWindow(this, emu_thread.get(), system, false);
secondary_window = new GRenderWindow(this, emu_thread.get(), system, true);
2014-04-21 23:15:17 -04:00
render_window->hide();
secondary_window->hide();
secondary_window->setParent(nullptr);
2014-03-31 22:26:50 -04:00
action_secondary_fullscreen = new QAction(secondary_window);
action_secondary_toggle_screen = new QAction(secondary_window);
action_secondary_swap_screen = new QAction(secondary_window);
action_secondary_rotate_screen = new QAction(secondary_window);
game_list = new GameList(*play_time_manager, this);
ui->horizontalLayout->addWidget(game_list);
2017-02-18 12:09:14 -08:00
game_list_placeholder = new GameListPlaceholder(this);
ui->horizontalLayout->addWidget(game_list_placeholder);
game_list_placeholder->setVisible(false);
loading_screen = new LoadingScreen(this);
loading_screen->hide();
ui->horizontalLayout->addWidget(loading_screen);
connect(loading_screen, &LoadingScreen::Hidden, this, [&] {
loading_screen->Clear();
if (emulation_running) {
render_window->show();
render_window->setFocus();
render_window->activateWindow();
}
});
InputCommon::Init();
multiplayer_state = new MultiplayerState(system, this, game_list->GetModel(),
ui->action_Leave_Room, ui->action_Show_Room);
multiplayer_state->setVisible(false);
UpdateBootHomeMenuState();
2017-02-18 12:09:14 -08:00
// Create status bar
message_label = new QLabel();
// Configured separately for left alignment
message_label->setFrameStyle(QFrame::NoFrame);
message_label->setContentsMargins(4, 0, 4, 0);
message_label->setAlignment(Qt::AlignLeft);
statusBar()->addPermanentWidget(message_label, 1);
2017-12-02 15:35:20 -06:00
progress_bar = new QProgressBar();
progress_bar->hide();
statusBar()->addPermanentWidget(progress_bar);
artic_traffic_label = new QLabel();
artic_traffic_label->setToolTip(
tr("Current Artic Base traffic speed. Higher values indicate bigger transfer loads."));
2017-02-18 12:09:14 -08:00
emu_speed_label = new QLabel();
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
"indicate emulation is running faster or slower than a 3DS."));
2017-02-18 12:09:14 -08:00
game_fps_label = new QLabel();
game_fps_label->setToolTip(tr("How many frames per second the app is currently displaying. "
"This will vary from app to app and scene to scene."));
2017-02-18 12:09:14 -08:00
emu_frametime_label = new QLabel();
emu_frametime_label->setToolTip(
tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For "
"full-speed emulation this should be at most 16.67 ms."));
2017-02-18 12:09:14 -08:00
for (auto& label :
{artic_traffic_label, emu_speed_label, game_fps_label, emu_frametime_label}) {
2017-02-18 12:09:14 -08:00
label->setVisible(false);
2017-02-20 16:53:40 -08:00
label->setFrameStyle(QFrame::NoFrame);
label->setContentsMargins(4, 0, 4, 0);
statusBar()->addPermanentWidget(label);
2017-02-18 12:09:14 -08:00
}
// Setup Graphics API button
graphics_api_button = new QPushButton();
graphics_api_button->setObjectName(QStringLiteral("GraphicsAPIStatusBarButton"));
graphics_api_button->setFocusPolicy(Qt::NoFocus);
UpdateAPIIndicator();
connect(graphics_api_button, &QPushButton::clicked, this, [this] { UpdateAPIIndicator(true); });
statusBar()->insertPermanentWidget(0, graphics_api_button);
volume_popup = new QWidget(this);
volume_popup->setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Popup);
volume_popup->setLayout(new QVBoxLayout());
volume_popup->setMinimumWidth(200);
volume_slider = new QSlider(Qt::Horizontal);
volume_slider->setObjectName(QStringLiteral("volume_slider"));
volume_slider->setMaximum(100);
volume_slider->setPageStep(5);
connect(volume_slider, &QSlider::valueChanged, this, [this](int percentage) {
Settings::values.audio_muted = false;
const auto value = static_cast<float>(percentage) / volume_slider->maximum();
Settings::values.volume.SetValue(value);
UpdateVolumeUI();
});
volume_popup->layout()->addWidget(volume_slider);
volume_button = new QPushButton();
volume_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
volume_button->setFocusPolicy(Qt::NoFocus);
volume_button->setCheckable(true);
UpdateVolumeUI();
connect(volume_button, &QPushButton::clicked, this, [&] {
UpdateVolumeUI();
volume_popup->setVisible(!volume_popup->isVisible());
QRect rect = volume_button->geometry();
QPoint bottomLeft = statusBar()->mapToGlobal(rect.topLeft());
bottomLeft.setY(bottomLeft.y() - volume_popup->geometry().height());
volume_popup->setGeometry(QRect(bottomLeft, QSize(rect.width(), rect.height())));
});
statusBar()->insertPermanentWidget(1, volume_button);
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText());
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon());
2017-02-18 12:09:14 -08:00
statusBar()->setVisible(true);
// Removes an ugly inner border from the status bar widgets under Linux
setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
QActionGroup* actionGroup_ScreenLayouts = new QActionGroup(this);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Default);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Single_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Large_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Side_by_Side);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Separate_Windows);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Hybrid_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Custom_Layout);
Added "Small Screen Position" feature * error checking for layout value from older config * rename enum and update aspect ratio code * rewrite LargeFrameLayout to support multiple positions * add settings for smallscreenposition, fix minsize function * fixed framebuffer from res scale (screenshots) * add desktop UI for small screen position * small screen position submenu on desktop * fix int-float conversion warning * rename Above and Below to hopefully fix linux issue * Add Small Screen Position Setting to android settings menu * fix sliders to work with floats, mostly * fix android slider textinput ui * change None enums in settings and cam_params * Apply clang-format-18 * SettingsAdapter.kt: Make more null pointer exception resistant * Updated license headers * Code formatting nitpicks * fix bug in main.ui that was hiding menu * replace default layout with a special call to LargeFrame (like SideBySide does) * fix bug when "large screen" is actually narrower * edit documentation for LargeScreenLayout * update PortraitTopFullFrameLayout to use LargeFrameLayout * fix unary minus on unsigned int bug * Applied formatting correction * Added `const`s where appropriate * android: Add mention of the bottom-right small screen position being the default * review fixes + more constants * refactor all Upright calculations to a reverseLayout method, simplifying code and reducing bugs * Removed stray extra newline * SettingsAdapter.kt: Fixed some strange indentation * Removed unnecessary `if` in favour of direct value usage --------- Co-authored-by: Reg Tiangha <rtiangha@users.noreply.github.com> Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2024-10-29 14:22:51 -07:00
QActionGroup* actionGroup_SmallPositions = new QActionGroup(this);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_TopRight);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_MiddleRight);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_BottomRight);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_TopLeft);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_MiddleLeft);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_BottomLeft);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_Above);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_Below);
}
void GMainWindow::InitializeDebugWidgets() {
connect(ui->action_Create_Pica_Surface_Viewer, &QAction::triggered, this,
&GMainWindow::OnCreateGraphicsSurfaceViewer);
QMenu* debug_menu = ui->menu_View_Debugging;
2015-08-31 21:35:33 -07:00
#if MICROPROFILE_ENABLED
microProfileDialog = new MicroProfileDialog(this);
microProfileDialog->hide();
debug_menu->addAction(microProfileDialog->toggleViewAction());
#endif
registersWidget = new RegistersWidget(system, this);
2014-04-18 18:30:53 -04:00
addDockWidget(Qt::RightDockWidgetArea, registersWidget);
registersWidget->hide();
debug_menu->addAction(registersWidget->toggleViewAction());
connect(this, &GMainWindow::EmulationStarting, registersWidget,
&RegistersWidget::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, registersWidget,
&RegistersWidget::OnEmulationStopping);
2014-04-18 18:30:53 -04:00
graphicsWidget = new GPUCommandStreamWidget(system, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsWidget);
graphicsWidget->hide();
debug_menu->addAction(graphicsWidget->toggleViewAction());
graphicsCommandsWidget = new GPUCommandListWidget(system, this);
2014-05-18 17:52:22 +02:00
addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget);
graphicsCommandsWidget->hide();
debug_menu->addAction(graphicsCommandsWidget->toggleViewAction());
2014-05-18 17:52:22 +02:00
graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
graphicsBreakpointsWidget->hide();
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
graphicsVertexShaderWidget =
new GraphicsVertexShaderWidget(system, Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget);
graphicsVertexShaderWidget->hide();
debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction());
graphicsTracingWidget = new GraphicsTracingWidget(system, Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget);
graphicsTracingWidget->hide();
debug_menu->addAction(graphicsTracingWidget->toggleViewAction());
connect(this, &GMainWindow::EmulationStarting, graphicsTracingWidget,
&GraphicsTracingWidget::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, graphicsTracingWidget,
&GraphicsTracingWidget::OnEmulationStopping);
waitTreeWidget = new WaitTreeWidget(system, this);
2016-04-08 19:28:54 +03:00
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
waitTreeWidget->hide();
debug_menu->addAction(waitTreeWidget->toggleViewAction());
connect(this, &GMainWindow::EmulationStarting, waitTreeWidget,
&WaitTreeWidget::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, waitTreeWidget,
&WaitTreeWidget::OnEmulationStopping);
lleServiceModulesWidget = new LLEServiceModulesWidget(this);
addDockWidget(Qt::RightDockWidgetArea, lleServiceModulesWidget);
lleServiceModulesWidget->hide();
debug_menu->addAction(lleServiceModulesWidget->toggleViewAction());
connect(this, &GMainWindow::EmulationStarting,
[this] { lleServiceModulesWidget->setDisabled(true); });
connect(this, &GMainWindow::EmulationStopping, waitTreeWidget,
[this] { lleServiceModulesWidget->setDisabled(false); });
ipcRecorderWidget = new IPCRecorderWidget(system, this);
addDockWidget(Qt::RightDockWidgetArea, ipcRecorderWidget);
ipcRecorderWidget->hide();
debug_menu->addAction(ipcRecorderWidget->toggleViewAction());
connect(this, &GMainWindow::EmulationStarting, ipcRecorderWidget,
&IPCRecorderWidget::OnEmulationStarting);
}
void GMainWindow::InitializeRecentFileMenuActions() {
for (int i = 0; i < max_recent_files_item; ++i) {
actions_recent_files[i] = new QAction(this);
actions_recent_files[i]->setVisible(false);
connect(actions_recent_files[i], &QAction::triggered, this, &GMainWindow::OnMenuRecentFile);
ui->menu_recent_files->addAction(actions_recent_files[i]);
}
ui->menu_recent_files->addSeparator();
QAction* action_clear_recent_files = new QAction(this);
action_clear_recent_files->setText(tr("Clear Recent Files"));
connect(action_clear_recent_files, &QAction::triggered, this, [this] {
UISettings::values.recent_files.clear();
UpdateRecentFiles();
});
ui->menu_recent_files->addAction(action_clear_recent_files);
UpdateRecentFiles();
}
void GMainWindow::InitializeSaveStateMenuActions() {
for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) {
actions_load_state[i] = new QAction(this);
actions_load_state[i]->setData(i + 1);
connect(actions_load_state[i], &QAction::triggered, this, &GMainWindow::OnLoadState);
ui->menu_Load_State->addAction(actions_load_state[i]);
actions_save_state[i] = new QAction(this);
actions_save_state[i]->setData(i + 1);
connect(actions_save_state[i], &QAction::triggered, this, &GMainWindow::OnSaveState);
ui->menu_Save_State->addAction(actions_save_state[i]);
}
connect(ui->action_Load_from_Newest_Slot, &QAction::triggered, this, [this] {
UpdateSaveStates();
if (newest_slot != 0) {
actions_load_state[newest_slot - 1]->trigger();
}
});
connect(ui->action_Save_to_Oldest_Slot, &QAction::triggered, this, [this] {
UpdateSaveStates();
actions_save_state[oldest_slot - 1]->trigger();
});
connect(ui->menu_Load_State->menuAction(), &QAction::hovered, this,
&GMainWindow::UpdateSaveStates);
connect(ui->menu_Save_State->menuAction(), &QAction::hovered, this,
&GMainWindow::UpdateSaveStates);
UpdateSaveStates();
}
void GMainWindow::InitializeHotkeys() {
hotkey_registry.LoadHotkeys();
const QString main_window = QStringLiteral("Main Window");
const QString fullscreen = QStringLiteral("Fullscreen");
// QAction Hotkeys
const auto link_action_shortcut = [&](QAction* action, const QString& action_name,
const bool primary_only = false) {
static const QString main_window = QStringLiteral("Main Window");
action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name));
action->setAutoRepeat(false);
this->addAction(action);
if (!primary_only)
secondary_window->addAction(action);
};
link_action_shortcut(ui->action_Load_File, QStringLiteral("Load File"));
link_action_shortcut(ui->action_Load_Amiibo, QStringLiteral("Load Amiibo"));
link_action_shortcut(ui->action_Remove_Amiibo, QStringLiteral("Remove Amiibo"));
link_action_shortcut(ui->action_Exit, QStringLiteral("Exit Azahar"));
link_action_shortcut(ui->action_Restart, QStringLiteral("Restart Emulation"));
link_action_shortcut(ui->action_Pause, QStringLiteral("Continue/Pause Emulation"));
link_action_shortcut(ui->action_Stop, QStringLiteral("Stop Emulation"));
link_action_shortcut(ui->action_Show_Filter_Bar, QStringLiteral("Toggle Filter Bar"));
link_action_shortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
link_action_shortcut(ui->action_Fullscreen, fullscreen, true);
link_action_shortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
link_action_shortcut(ui->action_Screen_Layout_Swap_Screens, QStringLiteral("Swap Screens"));
link_action_shortcut(ui->action_Screen_Layout_Upright_Screens,
QStringLiteral("Rotate Screens Upright"));
link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame"));
link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot"));
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
link_action_shortcut(ui->action_View_Lobby, QStringLiteral("Multiplayer Browse Public Rooms"));
link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
link_action_shortcut(ui->action_Connect_To_Room,
QStringLiteral("Multiplayer Direct Connect to Room"));
link_action_shortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room"));
link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
// QShortcut Hotkeys
const auto connect_shortcut = [&](const QString& action_name, const auto& function) {
const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this);
2024-11-10 21:02:54 +00:00
const auto* secondary_hotkey =
hotkey_registry.GetHotkey(main_window, action_name, secondary_window);
connect(hotkey, &QShortcut::activated, this, function);
connect(secondary_hotkey, &QShortcut::activated, this, function);
};
connect_shortcut(QStringLiteral("Toggle Screen Layout"), &GMainWindow::ToggleScreenLayout);
connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] {
if (emulation_running) {
ui->action_Fullscreen->setChecked(false);
ToggleFullscreen();
}
});
connect_shortcut(QStringLiteral("Toggle Per-Application Speed"), [&] {
Settings::values.frame_limit.SetGlobal(!Settings::values.frame_limit.UsingGlobal());
UpdateStatusBar();
});
connect_shortcut(QStringLiteral("Toggle Texture Dumping"),
[&] { Settings::values.dump_textures = !Settings::values.dump_textures; });
Custom textures rewrite (#6452) * common: Add thread pool from yuzu * Is really useful for asynchronous operations like shader compilation and custom textures, will be used in following PRs * core: Improve ImageInterface * Provide a default implementation so frontends don't have to duplicate code registering the lodepng version * Add a dds version too which we will use in the next commit * rasterizer_cache: Rewrite custom textures * There's just too much to talk about here, look at the PR description for more details * rasterizer_cache: Implement basic pack configuration file * custom_tex_manager: Flip dumped textures * custom_tex_manager: Optimize custom texture hashing * If no convertions are needed then we can hash the decoded data directly removing the needed for duplicate decode * custom_tex_manager: Implement asynchronous texture loading * The file loading and decoding is offloaded into worker threads, while the upload itself still occurs in the main thread to avoid having to manage shared contexts * Address review comments * custom_tex_manager: Introduce custom material support * video_core: Move custom textures to separate directory * Also split the files to make the code cleaner * gl_texture_runtime: Generate mipmaps for material * custom_tex_manager: Prevent memory overflow when preloading * externals: Add dds-ktx as submodule * string_util: Return vector from SplitString * No code benefits from passing it as an argument * custom_textures: Use json config file * gl_rasterizer: Only bind material for unit 0 * Address review comments
2023-04-27 07:38:28 +03:00
connect_shortcut(QStringLiteral("Toggle Custom Textures"),
[&] { Settings::values.custom_textures = !Settings::values.custom_textures; });
// We use "static" here in order to avoid capturing by lambda due to a MSVC bug, which makes
// the variable hold a garbage value after this function exits
static constexpr u16 SPEED_LIMIT_STEP = 5;
connect_shortcut(QStringLiteral("Increase Speed Limit"), [&] {
if (Settings::values.frame_limit.GetValue() == 0) {
return;
}
if (Settings::values.frame_limit.GetValue() < 995 - SPEED_LIMIT_STEP) {
Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() +
SPEED_LIMIT_STEP);
} else {
Settings::values.frame_limit = 0;
}
UpdateStatusBar();
});
connect_shortcut(QStringLiteral("Decrease Speed Limit"), [&] {
if (Settings::values.frame_limit.GetValue() == 0) {
Settings::values.frame_limit = 995;
} else if (Settings::values.frame_limit.GetValue() > SPEED_LIMIT_STEP) {
Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() -
SPEED_LIMIT_STEP);
UpdateStatusBar();
}
UpdateStatusBar();
});
connect_shortcut(QStringLiteral("Audio Mute/Unmute"), &GMainWindow::OnMute);
connect_shortcut(QStringLiteral("Audio Volume Down"), &GMainWindow::OnDecreaseVolume);
connect_shortcut(QStringLiteral("Audio Volume Up"), &GMainWindow::OnIncreaseVolume);
// We use "static" here in order to avoid capturing by lambda due to a MSVC bug, which makes the
// variable hold a garbage value after this function exits
static constexpr u16 FACTOR_3D_STEP = 5;
connect_shortcut(QStringLiteral("Decrease 3D Factor"), [this] {
const auto factor_3d = Settings::values.factor_3d.GetValue();
if (factor_3d > 0) {
if (factor_3d % FACTOR_3D_STEP != 0) {
Settings::values.factor_3d = factor_3d - (factor_3d % FACTOR_3D_STEP);
} else {
Settings::values.factor_3d = factor_3d - FACTOR_3D_STEP;
}
UpdateStatusBar();
}
});
connect_shortcut(QStringLiteral("Increase 3D Factor"), [this] {
const auto factor_3d = Settings::values.factor_3d.GetValue();
if (factor_3d < 100) {
if (factor_3d % FACTOR_3D_STEP != 0) {
Settings::values.factor_3d =
factor_3d + FACTOR_3D_STEP - (factor_3d % FACTOR_3D_STEP);
} else {
Settings::values.factor_3d = factor_3d + FACTOR_3D_STEP;
}
UpdateStatusBar();
}
});
// Secondary Window QAction Hotkeys
const auto add_secondary_window_hotkey = [this](QAction* action, QKeySequence hotkey,
const char* slot) {
// This action will fire specifically when secondary_window is in focus
action->setShortcut(hotkey);
disconnect(action, SIGNAL(triggered()), this, slot);
connect(action, SIGNAL(triggered()), this, slot);
secondary_window->addAction(action);
};
// Use the same fullscreen hotkey as the main window
const auto fullscreen_hotkey = hotkey_registry.GetKeySequence(main_window, fullscreen);
add_secondary_window_hotkey(action_secondary_fullscreen, fullscreen_hotkey,
SLOT(ToggleSecondaryFullscreen()));
}
2014-03-31 22:26:50 -04:00
void GMainWindow::SetDefaultUIGeometry() {
2014-03-31 22:26:50 -04:00
// geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
2023-05-05 03:10:34 -07:00
const QRect screenRect = screen()->geometry();
const int w = screenRect.width() * 2 / 3;
const int h = screenRect.height() / 2;
const int x = (screenRect.x() + screenRect.width()) / 2 - w / 2;
const int y = (screenRect.y() + screenRect.height()) / 2 - h * 55 / 100;
2014-03-31 22:26:50 -04:00
setGeometry(x, y, w, h);
}
2014-03-31 22:26:50 -04:00
void GMainWindow::RestoreUIState() {
restoreGeometry(UISettings::values.geometry);
restoreState(UISettings::values.state);
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
#if MICROPROFILE_ENABLED
microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry);
microProfileDialog->setVisible(UISettings::values.microprofile_visible.GetValue());
#endif
game_list->LoadInterfaceLayout();
2015-09-02 08:56:38 -04:00
ui->action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode.GetValue());
2014-04-21 23:15:17 -04:00
ToggleWindowMode();
2014-03-31 22:26:50 -04:00
ui->action_Fullscreen->setChecked(UISettings::values.fullscreen.GetValue());
SyncMenuUISettings();
2017-10-11 20:21:09 +02:00
ui->action_Display_Dock_Widget_Headers->setChecked(
UISettings::values.display_titlebar.GetValue());
OnDisplayTitleBars(ui->action_Display_Dock_Widget_Headers->isChecked());
2017-02-18 12:09:14 -08:00
ui->action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar.GetValue());
game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
ui->action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar.GetValue());
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
}
2019-09-14 18:14:23 -03:00
void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
if (state != Qt::ApplicationHidden && state != Qt::ApplicationInactive &&
state != Qt::ApplicationActive) {
LOG_DEBUG(Frontend, "ApplicationState unusual flag: {} ", state);
}
if (!emulation_running) {
return;
}
if (UISettings::values.pause_when_in_background) {
if (emu_thread->IsRunning() &&
(state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) {
auto_paused = true;
OnPauseGame();
} else if (!emu_thread->IsRunning() && auto_paused && state == Qt::ApplicationActive) {
auto_paused = false;
OnStartGame();
}
}
if (UISettings::values.mute_when_in_background) {
if (!Settings::values.audio_muted &&
(state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) {
Settings::values.audio_muted = true;
auto_muted = true;
} else if (auto_muted && state == Qt::ApplicationActive) {
Settings::values.audio_muted = false;
auto_muted = false;
}
UpdateVolumeUI();
}
}
bool GApplicationEventFilter::eventFilter(QObject* object, QEvent* event) {
if (event->type() == QEvent::FileOpen) {
emit FileOpen(static_cast<QFileOpenEvent*>(event));
return true;
}
return false;
}
void GMainWindow::ConnectAppEvents() {
const auto filter = new GApplicationEventFilter();
QGuiApplication::instance()->installEventFilter(filter);
connect(filter, &GApplicationEventFilter::FileOpen, this, &GMainWindow::OnFileOpen);
}
void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory);
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
connect(game_list, &GameList::RemovePlayTimeRequested, this,
&GMainWindow::OnGameListRemovePlayTimeData);
connect(game_list, &GameList::CreateShortcut, this, &GMainWindow::OnGameListCreateShortcut);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory);
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
&GMainWindow::OnGameListAddDirectory);
connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
connect(game_list, &GameList::PopulatingCompleted, this,
[this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
2014-03-31 22:26:50 -04:00
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
&GMainWindow::OnGameListOpenPerGameProperties);
connect(this, &GMainWindow::EmulationStarting, render_window,
&GRenderWindow::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, render_window,
&GRenderWindow::OnEmulationStopping);
connect(this, &GMainWindow::EmulationStarting, secondary_window,
&GRenderWindow::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, secondary_window,
&GRenderWindow::OnEmulationStopping);
connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
2017-12-02 15:35:20 -06:00
connect(this, &GMainWindow::UpdateProgress, this, &GMainWindow::OnUpdateProgress);
2018-02-13 18:16:26 -06:00
connect(this, &GMainWindow::CIAInstallReport, this, &GMainWindow::OnCIAInstallReport);
connect(this, &GMainWindow::CIAInstallFinished, this, &GMainWindow::OnCIAInstallFinished);
connect(this, &GMainWindow::UpdateThemedIcons, multiplayer_state,
&MultiplayerState::UpdateThemedIcons);
2014-03-31 22:26:50 -04:00
}
void GMainWindow::ConnectMenuEvents() {
const auto connect_menu = [&](QAction* action, const auto& event_fn) {
connect(action, &QAction::triggered, this, event_fn);
// Add actions to this window so that hiding menus in fullscreen won't disable them
addAction(action);
// Add actions to the render window so that they work outside of single window mode
render_window->addAction(action);
};
// File
connect_menu(ui->action_Load_File, &GMainWindow::OnMenuLoadFile);
connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA);
connect_menu(ui->action_Connect_Artic, &GMainWindow::OnMenuConnectArticBase);
for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) {
connect_menu(ui->menu_Boot_Home_Menu->actions().at(region),
[this, region] { OnMenuBootHomeMenu(region); });
}
connect_menu(ui->action_Exit, &QMainWindow::close);
connect_menu(ui->action_Load_Amiibo, &GMainWindow::OnLoadAmiibo);
connect_menu(ui->action_Remove_Amiibo, &GMainWindow::OnRemoveAmiibo);
// Emulation
connect_menu(ui->action_Pause, &GMainWindow::OnPauseContinueGame);
connect_menu(ui->action_Stop, &GMainWindow::OnStopGame);
connect_menu(ui->action_Restart, [this] { BootGame(QString(game_path)); });
connect_menu(ui->action_Report_Compatibility, &GMainWindow::OnMenuReportCompatibility);
connect_menu(ui->action_Configure, &GMainWindow::OnConfigure);
connect_menu(ui->action_Configure_Current_Game, &GMainWindow::OnConfigurePerGame);
// View
connect_menu(ui->action_Single_Window_Mode, &GMainWindow::ToggleWindowMode);
connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars);
connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar);
connect(ui->action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
2018-01-19 13:42:21 +00:00
// Multiplayer
connect(ui->action_View_Lobby, &QAction::triggered, multiplayer_state,
&MultiplayerState::OnViewLobby);
connect(ui->action_Start_Room, &QAction::triggered, multiplayer_state,
&MultiplayerState::OnCreateRoom);
connect(ui->action_Leave_Room, &QAction::triggered, multiplayer_state,
&MultiplayerState::OnCloseRoom);
connect(ui->action_Connect_To_Room, &QAction::triggered, multiplayer_state,
&MultiplayerState::OnDirectConnectToRoom);
connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state,
&MultiplayerState::OnOpenNetworkRoom);
2018-01-19 13:42:21 +00:00
connect_menu(ui->action_Fullscreen, &GMainWindow::ToggleFullscreen);
connect_menu(ui->action_Screen_Layout_Default, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Single_Screen, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Large_Screen, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Hybrid_Screen, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Side_by_Side, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Separate_Windows, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Custom_Layout, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Swap_Screens, &GMainWindow::OnSwapScreens);
connect_menu(ui->action_Screen_Layout_Upright_Screens, &GMainWindow::OnRotateScreens);
Added "Small Screen Position" feature * error checking for layout value from older config * rename enum and update aspect ratio code * rewrite LargeFrameLayout to support multiple positions * add settings for smallscreenposition, fix minsize function * fixed framebuffer from res scale (screenshots) * add desktop UI for small screen position * small screen position submenu on desktop * fix int-float conversion warning * rename Above and Below to hopefully fix linux issue * Add Small Screen Position Setting to android settings menu * fix sliders to work with floats, mostly * fix android slider textinput ui * change None enums in settings and cam_params * Apply clang-format-18 * SettingsAdapter.kt: Make more null pointer exception resistant * Updated license headers * Code formatting nitpicks * fix bug in main.ui that was hiding menu * replace default layout with a special call to LargeFrame (like SideBySide does) * fix bug when "large screen" is actually narrower * edit documentation for LargeScreenLayout * update PortraitTopFullFrameLayout to use LargeFrameLayout * fix unary minus on unsigned int bug * Applied formatting correction * Added `const`s where appropriate * android: Add mention of the bottom-right small screen position being the default * review fixes + more constants * refactor all Upright calculations to a reverseLayout method, simplifying code and reducing bugs * Removed stray extra newline * SettingsAdapter.kt: Fixed some strange indentation * Removed unnecessary `if` in favour of direct value usage --------- Co-authored-by: Reg Tiangha <rtiangha@users.noreply.github.com> Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2024-10-29 14:22:51 -07:00
connect_menu(ui->action_Small_Screen_TopRight, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_MiddleRight, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_BottomRight, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_TopLeft, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_MiddleLeft, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_BottomLeft, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_Above, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_Below, &GMainWindow::ChangeSmallScreenPosition);
2018-07-08 13:55:32 +08:00
// Movie
connect_menu(ui->action_Record_Movie, &GMainWindow::OnRecordMovie);
connect_menu(ui->action_Play_Movie, &GMainWindow::OnPlayMovie);
connect_menu(ui->action_Close_Movie, &GMainWindow::OnCloseMovie);
connect_menu(ui->action_Save_Movie, &GMainWindow::OnSaveMovie);
connect_menu(ui->action_Movie_Read_Only_Mode,
[this](bool checked) { movie.SetReadOnly(checked); });
connect_menu(ui->action_Advance_Frame, [this] {
if (emulation_running && system.frame_limiter.IsFrameAdvancing()) {
system.frame_limiter.AdvanceFrame();
}
});
connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
connect_menu(ui->action_Dump_Video, &GMainWindow::OnDumpVideo);
// Help
connect_menu(ui->action_Open_Citra_Folder, &GMainWindow::OnOpenCitraFolder);
connect_menu(ui->action_Open_Log_Folder, []() {
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
connect_menu(ui->action_FAQ, []() {
QDesktopServices::openUrl(QUrl(QStringLiteral("https://azahar-emu.org/pages/faq/")));
});
connect_menu(ui->action_About, &GMainWindow::OnMenuAboutCitra);
}
void GMainWindow::UpdateMenuState() {
const bool is_paused =
!emu_thread || !emu_thread->IsRunning() || system.frame_limiter.IsFrameAdvancing();
const std::array running_actions{
ui->action_Stop,
ui->action_Restart,
ui->action_Configure_Current_Game,
ui->action_Report_Compatibility,
ui->action_Load_Amiibo,
ui->action_Remove_Amiibo,
ui->action_Pause,
ui->action_Advance_Frame,
};
for (QAction* action : running_actions) {
action->setEnabled(emulation_running);
}
ui->action_Capture_Screenshot->setEnabled(emulation_running);
ui->action_Advance_Frame->setEnabled(emulation_running && is_paused);
if (emulation_running && is_paused) {
ui->action_Pause->setText(tr("&Continue"));
} else {
ui->action_Pause->setText(tr("&Pause"));
}
}
void GMainWindow::OnDisplayTitleBars(bool show) {
QList<QDockWidget*> widgets = findChildren<QDockWidget*>();
if (show) {
for (QDockWidget* widget : widgets) {
QWidget* old = widget->titleBarWidget();
widget->setTitleBarWidget(nullptr);
if (old) {
delete old;
}
}
} else {
for (QDockWidget* widget : widgets) {
QWidget* old = widget->titleBarWidget();
widget->setTitleBarWidget(new QWidget());
if (old) {
delete old;
}
}
}
}
void GMainWindow::ShowMigrationCancelledMessage() {
QMessageBox::information(
this, tr("Migration"),
tr("You can manually re-trigger this prompt by deleting the "
"new user data directory:\n"
"%1")
.arg(QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))),
QMessageBox::Ok);
}
void GMainWindow::ShowMigrationPrompt() {
namespace fs = std::filesystem;
const QString migration_prompt_message =
tr("Would you like to migrate your data for use in Azahar?\n"
"(This may take a while; The old data will not be deleted)");
const bool citra_dir_found =
fs::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::LegacyCitraUserDir));
const bool lime3ds_dir_found =
fs::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::LegacyLime3DSUserDir));
if (citra_dir_found && lime3ds_dir_found) {
QMessageBox migration_prompt;
migration_prompt.setWindowTitle(tr("Migration"));
migration_prompt.setText(
tr("Azahar has detected emulator data for Citra and Lime3DS.\n\n") +
migration_prompt_message);
migration_prompt.setIcon(QMessageBox::Information);
const QAbstractButton* lime3dsButton =
migration_prompt.addButton(tr("Migrate from Lime3DS"), QMessageBox::YesRole);
const QAbstractButton* citraButton =
migration_prompt.addButton(tr("Migrate from Citra"), QMessageBox::YesRole);
migration_prompt.addButton(QMessageBox::No);
migration_prompt.exec();
if (citraButton == migration_prompt.clickedButton()) {
MigrateUserData(LegacyEmu::Citra);
}
if (lime3dsButton == migration_prompt.clickedButton()) {
MigrateUserData(LegacyEmu::Lime3DS);
}
return;
}
else if (citra_dir_found) {
if (QMessageBox::information(
this, tr("Migration"),
tr("Azahar has detected emulator data for Citra.\n\n") + migration_prompt_message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
MigrateUserData(LegacyEmu::Citra);
return;
}
}
else if (lime3ds_dir_found) {
if (QMessageBox::information(
this, tr("Migration"),
tr("Azahar has detected emulator data for Lime3DS.\n\n") + migration_prompt_message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
MigrateUserData(LegacyEmu::Lime3DS);
return;
}
}
// If we're here, the user chose not to migrate
ShowMigrationCancelledMessage();
}
void GMainWindow::MigrateUserData(const LegacyEmu selected_legacy_emu) {
namespace fs = std::filesystem;
const auto copy_options = fs::copy_options::update_existing | fs::copy_options::recursive;
std::string legacy_user_dir;
std::string legacy_config_dir;
std::string legacy_cache_dir;
if (LegacyEmu::Citra == selected_legacy_emu) {
legacy_user_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyCitraUserDir);
legacy_config_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyCitraConfigDir);
legacy_cache_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyCitraCacheDir);
} else if (LegacyEmu::Lime3DS == selected_legacy_emu) {
legacy_user_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyLime3DSUserDir);
legacy_config_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyLime3DSConfigDir);
legacy_cache_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyLime3DSCacheDir);
}
fs::copy(legacy_user_dir, FileUtil::GetUserPath(FileUtil::UserPath::UserDir), copy_options);
if (fs::is_directory(legacy_config_dir)) {
fs::copy(legacy_config_dir, FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir),
copy_options);
}
if (fs::is_directory(legacy_cache_dir)) {
fs::copy(legacy_cache_dir, FileUtil::GetUserPath(FileUtil::UserPath::CacheDir),
copy_options);
}
QMessageBox::information(
this, tr("Migration"),
tr("Data was migrated successfully. Azahar will now restart.\n\n"
"If you wish to clean up the files which were left in the old "
"data location, you can do so by deleting the following directory:\n"
"%1")
.arg(QString::fromStdString(legacy_user_dir)),
QMessageBox::Ok);
}
#if defined(HAVE_SDL2) && defined(__unix__) && !defined(__APPLE__)
static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
if (!QDBusConnection::sessionBus().isConnected()) {
return {};
}
// reference: https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.Inhibit
QDBusInterface xdp(QStringLiteral("org.freedesktop.portal.Desktop"),
QStringLiteral("/org/freedesktop/portal/desktop"),
QStringLiteral("org.freedesktop.portal.Inhibit"));
if (!xdp.isValid()) {
LOG_WARNING(Frontend, "Couldn't connect to XDP D-Bus endpoint");
return {};
}
QVariantMap options = {};
//: TRANSLATORS: This string is shown to the user to explain why Citra needs to prevent the
//: computer from sleeping
options.insert(QString::fromLatin1("reason"),
QCoreApplication::translate("GMainWindow", "Azahar is running an application"));
// 0x4: Suspend lock; 0x8: Idle lock
QDBusReply<QDBusObjectPath> reply =
xdp.call(QString::fromLatin1("Inhibit"),
QString::fromLatin1("x11:") + QString::number(window_id, 16), 12U, options);
if (reply.isValid()) {
return reply.value();
}
LOG_WARNING(Frontend, "Couldn't read Inhibit reply from XDP: {}",
reply.error().message().toStdString());
return {};
}
static void ReleaseWakeLockLinux(const QDBusObjectPath& lock) {
if (!QDBusConnection::sessionBus().isConnected()) {
return;
}
QDBusInterface unlocker(QString::fromLatin1("org.freedesktop.portal.Desktop"), lock.path(),
QString::fromLatin1("org.freedesktop.portal.Request"));
unlocker.call(QString::fromLatin1("Close"));
}
#endif // __unix__
void GMainWindow::PreventOSSleep() {
#ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
#elif defined(HAVE_SDL2)
SDL_DisableScreenSaver();
#if defined(__unix__) && !defined(__APPLE__)
auto reply = HoldWakeLockLinux(winId());
if (reply) {
wake_lock = std::move(reply.value());
}
#endif // defined(__unix__) && !defined(__APPLE__)
#endif // _WIN32
}
void GMainWindow::AllowOSSleep() {
#ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS);
#elif defined(HAVE_SDL2)
SDL_EnableScreenSaver();
#if defined(__unix__) && !defined(__APPLE__)
if (!wake_lock.path().isEmpty()) {
ReleaseWakeLockLinux(wake_lock);
}
#endif // defined(__unix__) && !defined(__APPLE__)
#endif // _WIN32
}
bool GMainWindow::LoadROM(const QString& filename) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread) {
ShutdownGame();
}
if (!render_window->InitRenderTarget() || !secondary_window->InitRenderTarget()) {
LOG_CRITICAL(Frontend, "Failed to initialize render targets!");
return false;
}
Prepare frontend for multiple graphics APIs (#6347) * externals: Update dynarmic * settings: Introduce GraphicsAPI enum * For now it's OpenGL only but will be expanded upon later * citra_qt: Introduce backend agnostic context management * Mostly a direct port from yuzu * core: Simplify context acquire * settings: Add option to create debug contexts * renderer_opengl: Abstract initialization to Driver * This commit also updates glad and adds some useful extensions which we will use in part 2 * Rasterizer construction is moved to the specific renderer instead of RendererBase. Software rendering has been disable to achieve this but will be brought back in the next commit. * video_core: Remove Init/Shutdown methods from renderer * The constructor and destructor can do the same job * In addition move opengl function loading to Qt since SDL already does this. Also remove ErrorVideoCore which is never reached * citra_qt: Decouple software renderer from opengl part 1 * citra: Decouple software renderer from opengl part 2 * android: Decouple software renderer from opengl part 3 * swrasterizer: Decouple software renderer from opengl part 4 * This commit simply enforces the renderer naming conventions in the software renderer * video_core: Move RendererBase to VideoCore * video_core: De-globalize screenshot state * video_core: Pass system to the renderers * video_core: Commonize shader uniform data * video_core: Abstract backend agnostic rasterizer operations * bootmanager: Remove references to OpenGL for macOS OpenGL macOS headers definitions clash heavily with each other * citra_qt: Proper title for api settings * video_core: Reduce boost usage * bootmanager: Fix hide mouse option Remove event handlers from RenderWidget for events that are already handled by the parent GRenderWindow. Also enable mouse tracking on the RenderWidget. * android: Remove software from graphics api list * code: Address review comments * citra: Port per-game settings read * Having to update the default value for all backends is a pain so lets centralize it * android: Rename to OpenGLES --------- Co-authored-by: MerryMage <MerryMage@users.noreply.github.com> Co-authored-by: Vitor Kiguchi <vitor-kiguchi@hotmail.com>
2023-03-27 14:29:17 +03:00
const auto scope = render_window->Acquire();
const Core::System::ResultStatus result{
system.Load(*render_window, filename.toStdString(), secondary_window)};
2014-04-03 21:24:07 -04:00
if (result != Core::System::ResultStatus::Success) {
switch (result) {
case Core::System::ResultStatus::ErrorGetLoader:
2018-06-29 14:18:07 +03:00
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString());
QMessageBox::critical(
this, tr("Invalid App Format"),
tr("Your app format is not supported.<br/>Please follow the guides to redump your "
"<a "
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
"dumping-game-cartridges/'>game "
"cartridges</a> or "
"<a "
"href='https://web.archive.org/web/20240304210011/https://citra-emu.org/wiki/"
"dumping-installed-titles/'>installed "
"titles</a>."));
break;
case Core::System::ResultStatus::ErrorSystemMode:
LOG_CRITICAL(Frontend, "Failed to load App!");
QMessageBox::critical(
this, tr("App Corrupted"),
tr("Your app is corrupted. <br/>Please follow the guides to redump your "
"<a "
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
"dumping-game-cartridges/'>game "
"cartridges</a> or "
"<a "
"href='https://web.archive.org/web/20240304210011/https://citra-emu.org/wiki/"
"dumping-installed-titles/'>installed "
"titles</a>."));
break;
2016-12-17 01:20:47 -05:00
case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: {
QMessageBox::critical(this, tr("App Encrypted"),
tr("Your app is encrypted. <br/>"
"<a "
"href='https://azahar-emu.org/blog/game-loading-changes/'>"
"Please check our blog for more info.</a>"));
break;
}
case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
QMessageBox::critical(
this, tr("Invalid App Format"),
tr("Your app format is not supported.<br/>Please follow the guides to redump your "
"<a "
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
"dumping-game-cartridges/'>game "
"cartridges</a> or "
"<a "
"href='https://web.archive.org/web/20240304210011/https://citra-emu.org/wiki/"
"dumping-installed-titles/'>installed "
"titles</a>."));
break;
case Core::System::ResultStatus::ErrorLoader_ErrorGbaTitle:
QMessageBox::critical(this, tr("Unsupported App"),
tr("GBA Virtual Console is not supported by Azahar."));
break;
case Core::System::ResultStatus::ErrorArticDisconnected:
QMessageBox::critical(
this, tr("Artic Base Server"),
tr(fmt::format(
"An error has occurred whilst communicating with the Artic Base Server.\n{}",
system.GetStatusDetails())
.c_str()));
break;
default:
QMessageBox::critical(
this, tr("Error while loading App!"),
tr("An unknown error occurred. Please see the log for more details."));
break;
}
return false;
2014-04-03 21:24:07 -04:00
}
std::string title;
system.GetAppLoader().ReadTitle(title);
game_title = QString::fromStdString(title);
UpdateWindowTitle();
u64 title_id;
system.GetAppLoader().ReadProgramId(title_id);
game_path = filename;
game_title_id = title_id;
return true;
}
void GMainWindow::BootGame(const QString& filename) {
if (emu_thread) {
ShutdownGame();
}
const bool is_artic = filename.startsWith(QString::fromStdString("articbase://"));
if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) {
const auto answer = QMessageBox::question(
this, tr("CIA must be installed before usage"),
tr("Before using this CIA, you must install it. Do you want to install it now?"),
QMessageBox::Yes | QMessageBox::No);
if (answer == QMessageBox::Yes)
InstallCIA(QStringList(filename));
return;
}
show_artic_label = is_artic;
LOG_INFO(Frontend, "Azahar starting...");
if (!is_artic) {
StoreRecentFile(filename); // Put the filename on top of the list
}
if (movie_record_on_start) {
movie.PrepareForRecording();
}
if (movie_playback_on_start) {
movie.PrepareForPlayback(movie_playback_path.toStdString());
}
const std::string path = filename.toStdString();
auto loader = Loader::GetLoader(path);
u64 title_id{0};
Loader::ResultStatus res = loader->ReadProgramId(title_id);
if (Loader::ResultStatus::Success == res) {
// Load per game settings
const std::string name{is_artic ? "" : FileUtil::GetFilename(filename.toStdString())};
const std::string config_file_name =
title_id == 0 ? name : fmt::format("{:016X}", title_id);
LOG_INFO(Frontend, "Loading per application config file for title {}", config_file_name);
QtConfig per_game_config(config_file_name, QtConfig::ConfigType::PerGameConfig);
}
// Artic Base Server cannot accept a client multiple times, so multiple loaders are not
// possible. Instead register the app loader early and do not create it again on system load.
if (!loader->SupportsMultipleInstancesForSameFile()) {
system.RegisterAppLoaderEarly(loader);
}
system.ApplySettings();
2023-07-03 02:17:03 +03:00
Settings::LogSettings();
// Save configurations
UpdateUISettings();
game_list->SaveInterfaceLayout();
config->Save();
if (!LoadROM(filename)) {
render_window->ReleaseRenderTarget();
secondary_window->ReleaseRenderTarget();
return;
}
// Set everything up
if (movie_record_on_start) {
movie.StartRecording(movie_record_path.toStdString(), movie_record_author.toStdString());
movie_record_on_start = false;
movie_record_path.clear();
movie_record_author.clear();
}
if (movie_playback_on_start) {
movie.StartPlayback(movie_playback_path.toStdString());
movie_playback_on_start = false;
movie_playback_path.clear();
}
ui->action_Advance_Frame->setEnabled(false);
if (video_dumping_on_start) {
StartVideoDumping(video_dumping_path);
video_dumping_on_start = false;
video_dumping_path.clear();
}
// Register debug widgets
if (graphicsWidget->isVisible()) {
graphicsWidget->Register();
}
// Create and start the emulation thread
emu_thread = std::make_unique<EmuThread>(system, *render_window);
emit EmulationStarting(emu_thread.get());
emu_thread->start();
2014-04-21 23:15:17 -04:00
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
connect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
connect(secondary_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
// before the CPU continues
connect(emu_thread.get(), &EmuThread::DebugModeEntered, registersWidget,
&RegistersWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection);
connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget,
&WaitTreeWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection);
connect(emu_thread.get(), &EmuThread::DebugModeLeft, registersWidget,
&RegistersWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget,
&WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen,
&LoadingScreen::OnLoadProgress, Qt::QueuedConnection);
connect(emu_thread.get(), &EmuThread::HideLoadingScreen, loading_screen,
&LoadingScreen::OnLoadComplete);
// Update the GUI
registersWidget->OnDebugModeEntered();
if (ui->action_Single_Window_Mode->isChecked()) {
2015-08-31 21:35:33 -07:00
game_list->hide();
game_list_placeholder->hide();
2015-08-31 21:35:33 -07:00
}
status_bar_update_timer.start(1000);
if (UISettings::values.hide_mouse) {
mouse_hide_timer.start();
setMouseTracking(true);
}
loading_screen->Prepare(system.GetAppLoader());
loading_screen->show();
emulation_running = true;
if (ui->action_Fullscreen->isChecked()) {
ShowFullscreen();
}
OnStartGame();
2014-03-31 22:26:50 -04:00
}
void GMainWindow::ShutdownGame() {
if (!emulation_running) {
return;
}
if (ui->action_Fullscreen->isChecked()) {
HideFullscreen();
}
auto video_dumper = system.GetVideoDumper();
if (video_dumper && video_dumper->IsDumping()) {
game_shutdown_delayed = true;
OnStopVideoDumping();
return;
}
AllowOSSleep();
discord_rpc->Pause();
emu_thread->RequestStop();
// Release emu threads from any breakpoints
// This belongs after RequestStop() and before wait() because if emulation stops on a GPU
// breakpoint after (or before) RequestStop() is called, the emulation would never be able
// to continue out to the main loop and terminate. Thus wait() would hang forever.
// TODO(bunnei): This function is not thread safe, but it's being used as if it were
Pica::g_debug_context->ClearBreakpoints();
// Unregister debug widgets
if (graphicsWidget->isVisible()) {
graphicsWidget->Unregister();
}
// Frame advancing must be cancelled in order to release the emu thread from waiting
system.frame_limiter.SetFrameAdvancing(false);
emit EmulationStopping();
// Wait for emulation thread to complete and delete it
emu_thread->wait();
emu_thread = nullptr;
OnCloseMovie();
discord_rpc->Update();
#ifdef __unix__
Common::Linux::StopGamemode();
#endif
// The emulation is stopped, so closing the window or not does not matter anymore
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
disconnect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
render_window->hide();
secondary_window->hide();
loading_screen->hide();
loading_screen->Clear();
if (game_list->IsEmpty()) {
game_list_placeholder->show();
} else {
game_list->show();
}
game_list->SetFilterFocus();
setMouseTracking(false);
// Disable status bar updates
status_bar_update_timer.stop();
message_label_used_for_movie = false;
show_artic_label = false;
artic_traffic_label->setVisible(false);
emu_speed_label->setVisible(false);
game_fps_label->setVisible(false);
emu_frametime_label->setVisible(false);
UpdateSaveStates();
emulation_running = false;
2018-06-02 11:27:09 +08:00
game_title.clear();
UpdateWindowTitle();
game_path.clear();
game_title_id = 0;
// Update the GUI
UpdateMenuState();
// When closing the game, destroy the GLWindow to clear the context after the game is closed
render_window->ReleaseRenderTarget();
secondary_window->ReleaseRenderTarget();
}
void GMainWindow::StoreRecentFile(const QString& filename) {
UISettings::values.recent_files.prepend(filename);
UISettings::values.recent_files.removeDuplicates();
while (UISettings::values.recent_files.size() > max_recent_files_item) {
UISettings::values.recent_files.removeLast();
}
UpdateRecentFiles();
}
void GMainWindow::UpdateRecentFiles() {
const int num_recent_files =
2023-05-05 03:10:34 -07:00
std::min(static_cast<int>(UISettings::values.recent_files.size()), max_recent_files_item);
for (int i = 0; i < num_recent_files; i++) {
const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(
QFileInfo(UISettings::values.recent_files[i]).fileName());
actions_recent_files[i]->setText(text);
actions_recent_files[i]->setData(UISettings::values.recent_files[i]);
actions_recent_files[i]->setToolTip(UISettings::values.recent_files[i]);
actions_recent_files[i]->setVisible(true);
}
for (int j = num_recent_files; j < max_recent_files_item; ++j) {
actions_recent_files[j]->setVisible(false);
}
// Enable the recent files menu if the list isn't empty
ui->menu_recent_files->setEnabled(num_recent_files != 0);
}
void GMainWindow::UpdateSaveStates() {
if (!system.IsPoweredOn()) {
ui->menu_Load_State->setEnabled(false);
ui->menu_Save_State->setEnabled(false);
return;
}
ui->menu_Load_State->setEnabled(true);
ui->menu_Save_State->setEnabled(true);
ui->action_Load_from_Newest_Slot->setEnabled(false);
oldest_slot = newest_slot = 0;
oldest_slot_time = std::numeric_limits<u64>::max();
newest_slot_time = 0;
u64 title_id;
if (system.GetAppLoader().ReadProgramId(title_id) != Loader::ResultStatus::Success) {
return;
}
2023-08-01 03:57:38 +03:00
auto savestates = Core::ListSaveStates(title_id, movie.GetCurrentMovieID());
for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) {
actions_load_state[i]->setEnabled(false);
actions_load_state[i]->setText(tr("Slot %1").arg(i + 1));
actions_save_state[i]->setText(tr("Slot %1").arg(i + 1));
}
for (const auto& savestate : savestates) {
const bool display_name =
savestate.status == Core::SaveStateInfo::ValidationStatus::RevisionDismatch &&
!savestate.build_name.empty();
const auto text =
tr("Slot %1 - %2 %3")
.arg(savestate.slot)
.arg(QDateTime::fromSecsSinceEpoch(savestate.time)
.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")))
.arg(display_name ? QString::fromStdString(savestate.build_name) : QLatin1String())
.trimmed();
actions_load_state[savestate.slot - 1]->setEnabled(true);
actions_load_state[savestate.slot - 1]->setText(text);
actions_save_state[savestate.slot - 1]->setText(text);
ui->action_Load_from_Newest_Slot->setEnabled(true);
if (savestate.time > newest_slot_time) {
newest_slot = savestate.slot;
newest_slot_time = savestate.time;
}
if (savestate.time < oldest_slot_time) {
oldest_slot = savestate.slot;
oldest_slot_time = savestate.time;
}
}
for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) {
if (!actions_load_state[i]->isEnabled()) {
// Prefer empty slot
oldest_slot = i + 1;
oldest_slot_time = 0;
break;
}
}
}
2015-08-31 21:35:33 -07:00
void GMainWindow::OnGameListLoadFile(QString game_path) {
if (ConfirmChangeGame()) {
BootGame(game_path);
}
2015-08-31 21:35:33 -07:00
}
void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
std::string path;
std::string open_target;
switch (target) {
case GameListOpenTarget::SAVE_DATA: {
open_target = "Save Data";
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, data_id);
break;
}
case GameListOpenTarget::EXT_DATA: {
open_target = "Extra Data";
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
path = FileSys::GetExtDataPathFromId(sdmc_dir, data_id);
break;
}
case GameListOpenTarget::APPLICATION: {
open_target = "Application";
auto media_type = Service::AM::GetTitleMediaType(data_id);
path = Service::AM::GetTitlePath(media_type, data_id) + "content/";
break;
}
case GameListOpenTarget::UPDATE_DATA: {
open_target = "Update Data";
path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, data_id + 0xe00000000) +
"content/";
break;
}
case GameListOpenTarget::TEXTURE_DUMP: {
2019-08-14 01:04:50 -04:00
open_target = "Dumped Textures";
path = fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), data_id);
break;
}
case GameListOpenTarget::TEXTURE_LOAD: {
2019-08-14 01:04:50 -04:00
open_target = "Custom Textures";
path = fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), data_id);
break;
}
case GameListOpenTarget::MODS: {
2020-02-07 14:44:34 +08:00
open_target = "Mods";
path = fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
data_id);
break;
}
case GameListOpenTarget::DLC_DATA: {
open_target = "DLC Data";
path = fmt::format("{}Nintendo 3DS/00000000000000000000000000000000/"
"00000000000000000000000000000000/title/0004008c/{:08x}/content/",
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), data_id);
break;
}
case GameListOpenTarget::SHADER_CACHE: {
open_target = "Shader Cache";
path = FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir);
break;
}
default:
2018-06-29 14:18:07 +03:00
LOG_ERROR(Frontend, "Unexpected target {}", static_cast<int>(target));
return;
}
QString qpath = QString::fromStdString(path);
QDir dir(qpath);
if (!dir.exists()) {
QMessageBox::critical(
this, tr("Error Opening %1 Folder").arg(QString::fromStdString(open_target)),
tr("Folder does not exist!"));
return;
}
LOG_INFO(Frontend, "Opening {} path for data_id={:016x}", open_target, data_id);
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
}
void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No) != QMessageBox::Yes) {
return;
}
play_time_manager->ResetProgramPlayTime(program_id);
game_list->PopulateAsync(UISettings::values.game_dirs);
}
bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
const std::string& comment,
const std::filesystem::path& icon_path,
const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords,
const std::string& name, const bool& skip_tryexec) try {
#if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD
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());
}
if (!skip_tryexec) {
fmt::print(shortcut_stream, "TryExec={}\n", command);
}
fmt::print(shortcut_stream, "Exec={} {}\n", command, arguments);
if (!categories.empty()) {
fmt::print(shortcut_stream, "Categories={}\n", categories);
}
if (!keywords.empty()) {
fmt::print(shortcut_stream, "Keywords={}\n", keywords);
}
return true;
#elif defined(_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(Common::UTF8ToUTF16W(command).data());
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;
#else // Unsupported platform
return false;
#endif
} catch (const std::exception& e) {
LOG_ERROR(Frontend, "Failed to create shortcut: {}", e.what());
return false;
}
// Messages in pre-defined message boxes for less code spaghetti
bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, int message,
const QString& game_title) {
int result = 0;
QMessageBox::StandardButtons buttons;
switch (message) {
case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_PROMPT:
buttons = QMessageBox::Yes | QMessageBox::No;
result = QMessageBox::information(
parent, tr("Create Shortcut"),
tr("Do you want to launch the application in fullscreen?"), buttons);
return result == QMessageBox::Yes;
case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS:
QMessageBox::information(parent, tr("Create Shortcut"),
tr("Successfully created a shortcut to %1").arg(game_title));
return false;
case GMainWindow::CREATE_SHORTCUT_MSGBOX_APPIMAGE_VOLATILE_WARNING:
buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel;
result =
QMessageBox::warning(this, tr("Create Shortcut"),
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;
QMessageBox::critical(parent, tr("Create Shortcut"),
tr("Failed to create a shortcut to %1").arg(game_title), buttons);
return false;
}
}
bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
std::filesystem::path& out_icon_path) {
// Get path to Citra icons directory & icon extension
std::string ico_extension = "png";
#if defined(_WIN32)
out_icon_path = FileUtil::GetUserPath(FileUtil::UserPath::IconsDir);
ico_extension = "ico";
#elif defined(__linux__) || defined(__FreeBSD__)
out_icon_path = FileUtil::GetUserDirectory("XDG_DATA_HOME") + "/icons/hicolor/256x256/";
#endif
// Create icons directory if it doesn't exist
if (!FileUtil::CreateFullPath(out_icon_path.string())) {
QMessageBox::critical(
this, tr("Create Icon"),
tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
.arg(QString::fromStdString(out_icon_path.string())),
QMessageBox::StandardButton::Ok);
out_icon_path.clear();
return false;
}
// Create icon file path
out_icon_path /= (program_id == 0 ? fmt::format("citra-{}.{}", game_file_name, ico_extension)
: fmt::format("citra-{:016X}.{}", program_id, ico_extension));
return true;
}
void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
GameListShortcutTarget target) {
std::string citra_command{};
bool skip_tryexec = false;
const char* env_flatpak_id = getenv("FLATPAK_ID");
if (env_flatpak_id) {
citra_command = fmt::format("flatpak run {}", env_flatpak_id);
skip_tryexec = true;
} else {
// Get path to Citra executable
const QStringList args = QApplication::arguments();
citra_command = args[0].toStdString();
// If relative path, make it an absolute path
if (citra_command.c_str()[0] == '.') {
citra_command = FileUtil::GetCurrentDir().value_or("") + DIR_SEP + citra_command;
}
}
// Shortcut path
std::filesystem::path shortcut_path{};
if (target == GameListShortcutTarget::Desktop) {
shortcut_path =
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
} else if (target == GameListShortcutTarget::Applications) {
shortcut_path = GetApplicationsDirectory();
}
// Icon path and title
if (!std::filesystem::exists(shortcut_path)) {
CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_ERROR, {});
LOG_ERROR(Frontend, "Invalid shortcut target");
return;
}
// Get title from game file
const auto loader = Loader::GetLoader(game_path);
std::string game_title = fmt::format("{:016X}", program_id);
if (loader->ReadTitle(game_title) != Loader::ResultStatus::Success) {
game_title = fmt::format("{:016x}", program_id);
}
// Delete illegal characters from title
const std::string illegal_chars = "<>:\"/\\|?*.";
for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) {
if (illegal_chars.find(*it) != std::string::npos) {
game_title.erase(it.base() - 1);
}
}
// Get icon from game file
std::vector<u8> icon_image_file;
if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
}
const QPixmap pixmap = GetQPixmapFromSMDH(icon_image_file);
const QImage icon_data = pixmap.toImage();
std::filesystem::path out_icon_path;
if (MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
if (!SaveIconToFile(out_icon_path, icon_data)) {
LOG_ERROR(Frontend, "Could not write icon to file");
}
}
const auto qt_game_title = QString::fromStdString(game_title);
#if defined(__linux__)
// Special case for AppImages
// Warn once if we are making a shortcut to a volatile AppImage
const std::string appimage_ending =
std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
if (citra_command.ends_with(appimage_ending) && !UISettings::values.shortcut_already_warned) {
if (CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_APPIMAGE_VOLATILE_WARNING,
qt_game_title)) {
return;
}
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
// Create shortcut
std::string arguments = fmt::format("\"{:s}\"", game_path);
if (CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_FULLSCREEN_PROMPT, qt_game_title)) {
arguments = "-f " + arguments;
}
const std::string comment = fmt::format("Start {:s} with the Azahar Emulator", game_title);
const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "3ds;Nintendo;";
if (CreateShortcutLink(shortcut_path, comment, out_icon_path, citra_command, arguments,
categories, keywords, game_title, skip_tryexec)) {
CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_SUCCESS, qt_game_title);
return;
}
CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_ERROR, qt_game_title);
}
void GMainWindow::OnGameListDumpRomFS(QString game_path, u64 program_id) {
auto* dialog = new QProgressDialog(tr("Dumping..."), tr("Cancel"), 0, 0, this);
dialog->setWindowModality(Qt::WindowModal);
dialog->setWindowFlags(dialog->windowFlags() &
~(Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint));
dialog->setCancelButton(nullptr);
dialog->setMinimumDuration(0);
dialog->setValue(0);
const auto base_path = fmt::format(
"{}romfs/{:016X}", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
const auto update_path =
fmt::format("{}romfs/{:016X}", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
program_id | 0x0004000e00000000);
using FutureWatcher = QFutureWatcher<std::pair<Loader::ResultStatus, Loader::ResultStatus>>;
auto* future_watcher = new FutureWatcher(this);
connect(future_watcher, &FutureWatcher::finished, this,
[this, dialog, base_path, update_path, future_watcher] {
dialog->hide();
const auto& [base, update] = future_watcher->result();
if (base != Loader::ResultStatus::Success) {
QMessageBox::critical(
this, tr("Azahar"),
tr("Could not dump base RomFS.\nRefer to the log for details."));
return;
}
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(base_path)));
if (update == Loader::ResultStatus::Success) {
QDesktopServices::openUrl(
QUrl::fromLocalFile(QString::fromStdString(update_path)));
}
});
auto future = QtConcurrent::run([game_path, base_path, update_path] {
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(game_path.toStdString());
return std::make_pair(loader->DumpRomFS(base_path), loader->DumpUpdateRomFS(update_path));
});
future_watcher->setFuture(future);
}
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
QString path;
if (directory == QStringLiteral("INSTALLED")) {
path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
"Nintendo "
"3DS/00000000000000000000000000000000/"
"00000000000000000000000000000000/title/00040000");
} else if (directory == QStringLiteral("SYSTEM")) {
path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"00000000000000000000000000000000/title/00040010");
} else {
path = directory;
}
if (!QFileInfo::exists(path)) {
QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!"));
return;
}
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}
void GMainWindow::OnGameListAddDirectory() {
const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (dir_path.isEmpty())
return;
UISettings::GameDir game_dir{dir_path, false, true};
if (!UISettings::values.game_dirs.contains(game_dir)) {
UISettings::values.game_dirs.append(game_dir);
game_list->PopulateAsync(UISettings::values.game_dirs);
} else {
LOG_WARNING(Frontend, "Selected directory is already in the application list");
}
}
void GMainWindow::OnGameListShowList(bool show) {
if (emulation_running && ui->action_Single_Window_Mode->isChecked())
return;
game_list->setVisible(show);
game_list_placeholder->setVisible(!show);
};
void GMainWindow::OnGameListOpenPerGameProperties(const QString& file) {
const auto loader = Loader::GetLoader(file.toStdString());
u64 title_id{};
if (!loader || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
QMessageBox::information(this, tr("Properties"),
tr("The application properties could not be loaded."));
return;
}
OpenPerGameConfiguration(title_id, file);
}
void GMainWindow::OnMenuLoadFile() {
const QString extensions = QStringLiteral("*.").append(
GameList::supported_file_extensions.join(QStringLiteral(" *.")));
const QString file_filter = tr("3DS Executable (%1);;All Files (*.*)",
"%1 is an identifier for the 3DS executable file extensions.")
.arg(extensions);
const QString filename = QFileDialog::getOpenFileName(
this, tr("Load File"), UISettings::values.roms_path, file_filter);
if (filename.isEmpty()) {
return;
}
UISettings::values.roms_path = QFileInfo(filename).path();
BootGame(filename);
2014-03-31 22:26:50 -04:00
}
2017-12-02 15:35:20 -06:00
void GMainWindow::OnMenuInstallCIA() {
2018-02-13 18:16:26 -06:00
QStringList filepaths = QFileDialog::getOpenFileNames(
this, tr("Load Files"), UISettings::values.roms_path,
tr("3DS Installation File (*.CIA*)") + QStringLiteral(";;") + tr("All Files (*.*)"));
if (filepaths.isEmpty()) {
2017-12-02 15:35:20 -06:00
return;
}
2017-12-02 15:35:20 -06:00
UISettings::values.roms_path = QFileInfo(filepaths[0]).path();
InstallCIA(filepaths);
}
void GMainWindow::OnMenuConnectArticBase() {
bool ok = false;
auto res = QInputDialog::getText(this, tr("Connect to Artic Base"),
tr("Enter Artic Base server address:"), QLineEdit::Normal,
UISettings::values.last_artic_base_addr, &ok);
if (ok) {
UISettings::values.last_artic_base_addr = res;
BootGame(QString::fromStdString("articbase://").append(res));
}
}
void GMainWindow::OnMenuBootHomeMenu(u32 region) {
BootGame(QString::fromStdString(Core::GetHomeMenuNcchPath(region)));
}
void GMainWindow::InstallCIA(QStringList filepaths) {
ui->action_Install_CIA->setEnabled(false);
game_list->SetDirectoryWatcherEnabled(false);
2017-12-02 15:35:20 -06:00
progress_bar->show();
2018-07-02 17:13:45 -05:00
progress_bar->setMaximum(INT_MAX);
2018-02-13 18:16:26 -06:00
2023-05-05 03:10:34 -07:00
(void)QtConcurrent::run([&, filepaths] {
2018-02-13 18:16:26 -06:00
Service::AM::InstallStatus status;
const auto cia_progress = [&](std::size_t written, std::size_t total) {
2018-02-16 14:53:38 -06:00
emit UpdateProgress(written, total);
};
for (const auto& current_path : filepaths) {
2018-02-13 18:16:26 -06:00
status = Service::AM::InstallCIA(current_path.toStdString(), cia_progress);
emit CIAInstallReport(status, current_path);
}
emit CIAInstallFinished();
2017-12-02 15:35:20 -06:00
});
}
void GMainWindow::OnUpdateProgress(std::size_t written, std::size_t total) {
2018-07-02 17:13:45 -05:00
progress_bar->setValue(
static_cast<int>(INT_MAX * (static_cast<double>(written) / static_cast<double>(total))));
2017-12-02 15:35:20 -06:00
}
2018-02-13 18:16:26 -06:00
void GMainWindow::OnCIAInstallReport(Service::AM::InstallStatus status, QString filepath) {
QString filename = QFileInfo(filepath).fileName();
switch (status) {
2017-12-02 15:35:20 -06:00
case Service::AM::InstallStatus::Success:
2018-02-13 18:16:26 -06:00
this->statusBar()->showMessage(tr("%1 has been installed successfully.").arg(filename));
2017-12-02 15:35:20 -06:00
break;
case Service::AM::InstallStatus::ErrorFailedToOpenFile:
QMessageBox::critical(this, tr("Unable to open File"),
2018-02-13 18:16:26 -06:00
tr("Could not open %1").arg(filename));
2017-12-02 15:35:20 -06:00
break;
case Service::AM::InstallStatus::ErrorAborted:
QMessageBox::critical(
this, tr("Installation aborted"),
2018-02-13 18:16:26 -06:00
tr("The installation of %1 was aborted. Please see the log for more details")
.arg(filename));
2017-12-02 15:35:20 -06:00
break;
case Service::AM::InstallStatus::ErrorInvalid:
2018-02-13 18:16:26 -06:00
QMessageBox::critical(this, tr("Invalid File"), tr("%1 is not a valid CIA").arg(filename));
2017-12-02 15:35:20 -06:00
break;
case Service::AM::InstallStatus::ErrorEncrypted:
QMessageBox::critical(this, tr("CIA Encrypted"),
tr("Your CIA file is encrypted.<br/>"
"<a "
"href='https://azahar-emu.org/blog/game-loading-changes/'>"
"Please check our blog for more info.</a>"));
2017-12-02 15:35:20 -06:00
break;
case Service::AM::InstallStatus::ErrorFileNotFound:
QMessageBox::critical(this, tr("Unable to find File"),
tr("Could not find %1").arg(filename));
break;
2017-12-02 15:35:20 -06:00
}
2018-02-13 18:16:26 -06:00
}
void GMainWindow::OnCIAInstallFinished() {
progress_bar->hide();
progress_bar->setValue(0);
game_list->SetDirectoryWatcherEnabled(true);
ui->action_Install_CIA->setEnabled(true);
game_list->PopulateAsync(UISettings::values.game_dirs);
2017-12-02 15:35:20 -06:00
}
void GMainWindow::UninstallTitles(
const std::vector<std::tuple<Service::FS::MediaType, u64, QString>>& titles) {
if (titles.empty()) {
return;
}
// Select the first title in the list as representative.
const auto first_name = std::get<QString>(titles[0]);
QProgressDialog progress(tr("Uninstalling '%1'...").arg(first_name), tr("Cancel"), 0,
static_cast<int>(titles.size()), this);
progress.setWindowModality(Qt::WindowModal);
QFutureWatcher<void> future_watcher;
QObject::connect(&future_watcher, &QFutureWatcher<void>::finished, &progress,
&QProgressDialog::reset);
QObject::connect(&progress, &QProgressDialog::canceled, &future_watcher,
&QFutureWatcher<void>::cancel);
QObject::connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &progress,
&QProgressDialog::setValue);
auto failed = false;
QString failed_name;
const auto uninstall_title = [&future_watcher, &failed, &failed_name](const auto& title) {
const auto name = std::get<QString>(title);
const auto media_type = std::get<Service::FS::MediaType>(title);
const auto program_id = std::get<u64>(title);
const auto result = Service::AM::UninstallProgram(media_type, program_id);
if (result.IsError()) {
LOG_ERROR(Frontend, "Failed to uninstall '{}': 0x{:08X}", name.toStdString(),
result.raw);
failed = true;
failed_name = name;
future_watcher.cancel();
}
};
future_watcher.setFuture(QtConcurrent::map(titles, uninstall_title));
progress.exec();
future_watcher.waitForFinished();
if (failed) {
QMessageBox::critical(this, tr("Azahar"), tr("Failed to uninstall '%1'.").arg(failed_name));
} else if (!future_watcher.isCanceled()) {
QMessageBox::information(this, tr("Azahar"),
tr("Successfully uninstalled '%1'.").arg(first_name));
}
}
void GMainWindow::OnMenuRecentFile() {
QAction* action = qobject_cast<QAction*>(sender());
2020-03-28 17:08:27 +00:00
ASSERT(action);
const QString filename = action->data().toString();
if (QFileInfo::exists(filename)) {
BootGame(filename);
} else {
// Display an error message and remove the file from the list.
QMessageBox::information(this, tr("File not found"),
tr("File \"%1\" not found").arg(filename));
UISettings::values.recent_files.removeOne(filename);
UpdateRecentFiles();
}
}
void GMainWindow::OnStartGame() {
2023-05-05 03:10:34 -07:00
qt_cameras->ResumeCameras();
PreventOSSleep();
emu_thread->SetRunning(true);
system.frame_limiter.SetFrameAdvancing(false);
graphics_api_button->setEnabled(false);
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
2014-04-03 21:24:07 -04:00
UpdateMenuState();
play_time_manager->SetProgramId(game_title_id);
play_time_manager->Start();
discord_rpc->Update();
#ifdef __unix__
Common::Linux::StartGamemode();
#endif
UpdateSaveStates();
UpdateStatusButtons();
2014-03-31 22:26:50 -04:00
}
void GMainWindow::OnRestartGame() {
if (!system.IsPoweredOn()) {
return;
}
// Make a copy since BootGame edits game_path
BootGame(QString(game_path));
}
void GMainWindow::OnPauseGame() {
system.frame_limiter.SetFrameAdvancing(true);
2023-05-05 03:10:34 -07:00
qt_cameras->PauseCameras();
play_time_manager->Stop();
UpdateMenuState();
AllowOSSleep();
#ifdef __unix__
Common::Linux::StopGamemode();
#endif
2014-03-31 22:26:50 -04:00
}
void GMainWindow::OnPauseContinueGame() {
if (emulation_running) {
if (emu_thread->IsRunning() && !system.frame_limiter.IsFrameAdvancing()) {
OnPauseGame();
} else {
OnStartGame();
}
}
}
void GMainWindow::OnStopGame() {
play_time_manager->Stop();
// Update game list to show new play time
game_list->PopulateAsync(UISettings::values.game_dirs);
ShutdownGame();
graphics_api_button->setEnabled(true);
Settings::RestoreGlobalState(false);
UpdateStatusButtons();
2014-03-31 22:26:50 -04:00
}
void GMainWindow::OnLoadComplete() {
loading_screen->OnLoadComplete();
UpdateSecondaryWindowVisibility();
}
void GMainWindow::OnMenuReportCompatibility() {
// NoOp
}
2017-10-11 20:21:09 +02:00
void GMainWindow::ToggleFullscreen() {
if (!emulation_running) {
return;
}
if (ui->action_Fullscreen->isChecked()) {
ShowFullscreen();
2017-10-11 20:21:09 +02:00
} else {
HideFullscreen();
}
}
void GMainWindow::ToggleSecondaryFullscreen() {
if (!emulation_running) {
return;
}
if (secondary_window->isFullScreen()) {
secondary_window->showNormal();
} else {
secondary_window->showFullScreen();
}
}
void GMainWindow::ShowFullscreen() {
if (ui->action_Single_Window_Mode->isChecked()) {
UISettings::values.geometry = saveGeometry();
ui->menubar->hide();
statusBar()->hide();
showFullScreen();
} else {
UISettings::values.renderwindow_geometry = render_window->saveGeometry();
render_window->showFullScreen();
}
}
void GMainWindow::HideFullscreen() {
if (ui->action_Single_Window_Mode->isChecked()) {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
ui->menubar->show();
showNormal();
restoreGeometry(UISettings::values.geometry);
} else {
render_window->showNormal();
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
2017-10-11 20:21:09 +02:00
}
}
void GMainWindow::ToggleWindowMode() {
if (ui->action_Single_Window_Mode->isChecked()) {
// Render in the main window...
2014-04-21 23:15:17 -04:00
render_window->BackupGeometry();
ui->horizontalLayout->addWidget(render_window);
render_window->setFocusPolicy(Qt::StrongFocus);
if (emulation_running) {
render_window->setVisible(true);
render_window->setFocus();
game_list->hide();
}
} else {
// Render in a separate window...
ui->horizontalLayout->removeWidget(render_window);
render_window->setParent(nullptr);
render_window->setFocusPolicy(Qt::NoFocus);
if (emulation_running) {
render_window->setVisible(true);
render_window->RestoreGeometry();
2015-08-31 21:35:33 -07:00
game_list->show();
}
2014-03-31 22:26:50 -04:00
}
}
void GMainWindow::UpdateSecondaryWindowVisibility() {
if (!emulation_running) {
return;
}
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
secondary_window->RestoreGeometry();
secondary_window->show();
} else {
secondary_window->BackupGeometry();
secondary_window->hide();
}
}
void GMainWindow::ChangeScreenLayout() {
Settings::LayoutOption new_layout = Settings::LayoutOption::Default;
if (ui->action_Screen_Layout_Default->isChecked()) {
new_layout = Settings::LayoutOption::Default;
} else if (ui->action_Screen_Layout_Single_Screen->isChecked()) {
new_layout = Settings::LayoutOption::SingleScreen;
} else if (ui->action_Screen_Layout_Large_Screen->isChecked()) {
new_layout = Settings::LayoutOption::LargeScreen;
Added "Small Screen Position" feature * error checking for layout value from older config * rename enum and update aspect ratio code * rewrite LargeFrameLayout to support multiple positions * add settings for smallscreenposition, fix minsize function * fixed framebuffer from res scale (screenshots) * add desktop UI for small screen position * small screen position submenu on desktop * fix int-float conversion warning * rename Above and Below to hopefully fix linux issue * Add Small Screen Position Setting to android settings menu * fix sliders to work with floats, mostly * fix android slider textinput ui * change None enums in settings and cam_params * Apply clang-format-18 * SettingsAdapter.kt: Make more null pointer exception resistant * Updated license headers * Code formatting nitpicks * fix bug in main.ui that was hiding menu * replace default layout with a special call to LargeFrame (like SideBySide does) * fix bug when "large screen" is actually narrower * edit documentation for LargeScreenLayout * update PortraitTopFullFrameLayout to use LargeFrameLayout * fix unary minus on unsigned int bug * Applied formatting correction * Added `const`s where appropriate * android: Add mention of the bottom-right small screen position being the default * review fixes + more constants * refactor all Upright calculations to a reverseLayout method, simplifying code and reducing bugs * Removed stray extra newline * SettingsAdapter.kt: Fixed some strange indentation * Removed unnecessary `if` in favour of direct value usage --------- Co-authored-by: Reg Tiangha <rtiangha@users.noreply.github.com> Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2024-10-29 14:22:51 -07:00
ui->menu_Small_Screen_Position->setEnabled(true);
} else if (ui->action_Screen_Layout_Hybrid_Screen->isChecked()) {
new_layout = Settings::LayoutOption::HybridScreen;
} else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) {
new_layout = Settings::LayoutOption::SideScreen;
} else if (ui->action_Screen_Layout_Separate_Windows->isChecked()) {
new_layout = Settings::LayoutOption::SeparateWindows;
} else if (ui->action_Screen_Layout_Custom_Layout->isChecked()) {
new_layout = Settings::LayoutOption::CustomLayout;
}
Settings::values.layout_option = new_layout;
Added "Small Screen Position" feature * error checking for layout value from older config * rename enum and update aspect ratio code * rewrite LargeFrameLayout to support multiple positions * add settings for smallscreenposition, fix minsize function * fixed framebuffer from res scale (screenshots) * add desktop UI for small screen position * small screen position submenu on desktop * fix int-float conversion warning * rename Above and Below to hopefully fix linux issue * Add Small Screen Position Setting to android settings menu * fix sliders to work with floats, mostly * fix android slider textinput ui * change None enums in settings and cam_params * Apply clang-format-18 * SettingsAdapter.kt: Make more null pointer exception resistant * Updated license headers * Code formatting nitpicks * fix bug in main.ui that was hiding menu * replace default layout with a special call to LargeFrame (like SideBySide does) * fix bug when "large screen" is actually narrower * edit documentation for LargeScreenLayout * update PortraitTopFullFrameLayout to use LargeFrameLayout * fix unary minus on unsigned int bug * Applied formatting correction * Added `const`s where appropriate * android: Add mention of the bottom-right small screen position being the default * review fixes + more constants * refactor all Upright calculations to a reverseLayout method, simplifying code and reducing bugs * Removed stray extra newline * SettingsAdapter.kt: Fixed some strange indentation * Removed unnecessary `if` in favour of direct value usage --------- Co-authored-by: Reg Tiangha <rtiangha@users.noreply.github.com> Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2024-10-29 14:22:51 -07:00
SyncMenuUISettings();
system.ApplySettings();
UpdateSecondaryWindowVisibility();
}
void GMainWindow::ChangeSmallScreenPosition() {
Settings::SmallScreenPosition new_position = Settings::SmallScreenPosition::BottomRight;
if (ui->action_Small_Screen_TopRight->isChecked()) {
new_position = Settings::SmallScreenPosition::TopRight;
} else if (ui->action_Small_Screen_MiddleRight->isChecked()) {
new_position = Settings::SmallScreenPosition::MiddleRight;
} else if (ui->action_Small_Screen_BottomRight->isChecked()) {
new_position = Settings::SmallScreenPosition::BottomRight;
} else if (ui->action_Small_Screen_TopLeft->isChecked()) {
new_position = Settings::SmallScreenPosition::TopLeft;
} else if (ui->action_Small_Screen_MiddleLeft->isChecked()) {
new_position = Settings::SmallScreenPosition::MiddleLeft;
} else if (ui->action_Small_Screen_BottomLeft->isChecked()) {
new_position = Settings::SmallScreenPosition::BottomLeft;
} else if (ui->action_Small_Screen_Above->isChecked()) {
new_position = Settings::SmallScreenPosition::AboveLarge;
} else if (ui->action_Small_Screen_Below->isChecked()) {
new_position = Settings::SmallScreenPosition::BelowLarge;
}
Settings::values.small_screen_position = new_position;
SyncMenuUISettings();
2023-06-30 13:39:38 +03:00
system.ApplySettings();
UpdateSecondaryWindowVisibility();
}
void GMainWindow::ToggleScreenLayout() {
const Settings::LayoutOption new_layout = []() {
switch (Settings::values.layout_option.GetValue()) {
case Settings::LayoutOption::Default:
return Settings::LayoutOption::SingleScreen;
case Settings::LayoutOption::SingleScreen:
return Settings::LayoutOption::LargeScreen;
case Settings::LayoutOption::LargeScreen:
return Settings::LayoutOption::HybridScreen;
case Settings::LayoutOption::HybridScreen:
return Settings::LayoutOption::SideScreen;
case Settings::LayoutOption::SideScreen:
return Settings::LayoutOption::SeparateWindows;
case Settings::LayoutOption::SeparateWindows:
return Settings::LayoutOption::CustomLayout;
case Settings::LayoutOption::CustomLayout:
return Settings::LayoutOption::Default;
default:
LOG_ERROR(Frontend, "Unknown layout option {}",
Settings::values.layout_option.GetValue());
return Settings::LayoutOption::Default;
}
}();
Settings::values.layout_option = new_layout;
SyncMenuUISettings();
2023-06-30 13:39:38 +03:00
system.ApplySettings();
UpdateSecondaryWindowVisibility();
}
void GMainWindow::OnSwapScreens() {
Settings::values.swap_screen = ui->action_Screen_Layout_Swap_Screens->isChecked();
2023-06-30 13:39:38 +03:00
system.ApplySettings();
}
void GMainWindow::OnRotateScreens() {
Settings::values.upright_screen = ui->action_Screen_Layout_Upright_Screens->isChecked();
2023-06-30 13:39:38 +03:00
system.ApplySettings();
}
void GMainWindow::TriggerSwapScreens() {
ui->action_Screen_Layout_Swap_Screens->trigger();
}
void GMainWindow::TriggerRotateScreens() {
ui->action_Screen_Layout_Upright_Screens->trigger();
}
void GMainWindow::OnSaveState() {
QAction* action = qobject_cast<QAction*>(sender());
ASSERT(action);
system.SendSignal(Core::System::Signal::Save, action->data().toUInt());
system.frame_limiter.AdvanceFrame();
2020-04-06 22:47:52 +01:00
newest_slot = action->data().toUInt();
2020-01-03 17:19:59 +00:00
}
void GMainWindow::OnLoadState() {
QAction* action = qobject_cast<QAction*>(sender());
ASSERT(action);
if (UISettings::values.save_state_warning) {
QMessageBox::warning(
this, tr("Savestates"),
tr("Warning: Savestates are NOT a replacement for in-application saves, "
"and are not meant to be reliable.\n\nUse at your own risk!"));
UISettings::values.save_state_warning = false;
config->Save();
}
system.SendSignal(Core::System::Signal::Load, action->data().toUInt());
system.frame_limiter.AdvanceFrame();
2020-01-03 17:19:59 +00:00
}
void GMainWindow::OnConfigure() {
game_list->SetDirectoryWatcherEnabled(false);
Settings::SetConfiguringGlobal(true);
ConfigureDialog configureDialog(this, hotkey_registry, system, gl_renderer, physical_devices,
!multiplayer_state->IsHostingPublicRoom());
connect(&configureDialog, &ConfigureDialog::LanguageChanged, this,
&GMainWindow::OnLanguageChanged);
2018-07-02 16:53:34 -05:00
auto old_theme = UISettings::values.theme;
2018-12-29 09:19:45 -05:00
const int old_input_profile_index = Settings::values.current_input_profile_index;
2018-12-28 21:13:57 -05:00
const auto old_input_profiles = Settings::values.input_profiles;
const auto old_touch_from_button_maps = Settings::values.touch_from_button_maps;
const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
#ifdef __unix__
const bool old_gamemode = Settings::values.enable_gamemode.GetValue();
#endif
2016-01-24 18:34:05 +01:00
auto result = configureDialog.exec();
game_list->SetDirectoryWatcherEnabled(true);
if (result == QDialog::Accepted) {
configureDialog.ApplyConfiguration();
InitializeHotkeys();
if (UISettings::values.theme != old_theme) {
2018-07-02 16:53:34 -05:00
UpdateUITheme();
}
if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
}
#ifdef __unix__
if (Settings::values.enable_gamemode.GetValue() != old_gamemode) {
SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
}
#endif
if (!multiplayer_state->IsHostingPublicRoom())
multiplayer_state->UpdateCredentials();
emit UpdateThemedIcons();
SyncMenuUISettings();
game_list->RefreshGameDirectory();
2016-01-24 21:54:04 +01:00
config->Save();
if (UISettings::values.hide_mouse && emulation_running) {
setMouseTracking(true);
mouse_hide_timer.start();
} else {
setMouseTracking(false);
}
UpdateSecondaryWindowVisibility();
UpdateBootHomeMenuState();
UpdateStatusButtons();
} else {
2018-12-28 21:13:57 -05:00
Settings::values.input_profiles = old_input_profiles;
Settings::values.touch_from_button_maps = old_touch_from_button_maps;
2018-12-29 09:19:45 -05:00
Settings::LoadProfile(old_input_profile_index);
2016-01-24 18:34:05 +01:00
}
2014-03-31 22:26:50 -04:00
}
void GMainWindow::OnLoadAmiibo() {
if (!emu_thread || !emu_thread->IsRunning()) [[unlikely]] {
return;
}
Service::SM::ServiceManager& sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
if (nfc == nullptr) {
return;
}
2023-11-29 00:15:27 +02:00
std::scoped_lock lock{system.Kernel().GetHLELock()};
if (nfc->IsTagActive()) {
QMessageBox::warning(this, tr("Error opening amiibo data file"),
tr("A tag is already in use."));
return;
}
if (!nfc->IsSearchingForAmiibos()) {
QMessageBox::warning(this, tr("Error opening amiibo data file"),
tr("Application is not looking for amiibos."));
return;
}
const QString extensions{QStringLiteral("*.bin")};
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter);
if (filename.isEmpty()) {
return;
}
LoadAmiibo(filename);
}
void GMainWindow::LoadAmiibo(const QString& filename) {
Service::SM::ServiceManager& sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
if (!nfc) [[unlikely]] {
return;
}
2023-11-29 00:15:27 +02:00
std::scoped_lock lock{system.Kernel().GetHLELock()};
if (!nfc->LoadAmiibo(filename.toStdString())) {
QMessageBox::warning(this, tr("Error opening amiibo data file"),
tr("Unable to open amiibo file \"%1\" for reading.").arg(filename));
return;
}
ui->action_Remove_Amiibo->setEnabled(true);
}
void GMainWindow::OnRemoveAmiibo() {
Service::SM::ServiceManager& sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
if (!nfc) [[unlikely]] {
return;
}
2023-11-29 00:15:27 +02:00
std::scoped_lock lock{system.Kernel().GetHLELock()};
nfc->RemoveAmiibo();
ui->action_Remove_Amiibo->setEnabled(false);
}
void GMainWindow::OnOpenCitraFolder() {
QDesktopServices::openUrl(QUrl::fromLocalFile(
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))));
}
void GMainWindow::OnToggleFilterBar() {
game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
if (ui->action_Show_Filter_Bar->isChecked()) {
game_list->SetFilterFocus();
} else {
game_list->ClearFilter();
}
}
void GMainWindow::OnCreateGraphicsSurfaceViewer() {
auto graphicsSurfaceViewerWidget =
new GraphicsSurfaceWidget(system, Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget);
// TODO: Maybe graphicsSurfaceViewerWidget->setFloating(true);
graphicsSurfaceViewerWidget->show();
}
2018-07-08 13:55:32 +08:00
void GMainWindow::OnRecordMovie() {
2023-08-01 03:57:38 +03:00
MovieRecordDialog dialog(this, system);
if (dialog.exec() != QDialog::Accepted) {
2018-07-08 13:55:32 +08:00
return;
}
movie_record_on_start = true;
movie_record_path = dialog.GetPath();
movie_record_author = dialog.GetAuthor();
if (emulation_running) { // Restart game
BootGame(QString(game_path));
2018-07-08 13:55:32 +08:00
}
ui->action_Close_Movie->setEnabled(true);
ui->action_Save_Movie->setEnabled(true);
}
void GMainWindow::OnPlayMovie() {
2023-08-01 03:57:38 +03:00
MoviePlayDialog dialog(this, game_list, system);
if (dialog.exec() != QDialog::Accepted) {
return;
}
movie_playback_on_start = true;
movie_playback_path = dialog.GetMoviePath();
BootGame(dialog.GetGamePath());
ui->action_Close_Movie->setEnabled(true);
ui->action_Save_Movie->setEnabled(false);
2018-07-08 13:55:32 +08:00
}
void GMainWindow::OnCloseMovie() {
if (movie_record_on_start) {
QMessageBox::information(this, tr("Record Movie"), tr("Movie recording cancelled."));
movie_record_on_start = false;
movie_record_path.clear();
movie_record_author.clear();
} else {
const bool was_running = emu_thread && emu_thread->IsRunning();
if (was_running) {
OnPauseGame();
}
const bool was_recording = movie.GetPlayMode() == Core::Movie::PlayMode::Recording;
movie.Shutdown();
if (was_recording) {
QMessageBox::information(this, tr("Movie Saved"),
tr("The movie is successfully saved."));
}
if (was_running) {
OnStartGame();
}
2018-07-08 13:55:32 +08:00
}
ui->action_Close_Movie->setEnabled(false);
ui->action_Save_Movie->setEnabled(false);
}
void GMainWindow::OnSaveMovie() {
const bool was_running = emu_thread && emu_thread->IsRunning();
if (was_running) {
OnPauseGame();
}
if (movie.GetPlayMode() == Core::Movie::PlayMode::Recording) {
movie.SaveMovie();
QMessageBox::information(this, tr("Movie Saved"), tr("The movie is successfully saved."));
} else {
LOG_ERROR(Frontend, "Tried to save movie while movie is not being recorded");
}
if (was_running) {
OnStartGame();
2018-07-08 13:55:32 +08:00
}
}
void GMainWindow::OnCaptureScreenshot() {
if (!emu_thread) [[unlikely]] {
return;
}
const bool was_running = emu_thread->IsRunning();
if (was_running || (QMessageBox::question(this, tr("Application will unpause"),
tr("The application will be unpaused, and the next "
"frame will be captured. Is this okay?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No) == QMessageBox::Yes)) {
if (was_running) {
OnPauseGame();
}
std::string path = UISettings::values.screenshot_path.GetValue();
if (!FileUtil::IsDirectory(path)) {
if (!FileUtil::CreateFullPath(path)) {
QMessageBox::information(
this, tr("Invalid Screenshot Directory"),
tr("Cannot create specified screenshot directory. Screenshot "
"path is set back to its default value."));
path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir);
path.append("screenshots/");
UISettings::values.screenshot_path = path;
};
}
static QRegularExpression expr(QStringLiteral("[\\/:?\"<>|]"));
const std::string filename = game_title.remove(expr).toStdString();
const std::string timestamp = QDateTime::currentDateTime()
.toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"))
.toStdString();
path.append(fmt::format("/{}_{}.png", filename, timestamp));
auto* const screenshot_window =
secondary_window->HasFocus() ? secondary_window : render_window;
screenshot_window->CaptureScreenshot(
UISettings::values.screenshot_resolution_factor.GetValue(),
QString::fromStdString(path));
OnStartGame();
}
}
void GMainWindow::ShowFFmpegErrorMessage() {
QMessageBox message_box;
message_box.setWindowTitle(tr("Could not load video dumper"));
message_box.setText(
tr("FFmpeg could not be loaded. Make sure you have a compatible version installed."
#ifdef _WIN32
"\n\nTo install FFmpeg to Lime, press Open and select your FFmpeg directory."
#endif
"\n\nTo view a guide on how to install FFmpeg, press Help."));
message_box.setStandardButtons(QMessageBox::Ok | QMessageBox::Help
#ifdef _WIN32
| QMessageBox::Open
#endif
);
auto result = message_box.exec();
if (result == QMessageBox::Help) {
QDesktopServices::openUrl(
QUrl(QStringLiteral("https://web.archive.org/web/20240301121456/https://"
"citra-emu.org/wiki/installing-ffmpeg-for-the-video-dumper/")));
#ifdef _WIN32
} else if (result == QMessageBox::Open) {
OnOpenFFmpeg();
#endif
}
}
void GMainWindow::OnDumpVideo() {
if (DynamicLibrary::FFmpeg::LoadFFmpeg()) {
if (ui->action_Dump_Video->isChecked()) {
OnStartVideoDumping();
} else {
OnStopVideoDumping();
}
} else {
ui->action_Dump_Video->setChecked(false);
ShowFFmpegErrorMessage();
}
}
#ifdef _WIN32
void GMainWindow::OnOpenFFmpeg() {
auto filename =
QFileDialog::getExistingDirectory(this, tr("Select FFmpeg Directory")).toStdString();
if (filename.empty()) {
return;
}
// Check for a bin directory if they chose the FFmpeg root directory.
auto bin_dir = filename + DIR_SEP + "bin";
if (!FileUtil::Exists(bin_dir)) {
// Otherwise, assume the user directly selected the directory containing the DLLs.
bin_dir = filename;
}
static const std::array library_names = {
Common::DynamicLibrary::GetLibraryName("avcodec", LIBAVCODEC_VERSION_MAJOR),
Common::DynamicLibrary::GetLibraryName("avfilter", LIBAVFILTER_VERSION_MAJOR),
Common::DynamicLibrary::GetLibraryName("avformat", LIBAVFORMAT_VERSION_MAJOR),
Common::DynamicLibrary::GetLibraryName("avutil", LIBAVUTIL_VERSION_MAJOR),
Common::DynamicLibrary::GetLibraryName("swresample", LIBSWRESAMPLE_VERSION_MAJOR),
};
for (auto& library_name : library_names) {
if (!FileUtil::Exists(bin_dir + DIR_SEP + library_name)) {
QMessageBox::critical(this, tr("Azahar"),
tr("The provided FFmpeg directory is missing %1. Please make "
"sure the correct directory was selected.")
.arg(QString::fromStdString(library_name)));
return;
}
}
std::atomic<bool> success(true);
auto process_file = [&success](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
auto file_path = directory + DIR_SEP + virtual_name;
if (file_path.ends_with(".dll")) {
auto destination_path = FileUtil::GetExeDirectory() + DIR_SEP + virtual_name;
if (!FileUtil::Copy(file_path, destination_path)) {
success.store(false);
return false;
}
}
return true;
};
FileUtil::ForeachDirectoryEntry(nullptr, bin_dir, process_file);
if (success.load()) {
QMessageBox::information(this, tr("Azahar"), tr("FFmpeg has been sucessfully installed."));
} else {
QMessageBox::critical(this, tr("Azahar"),
tr("Installation of FFmpeg failed. Check the log file for details."));
}
}
#endif
void GMainWindow::OnStartVideoDumping() {
2023-06-30 13:39:38 +03:00
DumpingDialog dialog(this, system);
if (dialog.exec() != QDialog::DialogCode::Accepted) {
ui->action_Dump_Video->setChecked(false);
return;
}
const auto path = dialog.GetFilePath();
if (emulation_running) {
StartVideoDumping(path);
} else {
video_dumping_on_start = true;
video_dumping_path = path;
}
}
void GMainWindow::StartVideoDumping(const QString& path) {
auto& renderer = system.GPU().Renderer();
const auto layout{Layout::FrameLayoutFromResolutionScale(renderer.GetResolutionScaleFactor())};
auto dumper = std::make_shared<VideoDumper::FFmpegBackend>(renderer);
if (dumper->StartDumping(path.toStdString(), layout)) {
system.RegisterVideoDumper(dumper);
} else {
QMessageBox::critical(
this, tr("Azahar"),
tr("Could not start video dumping.<br>Please ensure that the video encoder is "
"configured correctly.<br>Refer to the log for details."));
ui->action_Dump_Video->setChecked(false);
}
}
void GMainWindow::OnStopVideoDumping() {
ui->action_Dump_Video->setChecked(false);
if (video_dumping_on_start) {
video_dumping_on_start = false;
video_dumping_path.clear();
} else {
auto dumper = system.GetVideoDumper();
if (!dumper || !dumper->IsDumping()) {
return;
}
game_paused_for_dumping = emu_thread->IsRunning();
OnPauseGame();
auto future = QtConcurrent::run([dumper] { dumper->StopDumping(); });
auto* future_watcher = new QFutureWatcher<void>(this);
connect(future_watcher, &QFutureWatcher<void>::finished, this, [this] {
if (game_shutdown_delayed) {
game_shutdown_delayed = false;
ShutdownGame();
} else if (game_paused_for_dumping) {
game_paused_for_dumping = false;
OnStartGame();
}
});
future_watcher->setFuture(future);
}
}
void GMainWindow::UpdateStatusBar() {
if (!emu_thread) [[unlikely]] {
status_bar_update_timer.stop();
return;
}
// Update movie status
const u64 current = movie.GetCurrentInputIndex();
const u64 total = movie.GetTotalInputCount();
const auto play_mode = movie.GetPlayMode();
if (play_mode == Core::Movie::PlayMode::Recording) {
message_label->setText(tr("Recording %1").arg(current));
message_label_used_for_movie = true;
ui->action_Save_Movie->setEnabled(true);
} else if (play_mode == Core::Movie::PlayMode::Playing) {
message_label->setText(tr("Playing %1 / %2").arg(current).arg(total));
message_label_used_for_movie = true;
ui->action_Save_Movie->setEnabled(false);
} else if (play_mode == Core::Movie::PlayMode::MovieFinished) {
message_label->setText(tr("Movie Finished"));
message_label_used_for_movie = true;
ui->action_Save_Movie->setEnabled(false);
} else if (message_label_used_for_movie) { // Clear the label if movie was just closed
message_label->setText(QString{});
message_label_used_for_movie = false;
ui->action_Save_Movie->setEnabled(false);
}
auto results = system.GetAndResetPerfStats();
if (show_artic_label) {
const bool do_mb = results.artic_transmitted >= (1000.0 * 1000.0);
const double value = do_mb ? (results.artic_transmitted / (1000.0 * 1000.0))
: (results.artic_transmitted / 1000.0);
static const std::array<std::pair<Core::PerfStats::PerfArticEventBits, QString>, 5>
perf_events = {
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA,
tr("(Accessing SharedExtData)")),
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SYSTEM_SAVE_DATA,
tr("(Accessing SystemSaveData)")),
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA,
tr("(Accessing BossExtData)")),
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA,
tr("(Accessing ExtData)")),
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SAVE_DATA,
tr("(Accessing SaveData)")),
};
const QString unit = do_mb ? tr("MB/s") : tr("KB/s");
QString event{};
for (auto p : perf_events) {
if (results.artic_events.Get(p.first)) {
event = QString::fromStdString(" ") + p.second;
break;
}
}
static const std::array label_color = {QStringLiteral("#ffffff"), QStringLiteral("#eed202"),
QStringLiteral("#ff3333")};
int style_index;
if (value > 200.0) {
style_index = 2;
} else if (value > 125.0) {
style_index = 1;
} else {
style_index = 0;
}
const QString style_sheet =
QStringLiteral("QLabel { color: %0; }").arg(label_color[style_index]);
artic_traffic_label->setText(
tr("Artic Base Traffic: %1 %2%3").arg(value, 0, 'f', 0).arg(unit).arg(event));
artic_traffic_label->setStyleSheet(style_sheet);
}
if (Settings::values.frame_limit.GetValue() == 0) {
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
} else {
emu_speed_label->setText(tr("Speed: %1% / %2%")
2018-02-13 18:16:26 -06:00
.arg(results.emulation_speed * 100.0, 0, 'f', 0)
.arg(Settings::values.frame_limit.GetValue()));
}
game_fps_label->setText(tr("App: %1 FPS").arg(results.game_fps, 0, 'f', 0));
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
if (show_artic_label) {
artic_traffic_label->setVisible(true);
}
emu_speed_label->setVisible(true);
game_fps_label->setVisible(true);
emu_frametime_label->setVisible(true);
}
void GMainWindow::UpdateBootHomeMenuState() {
const auto current_region = Settings::values.region_value.GetValue();
for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) {
const auto path = Core::GetHomeMenuNcchPath(region);
ui->menu_Boot_Home_Menu->actions().at(region)->setEnabled(
(current_region == Settings::REGION_VALUE_AUTO_SELECT ||
current_region == static_cast<int>(region)) &&
!path.empty() && FileUtil::Exists(path));
}
}
void GMainWindow::HideMouseCursor() {
if (!emu_thread || !UISettings::values.hide_mouse.GetValue()) {
mouse_hide_timer.stop();
ShowMouseCursor();
return;
}
render_window->setCursor(QCursor(Qt::BlankCursor));
secondary_window->setCursor(QCursor(Qt::BlankCursor));
if (UISettings::values.single_window_mode.GetValue()) {
setCursor(QCursor(Qt::BlankCursor));
}
}
void GMainWindow::ShowMouseCursor() {
unsetCursor();
render_window->unsetCursor();
secondary_window->unsetCursor();
if (emu_thread && UISettings::values.hide_mouse) {
mouse_hide_timer.start();
}
}
void GMainWindow::OnMute() {
Settings::values.audio_muted = !Settings::values.audio_muted;
UpdateVolumeUI();
}
void GMainWindow::OnDecreaseVolume() {
Settings::values.audio_muted = false;
const auto current_volume =
static_cast<s32>(Settings::values.volume.GetValue() * volume_slider->maximum());
int step = 5;
if (current_volume <= 30) {
step = 2;
}
if (current_volume <= 6) {
step = 1;
}
const auto value =
static_cast<float>(std::max(current_volume - step, 0)) / volume_slider->maximum();
Settings::values.volume.SetValue(value);
UpdateVolumeUI();
}
void GMainWindow::OnIncreaseVolume() {
Settings::values.audio_muted = false;
const auto current_volume =
static_cast<s32>(Settings::values.volume.GetValue() * volume_slider->maximum());
int step = 5;
if (current_volume < 30) {
step = 2;
}
if (current_volume < 6) {
step = 1;
}
const auto value = static_cast<float>(current_volume + step) / volume_slider->maximum();
Settings::values.volume.SetValue(value);
UpdateVolumeUI();
}
void GMainWindow::UpdateVolumeUI() {
const auto volume_value =
static_cast<int>(Settings::values.volume.GetValue() * volume_slider->maximum());
volume_slider->setValue(volume_value);
if (Settings::values.audio_muted) {
volume_button->setChecked(false);
volume_button->setText(tr("VOLUME: MUTE"));
} else {
volume_button->setChecked(true);
volume_button->setText(tr("VOLUME: %1%", "Volume percentage (e.g. 50%)").arg(volume_value));
}
}
void GMainWindow::UpdateAPIIndicator(bool update) {
Add vulkan backend (#6512) * code: Prepare frontend for vulkan support * citra_qt: Add vulkan options to the GUI * vk_instance: Collect tooling info * renderer_vulkan: Add vulkan backend * qt: Fix fullscreen and resize issues on macOS. (#47) * qt: Fix bugged macOS full screen transition. * renderer/vulkan: Fix swapchain recreation destroying in-use semaphore. * renderer/vulkan: Make gl_Position invariant. (#48) This fixes an issue with black artifacts in Pokemon games on Apple GPUs. If the vertex calculations differ slightly between render passes, it can cause parts of model faces to fail depth test. * vk_renderpass_cache: Bump pixel format count * android: Custom driver code * vk_instance: Set moltenvk configuration * rasterizer_cache: Proper surface unregister * citra_qt: Fix invalid characters * vk_rasterizer: Correct special unbind * android: Allow async presentation toggle * vk_graphics_pipeline: Fix async shader compilation * We were actually waiting for the pipelines regardless of the setting, oops * vk_rasterizer: More robust attribute loading * android: Move PollEvents to OpenGL window * Vulkan does not need this and it causes problems * vk_instance: Enable robust buffer access * Improves stability on mali devices * vk_renderpass_cache: Bring back renderpass flushing * externals: Update vulkan-headers * gl_rasterizer: Separable shaders for everyone * vk_blit_helper: Corect depth to color convertion * renderer_vulkan: Implement reinterpretation with copy * Allows reinterpreteration with simply copy on AMD * vk_graphics_pipeline: Only fast compile if no shaders are pending * With this shaders weren't being compiled in parallel * vk_swapchain: Ensure vsync doesn't lock framerate * vk_present_window: Match guest swapchain size to vulkan image count * Less latency and fixes crashes that were caused by images being deleted before free * vk_instance: Blacklist VK_EXT_pipeline_creation_cache_control with nvidia gpus * Resolves crashes when async shader compilation is enabled * vk_rasterizer: Bump async threshold to 6 * Many games have fullscreen quads with 6 vertices. Fixes pokemon textures missing with async shaders * android: More robust surface recreation * renderer_vulkan: Fix dynamic state being lost * vk_pipeline_cache: Skip cache save when no pipeline cache exists * This is the cache when loading a save state * sdl: Fix surface initialization on macOS. (#49) * sdl: Fix surface initialization on macOS. * sdl: Fix render window events not being handled under Vulkan. * renderer/vulkan: Fix binding/unbinding of shadow rendering buffer. * vk_stream_buffer: Respect non coherent access alignment * Required by nvidia GPUs on MacOS * renderer/vulkan: Support VK_EXT_fragment_shader_interlock for shadow rendering. (#51) * renderer_vulkan: Port some recent shader fixes * vk_pipeline_cache: Improve shadow detection * vk_swapchain: Add missing check * renderer_vulkan: Fix hybrid screen * Revert "gl_rasterizer: Separable shaders for everyone" Causes crashes on mali GPUs, will need separate PR This reverts commit d22d556d30ff641b62dfece85738c96b7fbf7061. * renderer_vulkan: Fix flipped screenshot --------- Co-authored-by: Steveice10 <1269164+Steveice10@users.noreply.github.com>
2023-09-13 01:28:50 +03:00
static std::array graphics_apis = {QStringLiteral("SOFTWARE"), QStringLiteral("OPENGL"),
QStringLiteral("VULKAN")};
Add vulkan backend (#6512) * code: Prepare frontend for vulkan support * citra_qt: Add vulkan options to the GUI * vk_instance: Collect tooling info * renderer_vulkan: Add vulkan backend * qt: Fix fullscreen and resize issues on macOS. (#47) * qt: Fix bugged macOS full screen transition. * renderer/vulkan: Fix swapchain recreation destroying in-use semaphore. * renderer/vulkan: Make gl_Position invariant. (#48) This fixes an issue with black artifacts in Pokemon games on Apple GPUs. If the vertex calculations differ slightly between render passes, it can cause parts of model faces to fail depth test. * vk_renderpass_cache: Bump pixel format count * android: Custom driver code * vk_instance: Set moltenvk configuration * rasterizer_cache: Proper surface unregister * citra_qt: Fix invalid characters * vk_rasterizer: Correct special unbind * android: Allow async presentation toggle * vk_graphics_pipeline: Fix async shader compilation * We were actually waiting for the pipelines regardless of the setting, oops * vk_rasterizer: More robust attribute loading * android: Move PollEvents to OpenGL window * Vulkan does not need this and it causes problems * vk_instance: Enable robust buffer access * Improves stability on mali devices * vk_renderpass_cache: Bring back renderpass flushing * externals: Update vulkan-headers * gl_rasterizer: Separable shaders for everyone * vk_blit_helper: Corect depth to color convertion * renderer_vulkan: Implement reinterpretation with copy * Allows reinterpreteration with simply copy on AMD * vk_graphics_pipeline: Only fast compile if no shaders are pending * With this shaders weren't being compiled in parallel * vk_swapchain: Ensure vsync doesn't lock framerate * vk_present_window: Match guest swapchain size to vulkan image count * Less latency and fixes crashes that were caused by images being deleted before free * vk_instance: Blacklist VK_EXT_pipeline_creation_cache_control with nvidia gpus * Resolves crashes when async shader compilation is enabled * vk_rasterizer: Bump async threshold to 6 * Many games have fullscreen quads with 6 vertices. Fixes pokemon textures missing with async shaders * android: More robust surface recreation * renderer_vulkan: Fix dynamic state being lost * vk_pipeline_cache: Skip cache save when no pipeline cache exists * This is the cache when loading a save state * sdl: Fix surface initialization on macOS. (#49) * sdl: Fix surface initialization on macOS. * sdl: Fix render window events not being handled under Vulkan. * renderer/vulkan: Fix binding/unbinding of shadow rendering buffer. * vk_stream_buffer: Respect non coherent access alignment * Required by nvidia GPUs on MacOS * renderer/vulkan: Support VK_EXT_fragment_shader_interlock for shadow rendering. (#51) * renderer_vulkan: Port some recent shader fixes * vk_pipeline_cache: Improve shadow detection * vk_swapchain: Add missing check * renderer_vulkan: Fix hybrid screen * Revert "gl_rasterizer: Separable shaders for everyone" Causes crashes on mali GPUs, will need separate PR This reverts commit d22d556d30ff641b62dfece85738c96b7fbf7061. * renderer_vulkan: Fix flipped screenshot --------- Co-authored-by: Steveice10 <1269164+Steveice10@users.noreply.github.com>
2023-09-13 01:28:50 +03:00
static std::array graphics_api_colors = {QStringLiteral("#3ae400"), QStringLiteral("#00ccdd"),
QStringLiteral("#91242a")};
u32 api_index = static_cast<u32>(Settings::values.graphics_api.GetValue());
if (update) {
api_index = (api_index + 1) % graphics_apis.size();
// Skip past any disabled renderers.
#ifndef ENABLE_SOFTWARE_RENDERER
if (api_index == static_cast<u32>(Settings::GraphicsAPI::Software)) {
api_index = (api_index + 1) % graphics_apis.size();
}
#endif
#ifndef ENABLE_OPENGL
if (api_index == static_cast<u32>(Settings::GraphicsAPI::OpenGL)) {
api_index = (api_index + 1) % graphics_apis.size();
}
#endif
#ifndef ENABLE_VULKAN
if (api_index == static_cast<u32>(Settings::GraphicsAPI::Vulkan)) {
api_index = (api_index + 1) % graphics_apis.size();
}
#endif
Settings::values.graphics_api = static_cast<Settings::GraphicsAPI>(api_index);
}
const QString style_sheet = QStringLiteral("QPushButton { font-weight: bold; color: %0; }")
.arg(graphics_api_colors[api_index]);
graphics_api_button->setText(graphics_apis[api_index]);
graphics_api_button->setStyleSheet(style_sheet);
}
void GMainWindow::UpdateStatusButtons() {
UpdateAPIIndicator();
UpdateVolumeUI();
}
void GMainWindow::OnMouseActivity() {
ShowMouseCursor();
}
2020-08-15 10:55:54 -03:00
void GMainWindow::mouseMoveEvent([[maybe_unused]] QMouseEvent* event) {
OnMouseActivity();
}
2020-08-15 10:55:54 -03:00
void GMainWindow::mousePressEvent([[maybe_unused]] QMouseEvent* event) {
OnMouseActivity();
}
2020-08-15 10:55:54 -03:00
void GMainWindow::mouseReleaseEvent([[maybe_unused]] QMouseEvent* event) {
OnMouseActivity();
}
void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
QString status_message;
QString title, message;
QMessageBox::Icon error_severity_icon;
bool can_continue = true;
if (result == Core::System::ResultStatus::ErrorSystemFiles) {
const QString common_message =
tr("%1 is missing. Please <a "
"href='https://citra-emu.org/wiki/"
"dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>dump your "
"system archives</a>.<br/>Continuing emulation may result in crashes and bugs.");
if (!details.empty()) {
message = common_message.arg(QString::fromStdString(details));
} else {
message = common_message.arg(tr("A system archive"));
}
title = tr("System Archive Not Found");
status_message = tr("System Archive Missing");
error_severity_icon = QMessageBox::Icon::Critical;
2020-04-12 23:12:15 +01:00
} else if (result == Core::System::ResultStatus::ErrorSavestate) {
title = tr("Save/load Error");
message = QString::fromStdString(details);
error_severity_icon = QMessageBox::Icon::Warning;
} else if (result == Core::System::ResultStatus::ErrorArticDisconnected) {
title = tr("Artic Base Server");
message =
tr(fmt::format("A communication error has occurred. The game will quit.\n{}", details)
.c_str());
error_severity_icon = QMessageBox::Icon::Critical;
can_continue = false;
} else {
title = tr("Fatal Error");
message =
tr("A fatal error occurred. "
"<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Check "
"the log</a> for details."
"<br/>Continuing emulation may result in crashes and bugs.");
status_message = tr("Fatal Error encountered");
error_severity_icon = QMessageBox::Icon::Critical;
}
QMessageBox message_box;
message_box.setWindowTitle(title);
message_box.setText(message);
message_box.setIcon(error_severity_icon);
if (error_severity_icon == QMessageBox::Icon::Critical) {
if (can_continue) {
message_box.addButton(tr("Continue"), QMessageBox::RejectRole);
}
QPushButton* abort_button =
message_box.addButton(tr("Quit Application"), QMessageBox::AcceptRole);
if (result != Core::System::ResultStatus::ShutdownRequested)
message_box.exec();
if (!can_continue || result == Core::System::ResultStatus::ShutdownRequested ||
message_box.clickedButton() == abort_button) {
if (emu_thread) {
ShutdownGame();
return;
}
}
} else {
// This block should run when the error isn't too big of a deal
// e.g. when a save state can't be saved or loaded
message_box.addButton(tr("OK"), QMessageBox::RejectRole);
message_box.exec();
}
// Only show the message if the game is still running.
if (emu_thread) {
emu_thread->SetRunning(true);
message_label->setText(status_message);
message_label_used_for_movie = false;
}
}
void GMainWindow::OnMenuAboutCitra() {
AboutDialog about{this};
about.exec();
}
bool GMainWindow::ConfirmClose() {
if (!emu_thread || !UISettings::values.confirm_before_closing) {
return true;
}
QMessageBox::StandardButton answer =
QMessageBox::question(this, tr("Azahar"), tr("Would you like to exit now?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return answer != QMessageBox::No;
}
void GMainWindow::closeEvent(QCloseEvent* event) {
if (!ConfirmClose()) {
event->ignore();
return;
}
UpdateUISettings();
game_list->SaveInterfaceLayout();
hotkey_registry.SaveHotkeys();
2014-03-31 22:26:50 -04:00
// Shutdown session if the emu thread is active...
if (emu_thread) {
ShutdownGame();
}
2014-03-31 22:26:50 -04:00
render_window->close();
secondary_window->close();
multiplayer_state->Close();
InputCommon::Shutdown();
2014-03-31 22:26:50 -04:00
QWidget::closeEvent(event);
}
2019-11-28 10:56:58 -07:00
static bool IsSingleFileDropEvent(const QMimeData* mime) {
return mime->hasUrls() && mime->urls().length() == 1;
2017-02-15 21:23:30 -06:00
}
static const std::array<std::string, 8> AcceptedExtensions = {"cci", "cxi", "bin", "3dsx",
"app", "elf", "axf"};
2019-11-28 10:56:58 -07:00
static bool IsCorrectFileExtension(const QMimeData* mime) {
const QString& filename = mime->urls().at(0).toLocalFile();
return std::find(AcceptedExtensions.begin(), AcceptedExtensions.end(),
QFileInfo(filename).suffix().toStdString()) != AcceptedExtensions.end();
}
static bool IsAcceptableDropEvent(QDropEvent* event) {
return IsSingleFileDropEvent(event->mimeData()) && IsCorrectFileExtension(event->mimeData());
}
void GMainWindow::AcceptDropEvent(QDropEvent* event) {
if (IsAcceptableDropEvent(event)) {
event->setDropAction(Qt::DropAction::LinkAction);
event->accept();
}
}
bool GMainWindow::DropAction(QDropEvent* event) {
if (!IsAcceptableDropEvent(event)) {
return false;
}
const QMimeData* mime_data = event->mimeData();
const QString& filename = mime_data->urls().at(0).toLocalFile();
if (emulation_running && QFileInfo(filename).suffix() == QStringLiteral("bin")) {
// Amiibo
LoadAmiibo(filename);
} else {
// Game
if (ConfirmChangeGame()) {
BootGame(filename);
}
2017-02-15 21:23:30 -06:00
}
2019-11-28 10:56:58 -07:00
return true;
}
void GMainWindow::OnFileOpen(const QFileOpenEvent* event) {
BootGame(event->file());
}
2019-11-28 10:56:58 -07:00
void GMainWindow::dropEvent(QDropEvent* event) {
DropAction(event);
2017-02-15 21:23:30 -06:00
}
void GMainWindow::dragEnterEvent(QDragEnterEvent* event) {
2019-11-28 10:56:58 -07:00
AcceptDropEvent(event);
2017-02-15 21:23:30 -06:00
}
void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
2019-11-28 10:56:58 -07:00
AcceptDropEvent(event);
2017-02-15 21:23:30 -06:00
}
bool GMainWindow::ConfirmChangeGame() {
if (!emu_thread) [[unlikely]] {
2017-02-15 21:23:30 -06:00
return true;
}
2017-02-15 21:23:30 -06:00
auto answer = QMessageBox::question(
2025-02-27 15:24:32 +00:00
this, tr("Azahar"),
tr("The application is still running. Would you like to stop emulation?"),
2017-02-15 21:23:30 -06:00
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return answer != QMessageBox::No;
}
void GMainWindow::filterBarSetChecked(bool state) {
ui->action_Show_Filter_Bar->setChecked(state);
emit(OnToggleFilterBar());
}
void GMainWindow::UpdateUITheme() {
2023-11-01 17:58:02 -07:00
const QString icons_base_path = QStringLiteral(":/icons/");
const QString default_theme = QStringLiteral("default");
const QString default_theme_path = icons_base_path + default_theme;
const QString& current_theme = UISettings::values.theme;
const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);
QStringList theme_paths(default_theme_paths);
if (is_default_theme || current_theme.isEmpty()) {
const QString theme_uri(QStringLiteral(":default/style.qss"));
QFile f(theme_uri);
if (f.open(QFile::ReadOnly | QFile::Text)) {
QTextStream ts(&f);
qApp->setStyleSheet(ts.readAll());
setStyleSheet(ts.readAll());
} else {
LOG_ERROR(Frontend,
"Unable to open default stylesheet, falling back to empty stylesheet");
qApp->setStyleSheet({});
setStyleSheet({});
}
2023-11-01 17:58:02 -07:00
theme_paths.append(default_theme_path);
QIcon::setThemeName(default_theme);
} else {
const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
QFile f(theme_uri);
if (f.open(QFile::ReadOnly | QFile::Text)) {
QTextStream ts(&f);
qApp->setStyleSheet(ts.readAll());
setStyleSheet(ts.readAll());
} else {
LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
}
2023-11-01 17:58:02 -07:00
const QString current_theme_path = icons_base_path + current_theme;
theme_paths.append({default_theme_path, current_theme_path});
QIcon::setThemeName(current_theme);
}
QIcon::setThemeSearchPaths(theme_paths);
}
void GMainWindow::LoadTranslation() {
// If the selected language is English, no need to install any translation
if (UISettings::values.language == QStringLiteral("en")) {
return;
}
bool loaded;
if (UISettings::values.language.isEmpty()) {
// Use the system's default locale
loaded = translator.load(QLocale::system(), {}, {}, QStringLiteral(":/languages/"));
} else {
// Otherwise load from the specified file
loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/"));
}
if (loaded) {
qApp->installTranslator(&translator);
} else {
UISettings::values.language = QStringLiteral("en");
}
}
void GMainWindow::OnLanguageChanged(const QString& locale) {
if (UISettings::values.language != QStringLiteral("en")) {
qApp->removeTranslator(&translator);
}
UISettings::values.language = locale;
LoadTranslation();
ui->retranslateUi(this);
2018-07-24 18:57:45 +08:00
RetranslateStatusBar();
UpdateWindowTitle();
}
void GMainWindow::OnConfigurePerGame() {
u64 title_id{};
system.GetAppLoader().ReadProgramId(title_id);
OpenPerGameConfiguration(title_id, game_path);
}
void GMainWindow::OpenPerGameConfiguration(u64 title_id, const QString& file_name) {
Settings::SetConfiguringGlobal(false);
ConfigurePerGame dialog(this, title_id, file_name, gl_renderer, physical_devices, system);
const auto result = dialog.exec();
if (result != QDialog::Accepted) {
Settings::RestoreGlobalState(system.IsPoweredOn());
return;
} else if (result == QDialog::Accepted) {
dialog.ApplyConfiguration();
}
// Do not cause the global config to write local settings into the config file
const bool is_powered_on = system.IsPoweredOn();
Settings::RestoreGlobalState(system.IsPoweredOn());
if (!is_powered_on) {
config->Save();
}
UpdateStatusButtons();
}
2018-07-08 13:55:32 +08:00
void GMainWindow::OnMoviePlaybackCompleted() {
OnPauseGame();
2018-07-08 13:55:32 +08:00
QMessageBox::information(this, tr("Playback Completed"), tr("Movie playback completed."));
}
void GMainWindow::UpdateWindowTitle() {
const QString full_name = QString::fromUtf8(Common::g_build_fullname);
2018-06-02 11:27:09 +08:00
if (game_title.isEmpty()) {
setWindowTitle(QStringLiteral("Azahar %1").arg(full_name));
2018-06-02 11:27:09 +08:00
} else {
setWindowTitle(QStringLiteral("Azahar %1 | %2").arg(full_name, game_title));
render_window->setWindowTitle(
QStringLiteral("Azahar %1 | %2 | %3").arg(full_name, game_title, tr("Primary Window")));
secondary_window->setWindowTitle(QStringLiteral("Azahar %1 | %2 | %3")
.arg(full_name, game_title, tr("Secondary Window")));
2018-06-02 11:27:09 +08:00
}
}
void GMainWindow::UpdateUISettings() {
if (!ui->action_Fullscreen->isChecked()) {
UISettings::values.geometry = saveGeometry();
UISettings::values.renderwindow_geometry = render_window->saveGeometry();
}
UISettings::values.state = saveState();
#if MICROPROFILE_ENABLED
UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry();
UISettings::values.microprofile_visible = microProfileDialog->isVisible();
#endif
UISettings::values.single_window_mode = ui->action_Single_Window_Mode->isChecked();
UISettings::values.fullscreen = ui->action_Fullscreen->isChecked();
UISettings::values.display_titlebar = ui->action_Display_Dock_Widget_Headers->isChecked();
UISettings::values.show_filter_bar = ui->action_Show_Filter_Bar->isChecked();
UISettings::values.show_status_bar = ui->action_Show_Status_Bar->isChecked();
UISettings::values.first_start = false;
}
void GMainWindow::SyncMenuUISettings() {
ui->action_Screen_Layout_Default->setChecked(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::Default);
ui->action_Screen_Layout_Single_Screen->setChecked(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::SingleScreen);
ui->action_Screen_Layout_Large_Screen->setChecked(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::LargeScreen);
ui->action_Screen_Layout_Hybrid_Screen->setChecked(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::HybridScreen);
ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::SideScreen);
ui->action_Screen_Layout_Separate_Windows->setChecked(
Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
ui->action_Screen_Layout_Custom_Layout->setChecked(Settings::values.layout_option.GetValue() ==
2024-11-10 21:02:54 +00:00
Settings::LayoutOption::CustomLayout);
ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen.GetValue());
ui->action_Screen_Layout_Upright_Screens->setChecked(
Settings::values.upright_screen.GetValue());
Added "Small Screen Position" feature * error checking for layout value from older config * rename enum and update aspect ratio code * rewrite LargeFrameLayout to support multiple positions * add settings for smallscreenposition, fix minsize function * fixed framebuffer from res scale (screenshots) * add desktop UI for small screen position * small screen position submenu on desktop * fix int-float conversion warning * rename Above and Below to hopefully fix linux issue * Add Small Screen Position Setting to android settings menu * fix sliders to work with floats, mostly * fix android slider textinput ui * change None enums in settings and cam_params * Apply clang-format-18 * SettingsAdapter.kt: Make more null pointer exception resistant * Updated license headers * Code formatting nitpicks * fix bug in main.ui that was hiding menu * replace default layout with a special call to LargeFrame (like SideBySide does) * fix bug when "large screen" is actually narrower * edit documentation for LargeScreenLayout * update PortraitTopFullFrameLayout to use LargeFrameLayout * fix unary minus on unsigned int bug * Applied formatting correction * Added `const`s where appropriate * android: Add mention of the bottom-right small screen position being the default * review fixes + more constants * refactor all Upright calculations to a reverseLayout method, simplifying code and reducing bugs * Removed stray extra newline * SettingsAdapter.kt: Fixed some strange indentation * Removed unnecessary `if` in favour of direct value usage --------- Co-authored-by: Reg Tiangha <rtiangha@users.noreply.github.com> Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2024-10-29 14:22:51 -07:00
ui->menu_Small_Screen_Position->setEnabled(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::LargeScreen);
ui->action_Small_Screen_TopRight->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::TopRight);
ui->action_Small_Screen_MiddleRight->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::MiddleRight);
ui->action_Small_Screen_BottomRight->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BottomRight);
ui->action_Small_Screen_TopLeft->setChecked(Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::TopLeft);
ui->action_Small_Screen_MiddleLeft->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::MiddleLeft);
ui->action_Small_Screen_BottomLeft->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BottomLeft);
ui->action_Small_Screen_Above->setChecked(Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::AboveLarge);
ui->action_Small_Screen_Below->setChecked(Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BelowLarge);
}
2018-07-24 18:57:45 +08:00
void GMainWindow::RetranslateStatusBar() {
if (emu_thread)
UpdateStatusBar();
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
"indicate emulation is running faster or slower than a 3DS."));
game_fps_label->setToolTip(tr("How many frames per second the app is currently displaying. "
"This will vary from app to app and scene to scene."));
2018-07-24 18:57:45 +08:00
emu_frametime_label->setToolTip(
tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For "
"full-speed emulation this should be at most 16.67 ms."));
multiplayer_state->retranslateUi();
}
void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
#ifdef USE_DISCORD_PRESENCE
if (state) {
2023-08-01 03:57:38 +03:00
discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>(system);
} else {
discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
}
#else
discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
#endif
discord_rpc->Update();
}
#ifdef __unix__
void GMainWindow::SetGamemodeEnabled(bool state) {
if (emulation_running) {
Common::Linux::SetGamemodeState(state);
}
}
#endif
2014-03-31 22:26:50 -04:00
#ifdef main
#undef main
#endif
2023-05-05 03:10:34 -07:00
static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
#ifdef _WIN32
// For Windows, we want to avoid scaling artifacts on fractional scaling ratios.
// This is done by setting the optimal scaling policy for the primary screen.
// Create a temporary QApplication.
int temp_argc = 0;
char** temp_argv = nullptr;
QApplication temp{temp_argc, temp_argv};
// Get the current screen geometry.
const QScreen* primary_screen = QGuiApplication::primaryScreen();
if (!primary_screen) {
2023-05-05 03:10:34 -07:00
return Qt::HighDpiScaleFactorRoundingPolicy::PassThrough;
}
const QRect screen_rect = primary_screen->geometry();
2023-05-05 03:10:34 -07:00
const qreal real_ratio = primary_screen->devicePixelRatio();
const qreal real_width = std::trunc(screen_rect.width() * real_ratio);
const qreal real_height = std::trunc(screen_rect.height() * real_ratio);
// Recommended minimum width and height for proper window fit.
// Any screen with a lower resolution than this will still have a scale of 1.
2023-05-05 03:10:34 -07:00
constexpr qreal minimum_width = 1350.0;
constexpr qreal minimum_height = 900.0;
2023-05-05 03:10:34 -07:00
const qreal width_ratio = std::max(1.0, real_width / minimum_width);
const qreal height_ratio = std::max(1.0, real_height / minimum_height);
// Get the lower of the 2 ratios and truncate, this is the maximum integer scale.
2023-05-05 03:10:34 -07:00
const qreal max_ratio = std::trunc(std::min(width_ratio, height_ratio));
if (max_ratio > real_ratio) {
2023-05-05 03:10:34 -07:00
return Qt::HighDpiScaleFactorRoundingPolicy::Round;
} else {
2023-05-05 03:10:34 -07:00
return Qt::HighDpiScaleFactorRoundingPolicy::Floor;
}
#else
// Other OSes should be better than Windows at fractional scaling.
2023-05-05 03:10:34 -07:00
return Qt::HighDpiScaleFactorRoundingPolicy::PassThrough;
#endif
}
void LaunchQtFrontend(int argc, char* argv[]) {
Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
// Init settings params
QCoreApplication::setOrganizationName(QStringLiteral("Azahar Developers"));
QCoreApplication::setApplicationName(QStringLiteral("Azahar"));
2023-05-05 03:10:34 -07:00
auto rounding_policy = GetHighDpiRoundingPolicy();
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
#ifdef __APPLE__
auto bundle_dir = FileUtil::GetBundleDirectory();
if (bundle_dir) {
FileUtil::SetCurrentDir(bundle_dir.value() + "..");
}
#endif
2023-05-05 03:10:34 -07:00
#ifdef ENABLE_OPENGL
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#endif
2014-03-31 22:26:50 -04:00
QApplication app(argc, argv);
// Required when using .qrc resources from within a static library.
// See https://doc.qt.io/qt-5/resources.html#using-resources-in-a-library
Q_INIT_RESOURCE(compatibility_list);
Q_INIT_RESOURCE(theme_colorful);
Q_INIT_RESOURCE(theme_colorful_dark);
Q_INIT_RESOURCE(theme_colorful_midnight_blue);
Q_INIT_RESOURCE(theme_default);
Q_INIT_RESOURCE(theme_qdarkstyle);
Q_INIT_RESOURCE(theme_qdarkstyle_midnight_blue);
#ifdef ENABLE_QT_TRANSLATION
Q_INIT_RESOURCE(languages);
#endif
// Qt changes the locale and causes issues in float conversion using std::to_string() when
// generating shaders
setlocale(LC_ALL, "C");
auto& system{Core::System::GetInstance()};
// Register Qt image interface
system.RegisterImageInterface(std::make_shared<QtImageInterface>());
GMainWindow main_window(system);
// Register frontend applets
Frontend::RegisterDefaultApplets(system);
system.RegisterMiiSelector(std::make_shared<QtMiiSelector>(main_window));
system.RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window));
#ifdef __APPLE__
// Register microphone permission check.
system.RegisterMicPermissionCheck(&AppleAuthorization::CheckAuthorizationForMicrophone);
#endif
2014-03-31 22:26:50 -04:00
main_window.show();
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
2019-09-14 18:14:23 -03:00
&GMainWindow::OnAppFocusStateChanged);
int result = app.exec();
detached_tasks.WaitForAllTasks();
exit(result);
2014-03-31 22:26:50 -04:00
}